diff --git a/cypress/e2e/9-expression-editor-modal.cy.ts b/cypress/e2e/9-expression-editor-modal.cy.ts new file mode 100644 index 0000000000..f6aabae4df --- /dev/null +++ b/cypress/e2e/9-expression-editor-modal.cy.ts @@ -0,0 +1,65 @@ +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; + +const WorkflowPage = new WorkflowPageClass(); + +describe('Expression editor modal', () => { + before(() => { + cy.task('db:reset'); + cy.skipSetup(); + }); + + beforeEach(() => { + WorkflowPage.actions.visit(); + WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + WorkflowPage.actions.addNodeToCanvas('Hacker News'); + WorkflowPage.actions.openNodeNdv('Hacker News'); + WorkflowPage.actions.openExpressionEditor(); + }); + + it('should resolve primitive resolvables', () => { + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('1 + 2'); + WorkflowPage.getters.expressionModalOutput().contains(/^3$/); + WorkflowPage.getters.expressionModalInput().clear(); + + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('"ab" + "cd"'); + WorkflowPage.getters.expressionModalOutput().contains(/^abcd$/); + + WorkflowPage.getters.expressionModalInput().clear(); + + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('true && false'); + WorkflowPage.getters.expressionModalOutput().contains(/^false$/); + }); + + it('should resolve object resolvables', () => { + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('{{} a: 1'); + WorkflowPage.getters.expressionModalOutput().contains(/^\[Object: \{"a":1\}\]$/); + + WorkflowPage.getters.expressionModalInput().clear(); + + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('{{} a: 1 }.a{del}{del}'); + WorkflowPage.getters.expressionModalOutput().contains(/^1$/); + }); + + it('should resolve array resolvables', () => { + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('[1, 2, 3]'); + WorkflowPage.getters.expressionModalOutput().contains(/^\[Array: \[1,2,3\]\]$/); + + WorkflowPage.getters.expressionModalInput().clear(); + + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('[1, 2, 3][0]'); + WorkflowPage.getters.expressionModalOutput().contains(/^1$/); + }); + + it('should resolve $parameter[]', () => { + WorkflowPage.getters.expressionModalInput().type('{{'); + WorkflowPage.getters.expressionModalInput().type('$parameter["operation"]'); + WorkflowPage.getters.expressionModalOutput().contains(/^get$/); + }); +}); diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 5c2a24f19c..d42998c7dc 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -27,6 +27,8 @@ export class WorkflowPage extends BasePage { firstStepButton: () => cy.getByTestId('canvas-add-button'), isWorkflowSaved: () => this.getters.saveButton().should('match', 'span'), // In Element UI, disabled button turn into spans 🤷‍♂️ isWorkflowActivated: () => this.getters.activatorSwitch().should('have.class', 'is-checked'), + expressionModalInput: () => cy.getByTestId('expression-modal-input'), + expressionModalOutput: () => cy.getByTestId('expression-modal-output'), }; actions = { visit: () => { @@ -47,6 +49,9 @@ export class WorkflowPage extends BasePage { openNodeNdv: (nodeTypeName: string) => { this.getters.canvasNodeByName(nodeTypeName).dblclick(); }, + openExpressionEditor: () => { + cy.get('input[value="expression"]').parent('label').click(); + }, typeIntoParameterInput: (parameterName: string, content: string) => { this.getters.ndvParameterInput(parameterName).type(content); }, diff --git a/docker/images/n8n-custom/Dockerfile b/docker/images/n8n-custom/Dockerfile index c74b93123d..3d4f45247a 100644 --- a/docker/images/n8n-custom/Dockerfile +++ b/docker/images/n8n-custom/Dockerfile @@ -6,7 +6,6 @@ FROM n8nio/base:${NODE_VERSION} as builder COPY turbo.json package.json .npmrc pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json ./ COPY scripts ./scripts COPY packages ./packages -COPY patches ./patches RUN corepack enable && corepack prepare --activate RUN chown -R node:node . diff --git a/package.json b/package.json index 4d122df74c..e6eba80756 100644 --- a/package.json +++ b/package.json @@ -57,10 +57,6 @@ "typescript": "^4.8.4" }, "pnpm": { - "patchedDependencies": { - "quill@2.0.0-dev.4": "patches/quill@2.0.0-dev.4.patch", - "element-ui@2.15.12": "patches/element-ui@2.15.12.patch" - }, "onlyBuiltDependencies": [ "sqlite3", "vue-demi" diff --git a/packages/design-system/src/css/_tokens.scss b/packages/design-system/src/css/_tokens.scss index 28d5cdbb61..8ec5bc0861 100644 --- a/packages/design-system/src/css/_tokens.scss +++ b/packages/design-system/src/css/_tokens.scss @@ -391,6 +391,13 @@ --color-code-tags-variable: #c82829; --color-code-tags-definition: #4271ae; + --color-expression-editor-background: #fff; + --color-valid-resolvable-foreground: #29a568; + --color-valid-resolvable-background: #e1f3d8; + --color-invalid-resolvable-foreground: #f45959; + --color-invalid-resolvable-background: #fef0f0; + --color-expression-syntax-example: #f0f0f0; + // Generated Color Shades from 50 to 950 // Not yet used in design system @each $color in ('neutral', 'success', 'warning', 'danger') { diff --git a/packages/editor-ui/.eslintrc.js b/packages/editor-ui/.eslintrc.js index f3aca7a47e..448f378e82 100644 --- a/packages/editor-ui/.eslintrc.js +++ b/packages/editor-ui/.eslintrc.js @@ -10,6 +10,8 @@ module.exports = { extraFileExtensions: ['.vue'], }, + ignorePatterns: ['*.d.cts'], + rules: { // TODO: Remove these 'id-denylist': 'off', diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 196fb7d43f..52cd17bd7a 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -32,8 +32,8 @@ "@codemirror/lang-javascript": "^6.0.2", "@codemirror/language": "^6.2.1", "@codemirror/lint": "^6.0.0", - "@codemirror/state": "^6.1.1", - "@codemirror/view": "^6.2.1", + "@codemirror/state": "^6.1.4", + "@codemirror/view": "^6.5.1", "@fontsource/open-sans": "^4.5.0", "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-regular-svg-icons": "^6.1.1", @@ -61,8 +61,6 @@ "normalize-wheel": "^1.0.1", "pinia": "^2.0.22", "prismjs": "^1.17.1", - "quill": "2.0.0-dev.4", - "quill-autoformat": "^0.1.1", "timeago.js": "^4.0.2", "uuid": "^8.3.2", "v-click-outside": "^3.1.2", @@ -92,7 +90,6 @@ "@types/lodash.get": "^4.4.6", "@types/lodash.set": "^4.3.6", "@types/luxon": "^2.0.9", - "@types/quill": "^2.0.1", "@types/uuid": "^8.3.2", "@vitejs/plugin-legacy": "^1.8.2", "@vitejs/plugin-vue2": "^1.1.2", diff --git a/packages/editor-ui/src/components/ExpressionEdit.vue b/packages/editor-ui/src/components/ExpressionEdit.vue index 7e5918a8f1..16df12d7e9 100644 --- a/packages/editor-ui/src/components/ExpressionEdit.vue +++ b/packages/editor-ui/src/components/ExpressionEdit.vue @@ -19,19 +19,43 @@
- {{ $locale.baseText('expressionEdit.expression') }} +
+ {{ $locale.baseText('expressionEdit.expression') }} +
+
+ + {{ $locale.baseText('expressionEdit.anythingInside') }} + +
+ + {{ $locale.baseText('expressionEdit.isJavaScript') }} + + + {{ $locale.baseText('expressionEdit.learnMore') }} + +
- +
- {{ $locale.baseText('expressionEdit.result') }} + {{ $locale.baseText('expressionEdit.resultOfItem1') }}
- +
@@ -43,7 +67,8 @@ + + diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionModalOutput.vue b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionModalOutput.vue new file mode 100644 index 0000000000..6d40157df9 --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionModalOutput.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/braceHandler.ts b/packages/editor-ui/src/components/ExpressionEditorModal/braceHandler.ts new file mode 100644 index 0000000000..6cf2bd0cac --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/braceHandler.ts @@ -0,0 +1,57 @@ +import { closeBrackets, insertBracket } from '@codemirror/autocomplete'; +import { codePointAt, codePointSize, Extension } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; + +const braceInputHandler = EditorView.inputHandler.of((view, from, to, insert) => { + if (view.composing || view.state.readOnly) return false; + + const selection = view.state.selection.main; + + if ( + insert.length > 2 || + (insert.length === 2 && codePointSize(codePointAt(insert, 0)) === 1) || + from !== selection.from || + to !== selection.to + ) { + return false; + } + + const transaction = insertBracket(view.state, insert); + + if (!transaction) return false; + + view.dispatch(transaction); + + // customization to rearrange spacing and cursor for expression + + const cursor = view.state.selection.main.head; + + const isSecondBraceForNewExpression = + view.state.sliceDoc(cursor - 2, cursor) === '{{' && + view.state.sliceDoc(cursor, cursor + 1) === '}'; + + if (isSecondBraceForNewExpression) { + view.dispatch({ + changes: { from: cursor, to: cursor + 2, insert: ' }' }, + selection: { anchor: cursor + 1 }, + }); + + return true; + } + + const isFirstBraceForNewExpression = + view.state.sliceDoc(cursor - 1, cursor) === '{' && + view.state.sliceDoc(cursor, cursor + 1) === '}'; + + if (isFirstBraceForNewExpression) { + view.dispatch({ changes: { from: cursor, insert: ' ' } }); + + return true; + } + + return true; +}); + +const [_, bracketState] = closeBrackets() as readonly Extension[]; + +export const braceHandler = () => [braceInputHandler, bracketState]; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/colorDecorations.ts b/packages/editor-ui/src/components/ExpressionEditorModal/colorDecorations.ts new file mode 100644 index 0000000000..7e3f61e4cd --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/colorDecorations.ts @@ -0,0 +1,94 @@ +import { EditorView, Decoration, DecorationSet } from '@codemirror/view'; +import { StateField, StateEffect } from '@codemirror/state'; + +import { DYNAMICALLY_STYLED_RESOLVABLES_THEME, SYNTAX_HIGHLIGHTING_CLASSES } from './theme'; + +import type { ColoringStateEffect, Plaintext, Resolvable, Resolved } from './types'; + +const stateEffects = { + addColor: StateEffect.define({ + map: ({ from, to, kind, error }, change) => ({ + from: change.mapPos(from), + to: change.mapPos(to), + kind, + error, + }), + }), + removeColor: StateEffect.define<{ from: number; to: number }>({ + map: ({ from, to }, change) => ({ + from: change.mapPos(from), + to: change.mapPos(to), + }), + }), +}; + +const marks = { + valid: Decoration.mark({ class: SYNTAX_HIGHLIGHTING_CLASSES.validResolvable }), + invalid: Decoration.mark({ class: SYNTAX_HIGHLIGHTING_CLASSES.invalidResolvable }), +}; + +const coloringField = StateField.define({ + provide: (stateField) => EditorView.decorations.from(stateField), + create() { + return Decoration.none; + }, + update(colorings, transaction) { + colorings = colorings.map(transaction.changes); + + for (const txEffect of transaction.effects) { + if (txEffect.is(stateEffects.removeColor)) { + colorings = colorings.update({ + filter: (from, to) => txEffect.value.from !== from && txEffect.value.to !== to, + }); + } + + if (txEffect.is(stateEffects.addColor)) { + colorings = colorings.update({ + filter: (from, to) => txEffect.value.from !== from && txEffect.value.to !== to, + }); + + const decoration = txEffect.value.error ? marks.invalid : marks.valid; + + if (txEffect.value.from === 0 && txEffect.value.to === 0) continue; + + colorings = colorings.update({ + add: [decoration.range(txEffect.value.from, txEffect.value.to)], + }); + } + } + + return colorings; + }, +}); + +export function addColor(view: EditorView, segments: Array) { + const effects: Array> = segments.map(({ from, to, kind, error }) => + stateEffects.addColor.of({ from, to, kind, error }), + ); + + if (effects.length === 0) return; + + if (!view.state.field(coloringField, false)) { + effects.push( + StateEffect.appendConfig.of([coloringField, DYNAMICALLY_STYLED_RESOLVABLES_THEME]), + ); + } + + view.dispatch({ effects }); +} + +export function removeColor(view: EditorView, segments: Plaintext[]) { + const effects: Array> = segments.map(({ from, to }) => + stateEffects.removeColor.of({ from, to }), + ); + + if (effects.length === 0) return; + + if (!view.state.field(coloringField, false)) { + effects.push( + StateEffect.appendConfig.of([coloringField, DYNAMICALLY_STYLED_RESOLVABLES_THEME]), + ); + } + + view.dispatch({ effects }); +} diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.cjs b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.cjs new file mode 100644 index 0000000000..d14dbefc5f --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.cjs @@ -0,0 +1,57 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var autocomplete = require('@codemirror/autocomplete'); +var lr = require('@lezer/lr'); +var language = require('@codemirror/language'); +var highlight = require('@lezer/highlight'); + +// This file was generated by lezer-generator. You probably shouldn't edit it. +const parser = lr.LRParser.deserialize({ + version: 14, + states: "nQQOPOOOOOO'#Cc'#CcOOOO'#Ca'#CaQQOPOOOOOO-E6_-E6_", + stateData: "]~OQPORPOSPO~O", + goto: "cWPPPPPXP_QRORSRTQOR", + nodeNames: "⚠ Program Plaintext Resolvable BrokenResolvable", + maxTerm: 7, + skippedNodes: [0], + repeatNodeCount: 1, + tokenData: "4f~RRO#o[#o#p{#p~[~aRQ~O#o[#o#pj#p~[~mRO#o[#p~[~~v~{OQ~~!OSO#o[#o#p![#p~[~~v~!a!YS~OX&PX^![^p&Ppq![qr![rs![st![tu![uv![vw![wx![xy![yz![z{![{|![|}![}!O![!O!P![!P!Q![!Q![![![!]![!]!^![!^!_![!_!`![!`!a![!a!b![!b!c![!c!}![!}#O![#O#P&d#P#Q![#Q#R![#R#S![#S#T![#T#o![#o#p![#p#q![#q#r4W#r#s![#s#y&P#y#z![#z$f&P$f$g![$g#BY&P#BY#BZ![#BZ$IS&P$IS$I_![$I_$I|&P$I|$JO![$JO$JT&P$JT$JU![$JU$KV&P$KV$KW![$KW&FU&P&FU&FV![&FV~&P~&URS~O#q&P#q#r&_#r~&P~&dOS~~&i!YS~OX&PX^![^p&Ppq![qr![rs![st![tu![uv![vw![wx![xy![yz![z{![{|![|}![}!O![!O!P![!P!Q![!Q![![![!]![!]!^![!^!_![!_!`![!`!a![!a!b![!b!c![!c!}![!}#O![#O#P&d#P#Q![#Q#R![#R#S![#S#T![#T#o![#o#p![#p#q![#q#r*X#r#s![#s#y&P#y#z![#z$f&P$f$g![$g#BY&P#BY#BZ![#BZ$IS&P$IS$I_![$I_$I|&P$I|$JO![$JO$JT&P$JT$JU![$JU$KV&P$KV$KW![$KW&FU&P&FU&FV![&FV~&P~*^RS~O#q*g#q#r0s#r~*g~*j}X^*gpq*gqr*grs*gst*gtu*guv*gvw*gwx*gxy*gyz*gz{*g{|*g|}*g}!O*g!O!P*g!P!Q*g!Q![*g![!]*g!]!^*g!^!_*g!_!`*g!`!a*g!a!b*g!b!c*g!c!}*g!}#O*g#O#P-g#P#Q*g#Q#R*g#R#S*g#S#T*g#T#o*g#o#p*g#p#q*g#q#r3u#r#s*g#y#z*g$f$g*g#BY#BZ*g$IS$I_*g$I|$JO*g$JT$JU*g$KV$KW*g&FU&FV*g~-j}X^*gpq*gqr*grs*gst*gtu*guv*gvw*gwx*gxy*gyz*gz{*g{|*g|}*g}!O*g!O!P*g!P!Q*g!Q![*g![!]*g!]!^*g!^!_*g!_!`*g!`!a*g!a!b*g!b!c*g!c!}*g!}#O*g#O#P-g#P#Q*g#Q#R*g#R#S*g#S#T*g#T#o*g#o#p*g#p#q*g#q#r0g#r#s*g#y#z*g$f$g*g#BY#BZ*g$IS$I_*g$I|$JO*g$JT$JU*g$KV$KW*g&FU&FV*g~0jRO#q*g#q#r0s#r~*g~0x}R~X^*gpq*gqr*grs*gst*gtu*guv*gvw*gwx*gxy*gyz*gz{*g{|*g|}*g}!O*g!O!P*g!P!Q*g!Q![*g![!]*g!]!^*g!^!_*g!_!`*g!`!a*g!a!b*g!b!c*g!c!}*g!}#O*g#O#P-g#P#Q*g#Q#R*g#R#S*g#S#T*g#T#o*g#o#p*g#p#q*g#q#r3u#r#s*g#y#z*g$f$g*g#BY#BZ*g$IS$I_*g$I|$JO*g$JT$JU*g$KV$KW*g&FU&FV*g~3xRO#q*g#q#r4R#r~*g~4WOR~~4]RS~O#q*g#q#r4R#r~*g", + tokenizers: [0], + topRules: {"Program":[0,1]}, + tokenPrec: 0 +}); + +const parserWithMetaData = parser.configure({ + props: [ + language.foldNodeProp.add({ + Application: language.foldInside, + }), + highlight.styleTags({ + OpenMarker: highlight.tags.brace, + CloseMarker: highlight.tags.brace, + Plaintext: highlight.tags.content, + Resolvable: highlight.tags.string, + BrokenResolvable: highlight.tags.className, + }), + ], +}); +const n8nExpressionLanguage = language.LRLanguage.define({ + parser: parserWithMetaData, + languageData: { + commentTokens: { line: ";" }, + }, +}); +const completions = n8nExpressionLanguage.data.of({ + autocomplete: autocomplete.completeFromList([ + { label: "abcdefg", type: "keyword" }, + ]), +}); +function n8nExpression() { + return new language.LanguageSupport(n8nExpressionLanguage, [completions]); +} + +exports.n8nExpression = n8nExpression; +exports.n8nExpressionLanguage = n8nExpressionLanguage; +exports.parserWithMetaData = parserWithMetaData; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.d.cts b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.d.cts new file mode 100644 index 0000000000..8406d5dd59 --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.d.cts @@ -0,0 +1,5 @@ +import { LRLanguage, LanguageSupport } from "@codemirror/language"; +declare const parserWithMetaData: import("@lezer/lr").LRParser; +declare const n8nExpressionLanguage: LRLanguage; +declare function n8nExpression(): LanguageSupport; +export { parserWithMetaData, n8nExpressionLanguage, n8nExpression }; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.d.ts b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.d.ts new file mode 100644 index 0000000000..8406d5dd59 --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.d.ts @@ -0,0 +1,5 @@ +import { LRLanguage, LanguageSupport } from "@codemirror/language"; +declare const parserWithMetaData: import("@lezer/lr").LRParser; +declare const n8nExpressionLanguage: LRLanguage; +declare function n8nExpression(): LanguageSupport; +export { parserWithMetaData, n8nExpressionLanguage, n8nExpression }; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.js b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.js new file mode 100644 index 0000000000..d2b92e7c5a --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguagePack/index.js @@ -0,0 +1,51 @@ +import { completeFromList } from '@codemirror/autocomplete'; +import { LRParser } from '@lezer/lr'; +import { foldNodeProp, foldInside, LRLanguage, LanguageSupport } from '@codemirror/language'; +import { styleTags, tags } from '@lezer/highlight'; + +// This file was generated by lezer-generator. You probably shouldn't edit it. +const parser = LRParser.deserialize({ + version: 14, + states: "nQQOPOOOOOO'#Cc'#CcOOOO'#Ca'#CaQQOPOOOOOO-E6_-E6_", + stateData: "]~OQPORPOSPO~O", + goto: "cWPPPPPXP_QRORSRTQOR", + nodeNames: "⚠ Program Plaintext Resolvable BrokenResolvable", + maxTerm: 7, + skippedNodes: [0], + repeatNodeCount: 1, + tokenData: "4f~RRO#o[#o#p{#p~[~aRQ~O#o[#o#pj#p~[~mRO#o[#p~[~~v~{OQ~~!OSO#o[#o#p![#p~[~~v~!a!YS~OX&PX^![^p&Ppq![qr![rs![st![tu![uv![vw![wx![xy![yz![z{![{|![|}![}!O![!O!P![!P!Q![!Q![![![!]![!]!^![!^!_![!_!`![!`!a![!a!b![!b!c![!c!}![!}#O![#O#P&d#P#Q![#Q#R![#R#S![#S#T![#T#o![#o#p![#p#q![#q#r4W#r#s![#s#y&P#y#z![#z$f&P$f$g![$g#BY&P#BY#BZ![#BZ$IS&P$IS$I_![$I_$I|&P$I|$JO![$JO$JT&P$JT$JU![$JU$KV&P$KV$KW![$KW&FU&P&FU&FV![&FV~&P~&URS~O#q&P#q#r&_#r~&P~&dOS~~&i!YS~OX&PX^![^p&Ppq![qr![rs![st![tu![uv![vw![wx![xy![yz![z{![{|![|}![}!O![!O!P![!P!Q![!Q![![![!]![!]!^![!^!_![!_!`![!`!a![!a!b![!b!c![!c!}![!}#O![#O#P&d#P#Q![#Q#R![#R#S![#S#T![#T#o![#o#p![#p#q![#q#r*X#r#s![#s#y&P#y#z![#z$f&P$f$g![$g#BY&P#BY#BZ![#BZ$IS&P$IS$I_![$I_$I|&P$I|$JO![$JO$JT&P$JT$JU![$JU$KV&P$KV$KW![$KW&FU&P&FU&FV![&FV~&P~*^RS~O#q*g#q#r0s#r~*g~*j}X^*gpq*gqr*grs*gst*gtu*guv*gvw*gwx*gxy*gyz*gz{*g{|*g|}*g}!O*g!O!P*g!P!Q*g!Q![*g![!]*g!]!^*g!^!_*g!_!`*g!`!a*g!a!b*g!b!c*g!c!}*g!}#O*g#O#P-g#P#Q*g#Q#R*g#R#S*g#S#T*g#T#o*g#o#p*g#p#q*g#q#r3u#r#s*g#y#z*g$f$g*g#BY#BZ*g$IS$I_*g$I|$JO*g$JT$JU*g$KV$KW*g&FU&FV*g~-j}X^*gpq*gqr*grs*gst*gtu*guv*gvw*gwx*gxy*gyz*gz{*g{|*g|}*g}!O*g!O!P*g!P!Q*g!Q![*g![!]*g!]!^*g!^!_*g!_!`*g!`!a*g!a!b*g!b!c*g!c!}*g!}#O*g#O#P-g#P#Q*g#Q#R*g#R#S*g#S#T*g#T#o*g#o#p*g#p#q*g#q#r0g#r#s*g#y#z*g$f$g*g#BY#BZ*g$IS$I_*g$I|$JO*g$JT$JU*g$KV$KW*g&FU&FV*g~0jRO#q*g#q#r0s#r~*g~0x}R~X^*gpq*gqr*grs*gst*gtu*guv*gvw*gwx*gxy*gyz*gz{*g{|*g|}*g}!O*g!O!P*g!P!Q*g!Q![*g![!]*g!]!^*g!^!_*g!_!`*g!`!a*g!a!b*g!b!c*g!c!}*g!}#O*g#O#P-g#P#Q*g#Q#R*g#R#S*g#S#T*g#T#o*g#o#p*g#p#q*g#q#r3u#r#s*g#y#z*g$f$g*g#BY#BZ*g$IS$I_*g$I|$JO*g$JT$JU*g$KV$KW*g&FU&FV*g~3xRO#q*g#q#r4R#r~*g~4WOR~~4]RS~O#q*g#q#r4R#r~*g", + tokenizers: [0], + topRules: {"Program":[0,1]}, + tokenPrec: 0 +}); + +const parserWithMetaData = parser.configure({ + props: [ + foldNodeProp.add({ + Application: foldInside, + }), + styleTags({ + OpenMarker: tags.brace, + CloseMarker: tags.brace, + Plaintext: tags.content, + Resolvable: tags.string, + BrokenResolvable: tags.className, + }), + ], +}); +const n8nExpressionLanguage = LRLanguage.define({ + parser: parserWithMetaData, + languageData: { + commentTokens: { line: ";" }, + }, +}); +const completions = n8nExpressionLanguage.data.of({ + autocomplete: completeFromList([ + { label: "abcdefg", type: "keyword" }, + ]), +}); +function n8nExpression() { + return new LanguageSupport(n8nExpressionLanguage, [completions]); +} + +export { n8nExpression, n8nExpressionLanguage, parserWithMetaData }; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguageSupport.ts b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguageSupport.ts new file mode 100644 index 0000000000..8a3178aaae --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/n8nLanguageSupport.ts @@ -0,0 +1,20 @@ +import { LanguageSupport, LRLanguage } from '@codemirror/language'; +import { parseMixed } from '@lezer/common'; +import { parser as jsParser } from '@lezer/javascript'; +import { parserWithMetaData as n8nParser } from './n8nLanguagePack'; + +const parserWithNestedJsParser = n8nParser.configure({ + wrap: parseMixed((node) => { + if (node.type.isTop) return null; + + return node.name === 'Resolvable' + ? { parser: jsParser, overlay: (node) => node.type.name === 'Resolvable' } + : null; + }), +}); + +const n8nLanguage = LRLanguage.define({ parser: parserWithNestedJsParser }); + +export function n8nLanguageSupport() { + return new LanguageSupport(n8nLanguage); +} diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/resolvable.completions.ts b/packages/editor-ui/src/components/ExpressionEditorModal/resolvable.completions.ts new file mode 100644 index 0000000000..f7dfec6788 --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/resolvable.completions.ts @@ -0,0 +1,33 @@ +import { CompletionContext, CompletionResult } from '@codemirror/autocomplete'; +import { syntaxTree } from '@codemirror/language'; + +/** + * Completions available inside the resolvable segment `{{ ... }}` of an n8n expression. + * + * Currently unused. + */ +export function resolvableCompletions(context: CompletionContext): CompletionResult | null { + const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1); + + if (nodeBefore.name !== 'Resolvable') return null; + + const pattern = /(?('|")\w*('|"))\./; + + const preCursor = context.matchBefore(pattern); + + if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null; + + const match = preCursor.text.match(pattern); + + if (!match?.groups?.quotedString) return null; + + const { quotedString } = match.groups; + + return { + from: preCursor.from, + options: [ + { label: `${quotedString}.replace()`, info: 'Replace part of a string with another' }, + { label: `${quotedString}.slice()`, info: 'Copy part of a string' }, + ], + }; +} diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/theme.ts b/packages/editor-ui/src/components/ExpressionEditorModal/theme.ts new file mode 100644 index 0000000000..d0f997fb0f --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/theme.ts @@ -0,0 +1,62 @@ +import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; +import { EditorView } from '@codemirror/view'; +import { tags } from '@lezer/highlight'; + +export const SYNTAX_HIGHLIGHTING_CLASSES = { + validResolvable: 'cm-valid-resolvable', + invalidResolvable: 'cm-invalid-resolvable', + brokenResolvable: 'cm-broken-resolvable', + plaintext: 'cm-plaintext', +}; + +export const EXPRESSION_EDITOR_THEME = [ + EditorView.theme({ + '&': { + borderWidth: 'var(--border-width-base)', + borderStyle: 'var(--input-border-style, var(--border-style-base))', + borderColor: 'var(--input-border-color, var(--border-color-base))', + borderRadius: 'var(--input-border-radius, var(--border-radius-base))', + backgroundColor: 'var(--color-expression-editor-background)', + }, + '&.cm-focused': { + borderColor: 'var(--color-secondary)', + outline: 'unset !important', + }, + '.cm-content': { + fontFamily: "Menlo, Consolas, 'DejaVu Sans Mono', monospace !important", + height: '220px', + padding: 'var(--spacing-xs)', + color: 'var(--input-font-color, var(--color-text-dark))', + }, + '.cm-line': { + padding: '0', + }, + }), + syntaxHighlighting( + HighlightStyle.define([ + { + tag: tags.content, + class: SYNTAX_HIGHLIGHTING_CLASSES.plaintext, + }, + { + tag: tags.className, + class: SYNTAX_HIGHLIGHTING_CLASSES.brokenResolvable, + }, + /** + * Resolvables are dynamically styled with + * `cm-valid-resolvable` and `cm-invalid-resolvable` + */ + ]), + ), +]; + +export const DYNAMICALLY_STYLED_RESOLVABLES_THEME = EditorView.theme({ + ['.' + SYNTAX_HIGHLIGHTING_CLASSES.validResolvable]: { + color: 'var(--color-valid-resolvable-foreground)', + backgroundColor: 'var(--color-valid-resolvable-background)', + }, + ['.' + SYNTAX_HIGHLIGHTING_CLASSES.invalidResolvable]: { + color: 'var(--color-invalid-resolvable-foreground)', + backgroundColor: 'var(--color-invalid-resolvable-background)', + }, +}); diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/types.ts b/packages/editor-ui/src/components/ExpressionEditorModal/types.ts new file mode 100644 index 0000000000..6abbb0857b --- /dev/null +++ b/packages/editor-ui/src/components/ExpressionEditorModal/types.ts @@ -0,0 +1,24 @@ +type Range = { from: number; to: number }; + +export type RawSegment = { text: string; type: string } & Range; + +export type Segment = Plaintext | Resolvable; + +export type Plaintext = { kind: 'plaintext'; plaintext: string } & Range; + +export type Resolvable = { + kind: 'resolvable'; + resolvable: string; + resolved: unknown; + error: boolean; + fullError: Error | null; +} & Range; + +export type Resolved = Resolvable; + +export namespace ColoringStateEffect { + export type Value = { + kind: 'plaintext' | 'resolvable'; + error: boolean; + } & Range; +} diff --git a/packages/editor-ui/src/components/ExpressionInput.vue b/packages/editor-ui/src/components/ExpressionInput.vue deleted file mode 100644 index 2b1d6dce7b..0000000000 --- a/packages/editor-ui/src/components/ExpressionInput.vue +++ /dev/null @@ -1,373 +0,0 @@ - - - - - diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index dd840d1dc0..6ea6df7398 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -6,6 +6,7 @@ :parameter="parameter" :path="path" :eventSource="eventSource || 'ndv'" + :isReadOnly="isReadOnly" @closeDialog="closeExpressionEditDialog" @valueChanged="expressionUpdated" > diff --git a/packages/editor-ui/src/components/ParameterInputWrapper.vue b/packages/editor-ui/src/components/ParameterInputWrapper.vue index 326a90970d..2bc3da2abd 100644 --- a/packages/editor-ui/src/components/ParameterInputWrapper.vue +++ b/packages/editor-ui/src/components/ParameterInputWrapper.vue @@ -157,23 +157,14 @@ export default mixins( computedValue = this.$locale.baseText('parameterInput.emptyString'); } } catch (error) { - computedValue = `[${this.$locale.baseText('parameterInput.error')}}: ${error.message}]`; + computedValue = `[${this.$locale.baseText('parameterInput.error')}: ${error.message}]`; } return typeof computedValue === 'string' ? computedValue : JSON.stringify(computedValue); }, expressionOutput(): string | null { if (this.isValueExpression && this.expressionValueComputed) { - const inputData = this.ndvStore.ndvInputData; - if (!inputData || (inputData && inputData.length <= 1)) { - return this.expressionValueComputed; - } - - return this.$locale.baseText(`parameterInput.expressionResult`, { - interpolate: { - result: this.expressionValueComputed, - }, - }); + return this.expressionValueComputed; } return null; diff --git a/packages/editor-ui/src/components/VariableSelector.vue b/packages/editor-ui/src/components/VariableSelector.vue index 473b8046d1..93a9496e49 100644 --- a/packages/editor-ui/src/components/VariableSelector.vue +++ b/packages/editor-ui/src/components/VariableSelector.vue @@ -537,13 +537,15 @@ export default mixins( if (parentNode.length) { // If the node has an input node add the input data + const activeInputParentNode = parentNode.find(node => node === this.ndvStore.ndvInputNodeName)!; + // Check from which output to read the data. // Depends on how the nodes are connected. // (example "IF" node. If node is connected to "true" or to "false" output) - const nodeConnection = this.workflow.getNodeConnectionIndexes(activeNode.name, parentNode[0], 'main'); + const nodeConnection = this.workflow.getNodeConnectionIndexes(activeNode.name, activeInputParentNode, 'main'); const outputIndex = nodeConnection === undefined ? 0: nodeConnection.sourceIndex; - tempOutputData = this.getNodeRunDataOutput(parentNode[0], runData, filterText, itemIndex, 0, 'main', outputIndex, true) as IVariableSelectorOption[]; + tempOutputData = this.getNodeRunDataOutput(activeInputParentNode, runData, filterText, itemIndex, 0, 'main', outputIndex, true) as IVariableSelectorOption[]; const pinDataOptions: IVariableSelectorOption[] = [ { diff --git a/packages/editor-ui/src/components/VariableSelectorItem.vue b/packages/editor-ui/src/components/VariableSelectorItem.vue index c2b33925b1..487b35b72c 100644 --- a/packages/editor-ui/src/components/VariableSelectorItem.vue +++ b/packages/editor-ui/src/components/VariableSelectorItem.vue @@ -53,6 +53,20 @@ export default mixins(externalHooks).extend({ 'extendAll', 'item', ], + mounted() { + if (this.extended) return; + + const shouldAutoExtend = [ + this.$locale.baseText('variableSelectorItem.currentNode'), + this.$locale.baseText('variableSelectorItem.inputData'), + this.$locale.baseText('variableSelectorItem.binary'), + this.$locale.baseText('variableSelectorItem.json'), + ].includes(this.item.name) && this.item.key === undefined; + + if (shouldAutoExtend) { + this.extended = true; + } + }, computed: { itemAddOperations () { const returnOptions = [ diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts index 490133391c..9f98d9d196 100644 --- a/packages/editor-ui/src/constants.ts +++ b/packages/editor-ui/src/constants.ts @@ -69,7 +69,7 @@ export const COMMUNITY_NODES_NPM_INSTALLATION_URL = 'https://docs.npmjs.com/down export const COMMUNITY_NODES_RISKS_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/risks/`; export const COMMUNITY_NODES_BLOCKLIST_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/blocklist/`; export const CUSTOM_NODES_DOCS_URL = `https://docs.n8n.io/integrations/creating-nodes/code/create-n8n-nodes-module/`; - +export const EXPRESSIONS_DOCS_URL = 'https://docs.n8n.io/code-examples/expressions/'; // node types export const BAMBOO_HR_NODE_TYPE = 'n8n-nodes-base.bambooHr'; diff --git a/packages/editor-ui/src/mixins/workflowHelpers.ts b/packages/editor-ui/src/mixins/workflowHelpers.ts index 2c9f99b8d5..eb424bf84e 100644 --- a/packages/editor-ui/src/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/mixins/workflowHelpers.ts @@ -845,6 +845,7 @@ export const workflowHelpers = mixins( this.uiStore.stateIsDirty = false; this.$externalHooks().run('workflow.afterUpdate', { workflowData }); + this.getCurrentWorkflow(true); // refresh cache return true; } catch (e) { this.uiStore.removeActiveAction('workflowSaving'); diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index df6834be17..29fc447e38 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -479,10 +479,16 @@ "executionSidebar.searchPlaceholder": "Search executions...", "executionView.onPaste.title": "Cannot paste here", "executionView.onPaste.message": "This view is read-only. Switch to Workflow tab to be able to edit the current workflow", + "expressionEdit.anythingInside": "Anything inside", + "expressionEdit.isJavaScript": "is JavaScript.", + "expressionEdit.learnMore": "Learn more", "expressionEdit.editExpression": "Edit Expression", "expressionEdit.expression": "Expression", - "expressionEdit.result": "Result", + "expressionEdit.resultOfItem1": "Result of item 1", "expressionEdit.variableSelector": "Variable Selector", + "expressionModalInput.empty": "[empty]", + "expressionModalInput.undefined": "[undefined]", + "expressionModalInput.null": "null", "fakeDoor.credentialEdit.sharing.name": "Sharing", "fakeDoor.credentialEdit.sharing.actionBox.title": "Sharing is only available on n8n Cloud right now", "fakeDoor.credentialEdit.sharing.actionBox.description": "We’re working on bringing it to this edition of n8n, as a paid feature. If you’d like to be the first to hear when it’s ready, join the list.", @@ -1227,7 +1233,11 @@ "variableSelector.outputData": "Output Data", "variableSelector.parameters": "Parameters", "variableSelector.variableFilter": "Variable filter...", + "variableSelectorItem.binary": "Binary", + "variableSelectorItem.currentNode": "Current Node", "variableSelectorItem.empty": "--- EMPTY ---", + "variableSelectorItem.inputData": "Input Data", + "variableSelectorItem.json": "JSON", "variableSelectorItem.selectItem": "Select Item", "versionCard.breakingChanges": "Breaking changes", "versionCard.released": "Released", diff --git a/packages/editor-ui/tslint.json b/packages/editor-ui/tslint.json index fd305c3c48..9f199670bc 100644 --- a/packages/editor-ui/tslint.json +++ b/packages/editor-ui/tslint.json @@ -63,10 +63,6 @@ // "no-default-export": true, "no-duplicate-variable": true, "no-inferrable-types": true, - "no-namespace": [ - true, - "allow-declarations" - ], "no-reference": true, "no-string-throw": true, "no-unused-expression": true, diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 81f5dac4bf..d0bacdc7cb 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -277,7 +277,16 @@ export class Expression { throw error; } } + + if ( + error instanceof Error && + typeof error.message === 'string' && + error.name === 'SyntaxError' + ) { + throw new Error(error.message); + } } + return null; } diff --git a/packages/workflow/src/WorkflowDataProxy.ts b/packages/workflow/src/WorkflowDataProxy.ts index 375fccd2c5..547b5622f8 100644 --- a/packages/workflow/src/WorkflowDataProxy.ts +++ b/packages/workflow/src/WorkflowDataProxy.ts @@ -289,15 +289,12 @@ export class WorkflowDataProxy { if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) { if (that.workflow.getNode(nodeName)) { - throw new ExpressionError( - `The node "${nodeName}" hasn't been executed yet, so you can't reference its output data`, - { - runIndex: that.runIndex, - itemIndex: that.itemIndex, - }, - ); + throw new ExpressionError(`no data, execute "${nodeName}" node first`, { + runIndex: that.runIndex, + itemIndex: that.itemIndex, + }); } - throw new ExpressionError(`No node called "${nodeName}" in this workflow`, { + throw new ExpressionError(`"${nodeName}" node doesn't exist`, { runIndex: that.runIndex, itemIndex: that.itemIndex, }); @@ -335,13 +332,10 @@ export class WorkflowDataProxy { ); if (nodeConnection === undefined) { - throw new ExpressionError( - `The node "${that.activeNodeName}" is not connected with node "${nodeName}" so no data can get returned from it.`, - { - runIndex: that.runIndex, - itemIndex: that.itemIndex, - }, - ); + throw new ExpressionError(`connect ${that.activeNodeName} to ${nodeName}`, { + runIndex: that.runIndex, + itemIndex: that.itemIndex, + }); } outputIndex = nodeConnection.sourceIndex; } @@ -383,16 +377,16 @@ export class WorkflowDataProxy { const that = this; const node = this.workflow.nodes[nodeName]; - if (!node) { - return undefined; - } - return new Proxy( { binary: undefined, data: undefined, json: undefined }, { get(target, name, receiver) { name = name.toString(); + if (!node) { + throw new ExpressionError(`"${nodeName}" node doesn't exist`); + } + if (['binary', 'data', 'json'].includes(name)) { const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined); if (executionData.length <= that.itemIndex) { @@ -463,8 +457,11 @@ export class WorkflowDataProxy { {}, { get(target, name, receiver) { - if (process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true') { - throw new ExpressionError('Environment variable access got disabled', { + if ( + typeof process === 'undefined' || // env vars are inaccessible to frontend + process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' + ) { + throw new ExpressionError('access to env vars denied', { causeDetailed: 'If you need access please contact the administrator to remove the environment variable ‘N8N_BLOCK_ENV_ACCESS_IN_NODE‘', runIndex: that.runIndex, @@ -544,7 +541,7 @@ export class WorkflowDataProxy { const value = that.workflow[name as keyof typeof target]; if (value === undefined && name === 'id') { - throw new ExpressionError('Workflow is not saved', { + throw new ExpressionError('save workflow to view', { description: `Please save the workflow first to use $workflow`, runIndex: that.runIndex, itemIndex: that.itemIndex, @@ -906,7 +903,7 @@ export class WorkflowDataProxy { const referencedNode = that.workflow.getNode(nodeName); if (referencedNode === null) { - throw createExpressionError(`No node called ‘${nodeName}‘`); + throw createExpressionError(`"${nodeName}" node doesn't exist`); } return new Proxy( diff --git a/patches/quill@2.0.0-dev.4.patch b/patches/quill@2.0.0-dev.4.patch deleted file mode 100644 index 9446cf0d08..0000000000 --- a/patches/quill@2.0.0-dev.4.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/core/selection.js b/core/selection.js -index 9dfc94f127529675d443fa9b2cfff52962b3c7b0..7719a4163992fcd7e043a122439ebece3036860d 100644 ---- a/core/selection.js -+++ b/core/selection.js -@@ -151,7 +151,7 @@ class Selection { - } - let side = 'left'; - let rect; -- if (node instanceof Text) { -+ if (node instanceof Text || node.nodeName === '#text') { - if (offset < node.data.length) { - range.setStart(node, offset); - range.setEnd(node, offset + 1); -@@ -238,13 +238,13 @@ class Selection { - }; - [range.start, range.end].forEach(position => { - let { node, offset } = position; -- while (!(node instanceof Text) && node.childNodes.length > 0) { -+ while (!(node instanceof Text || node.nodeName === '#text') && node.childNodes.length > 0) { - if (node.childNodes.length > offset) { - node = node.childNodes[offset]; - offset = 0; - } else if (node.childNodes.length === offset) { - node = node.lastChild; -- if (node instanceof Text) { -+ if (node instanceof Text || node.nodeName === '#text') { - offset = node.data.length; - } else if (node.childNodes.length > 0) { - // Container case -diff --git a/dist/quill.core.js b/dist/quill.core.js -index 7c7891e6e2a748ebea0daf7f5210fd3f10d759bc..977fa23f5e2ee5b0edfb05e95363c9fb57a5b0c6 100644 ---- a/dist/quill.core.js -+++ b/dist/quill.core.js -@@ -290,7 +290,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * - /***/ (function(module, __webpack_exports__, __webpack_require__) { - - "use strict"; --eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Range\", function() { return Range; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Selection; });\n/* harmony import */ var parchment__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! parchment */ \"./node_modules/parchment/src/parchment.ts\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! clone */ \"./node_modules/clone/clone.js\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(clone__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! deep-equal */ \"./node_modules/deep-equal/index.js\");\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(deep_equal__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _emitter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./emitter */ \"./core/emitter.js\");\n/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./logger */ \"./core/logger.js\");\n\n\n\n\n\nconst debug = Object(_logger__WEBPACK_IMPORTED_MODULE_4__[\"default\"])('quill:selection');\n\nclass Range {\n constructor(index, length = 0) {\n this.index = index;\n this.length = length;\n }\n\n}\n\nclass Selection {\n constructor(scroll, emitter) {\n this.emitter = emitter;\n this.scroll = scroll;\n this.composing = false;\n this.mouseDown = false;\n this.root = this.scroll.domNode;\n this.cursor = this.scroll.create('cursor', this); // savedRange is last non-null range\n\n this.savedRange = new Range(0, 0);\n this.lastRange = this.savedRange;\n this.lastNative = null;\n this.handleComposition();\n this.handleDragging();\n this.emitter.listenDOM('selectionchange', document, () => {\n if (!this.mouseDown && !this.composing) {\n setTimeout(this.update.bind(this, _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER), 1);\n }\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_BEFORE_UPDATE, () => {\n if (!this.hasFocus()) return;\n const native = this.getNativeRange();\n if (native == null) return;\n if (native.start.node === this.cursor.textNode) return; // cursor.restore() will handle\n\n this.emitter.once(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_UPDATE, () => {\n try {\n if (this.root.contains(native.start.node) && this.root.contains(native.end.node)) {\n this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);\n }\n\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n } catch (ignored) {// ignore\n }\n });\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_OPTIMIZE, (mutations, context) => {\n if (context.range) {\n const {\n startNode,\n startOffset,\n endNode,\n endOffset\n } = context.range;\n this.setNativeRange(startNode, startOffset, endNode, endOffset);\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n });\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n\n handleComposition() {\n this.root.addEventListener('compositionstart', () => {\n this.composing = true;\n this.scroll.batchStart();\n });\n this.root.addEventListener('compositionend', () => {\n this.scroll.batchEnd();\n this.composing = false;\n\n if (this.cursor.parent) {\n const range = this.cursor.restore();\n if (!range) return;\n setTimeout(() => {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }, 1);\n }\n });\n }\n\n handleDragging() {\n this.emitter.listenDOM('mousedown', document.body, () => {\n this.mouseDown = true;\n });\n this.emitter.listenDOM('mouseup', document.body, () => {\n this.mouseDown = false;\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER);\n });\n }\n\n focus() {\n if (this.hasFocus()) return;\n this.root.focus();\n this.setRange(this.savedRange);\n }\n\n format(format, value) {\n this.scroll.update();\n const nativeRange = this.getNativeRange();\n if (nativeRange == null || !nativeRange.native.collapsed || this.scroll.query(format, parchment__WEBPACK_IMPORTED_MODULE_0__[\"Scope\"].BLOCK)) return;\n\n if (nativeRange.start.node !== this.cursor.textNode) {\n const blot = this.scroll.find(nativeRange.start.node, false);\n if (blot == null) return; // TODO Give blot ability to not split\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n const after = blot.split(nativeRange.start.offset);\n blot.parent.insertBefore(this.cursor, after);\n } else {\n blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen\n }\n\n this.cursor.attach();\n }\n\n this.cursor.format(format, value);\n this.scroll.optimize();\n this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);\n this.update();\n }\n\n getBounds(index, length = 0) {\n const scrollLength = this.scroll.length();\n index = Math.min(index, scrollLength - 1);\n length = Math.min(index + length, scrollLength - 1) - index;\n let node;\n let [leaf, offset] = this.scroll.leaf(index);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n const range = document.createRange();\n\n if (length > 0) {\n range.setStart(node, offset);\n [leaf, offset] = this.scroll.leaf(index + length);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n range.setEnd(node, offset);\n return range.getBoundingClientRect();\n }\n\n let side = 'left';\n let rect;\n\n if (node instanceof Text) {\n if (offset < node.data.length) {\n range.setStart(node, offset);\n range.setEnd(node, offset + 1);\n } else {\n range.setStart(node, offset - 1);\n range.setEnd(node, offset);\n side = 'right';\n }\n\n rect = range.getBoundingClientRect();\n } else {\n rect = leaf.domNode.getBoundingClientRect();\n if (offset > 0) side = 'right';\n }\n\n return {\n bottom: rect.top + rect.height,\n height: rect.height,\n left: rect[side],\n right: rect[side],\n top: rect.top,\n width: 0\n };\n }\n\n getNativeRange() {\n const selection = document.getSelection();\n if (selection == null || selection.rangeCount <= 0) return null;\n const nativeRange = selection.getRangeAt(0);\n if (nativeRange == null) return null;\n const range = this.normalizeNative(nativeRange);\n debug.info('getNativeRange', range);\n return range;\n }\n\n getRange() {\n const normalized = this.getNativeRange();\n if (normalized == null) return [null, null];\n const range = this.normalizedToRange(normalized);\n return [range, normalized];\n }\n\n hasFocus() {\n return document.activeElement === this.root || contains(this.root, document.activeElement);\n }\n\n normalizedToRange(range) {\n const positions = [[range.start.node, range.start.offset]];\n\n if (!range.native.collapsed) {\n positions.push([range.end.node, range.end.offset]);\n }\n\n const indexes = positions.map(position => {\n const [node, offset] = position;\n const blot = this.scroll.find(node, true);\n const index = blot.offset(this.scroll);\n\n if (offset === 0) {\n return index;\n }\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n return index + blot.index(node, offset);\n }\n\n return index + blot.length();\n });\n const end = Math.min(Math.max(...indexes), this.scroll.length() - 1);\n const start = Math.min(end, ...indexes);\n return new Range(start, end - start);\n }\n\n normalizeNative(nativeRange) {\n if (!contains(this.root, nativeRange.startContainer) || !nativeRange.collapsed && !contains(this.root, nativeRange.endContainer)) {\n return null;\n }\n\n const range = {\n start: {\n node: nativeRange.startContainer,\n offset: nativeRange.startOffset\n },\n end: {\n node: nativeRange.endContainer,\n offset: nativeRange.endOffset\n },\n native: nativeRange\n };\n [range.start, range.end].forEach(position => {\n let {\n node,\n offset\n } = position;\n\n while (!(node instanceof Text) && node.childNodes.length > 0) {\n if (node.childNodes.length > offset) {\n node = node.childNodes[offset];\n offset = 0;\n } else if (node.childNodes.length === offset) {\n node = node.lastChild;\n\n if (node instanceof Text) {\n offset = node.data.length;\n } else if (node.childNodes.length > 0) {\n // Container case\n offset = node.childNodes.length;\n } else {\n // Embed case\n offset = node.childNodes.length + 1;\n }\n } else {\n break;\n }\n }\n\n position.node = node;\n position.offset = offset;\n });\n return range;\n }\n\n rangeToNative(range) {\n const indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];\n const args = [];\n const scrollLength = this.scroll.length();\n indexes.forEach((index, i) => {\n index = Math.min(scrollLength - 1, index);\n const [leaf, leafOffset] = this.scroll.leaf(index);\n const [node, offset] = leaf.position(leafOffset, i !== 0);\n args.push(node, offset);\n });\n\n if (args.length < 2) {\n return args.concat(args);\n }\n\n return args;\n }\n\n scrollIntoView(scrollingContainer) {\n const range = this.lastRange;\n if (range == null) return;\n const bounds = this.getBounds(range.index, range.length);\n if (bounds == null) return;\n const limit = this.scroll.length() - 1;\n const [first] = this.scroll.line(Math.min(range.index, limit));\n let last = first;\n\n if (range.length > 0) {\n [last] = this.scroll.line(Math.min(range.index + range.length, limit));\n }\n\n if (first == null || last == null) return;\n const scrollBounds = scrollingContainer.getBoundingClientRect();\n\n if (bounds.top < scrollBounds.top) {\n scrollingContainer.scrollTop -= scrollBounds.top - bounds.top;\n } else if (bounds.bottom > scrollBounds.bottom) {\n scrollingContainer.scrollTop += bounds.bottom - scrollBounds.bottom;\n }\n }\n\n setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {\n debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);\n\n if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {\n return;\n }\n\n const selection = document.getSelection();\n if (selection == null) return;\n\n if (startNode != null) {\n if (!this.hasFocus()) this.root.focus();\n const {\n native\n } = this.getNativeRange() || {};\n\n if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {\n if (startNode.tagName === 'BR') {\n startOffset = Array.from(startNode.parentNode.childNodes).indexOf(startNode);\n startNode = startNode.parentNode;\n }\n\n if (endNode.tagName === 'BR') {\n endOffset = Array.from(endNode.parentNode.childNodes).indexOf(endNode);\n endNode = endNode.parentNode;\n }\n\n const range = document.createRange();\n range.setStart(startNode, startOffset);\n range.setEnd(endNode, endOffset);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n } else {\n selection.removeAllRanges();\n this.root.blur();\n }\n }\n\n setRange(range, force = false, source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.API) {\n if (typeof force === 'string') {\n source = force;\n force = false;\n }\n\n debug.info('setRange', range);\n\n if (range != null) {\n const args = this.rangeToNative(range);\n this.setNativeRange(...args, force);\n } else {\n this.setNativeRange(null);\n }\n\n this.update(source);\n }\n\n update(source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER) {\n const oldRange = this.lastRange;\n const [lastRange, nativeRange] = this.getRange();\n this.lastRange = lastRange;\n this.lastNative = nativeRange;\n\n if (this.lastRange != null) {\n this.savedRange = this.lastRange;\n }\n\n if (!deep_equal__WEBPACK_IMPORTED_MODULE_2___default()(oldRange, this.lastRange)) {\n if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {\n const range = this.cursor.restore();\n\n if (range) {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }\n }\n\n const args = [_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SELECTION_CHANGE, clone__WEBPACK_IMPORTED_MODULE_1___default()(this.lastRange), clone__WEBPACK_IMPORTED_MODULE_1___default()(oldRange), source];\n this.emitter.emit(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.EDITOR_CHANGE, ...args);\n\n if (source !== _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT) {\n this.emitter.emit(...args);\n }\n }\n }\n\n}\n\nfunction contains(parent, descendant) {\n try {\n // Firefox inserts inaccessible nodes around video elements\n descendant.parentNode; // eslint-disable-line no-unused-expressions\n } catch (e) {\n return false;\n }\n\n return parent.contains(descendant);\n}\n\n\n\n//# sourceURL=webpack://Quill/./core/selection.js?"); -+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Range\", function() { return Range; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Selection; });\n/* harmony import */ var parchment__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! parchment */ \"./node_modules/parchment/src/parchment.ts\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! clone */ \"./node_modules/clone/clone.js\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(clone__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! deep-equal */ \"./node_modules/deep-equal/index.js\");\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(deep_equal__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _emitter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./emitter */ \"./core/emitter.js\");\n/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./logger */ \"./core/logger.js\");\n\n\n\n\n\nconst debug = Object(_logger__WEBPACK_IMPORTED_MODULE_4__[\"default\"])('quill:selection');\n\nclass Range {\n constructor(index, length = 0) {\n this.index = index;\n this.length = length;\n }\n\n}\n\nclass Selection {\n constructor(scroll, emitter) {\n this.emitter = emitter;\n this.scroll = scroll;\n this.composing = false;\n this.mouseDown = false;\n this.root = this.scroll.domNode;\n this.cursor = this.scroll.create('cursor', this); // savedRange is last non-null range\n\n this.savedRange = new Range(0, 0);\n this.lastRange = this.savedRange;\n this.lastNative = null;\n this.handleComposition();\n this.handleDragging();\n this.emitter.listenDOM('selectionchange', document, () => {\n if (!this.mouseDown && !this.composing) {\n setTimeout(this.update.bind(this, _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER), 1);\n }\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_BEFORE_UPDATE, () => {\n if (!this.hasFocus()) return;\n const native = this.getNativeRange();\n if (native == null) return;\n if (native.start.node === this.cursor.textNode) return; // cursor.restore() will handle\n\n this.emitter.once(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_UPDATE, () => {\n try {\n if (this.root.contains(native.start.node) && this.root.contains(native.end.node)) {\n this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);\n }\n\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n } catch (ignored) {// ignore\n }\n });\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_OPTIMIZE, (mutations, context) => {\n if (context.range) {\n const {\n startNode,\n startOffset,\n endNode,\n endOffset\n } = context.range;\n this.setNativeRange(startNode, startOffset, endNode, endOffset);\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n });\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n\n handleComposition() {\n this.root.addEventListener('compositionstart', () => {\n this.composing = true;\n this.scroll.batchStart();\n });\n this.root.addEventListener('compositionend', () => {\n this.scroll.batchEnd();\n this.composing = false;\n\n if (this.cursor.parent) {\n const range = this.cursor.restore();\n if (!range) return;\n setTimeout(() => {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }, 1);\n }\n });\n }\n\n handleDragging() {\n this.emitter.listenDOM('mousedown', document.body, () => {\n this.mouseDown = true;\n });\n this.emitter.listenDOM('mouseup', document.body, () => {\n this.mouseDown = false;\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER);\n });\n }\n\n focus() {\n if (this.hasFocus()) return;\n this.root.focus();\n this.setRange(this.savedRange);\n }\n\n format(format, value) {\n this.scroll.update();\n const nativeRange = this.getNativeRange();\n if (nativeRange == null || !nativeRange.native.collapsed || this.scroll.query(format, parchment__WEBPACK_IMPORTED_MODULE_0__[\"Scope\"].BLOCK)) return;\n\n if (nativeRange.start.node !== this.cursor.textNode) {\n const blot = this.scroll.find(nativeRange.start.node, false);\n if (blot == null) return; // TODO Give blot ability to not split\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n const after = blot.split(nativeRange.start.offset);\n blot.parent.insertBefore(this.cursor, after);\n } else {\n blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen\n }\n\n this.cursor.attach();\n }\n\n this.cursor.format(format, value);\n this.scroll.optimize();\n this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);\n this.update();\n }\n\n getBounds(index, length = 0) {\n const scrollLength = this.scroll.length();\n index = Math.min(index, scrollLength - 1);\n length = Math.min(index + length, scrollLength - 1) - index;\n let node;\n let [leaf, offset] = this.scroll.leaf(index);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n const range = document.createRange();\n\n if (length > 0) {\n range.setStart(node, offset);\n [leaf, offset] = this.scroll.leaf(index + length);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n range.setEnd(node, offset);\n return range.getBoundingClientRect();\n }\n\n let side = 'left';\n let rect;\n\n if (node instanceof Text || node.nodeName === '#text') {\n if (offset < node.data.length) {\n range.setStart(node, offset);\n range.setEnd(node, offset + 1);\n } else {\n range.setStart(node, offset - 1);\n range.setEnd(node, offset);\n side = 'right';\n }\n\n rect = range.getBoundingClientRect();\n } else {\n rect = leaf.domNode.getBoundingClientRect();\n if (offset > 0) side = 'right';\n }\n\n return {\n bottom: rect.top + rect.height,\n height: rect.height,\n left: rect[side],\n right: rect[side],\n top: rect.top,\n width: 0\n };\n }\n\n getNativeRange() {\n const selection = document.getSelection();\n if (selection == null || selection.rangeCount <= 0) return null;\n const nativeRange = selection.getRangeAt(0);\n if (nativeRange == null) return null;\n const range = this.normalizeNative(nativeRange);\n debug.info('getNativeRange', range);\n return range;\n }\n\n getRange() {\n const normalized = this.getNativeRange();\n if (normalized == null) return [null, null];\n const range = this.normalizedToRange(normalized);\n return [range, normalized];\n }\n\n hasFocus() {\n return document.activeElement === this.root || contains(this.root, document.activeElement);\n }\n\n normalizedToRange(range) {\n const positions = [[range.start.node, range.start.offset]];\n\n if (!range.native.collapsed) {\n positions.push([range.end.node, range.end.offset]);\n }\n\n const indexes = positions.map(position => {\n const [node, offset] = position;\n const blot = this.scroll.find(node, true);\n const index = blot.offset(this.scroll);\n\n if (offset === 0) {\n return index;\n }\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n return index + blot.index(node, offset);\n }\n\n return index + blot.length();\n });\n const end = Math.min(Math.max(...indexes), this.scroll.length() - 1);\n const start = Math.min(end, ...indexes);\n return new Range(start, end - start);\n }\n\n normalizeNative(nativeRange) {\n if (!contains(this.root, nativeRange.startContainer) || !nativeRange.collapsed && !contains(this.root, nativeRange.endContainer)) {\n return null;\n }\n\n const range = {\n start: {\n node: nativeRange.startContainer,\n offset: nativeRange.startOffset\n },\n end: {\n node: nativeRange.endContainer,\n offset: nativeRange.endOffset\n },\n native: nativeRange\n };\n [range.start, range.end].forEach(position => {\n let {\n node,\n offset\n } = position;\n\n while (!(node instanceof Text) && node.childNodes.length > 0) {\n if (node.childNodes.length > offset) {\n node = node.childNodes[offset];\n offset = 0;\n } else if (node.childNodes.length === offset) {\n node = node.lastChild;\n\n if (node instanceof Text) {\n offset = node.data.length;\n } else if (node.childNodes.length > 0) {\n // Container case\n offset = node.childNodes.length;\n } else {\n // Embed case\n offset = node.childNodes.length + 1;\n }\n } else {\n break;\n }\n }\n\n position.node = node;\n position.offset = offset;\n });\n return range;\n }\n\n rangeToNative(range) {\n const indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];\n const args = [];\n const scrollLength = this.scroll.length();\n indexes.forEach((index, i) => {\n index = Math.min(scrollLength - 1, index);\n const [leaf, leafOffset] = this.scroll.leaf(index);\n const [node, offset] = leaf.position(leafOffset, i !== 0);\n args.push(node, offset);\n });\n\n if (args.length < 2) {\n return args.concat(args);\n }\n\n return args;\n }\n\n scrollIntoView(scrollingContainer) {\n const range = this.lastRange;\n if (range == null) return;\n const bounds = this.getBounds(range.index, range.length);\n if (bounds == null) return;\n const limit = this.scroll.length() - 1;\n const [first] = this.scroll.line(Math.min(range.index, limit));\n let last = first;\n\n if (range.length > 0) {\n [last] = this.scroll.line(Math.min(range.index + range.length, limit));\n }\n\n if (first == null || last == null) return;\n const scrollBounds = scrollingContainer.getBoundingClientRect();\n\n if (bounds.top < scrollBounds.top) {\n scrollingContainer.scrollTop -= scrollBounds.top - bounds.top;\n } else if (bounds.bottom > scrollBounds.bottom) {\n scrollingContainer.scrollTop += bounds.bottom - scrollBounds.bottom;\n }\n }\n\n setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {\n debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);\n\n if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {\n return;\n }\n\n const selection = document.getSelection();\n if (selection == null) return;\n\n if (startNode != null) {\n if (!this.hasFocus()) this.root.focus();\n const {\n native\n } = this.getNativeRange() || {};\n\n if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {\n if (startNode.tagName === 'BR') {\n startOffset = Array.from(startNode.parentNode.childNodes).indexOf(startNode);\n startNode = startNode.parentNode;\n }\n\n if (endNode.tagName === 'BR') {\n endOffset = Array.from(endNode.parentNode.childNodes).indexOf(endNode);\n endNode = endNode.parentNode;\n }\n\n const range = document.createRange();\n range.setStart(startNode, startOffset);\n range.setEnd(endNode, endOffset);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n } else {\n selection.removeAllRanges();\n this.root.blur();\n }\n }\n\n setRange(range, force = false, source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.API) {\n if (typeof force === 'string') {\n source = force;\n force = false;\n }\n\n debug.info('setRange', range);\n\n if (range != null) {\n const args = this.rangeToNative(range);\n this.setNativeRange(...args, force);\n } else {\n this.setNativeRange(null);\n }\n\n this.update(source);\n }\n\n update(source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER) {\n const oldRange = this.lastRange;\n const [lastRange, nativeRange] = this.getRange();\n this.lastRange = lastRange;\n this.lastNative = nativeRange;\n\n if (this.lastRange != null) {\n this.savedRange = this.lastRange;\n }\n\n if (!deep_equal__WEBPACK_IMPORTED_MODULE_2___default()(oldRange, this.lastRange)) {\n if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {\n const range = this.cursor.restore();\n\n if (range) {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }\n }\n\n const args = [_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SELECTION_CHANGE, clone__WEBPACK_IMPORTED_MODULE_1___default()(this.lastRange), clone__WEBPACK_IMPORTED_MODULE_1___default()(oldRange), source];\n this.emitter.emit(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.EDITOR_CHANGE, ...args);\n\n if (source !== _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT) {\n this.emitter.emit(...args);\n }\n }\n }\n\n}\n\nfunction contains(parent, descendant) {\n try {\n // Firefox inserts inaccessible nodes around video elements\n descendant.parentNode; // eslint-disable-line no-unused-expressions\n } catch (e) {\n return false;\n }\n\n return parent.contains(descendant);\n}\n\n\n\n//# sourceURL=webpack://Quill/./core/selection.js?"); - - /***/ }), - -diff --git a/dist/quill.js b/dist/quill.js -index 2d455617a699b1bee8bf81f3ef964f8499622977..0c46e3f6354d1aca2e69e494f8adda42126dc581 100644 ---- a/dist/quill.js -+++ b/dist/quill.js -@@ -620,7 +620,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * - /***/ (function(module, __webpack_exports__, __webpack_require__) { - - "use strict"; --eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Range\", function() { return Range; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Selection; });\n/* harmony import */ var parchment__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! parchment */ \"./node_modules/parchment/src/parchment.ts\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! clone */ \"./node_modules/clone/clone.js\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(clone__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! deep-equal */ \"./node_modules/deep-equal/index.js\");\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(deep_equal__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _emitter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./emitter */ \"./core/emitter.js\");\n/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./logger */ \"./core/logger.js\");\n\n\n\n\n\nconst debug = Object(_logger__WEBPACK_IMPORTED_MODULE_4__[\"default\"])('quill:selection');\n\nclass Range {\n constructor(index, length = 0) {\n this.index = index;\n this.length = length;\n }\n\n}\n\nclass Selection {\n constructor(scroll, emitter) {\n this.emitter = emitter;\n this.scroll = scroll;\n this.composing = false;\n this.mouseDown = false;\n this.root = this.scroll.domNode;\n this.cursor = this.scroll.create('cursor', this); // savedRange is last non-null range\n\n this.savedRange = new Range(0, 0);\n this.lastRange = this.savedRange;\n this.lastNative = null;\n this.handleComposition();\n this.handleDragging();\n this.emitter.listenDOM('selectionchange', document, () => {\n if (!this.mouseDown && !this.composing) {\n setTimeout(this.update.bind(this, _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER), 1);\n }\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_BEFORE_UPDATE, () => {\n if (!this.hasFocus()) return;\n const native = this.getNativeRange();\n if (native == null) return;\n if (native.start.node === this.cursor.textNode) return; // cursor.restore() will handle\n\n this.emitter.once(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_UPDATE, () => {\n try {\n if (this.root.contains(native.start.node) && this.root.contains(native.end.node)) {\n this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);\n }\n\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n } catch (ignored) {// ignore\n }\n });\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_OPTIMIZE, (mutations, context) => {\n if (context.range) {\n const {\n startNode,\n startOffset,\n endNode,\n endOffset\n } = context.range;\n this.setNativeRange(startNode, startOffset, endNode, endOffset);\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n });\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n\n handleComposition() {\n this.root.addEventListener('compositionstart', () => {\n this.composing = true;\n this.scroll.batchStart();\n });\n this.root.addEventListener('compositionend', () => {\n this.scroll.batchEnd();\n this.composing = false;\n\n if (this.cursor.parent) {\n const range = this.cursor.restore();\n if (!range) return;\n setTimeout(() => {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }, 1);\n }\n });\n }\n\n handleDragging() {\n this.emitter.listenDOM('mousedown', document.body, () => {\n this.mouseDown = true;\n });\n this.emitter.listenDOM('mouseup', document.body, () => {\n this.mouseDown = false;\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER);\n });\n }\n\n focus() {\n if (this.hasFocus()) return;\n this.root.focus();\n this.setRange(this.savedRange);\n }\n\n format(format, value) {\n this.scroll.update();\n const nativeRange = this.getNativeRange();\n if (nativeRange == null || !nativeRange.native.collapsed || this.scroll.query(format, parchment__WEBPACK_IMPORTED_MODULE_0__[\"Scope\"].BLOCK)) return;\n\n if (nativeRange.start.node !== this.cursor.textNode) {\n const blot = this.scroll.find(nativeRange.start.node, false);\n if (blot == null) return; // TODO Give blot ability to not split\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n const after = blot.split(nativeRange.start.offset);\n blot.parent.insertBefore(this.cursor, after);\n } else {\n blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen\n }\n\n this.cursor.attach();\n }\n\n this.cursor.format(format, value);\n this.scroll.optimize();\n this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);\n this.update();\n }\n\n getBounds(index, length = 0) {\n const scrollLength = this.scroll.length();\n index = Math.min(index, scrollLength - 1);\n length = Math.min(index + length, scrollLength - 1) - index;\n let node;\n let [leaf, offset] = this.scroll.leaf(index);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n const range = document.createRange();\n\n if (length > 0) {\n range.setStart(node, offset);\n [leaf, offset] = this.scroll.leaf(index + length);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n range.setEnd(node, offset);\n return range.getBoundingClientRect();\n }\n\n let side = 'left';\n let rect;\n\n if (node instanceof Text) {\n if (offset < node.data.length) {\n range.setStart(node, offset);\n range.setEnd(node, offset + 1);\n } else {\n range.setStart(node, offset - 1);\n range.setEnd(node, offset);\n side = 'right';\n }\n\n rect = range.getBoundingClientRect();\n } else {\n rect = leaf.domNode.getBoundingClientRect();\n if (offset > 0) side = 'right';\n }\n\n return {\n bottom: rect.top + rect.height,\n height: rect.height,\n left: rect[side],\n right: rect[side],\n top: rect.top,\n width: 0\n };\n }\n\n getNativeRange() {\n const selection = document.getSelection();\n if (selection == null || selection.rangeCount <= 0) return null;\n const nativeRange = selection.getRangeAt(0);\n if (nativeRange == null) return null;\n const range = this.normalizeNative(nativeRange);\n debug.info('getNativeRange', range);\n return range;\n }\n\n getRange() {\n const normalized = this.getNativeRange();\n if (normalized == null) return [null, null];\n const range = this.normalizedToRange(normalized);\n return [range, normalized];\n }\n\n hasFocus() {\n return document.activeElement === this.root || contains(this.root, document.activeElement);\n }\n\n normalizedToRange(range) {\n const positions = [[range.start.node, range.start.offset]];\n\n if (!range.native.collapsed) {\n positions.push([range.end.node, range.end.offset]);\n }\n\n const indexes = positions.map(position => {\n const [node, offset] = position;\n const blot = this.scroll.find(node, true);\n const index = blot.offset(this.scroll);\n\n if (offset === 0) {\n return index;\n }\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n return index + blot.index(node, offset);\n }\n\n return index + blot.length();\n });\n const end = Math.min(Math.max(...indexes), this.scroll.length() - 1);\n const start = Math.min(end, ...indexes);\n return new Range(start, end - start);\n }\n\n normalizeNative(nativeRange) {\n if (!contains(this.root, nativeRange.startContainer) || !nativeRange.collapsed && !contains(this.root, nativeRange.endContainer)) {\n return null;\n }\n\n const range = {\n start: {\n node: nativeRange.startContainer,\n offset: nativeRange.startOffset\n },\n end: {\n node: nativeRange.endContainer,\n offset: nativeRange.endOffset\n },\n native: nativeRange\n };\n [range.start, range.end].forEach(position => {\n let {\n node,\n offset\n } = position;\n\n while (!(node instanceof Text) && node.childNodes.length > 0) {\n if (node.childNodes.length > offset) {\n node = node.childNodes[offset];\n offset = 0;\n } else if (node.childNodes.length === offset) {\n node = node.lastChild;\n\n if (node instanceof Text) {\n offset = node.data.length;\n } else if (node.childNodes.length > 0) {\n // Container case\n offset = node.childNodes.length;\n } else {\n // Embed case\n offset = node.childNodes.length + 1;\n }\n } else {\n break;\n }\n }\n\n position.node = node;\n position.offset = offset;\n });\n return range;\n }\n\n rangeToNative(range) {\n const indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];\n const args = [];\n const scrollLength = this.scroll.length();\n indexes.forEach((index, i) => {\n index = Math.min(scrollLength - 1, index);\n const [leaf, leafOffset] = this.scroll.leaf(index);\n const [node, offset] = leaf.position(leafOffset, i !== 0);\n args.push(node, offset);\n });\n\n if (args.length < 2) {\n return args.concat(args);\n }\n\n return args;\n }\n\n scrollIntoView(scrollingContainer) {\n const range = this.lastRange;\n if (range == null) return;\n const bounds = this.getBounds(range.index, range.length);\n if (bounds == null) return;\n const limit = this.scroll.length() - 1;\n const [first] = this.scroll.line(Math.min(range.index, limit));\n let last = first;\n\n if (range.length > 0) {\n [last] = this.scroll.line(Math.min(range.index + range.length, limit));\n }\n\n if (first == null || last == null) return;\n const scrollBounds = scrollingContainer.getBoundingClientRect();\n\n if (bounds.top < scrollBounds.top) {\n scrollingContainer.scrollTop -= scrollBounds.top - bounds.top;\n } else if (bounds.bottom > scrollBounds.bottom) {\n scrollingContainer.scrollTop += bounds.bottom - scrollBounds.bottom;\n }\n }\n\n setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {\n debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);\n\n if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {\n return;\n }\n\n const selection = document.getSelection();\n if (selection == null) return;\n\n if (startNode != null) {\n if (!this.hasFocus()) this.root.focus();\n const {\n native\n } = this.getNativeRange() || {};\n\n if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {\n if (startNode.tagName === 'BR') {\n startOffset = Array.from(startNode.parentNode.childNodes).indexOf(startNode);\n startNode = startNode.parentNode;\n }\n\n if (endNode.tagName === 'BR') {\n endOffset = Array.from(endNode.parentNode.childNodes).indexOf(endNode);\n endNode = endNode.parentNode;\n }\n\n const range = document.createRange();\n range.setStart(startNode, startOffset);\n range.setEnd(endNode, endOffset);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n } else {\n selection.removeAllRanges();\n this.root.blur();\n }\n }\n\n setRange(range, force = false, source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.API) {\n if (typeof force === 'string') {\n source = force;\n force = false;\n }\n\n debug.info('setRange', range);\n\n if (range != null) {\n const args = this.rangeToNative(range);\n this.setNativeRange(...args, force);\n } else {\n this.setNativeRange(null);\n }\n\n this.update(source);\n }\n\n update(source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER) {\n const oldRange = this.lastRange;\n const [lastRange, nativeRange] = this.getRange();\n this.lastRange = lastRange;\n this.lastNative = nativeRange;\n\n if (this.lastRange != null) {\n this.savedRange = this.lastRange;\n }\n\n if (!deep_equal__WEBPACK_IMPORTED_MODULE_2___default()(oldRange, this.lastRange)) {\n if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {\n const range = this.cursor.restore();\n\n if (range) {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }\n }\n\n const args = [_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SELECTION_CHANGE, clone__WEBPACK_IMPORTED_MODULE_1___default()(this.lastRange), clone__WEBPACK_IMPORTED_MODULE_1___default()(oldRange), source];\n this.emitter.emit(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.EDITOR_CHANGE, ...args);\n\n if (source !== _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT) {\n this.emitter.emit(...args);\n }\n }\n }\n\n}\n\nfunction contains(parent, descendant) {\n try {\n // Firefox inserts inaccessible nodes around video elements\n descendant.parentNode; // eslint-disable-line no-unused-expressions\n } catch (e) {\n return false;\n }\n\n return parent.contains(descendant);\n}\n\n\n\n//# sourceURL=webpack://Quill/./core/selection.js?"); -+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Range\", function() { return Range; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Selection; });\n/* harmony import */ var parchment__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! parchment */ \"./node_modules/parchment/src/parchment.ts\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! clone */ \"./node_modules/clone/clone.js\");\n/* harmony import */ var clone__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(clone__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! deep-equal */ \"./node_modules/deep-equal/index.js\");\n/* harmony import */ var deep_equal__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(deep_equal__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _emitter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./emitter */ \"./core/emitter.js\");\n/* harmony import */ var _logger__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./logger */ \"./core/logger.js\");\n\n\n\n\n\nconst debug = Object(_logger__WEBPACK_IMPORTED_MODULE_4__[\"default\"])('quill:selection');\n\nclass Range {\n constructor(index, length = 0) {\n this.index = index;\n this.length = length;\n }\n\n}\n\nclass Selection {\n constructor(scroll, emitter) {\n this.emitter = emitter;\n this.scroll = scroll;\n this.composing = false;\n this.mouseDown = false;\n this.root = this.scroll.domNode;\n this.cursor = this.scroll.create('cursor', this); // savedRange is last non-null range\n\n this.savedRange = new Range(0, 0);\n this.lastRange = this.savedRange;\n this.lastNative = null;\n this.handleComposition();\n this.handleDragging();\n this.emitter.listenDOM('selectionchange', document, () => {\n if (!this.mouseDown && !this.composing) {\n setTimeout(this.update.bind(this, _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER), 1);\n }\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_BEFORE_UPDATE, () => {\n if (!this.hasFocus()) return;\n const native = this.getNativeRange();\n if (native == null) return;\n if (native.start.node === this.cursor.textNode) return; // cursor.restore() will handle\n\n this.emitter.once(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_UPDATE, () => {\n try {\n if (this.root.contains(native.start.node) && this.root.contains(native.end.node)) {\n this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);\n }\n\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n } catch (ignored) {// ignore\n }\n });\n });\n this.emitter.on(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SCROLL_OPTIMIZE, (mutations, context) => {\n if (context.range) {\n const {\n startNode,\n startOffset,\n endNode,\n endOffset\n } = context.range;\n this.setNativeRange(startNode, startOffset, endNode, endOffset);\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n });\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT);\n }\n\n handleComposition() {\n this.root.addEventListener('compositionstart', () => {\n this.composing = true;\n this.scroll.batchStart();\n });\n this.root.addEventListener('compositionend', () => {\n this.scroll.batchEnd();\n this.composing = false;\n\n if (this.cursor.parent) {\n const range = this.cursor.restore();\n if (!range) return;\n setTimeout(() => {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }, 1);\n }\n });\n }\n\n handleDragging() {\n this.emitter.listenDOM('mousedown', document.body, () => {\n this.mouseDown = true;\n });\n this.emitter.listenDOM('mouseup', document.body, () => {\n this.mouseDown = false;\n this.update(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER);\n });\n }\n\n focus() {\n if (this.hasFocus()) return;\n this.root.focus();\n this.setRange(this.savedRange);\n }\n\n format(format, value) {\n this.scroll.update();\n const nativeRange = this.getNativeRange();\n if (nativeRange == null || !nativeRange.native.collapsed || this.scroll.query(format, parchment__WEBPACK_IMPORTED_MODULE_0__[\"Scope\"].BLOCK)) return;\n\n if (nativeRange.start.node !== this.cursor.textNode) {\n const blot = this.scroll.find(nativeRange.start.node, false);\n if (blot == null) return; // TODO Give blot ability to not split\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n const after = blot.split(nativeRange.start.offset);\n blot.parent.insertBefore(this.cursor, after);\n } else {\n blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen\n }\n\n this.cursor.attach();\n }\n\n this.cursor.format(format, value);\n this.scroll.optimize();\n this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);\n this.update();\n }\n\n getBounds(index, length = 0) {\n const scrollLength = this.scroll.length();\n index = Math.min(index, scrollLength - 1);\n length = Math.min(index + length, scrollLength - 1) - index;\n let node;\n let [leaf, offset] = this.scroll.leaf(index);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n const range = document.createRange();\n\n if (length > 0) {\n range.setStart(node, offset);\n [leaf, offset] = this.scroll.leaf(index + length);\n if (leaf == null) return null;\n [node, offset] = leaf.position(offset, true);\n range.setEnd(node, offset);\n return range.getBoundingClientRect();\n }\n\n let side = 'left';\n let rect;\n\n if (node instanceof Text || node.nodeName === '#text') {\n if (offset < node.data.length) {\n range.setStart(node, offset);\n range.setEnd(node, offset + 1);\n } else {\n range.setStart(node, offset - 1);\n range.setEnd(node, offset);\n side = 'right';\n }\n\n rect = range.getBoundingClientRect();\n } else {\n rect = leaf.domNode.getBoundingClientRect();\n if (offset > 0) side = 'right';\n }\n\n return {\n bottom: rect.top + rect.height,\n height: rect.height,\n left: rect[side],\n right: rect[side],\n top: rect.top,\n width: 0\n };\n }\n\n getNativeRange() {\n const selection = document.getSelection();\n if (selection == null || selection.rangeCount <= 0) return null;\n const nativeRange = selection.getRangeAt(0);\n if (nativeRange == null) return null;\n const range = this.normalizeNative(nativeRange);\n debug.info('getNativeRange', range);\n return range;\n }\n\n getRange() {\n const normalized = this.getNativeRange();\n if (normalized == null) return [null, null];\n const range = this.normalizedToRange(normalized);\n return [range, normalized];\n }\n\n hasFocus() {\n return document.activeElement === this.root || contains(this.root, document.activeElement);\n }\n\n normalizedToRange(range) {\n const positions = [[range.start.node, range.start.offset]];\n\n if (!range.native.collapsed) {\n positions.push([range.end.node, range.end.offset]);\n }\n\n const indexes = positions.map(position => {\n const [node, offset] = position;\n const blot = this.scroll.find(node, true);\n const index = blot.offset(this.scroll);\n\n if (offset === 0) {\n return index;\n }\n\n if (blot instanceof parchment__WEBPACK_IMPORTED_MODULE_0__[\"LeafBlot\"]) {\n return index + blot.index(node, offset);\n }\n\n return index + blot.length();\n });\n const end = Math.min(Math.max(...indexes), this.scroll.length() - 1);\n const start = Math.min(end, ...indexes);\n return new Range(start, end - start);\n }\n\n normalizeNative(nativeRange) {\n if (!contains(this.root, nativeRange.startContainer) || !nativeRange.collapsed && !contains(this.root, nativeRange.endContainer)) {\n return null;\n }\n\n const range = {\n start: {\n node: nativeRange.startContainer,\n offset: nativeRange.startOffset\n },\n end: {\n node: nativeRange.endContainer,\n offset: nativeRange.endOffset\n },\n native: nativeRange\n };\n [range.start, range.end].forEach(position => {\n let {\n node,\n offset\n } = position;\n\n while (!(node instanceof Text) && node.childNodes.length > 0) {\n if (node.childNodes.length > offset) {\n node = node.childNodes[offset];\n offset = 0;\n } else if (node.childNodes.length === offset) {\n node = node.lastChild;\n\n if (node instanceof Text) {\n offset = node.data.length;\n } else if (node.childNodes.length > 0) {\n // Container case\n offset = node.childNodes.length;\n } else {\n // Embed case\n offset = node.childNodes.length + 1;\n }\n } else {\n break;\n }\n }\n\n position.node = node;\n position.offset = offset;\n });\n return range;\n }\n\n rangeToNative(range) {\n const indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];\n const args = [];\n const scrollLength = this.scroll.length();\n indexes.forEach((index, i) => {\n index = Math.min(scrollLength - 1, index);\n const [leaf, leafOffset] = this.scroll.leaf(index);\n const [node, offset] = leaf.position(leafOffset, i !== 0);\n args.push(node, offset);\n });\n\n if (args.length < 2) {\n return args.concat(args);\n }\n\n return args;\n }\n\n scrollIntoView(scrollingContainer) {\n const range = this.lastRange;\n if (range == null) return;\n const bounds = this.getBounds(range.index, range.length);\n if (bounds == null) return;\n const limit = this.scroll.length() - 1;\n const [first] = this.scroll.line(Math.min(range.index, limit));\n let last = first;\n\n if (range.length > 0) {\n [last] = this.scroll.line(Math.min(range.index + range.length, limit));\n }\n\n if (first == null || last == null) return;\n const scrollBounds = scrollingContainer.getBoundingClientRect();\n\n if (bounds.top < scrollBounds.top) {\n scrollingContainer.scrollTop -= scrollBounds.top - bounds.top;\n } else if (bounds.bottom > scrollBounds.bottom) {\n scrollingContainer.scrollTop += bounds.bottom - scrollBounds.bottom;\n }\n }\n\n setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {\n debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);\n\n if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {\n return;\n }\n\n const selection = document.getSelection();\n if (selection == null) return;\n\n if (startNode != null) {\n if (!this.hasFocus()) this.root.focus();\n const {\n native\n } = this.getNativeRange() || {};\n\n if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {\n if (startNode.tagName === 'BR') {\n startOffset = Array.from(startNode.parentNode.childNodes).indexOf(startNode);\n startNode = startNode.parentNode;\n }\n\n if (endNode.tagName === 'BR') {\n endOffset = Array.from(endNode.parentNode.childNodes).indexOf(endNode);\n endNode = endNode.parentNode;\n }\n\n const range = document.createRange();\n range.setStart(startNode, startOffset);\n range.setEnd(endNode, endOffset);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n } else {\n selection.removeAllRanges();\n this.root.blur();\n }\n }\n\n setRange(range, force = false, source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.API) {\n if (typeof force === 'string') {\n source = force;\n force = false;\n }\n\n debug.info('setRange', range);\n\n if (range != null) {\n const args = this.rangeToNative(range);\n this.setNativeRange(...args, force);\n } else {\n this.setNativeRange(null);\n }\n\n this.update(source);\n }\n\n update(source = _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.USER) {\n const oldRange = this.lastRange;\n const [lastRange, nativeRange] = this.getRange();\n this.lastRange = lastRange;\n this.lastNative = nativeRange;\n\n if (this.lastRange != null) {\n this.savedRange = this.lastRange;\n }\n\n if (!deep_equal__WEBPACK_IMPORTED_MODULE_2___default()(oldRange, this.lastRange)) {\n if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {\n const range = this.cursor.restore();\n\n if (range) {\n this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);\n }\n }\n\n const args = [_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.SELECTION_CHANGE, clone__WEBPACK_IMPORTED_MODULE_1___default()(this.lastRange), clone__WEBPACK_IMPORTED_MODULE_1___default()(oldRange), source];\n this.emitter.emit(_emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].events.EDITOR_CHANGE, ...args);\n\n if (source !== _emitter__WEBPACK_IMPORTED_MODULE_3__[\"default\"].sources.SILENT) {\n this.emitter.emit(...args);\n }\n }\n }\n\n}\n\nfunction contains(parent, descendant) {\n try {\n // Firefox inserts inaccessible nodes around video elements\n descendant.parentNode; // eslint-disable-line no-unused-expressions\n } catch (e) {\n return false;\n }\n\n return parent.contains(descendant);\n}\n\n\n\n//# sourceURL=webpack://Quill/./core/selection.js?"); - - /***/ }), - \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad7e4de991..2eee1d177d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,14 +10,6 @@ overrides: fork-ts-checker-webpack-plugin: ^6.0.4 globby: ^11.1.0 -patchedDependencies: - quill@2.0.0-dev.4: - hash: wzpqp2wkmsfkgdbvlakyt3wndy - path: patches/quill@2.0.0-dev.4.patch - element-ui@2.15.12: - hash: aaa3sc7bmwb4jwg35ga5npx4he - path: patches/element-ui@2.15.12.patch - importers: .: @@ -487,8 +479,8 @@ importers: '@codemirror/lang-javascript': ^6.0.2 '@codemirror/language': ^6.2.1 '@codemirror/lint': ^6.0.0 - '@codemirror/state': ^6.1.1 - '@codemirror/view': ^6.2.1 + '@codemirror/state': ^6.1.4 + '@codemirror/view': ^6.5.1 '@fontsource/open-sans': ^4.5.0 '@fortawesome/fontawesome-svg-core': ^1.2.35 '@fortawesome/free-regular-svg-icons': ^6.1.1 @@ -505,7 +497,6 @@ importers: '@types/lodash.get': ^4.4.6 '@types/lodash.set': ^4.3.6 '@types/luxon': ^2.0.9 - '@types/quill': ^2.0.1 '@types/uuid': ^8.3.2 '@vitejs/plugin-legacy': ^1.8.2 '@vitejs/plugin-vue2': ^1.1.2 @@ -533,8 +524,6 @@ importers: normalize-wheel: ^1.0.1 pinia: ^2.0.22 prismjs: ^1.17.1 - quill: 2.0.0-dev.4 - quill-autoformat: ^0.1.1 sass: ^1.55.0 sass-loader: ^10.1.1 string-template-parser: ^1.2.6 @@ -560,13 +549,13 @@ importers: vue2-touch-events: ^3.2.1 xss: ^1.0.10 dependencies: - '@codemirror/autocomplete': 6.3.0_2jvnbzbiudbravcwrws2kgi36q + '@codemirror/autocomplete': 6.3.0_wo7q3lvweq5evsu423o7qzum5i '@codemirror/commands': 6.1.2 '@codemirror/lang-javascript': 6.1.0 '@codemirror/language': 6.2.1 '@codemirror/lint': 6.0.0 - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 + '@codemirror/state': 6.1.4 + '@codemirror/view': 6.5.1 '@fontsource/open-sans': 4.5.12 '@fortawesome/fontawesome-svg-core': 1.2.36 '@fortawesome/free-regular-svg-icons': 6.2.0 @@ -594,8 +583,6 @@ importers: normalize-wheel: 1.0.1 pinia: 2.0.23_xjcbg5znturqejtkpd33hx726m prismjs: 1.29.0 - quill: 2.0.0-dev.4_wzpqp2wkmsfkgdbvlakyt3wndy - quill-autoformat: 0.1.2 timeago.js: 4.0.2 uuid: 8.3.2 v-click-outside: 3.2.0 @@ -624,7 +611,6 @@ importers: '@types/lodash.get': 4.4.7 '@types/lodash.set': 4.3.7 '@types/luxon': 2.4.0 - '@types/quill': 2.0.9 '@types/uuid': 8.3.4 '@vitejs/plugin-legacy': 1.8.2_vite@2.9.5 '@vitejs/plugin-vue2': 1.1.2_vite@2.9.5+vue@2.7.13 @@ -2627,7 +2613,7 @@ packages: minimist: 1.2.7 dev: true - /@codemirror/autocomplete/6.3.0_2jvnbzbiudbravcwrws2kgi36q: + /@codemirror/autocomplete/6.3.0_wo7q3lvweq5evsu423o7qzum5i: resolution: {integrity: sha512-4jEvh3AjJZTDKazd10J6ZsCIqaYxDMCeua5ouQxY8hlFIml+nr7le0SgBhT3SIytFBmdzPK3AUhXGuW3T79nVg==} peerDependencies: '@codemirror/language': ^6.0.0 @@ -2636,8 +2622,8 @@ packages: '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.2.1 - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 + '@codemirror/state': 6.1.4 + '@codemirror/view': 6.5.1 '@lezer/common': 1.0.1 dev: false @@ -2645,19 +2631,19 @@ packages: resolution: {integrity: sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==} dependencies: '@codemirror/language': 6.2.1 - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 + '@codemirror/state': 6.1.4 + '@codemirror/view': 6.5.1 '@lezer/common': 1.0.1 dev: false /@codemirror/lang-javascript/6.1.0: resolution: {integrity: sha512-wAWEY1Wdis2cKDy9A5q/rUmzLHFbZgoupJBcGaeMMsDPi68Rm90NsmzAEODE5kW8mYdRKFhQ157WJghOZ3yYdg==} dependencies: - '@codemirror/autocomplete': 6.3.0_2jvnbzbiudbravcwrws2kgi36q + '@codemirror/autocomplete': 6.3.0_wo7q3lvweq5evsu423o7qzum5i '@codemirror/language': 6.2.1 '@codemirror/lint': 6.0.0 - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 + '@codemirror/state': 6.1.4 + '@codemirror/view': 6.5.1 '@lezer/common': 1.0.1 '@lezer/javascript': 1.0.2 dev: false @@ -2665,8 +2651,8 @@ packages: /@codemirror/language/6.2.1: resolution: {integrity: sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==} dependencies: - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 + '@codemirror/state': 6.1.4 + '@codemirror/view': 6.5.1 '@lezer/common': 1.0.1 '@lezer/highlight': 1.1.1 '@lezer/lr': 1.2.3 @@ -2676,19 +2662,19 @@ packages: /@codemirror/lint/6.0.0: resolution: {integrity: sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==} dependencies: - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 + '@codemirror/state': 6.1.4 + '@codemirror/view': 6.5.1 crelt: 1.0.5 dev: false - /@codemirror/state/6.1.2: - resolution: {integrity: sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA==} + /@codemirror/state/6.1.4: + resolution: {integrity: sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==} dev: false - /@codemirror/view/6.3.1: - resolution: {integrity: sha512-NKPBphoV9W2Q6tKXk+ge4q5EhMOOC0rpwdGS80/slNSfsVqkN4gwXIEqSprXJFlf9aUKZU7WhPvqRBMNH+hJkQ==} + /@codemirror/view/6.5.1: + resolution: {integrity: sha512-xBKP8N3AXOs06VcKvIuvIQoUlGs7Hb78ftJWahLaRX909jKPMgGxR5XjvrawzTTZMSTU3DzdjDNPwG6fPM/ypQ==} dependencies: - '@codemirror/state': 6.1.2 + '@codemirror/state': 6.1.4 style-mod: 4.0.0 w3c-keyname: 2.2.6 dev: false @@ -5981,13 +5967,6 @@ packages: /@types/qs/6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - /@types/quill/2.0.9: - resolution: {integrity: sha512-/n40Ypp+jF3GDLqB/5z1P+Odq1K98txXbBgRDkG6Z90LGC1AwQPtZWNeOdDg0yUlgBSUASmpeDn3eBPUuPXtuw==} - dependencies: - parchment: 1.1.4 - quill-delta: 4.2.2 - dev: true - /@types/range-parser/1.2.4: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} @@ -8877,6 +8856,7 @@ packages: /clone/2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + dev: true /cloneable-readable/1.1.3: resolution: {integrity: sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==} @@ -9942,37 +9922,6 @@ packages: type-detect: 4.0.8 dev: true - /deep-equal/1.1.1: - resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==} - dependencies: - is-arguments: 1.1.1 - is-date-object: 1.0.5 - is-regex: 1.1.4 - object-is: 1.1.5 - object-keys: 1.1.1 - regexp.prototype.flags: 1.4.3 - dev: false - - /deep-equal/2.0.5: - resolution: {integrity: sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==} - dependencies: - call-bind: 1.0.2 - es-get-iterator: 1.1.2 - get-intrinsic: 1.1.3 - is-arguments: 1.1.1 - is-date-object: 1.0.5 - is-regex: 1.1.4 - isarray: 2.0.5 - object-is: 1.1.5 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.4.3 - side-channel: 1.0.4 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.1 - which-typed-array: 1.1.8 - dev: false - /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -10594,6 +10543,7 @@ packages: is-set: 2.0.2 is-string: 1.0.7 isarray: 2.0.5 + dev: true /es-module-lexer/0.9.3: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} @@ -11258,10 +11208,6 @@ packages: resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} dev: true - /eventemitter3/4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false - /events/1.1.1: resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} engines: {node: '>=0.4.x'} @@ -11544,6 +11490,7 @@ packages: /fast-diff/1.2.0: resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: true /fast-glob/3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} @@ -13416,6 +13363,7 @@ packages: /is-map/2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true /is-nan/1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} @@ -13521,6 +13469,7 @@ packages: /is-set/2.0.2: resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true /is-shared-array-buffer/1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} @@ -13582,22 +13531,11 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-weakmap/2.0.1: - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} - dev: false - /is-weakref/1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 - /is-weakset/2.0.2: - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.3 - dev: false - /is-whitespace-character/1.0.4: resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==} dev: true @@ -13638,6 +13576,7 @@ packages: /isarray/2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true /isbot/3.6.1: resolution: {integrity: sha512-e1RmjWns87x60QyiHberWWMJGutL3+Ad0nZ8cz735iDEDDS6ApPfKSFo4EMj0PmMZ0m0ntpWIM0ADdqDFvUJPQ==} @@ -15024,6 +14963,7 @@ packages: /lodash.clonedeep/4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false /lodash.debounce/4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -16316,14 +16256,6 @@ packages: /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - /object-is/1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - dev: false - /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -16689,14 +16621,6 @@ packages: dot-case: 3.0.4 tslib: 2.4.0 - /parchment/1.1.4: - resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} - dev: true - - /parchment/2.0.0-dev.2: - resolution: {integrity: sha512-4fgRny4pPISoML08Zp7poi52Dff3E2G1ORTi2D/acJ/RiROdDAMDB6VcQNfBcmehrX5Wixp6dxh6JjLyE5yUNQ==} - dev: false - /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -17868,41 +17792,6 @@ packages: /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - /quill-autoformat/0.1.2: - resolution: {integrity: sha512-kRe2rTSmcBDg/oCxhzbjmXXOpQUl0Gak6ZCQshxek/RLvdR8o715qC0WcBRUozqaYbR6PJ+0Z/piINqlYStxWw==} - engines: {node: '>=10.15.3', yarn: '>=1.15.2'} - dependencies: - quill: 2.0.0-dev.4_wzpqp2wkmsfkgdbvlakyt3wndy - dev: false - - /quill-delta/4.2.1: - resolution: {integrity: sha512-Y2nksOj6Q+4hizre8n0dml76vLNGK4/y86EoI1d7rv6EL1bx7DPDYRmqQMPu1UqFQO/uQuVHQ3fOmm4ZSzWrfA==} - dependencies: - deep-equal: 1.1.1 - extend: 3.0.2 - fast-diff: 1.2.0 - dev: false - - /quill-delta/4.2.2: - resolution: {integrity: sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==} - dependencies: - fast-diff: 1.2.0 - lodash.clonedeep: 4.5.0 - lodash.isequal: 4.5.0 - dev: true - - /quill/2.0.0-dev.4_wzpqp2wkmsfkgdbvlakyt3wndy: - resolution: {integrity: sha512-9WmMVCEIhf3lDdhzl+i+GBDeDl0BFi65waC4Im8Y4HudEJ9kEEb1lciAz9A8pcDmLMjiMbvz84lNt/U5OBS8Vg==} - dependencies: - clone: 2.1.2 - deep-equal: 2.0.5 - eventemitter3: 4.0.7 - extend: 3.0.2 - parchment: 2.0.0-dev.2 - quill-delta: 4.2.1 - dev: false - patched: true - /quoted-printable/1.0.1: resolution: {integrity: sha512-cihC68OcGiQOjGiXuo5Jk6XHANTHl1K4JLk/xlEJRTIXfy19Sg6XzB95XonYgr+1rB88bCpr7WZE7D7AlZow4g==} hasBin: true @@ -22096,15 +21985,6 @@ packages: is-string: 1.0.7 is-symbol: 1.0.4 - /which-collection/1.0.1: - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} - dependencies: - is-map: 2.0.2 - is-set: 2.0.2 - is-weakmap: 2.0.1 - is-weakset: 2.0.2 - dev: false - /which-module/1.0.0: resolution: {integrity: sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==} dev: true