Files
crossdesk/apps/harmony/entry/src/main/ets/mobile/AnnouncementText.ets
T

66 lines
2.9 KiB
Plaintext

export interface AnnouncementSpan { text: string; url: string; }
// Same restricted plain-text/link grammar as Android AnnouncementText.java.
export function isAnnouncementURL(address: string): boolean {
return !/[\x00-\x20\x7f\\<>\"]/.test(address) &&
/^https?:\/\/(?:[^\/?#]*@)?(?:\[[0-9a-f:.]+\]|[^\/?#:@\[\]]+)(?::[0-9]+)?(?:[\/?#]|$)/i.test(address);
}
function hasScheme(text: string, start: number): boolean {
return /^https?:\/\//i.test(text.substring(start, start + 8));
}
function urlEnd(text: string, start: number): number {
let parentheses = 0, brackets = 0, end = start;
for (; end < text.length; end++) {
const c = text.charAt(end);
if (c.charCodeAt(0) <= 0x20 || c.charCodeAt(0) === 0x7f || c === '\\' || c === '`' ||
'<>\"\'{},。;:!?、()【】《》「」『』“”‘’'.includes(c)) break;
if (c === '(') parentheses++;
if (c === ')') { if (parentheses === 0) break; parentheses--; }
if (c === '[') brackets++;
if (c === ']') { if (brackets === 0) break; brackets--; }
}
return end;
}
export function announcementSpans(body: string): AnnouncementSpan[] {
const text = body.replace(/\r\n/g, '\n'), result: AnnouncementSpan[] = [];
let plain = '';
for (let position = 0; position < text.length;) {
let label = '', address = '', next = position;
if (text.charAt(position) === '[') {
const labelEnd = text.indexOf(']', position + 1), start = labelEnd + 2;
if (labelEnd > position + 1 && start < text.length && text.charAt(labelEnd + 1) === '(' &&
!/[\[\]\r\n]/.test(text.substring(position + 1, labelEnd)) && hasScheme(text, start)) {
const end = urlEnd(text, start), url = text.substring(start, end);
if (text.charAt(end) === ')' && isAnnouncementURL(url)) {
label = text.substring(position + 1, labelEnd); address = url; next = end + 1;
}
}
}
if (!address && hasScheme(text, position)) {
let end = urlEnd(text, position);
while (end > position && '.,;:!?'.includes(text.charAt(end - 1))) end--;
const url = text.substring(position, end);
if (isAnnouncementURL(url)) { label = address = url; next = end; }
}
if (address) {
if (plain) { result.push({ text: plain, url: '' }); plain = ''; }
result.push({ text: label, url: address }); position = next;
} else plain += text.charAt(position++);
}
if (plain) result.push({ text: plain, url: '' });
return result;
}
export function announcementByteLength(text: string): number {
let bytes = 0;
for (let i = 0; i < text.length; i++) {
const c = text.charCodeAt(i);
if (c < 0x80) bytes++;
else if (c < 0x800) bytes += 2;
else if (c >= 0xd800 && c <= 0xdbff && i + 1 < text.length &&
text.charCodeAt(i + 1) >= 0xdc00 && text.charCodeAt(i + 1) <= 0xdfff) { bytes += 4; i++; }
else bytes += c >= 0xd800 && c <= 0xdfff ? 1 : 3; // Java UTF-8 replaces lone surrogates with '?'.
}
return bytes;
}