Merge pull request #143 from cheeaun/main

Update from main
This commit is contained in:
Chee Aun 2023-05-22 23:40:44 +08:00 committed by GitHub
commit 0a5d7267d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 613 additions and 498 deletions

716
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,11 +24,11 @@
"masto": "~5.11.3", "masto": "~5.11.3",
"mem": "~9.0.2", "mem": "~9.0.2",
"p-retry": "~5.1.2", "p-retry": "~5.1.2",
"p-throttle": "~5.0.0", "p-throttle": "~5.1.0",
"preact": "~10.13.2", "preact": "~10.15.0",
"react-hotkeys-hook": "~4.4.0", "react-hotkeys-hook": "~4.4.0",
"react-intersection-observer": "~9.4.3", "react-intersection-observer": "~9.4.3",
"react-quick-pinch-zoom": "~4.6.0", "react-quick-pinch-zoom": "~4.9.0",
"react-router-dom": "6.6.2", "react-router-dom": "6.6.2",
"string-length": "~5.0.1", "string-length": "~5.0.1",
"swiped-events": "~1.1.7", "swiped-events": "~1.1.7",
@ -44,12 +44,12 @@
"@trivago/prettier-plugin-sort-imports": "~4.1.1", "@trivago/prettier-plugin-sort-imports": "~4.1.1",
"postcss": "~8.4.23", "postcss": "~8.4.23",
"postcss-dark-theme-class": "~0.7.3", "postcss-dark-theme-class": "~0.7.3",
"postcss-preset-env": "~8.3.2", "postcss-preset-env": "~8.4.1",
"twitter-text": "~3.1.0", "twitter-text": "~3.1.0",
"vite": "~4.3.5", "vite": "~4.3.8",
"vite-plugin-generate-file": "~0.0.4", "vite-plugin-generate-file": "~0.0.4",
"vite-plugin-html-config": "~1.0.11", "vite-plugin-html-config": "~1.0.11",
"vite-plugin-pwa": "~0.14.7", "vite-plugin-pwa": "~0.15.0",
"vite-plugin-remove-console": "~2.1.1", "vite-plugin-remove-console": "~2.1.1",
"workbox-cacheable-response": "~6.5.4", "workbox-cacheable-response": "~6.5.4",
"workbox-expiration": "~6.5.4", "workbox-expiration": "~6.5.4",

View file

@ -59,8 +59,9 @@ registerRoute(iconsRoute);
// - /api/v1/custom_emojis // - /api/v1/custom_emojis
// - /api/v1/preferences // - /api/v1/preferences
// - /api/v1/lists/:id // - /api/v1/lists/:id
// - /api/v1/announcements
const apiExtendedRoute = new RegExpRoute( const apiExtendedRoute = new RegExpRoute(
/^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+)$/, /^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+|announcements)$/,
new StaleWhileRevalidate({ new StaleWhileRevalidate({
cacheName: 'api-extended', cacheName: 'api-extended',
plugins: [ plugins: [

View file

@ -262,10 +262,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
inset 0 3px var(--comment-line-color); inset 0 3px var(--comment-line-color);
} }
.timeline.contextual .replies[data-comments-level='4'] { .timeline.contextual .replies[data-comments-level='4'] {
overflow: auto; overflow-x: auto;
} }
.timeline.contextual .replies[data-comments-level='4']:has(.replies) { .timeline.contextual .replies[data-comments-level='4']:has(.replies) {
overflow: auto; overflow-x: auto;
mask-image: linear-gradient(to left, transparent, black 32px); mask-image: linear-gradient(to left, transparent, black 32px);
} }
.timeline.contextual .timeline.contextual

View file

@ -91,7 +91,9 @@ function App() {
useEffect(() => { useEffect(() => {
const instanceURL = store.local.get('instanceURL'); const instanceURL = store.local.get('instanceURL');
const code = (window.location.search.match(/code=([^&]+)/) || [])[1]; const code = decodeURIComponent(
(window.location.search.match(/code=([^&]+)/) || [, ''])[1],
);
if (code) { if (code) {
console.log({ code }); console.log({ code });

3
src/assets/phanpy-bg.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 90 85">
<path fill="#fff" fill-opacity="0.4" fill-rule="evenodd" d="M88 64c6.4-29.6-2.4-55.2-31.7-62.7C31.9-4.9 8.1 11.6.8 42c-3.3 13.8 3.3 30.3 24.6 35.6l19.3 4.8c14.5 4 24.3 2.3 30.7-1.1a25 25 0 0 0 12.7-17.4Zm-8-1.8c5.4-24.7-1.1-46.7-25.7-53C34 4 14.8 18.6 8.8 44c-2.5 10.2 2.9 22 18.6 25.8l19.4 4.8c23.4 6.4 31.4-3.4 33.3-12.3ZM35.8 28.4c-3-1.4-14.5 6.4-15.3 17.8-.4 4.8 9 6.5 10.1-.3.7-4.8 2.6-9 4-12s2.3-5 1.2-5.5Zm19.8 15c-.5-5.9-1-10.6 1.7-11 4-.7 10.4 15.3 8.2 25.4-.6 3-9.6 1.6-9.4-4 .2-3.5-.2-7.1-.5-10.4Z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 614 B

View file

@ -493,7 +493,7 @@ function RelatedActions({ info, instance, authenticated }) {
> >
<div class="shazam-container-inner"> <div class="shazam-container-inner">
<p> <p>
Also followed by{' '} Followed by{' '}
<span class="ib"> <span class="ib">
{familiarFollowers.map((follower) => ( {familiarFollowers.map((follower) => (
<a <a

View file

@ -388,6 +388,7 @@
background-color: var(--bg-faded-color); background-color: var(--bg-faded-color);
border-radius: 8px; border-radius: 8px;
margin: 8px 0 0; margin: 8px 0 0;
display: block;
} }
#compose-container .poll-choices { #compose-container .poll-choices {

View file

@ -1,6 +1,5 @@
import './compose.css'; import './compose.css';
import { match } from '@formatjs/intl-localematcher';
import '@github/text-expander-element'; import '@github/text-expander-element';
import equal from 'fast-deep-equal'; import equal from 'fast-deep-equal';
import { forwardRef } from 'preact/compat'; import { forwardRef } from 'preact/compat';
@ -16,6 +15,7 @@ import urlRegex from '../data/url-regex';
import { api } from '../utils/api'; import { api } from '../utils/api';
import db from '../utils/db'; import db from '../utils/db';
import emojifyText from '../utils/emojify-text'; import emojifyText from '../utils/emojify-text';
import localeMatch from '../utils/locale-match';
import openCompose from '../utils/open-compose'; import openCompose from '../utils/open-compose';
import states, { saveStatus } from '../utils/states'; import states, { saveStatus } from '../utils/states';
import store from '../utils/store'; import store from '../utils/store';
@ -85,7 +85,7 @@ const observer = new IntersectionObserver((entries) => {
}); });
observer.observe(menu); observer.observe(menu);
const DEFAULT_LANG = match( const DEFAULT_LANG = localeMatch(
[new Intl.DateTimeFormat().resolvedOptions().locale, ...navigator.languages], [new Intl.DateTimeFormat().resolvedOptions().locale, ...navigator.languages],
supportedLanguages.map((l) => l[0]), supportedLanguages.map((l) => l[0]),
'en', 'en',

View file

@ -0,0 +1,43 @@
@media (min-width: 23em) {
.nav-menu {
display: flex;
width: auto;
padding: 0;
}
.nav-menu section {
padding: 8px 0;
width: 50%;
}
@keyframes phanpying {
0% {
background-position: 0 0, 0 0, 3em 85%;
}
100% {
background-position: 0 0, 0 0, 2em 90%;
}
}
.nav-menu section:last-child {
background-color: var(--bg-faded-color);
background-image: linear-gradient(
to right,
var(--divider-color) 1px,
transparent 1px
),
linear-gradient(to bottom left, var(--bg-blur-color), transparent),
url(../assets/phanpy-bg.svg);
background-repeat: no-repeat;
/* background-size: auto, auto, 200%; */
background-position: 0 0, 0 0, 2em 90%;
background-blend-mode: normal, normal, overlay;
box-shadow: inset 0 0 1px var(--bg-color);
position: sticky;
top: 0;
animation: phanpying 0.2s ease-in-out both;
}
.nav-menu section:last-child > .szh-menu__divider:first-child {
display: none;
}
.nav-menu .szh-menu__item span {
white-space: normal;
}
}

View file

@ -1,11 +1,7 @@
import { import './nav-menu.css';
ControlledMenu,
MenuDivider, import { ControlledMenu, MenuDivider, MenuItem } from '@szhsin/react-menu';
MenuItem, import { useRef, useState } from 'preact/hooks';
useClick,
useMenuState,
} from '@szhsin/react-menu';
import { useRef } from 'preact/hooks';
import { useLongPress } from 'use-long-press'; import { useLongPress } from 'use-long-press';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
@ -47,8 +43,7 @@ function NavMenu(props) {
); );
const buttonRef = useRef(); const buttonRef = useRef();
const [menuState, toggleMenu] = useMenuState(); const [menuState, setMenuState] = useState(undefined);
const anchorProps = useClick(menuState.state, toggleMenu);
return ( return (
<> <>
@ -59,7 +54,9 @@ function NavMenu(props) {
moreThanOneAccount ? 'with-avatar' : '' moreThanOneAccount ? 'with-avatar' : ''
} ${open ? 'active' : ''}`} } ${open ? 'active' : ''}`}
style={{ position: 'relative' }} style={{ position: 'relative' }}
{...anchorProps} onClick={() => {
setMenuState((state) => (!state ? 'open' : undefined));
}}
onContextMenu={(e) => { onContextMenu={(e) => {
e.preventDefault(); e.preventDefault();
states.showAccounts = true; states.showAccounts = true;
@ -78,17 +75,18 @@ function NavMenu(props) {
<Icon icon="menu" size={moreThanOneAccount ? 's' : 'l'} /> <Icon icon="menu" size={moreThanOneAccount ? 's' : 'l'} />
</button> </button>
<ControlledMenu <ControlledMenu
{...menuState} menuClassName="nav-menu"
state={menuState}
anchorRef={buttonRef} anchorRef={buttonRef}
onClose={() => { onClose={() => {
toggleMenu(false); setMenuState(undefined);
}} }}
containerProps={{ containerProps={{
style: { style: {
zIndex: 10, zIndex: 10,
}, },
onClick: () => { onClick: () => {
toggleMenu(false); setMenuState(undefined);
}, },
}} }}
portal={{ portal={{
@ -102,6 +100,7 @@ function NavMenu(props) {
boundingBoxPadding="8 8 8 8" boundingBoxPadding="8 8 8 8"
unmountOnClose unmountOnClose
> >
<section>
{!!snapStates.appVersion?.commitHash && {!!snapStates.appVersion?.commitHash &&
__COMMIT_HASH__ !== snapStates.appVersion.commitHash && ( __COMMIT_HASH__ !== snapStates.appVersion.commitHash && (
<> <>
@ -173,6 +172,8 @@ function NavMenu(props) {
<MenuLink to={`/${instance}/trending`}> <MenuLink to={`/${instance}/trending`}>
<Icon icon="chart" size="l" /> <span>Trending</span> <Icon icon="chart" size="l" /> <span>Trending</span>
</MenuLink> </MenuLink>
</section>
<section>
{authenticated && ( {authenticated && (
<> <>
<MenuDivider /> <MenuDivider />
@ -205,6 +206,7 @@ function NavMenu(props) {
</MenuItem> </MenuItem>
</> </>
)} )}
</section>
</ControlledMenu> </ControlledMenu>
</> </>
); );

View file

@ -518,7 +518,7 @@
.status .content > div > :is(ul, ol) { .status .content > div > :is(ul, ol) {
margin-block: min(0.75em, 12px); margin-block: min(0.75em, 12px);
margin-inline: 0; margin-inline: 0;
padding-inline-start: 1em; padding-inline-start: 1.5em;
} }
.status .content .invisible { .status .content .invisible {
display: none; display: none;

View file

@ -1,6 +1,5 @@
import './status.css'; import './status.css';
import { match } from '@formatjs/intl-localematcher';
import '@justinribeiro/lite-youtube'; import '@justinribeiro/lite-youtube';
import { import {
ControlledMenu, ControlledMenu,
@ -33,6 +32,7 @@ import getHTMLText from '../utils/getHTMLText';
import handleContentLinks from '../utils/handle-content-links'; import handleContentLinks from '../utils/handle-content-links';
import htmlContentLength from '../utils/html-content-length'; import htmlContentLength from '../utils/html-content-length';
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe'; import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
import localeMatch from '../utils/locale-match';
import niceDateTime from '../utils/nice-date-time'; import niceDateTime from '../utils/nice-date-time';
import shortenNumber from '../utils/shorten-number'; import shortenNumber from '../utils/shorten-number';
import showToast from '../utils/show-toast'; import showToast from '../utils/show-toast';
@ -105,10 +105,11 @@ function Status({
const { instance: currentInstance } = api(); const { instance: currentInstance } = api();
const sameInstance = instance === currentInstance; const sameInstance = instance === currentInstance;
const sKey = statusKey(statusID, instance); let sKey = statusKey(statusID, instance);
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
if (!status) { if (!status) {
status = snapStates.statuses[sKey] || snapStates.statuses[statusID]; status = snapStates.statuses[sKey] || snapStates.statuses[statusID];
sKey = statusKey(status?.id, instance);
} }
if (!status) { if (!status) {
return null; return null;
@ -408,9 +409,9 @@ function Status({
const differentLanguage = const differentLanguage =
language && language &&
language !== targetLanguage && language !== targetLanguage &&
!match([language], [targetLanguage]) && !localeMatch([language], [targetLanguage]) &&
!contentTranslationHideLanguages.find( !contentTranslationHideLanguages.find(
(l) => language === l || match([language], [l]), (l) => language === l || localeMatch([language], [l]),
); );
const menuInstanceRef = useRef(); const menuInstanceRef = useRef();
@ -977,6 +978,7 @@ function Status({
(result) => { (result) => {
if (!result) return; if (!result) return;
a.removeAttribute('target'); a.removeAttribute('target');
if (!sKey) return;
if (!Array.isArray(states.statusQuotes[sKey])) { if (!Array.isArray(states.statusQuotes[sKey])) {
states.statusQuotes[sKey] = []; states.statusQuotes[sKey] = [];
} }
@ -1102,7 +1104,7 @@ function Status({
<> <>
<Icon <Icon
icon={visibilityIconsMap[visibility]} icon={visibilityIconsMap[visibility]}
alt={visibility} alt={visibilityText[visibility]}
/>{' '} />{' '}
<a href={url} target="_blank"> <a href={url} target="_blank">
<time <time
@ -1954,6 +1956,7 @@ function FilteredStatus({ status, filterInfo, instance, containerProps = {} }) {
} }
const QuoteStatuses = memo(({ id, instance, level = 0 }) => { const QuoteStatuses = memo(({ id, instance, level = 0 }) => {
if (!id || !instance) return;
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
const sKey = statusKey(id, instance); const sKey = statusKey(id, instance);
const quotes = snapStates.statusQuotes[sKey]; const quotes = snapStates.statusQuotes[sKey];

View file

@ -103,14 +103,11 @@ function Notifications() {
setUIState('loading'); setUIState('loading');
(async () => { (async () => {
try { try {
const fetchNotificationsPromise = fetchNotifications(firstLoad);
const fetchFollowRequestsPromise = fetchFollowRequests(); const fetchFollowRequestsPromise = fetchFollowRequests();
const fetchAnnouncementsPromise = fetchAnnouncements(); const fetchAnnouncementsPromise = fetchAnnouncements();
const { done } = await fetchNotifications(firstLoad);
setShowMore(!done);
if (firstLoad) { if (firstLoad) {
const requests = await fetchFollowRequestsPromise;
setFollowRequests(requests);
const announcements = await fetchAnnouncementsPromise; const announcements = await fetchAnnouncementsPromise;
announcements.sort((a, b) => { announcements.sort((a, b) => {
// Sort by updatedAt first, then createdAt // Sort by updatedAt first, then createdAt
@ -119,8 +116,13 @@ function Notifications() {
return bDate - aDate; return bDate - aDate;
}); });
setAnnouncements(announcements); setAnnouncements(announcements);
const requests = await fetchFollowRequestsPromise;
setFollowRequests(requests);
} }
const { done } = await fetchNotificationsPromise;
setShowMore(!done);
setUIState('default'); setUIState('default');
} catch (e) { } catch (e) {
setUIState('error'); setUIState('error');

View file

@ -248,10 +248,17 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
totalDescendants.current = descendants?.length || 0; totalDescendants.current = descendants?.length || 0;
const missingStatuses = new Set();
ancestors.forEach((status) => { ancestors.forEach((status) => {
saveStatus(status, instance, { saveStatus(status, instance, {
skipThreading: true, skipThreading: true,
}); });
if (
status.inReplyToId &&
!ancestors.find((s) => s.id === status.inReplyToId)
) {
missingStatuses.add(status.inReplyToId);
}
}); });
const ancestorsIsThread = ancestors.every( const ancestorsIsThread = ancestors.every(
(s) => s.account.id === heroStatus.account.id, (s) => s.account.id === heroStatus.account.id,
@ -261,6 +268,15 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
saveStatus(status, instance, { saveStatus(status, instance, {
skipThreading: true, skipThreading: true,
}); });
if (
status.inReplyToId &&
!descendants.find((s) => s.id === status.inReplyToId) &&
status.inReplyToId !== heroStatus.id
) {
missingStatuses.add(status.inReplyToId);
}
if (status.inReplyToAccountId === status.account.id) { if (status.inReplyToAccountId === status.account.id) {
// If replying to self, it's part of the thread, level 1 // If replying to self, it's part of the thread, level 1
nestedDescendants.push(status); nestedDescendants.push(status);
@ -290,6 +306,9 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
}); });
console.log({ ancestors, descendants, nestedDescendants }); console.log({ ancestors, descendants, nestedDescendants });
if (missingStatuses.size) {
console.error('Missing statuses', [...missingStatuses]);
}
function expandReplies(_replies) { function expandReplies(_replies) {
return _replies?.map((_r) => ({ return _replies?.map((_r) => ({
@ -591,14 +610,17 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
const initialPageState = useRef(showMedia ? 'media+status' : 'status'); const initialPageState = useRef(showMedia ? 'media+status' : 'status');
const handleMediaClick = useCallback((e, i, media, status) => { const handleMediaClick = useCallback(
(e, i, media, status) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setSearchParams({ setSearchParams({
media: i + 1, media: i + 1,
mediaStatusID: status.id, mediaStatusID: status.id,
}); });
}, []); },
[id],
);
return ( return (
<div <div

View file

@ -1,7 +1,6 @@
import { match } from '@formatjs/intl-localematcher';
import translationTargetLanguages from '../data/lingva-target-languages'; import translationTargetLanguages from '../data/lingva-target-languages';
import localeMatch from './locale-match';
import states from './states'; import states from './states';
function getTranslateTargetLanguage(fromSettings = false) { function getTranslateTargetLanguage(fromSettings = false) {
@ -11,7 +10,7 @@ function getTranslateTargetLanguage(fromSettings = false) {
return contentTranslationTargetLanguage; return contentTranslationTargetLanguage;
} }
} }
return match( return localeMatch(
[ [
new Intl.DateTimeFormat().resolvedOptions().locale, new Intl.DateTimeFormat().resolvedOptions().locale,
...navigator.languages, ...navigator.languages,

View file

@ -0,0 +1,12 @@
import { match } from '@formatjs/intl-localematcher';
function localeMatch(...args) {
// Wrap in try/catch because localeMatcher throws on invalid locales
try {
return match(...args);
} catch (e) {
return false;
}
}
export default localeMatch;

View file

@ -110,6 +110,7 @@ export function hideAllModals() {
} }
export function statusKey(id, instance) { export function statusKey(id, instance) {
if (!id) return;
return instance ? `${instance}/${id}` : id; return instance ? `${instance}/${id}` : id;
} }