@kedataindo/docflow-core 0.0.49 → 0.0.50

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/dist/index.cjs CHANGED
@@ -49,11 +49,13 @@ __export(index_exports, {
49
49
  createEditor: () => createEditor,
50
50
  createPerformanceMonitor: () => createPerformanceMonitor,
51
51
  definePlugin: () => definePlugin,
52
+ detectClipboardSource: () => detectClipboardSource,
52
53
  findMatches: () => findMatches,
53
54
  getSearchState: () => getSearchState,
54
55
  httpKeyStorage: () => httpKeyStorage,
55
56
  localStorageKeyStorage: () => localStorageKeyStorage,
56
57
  memoryKeyStorage: () => memoryKeyStorage,
58
+ normalizeClipboardHTML: () => normalizeClipboardHTML,
57
59
  openaiCompatibleProvider: () => openaiCompatibleProvider,
58
60
  replaceAll: () => replaceAll,
59
61
  replaceCurrent: () => replaceCurrent,
@@ -460,13 +462,13 @@ function replaceAll(editor, replacement) {
460
462
  return s.matches.length;
461
463
  }
462
464
 
463
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
465
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
464
466
  var import_core4 = require("@tiptap/core");
465
467
  var import_state3 = require("@tiptap/pm/state");
466
468
  var import_transform = require("@tiptap/pm/transform");
467
469
  var import_view3 = require("@tiptap/pm/view");
468
470
 
469
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/utils.js
471
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/utils.js
470
472
  var updateCssVariables = (targetNode, config) => {
471
473
  const cssVariables = {
472
474
  "rm-page-height": `${config.pageHeight}px`,
@@ -640,7 +642,7 @@ var footerClickEvent = (pageNumber, onFooterClick) => {
640
642
  };
641
643
  };
642
644
 
643
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
645
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
644
646
  var page_count_meta_key = "PAGE_COUNT_META_KEY";
645
647
  var key = new import_state3.PluginKey("brDecoration");
646
648
  function buildDecorations(doc) {
@@ -807,11 +809,6 @@ var PaginationPlus = import_core4.Extension.create({
807
809
  width: 100%;
808
810
  display: contents;
809
811
  }
810
- .rm-with-pagination table tbody{
811
- display: table;
812
- max-height: 300px;
813
- overflow-y: auto;
814
- }
815
812
  .rm-with-pagination table tbody > tr{
816
813
  display: table-row !important;
817
814
  }
@@ -1134,6 +1131,7 @@ var getExistingPageCount = (view) => {
1134
1131
  }
1135
1132
  return 0;
1136
1133
  };
1134
+ var pageGapTracker = /* @__PURE__ */ new WeakMap();
1137
1135
  var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0) => {
1138
1136
  var _a;
1139
1137
  const editorDom = view.dom;
@@ -1151,8 +1149,36 @@ var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0)
1151
1149
  const lastPageGap = lastElementRect.bottom - lastPageBreakRect.bottom;
1152
1150
  if (lastPageGap > 0) {
1153
1151
  const addPage = Math.ceil(lastPageGap / pageContentAreaHeight);
1152
+ if (lastElementRect.height > pageContentAreaHeight) {
1153
+ const isSplittableTable = (lastElementOfEditor === null || lastElementOfEditor === void 0 ? void 0 : lastElementOfEditor.tagName) === "TABLE" && lastElementOfEditor.hasAttribute("data-tps-splittable");
1154
+ if (!isSplittableTable) {
1155
+ return currentPageCount;
1156
+ }
1157
+ }
1158
+ const prev = pageGapTracker.get(editorDom);
1159
+ if (prev && prev.pages < currentPageCount && lastPageGap > prev.gap - 2) {
1160
+ return currentPageCount;
1161
+ }
1162
+ pageGapTracker.set(editorDom, { gap: lastPageGap, pages: currentPageCount });
1163
+ let totalEditorContentHeight = 0;
1164
+ for (const child of editorDom.children) {
1165
+ if (!(child instanceof HTMLElement)) continue;
1166
+ if (child.hasAttribute("data-rm-pagination")) continue;
1167
+ if (child.classList.contains("rm-page-break")) continue;
1168
+ totalEditorContentHeight += child.getBoundingClientRect().height;
1169
+ }
1170
+ const maxPagesByContent = Math.ceil(totalEditorContentHeight / pageContentAreaHeight);
1171
+ const contentCap = Math.max(currentPageCount, maxPagesByContent + 1);
1172
+ if (currentPageCount + addPage > contentCap) {
1173
+ return contentCap;
1174
+ }
1175
+ const MAX_PAGES = 1e3;
1176
+ if (currentPageCount + addPage > MAX_PAGES) {
1177
+ return currentPageCount;
1178
+ }
1154
1179
  return currentPageCount + addPage;
1155
1180
  } else {
1181
+ pageGapTracker.delete(editorDom);
1156
1182
  const allBreaksAfterLastElement = Array.from(paginationElement.querySelectorAll(".breaker"));
1157
1183
  const allBreaksAfterLastElementRect = allBreaksAfterLastElement.filter((element) => element.getBoundingClientRect().top > lastElementRect.bottom);
1158
1184
  const removePage = allBreaksAfterLastElementRect.length;
@@ -1279,7 +1305,7 @@ function createDecoration(pageOptions, headerHeightMap, footerHeightMap) {
1279
1305
  return [pageWidget, firstHeaderWidget];
1280
1306
  }
1281
1307
 
1282
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/constants.js
1308
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/constants.js
1283
1309
  var A4_PAGE_SIZE = getPageSize(1123, 794, 95, 95, 76, 76);
1284
1310
  var A3_PAGE_SIZE = getPageSize(1591, 1123, 95, 95, 76, 76);
1285
1311
  var A5_PAGE_SIZE = getPageSize(794, 419, 76, 76, 57, 57);
@@ -1477,41 +1503,150 @@ function createPerformanceMonitor(options) {
1477
1503
  return monitor;
1478
1504
  }
1479
1505
 
1480
- // src/Editor.ts
1481
- function sanitizePastedHTML(html) {
1482
- let cleaned = html.replace(/<meta[^>]*>/gi, "");
1483
- cleaned = cleaned.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
1484
- cleaned = cleaned.replace(/<!--[\s\S]*?-->/g, "");
1485
- cleaned = cleaned.replace(/<link[^>]*>/gi, "");
1486
- cleaned = cleaned.replace(/<base[^>]*>/gi, "");
1487
- cleaned = cleaned.replace(/<title[^>]*>[\s\S]*?<\/title>/gi, "");
1488
- {
1489
- for (let pass = 0; pass < 10; pass++) {
1490
- const before = cleaned;
1491
- cleaned = cleaned.replace(
1492
- /<span\s([^>]*style\s*=\s*["'][^"']*(?:background-color|background)[^"']*["'][^>]*)>([^<]*(?:<(?!\/?span)[^>]*>[^<]*)*)<\/span>/gi,
1493
- (_full, attrs, content) => {
1494
- if (!/background(?:-color)?\s*:/.test(attrs)) return _full;
1495
- const colorMatch = attrs.match(/(?<!background-)color\s*:\s*([^;"]+)/i);
1496
- const colorVal = colorMatch ? colorMatch[1].trim() : null;
1497
- const markAttrs = colorVal ? attrs.replace(/(?<!background-)color\s*:\s*[^;"]+;?\s*/gi, "").trim() : attrs;
1498
- if (colorVal) {
1499
- return `<mark ${markAttrs}><span style="color: ${colorVal};">${content}</span></mark>`;
1500
- }
1501
- return `<mark ${markAttrs}>${content}</mark>`;
1502
- }
1503
- );
1504
- if (cleaned === before) break;
1506
+ // src/pasteNormalization.ts
1507
+ var KEEP_STYLE_PROPS = /* @__PURE__ */ new Set(["color", "background-color"]);
1508
+ var REMOVE_TAGS = /* @__PURE__ */ new Set([
1509
+ "meta",
1510
+ "style",
1511
+ "link",
1512
+ "base",
1513
+ "title",
1514
+ "script",
1515
+ "iframe",
1516
+ "object",
1517
+ "embed",
1518
+ "form",
1519
+ "input",
1520
+ "button",
1521
+ "select",
1522
+ "textarea",
1523
+ "noscript",
1524
+ // Word Office namespace dump paragraph
1525
+ "o:p"
1526
+ ]);
1527
+ var KEEP_ATTRS = {
1528
+ a: /* @__PURE__ */ new Set(["href", "style"]),
1529
+ img: /* @__PURE__ */ new Set(["src", "alt", "style"]),
1530
+ td: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1531
+ th: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1532
+ col: /* @__PURE__ */ new Set(["span"]),
1533
+ "*": /* @__PURE__ */ new Set(["style"])
1534
+ };
1535
+ function detectClipboardSource(html) {
1536
+ if (/id\s*=\s*["']docs-internal/i.test(html)) return "google-docs";
1537
+ if (/<o:p[\s>]|xmlns(:w|:o)?=|class\s*=\s*["']?Mso|mso-/i.test(html)) return "word";
1538
+ return "generic";
1539
+ }
1540
+ function parseStyleDeclarations(style) {
1541
+ const map = /* @__PURE__ */ new Map();
1542
+ for (const decl of style.split(";")) {
1543
+ const idx = decl.indexOf(":");
1544
+ if (idx < 0) continue;
1545
+ const prop = decl.slice(0, idx).trim().toLowerCase();
1546
+ const val = decl.slice(idx + 1).trim();
1547
+ if (prop && val) map.set(prop, val);
1548
+ }
1549
+ return map;
1550
+ }
1551
+ function cleanStyleAttr(el) {
1552
+ const style = el.getAttribute("style");
1553
+ if (!style) return;
1554
+ const kept = [];
1555
+ parseStyleDeclarations(style).forEach((val, prop) => {
1556
+ if (KEEP_STYLE_PROPS.has(prop)) kept.push(`${prop}: ${val}`);
1557
+ });
1558
+ if (kept.length) el.setAttribute("style", kept.join("; "));
1559
+ else el.removeAttribute("style");
1560
+ }
1561
+ function cleanAttrs(el) {
1562
+ const keep = /* @__PURE__ */ new Set([...KEEP_ATTRS["*"] ?? [], ...KEEP_ATTRS[el.tagName.toLowerCase()] ?? []]);
1563
+ for (const attr of Array.from(el.attributes)) {
1564
+ if (!keep.has(attr.name)) el.removeAttribute(attr.name);
1565
+ }
1566
+ }
1567
+ function unwrap(el) {
1568
+ const parent = el.parentNode;
1569
+ if (!parent) return;
1570
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
1571
+ parent.removeChild(el);
1572
+ }
1573
+ function convertHighlightSpan(span, styles) {
1574
+ const bg = styles.get("background-color") ?? styles.get("background");
1575
+ if (!bg) return;
1576
+ const color = styles.get("color");
1577
+ const mark = span.ownerDocument.createElement("mark");
1578
+ mark.setAttribute("style", `background-color: ${bg}`);
1579
+ if (color) {
1580
+ const inner = span.ownerDocument.createElement("span");
1581
+ inner.setAttribute("style", `color: ${color}`);
1582
+ while (span.firstChild) inner.appendChild(span.firstChild);
1583
+ mark.appendChild(inner);
1584
+ } else {
1585
+ while (span.firstChild) mark.appendChild(span.firstChild);
1586
+ }
1587
+ span.replaceWith(mark);
1588
+ }
1589
+ function normalizeElement(el, source) {
1590
+ if (el.tagName === "SPAN") {
1591
+ const styles = parseStyleDeclarations(el.getAttribute("style") ?? "");
1592
+ if (styles.has("background-color") || styles.has("background")) {
1593
+ convertHighlightSpan(el, styles);
1594
+ return;
1595
+ }
1596
+ cleanStyleAttr(el);
1597
+ cleanAttrs(el);
1598
+ if (el.attributes.length === 0) unwrap(el);
1599
+ return;
1600
+ }
1601
+ if (el.tagName === "B") {
1602
+ const styles = parseStyleDeclarations(el.getAttribute("style") ?? "");
1603
+ if (styles.get("font-weight")?.replace(/\s/g, "") === "normal") {
1604
+ unwrap(el);
1605
+ return;
1505
1606
  }
1506
1607
  }
1507
- cleaned = cleaned.replace(
1508
- /<b\s[^>]*style\s*=\s*["'][^"']*font-weight:\s*normal[^"']*["'][^>]*>/gi,
1509
- ""
1608
+ cleanStyleAttr(el);
1609
+ cleanAttrs(el);
1610
+ if (source === "word") {
1611
+ el.removeAttribute("class");
1612
+ }
1613
+ }
1614
+ function removeComments(root) {
1615
+ const walker = root.ownerDocument.createTreeWalker(
1616
+ root,
1617
+ 128
1618
+ /* NodeFilter.SHOW_COMMENT */
1510
1619
  );
1511
- cleaned = cleaned.replace(/<\/b>/gi, "");
1512
- cleaned = cleaned.replace(/\sid\s*=\s*["']docs-internal[^"']*["']/gi, "");
1513
- return cleaned;
1620
+ const comments = [];
1621
+ let node = walker.nextNode();
1622
+ while (node) {
1623
+ comments.push(node);
1624
+ node = walker.nextNode();
1625
+ }
1626
+ for (const c of comments) c.parentNode?.removeChild(c);
1514
1627
  }
1628
+ function normalizeClipboardHTML(html) {
1629
+ if (!html || typeof html !== "string") return html ?? "";
1630
+ if (!/[<][a-zA-Z]/.test(html)) return html;
1631
+ const source = detectClipboardSource(html);
1632
+ const doc = new DOMParser().parseFromString(html, "text/html");
1633
+ removeComments(doc.body);
1634
+ for (const tag of REMOVE_TAGS) {
1635
+ for (const el of Array.from(doc.body.getElementsByTagName(tag))) {
1636
+ el.parentNode?.removeChild(el);
1637
+ }
1638
+ }
1639
+ const walk = (node) => {
1640
+ if (node.nodeType !== 1) return;
1641
+ for (const child of Array.from(node.childNodes)) walk(child);
1642
+ normalizeElement(node, source);
1643
+ };
1644
+ for (const child of Array.from(doc.body.childNodes)) walk(child);
1645
+ return doc.body.innerHTML;
1646
+ }
1647
+ var sanitizePastedHTML = normalizeClipboardHTML;
1648
+
1649
+ // src/Editor.ts
1515
1650
  function migrateContent(content) {
1516
1651
  if (!content) return content;
1517
1652
  if (typeof content === "string") {
@@ -2473,11 +2608,13 @@ function openaiCompatibleProvider(config) {
2473
2608
  createEditor,
2474
2609
  createPerformanceMonitor,
2475
2610
  definePlugin,
2611
+ detectClipboardSource,
2476
2612
  findMatches,
2477
2613
  getSearchState,
2478
2614
  httpKeyStorage,
2479
2615
  localStorageKeyStorage,
2480
2616
  memoryKeyStorage,
2617
+ normalizeClipboardHTML,
2481
2618
  openaiCompatibleProvider,
2482
2619
  replaceAll,
2483
2620
  replaceCurrent,
package/dist/index.d.cts CHANGED
@@ -186,15 +186,30 @@ declare class PerformanceMonitor {
186
186
  declare function createPerformanceMonitor(options?: PerformanceMonitorOptions): PerformanceMonitor;
187
187
 
188
188
  /**
189
- * Sanitize pasted HTML content (e.g. from Google Docs) to prevent crashes
190
- * during ProseMirror parsing. Strips non-content tags like <meta>, <style>,
191
- * and HTML comments that are common in rich clipboard data but not valid
192
- * in the ProseMirror schema.
189
+ * DOM-based clipboard HTML normalization (Stage 2 of the paste pipeline
190
+ * docs/plans/clipboard-paste-pipeline-plan.md).
193
191
  *
194
- * Also converts Google Docs-specific markup into standard TipTap-compatible
195
- * HTML so that text color and background-color survive the paste.
192
+ * Replaces the old regex-based sanitizePastedHTML: pasted HTML from Google
193
+ * Docs, Word, Notion, or generic web pages is parsed into an inert Document,
194
+ * normalized against a whitelist, and serialized for ProseMirror's parser.
195
+ *
196
+ * Design decisions (see plan §3):
197
+ * - Typography normalizes to destination styles: font-family, font-size,
198
+ * line-height, margins and padding are stripped; only `color` and
199
+ * `background-color` survive as inline styles.
200
+ * - One pipeline for all HTML sources; detectClipboardSource only tunes
201
+ * which wrapper-stripping rules run.
202
+ * - Idempotent by construction: normalizing twice changes nothing.
203
+ */
204
+ type ClipboardSource = 'google-docs' | 'word' | 'generic';
205
+ declare function detectClipboardSource(html: string): ClipboardSource;
206
+ declare function normalizeClipboardHTML(html: string): string;
207
+ /**
208
+ * Backward-compatible name kept for existing callers
209
+ * (transformPastedHTML in Editor.ts, handlePaste in DocsEditor.vue).
196
210
  */
197
- declare function sanitizePastedHTML(html: string): string;
211
+ declare const sanitizePastedHTML: typeof normalizeClipboardHTML;
212
+
198
213
  interface EditorOptions {
199
214
  target?: HTMLElement;
200
215
  content?: object | string;
@@ -380,4 +395,4 @@ declare class SubdocumentProvider {
380
395
  destroy(): void;
381
396
  }
382
397
 
383
- export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, findMatches, getSearchState, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
398
+ export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.d.ts CHANGED
@@ -186,15 +186,30 @@ declare class PerformanceMonitor {
186
186
  declare function createPerformanceMonitor(options?: PerformanceMonitorOptions): PerformanceMonitor;
187
187
 
188
188
  /**
189
- * Sanitize pasted HTML content (e.g. from Google Docs) to prevent crashes
190
- * during ProseMirror parsing. Strips non-content tags like <meta>, <style>,
191
- * and HTML comments that are common in rich clipboard data but not valid
192
- * in the ProseMirror schema.
189
+ * DOM-based clipboard HTML normalization (Stage 2 of the paste pipeline
190
+ * docs/plans/clipboard-paste-pipeline-plan.md).
193
191
  *
194
- * Also converts Google Docs-specific markup into standard TipTap-compatible
195
- * HTML so that text color and background-color survive the paste.
192
+ * Replaces the old regex-based sanitizePastedHTML: pasted HTML from Google
193
+ * Docs, Word, Notion, or generic web pages is parsed into an inert Document,
194
+ * normalized against a whitelist, and serialized for ProseMirror's parser.
195
+ *
196
+ * Design decisions (see plan §3):
197
+ * - Typography normalizes to destination styles: font-family, font-size,
198
+ * line-height, margins and padding are stripped; only `color` and
199
+ * `background-color` survive as inline styles.
200
+ * - One pipeline for all HTML sources; detectClipboardSource only tunes
201
+ * which wrapper-stripping rules run.
202
+ * - Idempotent by construction: normalizing twice changes nothing.
203
+ */
204
+ type ClipboardSource = 'google-docs' | 'word' | 'generic';
205
+ declare function detectClipboardSource(html: string): ClipboardSource;
206
+ declare function normalizeClipboardHTML(html: string): string;
207
+ /**
208
+ * Backward-compatible name kept for existing callers
209
+ * (transformPastedHTML in Editor.ts, handlePaste in DocsEditor.vue).
196
210
  */
197
- declare function sanitizePastedHTML(html: string): string;
211
+ declare const sanitizePastedHTML: typeof normalizeClipboardHTML;
212
+
198
213
  interface EditorOptions {
199
214
  target?: HTMLElement;
200
215
  content?: object | string;
@@ -380,4 +395,4 @@ declare class SubdocumentProvider {
380
395
  destroy(): void;
381
396
  }
382
397
 
383
- export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, findMatches, getSearchState, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
398
+ export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.js CHANGED
@@ -402,13 +402,13 @@ function replaceAll(editor, replacement) {
402
402
  return s.matches.length;
403
403
  }
404
404
 
405
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
405
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
406
406
  import { Extension as Extension4 } from "@tiptap/core";
407
407
  import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
408
408
  import { ReplaceStep, ReplaceAroundStep, AddMarkStep, RemoveMarkStep, RemoveNodeMarkStep, AttrStep } from "@tiptap/pm/transform";
409
409
  import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "@tiptap/pm/view";
410
410
 
411
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/utils.js
411
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/utils.js
412
412
  var updateCssVariables = (targetNode, config) => {
413
413
  const cssVariables = {
414
414
  "rm-page-height": `${config.pageHeight}px`,
@@ -582,7 +582,7 @@ var footerClickEvent = (pageNumber, onFooterClick) => {
582
582
  };
583
583
  };
584
584
 
585
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
585
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/PaginationPlus.js
586
586
  var page_count_meta_key = "PAGE_COUNT_META_KEY";
587
587
  var key = new PluginKey3("brDecoration");
588
588
  function buildDecorations(doc) {
@@ -749,11 +749,6 @@ var PaginationPlus = Extension4.create({
749
749
  width: 100%;
750
750
  display: contents;
751
751
  }
752
- .rm-with-pagination table tbody{
753
- display: table;
754
- max-height: 300px;
755
- overflow-y: auto;
756
- }
757
752
  .rm-with-pagination table tbody > tr{
758
753
  display: table-row !important;
759
754
  }
@@ -1076,6 +1071,7 @@ var getExistingPageCount = (view) => {
1076
1071
  }
1077
1072
  return 0;
1078
1073
  };
1074
+ var pageGapTracker = /* @__PURE__ */ new WeakMap();
1079
1075
  var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0) => {
1080
1076
  var _a;
1081
1077
  const editorDom = view.dom;
@@ -1093,8 +1089,36 @@ var calculatePageCount = (view, pageOptions, headerHeight = 0, footerHeight = 0)
1093
1089
  const lastPageGap = lastElementRect.bottom - lastPageBreakRect.bottom;
1094
1090
  if (lastPageGap > 0) {
1095
1091
  const addPage = Math.ceil(lastPageGap / pageContentAreaHeight);
1092
+ if (lastElementRect.height > pageContentAreaHeight) {
1093
+ const isSplittableTable = (lastElementOfEditor === null || lastElementOfEditor === void 0 ? void 0 : lastElementOfEditor.tagName) === "TABLE" && lastElementOfEditor.hasAttribute("data-tps-splittable");
1094
+ if (!isSplittableTable) {
1095
+ return currentPageCount;
1096
+ }
1097
+ }
1098
+ const prev = pageGapTracker.get(editorDom);
1099
+ if (prev && prev.pages < currentPageCount && lastPageGap > prev.gap - 2) {
1100
+ return currentPageCount;
1101
+ }
1102
+ pageGapTracker.set(editorDom, { gap: lastPageGap, pages: currentPageCount });
1103
+ let totalEditorContentHeight = 0;
1104
+ for (const child of editorDom.children) {
1105
+ if (!(child instanceof HTMLElement)) continue;
1106
+ if (child.hasAttribute("data-rm-pagination")) continue;
1107
+ if (child.classList.contains("rm-page-break")) continue;
1108
+ totalEditorContentHeight += child.getBoundingClientRect().height;
1109
+ }
1110
+ const maxPagesByContent = Math.ceil(totalEditorContentHeight / pageContentAreaHeight);
1111
+ const contentCap = Math.max(currentPageCount, maxPagesByContent + 1);
1112
+ if (currentPageCount + addPage > contentCap) {
1113
+ return contentCap;
1114
+ }
1115
+ const MAX_PAGES = 1e3;
1116
+ if (currentPageCount + addPage > MAX_PAGES) {
1117
+ return currentPageCount;
1118
+ }
1096
1119
  return currentPageCount + addPage;
1097
1120
  } else {
1121
+ pageGapTracker.delete(editorDom);
1098
1122
  const allBreaksAfterLastElement = Array.from(paginationElement.querySelectorAll(".breaker"));
1099
1123
  const allBreaksAfterLastElementRect = allBreaksAfterLastElement.filter((element) => element.getBoundingClientRect().top > lastElementRect.bottom);
1100
1124
  const removePage = allBreaksAfterLastElementRect.length;
@@ -1221,7 +1245,7 @@ function createDecoration(pageOptions, headerHeightMap, footerHeightMap) {
1221
1245
  return [pageWidget, firstHeaderWidget];
1222
1246
  }
1223
1247
 
1224
- // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/tiptap-pagination-plus/dist/constants.js
1248
+ // ../../node_modules/.pnpm/tiptap-pagination-plus@3.1.0_patch_hash=4857789ba5672a185fb96bdd854e03e0c6ec8ed26abfeea_16730b2b361cd3f71113c52459ed62fd/node_modules/tiptap-pagination-plus/dist/constants.js
1225
1249
  var A4_PAGE_SIZE = getPageSize(1123, 794, 95, 95, 76, 76);
1226
1250
  var A3_PAGE_SIZE = getPageSize(1591, 1123, 95, 95, 76, 76);
1227
1251
  var A5_PAGE_SIZE = getPageSize(794, 419, 76, 76, 57, 57);
@@ -1419,41 +1443,150 @@ function createPerformanceMonitor(options) {
1419
1443
  return monitor;
1420
1444
  }
1421
1445
 
1422
- // src/Editor.ts
1423
- function sanitizePastedHTML(html) {
1424
- let cleaned = html.replace(/<meta[^>]*>/gi, "");
1425
- cleaned = cleaned.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
1426
- cleaned = cleaned.replace(/<!--[\s\S]*?-->/g, "");
1427
- cleaned = cleaned.replace(/<link[^>]*>/gi, "");
1428
- cleaned = cleaned.replace(/<base[^>]*>/gi, "");
1429
- cleaned = cleaned.replace(/<title[^>]*>[\s\S]*?<\/title>/gi, "");
1430
- {
1431
- for (let pass = 0; pass < 10; pass++) {
1432
- const before = cleaned;
1433
- cleaned = cleaned.replace(
1434
- /<span\s([^>]*style\s*=\s*["'][^"']*(?:background-color|background)[^"']*["'][^>]*)>([^<]*(?:<(?!\/?span)[^>]*>[^<]*)*)<\/span>/gi,
1435
- (_full, attrs, content) => {
1436
- if (!/background(?:-color)?\s*:/.test(attrs)) return _full;
1437
- const colorMatch = attrs.match(/(?<!background-)color\s*:\s*([^;"]+)/i);
1438
- const colorVal = colorMatch ? colorMatch[1].trim() : null;
1439
- const markAttrs = colorVal ? attrs.replace(/(?<!background-)color\s*:\s*[^;"]+;?\s*/gi, "").trim() : attrs;
1440
- if (colorVal) {
1441
- return `<mark ${markAttrs}><span style="color: ${colorVal};">${content}</span></mark>`;
1442
- }
1443
- return `<mark ${markAttrs}>${content}</mark>`;
1444
- }
1445
- );
1446
- if (cleaned === before) break;
1446
+ // src/pasteNormalization.ts
1447
+ var KEEP_STYLE_PROPS = /* @__PURE__ */ new Set(["color", "background-color"]);
1448
+ var REMOVE_TAGS = /* @__PURE__ */ new Set([
1449
+ "meta",
1450
+ "style",
1451
+ "link",
1452
+ "base",
1453
+ "title",
1454
+ "script",
1455
+ "iframe",
1456
+ "object",
1457
+ "embed",
1458
+ "form",
1459
+ "input",
1460
+ "button",
1461
+ "select",
1462
+ "textarea",
1463
+ "noscript",
1464
+ // Word Office namespace dump paragraph
1465
+ "o:p"
1466
+ ]);
1467
+ var KEEP_ATTRS = {
1468
+ a: /* @__PURE__ */ new Set(["href", "style"]),
1469
+ img: /* @__PURE__ */ new Set(["src", "alt", "style"]),
1470
+ td: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1471
+ th: /* @__PURE__ */ new Set(["colspan", "rowspan", "style"]),
1472
+ col: /* @__PURE__ */ new Set(["span"]),
1473
+ "*": /* @__PURE__ */ new Set(["style"])
1474
+ };
1475
+ function detectClipboardSource(html) {
1476
+ if (/id\s*=\s*["']docs-internal/i.test(html)) return "google-docs";
1477
+ if (/<o:p[\s>]|xmlns(:w|:o)?=|class\s*=\s*["']?Mso|mso-/i.test(html)) return "word";
1478
+ return "generic";
1479
+ }
1480
+ function parseStyleDeclarations(style) {
1481
+ const map = /* @__PURE__ */ new Map();
1482
+ for (const decl of style.split(";")) {
1483
+ const idx = decl.indexOf(":");
1484
+ if (idx < 0) continue;
1485
+ const prop = decl.slice(0, idx).trim().toLowerCase();
1486
+ const val = decl.slice(idx + 1).trim();
1487
+ if (prop && val) map.set(prop, val);
1488
+ }
1489
+ return map;
1490
+ }
1491
+ function cleanStyleAttr(el) {
1492
+ const style = el.getAttribute("style");
1493
+ if (!style) return;
1494
+ const kept = [];
1495
+ parseStyleDeclarations(style).forEach((val, prop) => {
1496
+ if (KEEP_STYLE_PROPS.has(prop)) kept.push(`${prop}: ${val}`);
1497
+ });
1498
+ if (kept.length) el.setAttribute("style", kept.join("; "));
1499
+ else el.removeAttribute("style");
1500
+ }
1501
+ function cleanAttrs(el) {
1502
+ const keep = /* @__PURE__ */ new Set([...KEEP_ATTRS["*"] ?? [], ...KEEP_ATTRS[el.tagName.toLowerCase()] ?? []]);
1503
+ for (const attr of Array.from(el.attributes)) {
1504
+ if (!keep.has(attr.name)) el.removeAttribute(attr.name);
1505
+ }
1506
+ }
1507
+ function unwrap(el) {
1508
+ const parent = el.parentNode;
1509
+ if (!parent) return;
1510
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
1511
+ parent.removeChild(el);
1512
+ }
1513
+ function convertHighlightSpan(span, styles) {
1514
+ const bg = styles.get("background-color") ?? styles.get("background");
1515
+ if (!bg) return;
1516
+ const color = styles.get("color");
1517
+ const mark = span.ownerDocument.createElement("mark");
1518
+ mark.setAttribute("style", `background-color: ${bg}`);
1519
+ if (color) {
1520
+ const inner = span.ownerDocument.createElement("span");
1521
+ inner.setAttribute("style", `color: ${color}`);
1522
+ while (span.firstChild) inner.appendChild(span.firstChild);
1523
+ mark.appendChild(inner);
1524
+ } else {
1525
+ while (span.firstChild) mark.appendChild(span.firstChild);
1526
+ }
1527
+ span.replaceWith(mark);
1528
+ }
1529
+ function normalizeElement(el, source) {
1530
+ if (el.tagName === "SPAN") {
1531
+ const styles = parseStyleDeclarations(el.getAttribute("style") ?? "");
1532
+ if (styles.has("background-color") || styles.has("background")) {
1533
+ convertHighlightSpan(el, styles);
1534
+ return;
1535
+ }
1536
+ cleanStyleAttr(el);
1537
+ cleanAttrs(el);
1538
+ if (el.attributes.length === 0) unwrap(el);
1539
+ return;
1540
+ }
1541
+ if (el.tagName === "B") {
1542
+ const styles = parseStyleDeclarations(el.getAttribute("style") ?? "");
1543
+ if (styles.get("font-weight")?.replace(/\s/g, "") === "normal") {
1544
+ unwrap(el);
1545
+ return;
1447
1546
  }
1448
1547
  }
1449
- cleaned = cleaned.replace(
1450
- /<b\s[^>]*style\s*=\s*["'][^"']*font-weight:\s*normal[^"']*["'][^>]*>/gi,
1451
- ""
1548
+ cleanStyleAttr(el);
1549
+ cleanAttrs(el);
1550
+ if (source === "word") {
1551
+ el.removeAttribute("class");
1552
+ }
1553
+ }
1554
+ function removeComments(root) {
1555
+ const walker = root.ownerDocument.createTreeWalker(
1556
+ root,
1557
+ 128
1558
+ /* NodeFilter.SHOW_COMMENT */
1452
1559
  );
1453
- cleaned = cleaned.replace(/<\/b>/gi, "");
1454
- cleaned = cleaned.replace(/\sid\s*=\s*["']docs-internal[^"']*["']/gi, "");
1455
- return cleaned;
1560
+ const comments = [];
1561
+ let node = walker.nextNode();
1562
+ while (node) {
1563
+ comments.push(node);
1564
+ node = walker.nextNode();
1565
+ }
1566
+ for (const c of comments) c.parentNode?.removeChild(c);
1456
1567
  }
1568
+ function normalizeClipboardHTML(html) {
1569
+ if (!html || typeof html !== "string") return html ?? "";
1570
+ if (!/[<][a-zA-Z]/.test(html)) return html;
1571
+ const source = detectClipboardSource(html);
1572
+ const doc = new DOMParser().parseFromString(html, "text/html");
1573
+ removeComments(doc.body);
1574
+ for (const tag of REMOVE_TAGS) {
1575
+ for (const el of Array.from(doc.body.getElementsByTagName(tag))) {
1576
+ el.parentNode?.removeChild(el);
1577
+ }
1578
+ }
1579
+ const walk = (node) => {
1580
+ if (node.nodeType !== 1) return;
1581
+ for (const child of Array.from(node.childNodes)) walk(child);
1582
+ normalizeElement(node, source);
1583
+ };
1584
+ for (const child of Array.from(doc.body.childNodes)) walk(child);
1585
+ return doc.body.innerHTML;
1586
+ }
1587
+ var sanitizePastedHTML = normalizeClipboardHTML;
1588
+
1589
+ // src/Editor.ts
1457
1590
  function migrateContent(content) {
1458
1591
  if (!content) return content;
1459
1592
  if (typeof content === "string") {
@@ -2151,11 +2284,13 @@ export {
2151
2284
  createEditor,
2152
2285
  createPerformanceMonitor,
2153
2286
  definePlugin,
2287
+ detectClipboardSource,
2154
2288
  findMatches,
2155
2289
  getSearchState,
2156
2290
  httpKeyStorage,
2157
2291
  localStorageKeyStorage,
2158
2292
  memoryKeyStorage,
2293
+ normalizeClipboardHTML,
2159
2294
  openaiCompatibleProvider,
2160
2295
  replaceAll,
2161
2296
  replaceCurrent,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-core",
3
3
  "license": "UNLICENSED",
4
- "version": "0.0.49",
4
+ "version": "0.0.50",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",