@scrider/formatter 1.1.0 → 1.3.0
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 +31 -2
- package/dist/index.cjs +183 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -3
- package/dist/index.d.ts +146 -3
- package/dist/index.js +179 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Schema, conversion and block handlers for rich-text content. HTML, Markdown, san
|
|
|
8
8
|
|
|
9
9
|
## Key Features
|
|
10
10
|
|
|
11
|
-
- **Schema** — extensible format registry (
|
|
11
|
+
- **Schema** — extensible format registry (32 built-in formats: inline, block, embed — including `softBreak` for Shift+Enter line breaks)
|
|
12
12
|
- **HTML conversion** — `deltaToHtml()` / `htmlToDelta()` with DOM adapters (browser + Node.js)
|
|
13
13
|
- **Markdown conversion** — `deltaToMarkdown()` / `markdownToDelta()` (GFM, math, footnotes)
|
|
14
14
|
- **Block handlers** — tables, footnotes, alerts, columns, inline-box
|
|
@@ -65,7 +65,7 @@ const delta = htmlToDelta(html, { registry });
|
|
|
65
65
|
```typescript
|
|
66
66
|
import { Registry, createDefaultRegistry, BlockHandlerRegistry } from '@scrider/formatter';
|
|
67
67
|
|
|
68
|
-
const registry = createDefaultRegistry(); //
|
|
68
|
+
const registry = createDefaultRegistry(); // 32 built-in formats
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
### HTML Conversion
|
|
@@ -86,6 +86,35 @@ deltaToMarkdown(delta, options?) // Delta → Markdown string
|
|
|
86
86
|
await markdownToDelta(markdown, options?) // Markdown string → Delta (async)
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
### Soft Line Break (`softBreak` embed)
|
|
90
|
+
|
|
91
|
+
A `Shift+Enter` style line break that does **not** split the containing block. Stored in Delta as `{ insert: { softBreak: true } }` and round-tripped consistently across all three layers:
|
|
92
|
+
|
|
93
|
+
| Direction | Encoding |
|
|
94
|
+
|-----------|----------|
|
|
95
|
+
| HTML | `<br data-scrider-embed>` (the marker disambiguates it from the `<br>` placeholder inside an empty paragraph) |
|
|
96
|
+
| Markdown | `" \n"` by default — GFM hard break; switch to inline `<br>` via `deltaToMarkdown(delta, { softBreakStyle: 'html' })` |
|
|
97
|
+
|
|
98
|
+
`htmlToDelta` also recognises bare `<br>` between content (e.g. `<p>foo<br>bar</p>`) as a soft break, while keeping the leading / placeholder shapes (`<p><br></p>`, `<p><br>foo</p>`) as regular newlines for backward compatibility.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { Delta, deltaToHtml, deltaToMarkdown } from '@scrider/formatter';
|
|
102
|
+
|
|
103
|
+
const doc = new Delta()
|
|
104
|
+
.insert('hello')
|
|
105
|
+
.insert({ softBreak: true })
|
|
106
|
+
.insert('world\n');
|
|
107
|
+
|
|
108
|
+
deltaToHtml(doc);
|
|
109
|
+
// → '<p>hello<br data-scrider-embed>world</p>'
|
|
110
|
+
|
|
111
|
+
deltaToMarkdown(doc);
|
|
112
|
+
// → 'hello \nworld'
|
|
113
|
+
|
|
114
|
+
deltaToMarkdown(doc, { softBreakStyle: 'html' });
|
|
115
|
+
// → 'hello<br>world'
|
|
116
|
+
```
|
|
117
|
+
|
|
89
118
|
### Sanitization
|
|
90
119
|
|
|
91
120
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -63,6 +63,7 @@ __export(index_exports, {
|
|
|
63
63
|
dividerFormat: () => dividerFormat,
|
|
64
64
|
escapeHtml: () => escapeHtml,
|
|
65
65
|
extractBoxOpAttributes: () => extractBoxOpAttributes,
|
|
66
|
+
extractTableRegion: () => extractTableRegion,
|
|
66
67
|
fontFormat: () => fontFormat,
|
|
67
68
|
footnoteRefFormat: () => footnoteRefFormat,
|
|
68
69
|
footnotesBlockHandler: () => footnotesBlockHandler,
|
|
@@ -77,6 +78,7 @@ __export(index_exports, {
|
|
|
77
78
|
isAdapterAvailable: () => isAdapterAvailable,
|
|
78
79
|
isElement: () => isElement,
|
|
79
80
|
isRemarkAvailable: () => isRemarkAvailable,
|
|
81
|
+
isTableNewlineOp: () => isTableNewlineOp,
|
|
80
82
|
isTextNode: () => isTextNode,
|
|
81
83
|
isValidColor: () => isValidColor,
|
|
82
84
|
isValidHexColor: () => isValidHexColor,
|
|
@@ -89,10 +91,12 @@ __export(index_exports, {
|
|
|
89
91
|
markdownToDeltaSync: () => markdownToDeltaSync,
|
|
90
92
|
nodeAdapter: () => nodeAdapter,
|
|
91
93
|
normalizeDelta: () => normalizeDelta,
|
|
94
|
+
preloadRemark: () => preloadRemark,
|
|
92
95
|
sanitizeDelta: () => sanitizeDelta,
|
|
93
96
|
sizeFormat: () => sizeFormat,
|
|
94
97
|
slugify: () => slugify,
|
|
95
98
|
slugifyWithDedup: () => slugifyWithDedup,
|
|
99
|
+
softBreakFormat: () => softBreakFormat,
|
|
96
100
|
strikeFormat: () => strikeFormat,
|
|
97
101
|
subscriptFormat: () => subscriptFormat,
|
|
98
102
|
superscriptFormat: () => superscriptFormat,
|
|
@@ -2017,7 +2021,13 @@ var EMBED_RENDERERS = {
|
|
|
2017
2021
|
const id = typeof value === "string" ? value : String(value);
|
|
2018
2022
|
return `<sup class="footnote-ref"><a href="#fn-${escapeHtml(id)}" id="fnref-${escapeHtml(id)}">[${escapeHtml(id)}]</a></sup>`;
|
|
2019
2023
|
},
|
|
2020
|
-
divider: () => "<hr>"
|
|
2024
|
+
divider: () => "<hr>",
|
|
2025
|
+
// Soft line break (Shift+Enter equivalent). Emitted with an explicit
|
|
2026
|
+
// `data-scrider-embed` marker so that html-to-delta can distinguish this
|
|
2027
|
+
// embed from the placeholder `<br>` that appears inside an empty
|
|
2028
|
+
// paragraph (`<p><br></p>`) without relying solely on positional
|
|
2029
|
+
// heuristics. See `soft-break.ts` for the format definition.
|
|
2030
|
+
softBreak: () => "<br data-scrider-embed>"
|
|
2021
2031
|
};
|
|
2022
2032
|
var TAG_TO_INLINE_FORMAT = {
|
|
2023
2033
|
strong: { format: "bold", value: true },
|
|
@@ -2255,6 +2265,33 @@ var imageFormat = {
|
|
|
2255
2265
|
}
|
|
2256
2266
|
};
|
|
2257
2267
|
|
|
2268
|
+
// src/schema/formats/embed/soft-break.ts
|
|
2269
|
+
var softBreakFormat = {
|
|
2270
|
+
name: "softBreak",
|
|
2271
|
+
scope: "embed",
|
|
2272
|
+
normalize(value) {
|
|
2273
|
+
return !!value;
|
|
2274
|
+
},
|
|
2275
|
+
validate(value) {
|
|
2276
|
+
return value === true;
|
|
2277
|
+
},
|
|
2278
|
+
render() {
|
|
2279
|
+
return "<br data-scrider-embed>";
|
|
2280
|
+
},
|
|
2281
|
+
match(element) {
|
|
2282
|
+
if (element.tagName.toLowerCase() !== "br") return null;
|
|
2283
|
+
if (!element.hasAttribute("data-scrider-embed")) return null;
|
|
2284
|
+
return { value: true };
|
|
2285
|
+
}
|
|
2286
|
+
// NB: Markdown rendering is intentionally NOT implemented on the format
|
|
2287
|
+
// itself. The choice between `" \n"` (GFM spaces) and inline `<br>`
|
|
2288
|
+
// depends on the caller-provided `softBreakStyle` option on
|
|
2289
|
+
// `deltaToMarkdown`, so the converter handles it as a built-in special
|
|
2290
|
+
// case instead of going through `Format.toMarkdown`. The Markdown side
|
|
2291
|
+
// of the round-trip is symmetric: `markdownToDelta` recognises both
|
|
2292
|
+
// `break` AST nodes and inline `<br>` HTML and emits this embed.
|
|
2293
|
+
};
|
|
2294
|
+
|
|
2258
2295
|
// src/schema/formats/embed/video.ts
|
|
2259
2296
|
var videoFormat = {
|
|
2260
2297
|
name: "video",
|
|
@@ -2357,6 +2394,7 @@ var defaultEmbedFormats = [
|
|
|
2357
2394
|
videoFormat,
|
|
2358
2395
|
formulaFormat,
|
|
2359
2396
|
dividerFormat,
|
|
2397
|
+
softBreakFormat,
|
|
2360
2398
|
blockFormat,
|
|
2361
2399
|
footnoteRefFormat
|
|
2362
2400
|
];
|
|
@@ -3187,7 +3225,12 @@ function htmlToDelta(html, options = {}) {
|
|
|
3187
3225
|
return;
|
|
3188
3226
|
}
|
|
3189
3227
|
if (tagName === "br") {
|
|
3190
|
-
|
|
3228
|
+
const hasMarker = node.hasAttribute("data-scrider-embed");
|
|
3229
|
+
if (hasMarker || hasMeaningfulPrevSibling(node)) {
|
|
3230
|
+
context.pushEmbed({ softBreak: true });
|
|
3231
|
+
} else {
|
|
3232
|
+
context.pushNewline();
|
|
3233
|
+
}
|
|
3191
3234
|
return;
|
|
3192
3235
|
}
|
|
3193
3236
|
processChildren(node);
|
|
@@ -3621,6 +3664,23 @@ function normalizeText(text, pendingText, atLineStart) {
|
|
|
3621
3664
|
}
|
|
3622
3665
|
return text;
|
|
3623
3666
|
}
|
|
3667
|
+
function hasMeaningfulPrevSibling(brNode) {
|
|
3668
|
+
const parent = brNode.parentNode;
|
|
3669
|
+
if (!parent) return false;
|
|
3670
|
+
const children = parent.childNodes;
|
|
3671
|
+
for (let i = 0; i < children.length; i++) {
|
|
3672
|
+
const child = children[i];
|
|
3673
|
+
if (!child) continue;
|
|
3674
|
+
if (child === brNode) return false;
|
|
3675
|
+
if (child.nodeType === NODE_TYPE.TEXT_NODE) {
|
|
3676
|
+
const text = child.textContent ?? "";
|
|
3677
|
+
if (text.trim().length > 0) return true;
|
|
3678
|
+
} else if (isElement(child)) {
|
|
3679
|
+
return true;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
return false;
|
|
3683
|
+
}
|
|
3624
3684
|
function findTagHandler(handlers, element, tagName) {
|
|
3625
3685
|
const className = element.getAttribute("class");
|
|
3626
3686
|
if (className) {
|
|
@@ -3756,7 +3816,9 @@ function deltaToMarkdown(delta, options = {}) {
|
|
|
3756
3816
|
embedRenderers = {},
|
|
3757
3817
|
blockHandlers,
|
|
3758
3818
|
prettyHtml = false,
|
|
3759
|
-
registry
|
|
3819
|
+
registry,
|
|
3820
|
+
softBreakStyle = "spaces",
|
|
3821
|
+
trimTrailingNewlines = false
|
|
3760
3822
|
} = options;
|
|
3761
3823
|
const useLatexDelimiters = mathSyntax === "latex";
|
|
3762
3824
|
const lines = splitIntoLines2(delta.ops);
|
|
@@ -3772,7 +3834,9 @@ function deltaToMarkdown(delta, options = {}) {
|
|
|
3772
3834
|
const isBlockquote = !!attrs.blockquote;
|
|
3773
3835
|
if (typeof attrs["table-row"] === "number" && typeof attrs["table-col"] === "number") {
|
|
3774
3836
|
const tableLines = collectTableLines2(lines, i);
|
|
3775
|
-
result.push(
|
|
3837
|
+
result.push(
|
|
3838
|
+
renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters, registry, softBreakStyle)
|
|
3839
|
+
);
|
|
3776
3840
|
result.push("");
|
|
3777
3841
|
i += tableLines.length - 1;
|
|
3778
3842
|
lastListType = null;
|
|
@@ -3788,7 +3852,16 @@ function deltaToMarkdown(delta, options = {}) {
|
|
|
3788
3852
|
const codeLines = collectCodeBlock(lines, i);
|
|
3789
3853
|
const language = getCodeBlockLanguage2(attrs);
|
|
3790
3854
|
const code = codeLines.map(
|
|
3791
|
-
(l) => renderLineContent2(
|
|
3855
|
+
(l) => renderLineContent2(
|
|
3856
|
+
l.ops,
|
|
3857
|
+
embedRenderers,
|
|
3858
|
+
true,
|
|
3859
|
+
false,
|
|
3860
|
+
blockHandlers,
|
|
3861
|
+
false,
|
|
3862
|
+
registry,
|
|
3863
|
+
softBreakStyle
|
|
3864
|
+
)
|
|
3792
3865
|
).join("\n");
|
|
3793
3866
|
if (language === "math") {
|
|
3794
3867
|
if (mathBlock === false) {
|
|
@@ -3838,7 +3911,8 @@ ${code}
|
|
|
3838
3911
|
useLatexDelimiters,
|
|
3839
3912
|
blockHandlers,
|
|
3840
3913
|
prettyHtml,
|
|
3841
|
-
registry
|
|
3914
|
+
registry,
|
|
3915
|
+
softBreakStyle
|
|
3842
3916
|
);
|
|
3843
3917
|
if (!content && !hasBlockFormat(attrs)) {
|
|
3844
3918
|
result.push(preserveEmptyLines ? "<br>" : "");
|
|
@@ -3854,7 +3928,8 @@ ${code}
|
|
|
3854
3928
|
lastIndent = indent;
|
|
3855
3929
|
lastWasBlockquote = isBlockquote;
|
|
3856
3930
|
}
|
|
3857
|
-
|
|
3931
|
+
const md = result.join("\n");
|
|
3932
|
+
return trimTrailingNewlines ? md.replace(/\n+$/, "") : md;
|
|
3858
3933
|
}
|
|
3859
3934
|
function hasBlockFormat(attrs) {
|
|
3860
3935
|
return !!(attrs.header || attrs.list || attrs.blockquote || attrs["code-block"] || attrs.align || attrs.indent);
|
|
@@ -3939,7 +4014,7 @@ function collectTableLines2(lines, startIndex) {
|
|
|
3939
4014
|
}
|
|
3940
4015
|
return result;
|
|
3941
4016
|
}
|
|
3942
|
-
function renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters = false, registry) {
|
|
4017
|
+
function renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters = false, registry, softBreakStyle = "spaces") {
|
|
3943
4018
|
const rows = /* @__PURE__ */ new Map();
|
|
3944
4019
|
for (const line of tableLines) {
|
|
3945
4020
|
const attrs = line.attributes;
|
|
@@ -3976,7 +4051,9 @@ function renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters = fa
|
|
|
3976
4051
|
const mdLines = [];
|
|
3977
4052
|
if (headerRows.length > 0) {
|
|
3978
4053
|
for (const [, row] of headerRows) {
|
|
3979
|
-
mdLines.push(
|
|
4054
|
+
mdLines.push(
|
|
4055
|
+
renderMdRow(row.cells, maxCol, embedRenderers, useLatexDelimiters, registry, softBreakStyle)
|
|
4056
|
+
);
|
|
3980
4057
|
}
|
|
3981
4058
|
mdLines.push(renderMdSeparator(maxCol, colAligns));
|
|
3982
4059
|
} else {
|
|
@@ -3984,15 +4061,19 @@ function renderMarkdownTable(tableLines, embedRenderers, useLatexDelimiters = fa
|
|
|
3984
4061
|
for (let col = 0; col <= maxCol; col++) {
|
|
3985
4062
|
emptyRow.set(col, { ops: [] });
|
|
3986
4063
|
}
|
|
3987
|
-
mdLines.push(
|
|
4064
|
+
mdLines.push(
|
|
4065
|
+
renderMdRow(emptyRow, maxCol, embedRenderers, useLatexDelimiters, registry, softBreakStyle)
|
|
4066
|
+
);
|
|
3988
4067
|
mdLines.push(renderMdSeparator(maxCol, colAligns));
|
|
3989
4068
|
}
|
|
3990
4069
|
for (const [, row] of bodyRows) {
|
|
3991
|
-
mdLines.push(
|
|
4070
|
+
mdLines.push(
|
|
4071
|
+
renderMdRow(row.cells, maxCol, embedRenderers, useLatexDelimiters, registry, softBreakStyle)
|
|
4072
|
+
);
|
|
3992
4073
|
}
|
|
3993
4074
|
return mdLines.join("\n");
|
|
3994
4075
|
}
|
|
3995
|
-
function renderMdRow(cells, maxCol, embedRenderers, useLatexDelimiters = false, registry) {
|
|
4076
|
+
function renderMdRow(cells, maxCol, embedRenderers, useLatexDelimiters = false, registry, softBreakStyle = "spaces") {
|
|
3996
4077
|
const parts = [];
|
|
3997
4078
|
for (let col = 0; col <= maxCol; col++) {
|
|
3998
4079
|
const cell = cells.get(col);
|
|
@@ -4003,7 +4084,10 @@ function renderMdRow(cells, maxCol, embedRenderers, useLatexDelimiters = false,
|
|
|
4003
4084
|
useLatexDelimiters,
|
|
4004
4085
|
void 0,
|
|
4005
4086
|
false,
|
|
4006
|
-
registry
|
|
4087
|
+
registry,
|
|
4088
|
+
softBreakStyle,
|
|
4089
|
+
true
|
|
4090
|
+
// inTableCell — softBreak must use <br>, never " \n"
|
|
4007
4091
|
) : "";
|
|
4008
4092
|
parts.push(content.replace(/\|/g, "\\|"));
|
|
4009
4093
|
}
|
|
@@ -4032,7 +4116,7 @@ function getCodeBlockLanguage2(attributes) {
|
|
|
4032
4116
|
}
|
|
4033
4117
|
return void 0;
|
|
4034
4118
|
}
|
|
4035
|
-
function renderLineContent2(ops, embedRenderers, inCodeBlock, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry) {
|
|
4119
|
+
function renderLineContent2(ops, embedRenderers, inCodeBlock, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry, softBreakStyle = "spaces", inTableCell = false) {
|
|
4036
4120
|
let result = "";
|
|
4037
4121
|
for (const op of ops) {
|
|
4038
4122
|
const attrs = op.attributes;
|
|
@@ -4052,7 +4136,9 @@ function renderLineContent2(ops, embedRenderers, inCodeBlock, useLatexDelimiters
|
|
|
4052
4136
|
useLatexDelimiters,
|
|
4053
4137
|
blockHandlers,
|
|
4054
4138
|
prettyHtml,
|
|
4055
|
-
registry
|
|
4139
|
+
registry,
|
|
4140
|
+
softBreakStyle,
|
|
4141
|
+
inTableCell
|
|
4056
4142
|
);
|
|
4057
4143
|
}
|
|
4058
4144
|
}
|
|
@@ -4086,13 +4172,17 @@ function renderInlineText2(text, attributes) {
|
|
|
4086
4172
|
}
|
|
4087
4173
|
return result;
|
|
4088
4174
|
}
|
|
4089
|
-
function renderEmbed2(embed, attributes, customRenderers, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry) {
|
|
4175
|
+
function renderEmbed2(embed, attributes, customRenderers, useLatexDelimiters = false, blockHandlers, prettyHtml = false, registry, softBreakStyle = "spaces", inTableCell = false) {
|
|
4090
4176
|
const entries = Object.entries(embed);
|
|
4091
4177
|
if (entries.length === 0) return "";
|
|
4092
4178
|
const firstEntry = entries[0];
|
|
4093
4179
|
if (!firstEntry) return "";
|
|
4094
4180
|
const embedType = firstEntry[0];
|
|
4095
4181
|
const embedValue = firstEntry[1];
|
|
4182
|
+
if (embedType === "softBreak") {
|
|
4183
|
+
if (inTableCell) return "<br>";
|
|
4184
|
+
return softBreakStyle === "html" ? "<br>" : " \n";
|
|
4185
|
+
}
|
|
4096
4186
|
if (embedType === "block" && blockHandlers) {
|
|
4097
4187
|
const blockData = embedValue;
|
|
4098
4188
|
if (blockData && typeof blockData.type === "string") {
|
|
@@ -4243,6 +4333,8 @@ var remarkGfm = null;
|
|
|
4243
4333
|
var remarkMath = null;
|
|
4244
4334
|
var unified = null;
|
|
4245
4335
|
function isRemarkAvailable() {
|
|
4336
|
+
if (unified && remarkParse) return true;
|
|
4337
|
+
if (typeof require === "undefined") return false;
|
|
4246
4338
|
try {
|
|
4247
4339
|
require.resolve("unified");
|
|
4248
4340
|
require.resolve("remark-parse");
|
|
@@ -4251,8 +4343,8 @@ function isRemarkAvailable() {
|
|
|
4251
4343
|
return false;
|
|
4252
4344
|
}
|
|
4253
4345
|
}
|
|
4254
|
-
async function
|
|
4255
|
-
if (unified) return;
|
|
4346
|
+
async function preloadRemark() {
|
|
4347
|
+
if (unified && remarkParse && remarkGfm) return true;
|
|
4256
4348
|
try {
|
|
4257
4349
|
const [unifiedMod, remarkParseMod, remarkGfmMod] = await Promise.all([
|
|
4258
4350
|
import("unified"),
|
|
@@ -4263,15 +4355,16 @@ async function loadRemark() {
|
|
|
4263
4355
|
remarkParse = remarkParseMod.default;
|
|
4264
4356
|
remarkGfm = remarkGfmMod.default;
|
|
4265
4357
|
} catch {
|
|
4266
|
-
|
|
4267
|
-
"remark is not installed. Install with: pnpm add unified remark-parse remark-gfm"
|
|
4268
|
-
);
|
|
4358
|
+
return false;
|
|
4269
4359
|
}
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4360
|
+
if (!remarkMath) {
|
|
4361
|
+
try {
|
|
4362
|
+
const remarkMathMod = await import("remark-math");
|
|
4363
|
+
remarkMath = remarkMathMod.default;
|
|
4364
|
+
} catch {
|
|
4365
|
+
}
|
|
4274
4366
|
}
|
|
4367
|
+
return true;
|
|
4275
4368
|
}
|
|
4276
4369
|
function preprocessMarkdown(markdown, mathBlock) {
|
|
4277
4370
|
markdown = markdown.replace(/\\\((.+?)\\\)/g, (_match, content) => `$${content}$`);
|
|
@@ -4295,9 +4388,11 @@ async function markdownToDelta(markdown, options = {}) {
|
|
|
4295
4388
|
nodeHandlers = {}
|
|
4296
4389
|
} = options;
|
|
4297
4390
|
markdown = preprocessMarkdown(markdown, mathBlock);
|
|
4298
|
-
await
|
|
4299
|
-
if (!unified || !remarkParse) {
|
|
4300
|
-
throw new Error(
|
|
4391
|
+
const loaded = await preloadRemark();
|
|
4392
|
+
if (!loaded || !unified || !remarkParse) {
|
|
4393
|
+
throw new Error(
|
|
4394
|
+
"remark is not installed. Install with: pnpm add unified remark-parse remark-gfm"
|
|
4395
|
+
);
|
|
4301
4396
|
}
|
|
4302
4397
|
let processor = unified().use(remarkParse);
|
|
4303
4398
|
if (gfm && remarkGfm) {
|
|
@@ -4326,6 +4421,11 @@ function markdownToDeltaSync(markdown, options = {}) {
|
|
|
4326
4421
|
} = options;
|
|
4327
4422
|
markdown = preprocessMarkdown(markdown, mathBlock);
|
|
4328
4423
|
if (!unified || !remarkParse) {
|
|
4424
|
+
if (typeof require === "undefined") {
|
|
4425
|
+
throw new Error(
|
|
4426
|
+
"markdownToDeltaSync requires remark to be preloaded in this environment. `require()` is not available (likely browser ESM). Call `await preloadRemark()` once on application startup before using the sync API, or use the async `markdownToDelta()` instead."
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4329
4429
|
try {
|
|
4330
4430
|
const unifiedMod = require("unified");
|
|
4331
4431
|
const remarkParseMod = require("remark-parse");
|
|
@@ -4571,7 +4671,7 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
|
|
|
4571
4671
|
footnoteDefinitions.set(node.identifier ?? "", node);
|
|
4572
4672
|
break;
|
|
4573
4673
|
case "break":
|
|
4574
|
-
context.
|
|
4674
|
+
context.pushEmbed({ softBreak: true });
|
|
4575
4675
|
break;
|
|
4576
4676
|
case "html": {
|
|
4577
4677
|
const htmlContent = node.value ?? "";
|
|
@@ -4877,8 +4977,8 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
|
|
|
4877
4977
|
context.pushNewline();
|
|
4878
4978
|
return;
|
|
4879
4979
|
}
|
|
4880
|
-
if (/^<br\
|
|
4881
|
-
context.
|
|
4980
|
+
if (/^<br\b[^>]*\/?>$/i.test(html)) {
|
|
4981
|
+
context.pushEmbed({ softBreak: true });
|
|
4882
4982
|
return;
|
|
4883
4983
|
}
|
|
4884
4984
|
}
|
|
@@ -4914,6 +5014,54 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
|
|
|
4914
5014
|
}
|
|
4915
5015
|
return delta;
|
|
4916
5016
|
}
|
|
5017
|
+
|
|
5018
|
+
// src/conversion/markdown/table-region.ts
|
|
5019
|
+
var import_delta11 = require("@scrider/delta");
|
|
5020
|
+
function isTableNewlineOp(op) {
|
|
5021
|
+
if (!op || !(0, import_delta11.isInsert)(op) || !(0, import_delta11.isTextInsert)(op)) return false;
|
|
5022
|
+
if (!op.insert.includes("\n")) return false;
|
|
5023
|
+
return !!op.attributes && "table-row" in op.attributes;
|
|
5024
|
+
}
|
|
5025
|
+
function extractTableRegion(ops, hintOpIdx) {
|
|
5026
|
+
if (hintOpIdx < 0 || hintOpIdx >= ops.length) return null;
|
|
5027
|
+
let probeIdx = -1;
|
|
5028
|
+
for (let i = hintOpIdx; i < ops.length; i++) {
|
|
5029
|
+
const op = ops[i];
|
|
5030
|
+
if (!op || !(0, import_delta11.isInsert)(op)) continue;
|
|
5031
|
+
if ((0, import_delta11.isTextInsert)(op) && op.insert.includes("\n")) {
|
|
5032
|
+
probeIdx = i;
|
|
5033
|
+
break;
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
if (probeIdx < 0) return null;
|
|
5037
|
+
if (!isTableNewlineOp(ops[probeIdx])) return null;
|
|
5038
|
+
let endOpIdx = probeIdx;
|
|
5039
|
+
for (let i = probeIdx + 1; i < ops.length; i++) {
|
|
5040
|
+
const op = ops[i];
|
|
5041
|
+
if (!op || !(0, import_delta11.isInsert)(op)) break;
|
|
5042
|
+
if ((0, import_delta11.isTextInsert)(op) && op.insert.includes("\n")) {
|
|
5043
|
+
if (isTableNewlineOp(op)) {
|
|
5044
|
+
endOpIdx = i;
|
|
5045
|
+
} else {
|
|
5046
|
+
break;
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
let startOpIdx = 0;
|
|
5051
|
+
for (let i = probeIdx - 1; i >= 0; i--) {
|
|
5052
|
+
const op = ops[i];
|
|
5053
|
+
if (!op || !(0, import_delta11.isInsert)(op)) {
|
|
5054
|
+
startOpIdx = i + 1;
|
|
5055
|
+
break;
|
|
5056
|
+
}
|
|
5057
|
+
if ((0, import_delta11.isTextInsert)(op) && op.insert.includes("\n") && !isTableNewlineOp(op)) {
|
|
5058
|
+
startOpIdx = i + 1;
|
|
5059
|
+
break;
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
const regionOps = ops.slice(startOpIdx, endOpIdx + 1);
|
|
5063
|
+
return { startOpIdx, endOpIdx, ops: regionOps };
|
|
5064
|
+
}
|
|
4917
5065
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4918
5066
|
0 && (module.exports = {
|
|
4919
5067
|
ALERT_TYPES,
|
|
@@ -4948,6 +5096,7 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
|
|
|
4948
5096
|
dividerFormat,
|
|
4949
5097
|
escapeHtml,
|
|
4950
5098
|
extractBoxOpAttributes,
|
|
5099
|
+
extractTableRegion,
|
|
4951
5100
|
fontFormat,
|
|
4952
5101
|
footnoteRefFormat,
|
|
4953
5102
|
footnotesBlockHandler,
|
|
@@ -4962,6 +5111,7 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
|
|
|
4962
5111
|
isAdapterAvailable,
|
|
4963
5112
|
isElement,
|
|
4964
5113
|
isRemarkAvailable,
|
|
5114
|
+
isTableNewlineOp,
|
|
4965
5115
|
isTextNode,
|
|
4966
5116
|
isValidColor,
|
|
4967
5117
|
isValidHexColor,
|
|
@@ -4974,10 +5124,12 @@ function astToDelta(tree, customHandlers, mathBlock, mermaidBlock, plantumlBlock
|
|
|
4974
5124
|
markdownToDeltaSync,
|
|
4975
5125
|
nodeAdapter,
|
|
4976
5126
|
normalizeDelta,
|
|
5127
|
+
preloadRemark,
|
|
4977
5128
|
sanitizeDelta,
|
|
4978
5129
|
sizeFormat,
|
|
4979
5130
|
slugify,
|
|
4980
5131
|
slugifyWithDedup,
|
|
5132
|
+
softBreakFormat,
|
|
4981
5133
|
strikeFormat,
|
|
4982
5134
|
subscriptFormat,
|
|
4983
5135
|
superscriptFormat,
|