Refactor components from status

pull/1254/head
Lim Chee Aun 2025-08-25 19:55:00 +08:00
rodzic 9ccbe244af
commit 1a25b32cdc
12 zmienionych plików z 1565 dodań i 1499 usunięć

Wyświetl plik

@ -0,0 +1,26 @@
import { Trans } from '@lingui/react/macro';
import Icon from './icon';
import NameText from './name-text';
function Byline({ authors, hidden, children }) {
if (hidden) return children;
if (!authors?.[0]?.account?.id) return children;
const author = authors[0].account;
return (
<div class="card-byline">
{children}
<div class="card-byline-author">
<Icon icon="link" size="s" />{' '}
<small>
<Trans comment="More from [Author]">
More from <NameText account={author} showAvatar />
</Trans>
</small>
</div>
</div>
);
}
export default Byline;

Wyświetl plik

@ -0,0 +1,164 @@
import 'temml/dist/Temml-Local.css';
import { useLingui } from '@lingui/react/macro';
import { useCallback, useState } from 'preact/hooks';
import showToast from '../utils/show-toast';
import Icon from './icon';
// Follow https://mathstodon.xyz/about
// > You can use LaTeX in toots here! Use \( and \) for inline, and \[ and \] for display mode.
const DELIMITERS_PATTERNS = [
// '\\$\\$[\\s\\S]*?\\$\\$', // $$...$$
'\\\\\\[[\\s\\S]*?\\\\\\]', // \[...\]
'\\\\\\([\\s\\S]*?\\\\\\)', // \(...\)
// '\\\\begin\\{(?:equation\\*?|align\\*?|alignat\\*?|gather\\*?|CD)\\}[\\s\\S]*?\\\\end\\{(?:equation\\*?|align\\*?|alignat\\*?|gather\\*?|CD)\\}', // AMS environments
// '\\\\(?:ref|eqref)\\{[^}]*\\}', // \ref{...}, \eqref{...}
];
const DELIMITERS_REGEX = new RegExp(DELIMITERS_PATTERNS.join('|'), 'g');
function cleanDOMForTemml(dom) {
// Define start and end delimiter patterns
const START_DELIMITERS = ['\\\\\\[', '\\\\\\(']; // \[ and \(
const startRegex = new RegExp(`(${START_DELIMITERS.join('|')})`);
// Walk through all text nodes
const walker = document.createTreeWalker(dom, NodeFilter.SHOW_TEXT);
const textNodes = [];
let node;
while ((node = walker.nextNode())) {
textNodes.push(node);
}
for (const textNode of textNodes) {
const text = textNode.textContent;
const startMatch = text.match(startRegex);
if (!startMatch) continue; // No start delimiter in this text node
// Find the matching end delimiter
const startDelimiter = startMatch[0];
const endDelimiter = startDelimiter === '\\[' ? '\\]' : '\\)';
// Collect nodes from start delimiter until end delimiter
const nodesToCombine = [textNode];
let currentNode = textNode;
let foundEnd = false;
let combinedText = text;
// Check if end delimiter is in the same text node
if (text.includes(endDelimiter)) {
foundEnd = true;
} else {
// Look through sibling nodes
while (currentNode.nextSibling && !foundEnd) {
const nextSibling = currentNode.nextSibling;
if (nextSibling.nodeType === Node.TEXT_NODE) {
nodesToCombine.push(nextSibling);
combinedText += nextSibling.textContent;
if (nextSibling.textContent.includes(endDelimiter)) {
foundEnd = true;
}
} else if (
nextSibling.nodeType === Node.ELEMENT_NODE &&
nextSibling.tagName === 'BR'
) {
nodesToCombine.push(nextSibling);
combinedText += '\n';
} else {
// Found a non-BR element, stop and don't process
break;
}
currentNode = nextSibling;
}
}
// Only process if we found the end delimiter and have nodes to combine
if (foundEnd && nodesToCombine.length > 1) {
// Replace the first text node with combined text
textNode.textContent = combinedText;
// Remove the other nodes
for (let i = 1; i < nodesToCombine.length; i++) {
nodesToCombine[i].remove();
}
}
}
}
const MathBlock = ({ content, contentRef, onRevert }) => {
DELIMITERS_REGEX.lastIndex = 0; // Reset index to prevent g trap
const hasLatexContent = DELIMITERS_REGEX.test(content);
if (!hasLatexContent) return null;
const { t } = useLingui();
const [mathRendered, setMathRendered] = useState(false);
const toggleMathRendering = useCallback(
async (e) => {
e.preventDefault();
e.stopPropagation();
if (mathRendered) {
// Revert to original content by refreshing PostContent
setMathRendered(false);
onRevert();
} else {
// Render math
try {
// This needs global because the codebase inside temml is calling a function from global.temml 🤦
const temml =
window.temml || (window.temml = (await import('temml'))?.default);
cleanDOMForTemml(contentRef.current);
const originalContentRefHTML = contentRef.current.innerHTML;
temml.renderMathInElement(contentRef.current, {
fences: '(', // This should sync with DELIMITERS_REGEX
annotate: true,
throwOnError: true,
errorCallback: (err) => {
console.warn('Failed to render LaTeX:', err);
},
});
const hasMath = contentRef.current.querySelector('math.tml-display');
const htmlChanged =
contentRef.current.innerHTML !== originalContentRefHTML;
if (hasMath && htmlChanged) {
setMathRendered(true);
} else {
showToast(t`Unable to format math`);
setMathRendered(false);
onRevert(); // Revert because DOM modified by cleanDOMForTemml
}
} catch (e) {
console.error('Failed to LaTeX:', e);
}
}
},
[mathRendered],
);
return (
<div class="math-block">
<Icon icon="formula" size="s" /> <span>{t`Math expressions found.`}</span>{' '}
<button type="button" class="light small" onClick={toggleMathRendering}>
{mathRendered
? t({
comment:
'Action to switch from rendered math back to raw (LaTeX) markup',
message: 'Show markup',
})
: t({
comment:
'Action to render math expressions from raw (LaTeX) markup',
message: 'Format math',
})}
</button>
</div>
);
};
export default MathBlock;

Wyświetl plik

@ -0,0 +1,116 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import isRTL from '../utils/is-rtl';
import Icon from './icon';
import Media from './media';
function MediaFirstContainer(props) {
const { mediaAttachments, language, postID, instance } = props;
const moreThanOne = mediaAttachments.length > 1;
const carouselRef = useRef();
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
let handleScroll = () => {
const { clientWidth, scrollLeft } = carouselRef.current;
const index = Math.round(Math.abs(scrollLeft) / clientWidth);
setCurrentIndex(index);
};
if (carouselRef.current) {
carouselRef.current.addEventListener('scroll', handleScroll, {
passive: true,
});
}
return () => {
if (carouselRef.current) {
carouselRef.current.removeEventListener('scroll', handleScroll);
}
};
}, []);
return (
<>
<div class="media-first-container">
<div class="media-first-carousel" ref={carouselRef}>
{mediaAttachments.map((media, i) => (
<div class="media-first-item" key={media.id}>
<Media
media={media}
lang={language}
to={`/${instance}/s/${postID}?media=${i + 1}`}
/>
</div>
))}
</div>
{moreThanOne && (
<div class="media-carousel-controls">
<div class="carousel-indexer">
{currentIndex + 1}/{mediaAttachments.length}
</div>
<label class="media-carousel-button">
<button
type="button"
class="carousel-button"
hidden={currentIndex === 0}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
carouselRef.current.focus();
carouselRef.current.scrollTo({
left:
carouselRef.current.clientWidth *
(currentIndex - 1) *
(isRTL() ? -1 : 1),
behavior: 'smooth',
});
}}
>
<Icon icon="arrow-left" />
</button>
</label>
<label class="media-carousel-button">
<button
type="button"
class="carousel-button"
hidden={currentIndex === mediaAttachments.length - 1}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
carouselRef.current.focus();
carouselRef.current.scrollTo({
left:
carouselRef.current.clientWidth *
(currentIndex + 1) *
(isRTL() ? -1 : 1),
behavior: 'smooth',
});
}}
>
<Icon icon="arrow-right" />
</button>
</label>
</div>
)}
</div>
{moreThanOne && (
<div
class="media-carousel-dots"
style={{
'--dots-count': mediaAttachments.length,
}}
>
{mediaAttachments.map((media, i) => (
<span
key={media.id}
class={`carousel-dot ${i === currentIndex ? 'active' : ''}`}
/>
))}
</div>
)}
</>
);
}
export default MediaFirstContainer;

Wyświetl plik

@ -0,0 +1,14 @@
function MultipleMediaFigure(props) {
const { enabled, children, lang, captionChildren } = props;
if (!enabled || !captionChildren) return children;
return (
<figure class="media-figure-multiple">
{children}
<figcaption lang={lang} dir="auto">
{captionChildren}
</figcaption>
</figure>
);
}
export default MultipleMediaFigure;

Wyświetl plik

@ -0,0 +1,64 @@
import { useLayoutEffect, useRef } from 'preact/hooks';
import enhanceContent from '../utils/enhance-content';
import handleContentLinks from '../utils/handle-content-links';
const HTTP_REGEX = /^http/i;
const PostContent =
/*memo(*/
({ post, instance, previewMode }) => {
const { content, emojis, language, mentions, url } = post;
const divRef = useRef();
useLayoutEffect(() => {
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 (
<div
ref={divRef}
lang={language}
dir="auto"
class="inner-content"
onClick={handleContentLinks({
mentions,
instance,
previewMode,
statusURL: url,
})}
// dangerouslySetInnerHTML={{
// __html: enhanceContent(content, {
// emojis,
// postEnhanceDOM: (dom) => {
// // Remove target="_blank" from links
// dom.querySelectorAll('a.u-url[target="_blank"]').forEach((a) => {
// if (!/http/i.test(a.innerText.trim())) {
// a.removeAttribute('target');
// }
// });
// },
// }),
// }}
/>
);
}; /*,
(oldProps, newProps) => {
const { post: oldPost } = oldProps;
const { post: newPost } = newProps;
return oldPost.content === newPost.content;
},
);*/
export default PostContent;

Wyświetl plik

@ -0,0 +1,394 @@
import { Trans, useLingui } from '@lingui/react/macro';
import prettify from 'html-prettify';
import emojifyText from '../utils/emojify-text';
import showToast from '../utils/show-toast';
import states, { statusKey } from '../utils/states';
import Icon from './icon';
function generateHTMLCode(post, instance, level = 0) {
const {
account: {
url: accountURL,
displayName,
acct,
username,
emojis: accountEmojis,
bot,
group,
},
id,
poll,
spoilerText,
language,
editedAt,
createdAt,
content,
mediaAttachments,
url,
emojis,
} = post;
const sKey = statusKey(id, instance);
const quotes = states.statusQuotes[sKey] || [];
const uniqueQuotes = quotes.filter(
(q, i, arr) => arr.findIndex((q2) => q2.url === q.url) === i,
);
const quoteStatusesHTML =
uniqueQuotes.length && level <= 2
? uniqueQuotes
.map((quote) => {
const { id, instance } = quote;
const sKey = statusKey(id, instance);
const s = states.statuses[sKey];
if (s) {
return generateHTMLCode(s, instance, ++level);
}
})
.join('')
: '';
const createdAtDate = new Date(createdAt);
// const editedAtDate = editedAt && new Date(editedAt);
const contentHTML =
emojifyText(content, emojis) +
'\n' +
quoteStatusesHTML +
'\n' +
(poll?.options?.length
? `
<p>📊:</p>
<ul>
${poll.options
.map(
(option) => `
<li>
${option.title}
${option.votesCount >= 0 ? ` (${option.votesCount})` : ''}
</li>
`,
)
.join('')}
</ul>`
: '') +
(mediaAttachments.length > 0
? '\n' +
mediaAttachments
.map((media) => {
const {
description,
meta,
previewRemoteUrl,
previewUrl,
remoteUrl,
url,
type,
} = media;
const { original = {}, small } = meta || {};
const width = small?.width || original?.width;
const height = small?.height || original?.height;
// Prefer remote over original
const sourceMediaURL = remoteUrl || url;
const previewMediaURL = previewRemoteUrl || previewUrl;
const mediaURL = previewMediaURL || sourceMediaURL;
const sourceMediaURLObj = sourceMediaURL
? URL.parse(sourceMediaURL)
: null;
const isVideoMaybe =
type === 'unknown' &&
sourceMediaURLObj &&
/\.(mp4|m4r|m4v|mov|webm)$/i.test(sourceMediaURLObj.pathname);
const isAudioMaybe =
type === 'unknown' &&
sourceMediaURLObj &&
/\.(mp3|ogg|wav|m4a|m4p|m4b)$/i.test(sourceMediaURLObj.pathname);
const isImage =
type === 'image' ||
(type === 'unknown' &&
previewMediaURL &&
!isVideoMaybe &&
!isAudioMaybe);
const isVideo = type === 'gifv' || type === 'video' || isVideoMaybe;
const isAudio = type === 'audio' || isAudioMaybe;
let mediaHTML = '';
if (isImage) {
mediaHTML = `<img src="${mediaURL}" width="${width}" height="${height}" alt="${description}" loading="lazy" />`;
} else if (isVideo) {
mediaHTML = `
<video src="${sourceMediaURL}" width="${width}" height="${height}" controls preload="auto" poster="${previewMediaURL}" loading="lazy"></video>
${description ? `<figcaption>${description}</figcaption>` : ''}
`;
} else if (isAudio) {
mediaHTML = `
<audio src="${sourceMediaURL}" controls preload="auto"></audio>
${description ? `<figcaption>${description}</figcaption>` : ''}
`;
} else {
mediaHTML = `
<a href="${sourceMediaURL}">📄 ${
description || sourceMediaURL
}</a>
`;
}
return `<figure>${mediaHTML}</figure>`;
})
.join('\n')
: '');
const htmlCode = `
<blockquote lang="${language}" cite="${url}" data-source="fediverse">
${
spoilerText
? `
<details>
<summary>${spoilerText}</summary>
${contentHTML}
</details>
`
: contentHTML
}
<footer>
${emojifyText(
displayName,
accountEmojis,
)} (@${acct}) ${!!createdAt ? `<a href="${url}"><time datetime="${createdAtDate.toISOString()}">${createdAtDate.toLocaleString()}</time></a>` : ''}
</footer>
</blockquote>
`;
return prettify(htmlCode);
}
function PostEmbedModal({ post, instance, onClose }) {
const { t } = useLingui();
const {
account: {
url: accountURL,
displayName,
username,
emojis: accountEmojis,
bot,
group,
},
id,
poll,
spoilerText,
language,
editedAt,
createdAt,
content,
mediaAttachments,
url,
emojis,
} = post;
const htmlCode = generateHTMLCode(post, instance);
return (
<div id="embed-post" class="sheet">
{!!onClose && (
<button type="button" class="sheet-close" onClick={onClose}>
<Icon icon="x" alt={t`Close`} />
</button>
)}
<header>
<h2>
<Trans>Embed post</Trans>
</h2>
</header>
<main tabIndex="-1">
<h3>
<Trans>HTML Code</Trans>
</h3>
<textarea
class="embed-code"
readonly
onClick={(e) => {
e.target.select();
}}
dir="auto"
>
{htmlCode}
</textarea>
<button
type="button"
onClick={() => {
try {
navigator.clipboard.writeText(htmlCode);
showToast(t`HTML code copied`);
} catch (e) {
console.error(e);
showToast(t`Unable to copy HTML code`);
}
}}
>
<Icon icon="clipboard" />{' '}
<span>
<Trans>Copy</Trans>
</span>
</button>
{!!mediaAttachments?.length && (
<section>
<p>
<Trans>Media attachments:</Trans>
</p>
<ol class="links-list">
{mediaAttachments.map((media) => {
return (
<li key={media.id}>
<a
href={media.remoteUrl || media.url}
target="_blank"
download
>
{media.remoteUrl || media.url}
</a>
</li>
);
})}
</ol>
</section>
)}
{!!accountEmojis?.length && (
<section>
<p>
<Trans>Account Emojis:</Trans>
</p>
<ul>
{accountEmojis.map((emoji) => {
return (
<li key={emoji.shortcode}>
<picture>
<source
srcset={emoji.staticUrl}
media="(prefers-reduced-motion: reduce)"
></source>
<img
class="shortcode-emoji emoji"
src={emoji.url}
alt={`:${emoji.shortcode}:`}
width="16"
height="16"
loading="lazy"
decoding="async"
/>
</picture>{' '}
<code>:{emoji.shortcode}:</code> (
<a href={emoji.url} target="_blank" download>
URL
</a>
)
{emoji.staticUrl ? (
<>
{' '}
(
<a href={emoji.staticUrl} target="_blank" download>
<Trans>static URL</Trans>
</a>
)
</>
) : null}
</li>
);
})}
</ul>
</section>
)}
{!!emojis?.length && (
<section>
<p>
<Trans>Emojis:</Trans>
</p>
<ul>
{emojis.map((emoji) => {
return (
<li key={emoji.shortcode}>
<picture>
<source
srcset={emoji.staticUrl}
media="(prefers-reduced-motion: reduce)"
></source>
<img
class="shortcode-emoji emoji"
src={emoji.url}
alt={`:${emoji.shortcode}:`}
width="16"
height="16"
loading="lazy"
decoding="async"
/>
</picture>{' '}
<code>:{emoji.shortcode}:</code> (
<a href={emoji.url} target="_blank" download>
URL
</a>
)
{emoji.staticUrl ? (
<>
{' '}
(
<a href={emoji.staticUrl} target="_blank" download>
<Trans>static URL</Trans>
</a>
)
</>
) : null}
</li>
);
})}
</ul>
</section>
)}
<section>
<small>
<p>
<Trans>Notes:</Trans>
</p>
<ul>
<li>
<Trans>
This is static, unstyled and scriptless. You may need to apply
your own styles and edit as needed.
</Trans>
</li>
<li>
<Trans>
Polls are not interactive, becomes a list with vote counts.
</Trans>
</li>
<li>
<Trans>
Media attachments can be images, videos, audios or any file
types.
</Trans>
</li>
<li>
<Trans>Post could be edited or deleted later.</Trans>
</li>
</ul>
</small>
</section>
<h3>
<Trans>Preview</Trans>
</h3>
<output
class="embed-preview"
dangerouslySetInnerHTML={{ __html: htmlCode }}
dir="auto"
/>
<p>
<small>
<Trans>Note: This preview is lightly styled.</Trans>
</small>
</p>
</main>
</div>
);
}
export default PostEmbedModal;

Wyświetl plik

@ -0,0 +1,68 @@
import { forwardRef } from 'preact/compat';
import { useEffect, useState } from 'preact/hooks';
import shortenNumber from '../utils/shorten-number';
import Icon from './icon';
const StatusButton = forwardRef((props, ref) => {
let {
checked,
count,
class: className,
title,
alt,
size,
icon,
iconSize = 'l',
onClick,
...otherProps
} = props;
if (typeof title === 'string') {
title = [title, title];
}
if (typeof alt === 'string') {
alt = [alt, alt];
}
const [buttonTitle, setButtonTitle] = useState(title[0] || '');
const [iconAlt, setIconAlt] = useState(alt[0] || '');
useEffect(() => {
if (checked) {
setButtonTitle(title[1] || '');
setIconAlt(alt[1] || '');
} else {
setButtonTitle(title[0] || '');
setIconAlt(alt[0] || '');
}
}, [checked, title, alt]);
return (
<button
ref={ref}
type="button"
title={buttonTitle}
class={`plain ${size ? 'small' : ''} ${className} ${
checked ? 'checked' : ''
}`}
onClick={(e) => {
if (!onClick) return;
e.preventDefault();
e.stopPropagation();
onClick(e);
}}
{...otherProps}
>
<Icon icon={icon} size={iconSize} alt={iconAlt} />
{!!count && (
<>
{' '}
<small title={count}>{shortenNumber(count)}</small>
</>
)}
</button>
);
});
export default StatusButton;

Wyświetl plik

@ -0,0 +1,290 @@
import '@justinribeiro/lite-youtube';
import { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio';
import getDomain from '../utils/get-domain';
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
import states from '../utils/states';
import unfurlMastodonLink from '../utils/unfurl-link';
import Byline from './byline';
import Icon from './icon';
import RelativeTime from './relative-time';
// "Post": Quote post + card link preview combo
// Assume all links from these domains are "posts"
// Mastodon links are "posts" too but they are converted to real quote posts and there's too many domains to check
// This is just "Progressive Enhancement"
function isCardPost(domain) {
return [
'x.com',
'twitter.com',
'threads.net',
'bsky.app',
'bsky.brid.gy',
'fed.brid.gy',
].includes(domain);
}
function StatusCard({ card, selfReferential, selfAuthor, instance }) {
const snapStates = useSnapshot(states);
const {
blurhash,
title,
description,
html,
providerName,
providerUrl,
authorName,
authorUrl,
width,
height,
image,
imageDescription,
url,
type,
embedUrl,
language,
publishedAt,
authors,
} = card;
/* type
link = Link OEmbed
photo = Photo OEmbed
video = Video OEmbed
rich = iframe OEmbed. Not currently accepted, so won't show up in practice.
*/
const hasText = title || providerName || authorName;
const isLandscape = width / height >= 1.2;
const size = isLandscape ? 'large' : '';
const [cardStatusURL, setCardStatusURL] = useState(null);
// const [cardStatusID, setCardStatusID] = useState(null);
useEffect(() => {
if (hasText && image && !selfReferential && isMastodonLinkMaybe(url)) {
unfurlMastodonLink(instance, url).then((result) => {
if (!result) return;
const { id, url } = result;
setCardStatusURL('#' + url);
// NOTE: This is for quote post
// (async () => {
// const { masto } = api({ instance });
// const status = await masto.v1.statuses.$select(id).fetch();
// saveStatus(status, instance);
// setCardStatusID(id);
// })();
});
}
}, [hasText, image, selfReferential]);
// if (cardStatusID) {
// return (
// <Status statusID={cardStatusID} instance={instance} size="s" readOnly />
// );
// }
if (snapStates.unfurledLinks[url]) return null;
const hasIframeHTML = /<iframe/i.test(html);
const handleClick = useCallback(
(e) => {
if (hasIframeHTML) {
e.preventDefault();
states.showEmbedModal = {
html,
url: url || embedUrl,
width,
height,
};
}
},
[hasIframeHTML],
);
const [blurhashImage, setBlurhashImage] = useState(null);
if (hasText && (image || (type === 'photo' && blurhash))) {
const domain = getDomain(url);
const rgbAverageColor =
image && blurhash ? getBlurHashAverageColor(blurhash) : null;
if (!image) {
const w = 44;
const h = 44;
const blurhashPixels = decodeBlurHash(blurhash, w, h);
const canvas = window.OffscreenCanvas
? new OffscreenCanvas(1, 1)
: document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
const imageData = ctx.createImageData(w, h);
imageData.data.set(blurhashPixels);
ctx.putImageData(imageData, 0, 0);
try {
if (window.OffscreenCanvas) {
canvas.convertToBlob().then((blob) => {
setBlurhashImage(URL.createObjectURL(blob));
});
} else {
setBlurhashImage(canvas.toDataURL());
}
} catch (e) {
// Silently fail
console.error(e);
}
}
const isPost = isCardPost(domain);
return (
<Byline hidden={!!selfAuthor} authors={authors}>
<a
href={cardStatusURL || url}
target={cardStatusURL ? null : '_blank'}
rel="nofollow noopener"
class={`card link ${isPost ? 'card-post' : ''} ${
blurhashImage ? '' : size
}`}
style={{
'--average-color':
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
}}
onClick={handleClick}
>
<div class="card-image">
<img
src={image || blurhashImage}
width={width}
height={height}
loading="lazy"
decoding="async"
fetchPriority="low"
alt={imageDescription || ''}
onError={(e) => {
try {
e.target.style.display = 'none';
} catch (e) {}
}}
style={{
'--anim-duration':
width &&
height &&
`${Math.min(
Math.max(Math.max(width, height) / 100, 5),
120,
)}s`,
}}
/>
</div>
<div class="meta-container" lang={language}>
<p class="meta domain">
<span class="domain">{domain}</span>{' '}
{!!publishedAt && <>&middot; </>}
{!!publishedAt && (
<>
<RelativeTime datetime={publishedAt} format="micro" />
</>
)}
</p>
<p class="title" dir="auto" title={title}>
{title}
</p>
<p class="meta" dir="auto" title={description}>
{description ||
(!!publishedAt && (
<RelativeTime datetime={publishedAt} format="micro" />
))}
</p>
</div>
</a>
</Byline>
);
} else if (type === 'photo') {
return (
<a
href={url}
target="_blank"
rel="nofollow noopener"
class="card photo"
onClick={handleClick}
>
<img
src={embedUrl}
width={width}
height={height}
alt={title || description}
loading="lazy"
style={{
height: 'auto',
aspectRatio: `${width}/${height}`,
}}
/>
</a>
);
} else {
if (type === 'video') {
if (/youtube/i.test(providerName)) {
// Get ID from e.g. https://www.youtube.com/watch?v=[VIDEO_ID]
const videoID = url.match(/watch\?v=([^&]+)/)?.[1];
if (videoID) {
return (
<a class="card video" onClick={handleClick}>
<lite-youtube videoid={videoID} nocookie autoPause></lite-youtube>
</a>
);
}
}
// return (
// <div
// class="card video"
// style={{
// aspectRatio: `${width}/${height}`,
// }}
// dangerouslySetInnerHTML={{ __html: html }}
// />
// );
}
if (hasText && !image) {
const domain = getDomain(url);
const isPost = isCardPost(domain);
return (
<a
href={cardStatusURL || url}
target={cardStatusURL ? null : '_blank'}
rel="nofollow noopener"
class={`card link ${isPost ? 'card-post' : ''} no-image`}
lang={language}
dir="auto"
onClick={handleClick}
>
<div class="meta-container">
<p class="meta domain">
<span class="domain">
<Icon icon="link" size="s" /> <span>{domain}</span>
</span>{' '}
{!!publishedAt && <>&middot; </>}
{!!publishedAt && (
<>
<RelativeTime datetime={publishedAt} format="micro" />
</>
)}
</p>
<p class="title" title={title}>
{title}
</p>
<p class="meta" title={description || providerName || authorName}>
{description || providerName || authorName}
</p>
</div>
</a>
);
}
}
}
export default StatusCard;

Wyświetl plik

@ -0,0 +1,82 @@
import { Trans } from '@lingui/react/macro';
import { useContext } from 'preact/hooks';
import { useSnapshot } from 'valtio';
import FilterContext from '../utils/filter-context';
import { isFiltered } from '../utils/filters';
import states, { getStatus, statusKey } from '../utils/states';
import statusPeek from '../utils/status-peek';
import { getCurrentAccID } from '../utils/store-utils';
import Avatar from './avatar';
function StatusCompact({ sKey }) {
const snapStates = useSnapshot(states);
const statusReply = snapStates.statusReply[sKey];
if (!statusReply) return null;
const { id, instance } = statusReply;
const status = getStatus(id, instance);
if (!status) return null;
const {
account: { id: accountId },
sensitive,
spoilerText,
account: { avatar, avatarStatic, bot } = {},
visibility,
content,
language,
filtered,
} = status;
if (sensitive || spoilerText) return null;
if (!content) return null;
const srKey = statusKey(id, instance);
const statusPeekText = statusPeek(status);
const currentAccount = getCurrentAccID();
const isSelf = currentAccount && currentAccount === accountId;
const filterContext = useContext(FilterContext);
let filterInfo = !isSelf && isFiltered(filtered, filterContext);
// This is fine. Images are converted to emojis so they are
// in a way, already "obscured"
if (filterInfo?.action === 'blur') filterInfo = null;
if (filterInfo?.action === 'hide') return null;
const filterTitleStr = filterInfo?.titlesStr || '';
return (
<article
class={`status compact-reply shazam ${
visibility === 'direct' ? 'visibility-direct' : ''
}`}
tabindex="-1"
data-state-post-id={srKey}
>
<Avatar url={avatarStatic || avatar} squircle={bot} />
<div
class="content-compact"
title={statusPeekText}
lang={language}
dir="auto"
>
{filterInfo ? (
<b class="status-filtered-badge badge-meta" title={filterTitleStr}>
<span>
<Trans>Filtered</Trans>
</span>
<span>{filterTitleStr}</span>
</b>
) : (
<span>{statusPeekText}</span>
)}
</div>
</article>
);
}
export default StatusCompact;

Plik diff jest za duży Load Diff

438
src/locales/en.po wygenerowano
Wyświetl plik

@ -34,7 +34,7 @@ msgstr ""
#: src/components/account-block.jsx:190
#: src/components/account-info.jsx:710
#: src/components/status.jsx:754
#: src/components/status.jsx:519
msgid "Group"
msgstr ""
@ -108,11 +108,11 @@ msgstr ""
#: src/components/compose.jsx:2812
#: src/components/media-alt-modal.jsx:55
#: src/components/media-modal.jsx:363
#: src/components/status.jsx:2006
#: src/components/status.jsx:2023
#: src/components/status.jsx:2154
#: src/components/status.jsx:2778
#: src/components/status.jsx:2781
#: src/components/status.jsx:1771
#: src/components/status.jsx:1788
#: src/components/status.jsx:1919
#: src/components/status.jsx:2543
#: src/components/status.jsx:2546
#: src/pages/account-statuses.jsx:539
#: src/pages/accounts.jsx:118
#: src/pages/hashtag.jsx:203
@ -217,7 +217,7 @@ msgid "Original"
msgstr ""
#: src/components/account-info.jsx:978
#: src/components/status.jsx:2563
#: src/components/status.jsx:2328
#: src/pages/catchup.jsx:71
#: src/pages/catchup.jsx:1448
#: src/pages/catchup.jsx:2061
@ -343,30 +343,30 @@ msgid "Add/Remove from Lists"
msgstr ""
#: src/components/account-info.jsx:1522
#: src/components/status.jsx:1428
#: src/components/status.jsx:1193
msgid "Link copied"
msgstr ""
#: src/components/account-info.jsx:1525
#: src/components/status.jsx:1431
#: src/components/status.jsx:1196
msgid "Unable to copy link"
msgstr ""
#: src/components/account-info.jsx:1531
#: src/components/post-embed-modal.jsx:232
#: src/components/shortcuts-settings.jsx:1059
#: src/components/status.jsx:1437
#: src/components/status.jsx:3556
#: src/components/status.jsx:1202
msgid "Copy"
msgstr ""
#: src/components/account-info.jsx:1546
#: src/components/shortcuts-settings.jsx:1077
#: src/components/status.jsx:1453
#: src/components/status.jsx:1218
msgid "Sharing doesn't seem to work."
msgstr ""
#: src/components/account-info.jsx:1552
#: src/components/status.jsx:1459
#: src/components/status.jsx:1224
msgid "Share…"
msgstr ""
@ -476,13 +476,13 @@ msgstr ""
#: src/components/media-alt-modal.jsx:43
#: src/components/media-modal.jsx:327
#: src/components/notification-service.jsx:157
#: src/components/post-embed-modal.jsx:196
#: src/components/report-modal.jsx:118
#: src/components/shortcuts-settings.jsx:230
#: src/components/shortcuts-settings.jsx:583
#: src/components/shortcuts-settings.jsx:783
#: src/components/status.jsx:3280
#: src/components/status.jsx:3520
#: src/components/status.jsx:4029
#: src/components/status.jsx:2742
#: src/components/status.jsx:2954
#: src/pages/accounts.jsx:45
#: src/pages/catchup.jsx:1584
#: src/pages/filters.jsx:225
@ -605,6 +605,11 @@ msgstr ""
msgid "Cloak mode enabled"
msgstr ""
#. More from [Author]
#: src/components/byline.jsx:17
msgid "More from <0/>"
msgstr "More from <0/>"
#: src/components/columns.jsx:27
#: src/components/nav-menu.jsx:181
#: src/components/shortcuts-settings.jsx:139
@ -732,7 +737,7 @@ msgid "Attachment #{i} failed"
msgstr "Attachment #{i} failed"
#: src/components/compose.jsx:1215
#: src/components/status.jsx:2338
#: src/components/status.jsx:2103
#: src/components/timeline.jsx:1015
msgid "Content warning"
msgstr ""
@ -742,7 +747,7 @@ msgid "Content warning or sensitive media"
msgstr "Content warning or sensitive media"
#: src/components/compose.jsx:1267
#: src/components/status.jsx:101
#: src/components/status.jsx:87
#: src/pages/settings.jsx:318
msgid "Public"
msgstr ""
@ -750,25 +755,25 @@ msgstr ""
#: src/components/compose.jsx:1272
#: src/components/nav-menu.jsx:349
#: src/components/shortcuts-settings.jsx:165
#: src/components/status.jsx:102
#: src/components/status.jsx:88
msgid "Local"
msgstr ""
#: src/components/compose.jsx:1276
#: src/components/status.jsx:103
#: src/components/status.jsx:89
#: src/pages/settings.jsx:321
msgid "Unlisted"
msgstr ""
#: src/components/compose.jsx:1279
#: src/components/status.jsx:104
#: src/components/status.jsx:90
#: src/pages/settings.jsx:324
msgid "Followers only"
msgstr ""
#: src/components/compose.jsx:1282
#: src/components/status.jsx:105
#: src/components/status.jsx:2218
#: src/components/status.jsx:91
#: src/components/status.jsx:1983
msgid "Private mention"
msgstr ""
@ -805,10 +810,10 @@ msgstr "Schedule"
#: src/components/compose.jsx:1671
#: src/components/keyboard-shortcuts-help.jsx:155
#: src/components/status.jsx:1200
#: src/components/status.jsx:1986
#: src/components/status.jsx:1987
#: src/components/status.jsx:2682
#: src/components/status.jsx:965
#: src/components/status.jsx:1751
#: src/components/status.jsx:1752
#: src/components/status.jsx:2447
msgid "Reply"
msgstr ""
@ -1030,7 +1035,7 @@ msgstr ""
#: src/components/drafts.jsx:126
#: src/components/list-add-edit.jsx:188
#: src/components/status.jsx:1603
#: src/components/status.jsx:1368
#: src/pages/filters.jsx:603
#: src/pages/scheduled-posts.jsx:368
msgid "Delete…"
@ -1239,10 +1244,10 @@ msgid "<0>l</0> or <1>f</1>"
msgstr ""
#: src/components/keyboard-shortcuts-help.jsx:176
#: src/components/status.jsx:1208
#: src/components/status.jsx:2709
#: src/components/status.jsx:2732
#: src/components/status.jsx:2733
#: src/components/status.jsx:973
#: src/components/status.jsx:2474
#: src/components/status.jsx:2497
#: src/components/status.jsx:2498
msgid "Boost"
msgstr ""
@ -1251,9 +1256,9 @@ msgid "<0>Shift</0> + <1>b</1>"
msgstr ""
#: src/components/keyboard-shortcuts-help.jsx:184
#: src/components/status.jsx:1271
#: src/components/status.jsx:2757
#: src/components/status.jsx:2758
#: src/components/status.jsx:1036
#: src/components/status.jsx:2522
#: src/components/status.jsx:2523
msgid "Bookmark"
msgstr ""
@ -1312,20 +1317,38 @@ msgstr ""
msgid "Posts on this list are hidden from Home/Following"
msgstr "Posts on this list are hidden from Home/Following"
#: src/components/math-block.jsx:132
msgid "Unable to format math"
msgstr "Unable to format math"
#: src/components/math-block.jsx:146
msgid "Math expressions found."
msgstr "Math expressions found."
#. Action to switch from rendered math back to raw (LaTeX) markup
#: src/components/math-block.jsx:149
msgid "Show markup"
msgstr "Show markup"
#. Action to render math expressions from raw (LaTeX) markup
#: src/components/math-block.jsx:154
msgid "Format math"
msgstr "Format math"
#: src/components/media-alt-modal.jsx:48
#: src/components/media.jsx:51
msgid "Media description"
msgstr ""
#: src/components/media-alt-modal.jsx:67
#: src/components/status.jsx:1314
#: src/components/status.jsx:1323
#: src/components/status.jsx:1079
#: src/components/status.jsx:1088
#: src/components/translation-block.jsx:239
msgid "Translate"
msgstr ""
#: src/components/media-alt-modal.jsx:78
#: src/components/status.jsx:1342
#: src/components/status.jsx:1107
msgid "Speak"
msgstr ""
@ -1362,9 +1385,9 @@ msgid "Filtered: {filterTitleStr}"
msgstr ""
#: src/components/media-post.jsx:133
#: src/components/status.jsx:3859
#: src/components/status.jsx:3955
#: src/components/status.jsx:4033
#: src/components/status-compact.jsx:70
#: src/components/status.jsx:2880
#: src/components/status.jsx:2958
#: src/components/timeline.jsx:1004
#: src/pages/catchup.jsx:75
#: src/pages/catchup.jsx:1880
@ -1676,8 +1699,8 @@ msgid "[Unknown notification type: {type}]"
msgstr ""
#: src/components/notification.jsx:451
#: src/components/status.jsx:1285
#: src/components/status.jsx:1295
#: src/components/status.jsx:1050
#: src/components/status.jsx:1060
msgid "Boosted/Liked by…"
msgstr ""
@ -1703,7 +1726,7 @@ msgid "View #Wrapstodon"
msgstr "View #Wrapstodon"
#: src/components/notification.jsx:801
#: src/components/status.jsx:486
#: src/components/status.jsx:260
msgid "Read more →"
msgstr ""
@ -1766,6 +1789,68 @@ msgstr ""
msgid "Ending"
msgstr ""
#: src/components/post-embed-modal.jsx:201
#: src/components/status.jsx:1237
msgid "Embed post"
msgstr ""
#: src/components/post-embed-modal.jsx:206
msgid "HTML Code"
msgstr ""
#: src/components/post-embed-modal.jsx:223
msgid "HTML code copied"
msgstr ""
#: src/components/post-embed-modal.jsx:226
msgid "Unable to copy HTML code"
msgstr ""
#: src/components/post-embed-modal.jsx:238
msgid "Media attachments:"
msgstr ""
#: src/components/post-embed-modal.jsx:260
msgid "Account Emojis:"
msgstr ""
#: src/components/post-embed-modal.jsx:291
#: src/components/post-embed-modal.jsx:336
msgid "static URL"
msgstr ""
#: src/components/post-embed-modal.jsx:305
msgid "Emojis:"
msgstr ""
#: src/components/post-embed-modal.jsx:350
msgid "Notes:"
msgstr ""
#: src/components/post-embed-modal.jsx:354
msgid "This is static, unstyled and scriptless. You may need to apply your own styles and edit as needed."
msgstr ""
#: src/components/post-embed-modal.jsx:360
msgid "Polls are not interactive, becomes a list with vote counts."
msgstr ""
#: src/components/post-embed-modal.jsx:365
msgid "Media attachments can be images, videos, audios or any file types."
msgstr ""
#: src/components/post-embed-modal.jsx:371
msgid "Post could be edited or deleted later."
msgstr ""
#: src/components/post-embed-modal.jsx:377
msgid "Preview"
msgstr ""
#: src/components/post-embed-modal.jsx:386
msgid "Note: This preview is lightly styled."
msgstr ""
#: src/components/recent-searches.jsx:27
msgid "Cleared recent searches"
msgstr "Cleared recent searches"
@ -2030,7 +2115,7 @@ msgid "Move down"
msgstr ""
#: src/components/shortcuts-settings.jsx:379
#: src/components/status.jsx:1565
#: src/components/status.jsx:1330
#: src/pages/list.jsx:195
msgid "Edit"
msgstr ""
@ -2229,356 +2314,271 @@ msgstr ""
msgid "Import/export settings from/to instance server (Very experimental)"
msgstr ""
#: src/components/status.jsx:351
msgid "Unable to format math"
msgstr "Unable to format math"
#: src/components/status.jsx:365
msgid "Math expressions found."
msgstr "Math expressions found."
#. Action to switch from rendered math back to raw (LaTeX) markup
#: src/components/status.jsx:368
msgid "Show markup"
msgstr "Show markup"
#. Action to render math expressions from raw (LaTeX) markup
#: src/components/status.jsx:373
msgid "Format math"
msgstr "Format math"
#: src/components/status.jsx:778
#: src/components/status.jsx:543
msgid "<0/> <1>boosted</1>"
msgstr "<0/> <1>boosted</1>"
#: src/components/status.jsx:881
#: src/components/status.jsx:646
msgid "Sorry, your current logged-in instance can't interact with this post from another instance."
msgstr ""
#. placeholder {0}: username || acct
#: src/components/status.jsx:1035
#: src/components/status.jsx:800
msgid "Unliked @{0}'s post"
msgstr ""
#. placeholder {0}: username || acct
#: src/components/status.jsx:1036
#: src/components/status.jsx:801
msgid "Liked @{0}'s post"
msgstr "Liked @{0}'s post"
#. placeholder {0}: username || acct
#: src/components/status.jsx:1075
#: src/components/status.jsx:840
msgid "Unbookmarked @{0}'s post"
msgstr "Unbookmarked @{0}'s post"
#. placeholder {0}: username || acct
#: src/components/status.jsx:1076
#: src/components/status.jsx:841
msgid "Bookmarked @{0}'s post"
msgstr "Bookmarked @{0}'s post"
#: src/components/status.jsx:1177
#: src/components/status.jsx:942
msgid "Some media have no descriptions."
msgstr ""
#. placeholder {0}: rtf.format(-statusMonthsAgo, 'month')
#: src/components/status.jsx:1184
#: src/components/status.jsx:949
msgid "Old post (<0>{0}</0>)"
msgstr ""
#: src/components/status.jsx:1208
#: src/components/status.jsx:1248
#: src/components/status.jsx:2709
#: src/components/status.jsx:2732
#: src/components/status.jsx:973
#: src/components/status.jsx:1013
#: src/components/status.jsx:2474
#: src/components/status.jsx:2497
msgid "Unboost"
msgstr ""
#: src/components/status.jsx:1224
#: src/components/status.jsx:2724
#: src/components/status.jsx:989
#: src/components/status.jsx:2489
msgid "Quote"
msgstr ""
#. placeholder {0}: username || acct
#: src/components/status.jsx:1236
#: src/components/status.jsx:1702
#: src/components/status.jsx:1001
#: src/components/status.jsx:1467
msgid "Unboosted @{0}'s post"
msgstr "Unboosted @{0}'s post"
#. placeholder {0}: username || acct
#: src/components/status.jsx:1237
#: src/components/status.jsx:1703
#: src/components/status.jsx:1002
#: src/components/status.jsx:1468
msgid "Boosted @{0}'s post"
msgstr "Boosted @{0}'s post"
#: src/components/status.jsx:1249
#: src/components/status.jsx:1014
msgid "Boost…"
msgstr ""
#: src/components/status.jsx:1261
#: src/components/status.jsx:1996
#: src/components/status.jsx:2745
#: src/components/status.jsx:1026
#: src/components/status.jsx:1761
#: src/components/status.jsx:2510
msgid "Unlike"
msgstr ""
#: src/components/status.jsx:1262
#: src/components/status.jsx:1996
#: src/components/status.jsx:1997
#: src/components/status.jsx:2745
#: src/components/status.jsx:2746
#: src/components/status.jsx:1027
#: src/components/status.jsx:1761
#: src/components/status.jsx:1762
#: src/components/status.jsx:2510
#: src/components/status.jsx:2511
msgid "Like"
msgstr ""
#: src/components/status.jsx:1271
#: src/components/status.jsx:2757
#: src/components/status.jsx:1036
#: src/components/status.jsx:2522
msgid "Unbookmark"
msgstr ""
#: src/components/status.jsx:1354
#: src/components/status.jsx:1119
msgid "Post text copied"
msgstr "Post text copied"
#: src/components/status.jsx:1357
#: src/components/status.jsx:1122
msgid "Unable to copy post text"
msgstr "Unable to copy post text"
#: src/components/status.jsx:1363
#: src/components/status.jsx:1128
msgid "Copy post text"
msgstr "Copy post text"
#. placeholder {0}: username || acct
#: src/components/status.jsx:1381
#: src/components/status.jsx:1146
msgid "View post by <0>@{0}</0>"
msgstr ""
#: src/components/status.jsx:1402
#: src/components/status.jsx:1167
msgid "Show Edit History"
msgstr ""
#: src/components/status.jsx:1405
#: src/components/status.jsx:1170
msgid "Edited: {editedDateText}"
msgstr ""
#: src/components/status.jsx:1472
#: src/components/status.jsx:3525
msgid "Embed post"
msgstr ""
#: src/components/status.jsx:1486
#: src/components/status.jsx:1251
msgid "Conversation unmuted"
msgstr ""
#: src/components/status.jsx:1486
#: src/components/status.jsx:1251
msgid "Conversation muted"
msgstr ""
#: src/components/status.jsx:1492
#: src/components/status.jsx:1257
msgid "Unable to unmute conversation"
msgstr ""
#: src/components/status.jsx:1493
#: src/components/status.jsx:1258
msgid "Unable to mute conversation"
msgstr ""
#: src/components/status.jsx:1502
#: src/components/status.jsx:1267
msgid "Unmute conversation"
msgstr ""
#: src/components/status.jsx:1509
#: src/components/status.jsx:1274
msgid "Mute conversation"
msgstr ""
#: src/components/status.jsx:1525
#: src/components/status.jsx:1290
msgid "Post unpinned from profile"
msgstr ""
#: src/components/status.jsx:1526
#: src/components/status.jsx:1291
msgid "Post pinned to profile"
msgstr ""
#: src/components/status.jsx:1531
#: src/components/status.jsx:1296
msgid "Unable to unpin post"
msgstr ""
#: src/components/status.jsx:1531
#: src/components/status.jsx:1296
msgid "Unable to pin post"
msgstr ""
#: src/components/status.jsx:1540
#: src/components/status.jsx:1305
msgid "Unpin from profile"
msgstr ""
#: src/components/status.jsx:1547
#: src/components/status.jsx:1312
msgid "Pin to profile"
msgstr ""
#: src/components/status.jsx:1576
#: src/components/status.jsx:1341
msgid "Delete this post?"
msgstr ""
#: src/components/status.jsx:1592
#: src/components/status.jsx:1357
msgid "Post deleted"
msgstr ""
#: src/components/status.jsx:1595
#: src/components/status.jsx:1360
msgid "Unable to delete post"
msgstr ""
#: src/components/status.jsx:1623
#: src/components/status.jsx:1388
msgid "Report post…"
msgstr ""
#: src/components/status.jsx:1997
#: src/components/status.jsx:2033
#: src/components/status.jsx:2746
#: src/components/status.jsx:1762
#: src/components/status.jsx:1798
#: src/components/status.jsx:2511
msgid "Liked"
msgstr ""
#: src/components/status.jsx:2030
#: src/components/status.jsx:2733
#: src/components/status.jsx:1795
#: src/components/status.jsx:2498
msgid "Boosted"
msgstr ""
#: src/components/status.jsx:2040
#: src/components/status.jsx:2758
#: src/components/status.jsx:1805
#: src/components/status.jsx:2523
msgid "Bookmarked"
msgstr ""
#: src/components/status.jsx:2044
#: src/components/status.jsx:1809
msgid "Pinned"
msgstr ""
#: src/components/status.jsx:2096
#: src/components/status.jsx:2571
#: src/components/status.jsx:1861
#: src/components/status.jsx:2336
msgid "Deleted"
msgstr ""
#: src/components/status.jsx:2137
#: src/components/status.jsx:1902
msgid "{repliesCount, plural, one {# reply} other {# replies}}"
msgstr ""
#: src/components/status.jsx:2301
#: src/components/status.jsx:2363
#: src/components/status.jsx:2467
#: src/components/status.jsx:2066
#: src/components/status.jsx:2128
#: src/components/status.jsx:2232
msgid "Show less"
msgstr ""
#: src/components/status.jsx:2301
#: src/components/status.jsx:2363
#: src/components/status.jsx:2066
#: src/components/status.jsx:2128
msgid "Show content"
msgstr ""
#. placeholder {0}: filterInfo.titlesStr
#. placeholder {0}: filterInfo?.titlesStr
#: src/components/status.jsx:2463
#: src/components/status.jsx:2228
#: src/pages/catchup.jsx:1879
msgid "Filtered: {0}"
msgstr "Filtered: {0}"
#: src/components/status.jsx:2467
#: src/components/status.jsx:2232
msgid "Show media"
msgstr ""
#: src/components/status.jsx:2606
#: src/components/status.jsx:2371
msgid "Edited"
msgstr ""
#: src/components/status.jsx:2683
#: src/components/status.jsx:2448
msgid "Comments"
msgstr ""
#. More from [Author]
#: src/components/status.jsx:2983
msgid "More from <0/>"
msgstr "More from <0/>"
#: src/components/status.jsx:2633
msgid "Post hidden by your filters"
msgstr "Post hidden by your filters"
#: src/components/status.jsx:3285
#: src/components/status.jsx:2634
msgid "Post pending"
msgstr "Post pending"
#: src/components/status.jsx:2635
#: src/components/status.jsx:2636
#: src/components/status.jsx:2637
#: src/components/status.jsx:2638
msgid "Post unavailable"
msgstr "Post unavailable"
#: src/components/status.jsx:2747
msgid "Edit History"
msgstr ""
#: src/components/status.jsx:3289
#: src/components/status.jsx:2751
msgid "Failed to load history"
msgstr ""
#: src/components/status.jsx:3294
#: src/components/status.jsx:2756
#: src/pages/annual-report.jsx:45
msgid "Loading…"
msgstr ""
#: src/components/status.jsx:3530
msgid "HTML Code"
msgstr ""
#: src/components/status.jsx:3547
msgid "HTML code copied"
msgstr ""
#: src/components/status.jsx:3550
msgid "Unable to copy HTML code"
msgstr ""
#: src/components/status.jsx:3562
msgid "Media attachments:"
msgstr ""
#: src/components/status.jsx:3584
msgid "Account Emojis:"
msgstr ""
#: src/components/status.jsx:3615
#: src/components/status.jsx:3660
msgid "static URL"
msgstr ""
#: src/components/status.jsx:3629
msgid "Emojis:"
msgstr ""
#: src/components/status.jsx:3674
msgid "Notes:"
msgstr ""
#: src/components/status.jsx:3678
msgid "This is static, unstyled and scriptless. You may need to apply your own styles and edit as needed."
msgstr ""
#: src/components/status.jsx:3684
msgid "Polls are not interactive, becomes a list with vote counts."
msgstr ""
#: src/components/status.jsx:3689
msgid "Media attachments can be images, videos, audios or any file types."
msgstr ""
#: src/components/status.jsx:3695
msgid "Post could be edited or deleted later."
msgstr ""
#: src/components/status.jsx:3701
msgid "Preview"
msgstr ""
#: src/components/status.jsx:3710
msgid "Note: This preview is lightly styled."
msgstr ""
#. [Name] [Visibility icon] boosted
#: src/components/status.jsx:3963
#: src/components/status.jsx:2888
msgid "<0/> <1/> boosted"
msgstr "<0/> <1/> boosted"
#: src/components/status.jsx:4065
msgid "Post hidden by your filters"
msgstr "Post hidden by your filters"
#: src/components/status.jsx:4066
msgid "Post pending"
msgstr "Post pending"
#: src/components/status.jsx:4067
#: src/components/status.jsx:4068
#: src/components/status.jsx:4069
#: src/components/status.jsx:4070
msgid "Post unavailable"
msgstr "Post unavailable"
#: src/components/thread-badge.jsx:22
#: src/components/thread-badge.jsx:37
#: src/components/thread-badge.jsx:52

Wyświetl plik

@ -1,3 +1,4 @@
import mem from './mem';
import store from './store';
export function getAccounts() {
@ -50,6 +51,16 @@ export function getCurrentAccountID() {
return null;
}
// Memoized version of getCurrentAccountID for performance
export const getCurrentAccID = mem(
() => {
return getCurrentAccountID();
},
{
maxAge: 60 * 1000, // 1 minute
},
);
export function setCurrentAccountID(id) {
try {
store.session.set('currentAccount', id);