fix: preserve placeholder case when restoring project text
This commit is contained in:
17
src/lib/restore-case.ts
Normal file
17
src/lib/restore-case.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/** Keep the saved spelling for the canonical placeholder and adapt edited case variants. */
|
||||||
|
export function restoreOriginalCase(match: string, key: string, original: string): string {
|
||||||
|
if (match === key) return original;
|
||||||
|
const letters = match.replace(/[^\p{L}]/gu, "");
|
||||||
|
if (!letters) return original;
|
||||||
|
if (letters === letters.toLocaleLowerCase("de")) return original.toLocaleLowerCase("de");
|
||||||
|
if (letters === letters.toLocaleUpperCase("de")) return original.toLocaleUpperCase("de");
|
||||||
|
if (/^\p{Lu}\p{Ll}+$/u.test(letters)) {
|
||||||
|
const originalLetters = original.replace(/[^\p{L}]/gu, "");
|
||||||
|
if (originalLetters && (originalLetters === originalLetters.toLocaleLowerCase("de")
|
||||||
|
|| originalLetters === originalLetters.toLocaleUpperCase("de"))) {
|
||||||
|
return original.toLocaleLowerCase("de").replace(/(^|[^\p{L}])(\p{L})/gu,
|
||||||
|
(_, prefix: string, letter: string) => prefix + letter.toLocaleUpperCase("de"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { PlaceholderMapping } from "../types";
|
import type { PlaceholderMapping } from "../types";
|
||||||
import type { ProjectRun } from "./velum-project";
|
import type { ProjectRun } from "./velum-project";
|
||||||
|
import { restoreOriginalCase } from "./restore-case";
|
||||||
|
|
||||||
function escapeRegExp(value: string): string {
|
function escapeRegExp(value: string): string {
|
||||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
@@ -98,6 +99,13 @@ function replaceNode(
|
|||||||
session: boolean,
|
session: boolean,
|
||||||
): { value: string; count: number } {
|
): { value: string; count: number } {
|
||||||
const candidates: Match[] = [];
|
const candidates: Match[] = [];
|
||||||
|
const foldedCounts = new Map<string, number>();
|
||||||
|
if (!session) {
|
||||||
|
for (const key of Object.keys(mapping)) {
|
||||||
|
const folded = key.toUpperCase();
|
||||||
|
foldedCounts.set(folded, (foldedCounts.get(folded) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const [key, entry] of Object.entries(mapping)) {
|
for (const [key, entry] of Object.entries(mapping)) {
|
||||||
const stripped = key.replace(/[<>]/g, "");
|
const stripped = key.replace(/[<>]/g, "");
|
||||||
const bare = session ? stripped : key;
|
const bare = session ? stripped : key;
|
||||||
@@ -105,7 +113,7 @@ function replaceNode(
|
|||||||
matchModes?.[key] === "anywhere"
|
matchModes?.[key] === "anywhere"
|
||||||
? escapeRegExp(bare)
|
? escapeRegExp(bare)
|
||||||
: `(?<![\\p{L}\\p{N}])${escapeRegExp(bare)}(?![\\p{L}\\p{N}])`;
|
: `(?<![\\p{L}\\p{N}])${escapeRegExp(bare)}(?![\\p{L}\\p{N}])`;
|
||||||
const patterns = [new RegExp(boundary, "gu")];
|
const patterns = [new RegExp(boundary, session ? "gu" : "giu")];
|
||||||
if (session) {
|
if (session) {
|
||||||
patterns.unshift(
|
patterns.unshift(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
@@ -116,10 +124,12 @@ function replaceNode(
|
|||||||
}
|
}
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
for (const match of value.matchAll(pattern)) {
|
for (const match of value.matchAll(pattern)) {
|
||||||
|
if (!session && match[0] !== key && (Object.prototype.hasOwnProperty.call(mapping, match[0])
|
||||||
|
|| foldedCounts.get(key.toUpperCase()) !== 1)) continue;
|
||||||
candidates.push({
|
candidates.push({
|
||||||
start: match.index,
|
start: match.index,
|
||||||
end: match.index + match[0].length,
|
end: match.index + match[0].length,
|
||||||
replacement: escapeHtml(entry.original),
|
replacement: escapeHtml(session ? entry.original : restoreOriginalCase(match[0], key, entry.original)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { environment } from "@raycast/api";
|
|||||||
import type { EntityType, PlaceholderMapping } from "../types";
|
import type { EntityType, PlaceholderMapping } from "../types";
|
||||||
import { whitelistKey } from "./project-whitelist";
|
import { whitelistKey } from "./project-whitelist";
|
||||||
import type { ProjectWhitelistEntry } from "./project-whitelist";
|
import type { ProjectWhitelistEntry } from "./project-whitelist";
|
||||||
|
import { restoreOriginalCase } from "./restore-case";
|
||||||
|
|
||||||
export { whitelistKey } from "./project-whitelist";
|
export { whitelistKey } from "./project-whitelist";
|
||||||
export type { ProjectWhitelistEntry } from "./project-whitelist";
|
export type { ProjectWhitelistEntry } from "./project-whitelist";
|
||||||
@@ -265,7 +266,7 @@ function restorePattern(mapping: PlaceholderMapping, matchModes: ProjectRun["mat
|
|||||||
originalFor: (match) => {
|
originalFor: (match) => {
|
||||||
const key = Object.prototype.hasOwnProperty.call(mapping, match)
|
const key = Object.prototype.hasOwnProperty.call(mapping, match)
|
||||||
? match : foldedKeys.get(match.toUpperCase());
|
? match : foldedKeys.get(match.toUpperCase());
|
||||||
return key ? mapping[key].original : undefined;
|
return key ? restoreOriginalCase(match, key, mapping[key].original) : undefined;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,16 @@ describe("formatierte Textwiederherstellung", () => {
|
|||||||
),
|
),
|
||||||
).toBe("<b>abcMax Mustermannxyz</b>");
|
).toBe("<b>abcMax Mustermannxyz</b>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("erhält HTML-Formatierung auch bei anders geschriebenen Platzhaltern", () => {
|
||||||
|
const html = "<b>person_01</b> und <i>Person_01</i>";
|
||||||
|
expect(restoreRichHtml(html, "person_01 und Person_01",
|
||||||
|
"max mustermann und Max Mustermann", mapping))
|
||||||
|
.toBe("<b>max mustermann</b> und <i>Max Mustermann</i>");
|
||||||
|
expect(restoreRichHtml("<b>Person_01</b> / <i>PERSON_01</i>",
|
||||||
|
"Person_01 / PERSON_01", "Person_01 / Eins", {
|
||||||
|
PERSON_01: { original: "Eins", type: "PERSON" },
|
||||||
|
person_01: { original: "Zwei", type: "PERSON" },
|
||||||
|
})).toBe("<b>Person_01</b> / <i>Eins</i>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -134,10 +134,19 @@ describe("Velum-Projektstände", () => {
|
|||||||
})).toBe("Archiv/BerichtMax Mustermann.docx");
|
})).toBe("Archiv/BerichtMax Mustermann.docx");
|
||||||
expect(project.restorePath("archiv/bericht_person_01.docx", {
|
expect(project.restorePath("archiv/bericht_person_01.docx", {
|
||||||
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
||||||
})).toBe("archiv/bericht_Max Mustermann.docx");
|
})).toBe("archiv/bericht_max mustermann.docx");
|
||||||
expect(project.restoreTextWithCount("person_01 und Person_01", {
|
expect(project.restoreTextWithCount("person_01 und Person_01", {
|
||||||
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
||||||
})).toEqual({ text: "Max Mustermann und Max Mustermann", replacements: 2 });
|
})).toEqual({ text: "max mustermann und Max Mustermann", replacements: 2 });
|
||||||
|
expect(project.restoreText("PERSON_01 / person_01 / Person_01", {
|
||||||
|
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
||||||
|
})).toBe("Max Mustermann / max mustermann / Max Mustermann");
|
||||||
|
expect(project.restoreText("KUNDE_01 / kunde_01 / Kunde_01", {
|
||||||
|
KUNDE_01: { original: "ACME GMBH", type: "KUNDE" },
|
||||||
|
})).toBe("ACME GMBH / acme gmbh / Acme Gmbh");
|
||||||
|
expect(project.restoreText("person_01 / PERSON_01", {
|
||||||
|
person_01: { original: "max mustermann", type: "PERSON" },
|
||||||
|
})).toBe("max mustermann / MAX MUSTERMANN");
|
||||||
expect(project.restoreText("BerichtPERSON_01", {
|
expect(project.restoreText("BerichtPERSON_01", {
|
||||||
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
PERSON_01: { original: "Max Mustermann", type: "PERSON" },
|
||||||
})).toBe("BerichtPERSON_01");
|
})).toBe("BerichtPERSON_01");
|
||||||
|
|||||||
Reference in New Issue
Block a user