diff --git a/src/components/status.jsx b/src/components/status.jsx
index b776299f..25490917 100644
--- a/src/components/status.jsx
+++ b/src/components/status.jsx
@@ -1819,7 +1819,7 @@ const unfurlMastodonLink = throttle(
const root = document.documentElement;
const defaultBoundingBoxPadding = 8;
-function safeBoundingBoxPadding() {
+function _safeBoundingBoxPadding() {
// Get safe area inset variables from root
const style = getComputedStyle(root);
const safeAreaInsetTop = style.getPropertyValue('--sai-top');
@@ -1837,6 +1837,9 @@ function safeBoundingBoxPadding() {
// console.log(str);
return str;
}
+const safeBoundingBoxPadding = mem(_safeBoundingBoxPadding, {
+ maxAge: 10_000, // 10 seconds
+});
function FilteredStatus({ status, filterInfo, instance, containerProps = {} }) {
const {
diff --git a/src/utils/enhance-content.js b/src/utils/enhance-content.js
index c91c6328..bf91733b 100644
--- a/src/utils/enhance-content.js
+++ b/src/utils/enhance-content.js
@@ -7,174 +7,193 @@ function enhanceContent(content, opts = {}) {
let enhancedContent = content;
const dom = document.createElement('div');
dom.innerHTML = enhancedContent;
+ const hasLink = / {
- link.setAttribute('target', '_blank');
- });
+ if (hasLink) {
+ const noTargetBlankLinks = Array.from(
+ dom.querySelectorAll('a:not([target="_blank"])'),
+ );
+ noTargetBlankLinks.forEach((link) => {
+ link.setAttribute('target', '_blank');
+ });
+ }
// Spanify un-spanned mentions
- const notMentionLinks = Array.from(dom.querySelectorAll('a[href]'));
- notMentionLinks.forEach((link) => {
- const text = link.innerText.trim();
- const hasChildren = link.querySelector('*');
- // If text looks like @username@domain, then it's a mention
- if (/^@[^@]+(@[^@]+)?$/g.test(text)) {
- // Only show @username
- const username = text.split('@')[1];
- if (!hasChildren) link.innerHTML = `@${username}`;
- link.classList.add('mention');
- }
- // If text looks like #hashtag, then it's a hashtag
- if (/^#[^#]+$/g.test(text)) {
- if (!hasChildren) link.innerHTML = `#${text.slice(1)}`;
- link.classList.add('mention', 'hashtag');
- }
- });
+ if (hasLink) {
+ const notMentionLinks = Array.from(dom.querySelectorAll('a[href]'));
+ notMentionLinks.forEach((link) => {
+ const text = link.innerText.trim();
+ const hasChildren = link.querySelector('*');
+ // If text looks like @username@domain, then it's a mention
+ if (/^@[^@]+(@[^@]+)?$/g.test(text)) {
+ // Only show @username
+ const username = text.split('@')[1];
+ if (!hasChildren) link.innerHTML = `@${username}`;
+ link.classList.add('mention');
+ }
+ // If text looks like #hashtag, then it's a hashtag
+ if (/^#[^#]+$/g.test(text)) {
+ if (!hasChildren) link.innerHTML = `#${text.slice(1)}`;
+ link.classList.add('mention', 'hashtag');
+ }
+ });
+ }
// EMOJIS
// ======
// Convert :shortcode: to
- let textNodes = extractTextNodes(dom);
- textNodes.forEach((node) => {
- let html = node.nodeValue
- .replace(/&/g, '&')
- .replace(//g, '>');
- if (emojis) {
- html = emojifyText(html, emojis);
- }
- fauxDiv.innerHTML = html;
- const nodes = Array.from(fauxDiv.childNodes);
- node.replaceWith(...nodes);
- });
+ let textNodes;
+ if (enhancedContent.indexOf(':') !== -1) {
+ textNodes = extractTextNodes(dom);
+ textNodes.forEach((node) => {
+ let html = node.nodeValue
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+ if (emojis) {
+ html = emojifyText(html, emojis);
+ }
+ fauxDiv.innerHTML = html;
+ const nodes = Array.from(fauxDiv.childNodes);
+ node.replaceWith(...nodes);
+ });
+ }
// CODE BLOCKS
// ===========
// Convert ```code``` to
- const blocks = Array.from(dom.querySelectorAll('p')).filter((p) =>
- /^```[^]+```$/g.test(p.innerText.trim()),
- );
- blocks.forEach((block) => {
- const pre = document.createElement('pre');
- // Replace code
with newlines
- block.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
- pre.innerHTML = `${block.innerHTML.trim()}
`;
- block.replaceWith(pre);
- });
+ if (hasCodeBlock) {
+ const blocks = Array.from(dom.querySelectorAll('p')).filter((p) =>
+ /^```[^]+```$/g.test(p.innerText.trim()),
+ );
+ blocks.forEach((block) => {
+ const pre = document.createElement('pre');
+ // Replace
with newlines
+ block.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
+ pre.innerHTML = `${block.innerHTML.trim()}
`;
+ block.replaceWith(pre);
+ });
+ }
// Convert multi-paragraph code blocks to
- const paragraphs = Array.from(dom.querySelectorAll('p'));
- // Filter out paragraphs with ``` in beginning only
- const codeBlocks = paragraphs.filter((p) => /^```/g.test(p.innerText));
- // For each codeBlocks, get all paragraphs until the last paragraph with ``` at the end only
- codeBlocks.forEach((block) => {
- const nextParagraphs = [block];
- let hasCodeBlock = false;
- let currentBlock = block;
- while (currentBlock.nextElementSibling) {
- const next = currentBlock.nextElementSibling;
- if (next && next.tagName === 'P') {
- if (/```$/g.test(next.innerText)) {
- nextParagraphs.push(next);
- hasCodeBlock = true;
- break;
+ if (hasCodeBlock) {
+ const paragraphs = Array.from(dom.querySelectorAll('p'));
+ // Filter out paragraphs with ``` in beginning only
+ const codeBlocks = paragraphs.filter((p) => /^```/g.test(p.innerText));
+ // For each codeBlocks, get all paragraphs until the last paragraph with ``` at the end only
+ codeBlocks.forEach((block) => {
+ const nextParagraphs = [block];
+ let hasCodeBlock = false;
+ let currentBlock = block;
+ while (currentBlock.nextElementSibling) {
+ const next = currentBlock.nextElementSibling;
+ if (next && next.tagName === 'P') {
+ if (/```$/g.test(next.innerText)) {
+ nextParagraphs.push(next);
+ hasCodeBlock = true;
+ break;
+ } else {
+ nextParagraphs.push(next);
+ }
} else {
- nextParagraphs.push(next);
+ break;
}
- } else {
- break;
+ currentBlock = next;
}
- currentBlock = next;
- }
- if (hasCodeBlock) {
- const pre = document.createElement('pre');
- nextParagraphs.forEach((p) => {
- // Replace code
with newlines
- p.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
- });
- const codeText = nextParagraphs.map((p) => p.innerHTML).join('\n\n');
- pre.innerHTML = `${codeText}
`;
- block.replaceWith(pre);
- nextParagraphs.forEach((p) => p.remove());
- }
- });
+ if (hasCodeBlock) {
+ const pre = document.createElement('pre');
+ nextParagraphs.forEach((p) => {
+ // Replace
with newlines
+ p.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
+ });
+ const codeText = nextParagraphs.map((p) => p.innerHTML).join('\n\n');
+ pre.innerHTML = `${codeText}
`;
+ block.replaceWith(pre);
+ nextParagraphs.forEach((p) => p.remove());
+ }
+ });
+ }
// INLINE CODE
// ===========
// Convert `code` to code
- textNodes = extractTextNodes(dom);
- textNodes.forEach((node) => {
- let html = node.nodeValue
- .replace(/&/g, '&')
- .replace(//g, '>');
- if (/`[^`]+`/g.test(html)) {
- html = html.replaceAll(/(`[^]+?`)/g, '$1
');
- }
- fauxDiv.innerHTML = html;
- const nodes = Array.from(fauxDiv.childNodes);
- node.replaceWith(...nodes);
- });
+ if (enhancedContent.indexOf('`') !== -1) {
+ textNodes = extractTextNodes(dom);
+ textNodes.forEach((node) => {
+ let html = node.nodeValue
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+ if (/`[^`]+`/g.test(html)) {
+ html = html.replaceAll(/(`[^]+?`)/g, '$1
');
+ }
+ fauxDiv.innerHTML = html;
+ const nodes = Array.from(fauxDiv.childNodes);
+ node.replaceWith(...nodes);
+ });
+ }
// TWITTER USERNAMES
// =================
// Convert @username@twitter.com to @username@twitter.com
- textNodes = extractTextNodes(dom, {
- rejectFilter: ['A'],
- });
- textNodes.forEach((node) => {
- let html = node.nodeValue
- .replace(/&/g, '&')
- .replace(//g, '>');
- if (/@[a-zA-Z0-9_]+@twitter\.com/g.test(html)) {
- html = html.replaceAll(
- /(@([a-zA-Z0-9_]+)@twitter\.com)/g,
- '$1',
- );
- }
- fauxDiv.innerHTML = html;
- const nodes = Array.from(fauxDiv.childNodes);
- node.replaceWith(...nodes);
- });
+ if (/twitter\.com/i.test(enhancedContent)) {
+ textNodes = extractTextNodes(dom, {
+ rejectFilter: ['A'],
+ });
+ textNodes.forEach((node) => {
+ let html = node.nodeValue
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+ if (/@[a-zA-Z0-9_]+@twitter\.com/g.test(html)) {
+ html = html.replaceAll(
+ /(@([a-zA-Z0-9_]+)@twitter\.com)/g,
+ '$1',
+ );
+ }
+ fauxDiv.innerHTML = html;
+ const nodes = Array.from(fauxDiv.childNodes);
+ node.replaceWith(...nodes);
+ });
+ }
// HASHTAG STUFFING
// ================
// Get the
that contains a lot of hashtags, add a class to it - const hashtagStuffedParagraph = Array.from(dom.querySelectorAll('p')).find( - (p) => { - let hashtagCount = 0; - for (let i = 0; i < p.childNodes.length; i++) { - const node = p.childNodes[i]; + if (enhancedContent.indexOf('#') !== -1) { + const hashtagStuffedParagraph = Array.from(dom.querySelectorAll('p')).find( + (p) => { + let hashtagCount = 0; + for (let i = 0; i < p.childNodes.length; i++) { + const node = p.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE) { - const text = node.textContent.trim(); - if (text !== '') { - return false; - } - } else if (node.tagName === 'A') { - const linkText = node.textContent.trim(); - if (!linkText || !linkText.startsWith('#')) { - return false; + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent.trim(); + if (text !== '') { + return false; + } + } else if (node.tagName === 'A') { + const linkText = node.textContent.trim(); + if (!linkText || !linkText.startsWith('#')) { + return false; + } else { + hashtagCount++; + } } else { - hashtagCount++; + return false; } - } else { - return false; } - } - // Only consider "stuffing" if there are more than 3 hashtags - return hashtagCount > 3; - }, - ); - if (hashtagStuffedParagraph) { - hashtagStuffedParagraph.classList.add('hashtag-stuffing'); - hashtagStuffedParagraph.title = hashtagStuffedParagraph.innerText; + // Only consider "stuffing" if there are more than 3 hashtags + return hashtagCount > 3; + }, + ); + if (hashtagStuffedParagraph) { + hashtagStuffedParagraph.classList.add('hashtag-stuffing'); + hashtagStuffedParagraph.title = hashtagStuffedParagraph.innerText; + } } if (postEnhanceDOM) { diff --git a/src/utils/locale-match.jsx b/src/utils/locale-match.jsx index f67d1fad..8c46ec65 100644 --- a/src/utils/locale-match.jsx +++ b/src/utils/locale-match.jsx @@ -1,6 +1,7 @@ import { match } from '@formatjs/intl-localematcher'; +import mem from 'mem'; -function localeMatch(...args) { +function _localeMatch(...args) { // Wrap in try/catch because localeMatcher throws on invalid locales try { return match(...args); @@ -8,5 +9,8 @@ function localeMatch(...args) { return false; } } +const localeMatch = mem(_localeMatch, { + cacheKey: (args) => args.join(), +}); export default localeMatch;