@stll/folio-core 0.55.0 → 0.56.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/dist/ai-edits/apply.js +169 -38
- package/dist/ai-edits/headless.js +2 -2
- package/dist/ai-edits/types.d.ts +7 -1
- package/dist/compare/compare.js +1 -0
- package/dist/compare/reproducible-package.d.ts +0 -17
- package/dist/compare/reproducible-package.js +12 -18
- package/dist/controller/canonicalClipboard.d.ts +23 -0
- package/dist/controller/canonicalClipboard.js +476 -0
- package/dist/controller/canonicalClipboardResources.d.ts +21 -0
- package/dist/controller/canonicalClipboardResources.js +382 -0
- package/dist/controller/canonicalComposition.d.ts +20 -3
- package/dist/controller/canonicalComposition.js +166 -38
- package/dist/controller/canonicalInlineProjection.d.ts +113 -0
- package/dist/controller/canonicalInlineProjection.js +393 -0
- package/dist/controller/canonicalInput.d.ts +13 -4
- package/dist/controller/canonicalInput.js +52 -13
- package/dist/controller/canonicalInputTimer.d.ts +7 -0
- package/dist/controller/canonicalInputTimer.js +11 -0
- package/dist/controller/canonicalPublicOperations.d.ts +48 -0
- package/dist/controller/canonicalPublicOperations.js +556 -0
- package/dist/controller/canonicalSession.d.ts +24 -6
- package/dist/controller/canonicalSession.js +259 -111
- package/dist/controller/canonicalStoryEditor.d.ts +5 -3
- package/dist/controller/canonicalStoryEditor.js +7 -2
- package/dist/controller/canonicalStructure.js +57 -4
- package/dist/controller/canonicalTocSelection.d.ts +11 -0
- package/dist/controller/canonicalTocSelection.js +43 -0
- package/dist/controller/folioEditor.d.ts +2 -9
- package/dist/controller/folioEditor.js +5 -4
- package/dist/controller/headerFooterEditorManager.d.ts +2 -1
- package/dist/controller/headerFooterEditorManager.js +8 -7
- package/dist/controller/hiddenEditorApi.d.ts +12 -2
- package/dist/controller/hiddenEditorApi.js +4 -0
- package/dist/controller/hiddenEditorManager.d.ts +3 -1
- package/dist/controller/hiddenEditorManager.js +216 -34
- package/dist/controller/noteEditorManager.d.ts +2 -1
- package/dist/controller/noteEditorManager.js +8 -7
- package/dist/document-operations.d.ts +8 -1
- package/dist/document-operations.js +6 -4
- package/dist/docx/blockPlainText.d.ts +1 -1
- package/dist/docx/blockPlainText.js +11 -3
- package/dist/docx/canonicalResourceSave.d.ts +16 -0
- package/dist/docx/canonicalResourceSave.js +27 -0
- package/dist/docx/canonicalSave.d.ts +29 -0
- package/dist/docx/canonicalSave.js +101 -0
- package/dist/docx/canonicalStoryRepack.d.ts +1 -1
- package/dist/docx/canonicalStoryRepack.js +38 -5
- package/dist/docx/documentParser.d.ts +1 -13
- package/dist/docx/documentParser.js +1 -47
- package/dist/docx/ensureParaIds.js +10 -4
- package/dist/docx/footnoteParser.d.ts +1 -11
- package/dist/docx/footnoteParser.js +1 -16
- package/dist/docx/headerFooterParser.d.ts +1 -9
- package/dist/docx/headerFooterParser.js +1 -12
- package/dist/docx/headerFooterVerbatim.d.ts +26 -1
- package/dist/docx/headerFooterVerbatim.js +81 -3
- package/dist/docx/imageParser.d.ts +13 -1
- package/dist/docx/imageParser.js +20 -11
- package/dist/docx/metadataPrivacy.js +6 -1
- package/dist/docx/packageParts.js +9 -3
- package/dist/docx/paragraphPropertySource.js +2 -1
- package/dist/docx/parser.js +2 -1
- package/dist/docx/removeHeaderFooterParts.js +17 -6
- package/dist/docx/rezip.d.ts +49 -5
- package/dist/docx/rezip.js +745 -229
- package/dist/docx/saveDiagnostics.d.ts +11 -0
- package/dist/docx/selectiveSave.d.ts +2 -1
- package/dist/docx/selectiveSave.js +32 -3
- package/dist/docx/selectiveXmlPatch.js +7 -5
- package/dist/docx/serializer/documentSerializer.d.ts +9 -2
- package/dist/docx/serializer/documentSerializer.js +29 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
- package/dist/docx/storyBlockReplay.d.ts +21 -1
- package/dist/docx/storyBlockReplay.js +113 -9
- package/dist/docx/storyPlainText.d.ts +20 -0
- package/dist/docx/storyPlainText.js +23 -0
- package/dist/docx/structuralXmlPatch.js +10 -22
- package/dist/docx/styleParser.d.ts +5 -1
- package/dist/docx/styleParser.js +1 -1
- package/dist/docx/tableParser.d.ts +1 -8
- package/dist/docx/tableParser.js +1 -28
- package/dist/docx/textBoxParser.d.ts +1 -5
- package/dist/docx/textBoxParser.js +1 -20
- package/dist/docx/unzip.js +6 -1
- package/dist/internal/acceptedBlockProjection.d.ts +12 -0
- package/dist/internal/acceptedBlockProjection.js +45 -0
- package/dist/managers/DocumentLoaderManager.js +2 -1
- package/dist/markdown/escape.d.ts +3 -1
- package/dist/markdown/escape.js +9 -2
- package/dist/markdown/renderBlock.js +5 -120
- package/dist/markdown/renderParagraph.js +4 -2
- package/dist/markdown/renderRuns.d.ts +7 -1
- package/dist/markdown/renderRuns.js +32 -13
- package/dist/prosemirror/canonicalCommands.d.ts +28 -4
- package/dist/prosemirror/canonicalCommands.js +11 -4
- package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
- package/dist/prosemirror/canonicalSelectionRange.js +15 -0
- package/dist/prosemirror/clipboardIntent.d.ts +7 -0
- package/dist/prosemirror/clipboardIntent.js +9 -0
- package/dist/prosemirror/commands/pageBreak.js +2 -2
- package/dist/prosemirror/commands/pastePlainText.js +2 -1
- package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
- package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
- package/dist/prosemirror/conversion/toProseDoc.js +5 -4
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
- package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
- package/dist/prosemirror/insertOperations.js +2 -1
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
- package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
- package/dist/prosemirror/plugins/suggestionMode.js +7 -5
- package/dist/types/canonicalCapabilities.d.ts +215 -0
- package/dist/types/canonicalCapabilities.js +212 -0
- package/dist/types/canonicalSave.d.ts +11 -0
- package/dist/types/canonicalSave.js +0 -0
- package/dist/types/docxSerialization.d.ts +14 -0
- package/dist/types/docxSerialization.js +7 -0
- package/package.json +4 -4
package/dist/docx/tableParser.js
CHANGED
|
@@ -1406,33 +1406,6 @@ function isCellHorizontallyMerged(cell) {
|
|
|
1406
1406
|
return (cell.formatting?.gridSpan ?? 1) > 1;
|
|
1407
1407
|
}
|
|
1408
1408
|
/**
|
|
1409
|
-
* Get the plain text content of a table
|
|
1410
|
-
*
|
|
1411
|
-
* @param table - The table to extract text from
|
|
1412
|
-
* @returns Plain text content
|
|
1413
|
-
*/
|
|
1414
|
-
function getTableText(table) {
|
|
1415
|
-
const rows = [];
|
|
1416
|
-
for (const row of table.rows) {
|
|
1417
|
-
const cells = [];
|
|
1418
|
-
for (const cell of row.cells) {
|
|
1419
|
-
const cellText = cell.content.filter((c) => c.type === "paragraph").map((p) => getParagraphText(p)).join("\n");
|
|
1420
|
-
cells.push(cellText);
|
|
1421
|
-
}
|
|
1422
|
-
rows.push(cells.join(" "));
|
|
1423
|
-
}
|
|
1424
|
-
return rows.join("\n");
|
|
1425
|
-
}
|
|
1426
|
-
/**
|
|
1427
|
-
* Helper to get paragraph text (simplified)
|
|
1428
|
-
*/
|
|
1429
|
-
function getParagraphText(para) {
|
|
1430
|
-
return para.content.filter((c) => "content" in c).flatMap((run) => {
|
|
1431
|
-
if (!("content" in run) || !Array.isArray(run.content)) return [];
|
|
1432
|
-
return run.content.filter((c) => typeof c === "object" && c !== null && "type" in c && c.type === "text" && "text" in c).map((c) => c.text);
|
|
1433
|
-
}).join("");
|
|
1434
|
-
}
|
|
1435
|
-
/**
|
|
1436
1409
|
* Check if table has header row
|
|
1437
1410
|
*
|
|
1438
1411
|
* @param table - The table to check
|
|
@@ -1461,4 +1434,4 @@ function isFloatingTable(table) {
|
|
|
1461
1434
|
return table.formatting?.floating !== void 0;
|
|
1462
1435
|
}
|
|
1463
1436
|
//#endregion
|
|
1464
|
-
export { CELL_CONTENT_HANDLERS, MAX_TABLE_COLUMNS, ROW_CONTENT_HANDLERS, TABLE_CONTENT_HANDLERS, getHeaderRows, getTableColumnCount, getTableRowCount,
|
|
1437
|
+
export { CELL_CONTENT_HANDLERS, MAX_TABLE_COLUMNS, ROW_CONTENT_HANDLERS, TABLE_CONTENT_HANDLERS, getHeaderRows, getTableColumnCount, getTableRowCount, hasHeaderRow, isCellHorizontallyMerged, isCellMergeContinuation, isCellMergeStart, isFloatingTable, isTableCellMergeRevisionContinuation, isTableCellMergeRevisionValue, parseCellMargins, parseConditionalFormatStyle, parseFloatingTableProperties, parseTable, parseTableBorders, parseTableCell, parseTableCellProperties, parseTableGrid, parseTableLook, parseTableMeasurement, parseTableProperties, parseTablePropertyExceptions, parseTableRow, parseTableRowProperties };
|
|
@@ -103,10 +103,6 @@ declare function hasTextBoxOutline(textBox: document_d_exports.TextBox): boolean
|
|
|
103
103
|
* Check if text box has content
|
|
104
104
|
*/
|
|
105
105
|
declare function hasTextBoxContent(textBox: document_d_exports.TextBox): boolean;
|
|
106
|
-
/**
|
|
107
|
-
* Get plain text from text box (helper for search/indexing)
|
|
108
|
-
*/
|
|
109
|
-
declare function getTextBoxText(textBox: document_d_exports.TextBox): string;
|
|
110
106
|
/**
|
|
111
107
|
* Resolve fill color to CSS color string
|
|
112
108
|
*/
|
|
@@ -151,4 +147,4 @@ type ScanRunForTextBoxDrawingsOptions = {
|
|
|
151
147
|
};
|
|
152
148
|
declare const scanRunForTextBoxDrawings: ({ xmlRun, claimedByRunParser }: ScanRunForTextBoxDrawingsOptions) => TextBoxRunScan;
|
|
153
149
|
//#endregion
|
|
154
|
-
export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxDrawing, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx,
|
|
150
|
+
export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxDrawing, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
|
|
@@ -285,25 +285,6 @@ function hasTextBoxContent(textBox) {
|
|
|
285
285
|
return textBox.content.length > 0;
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
288
|
-
* Get plain text from text box (helper for search/indexing)
|
|
289
|
-
*/
|
|
290
|
-
function getTextBoxText(textBox) {
|
|
291
|
-
return textBox.content.map(getTextBoxBlockText).join("\n");
|
|
292
|
-
}
|
|
293
|
-
const getTextBoxBlockText = (block) => {
|
|
294
|
-
if (block.type === "paragraph") {
|
|
295
|
-
const runTexts = [];
|
|
296
|
-
for (const item of block.content) {
|
|
297
|
-
if (item.type !== "run") continue;
|
|
298
|
-
for (const content of item.content) if (content.type === "text") runTexts.push(content.text);
|
|
299
|
-
}
|
|
300
|
-
return runTexts.join("");
|
|
301
|
-
}
|
|
302
|
-
if (block.type === "table") return block.rows.map((row) => row.cells.map((cell) => cell.content.map(getTextBoxBlockText).join("\n")).join(" ")).join("\n");
|
|
303
|
-
if (block.type !== "blockSdt" && block.type !== "blockCustomXml") return "";
|
|
304
|
-
return block.content.map(getTextBoxBlockText).join("\n");
|
|
305
|
-
};
|
|
306
|
-
/**
|
|
307
288
|
* Resolve fill color to CSS color string
|
|
308
289
|
*/
|
|
309
290
|
function resolveTextBoxFillColor(textBox) {
|
|
@@ -380,4 +361,4 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
|
|
|
380
361
|
};
|
|
381
362
|
};
|
|
382
363
|
//#endregion
|
|
383
|
-
export { extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx,
|
|
364
|
+
export { extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
|
package/dist/docx/unzip.js
CHANGED
|
@@ -5,6 +5,7 @@ import { openDocxBuffer } from "./encryption/openEncryptedDocx.js";
|
|
|
5
5
|
import { RASTER_MIME_TYPES, detectRasterMimeType } from "./rasterMime.js";
|
|
6
6
|
import { decodeXmlBytes } from "./xmlEncoding.js";
|
|
7
7
|
import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits, createXmlPackageBudget } from "./xmlResourceLimits.js";
|
|
8
|
+
import { writeZipPart } from "@stll/docx-core/zip";
|
|
8
9
|
import JSZip from "jszip";
|
|
9
10
|
//#region src/docx/unzip.ts
|
|
10
11
|
/**
|
|
@@ -282,7 +283,11 @@ const replaceRawDocxXmlParts = async (content, parts) => {
|
|
|
282
283
|
lowerPath: path.toLowerCase(),
|
|
283
284
|
content: xmlContent
|
|
284
285
|
});
|
|
285
|
-
|
|
286
|
+
writeZipPart({
|
|
287
|
+
zip: content.originalZip,
|
|
288
|
+
path,
|
|
289
|
+
data: xmlContent
|
|
290
|
+
});
|
|
286
291
|
identitiesChanged = true;
|
|
287
292
|
}
|
|
288
293
|
if (identitiesChanged) content.originalBuffer = await content.originalZip.generateAsync({
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
//#region src/internal/acceptedBlockProjection.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Read a model story through the same structural resolver as Accept All and
|
|
5
|
+
* the editor's No Markup view. The authored model is never changed.
|
|
6
|
+
*/
|
|
7
|
+
declare const projectAcceptedBlocks: (blocks: readonly document_d_exports.BlockContent[], pkg: document_d_exports.DocxPackage | undefined) => {
|
|
8
|
+
blocks: readonly document_d_exports.BlockContent[];
|
|
9
|
+
completeness: string;
|
|
10
|
+
} | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { projectAcceptedBlocks };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getCachedNumberingMap } from "../docx/numberingParser.js";
|
|
2
|
+
import { proseDocToBlocks } from "../prosemirror/conversion/fromProseDoc.js";
|
|
3
|
+
import { headerFooterToProseDoc } from "../prosemirror/conversion/toProseDoc.js";
|
|
4
|
+
import { createStyleEngine } from "../style-engine/styleEngine.js";
|
|
5
|
+
import { resolveWholeStory } from "./wholeStoryRevisionResolution.js";
|
|
6
|
+
import { panic } from "better-result";
|
|
7
|
+
//#region src/internal/acceptedBlockProjection.ts
|
|
8
|
+
/** Inline revisions alone are already handled by the Markdown run renderer. */
|
|
9
|
+
const hasBlockRevision = (blocks) => blocks.some((block) => {
|
|
10
|
+
switch (block.type) {
|
|
11
|
+
case "paragraph": return block.pPrMark !== void 0;
|
|
12
|
+
case "table": return block.rows.some((row) => row.structuralChange !== void 0 || row.cells.some((cell) => cell.structuralChange !== void 0 || hasBlockRevision(cell.content)));
|
|
13
|
+
case "blockSdt":
|
|
14
|
+
case "blockCustomXml": return hasBlockRevision(block.content);
|
|
15
|
+
case "preservedBlock":
|
|
16
|
+
case "bookmarkStart":
|
|
17
|
+
case "bookmarkEnd": return false;
|
|
18
|
+
default: return panic("Unsupported block in accepted projection", { unsupported: block });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Read a model story through the same structural resolver as Accept All and
|
|
23
|
+
* the editor's No Markup view. The authored model is never changed.
|
|
24
|
+
*/
|
|
25
|
+
const projectAcceptedBlocks = (blocks, pkg) => {
|
|
26
|
+
if (!hasBlockRevision(blocks)) return null;
|
|
27
|
+
const options = {
|
|
28
|
+
...pkg?.styles !== void 0 && { styles: pkg.styles },
|
|
29
|
+
...pkg?.theme !== void 0 && { theme: pkg.theme },
|
|
30
|
+
...pkg?.numbering !== void 0 && { numbering: pkg.numbering }
|
|
31
|
+
};
|
|
32
|
+
const doc = headerFooterToProseDoc([...blocks], options);
|
|
33
|
+
const result = resolveWholeStory({
|
|
34
|
+
doc,
|
|
35
|
+
mode: "accept",
|
|
36
|
+
styleResolver: createStyleEngine(pkg?.styles),
|
|
37
|
+
numbering: pkg?.numbering ? getCachedNumberingMap(pkg.numbering) : null
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
blocks: result.resolved.eq(doc) ? blocks : proseDocToBlocks(result.resolved, [...blocks], pkg?.styles),
|
|
41
|
+
completeness: result.failed ? "partial" : "complete"
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { projectAcceptedBlocks };
|
|
@@ -2,6 +2,7 @@ import { prepareCanonicalDocxInput } from "../docx/canonicalSessionInput.js";
|
|
|
2
2
|
import { inspectDocxCompatibility } from "../docx/compatibility.js";
|
|
3
3
|
import { parseDocx } from "../docx/parser.js";
|
|
4
4
|
import { recordDocumentLoadPhase } from "../layout-engine/layoutInstrumentation.js";
|
|
5
|
+
import { CANONICAL_GAP, usesCanonicalSession } from "../types/canonicalCapabilities.js";
|
|
5
6
|
import { resetAuthorColors } from "../utils/authorColors.js";
|
|
6
7
|
import { loadFontsWithMapping } from "../utils/fontLoader.js";
|
|
7
8
|
//#region src/managers/DocumentLoaderManager.ts
|
|
@@ -62,7 +63,7 @@ var DocumentLoaderManager = class {
|
|
|
62
63
|
let doc;
|
|
63
64
|
try {
|
|
64
65
|
let input = buffer;
|
|
65
|
-
if (this.callbacks.getExperimentalSession?.()
|
|
66
|
+
if (usesCanonicalSession(this.callbacks.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting)) {
|
|
66
67
|
const prepared = await prepareCanonicalDocxInput(buffer);
|
|
67
68
|
if (prepared.isErr()) throw prepared.error;
|
|
68
69
|
input = prepared.value;
|
|
@@ -25,6 +25,8 @@ declare function escapeInline(text: string): string;
|
|
|
25
25
|
* line.
|
|
26
26
|
*/
|
|
27
27
|
declare function escapeTableCell(text: string): string;
|
|
28
|
+
/** Single-line Markdown blocks express inline breaks without their two-space syntax. */
|
|
29
|
+
declare const inlineBreaksToHtml: (text: string) => string;
|
|
28
30
|
declare function escapeLinkUrl(url: string): string;
|
|
29
31
|
/**
|
|
30
32
|
* Alt text inside ``. Raw (un-escaped) text, so escape the backslash
|
|
@@ -33,4 +35,4 @@ declare function escapeLinkUrl(url: string): string;
|
|
|
33
35
|
*/
|
|
34
36
|
declare function escapeAltText(text: string): string;
|
|
35
37
|
//#endregion
|
|
36
|
-
export { escapeAltText, escapeInline, escapeLinkUrl, escapeTableCell };
|
|
38
|
+
export { escapeAltText, escapeInline, escapeLinkUrl, escapeTableCell, inlineBreaksToHtml };
|
package/dist/markdown/escape.js
CHANGED
|
@@ -30,8 +30,15 @@ function escapeInline(text) {
|
|
|
30
30
|
* line.
|
|
31
31
|
*/
|
|
32
32
|
function escapeTableCell(text) {
|
|
33
|
-
return text.replace(/\|/gu, "|")
|
|
33
|
+
return inlineBreaksToHtml(text.replace(/\|/gu, "|"));
|
|
34
34
|
}
|
|
35
|
+
/** Single-line Markdown blocks express inline breaks without their two-space syntax. */
|
|
36
|
+
const inlineBreaksToHtml = (text) => text.split(/\r?\n/u).map((line, index, lines) => {
|
|
37
|
+
if (index === lines.length - 1) return line;
|
|
38
|
+
let end = line.length;
|
|
39
|
+
while (end > 0 && line.at(end - 1) === " ") end--;
|
|
40
|
+
return line.slice(0, end);
|
|
41
|
+
}).join("<br>");
|
|
35
42
|
/**
|
|
36
43
|
* Hyperlink URLs in inline form need parens balanced; we URL-encode the
|
|
37
44
|
* problematic characters rather than escape, so the link still resolves.
|
|
@@ -55,4 +62,4 @@ function escapeAltText(text) {
|
|
|
55
62
|
return text.replace(/(?<ch>[\\[\]])/gu, "\\$<ch>").replace(/\r?\n/gu, " ");
|
|
56
63
|
}
|
|
57
64
|
//#endregion
|
|
58
|
-
export { escapeAltText, escapeInline, escapeLinkUrl, escapeTableCell };
|
|
65
|
+
export { escapeAltText, escapeInline, escapeLinkUrl, escapeTableCell, inlineBreaksToHtml };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ALT_CHUNK_READER_DIAGNOSTIC, isAltChunkMarkup } from "../docx/altChunk.js";
|
|
2
2
|
import { OPAQUE_REVISION_CARRIER_READER_DIAGNOSTIC, isOpaqueNestedRowMarkup, opaqueRevisionCarrierName } from "../docx/opaqueCarrier.js";
|
|
3
|
-
import {
|
|
3
|
+
import { projectAcceptedBlocks } from "../internal/acceptedBlockProjection.js";
|
|
4
|
+
import { pushWarning } from "./internals.js";
|
|
4
5
|
import { renderParagraphBlock } from "./renderParagraph.js";
|
|
5
6
|
import { renderTable } from "./renderTable.js";
|
|
6
7
|
import { panic } from "better-result";
|
|
@@ -10,130 +11,14 @@ import { panic } from "better-result";
|
|
|
10
11
|
* markdown for each block, suppressing redundant blank lines between list items
|
|
11
12
|
* of the same list. Ported from eigenpal/docx-editor PR #595.
|
|
12
13
|
*/
|
|
13
|
-
/**
|
|
14
|
-
* In `trackedChanges: "clean"` mode every change is accepted, as the editor's
|
|
15
|
-
* accept-all resolves the story (`internal/wholeStoryRevisionResolution.ts`).
|
|
16
|
-
*
|
|
17
|
-
* A paragraph whose mark is pending deletion loses its break on accept and
|
|
18
|
-
* runs on into the paragraph after it, which is the paragraph left: its mark
|
|
19
|
-
* ends the joined text, and a paragraph's properties (style, list) live on its
|
|
20
|
-
* mark. A run of consecutive deletions collapses into that last paragraph.
|
|
21
|
-
*
|
|
22
|
-
* With a table right after it, its words run on into the table's first cell
|
|
23
|
-
* and lead that cell's first paragraph (`prosemirror/tableRunIn.ts`), unless
|
|
24
|
-
* it ends a section, keeps nothing once accepted, or that cell is pending.
|
|
25
|
-
* Otherwise a paragraph that cannot join keeps its place, or goes when it
|
|
26
|
-
* keeps nothing and something follows it.
|
|
27
|
-
*/
|
|
28
|
-
function mergeAcceptedParagraphBreaks(blocks) {
|
|
29
|
-
const reversed = [];
|
|
30
|
-
let survivor = null;
|
|
31
|
-
let tableFollows = false;
|
|
32
|
-
let followed = false;
|
|
33
|
-
const flush = () => {
|
|
34
|
-
if (survivor) reversed.push(survivor);
|
|
35
|
-
survivor = null;
|
|
36
|
-
};
|
|
37
|
-
for (let index = blocks.length - 1; index >= 0; index--) {
|
|
38
|
-
const block = blocks[index];
|
|
39
|
-
if (!block) continue;
|
|
40
|
-
if (block.type !== "paragraph") {
|
|
41
|
-
flush();
|
|
42
|
-
reversed.push(block);
|
|
43
|
-
tableFollows = block.type === "table";
|
|
44
|
-
followed ||= tableFollows;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const goes = block.pPrMark?.kind === "del" || block.pPrMark?.kind === "moveFrom";
|
|
48
|
-
if (goes && survivor) {
|
|
49
|
-
survivor = cloneParagraphWithoutPropertySource(survivor, { content: [...block.content, ...survivor.content] });
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
const table = reversed.at(-1);
|
|
53
|
-
if (goes && tableFollows && table?.type === "table") {
|
|
54
|
-
const ranIn = tableWithParagraphRunIn(table, block);
|
|
55
|
-
if (ranIn) {
|
|
56
|
-
reversed[reversed.length - 1] = ranIn;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
flush();
|
|
61
|
-
tableFollows = false;
|
|
62
|
-
if (goes && holdsNothingOnAccept(block) && followed && index + reversed.length > 0) continue;
|
|
63
|
-
survivor = block;
|
|
64
|
-
followed = true;
|
|
65
|
-
}
|
|
66
|
-
flush();
|
|
67
|
-
return reversed.reverse();
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Whether the cell a paragraph's words would run into is itself pending (its
|
|
71
|
-
* row or the cell, through nested tables' first cells), as
|
|
72
|
-
* `prosemirror/tableRunIn.ts` reads it.
|
|
73
|
-
*/
|
|
74
|
-
const runInCellPending = (table) => {
|
|
75
|
-
const row = table.rows[0];
|
|
76
|
-
const cell = row?.cells[0];
|
|
77
|
-
if (!row || !cell) return false;
|
|
78
|
-
if (row.structuralChange !== void 0 || cell.structuralChange !== void 0) return true;
|
|
79
|
-
const first = cell.content[0];
|
|
80
|
-
return first?.type === "table" && runInCellPending(first);
|
|
81
|
-
};
|
|
82
|
-
/**
|
|
83
|
-
* `table` with `paragraph`'s words leading its first cell's first paragraph,
|
|
84
|
-
* descending into nested tables, or null when the paragraph keeps its place.
|
|
85
|
-
*/
|
|
86
|
-
const tableWithParagraphRunIn = (table, paragraph) => {
|
|
87
|
-
if (paragraph.sectionProperties !== void 0 || holdsNothingOnAccept(paragraph)) return null;
|
|
88
|
-
if (runInCellPending(table)) return null;
|
|
89
|
-
const runIn = (current) => {
|
|
90
|
-
const [row, ...rows] = current.rows;
|
|
91
|
-
const [cell, ...cells] = row?.cells ?? [];
|
|
92
|
-
const [first, ...rest] = cell?.content ?? [];
|
|
93
|
-
if (!row || !cell || !first) return null;
|
|
94
|
-
let led = null;
|
|
95
|
-
if (first.type === "paragraph") led = cloneParagraphWithoutPropertySource(first, { content: [...paragraph.content, ...first.content] });
|
|
96
|
-
else if (first.type === "table") led = runIn(first);
|
|
97
|
-
if (!led) return null;
|
|
98
|
-
return {
|
|
99
|
-
...current,
|
|
100
|
-
rows: [{
|
|
101
|
-
...row,
|
|
102
|
-
cells: [{
|
|
103
|
-
...cell,
|
|
104
|
-
content: [led, ...rest]
|
|
105
|
-
}, ...cells]
|
|
106
|
-
}, ...rows]
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
return runIn(table);
|
|
110
|
-
};
|
|
111
|
-
/** Paragraph content that shows nothing: range boundaries and anchors. */
|
|
112
|
-
const ZERO_WIDTH_CONTENT = /* @__PURE__ */ new Set([
|
|
113
|
-
"bookmarkStart",
|
|
114
|
-
"bookmarkEnd",
|
|
115
|
-
"commentRangeStart",
|
|
116
|
-
"commentRangeEnd",
|
|
117
|
-
"commentReference",
|
|
118
|
-
"moveFromRangeStart",
|
|
119
|
-
"moveFromRangeEnd",
|
|
120
|
-
"moveToRangeStart",
|
|
121
|
-
"moveToRangeEnd"
|
|
122
|
-
]);
|
|
123
|
-
/** Whether an item shows nothing once every change is accepted. */
|
|
124
|
-
const showsNothingOnAccept = (item) => {
|
|
125
|
-
if (item.type === "deletion" || item.type === "moveFrom" || ZERO_WIDTH_CONTENT.has(item.type)) return true;
|
|
126
|
-
if (item.type === "insertion" || item.type === "moveTo") return item.content.every(showsNothingOnAccept);
|
|
127
|
-
return item.type === "run" && item.content.every((content) => content.type === "renderedPageBreak");
|
|
128
|
-
};
|
|
129
|
-
/** Whether a paragraph keeps no visible content once every change is accepted. */
|
|
130
|
-
const holdsNothingOnAccept = (paragraph) => paragraph.content.every(showsNothingOnAccept);
|
|
131
14
|
function renderBlocks(ctx, pkg, blocks) {
|
|
132
15
|
const outerListIndentWidths = ctx.listIndentWidths;
|
|
133
16
|
ctx.listIndentWidths = [];
|
|
134
17
|
const out = [];
|
|
135
18
|
let prevWasListItem = false;
|
|
136
|
-
const
|
|
19
|
+
const projection = ctx.opts.trackedChanges === "clean" ? projectAcceptedBlocks(blocks, pkg) : null;
|
|
20
|
+
if (projection?.completeness === "partial") pushWarning(ctx, "Some tracked structural changes could not be resolved.");
|
|
21
|
+
const ordered = projection?.blocks ?? blocks;
|
|
137
22
|
for (const block of ordered) switch (block.type) {
|
|
138
23
|
case "paragraph": {
|
|
139
24
|
const { markdown: md, isListItem } = renderParagraphBlock(ctx, pkg, block);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isQuoteStyle, resolveHeadingLevel } from "../docx/builtInStyles.js";
|
|
2
2
|
import { listLabelAttrsFromRendering } from "../prosemirror/listLabels.js";
|
|
3
|
+
import { inlineBreaksToHtml } from "./escape.js";
|
|
3
4
|
import { renderParagraphInline } from "./renderRuns.js";
|
|
4
5
|
//#region src/markdown/renderParagraph.ts
|
|
5
6
|
/**
|
|
@@ -37,13 +38,14 @@ function renderParagraphBlock(ctx, pkg, para) {
|
|
|
37
38
|
const headingLevel = markdownHeadingLevel(ctx, para);
|
|
38
39
|
if (headingLevel !== void 0) {
|
|
39
40
|
ctx.listIndentWidths = [];
|
|
40
|
-
if (!inline) return {
|
|
41
|
+
if (!inline || !inline.trim() && label === void 0) return {
|
|
41
42
|
markdown: "",
|
|
42
43
|
isListItem: false
|
|
43
44
|
};
|
|
44
45
|
const hashes = "#".repeat(Math.min(MAX_MARKDOWN_HEADING_LEVEL, headingLevel + 1));
|
|
46
|
+
const headingInline = inlineBreaksToHtml(inline);
|
|
45
47
|
return {
|
|
46
|
-
markdown: label ? `${hashes} ${label} ${
|
|
48
|
+
markdown: label ? `${hashes} ${label} ${headingInline}` : `${hashes} ${headingInline}`,
|
|
47
49
|
isListItem: false
|
|
48
50
|
};
|
|
49
51
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
import { RenderContext } from "./types.js";
|
|
3
3
|
//#region src/markdown/renderRuns.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Inline marks we recognize. Order matters: we open from outermost to innermost
|
|
6
|
+
* so the output reads cleanly (`***bold italic***`), not the reverse.
|
|
7
|
+
*/
|
|
8
|
+
type MarkKey = "bold" | "italic" | "code" | "strike";
|
|
9
|
+
declare function applyMarks(text: string, marks: readonly MarkKey[]): string;
|
|
4
10
|
/**
|
|
5
11
|
* The character beside a run's delimiters: `""` at the paragraph's edge,
|
|
6
12
|
* `undefined` when it cannot be told (read as a letter, the strictest case).
|
|
@@ -16,4 +22,4 @@ type RunNeighbors = {
|
|
|
16
22
|
*/
|
|
17
23
|
declare function renderParagraphInline(ctx: RenderContext, pkg: document_d_exports.DocxPackage | undefined, content: readonly document_d_exports.ParagraphContent[], paraId: string | undefined, edges?: RunNeighbors): string;
|
|
18
24
|
//#endregion
|
|
19
|
-
export { renderParagraphInline };
|
|
25
|
+
export { applyMarks, renderParagraphInline };
|
|
@@ -40,22 +40,32 @@ function marksFor(run) {
|
|
|
40
40
|
if (ascii && MONOSPACE_FONTS.has(ascii)) out.push("code");
|
|
41
41
|
return out;
|
|
42
42
|
}
|
|
43
|
+
const splitEdgeWhitespace = (text) => {
|
|
44
|
+
const start = text.length - text.trimStart().length;
|
|
45
|
+
const end = text.trimEnd().length;
|
|
46
|
+
return {
|
|
47
|
+
leading: text.slice(0, start),
|
|
48
|
+
core: text.slice(start, Math.max(start, end)),
|
|
49
|
+
trailing: text.slice(Math.max(start, end))
|
|
50
|
+
};
|
|
51
|
+
};
|
|
43
52
|
function applyMarks(text, marks) {
|
|
44
|
-
|
|
53
|
+
const { leading, core, trailing } = splitEdgeWhitespace(text);
|
|
54
|
+
if (!core || marks.length === 0) return text;
|
|
45
55
|
if (marks.includes("code")) {
|
|
46
|
-
const literal =
|
|
56
|
+
const literal = core.replace(/\\(?<char>[\\`*[\]_<~])/gu, "$<char>");
|
|
47
57
|
let longestRun = 0;
|
|
48
58
|
for (const m of literal.matchAll(/`+/gu)) longestRun = Math.max(longestRun, m[0].length);
|
|
49
59
|
const fence = "`".repeat(longestRun + 1);
|
|
50
60
|
const pad = literal.startsWith("`") || literal.endsWith("`") ? " " : "";
|
|
51
|
-
return `${fence}${pad}${literal}${pad}${fence}`;
|
|
61
|
+
return `${leading}${fence}${pad}${literal}${pad}${fence}${trailing}`;
|
|
52
62
|
}
|
|
53
|
-
let out =
|
|
63
|
+
let out = core;
|
|
54
64
|
for (const m of marks) {
|
|
55
65
|
const d = MARK_DELIMS[m];
|
|
56
66
|
out = `${d}${out}${d}`;
|
|
57
67
|
}
|
|
58
|
-
return out
|
|
68
|
+
return `${leading}${out}${trailing}`;
|
|
59
69
|
}
|
|
60
70
|
const UNKNOWN_NEIGHBORS = {
|
|
61
71
|
before: void 0,
|
|
@@ -250,11 +260,8 @@ function pieceOf(ctx, pkg, run, paraId) {
|
|
|
250
260
|
text: inner,
|
|
251
261
|
marks: new Set(marks)
|
|
252
262
|
};
|
|
253
|
-
const leadLen = inner.length - inner.trimStart().length;
|
|
254
|
-
const trailLen = inner.length - inner.trimEnd().length;
|
|
255
|
-
const core = inner.slice(leadLen, inner.length - trailLen);
|
|
256
263
|
return {
|
|
257
|
-
text:
|
|
264
|
+
text: inner.split("\n").map((line) => applyMarks(line, marks)).join("\n"),
|
|
258
265
|
marks: /* @__PURE__ */ new Set()
|
|
259
266
|
};
|
|
260
267
|
}
|
|
@@ -346,6 +353,8 @@ function renderHyperlink(ctx, pkg, link, paraId) {
|
|
|
346
353
|
after: "]"
|
|
347
354
|
} : UNKNOWN_NEIGHBORS);
|
|
348
355
|
if (!inner) return "";
|
|
356
|
+
const { leading, core, trailing } = splitEdgeWhitespace(inner);
|
|
357
|
+
if (!core) return inner;
|
|
349
358
|
const href = link.href ?? (link.anchor ? `#${link.anchor}` : "");
|
|
350
359
|
if (!href) {
|
|
351
360
|
pushWarning(ctx, "hyperlink missing href and anchor; rendered as plain text");
|
|
@@ -357,9 +366,9 @@ function renderHyperlink(ctx, pkg, link, paraId) {
|
|
|
357
366
|
href,
|
|
358
367
|
refNumber
|
|
359
368
|
});
|
|
360
|
-
return
|
|
369
|
+
return `${leading}[${core}][${refNumber}]${trailing}`;
|
|
361
370
|
}
|
|
362
|
-
return
|
|
371
|
+
return `${leading}[${core}](${escapeLinkUrl(href)})${trailing}`;
|
|
363
372
|
}
|
|
364
373
|
function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
|
|
365
374
|
const renderChild = (child) => {
|
|
@@ -419,6 +428,16 @@ function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
|
|
|
419
428
|
default: return wrapMoveTo(ctx, wrapper.info, inner);
|
|
420
429
|
}
|
|
421
430
|
}
|
|
431
|
+
/** Clean revisions have no inline boundary; group their runs with surrounding runs. */
|
|
432
|
+
const cleanRevisionContent = (content) => content.flatMap((item) => {
|
|
433
|
+
switch (item.type) {
|
|
434
|
+
case "insertion":
|
|
435
|
+
case "moveTo": return cleanRevisionContent(item.content);
|
|
436
|
+
case "deletion":
|
|
437
|
+
case "moveFrom": return [];
|
|
438
|
+
default: return [item];
|
|
439
|
+
}
|
|
440
|
+
});
|
|
422
441
|
/**
|
|
423
442
|
* Render the full inline content of a paragraph, tracking comment-range
|
|
424
443
|
* boundaries to apply the configured wrapper.
|
|
@@ -429,7 +448,7 @@ function renderParagraphInline(ctx, pkg, content, paraId, edges = {
|
|
|
429
448
|
}) {
|
|
430
449
|
let out = "";
|
|
431
450
|
const openComments = [];
|
|
432
|
-
const items = content.filter((item) => !SILENT_MARKERS.has(item.type) && !(ctx.opts.comments === "strip" && COMMENT_MARKERS.has(item.type)));
|
|
451
|
+
const items = (ctx.opts.trackedChanges === "clean" ? cleanRevisionContent(content) : content).filter((item) => !SILENT_MARKERS.has(item.type) && !(ctx.opts.comments === "strip" && COMMENT_MARKERS.has(item.type)));
|
|
433
452
|
const around = (index) => {
|
|
434
453
|
const next = items[index + 1];
|
|
435
454
|
return {
|
|
@@ -557,4 +576,4 @@ function applyCommentWrapping(ctx, slot, current) {
|
|
|
557
576
|
}, inner);
|
|
558
577
|
}
|
|
559
578
|
//#endregion
|
|
560
|
-
export { renderParagraphInline };
|
|
579
|
+
export { applyMarks, renderParagraphInline };
|
|
@@ -4,6 +4,28 @@ import { ParagraphPropsPatch, RunPropsPatch } from "@stll/docx-core/ops";
|
|
|
4
4
|
//#region src/prosemirror/canonicalCommands.d.ts
|
|
5
5
|
/** Command meaning before canonical positions are resolved by the session. */
|
|
6
6
|
type CanonicalCommandIntent = {
|
|
7
|
+
type: "generateTOC";
|
|
8
|
+
at: number;
|
|
9
|
+
title: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "setHyperlink";
|
|
12
|
+
from: number;
|
|
13
|
+
to: number;
|
|
14
|
+
href: string;
|
|
15
|
+
tooltip?: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "removeHyperlink";
|
|
18
|
+
from: number;
|
|
19
|
+
to: number;
|
|
20
|
+
hyperlinkStyleId?: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "insertHyperlink";
|
|
23
|
+
from: number;
|
|
24
|
+
to: number;
|
|
25
|
+
text: string;
|
|
26
|
+
href: string;
|
|
27
|
+
tooltip?: string;
|
|
28
|
+
} | {
|
|
7
29
|
type: "formatRun";
|
|
8
30
|
from: number;
|
|
9
31
|
to: number;
|
|
@@ -36,9 +58,6 @@ type CanonicalCommandDescriptor = (state: EditorState) => readonly CanonicalComm
|
|
|
36
58
|
declare const withCanonicalCommand: (command: Command, descriptor: CanonicalCommandDescriptor) => Command;
|
|
37
59
|
declare const getCanonicalCommandIntents: (command: Command, state: EditorState) => readonly CanonicalCommandIntent[] | undefined;
|
|
38
60
|
declare const canonicalRunFormatting: (state: EditorState, patch: RunPropsPatch) => readonly [{
|
|
39
|
-
readonly type: "formatRun";
|
|
40
|
-
readonly from: number;
|
|
41
|
-
readonly to: number;
|
|
42
61
|
readonly patch: import("@stll/docx-core/ops").FormattingPatch<{
|
|
43
62
|
bold?: boolean | undefined;
|
|
44
63
|
boldCs?: boolean | undefined;
|
|
@@ -91,7 +110,12 @@ declare const canonicalRunFormatting: (state: EditorState, patch: RunPropsPatch)
|
|
|
91
110
|
styleId?: string | undefined;
|
|
92
111
|
preserved?: import("@stll/docx-core/model").PreservedMarkup | undefined;
|
|
93
112
|
}>;
|
|
113
|
+
readonly from: number;
|
|
114
|
+
readonly to: number;
|
|
115
|
+
readonly type: "formatRun";
|
|
94
116
|
}];
|
|
95
117
|
declare const withCanonicalParagraphFormatting: (command: Command, patch: ParagraphPropsPatch | ((paragraph: Node) => ParagraphPropsPatch)) => Command;
|
|
118
|
+
/** Commands that read the selection start apply that same patch across selected paragraphs. */
|
|
119
|
+
declare const withCanonicalStartParagraphFormatting: (command: Command, patch: (paragraph: Node) => ParagraphPropsPatch) => Command;
|
|
96
120
|
//#endregion
|
|
97
|
-
export { CanonicalCommandIntent, canonicalRunFormatting, getCanonicalCommandIntents, withCanonicalCommand, withCanonicalParagraphFormatting };
|
|
121
|
+
export { CanonicalCommandIntent, canonicalRunFormatting, getCanonicalCommandIntents, withCanonicalCommand, withCanonicalParagraphFormatting, withCanonicalStartParagraphFormatting };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalSelectionRange } from "./canonicalSelectionRange.js";
|
|
1
2
|
//#region src/prosemirror/canonicalCommands.ts
|
|
2
3
|
const descriptors = /* @__PURE__ */ new WeakMap();
|
|
3
4
|
/** Registration leaves execution and side-effect-free command probes unchanged. */
|
|
@@ -8,11 +9,10 @@ const withCanonicalCommand = (command, descriptor) => {
|
|
|
8
9
|
const getCanonicalCommandIntents = (command, state) => descriptors.get(command)?.(state);
|
|
9
10
|
const canonicalRunFormatting = (state, patch) => [{
|
|
10
11
|
type: "formatRun",
|
|
11
|
-
|
|
12
|
-
to: state.selection.to,
|
|
12
|
+
...canonicalSelectionRange(state),
|
|
13
13
|
patch
|
|
14
14
|
}];
|
|
15
|
-
const
|
|
15
|
+
const canonicalParagraphFormatting = (state, patch) => {
|
|
16
16
|
const intents = [];
|
|
17
17
|
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, position) => {
|
|
18
18
|
if (node.type.name !== "paragraph") return;
|
|
@@ -23,6 +23,13 @@ const withCanonicalParagraphFormatting = (command, patch) => withCanonicalComman
|
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
return intents;
|
|
26
|
+
};
|
|
27
|
+
const withCanonicalParagraphFormatting = (command, patch) => withCanonicalCommand(command, (state) => canonicalParagraphFormatting(state, patch));
|
|
28
|
+
/** Commands that read the selection start apply that same patch across selected paragraphs. */
|
|
29
|
+
const withCanonicalStartParagraphFormatting = (command, patch) => withCanonicalCommand(command, (state) => {
|
|
30
|
+
const paragraph = state.selection.$from.parent;
|
|
31
|
+
if (paragraph.type.name !== "paragraph") return [];
|
|
32
|
+
return canonicalParagraphFormatting(state, patch(paragraph));
|
|
26
33
|
});
|
|
27
34
|
//#endregion
|
|
28
|
-
export { canonicalRunFormatting, getCanonicalCommandIntents, withCanonicalCommand, withCanonicalParagraphFormatting };
|
|
35
|
+
export { canonicalRunFormatting, getCanonicalCommandIntents, withCanonicalCommand, withCanonicalParagraphFormatting, withCanonicalStartParagraphFormatting };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { EditorState } from "prosemirror-state";
|
|
2
|
+
//#region src/prosemirror/canonicalSelectionRange.d.ts
|
|
3
|
+
/** Whole-document selection addresses paragraph content rather than document edges. */
|
|
4
|
+
declare const canonicalSelectionRange: ({ doc, selection }: EditorState) => {
|
|
5
|
+
from: number;
|
|
6
|
+
to: number;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { canonicalSelectionRange };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AllSelection, Selection } from "prosemirror-state";
|
|
2
|
+
//#region src/prosemirror/canonicalSelectionRange.ts
|
|
3
|
+
/** Whole-document selection addresses paragraph content rather than document edges. */
|
|
4
|
+
const canonicalSelectionRange = ({ doc, selection }) => {
|
|
5
|
+
if (selection instanceof AllSelection) return {
|
|
6
|
+
from: Selection.atStart(doc).from,
|
|
7
|
+
to: Selection.atEnd(doc).to
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
from: selection.from,
|
|
11
|
+
to: selection.to
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { canonicalSelectionRange };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Slice } from "prosemirror-model";
|
|
2
|
+
import { EditorView } from "prosemirror-view";
|
|
3
|
+
//#region src/prosemirror/clipboardIntent.d.ts
|
|
4
|
+
declare const registerClipboardIntentHandler: (view: EditorView, handler: (slice: Slice, plain: boolean) => boolean) => void;
|
|
5
|
+
declare const dispatchClipboardIntent: (view: EditorView, slice: Slice) => boolean | undefined;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { dispatchClipboardIntent, registerClipboardIntentHandler };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/prosemirror/clipboardIntent.ts
|
|
2
|
+
/** Async clipboard reads resolve against the current owning session and selection. */
|
|
3
|
+
const handlers = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
const registerClipboardIntentHandler = (view, handler) => {
|
|
5
|
+
handlers.set(view, handler);
|
|
6
|
+
};
|
|
7
|
+
const dispatchClipboardIntent = (view, slice) => handlers.get(view)?.(slice, true);
|
|
8
|
+
//#endregion
|
|
9
|
+
export { dispatchClipboardIntent, registerClipboardIntentHandler };
|