Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40d5781c6b | ||
|
|
2a0bd6dda2 | ||
|
|
f6eeddde2d | ||
|
|
9394a4592d | ||
|
|
ff34142824 | ||
|
|
b12bacbc12 | ||
|
|
2e6261c62a | ||
|
|
42100f814c | ||
|
|
b030001387 | ||
|
|
a5abc5db27 | ||
|
|
c52143fdfc | ||
|
|
194335b4d3 | ||
|
|
a826b90428 | ||
|
|
723daabb7d |
16
README.md
16
README.md
@ -1,3 +1,19 @@
|
||||
## Note: This fork is for use in Signal Desktop
|
||||
|
||||
The biggest change is moving from ESM to CJS.
|
||||
|
||||
Beyond that, there are a few other changes:
|
||||
- keyboard: fix a crash in keydown handler when line is missing
|
||||
- clipboard: add `time` element to list of elements in `isLine`
|
||||
- clipboard: update `matchNewline` to prevent too many newlines in a row
|
||||
- clipboard: update `convert` to apply provided formats to both text and html inputs
|
||||
- scroll: use `contenteditable="plaintext-only"` to fully prevent image paste
|
||||
- clipboard: introduce two new options:
|
||||
- `disableDefaultListeners` - if set to something truthy, disables the clipboard module's `cut`/`copy`/`paste` handlers
|
||||
- `defaultMatchersOverride` - if set, overrides all the default clipboard matchers, giving full control of the list of installed matchers
|
||||
|
||||
It's published as `@signalapp/quill-cjs`.
|
||||
|
||||
<h1 align="center">
|
||||
<a href="https://quilljs.com/" title="Quill">Quill Rich Text Editor</a>
|
||||
</h1>
|
||||
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@ -5709,6 +5709,16 @@
|
||||
"integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@signalapp/parchment-cjs": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@signalapp/parchment-cjs/-/parchment-cjs-3.0.1.tgz",
|
||||
"integrity": "sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@signalapp/quill-cjs": {
|
||||
"resolved": "packages/quill",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
@ -6184,19 +6194,11 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
||||
"integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash-es": {
|
||||
"version": "4.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||
"version": "4.17.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz",
|
||||
"integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "3.0.15",
|
||||
@ -13233,13 +13235,7 @@
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
@ -14840,11 +14836,6 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/parchment": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
|
||||
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@ -15550,10 +15541,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"resolved": "packages/quill",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/quill-delta": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
|
||||
@ -19236,12 +19223,13 @@
|
||||
}
|
||||
},
|
||||
"packages/quill": {
|
||||
"version": "2.0.3",
|
||||
"name": "@signalapp/quill-cjs",
|
||||
"version": "2.1.0",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@signalapp/parchment-cjs": "3.0.1",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parchment": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"quill-delta": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -19251,7 +19239,7 @@
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@playwright/test": "1.44.1",
|
||||
"@types/highlight.js": "^9.12.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
## Note: This fork is for use in Signal Desktop
|
||||
|
||||
# Quill
|
||||
|
||||
This is the main package of Quill.
|
||||
|
||||
@ -2,7 +2,7 @@ const pkg = require('./package.json');
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { modules: false }],
|
||||
['@babel/preset-env', { modules: 'cjs' }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: [
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
{
|
||||
"name": "quill",
|
||||
"version": "2.0.3",
|
||||
"name": "@signalapp/quill-cjs",
|
||||
"version": "2.1.2",
|
||||
"description": "Your powerful, rich text editor",
|
||||
"author": "Jason Chen <jhchen7@gmail.com>",
|
||||
"homepage": "https://quilljs.com",
|
||||
"main": "quill.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parchment": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"@signalapp/parchment-cjs": "3.0.1",
|
||||
"quill-delta": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -19,7 +18,7 @@
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@playwright/test": "1.44.1",
|
||||
"@types/highlight.js": "^9.12.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
|
||||
@ -11,7 +11,7 @@ rm -rf $DIST
|
||||
mkdir $DIST
|
||||
mv $TMPDIR/src/* $DIST
|
||||
rm -rf $TMPDIR
|
||||
npx babel src --out-dir $DIST --copy-files --no-copy-ignored --extensions .ts --source-maps
|
||||
npx babel src --out-dir $DIST --copy-files --no-copy-ignored --extensions .ts
|
||||
npx webpack -- --mode $1
|
||||
# https://github.com/webpack-contrib/mini-css-extract-plugin/issues/151
|
||||
rm -rf $DIST/dist/*.css.js $DIST/dist/*.css.js.*
|
||||
|
||||
@ -4,8 +4,8 @@ import {
|
||||
EmbedBlot,
|
||||
LeafBlot,
|
||||
Scope,
|
||||
} from 'parchment';
|
||||
import type { Blot, Parent } from 'parchment';
|
||||
} from '@signalapp/parchment-cjs';
|
||||
import type { Blot, Parent } from '@signalapp/parchment-cjs';
|
||||
import Delta from 'quill-delta';
|
||||
import Break from './break.js';
|
||||
import Inline from './inline.js';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EmbedBlot } from 'parchment';
|
||||
import { EmbedBlot } from '@signalapp/parchment-cjs';
|
||||
|
||||
class Break extends EmbedBlot {
|
||||
static value() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ContainerBlot } from 'parchment';
|
||||
import { ContainerBlot } from '@signalapp/parchment-cjs';
|
||||
|
||||
class Container extends ContainerBlot {}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { EmbedBlot, Scope } from 'parchment';
|
||||
import type { Parent, ScrollBlot } from 'parchment';
|
||||
import { EmbedBlot, Scope } from '@signalapp/parchment-cjs';
|
||||
import type { Parent, ScrollBlot } from '@signalapp/parchment-cjs';
|
||||
import type Selection from '../core/selection.js';
|
||||
import TextBlot from './text.js';
|
||||
import type { EmbedContextRange } from './embed.js';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ScrollBlot } from 'parchment';
|
||||
import { EmbedBlot } from 'parchment';
|
||||
import type { ScrollBlot } from '@signalapp/parchment-cjs';
|
||||
import { EmbedBlot } from '@signalapp/parchment-cjs';
|
||||
import TextBlot from './text.js';
|
||||
|
||||
const GUARD_TEXT = '\uFEFF';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { EmbedBlot, InlineBlot, Scope } from 'parchment';
|
||||
import type { BlotConstructor } from 'parchment';
|
||||
import { EmbedBlot, InlineBlot, Scope } from '@signalapp/parchment-cjs';
|
||||
import type { BlotConstructor } from '@signalapp/parchment-cjs';
|
||||
import Break from './break.js';
|
||||
import Text from './text.js';
|
||||
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
import { ContainerBlot, LeafBlot, Scope, ScrollBlot } from 'parchment';
|
||||
import type { Blot, Parent, EmbedBlot, ParentBlot, Registry } from 'parchment';
|
||||
import {
|
||||
ContainerBlot,
|
||||
LeafBlot,
|
||||
Scope,
|
||||
ScrollBlot,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
import type {
|
||||
Blot,
|
||||
Parent,
|
||||
EmbedBlot,
|
||||
ParentBlot,
|
||||
Registry,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
import Delta, { AttributeMap, Op } from 'quill-delta';
|
||||
import Emitter from '../core/emitter.js';
|
||||
import type { EmitterSource } from '../core/emitter.js';
|
||||
@ -96,7 +107,10 @@ class Scroll extends ScrollBlot {
|
||||
}
|
||||
|
||||
enable(enabled = true) {
|
||||
this.domNode.setAttribute('contenteditable', enabled ? 'true' : 'false');
|
||||
this.domNode.setAttribute(
|
||||
'contenteditable',
|
||||
enabled ? 'plaintext-only' : 'false',
|
||||
);
|
||||
}
|
||||
|
||||
formatAt(index: number, length: number, format: string, value: unknown) {
|
||||
@ -210,7 +224,8 @@ class Scroll extends ScrollBlot {
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.domNode.getAttribute('contenteditable') === 'true';
|
||||
const value = this.domNode.getAttribute('contenteditable');
|
||||
return value === 'true' || value === 'plaintext-only';
|
||||
}
|
||||
|
||||
leaf(index: number): [LeafBlot | null, number] {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { TextBlot } from 'parchment';
|
||||
import { TextBlot } from '@signalapp/parchment-cjs';
|
||||
|
||||
class Text extends TextBlot {}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { cloneDeep, isEqual, merge } from 'lodash-es';
|
||||
import { LeafBlot, EmbedBlot, Scope, ParentBlot } from 'parchment';
|
||||
import type { Blot } from 'parchment';
|
||||
import { cloneDeep, isEqual, merge } from 'lodash';
|
||||
import {
|
||||
LeafBlot,
|
||||
EmbedBlot,
|
||||
Scope,
|
||||
ParentBlot,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
import type { Blot } from '@signalapp/parchment-cjs';
|
||||
import Delta, { AttributeMap, Op } from 'quill-delta';
|
||||
import Block, { BlockEmbed, bubbleFormats } from '../blots/block.js';
|
||||
import Break from '../blots/break.js';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import * as Parchment from 'parchment';
|
||||
import { merge } from 'lodash';
|
||||
import * as Parchment from '@signalapp/parchment-cjs';
|
||||
import type { Op } from 'quill-delta';
|
||||
import Delta from 'quill-delta';
|
||||
import type { BlockEmbed } from '../blots/block.js';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { LeafBlot, Scope } from 'parchment';
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
import { LeafBlot, Scope } from '@signalapp/parchment-cjs';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
import Emitter from './emitter.js';
|
||||
import type { EmitterSource } from './emitter.js';
|
||||
import logger from './logger.js';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Registry } from 'parchment';
|
||||
import { Registry } from '@signalapp/parchment-cjs';
|
||||
|
||||
const MAX_REGISTER_ITERATIONS = 100;
|
||||
const CORE_FORMATS = ['block', 'break', 'cursor', 'inline', 'scroll', 'text'];
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { Attributor, ClassAttributor, Scope, StyleAttributor } from 'parchment';
|
||||
import {
|
||||
Attributor,
|
||||
ClassAttributor,
|
||||
Scope,
|
||||
StyleAttributor,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
|
||||
const config = {
|
||||
scope: Scope.BLOCK,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ClassAttributor, Scope } from 'parchment';
|
||||
import { ClassAttributor, Scope } from '@signalapp/parchment-cjs';
|
||||
import { ColorAttributor } from './color.js';
|
||||
|
||||
const BackgroundClass = new ClassAttributor('background', 'ql-bg', {
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { ClassAttributor, Scope, StyleAttributor } from 'parchment';
|
||||
import {
|
||||
ClassAttributor,
|
||||
Scope,
|
||||
StyleAttributor,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
|
||||
class ColorAttributor extends StyleAttributor {
|
||||
value(domNode: HTMLElement) {
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { Attributor, ClassAttributor, Scope, StyleAttributor } from 'parchment';
|
||||
import {
|
||||
Attributor,
|
||||
ClassAttributor,
|
||||
Scope,
|
||||
StyleAttributor,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
|
||||
const config = {
|
||||
scope: Scope.BLOCK,
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { ClassAttributor, Scope, StyleAttributor } from 'parchment';
|
||||
import {
|
||||
ClassAttributor,
|
||||
Scope,
|
||||
StyleAttributor,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
|
||||
const config = {
|
||||
scope: Scope.INLINE,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EmbedBlot } from 'parchment';
|
||||
import { EmbedBlot } from '@signalapp/parchment-cjs';
|
||||
import { sanitize } from './link.js';
|
||||
|
||||
const ATTRIBUTES = ['alt', 'height', 'width'];
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ClassAttributor, Scope } from 'parchment';
|
||||
import { ClassAttributor, Scope } from '@signalapp/parchment-cjs';
|
||||
|
||||
class IndentAttributor extends ClassAttributor {
|
||||
add(node: HTMLElement, value: string | number) {
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { ClassAttributor, Scope, StyleAttributor } from 'parchment';
|
||||
import {
|
||||
ClassAttributor,
|
||||
Scope,
|
||||
StyleAttributor,
|
||||
} from '@signalapp/parchment-cjs';
|
||||
|
||||
const SizeClass = new ClassAttributor('size', 'ql-size', {
|
||||
scope: Scope.INLINE,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { LinkedList } from 'parchment';
|
||||
import type { LinkedList } from '@signalapp/parchment-cjs';
|
||||
import Block from '../blots/block.js';
|
||||
import Container from '../blots/container.js';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ScrollBlot } from 'parchment';
|
||||
import type { ScrollBlot } from '@signalapp/parchment-cjs';
|
||||
import {
|
||||
Attributor,
|
||||
BlockBlot,
|
||||
@ -6,8 +6,9 @@ import {
|
||||
EmbedBlot,
|
||||
Scope,
|
||||
StyleAttributor,
|
||||
} from 'parchment';
|
||||
} from '@signalapp/parchment-cjs';
|
||||
import Delta from 'quill-delta';
|
||||
import type { AttributeMap } from 'quill-delta';
|
||||
import { BlockEmbed } from '../blots/block.js';
|
||||
import type { EmitterSource } from '../core/emitter.js';
|
||||
import logger from '../core/logger.js';
|
||||
@ -27,7 +28,12 @@ import normalizeExternalHTML from './normalizeExternalHTML/index.js';
|
||||
const debug = logger('quill:clipboard');
|
||||
|
||||
type Selector = string | Node['TEXT_NODE'] | Node['ELEMENT_NODE'];
|
||||
type Matcher = (node: Node, delta: Delta, scroll: ScrollBlot) => Delta;
|
||||
type Matcher = (
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) => Delta;
|
||||
|
||||
const CLIPBOARD_CONFIG: [Selector, Matcher][] = [
|
||||
[Node.TEXT_NODE, matchText],
|
||||
@ -69,6 +75,8 @@ const STYLE_ATTRIBUTORS = [
|
||||
|
||||
interface ClipboardOptions {
|
||||
matchers: [Selector, Matcher][];
|
||||
defaultMatchersOverride?: [Selector, Matcher][];
|
||||
disableDefaultListeners?: boolean;
|
||||
}
|
||||
|
||||
class Clipboard extends Module<ClipboardOptions> {
|
||||
@ -80,17 +88,24 @@ class Clipboard extends Module<ClipboardOptions> {
|
||||
|
||||
constructor(quill: Quill, options: Partial<ClipboardOptions>) {
|
||||
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));
|
||||
|
||||
if (!options.disableDefaultListeners) {
|
||||
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(
|
||||
([selector, matcher]) => {
|
||||
const baseMatchers = options.defaultMatchersOverride ?? CLIPBOARD_CONFIG;
|
||||
baseMatchers
|
||||
.concat(this.options.matchers ?? [])
|
||||
.forEach(([selector, matcher]) => {
|
||||
this.addMatcher(selector, matcher);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addMatcher(selector: Selector, matcher: Matcher) {
|
||||
@ -109,7 +124,7 @@ class Clipboard extends Module<ClipboardOptions> {
|
||||
if (!html) {
|
||||
return new Delta().insert(text || '', formats);
|
||||
}
|
||||
const delta = this.convertHTML(html);
|
||||
const delta = this.convertHTML(html, formats);
|
||||
// Remove trailing newline
|
||||
if (
|
||||
deltaEndsWith(delta, '\n') &&
|
||||
@ -124,7 +139,7 @@ class Clipboard extends Module<ClipboardOptions> {
|
||||
normalizeExternalHTML(doc);
|
||||
}
|
||||
|
||||
protected convertHTML(html: string) {
|
||||
protected convertHTML(html: string, formats: Record<string, unknown> = {}) {
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
this.normalizeHTML(doc);
|
||||
const container = doc.body;
|
||||
@ -139,6 +154,7 @@ class Clipboard extends Module<ClipboardOptions> {
|
||||
elementMatchers,
|
||||
textMatchers,
|
||||
nodeMatches,
|
||||
formats,
|
||||
);
|
||||
}
|
||||
|
||||
@ -282,6 +298,7 @@ function applyFormat(
|
||||
format: string,
|
||||
value: unknown,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
): Delta {
|
||||
if (!scroll.query(format)) {
|
||||
return delta;
|
||||
@ -290,10 +307,14 @@ function applyFormat(
|
||||
return delta.reduce((newDelta, op) => {
|
||||
if (!op.insert) return newDelta;
|
||||
if (op.attributes && op.attributes[format]) {
|
||||
return newDelta.push(op);
|
||||
return newDelta.insert(op.insert, { ...op.attributes, ...attributes });
|
||||
}
|
||||
const formats = value ? { [format]: value } : {};
|
||||
return newDelta.insert(op.insert, { ...formats, ...op.attributes });
|
||||
return newDelta.insert(op.insert, {
|
||||
...formats,
|
||||
...op.attributes,
|
||||
...attributes,
|
||||
});
|
||||
}, new Delta());
|
||||
}
|
||||
|
||||
@ -349,6 +370,7 @@ function isLine(node: Node, scroll: ScrollBlot) {
|
||||
'section',
|
||||
'table',
|
||||
'td',
|
||||
'time',
|
||||
'tr',
|
||||
'ul',
|
||||
'video',
|
||||
@ -384,11 +406,12 @@ function traverse(
|
||||
elementMatchers: Matcher[],
|
||||
textMatchers: Matcher[],
|
||||
nodeMatches: WeakMap<Node, Matcher[]>,
|
||||
attributes: AttributeMap,
|
||||
): Delta {
|
||||
// Post-order
|
||||
if (node.nodeType === node.TEXT_NODE) {
|
||||
return textMatchers.reduce((delta: Delta, matcher) => {
|
||||
return matcher(node, delta, scroll);
|
||||
return matcher(node, delta, scroll, attributes);
|
||||
}, new Delta());
|
||||
}
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
@ -399,14 +422,20 @@ function traverse(
|
||||
elementMatchers,
|
||||
textMatchers,
|
||||
nodeMatches,
|
||||
attributes,
|
||||
);
|
||||
if (childNode.nodeType === node.ELEMENT_NODE) {
|
||||
childrenDelta = elementMatchers.reduce((reducedDelta, matcher) => {
|
||||
return matcher(childNode as HTMLElement, reducedDelta, scroll);
|
||||
return matcher(
|
||||
childNode as HTMLElement,
|
||||
reducedDelta,
|
||||
scroll,
|
||||
attributes,
|
||||
);
|
||||
}, childrenDelta);
|
||||
childrenDelta = (nodeMatches.get(childNode) || []).reduce(
|
||||
(reducedDelta, matcher) => {
|
||||
return matcher(childNode, reducedDelta, scroll);
|
||||
return matcher(childNode, reducedDelta, scroll, attributes);
|
||||
},
|
||||
childrenDelta,
|
||||
);
|
||||
@ -417,13 +446,23 @@ function traverse(
|
||||
return new Delta();
|
||||
}
|
||||
|
||||
function createMatchAlias(format: string) {
|
||||
return (_node: Element, delta: Delta, scroll: ScrollBlot) => {
|
||||
return applyFormat(delta, format, true, scroll);
|
||||
function createMatchAlias(format: string): Matcher {
|
||||
return (
|
||||
_node: Element,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) => {
|
||||
return applyFormat(delta, format, true, scroll, attributes);
|
||||
};
|
||||
}
|
||||
|
||||
function matchAttributor(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchAttributor(
|
||||
node: HTMLElement,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
targetAttributes: AttributeMap,
|
||||
) {
|
||||
const attributes = Attributor.keys(node);
|
||||
const classes = ClassAttributor.keys(node);
|
||||
const styles = StyleAttributor.keys(node);
|
||||
@ -449,12 +488,18 @@ function matchAttributor(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
|
||||
});
|
||||
|
||||
return Object.entries(formats).reduce(
|
||||
(newDelta, [name, value]) => applyFormat(newDelta, name, value, scroll),
|
||||
(newDelta, [name, value]) =>
|
||||
applyFormat(newDelta, name, value, scroll, targetAttributes),
|
||||
delta,
|
||||
);
|
||||
}
|
||||
|
||||
function matchBlot(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchBlot(
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
const match = scroll.query(node);
|
||||
if (match == null) return delta;
|
||||
// @ts-expect-error
|
||||
@ -483,33 +528,49 @@ function matchBlot(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
match.blotName,
|
||||
match.formats(node, scroll),
|
||||
scroll,
|
||||
attributes,
|
||||
);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
function matchBreak(node: Node, delta: Delta) {
|
||||
function matchBreak(
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
_scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
if (!deltaEndsWith(delta, '\n')) {
|
||||
delta.insert('\n');
|
||||
delta.insert('\n', attributes);
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
function matchCodeBlock(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchCodeBlock(
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
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);
|
||||
return applyFormat(delta, 'code-block', language, scroll, attributes);
|
||||
}
|
||||
|
||||
function matchIgnore() {
|
||||
return new Delta();
|
||||
}
|
||||
|
||||
function matchIndent(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchIndent(
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
const match = scroll.query(node);
|
||||
if (
|
||||
match == null ||
|
||||
@ -534,11 +595,20 @@ function matchIndent(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
if (op.attributes && typeof op.attributes.indent === 'number') {
|
||||
return composed.push(op);
|
||||
}
|
||||
return composed.insert(op.insert, { indent, ...(op.attributes || {}) });
|
||||
return composed.insert(op.insert, {
|
||||
indent,
|
||||
...(op.attributes || {}),
|
||||
attributes,
|
||||
});
|
||||
}, new Delta());
|
||||
}
|
||||
|
||||
function matchList(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchList(
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
const element = node as Element;
|
||||
let list = element.tagName === 'OL' ? 'ordered' : 'bullet';
|
||||
|
||||
@ -547,27 +617,48 @@ function matchList(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
list = checkedAttr === 'true' ? 'checked' : 'unchecked';
|
||||
}
|
||||
|
||||
return applyFormat(delta, 'list', list, scroll);
|
||||
return applyFormat(delta, 'list', list, scroll, attributes);
|
||||
}
|
||||
|
||||
function matchNewline(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
if (!deltaEndsWith(delta, '\n')) {
|
||||
function deltaIs(delta: Delta, text: string): boolean {
|
||||
let allText = '';
|
||||
for (
|
||||
let i = delta.ops.length - 1;
|
||||
i >= 0 && allText.length <= text.length;
|
||||
i -= 1
|
||||
) {
|
||||
const op = delta.ops[i];
|
||||
if (typeof op.insert !== 'string') {
|
||||
break;
|
||||
}
|
||||
allText = op.insert + allText;
|
||||
}
|
||||
return allText === text;
|
||||
}
|
||||
|
||||
function matchNewline(
|
||||
node: Node,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
if (!deltaIs(delta, '\n') && !deltaEndsWith(delta, '\n\n')) {
|
||||
if (
|
||||
isLine(node, scroll) &&
|
||||
(node.childNodes.length > 0 || node instanceof HTMLParagraphElement)
|
||||
) {
|
||||
return delta.insert('\n');
|
||||
return delta.insert('\n', attributes);
|
||||
}
|
||||
if (delta.length() > 0 && node.nextSibling) {
|
||||
let nextSibling: Node | null = node.nextSibling;
|
||||
while (nextSibling != null) {
|
||||
if (isLine(nextSibling, scroll)) {
|
||||
return delta.insert('\n');
|
||||
return delta.insert('\n', attributes);
|
||||
}
|
||||
const match = scroll.query(nextSibling);
|
||||
// @ts-expect-error
|
||||
if (match && match.prototype instanceof BlockEmbed) {
|
||||
return delta.insert('\n');
|
||||
return delta.insert('\n', attributes);
|
||||
}
|
||||
nextSibling = nextSibling.firstChild;
|
||||
}
|
||||
@ -576,7 +667,12 @@ function matchNewline(node: Node, delta: Delta, scroll: ScrollBlot) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
function matchStyles(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchStyles(
|
||||
node: HTMLElement,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
const formats: Record<string, unknown> = {};
|
||||
const style: Partial<CSSStyleDeclaration> = node.style || {};
|
||||
if (style.fontStyle === 'italic') {
|
||||
@ -596,13 +692,14 @@ function matchStyles(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
|
||||
formats.bold = true;
|
||||
}
|
||||
delta = Object.entries(formats).reduce(
|
||||
(newDelta, [name, value]) => applyFormat(newDelta, name, value, scroll),
|
||||
(newDelta, [name, value]) =>
|
||||
applyFormat(newDelta, name, value, scroll, attributes),
|
||||
delta,
|
||||
);
|
||||
// @ts-expect-error
|
||||
if (parseFloat(style.textIndent || 0) > 0) {
|
||||
// Could be 0.5in
|
||||
return new Delta().insert('\t').concat(delta);
|
||||
return new Delta().insert('\t', attributes).concat(delta);
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
@ -611,6 +708,7 @@ function matchTable(
|
||||
node: HTMLTableRowElement,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
const table =
|
||||
node.parentElement?.tagName === 'TABLE'
|
||||
@ -619,17 +717,22 @@ function matchTable(
|
||||
if (table != null) {
|
||||
const rows = Array.from(table.querySelectorAll('tr'));
|
||||
const row = rows.indexOf(node) + 1;
|
||||
return applyFormat(delta, 'table', row, scroll);
|
||||
return applyFormat(delta, 'table', row, scroll, attributes);
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
|
||||
function matchText(
|
||||
node: HTMLElement,
|
||||
delta: Delta,
|
||||
scroll: ScrollBlot,
|
||||
attributes: AttributeMap,
|
||||
) {
|
||||
// @ts-expect-error
|
||||
let text = node.data as string;
|
||||
// Word represents empty line with <o:p> </o:p>
|
||||
if (node.parentElement?.tagName === 'O:P') {
|
||||
return delta.insert(text.trim());
|
||||
return delta.insert(text.trim(), attributes);
|
||||
}
|
||||
if (!isPre(node)) {
|
||||
if (
|
||||
@ -665,13 +768,14 @@ function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
|
||||
// done removing whitespace and can normalize all to regular space
|
||||
text = text.replaceAll('\u00a0', ' ');
|
||||
}
|
||||
return delta.insert(text);
|
||||
return delta.insert(text, attributes);
|
||||
}
|
||||
|
||||
export {
|
||||
Clipboard as default,
|
||||
matchAttributor,
|
||||
matchBlot,
|
||||
matchBreak,
|
||||
matchNewline,
|
||||
matchText,
|
||||
traverse,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Scope } from 'parchment';
|
||||
import { Scope } from '@signalapp/parchment-cjs';
|
||||
import type Delta from 'quill-delta';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
import Delta, { AttributeMap } from 'quill-delta';
|
||||
import { EmbedBlot, Scope, TextBlot } from 'parchment';
|
||||
import type { Blot, BlockBlot } from 'parchment';
|
||||
import { EmbedBlot, Scope, TextBlot } from '@signalapp/parchment-cjs';
|
||||
import type { Blot, BlockBlot } from '@signalapp/parchment-cjs';
|
||||
import Quill from '../core/quill.js';
|
||||
import logger from '../core/logger.js';
|
||||
import Module from '../core/module.js';
|
||||
@ -206,8 +206,7 @@ class Keyboard extends Module<KeyboardOptions> {
|
||||
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,
|
||||
empty: range.length === 0 && (line?.length() ?? 0) <= 1,
|
||||
format: this.quill.getFormat(range),
|
||||
line,
|
||||
offset,
|
||||
@ -351,6 +350,13 @@ class Keyboard extends Module<KeyboardOptions> {
|
||||
this.quill.updateContents(delta, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
|
||||
this.quill.focus();
|
||||
|
||||
Object.keys(context.format).forEach((name) => {
|
||||
if (lineFormats[name] != null) return;
|
||||
if (Array.isArray(context.format[name])) return;
|
||||
if (name === 'code' || name === 'link') return;
|
||||
this.quill.format(name, context.format[name], Quill.sources.USER);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Delta from 'quill-delta';
|
||||
import { ClassAttributor, Scope } from 'parchment';
|
||||
import type { Blot, ScrollBlot } from 'parchment';
|
||||
import { ClassAttributor, Scope } from '@signalapp/parchment-cjs';
|
||||
import type { Blot, ScrollBlot } from '@signalapp/parchment-cjs';
|
||||
import Inline from '../blots/inline.js';
|
||||
import Quill from '../core/quill.js';
|
||||
import Module from '../core/module.js';
|
||||
@ -302,6 +302,10 @@ class Syntax extends Module<SyntaxOptions> {
|
||||
const container = this.quill.root.ownerDocument.createElement('div');
|
||||
container.classList.add(CodeBlock.className);
|
||||
container.innerHTML = highlight(this.options.hljs, language, text);
|
||||
const attributes = this.quill.getFormat(
|
||||
this.quill.selection.savedRange.index,
|
||||
);
|
||||
|
||||
return traverse(
|
||||
this.quill.scroll,
|
||||
container,
|
||||
@ -329,6 +333,7 @@ class Syntax extends Module<SyntaxOptions> {
|
||||
},
|
||||
],
|
||||
new WeakMap(),
|
||||
attributes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Delta from 'quill-delta';
|
||||
import { EmbedBlot, Scope } from 'parchment';
|
||||
import { EmbedBlot, Scope } from '@signalapp/parchment-cjs';
|
||||
import Quill from '../core/quill.js';
|
||||
import logger from '../core/logger.js';
|
||||
import Module from '../core/module.js';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ParentBlot } from 'parchment';
|
||||
import { ParentBlot } from '@signalapp/parchment-cjs';
|
||||
import Module from '../core/module.js';
|
||||
import Quill from '../core/quill.js';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import { merge } from 'lodash';
|
||||
import type Quill from '../core/quill.js';
|
||||
import Emitter from '../core/emitter.js';
|
||||
import Theme from '../core/theme.js';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import { merge } from 'lodash';
|
||||
import Emitter from '../core/emitter.js';
|
||||
import BaseTheme, { BaseTooltip } from './base.js';
|
||||
import { Range } from '../core/selection.js';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import { merge } from 'lodash';
|
||||
import Emitter from '../core/emitter.js';
|
||||
import BaseTheme, { BaseTooltip } from './base.js';
|
||||
import LinkBlot from '../formats/link.js';
|
||||
|
||||
@ -3,7 +3,7 @@ import Quill, { Delta } from '../../src/quill.js';
|
||||
import type { EmitterSource, Parchment, Range } from '../../src/quill.js';
|
||||
import type { default as Block, BlockEmbed } from '../../src/blots/block.js';
|
||||
import SnowTheme from '../../src/themes/snow.js';
|
||||
import { LeafBlot } from 'parchment';
|
||||
import { LeafBlot } from '@signalapp/parchment-cjs';
|
||||
|
||||
{
|
||||
const Counter = (quill: Quill, options: { unit: string }) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Registry } from 'parchment';
|
||||
import type { Attributor } from 'parchment';
|
||||
import { Registry } from '@signalapp/parchment-cjs';
|
||||
import type { Attributor } from '@signalapp/parchment-cjs';
|
||||
|
||||
import Block from '../../../src/blots/block.js';
|
||||
import Break from '../../../src/blots/break.js';
|
||||
|
||||
@ -3,7 +3,7 @@ import Editor from '../../../src/core/editor.js';
|
||||
import Block from '../../../src/blots/block.js';
|
||||
import { Range } from '../../../src/core/selection.js';
|
||||
import Scroll from '../../../src/blots/scroll.js';
|
||||
import { Registry } from 'parchment';
|
||||
import { Registry } from '@signalapp/parchment-cjs';
|
||||
import Text from '../../../src/blots/text.js';
|
||||
import Emitter from '../../../src/core/emitter.js';
|
||||
import Break from '../../../src/blots/break.js';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import '../../../src/quill.js';
|
||||
import Delta from 'quill-delta';
|
||||
import { LeafBlot, Registry } from 'parchment';
|
||||
import { LeafBlot, Registry } from '@signalapp/parchment-cjs';
|
||||
import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
import Emitter from '../../../src/core/emitter.js';
|
||||
|
||||
@ -3,7 +3,7 @@ import { describe, expect, test, vitest } from 'vitest';
|
||||
import createRegistryWithFormats from '../../../../src/core/utils/createRegistryWithFormats.js';
|
||||
import { globalRegistry } from '../../../../src/core/quill.js';
|
||||
import logger from '../../../../src/core/logger.js';
|
||||
import { Registry } from 'parchment';
|
||||
import { Registry } from '@signalapp/parchment-cjs';
|
||||
import Inline from '../../../../src/blots/inline.js';
|
||||
import Container from '../../../../src/blots/container.js';
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from '../../../src/formats/table.js';
|
||||
import Video from '../../../src/formats/video.js';
|
||||
import { createRegistry } from '../__helpers__/factory.js';
|
||||
import type { RegistryDefinition } from 'parchment';
|
||||
import type { RegistryDefinition } from '@signalapp/parchment-cjs';
|
||||
import {
|
||||
DirectionAttribute,
|
||||
DirectionClass,
|
||||
|
||||
@ -11,11 +11,12 @@
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": false,
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "bundler",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true
|
||||
"noUnusedLocals": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*", "test/**/*"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user