2023-01-02 18:45:49 +00:00
|
|
|
import defaultTo from 'lodash/defaultTo';
|
|
|
|
import { IFormattingTool } from '../types';
|
|
|
|
|
|
|
|
export interface IRichTextFormat {
|
|
|
|
type: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface IRichTextValue {
|
|
|
|
text: string;
|
|
|
|
formats: IRichTextFormat[][];
|
|
|
|
start: number|null;
|
|
|
|
end: number|null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const findToolForElement = (
|
|
|
|
tools: IFormattingTool[],
|
|
|
|
element: Element,
|
|
|
|
): IFormattingTool|null => tools.find(tool => {
|
|
|
|
if (tool.tagName && element.tagName !== tool.tagName) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tool.className && !element.classList.contains(tool.className)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}) || null;
|
|
|
|
|
|
|
|
export const createFromString = (
|
|
|
|
value: string = '',
|
|
|
|
formats: IRichTextFormat[] = [],
|
|
|
|
): IRichTextValue => ({
|
|
|
|
text: value,
|
|
|
|
formats: (new Array(value.length)).fill([...formats]),
|
|
|
|
start: null,
|
|
|
|
end: null,
|
|
|
|
});
|
|
|
|
|
|
|
|
const createFromDOMNodeRecursively = (
|
|
|
|
tools: IFormattingTool[],
|
|
|
|
node: ChildNode,
|
|
|
|
formats: IRichTextFormat[] = [],
|
|
|
|
): IRichTextValue => {
|
|
|
|
if (node.nodeType === node.TEXT_NODE) {
|
|
|
|
return createFromString(node.textContent || '', formats);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.nodeType === node.ELEMENT_NODE) {
|
|
|
|
return createFromDOMElement(tools, node as Element, formats);
|
|
|
|
}
|
|
|
|
|
|
|
|
return createFromString('', []);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const createFromDOMElement = (
|
|
|
|
tools: IFormattingTool[],
|
|
|
|
element: Element,
|
|
|
|
formats: IRichTextFormat[] = [],
|
|
|
|
): IRichTextValue => {
|
|
|
|
const tool = findToolForElement(tools, element);
|
|
|
|
const subFormats = tool
|
|
|
|
? [
|
|
|
|
...formats.filter(f => f.type !== tool.name),
|
|
|
|
{ type: tool.name },
|
|
|
|
]
|
|
|
|
: [ ...formats ];
|
|
|
|
|
|
|
|
const nodes = Array.from(element.childNodes);
|
|
|
|
return concat(...nodes.map(node => createFromDOMNodeRecursively(tools, node, subFormats)));
|
|
|
|
};
|
|
|
|
|
|
|
|
export const createFromDOMString = (
|
|
|
|
tools: IFormattingTool[],
|
|
|
|
value: string,
|
|
|
|
formats: IRichTextFormat[] = [],
|
|
|
|
): IRichTextValue => {
|
|
|
|
const div = document.createElement('div');
|
|
|
|
div.innerHTML = value;
|
|
|
|
return createFromDOMElement(tools, div, formats);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const create = (
|
|
|
|
tools: IFormattingTool[],
|
|
|
|
value: Element|string = '',
|
|
|
|
): IRichTextValue => {
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
return createFromDOMString(tools, value);
|
|
|
|
}
|
|
|
|
return createFromDOMElement(tools, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const toHTMLString = (
|
|
|
|
tools: IFormattingTool[],
|
|
|
|
value: IRichTextValue,
|
|
|
|
) => {
|
|
|
|
const tools = [...tools].sort((a, b) => {
|
|
|
|
const tensionA = defaultTo(a.surfaceTension, Infinity);
|
|
|
|
const tensionB = defaultTo(b.surfaceTension, Infinity);
|
|
|
|
return tensionA - tensionB;
|
|
|
|
});
|
|
|
|
|
|
|
|
const elementTree = [];
|
|
|
|
const string = '';
|
|
|
|
for (let i = 0; i < value.text.length; i++) {
|
|
|
|
const c = value.text[i];
|
|
|
|
const formats = value.formats[i];
|
|
|
|
const activeFormats = value.formats[i - 1] || [];
|
|
|
|
|
|
|
|
const removedFormats = activeFormats
|
|
|
|
.filter(a => !formats
|
|
|
|
.find(f => f.type === a.type
|
|
|
|
&& JSON.stringify(f) === JSON.stringify(a)));
|
|
|
|
const addedFormats = formats
|
|
|
|
.filter(f => !activeFormats
|
|
|
|
.find(a => a.type === f.type
|
|
|
|
&& JSON.stringify(a) === JSON.stringify(f)));
|
|
|
|
|
|
|
|
console.log(c);
|
2024-10-09 12:56:50 +00:00
|
|
|
for (let removedFormat of removedFormats) {
|
|
|
|
const tool = tools.find(tool => tool.name === removedFormat.type);
|
|
|
|
if (!tool) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
tool.
|
|
|
|
}
|
2023-01-02 18:45:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getActiveFormats = (richTextValue: IRichTextValue): IRichTextFormat[] =>
|
|
|
|
richTextValue.start !== null
|
|
|
|
? richTextValue.formats[richTextValue.start]
|
|
|
|
: [];
|
|
|
|
|
|
|
|
export const applyFormat = (
|
|
|
|
value: IRichTextValue,
|
|
|
|
format: IRichTextFormat,
|
|
|
|
startIndex?: number,
|
|
|
|
endIndex?: number,
|
|
|
|
): IRichTextValue => {
|
|
|
|
const start = defaultTo(defaultTo(startIndex, value.start), 0);
|
|
|
|
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...value,
|
|
|
|
formats: [
|
|
|
|
...value.formats.slice(0, start),
|
|
|
|
...value.formats.slice(start, end).map(letterFormatList => [
|
|
|
|
...letterFormatList.filter(letterFormat => letterFormat.type === format.type),
|
|
|
|
format,
|
|
|
|
]),
|
|
|
|
...value.formats.slice(end),
|
|
|
|
],
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const removeFormat = (
|
|
|
|
value: IRichTextValue,
|
|
|
|
format: IRichTextFormat,
|
|
|
|
startIndex?: number,
|
|
|
|
endIndex?: number,
|
|
|
|
): IRichTextValue => {
|
|
|
|
const start = defaultTo(defaultTo(startIndex, value.start), 0);
|
|
|
|
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...value,
|
|
|
|
formats: [
|
|
|
|
...value.formats.slice(0, start),
|
|
|
|
...value.formats.slice(start, end)
|
|
|
|
.map(letterFormatList => letterFormatList.filter(letterFormat => letterFormat.type === format.type)),
|
|
|
|
...value.formats.slice(end),
|
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export const toggleFormat = (
|
|
|
|
value: IRichTextValue,
|
|
|
|
format: IRichTextFormat,
|
|
|
|
): IRichTextValue => {
|
|
|
|
const activeFormats = getActiveFormats(value);
|
|
|
|
if (activeFormats.find(f => f.type === format.type)) {
|
|
|
|
return removeFormat(value, format);
|
|
|
|
}
|
|
|
|
|
|
|
|
return applyFormat(value, format);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const concat = (...richTextValues:IRichTextValue[]): IRichTextValue => richTextValues
|
|
|
|
.reduce((newValue, value) => ({
|
|
|
|
text: newValue.text + value.text,
|
|
|
|
formats: [
|
|
|
|
...newValue.formats,
|
|
|
|
...value.formats,
|
|
|
|
],
|
|
|
|
start: newValue.start !== null ? newValue.start : value.start,
|
|
|
|
end: value.end !== null ? value.end : newValue.end,
|
|
|
|
}), { text: '', formats: [], start: null, end: null });
|
|
|
|
|
|
|
|
export const join = (
|
|
|
|
richTextValues:IRichTextValue[],
|
|
|
|
separator?: string|IRichTextValue,
|
|
|
|
): IRichTextValue => richTextValues
|
|
|
|
.reduce((total, value) => concat(
|
|
|
|
total,
|
|
|
|
...(separator
|
|
|
|
? [typeof separator === 'string' ? createFromString(separator) : separator]
|
|
|
|
: []),
|
|
|
|
value,
|
|
|
|
), createFromString());
|
|
|
|
|
|
|
|
export const slice = (
|
|
|
|
value: IRichTextValue,
|
|
|
|
startIndex?: number,
|
|
|
|
endIndex?: number,
|
|
|
|
): IRichTextValue => {
|
|
|
|
const start = defaultTo(defaultTo(startIndex, value.start), 0);
|
|
|
|
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
|
|
|
|
|
|
|
return {
|
|
|
|
text: value.text.slice(start, end),
|
|
|
|
formats: value.formats.slice(start, end),
|
|
|
|
start: value.start !== null ? (value.start >= start ? value.start - start : null) : null,
|
|
|
|
end: value.end !== null ? (value.end <= end ? end - value.end : null) : null,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const insert = (
|
|
|
|
value: IRichTextValue,
|
|
|
|
valueToInsert: IRichTextValue|string,
|
|
|
|
startIndex?: number,
|
|
|
|
endIndex?: number,
|
|
|
|
) => {
|
|
|
|
const start = defaultTo(defaultTo(startIndex, value.start), value.text.length);
|
|
|
|
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
|
|
|
|
|
|
|
return concat(
|
|
|
|
slice(value, 0, start),
|
|
|
|
typeof valueToInsert === 'string' ? createFromString(valueToInsert) : valueToInsert,
|
|
|
|
slice(value, end, value.text.length),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const split = (
|
|
|
|
value: IRichTextValue,
|
|
|
|
valueToInsert: IRichTextValue|string,
|
|
|
|
startIndex?: number,
|
|
|
|
endIndex?: number,
|
|
|
|
) => {
|
|
|
|
const start = defaultTo(defaultTo(startIndex, value.start), value.text.length);
|
|
|
|
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
|
|
|
|
|
|
|
return concat(
|
|
|
|
slice(value, 0, start),
|
|
|
|
typeof valueToInsert === 'string' ? createFromString(valueToInsert) : valueToInsert,
|
|
|
|
slice(value, end, value.text.length),
|
|
|
|
);
|
|
|
|
}
|