@jhl548/duplicate-doc-core 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - Shared document, range, duplicate point, and editor snapshot types.
8
8
  - Similarity-based highlight color and CSS class helpers.
9
9
  - Range normalization and document filtering utilities.
10
+ - Plain-text highlight resolution helpers for checking whether a range can still render after edits.
10
11
  - Tiptap duplicate highlight extension.
11
12
  - Shared highlight styles via `@jhl548/duplicate-doc-core/style.css`.
12
13
 
@@ -22,6 +23,7 @@ npm install @jhl548/duplicate-doc-core
22
23
  import {
23
24
  DuplicateHighlightExtension,
24
25
  filterHighlightsForDocument,
26
+ hasResolvableHighlightInPlainText,
25
27
  type DuplicateHighlight,
26
28
  type NormalizedDocument
27
29
  } from "@jhl548/duplicate-doc-core";
@@ -29,3 +31,5 @@ import "@jhl548/duplicate-doc-core/style.css";
29
31
  ```
30
32
 
31
33
  This package is intended to be consumed directly by framework adapters such as `@jhl548/duplicate-doc-vue`, or by applications that need access to the shared duplicate document model.
34
+
35
+ Use `hasResolvableHighlightInPlainText(plainText, highlight)` before switching documents when the app needs to know whether at least one highlight range can still be resolved in the current edited text.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./colors.js";
2
+ export * from "./position.js";
2
3
  export * from "./ranges.js";
3
4
  export * from "./scroll.js";
5
+ export * from "./selection.js";
4
6
  export * from "./tiptapDuplicateHighlight.js";
5
7
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from "./colors.js";
2
+ export * from "./position.js";
2
3
  export * from "./ranges.js";
3
4
  export * from "./scroll.js";
5
+ export * from "./selection.js";
4
6
  export * from "./tiptapDuplicateHighlight.js";
5
7
  export * from "./types.js";
6
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,YAAY,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { DocumentSelectionChangePayload, RangeMapEntry } from "./types.js";
2
+ export interface PopupPosition {
3
+ top: number;
4
+ left: number;
5
+ placement: "above" | "below";
6
+ }
7
+ export interface SelectionPopupData {
8
+ documentId: string;
9
+ selection: DocumentSelectionChangePayload;
10
+ overlappingEntries: RangeMapEntry[];
11
+ }
12
+ export declare function calcSelectionPopupPosition(selRect: {
13
+ top: number;
14
+ bottom: number;
15
+ left: number;
16
+ right: number;
17
+ height: number;
18
+ }, viewportWidth: number, viewportHeight: number, popupRect?: {
19
+ width: number;
20
+ height: number;
21
+ }): PopupPosition;
22
+ export declare function getDomSelectionRect(): {
23
+ top: number;
24
+ bottom: number;
25
+ left: number;
26
+ right: number;
27
+ height: number;
28
+ } | null;
29
+ export declare function isDomSelectionInsideContainer(containerElement: HTMLElement): boolean;
@@ -0,0 +1,63 @@
1
+ export function calcSelectionPopupPosition(selRect, viewportWidth, viewportHeight, popupRect) {
2
+ const GAP = 8;
3
+ const popupWidth = popupRect?.width ?? 360;
4
+ const popupHeight = popupRect?.height ?? 200;
5
+ let left = selRect.left + (selRect.right - selRect.left) / 2 - popupWidth / 2;
6
+ left = Math.max(8, Math.min(left, viewportWidth - popupWidth - 8));
7
+ let top;
8
+ let placement;
9
+ const spaceBelow = viewportHeight - selRect.bottom;
10
+ const spaceAbove = selRect.top;
11
+ if (spaceBelow >= popupHeight + GAP || spaceBelow >= spaceAbove) {
12
+ placement = "below";
13
+ top = selRect.bottom + GAP;
14
+ }
15
+ else {
16
+ placement = "above";
17
+ top = selRect.top - popupHeight - GAP;
18
+ }
19
+ return { top, left, placement };
20
+ }
21
+ export function getDomSelectionRect() {
22
+ const domSelection = window.getSelection();
23
+ if (!domSelection || domSelection.isCollapsed || domSelection.rangeCount === 0) {
24
+ return null;
25
+ }
26
+ const domRange = domSelection.getRangeAt(0);
27
+ const rect = domRange.getBoundingClientRect();
28
+ if (rect.width === 0 && rect.height === 0) {
29
+ return null;
30
+ }
31
+ return {
32
+ top: rect.top,
33
+ bottom: rect.bottom,
34
+ left: rect.left,
35
+ right: rect.right,
36
+ height: rect.height
37
+ };
38
+ }
39
+ export function isDomSelectionInsideContainer(containerElement) {
40
+ const domSelection = window.getSelection();
41
+ if (!domSelection || domSelection.isCollapsed || domSelection.rangeCount === 0) {
42
+ return false;
43
+ }
44
+ const domRange = domSelection.getRangeAt(0);
45
+ const containerRect = containerElement.getBoundingClientRect();
46
+ if (domRange.startContainer) {
47
+ const node = domRange.startContainer;
48
+ if (!containerElement.contains(node.nodeType === Node.TEXT_NODE ? node.parentNode : node)) {
49
+ return false;
50
+ }
51
+ }
52
+ else {
53
+ return false;
54
+ }
55
+ const rect = domRange.getBoundingClientRect();
56
+ if (rect.width === 0 && rect.height === 0) {
57
+ return false;
58
+ }
59
+ const isHorizontallyVisible = rect.right > containerRect.left && rect.left < containerRect.right;
60
+ const isVerticallyVisible = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
61
+ return isHorizontallyVisible && isVerticallyVisible;
62
+ }
63
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { RangeMapEntry, TextRange } from "./types.js";
2
+ export interface SelectionRangeInfo {
3
+ selectedText: string;
4
+ from: number;
5
+ to: number;
6
+ empty: boolean;
7
+ plainTextOffset: {
8
+ start: number;
9
+ end: number;
10
+ } | null;
11
+ selectedRange: TextRange | null;
12
+ }
13
+ export declare function getSelectionRangeInfo(editor: {
14
+ state: {
15
+ selection: {
16
+ from: number;
17
+ to: number;
18
+ empty: boolean;
19
+ };
20
+ doc: {
21
+ textBetween: (from: number, to: number, separator: string | null, leafText?: string | null | ((leafNode: unknown) => string)) => string;
22
+ };
23
+ };
24
+ }): SelectionRangeInfo;
25
+ export declare function findOverlappingRangeMapEntries(rangeMap: RangeMapEntry[], plainTextOffset: {
26
+ start: number;
27
+ end: number;
28
+ }): RangeMapEntry[];
@@ -0,0 +1,39 @@
1
+ import { buildPlainTextPositionMap } from "./tiptapDuplicateHighlight.js";
2
+ export function getSelectionRangeInfo(editor) {
3
+ const { from, to, empty } = editor.state.selection;
4
+ const selectedText = editor.state.doc.textBetween(from, to, "\n");
5
+ let plainTextOffset = null;
6
+ let selectedRange = null;
7
+ if (!empty) {
8
+ const positions = buildPlainTextPositionMap(editor.state.doc);
9
+ const startIdx = positions.indexOf(from);
10
+ const lastIdx = positions.lastIndexOf(to - 1);
11
+ if (startIdx !== -1 && lastIdx !== -1 && lastIdx >= startIdx) {
12
+ const endIdx = lastIdx + 1;
13
+ plainTextOffset = { start: startIdx, end: endIdx };
14
+ selectedRange = {
15
+ start: startIdx,
16
+ end: endIdx,
17
+ matchedText: selectedText
18
+ };
19
+ }
20
+ }
21
+ return {
22
+ selectedText,
23
+ from,
24
+ to,
25
+ empty,
26
+ plainTextOffset,
27
+ selectedRange
28
+ };
29
+ }
30
+ export function findOverlappingRangeMapEntries(rangeMap, plainTextOffset) {
31
+ return rangeMap.filter((entry) => {
32
+ const entryStart = entry.textStart;
33
+ const entryEnd = entry.textEnd;
34
+ const selStart = plainTextOffset.start;
35
+ const selEnd = plainTextOffset.end;
36
+ return selStart < entryEnd && selEnd > entryStart;
37
+ });
38
+ }
39
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAY1E,MAAM,UAAU,qBAAqB,CAAC,MAKrC;IACC,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;IACnD,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IAElE,IAAI,eAAe,GAA0C,IAAI,CAAC;IAClE,IAAI,aAAa,GAAqB,IAAI,CAAC;IAE3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,yBAAyB,CAAC,MAAM,CAAC,KAAK,CAAC,GAAU,CAAC,CAAC;QAErE,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAE9C,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK,CAAC,CAAC,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC7D,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC;YAC3B,eAAe,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;YAEnD,aAAa,GAAG;gBACd,KAAK,EAAE,QAAQ;gBACf,GAAG,EAAE,MAAM;gBACX,WAAW,EAAE,YAAY;aAC1B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,YAAY;QACZ,IAAI;QACJ,EAAE;QACF,KAAK;QACL,eAAe;QACf,aAAa;KACd,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,QAAyB,EACzB,eAA+C;IAE/C,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAC/B,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;QACnC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC;QAC/B,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC;QACvC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC;QAEnC,OAAO,QAAQ,GAAG,QAAQ,IAAI,MAAM,GAAG,UAAU,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -10,12 +10,39 @@ declare module "@tiptap/core" {
10
10
  };
11
11
  }
12
12
  }
13
- export declare const duplicateHighlightPluginKey: PluginKey<DecorationSet>;
14
- export declare function buildPlainTextPositionMap(doc: ProseMirrorNode): number[];
15
- export declare function mapTextRangeToDocPositions(positions: number[], range: TextRange): {
13
+ interface MappedRange {
16
14
  from: number;
17
15
  to: number;
18
- } | null;
16
+ }
17
+ interface HighlightAnchor extends MappedRange {
18
+ key: string;
19
+ attrs: Record<string, string | undefined>;
20
+ expectedText?: string;
21
+ valid: boolean;
22
+ }
23
+ interface DuplicateHighlightState {
24
+ decorations: DecorationSet;
25
+ anchors: Record<string, HighlightAnchor>;
26
+ activeKeys: string[];
27
+ }
28
+ interface PlainTextSnapshot {
29
+ text: string;
30
+ positions: number[];
31
+ }
32
+ export interface ResolvedPlainTextRange {
33
+ start: number;
34
+ end: number;
35
+ matchedText: string;
36
+ source: "direct" | "fallback";
37
+ }
38
+ export declare const duplicateHighlightPluginKey: PluginKey<DuplicateHighlightState>;
39
+ export declare function buildPlainTextSnapshot(doc: ProseMirrorNode): PlainTextSnapshot;
40
+ export declare function buildPlainTextPositionMap(doc: ProseMirrorNode): number[];
41
+ export declare function mapTextRangeToDocPositions(positions: number[], range: TextRange): MappedRange | null;
42
+ export declare function resolveTextRangeInPlainText(plainText: string, range: TextRange, expectedText?: string | undefined): ResolvedPlainTextRange | null;
43
+ export declare function isTextRangeResolvableInPlainText(plainText: string, range: TextRange, expectedText?: string | undefined): boolean;
44
+ export declare function hasResolvableHighlightInPlainText(plainText: string, highlight: DuplicateHighlight): boolean;
19
45
  export declare const DuplicateHighlightExtension: Extension<any, {
20
46
  highlights: DuplicateHighlight[];
21
47
  }>;
48
+ export {};
@@ -2,13 +2,15 @@ import { Extension } from "@tiptap/core";
2
2
  import { Plugin, PluginKey } from "@tiptap/pm/state";
3
3
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
4
4
  import { createHighlightClass } from "./colors.js";
5
- import { normalizeRanges } from "./ranges.js";
5
+ import { normalizeRange, normalizeRanges } from "./ranges.js";
6
6
  export const duplicateHighlightPluginKey = new PluginKey("duplicate-highlight");
7
- export function buildPlainTextPositionMap(doc) {
7
+ export function buildPlainTextSnapshot(doc) {
8
+ const textParts = [];
8
9
  const positions = [];
9
10
  doc.forEach((blockNode, blockOffset, blockIndex) => {
10
11
  // 后端 plainText 以换行分隔块级节点,这里补齐同样的偏移,避免第二段以后定位偏移。
11
12
  if (blockIndex > 0) {
13
+ textParts.push("\n");
12
14
  positions.push(blockOffset);
13
15
  }
14
16
  blockNode.descendants((node, relativePos) => {
@@ -18,12 +20,19 @@ export function buildPlainTextPositionMap(doc) {
18
20
  const text = node.text ?? "";
19
21
  const absolutePos = blockOffset + 1 + relativePos;
20
22
  for (let index = 0; index < text.length; index += 1) {
23
+ textParts.push(text[index] ?? "");
21
24
  positions.push(absolutePos + index);
22
25
  }
23
26
  return true;
24
27
  });
25
28
  });
26
- return positions;
29
+ return {
30
+ text: textParts.join(""),
31
+ positions
32
+ };
33
+ }
34
+ export function buildPlainTextPositionMap(doc) {
35
+ return buildPlainTextSnapshot(doc).positions;
27
36
  }
28
37
  export function mapTextRangeToDocPositions(positions, range) {
29
38
  const from = positions[range.start];
@@ -34,28 +43,211 @@ export function mapTextRangeToDocPositions(positions, range) {
34
43
  const to = lastIncludedPosition + 1;
35
44
  return to > from ? { from, to } : null;
36
45
  }
37
- function buildDecorations(doc, highlights) {
38
- const decorations = [];
39
- const positions = buildPlainTextPositionMap(doc);
46
+ function getRangeText(doc, range) {
47
+ return doc.textBetween(range.from, range.to, "\n", "");
48
+ }
49
+ function rangeStillMatches(doc, range, text) {
50
+ if (!text) {
51
+ return true;
52
+ }
53
+ return getRangeText(doc, range) === text;
54
+ }
55
+ function getExpectedText(doc, range, text) {
56
+ return text || getRangeText(doc, range) || undefined;
57
+ }
58
+ function findClosestTextIndex(text, target, preferredIndex) {
59
+ if (!target) {
60
+ return -1;
61
+ }
62
+ let closestIndex = -1;
63
+ let closestDistance = Number.POSITIVE_INFINITY;
64
+ let searchIndex = text.indexOf(target);
65
+ while (searchIndex >= 0) {
66
+ const distance = Math.abs(searchIndex - preferredIndex);
67
+ if (distance < closestDistance) {
68
+ closestIndex = searchIndex;
69
+ closestDistance = distance;
70
+ }
71
+ searchIndex = text.indexOf(target, searchIndex + 1);
72
+ }
73
+ return closestIndex;
74
+ }
75
+ export function resolveTextRangeInPlainText(plainText, range, expectedText = range.matchedText) {
76
+ const normalizedRange = normalizeRange(range);
77
+ if (!normalizedRange) {
78
+ return null;
79
+ }
80
+ const directText = plainText.slice(normalizedRange.start, normalizedRange.end);
81
+ const isDirectRangeInBounds = normalizedRange.end <= plainText.length && directText.length > 0;
82
+ if (isDirectRangeInBounds && (!expectedText || directText === expectedText)) {
83
+ return {
84
+ start: normalizedRange.start,
85
+ end: normalizedRange.end,
86
+ matchedText: expectedText || directText,
87
+ source: "direct"
88
+ };
89
+ }
90
+ if (!expectedText) {
91
+ return null;
92
+ }
93
+ const fallbackStart = findClosestTextIndex(plainText, expectedText, normalizedRange.start);
94
+ if (fallbackStart < 0) {
95
+ return null;
96
+ }
97
+ return {
98
+ start: fallbackStart,
99
+ end: fallbackStart + expectedText.length,
100
+ matchedText: expectedText,
101
+ source: "fallback"
102
+ };
103
+ }
104
+ export function isTextRangeResolvableInPlainText(plainText, range, expectedText = range.matchedText) {
105
+ return Boolean(resolveTextRangeInPlainText(plainText, range, expectedText));
106
+ }
107
+ export function hasResolvableHighlightInPlainText(plainText, highlight) {
108
+ return normalizeRanges(highlight.ranges).some((range) => isTextRangeResolvableInPlainText(plainText, range, range.matchedText));
109
+ }
110
+ function getRangeKey(highlight, range, index) {
111
+ return [
112
+ highlight.documentId,
113
+ highlight.duplicateId,
114
+ range.blockId ?? "unknown-block",
115
+ range.start,
116
+ range.end,
117
+ index
118
+ ].join("::");
119
+ }
120
+ function getDecorationAttrs(highlight, range) {
121
+ return {
122
+ class: createHighlightClass(highlight.similarity, highlight.active, highlight.ignored),
123
+ "data-duplicate-id": highlight.duplicateId,
124
+ "data-section-path": highlight.sectionPath?.join(" / ") ?? range.sectionPath?.join(" / "),
125
+ "data-region": highlight.region ?? range.region,
126
+ "data-semantic-type": highlight.semanticType ?? range.semanticType,
127
+ "data-noise-reason": highlight.noiseReason,
128
+ "data-table-id": highlight.tableContext?.tableId ?? range.tableContext?.tableId
129
+ };
130
+ }
131
+ function createAnchor(doc, snapshot, highlight, range, index, expectedText = range.matchedText) {
132
+ const mappedRange = mapTextRangeToDocPositions(snapshot.positions, range);
133
+ if (mappedRange && rangeStillMatches(doc, mappedRange, expectedText)) {
134
+ return {
135
+ key: getRangeKey(highlight, range, index),
136
+ from: mappedRange.from,
137
+ to: mappedRange.to,
138
+ attrs: getDecorationAttrs(highlight, range),
139
+ expectedText: getExpectedText(doc, mappedRange, expectedText),
140
+ valid: true
141
+ };
142
+ }
143
+ const fallbackStart = findClosestTextIndex(snapshot.text, expectedText ?? "", range.start);
144
+ const fallbackRange = fallbackStart >= 0 && expectedText
145
+ ? mapTextRangeToDocPositions(snapshot.positions, {
146
+ ...range,
147
+ start: fallbackStart,
148
+ end: fallbackStart + expectedText.length
149
+ })
150
+ : null;
151
+ if (!fallbackRange || !rangeStillMatches(doc, fallbackRange, expectedText)) {
152
+ return null;
153
+ }
154
+ return {
155
+ key: getRangeKey(highlight, range, index),
156
+ from: fallbackRange.from,
157
+ to: fallbackRange.to,
158
+ attrs: getDecorationAttrs(highlight, range),
159
+ expectedText: getExpectedText(doc, fallbackRange, expectedText),
160
+ valid: true
161
+ };
162
+ }
163
+ function buildDecorations(doc, anchors, activeKeys) {
164
+ const activeKeySet = new Set(activeKeys);
165
+ const decorations = anchors
166
+ .filter((anchor) => anchor.valid && activeKeySet.has(anchor.key) && anchor.to > anchor.from)
167
+ .map((anchor) => Decoration.inline(anchor.from, anchor.to, anchor.attrs));
168
+ return DecorationSet.create(doc, decorations);
169
+ }
170
+ function buildHighlightState(doc, highlights, previousAnchors = {}) {
171
+ const snapshot = buildPlainTextSnapshot(doc);
172
+ const anchors = { ...previousAnchors };
173
+ const activeKeys = [];
40
174
  for (const highlight of highlights) {
41
175
  const ranges = normalizeRanges(highlight.ranges);
42
- for (const range of ranges) {
43
- const mappedRange = mapTextRangeToDocPositions(positions, range);
44
- if (!mappedRange) {
176
+ for (const [index, range] of ranges.entries()) {
177
+ const key = getRangeKey(highlight, range, index);
178
+ const previousAnchor = anchors[key];
179
+ activeKeys.push(key);
180
+ if (previousAnchor && !previousAnchor.valid) {
181
+ const restoredAnchor = createAnchor(doc, snapshot, highlight, range, index, previousAnchor.expectedText);
182
+ if (restoredAnchor) {
183
+ anchors[key] = restoredAnchor;
184
+ }
185
+ else {
186
+ anchors[key] = {
187
+ ...previousAnchor,
188
+ attrs: getDecorationAttrs(highlight, range)
189
+ };
190
+ }
45
191
  continue;
46
192
  }
47
- decorations.push(Decoration.inline(mappedRange.from, mappedRange.to, {
48
- class: createHighlightClass(highlight.similarity, highlight.active, highlight.ignored),
49
- "data-duplicate-id": highlight.duplicateId,
50
- "data-section-path": highlight.sectionPath?.join(" / ") ?? range.sectionPath?.join(" / "),
51
- "data-region": highlight.region ?? range.region,
52
- "data-semantic-type": highlight.semanticType ?? range.semanticType,
53
- "data-noise-reason": highlight.noiseReason,
54
- "data-table-id": highlight.tableContext?.tableId ?? range.tableContext?.tableId
55
- }));
193
+ if (previousAnchor) {
194
+ const updatedAnchor = {
195
+ ...previousAnchor,
196
+ attrs: getDecorationAttrs(highlight, range)
197
+ };
198
+ if (rangeStillMatches(doc, updatedAnchor, updatedAnchor.expectedText)) {
199
+ anchors[key] = updatedAnchor;
200
+ }
201
+ else {
202
+ anchors[key] =
203
+ createAnchor(doc, snapshot, highlight, range, index, updatedAnchor.expectedText) ?? {
204
+ ...updatedAnchor,
205
+ valid: false
206
+ };
207
+ }
208
+ continue;
209
+ }
210
+ const anchor = createAnchor(doc, snapshot, highlight, range, index);
211
+ if (anchor) {
212
+ anchors[key] = anchor;
213
+ }
56
214
  }
57
215
  }
58
- return DecorationSet.create(doc, decorations);
216
+ return {
217
+ decorations: buildDecorations(doc, Object.values(anchors), activeKeys),
218
+ anchors,
219
+ activeKeys
220
+ };
221
+ }
222
+ function mapAnchors(doc, anchors, activeKeys, trMapping) {
223
+ const nextAnchors = {};
224
+ const activeKeySet = new Set(activeKeys);
225
+ for (const [key, anchor] of Object.entries(anchors)) {
226
+ if (!activeKeySet.has(key)) {
227
+ nextAnchors[key] = anchor;
228
+ continue;
229
+ }
230
+ if (!anchor.valid) {
231
+ nextAnchors[key] = anchor;
232
+ continue;
233
+ }
234
+ const fromResult = trMapping.mapResult(anchor.from, 1);
235
+ const toResult = trMapping.mapResult(anchor.to, -1);
236
+ const from = fromResult.pos;
237
+ const to = toResult.pos;
238
+ const wasDeleted = fromResult.deleted || toResult.deleted || to <= from;
239
+ const mappedAnchor = {
240
+ ...anchor,
241
+ from,
242
+ to
243
+ };
244
+ const textChanged = !rangeStillMatches(doc, mappedAnchor, anchor.expectedText);
245
+ nextAnchors[key] = {
246
+ ...mappedAnchor,
247
+ valid: !wasDeleted && to > from && to <= doc.content.size && !textChanged
248
+ };
249
+ }
250
+ return nextAnchors;
59
251
  }
60
252
  export const DuplicateHighlightExtension = Extension.create({
61
253
  name: "duplicateHighlight",
@@ -80,18 +272,23 @@ export const DuplicateHighlightExtension = Extension.create({
80
272
  new Plugin({
81
273
  key: duplicateHighlightPluginKey,
82
274
  state: {
83
- init: (_, state) => buildDecorations(state.doc, this.storage.highlights),
84
- apply: (tr, decorationSet, _oldState, newState) => {
275
+ init: (_, state) => buildHighlightState(state.doc, this.storage.highlights),
276
+ apply: (tr, pluginState, _oldState, newState) => {
85
277
  const meta = tr.getMeta(duplicateHighlightPluginKey);
86
278
  if (meta?.highlights) {
87
- return buildDecorations(newState.doc, meta.highlights);
279
+ return buildHighlightState(newState.doc, meta.highlights, pluginState.anchors);
88
280
  }
89
- return decorationSet.map(tr.mapping, tr.doc);
281
+ const anchors = mapAnchors(newState.doc, pluginState.anchors, pluginState.activeKeys, tr.mapping);
282
+ return {
283
+ ...pluginState,
284
+ decorations: buildDecorations(newState.doc, Object.values(anchors), pluginState.activeKeys),
285
+ anchors
286
+ };
90
287
  }
91
288
  },
92
289
  props: {
93
290
  decorations(state) {
94
- return duplicateHighlightPluginKey.getState(state);
291
+ return duplicateHighlightPluginKey.getState(state)?.decorations;
95
292
  }
96
293
  }
97
294
  })
@@ -1 +1 @@
1
- {"version":3,"file":"tiptapDuplicateHighlight.js","sourceRoot":"","sources":["../src/tiptapDuplicateHighlight.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tiptapDuplicateHighlight.js","sourceRoot":"","sources":["../src/tiptapDuplicateHighlight.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface TableCellContext {
12
12
  export interface TextRange {
13
13
  start: number;
14
14
  end: number;
15
+ matchedText?: string;
15
16
  blockId?: string;
16
17
  sectionPath?: string[];
17
18
  region?: DocumentRegion;
@@ -88,3 +89,15 @@ export interface EditorChangePayload {
88
89
  plainText: string;
89
90
  json?: unknown;
90
91
  }
92
+ export interface DocumentSelectionChangePayload {
93
+ documentId: string;
94
+ selectedText: string;
95
+ from: number;
96
+ to: number;
97
+ empty: boolean;
98
+ plainTextOffset: {
99
+ start: number;
100
+ end: number;
101
+ } | null;
102
+ selectedRange: TextRange | null;
103
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhl548/duplicate-doc-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/style.css CHANGED
@@ -29,3 +29,53 @@
29
29
  opacity: 0.62;
30
30
  border-bottom-style: dashed;
31
31
  }
32
+
33
+ .dupdoc-selection-popup {
34
+ position: fixed;
35
+ z-index: 1000;
36
+ background: #ffffff;
37
+ border: 1px solid rgba(15, 23, 42, 0.12);
38
+ border-radius: 12px;
39
+ box-shadow: 0 12px 40px rgba(15, 23, 42, 0.14);
40
+ overflow: auto;
41
+ }
42
+
43
+ .dupdoc-selection-popup__header {
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ padding: 10px 14px;
48
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
49
+ font-size: 13px;
50
+ font-weight: 600;
51
+ color: #344054;
52
+ background: #f9fafb;
53
+ }
54
+
55
+ .dupdoc-selection-popup__close {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ width: 20px;
60
+ height: 20px;
61
+ border: 0;
62
+ border-radius: 4px;
63
+ color: #667085;
64
+ background: transparent;
65
+ cursor: pointer;
66
+ font-size: 14px;
67
+ }
68
+
69
+ .dupdoc-selection-popup__close:hover {
70
+ background: rgba(15, 23, 42, 0.06);
71
+ color: #1f2937;
72
+ }
73
+
74
+ .dupdoc-selection-popup__body {
75
+ padding: 12px 14px;
76
+ font-size: 13px;
77
+ line-height: 1.6;
78
+ color: #475467;
79
+ max-height: 60vh;
80
+ overflow: auto;
81
+ }