@haklex/rich-diff-core 0.0.94 → 0.0.95

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.
@@ -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
@@ -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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haklex/rich-diff-core",
3
- "version": "0.0.94",
3
+ "version": "0.0.95",
4
4
  "description": "Lexical diff engine — character-level and structural diff with node decoration",
5
5
  "repository": {
6
6
  "type": "git",