@phuong-tran-redoc/document-engine-core 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,6 +112,11 @@ const saved = toCkHtml(editor.getHTML(), { wrapperClass: wrapperClass ?? false }
112
112
  verifyCkRoundTrip(storedHtml, saved); // before editing: is the document reproduced exactly?
113
113
  ```
114
114
 
115
+ CKEditor restricted-editing exceptions (`<span class="restricted-editing-exception">`) load as editable
116
+ regions when `EditableRegion` and `RestrictedEditing` are registered, and are saved back in that markup. In
117
+ restricted mode, set content with `editor.chain().setMeta('restrictedEditing', { allow: true }).setContent(html).run()`
118
+ — otherwise the restriction blocks the change once the document is not empty.
119
+
115
120
  On a backend (no DOM) pass a parser. `createCkDomParser()` uses happy-dom, which `@tiptap/html` already installs:
116
121
 
117
122
  ```typescript
package/index.esm.js CHANGED
@@ -61,8 +61,8 @@ function absoluteLengthToPx(value) {
61
61
  * `data-ck` attribute that the {@link CkCompat} extension carries through editing.
62
62
  * - {@link toCkHtml} runs on `editor.getHTML()`. It rebuilds the CKEditor markup from those
63
63
  * records: attribute order, `prop:value;` style formatting, `<figure class="table">`,
64
- * `<colgroup>`, bare text in single-paragraph cells, `<i>`, page breaks, dynamic fields and
65
- * the wrapper. Anything the user changed is taken from the editor; anything the editor does
64
+ * `<colgroup>`, bare text in single-paragraph cells, `<i>`, page breaks, dynamic fields,
65
+ * restricted-editing exceptions and the wrapper. Anything the user changed is taken from the editor; anything the editor does
66
66
  * not model is kept verbatim.
67
67
  *
68
68
  * Both functions need a `DOMParser`. In the browser the global one is used. In Node (no global DOM)
@@ -87,6 +87,7 @@ const CK_UNSUPPORTED_CONTENT = {
87
87
  dynamicImage: '.redr-dynamic-image',
88
88
  dealTable: '.redr-deal-table',
89
89
  editorColumn: '.redr-editor-column',
90
+ /** @deprecated Supported since 0.1.8 (loads as an editable region); no longer reported. */
90
91
  restrictedEditingException: '.restricted-editing-exception',
91
92
  image: 'img, figure.image',
92
93
  };
@@ -122,6 +123,8 @@ const DEFAULT_CELL_VERTICAL_ALIGN = 'middle';
122
123
  const NEUTRAL_DECLARATIONS = new Set(['margin-left:0px', 'margin-left:0', 'margin-left:nullpx']);
123
124
  const DYNAMIC_FIELD_BASE_CLASSES = ['red-dynamic-field', 'inline-field', 'redr-handlebar-field'];
124
125
  const DYNAMIC_FIELD_HAS_VALUE_CLASS = 'red-dynamic-field--has-value';
126
+ /** Markup of CKEditor's restricted-editing exception (an editable region). */
127
+ const EDITABLE_REGION_CK_CLASS = 'restricted-editing-exception';
125
128
  const PAGE_BREAK_HTML = '<div class="page-break" style="page-break-after:always;"><span style="display:none;">&nbsp;</span></div>';
126
129
  function parse(html, domParser) {
127
130
  if (!domParser && typeof DOMParser === 'undefined') {
@@ -257,7 +260,7 @@ function fromCkHtml(html, options = {}) {
257
260
  origin.c = Array.from(cols).map(attrsOf);
258
261
  writeOrigin(table, origin);
259
262
  });
260
- const unsupported = Object.keys(CK_UNSUPPORTED_CONTENT).filter((key) => body.querySelector(CK_UNSUPPORTED_CONTENT[key]));
263
+ const unsupported = Object.keys(CK_UNSUPPORTED_CONTENT).filter((key) => key !== 'restrictedEditingException' && body.querySelector(CK_UNSUPPORTED_CONTENT[key]));
261
264
  return { html: body.innerHTML, wrapperClass, unsupported };
262
265
  }
263
266
  /**
@@ -480,6 +483,33 @@ function exportDynamicFields(body) {
480
483
  el.replaceWith(span);
481
484
  });
482
485
  }
486
+ /**
487
+ * Write editable regions as CKEditor restricted-editing exceptions. The editor fills a region it
488
+ * creates empty with a zero-width space (and refills one emptied in restricted mode with a space);
489
+ * a region left without text is written as `&nbsp;` so it stays in the document (CKEditor's
490
+ * empty-content convention). The exception class is always kept, whatever the record says: it is
491
+ * what identifies the region in stored HTML.
492
+ */
493
+ function exportEditableRegions(body) {
494
+ body.querySelectorAll('span[data-editable-region]').forEach((el) => {
495
+ var _a, _b, _c;
496
+ const span = el.ownerDocument.createElement('span');
497
+ setAttributesInOrder(span, (_b = (_a = readOrigin(el)) === null || _a === void 0 ? void 0 : _a.a) !== null && _b !== void 0 ? _b : [['class', EDITABLE_REGION_CK_CLASS]]);
498
+ if (!span.classList.contains(EDITABLE_REGION_CK_CLASS))
499
+ span.classList.add(EDITABLE_REGION_CK_CLASS);
500
+ span.append(...Array.from(el.childNodes));
501
+ stripZeroWidthSpaces(span);
502
+ if (!((_c = span.textContent) === null || _c === void 0 ? void 0 : _c.trim()))
503
+ span.textContent = '\u00a0';
504
+ el.replaceWith(span);
505
+ });
506
+ }
507
+ function stripZeroWidthSpaces(node) {
508
+ var _a;
509
+ if (node.nodeType === 3)
510
+ node.nodeValue = ((_a = node.nodeValue) !== null && _a !== void 0 ? _a : '').replace(/\u200b/g, '');
511
+ node.childNodes.forEach(stripZeroWidthSpaces);
512
+ }
483
513
  /**
484
514
  * Tiptap's `TrailingNode` appends an empty paragraph whenever a document ends in a table or other
485
515
  * non-paragraph block. CKEditor allows that ending, so drop the paragraph unless the source had it.
@@ -555,12 +585,15 @@ function toCkHtml(html, options = {}) {
555
585
  const probe = body.ownerDocument.createElement('span');
556
586
  exportDynamicFields(body);
557
587
  exportPageBreaks(body);
588
+ exportEditableRegions(body);
558
589
  // Reconcile plain elements before tables restructure cells (so `<p>` attributes are final).
559
590
  body.querySelectorAll('*').forEach((el) => {
560
591
  var _a, _b;
561
592
  const tag = el.tagName.toLowerCase();
562
593
  if (tag === 'col' || el.closest('div.page-break') || el.matches('span[id^="red-dynamic-field"]'))
563
594
  return;
595
+ if (el.matches(`span.${EDITABLE_REGION_CK_CLASS}`))
596
+ return;
564
597
  reconcileElement(el, (_b = (_a = readOrigin(el)) === null || _a === void 0 ? void 0 : _a.a) !== null && _b !== void 0 ? _b : null, probe);
565
598
  });
566
599
  dropTrailingNode(body);
@@ -605,6 +638,7 @@ const CK_COMPAT_TYPES = [
605
638
  'link',
606
639
  'dynamicField',
607
640
  'pageBreak',
641
+ 'editableRegion',
608
642
  ];
609
643
  const ckOrigin = (fallback) => ({
610
644
  default: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phuong-tran-redoc/document-engine-core",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Realestatedoc (Redoc)",
@@ -9,8 +9,8 @@
9
9
  * `data-ck` attribute that the {@link CkCompat} extension carries through editing.
10
10
  * - {@link toCkHtml} runs on `editor.getHTML()`. It rebuilds the CKEditor markup from those
11
11
  * records: attribute order, `prop:value;` style formatting, `<figure class="table">`,
12
- * `<colgroup>`, bare text in single-paragraph cells, `<i>`, page breaks, dynamic fields and
13
- * the wrapper. Anything the user changed is taken from the editor; anything the editor does
12
+ * `<colgroup>`, bare text in single-paragraph cells, `<i>`, page breaks, dynamic fields,
13
+ * restricted-editing exceptions and the wrapper. Anything the user changed is taken from the editor; anything the editor does
14
14
  * not model is kept verbatim.
15
15
  *
16
16
  * Both functions need a `DOMParser`. In the browser the global one is used. In Node (no global DOM)
@@ -61,6 +61,7 @@ export declare const CK_UNSUPPORTED_CONTENT: {
61
61
  readonly dynamicImage: ".redr-dynamic-image";
62
62
  readonly dealTable: ".redr-deal-table";
63
63
  readonly editorColumn: ".redr-editor-column";
64
+ /** @deprecated Supported since 0.1.8 (loads as an editable region); no longer reported. */
64
65
  readonly restrictedEditingException: ".restricted-editing-exception";
65
66
  readonly image: "img, figure.image";
66
67
  };