UNPKG

text-compare-vue3

Version:

A powerful text comparison plugin for Vue.js with character-level diff support

303 lines (302 loc) 9.52 kB
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 };