@scrider/formatter 1.3.2 → 1.3.4

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
@@ -77,6 +77,23 @@ deltaToHtml(delta, { registry }) // Delta → HTML string
77
77
  htmlToDelta(html, { registry }) // HTML string → Delta
78
78
  ```
79
79
 
80
+ **Simple Table presentation (v1.3.4+)** — inline borders/shades for clipboard and Office paste (Delta structure unchanged):
81
+
82
+ ```typescript
83
+ import { deltaToHtml, type TablePresentation } from '@scrider/formatter';
84
+
85
+ const tablePresentation: TablePresentation = {
86
+ grid: true,
87
+ borderColor: '#e7e7e7',
88
+ headerBold: true,
89
+ headerCenter: true,
90
+ };
91
+
92
+ deltaToHtml(delta, { tablePresentation });
93
+ ```
94
+
95
+ See scrider-editor `docs/simple-tables.md` §8 for the full contract (`grid` / `line`, `headerShade`, `zebraRows`, `defaultCellAlign`, …).
96
+
80
97
  ### Markdown Conversion
81
98
 
82
99
  ```typescript
package/dist/index.cjs CHANGED
@@ -82,6 +82,7 @@ __export(index_exports, {
82
82
  isTextNode: () => isTextNode,
83
83
  isValidColor: () => isValidColor,
84
84
  isValidHexColor: () => isValidHexColor,
85
+ isZebraBodyRow: () => isZebraBodyRow,
85
86
  italicFormat: () => italicFormat,
86
87
  kbdFormat: () => kbdFormat,
87
88
  linkFormat: () => linkFormat,
@@ -92,6 +93,7 @@ __export(index_exports, {
92
93
  nodeAdapter: () => nodeAdapter,
93
94
  normalizeDelta: () => normalizeDelta,
94
95
  preloadRemark: () => preloadRemark,
96
+ resolveTablePresentation: () => resolveTablePresentation,
95
97
  sanitizeDelta: () => sanitizeDelta,
96
98
  sizeFormat: () => sizeFormat,
97
99
  slugify: () => slugify,
@@ -2599,6 +2601,68 @@ function slugifyWithDedup(text, usedSlugs) {
2599
2601
  return `${base}-${count}`;
2600
2602
  }
2601
2603
 
2604
+ // src/conversion/html/table-presentation.ts
2605
+ var DEFAULT_BORDER_COLOR = "#e7e7e7";
2606
+ var DEFAULT_HEADER_BG = "#f5f5f5";
2607
+ var DEFAULT_ZEBRA_BG = "#fafafa";
2608
+ var CELL_PADDING = "6px 13px";
2609
+ function resolveTablePresentation(presentation) {
2610
+ return {
2611
+ grid: presentation?.grid === true,
2612
+ line: presentation?.line === true && presentation?.grid !== true,
2613
+ borderColor: presentation?.borderColor ?? DEFAULT_BORDER_COLOR,
2614
+ headerShade: presentation?.headerShade === true,
2615
+ zebraRows: presentation?.zebraRows === true,
2616
+ headerBold: presentation?.headerBold === true,
2617
+ headerCenter: presentation?.headerCenter === true,
2618
+ defaultCellAlign: presentation?.defaultCellAlign ?? "left"
2619
+ };
2620
+ }
2621
+ function isZebraBodyRow(headerRowCount, bodyRowIndex) {
2622
+ return (headerRowCount + bodyRowIndex + 1) % 2 === 0;
2623
+ }
2624
+ function isTableCellAlign(value) {
2625
+ return value === "left" || value === "center" || value === "right";
2626
+ }
2627
+ function tableOpenTag(presentation) {
2628
+ if (!presentation.grid && !presentation.line) {
2629
+ return "<table>";
2630
+ }
2631
+ return `<table style="border-collapse: collapse">`;
2632
+ }
2633
+ function buildTableCellStyleAttr(params) {
2634
+ const { presentation, cellTag, colAlign, headerRowCount, bodyRowIndex } = params;
2635
+ const parts = [];
2636
+ parts.push(`padding: ${CELL_PADDING}`);
2637
+ const color = presentation.borderColor;
2638
+ if (presentation.grid) {
2639
+ parts.push(`border: 1px solid ${color}`);
2640
+ } else if (presentation.line) {
2641
+ const width = cellTag === "th" ? "1px" : "0.5px";
2642
+ parts.push(`border-bottom: ${width} solid ${color}`);
2643
+ }
2644
+ let textAlign;
2645
+ if (cellTag === "th" && presentation.headerCenter) {
2646
+ textAlign = "center";
2647
+ } else if (isTableCellAlign(colAlign)) {
2648
+ textAlign = colAlign;
2649
+ } else if (colAlign == null || colAlign === "left") {
2650
+ textAlign = presentation.defaultCellAlign;
2651
+ }
2652
+ if (textAlign && textAlign !== "left") {
2653
+ parts.push(`text-align: ${textAlign}`);
2654
+ }
2655
+ if (cellTag === "th" && presentation.headerBold) {
2656
+ parts.push("font-weight: bold");
2657
+ }
2658
+ if (cellTag === "th" && presentation.headerShade) {
2659
+ parts.push(`background-color: ${DEFAULT_HEADER_BG}`);
2660
+ } else if (cellTag === "td" && presentation.zebraRows && bodyRowIndex !== void 0 && isZebraBodyRow(headerRowCount, bodyRowIndex)) {
2661
+ parts.push(`background-color: ${DEFAULT_ZEBRA_BG}`);
2662
+ }
2663
+ return ` style="${parts.join("; ")}"`;
2664
+ }
2665
+
2602
2666
  // src/conversion/html/delta-to-html.ts
2603
2667
  function deltaToHtml(delta, options = {}) {
2604
2668
  const lines = splitIntoLines(delta);
@@ -2782,7 +2846,10 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2782
2846
  const bodyRows = sortedRows.filter(([, r]) => !r.isHeader);
2783
2847
  const indent = pretty ? " " : "";
2784
2848
  const nl = pretty ? "\n" : "";
2785
- let html = `<table>${nl}`;
2849
+ const usePresentation = options?.tablePresentation !== void 0;
2850
+ const presentation = usePresentation ? resolveTablePresentation(options.tablePresentation) : null;
2851
+ const headerRowCount = headerRows.length;
2852
+ let html = usePresentation && presentation ? `${tableOpenTag(presentation)}${nl}` : `<table>${nl}`;
2786
2853
  if (headerRows.length > 0) {
2787
2854
  html += `${indent}<thead>${nl}`;
2788
2855
  for (const [, row] of headerRows) {
@@ -2794,13 +2861,16 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2794
2861
  pretty,
2795
2862
  2,
2796
2863
  blockHandlers,
2797
- options
2864
+ options,
2865
+ presentation,
2866
+ headerRowCount
2798
2867
  );
2799
2868
  }
2800
2869
  html += `${indent}</thead>${nl}`;
2801
2870
  }
2802
2871
  if (bodyRows.length > 0) {
2803
2872
  html += `${indent}<tbody>${nl}`;
2873
+ let bodyRowIndex = 0;
2804
2874
  for (const [, row] of bodyRows) {
2805
2875
  html += renderTableRow(
2806
2876
  row.cells,
@@ -2810,8 +2880,12 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2810
2880
  pretty,
2811
2881
  2,
2812
2882
  blockHandlers,
2813
- options
2883
+ options,
2884
+ presentation,
2885
+ headerRowCount,
2886
+ bodyRowIndex
2814
2887
  );
2888
+ bodyRowIndex += 1;
2815
2889
  }
2816
2890
  html += `${indent}</tbody>${nl}`;
2817
2891
  }
@@ -2819,7 +2893,7 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2819
2893
  if (pretty) html += "\n";
2820
2894
  return html;
2821
2895
  }
2822
- function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options) {
2896
+ function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options, presentation, headerRowCount = 0, bodyRowIndex) {
2823
2897
  const indent = pretty ? " ".repeat(depth) : "";
2824
2898
  const cellIndent = pretty ? " ".repeat(depth + 1) : "";
2825
2899
  const nl = pretty ? "\n" : "";
@@ -2827,8 +2901,19 @@ function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, b
2827
2901
  for (let col = 0; col <= maxCol; col++) {
2828
2902
  const cell = cells.get(col);
2829
2903
  const content = cell ? renderLineContent(cell.ops, embedRenderers, blockHandlers, options) : "";
2830
- const alignStyle = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
2831
- html += `${cellIndent}<${cellTag}${alignStyle}>${content}</${cellTag}>${nl}`;
2904
+ let styleAttr = "";
2905
+ if (presentation) {
2906
+ styleAttr = buildTableCellStyleAttr({
2907
+ presentation,
2908
+ cellTag,
2909
+ colAlign: cell?.colAlign,
2910
+ headerRowCount,
2911
+ bodyRowIndex: cellTag === "td" ? bodyRowIndex : void 0
2912
+ });
2913
+ } else {
2914
+ styleAttr = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
2915
+ }
2916
+ html += `${cellIndent}<${cellTag}${styleAttr}>${content}</${cellTag}>${nl}`;
2832
2917
  }
2833
2918
  html += `${indent}</tr>${nl}`;
2834
2919
  return html;
@@ -3226,7 +3311,14 @@ function htmlToDelta(html, options = {}) {
3226
3311
  }
3227
3312
  if (tagName === "br") {
3228
3313
  const hasMarker = node.hasAttribute("data-scrider-embed");
3229
- if (hasMarker || hasMeaningfulPrevSibling(node)) {
3314
+ if (hasMarker) {
3315
+ context.pushEmbed({ softBreak: true });
3316
+ return;
3317
+ }
3318
+ if (isBrowserEmptyLineFiller(node)) {
3319
+ return;
3320
+ }
3321
+ if (hasMeaningfulPrevSibling(node)) {
3230
3322
  context.pushEmbed({ softBreak: true });
3231
3323
  } else {
3232
3324
  context.pushNewline();
@@ -3681,6 +3773,39 @@ function hasMeaningfulPrevSibling(brNode) {
3681
3773
  }
3682
3774
  return false;
3683
3775
  }
3776
+ function isBrowserEmptyLineFiller(brNode) {
3777
+ const parent = brNode.parentNode;
3778
+ if (!parent) return false;
3779
+ const children = parent.childNodes;
3780
+ let prevElement = null;
3781
+ let foundCurrent = false;
3782
+ for (let i = 0; i < children.length; i++) {
3783
+ const child = children[i];
3784
+ if (!child) continue;
3785
+ if (child === brNode) {
3786
+ foundCurrent = true;
3787
+ continue;
3788
+ }
3789
+ if (!foundCurrent) {
3790
+ if (child.nodeType === NODE_TYPE.TEXT_NODE) {
3791
+ const text = (child.textContent ?? "").replace(/[\s\u200B]/g, "");
3792
+ if (text.length > 0) prevElement = null;
3793
+ } else if (isElement(child)) {
3794
+ prevElement = child;
3795
+ }
3796
+ } else {
3797
+ if (child.nodeType === NODE_TYPE.TEXT_NODE) {
3798
+ const text = (child.textContent ?? "").replace(/[\s\u200B]/g, "");
3799
+ if (text.length > 0) return false;
3800
+ } else if (isElement(child)) {
3801
+ const tag = child.tagName?.toLowerCase();
3802
+ if (tag !== "br") return false;
3803
+ }
3804
+ }
3805
+ }
3806
+ if (!prevElement) return false;
3807
+ return prevElement.tagName?.toLowerCase() === "br";
3808
+ }
3684
3809
  function findTagHandler(handlers, element, tagName) {
3685
3810
  const className = element.getAttribute("class");
3686
3811
  if (className) {
@@ -5115,6 +5240,7 @@ function extractTableRegion(ops, hintOpIdx) {
5115
5240
  isTextNode,
5116
5241
  isValidColor,
5117
5242
  isValidHexColor,
5243
+ isZebraBodyRow,
5118
5244
  italicFormat,
5119
5245
  kbdFormat,
5120
5246
  linkFormat,
@@ -5125,6 +5251,7 @@ function extractTableRegion(ops, hintOpIdx) {
5125
5251
  nodeAdapter,
5126
5252
  normalizeDelta,
5127
5253
  preloadRemark,
5254
+ resolveTablePresentation,
5128
5255
  sanitizeDelta,
5129
5256
  sizeFormat,
5130
5257
  slugify,