commit
b8d92bceb2
|
@ -1099,7 +1099,7 @@ button.carousel-dot {
|
|||
font-weight: bold;
|
||||
color: var(--text-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);
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ import {
|
|||
import { getAccessToken } from './utils/auth';
|
||||
import openCompose from './utils/open-compose';
|
||||
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 { getCurrentAccount } from './utils/store-utils';
|
||||
import useInterval from './utils/useInterval';
|
||||
|
@ -111,7 +111,7 @@ function App() {
|
|||
if (code) {
|
||||
console.log({ code });
|
||||
// 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 clientSecret = store.session.get('clientSecret');
|
||||
|
@ -130,6 +130,7 @@ function App() {
|
|||
initInstance(masto, instanceURL),
|
||||
initAccount(masto, instanceURL, accessToken),
|
||||
]);
|
||||
initStates();
|
||||
initPreferences(masto);
|
||||
|
||||
setIsLoggedIn(true);
|
||||
|
@ -389,7 +390,7 @@ function App() {
|
|||
<AccountSheet
|
||||
account={snapStates.showAccount?.account || snapStates.showAccount}
|
||||
instance={snapStates.showAccount?.instance}
|
||||
onClose={({ destination }) => {
|
||||
onClose={({ destination } = {}) => {
|
||||
states.showAccount = false;
|
||||
if (destination) {
|
||||
states.showAccounts = false;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu';
|
||||
import { cloneElement } from 'preact';
|
||||
import { useRef } from 'preact/hooks';
|
||||
|
||||
function MenuConfirm({
|
||||
subMenu = false,
|
||||
|
@ -20,8 +21,10 @@ function MenuConfirm({
|
|||
return children;
|
||||
}
|
||||
const Parent = subMenu ? SubMenu : Menu;
|
||||
const menuRef = useRef();
|
||||
return (
|
||||
<Parent
|
||||
instanceRef={menuRef}
|
||||
openTrigger="clickOnly"
|
||||
direction="bottom"
|
||||
overflow="auto"
|
||||
|
@ -31,6 +34,19 @@ function MenuConfirm({
|
|||
{...restProps}
|
||||
menuButton={subMenu ? undefined : children}
|
||||
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}>
|
||||
{confirmLabel}
|
||||
|
|
|
@ -39,21 +39,25 @@ const contentText = {
|
|||
mention: 'mentioned you in their post.',
|
||||
status: 'published a post.',
|
||||
reblog: 'boosted your post.',
|
||||
'reblog+account': (count) => `boosted ${count} of your posts.`,
|
||||
reblog_reply: 'boosted your reply.',
|
||||
follow: 'followed you.',
|
||||
follow_request: 'requested to follow you.',
|
||||
favourite: 'favourited your post.',
|
||||
'favourite+account': (count) => `favourited ${count} of your posts.`,
|
||||
favourite_reply: 'favourited your reply.',
|
||||
poll: 'A poll you have voted in or created has ended.',
|
||||
'poll-self': 'A poll you have created has ended.',
|
||||
'poll-voted': 'A poll you have voted in has ended.',
|
||||
update: 'A post you interacted with has been edited.',
|
||||
'favourite+reblog': 'boosted & favourited your post.',
|
||||
'favourite+reblog+account': (count) =>
|
||||
`boosted & favourited ${count} of your posts.`,
|
||||
'favourite+reblog_reply': 'boosted & favourited your reply.',
|
||||
};
|
||||
|
||||
function Notification({ notification, instance, reload }) {
|
||||
const { id, status, account, _accounts } = notification;
|
||||
const { id, status, account, _accounts, _statuses } = notification;
|
||||
let { type } = notification;
|
||||
|
||||
// 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+reblog'
|
||||
) {
|
||||
text =
|
||||
contentText[isReplyToOthers ? `${type}_reply` : type] ||
|
||||
contentText[type];
|
||||
if (_statuses?.length > 1) {
|
||||
text = contentText[`${type}+account`];
|
||||
} else if (isReplyToOthers) {
|
||||
text = contentText[`${type}_reply`];
|
||||
} else {
|
||||
text = contentText[type];
|
||||
}
|
||||
} else {
|
||||
text = contentText[type];
|
||||
}
|
||||
if (typeof text === 'function') {
|
||||
text = text(_statuses?.length || _accounts?.length);
|
||||
}
|
||||
|
||||
if (type === 'mention' && !status) {
|
||||
// Could be deleted
|
||||
|
@ -206,7 +217,23 @@ function Notification({ notification, instance, reload }) {
|
|||
))}
|
||||
</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
|
||||
class={`status-link status-type-${type}`}
|
||||
to={
|
||||
|
|
|
@ -121,11 +121,14 @@
|
|||
.status-card > * {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
.status-card :is(.content, .poll) {
|
||||
|
|
|
@ -1170,9 +1170,7 @@ function Status({
|
|||
(option) =>
|
||||
`- ${option.title}${
|
||||
option.votesCount >= 0
|
||||
? ` (${option.votesCount} vote${
|
||||
option.votesCount !== 1 ? 's' : ''
|
||||
})`
|
||||
? ` (${option.votesCount})`
|
||||
: ''
|
||||
}`,
|
||||
)
|
||||
|
@ -1436,6 +1434,7 @@ function Card({ card, instance }) {
|
|||
url,
|
||||
type,
|
||||
embedUrl,
|
||||
language,
|
||||
} = card;
|
||||
|
||||
/* type
|
||||
|
@ -1499,6 +1498,7 @@ function Card({ card, instance }) {
|
|||
target={cardStatusURL ? null : '_blank'}
|
||||
rel="nofollow noopener noreferrer"
|
||||
class={`card link ${blurhashImage ? '' : size}`}
|
||||
lang={language}
|
||||
>
|
||||
<div class="card-image">
|
||||
<img
|
||||
|
@ -1567,6 +1567,7 @@ function Card({ card, instance }) {
|
|||
target={cardStatusURL ? null : '_blank'}
|
||||
rel="nofollow noopener noreferrer"
|
||||
class={`card link no-image`}
|
||||
lang={language}
|
||||
>
|
||||
<div class="meta-container">
|
||||
<p class="meta domain">
|
||||
|
|
|
@ -143,7 +143,7 @@ function Accounts({ onClose }) {
|
|||
accounts.splice(i, 1);
|
||||
store.local.setJSON('accounts', accounts);
|
||||
// location.reload();
|
||||
location.href = '/';
|
||||
location.href = location.pathname || '/';
|
||||
}}
|
||||
>
|
||||
<Icon icon="exit" />
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
filter: saturate(0.25);
|
||||
}
|
||||
.notification .status-link:not(.status-type-mention) > .status {
|
||||
font-size: calc(var(--text-size) * 0.9);
|
||||
max-height: 160px;
|
||||
overflow: hidden;
|
||||
/* fade out mask gradient bottom */
|
||||
|
@ -134,6 +135,38 @@
|
|||
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 {
|
||||
float: right;
|
||||
margin-top: 0.5em;
|
||||
|
|
|
@ -336,6 +336,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
ancestor: true,
|
||||
isThread: ancestorsIsThread,
|
||||
accountID: s.account.id,
|
||||
account: s.account,
|
||||
repliesCount: s.repliesCount,
|
||||
weight: calcStatusWeight(s),
|
||||
})),
|
||||
|
@ -705,6 +706,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
block: 'start',
|
||||
});
|
||||
}}
|
||||
title="Go to main post"
|
||||
>
|
||||
<Icon
|
||||
icon={heroPointer === 'down' ? 'arrow-down' : 'arrow-up'}
|
||||
|
@ -727,12 +729,31 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
});
|
||||
}}
|
||||
hidden={!ancestors.length || nearReachStart}
|
||||
title={`${ancestors.length} posts above ‒ Go to top`}
|
||||
>
|
||||
<Icon icon="arrow-up" />
|
||||
<Icon icon="comment" />{' '}
|
||||
<span class="insignificant">
|
||||
{shortenNumber(ancestors.length)}
|
||||
</span>
|
||||
{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">
|
||||
{shortenNumber(ancestors.length)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -4,8 +4,8 @@ const { VITE_CLIENT_NAME: CLIENT_NAME, VITE_WEBSITE: WEBSITE } = import.meta
|
|||
export async function registerApplication({ instanceURL }) {
|
||||
const registrationParams = new URLSearchParams({
|
||||
client_name: CLIENT_NAME,
|
||||
redirect_uris: location.origin,
|
||||
scopes: 'read write follow',
|
||||
redirect_uris: location.origin + location.pathname,
|
||||
website: WEBSITE,
|
||||
});
|
||||
const registrationResponse = await fetch(
|
||||
|
@ -27,7 +27,7 @@ export async function getAuthorizationURL({ instanceURL, client_id }) {
|
|||
const authorizationParams = new URLSearchParams({
|
||||
client_id,
|
||||
scope: 'read write follow',
|
||||
redirect_uri: location.origin,
|
||||
redirect_uri: location.origin + location.pathname,
|
||||
// redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||
response_type: 'code',
|
||||
});
|
||||
|
@ -44,7 +44,7 @@ export async function getAccessToken({
|
|||
const params = new URLSearchParams({
|
||||
client_id,
|
||||
client_secret,
|
||||
redirect_uri: location.origin,
|
||||
redirect_uri: location.origin + location.pathname,
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
scope: 'read write follow',
|
||||
|
|
|
@ -37,7 +37,40 @@ function groupNotifications(notifications) {
|
|||
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;
|
||||
|
|
|
@ -60,6 +60,30 @@ const states = proxy({
|
|||
|
||||
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) => {
|
||||
console.log('CHANGE', v);
|
||||
store.account.set('notificationsLast', states.notificationsLast);
|
||||
|
|
|
@ -25,6 +25,7 @@ const rollbarCode = fs.readFileSync(
|
|||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
mode: NODE_ENV,
|
||||
define: {
|
||||
__BUILD_TIME__: JSON.stringify(now),
|
||||
|
|
Loading…
Reference in a new issue