rapier-markdown-kit 1.2.45 → 1.2.46
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/LICENSE +12 -5
- package/README.md +131 -10
- package/dist/agent/vendor/BROWSER-ENTITIES.json +36 -0
- package/dist/agent/vendor/MARKDOWN-RUNTIME.json +136 -0
- package/dist/agent/vendor/MARKDOWNIT.json +582 -0
- package/dist/agent/vendor/NOTICE.txt +60 -0
- package/dist/agent/vendor/markdown-licenses/entities-LICENSE.txt +11 -0
- package/dist/agent/vendor/markdown-licenses/linkify-it-LICENSE.txt +22 -0
- package/dist/agent/vendor/markdown-licenses/markdown-it-LICENSE.txt +22 -0
- package/dist/agent/vendor/markdown-licenses/mdurl-LICENSE.txt +45 -0
- package/dist/agent/vendor/markdown-licenses/punycode.js-LICENSE-MIT.txt +20 -0
- package/dist/agent/vendor/markdown-licenses/uc.micro-LICENSE.txt +20 -0
- package/dist/agent/vendor/markdown-plugins.json +46 -0
- package/dist/agent/vendor/markdown-plugins.mjs +27 -0
- package/dist/agent/vendor/markdown-runtime.mjs +13 -0
- package/dist/kit/assets.d.mts +106 -0
- package/dist/kit/index.d.mts +151 -0
- package/dist/kit/layout.d.mts +61 -0
- package/dist/kit/ledger/carried.d.mts +12 -6
- package/dist/kit/ledger/carried.mjs +1 -1
- package/dist/kit/ledger/format.mjs +5 -3
- package/dist/kit/ledger/text-merge.mjs +33 -0
- package/dist/kit/ledger/transport.mjs +61 -2
- package/dist/kit/ledger.d.mts +137 -31
- package/dist/kit/markdown-it.d.mts +2 -2
- package/dist/kit/marks.d.mts +241 -0
- package/dist/kit/marks.mjs +39 -1
- package/dist/kit/model.d.mts +191 -0
- package/dist/kit/render-entry.d.mts +654 -0
- package/dist/kit/render.mjs +8 -7
- package/dist/kit/typography.d.mts +42 -0
- package/dist/kit/will.d.mts +26 -3
- package/dist/math/delimiters.mjs +38 -0
- package/dist/math/markdown-rules.mjs +103 -0
- package/dist/spec/document-fonts.mjs +2 -10
- package/dist/spec/md-assets.mjs +1 -0
- package/dist/spec/md-comments.mjs +384 -0
- package/dist/spec/md-marks.mjs +9 -0
- package/dist/spec/md-parser-config.mjs +463 -0
- package/dist/spec/md-parser-runtime.mjs +23 -0
- package/package.json +30 -8
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
export {
|
|
3
|
+
INDENT_LIMIT,
|
|
4
|
+
SPACING_FIELDS,
|
|
5
|
+
alignments,
|
|
6
|
+
decodeLayoutAttribute,
|
|
7
|
+
fields,
|
|
8
|
+
formatLayout,
|
|
9
|
+
imageStyle,
|
|
10
|
+
indentLevel,
|
|
11
|
+
linesHeightCss,
|
|
12
|
+
objectHeight,
|
|
13
|
+
parseLayout,
|
|
14
|
+
parseLayoutAttribute,
|
|
15
|
+
stepIndent,
|
|
16
|
+
textLayout,
|
|
17
|
+
textStyle,
|
|
18
|
+
validLayout,
|
|
19
|
+
wrapColumnFloor,
|
|
20
|
+
wrapNeighbour,
|
|
21
|
+
wrapTextBlock,
|
|
22
|
+
wraps
|
|
23
|
+
} from './layout.mjs';
|
|
24
|
+
export {
|
|
25
|
+
FONT_STACKS,
|
|
26
|
+
INK_CLOSE,
|
|
27
|
+
INK_INT_MAX,
|
|
28
|
+
INK_KINDS,
|
|
29
|
+
INK_OPEN_PATTERN,
|
|
30
|
+
INK_PATH_MAX,
|
|
31
|
+
PAGE_BREAK_MARKER,
|
|
32
|
+
TEXT_COLOR_NAMES,
|
|
33
|
+
fontFamilyCss,
|
|
34
|
+
formatColorRun,
|
|
35
|
+
formatFontRun,
|
|
36
|
+
formatInkOpen,
|
|
37
|
+
formatInkOpenFromBody,
|
|
38
|
+
formatInkRun,
|
|
39
|
+
formatPageBreak,
|
|
40
|
+
hasInkMarker,
|
|
41
|
+
inkOpenBody,
|
|
42
|
+
isInkClose,
|
|
43
|
+
isPageBreakBlock,
|
|
44
|
+
isPageBreakLine,
|
|
45
|
+
matchInkOpen,
|
|
46
|
+
mayRetireInk,
|
|
47
|
+
normalizeFontFamily,
|
|
48
|
+
pairInkMarkers,
|
|
49
|
+
pairInkSpans,
|
|
50
|
+
pairMarkers,
|
|
51
|
+
parseInkBody,
|
|
52
|
+
parseInkOpen,
|
|
53
|
+
prepareInkPaste,
|
|
54
|
+
quotedWillSpan,
|
|
55
|
+
readInkMatch,
|
|
56
|
+
scanInkMarkers,
|
|
57
|
+
sliceInkArrows,
|
|
58
|
+
stripInkMarkers,
|
|
59
|
+
typography,
|
|
60
|
+
typographySourceRanges
|
|
61
|
+
} from './marks.mjs';
|
|
62
|
+
export {
|
|
63
|
+
alphaProfile,
|
|
64
|
+
bandsProfile,
|
|
65
|
+
besideSlices,
|
|
66
|
+
boxClearance,
|
|
67
|
+
captureFontAuthority,
|
|
68
|
+
columnsFilled,
|
|
69
|
+
createLineMeasurer,
|
|
70
|
+
createPreparationScope,
|
|
71
|
+
edgeLines,
|
|
72
|
+
edgeMarks,
|
|
73
|
+
flowLines,
|
|
74
|
+
frameScale,
|
|
75
|
+
imageBox,
|
|
76
|
+
imageOnly,
|
|
77
|
+
invalidateRichMetrics,
|
|
78
|
+
layoutNextRichInlineLineRange,
|
|
79
|
+
lineMetrics,
|
|
80
|
+
linesHeight,
|
|
81
|
+
linesSlices,
|
|
82
|
+
linesTop,
|
|
83
|
+
mapFragment,
|
|
84
|
+
materializeRichInlineLineRange,
|
|
85
|
+
parseProfile,
|
|
86
|
+
pictureSlices,
|
|
87
|
+
polygonProfile,
|
|
88
|
+
prepareRichParagraph,
|
|
89
|
+
rasterReserved,
|
|
90
|
+
rasterTiltProfile,
|
|
91
|
+
rotatedBoundsRad,
|
|
92
|
+
rotatedRasterAlpha,
|
|
93
|
+
serializeProfile,
|
|
94
|
+
slotSequence,
|
|
95
|
+
slotsForBand,
|
|
96
|
+
supportsRichStyle,
|
|
97
|
+
wrapShape
|
|
98
|
+
} from './model.mjs';
|
|
99
|
+
export {
|
|
100
|
+
evaluateWill,
|
|
101
|
+
hasWillMarkers,
|
|
102
|
+
parseWill,
|
|
103
|
+
stripOneTerminator,
|
|
104
|
+
terminatorWidth,
|
|
105
|
+
willGovern,
|
|
106
|
+
willIntentOf,
|
|
107
|
+
willMarkerOf,
|
|
108
|
+
willOffsetBefore,
|
|
109
|
+
willRegionsIn,
|
|
110
|
+
willTouchesMarker,
|
|
111
|
+
willTransition
|
|
112
|
+
} from './will.mjs';
|
|
113
|
+
export {
|
|
114
|
+
IMAGE_LIMITS,
|
|
115
|
+
appendAsset,
|
|
116
|
+
appendAssetText,
|
|
117
|
+
assetOmissions,
|
|
118
|
+
assetTitle,
|
|
119
|
+
blockwiseAssets,
|
|
120
|
+
configureParser,
|
|
121
|
+
dataImage,
|
|
122
|
+
documentAssets,
|
|
123
|
+
escapeImageAlt,
|
|
124
|
+
imageEnvironment,
|
|
125
|
+
installMarkdownImages,
|
|
126
|
+
isAssetBlock,
|
|
127
|
+
markdownBodyOffset,
|
|
128
|
+
markdownParser,
|
|
129
|
+
mayRetireImageDefinitions,
|
|
130
|
+
normalizeLabel,
|
|
131
|
+
packAssetIndex,
|
|
132
|
+
parseAssets,
|
|
133
|
+
projectImageDefinitions,
|
|
134
|
+
referenceOccurs,
|
|
135
|
+
restoreAssetIndex,
|
|
136
|
+
retireDeletedImageDefinitions,
|
|
137
|
+
serializeAsset
|
|
138
|
+
} from './assets.mjs';
|
|
139
|
+
export {
|
|
140
|
+
createMarkdownRenderer,
|
|
141
|
+
createPrintRenderer,
|
|
142
|
+
createRenderSanitizer,
|
|
143
|
+
createRenderStyles,
|
|
144
|
+
createRenderer,
|
|
145
|
+
exportFontCss,
|
|
146
|
+
fontRequests,
|
|
147
|
+
requireDocumentFonts,
|
|
148
|
+
waitForFontRequests,
|
|
149
|
+
waitForFonts
|
|
150
|
+
} from './render-entry.mjs';
|
|
151
|
+
export * as ledger from './ledger.mjs';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
export {};
|
|
3
|
+
export type Alignment = 'left' | 'center' | 'right' | 'justify';
|
|
4
|
+
export type Wrap = 'around' | 'beside' | 'box' | 'behind' | 'front';
|
|
5
|
+
export type LineSpacing = number | `${number}pt` | `${number}pt+`;
|
|
6
|
+
export interface TextLayout {
|
|
7
|
+
first?: number;
|
|
8
|
+
indent?: number;
|
|
9
|
+
marker?: number;
|
|
10
|
+
hyphens?: 'auto';
|
|
11
|
+
caps?: 'small';
|
|
12
|
+
hanging?: 'on';
|
|
13
|
+
before?: number;
|
|
14
|
+
after?: number;
|
|
15
|
+
line?: LineSpacing;
|
|
16
|
+
}
|
|
17
|
+
interface LayoutFacts extends TextLayout {
|
|
18
|
+
rotate?: number;
|
|
19
|
+
opacity?: number;
|
|
20
|
+
lock?: 'on';
|
|
21
|
+
}
|
|
22
|
+
type LayoutSize = {width?: number; lines?: never} | {lines?: number; width?: never};
|
|
23
|
+
type LayoutPosition =
|
|
24
|
+
| {align?: Alignment; wrap?: never; x?: never; y?: never}
|
|
25
|
+
| {align?: never; wrap: Wrap; x?: number; y?: number}
|
|
26
|
+
| {align?: never; wrap?: never; width: number; x?: number; y?: never};
|
|
27
|
+
/** Numeric bounds and precision are checked by validLayout and formatLayout. */
|
|
28
|
+
export type Layout = LayoutFacts & LayoutSize & LayoutPosition;
|
|
29
|
+
export type LayoutField = 'align' | 'width' | 'lines' | 'wrap' | 'x' | 'y' | 'rotate' | 'opacity' |
|
|
30
|
+
'first' | 'indent' | 'marker' | 'lock' | 'hyphens' | 'caps' | 'hanging' | 'before' | 'after' | 'line';
|
|
31
|
+
export const fields: Set<LayoutField>;
|
|
32
|
+
export const SPACING_FIELDS: readonly ['before', 'after', 'line'];
|
|
33
|
+
export const alignments: Set<Alignment>;
|
|
34
|
+
export const wraps: Set<Wrap>;
|
|
35
|
+
export const INDENT_LIMIT: 64;
|
|
36
|
+
export function indentLevel(value: unknown): value is number;
|
|
37
|
+
export function stepIndent(value: number | null | undefined, step: number): number;
|
|
38
|
+
export function validLayout(value: unknown): value is Layout;
|
|
39
|
+
export function parseLayout(comment: string): Layout | null;
|
|
40
|
+
export function decodeLayoutAttribute(value: string | null | undefined): string;
|
|
41
|
+
export function parseLayoutAttribute(value: string | null | undefined): Layout | null;
|
|
42
|
+
export function textStyle(value: Layout, options?: {portable?: boolean; pointSize?: number; indentOffset?: number}): string;
|
|
43
|
+
export function textLayout(style: CSSStyleDeclaration | Readonly<Record<string, string>> | null | undefined,
|
|
44
|
+
options?: {listMarker?: boolean; pointSize?: number; offset?: number}): TextLayout;
|
|
45
|
+
export function imageStyle(value: Layout): string;
|
|
46
|
+
export function linesHeightCss(lines: number): string;
|
|
47
|
+
export interface ObjectSizeAttributes {
|
|
48
|
+
height?: number | `${number}` | 'auto';
|
|
49
|
+
viewport?: `${number}x${number}`;
|
|
50
|
+
resize?: 'scale' | 'reflow';
|
|
51
|
+
}
|
|
52
|
+
export function objectHeight(width: number, naturalHeight: number, attrs: ObjectSizeAttributes, line: number): number;
|
|
53
|
+
export function wrapTextBlock<T extends Element>(element: T | null | undefined): T | null;
|
|
54
|
+
export type WrapClassification<Stop> = string | false | null | undefined | {stop: Stop};
|
|
55
|
+
export function wrapNeighbour<T extends Element, Stop = T | null>(node: T,
|
|
56
|
+
classify: (node: Element) => WrapClassification<Stop>): Element | Stop | null;
|
|
57
|
+
export function wrapNeighbour<T, Stop = T | null>(node: T,
|
|
58
|
+
classify: (node: T) => WrapClassification<Stop>, step: (node: T, direction: 1 | -1) => T | null | undefined): T | Stop | null;
|
|
59
|
+
export function wrapColumnFloor(fontSize: number | null | undefined): number;
|
|
60
|
+
/** Throws TypeError when the supplied values do not belong to the closed v1 grammar. */
|
|
61
|
+
export function formatLayout(value: Layout): string;
|
|
@@ -10,13 +10,19 @@ export function documentTrailer(text: string, parts?: DocumentParts): string;
|
|
|
10
10
|
/** Splits a saved file into its exact text and verified parts; throws on a damaged or mismatched comment. */
|
|
11
11
|
export function readDocument(source: string): ReadParts;
|
|
12
12
|
export function writeParts(text: string, parts?: DocumentParts): string;
|
|
13
|
-
|
|
13
|
+
/** Inert browser script elements or equivalent parsed records; no DOM dependency. */
|
|
14
|
+
export interface CarriedElement { id: string; type: string; textContent: string | null; tagName?: string; }
|
|
15
|
+
export function readParts(text: string, elements: Iterable<CarriedElement>): {ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
|
|
14
16
|
export function validateParts(text: string, parts?: DocumentParts): {ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
|
|
15
|
-
export function takeParts(html: string, text?: string): {html: string; ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
|
|
17
|
+
export function takeParts(html: string, text?: string | null): {html: string; ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
|
|
16
18
|
export function encodeCarried(text: string): string;
|
|
17
19
|
export function decodeCarried(text: string): string;
|
|
18
|
-
export interface ComparisonBase { text: string; sha256: string; revision: string; name: string; by: string; at: string; }
|
|
19
|
-
export function readBase(value:
|
|
20
|
-
export function writeBase(text: string, value
|
|
21
|
-
export
|
|
20
|
+
export interface ComparisonBase { text: string; sha256: string; revision: string; name: string; by: string; at: string; fonts?: string; }
|
|
21
|
+
export function readBase(value: ComparisonBase): ComparisonBase;
|
|
22
|
+
export function writeBase(text: string, value?: ComparisonBase | null): string;
|
|
23
|
+
export interface ComparisonBaseElement extends CarriedElement {
|
|
24
|
+
getAttribute?: (name: string) => string | null;
|
|
25
|
+
name?: string; sha256?: string; revision?: string; by?: string; at?: string; fonts?: string;
|
|
26
|
+
}
|
|
27
|
+
export function readBaseElements(elements: Iterable<ComparisonBaseElement>): ComparisonBase | null;
|
|
22
28
|
export function takeBase(html: string): {html: string; base: ComparisonBase | null};
|
|
@@ -57,8 +57,8 @@ export function writeDocument(text, options) {
|
|
|
57
57
|
return text + documentTrailer(text, options);
|
|
58
58
|
}
|
|
59
59
|
function unfinishedTrailer(source) {
|
|
60
|
+
if (!source.includes(OPEN)) return false;
|
|
60
61
|
const at = source.lastIndexOf(OPEN);
|
|
61
|
-
if (at < 0) return false;
|
|
62
62
|
let fence = null;
|
|
63
63
|
for (const line of source.slice(0, at).split('\n')) {
|
|
64
64
|
const found = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
@@ -170,6 +170,7 @@ const expandedRows = new WeakMap();
|
|
|
170
170
|
const sameSplices = (a, b) => a === b || a.length === b.length &&
|
|
171
171
|
a.every((row, i) => row.pos === b[i].pos && row.removed === b[i].removed && row.inserted === b[i].inserted);
|
|
172
172
|
const sameEffect = (a, b) => a === b || !!a && !!b && canonicalJSON(a) === canonicalJSON(b);
|
|
173
|
+
const RECENT_TEXT_LIMIT = 1 << 19;
|
|
173
174
|
let recentLedgers = [];
|
|
174
175
|
function* produceLedger({text, metadata, records, documentAuthority, revision, root, complete = false, earlier = records} = {}, native = false) {
|
|
175
176
|
if (!_rapierValidMetadata(metadata)) throw fault('document metadata required');
|
|
@@ -239,9 +240,10 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
|
|
|
239
240
|
const replayedMetadata = prepared.replayMetadata(startMetadata);
|
|
240
241
|
if (!replayedMetadata || canonicalJSON(replayedMetadata) !== canonicalJSON(metadata)) throw fault('metadata does not describe this document');
|
|
241
242
|
const ledger = {...payload, sha256: yield* checksum(envelopeParts(payload), native)};
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
const others = recentLedgers.filter(row => row.rows[0] !== expanded[0] && row.documentAuthority === documentAuthority);
|
|
244
|
+
recentLedgers = text.length + startText.length > RECENT_TEXT_LIMIT ? [] : [{rows: expanded, headText: text, headMetadata: canonicalJSON(metadata),
|
|
245
|
+
headBytes, startText, startMetadata: {...startMetadata}, startFacts, startBytes: bytes, documentAuthority, complete: payload.complete,
|
|
246
|
+
startRoot, startRevision, chainRoot: payload.head.root, chainRevision: payload.head.revision}, ...others].slice(0, 4);
|
|
245
247
|
ownLedger(ledger);
|
|
246
248
|
proofs.set(ledger, {shell: Array.from(envelopeParts({...ledger, start: {...ledger.start, text: ''}})).join(''), startText,
|
|
247
249
|
text, metadata: {...metadata}, root, revision, prepared: _rapierPrepareHistoryRecords(ledger.records),
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {parseWill, stripOneTerminator} from '../../agent/will.mjs';
|
|
3
|
+
import {diff} from '../../packages/rapier-diff/index.mjs';
|
|
4
|
+
import {transposeRecord} from './transport.mjs';
|
|
5
|
+
import {parseComments, commentSplices, derivedCommentEdits} from '../../spec/md-comments.mjs';
|
|
3
6
|
|
|
4
7
|
export function willMergeConflict(base, ours, theirs) {
|
|
5
8
|
const snapshot = text => {
|
|
@@ -450,6 +453,32 @@ function textRefineGroup({before, av, bv}, options) {
|
|
|
450
453
|
}
|
|
451
454
|
return textRefineLevel(before, av, bv, options, 'word');
|
|
452
455
|
}
|
|
456
|
+
function mergeDerivedComments(base, ours, theirs, options) {
|
|
457
|
+
if (!base.includes('<!-- md-comments:')) return null;
|
|
458
|
+
try {
|
|
459
|
+
const parsed = [base, ours, theirs].map(parseComments);
|
|
460
|
+
if (parsed.some(value => !value.record || value.reason || !value.current)) return null;
|
|
461
|
+
const edits = [ours, theirs].map(text => {
|
|
462
|
+
const changed = diff(base, text, {_unit: 'char'}).edits;
|
|
463
|
+
if (changed.length > 64 || changed.some(row => row.coarse)) return null;
|
|
464
|
+
const rows = changed.map(row => ({pos: row.before[0], removed: base.slice(...row.before),
|
|
465
|
+
inserted: text.slice(...row.after)})).reverse();
|
|
466
|
+
const proof = derivedCommentEdits(base, rows);
|
|
467
|
+
if (!proof) return null;
|
|
468
|
+
const footer = commentSplices(base, proof.edits);
|
|
469
|
+
return footer.length === 1 ? {splices: [...proof.edits, ...footer], derivedCommentIndex: proof.edits.length} : null;
|
|
470
|
+
});
|
|
471
|
+
if (edits.some(value => !value)) return null;
|
|
472
|
+
const body = mergeText(...parsed.map(value => value.body), options);
|
|
473
|
+
if (!body.clean) return null;
|
|
474
|
+
const [oursId, theirsId] = textDeviceOptions(options);
|
|
475
|
+
const moved = transposeRecord(base, edits[1].splices, theirsId,
|
|
476
|
+
[{...edits[0], transaction: {id: oursId}}], edits[1].derivedCommentIndex);
|
|
477
|
+
if (parseComments(moved.text).body !== body.text) return null;
|
|
478
|
+
return {text: moved.text, clean: true, conflicts: []};
|
|
479
|
+
} catch (_) { return null; }
|
|
480
|
+
}
|
|
481
|
+
|
|
453
482
|
export function mergeText(base, ours, theirs, options = {}) {
|
|
454
483
|
canonicalNote(ours); canonicalNote(theirs); if (base !== null) canonicalNote(base);
|
|
455
484
|
const unchanged = value => ({text: value, clean: !value.includes(TEXT_CONFLICT), conflicts: value.includes(TEXT_CONFLICT) ? [{kind: 'existing-conflict', advisory: true}] : []});
|
|
@@ -457,6 +486,10 @@ export function mergeText(base, ours, theirs, options = {}) {
|
|
|
457
486
|
const willConflict = willMergeConflict(base, ours, theirs);
|
|
458
487
|
if (!willConflict && base === ours) return unchanged(theirs);
|
|
459
488
|
if (!willConflict && base === theirs) return unchanged(ours);
|
|
489
|
+
if (!willConflict && base !== null) {
|
|
490
|
+
const commented = mergeDerivedComments(base, ours, theirs, options);
|
|
491
|
+
if (commented) return commented;
|
|
492
|
+
}
|
|
460
493
|
const conflicts = []; let text = '', a, b;
|
|
461
494
|
const conflict = (before, a, b, kind) => {
|
|
462
495
|
const ab = inspectTextConflicts(a), bb = inspectTextConflicts(b);
|
|
@@ -4,6 +4,7 @@ import {projectInverseSource} from './inverse-source.mjs';
|
|
|
4
4
|
import {authoredBasis, authoredPlacement, authoredPlacements} from './authored-origin.mjs';
|
|
5
5
|
import {sha256} from './hash.mjs';
|
|
6
6
|
import {canonicalJSON} from './data.mjs';
|
|
7
|
+
import {parseComments, commentSplices, derivedCommentEdits} from '../../spec/md-comments.mjs';
|
|
7
8
|
|
|
8
9
|
const whole = text => typeof text === 'string' && !/[\uD800-\uDFFF]/u.test(text);
|
|
9
10
|
function admitted(rows) {
|
|
@@ -59,7 +60,65 @@ function applyBatch(text, rows) {
|
|
|
59
60
|
return {text: after, splices: exact};
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
export function transposeRecord(base, splices, id, records) {
|
|
63
|
+
export function transposeRecord(base, splices, id, records, derivedCommentIndex) {
|
|
64
|
+
const comments = transposeCommentRecord(base, splices, id, records, derivedCommentIndex);
|
|
65
|
+
return comments || transposeTextRecord(base, splices, id, records);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function commentRecordProof(source, splices, index) {
|
|
69
|
+
if (typeof source !== 'string' || !source.includes('<!-- md-comments:') ||
|
|
70
|
+
!Array.isArray(splices) || !Number.isSafeInteger(index) || index !== splices.length - 1) return null;
|
|
71
|
+
try {
|
|
72
|
+
const parsed = parseComments(source), proof = derivedCommentEdits(source, splices);
|
|
73
|
+
if (!parsed.record || parsed.reason || !parsed.current || !proof || proof.edits.length !== index ||
|
|
74
|
+
canonicalJSON(proof.edits) !== canonicalJSON(splices.slice(0, index))) return null;
|
|
75
|
+
let start = parsed.record.start;
|
|
76
|
+
for (const row of proof.edits) {
|
|
77
|
+
if (row.pos >= start || row.pos + row.removed.length > start) return null;
|
|
78
|
+
start += row.inserted.length - row.removed.length;
|
|
79
|
+
}
|
|
80
|
+
const derived = commentSplices(source, proof.edits);
|
|
81
|
+
if (derived.length !== 1 || canonicalJSON(derived[0]) !== canonicalJSON(splices[index])) return null;
|
|
82
|
+
return {parsed, edits: proof.edits, text: _rapierTransformSplices(source, splices)};
|
|
83
|
+
} catch (_) { return null; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function transposeCommentRecord(base, splices, id, records, derivedCommentIndex) {
|
|
87
|
+
if (!Array.isArray(records) || !records.length) return null;
|
|
88
|
+
const local = commentRecordProof(base, splices, derivedCommentIndex);
|
|
89
|
+
if (!local) return null;
|
|
90
|
+
const bodyRecords = []; let current = base;
|
|
91
|
+
for (const record of records) {
|
|
92
|
+
const proof = commentRecordProof(current, record.splices, record.derivedCommentIndex);
|
|
93
|
+
if (!proof) return null;
|
|
94
|
+
bodyRecords.push({...record, splices: proof.edits}); current = proof.text;
|
|
95
|
+
}
|
|
96
|
+
const moved = transposeTextRecord(local.parsed.body, local.edits, id, bodyRecords);
|
|
97
|
+
const derive = (source, rows) => {
|
|
98
|
+
const parsed = parseComments(source); let start = parsed.record?.start;
|
|
99
|
+
if (!parsed.record || parsed.reason || !parsed.current) throw new RangeError('canonical_act_rebase_conflict');
|
|
100
|
+
for (const row of rows) {
|
|
101
|
+
if (row.pos >= start || row.pos + row.removed.length > start) throw new RangeError('canonical_act_rebase_conflict');
|
|
102
|
+
start += row.inserted.length - row.removed.length;
|
|
103
|
+
}
|
|
104
|
+
const footer = commentSplices(source, rows), splices = [...rows, ...footer];
|
|
105
|
+
const text = _rapierTransformSplices(source, splices), after = text === null ? null : parseComments(text);
|
|
106
|
+
if (!after?.record || after.reason || !after.current) throw new RangeError('canonical_act_rebase_conflict');
|
|
107
|
+
return {text, splices, footer};
|
|
108
|
+
};
|
|
109
|
+
const accepted = derive(current, moved.splices);
|
|
110
|
+
let reciprocal = local.text;
|
|
111
|
+
const against = moved.against.map(record => {
|
|
112
|
+
const value = derive(reciprocal, record.splices); reciprocal = value.text;
|
|
113
|
+
return {...record, splices: value.splices};
|
|
114
|
+
});
|
|
115
|
+
if (reciprocal !== accepted.text || parseComments(accepted.text).body !== moved.text)
|
|
116
|
+
throw new RangeError('canonical_transport_invalid');
|
|
117
|
+
return {text: accepted.text, splices: accepted.splices, against,
|
|
118
|
+
sourceIndices: [...moved.sourceIndices, ...accepted.footer.map(() => derivedCommentIndex)]};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function transposeTextRecord(base, splices, id, records) {
|
|
63
122
|
if (typeof base !== 'string' || !Array.isArray(splices) || typeof id !== 'string' || !Array.isArray(records))
|
|
64
123
|
throw new RangeError('canonical_transport_invalid');
|
|
65
124
|
let source = base, current = base;
|
|
@@ -305,7 +364,7 @@ function authoredReplay(base, records, append = null) {
|
|
|
305
364
|
throw new RangeError('canonical_origin_invalid');
|
|
306
365
|
const accepted = projectInverse(text, aligned.records, id);
|
|
307
366
|
moved = {text: accepted.after, splices: accepted.splices};
|
|
308
|
-
} else moved = transposeRecord(basis.text, origin.splices, id, crossed);
|
|
367
|
+
} else moved = transposeRecord(basis.text, origin.splices, id, crossed, original.derivedCommentIndex);
|
|
309
368
|
if (_rapierTransformSplices(text, moved.splices) !== moved.text) throw new RangeError('canonical_origin_invalid');
|
|
310
369
|
placed.push({...original, splices: moved.splices}); text = moved.text; ids.add(id);
|
|
311
370
|
if (original.derivedCommentIndex != null) sourceIndices.set(id, moved.sourceIndices);
|
package/dist/kit/ledger.d.mts
CHANGED
|
@@ -2,17 +2,32 @@
|
|
|
2
2
|
// rapier-markdown-kit/rapier-ledger: a document's edit history with authorship, Undo of any act and offline merge.
|
|
3
3
|
// Offsets count UTF-16 code units; splices run in their recorded order.
|
|
4
4
|
export type ActorKind = 'human' | 'agent' | 'system';
|
|
5
|
-
export interface Actor { kind: ActorKind; id: string; }
|
|
5
|
+
export interface Actor { kind: ActorKind; id: string; name?: string; }
|
|
6
6
|
export type DocKind = 'markdown' | 'text' | 'code';
|
|
7
7
|
export interface Metadata { filename: string; docKind: DocKind; }
|
|
8
|
+
export interface MetadataEffect {
|
|
9
|
+
filename?: {before: string; after: string};
|
|
10
|
+
docKind?: {before: DocKind; after: DocKind};
|
|
11
|
+
}
|
|
12
|
+
export type LedgerData = null | boolean | number | string | LedgerData[] | {[key: string]: LedgerData};
|
|
8
13
|
export interface Splice { pos: number; removed: string; inserted: string; }
|
|
9
14
|
export interface Transaction {
|
|
10
15
|
id: string; documentAuthority: string; baseRevision: number; revision: number; actor: Actor;
|
|
11
|
-
transport:
|
|
12
|
-
affectedBlockIds:
|
|
13
|
-
createdAt:
|
|
16
|
+
transport: 'platform' | 'webmcp'; operation: string; requestId: string | null; sourceTransactionId: string | null;
|
|
17
|
+
affectedBlockIds: number[]; parent: string | null; reverts: string | null; reapplies: string | null;
|
|
18
|
+
createdAt: number | null; turnId?: string | null; sourceTransactionIds?: string[];
|
|
19
|
+
label?: string | null; turnLabel?: string | null; turnBaseRevision?: number;
|
|
20
|
+
contribution?: string; contributionBaseRevision?: number; remoteTransactionId?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AuthoredPlacement { basis: string[]; source: string; splices: Splice[]; }
|
|
23
|
+
/** A retained journal may omit navigation splices: the target's exact rows supply them. */
|
|
24
|
+
export interface JournalRecord {
|
|
25
|
+
transaction: Transaction; splices?: Splice[]; beforeHash: string; afterHash: string;
|
|
26
|
+
metadata?: MetadataEffect; authored?: AuthoredPlacement; derivedCommentIndex?: number;
|
|
27
|
+
changeSet?: {[key: string]: LedgerData};
|
|
14
28
|
}
|
|
15
|
-
|
|
29
|
+
/** An exported envelope expands every navigation record to exact splices. */
|
|
30
|
+
export interface LedgerRecord extends JournalRecord { splices: Splice[]; }
|
|
16
31
|
/** rapier-ledger/2: plain JSON. */
|
|
17
32
|
export interface LedgerEnvelope {
|
|
18
33
|
format: 'rapier-ledger/2';
|
|
@@ -24,9 +39,9 @@ export interface LedgerEnvelope {
|
|
|
24
39
|
sha256: string;
|
|
25
40
|
}
|
|
26
41
|
/** Runs of [start, end, actor kind, actor id, createdAt] over the current text. */
|
|
27
|
-
export interface AuthorshipEnvelope { format: 'rapier-authorship/1'; sha256: string; runs: [number, number, ActorKind, string,
|
|
42
|
+
export interface AuthorshipEnvelope { format: 'rapier-authorship/1'; sha256: string; runs: [number, number, ActorKind, string, number | null][]; }
|
|
28
43
|
export interface ReadLedger { ledger: LedgerEnvelope; text: string; metadata: Metadata; root: string; revision: number; complete: boolean; }
|
|
29
|
-
export interface LedgerInput { text: string; metadata: Metadata; records:
|
|
44
|
+
export interface LedgerInput { text: string; metadata: Metadata; records: JournalRecord[]; earlier?: JournalRecord[]; documentAuthority?: string; revision?: number; root?: string; complete?: boolean; }
|
|
30
45
|
|
|
31
46
|
/** A new ledger with no history. Give every copy that may later merge the same documentAuthority. */
|
|
32
47
|
export function openLedger(text: string, options?: {filename?: string; docKind?: DocKind; documentAuthority?: string}): LedgerEnvelope;
|
|
@@ -34,20 +49,31 @@ export function exportLedger(input: LedgerInput): LedgerEnvelope;
|
|
|
34
49
|
/** Verifies the envelope (and, when given, that it describes this text and metadata); throws when it does not. */
|
|
35
50
|
export function readLedger(input: LedgerEnvelope, expectedText?: string, expectedMetadata?: Metadata): ReadLedger;
|
|
36
51
|
/** Records one replacement of the whole text as an act by `actor`. */
|
|
37
|
-
export function replaceLedgerText(input: LedgerEnvelope, text: string, options?: {actor?: Actor; operation?: string; at?:
|
|
52
|
+
export function replaceLedgerText(input: LedgerEnvelope, text: string, options?: {actor?: Actor; operation?: string; at?: number | null}): LedgerEnvelope;
|
|
38
53
|
export function ledgerWithStart(input: LedgerEnvelope, text: string): LedgerEnvelope;
|
|
39
|
-
export
|
|
54
|
+
export interface HistoryEnvelope<Segment = unknown> {
|
|
55
|
+
schemaVersion: 5; documentAuthority: string; documentRevision: number;
|
|
56
|
+
ledger: LedgerRecord[]; branch: string[]; cursor: number; earliestRevision: number;
|
|
57
|
+
earliestHash: string; sourceRootId: string; earliestMetadata: Metadata; documentMetadata: Metadata;
|
|
58
|
+
segmentIdentity: Segment[]; historyComplete: boolean; trimReason: '' | 'carried_start'; trimmedBytes: 0;
|
|
59
|
+
}
|
|
60
|
+
export function historyEnvelope<Segment = unknown>(input: LedgerEnvelope, segmentIdentity?: Segment[], expectedText?: string, expectedMetadata?: Metadata): HistoryEnvelope<Segment>;
|
|
40
61
|
/** The actor id an agent writes under, for a door and an agent name. */
|
|
41
|
-
export function agentActorId(door: string, agent: string): string;
|
|
62
|
+
export function agentActorId(door: string, agent?: {name: string} | null): string;
|
|
42
63
|
export function textRoot(text: string): string;
|
|
43
64
|
export function rootAfter(root: string, splice: Splice): string;
|
|
44
|
-
|
|
65
|
+
/** Feed bytes, then call finish once. `bytes` counts the input supplied so far. */
|
|
66
|
+
export function sha256State(): {update(input: Uint8Array): void; finish(): string; readonly bytes: number};
|
|
45
67
|
|
|
46
68
|
export function authorship(input: LedgerEnvelope): AuthorshipEnvelope;
|
|
47
69
|
export function readAuthorship(input: AuthorshipEnvelope, text: string): AuthorshipEnvelope;
|
|
48
70
|
export function ledgerFromAuthorship(input: AuthorshipEnvelope, text: string, documentAuthority: string, metadata: Metadata): LedgerEnvelope;
|
|
49
71
|
|
|
50
|
-
export
|
|
72
|
+
export type MergeConflict =
|
|
73
|
+
| {kind: 'identity'; code: 'act_identity_conflict' | 'act_order_conflict'; actId?: string}
|
|
74
|
+
| {kind: 'history'; code: 'record_transport_conflict'}
|
|
75
|
+
| {kind: 'metadata'; code: 'metadata_assignment_conflict' | 'inverse_target_unavailable' | 'record_transport_conflict'; fields?: (keyof Metadata)[]}
|
|
76
|
+
| (TextMergeConflict & {code: 'text_overlap_unpreserved'});
|
|
51
77
|
export interface MergeResult {
|
|
52
78
|
text: string; metadata: Metadata; ledger: LedgerEnvelope;
|
|
53
79
|
/** False when a person must review: conflicts, or Will keep regions that differ. */
|
|
@@ -60,29 +86,109 @@ export function merge(first: LedgerEnvelope, second: LedgerEnvelope): MergeResul
|
|
|
60
86
|
export function transportInterval(start: number, end: number, splices: Splice[]): {start: number; end: number} | null;
|
|
61
87
|
export function transportTouchedInterval(start: number, end: number, splices: Splice[]): {start: number; end: number};
|
|
62
88
|
|
|
63
|
-
export
|
|
64
|
-
export interface
|
|
89
|
+
export type HistoryTransaction = Pick<Transaction, 'id' | 'baseRevision' | 'revision' | 'actor' | 'createdAt'> & Partial<Transaction>;
|
|
90
|
+
export interface HistoryRecord {
|
|
91
|
+
transaction: HistoryTransaction; splices?: Splice[]; beforeHash?: string; afterHash?: string;
|
|
92
|
+
metadata?: MetadataEffect; authored?: AuthoredPlacement; derivedCommentIndex?: number;
|
|
93
|
+
changeSet?: {[key: string]: LedgerData};
|
|
94
|
+
}
|
|
95
|
+
export interface HistoryInput {
|
|
96
|
+
source: string; metadata: Metadata; records: HistoryRecord[];
|
|
97
|
+
revision?: number; earliestRevision?: number; earliestRoot?: string; complete?: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface HistoryFailure {
|
|
100
|
+
ok: false;
|
|
101
|
+
reason: 'history_invalid' | 'history_unavailable' | 'range_invalid' | 'act_unavailable' | 'act_invalid' | 'act_forbidden' | 'will_refused';
|
|
102
|
+
}
|
|
103
|
+
export interface HistoryAct {
|
|
104
|
+
id: string; actor: Actor; createdAt: number | null; baseRevision: number; revision: number; operation: string;
|
|
105
|
+
turnId?: string; contribution?: string; sourceTransactionId?: string; sourceTransactionIds?: string[];
|
|
106
|
+
reverts?: string; reapplies?: string; label?: LedgerData; affectedBlockIds?: number[];
|
|
107
|
+
metadata?: MetadataEffect; active?: boolean;
|
|
108
|
+
}
|
|
109
|
+
export interface HistoryGroup { id: string; actor: Actor; actIds: string[]; createdAt: number | null; turnId?: string; }
|
|
110
|
+
export interface HistorySnapshot { source: string; revision: number; earliestRevision: number; complete: boolean; }
|
|
111
|
+
export interface HistoryReplay extends HistorySnapshot {
|
|
112
|
+
ok: true; initialSource: string; metadata: Metadata; initialMetadata: Metadata;
|
|
113
|
+
acts: HistoryAct[]; effectiveActIds: string[]; groups: HistoryGroup[];
|
|
114
|
+
}
|
|
115
|
+
export interface HistorySourceBefore extends HistorySnapshot { ok: true; metadata: Metadata; act: HistoryAct; }
|
|
116
|
+
export interface HistoryRange { from?: number; to?: number; actId?: string; }
|
|
117
|
+
export interface HistorySourceRange { from: number; to: number; source: string; }
|
|
118
|
+
export interface ProjectedHistoryAct extends HistoryAct { before: HistorySourceRange; after: HistorySourceRange; }
|
|
119
|
+
export interface HistoryProjection extends HistorySnapshot, HistorySourceRange {
|
|
120
|
+
ok: true; acts: ProjectedHistoryAct[]; groups: HistoryGroup[]; before?: HistorySourceRange;
|
|
121
|
+
}
|
|
122
|
+
export interface HistoryBlock<Id = unknown> { id: Id; from: number; to: number; }
|
|
123
|
+
export interface HistoryPlaces<Id = unknown> extends HistorySnapshot {
|
|
124
|
+
ok: true; initialSource: string; acts: HistoryAct[];
|
|
125
|
+
blocks: (HistoryBlock<Id> & {source: string; acts: ProjectedHistoryAct[]})[]; reversed: string[];
|
|
126
|
+
}
|
|
127
|
+
export interface UndoHunk {
|
|
128
|
+
actId: string; spliceIndex: number; hunkIndex: number; splice: Splice;
|
|
129
|
+
beforeSource: string; actSource: string; priorSplices: Splice[];
|
|
130
|
+
}
|
|
131
|
+
export interface SelectiveUndoOptions {
|
|
132
|
+
actor?: Actor;
|
|
133
|
+
/** Retain this exact hunk while reversing the other selected hunks. */
|
|
134
|
+
keepHunk?: (hunk: UndoHunk) => boolean;
|
|
135
|
+
/** Refinements must replay to the same exact text as the supplied splice. */
|
|
136
|
+
splitSplice?: (input: {actId: string; beforeSource: string; splice: Splice}) => Splice[];
|
|
137
|
+
}
|
|
138
|
+
export interface SupersededHunk { actId: string; hunk: string; laterActIds: string[]; reason: 'later_edit' | 'already_undone'; }
|
|
139
|
+
export interface SelectiveUndo {
|
|
140
|
+
ok: true; splices: Splice[]; after: string; actIds: string[]; sourceTransactionIds: string[];
|
|
141
|
+
metadata: MetadataEffect | null; afterMetadata: Metadata; effectChanged: boolean; unchanged: boolean;
|
|
142
|
+
superseded: SupersededHunk[]; overlaps: SupersededHunk[];
|
|
143
|
+
}
|
|
65
144
|
/** Reverses chosen acts while later work stays. */
|
|
66
|
-
export function selectiveUndo(input: HistoryInput, actIds: string[], options?:
|
|
67
|
-
export function replayHistory(input: HistoryInput):
|
|
68
|
-
export function sourceBefore(input: HistoryInput, actId: string):
|
|
69
|
-
export function historyProjection(input: HistoryInput, range?:
|
|
70
|
-
export function historyProjections(input: HistoryInput, ranges:
|
|
71
|
-
export function historyPlaces(input: HistoryInput, blocks:
|
|
72
|
-
export function groupHistoryActs(acts:
|
|
73
|
-
export function pictureHistorySplices(input:
|
|
74
|
-
|
|
75
|
-
export
|
|
76
|
-
export
|
|
77
|
-
|
|
78
|
-
|
|
145
|
+
export function selectiveUndo(input: HistoryInput, actIds: string[], options?: SelectiveUndoOptions): SelectiveUndo | HistoryFailure;
|
|
146
|
+
export function replayHistory(input: HistoryInput): HistoryReplay | HistoryFailure;
|
|
147
|
+
export function sourceBefore(input: HistoryInput, actId: string): HistorySourceBefore | HistoryFailure;
|
|
148
|
+
export function historyProjection(input: HistoryInput, range?: HistoryRange): HistoryProjection | HistoryFailure;
|
|
149
|
+
export function historyProjections(input: HistoryInput, ranges: (HistoryRange | undefined)[]): {ok: true; projections: (HistoryProjection | HistoryFailure)[]} | HistoryFailure;
|
|
150
|
+
export function historyPlaces<Id>(input: HistoryInput, blocks: HistoryBlock<Id>[]): HistoryPlaces<Id> | HistoryFailure;
|
|
151
|
+
export function groupHistoryActs(acts: Pick<HistoryAct, 'id' | 'actor' | 'createdAt' | 'turnId'>[]): HistoryGroup[];
|
|
152
|
+
export function pictureHistorySplices(input: Pick<HistoryInput, 'source' | 'records'>, target: HistoryRecord, redo: boolean): Splice[] | null;
|
|
153
|
+
/** Canonical input splices use `pos/removed/inserted`; the host's reciprocal log uses `at/remove/insert`. */
|
|
154
|
+
export interface SourceTransportEntry { client: string; splices: {at: number; remove: number; insert: string}[]; }
|
|
155
|
+
export interface AuthoredRecord {
|
|
156
|
+
transaction: Pick<Transaction, 'id'> & Partial<Transaction>; splices: Splice[];
|
|
157
|
+
authored?: AuthoredPlacement; metadata?: MetadataEffect; derivedCommentIndex?: number;
|
|
158
|
+
}
|
|
159
|
+
export interface SourceTransposition {
|
|
160
|
+
text: string; splices: Splice[]; sourceIndices: number[];
|
|
161
|
+
against: {transaction: {id: string}; splices: Splice[]}[];
|
|
162
|
+
}
|
|
163
|
+
export interface AuthoredHistory<Record extends AuthoredRecord = AuthoredRecord> {
|
|
164
|
+
text: string; records: Record[]; origins: Map<string, AuthoredPlacement>;
|
|
165
|
+
}
|
|
166
|
+
export interface ProjectedAuthoredHistory<Record extends AuthoredRecord = AuthoredRecord> extends AuthoredHistory<Record> {
|
|
167
|
+
sourceIndices: Map<string, number[]>;
|
|
168
|
+
}
|
|
169
|
+
export interface AuthoredTransposition<Record extends AuthoredRecord = AuthoredRecord> {
|
|
170
|
+
text: string; splices: Splice[]; submittedBefore: string; submitted: string; against: Record[];
|
|
171
|
+
}
|
|
172
|
+
export function transposeSource(base: string, splices: Splice[], client: string, log: SourceTransportEntry[]): SourceTransposition;
|
|
173
|
+
export function authoredBasis(records: Pick<AuthoredRecord, 'transaction' | 'authored'>[]): string[];
|
|
174
|
+
export function authoredPlacement(source: string, splices: Splice[], basis: string[]): AuthoredPlacement;
|
|
175
|
+
export function authoredHistory<Record extends AuthoredRecord>(base: string, records: Record[], selected: ReadonlySet<string>): ProjectedAuthoredHistory<Record>;
|
|
176
|
+
export function authoredHistory<Record extends AuthoredRecord>(base: string, records: Record[], selected?: null): AuthoredHistory<Record>;
|
|
177
|
+
export function authoredHistory<Record extends AuthoredRecord>(base: string, records: Record[], selected?: ReadonlySet<string> | null): AuthoredHistory<Record> | ProjectedAuthoredHistory<Record>;
|
|
178
|
+
export function transposeAuthored<Record extends AuthoredRecord>(base: string, records: Record[], incoming: AuthoredRecord & {authored: AuthoredPlacement}, submitted: string[]): AuthoredTransposition<Record>;
|
|
79
179
|
|
|
80
180
|
export {writeDocument, readDocument, writeParts, readParts} from './ledger/carried.mjs';
|
|
81
181
|
|
|
82
|
-
export interface
|
|
83
|
-
export interface
|
|
182
|
+
export interface TextConflictVariant { device: string; text: string; }
|
|
183
|
+
export interface TextConflictDescriptor { start: number; end: number; block: string; variants: TextConflictVariant[]; }
|
|
184
|
+
export interface TextConflict extends TextConflictDescriptor { kind: 'text'; id: string; }
|
|
185
|
+
export type TextMergeConflict =
|
|
186
|
+
| (TextConflictDescriptor & {kind: 'text' | 'no-ancestor' | 'will' | 'document-bindings' | 'ambiguous-alignment-or-budget'; base: string | null})
|
|
187
|
+
| {kind: 'existing-conflict'; advisory: true};
|
|
188
|
+
export interface TextMerge { text: string; clean: boolean; conflicts: TextMergeConflict[]; }
|
|
189
|
+
export interface TextMergeOptions { oursId: string; theirsId: string; }
|
|
84
190
|
/** Exact three-way text merge; a region both sides changed becomes a marked choice in the text. */
|
|
85
|
-
export function mergeText(base: string | null, ours: string, theirs: string, options?:
|
|
191
|
+
export function mergeText(base: string | null, ours: string, theirs: string, options?: TextMergeOptions): TextMerge;
|
|
86
192
|
export function inspectTextConflicts(text: string, options?: {nested?: boolean}): TextConflict[];
|
|
87
|
-
export function resolveTextConflict(text: string, conflict:
|
|
193
|
+
export function resolveTextConflict(text: string, conflict: TextConflictDescriptor, device: string | number): string;
|
|
88
194
|
export function mapTextConflictVariants(text: string, mapper: (variant: string) => string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// md.use(rapierMarkdown): Self-contained Markdown in markdown-it, with the reference style's markup.
|
|
3
|
-
|
|
4
|
-
export function rapierMarkdown<T extends
|
|
3
|
+
import type {MarkdownParser} from './marks.mjs';
|
|
4
|
+
export function rapierMarkdown<T extends MarkdownParser>(md: T): T;
|