Merge pull request #223 from cheeaun/main

Update from main
This commit is contained in:
Chee Aun 2023-09-01 13:00:48 +08:00 committed by GitHub
commit b8d92bceb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 183 additions and 23 deletions

View file

@ -1099,7 +1099,7 @@ button.carousel-dot {
font-weight: bold; font-weight: bold;
color: var(--text-color); color: var(--text-color);
background-color: var(--bg-faded-blur-color); background-color: var(--bg-faded-blur-color);
border: var(--hairline-width) solid var(--outline-color); border: 1px solid var(--outline-color);
box-shadow: 0 4px 32px var(--drop-shadow-color); box-shadow: 0 4px 32px var(--drop-shadow-color);
transition: all 0.2s ease-out; transition: all 0.2s ease-out;
} }

View file

@ -58,7 +58,7 @@ import {
import { getAccessToken } from './utils/auth'; import { getAccessToken } from './utils/auth';
import openCompose from './utils/open-compose'; import openCompose from './utils/open-compose';
import showToast from './utils/show-toast'; import showToast from './utils/show-toast';
import states, { getStatus, saveStatus } from './utils/states'; import states, { initStates, saveStatus } from './utils/states';
import store from './utils/store'; import store from './utils/store';
import { getCurrentAccount } from './utils/store-utils'; import { getCurrentAccount } from './utils/store-utils';
import useInterval from './utils/useInterval'; import useInterval from './utils/useInterval';
@ -111,7 +111,7 @@ function App() {
if (code) { if (code) {
console.log({ code }); console.log({ code });
// Clear the code from the URL // Clear the code from the URL
window.history.replaceState({}, document.title, '/'); window.history.replaceState({}, document.title, location.pathname || '/');
const clientID = store.session.get('clientID'); const clientID = store.session.get('clientID');
const clientSecret = store.session.get('clientSecret'); const clientSecret = store.session.get('clientSecret');
@ -130,6 +130,7 @@ function App() {
initInstance(masto, instanceURL), initInstance(masto, instanceURL),
initAccount(masto, instanceURL, accessToken), initAccount(masto, instanceURL, accessToken),
]); ]);
initStates();
initPreferences(masto); initPreferences(masto);
setIsLoggedIn(true); setIsLoggedIn(true);
@ -389,7 +390,7 @@ function App() {
<AccountSheet <AccountSheet
account={snapStates.showAccount?.account || snapStates.showAccount} account={snapStates.showAccount?.account || snapStates.showAccount}
instance={snapStates.showAccount?.instance} instance={snapStates.showAccount?.instance}
onClose={({ destination }) => { onClose={({ destination } = {}) => {
states.showAccount = false; states.showAccount = false;
if (destination) { if (destination) {
states.showAccounts = false; states.showAccounts = false;

View file

@ -1,5 +1,6 @@
import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu'; import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu';
import { cloneElement } from 'preact'; import { cloneElement } from 'preact';
import { useRef } from 'preact/hooks';
function MenuConfirm({ function MenuConfirm({
subMenu = false, subMenu = false,
@ -20,8 +21,10 @@ function MenuConfirm({
return children; return children;
} }
const Parent = subMenu ? SubMenu : Menu; const Parent = subMenu ? SubMenu : Menu;
const menuRef = useRef();
return ( return (
<Parent <Parent
instanceRef={menuRef}
openTrigger="clickOnly" openTrigger="clickOnly"
direction="bottom" direction="bottom"
overflow="auto" overflow="auto"
@ -31,6 +34,19 @@ function MenuConfirm({
{...restProps} {...restProps}
menuButton={subMenu ? undefined : children} menuButton={subMenu ? undefined : children}
label={subMenu ? children : undefined} label={subMenu ? children : undefined}
// Test fix for bug; submenus not opening on Android
itemProps={{
onPointerMove: (e) => {
if (e.pointerType === 'touch') {
menuRef.current?.openMenu?.();
}
},
onPointerLeave: (e) => {
if (e.pointerType === 'touch') {
menuRef.current?.openMenu?.();
}
},
}}
> >
<MenuItem className={menuItemClassName} onClick={onClick}> <MenuItem className={menuItemClassName} onClick={onClick}>
{confirmLabel} {confirmLabel}

View file

@ -39,21 +39,25 @@ const contentText = {
mention: 'mentioned you in their post.', mention: 'mentioned you in their post.',
status: 'published a post.', status: 'published a post.',
reblog: 'boosted your post.', reblog: 'boosted your post.',
'reblog+account': (count) => `boosted ${count} of your posts.`,
reblog_reply: 'boosted your reply.', reblog_reply: 'boosted your reply.',
follow: 'followed you.', follow: 'followed you.',
follow_request: 'requested to follow you.', follow_request: 'requested to follow you.',
favourite: 'favourited your post.', favourite: 'favourited your post.',
'favourite+account': (count) => `favourited ${count} of your posts.`,
favourite_reply: 'favourited your reply.', favourite_reply: 'favourited your reply.',
poll: 'A poll you have voted in or created has ended.', poll: 'A poll you have voted in or created has ended.',
'poll-self': 'A poll you have created has ended.', 'poll-self': 'A poll you have created has ended.',
'poll-voted': 'A poll you have voted in has ended.', 'poll-voted': 'A poll you have voted in has ended.',
update: 'A post you interacted with has been edited.', update: 'A post you interacted with has been edited.',
'favourite+reblog': 'boosted & favourited your post.', 'favourite+reblog': 'boosted & favourited your post.',
'favourite+reblog+account': (count) =>
`boosted & favourited ${count} of your posts.`,
'favourite+reblog_reply': 'boosted & favourited your reply.', 'favourite+reblog_reply': 'boosted & favourited your reply.',
}; };
function Notification({ notification, instance, reload }) { function Notification({ notification, instance, reload }) {
const { id, status, account, _accounts } = notification; const { id, status, account, _accounts, _statuses } = notification;
let { type } = notification; let { type } = notification;
// status = Attached when type of the notification is favourite, reblog, status, mention, poll, or update // status = Attached when type of the notification is favourite, reblog, status, mention, poll, or update
@ -90,12 +94,19 @@ function Notification({ notification, instance, reload }) {
type === 'favourite' || type === 'favourite' ||
type === 'favourite+reblog' type === 'favourite+reblog'
) { ) {
text = if (_statuses?.length > 1) {
contentText[isReplyToOthers ? `${type}_reply` : type] || text = contentText[`${type}+account`];
contentText[type]; } else if (isReplyToOthers) {
text = contentText[`${type}_reply`];
} else { } else {
text = contentText[type]; text = contentText[type];
} }
} else {
text = contentText[type];
}
if (typeof text === 'function') {
text = text(_statuses?.length || _accounts?.length);
}
if (type === 'mention' && !status) { if (type === 'mention' && !status) {
// Could be deleted // Could be deleted
@ -206,7 +217,23 @@ function Notification({ notification, instance, reload }) {
))} ))}
</p> </p>
)} )}
{status && ( {_statuses?.length > 1 && (
<ul class="notification-group-statuses">
{_statuses.map((status) => (
<li key={status.id}>
<Link
class={`status-link status-type-${type}`}
to={
instance ? `/${instance}/s/${status.id}` : `/s/${status.id}`
}
>
<Status status={status} size="s" />
</Link>
</li>
))}
</ul>
)}
{status && (!_statuses?.length || _statuses?.length <= 1) && (
<Link <Link
class={`status-link status-type-${type}`} class={`status-link status-type-${type}`}
to={ to={

View file

@ -121,11 +121,14 @@
.status-card > * { .status-card > * {
pointer-events: none; pointer-events: none;
} }
.status-card :is(.content, .poll, .media-container) { .status-card:not(.status-carousel .status)
:is(.content, .poll, .media-container) {
max-height: 160px !important; max-height: 160px !important;
overflow: hidden; overflow: hidden;
} }
.status.small .status-card :is(.content, .poll, .media-container) { .status.small:not(.status-carousel .status)
.status-card
:is(.content, .poll, .media-container) {
max-height: 80px !important; max-height: 80px !important;
} }
.status-card :is(.content, .poll) { .status-card :is(.content, .poll) {

View file

@ -1170,9 +1170,7 @@ function Status({
(option) => (option) =>
`- ${option.title}${ `- ${option.title}${
option.votesCount >= 0 option.votesCount >= 0
? ` (${option.votesCount} vote${ ? ` (${option.votesCount})`
option.votesCount !== 1 ? 's' : ''
})`
: '' : ''
}`, }`,
) )
@ -1436,6 +1434,7 @@ function Card({ card, instance }) {
url, url,
type, type,
embedUrl, embedUrl,
language,
} = card; } = card;
/* type /* type
@ -1499,6 +1498,7 @@ function Card({ card, instance }) {
target={cardStatusURL ? null : '_blank'} target={cardStatusURL ? null : '_blank'}
rel="nofollow noopener noreferrer" rel="nofollow noopener noreferrer"
class={`card link ${blurhashImage ? '' : size}`} class={`card link ${blurhashImage ? '' : size}`}
lang={language}
> >
<div class="card-image"> <div class="card-image">
<img <img
@ -1567,6 +1567,7 @@ function Card({ card, instance }) {
target={cardStatusURL ? null : '_blank'} target={cardStatusURL ? null : '_blank'}
rel="nofollow noopener noreferrer" rel="nofollow noopener noreferrer"
class={`card link no-image`} class={`card link no-image`}
lang={language}
> >
<div class="meta-container"> <div class="meta-container">
<p class="meta domain"> <p class="meta domain">

View file

@ -143,7 +143,7 @@ function Accounts({ onClose }) {
accounts.splice(i, 1); accounts.splice(i, 1);
store.local.setJSON('accounts', accounts); store.local.setJSON('accounts', accounts);
// location.reload(); // location.reload();
location.href = '/'; location.href = location.pathname || '/';
}} }}
> >
<Icon icon="exit" /> <Icon icon="exit" />

View file

@ -92,6 +92,7 @@
filter: saturate(0.25); filter: saturate(0.25);
} }
.notification .status-link:not(.status-type-mention) > .status { .notification .status-link:not(.status-type-mention) > .status {
font-size: calc(var(--text-size) * 0.9);
max-height: 160px; max-height: 160px;
overflow: hidden; overflow: hidden;
/* fade out mask gradient bottom */ /* fade out mask gradient bottom */
@ -134,6 +135,38 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
.notification-group-statuses {
margin: 0;
padding: 0;
list-style: none;
}
.notification-group-statuses > li {
margin: 0;
padding: 0;
list-style: none;
position: relative;
counter-increment: index;
}
.notification-group-statuses > li:before {
content: counter(index);
position: absolute;
left: 0;
font-size: 10px;
padding: 8px;
font-weight: bold;
}
.notification-group-statuses > li + li {
margin-top: -1px;
}
.notification-group-statuses > li:not(:last-child) .status-link {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.notification-group-statuses > li:not(:first-child) .status-link {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
#mentions-option { #mentions-option {
float: right; float: right;
margin-top: 0.5em; margin-top: 0.5em;

View file

@ -336,6 +336,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
ancestor: true, ancestor: true,
isThread: ancestorsIsThread, isThread: ancestorsIsThread,
accountID: s.account.id, accountID: s.account.id,
account: s.account,
repliesCount: s.repliesCount, repliesCount: s.repliesCount,
weight: calcStatusWeight(s), weight: calcStatusWeight(s),
})), })),
@ -705,6 +706,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
block: 'start', block: 'start',
}); });
}} }}
title="Go to main post"
> >
<Icon <Icon
icon={heroPointer === 'down' ? 'arrow-down' : 'arrow-up'} icon={heroPointer === 'down' ? 'arrow-down' : 'arrow-up'}
@ -727,12 +729,31 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
}); });
}} }}
hidden={!ancestors.length || nearReachStart} hidden={!ancestors.length || nearReachStart}
title={`${ancestors.length} posts above Go to top`}
> >
<Icon icon="arrow-up" /> <Icon icon="arrow-up" />
<Icon icon="comment" />{' '} {ancestors
.filter(
(a, i, arr) =>
arr.findIndex((b) => b.accountID === a.accountID) === i,
)
.slice(0, 3)
.map((ancestor) => (
<Avatar
key={ancestor.account.id}
url={ancestor.account.avatar}
alt={ancestor.account.displayName}
/>
))}
{/* <Icon icon="comment" />{' '} */}
{ancestors.length > 3 && (
<>
{' '}
<span class="insignificant"> <span class="insignificant">
{shortenNumber(ancestors.length)} {shortenNumber(ancestors.length)}
</span> </span>
</>
)}
</button> </button>
</> </>
)} )}

View file

@ -4,8 +4,8 @@ const { VITE_CLIENT_NAME: CLIENT_NAME, VITE_WEBSITE: WEBSITE } = import.meta
export async function registerApplication({ instanceURL }) { export async function registerApplication({ instanceURL }) {
const registrationParams = new URLSearchParams({ const registrationParams = new URLSearchParams({
client_name: CLIENT_NAME, client_name: CLIENT_NAME,
redirect_uris: location.origin,
scopes: 'read write follow', scopes: 'read write follow',
redirect_uris: location.origin + location.pathname,
website: WEBSITE, website: WEBSITE,
}); });
const registrationResponse = await fetch( const registrationResponse = await fetch(
@ -27,7 +27,7 @@ export async function getAuthorizationURL({ instanceURL, client_id }) {
const authorizationParams = new URLSearchParams({ const authorizationParams = new URLSearchParams({
client_id, client_id,
scope: 'read write follow', scope: 'read write follow',
redirect_uri: location.origin, redirect_uri: location.origin + location.pathname,
// redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', // redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
response_type: 'code', response_type: 'code',
}); });
@ -44,7 +44,7 @@ export async function getAccessToken({
const params = new URLSearchParams({ const params = new URLSearchParams({
client_id, client_id,
client_secret, client_secret,
redirect_uri: location.origin, redirect_uri: location.origin + location.pathname,
grant_type: 'authorization_code', grant_type: 'authorization_code',
code, code,
scope: 'read write follow', scope: 'read write follow',

View file

@ -37,7 +37,40 @@ function groupNotifications(notifications) {
cleanNotifications[j++] = n; cleanNotifications[j++] = n;
} }
} }
return cleanNotifications;
// 2nd pass to group "favourite+reblog"-type notifications by account if _accounts.length <= 1
// This means one acount has favourited and reblogged the multiple statuses
// The grouped notification
// - type: "favourite+reblog+account"
// - _statuses: [status, status, ...]
const notificationsMap2 = {};
const cleanNotifications2 = [];
for (let i = 0, j = 0; i < cleanNotifications.length; i++) {
const notification = cleanNotifications[i];
const { account, _accounts, type, createdAt } = notification;
const date = new Date(createdAt).toLocaleDateString();
if (type === 'favourite+reblog' && account && _accounts.length === 1) {
const key = `${account?.id}-${type}-${date}`;
const mappedNotification = notificationsMap2[key];
if (mappedNotification) {
mappedNotification._statuses.push(notification.status);
} else {
let n = (notificationsMap2[key] = {
...notification,
type,
_statuses: [notification.status],
});
cleanNotifications2[j++] = n;
}
} else {
cleanNotifications2[j++] = notification;
}
}
console.log({ notifications, cleanNotifications, cleanNotifications2 });
// return cleanNotifications;
return cleanNotifications2;
} }
export default groupNotifications; export default groupNotifications;

View file

@ -60,6 +60,30 @@ const states = proxy({
export default states; export default states;
export function initStates() {
// init all account based states
// all keys that uses store.account.get() should be initialized here
states.notificationsLast = store.account.get('notificationsLast') || null;
states.shortcuts = store.account.get('shortcuts') ?? [];
states.settings.autoRefresh =
store.account.get('settings-autoRefresh') ?? false;
states.settings.shortcutsViewMode =
store.account.get('settings-shortcutsViewMode') ?? null;
states.settings.shortcutsColumnsMode =
store.account.get('settings-shortcutsColumnsMode') ?? false;
states.settings.boostsCarousel =
store.account.get('settings-boostsCarousel') ?? true;
states.settings.contentTranslation =
store.account.get('settings-contentTranslation') ?? true;
states.settings.contentTranslationTargetLanguage =
store.account.get('settings-contentTranslationTargetLanguage') || null;
states.settings.contentTranslationHideLanguages =
store.account.get('settings-contentTranslationHideLanguages') || [];
states.settings.contentTranslationAutoInline =
store.account.get('settings-contentTranslationAutoInline') ?? false;
states.settings.cloakMode = store.account.get('settings-cloakMode') ?? false;
}
subscribeKey(states, 'notificationsLast', (v) => { subscribeKey(states, 'notificationsLast', (v) => {
console.log('CHANGE', v); console.log('CHANGE', v);
store.account.set('notificationsLast', states.notificationsLast); store.account.set('notificationsLast', states.notificationsLast);

View file

@ -25,6 +25,7 @@ const rollbarCode = fs.readFileSync(
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: './',
mode: NODE_ENV, mode: NODE_ENV,
define: { define: {
__BUILD_TIME__: JSON.stringify(now), __BUILD_TIME__: JSON.stringify(now),