Skip to content
Open
495 changes: 250 additions & 245 deletions package-lock.json

Large diffs are not rendered by default.

32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,41 @@
},
"dependencies": {
"@formatjs/intl-localematcher": "~0.8.8",
"@formatjs/intl-segmenter": "~12.2.7",
"@formatjs/intl-segmenter": "~12.2.8",
"@formkit/auto-animate": "~0.9.0",
"@github/text-expander-element": "~2.9.4",
"@iconify-json/mingcute": "~1.2.7",
"@justinribeiro/lite-youtube": "~1.9.0",
"@lingui/detect-locale": "~6.0.1",
"@lingui/react": "~6.0.1",
"@lingui/detect-locale": "~6.1.0",
"@lingui/react": "~6.1.0",
"@szhsin/react-menu": "~4.5.1",
"chroma-js": "~3.2.0",
"compare-versions": "~6.1.1",
"exifreader": "~4.38.1",
"exifreader": "~4.39.1",
"fast-blurhash": "~1.1.4",
"fast-equals": "~6.0.0",
"flexsearch": "~0.8.212",
"fuse.js": "~7.3.0",
"gifuct-js": "~2.1.2",
"html-prettify": "~1.0.7",
"idb-keyval": "~6.2.2",
"idb-keyval": "~6.2.4",
"intl-locale-textinfo-polyfill": "~3.0.0",
"js-cookie": "~3.0.5",
"js-cookie": "~3.0.7",
"just-debounce-it": "~3.2.0",
"lz-string": "~1.5.0",
"masto": "~7.11.1",
"micro-memoize": "~5.1.1",
"p-queue": "~9.2.0",
"p-queue": "~9.3.0",
"p-retry": "~8.0.0",
"preact": "10.29.1",
"preact": "10.29.2",
"punycode": "~2.3.1",
"qr": "~0.6.0",
"react-hotkeys-hook": "~5.2.4",
"react-intersection-observer": "~10.0.3",
"react-quick-pinch-zoom": "~5.1.1",
"react-router-dom": "6.6.2",
"swiped-events": "~1.2.0",
"temml": "~0.13.2",
"temml": "~0.13.3",
"tinyld": "~1.3.4",
"toastify-js": "~1.12.0",
"uid": "~2.0.2",
Expand All @@ -78,20 +78,20 @@
"@emnapi/core": "~1.10.0",
"@emnapi/runtime": "~1.10.0",
"@iconify/utils": "~3.1.3",
"@lingui/babel-plugin-lingui-macro": "~6.0.1",
"@lingui/cli": "~6.0.1",
"@lingui/vite-plugin": "~6.0.1",
"@lingui/babel-plugin-lingui-macro": "~6.1.0",
"@lingui/cli": "~6.1.0",
"@lingui/vite-plugin": "~6.1.0",
"@playwright/test": "~1.60.0",
"@preact/preset-vite": "~2.10.5",
"@types/node": "~25.8.0",
"oxfmt": "~0.49.0",
"postcss": "~8.5.14",
"@types/node": "~25.9.1",
"oxfmt": "~0.52.0",
"postcss": "~8.5.15",
"postcss-dark-theme-class": "~2.0.0",
"postcss-preset-env": "~11.3.0",
"prop-types": "^15.8.1",
"sonda": "~0.11.1",
"twitter-text": "~3.1.0",
"vite": "~8.0.13",
"vite": "~8.0.14",
"vite-plugin-generate-file": "~0.3.1",
"vite-plugin-html-config": "~2.0.2",
"vite-plugin-pwa": "~1.3.0",
Expand Down
30 changes: 17 additions & 13 deletions src/components/background-service.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,30 @@ export default memo(function BackgroundService() {
let sub;
let streamTimeout;
let pollNotifications;
let cancelled = false;
if (isLoggedIn && visible) {
const { masto, streaming, instance } = api();
(async () => {
// 1. Get the latest notification
await checkLatestNotification(masto, instance);

let hasStreaming = false;
// 2. Start streaming
const startPolling = () => {
if (cancelled) return;
console.log('🎏 Fallback to polling');
pollNotifications = setInterval(() => {
checkLatestNotification(masto, instance, true);
}, POLL_INTERVAL);
};

// 2. Start streaming or fall back to polling
if (streaming) {
streamTimeout = setTimeout(() => {
(async () => {
try {
hasStreaming = true;
sub = streaming.user.notification.subscribe();
console.log('🎏 Streaming notification', sub);
for await (const entry of sub) {
if (!sub) break;
if (!visible) break;
if (cancelled || !sub) break;
console.log('🔔🔔 Notification entry', entry);
if (entry.event === 'notification') {
console.log('🔔🔔 Notification', entry);
Expand All @@ -95,22 +101,20 @@ export default memo(function BackgroundService() {
}
console.log('💥 Streaming notification loop STOPPED');
} catch (e) {
hasStreaming = false;
console.error(e);
console.error('💥 Streaming error', e);
}

if (!hasStreaming) {
console.log('🎏 Streaming failed, fallback to polling');
pollNotifications = setInterval(() => {
checkLatestNotification(masto, instance, true);
}, POLL_INTERVAL);
}
startPolling();
})();
}, STREAMING_TIMEOUT);
} else {
console.log('🎏 No streaming available, polling directly');
startPolling();
}
})();
}
return () => {
cancelled = true;
sub?.unsubscribe?.();
sub = null;
clearTimeout(streamTimeout);
Expand Down
28 changes: 27 additions & 1 deletion src/components/media-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ function MediaModal({
};
}, []);

const prevIndexRef = useRef(currentIndex);
useEffect(() => {
const prevIndex = prevIndexRef.current;
prevIndexRef.current = currentIndex;
if (prevIndex === currentIndex) return;

const carousel = carouselRef.current;
if (!carousel) return;

carousel.querySelectorAll('video, audio').forEach((el) => {
if (el.muted) return;
const item = el.closest('.carousel-item');
if (item) {
const idx = Array.from(item.parentNode.children).indexOf(item);
if (idx !== currentIndex) {
el.pause();
}
}
});
}, [currentIndex]);

useEffect(() => {
let timer = setTimeout(() => {
carouselRef.current?.focus?.();
Expand Down Expand Up @@ -319,7 +340,12 @@ function MediaModal({
</span>
</button>
)}
<Media media={media} showOriginal lang={lang} />
<Media
media={media}
showOriginal
lang={lang}
autoplay={i === index}
/>
</div>
);
})}
Expand Down
12 changes: 9 additions & 3 deletions src/components/media.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function Media({
altIndex,
checkAspectRatio = true,
onClick,
autoplay = false,
}) {
let {
id,
Expand Down Expand Up @@ -585,7 +586,7 @@ function Media({
data-orientation="${orientation}"
style="view-transition-name: ${mediaVTN}"
preload="auto"
autoplay
${autoplay ? 'autoplay' : ''}
playsinline
${loopable ? 'loop' : ''}
controls
Expand Down Expand Up @@ -801,11 +802,16 @@ function Media({
preload="metadata"
controls
controlsList="nofullscreen"
autoPlay
autoPlay={autoplay}
playsInline
/>
) : (
<audio src={remoteUrl || url} preload="none" controls autoPlay />
<audio
src={remoteUrl || url}
preload="none"
controls
autoPlay={autoplay}
/>
)
) : previewUrl ? (
<img
Expand Down
7 changes: 6 additions & 1 deletion src/components/related-actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ function RelatedActions({
currentID = id;
} else if (!sameInstance && currentAuthenticated) {
// Grab this account from my logged-in instance
setRelationshipUIState('loading');
const acctHasInstance = info.acct.includes('@');
try {
const results = await currentMasto.v2.search.list({
Expand All @@ -124,11 +125,15 @@ function RelatedActions({
}
}

if (!currentID) return;
if (!currentID) {
setRelationshipUIState('default');
return;
}

if (currentAccount === currentID) {
// It's myself!
setIsSelf(true);
setRelationshipUIState('default');
return;
}

Expand Down
4 changes: 4 additions & 0 deletions src/components/status.css
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,10 @@
filter: contrast(0);
background-color: #000;
}
.title,
.meta {
background-color: currentColor;
}
* {
text-decoration-color: inherit;
text-decoration-thickness: 1.5em;
Expand Down
34 changes: 14 additions & 20 deletions src/components/timeline.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,10 @@ function Timeline({
const ts = (loadItemsTS.current = Date.now());
let { done, value } = await fetchItems(firstLoad);
if (ts !== loadItemsTS.current) return;
if (Array.isArray(value)) {
if (done) {
// Iterator has completed (no more pages)
setShowMore(false);
} else if (Array.isArray(value)) {
// Avoid grouping for pinned posts
const [pinnedPosts, otherPosts] = value.reduce(
(acc, item) => {
Expand Down Expand Up @@ -303,7 +306,8 @@ function Timeline({
if (!value.length) done = true;
setShowMore(!done);
} else {
// Iterator error state returns undefined - treat as error, not end of list
// Iterator returned unexpected data type
console.warn('Unexpected iterator result', { done, value });
throw new Error('Timeline load failed');
}
setUIState('default');
Expand Down Expand Up @@ -703,26 +707,16 @@ export const TimelineItem = memo(
const filteredItemsIDs = new Set();
// Here, we don't hide filtered posts, but we sort them last
fItems.sort((a, b) => {
// if (a._filtered && !b._filtered) {
// return 1;
// }
// if (!a._filtered && b._filtered) {
// return -1;
// }
const aFiltered = isFiltered(a.filtered, filterContext);
const bFiltered = isFiltered(b.filtered, filterContext);
if (aFiltered && aFiltered?.action !== 'blur') {
filteredItemsIDs.add(a.id);
}
if (bFiltered && bFiltered?.action !== 'blur') {
filteredItemsIDs.add(b.id);
}
if (aFiltered && !bFiltered) {
return 1;
}
if (!aFiltered && bFiltered) {
return -1;
}
const aShouldSort = aFiltered && aFiltered.action !== 'blur';
const bShouldSort = bFiltered && bFiltered.action !== 'blur';

if (aShouldSort) filteredItemsIDs.add(a.id);
if (bShouldSort) filteredItemsIDs.add(b.id);

if (aShouldSort && !bShouldSort) return 1;
if (!aShouldSort && bShouldSort) return -1;
return 0;
});

Expand Down
10 changes: 8 additions & 2 deletions src/components/timeline2.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,14 @@ function Timeline2({
const { max_id, min_id } = params;
let { value, originalValue, done } = result;

// Iterator error state returns undefined - treat as error, not end of list
if (value === undefined || value === null) {
if (done && (value === undefined || value === null)) {
// Iterator has completed (no more pages)
setShowOlder(false);
setShowNewer(false);
setUIState('default');
__BENCHMARK.end(`timeline-${id}-load`);
return;
} else if (value === undefined || value === null) {
throw new Error('Timeline load failed');
}

Expand Down
4 changes: 2 additions & 2 deletions src/locales/ca-ES.po

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading