@harbour-enterprises/superdoc 0.22.0-next.1 → 0.22.0-next.10

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.
Files changed (47) hide show
  1. package/dist/chunks/{PdfViewer-BpwMPbUj.es.js → PdfViewer-CJdQmuIm.es.js} +1 -1
  2. package/dist/chunks/{PdfViewer-B3KmcDup.cjs → PdfViewer-DE1NR4Ve.cjs} +1 -1
  3. package/dist/chunks/{index-Cw4YywoD.es.js → index-B9sHxXr_.es.js} +53 -26
  4. package/dist/chunks/{index-BOf6E2I4.cjs → index-nfoifSpX.cjs} +53 -26
  5. package/dist/chunks/{super-editor.es-DHDx2fsy.cjs → super-editor.es-DAP-fnHo.cjs} +2264 -1648
  6. package/dist/chunks/{super-editor.es-vfoWxyZL.es.js → super-editor.es-_iVPQ8J8.es.js} +2264 -1648
  7. package/dist/core/SuperDoc.d.ts +5 -0
  8. package/dist/core/SuperDoc.d.ts.map +1 -1
  9. package/dist/core/types/index.d.ts +4 -4
  10. package/dist/core/types/index.d.ts.map +1 -1
  11. package/dist/stores/comments-store.d.ts +4 -1
  12. package/dist/stores/comments-store.d.ts.map +1 -1
  13. package/dist/style.css +48 -44
  14. package/dist/super-editor/ai-writer.es.js +2 -2
  15. package/dist/super-editor/chunks/{converter-BcqEfCTg.js → converter-DK1NMJZB.js} +439 -169
  16. package/dist/super-editor/chunks/{docx-zipper-DZ9ph0iQ.js → docx-zipper-CmK8TyNb.js} +73 -12
  17. package/dist/super-editor/chunks/{editor-BC2sSIVa.js → editor-YR4uV-dp.js} +1902 -1607
  18. package/dist/super-editor/chunks/{toolbar-DNTo5DDf.js → toolbar-DzJyRvb0.js} +2 -2
  19. package/dist/super-editor/converter.es.js +1 -1
  20. package/dist/super-editor/docx-zipper.es.js +2 -2
  21. package/dist/super-editor/editor.es.js +3 -3
  22. package/dist/super-editor/file-zipper.es.js +1 -1
  23. package/dist/super-editor/src/core/DocxZipper.d.ts +1 -1
  24. package/dist/super-editor/src/core/super-converter/SuperConverter.d.ts +1 -13
  25. package/dist/super-editor/src/core/super-converter/exporter.d.ts +1 -0
  26. package/dist/super-editor/src/core/super-converter/helpers/tableFallbackHelpers.d.ts +24 -0
  27. package/dist/super-editor/src/extensions/custom-selection/custom-selection.d.ts +5 -1
  28. package/dist/super-editor/src/extensions/index.d.ts +2 -1
  29. package/dist/super-editor/src/extensions/structured-content/index.d.ts +1 -0
  30. package/dist/super-editor/src/extensions/structured-content/structured-content-commands.d.ts +67 -0
  31. package/dist/super-editor/src/extensions/structured-content/structuredContentHelpers/getStructuredContentBlockTags.d.ts +7 -0
  32. package/dist/super-editor/src/extensions/structured-content/structuredContentHelpers/getStructuredContentInlineTags.d.ts +7 -0
  33. package/dist/super-editor/src/extensions/structured-content/structuredContentHelpers/getStructuredContentTags.d.ts +7 -0
  34. package/dist/super-editor/src/extensions/structured-content/structuredContentHelpers/getStructuredContentTagsById.d.ts +8 -0
  35. package/dist/super-editor/src/extensions/structured-content/structuredContentHelpers/index.d.ts +4 -0
  36. package/dist/super-editor/src/utils/contextmenu-helpers.d.ts +24 -0
  37. package/dist/super-editor/style.css +4 -0
  38. package/dist/super-editor/super-editor.es.js +8 -16
  39. package/dist/super-editor/toolbar.es.js +2 -2
  40. package/dist/super-editor.cjs +1 -1
  41. package/dist/super-editor.es.js +1 -1
  42. package/dist/superdoc.cjs +2 -2
  43. package/dist/superdoc.es.js +2 -2
  44. package/dist/superdoc.umd.js +1665 -1022
  45. package/dist/superdoc.umd.js.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/super-editor/src/components/slash-menu/contextmenu-helpers.d.ts +0 -1
@@ -9,12 +9,12 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
9
9
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
10
10
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
11
11
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
12
- var _Attribute_static, getGlobalAttributes_fn, getNodeAndMarksAttributes_fn, _Schema_static, createNodesSchema_fn, createMarksSchema_fn, _events, _ExtensionService_instances, setupExtensions_fn, attachEditorEvents_fn, _editor, _stateValidators, _xmlValidators, _requiredNodeTypes, _requiredMarkTypes, _SuperValidator_instances, initializeValidators_fn, collectValidatorRequirements_fn, analyzeDocument_fn, _commandService, _Editor_instances, initContainerElement_fn, init_fn, initRichText_fn, onFocus_fn, checkHeadless_fn, registerCopyHandler_fn, insertNewFileData_fn, registerPluginByNameIfNotExists_fn, createExtensionService_fn, createCommandService_fn, createConverter_fn, initMedia_fn, initFonts_fn, createSchema_fn, generatePmData_fn, createView_fn, onCollaborationReady_fn, initComments_fn, initPagination_fn, dispatchTransaction_fn, handleNodeSelection_fn, prepareDocumentForImport_fn, prepareDocumentForExport_fn, endCollaboration_fn, validateDocumentInit_fn, validateDocumentExport_fn, initDevTools_fn, _ListItemNodeView_instances, init_fn2, _FieldAnnotationView_instances, createAnnotation_fn, _AutoPageNumberNodeView_instances, renderDom_fn, scheduleUpdateNodeStyle_fn, _DocumentSectionView_instances, init_fn3, addToolTip_fn;
12
+ var _Attribute_static, getGlobalAttributes_fn, getNodeAndMarksAttributes_fn, _Schema_static, createNodesSchema_fn, createMarksSchema_fn, _events, _ExtensionService_instances, setupExtensions_fn, attachEditorEvents_fn, _editor, _stateValidators, _xmlValidators, _requiredNodeTypes, _requiredMarkTypes, _SuperValidator_instances, initializeValidators_fn, collectValidatorRequirements_fn, analyzeDocument_fn, _commandService, _Editor_instances, initContainerElement_fn, init_fn, initRichText_fn, onFocus_fn, checkHeadless_fn, registerCopyHandler_fn, insertNewFileData_fn, registerPluginByNameIfNotExists_fn, createExtensionService_fn, createCommandService_fn, createConverter_fn, initMedia_fn, initFonts_fn, createSchema_fn, generatePmData_fn, createView_fn, onCollaborationReady_fn, initComments_fn, initPagination_fn, dispatchTransaction_fn, handleNodeSelection_fn, prepareDocumentForImport_fn, prepareDocumentForExport_fn, endCollaboration_fn, validateDocumentInit_fn, validateDocumentExport_fn, initDevTools_fn, _DocumentSectionView_instances, init_fn2, addToolTip_fn, _ListItemNodeView_instances, init_fn3, _FieldAnnotationView_instances, createAnnotation_fn, _AutoPageNumberNodeView_instances, renderDom_fn, scheduleUpdateNodeStyle_fn;
13
13
  import * as Y from "yjs";
14
14
  import { UndoManager, Item as Item$1, ContentType, Text as Text$1, XmlElement, encodeStateAsUpdate } from "yjs";
15
- import { P as PluginKey, a as Plugin, M as Mapping, N as NodeSelection, S as Selection, T as TextSelection, b as Slice, D as DOMSerializer, F as Fragment, c as DOMParser$1, d as Mark$1, e as dropPoint, A as AllSelection, p as process$1, B as Buffer2, f as callOrGet, g as getExtensionConfigField, h as getMarkType, i as getMarksFromSelection, j as getNodeType, k as getSchemaTypeNameByName, l as Schema$1, m as cleanSchemaItem, n as canSplit, o as defaultBlockAt$1, q as liftTarget, r as canJoin, s as joinPoint, t as replaceStep$1, R as ReplaceAroundStep$1, u as isTextSelection, v as getMarkRange, w as isMarkActive, x as isNodeActive, y as deleteProps, z as processContent, C as ReplaceStep, E as NodeRange, G as findWrapping, L as ListHelpers, H as findParentNode, I as isMacOS, J as isIOS, K as getSchemaTypeByName, O as inputRulesPlugin, Q as TrackDeleteMarkName, U as TrackInsertMarkName, V as v4, W as TrackFormatMarkName, X as comments_module_events, Y as findMark, Z as objectIncludes, _ as AddMarkStep, $ as RemoveMarkStep, a0 as twipsToLines, a1 as pixelsToTwips, a2 as helpers, a3 as posToDOMRect, a4 as CommandService, a5 as SuperConverter, a6 as createDocument, a7 as createDocFromMarkdown, a8 as createDocFromHTML, a9 as EditorState, aa as hasSomeParentWithClass, ab as isActive, ac as unflattenListsInHtml, ad as parseSizeUnit, ae as minMax, af as getLineHeightValueString, ag as InputRule, ah as kebabCase, ai as findParentNodeClosestToPos, aj as getListItemStyleDefinitions, ak as docxNumberigHelpers, al as parseIndentElement, am as combineIndents, an as SelectionRange, ao as Transform, ap as isInTable$1, aq as generateDocxRandomId, ar as insertNewRelationship, as as updateDOMAttributes, at as htmlHandler } from "./converter-BcqEfCTg.js";
15
+ import { P as PluginKey, a as Plugin, M as Mapping, N as NodeSelection, S as Selection, T as TextSelection, b as Slice, D as DOMSerializer, F as Fragment, c as DOMParser$1, d as Mark$1, e as dropPoint, A as AllSelection, p as process$1, B as Buffer2, f as callOrGet, g as getExtensionConfigField, h as getMarkType, i as getMarksFromSelection, j as getNodeType, k as getSchemaTypeNameByName, l as Schema$1, m as cleanSchemaItem, n as canSplit, o as defaultBlockAt$1, q as liftTarget, r as canJoin, s as joinPoint, t as replaceStep$1, R as ReplaceAroundStep$1, u as isTextSelection, v as getMarkRange, w as isMarkActive, x as isNodeActive, y as deleteProps, z as processContent, C as ReplaceStep, E as NodeRange, G as findWrapping, L as ListHelpers, H as findParentNode, I as isMacOS, J as isIOS, K as getSchemaTypeByName, O as inputRulesPlugin, Q as TrackDeleteMarkName, U as TrackInsertMarkName, V as v4, W as TrackFormatMarkName, X as comments_module_events, Y as findMark, Z as objectIncludes, _ as AddMarkStep, $ as RemoveMarkStep, a0 as twipsToLines, a1 as pixelsToTwips, a2 as helpers, a3 as posToDOMRect, a4 as CommandService, a5 as SuperConverter, a6 as createDocument, a7 as createDocFromMarkdown, a8 as createDocFromHTML, a9 as EditorState, aa as hasSomeParentWithClass, ab as isActive, ac as unflattenListsInHtml, ad as parseSizeUnit, ae as minMax, af as getLineHeightValueString, ag as updateDOMAttributes, ah as findChildren$5, ai as htmlHandler, aj as InputRule, ak as kebabCase, al as findParentNodeClosestToPos, am as getListItemStyleDefinitions, an as docxNumberigHelpers, ao as parseIndentElement, ap as combineIndents, aq as SelectionRange, ar as Transform, as as isInTable$1, at as generateDocxRandomId, au as insertNewRelationship } from "./converter-DK1NMJZB.js";
16
16
  import { ref, computed, createElementBlock, openBlock, withModifiers, Fragment as Fragment$1, renderList, normalizeClass, createCommentVNode, toDisplayString, createElementVNode, createApp } from "vue";
17
- import { D as DocxZipper } from "./docx-zipper-DZ9ph0iQ.js";
17
+ import { D as DocxZipper } from "./docx-zipper-CmK8TyNb.js";
18
18
  var GOOD_LEAF_SIZE = 200;
19
19
  var RopeSequence = function RopeSequence2() {
20
20
  };
@@ -11675,9 +11675,11 @@ const toggleHeaderFooterEditMode = ({ editor, focusedSectionEditor, isEditMode,
11675
11675
  item.editor.view.dom.setAttribute("documentmode", documentMode);
11676
11676
  });
11677
11677
  if (isEditMode) {
11678
- const pm = document.querySelector(".ProseMirror");
11679
- pm.classList.add("header-footer-edit");
11680
- pm.setAttribute("aria-readonly", true);
11678
+ const pm = editor.view?.dom || editor.options.element?.querySelector?.(".ProseMirror");
11679
+ if (pm) {
11680
+ pm.classList.add("header-footer-edit");
11681
+ pm.setAttribute("aria-readonly", true);
11682
+ }
11681
11683
  }
11682
11684
  if (focusedSectionEditor) {
11683
11685
  focusedSectionEditor.view.focus();
@@ -12105,28 +12107,25 @@ const handleTrackedChangeTransaction = (trackedChangeMeta, trackedChanges, newEd
12105
12107
  if (emitParams) editor.emit("commentsUpdate", emitParams);
12106
12108
  return newTrackedChanges;
12107
12109
  };
12108
- const getTrackedChangeText = ({ state, node, mark, marks, trackedChangeType, isDeletionInsertion }) => {
12110
+ const getTrackedChangeText = ({ state, nodes, mark, marks, trackedChangeType, isDeletionInsertion }) => {
12109
12111
  let trackedChangeText = "";
12110
12112
  let deletionText = "";
12111
12113
  if (trackedChangeType === TrackInsertMarkName) {
12112
- trackedChangeText = node?.text ?? "";
12114
+ trackedChangeText = nodes.reduce((acc, node) => {
12115
+ if (!node.marks.find((nodeMark) => nodeMark.type.name === mark.type.name)) return acc;
12116
+ acc += node?.text || node?.textContent || "";
12117
+ return acc;
12118
+ }, "");
12113
12119
  }
12114
12120
  if (trackedChangeType === TrackFormatMarkName) {
12115
12121
  trackedChangeText = translateFormatChangesToEnglish(mark.attrs);
12116
12122
  }
12117
12123
  if (trackedChangeType === TrackDeleteMarkName || isDeletionInsertion) {
12118
- deletionText = node?.text ?? "";
12119
- if (isDeletionInsertion) {
12120
- let { id } = marks.deletionMark.attrs;
12121
- let deletionNode = findNode$1(state.doc, (node2) => {
12122
- const { marks: marks2 = [] } = node2;
12123
- const changeMarks = marks2.filter((mark2) => TRACK_CHANGE_MARKS.includes(mark2.type.name));
12124
- if (!changeMarks.length) return false;
12125
- const hasMatchingId = changeMarks.find((mark2) => mark2.attrs.id === id);
12126
- if (hasMatchingId) return true;
12127
- });
12128
- deletionText = deletionNode?.node.text ?? "";
12129
- }
12124
+ deletionText = nodes.reduce((acc, node) => {
12125
+ if (!node.marks.find((nodeMark) => nodeMark.type.name === TrackDeleteMarkName)) return acc;
12126
+ acc += node?.text || node?.textContent || "";
12127
+ return acc;
12128
+ }, "");
12130
12129
  }
12131
12130
  return {
12132
12131
  deletionText,
@@ -12141,18 +12140,17 @@ const createOrUpdateTrackedChangeComment = ({ event, marks, deletionNodes, nodes
12141
12140
  const id = attrs.id;
12142
12141
  const node = nodes[0];
12143
12142
  const isDeletionInsertion = !!(marks.insertedMark && marks.deletionMark);
12144
- let existingNode;
12143
+ let nodesWithMark = [];
12145
12144
  newEditorState.doc.descendants((node2) => {
12146
12145
  const { marks: marks2 = [] } = node2;
12147
12146
  const changeMarks = marks2.filter((mark) => TRACK_CHANGE_MARKS.includes(mark.type.name));
12148
12147
  if (!changeMarks.length) return;
12149
12148
  const hasMatchingId = changeMarks.find((mark) => mark.attrs.id === id);
12150
- if (hasMatchingId) existingNode = node2;
12151
- if (existingNode) return false;
12149
+ if (hasMatchingId) nodesWithMark.push(node2);
12152
12150
  });
12153
12151
  const { deletionText, trackedChangeText } = getTrackedChangeText({
12154
12152
  state: newEditorState,
12155
- node: existingNode || node,
12153
+ nodes: nodesWithMark.length ? nodesWithMark : [node],
12156
12154
  mark: trackedMark,
12157
12155
  marks,
12158
12156
  trackedChangeType,
@@ -12182,14 +12180,6 @@ const createOrUpdateTrackedChangeComment = ({ event, marks, deletionNodes, nodes
12182
12180
  else if (event === "update") params2.event = comments_module_events.UPDATE;
12183
12181
  return params2;
12184
12182
  };
12185
- function findNode$1(node, predicate) {
12186
- let found = null;
12187
- node.descendants((node2, pos) => {
12188
- if (predicate(node2)) found = { node: node2, pos };
12189
- if (found) return false;
12190
- });
12191
- return found;
12192
- }
12193
12183
  function findRangeById(doc2, id) {
12194
12184
  let from2 = null, to = null;
12195
12185
  doc2.descendants((node, pos) => {
@@ -12707,6 +12697,7 @@ const generateTableIfNecessary = ({ tableNode, annotationValues, tr, state }) =>
12707
12697
  const mappedRowStart = tr.mapping.map(absoluteRowStart);
12708
12698
  const rowEnd = mappedRowStart + rowNode.nodeSize;
12709
12699
  tr.replaceWith(mappedRowStart, rowEnd, Fragment.from(newRows));
12700
+ tr.setMeta("tableGeneration", true);
12710
12701
  } catch (error) {
12711
12702
  console.error("Error during row generation:", error);
12712
12703
  throw error;
@@ -13111,7 +13102,7 @@ function findFieldAnnotationsBetween(from2, to, doc2) {
13111
13102
  }
13112
13103
  function findRemovedFieldAnnotations(tr) {
13113
13104
  let removedNodes = [];
13114
- if (!tr.steps.length || tr.meta && !Object.keys(tr.meta).every((meta) => ["inputType", "uiEvent", "paste"].includes(meta)) || ["historyUndo", "historyRedo"].includes(tr.getMeta("inputType")) || ["drop"].includes(tr.getMeta("uiEvent")) || tr.getMeta("fieldAnnotationUpdate") === true) {
13105
+ if (!tr.steps.length || tr.meta && !Object.keys(tr.meta).every((meta) => ["inputType", "uiEvent", "paste"].includes(meta)) || ["historyUndo", "historyRedo"].includes(tr.getMeta("inputType")) || ["drop"].includes(tr.getMeta("uiEvent")) || tr.getMeta("fieldAnnotationUpdate") === true || tr.getMeta("tableGeneration") === true) {
13115
13106
  return removedNodes;
13116
13107
  }
13117
13108
  const hasDeletion = transactionDeletedAnything(tr);
@@ -14332,7 +14323,7 @@ const _Editor = class _Editor extends EventEmitter {
14332
14323
  setDocumentMode(documentMode) {
14333
14324
  let cleanedMode = documentMode?.toLowerCase() || "editing";
14334
14325
  if (!this.extensionService || !this.state) return;
14335
- const pm = document.querySelector(".ProseMirror");
14326
+ const pm = this.view?.dom || this.options.element?.querySelector?.(".ProseMirror");
14336
14327
  if (this.options.role === "viewer") cleanedMode = "viewing";
14337
14328
  if (this.options.role === "suggester" && cleanedMode === "editing") cleanedMode = "suggesting";
14338
14329
  if (cleanedMode === "viewing") {
@@ -14820,7 +14811,8 @@ const _Editor = class _Editor extends EventEmitter {
14820
14811
  files: this.options.content
14821
14812
  },
14822
14813
  media,
14823
- true
14814
+ true,
14815
+ updatedDocs
14824
14816
  );
14825
14817
  return updatedDocs;
14826
14818
  }
@@ -15380,9 +15372,11 @@ createView_fn = function(element) {
15380
15372
  isEditMode: false,
15381
15373
  documentMode: this.options.documentMode
15382
15374
  });
15383
- const pm = document.querySelector(".ProseMirror");
15384
- pm.classList.remove("header-footer-edit");
15385
- pm.setAttribute("aria-readonly", false);
15375
+ const pm = this.view?.dom || this.options.element?.querySelector?.(".ProseMirror");
15376
+ if (pm) {
15377
+ pm.classList.remove("header-footer-edit");
15378
+ pm.setAttribute("aria-readonly", false);
15379
+ }
15386
15380
  }
15387
15381
  setWordSelection(view, pos);
15388
15382
  }
@@ -16973,580 +16967,1590 @@ const SlashMenu = Extension.create({
16973
16967
  return this.editor.options.isHeadless ? [] : [slashMenuPlugin];
16974
16968
  }
16975
16969
  });
16976
- const Document = Node$1.create({
16977
- name: "doc",
16978
- topNode: true,
16979
- content: "block+",
16980
- parseDOM() {
16981
- return [{ tag: "doc" }];
16982
- },
16983
- renderDOM() {
16984
- return ["doc", 0];
16985
- },
16986
- addAttributes() {
16987
- return {
16988
- attributes: {
16989
- rendered: false,
16990
- "aria-label": "Document node"
16991
- }
16992
- };
16993
- },
16994
- addCommands() {
16995
- return {
16996
- /**
16997
- * Get document statistics
16998
- * @category Command
16999
- * @example
17000
- * // Get word and character count
17001
- * const stats = editor.commands.getDocumentStats()
17002
- * console.log(`${stats.words} words, ${stats.characters} characters`)
17003
- * @note Returns word count, character count, and paragraph count
17004
- */
17005
- getDocumentStats: () => ({ editor }) => {
17006
- const text = editor.getText();
17007
- const words = text.split(/\s+/).filter((word) => word.length > 0).length;
17008
- const characters = text.length;
17009
- const paragraphs = editor.state.doc.content.childCount;
17010
- return {
17011
- words,
17012
- characters,
17013
- paragraphs
17014
- };
17015
- },
17016
- /**
17017
- * Clear entire document
17018
- * @category Command
17019
- * @example
17020
- * editor.commands.clearDocument()
17021
- * @note Replaces all content with an empty paragraph
17022
- */
17023
- clearDocument: () => ({ commands: commands2 }) => {
17024
- return commands2.setContent("<p></p>");
16970
+ class StructuredContentViewBase {
16971
+ constructor(props) {
16972
+ __publicField(this, "node");
16973
+ __publicField(this, "view");
16974
+ __publicField(this, "getPos");
16975
+ __publicField(this, "decorations");
16976
+ __publicField(this, "innerDecorations");
16977
+ __publicField(this, "editor");
16978
+ __publicField(this, "extension");
16979
+ __publicField(this, "htmlAttributes");
16980
+ __publicField(this, "root");
16981
+ __publicField(this, "isDragging", false);
16982
+ this.node = props.node;
16983
+ this.view = props.editor.view;
16984
+ this.getPos = props.getPos;
16985
+ this.decorations = props.decorations;
16986
+ this.innerDecorations = props.innerDecorations;
16987
+ this.editor = props.editor;
16988
+ this.extension = props.extension;
16989
+ this.htmlAttributes = props.htmlAttributes;
16990
+ this.mount(props);
16991
+ }
16992
+ mount() {
16993
+ return;
16994
+ }
16995
+ get dom() {
16996
+ return this.root;
16997
+ }
16998
+ get contentDOM() {
16999
+ return null;
17000
+ }
17001
+ update(node, decorations, innerDecorations) {
17002
+ if (node.type !== this.node.type) {
17003
+ return false;
17004
+ }
17005
+ this.node = node;
17006
+ this.decorations = decorations;
17007
+ this.innerDecorations = innerDecorations;
17008
+ this.updateHTMLAttributes();
17009
+ return true;
17010
+ }
17011
+ stopEvent(event) {
17012
+ if (!this.dom) return false;
17013
+ const target = event.target;
17014
+ const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target);
17015
+ if (!isInElement) return false;
17016
+ const isDragEvent = event.type.startsWith("drag");
17017
+ const isDropEvent = event.type === "drop";
17018
+ const isInput = ["INPUT", "BUTTON", "SELECT", "TEXTAREA"].includes(target.tagName) || target.isContentEditable;
17019
+ if (isInput && !isDropEvent && !isDragEvent) return true;
17020
+ const { isEditable } = this.editor;
17021
+ const { isDragging } = this;
17022
+ const isDraggable = !!this.node.type.spec.draggable;
17023
+ const isSelectable = NodeSelection.isSelectable(this.node);
17024
+ const isCopyEvent = event.type === "copy";
17025
+ const isPasteEvent = event.type === "paste";
17026
+ const isCutEvent = event.type === "cut";
17027
+ const isClickEvent = event.type === "mousedown";
17028
+ if (!isDraggable && isSelectable && isDragEvent && event.target === this.dom) {
17029
+ event.preventDefault();
17030
+ }
17031
+ if (isDraggable && isDragEvent && !isDragging && event.target === this.dom) {
17032
+ event.preventDefault();
17033
+ return false;
17034
+ }
17035
+ if (isDraggable && isEditable && !isDragging && isClickEvent) {
17036
+ const dragHandle = target.closest("[data-drag-handle]");
17037
+ const isValidDragHandle = dragHandle && (this.dom === dragHandle || this.dom.contains(dragHandle));
17038
+ if (isValidDragHandle) {
17039
+ this.isDragging = true;
17040
+ document.addEventListener(
17041
+ "dragend",
17042
+ () => {
17043
+ this.isDragging = false;
17044
+ },
17045
+ { once: true }
17046
+ );
17047
+ document.addEventListener(
17048
+ "drop",
17049
+ () => {
17050
+ this.isDragging = false;
17051
+ },
17052
+ { once: true }
17053
+ );
17054
+ document.addEventListener(
17055
+ "mouseup",
17056
+ () => {
17057
+ this.isDragging = false;
17058
+ },
17059
+ { once: true }
17060
+ );
17025
17061
  }
17026
- };
17062
+ }
17063
+ if (isDragging || isDropEvent || isCopyEvent || isPasteEvent || isCutEvent || isClickEvent && isSelectable) {
17064
+ return false;
17065
+ }
17066
+ return true;
17027
17067
  }
17028
- });
17029
- const Text = Node$1.create({
17030
- name: "text",
17031
- group: "inline",
17032
- inline: true,
17033
- addOptions() {
17034
- return {};
17068
+ ignoreMutation(mutation) {
17069
+ if (!this.dom || !this.contentDOM) return true;
17070
+ if (this.node.isLeaf || this.node.isAtom) return true;
17071
+ if (mutation.type === "selection") return false;
17072
+ if (this.contentDOM === mutation.target && mutation.type === "attributes") return true;
17073
+ if (this.contentDOM.contains(mutation.target)) return false;
17074
+ return true;
17035
17075
  }
17036
- });
17037
- const splitRun = () => (props) => {
17038
- const { state, view, tr } = props;
17039
- const { $from, empty: empty2 } = state.selection;
17040
- if (!empty2) return false;
17041
- if ($from.parent.type.name !== "run") return false;
17042
- const handled = splitBlock(state, (transaction) => {
17076
+ destroy() {
17077
+ this.dom.remove();
17078
+ this.contentDOM?.remove();
17079
+ }
17080
+ updateAttributes(attrs) {
17081
+ const pos = this.getPos();
17082
+ if (typeof pos !== "number") {
17083
+ return;
17084
+ }
17085
+ return this.view.dispatch(
17086
+ this.view.state.tr.setNodeMarkup(pos, void 0, {
17087
+ ...this.node.attrs,
17088
+ ...attrs
17089
+ })
17090
+ );
17091
+ }
17092
+ updateHTMLAttributes() {
17093
+ const { extensionService } = this.editor;
17094
+ const { attributes } = extensionService;
17095
+ const extensionAttrs = attributes.filter((i) => i.type === this.node.type.name);
17096
+ this.htmlAttributes = Attribute.getAttributesToRender(this.node, extensionAttrs);
17097
+ }
17098
+ createDragHandle() {
17099
+ const dragHandle = document.createElement("span");
17100
+ dragHandle.classList.add("sd-structured-content-draggable");
17101
+ dragHandle.draggable = true;
17102
+ dragHandle.contentEditable = "false";
17103
+ dragHandle.dataset.dragHandle = "";
17104
+ const textElement = document.createElement("span");
17105
+ textElement.textContent = this.node.attrs.alias || "Structured content";
17106
+ dragHandle.append(textElement);
17107
+ return dragHandle;
17108
+ }
17109
+ onDragStart(event) {
17110
+ const { view } = this.editor;
17111
+ const target = event.target;
17112
+ const dragHandle = target.nodeType === 3 ? target.parentElement?.closest("[data-drag-handle]") : target.closest("[data-drag-handle]");
17113
+ if (!this.dom || this.contentDOM?.contains(target) || !dragHandle) {
17114
+ return;
17115
+ }
17116
+ let x = 0;
17117
+ let y = 0;
17118
+ if (this.dom !== dragHandle) {
17119
+ const domBox = this.dom.getBoundingClientRect();
17120
+ const handleBox = dragHandle.getBoundingClientRect();
17121
+ const offsetX = event.offsetX ?? event.nativeEvent?.offsetX;
17122
+ const offsetY = event.offsetY ?? event.nativeEvent?.offsetY;
17123
+ x = handleBox.x - domBox.x + offsetX;
17124
+ y = handleBox.y - domBox.y + offsetY;
17125
+ }
17126
+ event.dataTransfer?.setDragImage(this.dom, x, y);
17127
+ const pos = this.getPos();
17128
+ if (typeof pos !== "number") {
17129
+ return;
17130
+ }
17131
+ const selection = NodeSelection.create(view.state.doc, pos);
17132
+ const transaction = view.state.tr.setSelection(selection);
17043
17133
  view.dispatch(transaction);
17044
- });
17045
- if (handled) {
17046
- tr.setMeta("preventDispatch", true);
17047
17134
  }
17048
- return handled;
17049
- };
17050
- const Run = OxmlNode.create({
17051
- name: "run",
17052
- oXmlName: "w:r",
17053
- group: "inline",
17135
+ }
17136
+ class StructuredContentInlineView extends StructuredContentViewBase {
17137
+ constructor(props) {
17138
+ super(props);
17139
+ }
17140
+ mount() {
17141
+ this.buildView();
17142
+ }
17143
+ get contentDOM() {
17144
+ const contentElement = this.dom?.querySelector(`.${structuredContentInnerClass$1}`);
17145
+ return contentElement || null;
17146
+ }
17147
+ createElement() {
17148
+ const element = document.createElement("span");
17149
+ element.classList.add(structuredContentClass$1);
17150
+ element.setAttribute("data-structured-content", "");
17151
+ const contentElement = document.createElement("span");
17152
+ contentElement.classList.add(structuredContentInnerClass$1);
17153
+ element.append(contentElement);
17154
+ const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
17155
+ updateDOMAttributes(element, { ...domAttrs });
17156
+ return { element, contentElement };
17157
+ }
17158
+ buildView() {
17159
+ const { element } = this.createElement();
17160
+ const dragHandle = this.createDragHandle();
17161
+ element.prepend(dragHandle);
17162
+ element.addEventListener("dragstart", (e) => this.onDragStart(e));
17163
+ this.root = element;
17164
+ }
17165
+ updateView() {
17166
+ const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
17167
+ updateDOMAttributes(this.dom, { ...domAttrs });
17168
+ }
17169
+ update(node, decorations, innerDecorations) {
17170
+ const result = super.update(node, decorations, innerDecorations);
17171
+ if (!result) return false;
17172
+ this.updateView();
17173
+ return true;
17174
+ }
17175
+ }
17176
+ const structuredContentClass$1 = "sd-structured-content";
17177
+ const structuredContentInnerClass$1 = "sd-structured-content__content";
17178
+ const StructuredContent = Node$1.create({
17179
+ name: "structuredContent",
17180
+ group: "inline structuredContent",
17054
17181
  inline: true,
17055
17182
  content: "inline*",
17056
- selectable: false,
17057
- childToAttributes: ["runProperties"],
17183
+ isolating: true,
17184
+ atom: false,
17185
+ // false - has editable content.
17186
+ draggable: true,
17058
17187
  addOptions() {
17059
17188
  return {
17060
17189
  htmlAttributes: {
17061
- "data-run": "1"
17190
+ class: structuredContentClass$1,
17191
+ "aria-label": "Structured content node"
17062
17192
  }
17063
17193
  };
17064
17194
  },
17065
17195
  addAttributes() {
17066
17196
  return {
17067
- runProperties: {
17197
+ id: {
17068
17198
  default: null,
17069
- rendered: false,
17070
- keepOnSplit: true
17199
+ parseDOM: (elem) => elem.getAttribute("data-id"),
17200
+ renderDOM: (attrs) => {
17201
+ if (!attrs.id) return {};
17202
+ return { "data-id": attrs.id };
17203
+ }
17071
17204
  },
17072
- rsidR: {
17205
+ tag: {
17073
17206
  default: null,
17074
- rendered: false,
17075
- keepOnSplit: true
17207
+ parseDOM: (elem) => elem.getAttribute("data-tag"),
17208
+ renderDOM: (attrs) => {
17209
+ if (!attrs.tag) return {};
17210
+ return { "data-tag": attrs.tag };
17211
+ }
17076
17212
  },
17077
- rsidRPr: {
17213
+ alias: {
17078
17214
  default: null,
17079
- rendered: false,
17080
- keepOnSplit: true
17215
+ parseDOM: (elem) => elem.getAttribute("data-alias"),
17216
+ renderDOM: (attrs) => {
17217
+ if (!attrs.alias) return {};
17218
+ return { "data-alias": attrs.alias };
17219
+ }
17081
17220
  },
17082
- rsidDel: {
17083
- default: null,
17084
- rendered: false,
17085
- keepOnSplit: true
17221
+ sdtPr: {
17222
+ rendered: false
17086
17223
  }
17087
17224
  };
17088
17225
  },
17089
- addCommands() {
17090
- return {
17091
- splitRun
17092
- };
17093
- },
17094
17226
  parseDOM() {
17095
- return [{ tag: "span[data-run]" }];
17227
+ return [{ tag: "span[data-structured-content]" }];
17096
17228
  },
17097
17229
  renderDOM({ htmlAttributes }) {
17098
- const base2 = Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes);
17099
- return ["span", base2, 0];
17230
+ return [
17231
+ "span",
17232
+ Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, {
17233
+ "data-structured-content": ""
17234
+ }),
17235
+ 0
17236
+ ];
17237
+ },
17238
+ addNodeView() {
17239
+ return (props) => {
17240
+ return new StructuredContentInlineView({ ...props });
17241
+ };
17100
17242
  }
17101
17243
  });
17102
- const inputRegex$1 = /^\s*([-+*])\s$/;
17103
- const BulletList = Node$1.create({
17104
- name: "bulletList",
17105
- group: "block list",
17106
- selectable: false,
17107
- content() {
17108
- return `${this.options.itemTypeName}+`;
17109
- },
17244
+ class StructuredContentBlockView extends StructuredContentViewBase {
17245
+ constructor(props) {
17246
+ super(props);
17247
+ }
17248
+ mount() {
17249
+ this.buildView();
17250
+ }
17251
+ get contentDOM() {
17252
+ const contentElement = this.dom?.querySelector(`.${structuredContentInnerClass}`);
17253
+ return contentElement || null;
17254
+ }
17255
+ createElement() {
17256
+ const element = document.createElement("div");
17257
+ element.classList.add(structuredContentClass);
17258
+ element.setAttribute("data-structured-content-block", "");
17259
+ const contentElement = document.createElement("div");
17260
+ contentElement.classList.add(structuredContentInnerClass);
17261
+ element.append(contentElement);
17262
+ const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
17263
+ updateDOMAttributes(element, { ...domAttrs });
17264
+ return { element, contentElement };
17265
+ }
17266
+ buildView() {
17267
+ const { element } = this.createElement();
17268
+ const dragHandle = this.createDragHandle();
17269
+ element.prepend(dragHandle);
17270
+ element.addEventListener("dragstart", (e) => this.onDragStart(e));
17271
+ this.root = element;
17272
+ }
17273
+ updateView() {
17274
+ const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
17275
+ updateDOMAttributes(this.dom, { ...domAttrs });
17276
+ }
17277
+ update(node, decorations, innerDecorations) {
17278
+ const result = super.update(node, decorations, innerDecorations);
17279
+ if (!result) return false;
17280
+ this.updateView();
17281
+ return true;
17282
+ }
17283
+ }
17284
+ const structuredContentClass = "sd-structured-content-block";
17285
+ const structuredContentInnerClass = "sd-structured-content-block__content";
17286
+ const StructuredContentBlock = Node$1.create({
17287
+ name: "structuredContentBlock",
17288
+ group: "block structuredContent",
17289
+ content: "block*",
17290
+ isolating: true,
17291
+ atom: false,
17292
+ // false - has editable content.
17293
+ draggable: true,
17110
17294
  addOptions() {
17111
17295
  return {
17112
- itemTypeName: "listItem",
17113
17296
  htmlAttributes: {
17114
- "aria-label": "Bullet list node"
17115
- },
17116
- keepMarks: true,
17117
- keepAttributes: false
17297
+ class: structuredContentClass,
17298
+ "aria-label": "Structured content block node"
17299
+ }
17118
17300
  };
17119
17301
  },
17120
- parseDOM() {
17121
- return [{ tag: "ul" }];
17122
- },
17123
- renderDOM({ htmlAttributes }) {
17124
- const attributes = Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes);
17125
- return ["ul", attributes, 0];
17126
- },
17127
17302
  addAttributes() {
17128
17303
  return {
17129
- "list-style-type": {
17130
- default: "bullet",
17131
- rendered: false
17304
+ id: {
17305
+ default: null,
17306
+ parseDOM: (elem) => elem.getAttribute("data-id"),
17307
+ renderDOM: (attrs) => {
17308
+ if (!attrs.id) return {};
17309
+ return { "data-id": attrs.id };
17310
+ }
17132
17311
  },
17133
- listId: {
17134
- rendered: false
17312
+ tag: {
17313
+ default: null,
17314
+ parseDOM: (elem) => elem.getAttribute("data-tag"),
17315
+ renderDOM: (attrs) => {
17316
+ if (!attrs.tag) return {};
17317
+ return { "data-tag": attrs.tag };
17318
+ }
17135
17319
  },
17136
- sdBlockId: {
17320
+ alias: {
17137
17321
  default: null,
17138
- keepOnSplit: false,
17139
- parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
17322
+ parseDOM: (elem) => elem.getAttribute("data-alias"),
17140
17323
  renderDOM: (attrs) => {
17141
- return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
17324
+ if (!attrs.alias) return {};
17325
+ return { "data-alias": attrs.alias };
17142
17326
  }
17143
17327
  },
17144
- attributes: {
17145
- rendered: false,
17146
- keepOnSplit: true
17328
+ sdtPr: {
17329
+ rendered: false
17147
17330
  }
17148
17331
  };
17149
17332
  },
17333
+ parseDOM() {
17334
+ return [{ tag: "div[data-structured-content-block]" }];
17335
+ },
17336
+ renderDOM({ htmlAttributes }) {
17337
+ return [
17338
+ "div",
17339
+ Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, {
17340
+ "data-structured-content-block": ""
17341
+ }),
17342
+ 0
17343
+ ];
17344
+ },
17345
+ addNodeView() {
17346
+ return (props) => {
17347
+ return new StructuredContentBlockView({ ...props });
17348
+ };
17349
+ }
17350
+ });
17351
+ function getStructuredContentTagsById(idOrIds, state) {
17352
+ const result = findChildren$5(state.doc, (node) => {
17353
+ const isStructuredContent = ["structuredContent", "structuredContentBlock"].includes(node.type.name);
17354
+ if (Array.isArray(idOrIds)) {
17355
+ return isStructuredContent && idOrIds.includes(node.attrs.id);
17356
+ } else {
17357
+ return isStructuredContent && node.attrs.id === idOrIds;
17358
+ }
17359
+ });
17360
+ return result;
17361
+ }
17362
+ function getStructuredContentTags(state) {
17363
+ const result = findChildren$5(state.doc, (node) => {
17364
+ return node.type.name === "structuredContent" || node.type.name === "structuredContentBlock";
17365
+ });
17366
+ return result;
17367
+ }
17368
+ function getStructuredContentInlineTags(state) {
17369
+ const result = findChildren$5(state.doc, (node) => node.type.name === "structuredContent");
17370
+ return result;
17371
+ }
17372
+ function getStructuredContentBlockTags(state) {
17373
+ const result = findChildren$5(state.doc, (node) => node.type.name === "structuredContentBlock");
17374
+ return result;
17375
+ }
17376
+ const structuredContentHelpers = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
17377
+ __proto__: null,
17378
+ getStructuredContentBlockTags,
17379
+ getStructuredContentInlineTags,
17380
+ getStructuredContentTags,
17381
+ getStructuredContentTagsById
17382
+ }, Symbol.toStringTag, { value: "Module" }));
17383
+ const STRUCTURED_CONTENT_NAMES = ["structuredContent", "structuredContentBlock"];
17384
+ const StructuredContentCommands = Extension.create({
17385
+ name: "structuredContentCommands",
17150
17386
  addCommands() {
17151
17387
  return {
17152
17388
  /**
17153
- * Toggle a bullet list at the current selection
17389
+ * Inserts a structured content inline at selection.
17154
17390
  * @category Command
17155
- * @example
17156
- * // Toggle bullet list on selected text
17157
- * editor.commands.toggleBulletList()
17158
- * @note Converts selected paragraphs to list items or removes list formatting
17391
+ * @param {StructuredContentInlineInsert} options
17159
17392
  */
17160
- toggleBulletList: () => (params2) => {
17161
- return toggleList(this.type)(params2);
17162
- }
17163
- };
17164
- },
17165
- addShortcuts() {
17166
- return {
17167
- "Mod-Shift-8": () => {
17168
- return this.editor.commands.toggleBulletList();
17169
- }
17170
- };
17171
- },
17172
- addInputRules() {
17173
- return [
17174
- new InputRule({
17175
- match: inputRegex$1,
17176
- handler: ({ state, range }) => {
17177
- const $pos = state.selection.$from;
17178
- const listItemType = state.schema.nodes.listItem;
17179
- for (let depth = $pos.depth; depth >= 0; depth--) {
17180
- if ($pos.node(depth).type === listItemType) {
17181
- return null;
17182
- }
17393
+ insertStructuredContentInline: (options = {}) => ({ editor, dispatch, state, tr }) => {
17394
+ const { schema } = editor;
17395
+ let { from: from2, to } = state.selection;
17396
+ if (dispatch) {
17397
+ const selectionText = state.doc.textBetween(from2, to);
17398
+ let content = null;
17399
+ if (selectionText) {
17400
+ content = schema.text(selectionText);
17183
17401
  }
17184
- const { tr } = state;
17185
- tr.delete(range.from, range.to);
17186
- ListHelpers.createNewList({
17187
- listType: this.type,
17188
- tr,
17189
- editor: this.editor
17190
- });
17191
- }
17192
- })
17193
- ];
17194
- }
17195
- });
17196
- const inputRegex = /^(\d+)\.\s$/;
17197
- const OrderedList = Node$1.create({
17198
- name: "orderedList",
17199
- group: "block list",
17200
- selectable: false,
17201
- content() {
17202
- return `${this.options.itemTypeName}+`;
17203
- },
17204
- addOptions() {
17205
- return {
17206
- itemTypeName: "listItem",
17207
- htmlAttributes: {
17208
- "aria-label": "Ordered list node"
17209
- },
17210
- keepMarks: true,
17211
- keepAttributes: false,
17212
- listStyleTypes: ["decimal", "lowerAlpha", "lowerRoman"]
17213
- };
17214
- },
17215
- addAttributes() {
17216
- return {
17217
- order: {
17218
- default: 1,
17219
- parseDOM: (element) => {
17220
- return element.hasAttribute("start") ? parseInt(element.getAttribute("start") || "", 10) : 1;
17221
- },
17222
- renderDOM: (attrs) => {
17223
- return {
17224
- start: attrs.order
17402
+ if (options.text) {
17403
+ content = schema.text(options.text);
17404
+ }
17405
+ if (options.json) {
17406
+ content = schema.nodeFromJSON(options.json);
17407
+ }
17408
+ if (!content) {
17409
+ content = schema.text(" ");
17410
+ }
17411
+ const attrs = {
17412
+ ...options.attrs,
17413
+ id: options.attrs?.id || randomId(),
17414
+ tag: "inline_text_sdt",
17415
+ alias: options.attrs?.alias || "Structured content"
17225
17416
  };
17417
+ const node = schema.nodes.structuredContent.create(attrs, content, null);
17418
+ const parent = findParentNode((node2) => node2.type.name === "structuredContent")(state.selection);
17419
+ if (parent) {
17420
+ const insertPos = parent.pos + parent.node.nodeSize;
17421
+ from2 = to = insertPos;
17422
+ }
17423
+ tr.replaceWith(from2, to, node);
17226
17424
  }
17425
+ return true;
17227
17426
  },
17228
- sdBlockId: {
17229
- default: null,
17230
- keepOnSplit: false,
17231
- parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
17232
- renderDOM: (attrs) => {
17233
- return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
17234
- }
17235
- },
17236
- syncId: {
17237
- default: null,
17238
- parseDOM: (elem) => elem.getAttribute("data-sync-id"),
17239
- renderDOM: (attrs) => {
17240
- if (!attrs.syncId) return {};
17241
- return {
17242
- "data-sync-id": attrs.syncId
17427
+ /**
17428
+ * Inserts a structured content block at selection.
17429
+ * @category Command
17430
+ * @param {StructuredContentBlockInsert} options
17431
+ */
17432
+ insertStructuredContentBlock: (options = {}) => ({ editor, dispatch, state, tr }) => {
17433
+ const { schema } = editor;
17434
+ let { from: from2, to } = state.selection;
17435
+ if (dispatch) {
17436
+ const selectionContent = state.selection.content();
17437
+ let content = null;
17438
+ if (selectionContent.size) {
17439
+ content = selectionContent.content;
17440
+ }
17441
+ if (options.html) {
17442
+ const html = htmlHandler(options.html, editor);
17443
+ const doc2 = DOMParser$1.fromSchema(schema).parse(html);
17444
+ content = doc2.content;
17445
+ }
17446
+ if (options.json) {
17447
+ content = schema.nodeFromJSON(options.json);
17448
+ }
17449
+ if (!content) {
17450
+ content = schema.nodeFromJSON({ type: "paragraph", content: [] });
17451
+ }
17452
+ const attrs = {
17453
+ ...options.attrs,
17454
+ id: options.attrs?.id || randomId(),
17455
+ tag: "block_table_sdt",
17456
+ alias: options.attrs?.alias || "Structured content"
17243
17457
  };
17458
+ const node = schema.nodes.structuredContentBlock.create(attrs, content, null);
17459
+ const parent = findParentNode((node2) => node2.type.name === "structuredContentBlock")(state.selection);
17460
+ if (parent) {
17461
+ const insertPos = parent.pos + parent.node.nodeSize;
17462
+ from2 = to = insertPos;
17463
+ }
17464
+ tr.replaceRangeWith(from2, to, node);
17244
17465
  }
17245
- // rendered: false,
17466
+ return true;
17246
17467
  },
17247
- listId: {
17248
- keepOnSplit: true,
17249
- parseDOM: (elem) => elem.getAttribute("data-list-id"),
17250
- renderDOM: (attrs) => {
17251
- if (!attrs.listId) return {};
17252
- return {
17253
- "data-list-id": attrs.listId
17254
- };
17468
+ /**
17469
+ * Updates a structured content attributes or content.
17470
+ * If the updated node does not match the schema, it will not be updated.
17471
+ * @category Command
17472
+ * @param {string} id
17473
+ * @param {StructuredContentUpdate} options
17474
+ */
17475
+ updateStructuredContentById: (id, options = {}) => ({ editor, dispatch, state, tr }) => {
17476
+ const structuredContentTags = getStructuredContentTagsById(id, state);
17477
+ if (!structuredContentTags.length) {
17478
+ return true;
17255
17479
  }
17480
+ const { schema } = editor;
17481
+ if (dispatch) {
17482
+ const structuredContent = structuredContentTags[0];
17483
+ const { pos, node } = structuredContent;
17484
+ const posFrom = pos;
17485
+ const posTo = pos + node.nodeSize;
17486
+ let content = null;
17487
+ if (options.text) {
17488
+ content = schema.text(options.text);
17489
+ }
17490
+ if (options.html) {
17491
+ const html = htmlHandler(options.html, editor);
17492
+ const doc2 = DOMParser$1.fromSchema(schema).parse(html);
17493
+ content = doc2.content;
17494
+ }
17495
+ if (options.json) {
17496
+ content = schema.nodeFromJSON(options.json);
17497
+ }
17498
+ if (!content) {
17499
+ content = node.content;
17500
+ }
17501
+ const updatedNode = node.type.create({ ...node.attrs, ...options.attrs }, content, node.marks);
17502
+ try {
17503
+ updatedNode.check();
17504
+ } catch {
17505
+ console.error("Updated node does not conform to the schema");
17506
+ return false;
17507
+ }
17508
+ tr.replaceWith(posFrom, posTo, updatedNode);
17509
+ }
17510
+ return true;
17256
17511
  },
17257
- "list-style-type": {
17258
- default: "decimal",
17259
- rendered: false
17260
- },
17261
- attributes: {
17262
- rendered: false,
17263
- keepOnSplit: true
17264
- }
17265
- };
17266
- },
17267
- parseDOM() {
17268
- return [{ tag: "ol" }];
17269
- },
17270
- renderDOM({ htmlAttributes }) {
17271
- const { start: start2, ...restAttributes } = htmlAttributes;
17272
- return start2 === 1 ? ["ol", Attribute.mergeAttributes(this.options.htmlAttributes, restAttributes), 0] : ["ol", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes), 0];
17273
- },
17274
- addCommands() {
17275
- return {
17276
17512
  /**
17277
- * Toggle ordered list formatting
17513
+ * Removes a structured content.
17278
17514
  * @category Command
17279
- * @example
17280
- * editor.commands.toggleOrderedList()
17281
- * @note Converts selection to ordered list or back to paragraphs
17515
+ * @param {Array<{ node: Node, pos: number }>} structuredContentTags
17282
17516
  */
17283
- toggleOrderedList: () => (params2) => {
17284
- return toggleList(this.type)(params2);
17517
+ deleteStructuredContent: (structuredContentTags) => ({ dispatch, tr }) => {
17518
+ if (!structuredContentTags.length) {
17519
+ return true;
17520
+ }
17521
+ if (dispatch) {
17522
+ structuredContentTags.forEach((structuredContent) => {
17523
+ const { pos, node } = structuredContent;
17524
+ const posFrom = tr.mapping.map(pos);
17525
+ const posTo = tr.mapping.map(pos + node.nodeSize);
17526
+ const currentNode = tr.doc.nodeAt(posFrom);
17527
+ if (currentNode && node.eq(currentNode)) {
17528
+ tr.delete(posFrom, posTo);
17529
+ }
17530
+ });
17531
+ }
17532
+ return true;
17285
17533
  },
17286
17534
  /**
17287
- * Restart list node numbering
17535
+ * Removes a structured content by ID.
17288
17536
  * @category Command
17289
- * @param {Array} followingNodes - Nodes to restart
17290
- * @param {number} pos - Starting position
17291
- * @example
17292
- * editor.commands.restartListNodes(nodes, position)
17293
- * @note Resets list numbering for specified nodes
17537
+ * @param {string | string[]} idOrIds
17294
17538
  */
17295
- restartListNodes: (followingNodes, pos) => ({ tr }) => {
17296
- let currentNodePos = pos;
17297
- const nodes = followingNodes.map((node) => {
17298
- const resultNode = {
17299
- node,
17300
- pos: currentNodePos
17301
- };
17302
- currentNodePos += node.nodeSize;
17303
- return resultNode;
17304
- });
17305
- nodes.forEach((item) => {
17306
- const { pos: pos2 } = item;
17307
- const newPos = tr.mapping.map(pos2);
17308
- tr.setNodeMarkup(newPos, void 0, {});
17309
- });
17539
+ deleteStructuredContentById: (idOrIds) => ({ dispatch, state, tr }) => {
17540
+ const structuredContentTags = getStructuredContentTagsById(idOrIds, state);
17541
+ if (!structuredContentTags.length) {
17542
+ return true;
17543
+ }
17544
+ if (dispatch) {
17545
+ structuredContentTags.forEach((structuredContent) => {
17546
+ const { pos, node } = structuredContent;
17547
+ const posFrom = tr.mapping.map(pos);
17548
+ const posTo = tr.mapping.map(pos + node.nodeSize);
17549
+ const currentNode = tr.doc.nodeAt(posFrom);
17550
+ if (currentNode && node.eq(currentNode)) {
17551
+ tr.delete(posFrom, posTo);
17552
+ }
17553
+ });
17554
+ }
17310
17555
  return true;
17311
17556
  },
17312
17557
  /**
17313
- * Update ordered list style type based on nesting level
17558
+ * Removes a structured content at cursor, preserving its content.
17314
17559
  * @category Command
17315
- * @example
17316
- * editor.commands.updateOrderedListStyleType()
17317
- * @note Cycles through decimal -> lowerAlpha -> lowerRoman based on depth
17318
17560
  */
17319
- updateOrderedListStyleType: () => ({ dispatch, tr }) => {
17320
- let list = findParentNode((node) => node.type.name === this.name)(tr.selection);
17321
- if (!list) {
17561
+ deleteStructuredContentAtSelection: () => ({ editor, dispatch, state, tr }) => {
17562
+ const predicate = (node) => STRUCTURED_CONTENT_NAMES.includes(node.type.name);
17563
+ const structuredContent = findParentNode(predicate)(state.selection);
17564
+ if (!structuredContent) {
17322
17565
  return true;
17323
17566
  }
17324
17567
  if (dispatch) {
17325
- let listLevel = (list.depth - 1) / 2;
17326
- let listStyleTypes = this.options.listStyleTypes;
17327
- let listStyle = listStyleTypes[listLevel % listStyleTypes.length];
17328
- let currentListStyle = list.node.attrs["list-style-type"];
17329
- let nodeAtPos = tr.doc.nodeAt(list.pos);
17330
- if (currentListStyle !== listStyle && nodeAtPos.eq(list.node)) {
17331
- tr.setNodeMarkup(list.pos, void 0, {
17332
- ...list.node.attrs,
17333
- ...{
17334
- "list-style-type": listStyle
17335
- }
17336
- });
17337
- }
17568
+ const { node, pos } = structuredContent;
17569
+ const posFrom = pos;
17570
+ const posTo = posFrom + node.nodeSize;
17571
+ const content = node.content;
17572
+ tr.replaceWith(posFrom, posTo, content);
17338
17573
  }
17339
17574
  return true;
17340
17575
  }
17341
17576
  };
17342
17577
  },
17343
- addShortcuts() {
17578
+ addHelpers() {
17344
17579
  return {
17345
- "Mod-Shift-7": () => {
17346
- return this.editor.commands.toggleOrderedList();
17347
- }
17580
+ ...structuredContentHelpers
17348
17581
  };
17349
- },
17350
- addInputRules() {
17351
- return [
17352
- new InputRule({
17353
- match: inputRegex,
17354
- handler: ({ state, range }) => {
17355
- const $pos = state.selection.$from;
17356
- const listItemType = state.schema.nodes.listItem;
17357
- for (let depth = $pos.depth; depth >= 0; depth--) {
17358
- if ($pos.node(depth).type === listItemType) {
17359
- return null;
17360
- }
17361
- }
17362
- const { tr } = state;
17363
- tr.delete(range.from, range.to);
17364
- ListHelpers.createNewList({
17365
- listType: this.type,
17366
- tr,
17367
- editor: this.editor
17368
- });
17369
- }
17370
- })
17371
- ];
17372
17582
  }
17373
17583
  });
17374
- const generateOrderedListIndex = ({ listLevel, lvlText, listNumberingType, customFormat }) => {
17375
- const handler = listIndexMap[listNumberingType];
17376
- return handler ? handler(listLevel, lvlText, customFormat) : null;
17377
- };
17378
- const handleDecimal = (path, lvlText) => generateNumbering(path, lvlText, String);
17379
- const handleRoman = (path, lvlText) => generateNumbering(path, lvlText, intToRoman);
17380
- const handleLowerRoman = (path, lvlText) => handleRoman(path, lvlText).toLowerCase();
17381
- const handleLowerAlpha = (path, lvlText) => handleAlpha(path, lvlText).toLowerCase();
17382
- const handleAlpha = (path, lvlText) => generateNumbering(path, lvlText, (p) => intToAlpha(p));
17383
- const handleOrdinal = (path, lvlText) => generateNumbering(path, lvlText, ordinalFormatter);
17384
- const handleCustom = (path, lvlText, customFormat) => generateFromCustom(path, lvlText, customFormat);
17385
- const handleJapaneseCounting = (path, lvlText) => generateNumbering(path, lvlText, intToJapaneseCounting);
17386
- const listIndexMap = {
17387
- decimal: handleDecimal,
17388
- lowerRoman: handleLowerRoman,
17389
- upperRoman: handleRoman,
17390
- lowerLetter: handleLowerAlpha,
17391
- upperLetter: handleAlpha,
17392
- ordinal: handleOrdinal,
17393
- custom: handleCustom,
17394
- japaneseCounting: handleJapaneseCounting
17395
- };
17396
- const createNumbering = (values, lvlText) => {
17397
- return values.reduce((acc, value, index2) => {
17398
- return value > 9 ? acc.replace(/^0/, "").replace(`%${index2 + 1}`, value) : acc.replace(`%${index2 + 1}`, value);
17399
- }, lvlText);
17584
+ const randomId = () => {
17585
+ return Math.floor(Math.random() * 4294967295).toString();
17400
17586
  };
17401
- const generateNumbering = (path, lvlText, formatter) => {
17402
- const formattedValues = path.map(formatter);
17403
- return createNumbering(formattedValues, lvlText);
17404
- };
17405
- const ordinalFormatter = (level) => {
17406
- const suffixes = ["th", "st", "nd", "rd"];
17407
- const value = level % 100;
17408
- const suffix = suffixes[(value - 20) % 10] || suffixes[value] || suffixes[0];
17409
- const p = level + suffix;
17410
- return p;
17411
- };
17412
- const generateFromCustom = (path, lvlText, customFormat) => {
17413
- if (customFormat !== "001, 002, 003, ...") return generateNumbering(path, lvlText, String);
17414
- const match = customFormat.match(/(\d+)/);
17415
- if (!match) throw new Error("Invalid format string: no numeric pattern found");
17416
- const sample = match[1];
17417
- const digitCount = sample.length;
17418
- const index2 = path.pop();
17419
- return String(index2).padStart(digitCount, "0");
17420
- };
17421
- const intToRoman = (num) => {
17422
- const romanNumeralMap = [
17423
- { value: 1e3, numeral: "M" },
17424
- { value: 900, numeral: "CM" },
17425
- { value: 500, numeral: "D" },
17426
- { value: 400, numeral: "CD" },
17427
- { value: 100, numeral: "C" },
17428
- { value: 90, numeral: "XC" },
17429
- { value: 50, numeral: "L" },
17430
- { value: 40, numeral: "XL" },
17431
- { value: 10, numeral: "X" },
17432
- { value: 9, numeral: "IX" },
17433
- { value: 5, numeral: "V" },
17434
- { value: 4, numeral: "IV" },
17435
- { value: 1, numeral: "I" }
17436
- ];
17437
- let result = "";
17438
- for (const { value, numeral } of romanNumeralMap) {
17439
- while (num >= value) {
17440
- result += numeral;
17441
- num -= value;
17442
- }
17587
+ class DocumentSectionView {
17588
+ constructor(node, getPos, decorations, editor) {
17589
+ __privateAdd(this, _DocumentSectionView_instances);
17590
+ this.node = node;
17591
+ this.editor = editor;
17592
+ this.decorations = decorations;
17593
+ this.view = editor.view;
17594
+ this.getPos = getPos;
17595
+ __privateMethod(this, _DocumentSectionView_instances, init_fn2).call(this);
17443
17596
  }
17444
- return result;
17597
+ }
17598
+ _DocumentSectionView_instances = new WeakSet();
17599
+ init_fn2 = function() {
17600
+ const { attrs } = this.node;
17601
+ const { id, title, description } = attrs;
17602
+ this.dom = document.createElement("div");
17603
+ this.dom.className = "sd-document-section-block";
17604
+ this.dom.setAttribute("data-id", id);
17605
+ this.dom.setAttribute("data-title", title);
17606
+ this.dom.setAttribute("data-description", description);
17607
+ this.dom.setAttribute("aria-label", "Document section");
17608
+ __privateMethod(this, _DocumentSectionView_instances, addToolTip_fn).call(this);
17609
+ this.contentDOM = document.createElement("div");
17610
+ this.contentDOM.className = "sd-document-section-block-content";
17611
+ this.contentDOM.setAttribute("contenteditable", "true");
17612
+ this.dom.appendChild(this.contentDOM);
17445
17613
  };
17446
- const intToAlpha = (num) => {
17447
- let result = "";
17448
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
17449
- while (num > 0) {
17450
- let index2 = (num - 1) % 26;
17451
- result = alphabet[index2] + result;
17452
- num = Math.floor((num - 1) / 26);
17453
- }
17454
- return result;
17614
+ addToolTip_fn = function() {
17615
+ const { title } = this.node.attrs;
17616
+ this.infoDiv = document.createElement("div");
17617
+ this.infoDiv.className = "sd-document-section-block-info";
17618
+ const textSpan = document.createElement("span");
17619
+ textSpan.textContent = title || "Document section";
17620
+ this.infoDiv.appendChild(textSpan);
17621
+ this.infoDiv.setAttribute("contenteditable", "false");
17622
+ this.dom.appendChild(this.infoDiv);
17455
17623
  };
17456
- const intToJapaneseCounting = (num) => {
17457
- const digits = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
17458
- const units = ["", "十", "百", "千"];
17459
- if (num === 0) return "零";
17460
- if (num < 10) return digits[num];
17461
- let result = "";
17462
- let tempNum = num;
17463
- let unitIndex = 0;
17464
- while (tempNum > 0) {
17465
- const digit = tempNum % 10;
17466
- if (digit !== 0) {
17467
- const digitStr = digit === 1 && unitIndex > 0 ? "" : digits[digit];
17468
- result = digitStr + (unitIndex > 0 ? units[unitIndex] : "") + result;
17469
- } else if (result && tempNum > 0) {
17470
- if (!result.startsWith("零") && tempNum % 100 !== 0) {
17471
- result = "零" + result;
17472
- }
17624
+ const getAllSections = (editor) => {
17625
+ if (!editor) return [];
17626
+ const type = editor.schema.nodes.documentSection;
17627
+ if (!type) return [];
17628
+ const sections = [];
17629
+ const { state } = editor;
17630
+ state.doc.descendants((node, pos) => {
17631
+ if (node.type.name === type.name) {
17632
+ sections.push({ node, pos });
17473
17633
  }
17474
- tempNum = Math.floor(tempNum / 10);
17475
- unitIndex++;
17476
- if (unitIndex > 3) break;
17477
- }
17478
- if (num >= 10 && num < 20) {
17479
- result = result.replace(/^一十/, "十");
17480
- }
17634
+ });
17635
+ return sections;
17636
+ };
17637
+ const exportSectionsToHTML = (editor) => {
17638
+ const sections = getAllSections(editor);
17639
+ const processedSections = /* @__PURE__ */ new Set();
17640
+ const result = [];
17641
+ sections.forEach(({ node }) => {
17642
+ const { attrs } = node;
17643
+ const { id, title, description } = attrs;
17644
+ if (processedSections.has(id)) return;
17645
+ processedSections.add(id);
17646
+ const html = getHTMLFromNode(node, editor);
17647
+ result.push({
17648
+ id,
17649
+ title,
17650
+ description,
17651
+ html
17652
+ });
17653
+ });
17481
17654
  return result;
17482
17655
  };
17483
- const CustomSelectionPluginKey = new PluginKey("CustomSelection");
17484
- const shouldAllowNativeContextMenu = (event) => {
17485
- if (!event) return false;
17486
- if (event.ctrlKey || event.metaKey) {
17487
- return true;
17488
- }
17489
- const isKeyboardInvocation = event.type === "contextmenu" && typeof event.detail === "number" && event.detail === 0 && (event.button === 0 || event.button === void 0) && event.clientX === 0 && event.clientY === 0;
17490
- return Boolean(isKeyboardInvocation);
17656
+ const getHTMLFromNode = (node, editor) => {
17657
+ const tempDocument = document.implementation.createHTMLDocument();
17658
+ const container = tempDocument.createElement("div");
17659
+ const fragment = DOMSerializer.fromSchema(editor.schema).serializeFragment(node.content);
17660
+ container.appendChild(fragment);
17661
+ let html = container.innerHTML;
17662
+ return html;
17491
17663
  };
17492
- const handleClickOutside = (event, editor) => {
17493
- const editorElem = editor?.options?.element;
17494
- if (!editorElem) return;
17495
- const isInsideEditor = editorElem?.contains(event.target);
17496
- if (!isInsideEditor) {
17497
- editor.setOptions({
17498
- focusTarget: event.target
17499
- });
17500
- } else {
17501
- editor.setOptions({
17502
- focusTarget: null
17664
+ const exportSectionsToJSON = (editor) => {
17665
+ const sections = getAllSections(editor);
17666
+ const processedSections = /* @__PURE__ */ new Set();
17667
+ const result = [];
17668
+ sections.forEach(({ node }) => {
17669
+ const { attrs } = node;
17670
+ const { id, title, description } = attrs;
17671
+ if (processedSections.has(id)) return;
17672
+ processedSections.add(id);
17673
+ result.push({
17674
+ id,
17675
+ title,
17676
+ description,
17677
+ content: node.toJSON()
17503
17678
  });
17504
- }
17679
+ });
17680
+ return result;
17505
17681
  };
17506
- function getFocusMeta(tr) {
17507
- return tr.getMeta(CustomSelectionPluginKey);
17508
- }
17509
- function setFocusMeta(tr, value) {
17510
- return tr.setMeta(CustomSelectionPluginKey, value);
17511
- }
17512
- function getFocusState(state) {
17513
- return CustomSelectionPluginKey.getState(state);
17514
- }
17515
- const isToolbarInput = (target) => {
17516
- return !!target?.closest(".button-text-input") || target?.classList?.contains("button-text-input");
17682
+ const getLinkedSectionEditor = (id, options, editor) => {
17683
+ const sections = getAllSections(editor);
17684
+ const section = sections.find((s) => s.node.attrs.id === id);
17685
+ if (!section) return null;
17686
+ const child = editor.createChildEditor({
17687
+ ...options,
17688
+ onUpdate: ({ editor: childEditor, transaction }) => {
17689
+ const isFromtLinkedParent = transaction.getMeta("fromLinkedParent");
17690
+ if (isFromtLinkedParent) return;
17691
+ const updatedContent = childEditor.state.doc.content;
17692
+ const sectionNode = getAllSections(editor)?.find((s) => s.node.attrs.id === id);
17693
+ if (!sectionNode) return;
17694
+ const { pos, node } = sectionNode;
17695
+ const newNode = node.type.create(node.attrs, updatedContent, node.marks);
17696
+ const tr = editor.state.tr.replaceWith(pos, pos + node.nodeSize, newNode);
17697
+ tr.setMeta("fromLinkedChild", true);
17698
+ editor.view.dispatch(tr);
17699
+ }
17700
+ });
17701
+ editor.on("update", ({ transaction }) => {
17702
+ const isFromLinkedChild = transaction.getMeta("fromLinkedChild");
17703
+ if (isFromLinkedChild) return;
17704
+ const sectionNode = getAllSections(editor)?.find((s) => s.node.attrs.id === id);
17705
+ if (!sectionNode) return;
17706
+ const sectionContent = sectionNode.node.content;
17707
+ const json = {
17708
+ type: "doc",
17709
+ content: sectionContent.content.map((node) => node.toJSON())
17710
+ };
17711
+ const childTr = child.state.tr;
17712
+ childTr.setMeta("fromLinkedParent", true);
17713
+ childTr.replaceWith(0, child.state.doc.content.size, child.schema.nodeFromJSON(json));
17714
+ child.view.dispatch(childTr);
17715
+ });
17716
+ return child;
17517
17717
  };
17518
- const isToolbarButton = (target) => {
17519
- return !!target?.closest(".toolbar-button") || target?.classList?.contains("toolbar-button");
17718
+ const SectionHelpers = {
17719
+ getAllSections,
17720
+ exportSectionsToHTML,
17721
+ exportSectionsToJSON,
17722
+ getLinkedSectionEditor
17520
17723
  };
17521
- const CustomSelection = Extension.create({
17522
- name: "customSelection",
17523
- addPmPlugins() {
17524
- const editor = this.editor;
17525
- const customSelectionPlugin = new Plugin({
17526
- key: CustomSelectionPluginKey,
17527
- state: {
17528
- init: () => ({
17529
- focused: false,
17530
- preservedSelection: null,
17531
- showVisualSelection: false
17532
- }),
17533
- apply: (tr, value) => {
17534
- const meta = getFocusMeta(tr);
17535
- if (meta !== void 0) {
17536
- return { ...value, ...meta };
17537
- }
17538
- return value;
17724
+ const DocumentSection = Node$1.create({
17725
+ name: "documentSection",
17726
+ group: "block",
17727
+ content: "block*",
17728
+ atom: true,
17729
+ isolating: true,
17730
+ addOptions() {
17731
+ return {
17732
+ htmlAttributes: {
17733
+ class: "sd-document-section-block",
17734
+ "aria-label": "Structured content block"
17735
+ }
17736
+ };
17737
+ },
17738
+ parseDOM() {
17739
+ return [
17740
+ {
17741
+ tag: "div.sd-document-section-block",
17742
+ priority: 60
17743
+ }
17744
+ ];
17745
+ },
17746
+ renderDOM({ htmlAttributes }) {
17747
+ return ["div", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes), 0];
17748
+ },
17749
+ addAttributes() {
17750
+ return {
17751
+ id: {},
17752
+ sdBlockId: {
17753
+ default: null,
17754
+ keepOnSplit: false,
17755
+ parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
17756
+ renderDOM: (attrs) => {
17757
+ return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
17539
17758
  }
17540
17759
  },
17541
- view: () => {
17542
- const clickHandler = (event) => handleClickOutside(event, editor);
17543
- document?.addEventListener("mousedown", clickHandler);
17544
- return {
17545
- destroy: () => {
17546
- document?.removeEventListener("mousedown", clickHandler);
17547
- }
17548
- };
17549
- },
17760
+ title: {},
17761
+ description: {},
17762
+ sectionType: {},
17763
+ isLocked: { default: false }
17764
+ };
17765
+ },
17766
+ addNodeView() {
17767
+ return ({ node, editor, getPos, decorations }) => {
17768
+ return new DocumentSectionView(node, getPos, decorations, editor);
17769
+ };
17770
+ },
17771
+ addCommands() {
17772
+ return {
17773
+ /**
17774
+ * Create a lockable content section
17775
+ * @category Command
17776
+ * @param {SectionCreate} [options={}] - Section configuration
17777
+ * @example
17778
+ * editor.commands.createDocumentSection({
17779
+ * id: 1,
17780
+ * title: 'Terms & Conditions',
17781
+ * isLocked: true,
17782
+ * html: '<p>Legal content...</p>'
17783
+ * })
17784
+ */
17785
+ createDocumentSection: (options = {}) => ({ tr, state, dispatch, editor }) => {
17786
+ const { selection } = state;
17787
+ let { from: from2, to } = selection;
17788
+ let content = selection.content().content;
17789
+ const { html: optionsHTML, json: optionsJSON } = options;
17790
+ if (optionsHTML) {
17791
+ const html = htmlHandler(optionsHTML, this.editor);
17792
+ const doc2 = DOMParser$1.fromSchema(this.editor.schema).parse(html);
17793
+ content = doc2.content;
17794
+ }
17795
+ if (optionsJSON) {
17796
+ content = this.editor.schema.nodeFromJSON(optionsJSON);
17797
+ }
17798
+ if (!content?.content?.length) {
17799
+ content = this.editor.schema.nodeFromJSON({ type: "paragraph", content: [] });
17800
+ }
17801
+ if (!options.id) {
17802
+ const allSections = SectionHelpers.getAllSections(editor);
17803
+ options.id = allSections.length + 1;
17804
+ }
17805
+ if (!options.title) {
17806
+ options.title = "Document section";
17807
+ }
17808
+ const node = this.type.createAndFill(options, content);
17809
+ if (!node) return false;
17810
+ const isAlreadyInSdtBlock = findParentNode((node2) => node2.type.name === "documentSection")(selection);
17811
+ if (isAlreadyInSdtBlock && isAlreadyInSdtBlock.node) {
17812
+ const insertPos2 = isAlreadyInSdtBlock.pos + isAlreadyInSdtBlock.node.nodeSize;
17813
+ from2 = insertPos2;
17814
+ to = insertPos2;
17815
+ }
17816
+ tr.replaceRangeWith(from2, to, node);
17817
+ const nodeEnd = from2 + node.nodeSize;
17818
+ let shouldInsertParagraph = true;
17819
+ let insertPos = nodeEnd;
17820
+ if (nodeEnd >= tr.doc.content.size) {
17821
+ insertPos = tr.doc.content.size;
17822
+ if (insertPos > 0) {
17823
+ const $endPos = tr.doc.resolve(insertPos);
17824
+ if ($endPos.nodeBefore && $endPos.nodeBefore.type.name === "paragraph") {
17825
+ shouldInsertParagraph = false;
17826
+ }
17827
+ }
17828
+ }
17829
+ if (shouldInsertParagraph) {
17830
+ const emptyParagraph = tr.doc.type.schema.nodes.paragraph.create();
17831
+ tr.insert(insertPos, emptyParagraph);
17832
+ }
17833
+ if (dispatch) {
17834
+ tr.setMeta("documentSection", { action: "create" });
17835
+ dispatch(tr);
17836
+ setTimeout(() => {
17837
+ try {
17838
+ const currentState = editor.state;
17839
+ const docSize = currentState.doc.content.size;
17840
+ let targetPos = from2 + node.nodeSize;
17841
+ if (shouldInsertParagraph) {
17842
+ targetPos += 1;
17843
+ }
17844
+ targetPos = Math.min(targetPos, docSize);
17845
+ if (targetPos < docSize && targetPos > 0) {
17846
+ const newSelection = Selection.near(currentState.doc.resolve(targetPos));
17847
+ const newTr = currentState.tr.setSelection(newSelection);
17848
+ editor.view.dispatch(newTr);
17849
+ }
17850
+ } catch (e) {
17851
+ console.warn("Could not set delayed selection:", e);
17852
+ }
17853
+ }, 0);
17854
+ }
17855
+ return true;
17856
+ },
17857
+ /**
17858
+ * Remove section wrapper at cursor, preserving its content
17859
+ * @category Command
17860
+ * @example
17861
+ * editor.commands.removeSectionAtSelection()
17862
+ * @note Content stays in document, only section wrapper is removed
17863
+ */
17864
+ removeSectionAtSelection: () => ({ tr, dispatch }) => {
17865
+ const sdtNode = findParentNode((node2) => node2.type.name === "documentSection")(tr.selection);
17866
+ if (!sdtNode) return false;
17867
+ const { node, pos } = sdtNode;
17868
+ const nodeStart = pos;
17869
+ const nodeEnd = nodeStart + node.nodeSize;
17870
+ const contentToPreserve = node.content;
17871
+ tr.delete(nodeStart, nodeEnd);
17872
+ if (contentToPreserve.size > 0) {
17873
+ tr.insert(nodeStart, contentToPreserve);
17874
+ }
17875
+ const newPos = Math.min(nodeStart, tr.doc.content.size);
17876
+ tr.setSelection(Selection.near(tr.doc.resolve(newPos)));
17877
+ if (dispatch) {
17878
+ tr.setMeta("documentSection", { action: "delete" });
17879
+ dispatch(tr);
17880
+ }
17881
+ return true;
17882
+ },
17883
+ /**
17884
+ * Delete section and all its content
17885
+ * @category Command
17886
+ * @param {number} id - Section to delete
17887
+ * @example
17888
+ * editor.commands.removeSectionById(123)
17889
+ */
17890
+ removeSectionById: (id) => ({ tr, dispatch }) => {
17891
+ const sections = SectionHelpers.getAllSections(this.editor);
17892
+ const sectionToRemove = sections.find(({ node: node2 }) => node2.attrs.id === id);
17893
+ if (!sectionToRemove) return false;
17894
+ const { pos, node } = sectionToRemove;
17895
+ const nodeStart = pos;
17896
+ const nodeEnd = nodeStart + node.nodeSize;
17897
+ tr.delete(nodeStart, nodeEnd);
17898
+ if (dispatch) {
17899
+ tr.setMeta("documentSection", { action: "delete", id });
17900
+ dispatch(tr);
17901
+ }
17902
+ return true;
17903
+ },
17904
+ /**
17905
+ * Lock section against edits
17906
+ * @category Command
17907
+ * @param {number} id - Section to lock
17908
+ * @example
17909
+ * editor.commands.lockSectionById(123)
17910
+ */
17911
+ lockSectionById: (id) => ({ tr, dispatch }) => {
17912
+ const sections = SectionHelpers.getAllSections(this.editor);
17913
+ const sectionToLock = sections.find(({ node }) => node.attrs.id === id);
17914
+ if (!sectionToLock) return false;
17915
+ tr.setNodeMarkup(sectionToLock.pos, null, { ...sectionToLock.node.attrs, isLocked: true });
17916
+ if (dispatch) {
17917
+ tr.setMeta("documentSection", { action: "lock", id });
17918
+ dispatch(tr);
17919
+ }
17920
+ return true;
17921
+ },
17922
+ /**
17923
+ * Modify section attributes or content
17924
+ * @category Command
17925
+ * @param {SectionUpdate} options - Changes to apply
17926
+ * @example
17927
+ * editor.commands.updateSectionById({ id: 123, attrs: { isLocked: false } })
17928
+ * editor.commands.updateSectionById({ id: 123, html: '<p>New content</p>' })
17929
+ * editor.commands.updateSectionById({
17930
+ * id: 123,
17931
+ * html: '<p>Updated</p>',
17932
+ * attrs: { title: 'New Title' }
17933
+ * })
17934
+ */
17935
+ updateSectionById: ({ id, html, json, attrs }) => ({ tr, dispatch, editor }) => {
17936
+ const sections = SectionHelpers.getAllSections(editor || this.editor);
17937
+ const sectionToUpdate = sections.find(({ node: node2 }) => node2.attrs.id === id);
17938
+ if (!sectionToUpdate) return false;
17939
+ const { pos, node } = sectionToUpdate;
17940
+ let newContent = null;
17941
+ if (html) {
17942
+ const htmlDoc = htmlHandler(html, editor || this.editor);
17943
+ const doc2 = DOMParser$1.fromSchema((editor || this.editor).schema).parse(htmlDoc);
17944
+ newContent = doc2.content;
17945
+ }
17946
+ if (json) {
17947
+ newContent = (editor || this.editor).schema.nodeFromJSON(json);
17948
+ }
17949
+ if (!newContent) {
17950
+ newContent = node.content;
17951
+ }
17952
+ const updatedNode = node.type.create({ ...node.attrs, ...attrs }, newContent, node.marks);
17953
+ tr.replaceWith(pos, pos + node.nodeSize, updatedNode);
17954
+ if (dispatch) {
17955
+ tr.setMeta("documentSection", { action: "update", id, attrs });
17956
+ dispatch(tr);
17957
+ }
17958
+ return true;
17959
+ }
17960
+ };
17961
+ },
17962
+ addHelpers() {
17963
+ return {
17964
+ ...SectionHelpers
17965
+ };
17966
+ }
17967
+ });
17968
+ const Document = Node$1.create({
17969
+ name: "doc",
17970
+ topNode: true,
17971
+ content: "block+",
17972
+ parseDOM() {
17973
+ return [{ tag: "doc" }];
17974
+ },
17975
+ renderDOM() {
17976
+ return ["doc", 0];
17977
+ },
17978
+ addAttributes() {
17979
+ return {
17980
+ attributes: {
17981
+ rendered: false,
17982
+ "aria-label": "Document node"
17983
+ }
17984
+ };
17985
+ },
17986
+ addCommands() {
17987
+ return {
17988
+ /**
17989
+ * Get document statistics
17990
+ * @category Command
17991
+ * @example
17992
+ * // Get word and character count
17993
+ * const stats = editor.commands.getDocumentStats()
17994
+ * console.log(`${stats.words} words, ${stats.characters} characters`)
17995
+ * @note Returns word count, character count, and paragraph count
17996
+ */
17997
+ getDocumentStats: () => ({ editor }) => {
17998
+ const text = editor.getText();
17999
+ const words = text.split(/\s+/).filter((word) => word.length > 0).length;
18000
+ const characters = text.length;
18001
+ const paragraphs = editor.state.doc.content.childCount;
18002
+ return {
18003
+ words,
18004
+ characters,
18005
+ paragraphs
18006
+ };
18007
+ },
18008
+ /**
18009
+ * Clear entire document
18010
+ * @category Command
18011
+ * @example
18012
+ * editor.commands.clearDocument()
18013
+ * @note Replaces all content with an empty paragraph
18014
+ */
18015
+ clearDocument: () => ({ commands: commands2 }) => {
18016
+ return commands2.setContent("<p></p>");
18017
+ }
18018
+ };
18019
+ }
18020
+ });
18021
+ const Text = Node$1.create({
18022
+ name: "text",
18023
+ group: "inline",
18024
+ inline: true,
18025
+ addOptions() {
18026
+ return {};
18027
+ }
18028
+ });
18029
+ const splitRun = () => (props) => {
18030
+ const { state, view, tr } = props;
18031
+ const { $from, empty: empty2 } = state.selection;
18032
+ if (!empty2) return false;
18033
+ if ($from.parent.type.name !== "run") return false;
18034
+ const handled = splitBlock(state, (transaction) => {
18035
+ view.dispatch(transaction);
18036
+ });
18037
+ if (handled) {
18038
+ tr.setMeta("preventDispatch", true);
18039
+ }
18040
+ return handled;
18041
+ };
18042
+ const Run = OxmlNode.create({
18043
+ name: "run",
18044
+ oXmlName: "w:r",
18045
+ group: "inline",
18046
+ inline: true,
18047
+ content: "inline*",
18048
+ selectable: false,
18049
+ childToAttributes: ["runProperties"],
18050
+ addOptions() {
18051
+ return {
18052
+ htmlAttributes: {
18053
+ "data-run": "1"
18054
+ }
18055
+ };
18056
+ },
18057
+ addAttributes() {
18058
+ return {
18059
+ runProperties: {
18060
+ default: null,
18061
+ rendered: false,
18062
+ keepOnSplit: true
18063
+ },
18064
+ rsidR: {
18065
+ default: null,
18066
+ rendered: false,
18067
+ keepOnSplit: true
18068
+ },
18069
+ rsidRPr: {
18070
+ default: null,
18071
+ rendered: false,
18072
+ keepOnSplit: true
18073
+ },
18074
+ rsidDel: {
18075
+ default: null,
18076
+ rendered: false,
18077
+ keepOnSplit: true
18078
+ }
18079
+ };
18080
+ },
18081
+ addCommands() {
18082
+ return {
18083
+ splitRun
18084
+ };
18085
+ },
18086
+ parseDOM() {
18087
+ return [{ tag: "span[data-run]" }];
18088
+ },
18089
+ renderDOM({ htmlAttributes }) {
18090
+ const base2 = Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes);
18091
+ return ["span", base2, 0];
18092
+ }
18093
+ });
18094
+ const inputRegex$1 = /^\s*([-+*])\s$/;
18095
+ const BulletList = Node$1.create({
18096
+ name: "bulletList",
18097
+ group: "block list",
18098
+ selectable: false,
18099
+ content() {
18100
+ return `${this.options.itemTypeName}+`;
18101
+ },
18102
+ addOptions() {
18103
+ return {
18104
+ itemTypeName: "listItem",
18105
+ htmlAttributes: {
18106
+ "aria-label": "Bullet list node"
18107
+ },
18108
+ keepMarks: true,
18109
+ keepAttributes: false
18110
+ };
18111
+ },
18112
+ parseDOM() {
18113
+ return [{ tag: "ul" }];
18114
+ },
18115
+ renderDOM({ htmlAttributes }) {
18116
+ const attributes = Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes);
18117
+ return ["ul", attributes, 0];
18118
+ },
18119
+ addAttributes() {
18120
+ return {
18121
+ "list-style-type": {
18122
+ default: "bullet",
18123
+ rendered: false
18124
+ },
18125
+ listId: {
18126
+ rendered: false
18127
+ },
18128
+ sdBlockId: {
18129
+ default: null,
18130
+ keepOnSplit: false,
18131
+ parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
18132
+ renderDOM: (attrs) => {
18133
+ return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
18134
+ }
18135
+ },
18136
+ attributes: {
18137
+ rendered: false,
18138
+ keepOnSplit: true
18139
+ }
18140
+ };
18141
+ },
18142
+ addCommands() {
18143
+ return {
18144
+ /**
18145
+ * Toggle a bullet list at the current selection
18146
+ * @category Command
18147
+ * @example
18148
+ * // Toggle bullet list on selected text
18149
+ * editor.commands.toggleBulletList()
18150
+ * @note Converts selected paragraphs to list items or removes list formatting
18151
+ */
18152
+ toggleBulletList: () => (params2) => {
18153
+ return toggleList(this.type)(params2);
18154
+ }
18155
+ };
18156
+ },
18157
+ addShortcuts() {
18158
+ return {
18159
+ "Mod-Shift-8": () => {
18160
+ return this.editor.commands.toggleBulletList();
18161
+ }
18162
+ };
18163
+ },
18164
+ addInputRules() {
18165
+ return [
18166
+ new InputRule({
18167
+ match: inputRegex$1,
18168
+ handler: ({ state, range }) => {
18169
+ const $pos = state.selection.$from;
18170
+ const listItemType = state.schema.nodes.listItem;
18171
+ for (let depth = $pos.depth; depth >= 0; depth--) {
18172
+ if ($pos.node(depth).type === listItemType) {
18173
+ return null;
18174
+ }
18175
+ }
18176
+ const { tr } = state;
18177
+ tr.delete(range.from, range.to);
18178
+ ListHelpers.createNewList({
18179
+ listType: this.type,
18180
+ tr,
18181
+ editor: this.editor
18182
+ });
18183
+ }
18184
+ })
18185
+ ];
18186
+ }
18187
+ });
18188
+ const inputRegex = /^(\d+)\.\s$/;
18189
+ const OrderedList = Node$1.create({
18190
+ name: "orderedList",
18191
+ group: "block list",
18192
+ selectable: false,
18193
+ content() {
18194
+ return `${this.options.itemTypeName}+`;
18195
+ },
18196
+ addOptions() {
18197
+ return {
18198
+ itemTypeName: "listItem",
18199
+ htmlAttributes: {
18200
+ "aria-label": "Ordered list node"
18201
+ },
18202
+ keepMarks: true,
18203
+ keepAttributes: false,
18204
+ listStyleTypes: ["decimal", "lowerAlpha", "lowerRoman"]
18205
+ };
18206
+ },
18207
+ addAttributes() {
18208
+ return {
18209
+ order: {
18210
+ default: 1,
18211
+ parseDOM: (element) => {
18212
+ return element.hasAttribute("start") ? parseInt(element.getAttribute("start") || "", 10) : 1;
18213
+ },
18214
+ renderDOM: (attrs) => {
18215
+ return {
18216
+ start: attrs.order
18217
+ };
18218
+ }
18219
+ },
18220
+ sdBlockId: {
18221
+ default: null,
18222
+ keepOnSplit: false,
18223
+ parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
18224
+ renderDOM: (attrs) => {
18225
+ return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
18226
+ }
18227
+ },
18228
+ syncId: {
18229
+ default: null,
18230
+ parseDOM: (elem) => elem.getAttribute("data-sync-id"),
18231
+ renderDOM: (attrs) => {
18232
+ if (!attrs.syncId) return {};
18233
+ return {
18234
+ "data-sync-id": attrs.syncId
18235
+ };
18236
+ }
18237
+ // rendered: false,
18238
+ },
18239
+ listId: {
18240
+ keepOnSplit: true,
18241
+ parseDOM: (elem) => elem.getAttribute("data-list-id"),
18242
+ renderDOM: (attrs) => {
18243
+ if (!attrs.listId) return {};
18244
+ return {
18245
+ "data-list-id": attrs.listId
18246
+ };
18247
+ }
18248
+ },
18249
+ "list-style-type": {
18250
+ default: "decimal",
18251
+ rendered: false
18252
+ },
18253
+ attributes: {
18254
+ rendered: false,
18255
+ keepOnSplit: true
18256
+ }
18257
+ };
18258
+ },
18259
+ parseDOM() {
18260
+ return [{ tag: "ol" }];
18261
+ },
18262
+ renderDOM({ htmlAttributes }) {
18263
+ const { start: start2, ...restAttributes } = htmlAttributes;
18264
+ return start2 === 1 ? ["ol", Attribute.mergeAttributes(this.options.htmlAttributes, restAttributes), 0] : ["ol", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes), 0];
18265
+ },
18266
+ addCommands() {
18267
+ return {
18268
+ /**
18269
+ * Toggle ordered list formatting
18270
+ * @category Command
18271
+ * @example
18272
+ * editor.commands.toggleOrderedList()
18273
+ * @note Converts selection to ordered list or back to paragraphs
18274
+ */
18275
+ toggleOrderedList: () => (params2) => {
18276
+ return toggleList(this.type)(params2);
18277
+ },
18278
+ /**
18279
+ * Restart list node numbering
18280
+ * @category Command
18281
+ * @param {Array} followingNodes - Nodes to restart
18282
+ * @param {number} pos - Starting position
18283
+ * @example
18284
+ * editor.commands.restartListNodes(nodes, position)
18285
+ * @note Resets list numbering for specified nodes
18286
+ */
18287
+ restartListNodes: (followingNodes, pos) => ({ tr }) => {
18288
+ let currentNodePos = pos;
18289
+ const nodes = followingNodes.map((node) => {
18290
+ const resultNode = {
18291
+ node,
18292
+ pos: currentNodePos
18293
+ };
18294
+ currentNodePos += node.nodeSize;
18295
+ return resultNode;
18296
+ });
18297
+ nodes.forEach((item) => {
18298
+ const { pos: pos2 } = item;
18299
+ const newPos = tr.mapping.map(pos2);
18300
+ tr.setNodeMarkup(newPos, void 0, {});
18301
+ });
18302
+ return true;
18303
+ },
18304
+ /**
18305
+ * Update ordered list style type based on nesting level
18306
+ * @category Command
18307
+ * @example
18308
+ * editor.commands.updateOrderedListStyleType()
18309
+ * @note Cycles through decimal -> lowerAlpha -> lowerRoman based on depth
18310
+ */
18311
+ updateOrderedListStyleType: () => ({ dispatch, tr }) => {
18312
+ let list = findParentNode((node) => node.type.name === this.name)(tr.selection);
18313
+ if (!list) {
18314
+ return true;
18315
+ }
18316
+ if (dispatch) {
18317
+ let listLevel = (list.depth - 1) / 2;
18318
+ let listStyleTypes = this.options.listStyleTypes;
18319
+ let listStyle = listStyleTypes[listLevel % listStyleTypes.length];
18320
+ let currentListStyle = list.node.attrs["list-style-type"];
18321
+ let nodeAtPos = tr.doc.nodeAt(list.pos);
18322
+ if (currentListStyle !== listStyle && nodeAtPos.eq(list.node)) {
18323
+ tr.setNodeMarkup(list.pos, void 0, {
18324
+ ...list.node.attrs,
18325
+ ...{
18326
+ "list-style-type": listStyle
18327
+ }
18328
+ });
18329
+ }
18330
+ }
18331
+ return true;
18332
+ }
18333
+ };
18334
+ },
18335
+ addShortcuts() {
18336
+ return {
18337
+ "Mod-Shift-7": () => {
18338
+ return this.editor.commands.toggleOrderedList();
18339
+ }
18340
+ };
18341
+ },
18342
+ addInputRules() {
18343
+ return [
18344
+ new InputRule({
18345
+ match: inputRegex,
18346
+ handler: ({ state, range }) => {
18347
+ const $pos = state.selection.$from;
18348
+ const listItemType = state.schema.nodes.listItem;
18349
+ for (let depth = $pos.depth; depth >= 0; depth--) {
18350
+ if ($pos.node(depth).type === listItemType) {
18351
+ return null;
18352
+ }
18353
+ }
18354
+ const { tr } = state;
18355
+ tr.delete(range.from, range.to);
18356
+ ListHelpers.createNewList({
18357
+ listType: this.type,
18358
+ tr,
18359
+ editor: this.editor
18360
+ });
18361
+ }
18362
+ })
18363
+ ];
18364
+ }
18365
+ });
18366
+ const generateOrderedListIndex = ({ listLevel, lvlText, listNumberingType, customFormat }) => {
18367
+ const handler = listIndexMap[listNumberingType];
18368
+ return handler ? handler(listLevel, lvlText, customFormat) : null;
18369
+ };
18370
+ const handleDecimal = (path, lvlText) => generateNumbering(path, lvlText, String);
18371
+ const handleRoman = (path, lvlText) => generateNumbering(path, lvlText, intToRoman);
18372
+ const handleLowerRoman = (path, lvlText) => handleRoman(path, lvlText).toLowerCase();
18373
+ const handleLowerAlpha = (path, lvlText) => handleAlpha(path, lvlText).toLowerCase();
18374
+ const handleAlpha = (path, lvlText) => generateNumbering(path, lvlText, (p) => intToAlpha(p));
18375
+ const handleOrdinal = (path, lvlText) => generateNumbering(path, lvlText, ordinalFormatter);
18376
+ const handleCustom = (path, lvlText, customFormat) => generateFromCustom(path, lvlText, customFormat);
18377
+ const handleJapaneseCounting = (path, lvlText) => generateNumbering(path, lvlText, intToJapaneseCounting);
18378
+ const listIndexMap = {
18379
+ decimal: handleDecimal,
18380
+ lowerRoman: handleLowerRoman,
18381
+ upperRoman: handleRoman,
18382
+ lowerLetter: handleLowerAlpha,
18383
+ upperLetter: handleAlpha,
18384
+ ordinal: handleOrdinal,
18385
+ custom: handleCustom,
18386
+ japaneseCounting: handleJapaneseCounting
18387
+ };
18388
+ const createNumbering = (values, lvlText) => {
18389
+ return values.reduce((acc, value, index2) => {
18390
+ return value > 9 ? acc.replace(/^0/, "").replace(`%${index2 + 1}`, value) : acc.replace(`%${index2 + 1}`, value);
18391
+ }, lvlText);
18392
+ };
18393
+ const generateNumbering = (path, lvlText, formatter) => {
18394
+ const formattedValues = path.map(formatter);
18395
+ return createNumbering(formattedValues, lvlText);
18396
+ };
18397
+ const ordinalFormatter = (level) => {
18398
+ const suffixes = ["th", "st", "nd", "rd"];
18399
+ const value = level % 100;
18400
+ const suffix = suffixes[(value - 20) % 10] || suffixes[value] || suffixes[0];
18401
+ const p = level + suffix;
18402
+ return p;
18403
+ };
18404
+ const generateFromCustom = (path, lvlText, customFormat) => {
18405
+ if (customFormat !== "001, 002, 003, ...") return generateNumbering(path, lvlText, String);
18406
+ const match = customFormat.match(/(\d+)/);
18407
+ if (!match) throw new Error("Invalid format string: no numeric pattern found");
18408
+ const sample = match[1];
18409
+ const digitCount = sample.length;
18410
+ const index2 = path.pop();
18411
+ return String(index2).padStart(digitCount, "0");
18412
+ };
18413
+ const intToRoman = (num) => {
18414
+ const romanNumeralMap = [
18415
+ { value: 1e3, numeral: "M" },
18416
+ { value: 900, numeral: "CM" },
18417
+ { value: 500, numeral: "D" },
18418
+ { value: 400, numeral: "CD" },
18419
+ { value: 100, numeral: "C" },
18420
+ { value: 90, numeral: "XC" },
18421
+ { value: 50, numeral: "L" },
18422
+ { value: 40, numeral: "XL" },
18423
+ { value: 10, numeral: "X" },
18424
+ { value: 9, numeral: "IX" },
18425
+ { value: 5, numeral: "V" },
18426
+ { value: 4, numeral: "IV" },
18427
+ { value: 1, numeral: "I" }
18428
+ ];
18429
+ let result = "";
18430
+ for (const { value, numeral } of romanNumeralMap) {
18431
+ while (num >= value) {
18432
+ result += numeral;
18433
+ num -= value;
18434
+ }
18435
+ }
18436
+ return result;
18437
+ };
18438
+ const intToAlpha = (num) => {
18439
+ let result = "";
18440
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
18441
+ while (num > 0) {
18442
+ let index2 = (num - 1) % 26;
18443
+ result = alphabet[index2] + result;
18444
+ num = Math.floor((num - 1) / 26);
18445
+ }
18446
+ return result;
18447
+ };
18448
+ const intToJapaneseCounting = (num) => {
18449
+ const digits = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
18450
+ const units = ["", "十", "百", "千"];
18451
+ if (num === 0) return "零";
18452
+ if (num < 10) return digits[num];
18453
+ let result = "";
18454
+ let tempNum = num;
18455
+ let unitIndex = 0;
18456
+ while (tempNum > 0) {
18457
+ const digit = tempNum % 10;
18458
+ if (digit !== 0) {
18459
+ const digitStr = digit === 1 && unitIndex > 0 ? "" : digits[digit];
18460
+ result = digitStr + (unitIndex > 0 ? units[unitIndex] : "") + result;
18461
+ } else if (result && tempNum > 0) {
18462
+ if (!result.startsWith("零") && tempNum % 100 !== 0) {
18463
+ result = "零" + result;
18464
+ }
18465
+ }
18466
+ tempNum = Math.floor(tempNum / 10);
18467
+ unitIndex++;
18468
+ if (unitIndex > 3) break;
18469
+ }
18470
+ if (num >= 10 && num < 20) {
18471
+ result = result.replace(/^一十/, "十");
18472
+ }
18473
+ return result;
18474
+ };
18475
+ const isKeyboardInvocation = (event) => {
18476
+ return event.type === "contextmenu" && typeof event.detail === "number" && event.detail === 0 && (event.button === 0 || event.button === void 0) && event.clientX === 0 && event.clientY === 0;
18477
+ };
18478
+ const prefersNativeMenu = (event) => {
18479
+ if (!event) return false;
18480
+ if (event.ctrlKey || event.metaKey) {
18481
+ return true;
18482
+ }
18483
+ return isKeyboardInvocation(event);
18484
+ };
18485
+ const shouldAllowNativeContextMenu = (event) => {
18486
+ return prefersNativeMenu(event);
18487
+ };
18488
+ const shouldBypassContextMenu = shouldAllowNativeContextMenu;
18489
+ const DEFAULT_SELECTION_STATE = Object.freeze({
18490
+ focused: false,
18491
+ preservedSelection: null,
18492
+ showVisualSelection: false,
18493
+ skipFocusReset: false
18494
+ });
18495
+ const normalizeSelectionState = (state = {}) => ({
18496
+ ...DEFAULT_SELECTION_STATE,
18497
+ ...state
18498
+ });
18499
+ const CustomSelectionPluginKey = new PluginKey("CustomSelection");
18500
+ const handleClickOutside = (event, editor) => {
18501
+ const editorElem = editor?.options?.element;
18502
+ if (!editorElem) return;
18503
+ const isInsideEditor = editorElem?.contains(event.target);
18504
+ if (!isInsideEditor) {
18505
+ editor.setOptions({
18506
+ focusTarget: event.target
18507
+ });
18508
+ } else {
18509
+ editor.setOptions({
18510
+ focusTarget: null
18511
+ });
18512
+ }
18513
+ };
18514
+ function getFocusMeta(tr) {
18515
+ return tr.getMeta(CustomSelectionPluginKey);
18516
+ }
18517
+ function setFocusMeta(tr, value) {
18518
+ return tr.setMeta(CustomSelectionPluginKey, value);
18519
+ }
18520
+ function getFocusState(state) {
18521
+ return CustomSelectionPluginKey.getState(state);
18522
+ }
18523
+ const isToolbarInput = (target) => {
18524
+ return !!target?.closest(".button-text-input") || target?.classList?.contains("button-text-input");
18525
+ };
18526
+ const isToolbarButton = (target) => {
18527
+ return !!target?.closest(".toolbar-button") || target?.classList?.contains("toolbar-button");
18528
+ };
18529
+ const CustomSelection = Extension.create({
18530
+ name: "customSelection",
18531
+ addPmPlugins() {
18532
+ const editor = this.editor;
18533
+ const customSelectionPlugin = new Plugin({
18534
+ key: CustomSelectionPluginKey,
18535
+ state: {
18536
+ init: () => ({ ...DEFAULT_SELECTION_STATE }),
18537
+ apply: (tr, value) => {
18538
+ const meta = getFocusMeta(tr);
18539
+ if (meta !== void 0) {
18540
+ return { ...value, ...meta };
18541
+ }
18542
+ return value;
18543
+ }
18544
+ },
18545
+ view: () => {
18546
+ const clickHandler = (event) => handleClickOutside(event, editor);
18547
+ document?.addEventListener("mousedown", clickHandler);
18548
+ return {
18549
+ destroy: () => {
18550
+ document?.removeEventListener("mousedown", clickHandler);
18551
+ }
18552
+ };
18553
+ },
17550
18554
  props: {
17551
18555
  handleDOMEvents: {
17552
18556
  contextmenu: (view, event) => {
@@ -17560,7 +18564,8 @@ const CustomSelection = Extension.create({
17560
18564
  setFocusMeta(view.state.tr, {
17561
18565
  focused: true,
17562
18566
  preservedSelection: selection,
17563
- showVisualSelection: true
18567
+ showVisualSelection: true,
18568
+ skipFocusReset: true
17564
18569
  })
17565
18570
  );
17566
18571
  }
@@ -17581,7 +18586,8 @@ const CustomSelection = Extension.create({
17581
18586
  setFocusMeta(view.state.tr, {
17582
18587
  focused: true,
17583
18588
  preservedSelection: selection2,
17584
- showVisualSelection: true
18589
+ showVisualSelection: true,
18590
+ skipFocusReset: true
17585
18591
  })
17586
18592
  );
17587
18593
  this.editor.setOptions({
@@ -17604,7 +18610,8 @@ const CustomSelection = Extension.create({
17604
18610
  setFocusMeta(view.state.tr, {
17605
18611
  focused: true,
17606
18612
  preservedSelection: selection,
17607
- showVisualSelection: true
18613
+ showVisualSelection: true,
18614
+ skipFocusReset: false
17608
18615
  })
17609
18616
  );
17610
18617
  this.editor.setOptions({
@@ -17622,7 +18629,8 @@ const CustomSelection = Extension.create({
17622
18629
  setFocusMeta(view.state.tr, {
17623
18630
  focused: true,
17624
18631
  preservedSelection: selection,
17625
- showVisualSelection: true
18632
+ showVisualSelection: true,
18633
+ skipFocusReset: false
17626
18634
  })
17627
18635
  );
17628
18636
  }
@@ -17633,7 +18641,8 @@ const CustomSelection = Extension.create({
17633
18641
  setFocusMeta(view.state.tr, {
17634
18642
  focused: false,
17635
18643
  preservedSelection: null,
17636
- showVisualSelection: false
18644
+ showVisualSelection: false,
18645
+ skipFocusReset: false
17637
18646
  })
17638
18647
  );
17639
18648
  if (!selection.empty && !this.editor.options.element?.contains(target)) {
@@ -17650,12 +18659,20 @@ const CustomSelection = Extension.create({
17650
18659
  const isElement2 = target instanceof Element;
17651
18660
  const isToolbarBtn = isElement2 && isToolbarButton(target);
17652
18661
  const isToolbarInp = isElement2 && isToolbarInput(target);
18662
+ const focusState = getFocusState(view.state);
18663
+ if (focusState?.skipFocusReset) {
18664
+ view.dispatch(
18665
+ setFocusMeta(view.state.tr, normalizeSelectionState({ ...focusState, skipFocusReset: false }))
18666
+ );
18667
+ return false;
18668
+ }
17653
18669
  if (!isToolbarBtn && !isToolbarInp) {
17654
18670
  view.dispatch(
17655
18671
  setFocusMeta(view.state.tr, {
17656
18672
  focused: false,
17657
18673
  preservedSelection: null,
17658
- showVisualSelection: false
18674
+ showVisualSelection: false,
18675
+ skipFocusReset: false
17659
18676
  })
17660
18677
  );
17661
18678
  }
@@ -17666,12 +18683,16 @@ const CustomSelection = Extension.create({
17666
18683
  const isToolbarBtn = isElement2 && isToolbarButton(target);
17667
18684
  const isToolbarInp = isElement2 && isToolbarInput(target);
17668
18685
  const state = getFocusState(view.state);
18686
+ if (state?.skipFocusReset) {
18687
+ return false;
18688
+ }
17669
18689
  if (isToolbarBtn || isToolbarInp) {
17670
18690
  view.dispatch(
17671
18691
  setFocusMeta(view.state.tr, {
17672
18692
  focused: true,
17673
18693
  preservedSelection: state.preservedSelection || view.state.selection,
17674
- showVisualSelection: true
18694
+ showVisualSelection: true,
18695
+ skipFocusReset: false
17675
18696
  })
17676
18697
  );
17677
18698
  } else {
@@ -17679,7 +18700,8 @@ const CustomSelection = Extension.create({
17679
18700
  setFocusMeta(view.state.tr, {
17680
18701
  focused: false,
17681
18702
  preservedSelection: null,
17682
- showVisualSelection: false
18703
+ showVisualSelection: false,
18704
+ skipFocusReset: false
17683
18705
  })
17684
18706
  );
17685
18707
  }
@@ -18525,7 +19547,7 @@ class ListItemNodeView {
18525
19547
  this.decorations = decorations;
18526
19548
  this.view = editor.view;
18527
19549
  this.getPos = getPos;
18528
- __privateMethod(this, _ListItemNodeView_instances, init_fn2).call(this);
19550
+ __privateMethod(this, _ListItemNodeView_instances, init_fn3).call(this);
18529
19551
  activeListItemNodeViews.add(this);
18530
19552
  }
18531
19553
  refreshIndentStyling() {
@@ -18586,7 +19608,7 @@ class ListItemNodeView {
18586
19608
  }
18587
19609
  }
18588
19610
  _ListItemNodeView_instances = new WeakSet();
18589
- init_fn2 = function() {
19611
+ init_fn3 = function() {
18590
19612
  const { attrs } = this.node;
18591
19613
  const { listLevel, listNumberingType, lvlText, numId, level, customFormat } = attrs;
18592
19614
  let orderMarker = "";
@@ -25695,984 +26717,335 @@ const PageNumber = Node$1.create({
25695
26717
  }
25696
26718
  };
25697
26719
  },
25698
- addAttributes() {
25699
- return {
25700
- marksAsAttrs: {
25701
- default: null,
25702
- rendered: false
25703
- }
25704
- };
25705
- },
25706
- addNodeView() {
25707
- return ({ node, editor, getPos, decorations }) => {
25708
- const htmlAttributes = this.options.htmlAttributes;
25709
- return new AutoPageNumberNodeView(node, getPos, decorations, editor, htmlAttributes);
25710
- };
25711
- },
25712
- parseDOM() {
25713
- return [{ tag: 'span[data-id="auto-page-number"' }];
25714
- },
25715
- renderDOM({ htmlAttributes }) {
25716
- return ["span", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes)];
25717
- },
25718
- addCommands() {
25719
- return {
25720
- /**
25721
- * Insert an automatic page number
25722
- * @category Command
25723
- * @returns {Function} Command function
25724
- * @example
25725
- * editor.commands.addAutoPageNumber()
25726
- * @note Only works in header/footer contexts
25727
- */
25728
- addAutoPageNumber: () => ({ tr, dispatch, state, editor }) => {
25729
- const { options } = editor;
25730
- if (!options.isHeaderOrFooter) return false;
25731
- const { schema } = state;
25732
- const pageNumberType = schema?.nodes?.["page-number"];
25733
- if (!pageNumberType) return false;
25734
- const pageNumberNodeJSON = { type: "page-number" };
25735
- const pageNumberNode = schema.nodeFromJSON(pageNumberNodeJSON);
25736
- if (dispatch) {
25737
- tr.replaceSelectionWith(pageNumberNode, false);
25738
- tr.setMeta("forceUpdatePagination", true);
25739
- }
25740
- return true;
25741
- }
25742
- };
25743
- },
25744
- addShortcuts() {
25745
- return {
25746
- "Mod-Shift-alt-p": () => this.editor.commands.addAutoPageNumber()
25747
- };
25748
- }
25749
- });
25750
- const TotalPageCount = Node$1.create({
25751
- name: "total-page-number",
25752
- group: "inline",
25753
- inline: true,
25754
- atom: true,
25755
- draggable: false,
25756
- selectable: false,
25757
- content: "text*",
25758
- addOptions() {
25759
- return {
25760
- htmlAttributes: {
25761
- contenteditable: false,
25762
- "data-id": "auto-total-pages",
25763
- "aria-label": "Total page count node",
25764
- class: "sd-editor-auto-total-pages"
25765
- }
25766
- };
25767
- },
25768
- addAttributes() {
25769
- return {
25770
- marksAsAttrs: {
25771
- default: null,
25772
- rendered: false
25773
- }
25774
- };
25775
- },
25776
- addNodeView() {
25777
- return ({ node, editor, getPos, decorations }) => {
25778
- const htmlAttributes = this.options.htmlAttributes;
25779
- return new AutoPageNumberNodeView(node, getPos, decorations, editor, htmlAttributes);
25780
- };
25781
- },
25782
- parseDOM() {
25783
- return [{ tag: 'span[data-id="auto-total-pages"' }];
25784
- },
25785
- renderDOM({ htmlAttributes }) {
25786
- return ["span", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes), 0];
25787
- },
25788
- addCommands() {
25789
- return {
25790
- /**
25791
- * Insert total page count
25792
- * @category Command
25793
- * @returns {Function} Command function
25794
- * @example
25795
- * editor.commands.addTotalPageCount()
25796
- * @note Only works in header/footer contexts
25797
- */
25798
- addTotalPageCount: () => ({ tr, dispatch, state, editor }) => {
25799
- const { options } = editor;
25800
- if (!options.isHeaderOrFooter) return false;
25801
- const { schema } = state;
25802
- const pageNumberType = schema.nodes?.["total-page-number"];
25803
- if (!pageNumberType) return false;
25804
- const currentPages = editor?.options?.parentEditor?.currentTotalPages || 1;
25805
- const pageNumberNode = {
25806
- type: "total-page-number",
25807
- content: [{ type: "text", text: String(currentPages) }]
25808
- };
25809
- const pageNode = schema.nodeFromJSON(pageNumberNode);
25810
- if (dispatch) {
25811
- tr.replaceSelectionWith(pageNode, false);
25812
- }
25813
- return true;
25814
- }
25815
- };
25816
- },
25817
- addShortcuts() {
25818
- return {
25819
- "Mod-Shift-alt-c": () => this.editor.commands.addTotalPageCount()
25820
- };
25821
- }
25822
- });
25823
- const getNodeAttributes = (nodeName, editor) => {
25824
- switch (nodeName) {
25825
- case "page-number":
25826
- return {
25827
- text: editor.options.currentPageNumber || "1",
25828
- className: "sd-editor-auto-page-number",
25829
- dataId: "auto-page-number",
25830
- ariaLabel: "Page number node"
25831
- };
25832
- case "total-page-number":
25833
- return {
25834
- text: editor.options.parentEditor?.currentTotalPages || "1",
25835
- className: "sd-editor-auto-total-pages",
25836
- dataId: "auto-total-pages",
25837
- ariaLabel: "Total page count node"
25838
- };
25839
- default:
25840
- return {};
25841
- }
25842
- };
25843
- class AutoPageNumberNodeView {
25844
- constructor(node, getPos, decorations, editor, htmlAttributes = {}) {
25845
- __privateAdd(this, _AutoPageNumberNodeView_instances);
25846
- this.node = node;
25847
- this.editor = editor;
25848
- this.view = editor.view;
25849
- this.getPos = getPos;
25850
- this.editor = editor;
25851
- this.dom = __privateMethod(this, _AutoPageNumberNodeView_instances, renderDom_fn).call(this, node, htmlAttributes);
25852
- }
25853
- update(node) {
25854
- const incomingType = node?.type?.name;
25855
- const currentType = this.node?.type?.name;
25856
- if (!incomingType || incomingType !== currentType) return false;
25857
- this.node = node;
25858
- return true;
25859
- }
25860
- }
25861
- _AutoPageNumberNodeView_instances = new WeakSet();
25862
- renderDom_fn = function(node, htmlAttributes) {
25863
- const attrs = getNodeAttributes(this.node.type.name, this.editor);
25864
- const content = document.createTextNode(String(attrs.text));
25865
- const nodeContent = document.createElement("span");
25866
- nodeContent.className = attrs.className;
25867
- nodeContent.setAttribute("data-id", attrs.dataId);
25868
- nodeContent.setAttribute("aria-label", attrs.ariaLabel);
25869
- const currentPos = this.getPos();
25870
- const { styles, marks } = getMarksFromNeighbors(currentPos, this.view);
25871
- __privateMethod(this, _AutoPageNumberNodeView_instances, scheduleUpdateNodeStyle_fn).call(this, currentPos, marks);
25872
- Object.assign(nodeContent.style, styles);
25873
- nodeContent.appendChild(content);
25874
- Object.entries(htmlAttributes).forEach(([key2, value]) => {
25875
- if (value) nodeContent.setAttribute(key2, value);
25876
- });
25877
- return nodeContent;
25878
- };
25879
- scheduleUpdateNodeStyle_fn = function(pos, marks) {
25880
- setTimeout(() => {
25881
- const { state } = this.editor;
25882
- const { dispatch } = this.view;
25883
- const node = state.doc.nodeAt(pos);
25884
- if (!node || node.isText) return;
25885
- const currentMarks = node.attrs.marksAsAttrs || [];
25886
- const newMarks = marks.map((m) => ({ type: m.type.name, attrs: m.attrs }));
25887
- const isEqual = JSON.stringify(currentMarks) === JSON.stringify(newMarks);
25888
- if (isEqual) return;
25889
- const newAttrs = {
25890
- ...node.attrs,
25891
- marksAsAttrs: newMarks
25892
- };
25893
- const tr = state.tr.setNodeMarkup(pos, void 0, newAttrs);
25894
- dispatch(tr);
25895
- }, 0);
25896
- };
25897
- const getMarksFromNeighbors = (currentPos, view) => {
25898
- const $pos = view.state.doc.resolve(currentPos);
25899
- const styles = {};
25900
- const marks = [];
25901
- const before = $pos.nodeBefore;
25902
- if (before) {
25903
- Object.assign(styles, processMarks(before.marks));
25904
- marks.push(...before.marks);
25905
- }
25906
- const after = $pos.nodeAfter;
25907
- if (after) {
25908
- Object.assign(styles, { ...styles, ...processMarks(after.marks) });
25909
- marks.push(...after.marks);
25910
- }
25911
- return {
25912
- styles,
25913
- marks
25914
- };
25915
- };
25916
- const processMarks = (marks) => {
25917
- const styles = {};
25918
- marks.forEach((mark) => {
25919
- const { type, attrs } = mark;
25920
- switch (type.name) {
25921
- case "textStyle":
25922
- if (attrs.fontFamily) styles["font-family"] = attrs.fontFamily;
25923
- if (attrs.fontSize) styles["font-size"] = attrs.fontSize;
25924
- if (attrs.color) styles["color"] = attrs.color;
25925
- if (attrs.backgroundColor) styles["background-color"] = attrs.backgroundColor;
25926
- break;
25927
- case "bold":
25928
- styles["font-weight"] = "bold";
25929
- break;
25930
- case "italic":
25931
- styles["font-style"] = "italic";
25932
- break;
25933
- case "underline":
25934
- styles["text-decoration"] = (styles["text-decoration"] || "") + " underline";
25935
- break;
25936
- case "strike":
25937
- styles["text-decoration"] = (styles["text-decoration"] || "") + " line-through";
25938
- break;
25939
- default:
25940
- if (attrs?.style) {
25941
- Object.entries(attrs.style).forEach(([key2, value]) => {
25942
- styles[key2] = value;
25943
- });
25944
- }
25945
- break;
25946
- }
25947
- });
25948
- return styles;
25949
- };
25950
- const ShapeContainer = Node$1.create({
25951
- name: "shapeContainer",
25952
- group: "block",
25953
- content: "block+",
25954
- isolating: true,
25955
- addOptions() {
25956
- return {
25957
- htmlAttributes: {
25958
- class: "sd-editor-shape-container",
25959
- "aria-label": "Shape container node"
25960
- }
25961
- };
25962
- },
25963
- addAttributes() {
25964
- return {
25965
- fillcolor: {
25966
- renderDOM: (attrs) => {
25967
- if (!attrs.fillcolor) return {};
25968
- return {
25969
- style: `background-color: ${attrs.fillcolor}`
25970
- };
25971
- }
25972
- },
25973
- sdBlockId: {
25974
- default: null,
25975
- keepOnSplit: false,
25976
- parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
25977
- renderDOM: (attrs) => {
25978
- return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
25979
- }
25980
- },
25981
- style: {
25982
- renderDOM: (attrs) => {
25983
- if (!attrs.style) return {};
25984
- return {
25985
- style: attrs.style
25986
- };
25987
- }
25988
- },
25989
- wrapAttributes: {
25990
- rendered: false
25991
- },
25992
- attributes: {
25993
- rendered: false
25994
- }
25995
- };
25996
- },
25997
- parseDOM() {
25998
- return [
25999
- {
26000
- tag: `div[data-type="${this.name}"]`
26001
- }
26002
- ];
26003
- },
26004
- renderDOM({ htmlAttributes }) {
26005
- return [
26006
- "div",
26007
- Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, { "data-type": this.name }),
26008
- 0
26009
- ];
26010
- }
26011
- });
26012
- const ShapeTextbox = Node$1.create({
26013
- name: "shapeTextbox",
26014
- group: "block",
26015
- content: "paragraph* block*",
26016
- isolating: true,
26017
- addOptions() {
26018
- return {
26019
- htmlAttributes: {
26020
- class: "sd-editor-shape-textbox",
26021
- "aria-label": "Shape textbox node"
26022
- }
26023
- };
26024
- },
26025
- addAttributes() {
26026
- return {
26027
- sdBlockId: {
26028
- default: null,
26029
- keepOnSplit: false,
26030
- parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
26031
- renderDOM: (attrs) => {
26032
- return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
26033
- }
26034
- },
26035
- attributes: {
26036
- rendered: false
26037
- }
26038
- };
26039
- },
26040
- parseDOM() {
26041
- return [
26042
- {
26043
- tag: `div[data-type="${this.name}"]`
26044
- }
26045
- ];
26046
- },
26047
- renderDOM({ htmlAttributes }) {
26048
- return [
26049
- "div",
26050
- Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, { "data-type": this.name }),
26051
- 0
26052
- ];
26053
- }
26054
- });
26055
- const ContentBlock = Node$1.create({
26056
- name: "contentBlock",
26057
- group: "inline",
26058
- content: "",
26059
- isolating: true,
26060
- atom: true,
26061
- inline: true,
26062
- addOptions() {
26063
- return {
26064
- htmlAttributes: {
26065
- contenteditable: false
26066
- }
26067
- };
26068
- },
26069
- addAttributes() {
26070
- return {
26071
- horizontalRule: {
26072
- default: false,
26073
- renderDOM: ({ horizontalRule }) => {
26074
- if (!horizontalRule) return {};
26075
- return { "data-horizontal-rule": "true" };
26076
- }
26077
- },
26078
- size: {
26079
- default: null,
26080
- renderDOM: ({ size }) => {
26081
- if (!size) return {};
26082
- let style = "";
26083
- if (size.top) style += `top: ${size.top}px; `;
26084
- if (size.left) style += `left: ${size.left}px; `;
26085
- if (size.width) style += `width: ${size.width.toString().endsWith("%") ? size.width : `${size.width}px`}; `;
26086
- if (size.height)
26087
- style += `height: ${size.height.toString().endsWith("%") ? size.height : `${size.height}px`}; `;
26088
- return { style };
26089
- }
26090
- },
26091
- background: {
26092
- default: null,
26093
- renderDOM: (attrs) => {
26094
- if (!attrs.background) return {};
26095
- return {
26096
- style: `background-color: ${attrs.background}`
26097
- };
26098
- }
26099
- },
26100
- drawingContent: {
26101
- rendered: false
26102
- },
26103
- attributes: {
26720
+ addAttributes() {
26721
+ return {
26722
+ marksAsAttrs: {
26723
+ default: null,
26104
26724
  rendered: false
26105
26725
  }
26106
26726
  };
26107
26727
  },
26728
+ addNodeView() {
26729
+ return ({ node, editor, getPos, decorations }) => {
26730
+ const htmlAttributes = this.options.htmlAttributes;
26731
+ return new AutoPageNumberNodeView(node, getPos, decorations, editor, htmlAttributes);
26732
+ };
26733
+ },
26108
26734
  parseDOM() {
26109
- return [
26110
- {
26111
- tag: `div[data-type="${this.name}"]`
26112
- }
26113
- ];
26735
+ return [{ tag: 'span[data-id="auto-page-number"' }];
26114
26736
  },
26115
26737
  renderDOM({ htmlAttributes }) {
26116
- return ["div", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, { "data-type": this.name })];
26738
+ return ["span", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes)];
26117
26739
  },
26118
26740
  addCommands() {
26119
26741
  return {
26120
26742
  /**
26121
- * Insert a horizontal rule
26122
- * @category Command
26123
- * @example
26124
- * editor.commands.insertHorizontalRule()
26125
- * @note Creates a visual separator between content sections
26126
- */
26127
- insertHorizontalRule: () => ({ commands: commands2 }) => {
26128
- return commands2.insertContent({
26129
- type: this.name,
26130
- attrs: {
26131
- horizontalRule: true,
26132
- size: { width: "100%", height: 2 },
26133
- background: "#e5e7eb"
26134
- }
26135
- });
26136
- },
26137
- /**
26138
- * Insert a content block
26743
+ * Insert an automatic page number
26139
26744
  * @category Command
26140
- * @param {ContentBlockConfig} config - Block configuration
26141
- * @example
26142
- * // Insert a spacer block
26143
- * editor.commands.insertContentBlock({ size: { height: 20 } })
26144
- *
26745
+ * @returns {Function} Command function
26145
26746
  * @example
26146
- * // Insert a colored divider
26147
- * editor.commands.insertContentBlock({
26148
- * size: { width: '50%', height: 3 },
26149
- * background: '#3b82f6'
26150
- * })
26151
- * @note Used for spacing, dividers, and special inline content
26747
+ * editor.commands.addAutoPageNumber()
26748
+ * @note Only works in header/footer contexts
26152
26749
  */
26153
- insertContentBlock: (config) => ({ commands: commands2 }) => {
26154
- return commands2.insertContent({
26155
- type: this.name,
26156
- attrs: config
26157
- });
26750
+ addAutoPageNumber: () => ({ tr, dispatch, state, editor }) => {
26751
+ const { options } = editor;
26752
+ if (!options.isHeaderOrFooter) return false;
26753
+ const { schema } = state;
26754
+ const pageNumberType = schema?.nodes?.["page-number"];
26755
+ if (!pageNumberType) return false;
26756
+ const pageNumberNodeJSON = { type: "page-number" };
26757
+ const pageNumberNode = schema.nodeFromJSON(pageNumberNodeJSON);
26758
+ if (dispatch) {
26759
+ tr.replaceSelectionWith(pageNumberNode, false);
26760
+ tr.setMeta("forceUpdatePagination", true);
26761
+ }
26762
+ return true;
26158
26763
  }
26159
26764
  };
26765
+ },
26766
+ addShortcuts() {
26767
+ return {
26768
+ "Mod-Shift-alt-p": () => this.editor.commands.addAutoPageNumber()
26769
+ };
26160
26770
  }
26161
26771
  });
26162
- class StructuredContentViewBase {
26163
- constructor(props) {
26164
- __publicField(this, "node");
26165
- __publicField(this, "view");
26166
- __publicField(this, "getPos");
26167
- __publicField(this, "decorations");
26168
- __publicField(this, "innerDecorations");
26169
- __publicField(this, "editor");
26170
- __publicField(this, "extension");
26171
- __publicField(this, "htmlAttributes");
26172
- __publicField(this, "root");
26173
- __publicField(this, "isDragging", false);
26174
- this.node = props.node;
26175
- this.view = props.editor.view;
26176
- this.getPos = props.getPos;
26177
- this.decorations = props.decorations;
26178
- this.innerDecorations = props.innerDecorations;
26179
- this.editor = props.editor;
26180
- this.extension = props.extension;
26181
- this.htmlAttributes = props.htmlAttributes;
26182
- this.mount(props);
26183
- }
26184
- mount() {
26185
- return;
26186
- }
26187
- get dom() {
26188
- return this.root;
26189
- }
26190
- get contentDOM() {
26191
- return null;
26192
- }
26193
- update(node, decorations, innerDecorations) {
26194
- if (node.type !== this.node.type) {
26195
- return false;
26196
- }
26197
- this.node = node;
26198
- this.decorations = decorations;
26199
- this.innerDecorations = innerDecorations;
26200
- this.updateHTMLAttributes();
26201
- return true;
26202
- }
26203
- stopEvent(event) {
26204
- if (!this.dom) return false;
26205
- const target = event.target;
26206
- const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target);
26207
- if (!isInElement) return false;
26208
- const isDragEvent = event.type.startsWith("drag");
26209
- const isDropEvent = event.type === "drop";
26210
- const isInput = ["INPUT", "BUTTON", "SELECT", "TEXTAREA"].includes(target.tagName) || target.isContentEditable;
26211
- if (isInput && !isDropEvent && !isDragEvent) return true;
26212
- const { isEditable } = this.editor;
26213
- const { isDragging } = this;
26214
- const isDraggable = !!this.node.type.spec.draggable;
26215
- const isSelectable = NodeSelection.isSelectable(this.node);
26216
- const isCopyEvent = event.type === "copy";
26217
- const isPasteEvent = event.type === "paste";
26218
- const isCutEvent = event.type === "cut";
26219
- const isClickEvent = event.type === "mousedown";
26220
- if (!isDraggable && isSelectable && isDragEvent && event.target === this.dom) {
26221
- event.preventDefault();
26222
- }
26223
- if (isDraggable && isDragEvent && !isDragging && event.target === this.dom) {
26224
- event.preventDefault();
26225
- return false;
26226
- }
26227
- if (isDraggable && isEditable && !isDragging && isClickEvent) {
26228
- const dragHandle = target.closest("[data-drag-handle]");
26229
- const isValidDragHandle = dragHandle && (this.dom === dragHandle || this.dom.contains(dragHandle));
26230
- if (isValidDragHandle) {
26231
- this.isDragging = true;
26232
- document.addEventListener(
26233
- "dragend",
26234
- () => {
26235
- this.isDragging = false;
26236
- },
26237
- { once: true }
26238
- );
26239
- document.addEventListener(
26240
- "drop",
26241
- () => {
26242
- this.isDragging = false;
26243
- },
26244
- { once: true }
26245
- );
26246
- document.addEventListener(
26247
- "mouseup",
26248
- () => {
26249
- this.isDragging = false;
26250
- },
26251
- { once: true }
26252
- );
26253
- }
26254
- }
26255
- if (isDragging || isDropEvent || isCopyEvent || isPasteEvent || isCutEvent || isClickEvent && isSelectable) {
26256
- return false;
26257
- }
26258
- return true;
26259
- }
26260
- ignoreMutation(mutation) {
26261
- if (!this.dom || !this.contentDOM) return true;
26262
- if (this.node.isLeaf || this.node.isAtom) return true;
26263
- if (mutation.type === "selection") return false;
26264
- if (this.contentDOM === mutation.target && mutation.type === "attributes") return true;
26265
- if (this.contentDOM.contains(mutation.target)) return false;
26266
- return true;
26267
- }
26268
- destroy() {
26269
- this.dom.remove();
26270
- this.contentDOM?.remove();
26271
- }
26272
- updateAttributes(attrs) {
26273
- const pos = this.getPos();
26274
- if (typeof pos !== "number") {
26275
- return;
26276
- }
26277
- return this.view.dispatch(
26278
- this.view.state.tr.setNodeMarkup(pos, void 0, {
26279
- ...this.node.attrs,
26280
- ...attrs
26281
- })
26282
- );
26283
- }
26284
- updateHTMLAttributes() {
26285
- const { extensionService } = this.editor;
26286
- const { attributes } = extensionService;
26287
- const extensionAttrs = attributes.filter((i) => i.type === this.node.type.name);
26288
- this.htmlAttributes = Attribute.getAttributesToRender(this.node, extensionAttrs);
26289
- }
26290
- createDragHandle() {
26291
- const dragHandle = document.createElement("span");
26292
- dragHandle.classList.add("sd-structured-content-draggable");
26293
- dragHandle.draggable = true;
26294
- dragHandle.contentEditable = "false";
26295
- dragHandle.dataset.dragHandle = "";
26296
- const textElement = document.createElement("span");
26297
- textElement.textContent = "Structured content";
26298
- dragHandle.append(textElement);
26299
- return dragHandle;
26300
- }
26301
- onDragStart(event) {
26302
- const { view } = this.editor;
26303
- const target = event.target;
26304
- const dragHandle = target.nodeType === 3 ? target.parentElement?.closest("[data-drag-handle]") : target.closest("[data-drag-handle]");
26305
- if (!this.dom || this.contentDOM?.contains(target) || !dragHandle) {
26306
- return;
26307
- }
26308
- let x = 0;
26309
- let y = 0;
26310
- if (this.dom !== dragHandle) {
26311
- const domBox = this.dom.getBoundingClientRect();
26312
- const handleBox = dragHandle.getBoundingClientRect();
26313
- const offsetX = event.offsetX ?? event.nativeEvent?.offsetX;
26314
- const offsetY = event.offsetY ?? event.nativeEvent?.offsetY;
26315
- x = handleBox.x - domBox.x + offsetX;
26316
- y = handleBox.y - domBox.y + offsetY;
26317
- }
26318
- event.dataTransfer?.setDragImage(this.dom, x, y);
26319
- const pos = this.getPos();
26320
- if (typeof pos !== "number") {
26321
- return;
26322
- }
26323
- const selection = NodeSelection.create(view.state.doc, pos);
26324
- const transaction = view.state.tr.setSelection(selection);
26325
- view.dispatch(transaction);
26326
- }
26327
- }
26328
- class StructuredContentInlineView extends StructuredContentViewBase {
26329
- constructor(props) {
26330
- super(props);
26331
- }
26332
- mount() {
26333
- this.buildView();
26334
- }
26335
- get contentDOM() {
26336
- const contentElement = this.dom?.querySelector(`.${structuredContentInnerClass$1}`);
26337
- return contentElement || null;
26338
- }
26339
- createElement() {
26340
- const element = document.createElement("span");
26341
- element.classList.add(structuredContentClass$1);
26342
- element.setAttribute("data-structured-content", "");
26343
- const contentElement = document.createElement("span");
26344
- contentElement.classList.add(structuredContentInnerClass$1);
26345
- element.append(contentElement);
26346
- const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
26347
- updateDOMAttributes(element, { ...domAttrs });
26348
- return { element, contentElement };
26349
- }
26350
- buildView() {
26351
- const { element } = this.createElement();
26352
- const dragHandle = this.createDragHandle();
26353
- element.prepend(dragHandle);
26354
- element.addEventListener("dragstart", (e) => this.onDragStart(e));
26355
- this.root = element;
26356
- }
26357
- updateView() {
26358
- const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
26359
- updateDOMAttributes(this.dom, { ...domAttrs });
26360
- }
26361
- update(node, decorations, innerDecorations) {
26362
- const result = super.update(node, decorations, innerDecorations);
26363
- if (!result) return false;
26364
- this.updateView();
26365
- return true;
26366
- }
26367
- }
26368
- const structuredContentClass$1 = "sd-structured-content";
26369
- const structuredContentInnerClass$1 = "sd-structured-content__content";
26370
- const StructuredContent = Node$1.create({
26371
- name: "structuredContent",
26372
- group: "inline structuredContent",
26772
+ const TotalPageCount = Node$1.create({
26773
+ name: "total-page-number",
26774
+ group: "inline",
26373
26775
  inline: true,
26374
- content: "inline*",
26375
- isolating: true,
26376
- atom: false,
26377
- // false - has editable content.
26378
- draggable: true,
26776
+ atom: true,
26777
+ draggable: false,
26778
+ selectable: false,
26779
+ content: "text*",
26379
26780
  addOptions() {
26380
26781
  return {
26381
26782
  htmlAttributes: {
26382
- class: structuredContentClass$1,
26383
- "aria-label": "Structured content node"
26783
+ contenteditable: false,
26784
+ "data-id": "auto-total-pages",
26785
+ "aria-label": "Total page count node",
26786
+ class: "sd-editor-auto-total-pages"
26384
26787
  }
26385
26788
  };
26386
26789
  },
26387
26790
  addAttributes() {
26388
26791
  return {
26389
- id: {
26792
+ marksAsAttrs: {
26390
26793
  default: null,
26391
- parseDOM: (elem) => elem.getAttribute("data-id"),
26392
- renderDOM: (attrs) => {
26393
- if (!attrs.id) return {};
26394
- return { "data-id": attrs.id };
26395
- }
26396
- },
26397
- sdtPr: {
26398
26794
  rendered: false
26399
26795
  }
26400
26796
  };
26401
26797
  },
26798
+ addNodeView() {
26799
+ return ({ node, editor, getPos, decorations }) => {
26800
+ const htmlAttributes = this.options.htmlAttributes;
26801
+ return new AutoPageNumberNodeView(node, getPos, decorations, editor, htmlAttributes);
26802
+ };
26803
+ },
26402
26804
  parseDOM() {
26403
- return [{ tag: "span[data-structured-content]" }];
26805
+ return [{ tag: 'span[data-id="auto-total-pages"' }];
26404
26806
  },
26405
26807
  renderDOM({ htmlAttributes }) {
26406
- return [
26407
- "span",
26408
- Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, {
26409
- "data-structured-content": ""
26410
- }),
26411
- 0
26412
- ];
26808
+ return ["span", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes), 0];
26413
26809
  },
26414
- addNodeView() {
26415
- return (props) => {
26416
- return new StructuredContentInlineView({ ...props });
26810
+ addCommands() {
26811
+ return {
26812
+ /**
26813
+ * Insert total page count
26814
+ * @category Command
26815
+ * @returns {Function} Command function
26816
+ * @example
26817
+ * editor.commands.addTotalPageCount()
26818
+ * @note Only works in header/footer contexts
26819
+ */
26820
+ addTotalPageCount: () => ({ tr, dispatch, state, editor }) => {
26821
+ const { options } = editor;
26822
+ if (!options.isHeaderOrFooter) return false;
26823
+ const { schema } = state;
26824
+ const pageNumberType = schema.nodes?.["total-page-number"];
26825
+ if (!pageNumberType) return false;
26826
+ const currentPages = editor?.options?.parentEditor?.currentTotalPages || 1;
26827
+ const pageNumberNode = {
26828
+ type: "total-page-number",
26829
+ content: [{ type: "text", text: String(currentPages) }]
26830
+ };
26831
+ const pageNode = schema.nodeFromJSON(pageNumberNode);
26832
+ if (dispatch) {
26833
+ tr.replaceSelectionWith(pageNode, false);
26834
+ }
26835
+ return true;
26836
+ }
26837
+ };
26838
+ },
26839
+ addShortcuts() {
26840
+ return {
26841
+ "Mod-Shift-alt-c": () => this.editor.commands.addTotalPageCount()
26417
26842
  };
26418
26843
  }
26419
26844
  });
26420
- class StructuredContentBlockView extends StructuredContentViewBase {
26421
- constructor(props) {
26422
- super(props);
26423
- }
26424
- mount() {
26425
- this.buildView();
26426
- }
26427
- get contentDOM() {
26428
- const contentElement = this.dom?.querySelector(`.${structuredContentInnerClass}`);
26429
- return contentElement || null;
26430
- }
26431
- createElement() {
26432
- const element = document.createElement("div");
26433
- element.classList.add(structuredContentClass);
26434
- element.setAttribute("data-structured-content-block", "");
26435
- const contentElement = document.createElement("div");
26436
- contentElement.classList.add(structuredContentInnerClass);
26437
- element.append(contentElement);
26438
- const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
26439
- updateDOMAttributes(element, { ...domAttrs });
26440
- return { element, contentElement };
26441
- }
26442
- buildView() {
26443
- const { element } = this.createElement();
26444
- const dragHandle = this.createDragHandle();
26445
- element.prepend(dragHandle);
26446
- element.addEventListener("dragstart", (e) => this.onDragStart(e));
26447
- this.root = element;
26845
+ const getNodeAttributes = (nodeName, editor) => {
26846
+ switch (nodeName) {
26847
+ case "page-number":
26848
+ return {
26849
+ text: editor.options.currentPageNumber || "1",
26850
+ className: "sd-editor-auto-page-number",
26851
+ dataId: "auto-page-number",
26852
+ ariaLabel: "Page number node"
26853
+ };
26854
+ case "total-page-number":
26855
+ return {
26856
+ text: editor.options.parentEditor?.currentTotalPages || "1",
26857
+ className: "sd-editor-auto-total-pages",
26858
+ dataId: "auto-total-pages",
26859
+ ariaLabel: "Total page count node"
26860
+ };
26861
+ default:
26862
+ return {};
26448
26863
  }
26449
- updateView() {
26450
- const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
26451
- updateDOMAttributes(this.dom, { ...domAttrs });
26864
+ };
26865
+ class AutoPageNumberNodeView {
26866
+ constructor(node, getPos, decorations, editor, htmlAttributes = {}) {
26867
+ __privateAdd(this, _AutoPageNumberNodeView_instances);
26868
+ this.node = node;
26869
+ this.editor = editor;
26870
+ this.view = editor.view;
26871
+ this.getPos = getPos;
26872
+ this.editor = editor;
26873
+ this.dom = __privateMethod(this, _AutoPageNumberNodeView_instances, renderDom_fn).call(this, node, htmlAttributes);
26452
26874
  }
26453
- update(node, decorations, innerDecorations) {
26454
- const result = super.update(node, decorations, innerDecorations);
26455
- if (!result) return false;
26456
- this.updateView();
26875
+ update(node) {
26876
+ const incomingType = node?.type?.name;
26877
+ const currentType = this.node?.type?.name;
26878
+ if (!incomingType || incomingType !== currentType) return false;
26879
+ this.node = node;
26457
26880
  return true;
26458
26881
  }
26459
26882
  }
26460
- const structuredContentClass = "sd-structured-content-block";
26461
- const structuredContentInnerClass = "sd-structured-content-block__content";
26462
- const StructuredContentBlock = Node$1.create({
26463
- name: "structuredContentBlock",
26464
- group: "block structuredContent",
26465
- content: "block*",
26883
+ _AutoPageNumberNodeView_instances = new WeakSet();
26884
+ renderDom_fn = function(node, htmlAttributes) {
26885
+ const attrs = getNodeAttributes(this.node.type.name, this.editor);
26886
+ const content = document.createTextNode(String(attrs.text));
26887
+ const nodeContent = document.createElement("span");
26888
+ nodeContent.className = attrs.className;
26889
+ nodeContent.setAttribute("data-id", attrs.dataId);
26890
+ nodeContent.setAttribute("aria-label", attrs.ariaLabel);
26891
+ const currentPos = this.getPos();
26892
+ const { styles, marks } = getMarksFromNeighbors(currentPos, this.view);
26893
+ __privateMethod(this, _AutoPageNumberNodeView_instances, scheduleUpdateNodeStyle_fn).call(this, currentPos, marks);
26894
+ Object.assign(nodeContent.style, styles);
26895
+ nodeContent.appendChild(content);
26896
+ Object.entries(htmlAttributes).forEach(([key2, value]) => {
26897
+ if (value) nodeContent.setAttribute(key2, value);
26898
+ });
26899
+ return nodeContent;
26900
+ };
26901
+ scheduleUpdateNodeStyle_fn = function(pos, marks) {
26902
+ setTimeout(() => {
26903
+ const { state } = this.editor;
26904
+ const { dispatch } = this.view;
26905
+ const node = state.doc.nodeAt(pos);
26906
+ if (!node || node.isText) return;
26907
+ const currentMarks = node.attrs.marksAsAttrs || [];
26908
+ const newMarks = marks.map((m) => ({ type: m.type.name, attrs: m.attrs }));
26909
+ const isEqual = JSON.stringify(currentMarks) === JSON.stringify(newMarks);
26910
+ if (isEqual) return;
26911
+ const newAttrs = {
26912
+ ...node.attrs,
26913
+ marksAsAttrs: newMarks
26914
+ };
26915
+ const tr = state.tr.setNodeMarkup(pos, void 0, newAttrs);
26916
+ dispatch(tr);
26917
+ }, 0);
26918
+ };
26919
+ const getMarksFromNeighbors = (currentPos, view) => {
26920
+ const $pos = view.state.doc.resolve(currentPos);
26921
+ const styles = {};
26922
+ const marks = [];
26923
+ const before = $pos.nodeBefore;
26924
+ if (before) {
26925
+ Object.assign(styles, processMarks(before.marks));
26926
+ marks.push(...before.marks);
26927
+ }
26928
+ const after = $pos.nodeAfter;
26929
+ if (after) {
26930
+ Object.assign(styles, { ...styles, ...processMarks(after.marks) });
26931
+ marks.push(...after.marks);
26932
+ }
26933
+ return {
26934
+ styles,
26935
+ marks
26936
+ };
26937
+ };
26938
+ const processMarks = (marks) => {
26939
+ const styles = {};
26940
+ marks.forEach((mark) => {
26941
+ const { type, attrs } = mark;
26942
+ switch (type.name) {
26943
+ case "textStyle":
26944
+ if (attrs.fontFamily) styles["font-family"] = attrs.fontFamily;
26945
+ if (attrs.fontSize) styles["font-size"] = attrs.fontSize;
26946
+ if (attrs.color) styles["color"] = attrs.color;
26947
+ if (attrs.backgroundColor) styles["background-color"] = attrs.backgroundColor;
26948
+ break;
26949
+ case "bold":
26950
+ styles["font-weight"] = "bold";
26951
+ break;
26952
+ case "italic":
26953
+ styles["font-style"] = "italic";
26954
+ break;
26955
+ case "underline":
26956
+ styles["text-decoration"] = (styles["text-decoration"] || "") + " underline";
26957
+ break;
26958
+ case "strike":
26959
+ styles["text-decoration"] = (styles["text-decoration"] || "") + " line-through";
26960
+ break;
26961
+ default:
26962
+ if (attrs?.style) {
26963
+ Object.entries(attrs.style).forEach(([key2, value]) => {
26964
+ styles[key2] = value;
26965
+ });
26966
+ }
26967
+ break;
26968
+ }
26969
+ });
26970
+ return styles;
26971
+ };
26972
+ const ShapeContainer = Node$1.create({
26973
+ name: "shapeContainer",
26974
+ group: "block",
26975
+ content: "block+",
26466
26976
  isolating: true,
26467
- atom: false,
26468
- // false - has editable content.
26469
- draggable: true,
26470
26977
  addOptions() {
26471
26978
  return {
26472
26979
  htmlAttributes: {
26473
- class: structuredContentClass,
26474
- "aria-label": "Structured content block node"
26980
+ class: "sd-editor-shape-container",
26981
+ "aria-label": "Shape container node"
26475
26982
  }
26476
26983
  };
26477
26984
  },
26478
26985
  addAttributes() {
26479
26986
  return {
26480
- id: {
26987
+ fillcolor: {
26988
+ renderDOM: (attrs) => {
26989
+ if (!attrs.fillcolor) return {};
26990
+ return {
26991
+ style: `background-color: ${attrs.fillcolor}`
26992
+ };
26993
+ }
26994
+ },
26995
+ sdBlockId: {
26481
26996
  default: null,
26482
- parseDOM: (elem) => elem.getAttribute("data-id"),
26997
+ keepOnSplit: false,
26998
+ parseDOM: (elem) => elem.getAttribute("data-sd-block-id"),
26483
26999
  renderDOM: (attrs) => {
26484
- if (!attrs.id) return {};
26485
- return { "data-id": attrs.id };
27000
+ return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
26486
27001
  }
26487
27002
  },
26488
- sdtPr: {
27003
+ style: {
27004
+ renderDOM: (attrs) => {
27005
+ if (!attrs.style) return {};
27006
+ return {
27007
+ style: attrs.style
27008
+ };
27009
+ }
27010
+ },
27011
+ wrapAttributes: {
27012
+ rendered: false
27013
+ },
27014
+ attributes: {
26489
27015
  rendered: false
26490
27016
  }
26491
27017
  };
26492
27018
  },
26493
27019
  parseDOM() {
26494
- return [{ tag: "div[data-structured-content-block]" }];
27020
+ return [
27021
+ {
27022
+ tag: `div[data-type="${this.name}"]`
27023
+ }
27024
+ ];
26495
27025
  },
26496
27026
  renderDOM({ htmlAttributes }) {
26497
27027
  return [
26498
27028
  "div",
26499
- Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, {
26500
- "data-structured-content-block": ""
26501
- }),
27029
+ Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, { "data-type": this.name }),
26502
27030
  0
26503
27031
  ];
26504
- },
26505
- addNodeView() {
26506
- return (props) => {
26507
- return new StructuredContentBlockView({ ...props });
26508
- };
26509
27032
  }
26510
27033
  });
26511
- class DocumentSectionView {
26512
- constructor(node, getPos, decorations, editor) {
26513
- __privateAdd(this, _DocumentSectionView_instances);
26514
- this.node = node;
26515
- this.editor = editor;
26516
- this.decorations = decorations;
26517
- this.view = editor.view;
26518
- this.getPos = getPos;
26519
- __privateMethod(this, _DocumentSectionView_instances, init_fn3).call(this);
26520
- }
26521
- }
26522
- _DocumentSectionView_instances = new WeakSet();
26523
- init_fn3 = function() {
26524
- const { attrs } = this.node;
26525
- const { id, title, description } = attrs;
26526
- this.dom = document.createElement("div");
26527
- this.dom.className = "sd-document-section-block";
26528
- this.dom.setAttribute("data-id", id);
26529
- this.dom.setAttribute("data-title", title);
26530
- this.dom.setAttribute("data-description", description);
26531
- this.dom.setAttribute("aria-label", "Document section");
26532
- __privateMethod(this, _DocumentSectionView_instances, addToolTip_fn).call(this);
26533
- this.contentDOM = document.createElement("div");
26534
- this.contentDOM.className = "sd-document-section-block-content";
26535
- this.contentDOM.setAttribute("contenteditable", "true");
26536
- this.dom.appendChild(this.contentDOM);
26537
- };
26538
- addToolTip_fn = function() {
26539
- const { title } = this.node.attrs;
26540
- this.infoDiv = document.createElement("div");
26541
- this.infoDiv.className = "sd-document-section-block-info";
26542
- const textSpan = document.createElement("span");
26543
- textSpan.textContent = title || "Document section";
26544
- this.infoDiv.appendChild(textSpan);
26545
- this.infoDiv.setAttribute("contenteditable", "false");
26546
- this.dom.appendChild(this.infoDiv);
26547
- };
26548
- const getAllSections = (editor) => {
26549
- if (!editor) return [];
26550
- const type = editor.schema.nodes.documentSection;
26551
- if (!type) return [];
26552
- const sections = [];
26553
- const { state } = editor;
26554
- state.doc.descendants((node, pos) => {
26555
- if (node.type.name === type.name) {
26556
- sections.push({ node, pos });
26557
- }
26558
- });
26559
- return sections;
26560
- };
26561
- const exportSectionsToHTML = (editor) => {
26562
- const sections = getAllSections(editor);
26563
- const processedSections = /* @__PURE__ */ new Set();
26564
- const result = [];
26565
- sections.forEach(({ node }) => {
26566
- const { attrs } = node;
26567
- const { id, title, description } = attrs;
26568
- if (processedSections.has(id)) return;
26569
- processedSections.add(id);
26570
- const html = getHTMLFromNode(node, editor);
26571
- result.push({
26572
- id,
26573
- title,
26574
- description,
26575
- html
26576
- });
26577
- });
26578
- return result;
26579
- };
26580
- const getHTMLFromNode = (node, editor) => {
26581
- const tempDocument = document.implementation.createHTMLDocument();
26582
- const container = tempDocument.createElement("div");
26583
- const fragment = DOMSerializer.fromSchema(editor.schema).serializeFragment(node.content);
26584
- container.appendChild(fragment);
26585
- let html = container.innerHTML;
26586
- return html;
26587
- };
26588
- const exportSectionsToJSON = (editor) => {
26589
- const sections = getAllSections(editor);
26590
- const processedSections = /* @__PURE__ */ new Set();
26591
- const result = [];
26592
- sections.forEach(({ node }) => {
26593
- const { attrs } = node;
26594
- const { id, title, description } = attrs;
26595
- if (processedSections.has(id)) return;
26596
- processedSections.add(id);
26597
- result.push({
26598
- id,
26599
- title,
26600
- description,
26601
- content: node.toJSON()
26602
- });
26603
- });
26604
- return result;
26605
- };
26606
- const getLinkedSectionEditor = (id, options, editor) => {
26607
- const sections = getAllSections(editor);
26608
- const section = sections.find((s) => s.node.attrs.id === id);
26609
- if (!section) return null;
26610
- const child = editor.createChildEditor({
26611
- ...options,
26612
- onUpdate: ({ editor: childEditor, transaction }) => {
26613
- const isFromtLinkedParent = transaction.getMeta("fromLinkedParent");
26614
- if (isFromtLinkedParent) return;
26615
- const updatedContent = childEditor.state.doc.content;
26616
- const sectionNode = getAllSections(editor)?.find((s) => s.node.attrs.id === id);
26617
- if (!sectionNode) return;
26618
- const { pos, node } = sectionNode;
26619
- const newNode = node.type.create(node.attrs, updatedContent, node.marks);
26620
- const tr = editor.state.tr.replaceWith(pos, pos + node.nodeSize, newNode);
26621
- tr.setMeta("fromLinkedChild", true);
26622
- editor.view.dispatch(tr);
26623
- }
26624
- });
26625
- editor.on("update", ({ transaction }) => {
26626
- const isFromLinkedChild = transaction.getMeta("fromLinkedChild");
26627
- if (isFromLinkedChild) return;
26628
- const sectionNode = getAllSections(editor)?.find((s) => s.node.attrs.id === id);
26629
- if (!sectionNode) return;
26630
- const sectionContent = sectionNode.node.content;
26631
- const json = {
26632
- type: "doc",
26633
- content: sectionContent.content.map((node) => node.toJSON())
26634
- };
26635
- const childTr = child.state.tr;
26636
- childTr.setMeta("fromLinkedParent", true);
26637
- childTr.replaceWith(0, child.state.doc.content.size, child.schema.nodeFromJSON(json));
26638
- child.view.dispatch(childTr);
26639
- });
26640
- return child;
26641
- };
26642
- const SectionHelpers = {
26643
- getAllSections,
26644
- exportSectionsToHTML,
26645
- exportSectionsToJSON,
26646
- getLinkedSectionEditor
26647
- };
26648
- const DocumentSection = Node$1.create({
26649
- name: "documentSection",
27034
+ const ShapeTextbox = Node$1.create({
27035
+ name: "shapeTextbox",
26650
27036
  group: "block",
26651
- content: "block*",
26652
- atom: true,
27037
+ content: "paragraph* block*",
26653
27038
  isolating: true,
26654
27039
  addOptions() {
26655
27040
  return {
26656
27041
  htmlAttributes: {
26657
- class: "sd-document-section-block",
26658
- "aria-label": "Structured content block"
27042
+ class: "sd-editor-shape-textbox",
27043
+ "aria-label": "Shape textbox node"
26659
27044
  }
26660
27045
  };
26661
27046
  },
26662
- parseDOM() {
26663
- return [
26664
- {
26665
- tag: "div.sd-document-section-block",
26666
- priority: 60
26667
- }
26668
- ];
26669
- },
26670
- renderDOM({ htmlAttributes }) {
26671
- return ["div", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes), 0];
26672
- },
26673
27047
  addAttributes() {
26674
27048
  return {
26675
- id: {},
26676
27049
  sdBlockId: {
26677
27050
  default: null,
26678
27051
  keepOnSplit: false,
@@ -26681,212 +27054,131 @@ const DocumentSection = Node$1.create({
26681
27054
  return attrs.sdBlockId ? { "data-sd-block-id": attrs.sdBlockId } : {};
26682
27055
  }
26683
27056
  },
26684
- title: {},
26685
- description: {},
26686
- sectionType: {},
26687
- isLocked: { default: false }
27057
+ attributes: {
27058
+ rendered: false
27059
+ }
26688
27060
  };
26689
27061
  },
26690
- addNodeView() {
26691
- return ({ node, editor, getPos, decorations }) => {
26692
- return new DocumentSectionView(node, getPos, decorations, editor);
27062
+ parseDOM() {
27063
+ return [
27064
+ {
27065
+ tag: `div[data-type="${this.name}"]`
27066
+ }
27067
+ ];
27068
+ },
27069
+ renderDOM({ htmlAttributes }) {
27070
+ return [
27071
+ "div",
27072
+ Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, { "data-type": this.name }),
27073
+ 0
27074
+ ];
27075
+ }
27076
+ });
27077
+ const ContentBlock = Node$1.create({
27078
+ name: "contentBlock",
27079
+ group: "inline",
27080
+ content: "",
27081
+ isolating: true,
27082
+ atom: true,
27083
+ inline: true,
27084
+ addOptions() {
27085
+ return {
27086
+ htmlAttributes: {
27087
+ contenteditable: false
27088
+ }
26693
27089
  };
26694
27090
  },
26695
- addCommands() {
27091
+ addAttributes() {
26696
27092
  return {
26697
- /**
26698
- * Create a lockable content section
26699
- * @category Command
26700
- * @param {SectionCreate} [options={}] - Section configuration
26701
- * @example
26702
- * editor.commands.createDocumentSection({
26703
- * id: 1,
26704
- * title: 'Terms & Conditions',
26705
- * isLocked: true,
26706
- * html: '<p>Legal content...</p>'
26707
- * })
26708
- */
26709
- createDocumentSection: (options = {}) => ({ tr, state, dispatch, editor }) => {
26710
- const { selection } = state;
26711
- let { from: from2, to } = selection;
26712
- let content = selection.content().content;
26713
- const { html: optionsHTML, json: optionsJSON } = options;
26714
- if (optionsHTML) {
26715
- const html = htmlHandler(optionsHTML, this.editor);
26716
- const doc2 = DOMParser$1.fromSchema(this.editor.schema).parse(html);
26717
- content = doc2.content;
26718
- }
26719
- if (optionsJSON) {
26720
- content = this.editor.schema.nodeFromJSON(optionsJSON);
26721
- }
26722
- if (!content?.content?.length) {
26723
- content = this.editor.schema.nodeFromJSON({ type: "paragraph", content: [] });
26724
- }
26725
- if (!options.id) {
26726
- const allSections = SectionHelpers.getAllSections(editor);
26727
- options.id = allSections.length + 1;
26728
- }
26729
- if (!options.title) {
26730
- options.title = "Document section";
26731
- }
26732
- const node = this.type.createAndFill(options, content);
26733
- if (!node) return false;
26734
- const isAlreadyInSdtBlock = findParentNode((node2) => node2.type.name === "documentSection")(selection);
26735
- if (isAlreadyInSdtBlock && isAlreadyInSdtBlock.node) {
26736
- const insertPos2 = isAlreadyInSdtBlock.pos + isAlreadyInSdtBlock.node.nodeSize;
26737
- from2 = insertPos2;
26738
- to = insertPos2;
26739
- }
26740
- tr.replaceRangeWith(from2, to, node);
26741
- const nodeEnd = from2 + node.nodeSize;
26742
- let shouldInsertParagraph = true;
26743
- let insertPos = nodeEnd;
26744
- if (nodeEnd >= tr.doc.content.size) {
26745
- insertPos = tr.doc.content.size;
26746
- if (insertPos > 0) {
26747
- const $endPos = tr.doc.resolve(insertPos);
26748
- if ($endPos.nodeBefore && $endPos.nodeBefore.type.name === "paragraph") {
26749
- shouldInsertParagraph = false;
26750
- }
26751
- }
26752
- }
26753
- if (shouldInsertParagraph) {
26754
- const emptyParagraph = tr.doc.type.schema.nodes.paragraph.create();
26755
- tr.insert(insertPos, emptyParagraph);
26756
- }
26757
- if (dispatch) {
26758
- tr.setMeta("documentSection", { action: "create" });
26759
- dispatch(tr);
26760
- setTimeout(() => {
26761
- try {
26762
- const currentState = editor.state;
26763
- const docSize = currentState.doc.content.size;
26764
- let targetPos = from2 + node.nodeSize;
26765
- if (shouldInsertParagraph) {
26766
- targetPos += 1;
26767
- }
26768
- targetPos = Math.min(targetPos, docSize);
26769
- if (targetPos < docSize && targetPos > 0) {
26770
- const newSelection = Selection.near(currentState.doc.resolve(targetPos));
26771
- const newTr = currentState.tr.setSelection(newSelection);
26772
- editor.view.dispatch(newTr);
26773
- }
26774
- } catch (e) {
26775
- console.warn("Could not set delayed selection:", e);
26776
- }
26777
- }, 0);
27093
+ horizontalRule: {
27094
+ default: false,
27095
+ renderDOM: ({ horizontalRule }) => {
27096
+ if (!horizontalRule) return {};
27097
+ return { "data-horizontal-rule": "true" };
26778
27098
  }
26779
- return true;
26780
27099
  },
26781
- /**
26782
- * Remove section wrapper at cursor, preserving its content
26783
- * @category Command
26784
- * @example
26785
- * editor.commands.removeSectionAtSelection()
26786
- * @note Content stays in document, only section wrapper is removed
26787
- */
26788
- removeSectionAtSelection: () => ({ tr, dispatch }) => {
26789
- const sdtNode = findParentNode((node2) => node2.type.name === "documentSection")(tr.selection);
26790
- if (!sdtNode) return false;
26791
- const { node, pos } = sdtNode;
26792
- const nodeStart = pos;
26793
- const nodeEnd = nodeStart + node.nodeSize;
26794
- const contentToPreserve = node.content;
26795
- tr.delete(nodeStart, nodeEnd);
26796
- if (contentToPreserve.size > 0) {
26797
- tr.insert(nodeStart, contentToPreserve);
26798
- }
26799
- const newPos = Math.min(nodeStart, tr.doc.content.size);
26800
- tr.setSelection(Selection.near(tr.doc.resolve(newPos)));
26801
- if (dispatch) {
26802
- tr.setMeta("documentSection", { action: "delete" });
26803
- dispatch(tr);
27100
+ size: {
27101
+ default: null,
27102
+ renderDOM: ({ size }) => {
27103
+ if (!size) return {};
27104
+ let style = "";
27105
+ if (size.top) style += `top: ${size.top}px; `;
27106
+ if (size.left) style += `left: ${size.left}px; `;
27107
+ if (size.width) style += `width: ${size.width.toString().endsWith("%") ? size.width : `${size.width}px`}; `;
27108
+ if (size.height)
27109
+ style += `height: ${size.height.toString().endsWith("%") ? size.height : `${size.height}px`}; `;
27110
+ return { style };
26804
27111
  }
26805
- return true;
26806
27112
  },
26807
- /**
26808
- * Delete section and all its content
26809
- * @category Command
26810
- * @param {number} id - Section to delete
26811
- * @example
26812
- * editor.commands.removeSectionById(123)
26813
- */
26814
- removeSectionById: (id) => ({ tr, dispatch }) => {
26815
- const sections = SectionHelpers.getAllSections(this.editor);
26816
- const sectionToRemove = sections.find(({ node: node2 }) => node2.attrs.id === id);
26817
- if (!sectionToRemove) return false;
26818
- const { pos, node } = sectionToRemove;
26819
- const nodeStart = pos;
26820
- const nodeEnd = nodeStart + node.nodeSize;
26821
- tr.delete(nodeStart, nodeEnd);
26822
- if (dispatch) {
26823
- tr.setMeta("documentSection", { action: "delete", id });
26824
- dispatch(tr);
27113
+ background: {
27114
+ default: null,
27115
+ renderDOM: (attrs) => {
27116
+ if (!attrs.background) return {};
27117
+ return {
27118
+ style: `background-color: ${attrs.background}`
27119
+ };
26825
27120
  }
26826
- return true;
26827
27121
  },
27122
+ drawingContent: {
27123
+ rendered: false
27124
+ },
27125
+ attributes: {
27126
+ rendered: false
27127
+ }
27128
+ };
27129
+ },
27130
+ parseDOM() {
27131
+ return [
27132
+ {
27133
+ tag: `div[data-type="${this.name}"]`
27134
+ }
27135
+ ];
27136
+ },
27137
+ renderDOM({ htmlAttributes }) {
27138
+ return ["div", Attribute.mergeAttributes(this.options.htmlAttributes, htmlAttributes, { "data-type": this.name })];
27139
+ },
27140
+ addCommands() {
27141
+ return {
26828
27142
  /**
26829
- * Lock section against edits
27143
+ * Insert a horizontal rule
26830
27144
  * @category Command
26831
- * @param {number} id - Section to lock
26832
27145
  * @example
26833
- * editor.commands.lockSectionById(123)
27146
+ * editor.commands.insertHorizontalRule()
27147
+ * @note Creates a visual separator between content sections
26834
27148
  */
26835
- lockSectionById: (id) => ({ tr, dispatch }) => {
26836
- const sections = SectionHelpers.getAllSections(this.editor);
26837
- const sectionToLock = sections.find(({ node }) => node.attrs.id === id);
26838
- if (!sectionToLock) return false;
26839
- tr.setNodeMarkup(sectionToLock.pos, null, { ...sectionToLock.node.attrs, isLocked: true });
26840
- if (dispatch) {
26841
- tr.setMeta("documentSection", { action: "lock", id });
26842
- dispatch(tr);
26843
- }
26844
- return true;
27149
+ insertHorizontalRule: () => ({ commands: commands2 }) => {
27150
+ return commands2.insertContent({
27151
+ type: this.name,
27152
+ attrs: {
27153
+ horizontalRule: true,
27154
+ size: { width: "100%", height: 2 },
27155
+ background: "#e5e7eb"
27156
+ }
27157
+ });
26845
27158
  },
26846
27159
  /**
26847
- * Modify section attributes or content
27160
+ * Insert a content block
26848
27161
  * @category Command
26849
- * @param {SectionUpdate} options - Changes to apply
27162
+ * @param {ContentBlockConfig} config - Block configuration
26850
27163
  * @example
26851
- * editor.commands.updateSectionById({ id: 123, attrs: { isLocked: false } })
26852
- * editor.commands.updateSectionById({ id: 123, html: '<p>New content</p>' })
26853
- * editor.commands.updateSectionById({
26854
- * id: 123,
26855
- * html: '<p>Updated</p>',
26856
- * attrs: { title: 'New Title' }
27164
+ * // Insert a spacer block
27165
+ * editor.commands.insertContentBlock({ size: { height: 20 } })
27166
+ *
27167
+ * @example
27168
+ * // Insert a colored divider
27169
+ * editor.commands.insertContentBlock({
27170
+ * size: { width: '50%', height: 3 },
27171
+ * background: '#3b82f6'
26857
27172
  * })
27173
+ * @note Used for spacing, dividers, and special inline content
26858
27174
  */
26859
- updateSectionById: ({ id, html, json, attrs }) => ({ tr, dispatch, editor }) => {
26860
- const sections = SectionHelpers.getAllSections(editor || this.editor);
26861
- const sectionToUpdate = sections.find(({ node: node2 }) => node2.attrs.id === id);
26862
- if (!sectionToUpdate) return false;
26863
- const { pos, node } = sectionToUpdate;
26864
- let newContent = null;
26865
- if (html) {
26866
- const htmlDoc = htmlHandler(html, editor || this.editor);
26867
- const doc2 = DOMParser$1.fromSchema((editor || this.editor).schema).parse(htmlDoc);
26868
- newContent = doc2.content;
26869
- }
26870
- if (json) {
26871
- newContent = (editor || this.editor).schema.nodeFromJSON(json);
26872
- }
26873
- if (!newContent) {
26874
- newContent = node.content;
26875
- }
26876
- const updatedNode = node.type.create({ ...node.attrs, ...attrs }, newContent, node.marks);
26877
- tr.replaceWith(pos, pos + node.nodeSize, updatedNode);
26878
- if (dispatch) {
26879
- tr.setMeta("documentSection", { action: "update", id, attrs });
26880
- dispatch(tr);
26881
- }
26882
- return true;
27175
+ insertContentBlock: (config) => ({ commands: commands2 }) => {
27176
+ return commands2.insertContent({
27177
+ type: this.name,
27178
+ attrs: config
27179
+ });
26883
27180
  }
26884
27181
  };
26885
- },
26886
- addHelpers() {
26887
- return {
26888
- ...SectionHelpers
26889
- };
26890
27182
  }
26891
27183
  });
26892
27184
  const { findChildren } = helpers;
@@ -33454,7 +33746,8 @@ const nodeResizer = (nodeNames = ["image"], editor) => {
33454
33746
  const prevSelection = prevState.selection;
33455
33747
  if (selection.from !== prevSelection.from || selection.to !== prevSelection.to) {
33456
33748
  setTimeout(() => {
33457
- const selectedResizableWrapper = document.querySelector(".sd-editor-resizable-wrapper");
33749
+ const searchRoot = editorView?.dom;
33750
+ const selectedResizableWrapper = searchRoot?.querySelector(".sd-editor-resizable-wrapper");
33458
33751
  if (selectedResizableWrapper) {
33459
33752
  showResizeHandles(view2, selectedResizableWrapper);
33460
33753
  } else {
@@ -33735,6 +34028,7 @@ const getStarterExtensions = () => {
33735
34028
  Search,
33736
34029
  StructuredContent,
33737
34030
  StructuredContentBlock,
34031
+ StructuredContentCommands,
33738
34032
  DocumentSection,
33739
34033
  NodeResizer,
33740
34034
  CustomSelection,
@@ -33768,6 +34062,7 @@ export {
33768
34062
  SectionHelpers as o,
33769
34063
  getAllowedImageDimensions as p,
33770
34064
  replaceSelectionWithImagePlaceholder as r,
34065
+ shouldBypassContextMenu as s,
33771
34066
  useHighContrastMode as u,
33772
34067
  yUndoPluginKey as y
33773
34068
  };