add tiptap

This commit is contained in:
Timi
2026-08-21 01:11:23 +08:00
parent 763783b984
commit 68d9946b70
41 changed files with 5845 additions and 35 deletions
+134
View File
@@ -0,0 +1,134 @@
export type TextSearchOptions = {
caseSensitive: boolean;
useRegex: boolean;
};
export type TextSearchMatch = {
start: number;
end: number;
text: string;
};
export type TextSearchResult = {
matches: TextSearchMatch[];
error: string;
};
const buildFlags = (options: TextSearchOptions, global = true) => {
let flags = global ? "g" : "";
if (!options.caseSensitive) {
flags += "i";
}
return flags;
};
export const escapeSearchKeyword = (value: string) => (
value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
);
export const buildSearchRegex = (
keyword: string,
options: TextSearchOptions,
global = true
) => {
if (!keyword) {
return { regex: undefined, error: "" };
}
try {
const source = options.useRegex ? keyword : escapeSearchKeyword(keyword);
const regex = new RegExp(source, buildFlags(options, global));
const emptyMatch = "".match(regex);
if (emptyMatch && emptyMatch[0] === "") {
return {
regex: undefined,
error: "正则不能匹配空文本"
};
}
return { regex, error: "" };
} catch {
return {
regex: undefined,
error: "正则表达式无效"
};
}
};
export const findTextMatches = (
value: string,
keyword: string,
options: TextSearchOptions
): TextSearchResult => {
const { regex, error } = buildSearchRegex(keyword, options, true);
if (!regex || error) {
return {
matches: [],
error
};
}
const matches: TextSearchMatch[] = [];
let match = regex.exec(value);
while (match) {
if (!match[0]) {
return {
matches: [],
error: "正则不能匹配空文本"
};
}
matches.push({
start: match.index,
end: match.index + match[0].length,
text: match[0]
});
match = regex.exec(value);
}
return {
matches,
error: ""
};
};
export const replaceMatchText = (
matchText: string,
keyword: string,
replaceText: string,
options: TextSearchOptions
) => {
if (!options.useRegex) {
return replaceText;
}
const { regex, error } = buildSearchRegex(keyword, options, false);
if (!regex || error) {
return matchText;
}
return matchText.replace(regex, replaceText);
};
export const replaceAllText = (
value: string,
keyword: string,
replaceText: string,
options: TextSearchOptions
) => {
const { matches, error } = findTextMatches(value, keyword, options);
if (error || 1 > matches.length) {
return {
value,
count: 0,
error
};
}
let nextValue = "";
let lastEnd = 0;
matches.forEach((match) => {
nextValue += value.slice(lastEnd, match.start);
nextValue += replaceMatchText(match.text, keyword, replaceText, options);
lastEnd = match.end;
});
nextValue += value.slice(lastEnd);
return {
value: nextValue,
count: matches.length,
error: ""
};
};