@haklex/rich-diff-core 0.0.94 → 0.0.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compute-delta-stats.d.ts +41 -0
- package/dist/compute-delta-stats.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +146 -1
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SerializedEditorState } from 'lexical';
|
|
2
|
+
export interface DeltaStats {
|
|
3
|
+
chars: {
|
|
4
|
+
added: number;
|
|
5
|
+
removed: number;
|
|
6
|
+
};
|
|
7
|
+
lines: {
|
|
8
|
+
added: number;
|
|
9
|
+
removed: number;
|
|
10
|
+
};
|
|
11
|
+
words: {
|
|
12
|
+
added: number;
|
|
13
|
+
removed: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Computes delta statistics between two Lexical editor states.
|
|
18
|
+
*
|
|
19
|
+
* Returns counts of added/removed characters, words, and lines.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const stats = computeDeltaStats(oldState, newState);
|
|
24
|
+
* console.log(`+${stats.chars.added} / -${stats.chars.removed} chars`);
|
|
25
|
+
* console.log(`+${stats.words.added} / -${stats.words.removed} words`);
|
|
26
|
+
* console.log(`+${stats.lines.added} / -${stats.lines.removed} lines`);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function computeDeltaStats(oldState: SerializedEditorState, newState: SerializedEditorState): DeltaStats;
|
|
30
|
+
/**
|
|
31
|
+
* Formats delta stats as a human-readable string.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const stats = computeDeltaStats(oldState, newState);
|
|
36
|
+
* console.log(formatDeltaStats(stats));
|
|
37
|
+
* // "+91 lines, +2,122 words, +12,345 chars"
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatDeltaStats(stats: DeltaStats): string;
|
|
41
|
+
//# sourceMappingURL=compute-delta-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-delta-stats.d.ts","sourceRoot":"","sources":["../src/compute-delta-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAyB,MAAM,SAAS,CAAC;AAE5E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAsID;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,qBAAqB,GAC9B,UAAU,CAsBZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA2B1D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { DeltaStats } from './compute-delta-stats';
|
|
2
|
+
export { computeDeltaStats, formatDeltaStats } from './compute-delta-stats';
|
|
1
3
|
export type { DiffHunk, DiffOpType, MarkKind } from './compute-diff';
|
|
2
4
|
export { computeDiff, decorateSubtree, diffMergedNode, diffModifiedNode } from './compute-diff';
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,146 @@
|
|
|
1
|
+
function extractText(node) {
|
|
2
|
+
const n = node;
|
|
3
|
+
if (n.type === "text" && typeof n.text === "string") {
|
|
4
|
+
return n.text;
|
|
5
|
+
}
|
|
6
|
+
if (Array.isArray(n.children)) {
|
|
7
|
+
return n.children.map(extractText).join("");
|
|
8
|
+
}
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
function extractTextFromState(state) {
|
|
12
|
+
return state.root.children.map(extractText).join("\n");
|
|
13
|
+
}
|
|
14
|
+
function countWords(text) {
|
|
15
|
+
if (!text.trim()) return 0;
|
|
16
|
+
const cjkPattern = /[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]/g;
|
|
17
|
+
const cjkMatches = text.match(cjkPattern);
|
|
18
|
+
const cjkCount = cjkMatches ? cjkMatches.length : 0;
|
|
19
|
+
const nonCjkText = text.replaceAll(cjkPattern, " ");
|
|
20
|
+
const wordPattern = /[\p{L}\p{N}]+/gu;
|
|
21
|
+
const wordMatches = nonCjkText.match(wordPattern);
|
|
22
|
+
const wordCount = wordMatches ? wordMatches.length : 0;
|
|
23
|
+
return cjkCount + wordCount;
|
|
24
|
+
}
|
|
25
|
+
function countLines(state) {
|
|
26
|
+
return state.root.children.length;
|
|
27
|
+
}
|
|
28
|
+
function computeCharDiff(oldText, newText) {
|
|
29
|
+
const oldChars = Array.from(oldText);
|
|
30
|
+
const newChars = Array.from(newText);
|
|
31
|
+
const m = oldChars.length;
|
|
32
|
+
const n = newChars.length;
|
|
33
|
+
let prefix = 0;
|
|
34
|
+
while (prefix < m && prefix < n && oldChars[prefix] === newChars[prefix]) {
|
|
35
|
+
prefix++;
|
|
36
|
+
}
|
|
37
|
+
let oldSuffix = m - 1;
|
|
38
|
+
let newSuffix = n - 1;
|
|
39
|
+
while (oldSuffix >= prefix && newSuffix >= prefix && oldChars[oldSuffix] === newChars[newSuffix]) {
|
|
40
|
+
oldSuffix--;
|
|
41
|
+
newSuffix--;
|
|
42
|
+
}
|
|
43
|
+
const oldMiddle = oldChars.slice(prefix, oldSuffix + 1);
|
|
44
|
+
const newMiddle = newChars.slice(prefix, newSuffix + 1);
|
|
45
|
+
const MAX_MATRIX_CELLS = 1e5;
|
|
46
|
+
if (oldMiddle.length * newMiddle.length > MAX_MATRIX_CELLS) {
|
|
47
|
+
return {
|
|
48
|
+
removed: oldMiddle.join(""),
|
|
49
|
+
added: newMiddle.join("")
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (oldMiddle.length === 0) {
|
|
53
|
+
return { added: newMiddle.join(""), removed: "" };
|
|
54
|
+
}
|
|
55
|
+
if (newMiddle.length === 0) {
|
|
56
|
+
return { added: "", removed: oldMiddle.join("") };
|
|
57
|
+
}
|
|
58
|
+
const om = oldMiddle.length;
|
|
59
|
+
const nm = newMiddle.length;
|
|
60
|
+
const dp = Array.from(
|
|
61
|
+
{ length: om + 1 },
|
|
62
|
+
() => Array.from({ length: nm + 1 }).fill(0)
|
|
63
|
+
);
|
|
64
|
+
for (let i2 = 1; i2 <= om; i2++) {
|
|
65
|
+
for (let j2 = 1; j2 <= nm; j2++) {
|
|
66
|
+
if (oldMiddle[i2 - 1] === newMiddle[j2 - 1]) {
|
|
67
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
68
|
+
} else {
|
|
69
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const removedChars = [];
|
|
74
|
+
const addedChars = [];
|
|
75
|
+
let i = om;
|
|
76
|
+
let j = nm;
|
|
77
|
+
while (i > 0 && j > 0) {
|
|
78
|
+
if (oldMiddle[i - 1] === newMiddle[j - 1]) {
|
|
79
|
+
i--;
|
|
80
|
+
j--;
|
|
81
|
+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
82
|
+
removedChars.push(oldMiddle[i - 1]);
|
|
83
|
+
i--;
|
|
84
|
+
} else {
|
|
85
|
+
addedChars.push(newMiddle[j - 1]);
|
|
86
|
+
j--;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
while (i > 0) {
|
|
90
|
+
removedChars.push(oldMiddle[--i]);
|
|
91
|
+
}
|
|
92
|
+
while (j > 0) {
|
|
93
|
+
addedChars.push(newMiddle[--j]);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
added: addedChars.reverse().join(""),
|
|
97
|
+
removed: removedChars.reverse().join("")
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function computeDeltaStats(oldState, newState) {
|
|
101
|
+
const oldText = extractTextFromState(oldState);
|
|
102
|
+
const newText = extractTextFromState(newState);
|
|
103
|
+
const oldLines = countLines(oldState);
|
|
104
|
+
const newLines = countLines(newState);
|
|
105
|
+
const { added: addedText, removed: removedText } = computeCharDiff(oldText, newText);
|
|
106
|
+
return {
|
|
107
|
+
chars: {
|
|
108
|
+
added: Array.from(addedText).length,
|
|
109
|
+
removed: Array.from(removedText).length
|
|
110
|
+
},
|
|
111
|
+
words: {
|
|
112
|
+
added: countWords(addedText),
|
|
113
|
+
removed: countWords(removedText)
|
|
114
|
+
},
|
|
115
|
+
lines: {
|
|
116
|
+
added: Math.max(0, newLines - oldLines),
|
|
117
|
+
removed: Math.max(0, oldLines - newLines)
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function formatDeltaStats(stats) {
|
|
122
|
+
const parts = [];
|
|
123
|
+
const formatNumber = (n) => n.toLocaleString();
|
|
124
|
+
const formatDelta = (added, removed, unit) => {
|
|
125
|
+
if (added > 0 && removed > 0) {
|
|
126
|
+
return `+${formatNumber(added)}/-${formatNumber(removed)} ${unit}`;
|
|
127
|
+
}
|
|
128
|
+
if (added > 0) {
|
|
129
|
+
return `+${formatNumber(added)} ${unit}`;
|
|
130
|
+
}
|
|
131
|
+
if (removed > 0) {
|
|
132
|
+
return `-${formatNumber(removed)} ${unit}`;
|
|
133
|
+
}
|
|
134
|
+
return "";
|
|
135
|
+
};
|
|
136
|
+
const linesStr = formatDelta(stats.lines.added, stats.lines.removed, "lines");
|
|
137
|
+
const wordsStr = formatDelta(stats.words.added, stats.words.removed, "words");
|
|
138
|
+
const charsStr = formatDelta(stats.chars.added, stats.chars.removed, "chars");
|
|
139
|
+
if (linesStr) parts.push(linesStr);
|
|
140
|
+
if (wordsStr) parts.push(wordsStr);
|
|
141
|
+
if (charsStr) parts.push(charsStr);
|
|
142
|
+
return parts.join(", ") || "No changes";
|
|
143
|
+
}
|
|
1
144
|
const CHAR_DIFF_MAX_MATRIX_CELLS = 5e4;
|
|
2
145
|
const DELETE_MARK_STYLE = "background-color: color-mix(in srgb, var(--rc-alert-caution) 22%, transparent); text-decoration: line-through;";
|
|
3
146
|
const INSERT_MARK_STYLE = "background-color: color-mix(in srgb, var(--rc-alert-tip) 22%, transparent);";
|
|
@@ -448,8 +591,10 @@ function computeDiff(oldState, newState) {
|
|
|
448
591
|
return hunks;
|
|
449
592
|
}
|
|
450
593
|
export {
|
|
594
|
+
computeDeltaStats,
|
|
451
595
|
computeDiff,
|
|
452
596
|
decorateSubtree,
|
|
453
597
|
diffMergedNode,
|
|
454
|
-
diffModifiedNode
|
|
598
|
+
diffModifiedNode,
|
|
599
|
+
formatDeltaStats
|
|
455
600
|
};
|