зеркало из
https://github.com/rs-pro/activeadmin-quill_editor.git
synced 2026-08-28 15:36:17 +03:00
Merge pull request #61 from blocknotes/build/update-quill-to-2.0.3
Update Quill to version 2.0.3
Этот коммит содержится в:
1
Gemfile
1
Gemfile
@@ -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'
|
||||||
|
|||||||
198
app/assets/javascripts/activeadmin/quill.imageUploader.js
Обычный файл
198
app/assets/javascripts/activeadmin/quill.imageUploader.js
Обычный файл
@@ -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;
|
||||||
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
8
app/assets/javascripts/activeadmin/quill_editor/quill.min.js
поставляемый
8
app/assets/javascripts/activeadmin/quill_editor/quill.min.js
поставляемый
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
33
app/assets/stylesheets/activeadmin/quill.imageUploader.css
Обычный файл
33
app/assets/stylesheets/activeadmin/quill.imageUploader.css
Обычный файл
@@ -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
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user