Merge pull request #67 from cheeaun/main

Update from main
This commit is contained in:
Chee Aun 2023-02-22 00:47:07 +08:00 committed by GitHub
commit dda14587c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 1972 additions and 401 deletions

1873
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,7 @@
"mem": "~9.0.2", "mem": "~9.0.2",
"p-retry": "~5.1.2", "p-retry": "~5.1.2",
"preact": "~10.12.1", "preact": "~10.12.1",
"react-hotkeys-hook": "~4.3.5", "react-hotkeys-hook": "~4.3.6",
"react-intersection-observer": "~9.4.2", "react-intersection-observer": "~9.4.2",
"react-router-dom": "6.6.2", "react-router-dom": "6.6.2",
"string-length": "~5.0.1", "string-length": "~5.0.1",
@ -41,7 +41,7 @@
"postcss-dark-theme-class": "~0.7.3", "postcss-dark-theme-class": "~0.7.3",
"postcss-preset-env": "~8.0.1", "postcss-preset-env": "~8.0.1",
"twitter-text": "~3.1.0", "twitter-text": "~3.1.0",
"vite": "~4.1.2", "vite": "~4.1.3",
"vite-plugin-html-config": "~1.0.11", "vite-plugin-html-config": "~1.0.11",
"vite-plugin-html-env": "~1.2.7", "vite-plugin-html-env": "~1.2.7",
"vite-plugin-pwa": "~0.14.4", "vite-plugin-pwa": "~0.14.4",

View file

@ -618,10 +618,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
font-size: calc(100% + 25% * max(2 - var(--content-text-weight), 0)); font-size: calc(100% + 25% * max(2 - var(--content-text-weight), 0));
} }
.status-carousel .status-carousel
.content-container:is( .content-container[data-content-text-weight='1']
[style*='content-text-weight:1'],
[style*='content-text-weight: 1']
)
.media-container.media-eq1 { .media-container.media-eq1 {
/* LOL, this is madness, reading a value from the style attribute */ /* LOL, this is madness, reading a value from the style attribute */
height: auto; height: auto;

View file

@ -1,6 +1,6 @@
import './account.css'; import './account.css';
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import { api } from '../utils/api'; import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text'; import emojifyText from '../utils/emojify-text';
@ -17,12 +17,6 @@ import Link from './link';
function Account({ account, instance: propInstance, onClose }) { function Account({ account, instance: propInstance, onClose }) {
const { masto, instance, authenticated } = api({ instance: propInstance }); const { masto, instance, authenticated } = api({ instance: propInstance });
const {
masto: currentMasto,
instance: currentInstance,
authenticated: currentAuthenticated,
} = api();
const sameInstance = instance === currentInstance;
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');
const isString = typeof account === 'string'; const isString = typeof account === 'string';
const [info, setInfo] = useState(isString ? null : account); const [info, setInfo] = useState(isString ? null : account);
@ -88,89 +82,6 @@ function Account({ account, instance: propInstance, onClose }) {
username, username,
} = info || {}; } = info || {};
const [relationshipUIState, setRelationshipUIState] = useState('default');
const [relationship, setRelationship] = useState(null);
const [familiarFollowers, setFamiliarFollowers] = useState([]);
useEffect(() => {
if (info) {
const currentAccount = store.session.get('currentAccount');
let accountID;
(async () => {
if (sameInstance && authenticated) {
accountID = id;
} else if (!sameInstance && currentAuthenticated) {
// Grab this account from my logged-in instance
const acctHasInstance = info.acct.includes('@');
try {
const results = await currentMasto.v2.search({
q: acctHasInstance ? info.acct : `${info.username}@${instance}`,
type: 'accounts',
limit: 1,
resolve: true,
});
console.log('🥏 Fetched account from logged-in instance', results);
accountID = results.accounts[0].id;
} catch (e) {
console.error(e);
}
}
if (!accountID) return;
if (currentAccount === accountID) {
// It's myself!
return;
}
setRelationshipUIState('loading');
setFamiliarFollowers([]);
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
accountID,
]);
const fetchFamiliarFollowers =
currentMasto.v1.accounts.fetchFamiliarFollowers(accountID);
try {
const relationships = await fetchRelationships;
console.log('fetched relationship', relationships);
if (relationships.length) {
const relationship = relationships[0];
setRelationship(relationship);
if (!relationship.following) {
try {
const followers = await fetchFamiliarFollowers;
console.log('fetched familiar followers', followers);
setFamiliarFollowers(followers[0].accounts.slice(0, 10));
} catch (e) {
console.error(e);
}
}
}
setRelationshipUIState('default');
} catch (e) {
console.error(e);
setRelationshipUIState('error');
}
})();
}
}, [info, authenticated]);
const {
following,
showingReblogs,
notifying,
followedBy,
blocking,
blockedBy,
muting,
mutingNotifications,
requested,
domainBlocking,
endorsed,
} = relationship || {};
return ( return (
<div <div
id="account-container" id="account-container"
@ -300,6 +211,119 @@ function Account({ account, instance: propInstance, onClose }) {
</span> </span>
)} )}
</p> </p>
<RelatedActions
info={info}
instance={instance}
authenticated={authenticated}
/>
</main>
</>
)
)}
</div>
);
}
function RelatedActions({ info, instance, authenticated }) {
if (!info) return null;
const {
masto: currentMasto,
instance: currentInstance,
authenticated: currentAuthenticated,
} = api();
const sameInstance = instance === currentInstance;
const [relationshipUIState, setRelationshipUIState] = useState('default');
const [relationship, setRelationship] = useState(null);
const [familiarFollowers, setFamiliarFollowers] = useState([]);
const { id, locked } = info;
const accountID = useRef(id);
const {
following,
showingReblogs,
notifying,
followedBy,
blocking,
blockedBy,
muting,
mutingNotifications,
requested,
domainBlocking,
endorsed,
} = relationship || {};
useEffect(() => {
if (info) {
const currentAccount = store.session.get('currentAccount');
let currentID;
(async () => {
if (sameInstance && authenticated) {
currentID = id;
} else if (!sameInstance && currentAuthenticated) {
// Grab this account from my logged-in instance
const acctHasInstance = info.acct.includes('@');
try {
const results = await currentMasto.v2.search({
q: acctHasInstance ? info.acct : `${info.username}@${instance}`,
type: 'accounts',
limit: 1,
resolve: true,
});
console.log('🥏 Fetched account from logged-in instance', results);
currentID = results.accounts[0].id;
} catch (e) {
console.error(e);
}
}
if (!currentID) return;
if (currentAccount === currentID) {
// It's myself!
return;
}
accountID.current = currentID;
setRelationshipUIState('loading');
setFamiliarFollowers([]);
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
currentID,
]);
const fetchFamiliarFollowers =
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
try {
const relationships = await fetchRelationships;
console.log('fetched relationship', relationships);
if (relationships.length) {
const relationship = relationships[0];
setRelationship(relationship);
if (!relationship.following) {
try {
const followers = await fetchFamiliarFollowers;
console.log('fetched familiar followers', followers);
setFamiliarFollowers(followers[0].accounts.slice(0, 10));
} catch (e) {
console.error(e);
}
}
}
setRelationshipUIState('default');
} catch (e) {
console.error(e);
setRelationshipUIState('error');
}
})();
}
}, [info, authenticated]);
return (
<>
{familiarFollowers?.length > 0 && ( {familiarFollowers?.length > 0 && (
<p class="common-followers"> <p class="common-followers">
Common followers{' '} Common followers{' '}
@ -336,23 +360,29 @@ function Account({ account, instance: propInstance, onClose }) {
disabled={relationshipUIState === 'loading'} disabled={relationshipUIState === 'loading'}
onClick={() => { onClick={() => {
setRelationshipUIState('loading'); setRelationshipUIState('loading');
(async () => { (async () => {
try { try {
let newRelationship; let newRelationship;
if (following || requested) { if (following || requested) {
const yes = confirm( const yes = confirm(
requested requested
? 'Withdraw follow request?' ? 'Withdraw follow request?'
: `Unfollow @${info.acct || info.username}?`, : `Unfollow @${info.acct || info.username}?`,
); );
if (yes) { if (yes) {
newRelationship = newRelationship = await currentMasto.v1.accounts.unfollow(
await currentMasto.v1.accounts.unfollow(id); accountID.current,
);
} }
} else { } else {
newRelationship = newRelationship = await currentMasto.v1.accounts.follow(
await currentMasto.v1.accounts.follow(id); accountID.current,
);
} }
if (newRelationship) setRelationship(newRelationship); if (newRelationship) setRelationship(newRelationship);
setRelationshipUIState('default'); setRelationshipUIState('default');
} catch (e) { } catch (e) {
@ -382,11 +412,7 @@ function Account({ account, instance: propInstance, onClose }) {
</button> </button>
)} )}
</p> </p>
</main>
</> </>
)
)}
</div>
); );
} }

View file

@ -590,7 +590,8 @@ body:has(#modal-container .carousel) .status .media img:hover {
max-width: 480px; max-width: 480px;
/* max-height: 160px; */ /* max-height: 160px; */
} }
.status.large .card.large { .status.large .card.large,
.status-carousel .content-container[data-content-text-weight='1'] .card.large {
border-radius: 16px; border-radius: 16px;
flex-direction: column; flex-direction: column;
max-height: none; max-height: none;
@ -618,7 +619,11 @@ body:has(#modal-container .carousel) .status .media img:hover {
.status.large .card .card-image { .status.large .card .card-image {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
} }
.status.large .card.large .card-image { .status.large .card.large .card-image,
.status-carousel
.content-container[data-content-text-weight='1']
.card.large
.card-image {
flex-grow: 1; flex-grow: 1;
aspect-ratio: 1.91 / 1; aspect-ratio: 1.91 / 1;
width: 100%; width: 100%;

View file

@ -79,7 +79,7 @@ function Status({
avatar, avatar,
avatarStatic, avatarStatic,
id: accountId, id: accountId,
url, url: accountURL,
displayName, displayName,
username, username,
emojis: accountEmojis, emojis: accountEmojis,
@ -108,6 +108,7 @@ function Status({
mediaAttachments, mediaAttachments,
reblog, reblog,
uri, uri,
url,
emojis, emojis,
// Non-API props // Non-API props
_deleted, _deleted,
@ -128,7 +129,7 @@ function Status({
(mention) => mention.id === inReplyToAccountId, (mention) => mention.id === inReplyToAccountId,
); );
if (!inReplyToAccountRef && inReplyToAccountId === id) { if (!inReplyToAccountRef && inReplyToAccountId === id) {
inReplyToAccountRef = { url, username, displayName }; inReplyToAccountRef = { url: accountURL, username, displayName };
} }
const [inReplyToAccount, setInReplyToAccount] = useState(inReplyToAccountRef); const [inReplyToAccount, setInReplyToAccount] = useState(inReplyToAccountRef);
if (!withinContext && !inReplyToAccount && inReplyToAccountId) { if (!withinContext && !inReplyToAccount && inReplyToAccountId) {
@ -207,6 +208,9 @@ function Status({
const unauthInteractionErrorMessage = `Sorry, your current logged-in instance can't interact with this status from another instance.`; const unauthInteractionErrorMessage = `Sorry, your current logged-in instance can't interact with this status from another instance.`;
const textWeight = () =>
Math.round((spoilerText.length + htmlContentLength(content)) / 140) || 1;
return ( return (
<article <article
ref={statusRef} ref={statusRef}
@ -232,7 +236,7 @@ function Status({
)} )}
{size !== 's' && ( {size !== 's' && (
<a <a
href={url} href={accountURL}
tabindex="-1" tabindex="-1"
// target="_blank" // target="_blank"
title={`@${acct}`} title={`@${acct}`}
@ -268,7 +272,7 @@ function Status({
)} */} )} */}
{/* </span> */}{' '} {/* </span> */}{' '}
{size !== 'l' && {size !== 'l' &&
(uri ? ( (url ? (
<Link <Link
to={instance ? `/${instance}/s/${id}` : `/s/${id}`} to={instance ? `/${instance}/s/${id}` : `/s/${id}`}
class="time" class="time"
@ -325,12 +329,10 @@ function Status({
class={`content-container ${ class={`content-container ${
spoilerText || sensitive ? 'has-spoiler' : '' spoilerText || sensitive ? 'has-spoiler' : ''
} ${showSpoiler ? 'show-spoiler' : ''}`} } ${showSpoiler ? 'show-spoiler' : ''}`}
data-content-text-weight={contentTextWeight ? textWeight() : null}
style={ style={
(size === 'l' || contentTextWeight) && { (size === 'l' || contentTextWeight) && {
'--content-text-weight': '--content-text-weight': textWeight(),
Math.round(
(spoilerText.length + htmlContentLength(content)) / 140,
) || 1,
} }
} }
> >
@ -467,7 +469,7 @@ function Status({
<> <>
<div class="extra-meta"> <div class="extra-meta">
<Icon icon={visibilityIconsMap[visibility]} alt={visibility} />{' '} <Icon icon={visibilityIconsMap[visibility]} alt={visibility} />{' '}
<a href={uri} target="_blank"> <a href={url} target="_blank">
<time class="created" datetime={createdAtDate.toISOString()}> <time class="created" datetime={createdAtDate.toISOString()}>
{Intl.DateTimeFormat('en', { {Intl.DateTimeFormat('en', {
// Show year if not current year // Show year if not current year

View file

@ -40,8 +40,12 @@ function resetScrollPosition(id) {
function StatusPage() { function StatusPage() {
const { id, ...params } = useParams(); const { id, ...params } = useParams();
const { masto, instance, authenticated } = api({ instance: params.instance }); const { masto, instance } = api({ instance: params.instance });
const { masto: currentMasto, instance: currentInstance } = api(); const {
masto: currentMasto,
instance: currentInstance,
authenticated,
} = api();
const sameInstance = instance === currentInstance; const sameInstance = instance === currentInstance;
const navigate = useNavigate(); const navigate = useNavigate();
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
@ -622,19 +626,31 @@ function StatusPage() {
size="l" size="l"
/> />
</InView> </InView>
{!sameInstance && uiState !== 'loading' && ( {uiState !== 'loading' && !authenticated ? (
<div class="post-status-banner">
<p>
You're not logged in. Interactions (reply, boost,
etc) are not possible.
</p>
<Link to="/login" class="button">
Log in
</Link>
</div>
) : (
!sameInstance && (
<div class="post-status-banner"> <div class="post-status-banner">
<p> <p>
This post is from another instance ( This post is from another instance (
<b>{instance}</b>). Interactions (reply, boost, etc) <b>{instance}</b>). Interactions (reply, boost,
are not possible. etc) are not possible.
</p> </p>
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
(async () => { (async () => {
try { try {
const results = await currentMasto.v2.search({ const results =
await currentMasto.v2.search({
q: heroStatus.url, q: heroStatus.url,
type: 'statuses', type: 'statuses',
resolve: true, resolve: true,
@ -642,7 +658,11 @@ function StatusPage() {
}); });
if (results.statuses.length) { if (results.statuses.length) {
const status = results.statuses[0]; const status = results.statuses[0];
navigate(`/s/${status.id}`); navigate(
currentInstance
? `/${currentInstance}/s/${status.id}`
: `/s/${status.id}`,
);
} else { } else {
throw new Error('No results'); throw new Error('No results');
} }
@ -657,19 +677,7 @@ function StatusPage() {
enable interactions enable interactions
</button> </button>
</div> </div>
)} )
{sameInstance &&
!authenticated &&
uiState !== 'loading' && (
<div class="post-status-banner">
<p>
You're not logged in. Interactions (reply, boost,
etc) are not possible.
</p>
<Link to="/login" class="button">
Log in
</Link>
</div>
)} )}
</> </>
) : ( ) : (