mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-22 01:05:34 +03:00
It's micro-optimization week
Includes experimental replacing dangerouslySetInnerHTML with setting actual DOM
This commit is contained in:
parent
4d35c9d2c3
commit
65bd6fb1ef
7 changed files with 316 additions and 242 deletions
|
@ -23,10 +23,12 @@ const ctx = canvas.getContext('2d', {
|
||||||
});
|
});
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
const MISSING_IMAGE_PATH_REGEX = /missing\.png$/;
|
||||||
|
|
||||||
function Avatar({ url, size, alt = '', squircle, ...props }) {
|
function Avatar({ url, size, alt = '', squircle, ...props }) {
|
||||||
size = SIZES[size] || size || SIZES.m;
|
size = SIZES[size] || size || SIZES.m;
|
||||||
const avatarRef = useRef();
|
const avatarRef = useRef();
|
||||||
const isMissing = /missing\.png$/.test(url);
|
const isMissing = MISSING_IMAGE_PATH_REGEX.test(url);
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
ref={avatarRef}
|
ref={avatarRef}
|
||||||
|
|
|
@ -1,29 +1,40 @@
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
|
|
||||||
|
import mem from '../utils/mem';
|
||||||
|
|
||||||
import CustomEmoji from './custom-emoji';
|
import CustomEmoji from './custom-emoji';
|
||||||
|
|
||||||
|
const shortcodesRegexp = mem((shortcodes) => {
|
||||||
|
return new RegExp(`:(${shortcodes.join('|')}):`, 'g');
|
||||||
|
});
|
||||||
|
|
||||||
function EmojiText({ text, emojis }) {
|
function EmojiText({ text, emojis }) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
if (!emojis?.length) return text;
|
if (!emojis?.length) return text;
|
||||||
if (text.indexOf(':') === -1) return text;
|
if (text.indexOf(':') === -1) return text;
|
||||||
const regex = new RegExp(
|
// const regex = new RegExp(
|
||||||
`:(${emojis.map((e) => e.shortcode).join('|')}):`,
|
// `:(${emojis.map((e) => e.shortcode).join('|')}):`,
|
||||||
'g',
|
// 'g',
|
||||||
);
|
// );
|
||||||
|
const regex = shortcodesRegexp(emojis.map((e) => e.shortcode));
|
||||||
const elements = text.split(regex).map((word) => {
|
const elements = text.split(regex).map((word) => {
|
||||||
const emoji = emojis.find((e) => e.shortcode === word);
|
const emoji = emojis.find((e) => e.shortcode === word);
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
const { url, staticUrl } = emoji;
|
const { url, staticUrl } = emoji;
|
||||||
return <CustomEmoji staticUrl={staticUrl} alt={word} url={url} />;
|
return (
|
||||||
|
<CustomEmoji staticUrl={staticUrl} alt={word} url={url} key={word} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return word;
|
return word;
|
||||||
});
|
});
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(
|
export default mem(EmojiText);
|
||||||
EmojiText,
|
|
||||||
(oldProps, newProps) =>
|
// export default memo(
|
||||||
oldProps.text === newProps.text &&
|
// EmojiText,
|
||||||
oldProps.emojis?.length === newProps.emojis?.length,
|
// (oldProps, newProps) =>
|
||||||
);
|
// oldProps.text === newProps.text &&
|
||||||
|
// oldProps.emojis?.length === newProps.emojis?.length,
|
||||||
|
// );
|
||||||
|
|
|
@ -21,6 +21,11 @@ const nameCollator = mem((locale) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ACCT_REGEX = /([^@]+)(@.+)/i;
|
||||||
|
const SHORTCODES_REGEX = /(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/g;
|
||||||
|
const SPACES_REGEX = /\s+/g;
|
||||||
|
const NON_ALPHA_NUMERIC_REGEX = /[^a-z0-9@\.]/gi;
|
||||||
|
|
||||||
function NameText({
|
function NameText({
|
||||||
account,
|
account,
|
||||||
instance,
|
instance,
|
||||||
|
@ -42,17 +47,17 @@ function NameText({
|
||||||
bot,
|
bot,
|
||||||
username,
|
username,
|
||||||
} = account;
|
} = account;
|
||||||
const [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
|
const [_, acct1, acct2] = acct.match(ACCT_REGEX) || [, acct];
|
||||||
|
|
||||||
if (!instance) instance = api().instance;
|
if (!instance) instance = api().instance;
|
||||||
|
|
||||||
const trimmedUsername = username.toLowerCase().trim();
|
const trimmedUsername = username.toLowerCase().trim();
|
||||||
const trimmedDisplayName = (displayName || '').toLowerCase().trim();
|
const trimmedDisplayName = (displayName || '').toLowerCase().trim();
|
||||||
const shortenedDisplayName = trimmedDisplayName
|
const shortenedDisplayName = trimmedDisplayName
|
||||||
.replace(/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/g, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1
|
.replace(SHORTCODES_REGEX, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1
|
||||||
.replace(/\s+/g, ''); // E.g. "My name" === "myname"
|
.replace(SPACES_REGEX, ''); // E.g. "My name" === "myname"
|
||||||
const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(
|
const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(
|
||||||
/[^a-z0-9@\.]/gi,
|
NON_ALPHA_NUMERIC_REGEX,
|
||||||
'',
|
'',
|
||||||
); // Remove non-alphanumeric characters
|
); // Remove non-alphanumeric characters
|
||||||
|
|
||||||
|
@ -130,9 +135,11 @@ function NameText({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(NameText, (oldProps, newProps) => {
|
export default mem(NameText);
|
||||||
// Only care about account.id, the other props usually don't change
|
|
||||||
const { account } = oldProps;
|
// export default memo(NameText, (oldProps, newProps) => {
|
||||||
const { account: newAccount } = newProps;
|
// // Only care about account.id, the other props usually don't change
|
||||||
return account?.acct === newAccount?.acct;
|
// const { account } = oldProps;
|
||||||
});
|
// const { account: newAccount } = newProps;
|
||||||
|
// return account?.acct === newAccount?.acct;
|
||||||
|
// });
|
||||||
|
|
|
@ -124,11 +124,31 @@ function getPostText(status) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostContent = memo(
|
const HTTP_REGEX = /^http/i;
|
||||||
|
const PostContent =
|
||||||
|
/*memo(*/
|
||||||
({ post, instance, previewMode }) => {
|
({ post, instance, previewMode }) => {
|
||||||
const { content, emojis, language, mentions, url } = post;
|
const { content, emojis, language, mentions, url } = post;
|
||||||
|
|
||||||
|
const divRef = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
if (!divRef.current) return;
|
||||||
|
const dom = enhanceContent(content, {
|
||||||
|
emojis,
|
||||||
|
returnDOM: true,
|
||||||
|
});
|
||||||
|
// Remove target="_blank" from links
|
||||||
|
for (const a of dom.querySelectorAll('a.u-url[target="_blank"]')) {
|
||||||
|
if (!HTTP_REGEX.test(a.innerText.trim())) {
|
||||||
|
a.removeAttribute('target');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
divRef.current.replaceChildren(dom.cloneNode(true));
|
||||||
|
}, [content, emojis.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={divRef}
|
||||||
lang={language}
|
lang={language}
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="inner-content"
|
class="inner-content"
|
||||||
|
@ -138,28 +158,28 @@ const PostContent = memo(
|
||||||
previewMode,
|
previewMode,
|
||||||
statusURL: url,
|
statusURL: url,
|
||||||
})}
|
})}
|
||||||
dangerouslySetInnerHTML={{
|
// dangerouslySetInnerHTML={{
|
||||||
__html: enhanceContent(content, {
|
// __html: enhanceContent(content, {
|
||||||
emojis,
|
// emojis,
|
||||||
postEnhanceDOM: (dom) => {
|
// postEnhanceDOM: (dom) => {
|
||||||
// Remove target="_blank" from links
|
// // Remove target="_blank" from links
|
||||||
dom.querySelectorAll('a.u-url[target="_blank"]').forEach((a) => {
|
// dom.querySelectorAll('a.u-url[target="_blank"]').forEach((a) => {
|
||||||
if (!/http/i.test(a.innerText.trim())) {
|
// if (!/http/i.test(a.innerText.trim())) {
|
||||||
a.removeAttribute('target');
|
// a.removeAttribute('target');
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
}),
|
// }),
|
||||||
}}
|
// }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
}; /*,
|
||||||
(oldProps, newProps) => {
|
(oldProps, newProps) => {
|
||||||
const { post: oldPost } = oldProps;
|
const { post: oldPost } = oldProps;
|
||||||
const { post: newPost } = newProps;
|
const { post: newPost } = newProps;
|
||||||
return oldPost.content === newPost.content;
|
return oldPost.content === newPost.content;
|
||||||
},
|
},
|
||||||
);
|
);*/
|
||||||
|
|
||||||
const SIZE_CLASS = {
|
const SIZE_CLASS = {
|
||||||
s: 'small',
|
s: 'small',
|
||||||
|
|
288
src/locales/en.po
generated
288
src/locales/en.po
generated
|
@ -13,40 +13,40 @@ msgstr ""
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Plural-Forms: \n"
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
#: src/components/account-block.jsx:133
|
#: src/components/account-block.jsx:135
|
||||||
msgid "Locked"
|
msgid "Locked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:139
|
#: src/components/account-block.jsx:141
|
||||||
msgid "Posts: {0}"
|
msgid "Posts: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:144
|
#: src/components/account-block.jsx:146
|
||||||
msgid "Last posted: {0}"
|
msgid "Last posted: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:159
|
#: src/components/account-block.jsx:161
|
||||||
#: src/components/account-info.jsx:634
|
#: src/components/account-info.jsx:634
|
||||||
msgid "Automated"
|
msgid "Automated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:166
|
#: src/components/account-block.jsx:168
|
||||||
#: src/components/account-info.jsx:639
|
#: src/components/account-info.jsx:639
|
||||||
#: src/components/status.jsx:440
|
#: src/components/status.jsx:460
|
||||||
#: src/pages/catchup.jsx:1471
|
#: src/pages/catchup.jsx:1471
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:176
|
#: src/components/account-block.jsx:178
|
||||||
msgid "Mutual"
|
msgid "Mutual"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:180
|
#: src/components/account-block.jsx:182
|
||||||
#: src/components/account-info.jsx:1677
|
#: src/components/account-info.jsx:1677
|
||||||
msgid "Requested"
|
msgid "Requested"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:184
|
#: src/components/account-block.jsx:186
|
||||||
#: src/components/account-info.jsx:416
|
#: src/components/account-info.jsx:416
|
||||||
#: src/components/account-info.jsx:742
|
#: src/components/account-info.jsx:742
|
||||||
#: src/components/account-info.jsx:756
|
#: src/components/account-info.jsx:756
|
||||||
|
@ -58,21 +58,21 @@ msgstr ""
|
||||||
msgid "Following"
|
msgid "Following"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:188
|
#: src/components/account-block.jsx:190
|
||||||
#: src/components/account-info.jsx:1059
|
#: src/components/account-info.jsx:1059
|
||||||
msgid "Follows you"
|
msgid "Follows you"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:196
|
#: src/components/account-block.jsx:198
|
||||||
msgid "{followersCount, plural, one {# follower} other {# followers}}"
|
msgid "{followersCount, plural, one {# follower} other {# followers}}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:205
|
#: src/components/account-block.jsx:207
|
||||||
#: src/components/account-info.jsx:680
|
#: src/components/account-info.jsx:680
|
||||||
msgid "Verified"
|
msgid "Verified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-block.jsx:220
|
#: src/components/account-block.jsx:222
|
||||||
#: src/components/account-info.jsx:777
|
#: src/components/account-info.jsx:777
|
||||||
msgid "Joined <0>{0}</0>"
|
msgid "Joined <0>{0}</0>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -108,11 +108,11 @@ msgstr ""
|
||||||
#: src/components/compose.jsx:2463
|
#: src/components/compose.jsx:2463
|
||||||
#: src/components/media-alt-modal.jsx:45
|
#: src/components/media-alt-modal.jsx:45
|
||||||
#: src/components/media-modal.jsx:283
|
#: src/components/media-modal.jsx:283
|
||||||
#: src/components/status.jsx:1636
|
#: src/components/status.jsx:1656
|
||||||
#: src/components/status.jsx:1653
|
#: src/components/status.jsx:1673
|
||||||
#: src/components/status.jsx:1777
|
#: src/components/status.jsx:1797
|
||||||
#: src/components/status.jsx:2375
|
#: src/components/status.jsx:2395
|
||||||
#: src/components/status.jsx:2378
|
#: src/components/status.jsx:2398
|
||||||
#: src/pages/account-statuses.jsx:528
|
#: src/pages/account-statuses.jsx:528
|
||||||
#: src/pages/accounts.jsx:109
|
#: src/pages/accounts.jsx:109
|
||||||
#: src/pages/hashtag.jsx:199
|
#: src/pages/hashtag.jsx:199
|
||||||
|
@ -181,7 +181,7 @@ msgid "Original"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-info.jsx:859
|
#: src/components/account-info.jsx:859
|
||||||
#: src/components/status.jsx:2166
|
#: src/components/status.jsx:2186
|
||||||
#: src/pages/catchup.jsx:71
|
#: src/pages/catchup.jsx:71
|
||||||
#: src/pages/catchup.jsx:1445
|
#: src/pages/catchup.jsx:1445
|
||||||
#: src/pages/catchup.jsx:2056
|
#: src/pages/catchup.jsx:2056
|
||||||
|
@ -277,30 +277,30 @@ msgid "Add/Remove from Lists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-info.jsx:1299
|
#: src/components/account-info.jsx:1299
|
||||||
#: src/components/status.jsx:1079
|
#: src/components/status.jsx:1099
|
||||||
msgid "Link copied"
|
msgid "Link copied"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-info.jsx:1302
|
#: src/components/account-info.jsx:1302
|
||||||
#: src/components/status.jsx:1082
|
#: src/components/status.jsx:1102
|
||||||
msgid "Unable to copy link"
|
msgid "Unable to copy link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-info.jsx:1308
|
#: src/components/account-info.jsx:1308
|
||||||
#: src/components/shortcuts-settings.jsx:1056
|
#: src/components/shortcuts-settings.jsx:1056
|
||||||
#: src/components/status.jsx:1088
|
#: src/components/status.jsx:1108
|
||||||
#: src/components/status.jsx:3150
|
#: src/components/status.jsx:3170
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-info.jsx:1323
|
#: src/components/account-info.jsx:1323
|
||||||
#: src/components/shortcuts-settings.jsx:1074
|
#: src/components/shortcuts-settings.jsx:1074
|
||||||
#: src/components/status.jsx:1104
|
#: src/components/status.jsx:1124
|
||||||
msgid "Sharing doesn't seem to work."
|
msgid "Sharing doesn't seem to work."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/account-info.jsx:1329
|
#: src/components/account-info.jsx:1329
|
||||||
#: src/components/status.jsx:1110
|
#: src/components/status.jsx:1130
|
||||||
msgid "Share…"
|
msgid "Share…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -417,9 +417,9 @@ msgstr ""
|
||||||
#: src/components/shortcuts-settings.jsx:227
|
#: src/components/shortcuts-settings.jsx:227
|
||||||
#: src/components/shortcuts-settings.jsx:580
|
#: src/components/shortcuts-settings.jsx:580
|
||||||
#: src/components/shortcuts-settings.jsx:780
|
#: src/components/shortcuts-settings.jsx:780
|
||||||
#: src/components/status.jsx:2875
|
#: src/components/status.jsx:2895
|
||||||
#: src/components/status.jsx:3114
|
#: src/components/status.jsx:3134
|
||||||
#: src/components/status.jsx:3612
|
#: src/components/status.jsx:3632
|
||||||
#: src/pages/accounts.jsx:36
|
#: src/pages/accounts.jsx:36
|
||||||
#: src/pages/catchup.jsx:1581
|
#: src/pages/catchup.jsx:1581
|
||||||
#: src/pages/filters.jsx:224
|
#: src/pages/filters.jsx:224
|
||||||
|
@ -605,7 +605,7 @@ msgid "Attachment #{i} failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/compose.jsx:1118
|
#: src/components/compose.jsx:1118
|
||||||
#: src/components/status.jsx:1962
|
#: src/components/status.jsx:1982
|
||||||
#: src/components/timeline.jsx:984
|
#: src/components/timeline.jsx:984
|
||||||
msgid "Content warning"
|
msgid "Content warning"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -641,7 +641,7 @@ msgstr ""
|
||||||
|
|
||||||
#: src/components/compose.jsx:1185
|
#: src/components/compose.jsx:1185
|
||||||
#: src/components/status.jsx:96
|
#: src/components/status.jsx:96
|
||||||
#: src/components/status.jsx:1840
|
#: src/components/status.jsx:1860
|
||||||
msgid "Private mention"
|
msgid "Private mention"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -671,10 +671,10 @@ msgstr ""
|
||||||
|
|
||||||
#: src/components/compose.jsx:1479
|
#: src/components/compose.jsx:1479
|
||||||
#: src/components/keyboard-shortcuts-help.jsx:143
|
#: src/components/keyboard-shortcuts-help.jsx:143
|
||||||
#: src/components/status.jsx:831
|
#: src/components/status.jsx:851
|
||||||
#: src/components/status.jsx:1616
|
#: src/components/status.jsx:1636
|
||||||
#: src/components/status.jsx:1617
|
#: src/components/status.jsx:1637
|
||||||
#: src/components/status.jsx:2271
|
#: src/components/status.jsx:2291
|
||||||
msgid "Reply"
|
msgid "Reply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -889,7 +889,7 @@ msgstr ""
|
||||||
|
|
||||||
#: src/components/drafts.jsx:127
|
#: src/components/drafts.jsx:127
|
||||||
#: src/components/list-add-edit.jsx:183
|
#: src/components/list-add-edit.jsx:183
|
||||||
#: src/components/status.jsx:1251
|
#: src/components/status.jsx:1271
|
||||||
#: src/pages/filters.jsx:587
|
#: src/pages/filters.jsx:587
|
||||||
msgid "Delete…"
|
msgid "Delete…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -1089,10 +1089,10 @@ msgid "<0>l</0> or <1>f</1>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/keyboard-shortcuts-help.jsx:164
|
#: src/components/keyboard-shortcuts-help.jsx:164
|
||||||
#: src/components/status.jsx:839
|
#: src/components/status.jsx:859
|
||||||
#: src/components/status.jsx:2297
|
#: src/components/status.jsx:2317
|
||||||
#: src/components/status.jsx:2329
|
#: src/components/status.jsx:2349
|
||||||
#: src/components/status.jsx:2330
|
#: src/components/status.jsx:2350
|
||||||
msgid "Boost"
|
msgid "Boost"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1101,9 +1101,9 @@ msgid "<0>Shift</0> + <1>b</1>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/keyboard-shortcuts-help.jsx:172
|
#: src/components/keyboard-shortcuts-help.jsx:172
|
||||||
#: src/components/status.jsx:924
|
#: src/components/status.jsx:944
|
||||||
#: src/components/status.jsx:2354
|
#: src/components/status.jsx:2374
|
||||||
#: src/components/status.jsx:2355
|
#: src/components/status.jsx:2375
|
||||||
msgid "Bookmark"
|
msgid "Bookmark"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1162,15 +1162,15 @@ msgid "Media description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/media-alt-modal.jsx:57
|
#: src/components/media-alt-modal.jsx:57
|
||||||
#: src/components/status.jsx:968
|
#: src/components/status.jsx:988
|
||||||
#: src/components/status.jsx:995
|
#: src/components/status.jsx:1015
|
||||||
#: src/components/translation-block.jsx:195
|
#: src/components/translation-block.jsx:195
|
||||||
msgid "Translate"
|
msgid "Translate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/media-alt-modal.jsx:68
|
#: src/components/media-alt-modal.jsx:68
|
||||||
#: src/components/status.jsx:982
|
#: src/components/status.jsx:1002
|
||||||
#: src/components/status.jsx:1009
|
#: src/components/status.jsx:1029
|
||||||
msgid "Speak"
|
msgid "Speak"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1207,9 +1207,9 @@ msgid "Filtered: {filterTitleStr}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/media-post.jsx:133
|
#: src/components/media-post.jsx:133
|
||||||
#: src/components/status.jsx:3442
|
#: src/components/status.jsx:3462
|
||||||
#: src/components/status.jsx:3538
|
#: src/components/status.jsx:3558
|
||||||
#: src/components/status.jsx:3616
|
#: src/components/status.jsx:3636
|
||||||
#: src/components/timeline.jsx:973
|
#: src/components/timeline.jsx:973
|
||||||
#: src/pages/catchup.jsx:75
|
#: src/pages/catchup.jsx:75
|
||||||
#: src/pages/catchup.jsx:1876
|
#: src/pages/catchup.jsx:1876
|
||||||
|
@ -1489,8 +1489,8 @@ msgid "[Unknown notification type: {type}]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/notification.jsx:433
|
#: src/components/notification.jsx:433
|
||||||
#: src/components/status.jsx:938
|
#: src/components/status.jsx:958
|
||||||
#: src/components/status.jsx:948
|
#: src/components/status.jsx:968
|
||||||
msgid "Boosted/Liked by…"
|
msgid "Boosted/Liked by…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1512,7 +1512,7 @@ msgid "Learn more <0/>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/notification.jsx:745
|
#: src/components/notification.jsx:745
|
||||||
#: src/components/status.jsx:190
|
#: src/components/status.jsx:210
|
||||||
msgid "Read more →"
|
msgid "Read more →"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1805,7 +1805,7 @@ msgid "Move down"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/shortcuts-settings.jsx:376
|
#: src/components/shortcuts-settings.jsx:376
|
||||||
#: src/components/status.jsx:1216
|
#: src/components/status.jsx:1236
|
||||||
#: src/pages/list.jsx:170
|
#: src/pages/list.jsx:170
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -2003,297 +2003,297 @@ msgstr ""
|
||||||
msgid "Import/export settings from/to instance server (Very experimental)"
|
msgid "Import/export settings from/to instance server (Very experimental)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:464
|
#: src/components/status.jsx:484
|
||||||
msgid "<0/> <1>boosted</1>"
|
msgid "<0/> <1>boosted</1>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:563
|
#: src/components/status.jsx:583
|
||||||
msgid "Sorry, your current logged-in instance can't interact with this post from another instance."
|
msgid "Sorry, your current logged-in instance can't interact with this post from another instance."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:716
|
#: src/components/status.jsx:736
|
||||||
msgid "Unliked @{0}'s post"
|
msgid "Unliked @{0}'s post"
|
||||||
msgstr "Unliked @{0}'s post"
|
msgstr "Unliked @{0}'s post"
|
||||||
|
|
||||||
#: src/components/status.jsx:717
|
#: src/components/status.jsx:737
|
||||||
msgid "Liked @{0}'s post"
|
msgid "Liked @{0}'s post"
|
||||||
msgstr "Liked @{0}'s post"
|
msgstr "Liked @{0}'s post"
|
||||||
|
|
||||||
#: src/components/status.jsx:756
|
#: src/components/status.jsx:776
|
||||||
msgid "Unbookmarked @{0}'s post"
|
msgid "Unbookmarked @{0}'s post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:757
|
#: src/components/status.jsx:777
|
||||||
msgid "Bookmarked @{0}'s post"
|
msgid "Bookmarked @{0}'s post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:839
|
#: src/components/status.jsx:859
|
||||||
#: src/components/status.jsx:901
|
#: src/components/status.jsx:921
|
||||||
#: src/components/status.jsx:2297
|
#: src/components/status.jsx:2317
|
||||||
#: src/components/status.jsx:2329
|
#: src/components/status.jsx:2349
|
||||||
msgid "Unboost"
|
msgid "Unboost"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:855
|
#: src/components/status.jsx:875
|
||||||
#: src/components/status.jsx:2312
|
#: src/components/status.jsx:2332
|
||||||
msgid "Quote"
|
msgid "Quote"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:863
|
#: src/components/status.jsx:883
|
||||||
#: src/components/status.jsx:2321
|
#: src/components/status.jsx:2341
|
||||||
msgid "Some media have no descriptions."
|
msgid "Some media have no descriptions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:870
|
#: src/components/status.jsx:890
|
||||||
msgid "Old post (<0>{0}</0>)"
|
msgid "Old post (<0>{0}</0>)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:889
|
#: src/components/status.jsx:909
|
||||||
#: src/components/status.jsx:1341
|
#: src/components/status.jsx:1361
|
||||||
msgid "Unboosted @{0}'s post"
|
msgid "Unboosted @{0}'s post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:890
|
#: src/components/status.jsx:910
|
||||||
#: src/components/status.jsx:1342
|
#: src/components/status.jsx:1362
|
||||||
msgid "Boosted @{0}'s post"
|
msgid "Boosted @{0}'s post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:902
|
#: src/components/status.jsx:922
|
||||||
msgid "Boost…"
|
msgid "Boost…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:914
|
#: src/components/status.jsx:934
|
||||||
#: src/components/status.jsx:1626
|
#: src/components/status.jsx:1646
|
||||||
#: src/components/status.jsx:2342
|
#: src/components/status.jsx:2362
|
||||||
msgid "Unlike"
|
msgid "Unlike"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:915
|
#: src/components/status.jsx:935
|
||||||
#: src/components/status.jsx:1626
|
#: src/components/status.jsx:1646
|
||||||
#: src/components/status.jsx:1627
|
#: src/components/status.jsx:1647
|
||||||
#: src/components/status.jsx:2342
|
#: src/components/status.jsx:2362
|
||||||
#: src/components/status.jsx:2343
|
#: src/components/status.jsx:2363
|
||||||
msgid "Like"
|
msgid "Like"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:924
|
#: src/components/status.jsx:944
|
||||||
#: src/components/status.jsx:2354
|
#: src/components/status.jsx:2374
|
||||||
msgid "Unbookmark"
|
msgid "Unbookmark"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1032
|
#: src/components/status.jsx:1052
|
||||||
msgid "View post by <0>@{0}</0>"
|
msgid "View post by <0>@{0}</0>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1053
|
#: src/components/status.jsx:1073
|
||||||
msgid "Show Edit History"
|
msgid "Show Edit History"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1056
|
#: src/components/status.jsx:1076
|
||||||
msgid "Edited: {editedDateText}"
|
msgid "Edited: {editedDateText}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1123
|
#: src/components/status.jsx:1143
|
||||||
#: src/components/status.jsx:3119
|
#: src/components/status.jsx:3139
|
||||||
msgid "Embed post"
|
msgid "Embed post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1137
|
#: src/components/status.jsx:1157
|
||||||
msgid "Conversation unmuted"
|
msgid "Conversation unmuted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1137
|
#: src/components/status.jsx:1157
|
||||||
msgid "Conversation muted"
|
msgid "Conversation muted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1143
|
#: src/components/status.jsx:1163
|
||||||
msgid "Unable to unmute conversation"
|
msgid "Unable to unmute conversation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1144
|
#: src/components/status.jsx:1164
|
||||||
msgid "Unable to mute conversation"
|
msgid "Unable to mute conversation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1153
|
#: src/components/status.jsx:1173
|
||||||
msgid "Unmute conversation"
|
msgid "Unmute conversation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1160
|
#: src/components/status.jsx:1180
|
||||||
msgid "Mute conversation"
|
msgid "Mute conversation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1176
|
#: src/components/status.jsx:1196
|
||||||
msgid "Post unpinned from profile"
|
msgid "Post unpinned from profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1177
|
#: src/components/status.jsx:1197
|
||||||
msgid "Post pinned to profile"
|
msgid "Post pinned to profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1182
|
#: src/components/status.jsx:1202
|
||||||
msgid "Unable to unpin post"
|
msgid "Unable to unpin post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1182
|
#: src/components/status.jsx:1202
|
||||||
msgid "Unable to pin post"
|
msgid "Unable to pin post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1191
|
#: src/components/status.jsx:1211
|
||||||
msgid "Unpin from profile"
|
msgid "Unpin from profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1198
|
#: src/components/status.jsx:1218
|
||||||
msgid "Pin to profile"
|
msgid "Pin to profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1227
|
#: src/components/status.jsx:1247
|
||||||
msgid "Delete this post?"
|
msgid "Delete this post?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1240
|
#: src/components/status.jsx:1260
|
||||||
msgid "Post deleted"
|
msgid "Post deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1243
|
#: src/components/status.jsx:1263
|
||||||
msgid "Unable to delete post"
|
msgid "Unable to delete post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1271
|
#: src/components/status.jsx:1291
|
||||||
msgid "Report post…"
|
msgid "Report post…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1627
|
#: src/components/status.jsx:1647
|
||||||
#: src/components/status.jsx:1663
|
#: src/components/status.jsx:1683
|
||||||
#: src/components/status.jsx:2343
|
#: src/components/status.jsx:2363
|
||||||
msgid "Liked"
|
msgid "Liked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1660
|
#: src/components/status.jsx:1680
|
||||||
#: src/components/status.jsx:2330
|
#: src/components/status.jsx:2350
|
||||||
msgid "Boosted"
|
msgid "Boosted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1670
|
#: src/components/status.jsx:1690
|
||||||
#: src/components/status.jsx:2355
|
#: src/components/status.jsx:2375
|
||||||
msgid "Bookmarked"
|
msgid "Bookmarked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1674
|
#: src/components/status.jsx:1694
|
||||||
msgid "Pinned"
|
msgid "Pinned"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1719
|
#: src/components/status.jsx:1739
|
||||||
#: src/components/status.jsx:2174
|
#: src/components/status.jsx:2194
|
||||||
msgid "Deleted"
|
msgid "Deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1760
|
#: src/components/status.jsx:1780
|
||||||
msgid "{repliesCount, plural, one {# reply} other {# replies}}"
|
msgid "{repliesCount, plural, one {# reply} other {# replies}}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1849
|
#: src/components/status.jsx:1869
|
||||||
msgid "Thread{0}"
|
msgid "Thread{0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1925
|
#: src/components/status.jsx:1945
|
||||||
#: src/components/status.jsx:1987
|
#: src/components/status.jsx:2007
|
||||||
#: src/components/status.jsx:2072
|
#: src/components/status.jsx:2092
|
||||||
msgid "Show less"
|
msgid "Show less"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:1925
|
#: src/components/status.jsx:1945
|
||||||
#: src/components/status.jsx:1987
|
#: src/components/status.jsx:2007
|
||||||
msgid "Show content"
|
msgid "Show content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:2072
|
#: src/components/status.jsx:2092
|
||||||
msgid "Show media"
|
msgid "Show media"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:2195
|
#: src/components/status.jsx:2215
|
||||||
msgid "Edited"
|
msgid "Edited"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:2272
|
#: src/components/status.jsx:2292
|
||||||
msgid "Comments"
|
msgid "Comments"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. More from [Author]
|
#. More from [Author]
|
||||||
#: src/components/status.jsx:2581
|
#: src/components/status.jsx:2601
|
||||||
msgid "More from <0/>"
|
msgid "More from <0/>"
|
||||||
msgstr "More from <0/>"
|
msgstr "More from <0/>"
|
||||||
|
|
||||||
#: src/components/status.jsx:2880
|
#: src/components/status.jsx:2900
|
||||||
msgid "Edit History"
|
msgid "Edit History"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:2884
|
#: src/components/status.jsx:2904
|
||||||
msgid "Failed to load history"
|
msgid "Failed to load history"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:2889
|
#: src/components/status.jsx:2909
|
||||||
msgid "Loading…"
|
msgid "Loading…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3124
|
#: src/components/status.jsx:3144
|
||||||
msgid "HTML Code"
|
msgid "HTML Code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3141
|
#: src/components/status.jsx:3161
|
||||||
msgid "HTML code copied"
|
msgid "HTML code copied"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3144
|
#: src/components/status.jsx:3164
|
||||||
msgid "Unable to copy HTML code"
|
msgid "Unable to copy HTML code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3156
|
#: src/components/status.jsx:3176
|
||||||
msgid "Media attachments:"
|
msgid "Media attachments:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3178
|
#: src/components/status.jsx:3198
|
||||||
msgid "Account Emojis:"
|
msgid "Account Emojis:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3209
|
#: src/components/status.jsx:3229
|
||||||
#: src/components/status.jsx:3254
|
#: src/components/status.jsx:3274
|
||||||
msgid "static URL"
|
msgid "static URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3223
|
#: src/components/status.jsx:3243
|
||||||
msgid "Emojis:"
|
msgid "Emojis:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3268
|
#: src/components/status.jsx:3288
|
||||||
msgid "Notes:"
|
msgid "Notes:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3272
|
#: src/components/status.jsx:3292
|
||||||
msgid "This is static, unstyled and scriptless. You may need to apply your own styles and edit as needed."
|
msgid "This is static, unstyled and scriptless. You may need to apply your own styles and edit as needed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3278
|
#: src/components/status.jsx:3298
|
||||||
msgid "Polls are not interactive, becomes a list with vote counts."
|
msgid "Polls are not interactive, becomes a list with vote counts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3283
|
#: src/components/status.jsx:3303
|
||||||
msgid "Media attachments can be images, videos, audios or any file types."
|
msgid "Media attachments can be images, videos, audios or any file types."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3289
|
#: src/components/status.jsx:3309
|
||||||
msgid "Post could be edited or deleted later."
|
msgid "Post could be edited or deleted later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3295
|
#: src/components/status.jsx:3315
|
||||||
msgid "Preview"
|
msgid "Preview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3304
|
#: src/components/status.jsx:3324
|
||||||
msgid "Note: This preview is lightly styled."
|
msgid "Note: This preview is lightly styled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. [Name] [Visibility icon] boosted
|
#. [Name] [Visibility icon] boosted
|
||||||
#: src/components/status.jsx:3546
|
#: src/components/status.jsx:3566
|
||||||
msgid "<0/> <1/> boosted"
|
msgid "<0/> <1/> boosted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -4,53 +4,85 @@ import mem from './mem';
|
||||||
const fauxDiv = document.createElement('div');
|
const fauxDiv = document.createElement('div');
|
||||||
const whitelistLinkClasses = ['u-url', 'mention', 'hashtag'];
|
const whitelistLinkClasses = ['u-url', 'mention', 'hashtag'];
|
||||||
|
|
||||||
|
const HTML_CHARS_REGEX = /[&<>]/g;
|
||||||
|
function escapeHTML(html) {
|
||||||
|
return html.replace(
|
||||||
|
HTML_CHARS_REGEX,
|
||||||
|
(c) =>
|
||||||
|
({
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
}[c]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LINK_REGEX = /<a/i;
|
||||||
|
const HTTP_LINK_REGEX = /^https?:\/\//i;
|
||||||
|
const MENTION_REGEX = /^@[^@]+(@[^@]+)?$/g;
|
||||||
|
const HASHTAG_REGEX = /^#[^#]+$/g;
|
||||||
|
const CODE_BLOCK_REGEX = /^```[^]+```$/g;
|
||||||
|
const CODE_BLOCK_START_REGEX = /^```/g;
|
||||||
|
const CODE_BLOCK_END_REGEX = /```$/g;
|
||||||
|
const INLINE_CODE_REGEX = /`[^`]+`/g;
|
||||||
|
const TWITTER_DOMAIN_REGEX = /(twitter|x)\.com/i;
|
||||||
|
const TWITTER_MENTION_REGEX = /@[a-zA-Z0-9_]+@(twitter|x)\.com/g;
|
||||||
|
const TWITTER_MENTION_CAPTURE_REGEX = /(@([a-zA-Z0-9_]+)@(twitter|x)\.com)/g;
|
||||||
|
|
||||||
|
function createDOM(html, isDocumentFragment) {
|
||||||
|
const tpl = document.createElement('template');
|
||||||
|
tpl.innerHTML = html;
|
||||||
|
return isDocumentFragment ? tpl.content : tpl;
|
||||||
|
}
|
||||||
|
|
||||||
function _enhanceContent(content, opts = {}) {
|
function _enhanceContent(content, opts = {}) {
|
||||||
const { emojis, postEnhanceDOM = () => {} } = opts;
|
const { emojis, returnDOM, postEnhanceDOM = () => {} } = opts;
|
||||||
let enhancedContent = content;
|
let enhancedContent = content;
|
||||||
const dom = document.createElement('div');
|
// const dom = document.createElement('div');
|
||||||
dom.innerHTML = enhancedContent;
|
// dom.innerHTML = enhancedContent;
|
||||||
const hasLink = /<a/i.test(enhancedContent);
|
const dom = createDOM(enhancedContent, returnDOM);
|
||||||
|
const hasLink = LINK_REGEX.test(enhancedContent);
|
||||||
const hasCodeBlock = enhancedContent.includes('```');
|
const hasCodeBlock = enhancedContent.includes('```');
|
||||||
|
|
||||||
if (hasLink) {
|
if (hasLink) {
|
||||||
// Add target="_blank" to all links with no target="_blank"
|
// Add target="_blank" to all links with no target="_blank"
|
||||||
// E.g. `note` in `account`
|
// E.g. `note` in `account`
|
||||||
const noTargetBlankLinks = dom.querySelectorAll('a:not([target="_blank"])');
|
const noTargetBlankLinks = dom.querySelectorAll('a:not([target="_blank"])');
|
||||||
noTargetBlankLinks.forEach((link) => {
|
for (const link of noTargetBlankLinks) {
|
||||||
link.setAttribute('target', '_blank');
|
link.setAttribute('target', '_blank');
|
||||||
});
|
}
|
||||||
|
|
||||||
// Remove all classes except `u-url`, `mention`, `hashtag`
|
// Remove all classes except `u-url`, `mention`, `hashtag`
|
||||||
const links = dom.querySelectorAll('a[class]');
|
const links = dom.querySelectorAll('a[class]');
|
||||||
links.forEach((link) => {
|
for (const link of links) {
|
||||||
link.classList.forEach((c) => {
|
for (const c of link.classList) {
|
||||||
if (!whitelistLinkClasses.includes(c)) {
|
if (!whitelistLinkClasses.includes(c)) {
|
||||||
link.classList.remove(c);
|
link.classList.remove(c);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 'has-url-text' to all links that contains a url
|
// Add 'has-url-text' to all links that contains a url
|
||||||
if (hasLink) {
|
if (hasLink) {
|
||||||
const links = dom.querySelectorAll('a[href]');
|
const links = dom.querySelectorAll('a[href]');
|
||||||
links.forEach((link) => {
|
for (const link of links) {
|
||||||
if (/^https?:\/\//i.test(link.textContent.trim())) {
|
if (HTTP_LINK_REGEX.test(link.textContent.trim())) {
|
||||||
link.classList.add('has-url-text');
|
link.classList.add('has-url-text');
|
||||||
shortenLink(link);
|
shortenLink(link);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spanify un-spanned mentions
|
// Spanify un-spanned mentions
|
||||||
if (hasLink) {
|
if (hasLink) {
|
||||||
const links = dom.querySelectorAll('a[href]');
|
const links = dom.querySelectorAll('a[href]');
|
||||||
const usernames = [];
|
const usernames = [];
|
||||||
links.forEach((link) => {
|
for (const link of links) {
|
||||||
const text = link.innerText.trim();
|
const text = link.innerText.trim();
|
||||||
const hasChildren = link.querySelector('*');
|
const hasChildren = link.querySelector('*');
|
||||||
// If text looks like @username@domain, then it's a mention
|
// If text looks like @username@domain, then it's a mention
|
||||||
if (/^@[^@]+(@[^@]+)?$/g.test(text)) {
|
if (MENTION_REGEX.test(text)) {
|
||||||
// Only show @username
|
// Only show @username
|
||||||
const [_, username, domain] = text.split('@');
|
const [_, username, domain] = text.split('@');
|
||||||
if (!hasChildren) {
|
if (!hasChildren) {
|
||||||
|
@ -67,11 +99,11 @@ function _enhanceContent(content, opts = {}) {
|
||||||
link.classList.add('mention');
|
link.classList.add('mention');
|
||||||
}
|
}
|
||||||
// If text looks like #hashtag, then it's a hashtag
|
// If text looks like #hashtag, then it's a hashtag
|
||||||
if (/^#[^#]+$/g.test(text)) {
|
if (HASHTAG_REGEX.test(text)) {
|
||||||
if (!hasChildren) link.innerHTML = `#<span>${text.slice(1)}</span>`;
|
if (!hasChildren) link.innerHTML = `#<span>${text.slice(1)}</span>`;
|
||||||
link.classList.add('mention', 'hashtag');
|
link.classList.add('mention', 'hashtag');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EMOJIS
|
// EMOJIS
|
||||||
|
@ -80,18 +112,14 @@ function _enhanceContent(content, opts = {}) {
|
||||||
let textNodes;
|
let textNodes;
|
||||||
if (enhancedContent.includes(':')) {
|
if (enhancedContent.includes(':')) {
|
||||||
textNodes = extractTextNodes(dom);
|
textNodes = extractTextNodes(dom);
|
||||||
textNodes.forEach((node) => {
|
for (const node of textNodes) {
|
||||||
let html = node.nodeValue
|
let html = escapeHTML(node.nodeValue);
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
if (emojis) {
|
if (emojis) {
|
||||||
html = emojifyText(html, emojis);
|
html = emojifyText(html, emojis);
|
||||||
}
|
}
|
||||||
fauxDiv.innerHTML = html;
|
fauxDiv.innerHTML = html;
|
||||||
// const nodes = [...fauxDiv.childNodes];
|
|
||||||
node.replaceWith(...fauxDiv.childNodes);
|
node.replaceWith(...fauxDiv.childNodes);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CODE BLOCKS
|
// CODE BLOCKS
|
||||||
|
@ -99,31 +127,35 @@ function _enhanceContent(content, opts = {}) {
|
||||||
// Convert ```code``` to <pre><code>code</code></pre>
|
// Convert ```code``` to <pre><code>code</code></pre>
|
||||||
if (hasCodeBlock) {
|
if (hasCodeBlock) {
|
||||||
const blocks = [...dom.querySelectorAll('p')].filter((p) =>
|
const blocks = [...dom.querySelectorAll('p')].filter((p) =>
|
||||||
/^```[^]+```$/g.test(p.innerText.trim()),
|
CODE_BLOCK_REGEX.test(p.innerText.trim()),
|
||||||
);
|
);
|
||||||
blocks.forEach((block) => {
|
for (const block of blocks) {
|
||||||
const pre = document.createElement('pre');
|
const pre = document.createElement('pre');
|
||||||
// Replace <br /> with newlines
|
// Replace <br /> with newlines
|
||||||
block.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
|
for (const br of block.querySelectorAll('br')) {
|
||||||
|
br.replaceWith('\n');
|
||||||
|
}
|
||||||
pre.innerHTML = `<code>${block.innerHTML.trim()}</code>`;
|
pre.innerHTML = `<code>${block.innerHTML.trim()}</code>`;
|
||||||
block.replaceWith(pre);
|
block.replaceWith(pre);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert multi-paragraph code blocks to <pre><code>code</code></pre>
|
// Convert multi-paragraph code blocks to <pre><code>code</code></pre>
|
||||||
if (hasCodeBlock) {
|
if (hasCodeBlock) {
|
||||||
const paragraphs = [...dom.querySelectorAll('p')];
|
const paragraphs = [...dom.querySelectorAll('p')];
|
||||||
// Filter out paragraphs with ``` in beginning only
|
// Filter out paragraphs with ``` in beginning only
|
||||||
const codeBlocks = paragraphs.filter((p) => /^```/g.test(p.innerText));
|
const codeBlocks = paragraphs.filter((p) =>
|
||||||
|
CODE_BLOCK_START_REGEX.test(p.innerText),
|
||||||
|
);
|
||||||
// For each codeBlocks, get all paragraphs until the last paragraph with ``` at the end only
|
// For each codeBlocks, get all paragraphs until the last paragraph with ``` at the end only
|
||||||
codeBlocks.forEach((block) => {
|
for (const block of codeBlocks) {
|
||||||
const nextParagraphs = [block];
|
const nextParagraphs = [block];
|
||||||
let hasCodeBlock = false;
|
let hasCodeBlock = false;
|
||||||
let currentBlock = block;
|
let currentBlock = block;
|
||||||
while (currentBlock.nextElementSibling) {
|
while (currentBlock.nextElementSibling) {
|
||||||
const next = currentBlock.nextElementSibling;
|
const next = currentBlock.nextElementSibling;
|
||||||
if (next && next.tagName === 'P') {
|
if (next && next.tagName === 'P') {
|
||||||
if (/```$/g.test(next.innerText)) {
|
if (CODE_BLOCK_END_REGEX.test(next.innerText)) {
|
||||||
nextParagraphs.push(next);
|
nextParagraphs.push(next);
|
||||||
hasCodeBlock = true;
|
hasCodeBlock = true;
|
||||||
break;
|
break;
|
||||||
|
@ -137,16 +169,20 @@ function _enhanceContent(content, opts = {}) {
|
||||||
}
|
}
|
||||||
if (hasCodeBlock) {
|
if (hasCodeBlock) {
|
||||||
const pre = document.createElement('pre');
|
const pre = document.createElement('pre');
|
||||||
nextParagraphs.forEach((p) => {
|
for (const p of nextParagraphs) {
|
||||||
// Replace <br /> with newlines
|
// Replace <br /> with newlines
|
||||||
p.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
|
for (const br of p.querySelectorAll('br')) {
|
||||||
});
|
br.replaceWith('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
const codeText = nextParagraphs.map((p) => p.innerHTML).join('\n\n');
|
const codeText = nextParagraphs.map((p) => p.innerHTML).join('\n\n');
|
||||||
pre.innerHTML = `<code tabindex="0">${codeText}</code>`;
|
pre.innerHTML = `<code tabindex="0">${codeText}</code>`;
|
||||||
block.replaceWith(pre);
|
block.replaceWith(pre);
|
||||||
nextParagraphs.forEach((p) => p.remove());
|
for (const p of nextParagraphs) {
|
||||||
|
p.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// INLINE CODE
|
// INLINE CODE
|
||||||
|
@ -154,42 +190,36 @@ function _enhanceContent(content, opts = {}) {
|
||||||
// Convert `code` to <code>code</code>
|
// Convert `code` to <code>code</code>
|
||||||
if (enhancedContent.includes('`')) {
|
if (enhancedContent.includes('`')) {
|
||||||
textNodes = extractTextNodes(dom);
|
textNodes = extractTextNodes(dom);
|
||||||
textNodes.forEach((node) => {
|
for (const node of textNodes) {
|
||||||
let html = node.nodeValue
|
let html = escapeHTML(node.nodeValue);
|
||||||
.replace(/&/g, '&')
|
if (INLINE_CODE_REGEX.test(html)) {
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
if (/`[^`]+`/g.test(html)) {
|
|
||||||
html = html.replaceAll(/(`[^]+?`)/g, '<code>$1</code>');
|
html = html.replaceAll(/(`[^]+?`)/g, '<code>$1</code>');
|
||||||
}
|
}
|
||||||
fauxDiv.innerHTML = html;
|
fauxDiv.innerHTML = html;
|
||||||
// const nodes = [...fauxDiv.childNodes];
|
// const nodes = [...fauxDiv.childNodes];
|
||||||
node.replaceWith(...fauxDiv.childNodes);
|
node.replaceWith(...fauxDiv.childNodes);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TWITTER USERNAMES
|
// TWITTER USERNAMES
|
||||||
// =================
|
// =================
|
||||||
// Convert @username@twitter.com to <a href="https://twitter.com/username">@username@twitter.com</a>
|
// Convert @username@twitter.com to <a href="https://twitter.com/username">@username@twitter.com</a>
|
||||||
if (/twitter\.com/i.test(enhancedContent)) {
|
if (TWITTER_DOMAIN_REGEX.test(enhancedContent)) {
|
||||||
textNodes = extractTextNodes(dom, {
|
textNodes = extractTextNodes(dom, {
|
||||||
rejectFilter: ['A'],
|
rejectFilter: ['A'],
|
||||||
});
|
});
|
||||||
textNodes.forEach((node) => {
|
for (const node of textNodes) {
|
||||||
let html = node.nodeValue
|
let html = escapeHTML(node.nodeValue);
|
||||||
.replace(/&/g, '&')
|
if (TWITTER_MENTION_REGEX.test(html)) {
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
if (/@[a-zA-Z0-9_]+@twitter\.com/g.test(html)) {
|
|
||||||
html = html.replaceAll(
|
html = html.replaceAll(
|
||||||
/(@([a-zA-Z0-9_]+)@twitter\.com)/g,
|
TWITTER_MENTION_CAPTURE_REGEX,
|
||||||
'<a href="https://twitter.com/$2" rel="nofollow noopener noreferrer" target="_blank">$1</a>',
|
'<a href="https://twitter.com/$2" rel="nofollow noopener noreferrer" target="_blank">$1</a>',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fauxDiv.innerHTML = html;
|
fauxDiv.innerHTML = html;
|
||||||
// const nodes = [...fauxDiv.childNodes];
|
// const nodes = [...fauxDiv.childNodes];
|
||||||
node.replaceWith(...fauxDiv.childNodes);
|
node.replaceWith(...fauxDiv.childNodes);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HASHTAG STUFFING
|
// HASHTAG STUFFING
|
||||||
|
@ -235,22 +265,24 @@ function _enhanceContent(content, opts = {}) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (hashtagStuffedParagraphs?.length) {
|
if (hashtagStuffedParagraphs?.length) {
|
||||||
hashtagStuffedParagraphs.forEach((p) => {
|
for (const p of hashtagStuffedParagraphs) {
|
||||||
p.classList.add('hashtag-stuffing');
|
p.classList.add('hashtag-stuffing');
|
||||||
p.title = p.innerText;
|
p.title = p.innerText;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADD ASPECT RATIO TO ALL IMAGES
|
// ADD ASPECT RATIO TO ALL IMAGES
|
||||||
if (enhancedContent.includes('<img')) {
|
if (enhancedContent.includes('<img')) {
|
||||||
dom.querySelectorAll('img').forEach((img) => {
|
const imgs = dom.querySelectorAll('img');
|
||||||
|
for (let i = 0; i < imgs.length; i++) {
|
||||||
|
const img = imgs[i];
|
||||||
const width = img.getAttribute('width') || img.naturalWidth;
|
const width = img.getAttribute('width') || img.naturalWidth;
|
||||||
const height = img.getAttribute('height') || img.naturalHeight;
|
const height = img.getAttribute('height') || img.naturalHeight;
|
||||||
if (width && height) {
|
if (width && height) {
|
||||||
img.style.setProperty('--original-aspect-ratio', `${width}/${height}`);
|
img.style.setProperty('--original-aspect-ratio', `${width}/${height}`);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postEnhanceDOM) {
|
if (postEnhanceDOM) {
|
||||||
|
@ -258,9 +290,7 @@ function _enhanceContent(content, opts = {}) {
|
||||||
// postEnhanceDOM(dom); // mutate dom
|
// postEnhanceDOM(dom); // mutate dom
|
||||||
}
|
}
|
||||||
|
|
||||||
enhancedContent = dom.innerHTML;
|
return returnDOM ? dom : dom.innerHTML;
|
||||||
|
|
||||||
return enhancedContent;
|
|
||||||
}
|
}
|
||||||
const enhanceContent = mem(_enhanceContent);
|
const enhanceContent = mem(_enhanceContent);
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,9 @@ import moize from 'moize';
|
||||||
window._moize = moize;
|
window._moize = moize;
|
||||||
|
|
||||||
export default function mem(fn, opts = {}) {
|
export default function mem(fn, opts = {}) {
|
||||||
return moize(fn, { ...opts, maxSize: 50, isDeepEqual: true });
|
return moize(fn, {
|
||||||
|
...opts,
|
||||||
|
maxSize: 30,
|
||||||
|
isDeepEqual: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue