Add Generic Accounts modal
Also refactored whole bunch of stuff
This commit is contained in:
parent
dd2ca7bf35
commit
b57d8adf18
196
src/app.jsx
196
src/app.jsx
|
@ -7,33 +7,21 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from 'preact/hooks';
|
||||
import {
|
||||
matchPath,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from 'react-router-dom';
|
||||
import { matchPath, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import 'swiped-events';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
import AccountSheet from './components/account-sheet';
|
||||
import BackgroundService from './components/background-service';
|
||||
import Compose from './components/compose';
|
||||
import ComposeButton from './components/compose-button';
|
||||
import Drafts from './components/drafts';
|
||||
import { ICONS } from './components/icon';
|
||||
import KeyboardShortcutsHelp from './components/keyboard-shortcuts-help';
|
||||
import Loader from './components/loader';
|
||||
import MediaModal from './components/media-modal';
|
||||
import Modal from './components/modal';
|
||||
import Modals from './components/modals';
|
||||
import NotificationService from './components/notification-service';
|
||||
import SearchCommand from './components/search-command';
|
||||
import Shortcuts from './components/shortcuts';
|
||||
import ShortcutsSettings from './components/shortcuts-settings';
|
||||
import NotFound from './pages/404';
|
||||
import AccountStatuses from './pages/account-statuses';
|
||||
import Accounts from './pages/accounts';
|
||||
import Bookmarks from './pages/bookmarks';
|
||||
import Favourites from './pages/favourites';
|
||||
import FollowedHashtags from './pages/followed-hashtags';
|
||||
|
@ -48,7 +36,6 @@ import Mentions from './pages/mentions';
|
|||
import Notifications from './pages/notifications';
|
||||
import Public from './pages/public';
|
||||
import Search from './pages/search';
|
||||
import Settings from './pages/settings';
|
||||
import StatusRoute from './pages/status-route';
|
||||
import Trending from './pages/trending';
|
||||
import Welcome from './pages/welcome';
|
||||
|
@ -60,7 +47,7 @@ import {
|
|||
initPreferences,
|
||||
} from './utils/api';
|
||||
import { getAccessToken } from './utils/auth';
|
||||
import showToast from './utils/show-toast';
|
||||
import focusDeck from './utils/focus-deck';
|
||||
import states, { initStates } from './utils/states';
|
||||
import store from './utils/store';
|
||||
import { getCurrentAccount } from './utils/store-utils';
|
||||
|
@ -85,7 +72,6 @@ function App() {
|
|||
const snapStates = useSnapshot(states);
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [uiState, setUIState] = useState('loading');
|
||||
const navigate = useNavigate();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const theme = store.local.get('theme');
|
||||
|
@ -165,41 +151,9 @@ function App() {
|
|||
let location = useLocation();
|
||||
states.currentLocation = location.pathname;
|
||||
|
||||
const focusDeck = () => {
|
||||
let timer = setTimeout(() => {
|
||||
const columns = document.getElementById('columns');
|
||||
if (columns) {
|
||||
// Focus first column
|
||||
// columns.querySelector('.deck-container')?.focus?.();
|
||||
} else {
|
||||
const backDrop = document.querySelector('.deck-backdrop');
|
||||
if (backDrop) return;
|
||||
// Focus last deck
|
||||
const pages = document.querySelectorAll('.deck-container');
|
||||
const page = pages[pages.length - 1]; // last one
|
||||
if (page && page.tabIndex === -1) {
|
||||
console.log('FOCUS', page);
|
||||
page.focus();
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
};
|
||||
useEffect(focusDeck, [location, isLoggedIn]);
|
||||
const showModal =
|
||||
snapStates.showCompose ||
|
||||
snapStates.showSettings ||
|
||||
snapStates.showAccounts ||
|
||||
snapStates.showAccount ||
|
||||
snapStates.showDrafts ||
|
||||
snapStates.showMediaModal ||
|
||||
snapStates.showShortcutsSettings ||
|
||||
snapStates.showKeyboardShortcutsHelp;
|
||||
useEffect(() => {
|
||||
if (!showModal) focusDeck();
|
||||
}, [showModal]);
|
||||
|
||||
const { prevLocation } = snapStates;
|
||||
const prevLocation = snapStates.prevLocation;
|
||||
const backgroundLocation = useRef(prevLocation || null);
|
||||
const isModalPage = useMemo(() => {
|
||||
return (
|
||||
|
@ -294,147 +248,7 @@ function App() {
|
|||
snapStates.settings.shortcutsViewMode !== 'multi-column' && (
|
||||
<Shortcuts />
|
||||
)}
|
||||
{!!snapStates.showCompose && (
|
||||
<Modal>
|
||||
<Compose
|
||||
replyToStatus={
|
||||
typeof snapStates.showCompose !== 'boolean'
|
||||
? snapStates.showCompose.replyToStatus
|
||||
: window.__COMPOSE__?.replyToStatus || null
|
||||
}
|
||||
editStatus={
|
||||
states.showCompose?.editStatus ||
|
||||
window.__COMPOSE__?.editStatus ||
|
||||
null
|
||||
}
|
||||
draftStatus={
|
||||
states.showCompose?.draftStatus ||
|
||||
window.__COMPOSE__?.draftStatus ||
|
||||
null
|
||||
}
|
||||
onClose={(results) => {
|
||||
const { newStatus, instance } = results || {};
|
||||
states.showCompose = false;
|
||||
window.__COMPOSE__ = null;
|
||||
if (newStatus) {
|
||||
states.reloadStatusPage++;
|
||||
showToast({
|
||||
text: 'Post published. Check it out.',
|
||||
delay: 1000,
|
||||
duration: 10_000, // 10 seconds
|
||||
onClick: (toast) => {
|
||||
toast.hideToast();
|
||||
states.prevLocation = location;
|
||||
navigate(
|
||||
instance
|
||||
? `/${instance}/s/${newStatus.id}`
|
||||
: `/s/${newStatus.id}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showSettings && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showSettings = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Settings
|
||||
onClose={() => {
|
||||
states.showSettings = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showAccounts && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showAccounts = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Accounts
|
||||
onClose={() => {
|
||||
states.showAccounts = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showAccount && (
|
||||
<Modal
|
||||
class="light"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showAccount = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AccountSheet
|
||||
account={snapStates.showAccount?.account || snapStates.showAccount}
|
||||
instance={snapStates.showAccount?.instance}
|
||||
onClose={({ destination } = {}) => {
|
||||
states.showAccount = false;
|
||||
if (destination) {
|
||||
states.showAccounts = false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showDrafts && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showDrafts = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Drafts onClose={() => (states.showDrafts = false)} />
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showMediaModal && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (
|
||||
e.target === e.currentTarget ||
|
||||
e.target.classList.contains('media')
|
||||
) {
|
||||
states.showMediaModal = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MediaModal
|
||||
mediaAttachments={snapStates.showMediaModal.mediaAttachments}
|
||||
instance={snapStates.showMediaModal.instance}
|
||||
index={snapStates.showMediaModal.index}
|
||||
statusID={snapStates.showMediaModal.statusID}
|
||||
onClose={() => {
|
||||
states.showMediaModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showShortcutsSettings && (
|
||||
<Modal
|
||||
class="light"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showShortcutsSettings = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ShortcutsSettings
|
||||
onClose={() => (states.showShortcutsSettings = false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
<Modals />
|
||||
<NotificationService />
|
||||
<BackgroundService isLoggedIn={isLoggedIn} />
|
||||
<SearchCommand onClose={focusDeck} />
|
||||
|
|
|
@ -148,6 +148,12 @@
|
|||
overflow-x: auto;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
|
||||
[tabindex='0']:is(:hover, :focus) {
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.timeline-start .account-container .stats {
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -46,6 +46,8 @@ const MUTE_DURATIONS_LABELS = {
|
|||
604_800_000: '1 week',
|
||||
};
|
||||
|
||||
const LIMIT = 80;
|
||||
|
||||
function AccountInfo({
|
||||
account,
|
||||
fetchAccount = () => {},
|
||||
|
@ -53,6 +55,7 @@ function AccountInfo({
|
|||
instance,
|
||||
authenticated,
|
||||
}) {
|
||||
const { masto } = api();
|
||||
const [uiState, setUIState] = useState('default');
|
||||
const isString = typeof account === 'string';
|
||||
const [info, setInfo] = useState(isString ? null : account);
|
||||
|
@ -114,6 +117,59 @@ function AccountInfo({
|
|||
|
||||
const [headerCornerColors, setHeaderCornerColors] = useState([]);
|
||||
|
||||
const followersIterator = useRef();
|
||||
const familiarFollowersCache = useRef([]);
|
||||
async function fetchFollowers(firstLoad) {
|
||||
if (firstLoad || !followersIterator.current) {
|
||||
followersIterator.current = masto.v1.accounts.listFollowers(id, {
|
||||
limit: LIMIT,
|
||||
});
|
||||
}
|
||||
const results = await followersIterator.current.next();
|
||||
const { value } = results;
|
||||
let newValue = [];
|
||||
// On first load, fetch familiar followers, merge to top of results' `value`
|
||||
// Remove dups on every fetch
|
||||
if (firstLoad) {
|
||||
const familiarFollowers = await masto.v1.accounts.fetchFamiliarFollowers(
|
||||
id,
|
||||
);
|
||||
familiarFollowersCache.current = familiarFollowers[0].accounts;
|
||||
newValue = [
|
||||
...familiarFollowersCache.current,
|
||||
...value.filter(
|
||||
(account) =>
|
||||
!familiarFollowersCache.current.some(
|
||||
(familiar) => familiar.id === account.id,
|
||||
),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
newValue = value.filter(
|
||||
(account) =>
|
||||
!familiarFollowersCache.current.some(
|
||||
(familiar) => familiar.id === account.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...results,
|
||||
value: newValue,
|
||||
};
|
||||
}
|
||||
|
||||
const followingIterator = useRef();
|
||||
async function fetchFollowing(firstLoad) {
|
||||
if (firstLoad || !followingIterator.current) {
|
||||
followingIterator.current = masto.v1.accounts.listFollowing(id, {
|
||||
limit: LIMIT,
|
||||
});
|
||||
}
|
||||
const results = await followingIterator.current.next();
|
||||
return results;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`account-container ${uiState === 'loading' ? 'skeleton' : ''}`}
|
||||
|
@ -312,13 +368,30 @@ function AccountInfo({
|
|||
</div>
|
||||
)}
|
||||
<p class="stats">
|
||||
<div>
|
||||
<div
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Followers',
|
||||
fetchAccounts: fetchFollowers,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<span title={followersCount}>
|
||||
{shortenNumber(followersCount)}
|
||||
</span>{' '}
|
||||
Followers
|
||||
</div>
|
||||
<div class="insignificant">
|
||||
<div
|
||||
class="insignificant"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Following',
|
||||
fetchAccounts: fetchFollowing,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<span title={followingCount}>
|
||||
{shortenNumber(followingCount)}
|
||||
</span>{' '}
|
||||
|
|
42
src/components/generic-accounts.css
Normal file
42
src/components/generic-accounts.css
Normal file
|
@ -0,0 +1,42 @@
|
|||
#generic-accounts-container {
|
||||
.accounts-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
column-gap: 1.5em;
|
||||
row-gap: 16px;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-basis: 16em;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.account-block-acct {
|
||||
font-size: 80%;
|
||||
color: var(--text-insignificant-color);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.reactions-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
|
||||
.favourite-icon {
|
||||
color: var(--favourite-color);
|
||||
}
|
||||
|
||||
.reblog-icon {
|
||||
color: var(--reblog-color);
|
||||
}
|
||||
}
|
||||
}
|
135
src/components/generic-accounts.jsx
Normal file
135
src/components/generic-accounts.jsx
Normal file
|
@ -0,0 +1,135 @@
|
|||
import './generic-accounts.css';
|
||||
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { InView } from 'react-intersection-observer';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
import states from '../utils/states';
|
||||
|
||||
import AccountBlock from './account-block';
|
||||
import Icon from './icon';
|
||||
import Loader from './loader';
|
||||
|
||||
export default function GenericAccounts({ onClose = () => {} }) {
|
||||
const snapStates = useSnapshot(states);
|
||||
const [uiState, setUIState] = useState('default');
|
||||
const [accounts, setAccounts] = useState([]);
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
if (!snapStates.showGenericAccounts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
heading,
|
||||
fetchAccounts,
|
||||
accounts: staticAccounts,
|
||||
showReactions,
|
||||
} = snapStates.showGenericAccounts;
|
||||
|
||||
const loadAccounts = (firstLoad) => {
|
||||
if (!fetchAccounts) return;
|
||||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
const { done, value } = await fetchAccounts(firstLoad);
|
||||
if (Array.isArray(value)) {
|
||||
if (firstLoad) {
|
||||
setAccounts(value);
|
||||
} else {
|
||||
setAccounts((prev) => [...prev, ...value]);
|
||||
}
|
||||
setShowMore(!done);
|
||||
} else {
|
||||
setShowMore(false);
|
||||
}
|
||||
setUIState('default');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setUIState('error');
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (staticAccounts?.length > 0) {
|
||||
setAccounts(staticAccounts);
|
||||
} else {
|
||||
loadAccounts(true);
|
||||
}
|
||||
}, [staticAccounts]);
|
||||
|
||||
return (
|
||||
<div id="generic-accounts-container" class="sheet" tabindex="-1">
|
||||
<button type="button" class="sheet-close" onClick={onClose}>
|
||||
<Icon icon="x" />
|
||||
</button>
|
||||
<header>
|
||||
<h2>{heading || 'Accounts'}</h2>
|
||||
</header>
|
||||
<main>
|
||||
{accounts.length > 0 ? (
|
||||
<>
|
||||
<ul class="accounts-list">
|
||||
{accounts.map((account) => (
|
||||
<li key={account.id}>
|
||||
{showReactions && account._types?.length > 0 && (
|
||||
<div class="reactions-block">
|
||||
{account._types.map((type) => (
|
||||
<Icon
|
||||
icon={
|
||||
{
|
||||
reblog: 'rocket',
|
||||
favourite: 'heart',
|
||||
}[type]
|
||||
}
|
||||
class={`${type}-icon`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<AccountBlock account={account} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{uiState === 'default' ? (
|
||||
showMore ? (
|
||||
<InView
|
||||
onChange={(inView) => {
|
||||
if (inView) {
|
||||
loadAccounts();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="plain block"
|
||||
onClick={() => loadAccounts()}
|
||||
>
|
||||
Show more…
|
||||
</button>
|
||||
</InView>
|
||||
) : (
|
||||
<p class="ui-state insignificant">The end.</p>
|
||||
)
|
||||
) : (
|
||||
uiState === 'loading' && (
|
||||
<p class="ui-state">
|
||||
<Loader abrupt />
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
) : uiState === 'loading' ? (
|
||||
<p class="ui-state">
|
||||
<Loader abrupt />
|
||||
</p>
|
||||
) : uiState === 'error' ? (
|
||||
<p class="ui-state">Error loading accounts</p>
|
||||
) : (
|
||||
<p class="ui-state insignificant">Nothing to show</p>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -45,6 +45,7 @@ export const ICONS = {
|
|||
plus: () => import('@iconify-icons/mingcute/add-circle-line'),
|
||||
'chevron-left': () => import('@iconify-icons/mingcute/left-line'),
|
||||
'chevron-right': () => import('@iconify-icons/mingcute/right-line'),
|
||||
'chevron-down': () => import('@iconify-icons/mingcute/down-line'),
|
||||
reply: [
|
||||
() => import('@iconify-icons/mingcute/share-forward-line'),
|
||||
'180deg',
|
||||
|
|
190
src/components/modals.jsx
Normal file
190
src/components/modals.jsx
Normal file
|
@ -0,0 +1,190 @@
|
|||
import { subscribe, useSnapshot } from 'valtio';
|
||||
|
||||
import Accounts from '../pages/accounts';
|
||||
import Settings from '../pages/settings';
|
||||
import focusDeck from '../utils/focus-deck';
|
||||
import showToast from '../utils/show-toast';
|
||||
import states from '../utils/states';
|
||||
|
||||
import AccountSheet from './account-sheet';
|
||||
import Compose from './compose';
|
||||
import Drafts from './drafts';
|
||||
import GenericAccounts from './generic-accounts';
|
||||
import MediaModal from './media-modal';
|
||||
import Modal from './modal';
|
||||
import ShortcutsSettings from './shortcuts-settings';
|
||||
|
||||
subscribe(states, (changes) => {
|
||||
for (const [action, path, value, prevValue] of changes) {
|
||||
// When closing modal, focus on deck
|
||||
if (/^show/i.test(path) && !value) {
|
||||
focusDeck();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default function Modals() {
|
||||
const snapStates = useSnapshot(states);
|
||||
return (
|
||||
<>
|
||||
{!!snapStates.showCompose && (
|
||||
<Modal>
|
||||
<Compose
|
||||
replyToStatus={
|
||||
typeof snapStates.showCompose !== 'boolean'
|
||||
? snapStates.showCompose.replyToStatus
|
||||
: window.__COMPOSE__?.replyToStatus || null
|
||||
}
|
||||
editStatus={
|
||||
states.showCompose?.editStatus ||
|
||||
window.__COMPOSE__?.editStatus ||
|
||||
null
|
||||
}
|
||||
draftStatus={
|
||||
states.showCompose?.draftStatus ||
|
||||
window.__COMPOSE__?.draftStatus ||
|
||||
null
|
||||
}
|
||||
onClose={(results) => {
|
||||
const { newStatus, instance } = results || {};
|
||||
states.showCompose = false;
|
||||
window.__COMPOSE__ = null;
|
||||
if (newStatus) {
|
||||
states.reloadStatusPage++;
|
||||
showToast({
|
||||
text: 'Post published. Check it out.',
|
||||
delay: 1000,
|
||||
duration: 10_000, // 10 seconds
|
||||
onClick: (toast) => {
|
||||
toast.hideToast();
|
||||
states.prevLocation = location;
|
||||
// navigate(
|
||||
// instance
|
||||
// ? `/${instance}/s/${newStatus.id}`
|
||||
// : `/s/${newStatus.id}`,
|
||||
// );
|
||||
location.hash = instance
|
||||
? `/${instance}/s/${newStatus.id}`
|
||||
: `/s/${newStatus.id}`;
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showSettings && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showSettings = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Settings
|
||||
onClose={() => {
|
||||
states.showSettings = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showAccounts && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showAccounts = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Accounts
|
||||
onClose={() => {
|
||||
states.showAccounts = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showAccount && (
|
||||
<Modal
|
||||
class="light"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showAccount = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AccountSheet
|
||||
account={snapStates.showAccount?.account || snapStates.showAccount}
|
||||
instance={snapStates.showAccount?.instance}
|
||||
onClose={({ destination } = {}) => {
|
||||
states.showAccount = false;
|
||||
if (destination) {
|
||||
states.showAccounts = false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showDrafts && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showDrafts = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Drafts onClose={() => (states.showDrafts = false)} />
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showMediaModal && (
|
||||
<Modal
|
||||
onClick={(e) => {
|
||||
if (
|
||||
e.target === e.currentTarget ||
|
||||
e.target.classList.contains('media')
|
||||
) {
|
||||
states.showMediaModal = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MediaModal
|
||||
mediaAttachments={snapStates.showMediaModal.mediaAttachments}
|
||||
instance={snapStates.showMediaModal.instance}
|
||||
index={snapStates.showMediaModal.index}
|
||||
statusID={snapStates.showMediaModal.statusID}
|
||||
onClose={() => {
|
||||
states.showMediaModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showShortcutsSettings && (
|
||||
<Modal
|
||||
class="light"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showShortcutsSettings = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ShortcutsSettings
|
||||
onClose={() => (states.showShortcutsSettings = false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showGenericAccounts && (
|
||||
<Modal
|
||||
class="light"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
states.showGenericAccounts = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GenericAccounts
|
||||
onClose={() => (states.showGenericAccounts = false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -126,6 +126,21 @@ function Notification({ notification, instance, reload, isStatic }) {
|
|||
const formattedCreatedAt =
|
||||
notification.createdAt && new Date(notification.createdAt).toLocaleString();
|
||||
|
||||
const genericAccountsHeading =
|
||||
{
|
||||
'favourite+reblog': 'Boosted/Favourited by…',
|
||||
favourite: 'Favourited by…',
|
||||
reblog: 'Boosted by…',
|
||||
follow: 'Followed by…',
|
||||
}[type] || 'Accounts';
|
||||
const handleOpenGenericAccounts = () => {
|
||||
states.showGenericAccounts = {
|
||||
heading: genericAccountsHeading,
|
||||
accounts: _accounts,
|
||||
showReactions: type === 'favourite+reblog',
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={`notification notification-${type}`} tabIndex="0">
|
||||
<div
|
||||
|
@ -153,7 +168,9 @@ function Notification({ notification, instance, reload, isStatic }) {
|
|||
<>
|
||||
{_accounts?.length > 1 ? (
|
||||
<>
|
||||
<b>{_accounts.length} people</b>{' '}
|
||||
<b tabIndex="0" onClick={handleOpenGenericAccounts}>
|
||||
{_accounts.length} people
|
||||
</b>{' '}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
@ -228,6 +245,13 @@ function Notification({ notification, instance, reload, isStatic }) {
|
|||
</a>{' '}
|
||||
</>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
class="small plain"
|
||||
onClick={handleOpenGenericAccounts}
|
||||
>
|
||||
<Icon icon="chevron-down" />
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
{_statuses?.length > 1 && (
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
gap: 12px;
|
||||
animation: appear 0.2s ease-out;
|
||||
clear: both;
|
||||
|
||||
b[tabindex='0']:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.notification.notification-mention {
|
||||
margin-top: 16px;
|
||||
|
|
22
src/utils/focus-deck.jsx
Normal file
22
src/utils/focus-deck.jsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
const focusDeck = () => {
|
||||
let timer = setTimeout(() => {
|
||||
const columns = document.getElementById('columns');
|
||||
if (columns) {
|
||||
// Focus first column
|
||||
// columns.querySelector('.deck-container')?.focus?.();
|
||||
} else {
|
||||
const backDrop = document.querySelector('.deck-backdrop');
|
||||
if (backDrop) return;
|
||||
// Focus last deck
|
||||
const pages = document.querySelectorAll('.deck-container');
|
||||
const page = pages[pages.length - 1]; // last one
|
||||
if (page && page.tabIndex === -1) {
|
||||
console.log('FOCUS', page);
|
||||
page.focus();
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
};
|
||||
|
||||
export default focusDeck;
|
Loading…
Reference in a new issue