@stll/folio-core 0.56.0 → 0.58.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 +122 -22
- package/dist/ai-edits/batch-claims.d.ts +3 -1
- package/dist/ai-edits/batch-claims.js +25 -1
- package/dist/ai-edits/table-row-column-mutations.js +17 -3
- package/dist/ai-suggestions/apply.js +20 -1
- package/dist/compare/inline-atoms.js +40 -16
- package/dist/compare/inline-provenance.js +12 -5
- package/dist/compat/eigenpal.d.ts +2 -1
- package/dist/compat/eigenpal.js +2 -1
- package/dist/controller/canonicalClipboard.d.ts +3 -1
- package/dist/controller/canonicalClipboard.js +31 -13
- package/dist/controller/canonicalComments.d.ts +41 -0
- package/dist/controller/canonicalComments.js +152 -0
- package/dist/controller/canonicalComposition.d.ts +17 -4
- package/dist/controller/canonicalComposition.js +125 -20
- package/dist/controller/canonicalInlineProjection.d.ts +6 -1
- package/dist/controller/canonicalInlineProjection.js +86 -3
- package/dist/controller/canonicalInput.d.ts +1 -1
- package/dist/controller/canonicalInput.js +14 -4
- package/dist/controller/canonicalInputTimer.d.ts +10 -1
- package/dist/controller/canonicalInputTimer.js +24 -1
- package/dist/controller/canonicalPublicOperations.d.ts +2 -2
- package/dist/controller/canonicalPublicOperations.js +107 -15
- package/dist/controller/canonicalSelection.d.ts +18 -0
- package/dist/controller/canonicalSelection.js +41 -0
- package/dist/controller/canonicalSession.d.ts +4 -1
- package/dist/controller/canonicalSession.js +106 -36
- package/dist/controller/canonicalStoryEditor.d.ts +3 -1
- package/dist/controller/canonicalStoryEditor.js +21 -1
- package/dist/controller/canonicalStructure.js +8 -3
- package/dist/controller/folioEditor.js +13 -0
- package/dist/controller/headerFooterEditorManager.d.ts +1 -1
- package/dist/controller/headerFooterEditorManager.js +25 -11
- package/dist/controller/hiddenEditorApi.d.ts +13 -2
- package/dist/controller/hiddenEditorApi.js +5 -1
- package/dist/controller/hiddenEditorManager.d.ts +3 -1
- package/dist/controller/hiddenEditorManager.js +167 -14
- package/dist/controller/noteEditorManager.d.ts +1 -1
- package/dist/controller/noteEditorManager.js +28 -13
- package/dist/docx/builtInStyles.js +13 -6
- package/dist/docx/containerChildren.gen.d.ts +3 -1
- package/dist/docx/containerChildren.gen.js +33 -1
- package/dist/docx/fieldParser.js +14 -2
- package/dist/docx/hyperlinkParser.d.ts +1 -1
- package/dist/docx/hyperlinkParser.js +20 -1
- package/dist/docx/inlineWrapperContent.d.ts +4 -4
- package/dist/docx/inlineWrapperContent.js +4 -4
- package/dist/docx/linkedSdtPreservation.d.ts +10 -0
- package/dist/docx/linkedSdtPreservation.js +37 -0
- package/dist/docx/listRendering.d.ts +18 -0
- package/dist/{prosemirror/conversion/listRenderingDefinition.js → docx/listRendering.js} +17 -3
- package/dist/docx/paragraphParser.js +88 -67
- package/dist/docx/paragraphPropertySource.d.ts +22 -1
- package/dist/docx/paragraphPropertySource.js +107 -1
- package/dist/docx/preservedRunContent.js +2 -1
- package/dist/docx/relsParser.js +26 -2
- package/dist/docx/rezip.js +9 -6
- package/dist/docx/selectiveSave.js +10 -8
- package/dist/docx/serializer/commentSerializer.d.ts +4 -1
- package/dist/docx/serializer/commentSerializer.js +7 -3
- package/dist/docx/serializer/paragraphSerializer.js +207 -94
- package/dist/docx/server/materializeYjsDocx.js +15 -7
- package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
- package/dist/i18n/messages/ar.gen.js +4 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
- package/dist/i18n/messages/catalogs.gen.js +68 -0
- package/dist/i18n/messages/cs.gen.js +4 -0
- package/dist/i18n/messages/de.gen.js +4 -0
- package/dist/i18n/messages/en.gen.js +4 -0
- package/dist/i18n/messages/es.gen.js +4 -0
- package/dist/i18n/messages/et.gen.js +4 -0
- package/dist/i18n/messages/fr.gen.js +4 -0
- package/dist/i18n/messages/he.gen.js +4 -0
- package/dist/i18n/messages/hi.gen.js +4 -0
- package/dist/i18n/messages/hu.gen.js +4 -0
- package/dist/i18n/messages/lt.gen.js +4 -0
- package/dist/i18n/messages/lv.gen.js +4 -0
- package/dist/i18n/messages/messages.gen.d.ts +4 -0
- package/dist/i18n/messages/pl.gen.js +4 -0
- package/dist/i18n/messages/pt-BR.gen.js +4 -0
- package/dist/i18n/messages/sk.gen.js +4 -0
- package/dist/i18n/messages/tr.gen.js +4 -0
- package/dist/i18n/messages/zh-CN.gen.js +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/revisionResolutionInline.js +25 -3
- package/dist/markdown/internals.js +7 -0
- package/dist/markdown/renderParagraph.js +11 -12
- package/dist/markdown/renderRuns.js +3 -13
- package/dist/markdown/types.d.ts +3 -0
- package/dist/panel-layout.d.ts +11 -9
- package/dist/panel-layout.js +13 -9
- package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
- package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
- package/dist/prosemirror/attrs/index.js +9 -0
- package/dist/prosemirror/commands/comments.js +60 -35
- package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
- package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
- package/dist/prosemirror/commentOps.js +15 -1
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +150 -30
- package/dist/prosemirror/conversion/toProseDoc.js +128 -40
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +22 -5
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +22 -14
- package/dist/prosemirror/extensions/marks/markUtils.js +8 -7
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +8 -1
- package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
- package/dist/prosemirror/fieldRepresentation.js +18 -0
- package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
- package/dist/prosemirror/hyperlinkRemoval.js +28 -9
- package/dist/prosemirror/imagePictureNames.d.ts +12 -0
- package/dist/prosemirror/imagePictureNames.js +20 -0
- package/dist/prosemirror/listNumbering.js +11 -5
- package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
- package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
- package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
- package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
- package/dist/prosemirror/noteReferenceReview.js +14 -1
- package/dist/prosemirror/paragraphFormattingProvenance.js +1 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
- package/dist/prosemirror/plugins/documentStyles.js +17 -10
- package/dist/prosemirror/plugins/suggestionMode.js +47 -49
- package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
- package/dist/prosemirror/preservedXmlReview.js +35 -0
- package/dist/prosemirror/runFormattingFromMarks.js +2 -2
- package/dist/prosemirror/runFormattingReconciliation.js +5 -4
- package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
- package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
- package/dist/prosemirror/runStyleFormatting.d.ts +74 -2
- package/dist/prosemirror/runStyleFormatting.js +20 -1
- package/dist/prosemirror/schema/marks.d.ts +1 -0
- package/dist/prosemirror/schema/nodes.d.ts +3 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
- package/dist/prosemirror/styles/styleResolver.d.ts +4 -1
- package/dist/prosemirror/styles/styleResolver.js +32 -11
- package/dist/prosemirror/textInput.d.ts +5 -1
- package/dist/prosemirror/textInput.js +3 -3
- package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
- package/dist/prosemirror/trackedRevisionPath.js +39 -1
- package/dist/prosemirror/validation.js +3 -5
- package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
- package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
- package/dist/types/canonicalCapabilities.d.ts +18 -18
- package/dist/types/canonicalCapabilities.js +18 -18
- package/dist/types/canonicalComments.d.ts +51 -0
- package/dist/types/canonicalComments.js +0 -0
- package/dist/types/canonicalSections.d.ts +12 -0
- package/dist/types/canonicalSections.js +0 -0
- package/dist/utils/headingCollector.js +45 -4
- package/dist/utils/outlineDepth.d.ts +9 -0
- package/dist/utils/outlineDepth.js +9 -0
- package/package.json +2 -2
- package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +0 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readNoteMarkerAttrs } from "../internal/noteMarkerAttrs.js";
|
|
2
|
-
import { readFootnoteRefMarkAttrs, readPreservedXmlAttrs } from "../prosemirror/attrs/index.js";
|
|
2
|
+
import { readCommentMarkAttrs, readFootnoteRefMarkAttrs, readPreservedXmlAttrs } from "../prosemirror/attrs/index.js";
|
|
3
3
|
import { bookmarkMarkerFromAttrs, readBookmarkBoundaryAttrs } from "../prosemirror/bookmarkBoundaryAttrs.js";
|
|
4
4
|
import { HYPHEN_TEXT_CARRIERS } from "../prosemirror/conversion/hyphenTextCarriers.js";
|
|
5
5
|
import { readMoveRangeBoundaryAttrs } from "../prosemirror/moveRangeBoundaryAttrs.js";
|
|
@@ -279,10 +279,62 @@ const sameSourceMarker = (left, right) => {
|
|
|
279
279
|
default: return false;
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
|
+
/** Marks carry nonempty ranges; explicit range anchors carry empty ranges. */
|
|
283
|
+
const commentMarkTransitions = ({ source, paragraph, commentContext }) => {
|
|
284
|
+
const spans = inlineLeafSpans(source.content);
|
|
285
|
+
const rangedIds = new Set(commentContext?.rangedIds);
|
|
286
|
+
const pointIds = new Set(commentContext?.pointIds);
|
|
287
|
+
const finalIds = new Set(commentContext?.openIds);
|
|
288
|
+
for (const { node } of spans) {
|
|
289
|
+
if (node.type === "commentRangeStart" || node.type === "commentRangeEnd") rangedIds.add(node.id);
|
|
290
|
+
if (node.type === "commentReference") pointIds.add(node.id);
|
|
291
|
+
if (node.type === "commentRangeStart") finalIds.add(node.id);
|
|
292
|
+
if (node.type === "commentRangeEnd") finalIds.delete(node.id);
|
|
293
|
+
}
|
|
294
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
295
|
+
let previous = new Set(commentContext?.openIds);
|
|
296
|
+
let issue;
|
|
297
|
+
const record = (position, current) => {
|
|
298
|
+
const starts = new Set([...current].filter((id) => !previous.has(id)));
|
|
299
|
+
const ends = new Set([...previous].filter((id) => !current.has(id)));
|
|
300
|
+
if (starts.size > 0 || ends.size > 0) transitions.set(position, {
|
|
301
|
+
starts,
|
|
302
|
+
ends
|
|
303
|
+
});
|
|
304
|
+
previous = current;
|
|
305
|
+
};
|
|
306
|
+
paragraph.forEach((node, position) => {
|
|
307
|
+
if (issue !== void 0) return;
|
|
308
|
+
const current = /* @__PURE__ */ new Set();
|
|
309
|
+
for (const mark of node.marks) {
|
|
310
|
+
if (mark.type.name !== "comment") continue;
|
|
311
|
+
const attrs = readCommentMarkAttrs(mark);
|
|
312
|
+
if (!attrs.ok) {
|
|
313
|
+
issue = "Canonical editing requires valid comment mark attributes.";
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const id = attrs.value.commentId;
|
|
317
|
+
if (rangedIds.has(id)) current.add(id);
|
|
318
|
+
else if (!pointIds.has(id)) {
|
|
319
|
+
issue = "Canonical editing cannot attribute a comment mark to its source.";
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
record(position, current);
|
|
324
|
+
});
|
|
325
|
+
record(paragraph.content.size, finalIds);
|
|
326
|
+
return issue === void 0 ? Result.ok(transitions) : refuse(issue);
|
|
327
|
+
};
|
|
282
328
|
/** Bind rendered gaps to the operation owner's source leaf ordinals, including collapsed pairs. */
|
|
283
|
-
const projectCanonicalInline = ({ source, paragraph, pairedBookmarkIds }) => {
|
|
329
|
+
const projectCanonicalInline = ({ source, paragraph, pairedBookmarkIds, commentContext }) => {
|
|
284
330
|
const native = canonicalNativeCells(paragraph);
|
|
285
331
|
if (native.isErr()) return native;
|
|
332
|
+
const comments = commentMarkTransitions({
|
|
333
|
+
source,
|
|
334
|
+
paragraph,
|
|
335
|
+
...commentContext === void 0 ? {} : { commentContext }
|
|
336
|
+
});
|
|
337
|
+
if (comments.isErr()) return comments;
|
|
286
338
|
const spans = inlineLeafSpans(source.content);
|
|
287
339
|
const erasedBookmarks = /* @__PURE__ */ new Set();
|
|
288
340
|
const collectErasedBookmarks = (items, context) => {
|
|
@@ -296,6 +348,28 @@ const projectCanonicalInline = ({ source, paragraph, pairedBookmarkIds }) => {
|
|
|
296
348
|
}
|
|
297
349
|
};
|
|
298
350
|
collectErasedBookmarks(source.content, "paragraph");
|
|
351
|
+
const markedCommentIds = /* @__PURE__ */ new Set();
|
|
352
|
+
paragraph.descendants((node) => {
|
|
353
|
+
for (const mark of node.marks) {
|
|
354
|
+
if (mark.type.name !== "comment") continue;
|
|
355
|
+
const attrs = readCommentMarkAttrs(mark);
|
|
356
|
+
if (attrs.ok) markedCommentIds.add(attrs.value.commentId);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
const commentRangeOffsets = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const { node, before } of spans) {
|
|
361
|
+
if (node.type !== "commentRangeStart" && node.type !== "commentRangeEnd") continue;
|
|
362
|
+
const offsets = commentRangeOffsets.get(node.id) ?? {};
|
|
363
|
+
if (node.type === "commentRangeStart") offsets.start = before.offset;
|
|
364
|
+
else offsets.end = before.offset;
|
|
365
|
+
commentRangeOffsets.set(node.id, offsets);
|
|
366
|
+
}
|
|
367
|
+
const erasedCommentMarkers = /* @__PURE__ */ new Set();
|
|
368
|
+
for (const { node } of spans) {
|
|
369
|
+
if (node.type !== "commentRangeStart" && node.type !== "commentRangeEnd") continue;
|
|
370
|
+
const offsets = commentRangeOffsets.get(node.id);
|
|
371
|
+
if (!(offsets?.start !== void 0 && offsets.end !== void 0 && offsets.start === offsets.end) && markedCommentIds.has(node.id)) erasedCommentMarkers.add(node);
|
|
372
|
+
}
|
|
299
373
|
const text = paragraphLogicalText(source);
|
|
300
374
|
const boundaries = Array.from({ length: text.length + 1 }, () => []);
|
|
301
375
|
let index = 0;
|
|
@@ -314,7 +388,15 @@ const projectCanonicalInline = ({ source, paragraph, pairedBookmarkIds }) => {
|
|
|
314
388
|
const consumeErasedLeaves = (position) => {
|
|
315
389
|
while (consumed === 0) {
|
|
316
390
|
const span = spans.at(index);
|
|
317
|
-
if (
|
|
391
|
+
if (position !== null && span && (span.node.type === "commentRangeStart" || span.node.type === "commentRangeEnd")) {
|
|
392
|
+
const transition = comments.value.get(position);
|
|
393
|
+
if ((span.node.type === "commentRangeStart" ? transition?.starts : transition?.ends)?.delete(span.node.id)) {
|
|
394
|
+
index += 1;
|
|
395
|
+
addGap(position, span.after);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (!span || span.before.offset !== span.after.offset || !(erasedBookmarks.has(span.node) || erasedCommentMarkers.has(span.node) || CANONICAL_SOURCE_INLINE_POLICIES[span.node.type] === "container" && sourceContainerChildren(span.node).length === 0)) break;
|
|
318
400
|
index += 1;
|
|
319
401
|
if (position !== null) addGap(position, span.after);
|
|
320
402
|
}
|
|
@@ -381,6 +463,7 @@ const projectCanonicalInline = ({ source, paragraph, pairedBookmarkIds }) => {
|
|
|
381
463
|
}
|
|
382
464
|
}
|
|
383
465
|
consumeErasedLeaves(paragraph.content.size);
|
|
466
|
+
if ([...comments.value.values()].some(({ starts, ends }) => starts.size > 0 || ends.size > 0)) return refuse("Canonical editing cannot attribute a comment mark transition to its source.");
|
|
384
467
|
const missing = spans.at(index);
|
|
385
468
|
if (missing || consumed !== 0) return refuse(`Canonical editing cannot display the source positions of ${missing?.node.type ?? "incomplete text"}.`);
|
|
386
469
|
if (boundaries.some((gaps) => gaps.length === 0)) return refuse("Canonical editing lost a source character boundary.");
|
|
@@ -36,7 +36,7 @@ declare const createCanonicalInputBoundary: (options: CanonicalInputOptions) =>
|
|
|
36
36
|
handleDOMEvents: {
|
|
37
37
|
keydown: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
38
38
|
compositionstart: (view: EditorView) => boolean;
|
|
39
|
-
compositionend: (view: EditorView) => boolean;
|
|
39
|
+
compositionend: (view: EditorView, event: Event) => boolean;
|
|
40
40
|
mousedown: (view: EditorView) => boolean;
|
|
41
41
|
blur: (view: EditorView) => boolean;
|
|
42
42
|
beforeinput: (view: EditorView, event: InputEvent) => boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleEditorBeforeInput } from "../prosemirror/textInput.js";
|
|
1
|
+
import { getEditorInputTargetRange, handleEditorBeforeInput } from "../prosemirror/textInput.js";
|
|
2
2
|
import { createCanonicalComposition } from "./canonicalComposition.js";
|
|
3
3
|
import { deletionRange, isCanonicalJoinBoundary } from "./canonicalSession.js";
|
|
4
4
|
import { ReplaceStep } from "prosemirror-transform";
|
|
@@ -209,8 +209,8 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
209
209
|
composition.start(view);
|
|
210
210
|
return false;
|
|
211
211
|
},
|
|
212
|
-
compositionend: (view) => {
|
|
213
|
-
composition.ended(view);
|
|
212
|
+
compositionend: (view, event) => {
|
|
213
|
+
composition.ended(view, event instanceof CompositionEvent && typeof event.data === "string" ? event.data : null);
|
|
214
214
|
return false;
|
|
215
215
|
},
|
|
216
216
|
mousedown: (view) => {
|
|
@@ -230,15 +230,25 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
230
230
|
beginGesture(event.inputType === "insertFromComposition" ? "compositionFinal" : "new");
|
|
231
231
|
if (event.isComposing || CANONICAL_COMPOSITION_INPUT_TYPES.some((inputType) => inputType === event.inputType)) {
|
|
232
232
|
if (composition.active) {
|
|
233
|
-
composition.authorizeNative(view);
|
|
233
|
+
composition.authorizeNative(view, event.inputType === "insertFromComposition" ? "explicitFinal" : "provisional");
|
|
234
234
|
return false;
|
|
235
235
|
}
|
|
236
236
|
if (event.inputType === "insertFromComposition" && composition.authorizeLateFinal(view, event.data)) return false;
|
|
237
237
|
return refuseEvent(event, "Composition has no captured canonical baseline.");
|
|
238
238
|
}
|
|
239
239
|
if (composition.active) {
|
|
240
|
+
if (view.composing && (event.inputType === "deleteContentBackward" || event.inputType === "deleteContentForward")) {
|
|
241
|
+
const range = getEditorInputTargetRange(view, event);
|
|
242
|
+
if (range && composition.matchesCancellationRange(view, range)) {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
composition.cancel(view);
|
|
245
|
+
closeGroup();
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
240
249
|
const nativeFinal = view.composing && (event.inputType === "insertText" || event.inputType === "insertReplacementText");
|
|
241
250
|
const nativeStillComposing = view.composing;
|
|
251
|
+
if (nativeFinal) composition.authorizeNative(view, "explicitFinal");
|
|
242
252
|
const recovered = composition.recover(view);
|
|
243
253
|
composition.forgetCompleted();
|
|
244
254
|
proposal = { type: "idle" };
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { EditorView } from "prosemirror-view";
|
|
1
2
|
//#region src/controller/canonicalInputTimer.d.ts
|
|
2
3
|
/** Shared by timer producers and browser diagnostics, regardless of bundling. */
|
|
3
4
|
declare const CANONICAL_INPUT_TIMER_OWNER = "folio.canonical-input-timer";
|
|
5
|
+
/** Delay after native composition end before the canonical session settles it. */
|
|
6
|
+
declare const CANONICAL_COMPOSITION_FLUSH_DELAY_MS = 25;
|
|
4
7
|
/** Mark the scheduled wrapper, never the caller's potentially shared callback. */
|
|
5
8
|
declare const setCanonicalInputTimer: (callback: () => void, delay: number) => NodeJS.Timeout;
|
|
9
|
+
/**
|
|
10
|
+
* Run `retry` once the view's canonical composition has settled. The session
|
|
11
|
+
* settles on an equal-delay timer armed at native composition end, so a retry
|
|
12
|
+
* armed at or after that point runs after it. Returns a disposer.
|
|
13
|
+
*/
|
|
14
|
+
declare const afterCanonicalCompositionSettles: (view: EditorView, retry: () => void) => (() => void);
|
|
6
15
|
//#endregion
|
|
7
|
-
export { CANONICAL_INPUT_TIMER_OWNER, setCanonicalInputTimer };
|
|
16
|
+
export { CANONICAL_COMPOSITION_FLUSH_DELAY_MS, CANONICAL_INPUT_TIMER_OWNER, afterCanonicalCompositionSettles, setCanonicalInputTimer };
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
//#region src/controller/canonicalInputTimer.ts
|
|
2
2
|
/** Shared by timer producers and browser diagnostics, regardless of bundling. */
|
|
3
3
|
const CANONICAL_INPUT_TIMER_OWNER = "folio.canonical-input-timer";
|
|
4
|
+
/** Delay after native composition end before the canonical session settles it. */
|
|
5
|
+
const CANONICAL_COMPOSITION_FLUSH_DELAY_MS = 25;
|
|
4
6
|
/** Mark the scheduled wrapper, never the caller's potentially shared callback. */
|
|
5
7
|
const setCanonicalInputTimer = (callback, delay) => {
|
|
6
8
|
const ownedCallback = () => callback();
|
|
7
9
|
Object.defineProperty(ownedCallback, Symbol.for(CANONICAL_INPUT_TIMER_OWNER), { value: true });
|
|
8
10
|
return setTimeout(ownedCallback, delay);
|
|
9
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Run `retry` once the view's canonical composition has settled. The session
|
|
14
|
+
* settles on an equal-delay timer armed at native composition end, so a retry
|
|
15
|
+
* armed at or after that point runs after it. Returns a disposer.
|
|
16
|
+
*/
|
|
17
|
+
const afterCanonicalCompositionSettles = (view, retry) => {
|
|
18
|
+
let timer;
|
|
19
|
+
const schedule = () => {
|
|
20
|
+
timer = setCanonicalInputTimer(retry, 25);
|
|
21
|
+
};
|
|
22
|
+
const onEnd = () => {
|
|
23
|
+
view.dom.removeEventListener("compositionend", onEnd);
|
|
24
|
+
schedule();
|
|
25
|
+
};
|
|
26
|
+
if (view.composing) view.dom.addEventListener("compositionend", onEnd);
|
|
27
|
+
else schedule();
|
|
28
|
+
return () => {
|
|
29
|
+
view.dom.removeEventListener("compositionend", onEnd);
|
|
30
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
31
|
+
};
|
|
32
|
+
};
|
|
10
33
|
//#endregion
|
|
11
|
-
export { CANONICAL_INPUT_TIMER_OWNER, setCanonicalInputTimer };
|
|
34
|
+
export { CANONICAL_COMPOSITION_FLUSH_DELAY_MS, CANONICAL_INPUT_TIMER_OWNER, afterCanonicalCompositionSettles, setCanonicalInputTimer };
|
|
@@ -15,8 +15,8 @@ declare const CANONICAL_PUBLIC_OPERATION_DISPOSITIONS: {
|
|
|
15
15
|
readonly insertAfterBlock: "publicOps.unsupportedInline";
|
|
16
16
|
readonly insertBeforeBlock: "publicOps.unsupportedInline";
|
|
17
17
|
readonly deleteBlock: "publicOps.unsupportedInline";
|
|
18
|
-
readonly commentOnBlock: "
|
|
19
|
-
readonly commentOnRange: "
|
|
18
|
+
readonly commentOnBlock: "compile";
|
|
19
|
+
readonly commentOnRange: "compile";
|
|
20
20
|
readonly insertTable: "publicOps.tableProjection";
|
|
21
21
|
readonly insertSignatureTable: "publicOps.tableProjection";
|
|
22
22
|
readonly deleteTable: "publicOps.tableProjection";
|
|
@@ -6,9 +6,10 @@ import { wordDiffSessionFromOptions } from "../ai-edits/word-diff.js";
|
|
|
6
6
|
import { getFolioDocumentOperationIssues, getFolioDocumentOperationReceiptsForStory, parseFolioDocumentOperationBatch } from "../document-operations.js";
|
|
7
7
|
import { maxAnnotationIdInDoc } from "../prosemirror/plugins/revisionIds.js";
|
|
8
8
|
import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
|
|
9
|
+
import { canonicalCommentBody, compileCanonicalComments } from "./canonicalComments.js";
|
|
9
10
|
import { Result, TaggedError, panic } from "better-result";
|
|
10
11
|
import { Mark } from "prosemirror-model";
|
|
11
|
-
import { OP_STORIES, applyDocumentOps, compileEditorIntent, createEditorIntentIdAllocator, documentStories } from "@stll/docx-core/ops";
|
|
12
|
+
import { OP_STORIES, applyDocumentOps, compileEditorIntent, createEditorIntentIdAllocator, documentStories, findStoryBody, freshCommentId, paragraphLength } from "@stll/docx-core/ops";
|
|
12
13
|
//#region src/controller/canonicalPublicOperations.ts
|
|
13
14
|
/** Public batches compile against the read projection; only the canonical journal publishes. */
|
|
14
15
|
/** A new public kind cannot bypass a canonical compiler/refusal decision. */
|
|
@@ -23,8 +24,8 @@ const CANONICAL_PUBLIC_OPERATION_DISPOSITIONS = {
|
|
|
23
24
|
insertAfterBlock: CANONICAL_GAP.publicUnsupportedInline,
|
|
24
25
|
insertBeforeBlock: CANONICAL_GAP.publicUnsupportedInline,
|
|
25
26
|
deleteBlock: CANONICAL_GAP.publicUnsupportedInline,
|
|
26
|
-
commentOnBlock:
|
|
27
|
-
commentOnRange:
|
|
27
|
+
commentOnBlock: "compile",
|
|
28
|
+
commentOnRange: "compile",
|
|
28
29
|
insertTable: CANONICAL_GAP.publicTableProjection,
|
|
29
30
|
insertSignatureTable: CANONICAL_GAP.publicTableProjection,
|
|
30
31
|
deleteTable: CANONICAL_GAP.publicTableProjection,
|
|
@@ -35,6 +36,21 @@ const CANONICAL_PUBLIC_OPERATION_DISPOSITIONS = {
|
|
|
35
36
|
mergeTableCells: CANONICAL_GAP.publicTableProjection,
|
|
36
37
|
splitTableCell: CANONICAL_GAP.publicTableProjection
|
|
37
38
|
};
|
|
39
|
+
const resolveComment = ({ operation, from, to }) => {
|
|
40
|
+
if ("comment" in operation && operation.comment !== void 0) return {
|
|
41
|
+
type: "commented",
|
|
42
|
+
operation,
|
|
43
|
+
comment: {
|
|
44
|
+
text: operation.comment.text,
|
|
45
|
+
from,
|
|
46
|
+
to
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
type: "plain",
|
|
51
|
+
operation
|
|
52
|
+
};
|
|
53
|
+
};
|
|
38
54
|
const compilePublicIntents = ({ document, intents, mode, author, date, nextRevisionId, allocate }) => {
|
|
39
55
|
const ops = [];
|
|
40
56
|
const revisions = [];
|
|
@@ -183,8 +199,8 @@ var CanonicalPublicOperations = class {
|
|
|
183
199
|
continue;
|
|
184
200
|
}
|
|
185
201
|
const disposition = CANONICAL_PUBLIC_OPERATION_DISPOSITIONS[operation.type];
|
|
186
|
-
if (disposition !== "compile"
|
|
187
|
-
refusals.set(operation.id, { gap: disposition
|
|
202
|
+
if (disposition !== "compile") {
|
|
203
|
+
refusals.set(operation.id, { gap: disposition });
|
|
188
204
|
skip(operation.id, "unsupportedBlock");
|
|
189
205
|
continue;
|
|
190
206
|
}
|
|
@@ -217,7 +233,8 @@ var CanonicalPublicOperations = class {
|
|
|
217
233
|
const appliedById = /* @__PURE__ */ new Map();
|
|
218
234
|
const allocate = createEditorIntentIdAllocator();
|
|
219
235
|
const date = options.revisionStamp?.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
220
|
-
for (const
|
|
236
|
+
for (const resolvedOperation of resolved) {
|
|
237
|
+
const { operation, intents, normalizations: adjustments } = resolvedOperation;
|
|
221
238
|
const compiled = compilePublicIntents({
|
|
222
239
|
document,
|
|
223
240
|
intents,
|
|
@@ -232,18 +249,71 @@ var CanonicalPublicOperations = class {
|
|
|
232
249
|
skip(operation.id, "unsupportedBlock", compiled.error.message);
|
|
233
250
|
continue;
|
|
234
251
|
}
|
|
235
|
-
|
|
252
|
+
let nextDocument = compiled.value.document;
|
|
253
|
+
const operationOps = [...compiled.value.ops];
|
|
254
|
+
let commentId;
|
|
255
|
+
switch (resolvedOperation.type) {
|
|
256
|
+
case "plain": break;
|
|
257
|
+
case "commented": {
|
|
258
|
+
const { comment } = resolvedOperation;
|
|
259
|
+
const id = freshCommentId(nextDocument);
|
|
260
|
+
const before = findStoryBody(document, story)?.content.find((block) => block.type === "paragraph" && block.paraId === comment.from.blockId);
|
|
261
|
+
const after = findStoryBody(nextDocument, story)?.content.find((block) => block.type === "paragraph" && block.paraId === comment.from.blockId);
|
|
262
|
+
if (id.isErr() || before?.type !== "paragraph" || after?.type !== "paragraph") {
|
|
263
|
+
refusals.set(operation.id, { gap: CANONICAL_GAP.publicComments });
|
|
264
|
+
skip(operation.id, "unsupportedBlock", id.isErr() ? id.error.message : "The comment anchor no longer exists.");
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const to = {
|
|
268
|
+
...comment.to,
|
|
269
|
+
offset: comment.to.offset + paragraphLength(after) - paragraphLength(before)
|
|
270
|
+
};
|
|
271
|
+
const commented = compileCanonicalComments({
|
|
272
|
+
document: nextDocument,
|
|
273
|
+
command: {
|
|
274
|
+
type: "create",
|
|
275
|
+
comment: {
|
|
276
|
+
id: id.value,
|
|
277
|
+
author: options.author ?? "AI",
|
|
278
|
+
date,
|
|
279
|
+
done: false,
|
|
280
|
+
content: canonicalCommentBody(nextDocument, comment.text)
|
|
281
|
+
},
|
|
282
|
+
anchor: to.offset === comment.from.offset ? {
|
|
283
|
+
kind: "point",
|
|
284
|
+
at: comment.from
|
|
285
|
+
} : {
|
|
286
|
+
kind: "range",
|
|
287
|
+
from: comment.from,
|
|
288
|
+
to
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
if (commented.isErr()) {
|
|
293
|
+
refusals.set(operation.id, { gap: CANONICAL_GAP.publicComments });
|
|
294
|
+
skip(operation.id, "unsupportedBlock", commented.error.message);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
nextDocument = commented.value.document;
|
|
298
|
+
operationOps.push(...commented.value.ops);
|
|
299
|
+
commentId = id.value;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
default: panic(`Unknown resolved operation ${resolvedOperation}`);
|
|
303
|
+
}
|
|
304
|
+
if (operationOps.length === 0) {
|
|
236
305
|
skip(operation.id, "noopOperation");
|
|
237
306
|
continue;
|
|
238
307
|
}
|
|
239
308
|
normalizations.push(...adjustments);
|
|
240
|
-
document =
|
|
241
|
-
ops.push(...
|
|
242
|
-
nextRevisionId = compiled.value.nextRevisionId;
|
|
309
|
+
document = nextDocument;
|
|
310
|
+
ops.push(...operationOps);
|
|
311
|
+
nextRevisionId = Math.max(compiled.value.nextRevisionId, (commentId ?? -1) + 1);
|
|
243
312
|
const revisionIds = compiled.value.revisions;
|
|
244
313
|
const revisionId = revisionIds.at(0);
|
|
245
314
|
appliedById.set(operation.id, {
|
|
246
315
|
id: operation.id,
|
|
316
|
+
...commentId === void 0 ? {} : { commentId },
|
|
247
317
|
...revisionId === void 0 ? {} : {
|
|
248
318
|
revisionId,
|
|
249
319
|
revisionIds
|
|
@@ -328,17 +398,18 @@ var CanonicalPublicOperations = class {
|
|
|
328
398
|
reason,
|
|
329
399
|
message: message ?? `The operation was refused: ${reason}.`
|
|
330
400
|
}));
|
|
331
|
-
if ("comment" in operation) return refusal("unsupportedBlock", "Canonical comment operations are unavailable.");
|
|
332
401
|
switch (operation.type) {
|
|
333
402
|
case "replaceInBlock":
|
|
334
403
|
case "replaceRange":
|
|
335
404
|
case "replaceBlock":
|
|
336
405
|
case "splitBlock":
|
|
337
406
|
case "formatRange":
|
|
338
|
-
case "mergeBlockWithNext":
|
|
407
|
+
case "mergeBlockWithNext":
|
|
408
|
+
case "commentOnRange":
|
|
409
|
+
case "commentOnBlock": break;
|
|
339
410
|
default: return refusal("unsupportedBlock", `Canonical public operation ${operation.type} is unavailable.`);
|
|
340
411
|
}
|
|
341
|
-
const blockId = operation.type === "replaceRange" || operation.type === "formatRange" ? operation.range.blockId : operation.blockId;
|
|
412
|
+
const blockId = operation.type === "replaceRange" || operation.type === "formatRange" || operation.type === "commentOnRange" ? operation.range.blockId : operation.blockId;
|
|
342
413
|
const anchor = Object.hasOwn(options.snapshot.anchors, blockId) ? options.snapshot.anchors[blockId] : void 0;
|
|
343
414
|
const projection = this.options.session.projectStory(story);
|
|
344
415
|
if (!anchor || projection.isErr()) return refusal("missingBlock");
|
|
@@ -364,8 +435,17 @@ var CanonicalPublicOperations = class {
|
|
|
364
435
|
end = start + operation.find.length;
|
|
365
436
|
text = operation.replace;
|
|
366
437
|
break;
|
|
438
|
+
case "commentOnBlock":
|
|
439
|
+
if (operation.quote !== void 0) {
|
|
440
|
+
start = clean.text.indexOf(operation.quote);
|
|
441
|
+
if (start < 0) return refusal("missingFind");
|
|
442
|
+
if (clean.text.indexOf(operation.quote, start + 1) >= 0) return refusal("ambiguousFind");
|
|
443
|
+
end = start + operation.quote.length;
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
367
446
|
case "replaceRange":
|
|
368
447
|
case "formatRange":
|
|
448
|
+
case "commentOnRange":
|
|
369
449
|
start = operation.range.startOffset;
|
|
370
450
|
end = operation.range.endOffset;
|
|
371
451
|
if (hashFolioAIBlockText(clean.text.slice(start, end)) !== operation.range.selectedTextHash) return refusal("staleRange");
|
|
@@ -401,7 +481,19 @@ var CanonicalPublicOperations = class {
|
|
|
401
481
|
const from = projection.value.addressAt(range.from);
|
|
402
482
|
const to = projection.value.addressAt(range.to);
|
|
403
483
|
if (from.isErr() || to.isErr()) return refusal("unsupportedBlock");
|
|
404
|
-
|
|
484
|
+
const resolvedComment = resolveComment({
|
|
485
|
+
operation,
|
|
486
|
+
from: from.value,
|
|
487
|
+
to: to.value
|
|
488
|
+
});
|
|
489
|
+
if (operation.type === "commentOnBlock" || operation.type === "commentOnRange") return Result.ok({
|
|
490
|
+
...resolvedComment,
|
|
491
|
+
intents: [],
|
|
492
|
+
normalizations: [],
|
|
493
|
+
from: range.from,
|
|
494
|
+
to: range.to
|
|
495
|
+
});
|
|
496
|
+
if (resolvedComment.type === "plain" && operation.type !== "splitBlock" && operation.type !== "formatRange" && operation.type !== "mergeBlockWithNext" && clean.text.slice(start, end) === text) return refusal("noopOperation");
|
|
405
497
|
let intent;
|
|
406
498
|
switch (operation.type) {
|
|
407
499
|
case "splitBlock":
|
|
@@ -544,7 +636,7 @@ var CanonicalPublicOperations = class {
|
|
|
544
636
|
if (structural) claimedTo = paragraph.start + paragraph.node.content.size;
|
|
545
637
|
if (nextParagraph) claimedTo = nextParagraph.start + nextParagraph.node.content.size;
|
|
546
638
|
return Result.ok({
|
|
547
|
-
|
|
639
|
+
...resolvedComment,
|
|
548
640
|
intents,
|
|
549
641
|
normalizations,
|
|
550
642
|
from: structural ? paragraph.start : range.from,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CanonicalProjection, CanonicalSelection } from "./canonicalSession.js";
|
|
2
|
+
import { Transaction } from "prosemirror-state";
|
|
3
|
+
//#region src/controller/canonicalSelection.d.ts
|
|
4
|
+
type RestoreCanonicalSelectionOptions = {
|
|
5
|
+
transaction: Transaction;
|
|
6
|
+
projection: CanonicalProjection;
|
|
7
|
+
selection: CanonicalSelection;
|
|
8
|
+
unavailable: {
|
|
9
|
+
type: "refuse";
|
|
10
|
+
} | {
|
|
11
|
+
type: "near";
|
|
12
|
+
anchor: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/** Restore the admitted selection kind after a canonical projection changes. */
|
|
16
|
+
declare const restoreCanonicalSelection: ({ transaction, projection, selection, unavailable }: RestoreCanonicalSelectionOptions) => boolean;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { restoreCanonicalSelection };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { panic } from "better-result";
|
|
2
|
+
import { AllSelection, NodeSelection, TextSelection } from "prosemirror-state";
|
|
3
|
+
//#region src/controller/canonicalSelection.ts
|
|
4
|
+
/** Restore the admitted selection kind after a canonical projection changes. */
|
|
5
|
+
const restoreCanonicalSelection = ({ transaction, projection, selection, unavailable }) => {
|
|
6
|
+
switch (selection.type) {
|
|
7
|
+
case "inlineNode": {
|
|
8
|
+
const anchor = projection.positionAt(selection.anchor);
|
|
9
|
+
const head = projection.positionAt(selection.head);
|
|
10
|
+
if (anchor.isErr() || head.isErr()) return false;
|
|
11
|
+
const node = transaction.doc.nodeAt(anchor.value);
|
|
12
|
+
if (!node?.isInline || !node.isAtom || !NodeSelection.isSelectable(node) || anchor.value + node.nodeSize !== head.value) return false;
|
|
13
|
+
transaction.setSelection(NodeSelection.create(transaction.doc, anchor.value));
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
case "all":
|
|
17
|
+
transaction.setSelection(new AllSelection(transaction.doc));
|
|
18
|
+
return true;
|
|
19
|
+
case "text": {
|
|
20
|
+
const anchor = projection.positionAt(selection.anchor);
|
|
21
|
+
const head = projection.positionAt(selection.head);
|
|
22
|
+
if (anchor.isOk() && head.isOk()) {
|
|
23
|
+
transaction.setSelection(TextSelection.create(transaction.doc, anchor.value, head.value));
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
switch (unavailable.type) {
|
|
27
|
+
case "refuse": return false;
|
|
28
|
+
case "near":
|
|
29
|
+
transaction.setSelection(TextSelection.near(transaction.doc.resolve(Math.min(unavailable.anchor, transaction.doc.content.size))));
|
|
30
|
+
return true;
|
|
31
|
+
default: return panic(`Unknown selection restoration policy: ${unavailable}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
default: {
|
|
35
|
+
const exhaustive = selection.type;
|
|
36
|
+
return panic(`Unknown canonical selection type: ${exhaustive}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { restoreCanonicalSelection };
|
|
@@ -14,7 +14,7 @@ declare class CanonicalSessionError extends CanonicalSessionError_base<{
|
|
|
14
14
|
reason: "refused" | "noChange";
|
|
15
15
|
}> {}
|
|
16
16
|
type CanonicalSelection = {
|
|
17
|
-
type: "text" | "all";
|
|
17
|
+
type: "text" | "inlineNode" | "all";
|
|
18
18
|
anchor: TextPosition;
|
|
19
19
|
head: TextPosition;
|
|
20
20
|
};
|
|
@@ -59,6 +59,7 @@ type CanonicalCommit = {
|
|
|
59
59
|
document: document_d_exports.Document;
|
|
60
60
|
projection: CanonicalProjection;
|
|
61
61
|
bodyProjection: CanonicalProjection;
|
|
62
|
+
selection: CanonicalSelection;
|
|
62
63
|
touched: TouchedBlocks;
|
|
63
64
|
version: number;
|
|
64
65
|
origin: CanonicalOrigin;
|
|
@@ -116,6 +117,8 @@ declare class CanonicalSession {
|
|
|
116
117
|
private readonly undone;
|
|
117
118
|
private readonly styles;
|
|
118
119
|
constructor({ document, projection, styles }: CanonicalSessionSeedOptions);
|
|
120
|
+
/** Render only the committed comment projection while provisional IME blocks document snapshots. */
|
|
121
|
+
getCommittedComments(): import("@stll/docx-core/model").Comment[];
|
|
119
122
|
setMode(mode: CanonicalSessionMode): void;
|
|
120
123
|
hasStyle(styleId: string): boolean;
|
|
121
124
|
private advanceBlockId;
|