Merge pull request #61 from blocknotes/build/update-quill-to-2.0.3

Update Quill to version 2.0.3
Этот коммит содержится в:
Mattia Roccoberton
2025-04-15 08:12:47 +02:00
коммит произвёл GitHub
родитель 181aab7fd6 3617aab7f2
Коммит 533471c0d6
15 изменённых файлов: 345 добавлений и 22454 удалений

Просмотреть файл

@@ -48,6 +48,7 @@ gem 'cuprite'
gem 'rspec_junit_formatter' gem 'rspec_junit_formatter'
gem 'rspec-rails' gem 'rspec-rails'
gem 'simplecov', require: false gem 'simplecov', require: false
gem 'super_diff'
# Linters # Linters
gem 'fasterer' gem 'fasterer'

Просмотреть файл

@@ -0,0 +1,198 @@
import LoadingImage from "./blots/image.js";
class ImageUploader {
constructor(quill, options) {
this.quill = quill;
this.options = options;
this.range = null;
this.placeholderDelta = null;
if (typeof this.options.upload !== "function")
console.warn(
"[Missing config] upload function that returns a promise is required"
);
var toolbar = this.quill.getModule("toolbar");
if (toolbar) {
toolbar.addHandler("image", this.selectLocalImage.bind(this));
}
this.handleDrop = this.handleDrop.bind(this);
this.handlePaste = this.handlePaste.bind(this);
this.quill.root.addEventListener("drop", this.handleDrop, false);
this.quill.root.addEventListener("paste", this.handlePaste, false);
}
selectLocalImage() {
this.quill.focus();
this.range = this.quill.getSelection();
this.fileHolder = document.createElement("input");
this.fileHolder.setAttribute("type", "file");
this.fileHolder.setAttribute("accept", "image/*");
this.fileHolder.setAttribute("style", "visibility:hidden");
this.fileHolder.onchange = this.fileChanged.bind(this);
document.body.appendChild(this.fileHolder);
this.fileHolder.click();
window.requestAnimationFrame(() => {
document.body.removeChild(this.fileHolder);
});
}
handleDrop(evt) {
if (
evt.dataTransfer &&
evt.dataTransfer.files &&
evt.dataTransfer.files.length
) {
evt.stopPropagation();
evt.preventDefault();
if (document.caretRangeFromPoint) {
const selection = document.getSelection();
const range = document.caretRangeFromPoint(evt.clientX, evt.clientY);
if (selection && range) {
selection.setBaseAndExtent(
range.startContainer,
range.startOffset,
range.startContainer,
range.startOffset
);
}
} else {
const selection = document.getSelection();
const range = document.caretPositionFromPoint(evt.clientX, evt.clientY);
if (selection && range) {
selection.setBaseAndExtent(
range.offsetNode,
range.offset,
range.offsetNode,
range.offset
);
}
}
this.quill.focus();
this.range = this.quill.getSelection();
let file = evt.dataTransfer.files[0];
setTimeout(() => {
this.quill.focus();
this.range = this.quill.getSelection();
this.readAndUploadFile(file);
}, 0);
}
}
handlePaste(evt) {
let clipboard = evt.clipboardData || window.clipboardData;
// IE 11 is .files other browsers are .items
if (clipboard && (clipboard.items || clipboard.files)) {
let items = clipboard.items || clipboard.files;
const IMAGE_MIME_REGEX = /^image\/(jpe?g|gif|png|svg|webp)$/i;
for (let i = 0; i < items.length; i++) {
if (IMAGE_MIME_REGEX.test(items[i].type)) {
let file = items[i].getAsFile ? items[i].getAsFile() : items[i];
if (file) {
this.quill.focus();
this.range = this.quill.getSelection();
evt.preventDefault();
setTimeout(() => {
this.quill.focus();
this.range = this.quill.getSelection();
this.readAndUploadFile(file);
}, 0);
}
}
}
}
}
readAndUploadFile(file) {
let isUploadReject = false;
const fileReader = new FileReader();
fileReader.addEventListener(
"load",
() => {
if (!isUploadReject) {
let base64ImageSrc = fileReader.result;
this.insertBase64Image(base64ImageSrc);
}
},
false
);
if (file) {
fileReader.readAsDataURL(file);
}
this.options.upload(file).then(
(imageUrl) => {
this.insertToEditor(imageUrl);
},
(error) => {
isUploadReject = true;
this.removeBase64Image();
console.warn(error);
}
);
}
fileChanged() {
const file = this.fileHolder.files[0];
this.readAndUploadFile(file);
}
insertBase64Image(url) {
const range = this.range;
this.placeholderDelta = this.quill.insertEmbed(
range.index,
LoadingImage.blotName,
`${url}`,
"user"
);
}
insertToEditor(url) {
const range = this.range;
const lengthToDelete = this.calculatePlaceholderInsertLength();
// Delete the placeholder image
this.quill.deleteText(range.index, lengthToDelete, "user");
// Insert the server saved image
this.quill.insertEmbed(range.index, "image", `${url}`, "user");
range.index++;
this.quill.setSelection(range, "user");
}
// The length of the insert delta from insertBase64Image can vary depending on what part of the line the insert occurs
calculatePlaceholderInsertLength() {
return this.placeholderDelta.ops.reduce((accumulator, deltaOperation) => {
if (deltaOperation.hasOwnProperty('insert'))
accumulator++;
return accumulator;
}, 0);
}
removeBase64Image() {
const range = this.range;
const lengthToDelete = this.calculatePlaceholderInsertLength();
this.quill.deleteText(range.index, lengthToDelete, "user");
}
}
window.ImageUploader = ImageUploader;
export default ImageUploader;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@@ -0,0 +1,33 @@
.image-uploading {
position: relative;
display: inline-block;
}
.image-uploading img {
max-width: 98% !important;
filter: blur(5px);
opacity: 0.3;
}
.image-uploading::before {
content: "";
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 30px;
height: 30px;
margin-top: -15px;
margin-left: -15px;
border-radius: 50%;
border: 3px solid #ccc;
border-top-color: #1e986c;
z-index: 1;
animation: spinner 0.6s linear infinite;
}
@keyframes spinner {
to {
transform: rotate(360deg);
}
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@@ -29,3 +29,24 @@ bundle update
bin/rails s bin/rails s
# To try different versions of Rails/ActiveAdmin edit extra/dev_setup.sh # To try different versions of Rails/ActiveAdmin edit extra/dev_setup.sh
``` ```
### Update the editor
- Update the CSS/JS editor assets:
```sh
wget "https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" -O "app/assets/stylesheets/activeadmin/quill_editor/quill.snow.css"
wget "https://cdn.jsdelivr.net/npm/quill@2/dist/quill.bubble.css" -O "app/assets/stylesheets/activeadmin/quill_editor/quill.bubble.css"
wget "https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.css" -O "app/assets/stylesheets/activeadmin/quill_editor/quill.core.css"
wget "https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js" -O "app/assets/javascripts/activeadmin/quill_editor/quill.js"
wget "https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.js" -O "app/assets/javascripts/activeadmin/quill_editor/quill.core.js"
wget "https://github.com/NoelOConnell/quill-image-uploader/raw/refs/heads/master/src/quill.imageUploader.js" -O "app/assets/javascripts/activeadmin/quill.imageUploader.js"
wget "https://github.com/NoelOConnell/quill-image-uploader/raw/refs/heads/master/dist/quill.imageUploader.min.js" -O "app/assets/javascripts/activeadmin/quill.imageUploader.min.js"
wget "https://github.com/NoelOConnell/quill-image-uploader/raw/refs/heads/master/src/quill.imageUploader.css" -O "app/assets/stylesheets/activeadmin/quill.imageUploader.css"
wget "https://github.com/NoelOConnell/quill-image-uploader/raw/refs/heads/master/dist/quill.imageUploader.min.css" -O "app/assets/stylesheets/activeadmin/quill.imageUploader.min.css"
```
- Check the changes, most of them should be for updated files plus some new / removed file

Просмотреть файл

@@ -3,6 +3,6 @@
module ActiveAdmin module ActiveAdmin
module QuillEditor module QuillEditor
VERSION = '1.3.0' VERSION = '1.3.0'
QUILL_VERSION = '1.3.7' QUILL_VERSION = '2.0.3'
end end
end end

Просмотреть файл

@@ -16,6 +16,11 @@ module Shared
@content_element ||= find("#{selector} .ql-editor") @content_element ||= find("#{selector} .ql-editor")
end end
def open_dropdown(dropdown)
find("#{toolbar_selector} .ql-#{dropdown} .ql-picker-label").click
self
end
def toggle_bold def toggle_bold
find("#{toolbar_selector} button.ql-bold").click find("#{toolbar_selector} button.ql-bold").click
end end
@@ -31,5 +36,29 @@ module Shared
def toggle_link def toggle_link
find("#{toolbar_selector} button.ql-link").click find("#{toolbar_selector} button.ql-link").click
end end
def toggle_blockquote
find("#{toolbar_selector} button.ql-blockquote").click
end
def toggle_code_block
find("#{toolbar_selector} button.ql-code-block").click
end
def toggle_sub
find("#{toolbar_selector} button.ql-script[value='sub']").click
end
def toggle_super
find("#{toolbar_selector} button.ql-script[value='super']").click
end
def toggle_align_right
find("#{toolbar_selector} .ql-picker-item[data-value='right']").click
end
def tooltip_editing
find("#{editor_selector} .ql-tooltip.ql-editing")
end
end end
end end

Просмотреть файл

@@ -1,12 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'spec_helper' require 'super_diff/rspec'
require 'zeitwerk' require 'zeitwerk'
loader = Zeitwerk::Loader.new loader = Zeitwerk::Loader.new
loader.push_dir("#{__dir__}/page_objects") loader.push_dir("#{__dir__}/page_objects")
loader.setup loader.setup
require_relative 'spec_helper'
ENV['RAILS_ENV'] = 'test' ENV['RAILS_ENV'] = 'test'
require 'simplecov' require 'simplecov'
@@ -21,8 +23,6 @@ require 'capybara/rails'
Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require_relative f } Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require_relative f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin begin
ActiveRecord::Migration.maintain_test_schema! ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e rescue ActiveRecord::PendingMigrationError => e

Просмотреть файл

@@ -29,17 +29,48 @@ RSpec.describe 'Quill editor' do
end end
it 'edits some content using the editor' do it 'edits some content using the editor' do
editor << :return << 'More content' editor.select_all
editor.toggle_link
editor.tooltip_editing.send_keys(["https://blocknot.es", :return])
editor << :right << :return << 'More content'
editor.toggle_bold editor.toggle_bold
editor << 'Some bold' editor << 'Some bold'
editor.toggle_italic editor.toggle_italic
editor << 'Some italic' editor << 'Some italic'
editor.toggle_underline editor.toggle_underline
editor << 'Some underline' editor << 'Some underline' << :return
editor.toggle_blockquote
editor << 'blockquote enabled' << :return
editor.toggle_blockquote
editor.toggle_code_block
editor << 'code block enabled' << :return
editor.toggle_code_block
editor << "Some text"
editor.toggle_sub
editor << "sub text"
editor.toggle_sub
editor << " More text"
editor.toggle_super
editor << "sup text"
editor.toggle_super
editor << :return
editor.open_dropdown(:align).toggle_align_right
editor << "Text aligned on the right"
expect(editor.content).to eq <<~HTML.clean_multiline expect(editor.content).to eq <<~HTML.clean_multiline
<p>Some content</p> <p><a href="https://blocknot.es" rel="noopener noreferrer" target="_blank">Some content</a></p>
<p>More content<strong>Some bold<em>Some italic<u>Some underline</u></em></strong></p> <p>More content<strong>Some bold<em>Some italic<u>Some underline</u></em></strong></p>
<blockquote>blockquote enabled</blockquote>
<div class="ql-code-block-container" spellcheck="false">
<div class="ql-code-block">code block enabled</div>
</div>
<p>Some text<sub>sub text</sub> More text<sup>sup text</sup></p>
<p class="ql-align-right">Text aligned on the right</p>
HTML HTML
end end