first commit
This commit is contained in:
43
public/assets/quill/modules/clipboard.d.ts
vendored
Normal file
43
public/assets/quill/modules/clipboard.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { ScrollBlot } from 'parchment';
|
||||
import Delta from 'quill-delta';
|
||||
import type { EmitterSource } from '../core/emitter.js';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import type { Range } from '../core/selection.js';
|
||||
type Selector = string | Node['TEXT_NODE'] | Node['ELEMENT_NODE'];
|
||||
type Matcher = (node: Node, delta: Delta, scroll: ScrollBlot) => Delta;
|
||||
interface ClipboardOptions {
|
||||
matchers: [Selector, Matcher][];
|
||||
}
|
||||
declare class Clipboard extends Module<ClipboardOptions> {
|
||||
static DEFAULTS: ClipboardOptions;
|
||||
matchers: [Selector, Matcher][];
|
||||
constructor(quill: Quill, options: Partial<ClipboardOptions>);
|
||||
addMatcher(selector: Selector, matcher: Matcher): void;
|
||||
convert({ html, text }: {
|
||||
html?: string;
|
||||
text?: string;
|
||||
}, formats?: Record<string, unknown>): Delta;
|
||||
protected normalizeHTML(doc: Document): void;
|
||||
protected convertHTML(html: string): Delta;
|
||||
dangerouslyPasteHTML(html: string, source?: EmitterSource): void;
|
||||
dangerouslyPasteHTML(index: number, html: string, source?: EmitterSource): void;
|
||||
onCaptureCopy(e: ClipboardEvent, isCut?: boolean): void;
|
||||
private normalizeURIList;
|
||||
onCapturePaste(e: ClipboardEvent): void;
|
||||
onCopy(range: Range, isCut: boolean): {
|
||||
html: string;
|
||||
text: string;
|
||||
};
|
||||
onPaste(range: Range, { text, html }: {
|
||||
text?: string;
|
||||
html?: string;
|
||||
}): void;
|
||||
prepareMatching(container: Element, nodeMatches: WeakMap<Node, Matcher[]>): Matcher[][];
|
||||
}
|
||||
declare function traverse(scroll: ScrollBlot, node: ChildNode, elementMatchers: Matcher[], textMatchers: Matcher[], nodeMatches: WeakMap<Node, Matcher[]>): Delta;
|
||||
declare function matchAttributor(node: HTMLElement, delta: Delta, scroll: ScrollBlot): Delta;
|
||||
declare function matchBlot(node: Node, delta: Delta, scroll: ScrollBlot): Delta;
|
||||
declare function matchNewline(node: Node, delta: Delta, scroll: ScrollBlot): Delta;
|
||||
declare function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot): Delta;
|
||||
export { Clipboard as default, matchAttributor, matchBlot, matchNewline, matchText, traverse, };
|
||||
477
public/assets/quill/modules/clipboard.js
Normal file
477
public/assets/quill/modules/clipboard.js
Normal file
@@ -0,0 +1,477 @@
|
||||
import { Attributor, BlockBlot, ClassAttributor, EmbedBlot, Scope, StyleAttributor } from 'parchment';
|
||||
import Delta from 'quill-delta';
|
||||
import { BlockEmbed } from '../blots/block.js';
|
||||
import logger from '../core/logger.js';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import { AlignAttribute, AlignStyle } from '../formats/align.js';
|
||||
import { BackgroundStyle } from '../formats/background.js';
|
||||
import CodeBlock from '../formats/code.js';
|
||||
import { ColorStyle } from '../formats/color.js';
|
||||
import { DirectionAttribute, DirectionStyle } from '../formats/direction.js';
|
||||
import { FontStyle } from '../formats/font.js';
|
||||
import { SizeStyle } from '../formats/size.js';
|
||||
import { deleteRange } from './keyboard.js';
|
||||
import normalizeExternalHTML from './normalizeExternalHTML/index.js';
|
||||
const debug = logger('quill:clipboard');
|
||||
const CLIPBOARD_CONFIG = [[Node.TEXT_NODE, matchText], [Node.TEXT_NODE, matchNewline], ['br', matchBreak], [Node.ELEMENT_NODE, matchNewline], [Node.ELEMENT_NODE, matchBlot], [Node.ELEMENT_NODE, matchAttributor], [Node.ELEMENT_NODE, matchStyles], ['li', matchIndent], ['ol, ul', matchList], ['pre', matchCodeBlock], ['tr', matchTable], ['b', createMatchAlias('bold')], ['i', createMatchAlias('italic')], ['strike', createMatchAlias('strike')], ['style', matchIgnore]];
|
||||
const ATTRIBUTE_ATTRIBUTORS = [AlignAttribute, DirectionAttribute].reduce((memo, attr) => {
|
||||
memo[attr.keyName] = attr;
|
||||
return memo;
|
||||
}, {});
|
||||
const STYLE_ATTRIBUTORS = [AlignStyle, BackgroundStyle, ColorStyle, DirectionStyle, FontStyle, SizeStyle].reduce((memo, attr) => {
|
||||
memo[attr.keyName] = attr;
|
||||
return memo;
|
||||
}, {});
|
||||
class Clipboard extends Module {
|
||||
static DEFAULTS = {
|
||||
matchers: []
|
||||
};
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
this.quill.root.addEventListener('copy', e => this.onCaptureCopy(e, false));
|
||||
this.quill.root.addEventListener('cut', e => this.onCaptureCopy(e, true));
|
||||
this.quill.root.addEventListener('paste', this.onCapturePaste.bind(this));
|
||||
this.matchers = [];
|
||||
CLIPBOARD_CONFIG.concat(this.options.matchers ?? []).forEach(_ref => {
|
||||
let [selector, matcher] = _ref;
|
||||
this.addMatcher(selector, matcher);
|
||||
});
|
||||
}
|
||||
addMatcher(selector, matcher) {
|
||||
this.matchers.push([selector, matcher]);
|
||||
}
|
||||
convert(_ref2) {
|
||||
let {
|
||||
html,
|
||||
text
|
||||
} = _ref2;
|
||||
let formats = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
if (formats[CodeBlock.blotName]) {
|
||||
return new Delta().insert(text || '', {
|
||||
[CodeBlock.blotName]: formats[CodeBlock.blotName]
|
||||
});
|
||||
}
|
||||
if (!html) {
|
||||
return new Delta().insert(text || '', formats);
|
||||
}
|
||||
const delta = this.convertHTML(html);
|
||||
// Remove trailing newline
|
||||
if (deltaEndsWith(delta, '\n') && (delta.ops[delta.ops.length - 1].attributes == null || formats.table)) {
|
||||
return delta.compose(new Delta().retain(delta.length() - 1).delete(1));
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
normalizeHTML(doc) {
|
||||
normalizeExternalHTML(doc);
|
||||
}
|
||||
convertHTML(html) {
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
this.normalizeHTML(doc);
|
||||
const container = doc.body;
|
||||
const nodeMatches = new WeakMap();
|
||||
const [elementMatchers, textMatchers] = this.prepareMatching(container, nodeMatches);
|
||||
return traverse(this.quill.scroll, container, elementMatchers, textMatchers, nodeMatches);
|
||||
}
|
||||
dangerouslyPasteHTML(index, html) {
|
||||
let source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Quill.sources.API;
|
||||
if (typeof index === 'string') {
|
||||
const delta = this.convert({
|
||||
html: index,
|
||||
text: ''
|
||||
});
|
||||
// @ts-expect-error
|
||||
this.quill.setContents(delta, html);
|
||||
this.quill.setSelection(0, Quill.sources.SILENT);
|
||||
} else {
|
||||
const paste = this.convert({
|
||||
html,
|
||||
text: ''
|
||||
});
|
||||
this.quill.updateContents(new Delta().retain(index).concat(paste), source);
|
||||
this.quill.setSelection(index + paste.length(), Quill.sources.SILENT);
|
||||
}
|
||||
}
|
||||
onCaptureCopy(e) {
|
||||
let isCut = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
if (e.defaultPrevented) return;
|
||||
e.preventDefault();
|
||||
const [range] = this.quill.selection.getRange();
|
||||
if (range == null) return;
|
||||
const {
|
||||
html,
|
||||
text
|
||||
} = this.onCopy(range, isCut);
|
||||
e.clipboardData?.setData('text/plain', text);
|
||||
e.clipboardData?.setData('text/html', html);
|
||||
if (isCut) {
|
||||
deleteRange({
|
||||
range,
|
||||
quill: this.quill
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* https://www.iana.org/assignments/media-types/text/uri-list
|
||||
*/
|
||||
normalizeURIList(urlList) {
|
||||
return urlList.split(/\r?\n/)
|
||||
// Ignore all comments
|
||||
.filter(url => url[0] !== '#').join('\n');
|
||||
}
|
||||
onCapturePaste(e) {
|
||||
if (e.defaultPrevented || !this.quill.isEnabled()) return;
|
||||
e.preventDefault();
|
||||
const range = this.quill.getSelection(true);
|
||||
if (range == null) return;
|
||||
const html = e.clipboardData?.getData('text/html');
|
||||
let text = e.clipboardData?.getData('text/plain');
|
||||
if (!html && !text) {
|
||||
const urlList = e.clipboardData?.getData('text/uri-list');
|
||||
if (urlList) {
|
||||
text = this.normalizeURIList(urlList);
|
||||
}
|
||||
}
|
||||
const files = Array.from(e.clipboardData?.files || []);
|
||||
if (!html && files.length > 0) {
|
||||
this.quill.uploader.upload(range, files);
|
||||
return;
|
||||
}
|
||||
if (html && files.length > 0) {
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
if (doc.body.childElementCount === 1 && doc.body.firstElementChild?.tagName === 'IMG') {
|
||||
this.quill.uploader.upload(range, files);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.onPaste(range, {
|
||||
html,
|
||||
text
|
||||
});
|
||||
}
|
||||
onCopy(range) {
|
||||
const text = this.quill.getText(range);
|
||||
const html = this.quill.getSemanticHTML(range);
|
||||
return {
|
||||
html,
|
||||
text
|
||||
};
|
||||
}
|
||||
onPaste(range, _ref3) {
|
||||
let {
|
||||
text,
|
||||
html
|
||||
} = _ref3;
|
||||
const formats = this.quill.getFormat(range.index);
|
||||
const pastedDelta = this.convert({
|
||||
text,
|
||||
html
|
||||
}, formats);
|
||||
debug.log('onPaste', pastedDelta, {
|
||||
text,
|
||||
html
|
||||
});
|
||||
const delta = new Delta().retain(range.index).delete(range.length).concat(pastedDelta);
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
// range.length contributes to delta.length()
|
||||
this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
|
||||
this.quill.scrollSelectionIntoView();
|
||||
}
|
||||
prepareMatching(container, nodeMatches) {
|
||||
const elementMatchers = [];
|
||||
const textMatchers = [];
|
||||
this.matchers.forEach(pair => {
|
||||
const [selector, matcher] = pair;
|
||||
switch (selector) {
|
||||
case Node.TEXT_NODE:
|
||||
textMatchers.push(matcher);
|
||||
break;
|
||||
case Node.ELEMENT_NODE:
|
||||
elementMatchers.push(matcher);
|
||||
break;
|
||||
default:
|
||||
Array.from(container.querySelectorAll(selector)).forEach(node => {
|
||||
if (nodeMatches.has(node)) {
|
||||
const matches = nodeMatches.get(node);
|
||||
matches?.push(matcher);
|
||||
} else {
|
||||
nodeMatches.set(node, [matcher]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return [elementMatchers, textMatchers];
|
||||
}
|
||||
}
|
||||
function applyFormat(delta, format, value, scroll) {
|
||||
if (!scroll.query(format)) {
|
||||
return delta;
|
||||
}
|
||||
return delta.reduce((newDelta, op) => {
|
||||
if (!op.insert) return newDelta;
|
||||
if (op.attributes && op.attributes[format]) {
|
||||
return newDelta.push(op);
|
||||
}
|
||||
const formats = value ? {
|
||||
[format]: value
|
||||
} : {};
|
||||
return newDelta.insert(op.insert, {
|
||||
...formats,
|
||||
...op.attributes
|
||||
});
|
||||
}, new Delta());
|
||||
}
|
||||
function deltaEndsWith(delta, text) {
|
||||
let endText = '';
|
||||
for (let i = delta.ops.length - 1; i >= 0 && endText.length < text.length; --i // eslint-disable-line no-plusplus
|
||||
) {
|
||||
const op = delta.ops[i];
|
||||
if (typeof op.insert !== 'string') break;
|
||||
endText = op.insert + endText;
|
||||
}
|
||||
return endText.slice(-1 * text.length) === text;
|
||||
}
|
||||
function isLine(node, scroll) {
|
||||
if (!(node instanceof Element)) return false;
|
||||
const match = scroll.query(node);
|
||||
// @ts-expect-error
|
||||
if (match && match.prototype instanceof EmbedBlot) return false;
|
||||
return ['address', 'article', 'blockquote', 'canvas', 'dd', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'iframe', 'li', 'main', 'nav', 'ol', 'output', 'p', 'pre', 'section', 'table', 'td', 'tr', 'ul', 'video'].includes(node.tagName.toLowerCase());
|
||||
}
|
||||
function isBetweenInlineElements(node, scroll) {
|
||||
return node.previousElementSibling && node.nextElementSibling && !isLine(node.previousElementSibling, scroll) && !isLine(node.nextElementSibling, scroll);
|
||||
}
|
||||
const preNodes = new WeakMap();
|
||||
function isPre(node) {
|
||||
if (node == null) return false;
|
||||
if (!preNodes.has(node)) {
|
||||
// @ts-expect-error
|
||||
if (node.tagName === 'PRE') {
|
||||
preNodes.set(node, true);
|
||||
} else {
|
||||
preNodes.set(node, isPre(node.parentNode));
|
||||
}
|
||||
}
|
||||
return preNodes.get(node);
|
||||
}
|
||||
function traverse(scroll, node, elementMatchers, textMatchers, nodeMatches) {
|
||||
// Post-order
|
||||
if (node.nodeType === node.TEXT_NODE) {
|
||||
return textMatchers.reduce((delta, matcher) => {
|
||||
return matcher(node, delta, scroll);
|
||||
}, new Delta());
|
||||
}
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
return Array.from(node.childNodes || []).reduce((delta, childNode) => {
|
||||
let childrenDelta = traverse(scroll, childNode, elementMatchers, textMatchers, nodeMatches);
|
||||
if (childNode.nodeType === node.ELEMENT_NODE) {
|
||||
childrenDelta = elementMatchers.reduce((reducedDelta, matcher) => {
|
||||
return matcher(childNode, reducedDelta, scroll);
|
||||
}, childrenDelta);
|
||||
childrenDelta = (nodeMatches.get(childNode) || []).reduce((reducedDelta, matcher) => {
|
||||
return matcher(childNode, reducedDelta, scroll);
|
||||
}, childrenDelta);
|
||||
}
|
||||
return delta.concat(childrenDelta);
|
||||
}, new Delta());
|
||||
}
|
||||
return new Delta();
|
||||
}
|
||||
function createMatchAlias(format) {
|
||||
return (_node, delta, scroll) => {
|
||||
return applyFormat(delta, format, true, scroll);
|
||||
};
|
||||
}
|
||||
function matchAttributor(node, delta, scroll) {
|
||||
const attributes = Attributor.keys(node);
|
||||
const classes = ClassAttributor.keys(node);
|
||||
const styles = StyleAttributor.keys(node);
|
||||
const formats = {};
|
||||
attributes.concat(classes).concat(styles).forEach(name => {
|
||||
let attr = scroll.query(name, Scope.ATTRIBUTE);
|
||||
if (attr != null) {
|
||||
formats[attr.attrName] = attr.value(node);
|
||||
if (formats[attr.attrName]) return;
|
||||
}
|
||||
attr = ATTRIBUTE_ATTRIBUTORS[name];
|
||||
if (attr != null && (attr.attrName === name || attr.keyName === name)) {
|
||||
formats[attr.attrName] = attr.value(node) || undefined;
|
||||
}
|
||||
attr = STYLE_ATTRIBUTORS[name];
|
||||
if (attr != null && (attr.attrName === name || attr.keyName === name)) {
|
||||
attr = STYLE_ATTRIBUTORS[name];
|
||||
formats[attr.attrName] = attr.value(node) || undefined;
|
||||
}
|
||||
});
|
||||
return Object.entries(formats).reduce((newDelta, _ref4) => {
|
||||
let [name, value] = _ref4;
|
||||
return applyFormat(newDelta, name, value, scroll);
|
||||
}, delta);
|
||||
}
|
||||
function matchBlot(node, delta, scroll) {
|
||||
const match = scroll.query(node);
|
||||
if (match == null) return delta;
|
||||
// @ts-expect-error
|
||||
if (match.prototype instanceof EmbedBlot) {
|
||||
const embed = {};
|
||||
// @ts-expect-error
|
||||
const value = match.value(node);
|
||||
if (value != null) {
|
||||
// @ts-expect-error
|
||||
embed[match.blotName] = value;
|
||||
// @ts-expect-error
|
||||
return new Delta().insert(embed, match.formats(node, scroll));
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
if (match.prototype instanceof BlockBlot && !deltaEndsWith(delta, '\n')) {
|
||||
delta.insert('\n');
|
||||
}
|
||||
if ('blotName' in match && 'formats' in match && typeof match.formats === 'function') {
|
||||
return applyFormat(delta, match.blotName, match.formats(node, scroll), scroll);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
function matchBreak(node, delta) {
|
||||
if (!deltaEndsWith(delta, '\n')) {
|
||||
delta.insert('\n');
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
function matchCodeBlock(node, delta, scroll) {
|
||||
const match = scroll.query('code-block');
|
||||
const language = match && 'formats' in match && typeof match.formats === 'function' ? match.formats(node, scroll) : true;
|
||||
return applyFormat(delta, 'code-block', language, scroll);
|
||||
}
|
||||
function matchIgnore() {
|
||||
return new Delta();
|
||||
}
|
||||
function matchIndent(node, delta, scroll) {
|
||||
const match = scroll.query(node);
|
||||
if (match == null ||
|
||||
// @ts-expect-error
|
||||
match.blotName !== 'list' || !deltaEndsWith(delta, '\n')) {
|
||||
return delta;
|
||||
}
|
||||
let indent = -1;
|
||||
let parent = node.parentNode;
|
||||
while (parent != null) {
|
||||
// @ts-expect-error
|
||||
if (['OL', 'UL'].includes(parent.tagName)) {
|
||||
indent += 1;
|
||||
}
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
if (indent <= 0) return delta;
|
||||
return delta.reduce((composed, op) => {
|
||||
if (!op.insert) return composed;
|
||||
if (op.attributes && typeof op.attributes.indent === 'number') {
|
||||
return composed.push(op);
|
||||
}
|
||||
return composed.insert(op.insert, {
|
||||
indent,
|
||||
...(op.attributes || {})
|
||||
});
|
||||
}, new Delta());
|
||||
}
|
||||
function matchList(node, delta, scroll) {
|
||||
const element = node;
|
||||
let list = element.tagName === 'OL' ? 'ordered' : 'bullet';
|
||||
const checkedAttr = element.getAttribute('data-checked');
|
||||
if (checkedAttr) {
|
||||
list = checkedAttr === 'true' ? 'checked' : 'unchecked';
|
||||
}
|
||||
return applyFormat(delta, 'list', list, scroll);
|
||||
}
|
||||
function matchNewline(node, delta, scroll) {
|
||||
if (!deltaEndsWith(delta, '\n')) {
|
||||
if (isLine(node, scroll) && (node.childNodes.length > 0 || node instanceof HTMLParagraphElement)) {
|
||||
return delta.insert('\n');
|
||||
}
|
||||
if (delta.length() > 0 && node.nextSibling) {
|
||||
let nextSibling = node.nextSibling;
|
||||
while (nextSibling != null) {
|
||||
if (isLine(nextSibling, scroll)) {
|
||||
return delta.insert('\n');
|
||||
}
|
||||
const match = scroll.query(nextSibling);
|
||||
// @ts-expect-error
|
||||
if (match && match.prototype instanceof BlockEmbed) {
|
||||
return delta.insert('\n');
|
||||
}
|
||||
nextSibling = nextSibling.firstChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
function matchStyles(node, delta, scroll) {
|
||||
const formats = {};
|
||||
const style = node.style || {};
|
||||
if (style.fontStyle === 'italic') {
|
||||
formats.italic = true;
|
||||
}
|
||||
if (style.textDecoration === 'underline') {
|
||||
formats.underline = true;
|
||||
}
|
||||
if (style.textDecoration === 'line-through') {
|
||||
formats.strike = true;
|
||||
}
|
||||
if (style.fontWeight?.startsWith('bold') ||
|
||||
// @ts-expect-error Fix me later
|
||||
parseInt(style.fontWeight, 10) >= 700) {
|
||||
formats.bold = true;
|
||||
}
|
||||
delta = Object.entries(formats).reduce((newDelta, _ref5) => {
|
||||
let [name, value] = _ref5;
|
||||
return applyFormat(newDelta, name, value, scroll);
|
||||
}, delta);
|
||||
// @ts-expect-error
|
||||
if (parseFloat(style.textIndent || 0) > 0) {
|
||||
// Could be 0.5in
|
||||
return new Delta().insert('\t').concat(delta);
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
function matchTable(node, delta, scroll) {
|
||||
const table = node.parentElement?.tagName === 'TABLE' ? node.parentElement : node.parentElement?.parentElement;
|
||||
if (table != null) {
|
||||
const rows = Array.from(table.querySelectorAll('tr'));
|
||||
const row = rows.indexOf(node) + 1;
|
||||
return applyFormat(delta, 'table', row, scroll);
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
function matchText(node, delta, scroll) {
|
||||
// @ts-expect-error
|
||||
let text = node.data;
|
||||
// Word represents empty line with <o:p> </o:p>
|
||||
if (node.parentElement?.tagName === 'O:P') {
|
||||
return delta.insert(text.trim());
|
||||
}
|
||||
if (!isPre(node)) {
|
||||
if (text.trim().length === 0 && text.includes('\n') && !isBetweenInlineElements(node, scroll)) {
|
||||
return delta;
|
||||
}
|
||||
// convert all non-nbsp whitespace into regular space
|
||||
text = text.replace(/[^\S\u00a0]/g, ' ');
|
||||
// collapse consecutive spaces into one
|
||||
text = text.replace(/ {2,}/g, ' ');
|
||||
if (node.previousSibling == null && node.parentElement != null && isLine(node.parentElement, scroll) || node.previousSibling instanceof Element && isLine(node.previousSibling, scroll)) {
|
||||
// block structure means we don't need leading space
|
||||
text = text.replace(/^ /, '');
|
||||
}
|
||||
if (node.nextSibling == null && node.parentElement != null && isLine(node.parentElement, scroll) || node.nextSibling instanceof Element && isLine(node.nextSibling, scroll)) {
|
||||
// block structure means we don't need trailing space
|
||||
text = text.replace(/ $/, '');
|
||||
}
|
||||
// done removing whitespace and can normalize all to regular space
|
||||
text = text.replaceAll('\u00a0', ' ');
|
||||
}
|
||||
return delta.insert(text);
|
||||
}
|
||||
export { Clipboard as default, matchAttributor, matchBlot, matchNewline, matchText, traverse };
|
||||
//# sourceMappingURL=clipboard.js.map
|
||||
1
public/assets/quill/modules/clipboard.js.map
Normal file
1
public/assets/quill/modules/clipboard.js.map
Normal file
File diff suppressed because one or more lines are too long
36
public/assets/quill/modules/history.d.ts
vendored
Normal file
36
public/assets/quill/modules/history.d.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import type Delta from 'quill-delta';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import type Scroll from '../blots/scroll.js';
|
||||
import type { Range } from '../core/selection.js';
|
||||
export interface HistoryOptions {
|
||||
userOnly: boolean;
|
||||
delay: number;
|
||||
maxStack: number;
|
||||
}
|
||||
export interface StackItem {
|
||||
delta: Delta;
|
||||
range: Range | null;
|
||||
}
|
||||
interface Stack {
|
||||
undo: StackItem[];
|
||||
redo: StackItem[];
|
||||
}
|
||||
declare class History extends Module<HistoryOptions> {
|
||||
static DEFAULTS: HistoryOptions;
|
||||
lastRecorded: number;
|
||||
ignoreChange: boolean;
|
||||
stack: Stack;
|
||||
currentRange: Range | null;
|
||||
constructor(quill: Quill, options: Partial<HistoryOptions>);
|
||||
change(source: 'undo' | 'redo', dest: 'redo' | 'undo'): void;
|
||||
clear(): void;
|
||||
cutoff(): void;
|
||||
record(changeDelta: Delta, oldDelta: Delta): void;
|
||||
redo(): void;
|
||||
transform(delta: Delta): void;
|
||||
undo(): void;
|
||||
protected restoreSelection(stackItem: StackItem): void;
|
||||
}
|
||||
declare function getLastChangeIndex(scroll: Scroll, delta: Delta): number;
|
||||
export { History as default, getLastChangeIndex };
|
||||
178
public/assets/quill/modules/history.js
Normal file
178
public/assets/quill/modules/history.js
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Scope } from 'parchment';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
class History extends Module {
|
||||
static DEFAULTS = {
|
||||
delay: 1000,
|
||||
maxStack: 100,
|
||||
userOnly: false
|
||||
};
|
||||
lastRecorded = 0;
|
||||
ignoreChange = false;
|
||||
stack = {
|
||||
undo: [],
|
||||
redo: []
|
||||
};
|
||||
currentRange = null;
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
this.quill.on(Quill.events.EDITOR_CHANGE, (eventName, value, oldValue, source) => {
|
||||
if (eventName === Quill.events.SELECTION_CHANGE) {
|
||||
if (value && source !== Quill.sources.SILENT) {
|
||||
this.currentRange = value;
|
||||
}
|
||||
} else if (eventName === Quill.events.TEXT_CHANGE) {
|
||||
if (!this.ignoreChange) {
|
||||
if (!this.options.userOnly || source === Quill.sources.USER) {
|
||||
this.record(value, oldValue);
|
||||
} else {
|
||||
this.transform(value);
|
||||
}
|
||||
}
|
||||
this.currentRange = transformRange(this.currentRange, value);
|
||||
}
|
||||
});
|
||||
this.quill.keyboard.addBinding({
|
||||
key: 'z',
|
||||
shortKey: true
|
||||
}, this.undo.bind(this));
|
||||
this.quill.keyboard.addBinding({
|
||||
key: ['z', 'Z'],
|
||||
shortKey: true,
|
||||
shiftKey: true
|
||||
}, this.redo.bind(this));
|
||||
if (/Win/i.test(navigator.platform)) {
|
||||
this.quill.keyboard.addBinding({
|
||||
key: 'y',
|
||||
shortKey: true
|
||||
}, this.redo.bind(this));
|
||||
}
|
||||
this.quill.root.addEventListener('beforeinput', event => {
|
||||
if (event.inputType === 'historyUndo') {
|
||||
this.undo();
|
||||
event.preventDefault();
|
||||
} else if (event.inputType === 'historyRedo') {
|
||||
this.redo();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
change(source, dest) {
|
||||
if (this.stack[source].length === 0) return;
|
||||
const item = this.stack[source].pop();
|
||||
if (!item) return;
|
||||
const base = this.quill.getContents();
|
||||
const inverseDelta = item.delta.invert(base);
|
||||
this.stack[dest].push({
|
||||
delta: inverseDelta,
|
||||
range: transformRange(item.range, inverseDelta)
|
||||
});
|
||||
this.lastRecorded = 0;
|
||||
this.ignoreChange = true;
|
||||
this.quill.updateContents(item.delta, Quill.sources.USER);
|
||||
this.ignoreChange = false;
|
||||
this.restoreSelection(item);
|
||||
}
|
||||
clear() {
|
||||
this.stack = {
|
||||
undo: [],
|
||||
redo: []
|
||||
};
|
||||
}
|
||||
cutoff() {
|
||||
this.lastRecorded = 0;
|
||||
}
|
||||
record(changeDelta, oldDelta) {
|
||||
if (changeDelta.ops.length === 0) return;
|
||||
this.stack.redo = [];
|
||||
let undoDelta = changeDelta.invert(oldDelta);
|
||||
let undoRange = this.currentRange;
|
||||
const timestamp = Date.now();
|
||||
if (
|
||||
// @ts-expect-error Fix me later
|
||||
this.lastRecorded + this.options.delay > timestamp && this.stack.undo.length > 0) {
|
||||
const item = this.stack.undo.pop();
|
||||
if (item) {
|
||||
undoDelta = undoDelta.compose(item.delta);
|
||||
undoRange = item.range;
|
||||
}
|
||||
} else {
|
||||
this.lastRecorded = timestamp;
|
||||
}
|
||||
if (undoDelta.length() === 0) return;
|
||||
this.stack.undo.push({
|
||||
delta: undoDelta,
|
||||
range: undoRange
|
||||
});
|
||||
// @ts-expect-error Fix me later
|
||||
if (this.stack.undo.length > this.options.maxStack) {
|
||||
this.stack.undo.shift();
|
||||
}
|
||||
}
|
||||
redo() {
|
||||
this.change('redo', 'undo');
|
||||
}
|
||||
transform(delta) {
|
||||
transformStack(this.stack.undo, delta);
|
||||
transformStack(this.stack.redo, delta);
|
||||
}
|
||||
undo() {
|
||||
this.change('undo', 'redo');
|
||||
}
|
||||
restoreSelection(stackItem) {
|
||||
if (stackItem.range) {
|
||||
this.quill.setSelection(stackItem.range, Quill.sources.USER);
|
||||
} else {
|
||||
const index = getLastChangeIndex(this.quill.scroll, stackItem.delta);
|
||||
this.quill.setSelection(index, Quill.sources.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
function transformStack(stack, delta) {
|
||||
let remoteDelta = delta;
|
||||
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
||||
const oldItem = stack[i];
|
||||
stack[i] = {
|
||||
delta: remoteDelta.transform(oldItem.delta, true),
|
||||
range: oldItem.range && transformRange(oldItem.range, remoteDelta)
|
||||
};
|
||||
remoteDelta = oldItem.delta.transform(remoteDelta);
|
||||
if (stack[i].delta.length() === 0) {
|
||||
stack.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function endsWithNewlineChange(scroll, delta) {
|
||||
const lastOp = delta.ops[delta.ops.length - 1];
|
||||
if (lastOp == null) return false;
|
||||
if (lastOp.insert != null) {
|
||||
return typeof lastOp.insert === 'string' && lastOp.insert.endsWith('\n');
|
||||
}
|
||||
if (lastOp.attributes != null) {
|
||||
return Object.keys(lastOp.attributes).some(attr => {
|
||||
return scroll.query(attr, Scope.BLOCK) != null;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function getLastChangeIndex(scroll, delta) {
|
||||
const deleteLength = delta.reduce((length, op) => {
|
||||
return length + (op.delete || 0);
|
||||
}, 0);
|
||||
let changeIndex = delta.length() - deleteLength;
|
||||
if (endsWithNewlineChange(scroll, delta)) {
|
||||
changeIndex -= 1;
|
||||
}
|
||||
return changeIndex;
|
||||
}
|
||||
function transformRange(range, delta) {
|
||||
if (!range) return range;
|
||||
const start = delta.transformPosition(range.index);
|
||||
const end = delta.transformPosition(range.index + range.length);
|
||||
return {
|
||||
index: start,
|
||||
length: end - start
|
||||
};
|
||||
}
|
||||
export { History as default, getLastChangeIndex };
|
||||
//# sourceMappingURL=history.js.map
|
||||
1
public/assets/quill/modules/history.js.map
Normal file
1
public/assets/quill/modules/history.js.map
Normal file
File diff suppressed because one or more lines are too long
10
public/assets/quill/modules/input.d.ts
vendored
Normal file
10
public/assets/quill/modules/input.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
declare class Input extends Module {
|
||||
constructor(quill: Quill, options: Record<string, never>);
|
||||
private deleteRange;
|
||||
private replaceText;
|
||||
private handleBeforeInput;
|
||||
private handleCompositionStart;
|
||||
}
|
||||
export default Input;
|
||||
83
public/assets/quill/modules/input.js
Normal file
83
public/assets/quill/modules/input.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import Delta from 'quill-delta';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import { deleteRange } from './keyboard.js';
|
||||
const INSERT_TYPES = ['insertText', 'insertReplacementText'];
|
||||
class Input extends Module {
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
quill.root.addEventListener('beforeinput', event => {
|
||||
this.handleBeforeInput(event);
|
||||
});
|
||||
|
||||
// Gboard with English input on Android triggers `compositionstart` sometimes even
|
||||
// users are not going to type anything.
|
||||
if (!/Android/i.test(navigator.userAgent)) {
|
||||
quill.on(Quill.events.COMPOSITION_BEFORE_START, () => {
|
||||
this.handleCompositionStart();
|
||||
});
|
||||
}
|
||||
}
|
||||
deleteRange(range) {
|
||||
deleteRange({
|
||||
range,
|
||||
quill: this.quill
|
||||
});
|
||||
}
|
||||
replaceText(range) {
|
||||
let text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||
if (range.length === 0) return false;
|
||||
if (text) {
|
||||
// Follow the native behavior that inherits the formats of the first character
|
||||
const formats = this.quill.getFormat(range.index, 1);
|
||||
this.deleteRange(range);
|
||||
this.quill.updateContents(new Delta().retain(range.index).insert(text, formats), Quill.sources.USER);
|
||||
} else {
|
||||
this.deleteRange(range);
|
||||
}
|
||||
this.quill.setSelection(range.index + text.length, 0, Quill.sources.SILENT);
|
||||
return true;
|
||||
}
|
||||
handleBeforeInput(event) {
|
||||
if (this.quill.composition.isComposing || event.defaultPrevented || !INSERT_TYPES.includes(event.inputType)) {
|
||||
return;
|
||||
}
|
||||
const staticRange = event.getTargetRanges ? event.getTargetRanges()[0] : null;
|
||||
if (!staticRange || staticRange.collapsed === true) {
|
||||
return;
|
||||
}
|
||||
const text = getPlainTextFromInputEvent(event);
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
const normalized = this.quill.selection.normalizeNative(staticRange);
|
||||
const range = normalized ? this.quill.selection.normalizedToRange(normalized) : null;
|
||||
if (range && this.replaceText(range, text)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
handleCompositionStart() {
|
||||
const range = this.quill.getSelection();
|
||||
if (range) {
|
||||
this.replaceText(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
function getPlainTextFromInputEvent(event) {
|
||||
// When `inputType` is "insertText":
|
||||
// - `event.data` should be string (Safari uses `event.dataTransfer`).
|
||||
// - `event.dataTransfer` should be null.
|
||||
// When `inputType` is "insertReplacementText":
|
||||
// - `event.data` should be null.
|
||||
// - `event.dataTransfer` should contain "text/plain" data.
|
||||
|
||||
if (typeof event.data === 'string') {
|
||||
return event.data;
|
||||
}
|
||||
if (event.dataTransfer?.types.includes('text/plain')) {
|
||||
return event.dataTransfer.getData('text/plain');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export default Input;
|
||||
//# sourceMappingURL=input.js.map
|
||||
1
public/assets/quill/modules/input.js.map
Normal file
1
public/assets/quill/modules/input.js.map
Normal file
File diff suppressed because one or more lines are too long
58
public/assets/quill/modules/keyboard.d.ts
vendored
Normal file
58
public/assets/quill/modules/keyboard.d.ts
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { BlockBlot } from 'parchment';
|
||||
import Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
import type { BlockEmbed } from '../blots/block.js';
|
||||
import type { Range } from '../core/selection.js';
|
||||
declare const SHORTKEY: string;
|
||||
export interface Context {
|
||||
collapsed: boolean;
|
||||
empty: boolean;
|
||||
offset: number;
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
format: Record<string, unknown>;
|
||||
event: KeyboardEvent;
|
||||
line: BlockEmbed | BlockBlot;
|
||||
}
|
||||
interface BindingObject extends Partial<Omit<Context, 'prefix' | 'suffix' | 'format'>> {
|
||||
key: number | string | string[];
|
||||
shortKey?: boolean | null;
|
||||
shiftKey?: boolean | null;
|
||||
altKey?: boolean | null;
|
||||
metaKey?: boolean | null;
|
||||
ctrlKey?: boolean | null;
|
||||
prefix?: RegExp;
|
||||
suffix?: RegExp;
|
||||
format?: Record<string, unknown> | string[];
|
||||
handler?: (this: {
|
||||
quill: Quill;
|
||||
}, range: Range, curContext: Context, binding: NormalizedBinding) => boolean | void;
|
||||
}
|
||||
type Binding = BindingObject | string | number;
|
||||
interface NormalizedBinding extends Omit<BindingObject, 'key' | 'shortKey'> {
|
||||
key: string | number;
|
||||
}
|
||||
interface KeyboardOptions {
|
||||
bindings: Record<string, Binding>;
|
||||
}
|
||||
interface KeyboardOptions {
|
||||
bindings: Record<string, Binding>;
|
||||
}
|
||||
declare class Keyboard extends Module<KeyboardOptions> {
|
||||
static DEFAULTS: KeyboardOptions;
|
||||
static match(evt: KeyboardEvent, binding: BindingObject): boolean;
|
||||
bindings: Record<string, NormalizedBinding[]>;
|
||||
constructor(quill: Quill, options: Partial<KeyboardOptions>);
|
||||
addBinding(keyBinding: Binding, context?: Required<BindingObject['handler']> | Partial<Omit<BindingObject, 'key' | 'handler'>>, handler?: Required<BindingObject['handler']> | Partial<Omit<BindingObject, 'key' | 'handler'>>): void;
|
||||
listen(): void;
|
||||
handleBackspace(range: Range, context: Context): void;
|
||||
handleDelete(range: Range, context: Context): void;
|
||||
handleDeleteRange(range: Range): void;
|
||||
handleEnter(range: Range, context: Context): void;
|
||||
}
|
||||
declare function normalize(binding: Binding): BindingObject | null;
|
||||
declare function deleteRange({ quill, range }: {
|
||||
quill: Quill;
|
||||
range: Range;
|
||||
}): void;
|
||||
export { Keyboard as default, SHORTKEY, normalize, deleteRange };
|
||||
713
public/assets/quill/modules/keyboard.js
Normal file
713
public/assets/quill/modules/keyboard.js
Normal file
@@ -0,0 +1,713 @@
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
import Delta, { AttributeMap } from 'quill-delta';
|
||||
import { EmbedBlot, Scope, TextBlot } from 'parchment';
|
||||
import Quill from '../core/quill.js';
|
||||
import logger from '../core/logger.js';
|
||||
import Module from '../core/module.js';
|
||||
const debug = logger('quill:keyboard');
|
||||
const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';
|
||||
class Keyboard extends Module {
|
||||
static match(evt, binding) {
|
||||
if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(key => {
|
||||
return !!binding[key] !== evt[key] && binding[key] !== null;
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
return binding.key === evt.key || binding.key === evt.which;
|
||||
}
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
this.bindings = {};
|
||||
// @ts-expect-error Fix me later
|
||||
Object.keys(this.options.bindings).forEach(name => {
|
||||
// @ts-expect-error Fix me later
|
||||
if (this.options.bindings[name]) {
|
||||
// @ts-expect-error Fix me later
|
||||
this.addBinding(this.options.bindings[name]);
|
||||
}
|
||||
});
|
||||
this.addBinding({
|
||||
key: 'Enter',
|
||||
shiftKey: null
|
||||
}, this.handleEnter);
|
||||
this.addBinding({
|
||||
key: 'Enter',
|
||||
metaKey: null,
|
||||
ctrlKey: null,
|
||||
altKey: null
|
||||
}, () => {});
|
||||
if (/Firefox/i.test(navigator.userAgent)) {
|
||||
// Need to handle delete and backspace for Firefox in the general case #1171
|
||||
this.addBinding({
|
||||
key: 'Backspace'
|
||||
}, {
|
||||
collapsed: true
|
||||
}, this.handleBackspace);
|
||||
this.addBinding({
|
||||
key: 'Delete'
|
||||
}, {
|
||||
collapsed: true
|
||||
}, this.handleDelete);
|
||||
} else {
|
||||
this.addBinding({
|
||||
key: 'Backspace'
|
||||
}, {
|
||||
collapsed: true,
|
||||
prefix: /^.?$/
|
||||
}, this.handleBackspace);
|
||||
this.addBinding({
|
||||
key: 'Delete'
|
||||
}, {
|
||||
collapsed: true,
|
||||
suffix: /^.?$/
|
||||
}, this.handleDelete);
|
||||
}
|
||||
this.addBinding({
|
||||
key: 'Backspace'
|
||||
}, {
|
||||
collapsed: false
|
||||
}, this.handleDeleteRange);
|
||||
this.addBinding({
|
||||
key: 'Delete'
|
||||
}, {
|
||||
collapsed: false
|
||||
}, this.handleDeleteRange);
|
||||
this.addBinding({
|
||||
key: 'Backspace',
|
||||
altKey: null,
|
||||
ctrlKey: null,
|
||||
metaKey: null,
|
||||
shiftKey: null
|
||||
}, {
|
||||
collapsed: true,
|
||||
offset: 0
|
||||
}, this.handleBackspace);
|
||||
this.listen();
|
||||
}
|
||||
addBinding(keyBinding) {
|
||||
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
let handler = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
||||
const binding = normalize(keyBinding);
|
||||
if (binding == null) {
|
||||
debug.warn('Attempted to add invalid keyboard binding', binding);
|
||||
return;
|
||||
}
|
||||
if (typeof context === 'function') {
|
||||
context = {
|
||||
handler: context
|
||||
};
|
||||
}
|
||||
if (typeof handler === 'function') {
|
||||
handler = {
|
||||
handler
|
||||
};
|
||||
}
|
||||
const keys = Array.isArray(binding.key) ? binding.key : [binding.key];
|
||||
keys.forEach(key => {
|
||||
const singleBinding = {
|
||||
...binding,
|
||||
key,
|
||||
...context,
|
||||
...handler
|
||||
};
|
||||
this.bindings[singleBinding.key] = this.bindings[singleBinding.key] || [];
|
||||
this.bindings[singleBinding.key].push(singleBinding);
|
||||
});
|
||||
}
|
||||
listen() {
|
||||
this.quill.root.addEventListener('keydown', evt => {
|
||||
if (evt.defaultPrevented || evt.isComposing) return;
|
||||
|
||||
// evt.isComposing is false when pressing Enter/Backspace when composing in Safari
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=165004
|
||||
const isComposing = evt.keyCode === 229 && (evt.key === 'Enter' || evt.key === 'Backspace');
|
||||
if (isComposing) return;
|
||||
const bindings = (this.bindings[evt.key] || []).concat(this.bindings[evt.which] || []);
|
||||
const matches = bindings.filter(binding => Keyboard.match(evt, binding));
|
||||
if (matches.length === 0) return;
|
||||
// @ts-expect-error
|
||||
const blot = Quill.find(evt.target, true);
|
||||
if (blot && blot.scroll !== this.quill.scroll) return;
|
||||
const range = this.quill.getSelection();
|
||||
if (range == null || !this.quill.hasFocus()) return;
|
||||
const [line, offset] = this.quill.getLine(range.index);
|
||||
const [leafStart, offsetStart] = this.quill.getLeaf(range.index);
|
||||
const [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.getLeaf(range.index + range.length);
|
||||
const prefixText = leafStart instanceof TextBlot ? leafStart.value().slice(0, offsetStart) : '';
|
||||
const suffixText = leafEnd instanceof TextBlot ? leafEnd.value().slice(offsetEnd) : '';
|
||||
const curContext = {
|
||||
collapsed: range.length === 0,
|
||||
// @ts-expect-error Fix me later
|
||||
empty: range.length === 0 && line.length() <= 1,
|
||||
format: this.quill.getFormat(range),
|
||||
line,
|
||||
offset,
|
||||
prefix: prefixText,
|
||||
suffix: suffixText,
|
||||
event: evt
|
||||
};
|
||||
const prevented = matches.some(binding => {
|
||||
if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) {
|
||||
return false;
|
||||
}
|
||||
if (binding.empty != null && binding.empty !== curContext.empty) {
|
||||
return false;
|
||||
}
|
||||
if (binding.offset != null && binding.offset !== curContext.offset) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(binding.format)) {
|
||||
// any format is present
|
||||
if (binding.format.every(name => curContext.format[name] == null)) {
|
||||
return false;
|
||||
}
|
||||
} else if (typeof binding.format === 'object') {
|
||||
// all formats must match
|
||||
if (!Object.keys(binding.format).every(name => {
|
||||
// @ts-expect-error Fix me later
|
||||
if (binding.format[name] === true) return curContext.format[name] != null;
|
||||
// @ts-expect-error Fix me later
|
||||
if (binding.format[name] === false) return curContext.format[name] == null;
|
||||
// @ts-expect-error Fix me later
|
||||
return isEqual(binding.format[name], curContext.format[name]);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) {
|
||||
return false;
|
||||
}
|
||||
if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) {
|
||||
return false;
|
||||
}
|
||||
// @ts-expect-error Fix me later
|
||||
return binding.handler.call(this, range, curContext, binding) !== true;
|
||||
});
|
||||
if (prevented) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
handleBackspace(range, context) {
|
||||
// Check for astral symbols
|
||||
const length = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(context.prefix) ? 2 : 1;
|
||||
if (range.index === 0 || this.quill.getLength() <= 1) return;
|
||||
let formats = {};
|
||||
const [line] = this.quill.getLine(range.index);
|
||||
let delta = new Delta().retain(range.index - length).delete(length);
|
||||
if (context.offset === 0) {
|
||||
// Always deleting newline here, length always 1
|
||||
const [prev] = this.quill.getLine(range.index - 1);
|
||||
if (prev) {
|
||||
const isPrevLineEmpty = prev.statics.blotName === 'block' && prev.length() <= 1;
|
||||
if (!isPrevLineEmpty) {
|
||||
// @ts-expect-error Fix me later
|
||||
const curFormats = line.formats();
|
||||
const prevFormats = this.quill.getFormat(range.index - 1, 1);
|
||||
formats = AttributeMap.diff(curFormats, prevFormats) || {};
|
||||
if (Object.keys(formats).length > 0) {
|
||||
// line.length() - 1 targets \n in line, another -1 for newline being deleted
|
||||
const formatDelta = new Delta()
|
||||
// @ts-expect-error Fix me later
|
||||
.retain(range.index + line.length() - 2).retain(1, formats);
|
||||
delta = delta.compose(formatDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.focus();
|
||||
}
|
||||
handleDelete(range, context) {
|
||||
// Check for astral symbols
|
||||
const length = /^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(context.suffix) ? 2 : 1;
|
||||
if (range.index >= this.quill.getLength() - length) return;
|
||||
let formats = {};
|
||||
const [line] = this.quill.getLine(range.index);
|
||||
let delta = new Delta().retain(range.index).delete(length);
|
||||
// @ts-expect-error Fix me later
|
||||
if (context.offset >= line.length() - 1) {
|
||||
const [next] = this.quill.getLine(range.index + 1);
|
||||
if (next) {
|
||||
// @ts-expect-error Fix me later
|
||||
const curFormats = line.formats();
|
||||
const nextFormats = this.quill.getFormat(range.index, 1);
|
||||
formats = AttributeMap.diff(curFormats, nextFormats) || {};
|
||||
if (Object.keys(formats).length > 0) {
|
||||
delta = delta.retain(next.length() - 1).retain(1, formats);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.focus();
|
||||
}
|
||||
handleDeleteRange(range) {
|
||||
deleteRange({
|
||||
range,
|
||||
quill: this.quill
|
||||
});
|
||||
this.quill.focus();
|
||||
}
|
||||
handleEnter(range, context) {
|
||||
const lineFormats = Object.keys(context.format).reduce((formats, format) => {
|
||||
if (this.quill.scroll.query(format, Scope.BLOCK) && !Array.isArray(context.format[format])) {
|
||||
formats[format] = context.format[format];
|
||||
}
|
||||
return formats;
|
||||
}, {});
|
||||
const delta = new Delta().retain(range.index).delete(range.length).insert('\n', lineFormats);
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
|
||||
this.quill.focus();
|
||||
}
|
||||
}
|
||||
const defaultOptions = {
|
||||
bindings: {
|
||||
bold: makeFormatHandler('bold'),
|
||||
italic: makeFormatHandler('italic'),
|
||||
underline: makeFormatHandler('underline'),
|
||||
indent: {
|
||||
// highlight tab or tab at beginning of list, indent or blockquote
|
||||
key: 'Tab',
|
||||
format: ['blockquote', 'indent', 'list'],
|
||||
handler(range, context) {
|
||||
if (context.collapsed && context.offset !== 0) return true;
|
||||
this.quill.format('indent', '+1', Quill.sources.USER);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
outdent: {
|
||||
key: 'Tab',
|
||||
shiftKey: true,
|
||||
format: ['blockquote', 'indent', 'list'],
|
||||
// highlight tab or tab at beginning of list, indent or blockquote
|
||||
handler(range, context) {
|
||||
if (context.collapsed && context.offset !== 0) return true;
|
||||
this.quill.format('indent', '-1', Quill.sources.USER);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'outdent backspace': {
|
||||
key: 'Backspace',
|
||||
collapsed: true,
|
||||
shiftKey: null,
|
||||
metaKey: null,
|
||||
ctrlKey: null,
|
||||
altKey: null,
|
||||
format: ['indent', 'list'],
|
||||
offset: 0,
|
||||
handler(range, context) {
|
||||
if (context.format.indent != null) {
|
||||
this.quill.format('indent', '-1', Quill.sources.USER);
|
||||
} else if (context.format.list != null) {
|
||||
this.quill.format('list', false, Quill.sources.USER);
|
||||
}
|
||||
}
|
||||
},
|
||||
'indent code-block': makeCodeBlockHandler(true),
|
||||
'outdent code-block': makeCodeBlockHandler(false),
|
||||
'remove tab': {
|
||||
key: 'Tab',
|
||||
shiftKey: true,
|
||||
collapsed: true,
|
||||
prefix: /\t$/,
|
||||
handler(range) {
|
||||
this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
key: 'Tab',
|
||||
handler(range, context) {
|
||||
if (context.format.table) return true;
|
||||
this.quill.history.cutoff();
|
||||
const delta = new Delta().retain(range.index).delete(range.length).insert('\t');
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.history.cutoff();
|
||||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'blockquote empty enter': {
|
||||
key: 'Enter',
|
||||
collapsed: true,
|
||||
format: ['blockquote'],
|
||||
empty: true,
|
||||
handler() {
|
||||
this.quill.format('blockquote', false, Quill.sources.USER);
|
||||
}
|
||||
},
|
||||
'list empty enter': {
|
||||
key: 'Enter',
|
||||
collapsed: true,
|
||||
format: ['list'],
|
||||
empty: true,
|
||||
handler(range, context) {
|
||||
const formats = {
|
||||
list: false
|
||||
};
|
||||
if (context.format.indent) {
|
||||
formats.indent = false;
|
||||
}
|
||||
this.quill.formatLine(range.index, range.length, formats, Quill.sources.USER);
|
||||
}
|
||||
},
|
||||
'checklist enter': {
|
||||
key: 'Enter',
|
||||
collapsed: true,
|
||||
format: {
|
||||
list: 'checked'
|
||||
},
|
||||
handler(range) {
|
||||
const [line, offset] = this.quill.getLine(range.index);
|
||||
const formats = {
|
||||
// @ts-expect-error Fix me later
|
||||
...line.formats(),
|
||||
list: 'checked'
|
||||
};
|
||||
const delta = new Delta().retain(range.index).insert('\n', formats)
|
||||
// @ts-expect-error Fix me later
|
||||
.retain(line.length() - offset - 1).retain(1, {
|
||||
list: 'unchecked'
|
||||
});
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
|
||||
this.quill.scrollSelectionIntoView();
|
||||
}
|
||||
},
|
||||
'header enter': {
|
||||
key: 'Enter',
|
||||
collapsed: true,
|
||||
format: ['header'],
|
||||
suffix: /^$/,
|
||||
handler(range, context) {
|
||||
const [line, offset] = this.quill.getLine(range.index);
|
||||
const delta = new Delta().retain(range.index).insert('\n', context.format)
|
||||
// @ts-expect-error Fix me later
|
||||
.retain(line.length() - offset - 1).retain(1, {
|
||||
header: null
|
||||
});
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
|
||||
this.quill.scrollSelectionIntoView();
|
||||
}
|
||||
},
|
||||
'table backspace': {
|
||||
key: 'Backspace',
|
||||
format: ['table'],
|
||||
collapsed: true,
|
||||
offset: 0,
|
||||
handler() {}
|
||||
},
|
||||
'table delete': {
|
||||
key: 'Delete',
|
||||
format: ['table'],
|
||||
collapsed: true,
|
||||
suffix: /^$/,
|
||||
handler() {}
|
||||
},
|
||||
'table enter': {
|
||||
key: 'Enter',
|
||||
shiftKey: null,
|
||||
format: ['table'],
|
||||
handler(range) {
|
||||
const module = this.quill.getModule('table');
|
||||
if (module) {
|
||||
// @ts-expect-error
|
||||
const [table, row, cell, offset] = module.getTable(range);
|
||||
const shift = tableSide(table, row, cell, offset);
|
||||
if (shift == null) return;
|
||||
let index = table.offset();
|
||||
if (shift < 0) {
|
||||
const delta = new Delta().retain(index).insert('\n');
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + 1, range.length, Quill.sources.SILENT);
|
||||
} else if (shift > 0) {
|
||||
index += table.length();
|
||||
const delta = new Delta().retain(index).insert('\n');
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(index, Quill.sources.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'table tab': {
|
||||
key: 'Tab',
|
||||
shiftKey: null,
|
||||
format: ['table'],
|
||||
handler(range, context) {
|
||||
const {
|
||||
event,
|
||||
line: cell
|
||||
} = context;
|
||||
const offset = cell.offset(this.quill.scroll);
|
||||
if (event.shiftKey) {
|
||||
this.quill.setSelection(offset - 1, Quill.sources.USER);
|
||||
} else {
|
||||
this.quill.setSelection(offset + cell.length(), Quill.sources.USER);
|
||||
}
|
||||
}
|
||||
},
|
||||
'list autofill': {
|
||||
key: ' ',
|
||||
shiftKey: null,
|
||||
collapsed: true,
|
||||
format: {
|
||||
'code-block': false,
|
||||
blockquote: false,
|
||||
table: false
|
||||
},
|
||||
prefix: /^\s*?(\d+\.|-|\*|\[ ?\]|\[x\])$/,
|
||||
handler(range, context) {
|
||||
if (this.quill.scroll.query('list') == null) return true;
|
||||
const {
|
||||
length
|
||||
} = context.prefix;
|
||||
const [line, offset] = this.quill.getLine(range.index);
|
||||
if (offset > length) return true;
|
||||
let value;
|
||||
switch (context.prefix.trim()) {
|
||||
case '[]':
|
||||
case '[ ]':
|
||||
value = 'unchecked';
|
||||
break;
|
||||
case '[x]':
|
||||
value = 'checked';
|
||||
break;
|
||||
case '-':
|
||||
case '*':
|
||||
value = 'bullet';
|
||||
break;
|
||||
default:
|
||||
value = 'ordered';
|
||||
}
|
||||
this.quill.insertText(range.index, ' ', Quill.sources.USER);
|
||||
this.quill.history.cutoff();
|
||||
const delta = new Delta().retain(range.index - offset).delete(length + 1)
|
||||
// @ts-expect-error Fix me later
|
||||
.retain(line.length() - 2 - offset).retain(1, {
|
||||
list: value
|
||||
});
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.history.cutoff();
|
||||
this.quill.setSelection(range.index - length, Quill.sources.SILENT);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'code exit': {
|
||||
key: 'Enter',
|
||||
collapsed: true,
|
||||
format: ['code-block'],
|
||||
prefix: /^$/,
|
||||
suffix: /^\s*$/,
|
||||
handler(range) {
|
||||
const [line, offset] = this.quill.getLine(range.index);
|
||||
let numLines = 2;
|
||||
let cur = line;
|
||||
while (cur != null && cur.length() <= 1 && cur.formats()['code-block']) {
|
||||
// @ts-expect-error
|
||||
cur = cur.prev;
|
||||
numLines -= 1;
|
||||
// Requisite prev lines are empty
|
||||
if (numLines <= 0) {
|
||||
const delta = new Delta()
|
||||
// @ts-expect-error Fix me later
|
||||
.retain(range.index + line.length() - offset - 2).retain(1, {
|
||||
'code-block': null
|
||||
}).delete(1);
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index - 1, Quill.sources.SILENT);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'embed left': makeEmbedArrowHandler('ArrowLeft', false),
|
||||
'embed left shift': makeEmbedArrowHandler('ArrowLeft', true),
|
||||
'embed right': makeEmbedArrowHandler('ArrowRight', false),
|
||||
'embed right shift': makeEmbedArrowHandler('ArrowRight', true),
|
||||
'table down': makeTableArrowHandler(false),
|
||||
'table up': makeTableArrowHandler(true)
|
||||
}
|
||||
};
|
||||
Keyboard.DEFAULTS = defaultOptions;
|
||||
function makeCodeBlockHandler(indent) {
|
||||
return {
|
||||
key: 'Tab',
|
||||
shiftKey: !indent,
|
||||
format: {
|
||||
'code-block': true
|
||||
},
|
||||
handler(range, _ref) {
|
||||
let {
|
||||
event
|
||||
} = _ref;
|
||||
const CodeBlock = this.quill.scroll.query('code-block');
|
||||
// @ts-expect-error
|
||||
const {
|
||||
TAB
|
||||
} = CodeBlock;
|
||||
if (range.length === 0 && !event.shiftKey) {
|
||||
this.quill.insertText(range.index, TAB, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + TAB.length, Quill.sources.SILENT);
|
||||
return;
|
||||
}
|
||||
const lines = range.length === 0 ? this.quill.getLines(range.index, 1) : this.quill.getLines(range);
|
||||
let {
|
||||
index,
|
||||
length
|
||||
} = range;
|
||||
lines.forEach((line, i) => {
|
||||
if (indent) {
|
||||
line.insertAt(0, TAB);
|
||||
if (i === 0) {
|
||||
index += TAB.length;
|
||||
} else {
|
||||
length += TAB.length;
|
||||
}
|
||||
// @ts-expect-error Fix me later
|
||||
} else if (line.domNode.textContent.startsWith(TAB)) {
|
||||
line.deleteAt(0, TAB.length);
|
||||
if (i === 0) {
|
||||
index -= TAB.length;
|
||||
} else {
|
||||
length -= TAB.length;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.quill.update(Quill.sources.USER);
|
||||
this.quill.setSelection(index, length, Quill.sources.SILENT);
|
||||
}
|
||||
};
|
||||
}
|
||||
function makeEmbedArrowHandler(key, shiftKey) {
|
||||
const where = key === 'ArrowLeft' ? 'prefix' : 'suffix';
|
||||
return {
|
||||
key,
|
||||
shiftKey,
|
||||
altKey: null,
|
||||
[where]: /^$/,
|
||||
handler(range) {
|
||||
let {
|
||||
index
|
||||
} = range;
|
||||
if (key === 'ArrowRight') {
|
||||
index += range.length + 1;
|
||||
}
|
||||
const [leaf] = this.quill.getLeaf(index);
|
||||
if (!(leaf instanceof EmbedBlot)) return true;
|
||||
if (key === 'ArrowLeft') {
|
||||
if (shiftKey) {
|
||||
this.quill.setSelection(range.index - 1, range.length + 1, Quill.sources.USER);
|
||||
} else {
|
||||
this.quill.setSelection(range.index - 1, Quill.sources.USER);
|
||||
}
|
||||
} else if (shiftKey) {
|
||||
this.quill.setSelection(range.index, range.length + 1, Quill.sources.USER);
|
||||
} else {
|
||||
this.quill.setSelection(range.index + range.length + 1, Quill.sources.USER);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
function makeFormatHandler(format) {
|
||||
return {
|
||||
key: format[0],
|
||||
shortKey: true,
|
||||
handler(range, context) {
|
||||
this.quill.format(format, !context.format[format], Quill.sources.USER);
|
||||
}
|
||||
};
|
||||
}
|
||||
function makeTableArrowHandler(up) {
|
||||
return {
|
||||
key: up ? 'ArrowUp' : 'ArrowDown',
|
||||
collapsed: true,
|
||||
format: ['table'],
|
||||
handler(range, context) {
|
||||
// TODO move to table module
|
||||
const key = up ? 'prev' : 'next';
|
||||
const cell = context.line;
|
||||
const targetRow = cell.parent[key];
|
||||
if (targetRow != null) {
|
||||
if (targetRow.statics.blotName === 'table-row') {
|
||||
// @ts-expect-error
|
||||
let targetCell = targetRow.children.head;
|
||||
let cur = cell;
|
||||
while (cur.prev != null) {
|
||||
// @ts-expect-error
|
||||
cur = cur.prev;
|
||||
targetCell = targetCell.next;
|
||||
}
|
||||
const index = targetCell.offset(this.quill.scroll) + Math.min(context.offset, targetCell.length() - 1);
|
||||
this.quill.setSelection(index, 0, Quill.sources.USER);
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
const targetLine = cell.table()[key];
|
||||
if (targetLine != null) {
|
||||
if (up) {
|
||||
this.quill.setSelection(targetLine.offset(this.quill.scroll) + targetLine.length() - 1, 0, Quill.sources.USER);
|
||||
} else {
|
||||
this.quill.setSelection(targetLine.offset(this.quill.scroll), 0, Quill.sources.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
function normalize(binding) {
|
||||
if (typeof binding === 'string' || typeof binding === 'number') {
|
||||
binding = {
|
||||
key: binding
|
||||
};
|
||||
} else if (typeof binding === 'object') {
|
||||
binding = cloneDeep(binding);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (binding.shortKey) {
|
||||
binding[SHORTKEY] = binding.shortKey;
|
||||
delete binding.shortKey;
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
// TODO: Move into quill.ts or editor.ts
|
||||
function deleteRange(_ref2) {
|
||||
let {
|
||||
quill,
|
||||
range
|
||||
} = _ref2;
|
||||
const lines = quill.getLines(range);
|
||||
let formats = {};
|
||||
if (lines.length > 1) {
|
||||
const firstFormats = lines[0].formats();
|
||||
const lastFormats = lines[lines.length - 1].formats();
|
||||
formats = AttributeMap.diff(lastFormats, firstFormats) || {};
|
||||
}
|
||||
quill.deleteText(range, Quill.sources.USER);
|
||||
if (Object.keys(formats).length > 0) {
|
||||
quill.formatLine(range.index, 1, formats, Quill.sources.USER);
|
||||
}
|
||||
quill.setSelection(range.index, Quill.sources.SILENT);
|
||||
}
|
||||
function tableSide(_table, row, cell, offset) {
|
||||
if (row.prev == null && row.next == null) {
|
||||
if (cell.prev == null && cell.next == null) {
|
||||
return offset === 0 ? -1 : 1;
|
||||
}
|
||||
return cell.prev == null ? -1 : 1;
|
||||
}
|
||||
if (row.prev == null) {
|
||||
return -1;
|
||||
}
|
||||
if (row.next == null) {
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export { Keyboard as default, SHORTKEY, normalize, deleteRange };
|
||||
//# sourceMappingURL=keyboard.js.map
|
||||
1
public/assets/quill/modules/keyboard.js.map
Normal file
1
public/assets/quill/modules/keyboard.js.map
Normal file
File diff suppressed because one or more lines are too long
2
public/assets/quill/modules/normalizeExternalHTML/index.d.ts
vendored
Normal file
2
public/assets/quill/modules/normalizeExternalHTML/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const normalizeExternalHTML: (doc: Document) => void;
|
||||
export default normalizeExternalHTML;
|
||||
12
public/assets/quill/modules/normalizeExternalHTML/index.js
Normal file
12
public/assets/quill/modules/normalizeExternalHTML/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import googleDocs from './normalizers/googleDocs.js';
|
||||
import msWord from './normalizers/msWord.js';
|
||||
const NORMALIZERS = [msWord, googleDocs];
|
||||
const normalizeExternalHTML = doc => {
|
||||
if (doc.documentElement) {
|
||||
NORMALIZERS.forEach(normalize => {
|
||||
normalize(doc);
|
||||
});
|
||||
}
|
||||
};
|
||||
export default normalizeExternalHTML;
|
||||
//# sourceMappingURL=index.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","names":["googleDocs","msWord","NORMALIZERS","normalizeExternalHTML","doc","documentElement","forEach","normalize"],"sources":["../../../src/modules/normalizeExternalHTML/index.ts"],"sourcesContent":["import googleDocs from './normalizers/googleDocs.js';\nimport msWord from './normalizers/msWord.js';\n\nconst NORMALIZERS = [msWord, googleDocs];\n\nconst normalizeExternalHTML = (doc: Document) => {\n if (doc.documentElement) {\n NORMALIZERS.forEach((normalize) => {\n normalize(doc);\n });\n }\n};\n\nexport default normalizeExternalHTML;\n"],"mappings":"AAAA,OAAOA,UAAU,MAAM,6BAA6B;AACpD,OAAOC,MAAM,MAAM,yBAAyB;AAE5C,MAAMC,WAAW,GAAG,CAACD,MAAM,EAAED,UAAU,CAAC;AAExC,MAAMG,qBAAqB,GAAIC,GAAa,IAAK;EAC/C,IAAIA,GAAG,CAACC,eAAe,EAAE;IACvBH,WAAW,CAACI,OAAO,CAAEC,SAAS,IAAK;MACjCA,SAAS,CAACH,GAAG,CAAC;IAChB,CAAC,CAAC;EACJ;AACF,CAAC;AAED,eAAeD,qBAAqB","ignoreList":[]}
|
||||
1
public/assets/quill/modules/normalizeExternalHTML/normalizers/googleDocs.d.ts
vendored
Normal file
1
public/assets/quill/modules/normalizeExternalHTML/normalizers/googleDocs.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default function normalize(doc: Document): void;
|
||||
@@ -0,0 +1,24 @@
|
||||
const normalWeightRegexp = /font-weight:\s*normal/;
|
||||
const blockTagNames = ['P', 'OL', 'UL'];
|
||||
const isBlockElement = element => {
|
||||
return element && blockTagNames.includes(element.tagName);
|
||||
};
|
||||
const normalizeEmptyLines = doc => {
|
||||
Array.from(doc.querySelectorAll('br')).filter(br => isBlockElement(br.previousElementSibling) && isBlockElement(br.nextElementSibling)).forEach(br => {
|
||||
br.parentNode?.removeChild(br);
|
||||
});
|
||||
};
|
||||
const normalizeFontWeight = doc => {
|
||||
Array.from(doc.querySelectorAll('b[style*="font-weight"]')).filter(node => node.getAttribute('style')?.match(normalWeightRegexp)).forEach(node => {
|
||||
const fragment = doc.createDocumentFragment();
|
||||
fragment.append(...node.childNodes);
|
||||
node.parentNode?.replaceChild(fragment, node);
|
||||
});
|
||||
};
|
||||
export default function normalize(doc) {
|
||||
if (doc.querySelector('[id^="docs-internal-guid-"]')) {
|
||||
normalizeFontWeight(doc);
|
||||
normalizeEmptyLines(doc);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=googleDocs.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"googleDocs.js","names":["normalWeightRegexp","blockTagNames","isBlockElement","element","includes","tagName","normalizeEmptyLines","doc","Array","from","querySelectorAll","filter","br","previousElementSibling","nextElementSibling","forEach","parentNode","removeChild","normalizeFontWeight","node","getAttribute","match","fragment","createDocumentFragment","append","childNodes","replaceChild","normalize","querySelector"],"sources":["../../../../src/modules/normalizeExternalHTML/normalizers/googleDocs.ts"],"sourcesContent":["const normalWeightRegexp = /font-weight:\\s*normal/;\nconst blockTagNames = ['P', 'OL', 'UL'];\n\nconst isBlockElement = (element: Element | null) => {\n return element && blockTagNames.includes(element.tagName);\n};\n\nconst normalizeEmptyLines = (doc: Document) => {\n Array.from(doc.querySelectorAll('br'))\n .filter(\n (br) =>\n isBlockElement(br.previousElementSibling) &&\n isBlockElement(br.nextElementSibling),\n )\n .forEach((br) => {\n br.parentNode?.removeChild(br);\n });\n};\n\nconst normalizeFontWeight = (doc: Document) => {\n Array.from(doc.querySelectorAll('b[style*=\"font-weight\"]'))\n .filter((node) => node.getAttribute('style')?.match(normalWeightRegexp))\n .forEach((node) => {\n const fragment = doc.createDocumentFragment();\n fragment.append(...node.childNodes);\n node.parentNode?.replaceChild(fragment, node);\n });\n};\n\nexport default function normalize(doc: Document) {\n if (doc.querySelector('[id^=\"docs-internal-guid-\"]')) {\n normalizeFontWeight(doc);\n normalizeEmptyLines(doc);\n }\n}\n"],"mappings":"AAAA,MAAMA,kBAAkB,GAAG,uBAAuB;AAClD,MAAMC,aAAa,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;AAEvC,MAAMC,cAAc,GAAIC,OAAuB,IAAK;EAClD,OAAOA,OAAO,IAAIF,aAAa,CAACG,QAAQ,CAACD,OAAO,CAACE,OAAO,CAAC;AAC3D,CAAC;AAED,MAAMC,mBAAmB,GAAIC,GAAa,IAAK;EAC7CC,KAAK,CAACC,IAAI,CAACF,GAAG,CAACG,gBAAgB,CAAC,IAAI,CAAC,CAAC,CACnCC,MAAM,CACJC,EAAE,IACDV,cAAc,CAACU,EAAE,CAACC,sBAAsB,CAAC,IACzCX,cAAc,CAACU,EAAE,CAACE,kBAAkB,CACxC,CAAC,CACAC,OAAO,CAAEH,EAAE,IAAK;IACfA,EAAE,CAACI,UAAU,EAAEC,WAAW,CAACL,EAAE,CAAC;EAChC,CAAC,CAAC;AACN,CAAC;AAED,MAAMM,mBAAmB,GAAIX,GAAa,IAAK;EAC7CC,KAAK,CAACC,IAAI,CAACF,GAAG,CAACG,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,CACxDC,MAAM,CAAEQ,IAAI,IAAKA,IAAI,CAACC,YAAY,CAAC,OAAO,CAAC,EAAEC,KAAK,CAACrB,kBAAkB,CAAC,CAAC,CACvEe,OAAO,CAAEI,IAAI,IAAK;IACjB,MAAMG,QAAQ,GAAGf,GAAG,CAACgB,sBAAsB,CAAC,CAAC;IAC7CD,QAAQ,CAACE,MAAM,CAAC,GAAGL,IAAI,CAACM,UAAU,CAAC;IACnCN,IAAI,CAACH,UAAU,EAAEU,YAAY,CAACJ,QAAQ,EAAEH,IAAI,CAAC;EAC/C,CAAC,CAAC;AACN,CAAC;AAED,eAAe,SAASQ,SAASA,CAACpB,GAAa,EAAE;EAC/C,IAAIA,GAAG,CAACqB,aAAa,CAAC,6BAA6B,CAAC,EAAE;IACpDV,mBAAmB,CAACX,GAAG,CAAC;IACxBD,mBAAmB,CAACC,GAAG,CAAC;EAC1B;AACF","ignoreList":[]}
|
||||
1
public/assets/quill/modules/normalizeExternalHTML/normalizers/msWord.d.ts
vendored
Normal file
1
public/assets/quill/modules/normalizeExternalHTML/normalizers/msWord.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default function normalize(doc: Document): void;
|
||||
@@ -0,0 +1,87 @@
|
||||
const ignoreRegexp = /\bmso-list:[^;]*ignore/i;
|
||||
const idRegexp = /\bmso-list:[^;]*\bl(\d+)/i;
|
||||
const indentRegexp = /\bmso-list:[^;]*\blevel(\d+)/i;
|
||||
const parseListItem = (element, html) => {
|
||||
const style = element.getAttribute('style');
|
||||
const idMatch = style?.match(idRegexp);
|
||||
if (!idMatch) {
|
||||
return null;
|
||||
}
|
||||
const id = Number(idMatch[1]);
|
||||
const indentMatch = style?.match(indentRegexp);
|
||||
const indent = indentMatch ? Number(indentMatch[1]) : 1;
|
||||
const typeRegexp = new RegExp(`@list l${id}:level${indent}\\s*\\{[^\\}]*mso-level-number-format:\\s*([\\w-]+)`, 'i');
|
||||
const typeMatch = html.match(typeRegexp);
|
||||
const type = typeMatch && typeMatch[1] === 'bullet' ? 'bullet' : 'ordered';
|
||||
return {
|
||||
id,
|
||||
indent,
|
||||
type,
|
||||
element
|
||||
};
|
||||
};
|
||||
|
||||
// list items are represented as `p` tags with styles like `mso-list: l0 level1` where:
|
||||
// 1. "0" in "l0" means the list item id;
|
||||
// 2. "1" in "level1" means the indent level, starting from 1.
|
||||
const normalizeListItem = doc => {
|
||||
const msoList = Array.from(doc.querySelectorAll('[style*=mso-list]'));
|
||||
const ignored = [];
|
||||
const others = [];
|
||||
msoList.forEach(node => {
|
||||
const shouldIgnore = (node.getAttribute('style') || '').match(ignoreRegexp);
|
||||
if (shouldIgnore) {
|
||||
ignored.push(node);
|
||||
} else {
|
||||
others.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Each list item contains a marker wrapped with "mso-list: Ignore".
|
||||
ignored.forEach(node => node.parentNode?.removeChild(node));
|
||||
|
||||
// The list stype is not defined inline with the tag, instead, it's in the
|
||||
// style tag so we need to pass the html as a string.
|
||||
const html = doc.documentElement.innerHTML;
|
||||
const listItems = others.map(element => parseListItem(element, html)).filter(parsed => parsed);
|
||||
while (listItems.length) {
|
||||
const childListItems = [];
|
||||
let current = listItems.shift();
|
||||
// Group continuous items into the same group (aka "ul")
|
||||
while (current) {
|
||||
childListItems.push(current);
|
||||
current = listItems.length && listItems[0]?.element === current.element.nextElementSibling &&
|
||||
// Different id means the next item doesn't belong to this group.
|
||||
listItems[0].id === current.id ? listItems.shift() : null;
|
||||
}
|
||||
const ul = document.createElement('ul');
|
||||
childListItems.forEach(listItem => {
|
||||
const li = document.createElement('li');
|
||||
li.setAttribute('data-list', listItem.type);
|
||||
if (listItem.indent > 1) {
|
||||
li.setAttribute('class', `ql-indent-${listItem.indent - 1}`);
|
||||
}
|
||||
li.innerHTML = listItem.element.innerHTML;
|
||||
ul.appendChild(li);
|
||||
});
|
||||
const element = childListItems[0]?.element;
|
||||
const {
|
||||
parentNode
|
||||
} = element ?? {};
|
||||
if (element) {
|
||||
parentNode?.replaceChild(ul, element);
|
||||
}
|
||||
childListItems.slice(1).forEach(_ref => {
|
||||
let {
|
||||
element: e
|
||||
} = _ref;
|
||||
parentNode?.removeChild(e);
|
||||
});
|
||||
}
|
||||
};
|
||||
export default function normalize(doc) {
|
||||
if (doc.documentElement.getAttribute('xmlns:w') === 'urn:schemas-microsoft-com:office:word') {
|
||||
normalizeListItem(doc);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=msWord.js.map
|
||||
File diff suppressed because one or more lines are too long
50
public/assets/quill/modules/syntax.d.ts
vendored
Normal file
50
public/assets/quill/modules/syntax.d.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import Delta from 'quill-delta';
|
||||
import type { Blot, ScrollBlot } from 'parchment';
|
||||
import Inline from '../blots/inline.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
import CodeBlock, { CodeBlockContainer } from '../formats/code.js';
|
||||
declare class CodeToken extends Inline {
|
||||
static formats(node: Element, scroll: ScrollBlot): any;
|
||||
constructor(scroll: ScrollBlot, domNode: Node, value: unknown);
|
||||
format(format: string, value: unknown): void;
|
||||
optimize(...args: unknown[]): void;
|
||||
}
|
||||
declare class SyntaxCodeBlock extends CodeBlock {
|
||||
static create(value: unknown): HTMLElement;
|
||||
static formats(domNode: Node): any;
|
||||
static register(): void;
|
||||
format(name: string, value: unknown): void;
|
||||
replaceWith(name: string | Blot, value?: any): Blot;
|
||||
}
|
||||
declare class SyntaxCodeBlockContainer extends CodeBlockContainer {
|
||||
forceNext?: boolean;
|
||||
cachedText?: string | null;
|
||||
attach(): void;
|
||||
format(name: string, value: unknown): void;
|
||||
formatAt(index: number, length: number, name: string, value: unknown): void;
|
||||
highlight(highlight: (text: string, language: string) => Delta, forced?: boolean): void;
|
||||
html(index: number, length: number): string;
|
||||
optimize(context: Record<string, any>): void;
|
||||
}
|
||||
interface SyntaxOptions {
|
||||
interval: number;
|
||||
languages: {
|
||||
key: string;
|
||||
label: string;
|
||||
}[];
|
||||
hljs: any;
|
||||
}
|
||||
declare class Syntax extends Module<SyntaxOptions> {
|
||||
static DEFAULTS: SyntaxOptions & {
|
||||
hljs: any;
|
||||
};
|
||||
static register(): void;
|
||||
languages: Record<string, true>;
|
||||
constructor(quill: Quill, options: Partial<SyntaxOptions>);
|
||||
initListener(): void;
|
||||
initTimer(): void;
|
||||
highlight(blot?: SyntaxCodeBlockContainer | null, force?: boolean): void;
|
||||
highlightBlot(text: string, language?: string): Delta;
|
||||
}
|
||||
export { SyntaxCodeBlock as CodeBlock, CodeToken, Syntax as default };
|
||||
332
public/assets/quill/modules/syntax.js
Normal file
332
public/assets/quill/modules/syntax.js
Normal file
@@ -0,0 +1,332 @@
|
||||
import Delta from 'quill-delta';
|
||||
import { ClassAttributor, Scope } from 'parchment';
|
||||
import Inline from '../blots/inline.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
import { blockDelta } from '../blots/block.js';
|
||||
import BreakBlot from '../blots/break.js';
|
||||
import CursorBlot from '../blots/cursor.js';
|
||||
import TextBlot, { escapeText } from '../blots/text.js';
|
||||
import CodeBlock, { CodeBlockContainer } from '../formats/code.js';
|
||||
import { traverse } from './clipboard.js';
|
||||
const TokenAttributor = new ClassAttributor('code-token', 'hljs', {
|
||||
scope: Scope.INLINE
|
||||
});
|
||||
class CodeToken extends Inline {
|
||||
static formats(node, scroll) {
|
||||
while (node != null && node !== scroll.domNode) {
|
||||
if (node.classList && node.classList.contains(CodeBlock.className)) {
|
||||
// @ts-expect-error
|
||||
return super.formats(node, scroll);
|
||||
}
|
||||
// @ts-expect-error
|
||||
node = node.parentNode;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
constructor(scroll, domNode, value) {
|
||||
// @ts-expect-error
|
||||
super(scroll, domNode, value);
|
||||
TokenAttributor.add(this.domNode, value);
|
||||
}
|
||||
format(format, value) {
|
||||
if (format !== CodeToken.blotName) {
|
||||
super.format(format, value);
|
||||
} else if (value) {
|
||||
TokenAttributor.add(this.domNode, value);
|
||||
} else {
|
||||
TokenAttributor.remove(this.domNode);
|
||||
this.domNode.classList.remove(this.statics.className);
|
||||
}
|
||||
}
|
||||
optimize() {
|
||||
// @ts-expect-error
|
||||
super.optimize(...arguments);
|
||||
if (!TokenAttributor.value(this.domNode)) {
|
||||
this.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
CodeToken.blotName = 'code-token';
|
||||
CodeToken.className = 'ql-token';
|
||||
class SyntaxCodeBlock extends CodeBlock {
|
||||
static create(value) {
|
||||
const domNode = super.create(value);
|
||||
if (typeof value === 'string') {
|
||||
domNode.setAttribute('data-language', value);
|
||||
}
|
||||
return domNode;
|
||||
}
|
||||
static formats(domNode) {
|
||||
// @ts-expect-error
|
||||
return domNode.getAttribute('data-language') || 'plain';
|
||||
}
|
||||
static register() {} // Syntax module will register
|
||||
|
||||
format(name, value) {
|
||||
if (name === this.statics.blotName && value) {
|
||||
// @ts-expect-error
|
||||
this.domNode.setAttribute('data-language', value);
|
||||
} else {
|
||||
super.format(name, value);
|
||||
}
|
||||
}
|
||||
replaceWith(name, value) {
|
||||
this.formatAt(0, this.length(), CodeToken.blotName, false);
|
||||
return super.replaceWith(name, value);
|
||||
}
|
||||
}
|
||||
class SyntaxCodeBlockContainer extends CodeBlockContainer {
|
||||
attach() {
|
||||
super.attach();
|
||||
this.forceNext = false;
|
||||
// @ts-expect-error
|
||||
this.scroll.emitMount(this);
|
||||
}
|
||||
format(name, value) {
|
||||
if (name === SyntaxCodeBlock.blotName) {
|
||||
this.forceNext = true;
|
||||
this.children.forEach(child => {
|
||||
// @ts-expect-error
|
||||
child.format(name, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
formatAt(index, length, name, value) {
|
||||
if (name === SyntaxCodeBlock.blotName) {
|
||||
this.forceNext = true;
|
||||
}
|
||||
super.formatAt(index, length, name, value);
|
||||
}
|
||||
highlight(highlight) {
|
||||
let forced = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
if (this.children.head == null) return;
|
||||
const nodes = Array.from(this.domNode.childNodes).filter(node => node !== this.uiNode);
|
||||
const text = `${nodes.map(node => node.textContent).join('\n')}\n`;
|
||||
const language = SyntaxCodeBlock.formats(this.children.head.domNode);
|
||||
if (forced || this.forceNext || this.cachedText !== text) {
|
||||
if (text.trim().length > 0 || this.cachedText == null) {
|
||||
const oldDelta = this.children.reduce((delta, child) => {
|
||||
// @ts-expect-error
|
||||
return delta.concat(blockDelta(child, false));
|
||||
}, new Delta());
|
||||
const delta = highlight(text, language);
|
||||
oldDelta.diff(delta).reduce((index, _ref) => {
|
||||
let {
|
||||
retain,
|
||||
attributes
|
||||
} = _ref;
|
||||
// Should be all retains
|
||||
if (!retain) return index;
|
||||
if (attributes) {
|
||||
Object.keys(attributes).forEach(format => {
|
||||
if ([SyntaxCodeBlock.blotName, CodeToken.blotName].includes(format)) {
|
||||
// @ts-expect-error
|
||||
this.formatAt(index, retain, format, attributes[format]);
|
||||
}
|
||||
});
|
||||
}
|
||||
// @ts-expect-error
|
||||
return index + retain;
|
||||
}, 0);
|
||||
}
|
||||
this.cachedText = text;
|
||||
this.forceNext = false;
|
||||
}
|
||||
}
|
||||
html(index, length) {
|
||||
const [codeBlock] = this.children.find(index);
|
||||
const language = codeBlock ? SyntaxCodeBlock.formats(codeBlock.domNode) : 'plain';
|
||||
return `<pre data-language="${language}">\n${escapeText(this.code(index, length))}\n</pre>`;
|
||||
}
|
||||
optimize(context) {
|
||||
super.optimize(context);
|
||||
if (this.parent != null && this.children.head != null && this.uiNode != null) {
|
||||
const language = SyntaxCodeBlock.formats(this.children.head.domNode);
|
||||
// @ts-expect-error
|
||||
if (language !== this.uiNode.value) {
|
||||
// @ts-expect-error
|
||||
this.uiNode.value = language;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SyntaxCodeBlockContainer.allowedChildren = [SyntaxCodeBlock];
|
||||
SyntaxCodeBlock.requiredContainer = SyntaxCodeBlockContainer;
|
||||
SyntaxCodeBlock.allowedChildren = [CodeToken, CursorBlot, TextBlot, BreakBlot];
|
||||
const highlight = (lib, language, text) => {
|
||||
if (typeof lib.versionString === 'string') {
|
||||
const majorVersion = lib.versionString.split('.')[0];
|
||||
if (parseInt(majorVersion, 10) >= 11) {
|
||||
return lib.highlight(text, {
|
||||
language
|
||||
}).value;
|
||||
}
|
||||
}
|
||||
return lib.highlight(language, text).value;
|
||||
};
|
||||
class Syntax extends Module {
|
||||
static register() {
|
||||
Quill.register(CodeToken, true);
|
||||
Quill.register(SyntaxCodeBlock, true);
|
||||
Quill.register(SyntaxCodeBlockContainer, true);
|
||||
}
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
if (this.options.hljs == null) {
|
||||
throw new Error('Syntax module requires highlight.js. Please include the library on the page before Quill.');
|
||||
}
|
||||
// @ts-expect-error Fix me later
|
||||
this.languages = this.options.languages.reduce((memo, _ref2) => {
|
||||
let {
|
||||
key
|
||||
} = _ref2;
|
||||
memo[key] = true;
|
||||
return memo;
|
||||
}, {});
|
||||
this.highlightBlot = this.highlightBlot.bind(this);
|
||||
this.initListener();
|
||||
this.initTimer();
|
||||
}
|
||||
initListener() {
|
||||
this.quill.on(Quill.events.SCROLL_BLOT_MOUNT, blot => {
|
||||
if (!(blot instanceof SyntaxCodeBlockContainer)) return;
|
||||
const select = this.quill.root.ownerDocument.createElement('select');
|
||||
// @ts-expect-error Fix me later
|
||||
this.options.languages.forEach(_ref3 => {
|
||||
let {
|
||||
key,
|
||||
label
|
||||
} = _ref3;
|
||||
const option = select.ownerDocument.createElement('option');
|
||||
option.textContent = label;
|
||||
option.setAttribute('value', key);
|
||||
select.appendChild(option);
|
||||
});
|
||||
select.addEventListener('change', () => {
|
||||
blot.format(SyntaxCodeBlock.blotName, select.value);
|
||||
this.quill.root.focus(); // Prevent scrolling
|
||||
this.highlight(blot, true);
|
||||
});
|
||||
if (blot.uiNode == null) {
|
||||
blot.attachUI(select);
|
||||
if (blot.children.head) {
|
||||
select.value = SyntaxCodeBlock.formats(blot.children.head.domNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
initTimer() {
|
||||
let timer = null;
|
||||
this.quill.on(Quill.events.SCROLL_OPTIMIZE, () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
this.highlight();
|
||||
timer = null;
|
||||
}, this.options.interval);
|
||||
});
|
||||
}
|
||||
highlight() {
|
||||
let blot = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
||||
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
if (this.quill.selection.composing) return;
|
||||
this.quill.update(Quill.sources.USER);
|
||||
const range = this.quill.getSelection();
|
||||
const blots = blot == null ? this.quill.scroll.descendants(SyntaxCodeBlockContainer) : [blot];
|
||||
blots.forEach(container => {
|
||||
container.highlight(this.highlightBlot, force);
|
||||
});
|
||||
this.quill.update(Quill.sources.SILENT);
|
||||
if (range != null) {
|
||||
this.quill.setSelection(range, Quill.sources.SILENT);
|
||||
}
|
||||
}
|
||||
highlightBlot(text) {
|
||||
let language = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'plain';
|
||||
language = this.languages[language] ? language : 'plain';
|
||||
if (language === 'plain') {
|
||||
return escapeText(text).split('\n').reduce((delta, line, i) => {
|
||||
if (i !== 0) {
|
||||
delta.insert('\n', {
|
||||
[CodeBlock.blotName]: language
|
||||
});
|
||||
}
|
||||
return delta.insert(line);
|
||||
}, new Delta());
|
||||
}
|
||||
const container = this.quill.root.ownerDocument.createElement('div');
|
||||
container.classList.add(CodeBlock.className);
|
||||
container.innerHTML = highlight(this.options.hljs, language, text);
|
||||
return traverse(this.quill.scroll, container, [(node, delta) => {
|
||||
// @ts-expect-error
|
||||
const value = TokenAttributor.value(node);
|
||||
if (value) {
|
||||
return delta.compose(new Delta().retain(delta.length(), {
|
||||
[CodeToken.blotName]: value
|
||||
}));
|
||||
}
|
||||
return delta;
|
||||
}], [(node, delta) => {
|
||||
// @ts-expect-error
|
||||
return node.data.split('\n').reduce((memo, nodeText, i) => {
|
||||
if (i !== 0) memo.insert('\n', {
|
||||
[CodeBlock.blotName]: language
|
||||
});
|
||||
return memo.insert(nodeText);
|
||||
}, delta);
|
||||
}], new WeakMap());
|
||||
}
|
||||
}
|
||||
Syntax.DEFAULTS = {
|
||||
hljs: (() => {
|
||||
return window.hljs;
|
||||
})(),
|
||||
interval: 1000,
|
||||
languages: [{
|
||||
key: 'plain',
|
||||
label: 'Plain'
|
||||
}, {
|
||||
key: 'bash',
|
||||
label: 'Bash'
|
||||
}, {
|
||||
key: 'cpp',
|
||||
label: 'C++'
|
||||
}, {
|
||||
key: 'cs',
|
||||
label: 'C#'
|
||||
}, {
|
||||
key: 'css',
|
||||
label: 'CSS'
|
||||
}, {
|
||||
key: 'diff',
|
||||
label: 'Diff'
|
||||
}, {
|
||||
key: 'xml',
|
||||
label: 'HTML/XML'
|
||||
}, {
|
||||
key: 'java',
|
||||
label: 'Java'
|
||||
}, {
|
||||
key: 'javascript',
|
||||
label: 'JavaScript'
|
||||
}, {
|
||||
key: 'markdown',
|
||||
label: 'Markdown'
|
||||
}, {
|
||||
key: 'php',
|
||||
label: 'PHP'
|
||||
}, {
|
||||
key: 'python',
|
||||
label: 'Python'
|
||||
}, {
|
||||
key: 'ruby',
|
||||
label: 'Ruby'
|
||||
}, {
|
||||
key: 'sql',
|
||||
label: 'SQL'
|
||||
}]
|
||||
};
|
||||
export { SyntaxCodeBlock as CodeBlock, CodeToken, Syntax as default };
|
||||
//# sourceMappingURL=syntax.js.map
|
||||
1
public/assets/quill/modules/syntax.js.map
Normal file
1
public/assets/quill/modules/syntax.js.map
Normal file
File diff suppressed because one or more lines are too long
20
public/assets/quill/modules/table.d.ts
vendored
Normal file
20
public/assets/quill/modules/table.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import Module from '../core/module.js';
|
||||
import { TableCell, TableRow } from '../formats/table.js';
|
||||
declare class Table extends Module {
|
||||
static register(): void;
|
||||
constructor(...args: ConstructorParameters<typeof Module>);
|
||||
balanceTables(): void;
|
||||
deleteColumn(): void;
|
||||
deleteRow(): void;
|
||||
deleteTable(): void;
|
||||
getTable(range?: import("../core/selection.js").Range | null): [null, null, null, -1] | [Table, TableRow, TableCell, number];
|
||||
insertColumn(offset: number): void;
|
||||
insertColumnLeft(): void;
|
||||
insertColumnRight(): void;
|
||||
insertRow(offset: number): void;
|
||||
insertRowAbove(): void;
|
||||
insertRowBelow(): void;
|
||||
insertTable(rows: number, columns: number): void;
|
||||
listenBalanceCells(): void;
|
||||
}
|
||||
export default Table;
|
||||
125
public/assets/quill/modules/table.js
Normal file
125
public/assets/quill/modules/table.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import Delta from 'quill-delta';
|
||||
import Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
import { TableCell, TableRow, TableBody, TableContainer, tableId } from '../formats/table.js';
|
||||
class Table extends Module {
|
||||
static register() {
|
||||
Quill.register(TableCell);
|
||||
Quill.register(TableRow);
|
||||
Quill.register(TableBody);
|
||||
Quill.register(TableContainer);
|
||||
}
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.listenBalanceCells();
|
||||
}
|
||||
balanceTables() {
|
||||
this.quill.scroll.descendants(TableContainer).forEach(table => {
|
||||
table.balanceCells();
|
||||
});
|
||||
}
|
||||
deleteColumn() {
|
||||
const [table,, cell] = this.getTable();
|
||||
if (cell == null) return;
|
||||
// @ts-expect-error
|
||||
table.deleteColumn(cell.cellOffset());
|
||||
this.quill.update(Quill.sources.USER);
|
||||
}
|
||||
deleteRow() {
|
||||
const [, row] = this.getTable();
|
||||
if (row == null) return;
|
||||
row.remove();
|
||||
this.quill.update(Quill.sources.USER);
|
||||
}
|
||||
deleteTable() {
|
||||
const [table] = this.getTable();
|
||||
if (table == null) return;
|
||||
// @ts-expect-error
|
||||
const offset = table.offset();
|
||||
// @ts-expect-error
|
||||
table.remove();
|
||||
this.quill.update(Quill.sources.USER);
|
||||
this.quill.setSelection(offset, Quill.sources.SILENT);
|
||||
}
|
||||
getTable() {
|
||||
let range = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.quill.getSelection();
|
||||
if (range == null) return [null, null, null, -1];
|
||||
const [cell, offset] = this.quill.getLine(range.index);
|
||||
if (cell == null || cell.statics.blotName !== TableCell.blotName) {
|
||||
return [null, null, null, -1];
|
||||
}
|
||||
const row = cell.parent;
|
||||
const table = row.parent.parent;
|
||||
// @ts-expect-error
|
||||
return [table, row, cell, offset];
|
||||
}
|
||||
insertColumn(offset) {
|
||||
const range = this.quill.getSelection();
|
||||
if (!range) return;
|
||||
const [table, row, cell] = this.getTable(range);
|
||||
if (cell == null) return;
|
||||
const column = cell.cellOffset();
|
||||
table.insertColumn(column + offset);
|
||||
this.quill.update(Quill.sources.USER);
|
||||
let shift = row.rowOffset();
|
||||
if (offset === 0) {
|
||||
shift += 1;
|
||||
}
|
||||
this.quill.setSelection(range.index + shift, range.length, Quill.sources.SILENT);
|
||||
}
|
||||
insertColumnLeft() {
|
||||
this.insertColumn(0);
|
||||
}
|
||||
insertColumnRight() {
|
||||
this.insertColumn(1);
|
||||
}
|
||||
insertRow(offset) {
|
||||
const range = this.quill.getSelection();
|
||||
if (!range) return;
|
||||
const [table, row, cell] = this.getTable(range);
|
||||
if (cell == null) return;
|
||||
const index = row.rowOffset();
|
||||
table.insertRow(index + offset);
|
||||
this.quill.update(Quill.sources.USER);
|
||||
if (offset > 0) {
|
||||
this.quill.setSelection(range, Quill.sources.SILENT);
|
||||
} else {
|
||||
this.quill.setSelection(range.index + row.children.length, range.length, Quill.sources.SILENT);
|
||||
}
|
||||
}
|
||||
insertRowAbove() {
|
||||
this.insertRow(0);
|
||||
}
|
||||
insertRowBelow() {
|
||||
this.insertRow(1);
|
||||
}
|
||||
insertTable(rows, columns) {
|
||||
const range = this.quill.getSelection();
|
||||
if (range == null) return;
|
||||
const delta = new Array(rows).fill(0).reduce(memo => {
|
||||
const text = new Array(columns).fill('\n').join('');
|
||||
return memo.insert(text, {
|
||||
table: tableId()
|
||||
});
|
||||
}, new Delta().retain(range.index));
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index, Quill.sources.SILENT);
|
||||
this.balanceTables();
|
||||
}
|
||||
listenBalanceCells() {
|
||||
this.quill.on(Quill.events.SCROLL_OPTIMIZE, mutations => {
|
||||
mutations.some(mutation => {
|
||||
if (['TD', 'TR', 'TBODY', 'TABLE'].includes(mutation.target.tagName)) {
|
||||
this.quill.once(Quill.events.TEXT_CHANGE, (delta, old, source) => {
|
||||
if (source !== Quill.sources.USER) return;
|
||||
this.balanceTables();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
export default Table;
|
||||
//# sourceMappingURL=table.js.map
|
||||
1
public/assets/quill/modules/table.js.map
Normal file
1
public/assets/quill/modules/table.js.map
Normal file
File diff suppressed because one or more lines are too long
27
public/assets/quill/modules/tableEmbed.d.ts
vendored
Normal file
27
public/assets/quill/modules/tableEmbed.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import Delta from 'quill-delta';
|
||||
import type { Op } from 'quill-delta';
|
||||
import Module from '../core/module.js';
|
||||
export type CellData = {
|
||||
content?: Delta['ops'];
|
||||
attributes?: Record<string, unknown>;
|
||||
};
|
||||
export type TableRowColumnOp = Omit<Op, 'insert'> & {
|
||||
insert?: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
export interface TableData {
|
||||
rows?: Delta['ops'];
|
||||
columns?: Delta['ops'];
|
||||
cells?: Record<string, CellData>;
|
||||
}
|
||||
export declare const composePosition: (delta: Delta, index: number) => number | null;
|
||||
export declare const tableHandler: {
|
||||
compose(a: TableData, b: TableData, keepNull?: boolean): TableData;
|
||||
transform(a: TableData, b: TableData, priority: boolean): TableData;
|
||||
invert(change: TableData, base: TableData): TableData;
|
||||
};
|
||||
declare class TableEmbed extends Module {
|
||||
static register(): void;
|
||||
}
|
||||
export default TableEmbed;
|
||||
209
public/assets/quill/modules/tableEmbed.js
Normal file
209
public/assets/quill/modules/tableEmbed.js
Normal file
@@ -0,0 +1,209 @@
|
||||
import Delta, { OpIterator } from 'quill-delta';
|
||||
import Module from '../core/module.js';
|
||||
const parseCellIdentity = identity => {
|
||||
const parts = identity.split(':');
|
||||
return [Number(parts[0]) - 1, Number(parts[1]) - 1];
|
||||
};
|
||||
const stringifyCellIdentity = (row, column) => `${row + 1}:${column + 1}`;
|
||||
export const composePosition = (delta, index) => {
|
||||
let newIndex = index;
|
||||
const thisIter = new OpIterator(delta.ops);
|
||||
let offset = 0;
|
||||
while (thisIter.hasNext() && offset <= newIndex) {
|
||||
const length = thisIter.peekLength();
|
||||
const nextType = thisIter.peekType();
|
||||
thisIter.next();
|
||||
switch (nextType) {
|
||||
case 'delete':
|
||||
if (length > newIndex - offset) {
|
||||
return null;
|
||||
}
|
||||
newIndex -= length;
|
||||
break;
|
||||
case 'insert':
|
||||
newIndex += length;
|
||||
offset += length;
|
||||
break;
|
||||
default:
|
||||
offset += length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return newIndex;
|
||||
};
|
||||
const compactCellData = _ref => {
|
||||
let {
|
||||
content,
|
||||
attributes
|
||||
} = _ref;
|
||||
const data = {};
|
||||
if (content.length() > 0) {
|
||||
data.content = content.ops;
|
||||
}
|
||||
if (attributes && Object.keys(attributes).length > 0) {
|
||||
data.attributes = attributes;
|
||||
}
|
||||
return Object.keys(data).length > 0 ? data : null;
|
||||
};
|
||||
const compactTableData = _ref2 => {
|
||||
let {
|
||||
rows,
|
||||
columns,
|
||||
cells
|
||||
} = _ref2;
|
||||
const data = {};
|
||||
if (rows.length() > 0) {
|
||||
data.rows = rows.ops;
|
||||
}
|
||||
if (columns.length() > 0) {
|
||||
data.columns = columns.ops;
|
||||
}
|
||||
if (Object.keys(cells).length) {
|
||||
data.cells = cells;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const reindexCellIdentities = (cells, _ref3) => {
|
||||
let {
|
||||
rows,
|
||||
columns
|
||||
} = _ref3;
|
||||
const reindexedCells = {};
|
||||
Object.keys(cells).forEach(identity => {
|
||||
let [row, column] = parseCellIdentity(identity);
|
||||
|
||||
// @ts-expect-error Fix me later
|
||||
row = composePosition(rows, row);
|
||||
// @ts-expect-error Fix me later
|
||||
column = composePosition(columns, column);
|
||||
if (row !== null && column !== null) {
|
||||
const newPosition = stringifyCellIdentity(row, column);
|
||||
reindexedCells[newPosition] = cells[identity];
|
||||
}
|
||||
}, false);
|
||||
return reindexedCells;
|
||||
};
|
||||
export const tableHandler = {
|
||||
compose(a, b, keepNull) {
|
||||
const rows = new Delta(a.rows || []).compose(new Delta(b.rows || []));
|
||||
const columns = new Delta(a.columns || []).compose(new Delta(b.columns || []));
|
||||
const cells = reindexCellIdentities(a.cells || {}, {
|
||||
rows: new Delta(b.rows || []),
|
||||
columns: new Delta(b.columns || [])
|
||||
});
|
||||
Object.keys(b.cells || {}).forEach(identity => {
|
||||
const aCell = cells[identity] || {};
|
||||
// @ts-expect-error Fix me later
|
||||
const bCell = b.cells[identity];
|
||||
const content = new Delta(aCell.content || []).compose(new Delta(bCell.content || []));
|
||||
const attributes = Delta.AttributeMap.compose(aCell.attributes, bCell.attributes, keepNull);
|
||||
const cell = compactCellData({
|
||||
content,
|
||||
attributes
|
||||
});
|
||||
if (cell) {
|
||||
cells[identity] = cell;
|
||||
} else {
|
||||
delete cells[identity];
|
||||
}
|
||||
});
|
||||
return compactTableData({
|
||||
rows,
|
||||
columns,
|
||||
cells
|
||||
});
|
||||
},
|
||||
transform(a, b, priority) {
|
||||
const aDeltas = {
|
||||
rows: new Delta(a.rows || []),
|
||||
columns: new Delta(a.columns || [])
|
||||
};
|
||||
const bDeltas = {
|
||||
rows: new Delta(b.rows || []),
|
||||
columns: new Delta(b.columns || [])
|
||||
};
|
||||
const rows = aDeltas.rows.transform(bDeltas.rows, priority);
|
||||
const columns = aDeltas.columns.transform(bDeltas.columns, priority);
|
||||
const cells = reindexCellIdentities(b.cells || {}, {
|
||||
rows: bDeltas.rows.transform(aDeltas.rows, !priority),
|
||||
columns: bDeltas.columns.transform(aDeltas.columns, !priority)
|
||||
});
|
||||
Object.keys(a.cells || {}).forEach(identity => {
|
||||
let [row, column] = parseCellIdentity(identity);
|
||||
// @ts-expect-error Fix me later
|
||||
row = composePosition(rows, row);
|
||||
// @ts-expect-error Fix me later
|
||||
column = composePosition(columns, column);
|
||||
if (row !== null && column !== null) {
|
||||
const newIdentity = stringifyCellIdentity(row, column);
|
||||
|
||||
// @ts-expect-error Fix me later
|
||||
const aCell = a.cells[identity];
|
||||
const bCell = cells[newIdentity];
|
||||
if (bCell) {
|
||||
const content = new Delta(aCell.content || []).transform(new Delta(bCell.content || []), priority);
|
||||
const attributes = Delta.AttributeMap.transform(aCell.attributes, bCell.attributes, priority);
|
||||
const cell = compactCellData({
|
||||
content,
|
||||
attributes
|
||||
});
|
||||
if (cell) {
|
||||
cells[newIdentity] = cell;
|
||||
} else {
|
||||
delete cells[newIdentity];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return compactTableData({
|
||||
rows,
|
||||
columns,
|
||||
cells
|
||||
});
|
||||
},
|
||||
invert(change, base) {
|
||||
const rows = new Delta(change.rows || []).invert(new Delta(base.rows || []));
|
||||
const columns = new Delta(change.columns || []).invert(new Delta(base.columns || []));
|
||||
const cells = reindexCellIdentities(change.cells || {}, {
|
||||
rows,
|
||||
columns
|
||||
});
|
||||
Object.keys(cells).forEach(identity => {
|
||||
const changeCell = cells[identity] || {};
|
||||
const baseCell = (base.cells || {})[identity] || {};
|
||||
const content = new Delta(changeCell.content || []).invert(new Delta(baseCell.content || []));
|
||||
const attributes = Delta.AttributeMap.invert(changeCell.attributes, baseCell.attributes);
|
||||
const cell = compactCellData({
|
||||
content,
|
||||
attributes
|
||||
});
|
||||
if (cell) {
|
||||
cells[identity] = cell;
|
||||
} else {
|
||||
delete cells[identity];
|
||||
}
|
||||
});
|
||||
|
||||
// Cells may be removed when their row or column is removed
|
||||
// by row/column deltas. We should add them back.
|
||||
Object.keys(base.cells || {}).forEach(identity => {
|
||||
const [row, column] = parseCellIdentity(identity);
|
||||
if (composePosition(new Delta(change.rows || []), row) === null || composePosition(new Delta(change.columns || []), column) === null) {
|
||||
// @ts-expect-error Fix me later
|
||||
cells[identity] = base.cells[identity];
|
||||
}
|
||||
});
|
||||
return compactTableData({
|
||||
rows,
|
||||
columns,
|
||||
cells
|
||||
});
|
||||
}
|
||||
};
|
||||
class TableEmbed extends Module {
|
||||
static register() {
|
||||
Delta.registerEmbed('table-embed', tableHandler);
|
||||
}
|
||||
}
|
||||
export default TableEmbed;
|
||||
//# sourceMappingURL=tableEmbed.js.map
|
||||
1
public/assets/quill/modules/tableEmbed.js.map
Normal file
1
public/assets/quill/modules/tableEmbed.js.map
Normal file
File diff suppressed because one or more lines are too long
24
public/assets/quill/modules/toolbar.d.ts
vendored
Normal file
24
public/assets/quill/modules/toolbar.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
import type { Range } from '../core/selection.js';
|
||||
type Handler = (this: Toolbar, value: any) => void;
|
||||
export type ToolbarConfig = Array<string[] | Array<string | Record<string, unknown>>>;
|
||||
export interface ToolbarProps {
|
||||
container?: HTMLElement | ToolbarConfig | null;
|
||||
handlers?: Record<string, Handler>;
|
||||
option?: number;
|
||||
module?: boolean;
|
||||
theme?: boolean;
|
||||
}
|
||||
declare class Toolbar extends Module<ToolbarProps> {
|
||||
static DEFAULTS: ToolbarProps;
|
||||
container?: HTMLElement | null;
|
||||
controls: [string, HTMLElement][];
|
||||
handlers: Record<string, Handler>;
|
||||
constructor(quill: Quill, options: Partial<ToolbarProps>);
|
||||
addHandler(format: string, handler: Handler): void;
|
||||
attach(input: HTMLElement): void;
|
||||
update(range: Range | null): void;
|
||||
}
|
||||
declare function addControls(container: HTMLElement, groups: (string | Record<string, unknown>)[][] | (string | Record<string, unknown>)[]): void;
|
||||
export { Toolbar as default, addControls };
|
||||
265
public/assets/quill/modules/toolbar.js
Normal file
265
public/assets/quill/modules/toolbar.js
Normal file
@@ -0,0 +1,265 @@
|
||||
import Delta from 'quill-delta';
|
||||
import { EmbedBlot, Scope } from 'parchment';
|
||||
import Quill from '../core/quill.js';
|
||||
import logger from '../core/logger.js';
|
||||
import Module from '../core/module.js';
|
||||
const debug = logger('quill:toolbar');
|
||||
class Toolbar extends Module {
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
if (Array.isArray(this.options.container)) {
|
||||
const container = document.createElement('div');
|
||||
container.setAttribute('role', 'toolbar');
|
||||
addControls(container, this.options.container);
|
||||
quill.container?.parentNode?.insertBefore(container, quill.container);
|
||||
this.container = container;
|
||||
} else if (typeof this.options.container === 'string') {
|
||||
this.container = document.querySelector(this.options.container);
|
||||
} else {
|
||||
this.container = this.options.container;
|
||||
}
|
||||
if (!(this.container instanceof HTMLElement)) {
|
||||
debug.error('Container required for toolbar', this.options);
|
||||
return;
|
||||
}
|
||||
this.container.classList.add('ql-toolbar');
|
||||
this.controls = [];
|
||||
this.handlers = {};
|
||||
if (this.options.handlers) {
|
||||
Object.keys(this.options.handlers).forEach(format => {
|
||||
const handler = this.options.handlers?.[format];
|
||||
if (handler) {
|
||||
this.addHandler(format, handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
Array.from(this.container.querySelectorAll('button, select')).forEach(input => {
|
||||
// @ts-expect-error
|
||||
this.attach(input);
|
||||
});
|
||||
this.quill.on(Quill.events.EDITOR_CHANGE, () => {
|
||||
const [range] = this.quill.selection.getRange(); // quill.getSelection triggers update
|
||||
this.update(range);
|
||||
});
|
||||
}
|
||||
addHandler(format, handler) {
|
||||
this.handlers[format] = handler;
|
||||
}
|
||||
attach(input) {
|
||||
let format = Array.from(input.classList).find(className => {
|
||||
return className.indexOf('ql-') === 0;
|
||||
});
|
||||
if (!format) return;
|
||||
format = format.slice('ql-'.length);
|
||||
if (input.tagName === 'BUTTON') {
|
||||
input.setAttribute('type', 'button');
|
||||
}
|
||||
if (this.handlers[format] == null && this.quill.scroll.query(format) == null) {
|
||||
debug.warn('ignoring attaching to nonexistent format', format, input);
|
||||
return;
|
||||
}
|
||||
const eventName = input.tagName === 'SELECT' ? 'change' : 'click';
|
||||
input.addEventListener(eventName, e => {
|
||||
let value;
|
||||
if (input.tagName === 'SELECT') {
|
||||
// @ts-expect-error
|
||||
if (input.selectedIndex < 0) return;
|
||||
// @ts-expect-error
|
||||
const selected = input.options[input.selectedIndex];
|
||||
if (selected.hasAttribute('selected')) {
|
||||
value = false;
|
||||
} else {
|
||||
value = selected.value || false;
|
||||
}
|
||||
} else {
|
||||
if (input.classList.contains('ql-active')) {
|
||||
value = false;
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
value = input.value || !input.hasAttribute('value');
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
this.quill.focus();
|
||||
const [range] = this.quill.selection.getRange();
|
||||
if (this.handlers[format] != null) {
|
||||
this.handlers[format].call(this, value);
|
||||
} else if (
|
||||
// @ts-expect-error
|
||||
this.quill.scroll.query(format).prototype instanceof EmbedBlot) {
|
||||
value = prompt(`Enter ${format}`); // eslint-disable-line no-alert
|
||||
if (!value) return;
|
||||
this.quill.updateContents(new Delta()
|
||||
// @ts-expect-error Fix me later
|
||||
.retain(range.index)
|
||||
// @ts-expect-error Fix me later
|
||||
.delete(range.length).insert({
|
||||
[format]: value
|
||||
}), Quill.sources.USER);
|
||||
} else {
|
||||
this.quill.format(format, value, Quill.sources.USER);
|
||||
}
|
||||
this.update(range);
|
||||
});
|
||||
this.controls.push([format, input]);
|
||||
}
|
||||
update(range) {
|
||||
const formats = range == null ? {} : this.quill.getFormat(range);
|
||||
this.controls.forEach(pair => {
|
||||
const [format, input] = pair;
|
||||
if (input.tagName === 'SELECT') {
|
||||
let option = null;
|
||||
if (range == null) {
|
||||
option = null;
|
||||
} else if (formats[format] == null) {
|
||||
option = input.querySelector('option[selected]');
|
||||
} else if (!Array.isArray(formats[format])) {
|
||||
let value = formats[format];
|
||||
if (typeof value === 'string') {
|
||||
value = value.replace(/"/g, '\\"');
|
||||
}
|
||||
option = input.querySelector(`option[value="${value}"]`);
|
||||
}
|
||||
if (option == null) {
|
||||
// @ts-expect-error TODO fix me later
|
||||
input.value = ''; // TODO make configurable?
|
||||
// @ts-expect-error TODO fix me later
|
||||
input.selectedIndex = -1;
|
||||
} else {
|
||||
option.selected = true;
|
||||
}
|
||||
} else if (range == null) {
|
||||
input.classList.remove('ql-active');
|
||||
input.setAttribute('aria-pressed', 'false');
|
||||
} else if (input.hasAttribute('value')) {
|
||||
// both being null should match (default values)
|
||||
// '1' should match with 1 (headers)
|
||||
const value = formats[format];
|
||||
const isActive = value === input.getAttribute('value') || value != null && value.toString() === input.getAttribute('value') || value == null && !input.getAttribute('value');
|
||||
input.classList.toggle('ql-active', isActive);
|
||||
input.setAttribute('aria-pressed', isActive.toString());
|
||||
} else {
|
||||
const isActive = formats[format] != null;
|
||||
input.classList.toggle('ql-active', isActive);
|
||||
input.setAttribute('aria-pressed', isActive.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Toolbar.DEFAULTS = {};
|
||||
function addButton(container, format, value) {
|
||||
const input = document.createElement('button');
|
||||
input.setAttribute('type', 'button');
|
||||
input.classList.add(`ql-${format}`);
|
||||
input.setAttribute('aria-pressed', 'false');
|
||||
if (value != null) {
|
||||
input.value = value;
|
||||
input.setAttribute('aria-label', `${format}: ${value}`);
|
||||
} else {
|
||||
input.setAttribute('aria-label', format);
|
||||
}
|
||||
container.appendChild(input);
|
||||
}
|
||||
function addControls(container, groups) {
|
||||
if (!Array.isArray(groups[0])) {
|
||||
// @ts-expect-error
|
||||
groups = [groups];
|
||||
}
|
||||
groups.forEach(controls => {
|
||||
const group = document.createElement('span');
|
||||
group.classList.add('ql-formats');
|
||||
controls.forEach(control => {
|
||||
if (typeof control === 'string') {
|
||||
addButton(group, control);
|
||||
} else {
|
||||
const format = Object.keys(control)[0];
|
||||
const value = control[format];
|
||||
if (Array.isArray(value)) {
|
||||
addSelect(group, format, value);
|
||||
} else {
|
||||
addButton(group, format, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
container.appendChild(group);
|
||||
});
|
||||
}
|
||||
function addSelect(container, format, values) {
|
||||
const input = document.createElement('select');
|
||||
input.classList.add(`ql-${format}`);
|
||||
values.forEach(value => {
|
||||
const option = document.createElement('option');
|
||||
if (value !== false) {
|
||||
option.setAttribute('value', String(value));
|
||||
} else {
|
||||
option.setAttribute('selected', 'selected');
|
||||
}
|
||||
input.appendChild(option);
|
||||
});
|
||||
container.appendChild(input);
|
||||
}
|
||||
Toolbar.DEFAULTS = {
|
||||
container: null,
|
||||
handlers: {
|
||||
clean() {
|
||||
const range = this.quill.getSelection();
|
||||
if (range == null) return;
|
||||
if (range.length === 0) {
|
||||
const formats = this.quill.getFormat();
|
||||
Object.keys(formats).forEach(name => {
|
||||
// Clean functionality in existing apps only clean inline formats
|
||||
if (this.quill.scroll.query(name, Scope.INLINE) != null) {
|
||||
this.quill.format(name, false, Quill.sources.USER);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.quill.removeFormat(range.index, range.length, Quill.sources.USER);
|
||||
}
|
||||
},
|
||||
direction(value) {
|
||||
const {
|
||||
align
|
||||
} = this.quill.getFormat();
|
||||
if (value === 'rtl' && align == null) {
|
||||
this.quill.format('align', 'right', Quill.sources.USER);
|
||||
} else if (!value && align === 'right') {
|
||||
this.quill.format('align', false, Quill.sources.USER);
|
||||
}
|
||||
this.quill.format('direction', value, Quill.sources.USER);
|
||||
},
|
||||
indent(value) {
|
||||
const range = this.quill.getSelection();
|
||||
// @ts-expect-error
|
||||
const formats = this.quill.getFormat(range);
|
||||
// @ts-expect-error
|
||||
const indent = parseInt(formats.indent || 0, 10);
|
||||
if (value === '+1' || value === '-1') {
|
||||
let modifier = value === '+1' ? 1 : -1;
|
||||
if (formats.direction === 'rtl') modifier *= -1;
|
||||
this.quill.format('indent', indent + modifier, Quill.sources.USER);
|
||||
}
|
||||
},
|
||||
link(value) {
|
||||
if (value === true) {
|
||||
value = prompt('Enter link URL:'); // eslint-disable-line no-alert
|
||||
}
|
||||
this.quill.format('link', value, Quill.sources.USER);
|
||||
},
|
||||
list(value) {
|
||||
const range = this.quill.getSelection();
|
||||
// @ts-expect-error
|
||||
const formats = this.quill.getFormat(range);
|
||||
if (value === 'check') {
|
||||
if (formats.list === 'checked' || formats.list === 'unchecked') {
|
||||
this.quill.format('list', false, Quill.sources.USER);
|
||||
} else {
|
||||
this.quill.format('list', 'unchecked', Quill.sources.USER);
|
||||
}
|
||||
} else {
|
||||
this.quill.format('list', value, Quill.sources.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export { Toolbar as default, addControls };
|
||||
//# sourceMappingURL=toolbar.js.map
|
||||
1
public/assets/quill/modules/toolbar.js.map
Normal file
1
public/assets/quill/modules/toolbar.js.map
Normal file
File diff suppressed because one or more lines are too long
19
public/assets/quill/modules/uiNode.d.ts
vendored
Normal file
19
public/assets/quill/modules/uiNode.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
export declare const TTL_FOR_VALID_SELECTION_CHANGE = 100;
|
||||
declare class UINode extends Module {
|
||||
isListening: boolean;
|
||||
selectionChangeDeadline: number;
|
||||
constructor(quill: Quill, options: Record<string, never>);
|
||||
private handleArrowKeys;
|
||||
private handleNavigationShortcuts;
|
||||
/**
|
||||
* We only listen to the `selectionchange` event when
|
||||
* there is an intention of moving the caret to the beginning using shortcuts.
|
||||
* This is primarily implemented to prevent infinite loops, as we are changing
|
||||
* the selection within the handler of a `selectionchange` event.
|
||||
*/
|
||||
private ensureListeningToSelectionChange;
|
||||
private handleSelectionChange;
|
||||
}
|
||||
export default UINode;
|
||||
95
public/assets/quill/modules/uiNode.js
Normal file
95
public/assets/quill/modules/uiNode.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import { ParentBlot } from 'parchment';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
const isMac = /Mac/i.test(navigator.platform);
|
||||
|
||||
// Export for testing
|
||||
export const TTL_FOR_VALID_SELECTION_CHANGE = 100;
|
||||
|
||||
// A loose check to determine if the shortcut can move the caret before a UI node:
|
||||
// <ANY_PARENT>[CARET]<div class="ql-ui"></div>[CONTENT]</ANY_PARENT>
|
||||
const canMoveCaretBeforeUINode = event => {
|
||||
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
|
||||
// RTL scripts or moving from the end of the previous line
|
||||
event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Home') {
|
||||
return true;
|
||||
}
|
||||
if (isMac && event.key === 'a' && event.ctrlKey === true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
class UINode extends Module {
|
||||
isListening = false;
|
||||
selectionChangeDeadline = 0;
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
this.handleArrowKeys();
|
||||
this.handleNavigationShortcuts();
|
||||
}
|
||||
handleArrowKeys() {
|
||||
this.quill.keyboard.addBinding({
|
||||
key: ['ArrowLeft', 'ArrowRight'],
|
||||
offset: 0,
|
||||
shiftKey: null,
|
||||
handler(range, _ref) {
|
||||
let {
|
||||
line,
|
||||
event
|
||||
} = _ref;
|
||||
if (!(line instanceof ParentBlot) || !line.uiNode) {
|
||||
return true;
|
||||
}
|
||||
const isRTL = getComputedStyle(line.domNode)['direction'] === 'rtl';
|
||||
if (isRTL && event.key !== 'ArrowRight' || !isRTL && event.key !== 'ArrowLeft') {
|
||||
return true;
|
||||
}
|
||||
this.quill.setSelection(range.index - 1, range.length + (event.shiftKey ? 1 : 0), Quill.sources.USER);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
handleNavigationShortcuts() {
|
||||
this.quill.root.addEventListener('keydown', event => {
|
||||
if (!event.defaultPrevented && canMoveCaretBeforeUINode(event)) {
|
||||
this.ensureListeningToSelectionChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* We only listen to the `selectionchange` event when
|
||||
* there is an intention of moving the caret to the beginning using shortcuts.
|
||||
* This is primarily implemented to prevent infinite loops, as we are changing
|
||||
* the selection within the handler of a `selectionchange` event.
|
||||
*/
|
||||
ensureListeningToSelectionChange() {
|
||||
this.selectionChangeDeadline = Date.now() + TTL_FOR_VALID_SELECTION_CHANGE;
|
||||
if (this.isListening) return;
|
||||
this.isListening = true;
|
||||
const listener = () => {
|
||||
this.isListening = false;
|
||||
if (Date.now() <= this.selectionChangeDeadline) {
|
||||
this.handleSelectionChange();
|
||||
}
|
||||
};
|
||||
document.addEventListener('selectionchange', listener, {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
handleSelectionChange() {
|
||||
const selection = document.getSelection();
|
||||
if (!selection) return;
|
||||
const range = selection.getRangeAt(0);
|
||||
if (range.collapsed !== true || range.startOffset !== 0) return;
|
||||
const line = this.quill.scroll.find(range.startContainer);
|
||||
if (!(line instanceof ParentBlot) || !line.uiNode) return;
|
||||
const newRange = document.createRange();
|
||||
newRange.setStartAfter(line.uiNode);
|
||||
newRange.setEndAfter(line.uiNode);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(newRange);
|
||||
}
|
||||
}
|
||||
export default UINode;
|
||||
//# sourceMappingURL=uiNode.js.map
|
||||
1
public/assets/quill/modules/uiNode.js.map
Normal file
1
public/assets/quill/modules/uiNode.js.map
Normal file
File diff suppressed because one or more lines are too long
15
public/assets/quill/modules/uploader.d.ts
vendored
Normal file
15
public/assets/quill/modules/uploader.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
import type { Range } from '../core/selection.js';
|
||||
interface UploaderOptions {
|
||||
mimetypes: string[];
|
||||
handler: (this: {
|
||||
quill: Quill;
|
||||
}, range: Range, files: File[]) => void;
|
||||
}
|
||||
declare class Uploader extends Module<UploaderOptions> {
|
||||
static DEFAULTS: UploaderOptions;
|
||||
constructor(quill: Quill, options: Partial<UploaderOptions>);
|
||||
upload(range: Range, files: FileList | File[]): void;
|
||||
}
|
||||
export default Uploader;
|
||||
69
public/assets/quill/modules/uploader.js
Normal file
69
public/assets/quill/modules/uploader.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import Delta from 'quill-delta';
|
||||
import Emitter from '../core/emitter.js';
|
||||
import Module from '../core/module.js';
|
||||
class Uploader extends Module {
|
||||
constructor(quill, options) {
|
||||
super(quill, options);
|
||||
quill.root.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
let native = null;
|
||||
if (document.caretRangeFromPoint) {
|
||||
native = document.caretRangeFromPoint(e.clientX, e.clientY);
|
||||
// @ts-expect-error
|
||||
} else if (document.caretPositionFromPoint) {
|
||||
// @ts-expect-error
|
||||
const position = document.caretPositionFromPoint(e.clientX, e.clientY);
|
||||
native = document.createRange();
|
||||
native.setStart(position.offsetNode, position.offset);
|
||||
native.setEnd(position.offsetNode, position.offset);
|
||||
}
|
||||
const normalized = native && quill.selection.normalizeNative(native);
|
||||
if (normalized) {
|
||||
const range = quill.selection.normalizedToRange(normalized);
|
||||
if (e.dataTransfer?.files) {
|
||||
this.upload(range, e.dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
upload(range, files) {
|
||||
const uploads = [];
|
||||
Array.from(files).forEach(file => {
|
||||
if (file && this.options.mimetypes?.includes(file.type)) {
|
||||
uploads.push(file);
|
||||
}
|
||||
});
|
||||
if (uploads.length > 0) {
|
||||
// @ts-expect-error Fix me later
|
||||
this.options.handler.call(this, range, uploads);
|
||||
}
|
||||
}
|
||||
}
|
||||
Uploader.DEFAULTS = {
|
||||
mimetypes: ['image/png', 'image/jpeg'],
|
||||
handler(range, files) {
|
||||
if (!this.quill.scroll.query('image')) {
|
||||
return;
|
||||
}
|
||||
const promises = files.map(file => {
|
||||
return new Promise(resolve => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
Promise.all(promises).then(images => {
|
||||
const update = images.reduce((delta, image) => {
|
||||
return delta.insert({
|
||||
image
|
||||
});
|
||||
}, new Delta().retain(range.index).delete(range.length));
|
||||
this.quill.updateContents(update, Emitter.sources.USER);
|
||||
this.quill.setSelection(range.index + images.length, Emitter.sources.SILENT);
|
||||
});
|
||||
}
|
||||
};
|
||||
export default Uploader;
|
||||
//# sourceMappingURL=uploader.js.map
|
||||
1
public/assets/quill/modules/uploader.js.map
Normal file
1
public/assets/quill/modules/uploader.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user