@limetech/lime-elements 39.44.1 → 39.44.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/cjs/limel-form.cjs.entry.js +55 -2
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +10806 -10737
- package/dist/collection/components/text-editor/prosemirror-adapter/editor-config.js +75 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +52 -51
- package/dist/esm/limel-form.entry.js +55 -2
- package/dist/esm/limel-prosemirror-adapter.entry.js +10806 -10737
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/p-49f810e0.entry.js +1 -0
- package/dist/lime-elements/{p-09fb0765.entry.js → p-68eda654.entry.js} +1 -1
- package/dist/types/components/text-editor/prosemirror-adapter/editor-config.d.ts +60 -0
- package/dist/types/components/text-editor/prosemirror-adapter/prosemirror-adapter.d.ts +3 -1
- package/dist/types/components.d.ts +5 -3
- package/dist/types/util/link-helper.d.ts +1 -1
- package/package.json +4 -2
- package/dist/lime-elements/p-10186cbd.entry.js +0 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Schema } from "prosemirror-model";
|
|
2
|
+
import { schema as basicSchema } from "prosemirror-schema-basic";
|
|
3
|
+
import { addListNodes } from "prosemirror-schema-list";
|
|
4
|
+
import { exampleSetup } from "prosemirror-example-setup";
|
|
5
|
+
import { keymap } from "prosemirror-keymap";
|
|
6
|
+
import { editorMenuTypesArray } from "./menu/types";
|
|
7
|
+
import { strikethrough } from "./menu/menu-schema-extender";
|
|
8
|
+
import { linkMarkSpec } from "./plugins/link/link-mark";
|
|
9
|
+
import { createLinkPlugin } from "./plugins/link/link-plugin";
|
|
10
|
+
import { createImageInserterPlugin } from "./plugins/image/inserter";
|
|
11
|
+
import { createImageViewPlugin } from "./plugins/image/view";
|
|
12
|
+
import { createMenuStateTrackingPlugin } from "./plugins/menu-state-tracking-plugin";
|
|
13
|
+
import { createActionBarInteractionPlugin } from "./plugins/menu-action-interaction-plugin";
|
|
14
|
+
import { createTriggerPlugin } from "./plugins/trigger/factory";
|
|
15
|
+
import { getTableNodes, getTableEditingPlugins } from "./plugins/table-plugin";
|
|
16
|
+
import { getImageNode } from "./plugins/image/node";
|
|
17
|
+
import { createNodeSpec } from "../utils/plugin-factory";
|
|
18
|
+
/**
|
|
19
|
+
* Builds the ProseMirror schema used by the text editor.
|
|
20
|
+
*
|
|
21
|
+
* This is the single source of truth for the editor's schema: the
|
|
22
|
+
* `limel-prosemirror-adapter` component and any test that needs the real
|
|
23
|
+
* schema both call this, so the two can never drift apart.
|
|
24
|
+
*
|
|
25
|
+
* @param options - schema configuration derived from the editor's props
|
|
26
|
+
* @returns the configured ProseMirror schema
|
|
27
|
+
*/
|
|
28
|
+
export function buildEditorSchema(options) {
|
|
29
|
+
const { customElements, contentType, language, inlineImages } = options;
|
|
30
|
+
let nodes = basicSchema.spec.nodes;
|
|
31
|
+
for (const customElement of customElements) {
|
|
32
|
+
const newNodeSpec = createNodeSpec(customElement);
|
|
33
|
+
const nodeName = customElement.tagName;
|
|
34
|
+
nodes = nodes.append({ [nodeName]: newNodeSpec });
|
|
35
|
+
}
|
|
36
|
+
nodes = addListNodes(nodes, 'paragraph block*', 'block');
|
|
37
|
+
if (contentType === 'html') {
|
|
38
|
+
nodes = nodes.append(getTableNodes());
|
|
39
|
+
}
|
|
40
|
+
nodes = nodes.append(getImageNode(language, inlineImages));
|
|
41
|
+
return new Schema({
|
|
42
|
+
nodes: nodes,
|
|
43
|
+
marks: basicSchema.spec.marks.append({
|
|
44
|
+
strikethrough: strikethrough,
|
|
45
|
+
link: linkMarkSpec,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Builds the ordered list of ProseMirror plugins used by the text editor.
|
|
51
|
+
*
|
|
52
|
+
* Plugin order is significant: ProseMirror resolves event props
|
|
53
|
+
* (`handlePaste`, `handleDOMEvents`, `handleKeyDown`) by calling plugins in
|
|
54
|
+
* this order and stopping at the first that returns a truthy value, and it
|
|
55
|
+
* chains `appendTransaction` in this order. Changing the order changes
|
|
56
|
+
* behavior. Callbacks the plugins need are injected so this can be built
|
|
57
|
+
* outside the component (e.g. in tests).
|
|
58
|
+
*
|
|
59
|
+
* @param options - the schema, command factory, converter and plugin callbacks
|
|
60
|
+
* @returns the ordered plugin list
|
|
61
|
+
*/
|
|
62
|
+
export function buildEditorPlugins(options) {
|
|
63
|
+
const { schema, menuCommandFactory, contentConverter, language, contentType, triggerCharacters, inlineImages, onNewLinkSelection, onImagePasted, onActiveItemsChange, } = options;
|
|
64
|
+
return [
|
|
65
|
+
...exampleSetup({ schema: schema, menuBar: false }),
|
|
66
|
+
keymap(menuCommandFactory.buildKeymap()),
|
|
67
|
+
createTriggerPlugin(triggerCharacters, contentConverter),
|
|
68
|
+
createLinkPlugin(onNewLinkSelection),
|
|
69
|
+
createImageInserterPlugin(onImagePasted, inlineImages),
|
|
70
|
+
createImageViewPlugin(language),
|
|
71
|
+
createMenuStateTrackingPlugin(editorMenuTypesArray, menuCommandFactory, onActiveItemsChange),
|
|
72
|
+
createActionBarInteractionPlugin(menuCommandFactory),
|
|
73
|
+
...getTableEditingPlugins(contentType === 'html'),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
import { Host, h, } from "@stencil/core";
|
|
2
2
|
import { EditorState, Selection } from "prosemirror-state";
|
|
3
3
|
import { EditorView } from "prosemirror-view";
|
|
4
|
-
import {
|
|
5
|
-
import { schema } from "prosemirror-schema-basic";
|
|
6
|
-
import { addListNodes } from "prosemirror-schema-list";
|
|
7
|
-
import { exampleSetup } from "prosemirror-example-setup";
|
|
8
|
-
import { keymap } from "prosemirror-keymap";
|
|
4
|
+
import { DOMParser } from "prosemirror-model";
|
|
9
5
|
import { MenuCommandFactory } from "./menu/menu-commands";
|
|
10
6
|
import { menuTranslationIDs, getTextEditorMenuItems } from "./menu/menu-items";
|
|
11
7
|
import { MarkdownConverter } from "../utils/markdown-converter";
|
|
12
8
|
import { HTMLConverter } from "../utils/html-converter";
|
|
13
|
-
import { EditorMenuTypes
|
|
9
|
+
import { EditorMenuTypes } from "./menu/types";
|
|
14
10
|
import translate from "../../../global/translations";
|
|
15
11
|
import { createRandomString } from "../../../util/random-string";
|
|
16
12
|
import { isItem } from "../../action-bar/is-item";
|
|
17
13
|
import { cloneDeep, debounce } from "lodash-es";
|
|
18
|
-
import { strikethrough } from "./menu/menu-schema-extender";
|
|
19
|
-
import { createLinkPlugin } from "./plugins/link/link-plugin";
|
|
20
|
-
import { linkMarkSpec } from "./plugins/link/link-mark";
|
|
21
|
-
import { createImageInserterPlugin } from "./plugins/image/inserter";
|
|
22
|
-
import { createImageViewPlugin } from "./plugins/image/view";
|
|
23
|
-
import { createMenuStateTrackingPlugin } from "./plugins/menu-state-tracking-plugin";
|
|
24
|
-
import { createActionBarInteractionPlugin } from "./plugins/menu-action-interaction-plugin";
|
|
25
|
-
import { createNodeSpec } from "../utils/plugin-factory";
|
|
26
|
-
import { createTriggerPlugin } from "./plugins/trigger/factory";
|
|
27
14
|
import { isInlineImageTag, } from "../text-editor.types";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
15
|
+
import { imageCache } from "./plugins/image/node";
|
|
16
|
+
import { buildEditorSchema, buildEditorPlugins, } from "./editor-config";
|
|
30
17
|
import { getMetadataFromDoc, hasMetadataChanged, } from "../utils/metadata-utils";
|
|
31
18
|
const DEBOUNCE_TIMEOUT = 300;
|
|
32
19
|
/**
|
|
@@ -80,6 +67,7 @@ export class ProsemirrorAdapter {
|
|
|
80
67
|
this.changeWaiting = false;
|
|
81
68
|
this.transactionFired = false;
|
|
82
69
|
this.lastClickedPos = null;
|
|
70
|
+
this.focusRestoreTimeout = null;
|
|
83
71
|
this.metadata = { images: [], links: [] };
|
|
84
72
|
/**
|
|
85
73
|
* Used to stop change event emitting as result of getting updated value from consumer
|
|
@@ -167,11 +155,20 @@ export class ProsemirrorAdapter {
|
|
|
167
155
|
//
|
|
168
156
|
// To detect this, we wait one tick after focus. If no transaction has fired by then,
|
|
169
157
|
// we assume the selection is unresolved and manually move the cursor to the last clicked position.
|
|
158
|
+
//
|
|
159
|
+
// The clicked position is only meaningful for the focus event that the
|
|
160
|
+
// click itself triggered, so it is consumed here and cleared on blur.
|
|
161
|
+
// Focus regained without a click (e.g. switching back to the window)
|
|
162
|
+
// must leave the selection untouched.
|
|
170
163
|
this.transactionFired = false;
|
|
171
|
-
setTimeout(() => {
|
|
172
|
-
|
|
164
|
+
this.focusRestoreTimeout = setTimeout(() => {
|
|
165
|
+
const clickedPos = this.lastClickedPos;
|
|
166
|
+
this.lastClickedPos = null;
|
|
167
|
+
if (!this.transactionFired &&
|
|
168
|
+
clickedPos !== null &&
|
|
169
|
+
clickedPos <= this.view.state.doc.content.size) {
|
|
173
170
|
const { doc, tr } = this.view.state;
|
|
174
|
-
const resolvedPos = doc.resolve(
|
|
171
|
+
const resolvedPos = doc.resolve(clickedPos);
|
|
175
172
|
const selection = Selection.near(resolvedPos);
|
|
176
173
|
tr.setMeta('pointer', true);
|
|
177
174
|
this.view.dispatch(tr.setSelection(selection));
|
|
@@ -203,6 +200,7 @@ export class ProsemirrorAdapter {
|
|
|
203
200
|
this.changeWaiting = false;
|
|
204
201
|
}, DEBOUNCE_TIMEOUT);
|
|
205
202
|
this.handleBlur = () => {
|
|
203
|
+
this.lastClickedPos = null;
|
|
206
204
|
this.changeEmitter.flush();
|
|
207
205
|
};
|
|
208
206
|
this.portalId = createRandomString();
|
|
@@ -290,6 +288,13 @@ export class ProsemirrorAdapter {
|
|
|
290
288
|
}
|
|
291
289
|
disconnectedCallback() {
|
|
292
290
|
var _a, _b, _c, _d, _e;
|
|
291
|
+
// The pending caret restoration must not run against a destroyed
|
|
292
|
+
// editor view. Chromium clears the click position via `blur` when a
|
|
293
|
+
// focused editor is removed, but that is not guaranteed in every
|
|
294
|
+
// browser.
|
|
295
|
+
clearTimeout(this.focusRestoreTimeout);
|
|
296
|
+
this.focusRestoreTimeout = null;
|
|
297
|
+
this.lastClickedPos = null;
|
|
293
298
|
imageCache.clear();
|
|
294
299
|
this.host.removeEventListener('open-editor-link-menu', this.handleOpenLinkMenu);
|
|
295
300
|
(_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.dom) === null || _b === void 0 ? void 0 : _b.removeEventListener('blur', this.handleBlur);
|
|
@@ -297,7 +302,7 @@ export class ProsemirrorAdapter {
|
|
|
297
302
|
(_e = this.view) === null || _e === void 0 ? void 0 : _e.destroy();
|
|
298
303
|
}
|
|
299
304
|
render() {
|
|
300
|
-
return (h(Host, { key: '
|
|
305
|
+
return (h(Host, { key: '66da30fb04c3dc303f58153a8ca6c1b185aae681', onFocus: this.handleFocus }, h("div", { key: 'fbd8f2810334973488603dd19db7ba2402a14a13', id: "editor" }), this.renderToolbar(), this.renderLinkMenu()));
|
|
301
306
|
}
|
|
302
307
|
renderToolbar() {
|
|
303
308
|
if (this.actionBarItems.length === 0 || this.ui === 'no-toolbar') {
|
|
@@ -353,23 +358,11 @@ export class ProsemirrorAdapter {
|
|
|
353
358
|
this.lastEmittedValue = this.contentConverter.serialize(this.view, this.schema);
|
|
354
359
|
}
|
|
355
360
|
initializeSchema() {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
nodes = addListNodes(nodes, 'paragraph block*', 'block');
|
|
363
|
-
if (this.contentType === 'html') {
|
|
364
|
-
nodes = nodes.append(getTableNodes());
|
|
365
|
-
}
|
|
366
|
-
nodes = nodes.append(getImageNode(this.language, this.validatedInlineImages));
|
|
367
|
-
return new Schema({
|
|
368
|
-
nodes: nodes,
|
|
369
|
-
marks: schema.spec.marks.append({
|
|
370
|
-
strikethrough: strikethrough,
|
|
371
|
-
link: linkMarkSpec,
|
|
372
|
-
}),
|
|
361
|
+
return buildEditorSchema({
|
|
362
|
+
customElements: this.customElements,
|
|
363
|
+
contentType: this.contentType,
|
|
364
|
+
language: this.language,
|
|
365
|
+
inlineImages: this.validatedInlineImages,
|
|
373
366
|
});
|
|
374
367
|
}
|
|
375
368
|
async parseInitialContent() {
|
|
@@ -386,17 +379,18 @@ export class ProsemirrorAdapter {
|
|
|
386
379
|
createEditorState(initialDoc) {
|
|
387
380
|
return EditorState.create({
|
|
388
381
|
doc: initialDoc,
|
|
389
|
-
plugins:
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
382
|
+
plugins: buildEditorPlugins({
|
|
383
|
+
schema: this.schema,
|
|
384
|
+
menuCommandFactory: this.menuCommandFactory,
|
|
385
|
+
contentConverter: this.contentConverter,
|
|
386
|
+
language: this.language,
|
|
387
|
+
contentType: this.contentType,
|
|
388
|
+
triggerCharacters: this.triggerCharacters,
|
|
389
|
+
inlineImages: this.validatedInlineImages,
|
|
390
|
+
onNewLinkSelection: this.handleNewLinkSelection,
|
|
391
|
+
onImagePasted: this.imagePasted.emit,
|
|
392
|
+
onActiveItemsChange: this.updateActiveActionBarItems,
|
|
393
|
+
}),
|
|
400
394
|
});
|
|
401
395
|
}
|
|
402
396
|
async updateView(content) {
|
|
@@ -462,9 +456,16 @@ export class ProsemirrorAdapter {
|
|
|
462
456
|
"type": "string",
|
|
463
457
|
"mutable": false,
|
|
464
458
|
"complexType": {
|
|
465
|
-
"original": "
|
|
459
|
+
"original": "ContentType",
|
|
466
460
|
"resolved": "\"html\" | \"markdown\"",
|
|
467
|
-
"references": {
|
|
461
|
+
"references": {
|
|
462
|
+
"ContentType": {
|
|
463
|
+
"location": "import",
|
|
464
|
+
"path": "./editor-config",
|
|
465
|
+
"id": "src/components/text-editor/prosemirror-adapter/editor-config.ts::ContentType",
|
|
466
|
+
"referenceLocation": "ContentType"
|
|
467
|
+
}
|
|
468
|
+
}
|
|
468
469
|
},
|
|
469
470
|
"required": false,
|
|
470
471
|
"optional": false,
|
|
@@ -20539,7 +20539,12 @@ function requireFastUri () {
|
|
|
20539
20539
|
*/
|
|
20540
20540
|
function resolve (baseURI, relativeURI, options) {
|
|
20541
20541
|
const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' };
|
|
20542
|
-
const
|
|
20542
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
20543
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
20544
|
+
if (baseMalformed || relativeMalformed) {
|
|
20545
|
+
throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
|
|
20546
|
+
}
|
|
20547
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
20543
20548
|
schemelessOptions.skipEscape = true;
|
|
20544
20549
|
return serialize(resolved, schemelessOptions)
|
|
20545
20550
|
}
|
|
@@ -20715,6 +20720,19 @@ function requireFastUri () {
|
|
|
20715
20720
|
|
|
20716
20721
|
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
20717
20722
|
|
|
20723
|
+
// Captures the authority component (between "//" and the next "/", "?" or "#"),
|
|
20724
|
+
// with or without a scheme prefix, for the literal-backslash rejection below.
|
|
20725
|
+
const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
20726
|
+
|
|
20727
|
+
// Captures the leading authority-introducer region after an optional scheme: a
|
|
20728
|
+
// run of forward slashes, backslashes, and the characters the WHATWG URL parser
|
|
20729
|
+
// removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
|
|
20730
|
+
// is exactly "//". Node treats "\" as "/" on special schemes and strips those
|
|
20731
|
+
// characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
|
|
20732
|
+
// "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
|
|
20733
|
+
// the path group (host confusion / SSRF / redirect bypass).
|
|
20734
|
+
const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
20735
|
+
|
|
20718
20736
|
/**
|
|
20719
20737
|
* @param {import('./types/index').URIComponent} parsed
|
|
20720
20738
|
* @param {RegExpMatchArray} matches
|
|
@@ -20761,6 +20779,41 @@ function requireFastUri () {
|
|
|
20761
20779
|
}
|
|
20762
20780
|
}
|
|
20763
20781
|
|
|
20782
|
+
// A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
|
|
20783
|
+
// not an authority delimiter. Reject it in the authority rather than
|
|
20784
|
+
// rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
|
|
20785
|
+
// change the resource identified by an otherwise-invalid input, and lets "\"
|
|
20786
|
+
// act as a host delimiter here while Node's native URL parses a different
|
|
20787
|
+
// host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
|
|
20788
|
+
// untouched and remains valid encoded data.
|
|
20789
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX);
|
|
20790
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
|
|
20791
|
+
parsed.error = 'URI authority must not contain a literal backslash.';
|
|
20792
|
+
malformedAuthorityOrPort = true;
|
|
20793
|
+
}
|
|
20794
|
+
|
|
20795
|
+
// Reject a malformed or whitespace-smuggled authority introducer. fast-uri
|
|
20796
|
+
// only recognizes a literal "//"; anything else in the leading separator run
|
|
20797
|
+
// (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
|
|
20798
|
+
// Node strips) means the authority fast-uri parses differs from the one Node's
|
|
20799
|
+
// URL resolves. Reject rather than rewrite, mirroring the literal-backslash
|
|
20800
|
+
// guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
|
|
20801
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
20802
|
+
if (introducerMatch !== null) {
|
|
20803
|
+
const region = introducerMatch[1];
|
|
20804
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, '');
|
|
20805
|
+
// Two or more leading separators introduce an authority.
|
|
20806
|
+
if (normalizedRegion.length >= 2) {
|
|
20807
|
+
if (normalizedRegion.slice(0, 2) !== '//') {
|
|
20808
|
+
parsed.error = parsed.error || 'URI authority must not contain a literal backslash.';
|
|
20809
|
+
malformedAuthorityOrPort = true;
|
|
20810
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
20811
|
+
parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.';
|
|
20812
|
+
malformedAuthorityOrPort = true;
|
|
20813
|
+
}
|
|
20814
|
+
}
|
|
20815
|
+
}
|
|
20816
|
+
|
|
20764
20817
|
const matches = uri.match(URI_PARSE);
|
|
20765
20818
|
|
|
20766
20819
|
if (matches) {
|
|
@@ -20818,7 +20871,7 @@ function requireFastUri () {
|
|
|
20818
20871
|
if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
20819
20872
|
// convert Unicode IDN -> ASCII IDN
|
|
20820
20873
|
try {
|
|
20821
|
-
parsed.host = URL
|
|
20874
|
+
parsed.host = new URL('http://' + parsed.host).hostname;
|
|
20822
20875
|
} catch (e) {
|
|
20823
20876
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
20824
20877
|
}
|