@lax-wp/editor 0.4.15 → 0.4.16

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.
Files changed (47) hide show
  1. package/dist/components/sidebar/content/TrackChangesContent.d.ts +8 -0
  2. package/dist/components/sidebar/content/anonymization-page/anonymizationUtils.d.ts +1 -0
  3. package/dist/components/trackChanges/TrackChangesCards.d.ts +1 -0
  4. package/dist/components/trackChanges/index.d.ts +2 -0
  5. package/dist/components/trackChanges/utils.d.ts +5 -0
  6. package/dist/config/EditorConfig.d.ts +1 -1
  7. package/dist/constants/Extensions.d.ts +12 -0
  8. package/dist/extensions/AnonymizedText.d.ts +11 -1
  9. package/dist/extensions/VariableText.d.ts +20 -0
  10. package/dist/extensions/index.d.ts +1 -1
  11. package/dist/extensions/redline/changeTypes.d.ts +70 -0
  12. package/dist/extensions/redline/constants.d.ts +25 -0
  13. package/dist/extensions/redline/engine/blockNodes.d.ts +31 -0
  14. package/dist/extensions/redline/engine/clipboard.d.ts +16 -0
  15. package/dist/extensions/redline/engine/deletion.d.ts +34 -0
  16. package/dist/extensions/redline/engine/formatSplit.d.ts +16 -0
  17. package/dist/extensions/redline/engine/insertion.d.ts +26 -0
  18. package/dist/extensions/redline/engine/predicates.d.ts +73 -0
  19. package/dist/extensions/redline/engine/textReplace.d.ts +19 -0
  20. package/dist/extensions/redline/id.d.ts +7 -0
  21. package/dist/extensions/redline/index.d.ts +36 -0
  22. package/dist/extensions/redline/plugin.d.ts +34 -0
  23. package/dist/extensions/redline/query/scan.d.ts +52 -0
  24. package/dist/extensions/redline/review/applyChange.d.ts +25 -0
  25. package/dist/extensions/redline/review/index.d.ts +1 -0
  26. package/dist/extensions/redline/review/nodeResolvers.d.ts +29 -0
  27. package/dist/extensions/redline/review/pairedNodes.d.ts +38 -0
  28. package/dist/extensions/redline/review/structuralRevert.d.ts +20 -0
  29. package/dist/extensions/redline/review/textResolver.d.ts +9 -0
  30. package/dist/extensions/redline/schema/marks.d.ts +65 -0
  31. package/dist/extensions/redline/schema/shared.d.ts +34 -0
  32. package/dist/extensions/redline/session.d.ts +71 -0
  33. package/dist/extensions/redline/types.d.ts +24 -0
  34. package/dist/extensions/trackedChanges/TrackedChanges.d.ts +64 -0
  35. package/dist/extensions/trackedChanges/constants.d.ts +34 -0
  36. package/dist/extensions/trackedChanges/engine.d.ts +32 -0
  37. package/dist/extensions/trackedChanges/index.d.ts +13 -0
  38. package/dist/extensions/trackedChanges/marks.d.ts +20 -0
  39. package/dist/extensions/trackedChanges/normalize.d.ts +2 -0
  40. package/dist/extensions/trackedChanges/plugin.d.ts +32 -0
  41. package/dist/extensions/trackedChanges/query.d.ts +3 -0
  42. package/dist/extensions/trackedChanges/review.d.ts +34 -0
  43. package/dist/extensions/trackedChanges/types.d.ts +72 -0
  44. package/dist/extensions/trackedChanges/utils.d.ts +20 -0
  45. package/dist/index.es.js +7675 -7498
  46. package/dist/index.umd.js +85 -85
  47. package/package.json +1 -1
@@ -0,0 +1,8 @@
1
+ export interface TrackChangesContentProps {
2
+ onClose?: () => void;
3
+ }
4
+ /**
5
+ * M8 — review sidebar: pending suggestions grouped by id and author with
6
+ * accept / reject / bulk actions, all driven by the M4 command façade.
7
+ */
8
+ export declare const TrackChangesContent: ({ onClose }: TrackChangesContentProps) => import("react/jsx-runtime").JSX.Element;
@@ -13,6 +13,7 @@ export declare function setShowValue(editor: Editor, showValue: boolean, filter?
13
13
  */
14
14
  export declare function revertAnonymizedLinksToLinks(editor: Editor, filter?: Set<string>): void;
15
15
  export declare function revertToPlainText(editor: Editor, filter?: Set<string>): void;
16
+ export declare function revertVariablePii(editor: Editor, filter?: Set<string>): void;
16
17
  export declare function buildTagsFromEditor(editor: Editor): Record<string, ApiTagEntry[]>;
17
18
  export type AnonymizedVariableRevealState = 'placeholder' | 'revealed';
18
19
  /**
@@ -0,0 +1 @@
1
+ export declare const TrackChangesCards: () => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,2 @@
1
+ export { TrackChangesCards } from './TrackChangesCards';
2
+ export { relativeTime, TRACK_CHANGES_CARDS_WIDTH } from './utils';
@@ -0,0 +1,5 @@
1
+ export declare const TRACK_CHANGES_CARD_WIDTH = 300;
2
+ export declare const TRACK_CHANGES_CARD_GUTTER = 16;
3
+ export declare const TRACK_CHANGES_CARDS_WIDTH: number;
4
+ /** "just now" / "5m ago" / "3h ago", falling back to a short date. */
5
+ export declare function relativeTime(createdAt: number | null): string;
@@ -63,7 +63,7 @@ export interface ScanDocumentResult {
63
63
  * rewritten to the standard `link` mark (using the original URL stored in
64
64
  * `anonymizedData[0].value`) so it behaves identically to a default link.
65
65
  */
66
- export declare function normalizeAnonymizationApiResponse(jsonContent: Record<string, unknown>, placeholderFormat?: string): Record<string, unknown>;
66
+ export declare function normalizeAnonymizationApiResponse(jsonContent: Record<string, unknown>, placeholderFormat?: string, highlightChanges?: boolean): Record<string, unknown>;
67
67
  /** Payload for Save Changes (PII anonymization settings). Passed to onSaveAnonymizationSettings. */
68
68
  export interface AnonymizationSavePayload {
69
69
  document_id: string;
@@ -18,6 +18,18 @@ export declare const PAGE_BREAK_LINE_CLASS_NAME = "editor-page-break-line";
18
18
  export declare const VARIABLE_TEXT_DATA_VARIABLE_NAME = "data-variable-name";
19
19
  export declare const VARIABLE_TEXT_DATA_VALUE = "data-value";
20
20
  export declare const VARIABLE_TEXT_CLASS_NAME = "variable-text";
21
+ export declare const VARIABLE_TEXT_DATA_IS_PII = "data-pii";
22
+ export declare const VARIABLE_TEXT_DATA_PII_CATEGORY = "data-pii-category";
23
+ export declare const VARIABLE_TEXT_DATA_PII_PLACEHOLDER = "data-pii-placeholder";
24
+ export declare const VARIABLE_TEXT_DATA_PII_SHOW_VALUE = "data-pii-show-value";
25
+ export declare const VARIABLE_TEXT_DATA_PII_HIGHLIGHT = "data-pii-highlight";
26
+ export declare const VARIABLE_TEXT_DATA_PII_FORMAT = "data-pii-format";
27
+ /**
28
+ * Also worn by a `variableText` node while its PII is masked, so a masked
29
+ * variable renders as a plain anonymized chip instead of keeping the boxed
30
+ * `.variable-text` look.
31
+ */
32
+ export declare const ANONYMIZED_TEXT_CLASS_NAME = "anonymized-text";
21
33
  export declare const VARIABLE_TABLE_TYPE = "data-table-type";
22
34
  export declare const VARIABLE_TABLE_ID = "data-table-id";
23
35
  export declare const VARIABLE_TABLE_NAME = "data-table-name";
@@ -14,7 +14,17 @@ export declare const ANON_CATEGORY_COLORS: Record<string, {
14
14
  darkColor: string;
15
15
  }>;
16
16
  export declare function extractCategory(anonymizedData: string): string;
17
- /** Apply per-category inline colors and chip spacing to a DOM element. */
17
+ /**
18
+ * Apply per-category inline colors and chip spacing to a DOM element, given
19
+ * an already-known category key (e.g. "email"). Shared by `anonymizedText`
20
+ * (which derives the category from `anonymizedData`) and `variableText`
21
+ * (which already stores the category explicitly via `piiCategory`).
22
+ */
23
+ export declare function applyCategoryColorStyle(dom: HTMLElement, category: string, opts?: {
24
+ placeholderFormat?: string;
25
+ highlightChanges?: boolean;
26
+ }): void;
27
+ /** Apply per-category inline colors and chip spacing to a DOM element, deriving the category from a JSON `anonymizedData` string. */
18
28
  export declare function applyCategoryStyle(dom: HTMLElement, anonymizedData: string, opts?: {
19
29
  placeholderFormat?: string;
20
30
  highlightChanges?: boolean;
@@ -16,6 +16,26 @@ declare module '@tiptap/core' {
16
16
  * @param values - Record mapping variable names to their new values
17
17
  */
18
18
  updateVariableValues: (values: Record<string, string>) => ReturnType;
19
+ /**
20
+ * Flag every variable node matching `variableName` as containing PII.
21
+ * @param variableName - The name/key of the variable to flag
22
+ * @param pii - Category, display label, and initial reveal state.
23
+ * `highlightChanges`/`placeholderFormat` are display settings normally
24
+ * owned by the scan/normalize pipeline; when omitted here the node's
25
+ * existing values are kept.
26
+ */
27
+ setVariablePii: (variableName: string, pii: {
28
+ piiCategory: string;
29
+ placeholder: string;
30
+ showValue?: boolean;
31
+ highlightChanges?: boolean;
32
+ placeholderFormat?: string;
33
+ }) => ReturnType;
34
+ /**
35
+ * Clear PII flags from variable nodes, restoring plain display of `value`.
36
+ * @param variableName - Limit to a specific variable; omit to revert all PII-flagged variables
37
+ */
38
+ revertVariablePii: (variableName?: string) => ReturnType;
19
39
  };
20
40
  }
21
41
  }
@@ -1,5 +1,5 @@
1
1
  import { type EditorConfig } from "@/config/EditorConfig";
2
- export declare const EditorExtensions: (config?: EditorConfig) => (import("@tiptap/core").Node<import("@tiptap/extension-list").OrderedListOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-list").TaskItemOptions, any> | import("@tiptap/core").Extension<import("@/extensions/AIAutoCompletion").AIAutocompletionOptions, any> | import("@tiptap/core").Extension<any, any> | import("@tiptap/core").Extension<import("./Indent").IndentOptions, any> | import("@tiptap/core").Node<import("./ListItemWithDepthLimit").ListItemOptions, any> | import("@tiptap/core").Extension<import("./Redline").RedlineOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-table").TableOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-table").TableRowOptions, any> | import("@tiptap/core").Node<import("./VariableText").VariableOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-horizontal-rule").HorizontalRuleOptions, any> | import("@tiptap/core").Node<import("./CodeBlockWithToolbar").CodeBlockWithToolbarOptions, any> | import("@tiptap/core").Extension<import("./PageMargin").PageMarginOptions, any> | import("@tiptap/core").Extension<import("./PageBackground").PageBackgroundOptions, any> | import("@tiptap/core").Node<import("./PageBreak").PageBreakOptions, any> | import("@tiptap/core").Node<any, any> | import("@tiptap/core").Node<import("./CustomImageExtension").CustomImageExtensionOptions, any> | import("@tiptap/core").Node<import("./Signature").SignatureOptions, any> | import("@tiptap/core").Node<import("./AnonymizedText").AnonymizedTextOptions, any> | import("@tiptap/core").Mark<import("./AnonymizedLink").AnonymizedLinkOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").UndoRedoOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").CharacterCountOptions, import("@tiptap/extensions").CharacterCountStorage> | import("@tiptap/core").Extension<import("@tiptap/extensions").PlaceholderOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-typography").TypographyOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-text-style").TextStyleOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-text-style").FontSizeOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-superscript").SuperscriptExtensionOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-highlight").HighlightOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-list").TaskListOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-text-align").TextAlignOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-heading").HeadingOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-link").LinkOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-hard-break").HardBreakOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").TrailingNodeOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-collaboration").CollaborationOptions, import("@tiptap/extension-collaboration").CollaborationStorage> | import("@tiptap/core").Extension<import("@tiptap/extension-collaboration-caret").CollaborationCaretOptions, {
2
+ export declare const EditorExtensions: (config?: EditorConfig) => (import("@tiptap/core").Node<import("@tiptap/extension-list").OrderedListOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-list").TaskItemOptions, any> | import("@tiptap/core").Extension<import("@/extensions/AIAutoCompletion").AIAutocompletionOptions, any> | import("@tiptap/core").Extension<any, any> | import("@tiptap/core").Extension<import("./Indent").IndentOptions, any> | import("@tiptap/core").Node<import("./ListItemWithDepthLimit").ListItemOptions, any> | import("@tiptap/core").Extension<import("./Redline").RedlineOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-table").TableOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-table").TableRowOptions, any> | import("@tiptap/core").Node<import("./AnonymizedText").AnonymizedTextOptions, any> | import("@tiptap/core").Node<import("./VariableText").VariableOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-horizontal-rule").HorizontalRuleOptions, any> | import("@tiptap/core").Node<import("./CodeBlockWithToolbar").CodeBlockWithToolbarOptions, any> | import("@tiptap/core").Extension<import("./PageMargin").PageMarginOptions, any> | import("@tiptap/core").Extension<import("./PageBackground").PageBackgroundOptions, any> | import("@tiptap/core").Node<import("./PageBreak").PageBreakOptions, any> | import("@tiptap/core").Node<any, any> | import("@tiptap/core").Node<import("./CustomImageExtension").CustomImageExtensionOptions, any> | import("@tiptap/core").Node<import("./Signature").SignatureOptions, any> | import("@tiptap/core").Mark<import("./AnonymizedLink").AnonymizedLinkOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").UndoRedoOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").CharacterCountOptions, import("@tiptap/extensions").CharacterCountStorage> | import("@tiptap/core").Extension<import("@tiptap/extensions").PlaceholderOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-typography").TypographyOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-text-style").TextStyleOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-text-style").FontSizeOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-superscript").SuperscriptExtensionOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-highlight").HighlightOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-list").TaskListOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-text-align").TextAlignOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-heading").HeadingOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-link").LinkOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-hard-break").HardBreakOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").TrailingNodeOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-collaboration").CollaborationOptions, import("@tiptap/extension-collaboration").CollaborationStorage> | import("@tiptap/core").Extension<import("@tiptap/extension-collaboration-caret").CollaborationCaretOptions, {
3
3
  users: {
4
4
  clientId: number;
5
5
  [key: string]: any;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The RedlineChange contract (change-tracking.md M4 — query API output).
3
+ *
4
+ * This is the shape the query module produces from a document scan and the
5
+ * shape the review engine (M3) and panel UI (M8) consume. It lives in the
6
+ * engine layer; the UI re-exports it from components/redline for backward
7
+ * compatibility.
8
+ */
9
+ export type RedlineChangeType = 'user-insertion' | 'user-deletion' | 'ai-insertion' | 'ai-deletion' | 'user-replace' | 'ai-replace' | 'user-resize' | 'user-align' | 'ai-align' | 'user-style';
10
+ export interface RedlineComment {
11
+ id: string;
12
+ userId: string;
13
+ userName: string;
14
+ userProfilePic?: string;
15
+ timestamp: string;
16
+ text: string;
17
+ }
18
+ export interface RedlineChange {
19
+ id: string;
20
+ markIds?: string[];
21
+ type: RedlineChangeType;
22
+ content: string;
23
+ userId: string;
24
+ userName: string;
25
+ userProfilePic?: string;
26
+ timestamp: string;
27
+ position: number;
28
+ nodeType?: 'text' | 'table' | 'tableRow' | 'tableColumn' | 'blockquote' | 'link' | 'signature' | 'variable' | 'image' | 'divider';
29
+ tableData?: string;
30
+ tableRowData?: {
31
+ action: 'add' | 'delete';
32
+ rowId: string;
33
+ content?: string;
34
+ };
35
+ tableColumnData?: {
36
+ action: 'add' | 'delete';
37
+ columnId: string;
38
+ content?: string;
39
+ };
40
+ signatureData?: {
41
+ action: 'add' | 'delete' | 'update';
42
+ fileName?: string | null;
43
+ recipientEmail?: string;
44
+ };
45
+ variableData?: {
46
+ action: 'add' | 'delete';
47
+ variableName: string;
48
+ value?: string;
49
+ previousValue?: string;
50
+ };
51
+ formatChanges?: string;
52
+ linkData?: {
53
+ url: string;
54
+ action: 'add' | 'remove' | 'change';
55
+ oldUrl?: string;
56
+ };
57
+ imageData?: {
58
+ src: string;
59
+ previousSrc?: string | null;
60
+ width?: number | null;
61
+ height?: number | null;
62
+ previousWidth?: number | null;
63
+ previousHeight?: number | null;
64
+ alignment?: string | null;
65
+ previousAlignment?: string | null;
66
+ action: 'insert' | 'delete' | 'replace' | 'resize' | 'align';
67
+ };
68
+ dividerAction?: 'insert' | 'delete';
69
+ comments?: RedlineComment[];
70
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Redline constants — single source of truth for mark names, transaction
3
+ * meta keys, and node-attribute keys used by the tracking engine.
4
+ *
5
+ * Maps to change-tracking.md M0 (schema contract): every other module reads
6
+ * these names from here so the on-document encoding is defined in one place.
7
+ */
8
+ /** Inline mark names, one insertion/deletion pair per authoring mode. */
9
+ export declare const MARK_TYPES: {
10
+ readonly USER_INSERTION: "userInsertion";
11
+ readonly USER_DELETION: "userDeletion";
12
+ readonly AI_INSERTION: "aiInsertion";
13
+ readonly AI_DELETION: "aiDeletion";
14
+ };
15
+ export type RedlineMarkName = (typeof MARK_TYPES)[keyof typeof MARK_TYPES];
16
+ /** Set on transactions that should not be processed by insertion-tracking plugins. */
17
+ export declare const SKIP_REDLINE_INSERTION_TRACKING_META = "skipRedlineInsertionTracking";
18
+ /** Set on transactions produced by handleTextInput so the appendTransaction
19
+ * insertion tracker does not double-process the same edit. */
20
+ export declare const REDLINE_HANDLED_TEXT_INPUT_META = "redlineHandledTextInput";
21
+ /** Set on the transaction returned by RedlinePlugin for a paste batch, carrying the shared
22
+ * groupId so that other tracking plugins (e.g. the divider tracker) can stamp the same
23
+ * groupId on their tracked nodes and appear as one card in the redline panel. */
24
+ export declare const PASTE_BATCH_GROUP_META = "pasteBatchGroupId";
25
+ export declare function isRedlineMarkName(name: string): boolean;
@@ -0,0 +1,31 @@
1
+ import type { Transaction } from '@tiptap/pm/state';
2
+ import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
3
+ import type { RedlineEntity, RedlineMode } from '../types';
4
+ /**
5
+ * Marks block-level nodes (tables, dividers, images, signatures, variables)
6
+ * inside a deleted selection as pending-delete (change-tracking.md M1 —
7
+ * block-node deletion semantics).
8
+ *
9
+ * All nodes use the same mark id/timestamp as the accompanying text deletion
10
+ * so the whole gesture appears as one card in the redline panel. When the
11
+ * node was a same-user pending insertion, the prior state is remembered
12
+ * (redlinePriorAction / data-redline-prior-type) so rejecting the deletion
13
+ * restores the "pending insert" state instead of clearing tracking entirely.
14
+ *
15
+ * Two attribute schemes exist for historical/persistence reasons:
16
+ * - `redline*` node attrs (horizontalRule, image, signature, variableText),
17
+ * - `data-redline-*` attrs (table).
18
+ * Both are preserved verbatim — they are part of the stored-document format.
19
+ */
20
+ interface BlockDeletionContext {
21
+ mode: RedlineMode;
22
+ entity: RedlineEntity;
23
+ markId: string;
24
+ timestamp: string;
25
+ }
26
+ export declare function markBlockNodesPendingDelete(tr: Transaction, blockNodes: Array<{
27
+ pos: number;
28
+ node: ProseMirrorNode;
29
+ typeName: string;
30
+ }>, ctx: BlockDeletionContext): void;
31
+ export {};
@@ -0,0 +1,16 @@
1
+ import { Slice } from '@tiptap/pm/model';
2
+ import type { EditorView } from '@tiptap/pm/view';
3
+ import type { RedlineContext } from '../types';
4
+ import type { RedlineSession } from '../session';
5
+ export declare function sanitizeCopiedSlice(slice: Slice): Slice;
6
+ interface EngineDeps {
7
+ getContext: () => RedlineContext;
8
+ session: RedlineSession;
9
+ }
10
+ /**
11
+ * handlePaste prop — see module docs. Always returns false so TipTap's
12
+ * default paste proceeds; this handler only prepares batch identity and the
13
+ * in-place deletion mark for selection replacement.
14
+ */
15
+ export declare function createPasteHandler(deps: EngineDeps): (view: EditorView) => boolean;
16
+ export {};
@@ -0,0 +1,34 @@
1
+ import { EditorState, Transaction } from '@tiptap/pm/state';
2
+ import type { RedlineContext } from '../types';
3
+ import type { RedlineSession } from '../session';
4
+ /**
5
+ * Keyboard deletion semantics (change-tracking.md M1 delete-rewriting +
6
+ * M2 input behavior).
7
+ *
8
+ * Instead of removing content, Backspace/Delete/Cut add a deletion mark over
9
+ * the affected range and move the caret so continued deleting feels natural.
10
+ * The exceptions, in order of evaluation:
11
+ *
12
+ * - code blocks are tracked as whole units → default behavior,
13
+ * - edits inside a pending-insertion ancestor are draft edits → default,
14
+ * - already-deletion-marked content is never re-marked (no-op / caret skip),
15
+ * - the author deleting their own pending insertion physically removes it
16
+ * (cancellation) → default behavior.
17
+ *
18
+ * Backspace and forward-Delete share this implementation and differ only in
19
+ * range derivation, boundary checks, adjacency probing (merge direction for
20
+ * consecutive keystrokes), already-deleted skipping, and final caret
21
+ * placement.
22
+ */
23
+ interface EngineDeps {
24
+ getContext: () => RedlineContext;
25
+ session: RedlineSession;
26
+ }
27
+ type KeyHandler = (state: EditorState, dispatch?: (tr: Transaction) => void) => boolean;
28
+ export declare function createDeletionKeyHandler(direction: 'backward' | 'forward', deps: EngineDeps): KeyHandler;
29
+ /**
30
+ * Mod-x (cut) — writes the selection to the clipboard, then marks it as a
31
+ * pending deletion instead of removing it.
32
+ */
33
+ export declare function createCutKeyHandler(deps: EngineDeps): KeyHandler;
34
+ export {};
@@ -0,0 +1,16 @@
1
+ import { EditorState, Transaction } from '@tiptap/pm/state';
2
+ import type { MarkType } from '@tiptap/pm/model';
3
+ /**
4
+ * Splits pending format-change marks that overlap a new deletion range
5
+ * (change-tracking.md M1.4 — normalization of overlapping suggestions).
6
+ *
7
+ * When a deletion lands in the middle of a format-change suggestion, the
8
+ * suggestion is split into independent before/after changes so each part can
9
+ * be reviewed on its own: the "before" part keeps its original timestamp, the
10
+ * "after" part gets a new one (= separate panel card). A deletion covering the
11
+ * whole format mark simply removes it.
12
+ *
13
+ * This logic previously existed as two identical inline copies in the
14
+ * Backspace and Delete key handlers.
15
+ */
16
+ export declare function splitFormatChangeMarksOverlappingDeletion(state: EditorState, tr: Transaction, from: number, to: number, insertionMarkType: MarkType): void;
@@ -0,0 +1,26 @@
1
+ import type { EditorState, Transaction } from '@tiptap/pm/state';
2
+ import type { RedlineContext } from '../types';
3
+ import type { RedlineSession } from '../session';
4
+ /**
5
+ * Insertion tracking (change-tracking.md M1 insert-rewriting), implemented as
6
+ * an appendTransaction pass: any transaction that inserted content gets a
7
+ * follow-up transaction marking the new content with an insertion mark
8
+ * attributed to the current entity.
9
+ *
10
+ * Grouping rules (why consecutive typing is one card):
11
+ * - same-block adjacency: reuse the id of the neighboring insertion mark,
12
+ * - cross-block typing session: typing + Enter + typing stays one card until
13
+ * the cursor navigates away (session state),
14
+ * - paste batch: everything inserted by one paste shares one id.
15
+ *
16
+ * Exemption guards (change-tracking.md M1.3) — transactions that must pass
17
+ * through untracked: remote Yjs sync, undo/redo history, other tracking
18
+ * plugins' own output (skip meta), highlight-decoration state changes, and
19
+ * addToHistory:false transactions (except paste/drop).
20
+ */
21
+ interface EngineDeps {
22
+ getContext: () => RedlineContext;
23
+ session: RedlineSession;
24
+ }
25
+ export declare function createInsertionAppendTransaction(deps: EngineDeps): (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => Transaction | null;
26
+ export {};
@@ -0,0 +1,73 @@
1
+ import { EditorState } from '@tiptap/pm/state';
2
+ import type { Mark, MarkType, Node as ProseMirrorNode } from '@tiptap/pm/model';
3
+ import type { RedlineMode } from '../types';
4
+ /**
5
+ * Document predicates used by the tracking engine (change-tracking.md M1).
6
+ *
7
+ * These encode the engine's classification rules: what counts as pending
8
+ * insertion/deletion content, which contexts suppress tracking (code blocks,
9
+ * pending-insertion ancestors), and how adjacency to existing marks is
10
+ * detected. Pure functions over EditorState — no dispatching.
11
+ */
12
+ /**
13
+ * Checks if a text range contains insertion marks (user or AI).
14
+ * Used to implement cancellation: deleting inserted text removes the insertion mark.
15
+ */
16
+ export declare function hasInsertionMarks(state: EditorState, from: number, to: number): boolean;
17
+ /**
18
+ * Checks if insertion marks are ONLY format changes (not actual text insertions).
19
+ * Format changes should be treated as existing text and get deletion marks.
20
+ */
21
+ export declare function isFormatChangeOnly(state: EditorState, from: number, to: number): boolean;
22
+ /**
23
+ * Checks if a position is inside a code block.
24
+ * Code blocks should be tracked as whole units, not their text content.
25
+ */
26
+ export declare function isInsideCodeBlock(state: EditorState, from: number, to: number): boolean;
27
+ /**
28
+ * Checks if a text range contains deletion marks (user or AI).
29
+ * Used to implement cancellation: typing over deleted text removes the deletion mark.
30
+ */
31
+ export declare function hasDeletionMarks(state: EditorState, from: number, to: number): boolean;
32
+ /**
33
+ * Returns true if every text character in [from, to) has an insertion mark.
34
+ * Used to decide whether to physically delete selected text rather than adding
35
+ * a deletion mark (pending insertions deleted by the author should just vanish).
36
+ */
37
+ export declare function isEntirelyInsertionMarked(state: EditorState, from: number, to: number): boolean;
38
+ /**
39
+ * Rule 2.4 — Returns true if the range is ENTIRELY covered by deletion marks.
40
+ * A second deletion on an already-pending-deletion range is a no-op.
41
+ */
42
+ export declare function isEntirelyDeletionMarked(state: EditorState, from: number, to: number): boolean;
43
+ /**
44
+ * Rules 5.1–5.2 — Returns true if the range is inside a node that is itself
45
+ * pending insertion or pending deletion (e.g. a table cell inside a table that
46
+ * has redlineType="user-insertion"). Text changes inside a pending-insertion
47
+ * node are edits to the draft, not new tracked changes.
48
+ */
49
+ export declare function isInsidePendingNode(state: EditorState, from: number): boolean;
50
+ /**
51
+ * Returns the deletion mark that contains `insertPos` in the given document
52
+ * state, or null if the position is not inside a deletion mark.
53
+ * Used to detect typing/pasting inside a pending deletion zone.
54
+ */
55
+ export declare function getEnclosingDeletionMark(state: EditorState, insertPos: number, currentUserId: string, mode: RedlineMode): {
56
+ mark: Mark;
57
+ from: number;
58
+ to: number;
59
+ isSameUser: boolean;
60
+ } | null;
61
+ /**
62
+ * Returns trackable block nodes (tables, dividers) in a document range.
63
+ * Used to mark these nodes as pending-delete when a selection containing them is deleted.
64
+ */
65
+ export declare function collectTrackableBlockNodes(state: EditorState, from: number, to: number): Array<{
66
+ pos: number;
67
+ node: ProseMirrorNode;
68
+ typeName: string;
69
+ }>;
70
+ /**
71
+ * Resolves the appropriate mark type based on mode and operation.
72
+ */
73
+ export declare function getMarkType(state: EditorState, mode: RedlineMode, operation: 'insertion' | 'deletion'): MarkType | null;
@@ -0,0 +1,19 @@
1
+ import type { EditorView } from '@tiptap/pm/view';
2
+ import type { RedlineContext } from '../types';
3
+ /**
4
+ * handleTextInput — replacement semantics for typing over a selection
5
+ * (change-tracking.md M1 replace-rewriting: select + type).
6
+ *
7
+ * Existing text is never removed: it is marked deleted in place, and the
8
+ * typed text is inserted after it with an insertion mark (deletion +
9
+ * insertion form the visible "replace"). Typing over the author's own
10
+ * pending insertion simply replaces it — that content was never accepted,
11
+ * so no deletion record is needed.
12
+ *
13
+ * Plain cursor typing (from === to) is NOT handled here — it returns false
14
+ * so the default insert proceeds and the appendTransaction insertion tracker
15
+ * attributes it.
16
+ */
17
+ export declare function createTextInputHandler(deps: {
18
+ getContext: () => RedlineContext;
19
+ }): (view: EditorView, from: number, to: number, text: string) => boolean;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Suggestion identity (change-tracking.md M0.5 — ID policy).
3
+ *
4
+ * Mark ids must be collision-free across concurrent clients: UUIDv4 when
5
+ * available, otherwise a time+random fallback. Never counters.
6
+ */
7
+ export declare const createRedlineMarkId: () => string;
@@ -0,0 +1,36 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import type { RedlineOptions } from './types';
3
+ /**
4
+ * Unified Redline Extension — track changes for collaborative editing.
5
+ *
6
+ * Structured after change-tracking.md's module architecture:
7
+ * - M0 schema: schema/marks (inline marks), ImageRedline (image node attrs)
8
+ * - M1 engine: plugin.ts + engine/* (keyboard/paste/typing interception)
9
+ * - M1 trackers: FormatChange/Link/Image/DividerTrackingPlugin
10
+ * (appendTransaction diff trackers for non-text features)
11
+ * - M2 input: engine/clipboard, engine/textReplace, session typing state
12
+ * - M8 UI glue: MarkClickPlugin, MarkHighlightPlugin (decorations only)
13
+ * - M12 export: ViewModeDocumentPreview (clean-preview projection)
14
+ *
15
+ * The review engine (M3, accept/reject) and query API (M4, document scan)
16
+ * live in ./review and ./query and are consumed by the useRedline hook.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * Redline.configure({
21
+ * isActive: true,
22
+ * mode: 'user',
23
+ * currentEntity: {
24
+ * id: 'u1',
25
+ * name: 'Alice'
26
+ * }
27
+ * })
28
+ * ```
29
+ */
30
+ export declare const Redline: Extension<RedlineOptions, any>;
31
+ export type { RedlineOptions, RedlineEntity, RedlineMode, RedlineContext } from './types';
32
+ export { RedlinePlugin } from './plugin';
33
+ export { MARK_TYPES, SKIP_REDLINE_INSERTION_TRACKING_META, REDLINE_HANDLED_TEXT_INPUT_META, PASTE_BATCH_GROUP_META, isRedlineMarkName, } from './constants';
34
+ export { createRedlineMarkId } from './id';
35
+ export { redlineSession, RedlineSession } from './session';
36
+ export { UserInsertion, UserDeletion, AIInsertion, AIDeletion } from './schema/marks';
@@ -0,0 +1,34 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import type { RedlineMode, RedlineOptions } from './types';
3
+ /**
4
+ * RedlinePlugin — the tracking engine assembly (change-tracking.md M1 + M2).
5
+ *
6
+ * Composes the engine modules into four ProseMirror plugins:
7
+ * 1. keymap — Backspace/Delete/Mod-x rewritten as deletion marks
8
+ * (engine/deletion),
9
+ * 2. input props — copy sanitizing, paste batching + selection-replace,
10
+ * select+type replacement (engine/clipboard,
11
+ * engine/textReplace),
12
+ * 3. appendTransaction — insertion attribution and grouping
13
+ * (engine/insertion),
14
+ * 4. view — ends the cross-block typing session on pure cursor
15
+ * navigation.
16
+ *
17
+ * Mode/identity live in extension storage (live-toggled via
18
+ * `toggleRedlineMode`) and options (`currentEntity`, fixed at construction);
19
+ * the engine reads both through a single RedlineContext getter.
20
+ */
21
+ declare module '@tiptap/core' {
22
+ interface Commands<ReturnType> {
23
+ redline: {
24
+ /**
25
+ * Toggle redline tracking mode
26
+ * @param active - Whether to enable change tracking
27
+ * @param mode - Track changes for 'user' or 'ai'
28
+ */
29
+ toggleRedlineMode: (active: boolean, mode?: RedlineMode) => ReturnType;
30
+ };
31
+ }
32
+ }
33
+ export declare const RedlinePlugin: Extension<RedlineOptions, any>;
34
+ export type { RedlineOptions };
@@ -0,0 +1,52 @@
1
+ import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
2
+ import type { RedlineChange, RedlineChangeType } from '../changeTypes';
3
+ /**
4
+ * Document query module (change-tracking.md M4 — findSuggestions).
5
+ *
6
+ * `scanRedlineChanges(doc)` is a pure function: one descendants pass collects
7
+ * every marked text range and tracked node into MarkedNode records, which are
8
+ * then filtered (nested-change suppression inside tracked tables/rows/columns/
9
+ * blockquotes), grouped by mark id / group id into RedlineChange cards, and
10
+ * post-processed for display (format-category splitting, adjacent-card
11
+ * consolidation, hiding insertions fully covered by deletions).
12
+ *
13
+ * Results are memoized on document identity: ProseMirror docs are immutable,
14
+ * so the same doc reference always yields the same scan. This removes the
15
+ * O(doc) rescan on selection-only updates that the legacy hook performed.
16
+ */
17
+ export interface MarkedNode {
18
+ markId: string;
19
+ pos: number;
20
+ endPos: number;
21
+ text: string;
22
+ type: RedlineChangeType;
23
+ userId: string;
24
+ userName: string;
25
+ timestamp: string;
26
+ nodeType?: 'text' | 'table' | 'tableRow' | 'tableColumn' | 'blockquote' | 'link' | 'signature' | 'variable' | 'image' | 'divider';
27
+ /** Position of the enclosing tableCell/tableHeader, or null if not inside a cell.
28
+ * Used to prevent marks from different cells from being merged into one card. */
29
+ cellId?: number | null;
30
+ tableData?: string;
31
+ tableRowData?: string;
32
+ tableColumnData?: string;
33
+ signatureData?: string;
34
+ variableData?: string;
35
+ formatChanges?: string;
36
+ groupId?: string;
37
+ linkData?: string;
38
+ imageData?: RedlineChange['imageData'];
39
+ dividerAction?: 'insert' | 'delete';
40
+ /** Overrides the group lookup key so several mark ids collapse into one card
41
+ * (used to merge a contiguous deletion run regardless of per-gesture ids). */
42
+ groupKey?: string;
43
+ }
44
+ export declare const getFormatCategory: (formatKey: string) => string;
45
+ export declare const isListItemCreation: (change: RedlineChange) => boolean;
46
+ export interface RedlineScanResult {
47
+ /** Every change grouped by mark identity — the review engine operates on these. */
48
+ rawChanges: RedlineChange[];
49
+ /** Display list for the panel (insertions fully covered by deletions hidden). */
50
+ displayChanges: RedlineChange[];
51
+ }
52
+ export declare const scanRedlineChanges: (doc: ProseMirrorNode) => RedlineScanResult;
@@ -0,0 +1,25 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ import type { RedlineChange } from '../changeTypes';
3
+ /**
4
+ * Review engine entry points (change-tracking.md M3 — accept/reject as
5
+ * document transforms).
6
+ *
7
+ * `applyChangeOperation` builds ONE transaction per change: the resolver
8
+ * chain runs node-type resolvers first (falling through to the text path
9
+ * when a target node can't be located), and every mutation dispatches with
10
+ * the skip meta so the tracking engine never re-tracks a resolution
11
+ * (change-tracking.md M3.7). Selection is preserved and history is bypassed
12
+ * — resolutions are review actions, not edits.
13
+ *
14
+ * Bulk operations process changes bottom-up (descending position) so earlier
15
+ * positions stay valid across per-change transactions.
16
+ */
17
+ export declare const applyChangeOperation: (editor: Editor, change: RedlineChange, operation: "accept" | "reject") => boolean;
18
+ /**
19
+ * Accepts all changes (processes in reverse to avoid position shifts).
20
+ */
21
+ export declare const acceptAllChanges: (editor: Editor, rawChanges: RedlineChange[]) => void;
22
+ /**
23
+ * Rejects all changes (processes in reverse to avoid position shifts).
24
+ */
25
+ export declare const rejectAllChanges: (editor: Editor, rawChanges: RedlineChange[]) => void;
@@ -0,0 +1 @@
1
+ export { applyChangeOperation, acceptAllChanges, rejectAllChanges } from './applyChange';