text-compare-vue3
Version:
A powerful text comparison plugin for Vue.js with character-level diff support
303 lines (302 loc) • 9.52 kB
JavaScript
var D = Object.defineProperty;
var S = (s, e, r) => e in s ? D(s, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : s[e] = r;
var P = (s, e, r) => (S(s, typeof e != "symbol" ? e + "" : e, r), r);
import { ref as _, computed as p, defineComponent as k, watchEffect as E, openBlock as h, createElementBlock as v, createElementVNode as V, Fragment as w, normalizeClass as g, renderList as b, normalizeStyle as j, toDisplayString as y, createCommentVNode as C } from "vue";
function L(s, e, r = !0) {
if (!r || !s || !e)
return {
oldParts: [{ value: s || "-", removed: !1 }],
newParts: [{ value: e || "-", added: !1 }]
};
const o = [], t = [], n = Array.from(s), i = Array.from(e), u = [];
for (let l = 0; l <= n.length; l++) {
u[l] = [];
for (let d = 0; d <= i.length; d++)
l === 0 || d === 0 ? u[l][d] = 0 : n[l - 1] === i[d - 1] ? u[l][d] = u[l - 1][d - 1] + 1 : u[l][d] = Math.max(u[l - 1][d], u[l][d - 1]);
}
let f = n.length, a = i.length;
const c = [];
for (; f > 0 && a > 0; )
n[f - 1] === i[a - 1] ? (c.unshift({
char: n[f - 1],
oldIndex: f - 1,
newIndex: a - 1
}), f--, a--) : u[f - 1][a] > u[f][a - 1] ? f-- : a--;
c.length > 0 && (c[0].oldIndex > 0 && o.push({
value: s.slice(0, c[0].oldIndex),
removed: !0
}), c[0].newIndex > 0 && t.push({
value: e.slice(0, c[0].newIndex),
added: !0
}));
for (let l = 0; l < c.length; l++) {
const d = c[l], m = c[l + 1];
o.push({
value: d.char,
removed: !1
}), t.push({
value: d.char,
added: !1
}), m && (m.oldIndex - d.oldIndex > 1 && o.push({
value: s.slice(d.oldIndex + 1, m.oldIndex),
removed: !0
}), m.newIndex - d.newIndex > 1 && t.push({
value: e.slice(d.newIndex + 1, m.newIndex),
added: !0
})), d.oldIndex, d.newIndex;
}
if (c.length > 0) {
const l = c[c.length - 1];
l.oldIndex < n.length - 1 && o.push({
value: s.slice(l.oldIndex + 1),
removed: !0
}), l.newIndex < i.length - 1 && t.push({
value: e.slice(l.newIndex + 1),
added: !0
});
} else
o.push({
value: s,
removed: !0
}), t.push({
value: e,
added: !0
});
return { oldParts: o, newParts: t };
}
function R(s, e, r = {}) {
const o = _(s instanceof Object ? s.value : s), t = _(e instanceof Object ? e.value : e), n = p(() => {
try {
return L(o.value || "", t.value || "", !0);
} catch (a) {
return console.error("Error computing diff:", a), {
oldParts: [{ value: o.value || "", removed: !1 }],
newParts: [{ value: t.value || "", added: !1 }]
};
}
}), i = p(() => {
try {
const a = (o.value || "").length + (t.value || "").length;
return a === 0 ? 100 : o.value.length === 0 || t.value.length === 0 ? 0 : n.value.oldParts.filter((d) => !d.removed).reduce((d, m) => d + m.value.length, 0) * 2 / a * 100;
} catch (a) {
return console.error("Error computing similarity:", a), 0;
}
}), u = p(() => {
const { customColors: a = {} } = r;
return {
common: { color: a.commonColor || "inherit" },
removed: {
backgroundColor: "#ffeef0",
color: a.removedColor || "#f56c6c"
},
added: {
backgroundColor: "#e6ffed",
color: a.addedColor || "#28a745"
}
};
});
return {
diffResult: n,
similarity: i,
styles: u,
updateTexts: (a, c) => {
o.value = a || "", t.value = c || "";
}
};
}
const B = k({
name: "TextDiff",
props: {
oldText: {
type: String,
default: ""
},
newText: {
type: String,
default: ""
},
isDifferent: {
type: Boolean,
default: !1
},
mode: {
type: String,
default: "diff",
validator: (s) => typeof s == "string" && ["diff", "full"].includes(s)
},
options: {
type: Object,
default: () => ({})
},
showSimilarity: {
type: Boolean,
default: !1
}
},
setup(s) {
const { diffResult: e, similarity: r, styles: o, updateTexts: t } = R(
s.oldText || "",
s.newText || "",
s.options
), n = p(() => !s.oldText || !s.newText || s.oldText.trim() === "" || s.newText.trim() === "");
return E(() => {
t(s.oldText || "", s.newText || "");
}), {
diffResult: e,
similarity: r,
styles: o,
hasEmptyValue: n
};
}
});
const O = (s, e) => {
const r = s.__vccOpts || s;
for (const [o, t] of e)
r[o] = t;
return r;
}, A = { class: "text-diff" }, $ = { class: "text-diff__content" }, F = {
key: 0,
class: "text-diff__similarity"
};
function M(s, e, r, o, t, n) {
return h(), v("div", A, [
V("div", $, [
s.mode === "diff" ? (h(), v(w, { key: 0 }, [
s.diffResult ? (h(), v("div", {
key: 0,
class: g(["text-diff__row", { "text-diff__row--different": s.hasEmptyValue }])
}, [
(h(!0), v(w, null, b(s.diffResult.oldParts, (i, u) => (h(), v("span", {
key: "old-" + u,
class: g({
"text-diff__part": !0,
"text-diff__part--removed": i.removed || s.hasEmptyValue,
"text-diff__part--unchanged": !i.removed && !s.hasEmptyValue
}),
style: j(i.removed || s.hasEmptyValue ? s.styles.removed : s.styles.common)
}, y(i.value || "-"), 7))), 128))
], 2)) : C("", !0)
], 64)) : (h(), v("div", {
key: 1,
class: g(["text-diff__row", { "text-diff__row--different": s.isDifferent || s.hasEmptyValue }])
}, y(s.oldText || "-"), 3))
]),
s.showSimilarity && s.similarity !== null ? (h(), v("div", F, " 相似度: " + y(s.similarity.toFixed(2)) + "% ", 1)) : C("", !0)
]);
}
const N = /* @__PURE__ */ O(B, [["render", M], ["__scopeId", "data-v-1d465779"]]);
class G {
constructor(e = {}) {
P(this, "options");
this.options = {
ignoreCase: e.ignoreCase ?? !1,
ignoreSpace: e.ignoreSpace ?? !1,
timeout: e.timeout ?? 5e3,
ignoreRepeat: e.ignoreRepeat ?? !0
};
}
compare(e, r) {
if (this.options.ignoreCase && (e = e.toLowerCase(), r = r.toLowerCase()), this.options.ignoreSpace && (e = e.trim(), r = r.trim()), !this.options.ignoreRepeat) {
const i = this.countDuplicates(e, r);
if (i > 0)
return {
oldParts: [{ value: e, removed: !0 }],
newParts: [{ value: r, added: !0 }],
statistics: {
additions: 1,
deletions: 1,
changes: 0,
duplicates: i
}
};
}
if (e === r)
return {
oldParts: [{ value: e }],
newParts: [{ value: r }],
statistics: {
additions: 0,
deletions: 0,
changes: 0,
duplicates: 0
}
};
if (!e || !r)
return {
oldParts: [{ value: e || "", removed: !e }],
newParts: [{ value: r || "", added: !r }],
statistics: {
additions: e ? 0 : 1,
deletions: r ? 0 : 1,
changes: 0,
duplicates: 0
}
};
const o = Date.now(), t = this.createLCSMatrix(e, r), n = this.backtrack(t, e, r);
return Date.now() - o > this.options.timeout ? (console.warn("Diff computation timed out"), {
oldParts: [{ value: e, removed: !0 }],
newParts: [{ value: r, added: !0 }],
statistics: {
additions: 1,
deletions: 1,
changes: 0,
duplicates: 0
}
}) : n;
}
createLCSMatrix(e, r) {
const o = Array(e.length + 1).fill(0).map(() => Array(r.length + 1).fill(0));
for (let t = 1; t <= e.length; t++)
for (let n = 1; n <= r.length; n++)
e[t - 1] === r[n - 1] ? o[t][n] = o[t - 1][n - 1] + 1 : o[t][n] = Math.max(o[t - 1][n], o[t][n - 1]);
return o;
}
backtrack(e, r, o) {
let t = r.length, n = o.length;
const i = [], u = [];
let f = "", a = "", c = 0, l = 0, d = 0;
for (; t > 0 || n > 0; )
t > 0 && n > 0 && r[t - 1] === o[n - 1] ? (f = r[t - 1] + f, a = o[n - 1] + a, t--, n--) : n > 0 && (t === 0 || e[t][n - 1] >= e[t - 1][n]) ? (f && (i.unshift({ value: f }), u.unshift({ value: a }), f = "", a = ""), a = o[n - 1] + a, c++, n--) : t > 0 && (n === 0 || e[t][n - 1] < e[t - 1][n]) && (f && (i.unshift({ value: f }), u.unshift({ value: a }), f = "", a = ""), f = r[t - 1] + f, l++, t--);
(f || a) && (i.unshift({ value: f }), u.unshift({ value: a }));
const m = this.mergeParts(i, !0), I = this.mergeParts(u, !1);
return {
oldParts: m,
newParts: I,
statistics: {
additions: c,
deletions: l,
changes: d,
duplicates: this.countDuplicates(r, o)
}
};
}
mergeParts(e, r) {
const o = [];
let t = null;
for (const n of e) {
if (!n.value)
continue;
const i = r ? "removed" : "added", u = n[i];
!t || t[i] !== u ? (t && o.push(t), t = { value: n.value }, u && (t[i] = !0)) : t.value += n.value;
}
return t && o.push(t), o;
}
countDuplicates(e, r) {
const o = e.split(/\s+/).filter(Boolean), t = r.split(/\s+/).filter(Boolean), n = new Set(o), i = new Set(t);
let u = 0;
for (const f of n)
i.has(f) && u++;
return u;
}
}
const z = (s) => {
s.component("TextDiff", N);
}, H = { install: z };
export {
G as DiffEngine,
N as TextDiff,
H as default,
L as getDiffParts,
z as install,
R as useTextComparison
};