2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
2022-04-16 23:40:22 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import LinkifyIt from 'linkify-it';
|
|
|
|
|
|
|
|
export function graphemeAndLinkAwareSlice(
|
|
|
|
str: string,
|
|
|
|
length: number,
|
|
|
|
buffer = 100
|
|
|
|
): {
|
|
|
|
hasReadMore: boolean;
|
|
|
|
text: string;
|
|
|
|
} {
|
|
|
|
if (str.length <= length + buffer) {
|
|
|
|
return { text: str, hasReadMore: false };
|
|
|
|
}
|
|
|
|
|
|
|
|
let text: string | undefined;
|
|
|
|
|
|
|
|
for (const { index } of new Intl.Segmenter().segment(str)) {
|
|
|
|
if (!text && index >= length) {
|
|
|
|
text = str.slice(0, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (text && index > length) {
|
|
|
|
text = expandToIncludeEntireLink(str, text);
|
|
|
|
|
|
|
|
return {
|
|
|
|
text,
|
|
|
|
hasReadMore: text.length < str.length,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
text: str,
|
|
|
|
hasReadMore: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const expandToIncludeEntireLink = (
|
|
|
|
original: string,
|
|
|
|
truncated: string
|
|
|
|
): string => {
|
|
|
|
const linksInText = new LinkifyIt().match(original);
|
|
|
|
|
|
|
|
if (!linksInText) {
|
|
|
|
return truncated;
|
|
|
|
}
|
|
|
|
|
|
|
|
const invalidTruncationRanges: Array<LinkRange> = linksInText.map(
|
|
|
|
({ index: startIndex, lastIndex }) => ({ startIndex, lastIndex })
|
|
|
|
);
|
|
|
|
|
|
|
|
const truncatedLink: Array<LinkRange> = invalidTruncationRanges.filter(
|
|
|
|
({ startIndex, lastIndex }) =>
|
|
|
|
startIndex < truncated.length && lastIndex > truncated.length
|
|
|
|
);
|
|
|
|
|
2022-12-12 23:35:32 +00:00
|
|
|
if (truncatedLink.length === 0) {
|
|
|
|
return truncated;
|
|
|
|
}
|
2022-04-16 23:40:22 +00:00
|
|
|
|
|
|
|
return original.slice(0, truncatedLink[0].lastIndex);
|
|
|
|
};
|
|
|
|
|
|
|
|
type LinkRange = {
|
|
|
|
startIndex: number;
|
|
|
|
lastIndex: number;
|
|
|
|
};
|