@vonage/vivid 5.20.1 → 5.22.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/accordion/index.cjs +1 -1
- package/accordion/index.js +2 -2
- package/alert/index.cjs +1 -1
- package/alert/index.js +2 -2
- package/audio-player/index.cjs +3 -5
- package/audio-player/index.js +30 -32
- package/avatar/index.cjs +1 -1
- package/avatar/index.js +1 -1
- package/banner/index.cjs +1 -1
- package/banner/index.js +2 -2
- package/bundled/affix.cjs +4 -4
- package/bundled/affix.js +4 -4
- package/bundled/base-color-picker.cjs +2 -2
- package/bundled/base-color-picker.js +2 -2
- package/bundled/button.cjs +1 -1
- package/bundled/button.js +1 -1
- package/bundled/definition10.cjs +1 -1
- package/bundled/definition10.js +1 -1
- package/bundled/definition13.cjs +1 -1
- package/bundled/definition13.js +1 -1
- package/bundled/definition15.cjs +1 -1
- package/bundled/definition15.js +2 -2
- package/bundled/definition16.cjs +2 -2
- package/bundled/definition16.js +2 -2
- package/bundled/definition17.cjs +1 -1
- package/bundled/definition17.js +1 -1
- package/bundled/definition18.cjs +2 -2
- package/bundled/definition18.js +15 -6
- package/bundled/definition19.cjs +1 -1
- package/bundled/definition19.js +1 -1
- package/bundled/definition20.cjs +1 -1
- package/bundled/definition20.js +1 -1
- package/bundled/definition21.cjs +1 -1
- package/bundled/definition21.js +2 -2
- package/bundled/definition24.cjs +1 -1
- package/bundled/definition24.js +1 -1
- package/bundled/definition3.cjs +1 -1
- package/bundled/definition3.js +3 -3
- package/bundled/definition4.cjs +1 -1
- package/bundled/definition4.js +1 -1
- package/bundled/definition5.cjs +1 -1
- package/bundled/definition5.js +1 -1
- package/bundled/definition6.cjs +1 -1
- package/bundled/definition6.js +2 -2
- package/bundled/definition8.cjs +1 -1
- package/bundled/definition8.js +12 -6
- package/bundled/definition9.cjs +1 -1
- package/bundled/definition9.js +1 -1
- package/bundled/form-associated.cjs +1 -1
- package/bundled/form-associated.js +2 -2
- package/bundled/listbox.cjs +1 -1
- package/bundled/listbox.js +5 -5
- package/bundled/numberConverter.cjs +1 -1
- package/bundled/numberConverter.js +1 -1
- package/bundled/slider.template.cjs +1 -1
- package/bundled/slider.template.js +1 -1
- package/bundled/vivid-element.cjs +1 -1
- package/bundled/vivid-element.js +1 -1
- package/bundled/with-contextual-help.cjs +1 -1
- package/bundled/with-contextual-help.js +1 -1
- package/calendar/index.cjs +1 -1
- package/calendar/index.js +3 -3
- package/color-picker/index.cjs +20 -20
- package/color-picker/index.js +22 -22
- package/combobox/index.cjs +1 -1
- package/combobox/index.js +7 -7
- package/country-group/index.cjs +1 -1
- package/country-group/index.js +1 -1
- package/custom-elements.json +227 -185
- package/data-grid/index.cjs +2 -2
- package/data-grid/index.js +12 -10
- package/dial-pad/index.cjs +1 -1
- package/dial-pad/index.js +2 -2
- package/dialog/index.cjs +1 -1
- package/dialog/index.js +1 -1
- package/file-picker/index.cjs +1 -1
- package/file-picker/index.js +1 -1
- package/header/index.cjs +1 -1
- package/header/index.js +1 -1
- package/lib/audio-player/audio-player.d.ts +0 -1
- package/lib/calendar/helpers/calendar.keyboard-interactions.d.ts +1 -1
- package/lib/checkbox/checkbox.d.ts +16 -16
- package/lib/color-picker/color-picker.d.ts +16 -16
- package/lib/combobox/combobox.d.ts +21 -21
- package/lib/date-picker/date-picker.d.ts +26 -26
- package/lib/date-range-picker/date-range-picker.d.ts +14 -14
- package/lib/date-time-picker/date-time-picker.d.ts +24 -24
- package/lib/dial-pad/dial-pad.d.ts +1 -1
- package/lib/file-picker/file-picker.d.ts +21 -21
- package/lib/menu/menu.d.ts +1 -1
- package/lib/number-field/number-field.d.ts +22 -22
- package/lib/popup/popup.d.ts +2 -1
- package/lib/radio/radio.d.ts +16 -16
- package/lib/range-slider/range-slider.d.ts +10 -10
- package/lib/rich-text-editor/popover.d.ts +1 -1
- package/lib/rich-text-editor/rte/exports.d.ts +1 -0
- package/lib/rich-text-editor/rte/feature.d.ts +5 -5
- package/lib/rich-text-editor/rte/features/base.d.ts +1 -0
- package/lib/rich-text-editor/rte/features/drop-handler.d.ts +1 -2
- package/lib/rich-text-editor/rte/features/internal/core.d.ts +8 -0
- package/lib/rich-text-editor/rte/features/internal/history.d.ts +6 -0
- package/lib/rich-text-editor/rte/features/keyboard-shortcuts.d.ts +1 -1
- package/lib/rich-text-editor/rte/features/text-color-picker.d.ts +1 -1
- package/lib/rich-text-editor/rte/instance.d.ts +1 -0
- package/lib/rich-text-editor/rte/public-interface.d.ts +3 -0
- package/lib/rich-text-editor/rte/utils/ui.d.ts +1 -1
- package/lib/searchable-select/searchable-select.d.ts +21 -21
- package/lib/select/select.d.ts +22 -22
- package/lib/simple-color-picker/simple-color-picker.d.ts +5 -5
- package/lib/slider/slider.d.ts +5 -5
- package/lib/switch/switch.d.ts +5 -5
- package/lib/table/table-body.template.d.ts +1 -2
- package/lib/table/table-cell.template.d.ts +1 -2
- package/lib/table/table-head.template.d.ts +1 -2
- package/lib/table/table-row.template.d.ts +1 -2
- package/lib/table/table.template.d.ts +1 -2
- package/lib/text-area/text-area.d.ts +21 -21
- package/lib/text-field/text-field.d.ts +22 -22
- package/lib/time-picker/time-picker.d.ts +12 -12
- 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/nav-disclosure/index.cjs +1 -1
- package/nav-disclosure/index.js +1 -1
- package/nav-item/index.cjs +1 -1
- package/nav-item/index.js +1 -1
- package/package.json +7 -4
- package/pagination/index.cjs +1 -1
- package/pagination/index.js +9 -2
- package/popover/index.cjs +1 -1
- package/popover/index.js +1 -1
- package/progress/index.cjs +1 -1
- package/progress/index.js +1 -1
- package/radio-group/index.cjs +1 -1
- package/radio-group/index.js +21 -19
- package/range-slider/index.cjs +1 -1
- package/range-slider/index.js +1 -1
- package/rich-text-editor/index.cjs +13 -12
- package/rich-text-editor/index.js +1085 -1057
- package/searchable-select/index.cjs +1 -1
- package/searchable-select/index.js +1 -1
- package/selectable-box/index.cjs +1 -1
- package/selectable-box/index.js +6 -1
- package/shared/color-picker/base-color-picker.d.ts +5 -5
- package/shared/feedback/mixins.d.ts +1 -1
- package/shared/foundation/button/button.d.ts +5 -5
- package/shared/foundation/form-associated/form-associated.d.ts +10 -10
- package/shared/foundation/listbox/listbox.d.ts +1 -1
- package/shared/patterns/form-elements/form-element.d.ts +5 -5
- package/shared/patterns/form-elements/with-contextual-help.d.ts +5 -5
- package/shared/patterns/form-elements/with-error-text.d.ts +6 -6
- package/shared/picker-field/mixins/calendar-picker.d.ts +6 -6
- package/shared/picker-field/mixins/calendar-picker.template.d.ts +6 -6
- package/shared/picker-field/mixins/min-max-calendar-picker.d.ts +8 -8
- package/shared/picker-field/mixins/single-date-picker.d.ts +6 -6
- package/shared/picker-field/mixins/single-value-picker.d.ts +6 -6
- package/shared/picker-field/mixins/time-selection-picker.d.ts +6 -6
- package/shared/picker-field/mixins/time-selection-picker.template.d.ts +12 -12
- package/shared/picker-field/picker-field.d.ts +21 -21
- package/shared/utils/slottable-request.d.ts +1 -1
- package/switch/index.cjs +2 -2
- package/switch/index.js +2 -2
- package/table/index.cjs +5 -5
- package/table/index.js +5 -5
- package/tabs/index.cjs +1 -1
- package/tabs/index.js +3 -3
- package/tag/index.cjs +1 -1
- package/tag/index.js +1 -1
- package/tree-view/index.cjs +1 -1
- package/tree-view/index.js +0 -2
- package/unbundled/affix.cjs +4 -4
- package/unbundled/affix.js +4 -4
- package/unbundled/base-color-picker.cjs +3 -3
- package/unbundled/base-color-picker.js +3 -3
- package/unbundled/button.cjs +5 -5
- package/unbundled/button.js +5 -5
- package/unbundled/chunk.cjs +15 -0
- package/unbundled/chunk.js +13 -0
- package/unbundled/definition10.cjs +15 -4
- package/unbundled/definition10.js +15 -4
- package/unbundled/definition11.cjs +3 -3
- package/unbundled/definition11.js +3 -3
- package/unbundled/definition12.cjs +1 -1
- package/unbundled/definition12.js +1 -1
- package/unbundled/definition13.cjs +2 -2
- package/unbundled/definition13.js +2 -2
- package/unbundled/definition14.cjs +73 -76
- package/unbundled/definition14.js +73 -76
- package/unbundled/definition15.cjs +1 -1
- package/unbundled/definition15.js +1 -1
- package/unbundled/definition17.cjs +2 -2
- package/unbundled/definition17.js +2 -2
- package/unbundled/definition19.cjs +2 -1
- package/unbundled/definition19.js +2 -1
- package/unbundled/definition2.cjs +1 -1
- package/unbundled/definition2.js +1 -1
- package/unbundled/definition21.cjs +3 -3
- package/unbundled/definition21.js +3 -3
- package/unbundled/definition23.cjs +2 -2
- package/unbundled/definition23.js +2 -2
- package/unbundled/definition24.cjs +1 -1
- package/unbundled/definition24.js +1 -1
- package/unbundled/definition27.cjs +1 -1
- package/unbundled/definition27.js +1 -1
- package/unbundled/definition28.cjs +23 -23
- package/unbundled/definition28.js +23 -23
- package/unbundled/definition29.cjs +1 -1
- package/unbundled/definition29.js +1 -1
- package/unbundled/definition3.cjs +2 -2
- package/unbundled/definition3.js +2 -2
- package/unbundled/definition30.cjs +8 -8
- package/unbundled/definition30.js +8 -8
- package/unbundled/definition32.cjs +1 -1
- package/unbundled/definition32.js +1 -1
- package/unbundled/definition34.cjs +9 -8
- package/unbundled/definition34.js +9 -8
- package/unbundled/definition35.cjs +1 -1
- package/unbundled/definition35.js +1 -1
- package/unbundled/definition39.cjs +2 -2
- package/unbundled/definition39.js +2 -2
- package/unbundled/definition40.cjs +1 -1
- package/unbundled/definition40.js +1 -1
- package/unbundled/definition43.cjs +1 -1
- package/unbundled/definition43.js +1 -1
- package/unbundled/definition44.cjs +1 -1
- package/unbundled/definition44.js +1 -1
- package/unbundled/definition48.cjs +1 -1
- package/unbundled/definition48.js +1 -1
- package/unbundled/definition49.cjs +1 -1
- package/unbundled/definition49.js +1 -1
- package/unbundled/definition5.cjs +1 -1
- package/unbundled/definition5.js +1 -1
- package/unbundled/definition53.cjs +10 -3
- package/unbundled/definition53.js +10 -3
- package/unbundled/definition55.cjs +1 -1
- package/unbundled/definition55.js +1 -1
- package/unbundled/definition56.cjs +1 -1
- package/unbundled/definition56.js +1 -1
- package/unbundled/definition57.cjs +4 -5
- package/unbundled/definition57.js +4 -5
- package/unbundled/definition58.cjs +20 -19
- package/unbundled/definition58.js +20 -19
- package/unbundled/definition59.cjs +1 -1
- package/unbundled/definition59.js +1 -1
- package/unbundled/definition60.cjs +890 -851
- package/unbundled/definition60.js +890 -851
- package/unbundled/definition61.cjs +13 -7
- package/unbundled/definition61.js +13 -7
- package/unbundled/definition63.cjs +1 -1
- package/unbundled/definition63.js +1 -1
- package/unbundled/definition64.cjs +4 -1
- package/unbundled/definition64.js +4 -1
- package/unbundled/definition65.cjs +2 -1
- package/unbundled/definition65.js +2 -1
- package/unbundled/definition69.cjs +4 -3
- package/unbundled/definition69.js +4 -3
- package/unbundled/definition7.cjs +2 -2
- package/unbundled/definition7.js +2 -2
- package/unbundled/definition71.cjs +1 -1
- package/unbundled/definition71.js +1 -1
- package/unbundled/definition72.cjs +5 -5
- package/unbundled/definition72.js +5 -5
- package/unbundled/definition73.cjs +3 -3
- package/unbundled/definition73.js +3 -3
- package/unbundled/definition75.cjs +1 -1
- package/unbundled/definition75.js +1 -1
- package/unbundled/definition78.cjs +1 -1
- package/unbundled/definition78.js +1 -1
- package/unbundled/definition79.cjs +0 -2
- package/unbundled/definition79.js +0 -2
- package/unbundled/definition80.cjs +1 -1
- package/unbundled/definition80.js +1 -1
- package/unbundled/definition9.cjs +2 -2
- package/unbundled/definition9.js +2 -2
- package/unbundled/en-US.cjs +449 -0
- package/unbundled/en-US.js +445 -0
- package/unbundled/form-associated.cjs +2 -2
- package/unbundled/form-associated.js +2 -2
- package/unbundled/listbox.cjs +7 -7
- package/unbundled/listbox.js +7 -7
- package/unbundled/localized.cjs +2 -2
- package/unbundled/localized.js +1 -1
- package/unbundled/numberConverter.cjs +1 -1
- package/unbundled/numberConverter.js +1 -1
- package/unbundled/slider.template.cjs +1 -1
- package/unbundled/slider.template.js +1 -1
- package/unbundled/slottable-request.cjs +5 -5
- package/unbundled/slottable-request.js +5 -5
- package/unbundled/vivid-element.cjs +1 -1
- package/unbundled/vivid-element.js +1 -1
- package/unbundled/with-contextual-help.cjs +4 -1
- package/unbundled/with-contextual-help.js +4 -1
- package/video-player/index.cjs +1 -1
- package/video-player/index.js +1 -1
- package/vivid.api.json +60 -18
|
@@ -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";
|
|
@@ -111,12 +111,20 @@ var Kind = {
|
|
|
111
111
|
default: {
|
|
112
112
|
placement: void 0,
|
|
113
113
|
placementStrategy: PlacementStrategy.AutoPlacementHorizontal,
|
|
114
|
+
shiftCrossAxis: false,
|
|
114
115
|
strategy: "absolute"
|
|
115
116
|
},
|
|
116
117
|
autocomplete: {
|
|
117
118
|
placement: "bottom-start",
|
|
118
119
|
placementStrategy: PlacementStrategy.Flip,
|
|
120
|
+
shiftCrossAxis: false,
|
|
119
121
|
strategy: "fixed"
|
|
122
|
+
},
|
|
123
|
+
imageOptions: {
|
|
124
|
+
placement: "bottom",
|
|
125
|
+
placementStrategy: PlacementStrategy.Flip,
|
|
126
|
+
shiftCrossAxis: true,
|
|
127
|
+
strategy: "absolute"
|
|
120
128
|
}
|
|
121
129
|
};
|
|
122
130
|
var popoverTemplate = (context) => {
|
|
@@ -125,6 +133,7 @@ var popoverTemplate = (context) => {
|
|
|
125
133
|
:anchor="${(x) => x.anchorEl}"
|
|
126
134
|
:placement="${(x) => Kind[x.kind].placement}"
|
|
127
135
|
:placementStrategy="${(x) => Kind[x.kind].placementStrategy}"
|
|
136
|
+
:shiftCrossAxis="${(x) => Kind[x.kind].shiftCrossAxis}"
|
|
128
137
|
:open="${(x) => x.open}"
|
|
129
138
|
:offset="${(x) => x.offset}"
|
|
130
139
|
:strategy="${(x) => Kind[x.kind].strategy}"
|
|
@@ -238,927 +247,942 @@ var FeatureState = class {
|
|
|
238
247
|
//#region src/lib/rich-text-editor/rte/features/internal/core.style.scss?inline
|
|
239
248
|
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
249
|
//#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 = [];
|
|
250
|
+
//#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
|
|
251
|
+
var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
|
|
252
|
+
constructor(..._args) {
|
|
253
|
+
super(..._args);
|
|
254
|
+
this.name = "RteForeignHtmlFeature";
|
|
254
255
|
}
|
|
255
|
-
|
|
256
|
-
return
|
|
256
|
+
getPlugins(rte) {
|
|
257
|
+
return [this.contribution(new Plugin({ props: {
|
|
258
|
+
transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
|
|
259
|
+
clipboardParser: rte.foreignHtmlParser[impl].parser,
|
|
260
|
+
clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
|
|
261
|
+
} }))];
|
|
257
262
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
};
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
|
|
266
|
+
/**
|
|
267
|
+
* When the cursor is positioned after an inline atom node at the end of a line,
|
|
268
|
+
* browsers may display the cursor inside the atom.
|
|
269
|
+
*/
|
|
270
|
+
var atomCursorFix = ($cursor) => {
|
|
271
|
+
if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
|
|
272
|
+
const nodeBefore = $cursor.nodeBefore;
|
|
273
|
+
if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
|
|
274
|
+
return null;
|
|
275
|
+
};
|
|
276
|
+
var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
|
|
277
|
+
constructor(..._args) {
|
|
278
|
+
super(..._args);
|
|
279
|
+
this.name = "RteCursorFix";
|
|
265
280
|
}
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
getPlugins(rte) {
|
|
282
|
+
const cursorFixes = [atomCursorFix];
|
|
283
|
+
for (const markType of Object.values(rte.schema.marks)) {
|
|
284
|
+
const spec = markType.spec;
|
|
285
|
+
if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
|
|
286
|
+
}
|
|
287
|
+
return [this.contribution(new Plugin({ props: { decorations: (state) => {
|
|
288
|
+
const { $cursor } = state.selection;
|
|
289
|
+
if (!$cursor) return null;
|
|
290
|
+
let cursorFix = null;
|
|
291
|
+
for (const fn of cursorFixes) {
|
|
292
|
+
const result = fn($cursor, state);
|
|
293
|
+
if (result) Object.assign(cursorFix ??= {}, result);
|
|
294
|
+
}
|
|
295
|
+
if (!cursorFix) return null;
|
|
296
|
+
return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
|
|
297
|
+
const span = document.createElement("span");
|
|
298
|
+
span.textContent = "";
|
|
299
|
+
for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
|
|
300
|
+
return span;
|
|
301
|
+
}, { side: -1 })]);
|
|
302
|
+
} } }))];
|
|
268
303
|
}
|
|
269
|
-
|
|
270
|
-
|
|
304
|
+
};
|
|
305
|
+
featureFacade(RteCursorFixFeatureImpl);
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/lib/rich-text-editor/rte/features/internal/core.ts
|
|
308
|
+
/**
|
|
309
|
+
* Plugin to bring state from the host web component into ProseMirror.
|
|
310
|
+
*/
|
|
311
|
+
var hostBridgePlugin = new Plugin({ state: {
|
|
312
|
+
init() {
|
|
313
|
+
return null;
|
|
314
|
+
},
|
|
315
|
+
apply(tr, value) {
|
|
316
|
+
const meta = tr.getMeta(hostBridgePlugin);
|
|
317
|
+
if (meta) return meta;
|
|
318
|
+
else return value;
|
|
271
319
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
320
|
+
} });
|
|
321
|
+
var RteCoreImpl = class extends RteFeatureImpl {
|
|
322
|
+
constructor(..._args) {
|
|
323
|
+
super(..._args);
|
|
324
|
+
this.name = "RteCore";
|
|
325
|
+
this.disabled = new FeatureState(false);
|
|
326
|
+
this.scrollThreshold = void 0;
|
|
327
|
+
this.scrollMargin = void 0;
|
|
328
|
+
}
|
|
329
|
+
getStyles() {
|
|
330
|
+
return [
|
|
331
|
+
this.contribution(prosemirror_default),
|
|
332
|
+
this.contribution(core_style_default),
|
|
333
|
+
this.contribution(ui_style_default)
|
|
334
|
+
];
|
|
335
|
+
}
|
|
336
|
+
getPlugins(rte) {
|
|
337
|
+
const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
|
|
338
|
+
if (!atEnd) return {
|
|
339
|
+
type: node.type,
|
|
340
|
+
attrs: node.attrs
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
|
|
344
|
+
attrs: rte.textblockAttrs.extractFromNode(node)
|
|
345
|
+
};
|
|
346
|
+
}));
|
|
347
|
+
return [
|
|
348
|
+
this.contribution(this.disabled.plugin),
|
|
349
|
+
this.contribution(new Plugin({
|
|
350
|
+
props: {
|
|
351
|
+
editable: () => !this.disabled.getValue(rte),
|
|
352
|
+
handleDOMEvents: { click: (_view, event) => {
|
|
353
|
+
if (this.disabled.getValue(rte)) {
|
|
354
|
+
event.preventDefault();
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
} }
|
|
359
|
+
},
|
|
360
|
+
view: (view) => {
|
|
361
|
+
const popovers = view.dom.getRootNode().querySelector(".popovers");
|
|
362
|
+
const updateDisabled = () => {
|
|
363
|
+
const disabled = this.disabled.getValue(rte);
|
|
364
|
+
popovers.classList.toggle("popovers--disabled", disabled);
|
|
365
|
+
view.dom.parentElement.classList.toggle("editor--disabled", disabled);
|
|
366
|
+
};
|
|
367
|
+
updateDisabled();
|
|
368
|
+
return { update: () => {
|
|
369
|
+
updateDisabled();
|
|
370
|
+
} };
|
|
371
|
+
}
|
|
372
|
+
})),
|
|
373
|
+
this.contribution(keymap({
|
|
374
|
+
...baseKeymap,
|
|
375
|
+
Enter: enterKeyChainCommands,
|
|
376
|
+
"Shift-Enter": enterKeyChainCommands
|
|
377
|
+
})),
|
|
378
|
+
this.contribution(dropCursor()),
|
|
379
|
+
this.contribution(gapCursor()),
|
|
380
|
+
this.contribution(hostBridgePlugin)
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
getFeatures() {
|
|
384
|
+
return [
|
|
385
|
+
this,
|
|
386
|
+
new RteForeignHtmlFeatureImpl(),
|
|
387
|
+
new RteCursorFixFeatureImpl()
|
|
388
|
+
];
|
|
278
389
|
}
|
|
279
390
|
};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
391
|
+
featureFacade(RteCoreImpl);
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/lib/rich-text-editor/rte/utils/sanitization.ts
|
|
394
|
+
var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
395
|
+
var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
396
|
+
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
397
|
+
var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
|
|
398
|
+
/**
|
|
399
|
+
* Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
|
|
400
|
+
* Returns empty string if the URL is unsafe.
|
|
401
|
+
*/
|
|
402
|
+
var sanitizeLinkHref = (url) => {
|
|
403
|
+
if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
|
|
289
404
|
const anchor = document.createElement("a");
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
return anchor;
|
|
405
|
+
anchor.setAttribute("href", url);
|
|
406
|
+
/* v8 ignore next -- since href is already validated it's probably always present @preserve */
|
|
407
|
+
return DOMPurify.sanitize(anchor, {
|
|
408
|
+
RETURN_DOM: true,
|
|
409
|
+
...domPurifyConfig
|
|
410
|
+
}).querySelector("a").getAttribute("href") ?? "";
|
|
297
411
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
tooltip.anchor = enabled ? props.anchor : void 0;
|
|
310
|
-
});
|
|
311
|
-
const wrapper = createDiv(ctx, {
|
|
312
|
-
className: "ui-tooltip-wrapper",
|
|
313
|
-
slot: props.slot,
|
|
314
|
-
children: [props.anchor, tooltip]
|
|
315
|
-
});
|
|
316
|
-
wrapperTargets.set(wrapper, props.anchor);
|
|
317
|
-
return wrapper;
|
|
412
|
+
/**
|
|
413
|
+
* Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
|
|
414
|
+
* Returns empty string if the URL is unsafe.
|
|
415
|
+
*/
|
|
416
|
+
var sanitizeImageSrc = (url) => {
|
|
417
|
+
const img = document.createElement("img");
|
|
418
|
+
img.setAttribute("src", url);
|
|
419
|
+
return DOMPurify.sanitize(img, {
|
|
420
|
+
RETURN_DOM: true,
|
|
421
|
+
...domPurifyConfig
|
|
422
|
+
}).querySelector("img").getAttribute("src") ?? "";
|
|
318
423
|
};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
appearance,
|
|
331
|
-
disabled,
|
|
332
|
-
icon: props.icon,
|
|
333
|
-
autofocus: props.autofocus,
|
|
334
|
-
connotation,
|
|
335
|
-
[props.icon ? "ariaLabel" : "label"]: props.label,
|
|
336
|
-
pressed: () => Boolean(ctx.evalProp(props.active)),
|
|
337
|
-
ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
|
|
338
|
-
}, [on("click", props.onClick, (onClick) => () => {
|
|
339
|
-
if (!onClick() && ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
340
|
-
})]);
|
|
341
|
-
return createOptionalTooltip(ctx, {
|
|
342
|
-
enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
|
|
343
|
-
label: props.label,
|
|
344
|
-
anchor: button,
|
|
345
|
-
slot: props.slot
|
|
346
|
-
});
|
|
347
|
-
};
|
|
348
|
-
var createMenu = (ctx, props) => {
|
|
349
|
-
const menu = ctx.rte.createComponent(Menu);
|
|
350
|
-
ctx.bindToEl(menu, {
|
|
351
|
-
className: "ui-menu",
|
|
352
|
-
autoDismiss: true,
|
|
353
|
-
ariaLabel: props.label,
|
|
354
|
-
anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
|
|
355
|
-
placement: ctx.props.popupPlacement,
|
|
356
|
-
offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
|
|
357
|
-
}, [], props.children);
|
|
358
|
-
const fragment = document.createDocumentFragment();
|
|
359
|
-
fragment.appendChild(props.trigger);
|
|
360
|
-
fragment.appendChild(menu);
|
|
361
|
-
return fragment;
|
|
362
|
-
};
|
|
363
|
-
var createMenuItem = (ctx, props) => {
|
|
364
|
-
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
365
|
-
const item = ctx.rte.createComponent(MenuItem);
|
|
366
|
-
ctx.bindToEl(item, {
|
|
367
|
-
text: props.text,
|
|
368
|
-
checked: props.checked,
|
|
369
|
-
disabled,
|
|
370
|
-
controlType: "radio",
|
|
371
|
-
checkedAppearance: "tick-only"
|
|
372
|
-
}, [on("change", props.onSelect, (onSelect) => () => {
|
|
373
|
-
if (item.checked && !item.disabled) {
|
|
374
|
-
if (ctx.evalProp(props.checked) !== item.checked) {
|
|
375
|
-
onSelect();
|
|
376
|
-
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
})]);
|
|
380
|
-
return item;
|
|
381
|
-
};
|
|
382
|
-
var createButtonGroup = (ctx, props) => createDiv(ctx, {
|
|
383
|
-
slot: props.slot,
|
|
384
|
-
className: "ui-button-group",
|
|
385
|
-
children: props.children
|
|
424
|
+
/**
|
|
425
|
+
* Escapes a CSS value for insertion into style attribute.
|
|
426
|
+
* E.g. "15px; background: red" => "15px"
|
|
427
|
+
*/
|
|
428
|
+
var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/lib/rich-text-editor/rte/html-parser.ts
|
|
431
|
+
var copy = (obj) => ({ ...obj });
|
|
432
|
+
var parseRulesFromSchema = (schema) => ({
|
|
433
|
+
nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
|
|
434
|
+
marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
|
|
386
435
|
});
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
436
|
+
var RteHtmlParser = class {
|
|
437
|
+
constructor(config, options) {
|
|
438
|
+
this[impl] = new RteHtmlParserImpl(config[impl], options);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Converts an HTML string to an RteDocument.
|
|
442
|
+
*/
|
|
443
|
+
parseDocument(html, options) {
|
|
444
|
+
return this[impl].parser.parse(this.parseHtml(html, options), { preserveWhitespace: true }).toJSON();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Converts an HTML string to an RteFragment.
|
|
448
|
+
*/
|
|
449
|
+
parseFragment(html, options) {
|
|
450
|
+
return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
|
|
451
|
+
}
|
|
452
|
+
parseHtml(html, options) {
|
|
453
|
+
const dom = DOMPurify.sanitize(html, {
|
|
454
|
+
RETURN_DOM: true,
|
|
455
|
+
...domPurifyConfig
|
|
456
|
+
});
|
|
457
|
+
const container = document.createDocumentFragment();
|
|
458
|
+
container.appendChild(dom);
|
|
459
|
+
options?.modifyDom?.(container);
|
|
460
|
+
return container;
|
|
399
461
|
}
|
|
400
|
-
});
|
|
401
|
-
var createOption = (ctx, props) => {
|
|
402
|
-
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
403
|
-
const option = ctx.rte.createComponent(ListboxOption);
|
|
404
|
-
ctx.bindToEl(option, {
|
|
405
|
-
value: props.value,
|
|
406
|
-
text: props.text,
|
|
407
|
-
disabled
|
|
408
|
-
});
|
|
409
|
-
return option;
|
|
410
462
|
};
|
|
411
|
-
var
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
463
|
+
var RteHtmlParserImpl = class {
|
|
464
|
+
constructor(config, options) {
|
|
465
|
+
const rules = parseRulesFromSchema(config.schema);
|
|
466
|
+
options?.modifyParseRules?.(rules);
|
|
467
|
+
this.parser = buildDomParser(config.schema, rules);
|
|
468
|
+
}
|
|
469
|
+
transform(html) {
|
|
470
|
+
return DOMPurify.sanitize(html);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var buildDomParser = (schema, { marks, nodes }) => {
|
|
474
|
+
const parserRules = [];
|
|
475
|
+
const priority = (rule) => rule.priority ?? 50;
|
|
476
|
+
function insert(rule) {
|
|
477
|
+
let i = 0;
|
|
478
|
+
for (; i < parserRules.length; i++) {
|
|
479
|
+
const next = parserRules[i];
|
|
480
|
+
if (priority(next) < priority(rule)) break;
|
|
425
481
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
select.addEventListener("vwc-popup:open", () => {
|
|
432
|
-
hideTooltip = true;
|
|
433
|
-
ctx.updateBindings();
|
|
434
|
-
});
|
|
435
|
-
select.addEventListener("vwc-popup:close", () => {
|
|
436
|
-
hideTooltip = false;
|
|
437
|
-
ctx.updateBindings();
|
|
482
|
+
parserRules.splice(i, 0, rule);
|
|
483
|
+
}
|
|
484
|
+
for (const name in marks) marks[name]?.forEach((rule) => {
|
|
485
|
+
insert(rule = copy(rule));
|
|
486
|
+
if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
|
|
438
487
|
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
anchor: select
|
|
488
|
+
for (const name in nodes) nodes[name]?.forEach((rule) => {
|
|
489
|
+
insert(rule = copy(rule));
|
|
490
|
+
if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
|
|
443
491
|
});
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
ctx.bindToEl(divider, {
|
|
448
|
-
className: "ui-divider",
|
|
449
|
-
orientation: "vertical"
|
|
492
|
+
parserRules.push({
|
|
493
|
+
tag: "br",
|
|
494
|
+
closeParent: true
|
|
450
495
|
});
|
|
451
|
-
return
|
|
452
|
-
};
|
|
453
|
-
var createTextField = (ctx, props) => {
|
|
454
|
-
const textField = ctx.rte.createComponent(TextField);
|
|
455
|
-
ctx.bindToEl(textField, {
|
|
456
|
-
label: props.label,
|
|
457
|
-
value: props.value,
|
|
458
|
-
placeholder: props.placeholder,
|
|
459
|
-
slot: props.slot,
|
|
460
|
-
autofocus: props.autofocus,
|
|
461
|
-
type: props.type,
|
|
462
|
-
helperText: props.helperText
|
|
463
|
-
}, [on("input", props.onInput, (onInput) => () => {
|
|
464
|
-
onInput(textField.value);
|
|
465
|
-
})]);
|
|
466
|
-
return textField;
|
|
496
|
+
return new DOMParser(schema, parserRules);
|
|
467
497
|
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/lib/rich-text-editor/rte/html-serializer.ts
|
|
500
|
+
var RteHtmlSerializer = class {
|
|
501
|
+
constructor(config, options) {
|
|
502
|
+
this[impl] = new RteHtmlSerializerImpl(config[impl], options);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Converts an RteDocument to an HTML string.
|
|
506
|
+
*/
|
|
507
|
+
serializeDocument(doc, options) {
|
|
508
|
+
return this[impl].serializeFragment(doc.content, options);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Converts an RteFragment to an HTML string.
|
|
512
|
+
*/
|
|
513
|
+
serializeFragment(fragment, options) {
|
|
514
|
+
return this[impl].serializeFragment(fragment, options);
|
|
515
|
+
}
|
|
472
516
|
};
|
|
473
|
-
var
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
listeners.length = 0;
|
|
481
|
-
currentEl = null;
|
|
517
|
+
var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
|
|
518
|
+
constructor(config, options) {
|
|
519
|
+
this.config = config;
|
|
520
|
+
const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
|
|
521
|
+
Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
|
|
522
|
+
Object.assign(serializers.marks, options?.serializers?.marks ?? {});
|
|
523
|
+
this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
|
|
482
524
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
for (const
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
492
|
-
};
|
|
493
|
-
el.addEventListener(type, listener);
|
|
494
|
-
listeners.push({
|
|
495
|
-
el,
|
|
496
|
-
type,
|
|
497
|
-
handler: listener
|
|
498
|
-
});
|
|
525
|
+
static domSerializersFromSchema(schema) {
|
|
526
|
+
const result = {
|
|
527
|
+
nodes: {},
|
|
528
|
+
marks: {}
|
|
529
|
+
};
|
|
530
|
+
for (const name in schema.marks) {
|
|
531
|
+
const toDOM = schema.marks[name].spec.toDOM;
|
|
532
|
+
if (toDOM) result.marks[name] = toDOM;
|
|
499
533
|
}
|
|
534
|
+
for (const name in schema.nodes) {
|
|
535
|
+
const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
|
|
536
|
+
if (toDOM) result.nodes[name] = toDOM;
|
|
537
|
+
}
|
|
538
|
+
result.nodes.text = (node) => document.createTextNode(node.text);
|
|
539
|
+
return result;
|
|
540
|
+
}
|
|
541
|
+
serializeFragment(fragment, options) {
|
|
542
|
+
const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
|
|
543
|
+
const serializedFragment = this.serializer.serializeFragment(parsedFragment);
|
|
544
|
+
const container = document.createDocumentFragment();
|
|
545
|
+
container.appendChild(serializedFragment);
|
|
546
|
+
options?.modifyDom?.(container);
|
|
547
|
+
const output = document.createElement("div");
|
|
548
|
+
output.appendChild(container);
|
|
549
|
+
return output.innerHTML;
|
|
500
550
|
}
|
|
501
|
-
const processSlot = () => {
|
|
502
|
-
const first = slot.assignedElements({ flatten: true })[0];
|
|
503
|
-
if (first === currentEl) return;
|
|
504
|
-
cleanup();
|
|
505
|
-
if (first) {
|
|
506
|
-
currentEl = first;
|
|
507
|
-
applyPropsAndEvents(first);
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
slot.addEventListener("slotchange", processSlot);
|
|
511
|
-
queueMicrotask(processSlot);
|
|
512
|
-
return slot;
|
|
513
551
|
};
|
|
514
552
|
//#endregion
|
|
515
|
-
//#region src/lib/rich-text-editor/rte/
|
|
516
|
-
var
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
553
|
+
//#region src/lib/rich-text-editor/rte/instance.ts
|
|
554
|
+
var parseDocument = (schema, doc) => {
|
|
555
|
+
const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
|
|
556
|
+
if (!node) throw new Error("Document could not be parsed");
|
|
557
|
+
node.check();
|
|
558
|
+
return node;
|
|
559
|
+
};
|
|
560
|
+
var RteInstance = class {
|
|
561
|
+
constructor(config, options) {
|
|
562
|
+
this.options = options;
|
|
563
|
+
this.feature = (Feature, featureId) => {
|
|
564
|
+
return this[impl].getPublicInterface(Feature, featureId);
|
|
565
|
+
};
|
|
566
|
+
this[impl] = new RteInstanceImpl(this, config, options);
|
|
520
567
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}))];
|
|
568
|
+
/**
|
|
569
|
+
* Returns the current document state.
|
|
570
|
+
*/
|
|
571
|
+
getDocument() {
|
|
572
|
+
return this[impl].state.doc.toJSON();
|
|
527
573
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
icon: "undo-line",
|
|
534
|
-
disabled: (ctx) => !undo(ctx.view.state),
|
|
535
|
-
onClick: () => {
|
|
536
|
-
const { state, dispatch } = ctx.view;
|
|
537
|
-
undo(state, dispatch);
|
|
538
|
-
}
|
|
539
|
-
})
|
|
540
|
-
}, 1), this.contribution({
|
|
541
|
-
section: "history",
|
|
542
|
-
render: (ctx) => createButton(ctx, {
|
|
543
|
-
label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
|
|
544
|
-
icon: "redo-line",
|
|
545
|
-
disabled: (ctx) => !redo(ctx.view.state),
|
|
546
|
-
onClick: () => {
|
|
547
|
-
const { state, dispatch } = ctx.view;
|
|
548
|
-
redo(state, dispatch);
|
|
549
|
-
}
|
|
550
|
-
})
|
|
551
|
-
}, 2)];
|
|
574
|
+
/**
|
|
575
|
+
* Reset the editor to its initial state. Optionally, an initial document can be provided.
|
|
576
|
+
*/
|
|
577
|
+
reset(initialDocument) {
|
|
578
|
+
this[impl].reset(initialDocument);
|
|
552
579
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
580
|
+
/**
|
|
581
|
+
* Replaces the current selection with the given content.
|
|
582
|
+
* If no text is selected, this inserts the content at the cursor position.
|
|
583
|
+
*/
|
|
584
|
+
replaceSelection(content, options) {
|
|
585
|
+
const { state, tr, schema, dispatchTransaction } = this[impl];
|
|
586
|
+
tr.replaceSelection(new Slice(Fragment.fromJSON(schema, content), 0, 0));
|
|
587
|
+
tr.doc.check();
|
|
588
|
+
const $from = tr.doc.resolve(tr.mapping.map(state.selection.from));
|
|
589
|
+
const $to = tr.doc.resolve(tr.selection.to);
|
|
590
|
+
const [$head, $anchor] = options?.cursorPlacement === "start" ? [$from, $to] : [$to, $from];
|
|
591
|
+
if (options?.selectContent) tr.setSelection(state.selection instanceof AllSelection ? new AllSelection(tr.doc) : TextSelection.between($anchor, $head));
|
|
592
|
+
else tr.setSelection(TextSelection.between($head, $head));
|
|
593
|
+
dispatchTransaction(tr);
|
|
560
594
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
595
|
+
/**
|
|
596
|
+
* Replaces the entire document with the given new document.
|
|
597
|
+
* Unlike reset, this preserves the rest of the editor state. The undo history is preserved, so the user can undo the replacement.
|
|
598
|
+
*/
|
|
599
|
+
replaceDocument(newDocument, options) {
|
|
600
|
+
const { state, tr, schema, dispatchTransaction } = this[impl];
|
|
601
|
+
tr.replaceWith(0, state.doc.content.size, parseDocument(schema, newDocument));
|
|
602
|
+
if (options?.selectContent) tr.setSelection(new AllSelection(tr.doc));
|
|
603
|
+
else if (options?.cursorPlacement === "end") tr.setSelection(TextSelection.atEnd(tr.doc));
|
|
604
|
+
else tr.setSelection(TextSelection.atStart(tr.doc));
|
|
605
|
+
dispatchTransaction(tr);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Scrolls the cursor into view.
|
|
609
|
+
* Has no effect if the editor is not currently mounted.
|
|
610
|
+
*/
|
|
611
|
+
scrollIntoView() {
|
|
612
|
+
const instance = this[impl];
|
|
613
|
+
if (!instance.view) return;
|
|
614
|
+
instance.dispatchTransaction(instance.tr.scrollIntoView());
|
|
567
615
|
}
|
|
568
616
|
};
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
*/
|
|
575
|
-
var atomCursorFix = ($cursor) => {
|
|
576
|
-
if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
|
|
577
|
-
const nodeBefore = $cursor.nodeBefore;
|
|
578
|
-
if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
|
|
579
|
-
return null;
|
|
580
|
-
};
|
|
581
|
-
var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
|
|
582
|
-
constructor(..._args) {
|
|
583
|
-
super(..._args);
|
|
584
|
-
this.name = "RteCursorFix";
|
|
617
|
+
var RteInstanceImpl = class {
|
|
618
|
+
getFeature(name) {
|
|
619
|
+
const f = this.config.featureMap.get(name);
|
|
620
|
+
if (!f) throw new Error(`Feature not found: ${name}`);
|
|
621
|
+
return f;
|
|
585
622
|
}
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (
|
|
623
|
+
getPublicInterface(Feature, featureId) {
|
|
624
|
+
const instances = this.config.featureFacadesMap.get(Feature);
|
|
625
|
+
if (!instances || instances.length === 0) throw new Error(`Feature not found`);
|
|
626
|
+
if (instances[0].featureId !== void 0) {
|
|
627
|
+
if (featureId === void 0) throw new Error(`No featureId provided for multi-instance feature.`);
|
|
628
|
+
const instance = instances.find((f) => f.featureId === featureId);
|
|
629
|
+
if (!instance) throw new Error(`Feature with id "${featureId}" not found`);
|
|
630
|
+
return instance.getPublicInterface(this);
|
|
591
631
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (!$cursor) return null;
|
|
595
|
-
let cursorFix = null;
|
|
596
|
-
for (const fn of cursorFixes) {
|
|
597
|
-
const result = fn($cursor, state);
|
|
598
|
-
if (result) Object.assign(cursorFix ??= {}, result);
|
|
599
|
-
}
|
|
600
|
-
if (!cursorFix) return null;
|
|
601
|
-
return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
|
|
602
|
-
const span = document.createElement("span");
|
|
603
|
-
span.textContent = "";
|
|
604
|
-
for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
|
|
605
|
-
return span;
|
|
606
|
-
}, { side: -1 })]);
|
|
607
|
-
} } }))];
|
|
632
|
+
if (featureId !== void 0) throw new Error(`Feature does not support featureId`);
|
|
633
|
+
return instances[0].getPublicInterface(this);
|
|
608
634
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
635
|
+
constructor(instanceFacade, configFacade, options) {
|
|
636
|
+
this.options = options;
|
|
637
|
+
this.view = null;
|
|
638
|
+
this.dispatchTransaction = (tr) => {
|
|
639
|
+
const prevState = this.state;
|
|
640
|
+
this.state = this.state.apply(tr);
|
|
641
|
+
this.view?.updateState(this.state);
|
|
642
|
+
if (prevState.doc !== this.state.doc) this.options?.onChange?.();
|
|
643
|
+
return this.state;
|
|
644
|
+
};
|
|
645
|
+
this.facade = instanceFacade;
|
|
646
|
+
const config = configFacade[impl];
|
|
647
|
+
this.config = config;
|
|
648
|
+
this.schema = config.schema;
|
|
649
|
+
this.textblockAttrs = config.textblockAttrs;
|
|
650
|
+
this.features = config.features;
|
|
651
|
+
this.foreignHtmlParser = options?.foreignHtmlParser ?? new RteHtmlParser(configFacade);
|
|
652
|
+
this.foreignHtmlSerializer = options?.foreignHtmlSerializer ?? new RteHtmlSerializer(configFacade);
|
|
653
|
+
this.styles = new ElementStyles(sortedContributions(config.features.flatMap((f) => f.getStyles())));
|
|
654
|
+
this.initState(options?.initialDocument);
|
|
624
655
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
656
|
+
initState(initialDoc) {
|
|
657
|
+
this.state = EditorState.create({
|
|
658
|
+
doc: parseDocument(this.schema, initialDoc),
|
|
659
|
+
schema: this.config.schema,
|
|
660
|
+
plugins: sortedContributions(this.config.features.flatMap((feature) => feature.getPlugins(this)))
|
|
661
|
+
});
|
|
631
662
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
];
|
|
663
|
+
reset(initialDocument) {
|
|
664
|
+
const currentHostState = hostBridgePlugin.getState(this.state);
|
|
665
|
+
this.initState(initialDocument);
|
|
666
|
+
this.updateHostState(currentHostState);
|
|
667
|
+
this.view?.updateState(this.state);
|
|
638
668
|
}
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
})
|
|
650
|
-
return [
|
|
651
|
-
this.contribution(this.disabled.plugin),
|
|
652
|
-
this.contribution(new Plugin({
|
|
653
|
-
props: {
|
|
654
|
-
editable: () => !this.disabled.getValue(rte),
|
|
655
|
-
handleDOMEvents: { click: (_view, event) => {
|
|
656
|
-
if (this.disabled.getValue(rte)) {
|
|
657
|
-
event.preventDefault();
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
return false;
|
|
661
|
-
} }
|
|
662
|
-
},
|
|
663
|
-
view: (view) => {
|
|
664
|
-
const popovers = view.dom.getRootNode().querySelector(".popovers");
|
|
665
|
-
const updateDisabled = () => {
|
|
666
|
-
const disabled = this.disabled.getValue(rte);
|
|
667
|
-
popovers.classList.toggle("popovers--disabled", disabled);
|
|
668
|
-
view.dom.parentElement.classList.toggle("editor--disabled", disabled);
|
|
669
|
-
};
|
|
670
|
-
updateDisabled();
|
|
671
|
-
return { update: () => {
|
|
672
|
-
updateDisabled();
|
|
673
|
-
} };
|
|
674
|
-
}
|
|
675
|
-
})),
|
|
676
|
-
this.contribution(keymap({
|
|
677
|
-
...baseKeymap,
|
|
678
|
-
Enter: enterKeyChainCommands,
|
|
679
|
-
"Shift-Enter": enterKeyChainCommands
|
|
680
|
-
})),
|
|
681
|
-
this.contribution(dropCursor()),
|
|
682
|
-
this.contribution(gapCursor()),
|
|
683
|
-
this.contribution(hostBridgePlugin)
|
|
684
|
-
];
|
|
685
|
-
}
|
|
686
|
-
getFeatures() {
|
|
687
|
-
return [
|
|
688
|
-
this,
|
|
689
|
-
new RteHistoryFeatureImpl(),
|
|
690
|
-
new RteForeignHtmlFeatureImpl(),
|
|
691
|
-
new RteCursorFixFeatureImpl()
|
|
692
|
-
];
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
featureFacade(RteCoreImpl);
|
|
696
|
-
//#endregion
|
|
697
|
-
//#region src/lib/rich-text-editor/rte/utils/sanitization.ts
|
|
698
|
-
var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
699
|
-
var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
700
|
-
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
701
|
-
var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
|
|
702
|
-
/**
|
|
703
|
-
* Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
|
|
704
|
-
* Returns empty string if the URL is unsafe.
|
|
705
|
-
*/
|
|
706
|
-
var sanitizeLinkHref = (url) => {
|
|
707
|
-
if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
|
|
708
|
-
const anchor = document.createElement("a");
|
|
709
|
-
anchor.setAttribute("href", url);
|
|
710
|
-
/* v8 ignore next -- since href is already validated it's probably always present @preserve */
|
|
711
|
-
return DOMPurify.sanitize(anchor, {
|
|
712
|
-
RETURN_DOM: true,
|
|
713
|
-
...domPurifyConfig
|
|
714
|
-
}).querySelector("a").getAttribute("href") ?? "";
|
|
715
|
-
};
|
|
716
|
-
/**
|
|
717
|
-
* Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
|
|
718
|
-
* Returns empty string if the URL is unsafe.
|
|
719
|
-
*/
|
|
720
|
-
var sanitizeImageSrc = (url) => {
|
|
721
|
-
const img = document.createElement("img");
|
|
722
|
-
img.setAttribute("src", url);
|
|
723
|
-
return DOMPurify.sanitize(img, {
|
|
724
|
-
RETURN_DOM: true,
|
|
725
|
-
...domPurifyConfig
|
|
726
|
-
}).querySelector("img").getAttribute("src") ?? "";
|
|
727
|
-
};
|
|
728
|
-
/**
|
|
729
|
-
* Escapes a CSS value for insertion into style attribute.
|
|
730
|
-
* E.g. "15px; background: red" => "15px"
|
|
731
|
-
*/
|
|
732
|
-
var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
|
|
733
|
-
//#endregion
|
|
734
|
-
//#region src/lib/rich-text-editor/rte/html-parser.ts
|
|
735
|
-
var copy = (obj) => ({ ...obj });
|
|
736
|
-
var parseRulesFromSchema = (schema) => ({
|
|
737
|
-
nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
|
|
738
|
-
marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
|
|
739
|
-
});
|
|
740
|
-
var RteHtmlParser = class {
|
|
741
|
-
constructor(config, options) {
|
|
742
|
-
this[impl] = new RteHtmlParserImpl(config[impl], options);
|
|
669
|
+
createView(target) {
|
|
670
|
+
const core = this.config.featureMap.get("RteCore");
|
|
671
|
+
this.view = new EditorView((editor) => {
|
|
672
|
+
editor.part = "editor";
|
|
673
|
+
target.appendChild(editor);
|
|
674
|
+
}, {
|
|
675
|
+
state: this.state,
|
|
676
|
+
dispatchTransaction: this.dispatchTransaction,
|
|
677
|
+
...core?.scrollThreshold !== void 0 && { scrollThreshold: core.scrollThreshold },
|
|
678
|
+
...core?.scrollMargin !== void 0 && { scrollMargin: core.scrollMargin }
|
|
679
|
+
});
|
|
743
680
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
681
|
+
destroyViewIfNeeded() {
|
|
682
|
+
if (this.view) {
|
|
683
|
+
this.view.destroy();
|
|
684
|
+
this.view = null;
|
|
685
|
+
}
|
|
749
686
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
*/
|
|
753
|
-
parseFragment(html, options) {
|
|
754
|
-
return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
|
|
687
|
+
createComponent(type) {
|
|
688
|
+
return document.createElement(this.hostState().ctx.tagFor(type, true));
|
|
755
689
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
RETURN_DOM: true,
|
|
759
|
-
...domPurifyConfig
|
|
760
|
-
});
|
|
761
|
-
const container = document.createDocumentFragment();
|
|
762
|
-
container.appendChild(dom);
|
|
763
|
-
options?.modifyDom?.(container);
|
|
764
|
-
return container;
|
|
690
|
+
getLocale() {
|
|
691
|
+
return this.hostState().locale;
|
|
765
692
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
constructor(config, options) {
|
|
769
|
-
const rules = parseRulesFromSchema(config.schema);
|
|
770
|
-
options?.modifyParseRules?.(rules);
|
|
771
|
-
this.parser = buildDomParser(config.schema, rules);
|
|
693
|
+
get tr() {
|
|
694
|
+
return this.state.tr;
|
|
772
695
|
}
|
|
773
|
-
|
|
774
|
-
|
|
696
|
+
hostState() {
|
|
697
|
+
const state = hostBridgePlugin.getState(this.state);
|
|
698
|
+
if (!state) throw new Error("No host state available");
|
|
699
|
+
return state;
|
|
775
700
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const parserRules = [];
|
|
779
|
-
const priority = (rule) => rule.priority ?? 50;
|
|
780
|
-
function insert(rule) {
|
|
781
|
-
let i = 0;
|
|
782
|
-
for (; i < parserRules.length; i++) {
|
|
783
|
-
const next = parserRules[i];
|
|
784
|
-
if (priority(next) < priority(rule)) break;
|
|
785
|
-
}
|
|
786
|
-
parserRules.splice(i, 0, rule);
|
|
701
|
+
updateHostState(hostState) {
|
|
702
|
+
this.dispatchTransaction(this.tr.setMeta(hostBridgePlugin, hostState));
|
|
787
703
|
}
|
|
788
|
-
for (const name in marks) marks[name]?.forEach((rule) => {
|
|
789
|
-
insert(rule = copy(rule));
|
|
790
|
-
if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
|
|
791
|
-
});
|
|
792
|
-
for (const name in nodes) nodes[name]?.forEach((rule) => {
|
|
793
|
-
insert(rule = copy(rule));
|
|
794
|
-
if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
|
|
795
|
-
});
|
|
796
|
-
parserRules.push({
|
|
797
|
-
tag: "br",
|
|
798
|
-
closeParent: true
|
|
799
|
-
});
|
|
800
|
-
return new DOMParser(schema, parserRules);
|
|
801
704
|
};
|
|
802
705
|
//#endregion
|
|
803
|
-
//#region src/lib/rich-text-editor/rte/
|
|
804
|
-
var
|
|
805
|
-
constructor(
|
|
806
|
-
this
|
|
706
|
+
//#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
|
|
707
|
+
var TextblockAttrs = class {
|
|
708
|
+
constructor(specs) {
|
|
709
|
+
this.specs = specs;
|
|
710
|
+
this.attrs = {};
|
|
711
|
+
for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
|
|
807
712
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
*/
|
|
811
|
-
serializeDocument(doc, options) {
|
|
812
|
-
return this[impl].serializeFragment(doc.content, options);
|
|
713
|
+
fromDOM(dom) {
|
|
714
|
+
return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
|
|
813
715
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
*/
|
|
817
|
-
serializeFragment(fragment, options) {
|
|
818
|
-
return this[impl].serializeFragment(fragment, options);
|
|
716
|
+
getStyle(node) {
|
|
717
|
+
return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
|
|
819
718
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
this.config = config;
|
|
824
|
-
const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
|
|
825
|
-
Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
|
|
826
|
-
Object.assign(serializers.marks, options?.serializers?.marks ?? {});
|
|
827
|
-
this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
|
|
719
|
+
getDOMAttrsProperties(node) {
|
|
720
|
+
if (!this.specs.length) return {};
|
|
721
|
+
return { style: this.getStyle(node) };
|
|
828
722
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
marks: {}
|
|
833
|
-
};
|
|
834
|
-
for (const name in schema.marks) {
|
|
835
|
-
const toDOM = schema.marks[name].spec.toDOM;
|
|
836
|
-
if (toDOM) result.marks[name] = toDOM;
|
|
837
|
-
}
|
|
838
|
-
for (const name in schema.nodes) {
|
|
839
|
-
const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
|
|
840
|
-
if (toDOM) result.nodes[name] = toDOM;
|
|
841
|
-
}
|
|
842
|
-
result.nodes.text = (node) => document.createTextNode(node.text);
|
|
843
|
-
return result;
|
|
723
|
+
getDOMAttrs(node) {
|
|
724
|
+
if (!this.specs.length) return [];
|
|
725
|
+
return [this.getDOMAttrsProperties(node)];
|
|
844
726
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
const serializedFragment = this.serializer.serializeFragment(parsedFragment);
|
|
848
|
-
const container = document.createDocumentFragment();
|
|
849
|
-
container.appendChild(serializedFragment);
|
|
850
|
-
options?.modifyDom?.(container);
|
|
851
|
-
const output = document.createElement("div");
|
|
852
|
-
output.appendChild(container);
|
|
853
|
-
return output.innerHTML;
|
|
727
|
+
extractFromNode(node) {
|
|
728
|
+
return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
|
|
854
729
|
}
|
|
855
730
|
};
|
|
856
731
|
//#endregion
|
|
857
|
-
//#region src/lib/rich-text-editor/rte/
|
|
858
|
-
var
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
node.check();
|
|
862
|
-
return node;
|
|
863
|
-
};
|
|
864
|
-
var RteInstance = class {
|
|
865
|
-
constructor(config, options) {
|
|
866
|
-
this.options = options;
|
|
867
|
-
this.feature = (Feature, featureId) => {
|
|
868
|
-
return this[impl].getPublicInterface(Feature, featureId);
|
|
869
|
-
};
|
|
870
|
-
this[impl] = new RteInstanceImpl(this, config, options);
|
|
732
|
+
//#region src/lib/rich-text-editor/rte/utils/textblock-marks.ts
|
|
733
|
+
var TextblockMarks = class {
|
|
734
|
+
constructor(specs) {
|
|
735
|
+
this.specs = specs;
|
|
871
736
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
*/
|
|
875
|
-
getDocument() {
|
|
876
|
-
return this[impl].state.doc.toJSON();
|
|
737
|
+
getAllowedMarksForNode(nodeName) {
|
|
738
|
+
return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
|
|
877
739
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
*/
|
|
881
|
-
reset(initialDocument) {
|
|
882
|
-
this[impl].reset(initialDocument);
|
|
883
|
-
}
|
|
884
|
-
/**
|
|
885
|
-
* Replaces the current selection with the given content.
|
|
886
|
-
* If no text is selected, this inserts the content at the cursor position.
|
|
887
|
-
*/
|
|
888
|
-
replaceSelection(content, options) {
|
|
889
|
-
const { state, tr, schema, dispatchTransaction } = this[impl];
|
|
890
|
-
tr.replaceSelection(new Slice(Fragment.fromJSON(schema, content), 0, 0));
|
|
891
|
-
tr.doc.check();
|
|
892
|
-
const $from = tr.doc.resolve(tr.mapping.map(state.selection.from));
|
|
893
|
-
const $to = tr.doc.resolve(tr.selection.to);
|
|
894
|
-
const [$head, $anchor] = options?.cursorPlacement === "start" ? [$from, $to] : [$to, $from];
|
|
895
|
-
if (options?.selectContent) tr.setSelection(state.selection instanceof AllSelection ? new AllSelection(tr.doc) : TextSelection.between($anchor, $head));
|
|
896
|
-
else tr.setSelection(TextSelection.between($head, $head));
|
|
897
|
-
dispatchTransaction(tr);
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Replaces the entire document with the given new document.
|
|
901
|
-
* Unlike reset, this preserves the rest of the editor state. The undo history is preserved, so the user can undo the replacement.
|
|
902
|
-
*/
|
|
903
|
-
replaceDocument(newDocument, options) {
|
|
904
|
-
const { state, tr, schema, dispatchTransaction } = this[impl];
|
|
905
|
-
tr.replaceWith(0, state.doc.content.size, parseDocument(schema, newDocument));
|
|
906
|
-
if (options?.selectContent) tr.setSelection(new AllSelection(tr.doc));
|
|
907
|
-
else if (options?.cursorPlacement === "end") tr.setSelection(TextSelection.atEnd(tr.doc));
|
|
908
|
-
else tr.setSelection(TextSelection.atStart(tr.doc));
|
|
909
|
-
dispatchTransaction(tr);
|
|
740
|
+
getReferencedNodeNames() {
|
|
741
|
+
return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
|
|
910
742
|
}
|
|
911
743
|
};
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/lib/rich-text-editor/rte/view.ts
|
|
746
|
+
/**
|
|
747
|
+
* Converts an RteDocument to an RteView tree for rendering outside the editor.
|
|
748
|
+
* The logic is adapted from ProseMirror's DOM serialization to ensure the resulting structure
|
|
749
|
+
* matches the editor's HTML structure.
|
|
750
|
+
*/
|
|
751
|
+
var convertToView = (doc, ctx) => {
|
|
752
|
+
const schema = ctx.config[impl].schema;
|
|
753
|
+
const getMarkRank = (mark) => schema.marks[mark.type].rank;
|
|
754
|
+
const marksEqual = (a, b) => {
|
|
755
|
+
if (a.type !== b.type) return false;
|
|
756
|
+
const attrsA = a.attrs;
|
|
757
|
+
const attrsB = b.attrs;
|
|
758
|
+
if (attrsA === attrsB) return true;
|
|
759
|
+
/* v8 ignore next -- not currently reachable since all attrs are required @preserve */
|
|
760
|
+
if (!attrsA || !attrsB) return false;
|
|
761
|
+
const keysA = Object.keys(attrsA);
|
|
762
|
+
const keysB = Object.keys(attrsB);
|
|
763
|
+
/* v8 ignore next -- not currently reachable since all attrs are required @preserve */
|
|
764
|
+
if (keysA.length !== keysB.length) return false;
|
|
765
|
+
for (const key of keysA) if (attrsA[key] !== attrsB[key]) return false;
|
|
766
|
+
return true;
|
|
767
|
+
};
|
|
768
|
+
const convertFragment = (nodes) => {
|
|
769
|
+
const result = [];
|
|
770
|
+
const active = [];
|
|
771
|
+
const activeContents = [result];
|
|
772
|
+
for (const node of nodes) {
|
|
773
|
+
const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
|
|
774
|
+
let keep = 0;
|
|
775
|
+
let rendered = 0;
|
|
776
|
+
while (keep < active.length && rendered < nodeMarks.length) {
|
|
777
|
+
if (!marksEqual(active[keep], nodeMarks[rendered])) break;
|
|
778
|
+
keep++;
|
|
779
|
+
rendered++;
|
|
780
|
+
}
|
|
781
|
+
while (active.length > keep) {
|
|
782
|
+
active.pop();
|
|
783
|
+
activeContents.pop();
|
|
784
|
+
}
|
|
785
|
+
while (rendered < nodeMarks.length) {
|
|
786
|
+
const mark = nodeMarks[rendered];
|
|
787
|
+
const markContent = [];
|
|
788
|
+
activeContents[activeContents.length - 1].push({
|
|
789
|
+
type: "mark",
|
|
790
|
+
mark,
|
|
791
|
+
children: {
|
|
792
|
+
type: "fragment",
|
|
793
|
+
content: markContent,
|
|
794
|
+
[impl]: ctx
|
|
795
|
+
},
|
|
796
|
+
[impl]: ctx
|
|
797
|
+
});
|
|
798
|
+
active.push(mark);
|
|
799
|
+
activeContents.push(markContent);
|
|
800
|
+
rendered++;
|
|
801
|
+
}
|
|
802
|
+
activeContents[activeContents.length - 1].push({
|
|
803
|
+
type: "node",
|
|
804
|
+
node,
|
|
805
|
+
children: convertFragment(node.content ?? []),
|
|
806
|
+
[impl]: ctx
|
|
807
|
+
});
|
|
926
808
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
this.options = options;
|
|
932
|
-
this.view = null;
|
|
933
|
-
this.dispatchTransaction = (tr) => {
|
|
934
|
-
const prevState = this.state;
|
|
935
|
-
this.state = this.state.apply(tr);
|
|
936
|
-
this.view?.updateState(this.state);
|
|
937
|
-
if (prevState.doc !== this.state.doc) this.options?.onChange?.();
|
|
938
|
-
return this.state;
|
|
809
|
+
return {
|
|
810
|
+
type: "fragment",
|
|
811
|
+
content: result,
|
|
812
|
+
[impl]: ctx
|
|
939
813
|
};
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
this
|
|
953
|
-
doc: parseDocument(this.schema, initialDoc),
|
|
954
|
-
schema: this.config.schema,
|
|
955
|
-
plugins: sortedContributions(this.config.features.flatMap((feature) => feature.getPlugins(this)))
|
|
956
|
-
});
|
|
814
|
+
};
|
|
815
|
+
return {
|
|
816
|
+
type: "node",
|
|
817
|
+
node: doc,
|
|
818
|
+
children: convertFragment(doc.content),
|
|
819
|
+
[impl]: ctx
|
|
820
|
+
};
|
|
821
|
+
};
|
|
822
|
+
//#endregion
|
|
823
|
+
//#region src/lib/rich-text-editor/rte/config.ts
|
|
824
|
+
var RteConfig = class {
|
|
825
|
+
constructor(features) {
|
|
826
|
+
this[impl] = new RteConfigImpl(features);
|
|
957
827
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
this.initState(initialDocument);
|
|
961
|
-
this.updateHostState(currentHostState);
|
|
962
|
-
this.view?.updateState(this.state);
|
|
828
|
+
instantiateEditor(options) {
|
|
829
|
+
return new RteInstance(this, options);
|
|
963
830
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}, {
|
|
969
|
-
state: this.state,
|
|
970
|
-
dispatchTransaction: this.dispatchTransaction
|
|
831
|
+
instantiateView(document, options) {
|
|
832
|
+
return convertToView(document, {
|
|
833
|
+
config: this,
|
|
834
|
+
options: { ...options }
|
|
971
835
|
});
|
|
972
836
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
837
|
+
};
|
|
838
|
+
var RteConfigImpl = class {
|
|
839
|
+
constructor(featuresFacades) {
|
|
840
|
+
const resolveFeatures = (features) => features.flatMap((f) => f.getFeatures().flatMap((subFeature) => subFeature === f ? f : resolveFeatures([subFeature])));
|
|
841
|
+
this.features = resolveFeatures(featuresFacades.map(getFeatureImpl));
|
|
842
|
+
this.featureMap = /* @__PURE__ */ new Map();
|
|
843
|
+
for (const f of this.features) {
|
|
844
|
+
if (this.featureMap.has(f.name)) throw new Error(`Duplicate feature: ${f.name}`);
|
|
845
|
+
this.featureMap.set(f.name, f);
|
|
977
846
|
}
|
|
847
|
+
this.featureFacadesMap = /* @__PURE__ */ new Map();
|
|
848
|
+
for (const facade of featuresFacades) {
|
|
849
|
+
const FacadeClass = facade.constructor;
|
|
850
|
+
const feature = getFeatureImpl(facade);
|
|
851
|
+
const instances = this.featureFacadesMap.get(FacadeClass);
|
|
852
|
+
if (instances) instances.push(feature);
|
|
853
|
+
else this.featureFacadesMap.set(FacadeClass, [feature]);
|
|
854
|
+
}
|
|
855
|
+
if (!this.featureMap.has("RteBase")) throw new Error("RteBase feature is required");
|
|
856
|
+
if (this.featureMap.has("RteLinkFeature") && !this.featureMap.has("RteToolbarFeature")) throw new Error("RteToolbarFeature is required for RteLinkFeature");
|
|
857
|
+
this.textblockAttrs = new TextblockAttrs(sortedContributions(this.features.flatMap((f) => f.getTextblockAttrs())));
|
|
858
|
+
this.textblockMarks = new TextblockMarks(sortedContributions(this.features.flatMap((f) => f.getTextblockMarks())));
|
|
859
|
+
const schemaContributions = sortedContributions(this.features.flatMap((f) => f.getSchema(this.textblockAttrs, this.textblockMarks)));
|
|
860
|
+
const schemaSpec = {
|
|
861
|
+
nodes: {},
|
|
862
|
+
marks: {}
|
|
863
|
+
};
|
|
864
|
+
for (const schema of schemaContributions) {
|
|
865
|
+
Object.assign(schemaSpec.nodes, schema.nodes ?? {});
|
|
866
|
+
Object.assign(schemaSpec.marks, schema.marks ?? {});
|
|
867
|
+
}
|
|
868
|
+
for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
|
|
869
|
+
this.schema = new Schema(schemaSpec);
|
|
978
870
|
}
|
|
979
|
-
|
|
980
|
-
|
|
871
|
+
};
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/lib/rich-text-editor/rte/utils/ui.ts
|
|
874
|
+
var isPropBinding = (prop) => typeof prop === "function";
|
|
875
|
+
var on = (event, prop, handler) => [
|
|
876
|
+
event,
|
|
877
|
+
prop,
|
|
878
|
+
handler
|
|
879
|
+
];
|
|
880
|
+
var UiCtx = class {
|
|
881
|
+
constructor(view, rte, props) {
|
|
882
|
+
this.view = view;
|
|
883
|
+
this.rte = rte;
|
|
884
|
+
this.props = props;
|
|
885
|
+
this.bindings = [];
|
|
981
886
|
}
|
|
982
|
-
|
|
983
|
-
return
|
|
887
|
+
evalProp(prop) {
|
|
888
|
+
return isPropBinding(prop) ? prop(this) : prop;
|
|
984
889
|
}
|
|
985
|
-
|
|
986
|
-
return
|
|
890
|
+
bindProp(prop, bindFn) {
|
|
891
|
+
if (prop === void 0) return;
|
|
892
|
+
else if (isPropBinding(prop)) {
|
|
893
|
+
const binding = () => bindFn(prop(this));
|
|
894
|
+
this.bindings.push(binding);
|
|
895
|
+
binding();
|
|
896
|
+
} else bindFn(prop);
|
|
987
897
|
}
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
if (!state) throw new Error("No host state available");
|
|
991
|
-
return state;
|
|
898
|
+
updateBindings() {
|
|
899
|
+
for (const binding of this.bindings) binding();
|
|
992
900
|
}
|
|
993
|
-
|
|
994
|
-
this.
|
|
901
|
+
shouldReturnFocusToEditor() {
|
|
902
|
+
return this.evalProp(this.props.shouldReturnFocusToEditor);
|
|
903
|
+
}
|
|
904
|
+
bindToEl(target, props = {}, events = [], children = []) {
|
|
905
|
+
for (const name in props) this.bindProp(props[name], (value) => {
|
|
906
|
+
target[name] = value;
|
|
907
|
+
});
|
|
908
|
+
for (const [name, value, bindFn] of events) if (value) target.addEventListener(name, bindFn(value));
|
|
909
|
+
for (const child of children) target.appendChild(child);
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
var createDiv = (ctx, props) => {
|
|
913
|
+
const div = document.createElement("div");
|
|
914
|
+
ctx.bindToEl(div, {
|
|
915
|
+
className: props.className,
|
|
916
|
+
slot: props.slot
|
|
917
|
+
}, [], props.children);
|
|
918
|
+
return div;
|
|
919
|
+
};
|
|
920
|
+
var createAnchor = (ctx, props) => {
|
|
921
|
+
const anchor = document.createElement("a");
|
|
922
|
+
ctx.bindToEl(anchor, {
|
|
923
|
+
href: props.href,
|
|
924
|
+
target: props.target,
|
|
925
|
+
rel: props.rel,
|
|
926
|
+
className: props.className
|
|
927
|
+
}, [], props.children);
|
|
928
|
+
return anchor;
|
|
929
|
+
};
|
|
930
|
+
var wrapperTargets = /* @__PURE__ */ new WeakMap();
|
|
931
|
+
var createOptionalTooltip = (ctx, props) => {
|
|
932
|
+
const tooltip = ctx.rte.createComponent(Tooltip);
|
|
933
|
+
tooltip.setAttribute("exportparts", "vvd-theme-alternate");
|
|
934
|
+
ctx.bindToEl(tooltip, {
|
|
935
|
+
className: "ui-tooltip",
|
|
936
|
+
text: props.label,
|
|
937
|
+
placement: ctx.props.popupPlacement
|
|
938
|
+
});
|
|
939
|
+
ctx.bindProp(props.enabled, (enabled) => {
|
|
940
|
+
if (!enabled) tooltip.open = false;
|
|
941
|
+
tooltip.anchor = enabled ? props.anchor : void 0;
|
|
942
|
+
});
|
|
943
|
+
const wrapper = createDiv(ctx, {
|
|
944
|
+
className: "ui-tooltip-wrapper",
|
|
945
|
+
slot: props.slot,
|
|
946
|
+
children: [props.anchor, tooltip]
|
|
947
|
+
});
|
|
948
|
+
wrapperTargets.set(wrapper, props.anchor);
|
|
949
|
+
return wrapper;
|
|
950
|
+
};
|
|
951
|
+
var createButton = (ctx, props) => {
|
|
952
|
+
const variant = () => ctx.evalProp(props.variant) ?? "toolbar";
|
|
953
|
+
const size = () => variant() === "toolbar" ? "super-condensed" : "condensed";
|
|
954
|
+
const appearanceInactive = () => variant() === "popover-primary" ? "outlined" : "ghost-light";
|
|
955
|
+
const appearanceActive = () => variant() === "toolbar-menu" || variant() === "popover" ? "filled" : appearanceInactive();
|
|
956
|
+
const appearance = () => ctx.evalProp(props.active) ? appearanceActive() : appearanceInactive();
|
|
957
|
+
const connotation = () => ctx.evalProp(props.connotation) ?? (variant() === "toolbar-menu" && ctx.evalProp(props.active) ? "cta" : void 0);
|
|
958
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
959
|
+
const button = ctx.rte.createComponent(Button);
|
|
960
|
+
ctx.bindToEl(button, {
|
|
961
|
+
size,
|
|
962
|
+
appearance,
|
|
963
|
+
disabled,
|
|
964
|
+
icon: props.icon,
|
|
965
|
+
autofocus: props.autofocus,
|
|
966
|
+
connotation,
|
|
967
|
+
[props.icon ? "ariaLabel" : "label"]: props.label,
|
|
968
|
+
pressed: () => Boolean(ctx.evalProp(props.active)),
|
|
969
|
+
ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
|
|
970
|
+
}, [on("click", props.onClick, (onClick) => () => {
|
|
971
|
+
if (!onClick() && ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
972
|
+
})]);
|
|
973
|
+
return createOptionalTooltip(ctx, {
|
|
974
|
+
enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
|
|
975
|
+
label: props.label,
|
|
976
|
+
anchor: button,
|
|
977
|
+
slot: props.slot
|
|
978
|
+
});
|
|
979
|
+
};
|
|
980
|
+
var createMenu = (ctx, props) => {
|
|
981
|
+
const menu = ctx.rte.createComponent(Menu);
|
|
982
|
+
ctx.bindToEl(menu, {
|
|
983
|
+
className: "ui-menu",
|
|
984
|
+
autoDismiss: true,
|
|
985
|
+
ariaLabel: props.label,
|
|
986
|
+
anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
|
|
987
|
+
placement: ctx.props.popupPlacement,
|
|
988
|
+
offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
|
|
989
|
+
}, [], props.children);
|
|
990
|
+
const fragment = document.createDocumentFragment();
|
|
991
|
+
fragment.appendChild(props.trigger);
|
|
992
|
+
fragment.appendChild(menu);
|
|
993
|
+
return fragment;
|
|
994
|
+
};
|
|
995
|
+
var createMenuItem = (ctx, props) => {
|
|
996
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
997
|
+
const item = ctx.rte.createComponent(MenuItem);
|
|
998
|
+
ctx.bindToEl(item, {
|
|
999
|
+
text: props.text,
|
|
1000
|
+
checked: props.checked,
|
|
1001
|
+
disabled,
|
|
1002
|
+
controlType: "radio",
|
|
1003
|
+
checkedAppearance: "tick-only"
|
|
1004
|
+
}, [on("change", props.onSelect, (onSelect) => () => {
|
|
1005
|
+
if (item.checked && !item.disabled) {
|
|
1006
|
+
if (ctx.evalProp(props.checked) !== item.checked) {
|
|
1007
|
+
onSelect();
|
|
1008
|
+
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
})]);
|
|
1012
|
+
return item;
|
|
1013
|
+
};
|
|
1014
|
+
var createButtonGroup = (ctx, props) => createDiv(ctx, {
|
|
1015
|
+
slot: props.slot,
|
|
1016
|
+
className: "ui-button-group",
|
|
1017
|
+
children: props.children
|
|
1018
|
+
});
|
|
1019
|
+
function markActive(state, type) {
|
|
1020
|
+
const { from, $from, to, empty } = state.selection;
|
|
1021
|
+
if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
|
|
1022
|
+
else return state.doc.rangeHasMark(from, to, type);
|
|
1023
|
+
}
|
|
1024
|
+
var createMarkToggle = (ctx, props) => createButton(ctx, {
|
|
1025
|
+
label: props.label,
|
|
1026
|
+
icon: props.icon,
|
|
1027
|
+
active: () => markActive(ctx.view.state, props.markType),
|
|
1028
|
+
disabled: () => !toggleMark(props.markType)(ctx.view.state),
|
|
1029
|
+
onClick: () => {
|
|
1030
|
+
toggleMark(props.markType)(ctx.view.state, ctx.view.dispatch);
|
|
995
1031
|
}
|
|
1032
|
+
});
|
|
1033
|
+
var createOption = (ctx, props) => {
|
|
1034
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
|
|
1035
|
+
const option = ctx.rte.createComponent(ListboxOption);
|
|
1036
|
+
ctx.bindToEl(option, {
|
|
1037
|
+
value: props.value,
|
|
1038
|
+
text: props.text,
|
|
1039
|
+
disabled
|
|
1040
|
+
});
|
|
1041
|
+
return option;
|
|
1042
|
+
};
|
|
1043
|
+
var createSelect = (ctx, props) => {
|
|
1044
|
+
const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled));
|
|
1045
|
+
const select = ctx.rte.createComponent(Select);
|
|
1046
|
+
select.setAttribute("data-class", "ui-select");
|
|
1047
|
+
ctx.bindToEl(select, {
|
|
1048
|
+
placeholder: " ",
|
|
1049
|
+
ariaLabel: props.label,
|
|
1050
|
+
appearance: "ghost",
|
|
1051
|
+
disabled
|
|
1052
|
+
}, [on("change", props.onSelect, (onSelect) => () => {
|
|
1053
|
+
const value = select.value;
|
|
1054
|
+
if (value) {
|
|
1055
|
+
onSelect(value);
|
|
1056
|
+
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
1057
|
+
}
|
|
1058
|
+
})], props.children);
|
|
1059
|
+
queueMicrotask(() => {
|
|
1060
|
+
ctx.bindProp(props.value, (value) => select.value = value);
|
|
1061
|
+
});
|
|
1062
|
+
let hideTooltip = false;
|
|
1063
|
+
select.addEventListener("vwc-popup:open", () => {
|
|
1064
|
+
hideTooltip = true;
|
|
1065
|
+
ctx.updateBindings();
|
|
1066
|
+
});
|
|
1067
|
+
select.addEventListener("vwc-popup:close", () => {
|
|
1068
|
+
hideTooltip = false;
|
|
1069
|
+
ctx.updateBindings();
|
|
1070
|
+
});
|
|
1071
|
+
return createOptionalTooltip(ctx, {
|
|
1072
|
+
enabled: () => !hideTooltip && !disabled(),
|
|
1073
|
+
label: props.label,
|
|
1074
|
+
anchor: select
|
|
1075
|
+
});
|
|
1076
|
+
};
|
|
1077
|
+
var createDivider = (ctx) => {
|
|
1078
|
+
const divider = ctx.rte.createComponent(Divider);
|
|
1079
|
+
ctx.bindToEl(divider, {
|
|
1080
|
+
className: "ui-divider",
|
|
1081
|
+
orientation: "vertical"
|
|
1082
|
+
});
|
|
1083
|
+
return divider;
|
|
996
1084
|
};
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
getDOMAttrsProperties(node) {
|
|
1012
|
-
if (!this.specs.length) return {};
|
|
1013
|
-
return { style: this.getStyle(node) };
|
|
1014
|
-
}
|
|
1015
|
-
getDOMAttrs(node) {
|
|
1016
|
-
if (!this.specs.length) return [];
|
|
1017
|
-
return [this.getDOMAttrsProperties(node)];
|
|
1018
|
-
}
|
|
1019
|
-
extractFromNode(node) {
|
|
1020
|
-
return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
|
|
1021
|
-
}
|
|
1085
|
+
var createTextField = (ctx, props) => {
|
|
1086
|
+
const textField = ctx.rte.createComponent(TextField);
|
|
1087
|
+
ctx.bindToEl(textField, {
|
|
1088
|
+
label: props.label,
|
|
1089
|
+
value: props.value,
|
|
1090
|
+
placeholder: props.placeholder,
|
|
1091
|
+
slot: props.slot,
|
|
1092
|
+
autofocus: props.autofocus,
|
|
1093
|
+
type: props.type,
|
|
1094
|
+
helperText: props.helperText
|
|
1095
|
+
}, [on("input", props.onInput, (onInput) => () => {
|
|
1096
|
+
onInput(textField.value);
|
|
1097
|
+
})]);
|
|
1098
|
+
return textField;
|
|
1022
1099
|
};
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
this.specs = specs;
|
|
1028
|
-
}
|
|
1029
|
-
getAllowedMarksForNode(nodeName) {
|
|
1030
|
-
return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
|
|
1031
|
-
}
|
|
1032
|
-
getReferencedNodeNames() {
|
|
1033
|
-
return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
|
|
1034
|
-
}
|
|
1100
|
+
var createText = (ctx, props) => {
|
|
1101
|
+
const textNode = document.createTextNode("");
|
|
1102
|
+
ctx.bindToEl(textNode, { textContent: props.text });
|
|
1103
|
+
return textNode;
|
|
1035
1104
|
};
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
const
|
|
1047
|
-
if (
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const result = [];
|
|
1062
|
-
const active = [];
|
|
1063
|
-
const activeContents = [result];
|
|
1064
|
-
for (const node of nodes) {
|
|
1065
|
-
const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
|
|
1066
|
-
let keep = 0;
|
|
1067
|
-
let rendered = 0;
|
|
1068
|
-
while (keep < active.length && rendered < nodeMarks.length) {
|
|
1069
|
-
if (!marksEqual(active[keep], nodeMarks[rendered])) break;
|
|
1070
|
-
keep++;
|
|
1071
|
-
rendered++;
|
|
1072
|
-
}
|
|
1073
|
-
while (active.length > keep) {
|
|
1074
|
-
active.pop();
|
|
1075
|
-
activeContents.pop();
|
|
1076
|
-
}
|
|
1077
|
-
while (rendered < nodeMarks.length) {
|
|
1078
|
-
const mark = nodeMarks[rendered];
|
|
1079
|
-
const markContent = [];
|
|
1080
|
-
activeContents[activeContents.length - 1].push({
|
|
1081
|
-
type: "mark",
|
|
1082
|
-
mark,
|
|
1083
|
-
children: {
|
|
1084
|
-
type: "fragment",
|
|
1085
|
-
content: markContent,
|
|
1086
|
-
[impl]: ctx
|
|
1087
|
-
},
|
|
1088
|
-
[impl]: ctx
|
|
1089
|
-
});
|
|
1090
|
-
active.push(mark);
|
|
1091
|
-
activeContents.push(markContent);
|
|
1092
|
-
rendered++;
|
|
1093
|
-
}
|
|
1094
|
-
activeContents[activeContents.length - 1].push({
|
|
1095
|
-
type: "node",
|
|
1096
|
-
node,
|
|
1097
|
-
children: convertFragment(node.content ?? []),
|
|
1098
|
-
[impl]: ctx
|
|
1105
|
+
var createSingleSlot = (ctx, props) => {
|
|
1106
|
+
const slot = document.createElement("slot");
|
|
1107
|
+
slot.name = props.name;
|
|
1108
|
+
let currentEl = null;
|
|
1109
|
+
const listeners = [];
|
|
1110
|
+
function cleanup() {
|
|
1111
|
+
for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
|
|
1112
|
+
listeners.length = 0;
|
|
1113
|
+
currentEl = null;
|
|
1114
|
+
}
|
|
1115
|
+
for (const [key, prop] of Object.entries(props.assignedProps)) ctx.bindProp(prop, (value) => {
|
|
1116
|
+
if (currentEl) currentEl[key] = value;
|
|
1117
|
+
});
|
|
1118
|
+
function applyPropsAndEvents(el) {
|
|
1119
|
+
for (const [key, prop] of Object.entries(props.assignedProps)) el[key] = ctx.evalProp(prop);
|
|
1120
|
+
for (const [type, handler] of Object.entries(props.assignedEvents)) {
|
|
1121
|
+
const listener = (e) => {
|
|
1122
|
+
handler(e);
|
|
1123
|
+
if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
|
|
1124
|
+
};
|
|
1125
|
+
el.addEventListener(type, listener);
|
|
1126
|
+
listeners.push({
|
|
1127
|
+
el,
|
|
1128
|
+
type,
|
|
1129
|
+
handler: listener
|
|
1099
1130
|
});
|
|
1100
1131
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
children: convertFragment(doc.content),
|
|
1111
|
-
[impl]: ctx
|
|
1132
|
+
}
|
|
1133
|
+
const processSlot = () => {
|
|
1134
|
+
const first = slot.assignedElements({ flatten: true })[0];
|
|
1135
|
+
if (first === currentEl) return;
|
|
1136
|
+
cleanup();
|
|
1137
|
+
if (first) {
|
|
1138
|
+
currentEl = first;
|
|
1139
|
+
applyPropsAndEvents(first);
|
|
1140
|
+
}
|
|
1112
1141
|
};
|
|
1142
|
+
slot.addEventListener("slotchange", processSlot);
|
|
1143
|
+
queueMicrotask(processSlot);
|
|
1144
|
+
return slot;
|
|
1113
1145
|
};
|
|
1114
1146
|
//#endregion
|
|
1115
|
-
//#region src/lib/rich-text-editor/rte/
|
|
1116
|
-
var
|
|
1117
|
-
constructor(
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
return new RteInstance(this, options);
|
|
1147
|
+
//#region src/lib/rich-text-editor/rte/features/internal/history.ts
|
|
1148
|
+
var RteHistoryFeatureImpl = class extends RteFeatureImpl {
|
|
1149
|
+
constructor(config) {
|
|
1150
|
+
super();
|
|
1151
|
+
this.config = config;
|
|
1152
|
+
this.name = "RteHistoryFeature";
|
|
1122
1153
|
}
|
|
1123
|
-
|
|
1124
|
-
return
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1154
|
+
getPlugins() {
|
|
1155
|
+
return [this.contribution(history()), this.contribution(keymap({
|
|
1156
|
+
"Mod-z": undo,
|
|
1157
|
+
"Ctrl-y": redo,
|
|
1158
|
+
"Cmd-Z": redo
|
|
1159
|
+
}))];
|
|
1128
1160
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
marks: {}
|
|
1155
|
-
};
|
|
1156
|
-
for (const schema of schemaContributions) {
|
|
1157
|
-
Object.assign(schemaSpec.nodes, schema.nodes ?? {});
|
|
1158
|
-
Object.assign(schemaSpec.marks, schema.marks ?? {});
|
|
1159
|
-
}
|
|
1160
|
-
for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
|
|
1161
|
-
this.schema = new Schema(schemaSpec);
|
|
1161
|
+
getToolbarItems() {
|
|
1162
|
+
if (!this.config.showToolbarButtons) return [];
|
|
1163
|
+
return [this.contribution({
|
|
1164
|
+
section: "history",
|
|
1165
|
+
render: (ctx) => createButton(ctx, {
|
|
1166
|
+
label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
|
|
1167
|
+
icon: "undo-line",
|
|
1168
|
+
disabled: (ctx) => !undo(ctx.view.state),
|
|
1169
|
+
onClick: () => {
|
|
1170
|
+
const { state, dispatch } = ctx.view;
|
|
1171
|
+
undo(state, dispatch);
|
|
1172
|
+
}
|
|
1173
|
+
})
|
|
1174
|
+
}, 1), this.contribution({
|
|
1175
|
+
section: "history",
|
|
1176
|
+
render: (ctx) => createButton(ctx, {
|
|
1177
|
+
label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
|
|
1178
|
+
icon: "redo-line",
|
|
1179
|
+
disabled: (ctx) => !redo(ctx.view.state),
|
|
1180
|
+
onClick: () => {
|
|
1181
|
+
const { state, dispatch } = ctx.view;
|
|
1182
|
+
redo(state, dispatch);
|
|
1183
|
+
}
|
|
1184
|
+
})
|
|
1185
|
+
}, 2)];
|
|
1162
1186
|
}
|
|
1163
1187
|
};
|
|
1164
1188
|
//#endregion
|
|
@@ -1344,6 +1368,7 @@ var RteBaseImpl = class extends RteFeatureImpl {
|
|
|
1344
1368
|
return [
|
|
1345
1369
|
this,
|
|
1346
1370
|
new RteCoreImpl(),
|
|
1371
|
+
new RteHistoryFeatureImpl({ showToolbarButtons: this.config?.historyToolbarButtons ?? true }),
|
|
1347
1372
|
new RteBasicTextBlocksImpl({
|
|
1348
1373
|
heading1: this.config?.heading1 ?? false,
|
|
1349
1374
|
heading2: this.config?.heading2 ?? false,
|
|
@@ -1361,6 +1386,20 @@ var RteBaseImpl = class extends RteFeatureImpl {
|
|
|
1361
1386
|
},
|
|
1362
1387
|
set disabled(value) {
|
|
1363
1388
|
core.disabled.setValue(rte, value);
|
|
1389
|
+
},
|
|
1390
|
+
get scrollThreshold() {
|
|
1391
|
+
return core.scrollThreshold;
|
|
1392
|
+
},
|
|
1393
|
+
set scrollThreshold(value) {
|
|
1394
|
+
core.scrollThreshold = value;
|
|
1395
|
+
rte.view?.setProps({ scrollThreshold: value });
|
|
1396
|
+
},
|
|
1397
|
+
get scrollMargin() {
|
|
1398
|
+
return core.scrollMargin;
|
|
1399
|
+
},
|
|
1400
|
+
set scrollMargin(value) {
|
|
1401
|
+
core.scrollMargin = value;
|
|
1402
|
+
rte.view?.setProps({ scrollMargin: value });
|
|
1364
1403
|
}
|
|
1365
1404
|
};
|
|
1366
1405
|
}
|
|
@@ -1951,7 +1990,7 @@ var RteTextColorPickerFeatureImpl = class extends RteFeatureImpl {
|
|
|
1951
1990
|
if (selectionColor === null) selectionColor = observedColor ?? null;
|
|
1952
1991
|
else if (observedColor !== selectionColor) selectionColor = mixedColor;
|
|
1953
1992
|
};
|
|
1954
|
-
state.doc.nodesBetween(from, to, (node,
|
|
1993
|
+
state.doc.nodesBetween(from, to, (node, _pos, parent) => {
|
|
1955
1994
|
if (selectionColor === mixedColor) return false;
|
|
1956
1995
|
if (!node.isLeaf) {
|
|
1957
1996
|
if (node.type.allowsMarkType(textColor) && node.childCount === 0) observeColor(this.defaultColorForNode?.[node.type.name]);
|
|
@@ -2700,7 +2739,7 @@ var InlineImageView = class {
|
|
|
2700
2739
|
this.img.className = "inline-image";
|
|
2701
2740
|
this.update(node);
|
|
2702
2741
|
this.img.addEventListener("load", () => {
|
|
2703
|
-
const pos = this.getPos
|
|
2742
|
+
const pos = this.getPos();
|
|
2704
2743
|
if (pos) {
|
|
2705
2744
|
const { state, dispatch } = this.view;
|
|
2706
2745
|
const currentNode = state.doc.nodeAt(pos);
|
|
@@ -2807,6 +2846,7 @@ var RteInlineImageFeatureImpl = class extends RteFeatureImpl {
|
|
|
2807
2846
|
shouldReturnFocusToEditor: true
|
|
2808
2847
|
});
|
|
2809
2848
|
popover = rte.createComponent(Popover);
|
|
2849
|
+
popover.kind = "imageOptions";
|
|
2810
2850
|
popover.offset = 4;
|
|
2811
2851
|
const content = createDiv(ctx, {
|
|
2812
2852
|
className: "inline-image-popover",
|
|
@@ -2867,7 +2907,7 @@ var RteInlineImageFeatureImpl = class extends RteFeatureImpl {
|
|
|
2867
2907
|
} }))];
|
|
2868
2908
|
}
|
|
2869
2909
|
calculateSmallWidth(naturalWidth) {
|
|
2870
|
-
return Math.min(300, naturalWidth / 2)
|
|
2910
|
+
return `${Math.min(300, naturalWidth / 2)}px`;
|
|
2871
2911
|
}
|
|
2872
2912
|
getSelectedInlineImage(state) {
|
|
2873
2913
|
const sel = state.selection;
|
|
@@ -3000,13 +3040,13 @@ var RteFileHandlerFeatureImpl = class extends RteFeatureImpl {
|
|
|
3000
3040
|
if (resolved.type === "error") console.error("Error in handleFiles handler:", resolved.error);
|
|
3001
3041
|
};
|
|
3002
3042
|
return [this.contribution(insertPointPlaceholderPlugin), this.contribution(new Plugin({ props: {
|
|
3003
|
-
handlePaste: (
|
|
3043
|
+
handlePaste: (_view, event) => {
|
|
3004
3044
|
const files = Array.from(event.clipboardData.files);
|
|
3005
3045
|
if (!files.length) return false;
|
|
3006
3046
|
handleFiles(files, { type: "selection" });
|
|
3007
3047
|
return true;
|
|
3008
3048
|
},
|
|
3009
|
-
handleDrop: (view, event
|
|
3049
|
+
handleDrop: (view, event) => {
|
|
3010
3050
|
const files = Array.from(event.dataTransfer.files);
|
|
3011
3051
|
if (!files.length) return false;
|
|
3012
3052
|
let target = view.posAtCoords({
|
|
@@ -3035,7 +3075,7 @@ var RteDropHandlerFeatureImpl = class extends RteFeatureImpl {
|
|
|
3035
3075
|
this.config = config;
|
|
3036
3076
|
this.name = "RteDebugFeature";
|
|
3037
3077
|
}
|
|
3038
|
-
getPlugins(
|
|
3078
|
+
getPlugins() {
|
|
3039
3079
|
const dragOverResults = /* @__PURE__ */ new WeakMap();
|
|
3040
3080
|
let lastResult = false;
|
|
3041
3081
|
const onDragOver = (event) => {
|
|
@@ -3073,7 +3113,7 @@ var RteDropHandlerFeatureImpl = class extends RteFeatureImpl {
|
|
|
3073
3113
|
viewport.removeEventListener("dragleave", onDragLeave);
|
|
3074
3114
|
} };
|
|
3075
3115
|
} })), this.contribution(new Plugin({ props: { handleDOMEvents: {
|
|
3076
|
-
dragover: (
|
|
3116
|
+
dragover: (_view, event) => {
|
|
3077
3117
|
const result = this.config.onViewportDragOver?.(event) ?? false;
|
|
3078
3118
|
dragOverResults.set(event, result);
|
|
3079
3119
|
lastResult = result;
|
|
@@ -3084,7 +3124,7 @@ var RteDropHandlerFeatureImpl = class extends RteFeatureImpl {
|
|
|
3084
3124
|
}
|
|
3085
3125
|
return false;
|
|
3086
3126
|
},
|
|
3087
|
-
drop: (
|
|
3127
|
+
drop: () => {
|
|
3088
3128
|
return lastResult;
|
|
3089
3129
|
}
|
|
3090
3130
|
} } }), contributionPriority.high)];
|
|
@@ -3223,7 +3263,6 @@ var RteInputRuleFeature = featureFacade(RteInputRuleFeatureImpl);
|
|
|
3223
3263
|
//#endregion
|
|
3224
3264
|
//#region src/lib/rich-text-editor/rte/features/keyboard-shortcuts.ts
|
|
3225
3265
|
function toCommand(handler, rteInstance) {
|
|
3226
|
-
if (handler.length === 0) return (_state, _dispatch) => handler();
|
|
3227
3266
|
return (_state, _dispatch) => handler(rteInstance);
|
|
3228
3267
|
}
|
|
3229
3268
|
var RteKeyboardShortcutsFeatureImpl = class extends RteFeatureImpl {
|