@stll/folio-core 0.37.2 → 0.37.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/ai-edits/table-cell-mutations.js +10 -5
- package/dist/ai-edits/table-template.js +19 -5
- package/dist/controller/hiddenEditorManager.js +11 -0
- package/dist/controller/layoutPipeline.js +1 -1
- package/dist/display-list/build/textBoxPrimitives.js +34 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +5 -2
- package/dist/display-list/types.d.ts +8 -4
- package/dist/docx/paragraphPropertySource.d.ts +82 -2
- package/dist/docx/paragraphPropertySource.js +569 -3
- package/dist/docx/parser.js +9 -0
- package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
- package/dist/docx/server/materializeYjsDocx.js +21 -2
- package/dist/headless-layout.js +1 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +7 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +20 -3
- package/dist/layout-bridge/convert/toFlowBlocks.js +43 -12
- package/dist/layout-engine/index.js +20 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +7 -3
- package/dist/layout-engine/measure/measureBlocks.js +10 -7
- package/dist/layout-engine/measure/measureParagraph.d.ts +2 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -1
- package/dist/layout-engine/paginator.d.ts +1 -1
- package/dist/layout-engine/paginator.js +41 -4
- package/dist/layout-engine/textBoxFlow.d.ts +2 -0
- package/dist/layout-engine/textBoxFlow.js +9 -5
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/documentColors.d.ts +10 -1
- package/dist/layout-painter/documentColors.js +16 -1
- package/dist/layout-painter/renderParagraph.js +9 -24
- package/dist/layout-painter/renderTable.js +3 -3
- package/dist/layout-painter/renderTextBox.js +2 -1
- package/dist/pdf/pageSpace.d.ts +12 -1
- package/dist/pdf/pageSpace.js +24 -1
- package/dist/pdf/paint.js +2 -2
- package/dist/prosemirror/attrs/index.js +32 -8
- package/dist/prosemirror/commands/comments.js +12 -3
- package/dist/prosemirror/commands/tableCellMergeResolution.js +18 -11
- package/dist/prosemirror/conversion/fromProseDoc.js +114 -25
- package/dist/prosemirror/conversion/toProseDoc.js +101 -8
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +5 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +132 -41
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -61
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +16 -0
- package/dist/prosemirror/schema/nodes.d.ts +14 -2
- package/dist/prosemirror/schema/nodes.js +7 -0
- package/dist/prosemirror/yjsParagraphSourceContract.d.ts +9 -0
- package/dist/prosemirror/yjsParagraphSourceContract.js +26 -0
- package/dist/utils/rotationBoundingBox.d.ts +5 -1
- package/dist/utils/rotationBoundingBox.js +9 -1
- package/package.json +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource } from "../../../docx/paragraphPropertySource.js";
|
|
1
|
+
import { PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, getExplicitParagraphPropertySourceTransfers, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, paragraphPropertySourceTokenMatchesContract, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource } from "../../../docx/paragraphPropertySource.js";
|
|
2
2
|
import { deterministicHexId, generateHexId } from "../../../utils/hexId.js";
|
|
3
3
|
import { createExtension } from "../create.js";
|
|
4
4
|
import { ignoreTrackedChanges } from "./ParagraphChangeTrackerExtension.js";
|
|
5
|
+
import { panic } from "better-result";
|
|
5
6
|
import { Fragment } from "prosemirror-model";
|
|
6
7
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
7
8
|
//#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.ts
|
|
@@ -73,61 +74,100 @@ const mintParaId = (taken, seed, pos) => {
|
|
|
73
74
|
return id;
|
|
74
75
|
};
|
|
75
76
|
const isUsableParaId = (value) => typeof value === "string" && value.length > 0 && value !== "00000000";
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
77
|
+
const collectParagraphPropertySourceSeed = (doc) => {
|
|
78
|
+
const contract = getProseDocumentParagraphPropertySourceContract(doc);
|
|
79
|
+
if (!contract) return {
|
|
80
|
+
contract: null,
|
|
81
|
+
tokens: /* @__PURE__ */ new Set()
|
|
82
|
+
};
|
|
83
|
+
const counts = /* @__PURE__ */ new Map();
|
|
84
|
+
doc.descendants((node) => {
|
|
85
|
+
if (node.type.name !== "paragraph") return true;
|
|
86
|
+
const token = getProseParagraphPropertySourceToken(node);
|
|
87
|
+
if (paragraphPropertySourceTokenMatchesContract(token, contract)) counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
88
|
+
return false;
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
contract,
|
|
92
|
+
tokens: new Set([...counts].filter(([, count]) => count === 1).map(([token]) => token))
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const mapParagraphKeepers = (oldDoc, transactions, sourceSeed) => {
|
|
96
|
+
const paraIds = /* @__PURE__ */ new Map();
|
|
97
|
+
const sourceTokens = /* @__PURE__ */ new Map();
|
|
84
98
|
oldDoc.descendants((node, pos) => {
|
|
85
99
|
if (node.type.name !== "paragraph") return true;
|
|
86
100
|
const id = node.attrs["paraId"];
|
|
87
|
-
|
|
101
|
+
const token = getProseParagraphPropertySourceToken(node);
|
|
102
|
+
const mapsParaId = isUsableParaId(id) && !paraIds.has(id);
|
|
103
|
+
const mapsSourceToken = typeof token === "string" && sourceSeed.tokens.has(token);
|
|
104
|
+
if (!mapsParaId && !mapsSourceToken) return false;
|
|
88
105
|
let mapped = pos;
|
|
106
|
+
let mappedInterior = pos + 1;
|
|
89
107
|
let deleted = false;
|
|
90
|
-
for (const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
mapped = result.pos;
|
|
108
|
+
for (const transaction of transactions) {
|
|
109
|
+
const ownerResult = transaction.mapping.mapResult(mapped);
|
|
110
|
+
const interiorResult = transaction.mapping.mapResult(mappedInterior, -1);
|
|
111
|
+
deleted ||= interiorResult.deletedAcross;
|
|
112
|
+
mapped = ownerResult.pos;
|
|
113
|
+
mappedInterior = interiorResult.pos;
|
|
97
114
|
}
|
|
98
|
-
if (!deleted)
|
|
115
|
+
if (mapsParaId && !deleted) paraIds.set(id, mapped);
|
|
116
|
+
if (mapsSourceToken) sourceTokens.set(token, sourceTokens.has(token) ? { status: "ambiguous" } : {
|
|
117
|
+
pos: mapped,
|
|
118
|
+
status: deleted ? "deleted" : "mapped"
|
|
119
|
+
});
|
|
99
120
|
return false;
|
|
100
121
|
});
|
|
101
|
-
return
|
|
122
|
+
return {
|
|
123
|
+
paraIds,
|
|
124
|
+
sourceTokens
|
|
125
|
+
};
|
|
102
126
|
};
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
127
|
+
const collectParagraphCensus = (doc, sourceSeed) => {
|
|
128
|
+
const census = {
|
|
129
|
+
invalidSourceTokens: [],
|
|
130
|
+
missingParaIds: [],
|
|
131
|
+
paragraphs: /* @__PURE__ */ new Map(),
|
|
132
|
+
paraIds: /* @__PURE__ */ new Map(),
|
|
133
|
+
sourceTokens: /* @__PURE__ */ new Map()
|
|
134
|
+
};
|
|
135
|
+
const contractMatchesSeed = sourceSeed?.contract !== null && sourceSeed?.contract !== void 0 && getProseDocumentParagraphPropertySourceContract(doc) === sourceSeed.contract;
|
|
106
136
|
doc.descendants((node, pos) => {
|
|
107
137
|
if (node.type.name !== "paragraph") return true;
|
|
108
|
-
const
|
|
109
|
-
if (!isUsableParaId(id)) missing.push({
|
|
138
|
+
const occurrence = {
|
|
110
139
|
pos,
|
|
111
140
|
attrs: node.attrs
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
};
|
|
142
|
+
census.paragraphs.set(pos, occurrence);
|
|
143
|
+
const id = node.attrs["paraId"];
|
|
144
|
+
if (isUsableParaId(id)) {
|
|
145
|
+
const occurrences = census.paraIds.get(id) ?? [];
|
|
146
|
+
occurrences.push(occurrence);
|
|
147
|
+
census.paraIds.set(id, occurrences);
|
|
148
|
+
} else census.missingParaIds.push(occurrence);
|
|
149
|
+
if (sourceSeed) {
|
|
150
|
+
const token = getProseParagraphPropertySourceToken(node);
|
|
151
|
+
if (token !== null && token !== void 0) if (typeof token !== "string" || !contractMatchesSeed || !sourceSeed.contract || !sourceSeed.tokens.has(token) || !paragraphPropertySourceTokenMatchesContract(token, sourceSeed.contract)) census.invalidSourceTokens.push(occurrence);
|
|
152
|
+
else {
|
|
153
|
+
const occurrences = census.sourceTokens.get(token) ?? [];
|
|
154
|
+
occurrences.push(occurrence);
|
|
155
|
+
census.sourceTokens.set(token, occurrences);
|
|
156
|
+
}
|
|
120
157
|
}
|
|
121
158
|
return false;
|
|
122
159
|
});
|
|
123
|
-
|
|
124
|
-
|
|
160
|
+
return census;
|
|
161
|
+
};
|
|
162
|
+
const collectParaIdUpdatesFromCensus = (census, keeperPositions, deterministicSeed = null) => {
|
|
163
|
+
const needFreshId = [...census.missingParaIds];
|
|
164
|
+
for (const [id, occurrences] of census.paraIds) {
|
|
125
165
|
if (occurrences.length === 1) continue;
|
|
126
166
|
const keeperPos = keeperPositions?.get(id);
|
|
127
|
-
const keeper = occurrences.find((
|
|
167
|
+
const keeper = occurrences.find(({ pos }) => pos === keeperPos) ?? occurrences[0];
|
|
128
168
|
for (const occurrence of occurrences) if (occurrence !== keeper) needFreshId.push(occurrence);
|
|
129
169
|
}
|
|
130
|
-
const taken = new Set(
|
|
170
|
+
const taken = new Set(census.paraIds.keys());
|
|
131
171
|
const updates = [];
|
|
132
172
|
for (const { pos, attrs } of needFreshId) {
|
|
133
173
|
const newId = mintParaId(taken, deterministicSeed, pos);
|
|
@@ -140,9 +180,39 @@ const collectParaIdUpdates = (doc, keeperPositions, deterministicSeed = null) =>
|
|
|
140
180
|
}
|
|
141
181
|
});
|
|
142
182
|
}
|
|
143
|
-
updates.sort((
|
|
183
|
+
updates.sort((left, right) => left.pos - right.pos);
|
|
144
184
|
return updates;
|
|
145
185
|
};
|
|
186
|
+
const collectParaIdUpdates = (doc) => collectParaIdUpdatesFromCensus(collectParagraphCensus(doc));
|
|
187
|
+
const collectParagraphPropertySourceUpdates = (census, keeperPositions, transactions) => {
|
|
188
|
+
const updates = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const { pos } of census.invalidSourceTokens) updates.set(pos, null);
|
|
190
|
+
const explicitTransfers = transactions.flatMap((transaction) => [...getExplicitParagraphPropertySourceTransfers(transaction)]);
|
|
191
|
+
const explicitlySelected = new Set(explicitTransfers.map(({ selectedToken }) => selectedToken).filter((token) => token !== null));
|
|
192
|
+
const explicitlyDisplaced = new Set(explicitTransfers.map(({ displacedToken }) => displacedToken).filter((token) => token !== null));
|
|
193
|
+
const keptPositions = /* @__PURE__ */ new Set();
|
|
194
|
+
const keptTokens = /* @__PURE__ */ new Set();
|
|
195
|
+
for (const [token, occurrences] of census.sourceTokens) {
|
|
196
|
+
const mappedOwner = keeperPositions.get(token);
|
|
197
|
+
const mappedKeeper = mappedOwner?.status === "mapped" ? occurrences.find(({ pos }) => pos === mappedOwner.pos) : void 0;
|
|
198
|
+
const transferredKeeper = !mappedKeeper && explicitlySelected.has(token) && occurrences.length === 1 ? occurrences.at(0) : void 0;
|
|
199
|
+
const detachedKeeper = !mappedKeeper && !transferredKeeper && occurrences.length === 1 && (mappedOwner === void 0 || mappedOwner.status === "deleted" && mappedOwner.pos === occurrences.at(0)?.pos) ? occurrences.at(0) : void 0;
|
|
200
|
+
const keeper = mappedKeeper ?? transferredKeeper ?? detachedKeeper;
|
|
201
|
+
if (keeper) {
|
|
202
|
+
keptPositions.add(keeper.pos);
|
|
203
|
+
keptTokens.add(token);
|
|
204
|
+
}
|
|
205
|
+
for (const occurrence of occurrences) if (occurrence !== keeper) updates.set(occurrence.pos, null);
|
|
206
|
+
}
|
|
207
|
+
for (const [token, mappedOwner] of keeperPositions) {
|
|
208
|
+
if (mappedOwner.status !== "mapped" || keptTokens.has(token) || explicitlyDisplaced.has(token) || keptPositions.has(mappedOwner.pos) || !census.paragraphs.has(mappedOwner.pos)) continue;
|
|
209
|
+
updates.set(mappedOwner.pos, token);
|
|
210
|
+
}
|
|
211
|
+
return [...updates].map(([pos, token]) => ({
|
|
212
|
+
pos,
|
|
213
|
+
token
|
|
214
|
+
})).sort((left, right) => left.pos - right.pos);
|
|
215
|
+
};
|
|
146
216
|
const collectInitialParaIds = (doc) => {
|
|
147
217
|
let needsRewrite = false;
|
|
148
218
|
const taken = /* @__PURE__ */ new Set();
|
|
@@ -215,12 +285,20 @@ const ensureParaIdsInState = (state) => {
|
|
|
215
285
|
};
|
|
216
286
|
const createParaIdAllocatorPlugin = () => new Plugin({
|
|
217
287
|
key: paraIdAllocatorKey,
|
|
288
|
+
state: {
|
|
289
|
+
init: (_config, state) => collectParagraphPropertySourceSeed(state.doc),
|
|
290
|
+
apply: (_transaction, seed) => seed
|
|
291
|
+
},
|
|
218
292
|
appendTransaction(transactions, oldState, newState) {
|
|
219
293
|
if (!transactions.some((t) => t.docChanged)) return null;
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
294
|
+
const paragraphSourceSeed = paraIdAllocatorKey.getState(oldState);
|
|
295
|
+
if (!paragraphSourceSeed) panic("ParaId allocator lost its paragraph-property source seed");
|
|
296
|
+
const keeperPositions = mapParagraphKeepers(oldState.doc, transactions, paragraphSourceSeed);
|
|
297
|
+
const deterministicSeed = transactions.map((transaction) => transaction.getMeta(deterministicParaIdSeedKey)).find((value) => typeof value === "string") ?? null;
|
|
298
|
+
const census = collectParagraphCensus(newState.doc, paragraphSourceSeed);
|
|
299
|
+
const updates = collectParaIdUpdatesFromCensus(census, keeperPositions.paraIds, deterministicSeed);
|
|
300
|
+
const paragraphSourceUpdates = collectParagraphPropertySourceUpdates(census, keeperPositions.sourceTokens, transactions);
|
|
301
|
+
if (updates.length === 0 && paragraphSourceUpdates.length === 0) return null;
|
|
224
302
|
const tr = newState.tr;
|
|
225
303
|
for (const u of updates) setProseParagraphMarkupWithPropertySource({
|
|
226
304
|
attrs: u.attrs,
|
|
@@ -228,6 +306,19 @@ const createParaIdAllocatorPlugin = () => new Plugin({
|
|
|
228
306
|
pos: u.pos,
|
|
229
307
|
transaction: tr
|
|
230
308
|
});
|
|
309
|
+
for (const { pos, token } of paragraphSourceUpdates) {
|
|
310
|
+
const paragraph = tr.doc.nodeAt(pos);
|
|
311
|
+
if (!paragraph || paragraph.type.name !== "paragraph") panic("Paragraph-property token update lost its paragraph");
|
|
312
|
+
setProseParagraphMarkupWithPropertySource({
|
|
313
|
+
attrs: {
|
|
314
|
+
...paragraph.attrs,
|
|
315
|
+
[PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR]: token
|
|
316
|
+
},
|
|
317
|
+
ownership: "preserve",
|
|
318
|
+
pos,
|
|
319
|
+
transaction: tr
|
|
320
|
+
});
|
|
321
|
+
}
|
|
231
322
|
ignoreTrackedChanges(tr);
|
|
232
323
|
tr.setMeta(paraIdAllocatorKey, "allocated");
|
|
233
324
|
tr.setMeta("addToHistory", false);
|
|
@@ -1,66 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { OutlineStyleAttr } from "../../../types/documentEnumValues.js";
|
|
3
|
-
import { ImagePositionAttrs, TextBoxAttrs as TextBoxAttrs$1 } from "../../schema/nodes.js";
|
|
1
|
+
import { TextBoxAttrs as TextBoxAttrs$1 } from "../../schema/nodes.js";
|
|
4
2
|
import { NodeExtension } from "../types.js";
|
|
5
3
|
//#region src/prosemirror/extensions/nodes/TextBoxExtension.d.ts
|
|
6
|
-
type TextBoxAttrs =
|
|
7
|
-
/** Width in pixels */
|
|
8
|
-
width?: number;
|
|
9
|
-
/** Height in pixels */
|
|
10
|
-
height?: number;
|
|
11
|
-
/** Text fitting behavior */
|
|
12
|
-
autoFit?: document_d_exports.ShapeTextBody["autoFit"];
|
|
13
|
-
/** Horizontal text wrapping inside the box */
|
|
14
|
-
textWrap?: document_d_exports.ShapeTextBody["textWrap"];
|
|
15
|
-
/** Unique identifier */
|
|
16
|
-
textBoxId?: string;
|
|
17
|
-
/** Fill color as CSS color */
|
|
18
|
-
fillColor?: string;
|
|
19
|
-
/** Outline width in pixels */
|
|
20
|
-
outlineWidth?: number;
|
|
21
|
-
/** Outline color as CSS color */
|
|
22
|
-
outlineColor?: string;
|
|
23
|
-
/** Outline dash style, or `"none"` for an explicit no-outline. */
|
|
24
|
-
outlineStyle?: OutlineStyleAttr;
|
|
25
|
-
/** Internal margin top in pixels */
|
|
26
|
-
marginTop?: number;
|
|
27
|
-
/** Internal margin bottom in pixels */
|
|
28
|
-
marginBottom?: number;
|
|
29
|
-
/** Internal margin left in pixels */
|
|
30
|
-
marginLeft?: number;
|
|
31
|
-
/** Internal margin right in pixels */
|
|
32
|
-
marginRight?: number;
|
|
33
|
-
/** Vertical text alignment */
|
|
34
|
-
verticalAlign?: string;
|
|
35
|
-
/** Display mode */
|
|
36
|
-
displayMode?: "inline" | "float" | "block";
|
|
37
|
-
/** CSS float direction */
|
|
38
|
-
cssFloat?: "left" | "right" | "none";
|
|
39
|
-
/** Wrap type */
|
|
40
|
-
wrapType?: document_d_exports.ImageWrap["type"];
|
|
41
|
-
/** OOXML wrapText direction for anchored text boxes (eigenpal #474). */
|
|
42
|
-
wrapText?: "bothSides" | "left" | "right" | "largest";
|
|
43
|
-
/** Wrap distance from top edge, in pixels (OOXML distT, EMU-converted). */
|
|
44
|
-
distTop?: number;
|
|
45
|
-
/** Wrap distance from bottom edge, in pixels. */
|
|
46
|
-
distBottom?: number;
|
|
47
|
-
/** Wrap distance from left edge, in pixels. */
|
|
48
|
-
distLeft?: number;
|
|
49
|
-
/** Wrap distance from right edge, in pixels. */
|
|
50
|
-
distRight?: number;
|
|
51
|
-
/** Position for floating/anchored text boxes. */
|
|
52
|
-
position?: ImagePositionAttrs;
|
|
53
|
-
/** Original DOCX placement hint for save-path reconstruction. */
|
|
54
|
-
_docxPlacement?: "standalone" | "inlineWithPrevious";
|
|
55
|
-
/** Original DOCX paragraph group for standalone text-box reconstruction. */
|
|
56
|
-
_docxGroupId?: string;
|
|
57
|
-
/** Inline anchor linking this block node to its source run position. */
|
|
58
|
-
_docxAnchorId?: string;
|
|
59
|
-
/** Original run-level revision wrapper for save-path reconstruction. */
|
|
60
|
-
_docxTrackedChange?: TextBoxAttrs$1["_docxTrackedChange"];
|
|
61
|
-
/** Original inline content-control ancestry for save-path reconstruction. */
|
|
62
|
-
_docxInlineSdts?: TextBoxAttrs$1["_docxInlineSdts"];
|
|
63
|
-
};
|
|
4
|
+
type TextBoxAttrs = TextBoxAttrs$1;
|
|
64
5
|
declare const TextBoxExtension: (options?: Partial<Record<string, unknown>> | undefined) => NodeExtension;
|
|
65
6
|
//#endregion
|
|
66
7
|
export { TextBoxAttrs, TextBoxExtension };
|
|
@@ -2,6 +2,13 @@ import { IMAGE_WRAP_TYPE_VALUES, normalizeShapeTextAnchor } from "../../../types
|
|
|
2
2
|
import { expectTextBoxAttrs } from "../../attrs/index.js";
|
|
3
3
|
import { createNodeExtension } from "../create.js";
|
|
4
4
|
//#region src/prosemirror/extensions/nodes/TextBoxExtension.ts
|
|
5
|
+
/**
|
|
6
|
+
* TextBox Extension — editable text box node
|
|
7
|
+
*
|
|
8
|
+
* An isolating block node that contains paragraphs (and tables).
|
|
9
|
+
* Rendered as a positioned container with optional fill, outline, and margins.
|
|
10
|
+
* Supports inline and floating positioning.
|
|
11
|
+
*/
|
|
5
12
|
function parseTextBoxPosition(raw) {
|
|
6
13
|
if (!raw) return;
|
|
7
14
|
try {
|
|
@@ -42,6 +49,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
42
49
|
outlineWidth: { default: null },
|
|
43
50
|
outlineColor: { default: null },
|
|
44
51
|
outlineStyle: { default: null },
|
|
52
|
+
transform: { default: null },
|
|
45
53
|
marginTop: { default: 4 },
|
|
46
54
|
marginBottom: { default: 4 },
|
|
47
55
|
marginLeft: { default: 7 },
|
|
@@ -59,6 +67,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
59
67
|
_docxPlacement: { default: null },
|
|
60
68
|
_docxGroupId: { default: null },
|
|
61
69
|
_docxAnchorId: { default: null },
|
|
70
|
+
_docxTextBodyContentState: { default: { type: "authored" } },
|
|
62
71
|
_docxTrackedChange: { default: null },
|
|
63
72
|
_docxInlineSdts: { default: null }
|
|
64
73
|
},
|
|
@@ -73,6 +82,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
73
82
|
const textWrap = parseTextBoxTextWrap(d["textWrap"]);
|
|
74
83
|
const verticalAlign = parseTextBoxVerticalAlign(d["verticalAlign"]);
|
|
75
84
|
return {
|
|
85
|
+
_docxTextBodyContentState: { type: "authored" },
|
|
76
86
|
...d["width"] ? { width: Number(d["width"]) } : {},
|
|
77
87
|
...d["height"] ? { height: Number(d["height"]) } : {},
|
|
78
88
|
...autoFit ? { autoFit } : {},
|
|
@@ -82,6 +92,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
82
92
|
...d["outlineWidth"] ? { outlineWidth: Number(d["outlineWidth"]) } : {},
|
|
83
93
|
...d["outlineColor"] ? { outlineColor: d["outlineColor"] } : {},
|
|
84
94
|
...d["outlineStyle"] ? { outlineStyle: d["outlineStyle"] } : {},
|
|
95
|
+
...d["transform"] ? { transform: d["transform"] } : {},
|
|
85
96
|
...d["marginTop"] ? { marginTop: Number(d["marginTop"]) } : {},
|
|
86
97
|
...d["marginBottom"] ? { marginBottom: Number(d["marginBottom"]) } : {},
|
|
87
98
|
...d["marginLeft"] ? { marginLeft: Number(d["marginLeft"]) } : {},
|
|
@@ -111,6 +122,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
111
122
|
if (attrs.outlineWidth) domAttrs["data-outline-width"] = String(attrs.outlineWidth);
|
|
112
123
|
if (attrs.outlineColor) domAttrs["data-outline-color"] = attrs.outlineColor;
|
|
113
124
|
if (attrs.outlineStyle) domAttrs["data-outline-style"] = attrs.outlineStyle;
|
|
125
|
+
if (attrs.transform) domAttrs["data-transform"] = attrs.transform;
|
|
114
126
|
if (typeof attrs.marginTop === "number") domAttrs["data-margin-top"] = String(attrs.marginTop);
|
|
115
127
|
if (typeof attrs.marginBottom === "number") domAttrs["data-margin-bottom"] = String(attrs.marginBottom);
|
|
116
128
|
if (typeof attrs.marginLeft === "number") domAttrs["data-margin-left"] = String(attrs.marginLeft);
|
|
@@ -158,6 +170,10 @@ const TextBoxExtension = createNodeExtension({
|
|
|
158
170
|
styles.push("box-sizing: border-box");
|
|
159
171
|
styles.push("overflow: hidden");
|
|
160
172
|
styles.push("position: relative");
|
|
173
|
+
if (attrs.transform) {
|
|
174
|
+
styles.push(`transform: ${attrs.transform}`);
|
|
175
|
+
styles.push("transform-origin: center center");
|
|
176
|
+
}
|
|
161
177
|
domAttrs["style"] = styles.join("; ");
|
|
162
178
|
return [
|
|
163
179
|
"div",
|
|
@@ -488,6 +488,10 @@ type ShapeAttrs = {
|
|
|
488
488
|
/**
|
|
489
489
|
* Text box node attributes
|
|
490
490
|
*/
|
|
491
|
+
declare const TEXT_BOX_TEXT_BODY_CONTENT_STATE_TYPES: readonly ["source-empty", "authored"];
|
|
492
|
+
type TextBoxTextBodyContentState = { [Type in (typeof TEXT_BOX_TEXT_BODY_CONTENT_STATE_TYPES)[number]]: {
|
|
493
|
+
readonly type: Type;
|
|
494
|
+
}; }[(typeof TEXT_BOX_TEXT_BODY_CONTENT_STATE_TYPES)[number]];
|
|
491
495
|
type TextBoxAttrs = {
|
|
492
496
|
/** Width in pixels */
|
|
493
497
|
width?: number;
|
|
@@ -507,6 +511,8 @@ type TextBoxAttrs = {
|
|
|
507
511
|
outlineColor?: string;
|
|
508
512
|
/** Outline dash style, or `"none"` for an explicit no-outline. */
|
|
509
513
|
outlineStyle?: OutlineStyleAttr;
|
|
514
|
+
/** DrawingML rotation and/or flips, serialized as CSS transform functions. */
|
|
515
|
+
transform?: string;
|
|
510
516
|
/** Internal margin top in pixels */
|
|
511
517
|
marginTop?: number;
|
|
512
518
|
/** Internal margin bottom in pixels */
|
|
@@ -541,6 +547,12 @@ type TextBoxAttrs = {
|
|
|
541
547
|
_docxGroupId?: string;
|
|
542
548
|
/** Inline anchor linking this block node to its source run position. */
|
|
543
549
|
_docxAnchorId?: string;
|
|
550
|
+
/**
|
|
551
|
+
* Ownership of the schema-required placeholder paragraph. A source text
|
|
552
|
+
* body with no children needs one paragraph while it is editable, but that
|
|
553
|
+
* paragraph is not authored document content.
|
|
554
|
+
*/
|
|
555
|
+
_docxTextBodyContentState: TextBoxTextBodyContentState;
|
|
544
556
|
/** Original run-level revision wrapper for save-path reconstruction. */
|
|
545
557
|
_docxTrackedChange?: {
|
|
546
558
|
type: "insertion";
|
|
@@ -765,7 +777,7 @@ type TableCellAttrs = {
|
|
|
765
777
|
/** Preserve a DOCX vMerge restart even when PM cannot model it as a rowspan. */
|
|
766
778
|
_preserveVMergeRestart?: boolean;
|
|
767
779
|
/** Original DOCX vMerge continuation cells skipped into this PM rowspan. */
|
|
768
|
-
_docxVMergeContinuationCells?:
|
|
780
|
+
_docxVMergeContinuationCells?: unknown;
|
|
769
781
|
};
|
|
770
782
|
//#endregion
|
|
771
|
-
export { BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, PageBreakRunAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs };
|
|
783
|
+
export { BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, PageBreakRunAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TEXT_BOX_TEXT_BODY_CONTENT_STATE_TYPES, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs, TextBoxTextBodyContentState };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Node } from "prosemirror-model";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
//#region src/prosemirror/yjsParagraphSourceContract.d.ts
|
|
4
|
+
declare const proseDocumentParagraphSourceContract: (document: Node) => string | null;
|
|
5
|
+
declare const writeYjsParagraphSourceContract: (ydoc: Y.Doc, document: Node) => void;
|
|
6
|
+
declare const readYjsParagraphSourceContract: (ydoc: Y.Doc) => string | null;
|
|
7
|
+
declare const withParagraphSourceContract: (document: Node, contract: string) => Node;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { proseDocumentParagraphSourceContract, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsParagraphSourceContract };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, getProseDocumentParagraphPropertySourceContract } from "../docx/paragraphPropertySource.js";
|
|
2
|
+
import { panic } from "better-result";
|
|
3
|
+
//#region src/prosemirror/yjsParagraphSourceContract.ts
|
|
4
|
+
const FOLIO_YJS_METADATA_MAP_NAME = "folio:document-metadata";
|
|
5
|
+
const PARAGRAPH_SOURCE_CONTRACT_KEY = "paragraphSourceContract";
|
|
6
|
+
const proseDocumentParagraphSourceContract = (document) => {
|
|
7
|
+
return getProseDocumentParagraphPropertySourceContract(document);
|
|
8
|
+
};
|
|
9
|
+
const writeYjsParagraphSourceContract = (ydoc, document) => {
|
|
10
|
+
const contract = proseDocumentParagraphSourceContract(document);
|
|
11
|
+
if (!contract) panic("Cannot seed collaboration without a paragraph-property source contract");
|
|
12
|
+
ydoc.getMap(FOLIO_YJS_METADATA_MAP_NAME).set(PARAGRAPH_SOURCE_CONTRACT_KEY, contract);
|
|
13
|
+
};
|
|
14
|
+
const readYjsParagraphSourceContract = (ydoc) => {
|
|
15
|
+
const contract = ydoc.getMap(FOLIO_YJS_METADATA_MAP_NAME).get(PARAGRAPH_SOURCE_CONTRACT_KEY);
|
|
16
|
+
return typeof contract === "string" ? contract : null;
|
|
17
|
+
};
|
|
18
|
+
const withParagraphSourceContract = (document, contract) => {
|
|
19
|
+
if (document.type.name !== "doc") panic("A paragraph-property source contract can only attach to a document node");
|
|
20
|
+
return document.type.create({
|
|
21
|
+
...document.attrs,
|
|
22
|
+
[PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR]: contract
|
|
23
|
+
}, document.content, document.marks);
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { proseDocumentParagraphSourceContract, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsParagraphSourceContract };
|
|
@@ -5,7 +5,11 @@ type BoundingBox = {
|
|
|
5
5
|
height: number;
|
|
6
6
|
};
|
|
7
7
|
declare function parseRotationDegrees(transform: string | undefined): number;
|
|
8
|
+
/** Whether a DrawingML/CSS transform reflects around the vertical centre line. */
|
|
9
|
+
declare function hasHorizontalFlip(transform: string | undefined): boolean;
|
|
10
|
+
/** Whether a DrawingML/CSS transform reflects around the horizontal centre line. */
|
|
11
|
+
declare function hasVerticalFlip(transform: string | undefined): boolean;
|
|
8
12
|
declare function rotatedBoundingBox(w: number, h: number, deg: number): BoundingBox;
|
|
9
13
|
declare function inlineImageBoundingBox(run: ImageRun): BoundingBox;
|
|
10
14
|
//#endregion
|
|
11
|
-
export { BoundingBox, inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox };
|
|
15
|
+
export { BoundingBox, hasHorizontalFlip, hasVerticalFlip, inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox };
|
|
@@ -7,6 +7,14 @@ function parseRotationDegrees(transform) {
|
|
|
7
7
|
if (!Number.isFinite(raw)) return 0;
|
|
8
8
|
return (raw % 360 + 360) % 360;
|
|
9
9
|
}
|
|
10
|
+
/** Whether a DrawingML/CSS transform reflects around the vertical centre line. */
|
|
11
|
+
function hasHorizontalFlip(transform) {
|
|
12
|
+
return /scaleX\(\s*-1\s*\)/iu.test(transform ?? "");
|
|
13
|
+
}
|
|
14
|
+
/** Whether a DrawingML/CSS transform reflects around the horizontal centre line. */
|
|
15
|
+
function hasVerticalFlip(transform) {
|
|
16
|
+
return /scaleY\(\s*-1\s*\)/iu.test(transform ?? "");
|
|
17
|
+
}
|
|
10
18
|
function rotatedBoundingBox(w, h, deg) {
|
|
11
19
|
if (deg === 0 || deg === 180) return {
|
|
12
20
|
width: w,
|
|
@@ -33,4 +41,4 @@ function inlineImageBoundingBox(run) {
|
|
|
33
41
|
return rotatedBoundingBox(run.width, run.height, rotation);
|
|
34
42
|
}
|
|
35
43
|
//#endregion
|
|
36
|
-
export { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox };
|
|
44
|
+
export { hasHorizontalFlip, hasVerticalFlip, inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.4",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"perf": "bun scripts/profile-editor.ts"
|
|
122
122
|
},
|
|
123
123
|
"dependencies": {
|
|
124
|
-
"@stll/docx-core": "^0.19.
|
|
124
|
+
"@stll/docx-core": "^0.19.4",
|
|
125
125
|
"@stll/docx-utils": "^0.1.0",
|
|
126
126
|
"@stll/template-conditions": ">=0.4.0 <1.0.0",
|
|
127
127
|
"better-result": "3.0.1",
|