schlechtenburg/packages/core/lib/rich-text/values.tsx
2024-10-09 14:56:50 +02:00

262 lines
7 KiB
TypeScript

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);
for (let removedFormat of removedFormats) {
const tool = tools.find(tool => tool.name === removedFormat.type);
if (!tool) {
continue;
}
tool.
}
}
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),
);
}