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-rails'
gem 'simplecov', require: false
gem 'super_diff'
# Linters
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
# 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 QuillEditor
VERSION = '1.3.0'
QUILL_VERSION = '1.3.7'
QUILL_VERSION = '2.0.3'
end
end

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

@@ -16,6 +16,11 @@ module Shared
@content_element ||= find("#{selector} .ql-editor")
end
def open_dropdown(dropdown)
find("#{toolbar_selector} .ql-#{dropdown} .ql-picker-label").click
self
end
def toggle_bold
find("#{toolbar_selector} button.ql-bold").click
end
@@ -31,5 +36,29 @@ module Shared
def toggle_link
find("#{toolbar_selector} button.ql-link").click
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

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

@@ -1,12 +1,14 @@
# frozen_string_literal: true
require_relative 'spec_helper'
require 'super_diff/rspec'
require 'zeitwerk'
loader = Zeitwerk::Loader.new
loader.push_dir("#{__dir__}/page_objects")
loader.setup
require_relative 'spec_helper'
ENV['RAILS_ENV'] = 'test'
require 'simplecov'
@@ -21,8 +23,6 @@ require 'capybara/rails'
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
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e

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

@@ -29,17 +29,48 @@ RSpec.describe 'Quill editor' do
end
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 << 'Some bold'
editor.toggle_italic
editor << 'Some italic'
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
<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>
<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
end