thelounge/client/js/helpers/ircmessageparser/merge.ts

79 lines
2.5 KiB
TypeScript
Raw Normal View History

2019-11-16 18:24:03 +01:00
import anyIntersection from "./anyIntersection";
import fill from "./fill";
2022-05-15 00:18:06 +02:00
import {ChannelPart} from "./findChannels";
import {EmojiPart} from "./findEmoji";
import {NamePart} from "./findNames";
type TextPart = Part & {
text: string;
};
type Fragment = TextPart;
// Merge text part information within a styling fragment
2022-05-15 00:18:06 +02:00
function assign(textPart: Part, fragment: Fragment) {
const fragStart = fragment.start;
const start = Math.max(fragment.start, textPart.start);
const end = Math.min(fragment.end, textPart.end);
const text = fragment.text.slice(start - fragStart, end - fragStart);
return Object.assign({}, fragment, {start, end, text});
}
2022-05-15 00:18:06 +02:00
function sortParts(a: Part, b: Part) {
2018-04-19 18:00:46 +02:00
return a.start - b.start || b.end - a.end;
}
2022-05-15 00:18:06 +02:00
export type Part = {
start: number;
end: number;
fragments?: Fragment;
};
type MergedPart = TextPart | NamePart | EmojiPart | ChannelPart;
type MergedPartWithFragments = MergedPart & {fragments: Fragment[]};
2018-04-19 18:00:46 +02:00
// Merge the style fragments within the text parts, taking into account
// boundaries and text sections that have not matched to links or channels.
// For example, given a string "foobar" where "foo" and "bar" have been
// identified as parts (channels, links, etc.) and "fo", "ob" and "ar" have 3
// different styles, the first resulting part will contain fragments "fo" and
// "o", and the second resulting part will contain "b" and "ar". "o" and "b"
// fragments will contain duplicate styling attributes.
2022-05-15 00:18:06 +02:00
function merge(
textParts: MergedPart[],
styleFragments: Fragment[],
cleanText: string
): MergedPart[] {
// Remove overlapping parts
2022-05-15 00:18:06 +02:00
textParts = textParts.sort(sortParts).reduce<MergedPart[]>((prev, curr) => {
2019-07-17 11:33:59 +02:00
const intersection = prev.some((p) => anyIntersection(p, curr));
2018-04-19 18:00:46 +02:00
2019-07-17 11:33:59 +02:00
if (intersection) {
return prev;
}
2018-04-19 18:00:46 +02:00
2019-07-17 11:33:59 +02:00
return prev.concat([curr]);
}, []);
// Every section of the original text that has not been captured in a "part"
// is filled with "text" parts, dummy objects with start/end but no extra
// metadata.
2022-05-15 00:18:06 +02:00
const filled = fill(textParts, cleanText) as TextPart[];
const allParts: MergedPart[] = [...textParts, ...filled].sort(sortParts); // Sort all parts identified based on their position in the original text
// Distribute the style fragments within the text parts
return allParts.map((textPart) => {
2022-05-15 00:18:06 +02:00
// TODO: remove any type casting.
(textPart as any).fragments = styleFragments
.filter((fragment) => anyIntersection(textPart, fragment))
.map((fragment) => assign(textPart, fragment));
return textPart;
});
}
2019-11-16 18:24:03 +01:00
export default merge;