@scrider/formatter 1.3.3 → 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/dist/index.d.cts CHANGED
@@ -1292,6 +1292,48 @@ declare function validateDelta(delta: Delta, registry: Registry): boolean;
1292
1292
  */
1293
1293
  declare function cloneDelta(delta: Delta): Delta;
1294
1294
 
1295
+ /**
1296
+ * Simple Table HTML presentation options for deltaToHtml (clipboard, export).
1297
+ * Structural data stays in Delta (table-row, table-col-align); this only affects inline styles.
1298
+ */
1299
+ /** Column/cell horizontal alignment (GFM subset). */
1300
+ type TableCellAlign = 'left' | 'center' | 'right';
1301
+ /** Optional styling when serializing Simple Tables to HTML. */
1302
+ interface TablePresentation {
1303
+ /** Full 1px border on all cell sides. When true, `line` is ignored. */
1304
+ grid?: boolean;
1305
+ /** Bottom border only (DeepSeek / ChatGPT). Used when `grid` is not true. */
1306
+ line?: boolean;
1307
+ /** Border color as explicit hex (e.g. `#e7e7e7`). */
1308
+ borderColor?: string;
1309
+ /** Background on header cells (`th`). */
1310
+ headerShade?: boolean;
1311
+ /** Background on even table rows in the body (see `isZebraBodyRow`). */
1312
+ zebraRows?: boolean;
1313
+ /** `font-weight: bold` on `th`. */
1314
+ headerBold?: boolean;
1315
+ /** `text-align: center` on `th` (GitHub-style header). */
1316
+ headerCenter?: boolean;
1317
+ /**
1318
+ * Alignment for cells without `table-col-align` in Delta. Never overrides GFM column align.
1319
+ * @default 'left'
1320
+ */
1321
+ defaultCellAlign?: TableCellAlign;
1322
+ }
1323
+ interface ResolvedTablePresentation {
1324
+ grid: boolean;
1325
+ line: boolean;
1326
+ borderColor: string;
1327
+ headerShade: boolean;
1328
+ zebraRows: boolean;
1329
+ headerBold: boolean;
1330
+ headerCenter: boolean;
1331
+ defaultCellAlign: TableCellAlign;
1332
+ }
1333
+ declare function resolveTablePresentation(presentation?: TablePresentation): ResolvedTablePresentation;
1334
+ /** Match CSS `tr:nth-child(even) td` when header rows precede body in `<table>`. */
1335
+ declare function isZebraBodyRow(headerRowCount: number, bodyRowIndex: number): boolean;
1336
+
1295
1337
  /**
1296
1338
  * Delta → HTML Conversion
1297
1339
  *
@@ -1351,6 +1393,11 @@ interface DeltaToHtmlOptions {
1351
1393
  * This enables extensibility without modifying converter internals.
1352
1394
  */
1353
1395
  registry?: Registry;
1396
+ /**
1397
+ * Simple Table presentation (borders, shades, header style) as inline CSS for
1398
+ * Office/HTML export and clipboard. Does not change Delta; omitted = legacy bare `<table>`.
1399
+ */
1400
+ tablePresentation?: TablePresentation;
1354
1401
  }
1355
1402
  /**
1356
1403
  * Convert a Delta to an HTML string
@@ -1816,4 +1863,4 @@ declare function isTableNewlineOp(op: Op | undefined): boolean;
1816
1863
  */
1817
1864
  declare function extractTableRegion(ops: readonly Op[], hintOpIdx: number): TableRegion | null;
1818
1865
 
1819
- export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, Registry, type SanitizeOptions, type TableBlockData, type TableColAlignType, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, preloadRemark, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
1866
+ export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, Registry, type ResolvedTablePresentation, type SanitizeOptions, type TableBlockData, type TableCellAlign, type TableColAlignType, type TablePresentation, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, isZebraBodyRow, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, preloadRemark, resolveTablePresentation, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
package/dist/index.d.ts CHANGED
@@ -1292,6 +1292,48 @@ declare function validateDelta(delta: Delta, registry: Registry): boolean;
1292
1292
  */
1293
1293
  declare function cloneDelta(delta: Delta): Delta;
1294
1294
 
1295
+ /**
1296
+ * Simple Table HTML presentation options for deltaToHtml (clipboard, export).
1297
+ * Structural data stays in Delta (table-row, table-col-align); this only affects inline styles.
1298
+ */
1299
+ /** Column/cell horizontal alignment (GFM subset). */
1300
+ type TableCellAlign = 'left' | 'center' | 'right';
1301
+ /** Optional styling when serializing Simple Tables to HTML. */
1302
+ interface TablePresentation {
1303
+ /** Full 1px border on all cell sides. When true, `line` is ignored. */
1304
+ grid?: boolean;
1305
+ /** Bottom border only (DeepSeek / ChatGPT). Used when `grid` is not true. */
1306
+ line?: boolean;
1307
+ /** Border color as explicit hex (e.g. `#e7e7e7`). */
1308
+ borderColor?: string;
1309
+ /** Background on header cells (`th`). */
1310
+ headerShade?: boolean;
1311
+ /** Background on even table rows in the body (see `isZebraBodyRow`). */
1312
+ zebraRows?: boolean;
1313
+ /** `font-weight: bold` on `th`. */
1314
+ headerBold?: boolean;
1315
+ /** `text-align: center` on `th` (GitHub-style header). */
1316
+ headerCenter?: boolean;
1317
+ /**
1318
+ * Alignment for cells without `table-col-align` in Delta. Never overrides GFM column align.
1319
+ * @default 'left'
1320
+ */
1321
+ defaultCellAlign?: TableCellAlign;
1322
+ }
1323
+ interface ResolvedTablePresentation {
1324
+ grid: boolean;
1325
+ line: boolean;
1326
+ borderColor: string;
1327
+ headerShade: boolean;
1328
+ zebraRows: boolean;
1329
+ headerBold: boolean;
1330
+ headerCenter: boolean;
1331
+ defaultCellAlign: TableCellAlign;
1332
+ }
1333
+ declare function resolveTablePresentation(presentation?: TablePresentation): ResolvedTablePresentation;
1334
+ /** Match CSS `tr:nth-child(even) td` when header rows precede body in `<table>`. */
1335
+ declare function isZebraBodyRow(headerRowCount: number, bodyRowIndex: number): boolean;
1336
+
1295
1337
  /**
1296
1338
  * Delta → HTML Conversion
1297
1339
  *
@@ -1351,6 +1393,11 @@ interface DeltaToHtmlOptions {
1351
1393
  * This enables extensibility without modifying converter internals.
1352
1394
  */
1353
1395
  registry?: Registry;
1396
+ /**
1397
+ * Simple Table presentation (borders, shades, header style) as inline CSS for
1398
+ * Office/HTML export and clipboard. Does not change Delta; omitted = legacy bare `<table>`.
1399
+ */
1400
+ tablePresentation?: TablePresentation;
1354
1401
  }
1355
1402
  /**
1356
1403
  * Convert a Delta to an HTML string
@@ -1816,4 +1863,4 @@ declare function isTableNewlineOp(op: Op | undefined): boolean;
1816
1863
  */
1817
1864
  declare function extractTableRegion(ops: readonly Op[], hintOpIdx: number): TableRegion | null;
1818
1865
 
1819
- export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, Registry, type SanitizeOptions, type TableBlockData, type TableColAlignType, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, preloadRemark, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
1866
+ export { ALERT_TYPES, type AlertBlockData, type AlertType, type AlignType, BOX_FLOAT_VALUES, BOX_OVERFLOW_VALUES, type BlockContext, type BlockHandler, BlockHandlerRegistry, type BlockRenderOptions, type BoxBlockData, type BoxFloat, type BoxOpAttributes, type BoxOverflow, BrowserDOMAdapter, type CellAlign, type CellData, type ColumnsBlockData, type DOMAdapter, type DOMDocument, type DOMDocumentFragment, type DOMElement, type DOMNode, type DOMNodeList, type DeltaToHtmlOptions, type DeltaToMarkdownOptions, type FootnotesBlockData, type Format, type FormatDefinition, type FormatMatchResult, type FormatScope, type HtmlToDeltaOptions, type ListType, type MarkdownToDeltaOptions, NODE_TYPE, NodeDOMAdapter, Registry, type ResolvedTablePresentation, type SanitizeOptions, type TableBlockData, type TableCellAlign, type TableColAlignType, type TablePresentation, type TableRegion, alertBlockHandler, alignFormat, backgroundFormat, blockFormat, blockquoteFormat, boldFormat, boxBlockHandler, browserAdapter, cloneDelta, codeBlockFormat, codeFormat, colorFormat, columnsBlockHandler, createDefaultBlockHandlers, createDefaultRegistry, defaultBlockFormats, defaultEmbedFormats, defaultFormats, defaultInlineFormats, deltaToHtml, deltaToMarkdown, dividerFormat, escapeHtml, extractBoxOpAttributes, extractTableRegion, fontFormat, footnoteRefFormat, footnotesBlockHandler, formulaFormat, getAdapter, getNamedColors, headerFormat, headerIdFormat, htmlToDelta, imageFormat, indentFormat, isAdapterAvailable, isElement, isRemarkAvailable, isTableNewlineOp, isTextNode, isValidColor, isValidHexColor, isZebraBodyRow, italicFormat, kbdFormat, linkFormat, listFormat, markFormat, markdownToDelta, markdownToDeltaSync, nodeAdapter, normalizeDelta, preloadRemark, resolveTablePresentation, sanitizeDelta, sizeFormat, slugify, slugifyWithDedup, softBreakFormat, strikeFormat, subscriptFormat, superscriptFormat, tableBlockHandler, tableColAlignFormat, tableColFormat, tableHeaderFormat, tableRowFormat, toHexColor, underlineFormat, unescapeHtml, validateDelta, videoFormat };
package/dist/index.js CHANGED
@@ -2493,6 +2493,68 @@ function slugifyWithDedup(text, usedSlugs) {
2493
2493
  return `${base}-${count}`;
2494
2494
  }
2495
2495
 
2496
+ // src/conversion/html/table-presentation.ts
2497
+ var DEFAULT_BORDER_COLOR = "#e7e7e7";
2498
+ var DEFAULT_HEADER_BG = "#f5f5f5";
2499
+ var DEFAULT_ZEBRA_BG = "#fafafa";
2500
+ var CELL_PADDING = "6px 13px";
2501
+ function resolveTablePresentation(presentation) {
2502
+ return {
2503
+ grid: presentation?.grid === true,
2504
+ line: presentation?.line === true && presentation?.grid !== true,
2505
+ borderColor: presentation?.borderColor ?? DEFAULT_BORDER_COLOR,
2506
+ headerShade: presentation?.headerShade === true,
2507
+ zebraRows: presentation?.zebraRows === true,
2508
+ headerBold: presentation?.headerBold === true,
2509
+ headerCenter: presentation?.headerCenter === true,
2510
+ defaultCellAlign: presentation?.defaultCellAlign ?? "left"
2511
+ };
2512
+ }
2513
+ function isZebraBodyRow(headerRowCount, bodyRowIndex) {
2514
+ return (headerRowCount + bodyRowIndex + 1) % 2 === 0;
2515
+ }
2516
+ function isTableCellAlign(value) {
2517
+ return value === "left" || value === "center" || value === "right";
2518
+ }
2519
+ function tableOpenTag(presentation) {
2520
+ if (!presentation.grid && !presentation.line) {
2521
+ return "<table>";
2522
+ }
2523
+ return `<table style="border-collapse: collapse">`;
2524
+ }
2525
+ function buildTableCellStyleAttr(params) {
2526
+ const { presentation, cellTag, colAlign, headerRowCount, bodyRowIndex } = params;
2527
+ const parts = [];
2528
+ parts.push(`padding: ${CELL_PADDING}`);
2529
+ const color = presentation.borderColor;
2530
+ if (presentation.grid) {
2531
+ parts.push(`border: 1px solid ${color}`);
2532
+ } else if (presentation.line) {
2533
+ const width = cellTag === "th" ? "1px" : "0.5px";
2534
+ parts.push(`border-bottom: ${width} solid ${color}`);
2535
+ }
2536
+ let textAlign;
2537
+ if (cellTag === "th" && presentation.headerCenter) {
2538
+ textAlign = "center";
2539
+ } else if (isTableCellAlign(colAlign)) {
2540
+ textAlign = colAlign;
2541
+ } else if (colAlign == null || colAlign === "left") {
2542
+ textAlign = presentation.defaultCellAlign;
2543
+ }
2544
+ if (textAlign && textAlign !== "left") {
2545
+ parts.push(`text-align: ${textAlign}`);
2546
+ }
2547
+ if (cellTag === "th" && presentation.headerBold) {
2548
+ parts.push("font-weight: bold");
2549
+ }
2550
+ if (cellTag === "th" && presentation.headerShade) {
2551
+ parts.push(`background-color: ${DEFAULT_HEADER_BG}`);
2552
+ } else if (cellTag === "td" && presentation.zebraRows && bodyRowIndex !== void 0 && isZebraBodyRow(headerRowCount, bodyRowIndex)) {
2553
+ parts.push(`background-color: ${DEFAULT_ZEBRA_BG}`);
2554
+ }
2555
+ return ` style="${parts.join("; ")}"`;
2556
+ }
2557
+
2496
2558
  // src/conversion/html/delta-to-html.ts
2497
2559
  function deltaToHtml(delta, options = {}) {
2498
2560
  const lines = splitIntoLines(delta);
@@ -2676,7 +2738,10 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2676
2738
  const bodyRows = sortedRows.filter(([, r]) => !r.isHeader);
2677
2739
  const indent = pretty ? " " : "";
2678
2740
  const nl = pretty ? "\n" : "";
2679
- let html = `<table>${nl}`;
2741
+ const usePresentation = options?.tablePresentation !== void 0;
2742
+ const presentation = usePresentation ? resolveTablePresentation(options.tablePresentation) : null;
2743
+ const headerRowCount = headerRows.length;
2744
+ let html = usePresentation && presentation ? `${tableOpenTag(presentation)}${nl}` : `<table>${nl}`;
2680
2745
  if (headerRows.length > 0) {
2681
2746
  html += `${indent}<thead>${nl}`;
2682
2747
  for (const [, row] of headerRows) {
@@ -2688,13 +2753,16 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2688
2753
  pretty,
2689
2754
  2,
2690
2755
  blockHandlers,
2691
- options
2756
+ options,
2757
+ presentation,
2758
+ headerRowCount
2692
2759
  );
2693
2760
  }
2694
2761
  html += `${indent}</thead>${nl}`;
2695
2762
  }
2696
2763
  if (bodyRows.length > 0) {
2697
2764
  html += `${indent}<tbody>${nl}`;
2765
+ let bodyRowIndex = 0;
2698
2766
  for (const [, row] of bodyRows) {
2699
2767
  html += renderTableRow(
2700
2768
  row.cells,
@@ -2704,8 +2772,12 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2704
2772
  pretty,
2705
2773
  2,
2706
2774
  blockHandlers,
2707
- options
2775
+ options,
2776
+ presentation,
2777
+ headerRowCount,
2778
+ bodyRowIndex
2708
2779
  );
2780
+ bodyRowIndex += 1;
2709
2781
  }
2710
2782
  html += `${indent}</tbody>${nl}`;
2711
2783
  }
@@ -2713,7 +2785,7 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2713
2785
  if (pretty) html += "\n";
2714
2786
  return html;
2715
2787
  }
2716
- function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options) {
2788
+ function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options, presentation, headerRowCount = 0, bodyRowIndex) {
2717
2789
  const indent = pretty ? " ".repeat(depth) : "";
2718
2790
  const cellIndent = pretty ? " ".repeat(depth + 1) : "";
2719
2791
  const nl = pretty ? "\n" : "";
@@ -2721,8 +2793,19 @@ function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, b
2721
2793
  for (let col = 0; col <= maxCol; col++) {
2722
2794
  const cell = cells.get(col);
2723
2795
  const content = cell ? renderLineContent(cell.ops, embedRenderers, blockHandlers, options) : "";
2724
- const alignStyle = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
2725
- html += `${cellIndent}<${cellTag}${alignStyle}>${content}</${cellTag}>${nl}`;
2796
+ let styleAttr = "";
2797
+ if (presentation) {
2798
+ styleAttr = buildTableCellStyleAttr({
2799
+ presentation,
2800
+ cellTag,
2801
+ colAlign: cell?.colAlign,
2802
+ headerRowCount,
2803
+ bodyRowIndex: cellTag === "td" ? bodyRowIndex : void 0
2804
+ });
2805
+ } else {
2806
+ styleAttr = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
2807
+ }
2808
+ html += `${cellIndent}<${cellTag}${styleAttr}>${content}</${cellTag}>${nl}`;
2726
2809
  }
2727
2810
  html += `${indent}</tr>${nl}`;
2728
2811
  return html;
@@ -5048,6 +5131,7 @@ export {
5048
5131
  isTextNode,
5049
5132
  isValidColor,
5050
5133
  isValidHexColor,
5134
+ isZebraBodyRow,
5051
5135
  italicFormat,
5052
5136
  kbdFormat,
5053
5137
  linkFormat,
@@ -5058,6 +5142,7 @@ export {
5058
5142
  nodeAdapter,
5059
5143
  normalizeDelta,
5060
5144
  preloadRemark,
5145
+ resolveTablePresentation,
5061
5146
  sanitizeDelta,
5062
5147
  sizeFormat,
5063
5148
  slugify,