@vonage/vivid 5.20.0 → 5.21.0
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/alert/index.cjs +1 -1
- package/alert/index.js +1 -1
- package/banner/index.cjs +1 -1
- package/banner/index.js +1 -1
- package/bundled/definition18.cjs +1 -1
- package/bundled/definition18.js +1 -1
- package/bundled/definition6.cjs +1 -1
- package/bundled/definition6.js +1 -1
- package/bundled/vivid-element.cjs +1 -1
- package/bundled/vivid-element.js +1 -1
- package/combobox/index.cjs +1 -1
- package/combobox/index.js +1 -1
- package/custom-elements.json +2635 -2626
- package/file-picker/index.cjs +1 -1
- package/file-picker/index.js +1 -1
- package/lib/rich-text-editor/rte/features/base.d.ts +1 -0
- package/lib/rich-text-editor/rte/features/internal/history.d.ts +6 -0
- package/lib/rich-text-editor/rte/utils/ui.d.ts +2 -0
- package/locales/de-DE.cjs +178 -4
- package/locales/de-DE.js +179 -2
- package/locales/en-GB.cjs +9 -4
- package/locales/en-GB.js +10 -2
- package/locales/en-US.cjs +2 -270
- package/locales/en-US.js +1 -267
- package/locales/ja-JP.cjs +171 -4
- package/locales/ja-JP.js +172 -2
- package/locales/zh-CN.cjs +172 -4
- package/locales/zh-CN.js +173 -2
- package/package.json +6 -5
- package/rich-text-editor/index.cjs +12 -12
- package/rich-text-editor/index.js +935 -916
- package/searchable-select/index.cjs +1 -1
- package/searchable-select/index.js +1 -1
- package/switch/index.cjs +1 -1
- package/switch/index.js +1 -1
- package/tabs/index.cjs +1 -1
- package/tabs/index.js +1 -1
- package/unbundled/chunk.cjs +15 -0
- package/unbundled/chunk.js +13 -0
- package/unbundled/definition17.cjs +1 -1
- package/unbundled/definition17.js +1 -1
- package/unbundled/definition30.cjs +1 -1
- package/unbundled/definition30.js +1 -1
- package/unbundled/definition43.cjs +1 -1
- package/unbundled/definition43.js +1 -1
- package/unbundled/definition60.cjs +801 -767
- package/unbundled/definition60.js +801 -767
- package/unbundled/definition61.cjs +1 -1
- package/unbundled/definition61.js +1 -1
- package/unbundled/definition63.cjs +1 -1
- package/unbundled/definition63.js +1 -1
- package/unbundled/definition69.cjs +1 -1
- package/unbundled/definition69.js +1 -1
- package/unbundled/definition7.cjs +1 -1
- package/unbundled/definition7.js +1 -1
- package/unbundled/definition73.cjs +1 -1
- package/unbundled/definition73.js +1 -1
- package/unbundled/definition9.cjs +1 -1
- package/unbundled/definition9.js +1 -1
- package/unbundled/en-US.cjs +449 -0
- package/unbundled/en-US.js +445 -0
- package/unbundled/localized.cjs +2 -2
- package/unbundled/localized.js +1 -1
- package/unbundled/vivid-element.cjs +1 -1
- package/unbundled/vivid-element.js +1 -1
|
@@ -22,8 +22,8 @@ import { keymap } from "prosemirror-keymap";
|
|
|
22
22
|
import { autoJoin, baseKeymap, chainCommands, createParagraphNear, liftEmptyBlock, newlineInCode, splitBlockAs, toggleMark } from "prosemirror-commands";
|
|
23
23
|
import { dropCursor } from "prosemirror-dropcursor";
|
|
24
24
|
import { gapCursor } from "prosemirror-gapcursor";
|
|
25
|
-
import { closeHistory, history, redo, undo } from "prosemirror-history";
|
|
26
25
|
import DOMPurify from "dompurify";
|
|
26
|
+
import { closeHistory, history, redo, undo } from "prosemirror-history";
|
|
27
27
|
import { InputRule, inputRules, undoInputRule } from "prosemirror-inputrules";
|
|
28
28
|
import { RemoveMarkStep, ReplaceAroundStep, dropPoint } from "prosemirror-transform";
|
|
29
29
|
import { marks } from "prosemirror-schema-basic";
|
|
@@ -238,645 +238,333 @@ var FeatureState = class {
|
|
|
238
238
|
//#region src/lib/rich-text-editor/rte/features/internal/core.style.scss?inline
|
|
239
239
|
var core_style_default = ".ProseMirror{box-sizing:border-box;padding:var(--editor-padding-block) var(--editor-padding-inline);outline:none;flex:1 0 0}.ProseMirror-selectednode{--focus-stroke-gap-color:transparent;--focus-border-radius:2px;outline:none;position:relative}.ProseMirror-selectednode:after{box-shadow:0 0 0 4px color-mix(in srgb, var(--focus-stroke-color,var(--vvd-color-cta-500)), transparent 85%), inset 0 0 0 3px var(--focus-stroke-gap-color,currentColor);outline:1px solid var(--focus-stroke-color,var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset,0px));border-radius:var(--focus-border-radius,inherit);block-size:calc(100% + var(--focus-block-size-addition,4px));content:\"\";inline-size:calc(100% + var(--focus-block-size-addition,4px));display:block;position:absolute;inset-block-start:50%;inset-inline-start:50%;transform:translate(-50%,-50%)}.editor--disabled .ProseMirror{color:var(--vvd-color-neutral-300);cursor:not-allowed}";
|
|
240
240
|
//#endregion
|
|
241
|
-
//#region src/lib/rich-text-editor/rte/
|
|
242
|
-
var
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
handler
|
|
247
|
-
];
|
|
248
|
-
var UiCtx = class {
|
|
249
|
-
constructor(view, rte, props) {
|
|
250
|
-
this.view = view;
|
|
251
|
-
this.rte = rte;
|
|
252
|
-
this.props = props;
|
|
253
|
-
this.bindings = [];
|
|
254
|
-
}
|
|
255
|
-
evalProp(prop) {
|
|
256
|
-
return isPropBinding(prop) ? prop(this) : prop;
|
|
241
|
+
//#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
|
|
242
|
+
var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
|
|
243
|
+
constructor(..._args) {
|
|
244
|
+
super(..._args);
|
|
245
|
+
this.name = "RteForeignHtmlFeature";
|
|
257
246
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
} else bindFn(prop);
|
|
247
|
+
getPlugins(rte) {
|
|
248
|
+
return [this.contribution(new Plugin({ props: {
|
|
249
|
+
transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
|
|
250
|
+
clipboardParser: rte.foreignHtmlParser[impl].parser,
|
|
251
|
+
clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
|
|
252
|
+
} }))];
|
|
265
253
|
}
|
|
266
|
-
|
|
267
|
-
|
|
254
|
+
};
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
|
|
257
|
+
/**
|
|
258
|
+
* When the cursor is positioned after an inline atom node at the end of a line,
|
|
259
|
+
* browsers may display the cursor inside the atom.
|
|
260
|
+
*/
|
|
261
|
+
var atomCursorFix = ($cursor) => {
|
|
262
|
+
if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
|
|
263
|
+
const nodeBefore = $cursor.nodeBefore;
|
|
264
|
+
if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
|
|
265
|
+
return null;
|
|
266
|
+
};
|
|
267
|
+
var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
|
|
268
|
+
constructor(..._args) {
|
|
269
|
+
super(..._args);
|
|
270
|
+
this.name = "RteCursorFix";
|
|
268
271
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
getPlugins(rte) {
|
|
273
|
+
const cursorFixes = [atomCursorFix];
|
|
274
|
+
for (const markType of Object.values(rte.schema.marks)) {
|
|
275
|
+
const spec = markType.spec;
|
|
276
|
+
if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
|
|
277
|
+
}
|
|
278
|
+
return [this.contribution(new Plugin({ props: { decorations: (state) => {
|
|
279
|
+
const { $cursor } = state.selection;
|
|
280
|
+
if (!$cursor) return null;
|
|
281
|
+
let cursorFix = null;
|
|
282
|
+
for (const fn of cursorFixes) {
|
|
283
|
+
const result = fn($cursor, state);
|
|
284
|
+
if (result) Object.assign(cursorFix ??= {}, result);
|
|
285
|
+
}
|
|
286
|
+
if (!cursorFix) return null;
|
|
287
|
+
return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
|
|
288
|
+
const span = document.createElement("span");
|
|
289
|
+
span.textContent = "";
|
|
290
|
+
for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
|
|
291
|
+
return span;
|
|
292
|
+
}, { side: -1 })]);
|
|
293
|
+
} } }))];
|
|
275
294
|
}
|
|
276
295
|
};
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
296
|
+
featureFacade(RteCursorFixFeatureImpl);
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/lib/rich-text-editor/rte/features/internal/core.ts
|
|
299
|
+
/**
|
|
300
|
+
* Plugin to bring state from the host web component into ProseMirror.
|
|
301
|
+
*/
|
|
302
|
+
var hostBridgePlugin = new Plugin({ state: {
|
|
303
|
+
init() {
|
|
304
|
+
return null;
|
|
305
|
+
},
|
|
306
|
+
apply(tr, value) {
|
|
307
|
+
const meta = tr.getMeta(hostBridgePlugin);
|
|
308
|
+
if (meta) return meta;
|
|
309
|
+
else return value;
|
|
310
|
+
}
|
|
311
|
+
} });
|
|
312
|
+
var RteCoreImpl = class extends RteFeatureImpl {
|
|
313
|
+
constructor(..._args) {
|
|
314
|
+
super(..._args);
|
|
315
|
+
this.name = "RteCore";
|
|
316
|
+
this.disabled = new FeatureState(false);
|
|
317
|
+
}
|
|
318
|
+
getStyles() {
|
|
319
|
+
return [
|
|
320
|
+
this.contribution(prosemirror_default),
|
|
321
|
+
this.contribution(core_style_default),
|
|
322
|
+
this.contribution(ui_style_default)
|
|
323
|
+
];
|
|
324
|
+
}
|
|
325
|
+
getPlugins(rte) {
|
|
326
|
+
const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
|
|
327
|
+
if (!atEnd) return {
|
|
328
|
+
type: node.type,
|
|
329
|
+
attrs: node.attrs
|
|
330
|
+
};
|
|
331
|
+
return {
|
|
332
|
+
type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
|
|
333
|
+
attrs: rte.textblockAttrs.extractFromNode(node)
|
|
334
|
+
};
|
|
335
|
+
}));
|
|
336
|
+
return [
|
|
337
|
+
this.contribution(this.disabled.plugin),
|
|
338
|
+
this.contribution(new Plugin({
|
|
339
|
+
props: {
|
|
340
|
+
editable: () => !this.disabled.getValue(rte),
|
|
341
|
+
handleDOMEvents: { click: (_view, event) => {
|
|
342
|
+
if (this.disabled.getValue(rte)) {
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
} }
|
|
348
|
+
},
|
|
349
|
+
view: (view) => {
|
|
350
|
+
const popovers = view.dom.getRootNode().querySelector(".popovers");
|
|
351
|
+
const updateDisabled = () => {
|
|
352
|
+
const disabled = this.disabled.getValue(rte);
|
|
353
|
+
popovers.classList.toggle("popovers--disabled", disabled);
|
|
354
|
+
view.dom.parentElement.classList.toggle("editor--disabled", disabled);
|
|
355
|
+
};
|
|
356
|
+
updateDisabled();
|
|
357
|
+
return { update: () => {
|
|
358
|
+
updateDisabled();
|
|
359
|
+
} };
|
|
360
|
+
}
|
|
361
|
+
})),
|
|
362
|
+
this.contribution(keymap({
|
|
363
|
+
...baseKeymap,
|
|
364
|
+
Enter: enterKeyChainCommands,
|
|
365
|
+
"Shift-Enter": enterKeyChainCommands
|
|
366
|
+
})),
|
|
367
|
+
this.contribution(dropCursor()),
|
|
368
|
+
this.contribution(gapCursor()),
|
|
369
|
+
this.contribution(hostBridgePlugin)
|
|
370
|
+
];
|
|
371
|
+
}
|
|
372
|
+
getFeatures() {
|
|
373
|
+
return [
|
|
374
|
+
this,
|
|
375
|
+
new RteForeignHtmlFeatureImpl(),
|
|
376
|
+
new RteCursorFixFeatureImpl()
|
|
377
|
+
];
|
|
378
|
+
}
|
|
284
379
|
};
|
|
285
|
-
|
|
380
|
+
featureFacade(RteCoreImpl);
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/lib/rich-text-editor/rte/utils/sanitization.ts
|
|
383
|
+
var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
384
|
+
var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
385
|
+
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
386
|
+
var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
|
|
387
|
+
/**
|
|
388
|
+
* Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
|
|
389
|
+
* Returns empty string if the URL is unsafe.
|
|
390
|
+
*/
|
|
391
|
+
var sanitizeLinkHref = (url) => {
|
|
392
|
+
if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
|
|
286
393
|
const anchor = document.createElement("a");
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
return anchor;
|
|
394
|
+
anchor.setAttribute("href", url);
|
|
395
|
+
/* v8 ignore next -- since href is already validated it's probably always present @preserve */
|
|
396
|
+
return DOMPurify.sanitize(anchor, {
|
|
397
|
+
RETURN_DOM: true,
|
|
398
|
+
...domPurifyConfig
|
|
399
|
+
}).querySelector("a").getAttribute("href") ?? "";
|
|
294
400
|
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
tooltip.anchor = enabled ? props.anchor : void 0;
|
|
307
|
-
});
|
|
308
|
-
const wrapper = createDiv(ctx, {
|
|
309
|
-
className: "ui-tooltip-wrapper",
|
|
310
|
-
slot: props.slot,
|
|
311
|
-
children: [props.anchor, tooltip]
|
|
312
|
-
});
|
|
313
|
-
wrapperTargets.set(wrapper, props.anchor);
|
|
314
|
-
return wrapper;
|
|
401
|
+
/**
|
|
402
|
+
* Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
|
|
403
|
+
* Returns empty string if the URL is unsafe.
|
|
404
|
+
*/
|
|
405
|
+
var sanitizeImageSrc = (url) => {
|
|
406
|
+
const img = document.createElement("img");
|
|
407
|
+
img.setAttribute("src", url);
|
|
408
|
+
return DOMPurify.sanitize(img, {
|
|
409
|
+
RETURN_DOM: true,
|
|
410
|
+
...domPurifyConfig
|
|
411
|
+
}).querySelector("img").getAttribute("src") ?? "";
|
|
315
412
|
};
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
appearance,
|
|
328
|
-
disabled,
|
|
329
|
-
icon: props.icon,
|
|
330
|
-
autofocus: props.autofocus,
|
|
331
|
-
connotation,
|
|
332
|
-
[props.icon ? "ariaLabel" : "label"]: props.label,
|
|
333
|
-
pressed: () => Boolean(ctx.evalProp(props.active)),
|
|
334
|
-
ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
|
|
335
|
-
}, [on("click", props.onClick, (onClick) => () => {
|
|
336
|
-
if (!onClick()) ctx.view.focus();
|
|
337
|
-
})]);
|
|
338
|
-
return createOptionalTooltip(ctx, {
|
|
339
|
-
enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
|
|
340
|
-
label: props.label,
|
|
341
|
-
anchor: button,
|
|
342
|
-
slot: props.slot
|
|
343
|
-
});
|
|
344
|
-
};
|
|
345
|
-
var createMenu = (ctx, props) => {
|
|
346
|
-
const menu = ctx.rte.createComponent(Menu);
|
|
347
|
-
ctx.bindToEl(menu, {
|
|
348
|
-
className: "ui-menu",
|
|
349
|
-
autoDismiss: true,
|
|
350
|
-
ariaLabel: props.label,
|
|
351
|
-
anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
|
|
352
|
-
placement: ctx.props.popupPlacement,
|
|
353
|
-
offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
|
|
354
|
-
}, [], props.children);
|
|
355
|
-
const fragment = document.createDocumentFragment();
|
|
356
|
-
fragment.appendChild(props.trigger);
|
|
357
|
-
fragment.appendChild(menu);
|
|
358
|
-
return fragment;
|
|
359
|
-
};
|
|
360
|
-
var createMenuItem = (ctx, props) => {
|
|
361
|
-
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
362
|
-
const item = ctx.rte.createComponent(MenuItem);
|
|
363
|
-
ctx.bindToEl(item, {
|
|
364
|
-
text: props.text,
|
|
365
|
-
checked: props.checked,
|
|
366
|
-
disabled,
|
|
367
|
-
controlType: "radio",
|
|
368
|
-
checkedAppearance: "tick-only"
|
|
369
|
-
}, [on("change", props.onSelect, (onSelect) => () => {
|
|
370
|
-
if (item.checked && !item.disabled) {
|
|
371
|
-
if (ctx.evalProp(props.checked) !== item.checked) {
|
|
372
|
-
onSelect();
|
|
373
|
-
ctx.view.focus();
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
})]);
|
|
377
|
-
return item;
|
|
378
|
-
};
|
|
379
|
-
var createButtonGroup = (ctx, props) => createDiv(ctx, {
|
|
380
|
-
slot: props.slot,
|
|
381
|
-
className: "ui-button-group",
|
|
382
|
-
children: props.children
|
|
413
|
+
/**
|
|
414
|
+
* Escapes a CSS value for insertion into style attribute.
|
|
415
|
+
* E.g. "15px; background: red" => "15px"
|
|
416
|
+
*/
|
|
417
|
+
var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/lib/rich-text-editor/rte/html-parser.ts
|
|
420
|
+
var copy = (obj) => ({ ...obj });
|
|
421
|
+
var parseRulesFromSchema = (schema) => ({
|
|
422
|
+
nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
|
|
423
|
+
marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
|
|
383
424
|
});
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
425
|
+
var RteHtmlParser = class {
|
|
426
|
+
constructor(config, options) {
|
|
427
|
+
this[impl] = new RteHtmlParserImpl(config[impl], options);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Converts an HTML string to an RteDocument.
|
|
431
|
+
*/
|
|
432
|
+
parseDocument(html, options) {
|
|
433
|
+
return this[impl].parser.parse(this.parseHtml(html, options), { preserveWhitespace: true }).toJSON();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Converts an HTML string to an RteFragment.
|
|
437
|
+
*/
|
|
438
|
+
parseFragment(html, options) {
|
|
439
|
+
return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
|
|
440
|
+
}
|
|
441
|
+
parseHtml(html, options) {
|
|
442
|
+
const dom = DOMPurify.sanitize(html, {
|
|
443
|
+
RETURN_DOM: true,
|
|
444
|
+
...domPurifyConfig
|
|
445
|
+
});
|
|
446
|
+
const container = document.createDocumentFragment();
|
|
447
|
+
container.appendChild(dom);
|
|
448
|
+
options?.modifyDom?.(container);
|
|
449
|
+
return container;
|
|
396
450
|
}
|
|
397
|
-
});
|
|
398
|
-
var createOption = (ctx, props) => {
|
|
399
|
-
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
400
|
-
const option = ctx.rte.createComponent(ListboxOption);
|
|
401
|
-
ctx.bindToEl(option, {
|
|
402
|
-
value: props.value,
|
|
403
|
-
text: props.text,
|
|
404
|
-
disabled
|
|
405
|
-
});
|
|
406
|
-
return option;
|
|
407
451
|
};
|
|
408
|
-
var
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
452
|
+
var RteHtmlParserImpl = class {
|
|
453
|
+
constructor(config, options) {
|
|
454
|
+
const rules = parseRulesFromSchema(config.schema);
|
|
455
|
+
options?.modifyParseRules?.(rules);
|
|
456
|
+
this.parser = buildDomParser(config.schema, rules);
|
|
457
|
+
}
|
|
458
|
+
transform(html) {
|
|
459
|
+
return DOMPurify.sanitize(html);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var buildDomParser = (schema, { marks, nodes }) => {
|
|
463
|
+
const parserRules = [];
|
|
464
|
+
const priority = (rule) => rule.priority ?? 50;
|
|
465
|
+
function insert(rule) {
|
|
466
|
+
let i = 0;
|
|
467
|
+
for (; i < parserRules.length; i++) {
|
|
468
|
+
const next = parserRules[i];
|
|
469
|
+
if (priority(next) < priority(rule)) break;
|
|
422
470
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
select.addEventListener("vwc-popup:open", () => {
|
|
429
|
-
hideTooltip = true;
|
|
430
|
-
ctx.updateBindings();
|
|
431
|
-
});
|
|
432
|
-
select.addEventListener("vwc-popup:close", () => {
|
|
433
|
-
hideTooltip = false;
|
|
434
|
-
ctx.updateBindings();
|
|
471
|
+
parserRules.splice(i, 0, rule);
|
|
472
|
+
}
|
|
473
|
+
for (const name in marks) marks[name]?.forEach((rule) => {
|
|
474
|
+
insert(rule = copy(rule));
|
|
475
|
+
if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
|
|
435
476
|
});
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
anchor: select
|
|
477
|
+
for (const name in nodes) nodes[name]?.forEach((rule) => {
|
|
478
|
+
insert(rule = copy(rule));
|
|
479
|
+
if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
|
|
440
480
|
});
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
ctx.bindToEl(divider, {
|
|
445
|
-
className: "ui-divider",
|
|
446
|
-
orientation: "vertical"
|
|
481
|
+
parserRules.push({
|
|
482
|
+
tag: "br",
|
|
483
|
+
closeParent: true
|
|
447
484
|
});
|
|
448
|
-
return
|
|
449
|
-
};
|
|
450
|
-
var createTextField = (ctx, props) => {
|
|
451
|
-
const textField = ctx.rte.createComponent(TextField);
|
|
452
|
-
ctx.bindToEl(textField, {
|
|
453
|
-
label: props.label,
|
|
454
|
-
value: props.value,
|
|
455
|
-
placeholder: props.placeholder,
|
|
456
|
-
slot: props.slot,
|
|
457
|
-
autofocus: props.autofocus,
|
|
458
|
-
type: props.type,
|
|
459
|
-
helperText: props.helperText
|
|
460
|
-
}, [on("input", props.onInput, (onInput) => () => {
|
|
461
|
-
onInput(textField.value);
|
|
462
|
-
})]);
|
|
463
|
-
return textField;
|
|
464
|
-
};
|
|
465
|
-
var createText = (ctx, props) => {
|
|
466
|
-
const textNode = document.createTextNode("");
|
|
467
|
-
ctx.bindToEl(textNode, { textContent: props.text });
|
|
468
|
-
return textNode;
|
|
485
|
+
return new DOMParser(schema, parserRules);
|
|
469
486
|
};
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
function cleanup() {
|
|
476
|
-
for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
|
|
477
|
-
listeners.length = 0;
|
|
478
|
-
currentEl = null;
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/lib/rich-text-editor/rte/html-serializer.ts
|
|
489
|
+
var RteHtmlSerializer = class {
|
|
490
|
+
constructor(config, options) {
|
|
491
|
+
this[impl] = new RteHtmlSerializerImpl(config[impl], options);
|
|
479
492
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
listeners.push({
|
|
492
|
-
el,
|
|
493
|
-
type,
|
|
494
|
-
handler: listener
|
|
495
|
-
});
|
|
496
|
-
}
|
|
493
|
+
/**
|
|
494
|
+
* Converts an RteDocument to an HTML string.
|
|
495
|
+
*/
|
|
496
|
+
serializeDocument(doc, options) {
|
|
497
|
+
return this[impl].serializeFragment(doc.content, options);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Converts an RteFragment to an HTML string.
|
|
501
|
+
*/
|
|
502
|
+
serializeFragment(fragment, options) {
|
|
503
|
+
return this[impl].serializeFragment(fragment, options);
|
|
497
504
|
}
|
|
498
|
-
const processSlot = () => {
|
|
499
|
-
const first = slot.assignedElements({ flatten: true })[0];
|
|
500
|
-
if (first === currentEl) return;
|
|
501
|
-
cleanup();
|
|
502
|
-
if (first) {
|
|
503
|
-
currentEl = first;
|
|
504
|
-
applyPropsAndEvents(first);
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
slot.addEventListener("slotchange", processSlot);
|
|
508
|
-
queueMicrotask(processSlot);
|
|
509
|
-
return slot;
|
|
510
505
|
};
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
506
|
+
var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
|
|
507
|
+
constructor(config, options) {
|
|
508
|
+
this.config = config;
|
|
509
|
+
const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
|
|
510
|
+
Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
|
|
511
|
+
Object.assign(serializers.marks, options?.serializers?.marks ?? {});
|
|
512
|
+
this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
|
|
517
513
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
return [this.contribution({
|
|
527
|
-
section: "history",
|
|
528
|
-
render: (ctx) => createButton(ctx, {
|
|
529
|
-
label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
|
|
530
|
-
icon: "undo-line",
|
|
531
|
-
disabled: (ctx) => !undo(ctx.view.state),
|
|
532
|
-
onClick: () => {
|
|
533
|
-
const { state, dispatch } = ctx.view;
|
|
534
|
-
undo(state, dispatch);
|
|
535
|
-
}
|
|
536
|
-
})
|
|
537
|
-
}, 1), this.contribution({
|
|
538
|
-
section: "history",
|
|
539
|
-
render: (ctx) => createButton(ctx, {
|
|
540
|
-
label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
|
|
541
|
-
icon: "redo-line",
|
|
542
|
-
disabled: (ctx) => !redo(ctx.view.state),
|
|
543
|
-
onClick: () => {
|
|
544
|
-
const { state, dispatch } = ctx.view;
|
|
545
|
-
redo(state, dispatch);
|
|
546
|
-
}
|
|
547
|
-
})
|
|
548
|
-
}, 2)];
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
//#endregion
|
|
552
|
-
//#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
|
|
553
|
-
var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
|
|
554
|
-
constructor(..._args) {
|
|
555
|
-
super(..._args);
|
|
556
|
-
this.name = "RteForeignHtmlFeature";
|
|
557
|
-
}
|
|
558
|
-
getPlugins(rte) {
|
|
559
|
-
return [this.contribution(new Plugin({ props: {
|
|
560
|
-
transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
|
|
561
|
-
clipboardParser: rte.foreignHtmlParser[impl].parser,
|
|
562
|
-
clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
|
|
563
|
-
} }))];
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
//#endregion
|
|
567
|
-
//#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
|
|
568
|
-
/**
|
|
569
|
-
* When the cursor is positioned after an inline atom node at the end of a line,
|
|
570
|
-
* browsers may display the cursor inside the atom.
|
|
571
|
-
*/
|
|
572
|
-
var atomCursorFix = ($cursor) => {
|
|
573
|
-
if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
|
|
574
|
-
const nodeBefore = $cursor.nodeBefore;
|
|
575
|
-
if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
|
|
576
|
-
return null;
|
|
577
|
-
};
|
|
578
|
-
var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
|
|
579
|
-
constructor(..._args) {
|
|
580
|
-
super(..._args);
|
|
581
|
-
this.name = "RteCursorFix";
|
|
582
|
-
}
|
|
583
|
-
getPlugins(rte) {
|
|
584
|
-
const cursorFixes = [atomCursorFix];
|
|
585
|
-
for (const markType of Object.values(rte.schema.marks)) {
|
|
586
|
-
const spec = markType.spec;
|
|
587
|
-
if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
|
|
514
|
+
static domSerializersFromSchema(schema) {
|
|
515
|
+
const result = {
|
|
516
|
+
nodes: {},
|
|
517
|
+
marks: {}
|
|
518
|
+
};
|
|
519
|
+
for (const name in schema.marks) {
|
|
520
|
+
const toDOM = schema.marks[name].spec.toDOM;
|
|
521
|
+
if (toDOM) result.marks[name] = toDOM;
|
|
588
522
|
}
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (result) Object.assign(cursorFix ??= {}, result);
|
|
596
|
-
}
|
|
597
|
-
if (!cursorFix) return null;
|
|
598
|
-
return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
|
|
599
|
-
const span = document.createElement("span");
|
|
600
|
-
span.textContent = "";
|
|
601
|
-
for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
|
|
602
|
-
return span;
|
|
603
|
-
}, { side: -1 })]);
|
|
604
|
-
} } }))];
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
featureFacade(RteCursorFixFeatureImpl);
|
|
608
|
-
//#endregion
|
|
609
|
-
//#region src/lib/rich-text-editor/rte/features/internal/core.ts
|
|
610
|
-
/**
|
|
611
|
-
* Plugin to bring state from the host web component into ProseMirror.
|
|
612
|
-
*/
|
|
613
|
-
var hostBridgePlugin = new Plugin({ state: {
|
|
614
|
-
init() {
|
|
615
|
-
return null;
|
|
616
|
-
},
|
|
617
|
-
apply(tr, value) {
|
|
618
|
-
const meta = tr.getMeta(hostBridgePlugin);
|
|
619
|
-
if (meta) return meta;
|
|
620
|
-
else return value;
|
|
621
|
-
}
|
|
622
|
-
} });
|
|
623
|
-
var RteCoreImpl = class extends RteFeatureImpl {
|
|
624
|
-
constructor(..._args) {
|
|
625
|
-
super(..._args);
|
|
626
|
-
this.name = "RteCore";
|
|
627
|
-
this.disabled = new FeatureState(false);
|
|
628
|
-
}
|
|
629
|
-
getStyles() {
|
|
630
|
-
return [
|
|
631
|
-
this.contribution(prosemirror_default),
|
|
632
|
-
this.contribution(core_style_default),
|
|
633
|
-
this.contribution(ui_style_default)
|
|
634
|
-
];
|
|
635
|
-
}
|
|
636
|
-
getPlugins(rte) {
|
|
637
|
-
const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
|
|
638
|
-
if (!atEnd) return {
|
|
639
|
-
type: node.type,
|
|
640
|
-
attrs: node.attrs
|
|
641
|
-
};
|
|
642
|
-
return {
|
|
643
|
-
type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
|
|
644
|
-
attrs: rte.textblockAttrs.extractFromNode(node)
|
|
645
|
-
};
|
|
646
|
-
}));
|
|
647
|
-
return [
|
|
648
|
-
this.contribution(this.disabled.plugin),
|
|
649
|
-
this.contribution(new Plugin({
|
|
650
|
-
props: {
|
|
651
|
-
editable: () => !this.disabled.getValue(rte),
|
|
652
|
-
handleDOMEvents: { click: (_view, event) => {
|
|
653
|
-
if (this.disabled.getValue(rte)) {
|
|
654
|
-
event.preventDefault();
|
|
655
|
-
return true;
|
|
656
|
-
}
|
|
657
|
-
return false;
|
|
658
|
-
} }
|
|
659
|
-
},
|
|
660
|
-
view: (view) => {
|
|
661
|
-
const popovers = view.dom.getRootNode().querySelector(".popovers");
|
|
662
|
-
const updateDisabled = () => {
|
|
663
|
-
const disabled = this.disabled.getValue(rte);
|
|
664
|
-
popovers.classList.toggle("popovers--disabled", disabled);
|
|
665
|
-
view.dom.parentElement.classList.toggle("editor--disabled", disabled);
|
|
666
|
-
};
|
|
667
|
-
updateDisabled();
|
|
668
|
-
return { update: () => {
|
|
669
|
-
updateDisabled();
|
|
670
|
-
} };
|
|
671
|
-
}
|
|
672
|
-
})),
|
|
673
|
-
this.contribution(keymap({
|
|
674
|
-
...baseKeymap,
|
|
675
|
-
Enter: enterKeyChainCommands,
|
|
676
|
-
"Shift-Enter": enterKeyChainCommands
|
|
677
|
-
})),
|
|
678
|
-
this.contribution(dropCursor()),
|
|
679
|
-
this.contribution(gapCursor()),
|
|
680
|
-
this.contribution(hostBridgePlugin)
|
|
681
|
-
];
|
|
523
|
+
for (const name in schema.nodes) {
|
|
524
|
+
const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
|
|
525
|
+
if (toDOM) result.nodes[name] = toDOM;
|
|
526
|
+
}
|
|
527
|
+
result.nodes.text = (node) => document.createTextNode(node.text);
|
|
528
|
+
return result;
|
|
682
529
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
530
|
+
serializeFragment(fragment, options) {
|
|
531
|
+
const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
|
|
532
|
+
const serializedFragment = this.serializer.serializeFragment(parsedFragment);
|
|
533
|
+
const container = document.createDocumentFragment();
|
|
534
|
+
container.appendChild(serializedFragment);
|
|
535
|
+
options?.modifyDom?.(container);
|
|
536
|
+
const output = document.createElement("div");
|
|
537
|
+
output.appendChild(container);
|
|
538
|
+
return output.innerHTML;
|
|
690
539
|
}
|
|
691
540
|
};
|
|
692
|
-
featureFacade(RteCoreImpl);
|
|
693
541
|
//#endregion
|
|
694
|
-
//#region src/lib/rich-text-editor/rte/
|
|
695
|
-
var
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
* Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
|
|
701
|
-
* Returns empty string if the URL is unsafe.
|
|
702
|
-
*/
|
|
703
|
-
var sanitizeLinkHref = (url) => {
|
|
704
|
-
if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
|
|
705
|
-
const anchor = document.createElement("a");
|
|
706
|
-
anchor.setAttribute("href", url);
|
|
707
|
-
/* v8 ignore next -- since href is already validated it's probably always present @preserve */
|
|
708
|
-
return DOMPurify.sanitize(anchor, {
|
|
709
|
-
RETURN_DOM: true,
|
|
710
|
-
...domPurifyConfig
|
|
711
|
-
}).querySelector("a").getAttribute("href") ?? "";
|
|
712
|
-
};
|
|
713
|
-
/**
|
|
714
|
-
* Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
|
|
715
|
-
* Returns empty string if the URL is unsafe.
|
|
716
|
-
*/
|
|
717
|
-
var sanitizeImageSrc = (url) => {
|
|
718
|
-
const img = document.createElement("img");
|
|
719
|
-
img.setAttribute("src", url);
|
|
720
|
-
return DOMPurify.sanitize(img, {
|
|
721
|
-
RETURN_DOM: true,
|
|
722
|
-
...domPurifyConfig
|
|
723
|
-
}).querySelector("img").getAttribute("src") ?? "";
|
|
542
|
+
//#region src/lib/rich-text-editor/rte/instance.ts
|
|
543
|
+
var parseDocument = (schema, doc) => {
|
|
544
|
+
const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
|
|
545
|
+
if (!node) throw new Error("Document could not be parsed");
|
|
546
|
+
node.check();
|
|
547
|
+
return node;
|
|
724
548
|
};
|
|
725
|
-
|
|
726
|
-
* Escapes a CSS value for insertion into style attribute.
|
|
727
|
-
* E.g. "15px; background: red" => "15px"
|
|
728
|
-
*/
|
|
729
|
-
var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
|
|
730
|
-
//#endregion
|
|
731
|
-
//#region src/lib/rich-text-editor/rte/html-parser.ts
|
|
732
|
-
var copy = (obj) => ({ ...obj });
|
|
733
|
-
var parseRulesFromSchema = (schema) => ({
|
|
734
|
-
nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
|
|
735
|
-
marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
|
|
736
|
-
});
|
|
737
|
-
var RteHtmlParser = class {
|
|
549
|
+
var RteInstance = class {
|
|
738
550
|
constructor(config, options) {
|
|
739
|
-
this
|
|
551
|
+
this.options = options;
|
|
552
|
+
this.feature = (Feature, featureId) => {
|
|
553
|
+
return this[impl].getPublicInterface(Feature, featureId);
|
|
554
|
+
};
|
|
555
|
+
this[impl] = new RteInstanceImpl(this, config, options);
|
|
740
556
|
}
|
|
741
557
|
/**
|
|
742
|
-
*
|
|
558
|
+
* Returns the current document state.
|
|
743
559
|
*/
|
|
744
|
-
|
|
745
|
-
return this[impl].
|
|
560
|
+
getDocument() {
|
|
561
|
+
return this[impl].state.doc.toJSON();
|
|
746
562
|
}
|
|
747
563
|
/**
|
|
748
|
-
*
|
|
564
|
+
* Reset the editor to its initial state. Optionally, an initial document can be provided.
|
|
749
565
|
*/
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
parseHtml(html, options) {
|
|
754
|
-
const dom = DOMPurify.sanitize(html, {
|
|
755
|
-
RETURN_DOM: true,
|
|
756
|
-
...domPurifyConfig
|
|
757
|
-
});
|
|
758
|
-
const container = document.createDocumentFragment();
|
|
759
|
-
container.appendChild(dom);
|
|
760
|
-
options?.modifyDom?.(container);
|
|
761
|
-
return container;
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
var RteHtmlParserImpl = class {
|
|
765
|
-
constructor(config, options) {
|
|
766
|
-
const rules = parseRulesFromSchema(config.schema);
|
|
767
|
-
options?.modifyParseRules?.(rules);
|
|
768
|
-
this.parser = buildDomParser(config.schema, rules);
|
|
769
|
-
}
|
|
770
|
-
transform(html) {
|
|
771
|
-
return DOMPurify.sanitize(html);
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
var buildDomParser = (schema, { marks, nodes }) => {
|
|
775
|
-
const parserRules = [];
|
|
776
|
-
const priority = (rule) => rule.priority ?? 50;
|
|
777
|
-
function insert(rule) {
|
|
778
|
-
let i = 0;
|
|
779
|
-
for (; i < parserRules.length; i++) {
|
|
780
|
-
const next = parserRules[i];
|
|
781
|
-
if (priority(next) < priority(rule)) break;
|
|
782
|
-
}
|
|
783
|
-
parserRules.splice(i, 0, rule);
|
|
784
|
-
}
|
|
785
|
-
for (const name in marks) marks[name]?.forEach((rule) => {
|
|
786
|
-
insert(rule = copy(rule));
|
|
787
|
-
if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
|
|
788
|
-
});
|
|
789
|
-
for (const name in nodes) nodes[name]?.forEach((rule) => {
|
|
790
|
-
insert(rule = copy(rule));
|
|
791
|
-
if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
|
|
792
|
-
});
|
|
793
|
-
parserRules.push({
|
|
794
|
-
tag: "br",
|
|
795
|
-
closeParent: true
|
|
796
|
-
});
|
|
797
|
-
return new DOMParser(schema, parserRules);
|
|
798
|
-
};
|
|
799
|
-
//#endregion
|
|
800
|
-
//#region src/lib/rich-text-editor/rte/html-serializer.ts
|
|
801
|
-
var RteHtmlSerializer = class {
|
|
802
|
-
constructor(config, options) {
|
|
803
|
-
this[impl] = new RteHtmlSerializerImpl(config[impl], options);
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Converts an RteDocument to an HTML string.
|
|
807
|
-
*/
|
|
808
|
-
serializeDocument(doc, options) {
|
|
809
|
-
return this[impl].serializeFragment(doc.content, options);
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Converts an RteFragment to an HTML string.
|
|
813
|
-
*/
|
|
814
|
-
serializeFragment(fragment, options) {
|
|
815
|
-
return this[impl].serializeFragment(fragment, options);
|
|
816
|
-
}
|
|
817
|
-
};
|
|
818
|
-
var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
|
|
819
|
-
constructor(config, options) {
|
|
820
|
-
this.config = config;
|
|
821
|
-
const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
|
|
822
|
-
Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
|
|
823
|
-
Object.assign(serializers.marks, options?.serializers?.marks ?? {});
|
|
824
|
-
this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
|
|
825
|
-
}
|
|
826
|
-
static domSerializersFromSchema(schema) {
|
|
827
|
-
const result = {
|
|
828
|
-
nodes: {},
|
|
829
|
-
marks: {}
|
|
830
|
-
};
|
|
831
|
-
for (const name in schema.marks) {
|
|
832
|
-
const toDOM = schema.marks[name].spec.toDOM;
|
|
833
|
-
if (toDOM) result.marks[name] = toDOM;
|
|
834
|
-
}
|
|
835
|
-
for (const name in schema.nodes) {
|
|
836
|
-
const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
|
|
837
|
-
if (toDOM) result.nodes[name] = toDOM;
|
|
838
|
-
}
|
|
839
|
-
result.nodes.text = (node) => document.createTextNode(node.text);
|
|
840
|
-
return result;
|
|
841
|
-
}
|
|
842
|
-
serializeFragment(fragment, options) {
|
|
843
|
-
const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
|
|
844
|
-
const serializedFragment = this.serializer.serializeFragment(parsedFragment);
|
|
845
|
-
const container = document.createDocumentFragment();
|
|
846
|
-
container.appendChild(serializedFragment);
|
|
847
|
-
options?.modifyDom?.(container);
|
|
848
|
-
const output = document.createElement("div");
|
|
849
|
-
output.appendChild(container);
|
|
850
|
-
return output.innerHTML;
|
|
851
|
-
}
|
|
852
|
-
};
|
|
853
|
-
//#endregion
|
|
854
|
-
//#region src/lib/rich-text-editor/rte/instance.ts
|
|
855
|
-
var parseDocument = (schema, doc) => {
|
|
856
|
-
const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
|
|
857
|
-
if (!node) throw new Error("Document could not be parsed");
|
|
858
|
-
node.check();
|
|
859
|
-
return node;
|
|
860
|
-
};
|
|
861
|
-
var RteInstance = class {
|
|
862
|
-
constructor(config, options) {
|
|
863
|
-
this.options = options;
|
|
864
|
-
this.feature = (Feature, featureId) => {
|
|
865
|
-
return this[impl].getPublicInterface(Feature, featureId);
|
|
866
|
-
};
|
|
867
|
-
this[impl] = new RteInstanceImpl(this, config, options);
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Returns the current document state.
|
|
871
|
-
*/
|
|
872
|
-
getDocument() {
|
|
873
|
-
return this[impl].state.doc.toJSON();
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Reset the editor to its initial state. Optionally, an initial document can be provided.
|
|
877
|
-
*/
|
|
878
|
-
reset(initialDocument) {
|
|
879
|
-
this[impl].reset(initialDocument);
|
|
566
|
+
reset(initialDocument) {
|
|
567
|
+
this[impl].reset(initialDocument);
|
|
880
568
|
}
|
|
881
569
|
/**
|
|
882
570
|
* Replaces the current selection with the given content.
|
|
@@ -991,171 +679,487 @@ var RteInstanceImpl = class {
|
|
|
991
679
|
this.dispatchTransaction(this.tr.setMeta(hostBridgePlugin, hostState));
|
|
992
680
|
}
|
|
993
681
|
};
|
|
994
|
-
//#endregion
|
|
995
|
-
//#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
|
|
996
|
-
var TextblockAttrs = class {
|
|
997
|
-
constructor(specs) {
|
|
998
|
-
this.specs = specs;
|
|
999
|
-
this.attrs = {};
|
|
1000
|
-
for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
|
|
1001
|
-
}
|
|
1002
|
-
fromDOM(dom) {
|
|
1003
|
-
return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
|
|
1004
|
-
}
|
|
1005
|
-
getStyle(node) {
|
|
1006
|
-
return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
|
|
1007
|
-
}
|
|
1008
|
-
getDOMAttrsProperties(node) {
|
|
1009
|
-
if (!this.specs.length) return {};
|
|
1010
|
-
return { style: this.getStyle(node) };
|
|
1011
|
-
}
|
|
1012
|
-
getDOMAttrs(node) {
|
|
1013
|
-
if (!this.specs.length) return [];
|
|
1014
|
-
return [this.getDOMAttrsProperties(node)];
|
|
1015
|
-
}
|
|
1016
|
-
extractFromNode(node) {
|
|
1017
|
-
return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
|
|
1018
|
-
}
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
|
|
684
|
+
var TextblockAttrs = class {
|
|
685
|
+
constructor(specs) {
|
|
686
|
+
this.specs = specs;
|
|
687
|
+
this.attrs = {};
|
|
688
|
+
for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
|
|
689
|
+
}
|
|
690
|
+
fromDOM(dom) {
|
|
691
|
+
return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
|
|
692
|
+
}
|
|
693
|
+
getStyle(node) {
|
|
694
|
+
return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
|
|
695
|
+
}
|
|
696
|
+
getDOMAttrsProperties(node) {
|
|
697
|
+
if (!this.specs.length) return {};
|
|
698
|
+
return { style: this.getStyle(node) };
|
|
699
|
+
}
|
|
700
|
+
getDOMAttrs(node) {
|
|
701
|
+
if (!this.specs.length) return [];
|
|
702
|
+
return [this.getDOMAttrsProperties(node)];
|
|
703
|
+
}
|
|
704
|
+
extractFromNode(node) {
|
|
705
|
+
return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
//#endregion
|
|
709
|
+
//#region src/lib/rich-text-editor/rte/utils/textblock-marks.ts
|
|
710
|
+
var TextblockMarks = class {
|
|
711
|
+
constructor(specs) {
|
|
712
|
+
this.specs = specs;
|
|
713
|
+
}
|
|
714
|
+
getAllowedMarksForNode(nodeName) {
|
|
715
|
+
return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
|
|
716
|
+
}
|
|
717
|
+
getReferencedNodeNames() {
|
|
718
|
+
return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/lib/rich-text-editor/rte/view.ts
|
|
723
|
+
/**
|
|
724
|
+
* Converts an RteDocument to an RteView tree for rendering outside the editor.
|
|
725
|
+
* The logic is adapted from ProseMirror's DOM serialization to ensure the resulting structure
|
|
726
|
+
* matches the editor's HTML structure.
|
|
727
|
+
*/
|
|
728
|
+
var convertToView = (doc, ctx) => {
|
|
729
|
+
const schema = ctx.config[impl].schema;
|
|
730
|
+
const getMarkRank = (mark) => schema.marks[mark.type].rank;
|
|
731
|
+
const marksEqual = (a, b) => {
|
|
732
|
+
if (a.type !== b.type) return false;
|
|
733
|
+
const attrsA = a.attrs;
|
|
734
|
+
const attrsB = b.attrs;
|
|
735
|
+
if (attrsA === attrsB) return true;
|
|
736
|
+
/* v8 ignore next -- not currently reachable since all attrs are required @preserve */
|
|
737
|
+
if (!attrsA || !attrsB) return false;
|
|
738
|
+
const keysA = Object.keys(attrsA);
|
|
739
|
+
const keysB = Object.keys(attrsB);
|
|
740
|
+
/* v8 ignore next -- not currently reachable since all attrs are required @preserve */
|
|
741
|
+
if (keysA.length !== keysB.length) return false;
|
|
742
|
+
for (const key of keysA) if (attrsA[key] !== attrsB[key]) return false;
|
|
743
|
+
return true;
|
|
744
|
+
};
|
|
745
|
+
const convertFragment = (nodes) => {
|
|
746
|
+
const result = [];
|
|
747
|
+
const active = [];
|
|
748
|
+
const activeContents = [result];
|
|
749
|
+
for (const node of nodes) {
|
|
750
|
+
const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
|
|
751
|
+
let keep = 0;
|
|
752
|
+
let rendered = 0;
|
|
753
|
+
while (keep < active.length && rendered < nodeMarks.length) {
|
|
754
|
+
if (!marksEqual(active[keep], nodeMarks[rendered])) break;
|
|
755
|
+
keep++;
|
|
756
|
+
rendered++;
|
|
757
|
+
}
|
|
758
|
+
while (active.length > keep) {
|
|
759
|
+
active.pop();
|
|
760
|
+
activeContents.pop();
|
|
761
|
+
}
|
|
762
|
+
while (rendered < nodeMarks.length) {
|
|
763
|
+
const mark = nodeMarks[rendered];
|
|
764
|
+
const markContent = [];
|
|
765
|
+
activeContents[activeContents.length - 1].push({
|
|
766
|
+
type: "mark",
|
|
767
|
+
mark,
|
|
768
|
+
children: {
|
|
769
|
+
type: "fragment",
|
|
770
|
+
content: markContent,
|
|
771
|
+
[impl]: ctx
|
|
772
|
+
},
|
|
773
|
+
[impl]: ctx
|
|
774
|
+
});
|
|
775
|
+
active.push(mark);
|
|
776
|
+
activeContents.push(markContent);
|
|
777
|
+
rendered++;
|
|
778
|
+
}
|
|
779
|
+
activeContents[activeContents.length - 1].push({
|
|
780
|
+
type: "node",
|
|
781
|
+
node,
|
|
782
|
+
children: convertFragment(node.content ?? []),
|
|
783
|
+
[impl]: ctx
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
type: "fragment",
|
|
788
|
+
content: result,
|
|
789
|
+
[impl]: ctx
|
|
790
|
+
};
|
|
791
|
+
};
|
|
792
|
+
return {
|
|
793
|
+
type: "node",
|
|
794
|
+
node: doc,
|
|
795
|
+
children: convertFragment(doc.content),
|
|
796
|
+
[impl]: ctx
|
|
797
|
+
};
|
|
798
|
+
};
|
|
799
|
+
//#endregion
|
|
800
|
+
//#region src/lib/rich-text-editor/rte/config.ts
|
|
801
|
+
var RteConfig = class {
|
|
802
|
+
constructor(features) {
|
|
803
|
+
this[impl] = new RteConfigImpl(features);
|
|
804
|
+
}
|
|
805
|
+
instantiateEditor(options) {
|
|
806
|
+
return new RteInstance(this, options);
|
|
807
|
+
}
|
|
808
|
+
instantiateView(document, options) {
|
|
809
|
+
return convertToView(document, {
|
|
810
|
+
config: this,
|
|
811
|
+
options: { ...options }
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
var RteConfigImpl = class {
|
|
816
|
+
constructor(featuresFacades) {
|
|
817
|
+
const resolveFeatures = (features) => features.flatMap((f) => f.getFeatures().flatMap((subFeature) => subFeature === f ? f : resolveFeatures([subFeature])));
|
|
818
|
+
this.features = resolveFeatures(featuresFacades.map(getFeatureImpl));
|
|
819
|
+
this.featureMap = /* @__PURE__ */ new Map();
|
|
820
|
+
for (const f of this.features) {
|
|
821
|
+
if (this.featureMap.has(f.name)) throw new Error(`Duplicate feature: ${f.name}`);
|
|
822
|
+
this.featureMap.set(f.name, f);
|
|
823
|
+
}
|
|
824
|
+
this.featureFacadesMap = /* @__PURE__ */ new Map();
|
|
825
|
+
for (const facade of featuresFacades) {
|
|
826
|
+
const FacadeClass = facade.constructor;
|
|
827
|
+
const feature = getFeatureImpl(facade);
|
|
828
|
+
const instances = this.featureFacadesMap.get(FacadeClass);
|
|
829
|
+
if (instances) instances.push(feature);
|
|
830
|
+
else this.featureFacadesMap.set(FacadeClass, [feature]);
|
|
831
|
+
}
|
|
832
|
+
if (!this.featureMap.has("RteBase")) throw new Error("RteBase feature is required");
|
|
833
|
+
if (this.featureMap.has("RteLinkFeature") && !this.featureMap.has("RteToolbarFeature")) throw new Error("RteToolbarFeature is required for RteLinkFeature");
|
|
834
|
+
this.textblockAttrs = new TextblockAttrs(sortedContributions(this.features.flatMap((f) => f.getTextblockAttrs())));
|
|
835
|
+
this.textblockMarks = new TextblockMarks(sortedContributions(this.features.flatMap((f) => f.getTextblockMarks())));
|
|
836
|
+
const schemaContributions = sortedContributions(this.features.flatMap((f) => f.getSchema(this.textblockAttrs, this.textblockMarks)));
|
|
837
|
+
const schemaSpec = {
|
|
838
|
+
nodes: {},
|
|
839
|
+
marks: {}
|
|
840
|
+
};
|
|
841
|
+
for (const schema of schemaContributions) {
|
|
842
|
+
Object.assign(schemaSpec.nodes, schema.nodes ?? {});
|
|
843
|
+
Object.assign(schemaSpec.marks, schema.marks ?? {});
|
|
844
|
+
}
|
|
845
|
+
for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
|
|
846
|
+
this.schema = new Schema(schemaSpec);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
//#endregion
|
|
850
|
+
//#region src/lib/rich-text-editor/rte/utils/ui.ts
|
|
851
|
+
var isPropBinding = (prop) => typeof prop === "function";
|
|
852
|
+
var on = (event, prop, handler) => [
|
|
853
|
+
event,
|
|
854
|
+
prop,
|
|
855
|
+
handler
|
|
856
|
+
];
|
|
857
|
+
var UiCtx = class {
|
|
858
|
+
constructor(view, rte, props) {
|
|
859
|
+
this.view = view;
|
|
860
|
+
this.rte = rte;
|
|
861
|
+
this.props = props;
|
|
862
|
+
this.bindings = [];
|
|
863
|
+
}
|
|
864
|
+
evalProp(prop) {
|
|
865
|
+
return isPropBinding(prop) ? prop(this) : prop;
|
|
866
|
+
}
|
|
867
|
+
bindProp(prop, bindFn) {
|
|
868
|
+
if (prop === void 0) return;
|
|
869
|
+
else if (isPropBinding(prop)) {
|
|
870
|
+
const binding = () => bindFn(prop(this));
|
|
871
|
+
this.bindings.push(binding);
|
|
872
|
+
binding();
|
|
873
|
+
} else bindFn(prop);
|
|
874
|
+
}
|
|
875
|
+
updateBindings() {
|
|
876
|
+
for (const binding of this.bindings) binding();
|
|
877
|
+
}
|
|
878
|
+
shouldReturnFocusToEditor() {
|
|
879
|
+
return this.evalProp(this.props.shouldReturnFocusToEditor);
|
|
880
|
+
}
|
|
881
|
+
bindToEl(target, props = {}, events = [], children = []) {
|
|
882
|
+
for (const name in props) this.bindProp(props[name], (value) => {
|
|
883
|
+
target[name] = value;
|
|
884
|
+
});
|
|
885
|
+
for (const [name, value, bindFn] of events) if (value) target.addEventListener(name, bindFn(value));
|
|
886
|
+
for (const child of children) target.appendChild(child);
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
var createDiv = (ctx, props) => {
|
|
890
|
+
const div = document.createElement("div");
|
|
891
|
+
ctx.bindToEl(div, {
|
|
892
|
+
className: props.className,
|
|
893
|
+
slot: props.slot
|
|
894
|
+
}, [], props.children);
|
|
895
|
+
return div;
|
|
896
|
+
};
|
|
897
|
+
var createAnchor = (ctx, props) => {
|
|
898
|
+
const anchor = document.createElement("a");
|
|
899
|
+
ctx.bindToEl(anchor, {
|
|
900
|
+
href: props.href,
|
|
901
|
+
target: props.target,
|
|
902
|
+
rel: props.rel,
|
|
903
|
+
className: props.className
|
|
904
|
+
}, [], props.children);
|
|
905
|
+
return anchor;
|
|
906
|
+
};
|
|
907
|
+
var wrapperTargets = /* @__PURE__ */ new WeakMap();
|
|
908
|
+
var createOptionalTooltip = (ctx, props) => {
|
|
909
|
+
const tooltip = ctx.rte.createComponent(Tooltip);
|
|
910
|
+
tooltip.setAttribute("exportparts", "vvd-theme-alternate");
|
|
911
|
+
ctx.bindToEl(tooltip, {
|
|
912
|
+
className: "ui-tooltip",
|
|
913
|
+
text: props.label,
|
|
914
|
+
placement: ctx.props.popupPlacement
|
|
915
|
+
});
|
|
916
|
+
ctx.bindProp(props.enabled, (enabled) => {
|
|
917
|
+
if (!enabled) tooltip.open = false;
|
|
918
|
+
tooltip.anchor = enabled ? props.anchor : void 0;
|
|
919
|
+
});
|
|
920
|
+
const wrapper = createDiv(ctx, {
|
|
921
|
+
className: "ui-tooltip-wrapper",
|
|
922
|
+
slot: props.slot,
|
|
923
|
+
children: [props.anchor, tooltip]
|
|
924
|
+
});
|
|
925
|
+
wrapperTargets.set(wrapper, props.anchor);
|
|
926
|
+
return wrapper;
|
|
927
|
+
};
|
|
928
|
+
var createButton = (ctx, props) => {
|
|
929
|
+
const variant = () => ctx.evalProp(props.variant) ?? "toolbar";
|
|
930
|
+
const size = () => variant() === "toolbar" ? "super-condensed" : "condensed";
|
|
931
|
+
const appearanceInactive = () => variant() === "popover-primary" ? "outlined" : "ghost-light";
|
|
932
|
+
const appearanceActive = () => variant() === "toolbar-menu" || variant() === "popover" ? "filled" : appearanceInactive();
|
|
933
|
+
const appearance = () => ctx.evalProp(props.active) ? appearanceActive() : appearanceInactive();
|
|
934
|
+
const connotation = () => ctx.evalProp(props.connotation) ?? (variant() === "toolbar-menu" && ctx.evalProp(props.active) ? "cta" : void 0);
|
|
935
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
936
|
+
const button = ctx.rte.createComponent(Button);
|
|
937
|
+
ctx.bindToEl(button, {
|
|
938
|
+
size,
|
|
939
|
+
appearance,
|
|
940
|
+
disabled,
|
|
941
|
+
icon: props.icon,
|
|
942
|
+
autofocus: props.autofocus,
|
|
943
|
+
connotation,
|
|
944
|
+
[props.icon ? "ariaLabel" : "label"]: props.label,
|
|
945
|
+
pressed: () => Boolean(ctx.evalProp(props.active)),
|
|
946
|
+
ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
|
|
947
|
+
}, [on("click", props.onClick, (onClick) => () => {
|
|
948
|
+
if (!onClick() && ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
949
|
+
})]);
|
|
950
|
+
return createOptionalTooltip(ctx, {
|
|
951
|
+
enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
|
|
952
|
+
label: props.label,
|
|
953
|
+
anchor: button,
|
|
954
|
+
slot: props.slot
|
|
955
|
+
});
|
|
956
|
+
};
|
|
957
|
+
var createMenu = (ctx, props) => {
|
|
958
|
+
const menu = ctx.rte.createComponent(Menu);
|
|
959
|
+
ctx.bindToEl(menu, {
|
|
960
|
+
className: "ui-menu",
|
|
961
|
+
autoDismiss: true,
|
|
962
|
+
ariaLabel: props.label,
|
|
963
|
+
anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
|
|
964
|
+
placement: ctx.props.popupPlacement,
|
|
965
|
+
offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
|
|
966
|
+
}, [], props.children);
|
|
967
|
+
const fragment = document.createDocumentFragment();
|
|
968
|
+
fragment.appendChild(props.trigger);
|
|
969
|
+
fragment.appendChild(menu);
|
|
970
|
+
return fragment;
|
|
971
|
+
};
|
|
972
|
+
var createMenuItem = (ctx, props) => {
|
|
973
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
974
|
+
const item = ctx.rte.createComponent(MenuItem);
|
|
975
|
+
ctx.bindToEl(item, {
|
|
976
|
+
text: props.text,
|
|
977
|
+
checked: props.checked,
|
|
978
|
+
disabled,
|
|
979
|
+
controlType: "radio",
|
|
980
|
+
checkedAppearance: "tick-only"
|
|
981
|
+
}, [on("change", props.onSelect, (onSelect) => () => {
|
|
982
|
+
if (item.checked && !item.disabled) {
|
|
983
|
+
if (ctx.evalProp(props.checked) !== item.checked) {
|
|
984
|
+
onSelect();
|
|
985
|
+
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
})]);
|
|
989
|
+
return item;
|
|
990
|
+
};
|
|
991
|
+
var createButtonGroup = (ctx, props) => createDiv(ctx, {
|
|
992
|
+
slot: props.slot,
|
|
993
|
+
className: "ui-button-group",
|
|
994
|
+
children: props.children
|
|
995
|
+
});
|
|
996
|
+
function markActive(state, type) {
|
|
997
|
+
const { from, $from, to, empty } = state.selection;
|
|
998
|
+
if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
|
|
999
|
+
else return state.doc.rangeHasMark(from, to, type);
|
|
1000
|
+
}
|
|
1001
|
+
var createMarkToggle = (ctx, props) => createButton(ctx, {
|
|
1002
|
+
label: props.label,
|
|
1003
|
+
icon: props.icon,
|
|
1004
|
+
active: () => markActive(ctx.view.state, props.markType),
|
|
1005
|
+
disabled: () => !toggleMark(props.markType)(ctx.view.state),
|
|
1006
|
+
onClick: () => {
|
|
1007
|
+
toggleMark(props.markType)(ctx.view.state, ctx.view.dispatch);
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
var createOption = (ctx, props) => {
|
|
1011
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
1012
|
+
const option = ctx.rte.createComponent(ListboxOption);
|
|
1013
|
+
ctx.bindToEl(option, {
|
|
1014
|
+
value: props.value,
|
|
1015
|
+
text: props.text,
|
|
1016
|
+
disabled
|
|
1017
|
+
});
|
|
1018
|
+
return option;
|
|
1019
|
+
};
|
|
1020
|
+
var createSelect = (ctx, props) => {
|
|
1021
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled));
|
|
1022
|
+
const select = ctx.rte.createComponent(Select);
|
|
1023
|
+
select.setAttribute("data-class", "ui-select");
|
|
1024
|
+
ctx.bindToEl(select, {
|
|
1025
|
+
placeholder: " ",
|
|
1026
|
+
ariaLabel: props.label,
|
|
1027
|
+
appearance: "ghost",
|
|
1028
|
+
disabled
|
|
1029
|
+
}, [on("change", props.onSelect, (onSelect) => () => {
|
|
1030
|
+
const value = select.value;
|
|
1031
|
+
if (value) {
|
|
1032
|
+
onSelect(value);
|
|
1033
|
+
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
1034
|
+
}
|
|
1035
|
+
})], props.children);
|
|
1036
|
+
queueMicrotask(() => {
|
|
1037
|
+
ctx.bindProp(props.value, (value) => select.value = value);
|
|
1038
|
+
});
|
|
1039
|
+
let hideTooltip = false;
|
|
1040
|
+
select.addEventListener("vwc-popup:open", () => {
|
|
1041
|
+
hideTooltip = true;
|
|
1042
|
+
ctx.updateBindings();
|
|
1043
|
+
});
|
|
1044
|
+
select.addEventListener("vwc-popup:close", () => {
|
|
1045
|
+
hideTooltip = false;
|
|
1046
|
+
ctx.updateBindings();
|
|
1047
|
+
});
|
|
1048
|
+
return createOptionalTooltip(ctx, {
|
|
1049
|
+
enabled: () => !hideTooltip && !disabled(),
|
|
1050
|
+
label: props.label,
|
|
1051
|
+
anchor: select
|
|
1052
|
+
});
|
|
1053
|
+
};
|
|
1054
|
+
var createDivider = (ctx) => {
|
|
1055
|
+
const divider = ctx.rte.createComponent(Divider);
|
|
1056
|
+
ctx.bindToEl(divider, {
|
|
1057
|
+
className: "ui-divider",
|
|
1058
|
+
orientation: "vertical"
|
|
1059
|
+
});
|
|
1060
|
+
return divider;
|
|
1061
|
+
};
|
|
1062
|
+
var createTextField = (ctx, props) => {
|
|
1063
|
+
const textField = ctx.rte.createComponent(TextField);
|
|
1064
|
+
ctx.bindToEl(textField, {
|
|
1065
|
+
label: props.label,
|
|
1066
|
+
value: props.value,
|
|
1067
|
+
placeholder: props.placeholder,
|
|
1068
|
+
slot: props.slot,
|
|
1069
|
+
autofocus: props.autofocus,
|
|
1070
|
+
type: props.type,
|
|
1071
|
+
helperText: props.helperText
|
|
1072
|
+
}, [on("input", props.onInput, (onInput) => () => {
|
|
1073
|
+
onInput(textField.value);
|
|
1074
|
+
})]);
|
|
1075
|
+
return textField;
|
|
1019
1076
|
};
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
this.specs = specs;
|
|
1025
|
-
}
|
|
1026
|
-
getAllowedMarksForNode(nodeName) {
|
|
1027
|
-
return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
|
|
1028
|
-
}
|
|
1029
|
-
getReferencedNodeNames() {
|
|
1030
|
-
return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
|
|
1031
|
-
}
|
|
1077
|
+
var createText = (ctx, props) => {
|
|
1078
|
+
const textNode = document.createTextNode("");
|
|
1079
|
+
ctx.bindToEl(textNode, { textContent: props.text });
|
|
1080
|
+
return textNode;
|
|
1032
1081
|
};
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const result = [];
|
|
1059
|
-
const active = [];
|
|
1060
|
-
const activeContents = [result];
|
|
1061
|
-
for (const node of nodes) {
|
|
1062
|
-
const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
|
|
1063
|
-
let keep = 0;
|
|
1064
|
-
let rendered = 0;
|
|
1065
|
-
while (keep < active.length && rendered < nodeMarks.length) {
|
|
1066
|
-
if (!marksEqual(active[keep], nodeMarks[rendered])) break;
|
|
1067
|
-
keep++;
|
|
1068
|
-
rendered++;
|
|
1069
|
-
}
|
|
1070
|
-
while (active.length > keep) {
|
|
1071
|
-
active.pop();
|
|
1072
|
-
activeContents.pop();
|
|
1073
|
-
}
|
|
1074
|
-
while (rendered < nodeMarks.length) {
|
|
1075
|
-
const mark = nodeMarks[rendered];
|
|
1076
|
-
const markContent = [];
|
|
1077
|
-
activeContents[activeContents.length - 1].push({
|
|
1078
|
-
type: "mark",
|
|
1079
|
-
mark,
|
|
1080
|
-
children: {
|
|
1081
|
-
type: "fragment",
|
|
1082
|
-
content: markContent,
|
|
1083
|
-
[impl]: ctx
|
|
1084
|
-
},
|
|
1085
|
-
[impl]: ctx
|
|
1086
|
-
});
|
|
1087
|
-
active.push(mark);
|
|
1088
|
-
activeContents.push(markContent);
|
|
1089
|
-
rendered++;
|
|
1090
|
-
}
|
|
1091
|
-
activeContents[activeContents.length - 1].push({
|
|
1092
|
-
type: "node",
|
|
1093
|
-
node,
|
|
1094
|
-
children: convertFragment(node.content ?? []),
|
|
1095
|
-
[impl]: ctx
|
|
1082
|
+
var createSingleSlot = (ctx, props) => {
|
|
1083
|
+
const slot = document.createElement("slot");
|
|
1084
|
+
slot.name = props.name;
|
|
1085
|
+
let currentEl = null;
|
|
1086
|
+
const listeners = [];
|
|
1087
|
+
function cleanup() {
|
|
1088
|
+
for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
|
|
1089
|
+
listeners.length = 0;
|
|
1090
|
+
currentEl = null;
|
|
1091
|
+
}
|
|
1092
|
+
for (const [key, prop] of Object.entries(props.assignedProps)) ctx.bindProp(prop, (value) => {
|
|
1093
|
+
if (currentEl) currentEl[key] = value;
|
|
1094
|
+
});
|
|
1095
|
+
function applyPropsAndEvents(el) {
|
|
1096
|
+
for (const [key, prop] of Object.entries(props.assignedProps)) el[key] = ctx.evalProp(prop);
|
|
1097
|
+
for (const [type, handler] of Object.entries(props.assignedEvents)) {
|
|
1098
|
+
const listener = (e) => {
|
|
1099
|
+
handler(e);
|
|
1100
|
+
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
1101
|
+
};
|
|
1102
|
+
el.addEventListener(type, listener);
|
|
1103
|
+
listeners.push({
|
|
1104
|
+
el,
|
|
1105
|
+
type,
|
|
1106
|
+
handler: listener
|
|
1096
1107
|
});
|
|
1097
1108
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
children: convertFragment(doc.content),
|
|
1108
|
-
[impl]: ctx
|
|
1109
|
+
}
|
|
1110
|
+
const processSlot = () => {
|
|
1111
|
+
const first = slot.assignedElements({ flatten: true })[0];
|
|
1112
|
+
if (first === currentEl) return;
|
|
1113
|
+
cleanup();
|
|
1114
|
+
if (first) {
|
|
1115
|
+
currentEl = first;
|
|
1116
|
+
applyPropsAndEvents(first);
|
|
1117
|
+
}
|
|
1109
1118
|
};
|
|
1119
|
+
slot.addEventListener("slotchange", processSlot);
|
|
1120
|
+
queueMicrotask(processSlot);
|
|
1121
|
+
return slot;
|
|
1110
1122
|
};
|
|
1111
1123
|
//#endregion
|
|
1112
|
-
//#region src/lib/rich-text-editor/rte/
|
|
1113
|
-
var
|
|
1114
|
-
constructor(
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
return new RteInstance(this, options);
|
|
1124
|
+
//#region src/lib/rich-text-editor/rte/features/internal/history.ts
|
|
1125
|
+
var RteHistoryFeatureImpl = class extends RteFeatureImpl {
|
|
1126
|
+
constructor(config) {
|
|
1127
|
+
super();
|
|
1128
|
+
this.config = config;
|
|
1129
|
+
this.name = "RteHistoryFeature";
|
|
1119
1130
|
}
|
|
1120
|
-
|
|
1121
|
-
return
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1131
|
+
getPlugins() {
|
|
1132
|
+
return [this.contribution(history()), this.contribution(keymap({
|
|
1133
|
+
"Mod-z": undo,
|
|
1134
|
+
"Ctrl-y": redo,
|
|
1135
|
+
"Cmd-Z": redo
|
|
1136
|
+
}))];
|
|
1125
1137
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
marks: {}
|
|
1152
|
-
};
|
|
1153
|
-
for (const schema of schemaContributions) {
|
|
1154
|
-
Object.assign(schemaSpec.nodes, schema.nodes ?? {});
|
|
1155
|
-
Object.assign(schemaSpec.marks, schema.marks ?? {});
|
|
1156
|
-
}
|
|
1157
|
-
for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
|
|
1158
|
-
this.schema = new Schema(schemaSpec);
|
|
1138
|
+
getToolbarItems() {
|
|
1139
|
+
if (!this.config.showToolbarButtons) return [];
|
|
1140
|
+
return [this.contribution({
|
|
1141
|
+
section: "history",
|
|
1142
|
+
render: (ctx) => createButton(ctx, {
|
|
1143
|
+
label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
|
|
1144
|
+
icon: "undo-line",
|
|
1145
|
+
disabled: (ctx) => !undo(ctx.view.state),
|
|
1146
|
+
onClick: () => {
|
|
1147
|
+
const { state, dispatch } = ctx.view;
|
|
1148
|
+
undo(state, dispatch);
|
|
1149
|
+
}
|
|
1150
|
+
})
|
|
1151
|
+
}, 1), this.contribution({
|
|
1152
|
+
section: "history",
|
|
1153
|
+
render: (ctx) => createButton(ctx, {
|
|
1154
|
+
label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
|
|
1155
|
+
icon: "redo-line",
|
|
1156
|
+
disabled: (ctx) => !redo(ctx.view.state),
|
|
1157
|
+
onClick: () => {
|
|
1158
|
+
const { state, dispatch } = ctx.view;
|
|
1159
|
+
redo(state, dispatch);
|
|
1160
|
+
}
|
|
1161
|
+
})
|
|
1162
|
+
}, 2)];
|
|
1159
1163
|
}
|
|
1160
1164
|
};
|
|
1161
1165
|
//#endregion
|
|
@@ -1341,6 +1345,7 @@ var RteBaseImpl = class extends RteFeatureImpl {
|
|
|
1341
1345
|
return [
|
|
1342
1346
|
this,
|
|
1343
1347
|
new RteCoreImpl(),
|
|
1348
|
+
new RteHistoryFeatureImpl({ showToolbarButtons: this.config?.historyToolbarButtons ?? true }),
|
|
1344
1349
|
new RteBasicTextBlocksImpl({
|
|
1345
1350
|
heading1: this.config?.heading1 ?? false,
|
|
1346
1351
|
heading2: this.config?.heading2 ?? false,
|
|
@@ -1421,12 +1426,28 @@ var RteToolbarFeatureImpl = class extends RteFeatureImpl {
|
|
|
1421
1426
|
for (const toolbarItem of sortedContributions(rte.features.flatMap((f) => f.getToolbarItems(rte)))) itemsBySection.get(toolbarItem.section).push(toolbarItem);
|
|
1422
1427
|
const core = rte.getFeature("RteCore");
|
|
1423
1428
|
return [this.contribution(this.hidden.plugin), this.contribution(new Plugin({ view: (view) => {
|
|
1429
|
+
let focusCameFromEditorWithMouse = false;
|
|
1424
1430
|
const ctx = new UiCtx(view, rte, {
|
|
1425
1431
|
popupPlacement: this.config?.popupDirection === "outward" ? "bottom" : "top",
|
|
1426
1432
|
menuOffset: 8,
|
|
1427
|
-
disabled: () => core.disabled.getValue(rte)
|
|
1433
|
+
disabled: () => core.disabled.getValue(rte),
|
|
1434
|
+
shouldReturnFocusToEditor: () => focusCameFromEditorWithMouse
|
|
1428
1435
|
});
|
|
1429
1436
|
const toolbar = view.dom.getRootNode().querySelector(".toolbar");
|
|
1437
|
+
let mouseDownTime = -Infinity;
|
|
1438
|
+
const onMouseDown = () => {
|
|
1439
|
+
if (view.hasFocus()) focusCameFromEditorWithMouse = true;
|
|
1440
|
+
mouseDownTime = Date.now();
|
|
1441
|
+
};
|
|
1442
|
+
const onFocusIn = () => {
|
|
1443
|
+
if (Date.now() - mouseDownTime > 250) focusCameFromEditorWithMouse = false;
|
|
1444
|
+
};
|
|
1445
|
+
const onFocusOut = (e) => {
|
|
1446
|
+
if (!toolbar.contains(e.relatedTarget)) focusCameFromEditorWithMouse = false;
|
|
1447
|
+
};
|
|
1448
|
+
toolbar.addEventListener("mousedown", onMouseDown, { capture: true });
|
|
1449
|
+
toolbar.addEventListener("focusin", onFocusIn);
|
|
1450
|
+
toolbar.addEventListener("focusout", onFocusOut);
|
|
1430
1451
|
ctx.bindProp(() => this.hidden.getValue(rte), (hidden) => {
|
|
1431
1452
|
toolbar.classList.toggle("toolbar--hidden", hidden);
|
|
1432
1453
|
});
|
|
@@ -1448,6 +1469,10 @@ var RteToolbarFeatureImpl = class extends RteFeatureImpl {
|
|
|
1448
1469
|
},
|
|
1449
1470
|
destroy() {
|
|
1450
1471
|
toolbar.innerHTML = "";
|
|
1472
|
+
toolbar.removeEventListener("mousedown", onMouseDown, { capture: true });
|
|
1473
|
+
toolbar.removeEventListener("focusin", onFocusIn);
|
|
1474
|
+
toolbar.removeEventListener("focusout", onFocusOut);
|
|
1475
|
+
focusCameFromEditorWithMouse = false;
|
|
1451
1476
|
}
|
|
1452
1477
|
};
|
|
1453
1478
|
} }))];
|
|
@@ -2400,7 +2425,10 @@ var RteLinkFeatureImpl = class extends RteFeatureImpl {
|
|
|
2400
2425
|
return null;
|
|
2401
2426
|
} },
|
|
2402
2427
|
view: (view) => {
|
|
2403
|
-
const ctx = new UiCtx(view, rte, {
|
|
2428
|
+
const ctx = new UiCtx(view, rte, {
|
|
2429
|
+
popupPlacement: "bottom",
|
|
2430
|
+
shouldReturnFocusToEditor: true
|
|
2431
|
+
});
|
|
2404
2432
|
popup = rte.createComponent(Popover);
|
|
2405
2433
|
popup.anchorId = "current-link";
|
|
2406
2434
|
const content = createDiv(ctx, {
|
|
@@ -2525,6 +2553,7 @@ var RteLinkFeatureImpl = class extends RteFeatureImpl {
|
|
|
2525
2553
|
onClick: () => {
|
|
2526
2554
|
const { state, dispatch } = ctx.view;
|
|
2527
2555
|
this.insertLink(rte, urlField.value, textField.value)(state, dispatch);
|
|
2556
|
+
this.toolbarMenu.open = false;
|
|
2528
2557
|
}
|
|
2529
2558
|
});
|
|
2530
2559
|
const updateValidation = () => {
|
|
@@ -2775,7 +2804,10 @@ var RteInlineImageFeatureImpl = class extends RteFeatureImpl {
|
|
|
2775
2804
|
return [this.contribution(new Plugin({
|
|
2776
2805
|
props: { nodeViews: { inlineImage: (node, view, getPos) => new InlineImageView(node, view, getPos, this.config) } },
|
|
2777
2806
|
view: (view) => {
|
|
2778
|
-
const ctx = new UiCtx(view, rte, {
|
|
2807
|
+
const ctx = new UiCtx(view, rte, {
|
|
2808
|
+
popupPlacement: "bottom",
|
|
2809
|
+
shouldReturnFocusToEditor: true
|
|
2810
|
+
});
|
|
2779
2811
|
popover = rte.createComponent(Popover);
|
|
2780
2812
|
popover.offset = 4;
|
|
2781
2813
|
const content = createDiv(ctx, {
|
|
@@ -3193,7 +3225,6 @@ var RteInputRuleFeature = featureFacade(RteInputRuleFeatureImpl);
|
|
|
3193
3225
|
//#endregion
|
|
3194
3226
|
//#region src/lib/rich-text-editor/rte/features/keyboard-shortcuts.ts
|
|
3195
3227
|
function toCommand(handler, rteInstance) {
|
|
3196
|
-
if (handler.length === 0) return (_state, _dispatch) => handler();
|
|
3197
3228
|
return (_state, _dispatch) => handler(rteInstance);
|
|
3198
3229
|
}
|
|
3199
3230
|
var RteKeyboardShortcutsFeatureImpl = class extends RteFeatureImpl {
|
|
@@ -3356,7 +3387,10 @@ var RteSuggestFeatureImpl = class extends RteFeatureImpl {
|
|
|
3356
3387
|
return suggestState.loadingDecoration.add(state.doc, [Decoration.inline(suggestState.match.start, suggestState.match.end, { id: `suggest-anchor-${this.featureId}` })]);
|
|
3357
3388
|
} },
|
|
3358
3389
|
view: (view) => {
|
|
3359
|
-
const ctx = new UiCtx(view, rte, {
|
|
3390
|
+
const ctx = new UiCtx(view, rte, {
|
|
3391
|
+
popupPlacement: "bottom",
|
|
3392
|
+
shouldReturnFocusToEditor: true
|
|
3393
|
+
});
|
|
3360
3394
|
const popover = rte.createComponent(Popover);
|
|
3361
3395
|
popover.anchorId = `suggest-anchor-${this.featureId}`;
|
|
3362
3396
|
popover.kind = "autocomplete";
|