add tiptap
This commit is contained in:
@@ -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: ""
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user