Allow Lists to be in Shortcuts (except columns)
…and all various Lists-related improvements
This commit is contained in:
parent
8378d6fc1d
commit
f6a9f7807e
10 changed files with 302 additions and 87 deletions
|
@ -2288,10 +2288,10 @@ ul.link-list li a .icon {
|
||||||
filter: none !important;
|
filter: none !important;
|
||||||
}
|
}
|
||||||
.nav-menu-button .avatar {
|
.nav-menu-button .avatar {
|
||||||
transition: box-shadow 0.3s ease-out;
|
box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--link-light-color) !important;
|
||||||
}
|
}
|
||||||
.nav-menu-button:is(:hover, :focus, .active) .avatar {
|
.nav-menu-button:is(:hover, :focus, .active) .avatar {
|
||||||
box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--link-light-color);
|
box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--link-color) !important;
|
||||||
}
|
}
|
||||||
.nav-menu-button.with-avatar .icon {
|
.nav-menu-button.with-avatar .icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { api } from '../utils/api';
|
||||||
import enhanceContent from '../utils/enhance-content';
|
import enhanceContent from '../utils/enhance-content';
|
||||||
import getHTMLText from '../utils/getHTMLText';
|
import getHTMLText from '../utils/getHTMLText';
|
||||||
import handleContentLinks from '../utils/handle-content-links';
|
import handleContentLinks from '../utils/handle-content-links';
|
||||||
|
import { getLists } from '../utils/lists';
|
||||||
import niceDateTime from '../utils/nice-date-time';
|
import niceDateTime from '../utils/nice-date-time';
|
||||||
import pmem from '../utils/pmem';
|
import pmem from '../utils/pmem';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
|
@ -1558,13 +1559,12 @@ function AddRemoveListsSheet({ accountID, onClose }) {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const lists = await masto.v1.lists.list();
|
const lists = await getLists();
|
||||||
lists.sort((a, b) => a.title.localeCompare(b.title));
|
setLists(lists);
|
||||||
const listsContainingAccount = await masto.v1.accounts
|
const listsContainingAccount = await masto.v1.accounts
|
||||||
.$select(accountID)
|
.$select(accountID)
|
||||||
.lists.list();
|
.lists.list();
|
||||||
console.log({ lists, listsContainingAccount });
|
console.log({ lists, listsContainingAccount });
|
||||||
setLists(lists);
|
|
||||||
setListsContainingAccount(listsContainingAccount);
|
setListsContainingAccount(listsContainingAccount);
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -39,6 +39,8 @@ function Columns() {
|
||||||
if (!Component) return null;
|
if (!Component) return null;
|
||||||
// Don't show Search column with no query, for now
|
// Don't show Search column with no query, for now
|
||||||
if (type === 'search' && !params.query) return null;
|
if (type === 'search' && !params.query) return null;
|
||||||
|
// Don't show List column with no list, for now
|
||||||
|
if (type === 'list' && !params.id) return null;
|
||||||
return (
|
return (
|
||||||
<Component key={type + JSON.stringify(params)} {...params} columnMode />
|
<Component key={type + JSON.stringify(params)} {...params} columnMode />
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
|
import { addListStore, deleteListStore, updateListStore } from '../utils/lists';
|
||||||
import supports from '../utils/supports';
|
import supports from '../utils/supports';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
@ -75,6 +76,14 @@ function ListAddEdit({ list, onClose }) {
|
||||||
state: 'success',
|
state: 'success',
|
||||||
list: listResult,
|
list: listResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (editMode) {
|
||||||
|
updateListStore(listResult);
|
||||||
|
} else {
|
||||||
|
addListStore(listResult);
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
|
@ -146,6 +155,9 @@ function ListAddEdit({ list, onClose }) {
|
||||||
onClose?.({
|
onClose?.({
|
||||||
state: 'deleted',
|
state: 'deleted',
|
||||||
});
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
deleteListStore(list.id);
|
||||||
|
}, 1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
|
|
|
@ -7,11 +7,12 @@ import {
|
||||||
SubMenu,
|
SubMenu,
|
||||||
} from '@szhsin/react-menu';
|
} from '@szhsin/react-menu';
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
|
import { getLists } from '../utils/lists';
|
||||||
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
@ -24,16 +25,12 @@ function NavMenu(props) {
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const { masto, instance, authenticated } = api();
|
const { masto, instance, authenticated } = api();
|
||||||
|
|
||||||
const [currentAccount, setCurrentAccount] = useState();
|
const [currentAccount, moreThanOneAccount] = useMemo(() => {
|
||||||
const [moreThanOneAccount, setMoreThanOneAccount] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const accounts = store.local.getJSON('accounts') || [];
|
const accounts = store.local.getJSON('accounts') || [];
|
||||||
const acc = accounts.find(
|
const acc = accounts.find(
|
||||||
(account) => account.info.id === store.session.get('currentAccount'),
|
(account) => account.info.id === store.session.get('currentAccount'),
|
||||||
);
|
);
|
||||||
if (acc) setCurrentAccount(acc);
|
return [acc, accounts.length > 1];
|
||||||
setMoreThanOneAccount(accounts.length > 1);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Home = Following
|
// Home = Following
|
||||||
|
@ -89,6 +86,13 @@ function NavMenu(props) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [lists, setLists] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (menuState === 'open') {
|
||||||
|
getLists().then(setLists);
|
||||||
|
}
|
||||||
|
}, [menuState === 'open']);
|
||||||
|
|
||||||
const buttonClickTS = useRef();
|
const buttonClickTS = useRef();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -97,7 +101,7 @@ function NavMenu(props) {
|
||||||
type="button"
|
type="button"
|
||||||
class={`button plain nav-menu-button ${
|
class={`button plain nav-menu-button ${
|
||||||
moreThanOneAccount ? 'with-avatar' : ''
|
moreThanOneAccount ? 'with-avatar' : ''
|
||||||
} ${open ? 'active' : ''}`}
|
} ${menuState === 'open' ? 'active' : ''}`}
|
||||||
style={{ position: 'relative' }}
|
style={{ position: 'relative' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
buttonClickTS.current = Date.now();
|
buttonClickTS.current = Date.now();
|
||||||
|
@ -203,9 +207,38 @@ function NavMenu(props) {
|
||||||
<Icon icon="user" size="l" /> <span>Profile</span>
|
<Icon icon="user" size="l" /> <span>Profile</span>
|
||||||
</MenuLink>
|
</MenuLink>
|
||||||
)}
|
)}
|
||||||
<MenuLink to="/l">
|
{lists?.length > 0 ? (
|
||||||
<Icon icon="list" size="l" /> <span>Lists</span>
|
<SubMenu
|
||||||
</MenuLink>
|
overflow="auto"
|
||||||
|
gap={-8}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<Icon icon="list" size="l" />
|
||||||
|
<span class="menu-grow">Lists</span>
|
||||||
|
<Icon icon="chevron-right" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuLink to="/l">
|
||||||
|
<span>All Lists</span>
|
||||||
|
</MenuLink>
|
||||||
|
{lists?.length > 0 && (
|
||||||
|
<>
|
||||||
|
<MenuDivider />
|
||||||
|
{lists.map((list) => (
|
||||||
|
<MenuLink key={list.id} to={`/l/${list.id}`}>
|
||||||
|
<span>{list.title}</span>
|
||||||
|
</MenuLink>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</SubMenu>
|
||||||
|
) : (
|
||||||
|
<MenuLink to="/l">
|
||||||
|
<Icon icon="list" size="l" />
|
||||||
|
<span>Lists</span>
|
||||||
|
</MenuLink>
|
||||||
|
)}
|
||||||
<MenuLink to="/b">
|
<MenuLink to="/b">
|
||||||
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
|
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
|
||||||
</MenuLink>
|
</MenuLink>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import tabMenuBarUrl from '../assets/tab-menu-bar.svg';
|
||||||
|
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import { fetchFollowedTags } from '../utils/followed-tags';
|
import { fetchFollowedTags } from '../utils/followed-tags';
|
||||||
|
import { getLists, getListTitle } from '../utils/lists';
|
||||||
import pmem from '../utils/pmem';
|
import pmem from '../utils/pmem';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
@ -43,7 +44,7 @@ const TYPES = [
|
||||||
const TYPE_TEXT = {
|
const TYPE_TEXT = {
|
||||||
following: 'Home / Following',
|
following: 'Home / Following',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
list: 'List',
|
list: 'Lists',
|
||||||
public: 'Public (Local / Federated)',
|
public: 'Public (Local / Federated)',
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
'account-statuses': 'Account',
|
'account-statuses': 'Account',
|
||||||
|
@ -58,6 +59,7 @@ const TYPE_PARAMS = {
|
||||||
{
|
{
|
||||||
text: 'List ID',
|
text: 'List ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
notRequired: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
public: [
|
public: [
|
||||||
|
@ -122,10 +124,6 @@ const TYPE_PARAMS = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const fetchListTitle = pmem(async ({ id }) => {
|
|
||||||
const list = await api().masto.v1.lists.$select(id).fetch();
|
|
||||||
return list.title;
|
|
||||||
});
|
|
||||||
const fetchAccountTitle = pmem(async ({ id }) => {
|
const fetchAccountTitle = pmem(async ({ id }) => {
|
||||||
const account = await api().masto.v1.accounts.$select(id).fetch();
|
const account = await api().masto.v1.accounts.$select(id).fetch();
|
||||||
return account.username || account.acct || account.displayName;
|
return account.username || account.acct || account.displayName;
|
||||||
|
@ -150,10 +148,11 @@ export const SHORTCUTS_META = {
|
||||||
icon: 'notification',
|
icon: 'notification',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
id: 'list',
|
id: ({ id }) => (id ? 'list' : 'lists'),
|
||||||
title: fetchListTitle,
|
title: ({ id }) => (id ? getListTitle(id) : 'Lists'),
|
||||||
path: ({ id }) => `/l/${id}`,
|
path: ({ id }) => (id ? `/l/${id}` : '/l'),
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
|
excludeViewMode: ({ id }) => (!id ? ['multi-column'] : []),
|
||||||
},
|
},
|
||||||
public: {
|
public: {
|
||||||
id: 'public',
|
id: 'public',
|
||||||
|
@ -496,18 +495,8 @@ function ShortcutsSettings({ onClose }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FETCH_MAX_AGE = 1000 * 60; // 1 minute
|
|
||||||
const fetchLists = pmem(
|
|
||||||
() => {
|
|
||||||
const { masto } = api();
|
|
||||||
return masto.v1.lists.list();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxAge: FETCH_MAX_AGE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const FORM_NOTES = {
|
const FORM_NOTES = {
|
||||||
|
list: `Specific list is optional. For multi-column mode, list is required, else the column will not be shown.`,
|
||||||
search: `For multi-column mode, search term is required, else the column will not be shown.`,
|
search: `For multi-column mode, search term is required, else the column will not be shown.`,
|
||||||
hashtag: 'Multiple hashtags are supported. Space-separated.',
|
hashtag: 'Multiple hashtags are supported. Space-separated.',
|
||||||
};
|
};
|
||||||
|
@ -532,8 +521,7 @@ function ShortcutForm({
|
||||||
if (currentType !== 'list') return;
|
if (currentType !== 'list') return;
|
||||||
try {
|
try {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
const lists = await fetchLists();
|
const lists = await getLists();
|
||||||
lists.sort((a, b) => a.title.localeCompare(b.title));
|
|
||||||
setLists(lists);
|
setLists(lists);
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -644,6 +632,7 @@ function ShortcutForm({
|
||||||
disabled={disabled || uiState === 'loading'}
|
disabled={disabled || uiState === 'loading'}
|
||||||
defaultValue={editMode ? shortcut.id : undefined}
|
defaultValue={editMode ? shortcut.id : undefined}
|
||||||
>
|
>
|
||||||
|
<option value=""></option>
|
||||||
{lists.map((list) => (
|
{lists.map((list) => (
|
||||||
<option value={list.id}>{list.title}</option>
|
<option value={list.id}>{list.title}</option>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import './shortcuts.css';
|
import './shortcuts.css';
|
||||||
|
|
||||||
import { Menu, MenuItem } from '@szhsin/react-menu';
|
import { MenuDivider, SubMenu } from '@szhsin/react-menu';
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import { useMemo, useRef } from 'preact/hooks';
|
import { useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import { SHORTCUTS_META } from '../components/shortcuts-settings';
|
import { SHORTCUTS_META } from '../components/shortcuts-settings';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
|
import { getLists } from '../utils/lists';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
|
||||||
import AsyncText from './AsyncText';
|
import AsyncText from './AsyncText';
|
||||||
|
@ -34,47 +35,48 @@ function Shortcuts() {
|
||||||
|
|
||||||
const menuRef = useRef();
|
const menuRef = useRef();
|
||||||
|
|
||||||
const formattedShortcuts = useMemo(
|
const hasLists = useRef(false);
|
||||||
() =>
|
const formattedShortcuts = shortcuts
|
||||||
shortcuts
|
.map((pin, i) => {
|
||||||
.map((pin, i) => {
|
const { type, ...data } = pin;
|
||||||
const { type, ...data } = pin;
|
if (!SHORTCUTS_META[type]) return null;
|
||||||
if (!SHORTCUTS_META[type]) return null;
|
let { id, path, title, subtitle, icon } = SHORTCUTS_META[type];
|
||||||
let { id, path, title, subtitle, icon } = SHORTCUTS_META[type];
|
|
||||||
|
|
||||||
if (typeof id === 'function') {
|
if (typeof id === 'function') {
|
||||||
id = id(data, i);
|
id = id(data, i);
|
||||||
}
|
}
|
||||||
if (typeof path === 'function') {
|
if (typeof path === 'function') {
|
||||||
path = path(
|
path = path(
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
instance: data.instance || instance,
|
instance: data.instance || instance,
|
||||||
},
|
},
|
||||||
i,
|
i,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (typeof title === 'function') {
|
if (typeof title === 'function') {
|
||||||
title = title(data, i);
|
title = title(data, i);
|
||||||
}
|
}
|
||||||
if (typeof subtitle === 'function') {
|
if (typeof subtitle === 'function') {
|
||||||
subtitle = subtitle(data, i);
|
subtitle = subtitle(data, i);
|
||||||
}
|
}
|
||||||
if (typeof icon === 'function') {
|
if (typeof icon === 'function') {
|
||||||
icon = icon(data, i);
|
icon = icon(data, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (id === 'lists') {
|
||||||
id,
|
hasLists.current = true;
|
||||||
path,
|
}
|
||||||
title,
|
|
||||||
subtitle,
|
return {
|
||||||
icon,
|
id,
|
||||||
};
|
path,
|
||||||
})
|
title,
|
||||||
.filter(Boolean),
|
subtitle,
|
||||||
[shortcuts],
|
icon,
|
||||||
);
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {
|
useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {
|
||||||
|
@ -88,6 +90,8 @@ function Shortcuts() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [lists, setLists] = useState([]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="shortcuts">
|
<div id="shortcuts">
|
||||||
{snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? (
|
{snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? (
|
||||||
|
@ -147,6 +151,11 @@ function Shortcuts() {
|
||||||
menuClassName="glass-menu shortcuts-menu"
|
menuClassName="glass-menu shortcuts-menu"
|
||||||
gap={8}
|
gap={8}
|
||||||
position="anchor"
|
position="anchor"
|
||||||
|
onMenuChange={(e) => {
|
||||||
|
if (e.open && hasLists.current) {
|
||||||
|
getLists().then(setLists);
|
||||||
|
}
|
||||||
|
}}
|
||||||
menuButton={
|
menuButton={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -171,6 +180,35 @@ function Shortcuts() {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{formattedShortcuts.map(({ id, path, title, subtitle, icon }, i) => {
|
{formattedShortcuts.map(({ id, path, title, subtitle, icon }, i) => {
|
||||||
|
if (id === 'lists') {
|
||||||
|
return (
|
||||||
|
<SubMenu
|
||||||
|
menuClassName="glass-menu"
|
||||||
|
overflow="auto"
|
||||||
|
gap={-8}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<Icon icon={icon} size="l" />
|
||||||
|
<span class="menu-grow">
|
||||||
|
<AsyncText>{title}</AsyncText>
|
||||||
|
</span>
|
||||||
|
<Icon icon="chevron-right" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuLink to="/l">
|
||||||
|
<span>All Lists</span>
|
||||||
|
</MenuLink>
|
||||||
|
<MenuDivider />
|
||||||
|
{lists?.map((list) => (
|
||||||
|
<MenuLink key={list.id} to={`/l/${list.id}`}>
|
||||||
|
<span>{list.title}</span>
|
||||||
|
</MenuLink>
|
||||||
|
))}
|
||||||
|
</SubMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuLink
|
<MenuLink
|
||||||
to={path}
|
to={path}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import './lists.css';
|
import './lists.css';
|
||||||
|
|
||||||
import { Menu, MenuItem } from '@szhsin/react-menu';
|
import { Menu, MenuDivider, MenuItem } from '@szhsin/react-menu';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { InView } from 'react-intersection-observer';
|
import { InView } from 'react-intersection-observer';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
@ -12,10 +12,12 @@ import Link from '../components/link';
|
||||||
import ListAddEdit from '../components/list-add-edit';
|
import ListAddEdit from '../components/list-add-edit';
|
||||||
import Menu2 from '../components/menu2';
|
import Menu2 from '../components/menu2';
|
||||||
import MenuConfirm from '../components/menu-confirm';
|
import MenuConfirm from '../components/menu-confirm';
|
||||||
|
import MenuLink from '../components/menu-link';
|
||||||
import Modal from '../components/modal';
|
import Modal from '../components/modal';
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import { filteredItems } from '../utils/filters';
|
import { filteredItems } from '../utils/filters';
|
||||||
|
import { getList, getLists } from '../utils/lists';
|
||||||
import states, { saveStatus } from '../utils/states';
|
import states, { saveStatus } from '../utils/states';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
@ -71,13 +73,18 @@ function List(props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [lists, setLists] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getLists().then(setLists);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [list, setList] = useState({ title: 'List' });
|
const [list, setList] = useState({ title: 'List' });
|
||||||
// const [title, setTitle] = useState(`List`);
|
// const [title, setTitle] = useState(`List`);
|
||||||
useTitle(list.title, `/l/:id`);
|
useTitle(list.title, `/l/:id`);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const list = await masto.v1.lists.$select(id).fetch();
|
const list = await getList(id);
|
||||||
setList(list);
|
setList(list);
|
||||||
// setTitle(list.title);
|
// setTitle(list.title);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -107,9 +114,31 @@ function List(props) {
|
||||||
showReplyParent
|
showReplyParent
|
||||||
// refresh={reloadCount}
|
// refresh={reloadCount}
|
||||||
headerStart={
|
headerStart={
|
||||||
<Link to="/l" class="button plain">
|
// <Link to="/l" class="button plain">
|
||||||
<Icon icon="list" size="l" />
|
// <Icon icon="list" size="l" />
|
||||||
</Link>
|
// </Link>
|
||||||
|
<Menu2
|
||||||
|
overflow="auto"
|
||||||
|
menuButton={
|
||||||
|
<button type="button" class="plain">
|
||||||
|
<Icon icon="list" size="l" />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuLink to="/l">
|
||||||
|
<span>All Lists</span>
|
||||||
|
</MenuLink>
|
||||||
|
{lists?.length > 0 && (
|
||||||
|
<>
|
||||||
|
<MenuDivider />
|
||||||
|
{lists.map((list) => (
|
||||||
|
<MenuLink key={list.id} to={`/l/${list.id}`}>
|
||||||
|
<span>{list.title}</span>
|
||||||
|
</MenuLink>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Menu2>
|
||||||
}
|
}
|
||||||
headerEnd={
|
headerEnd={
|
||||||
<Menu2
|
<Menu2
|
||||||
|
|
|
@ -8,11 +8,10 @@ import ListAddEdit from '../components/list-add-edit';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import Modal from '../components/modal';
|
import Modal from '../components/modal';
|
||||||
import NavMenu from '../components/nav-menu';
|
import NavMenu from '../components/nav-menu';
|
||||||
import { api } from '../utils/api';
|
import { fetchLists } from '../utils/lists';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
function Lists() {
|
function Lists() {
|
||||||
const { masto } = api();
|
|
||||||
useTitle(`Lists`, `/l`);
|
useTitle(`Lists`, `/l`);
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
|
|
||||||
|
@ -22,8 +21,7 @@ function Lists() {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const lists = await masto.v1.lists.list();
|
const lists = await fetchLists();
|
||||||
lists.sort((a, b) => a.title.localeCompare(b.title));
|
|
||||||
console.log(lists);
|
console.log(lists);
|
||||||
setLists(lists);
|
setLists(lists);
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
|
|
114
src/utils/lists.js
Normal file
114
src/utils/lists.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { api } from './api';
|
||||||
|
import pmem from './pmem';
|
||||||
|
import store from './store';
|
||||||
|
|
||||||
|
const FETCH_MAX_AGE = 1000 * 60; // 1 minute
|
||||||
|
const MAX_AGE = 24 * 60 * 60 * 1000; // 1 day
|
||||||
|
|
||||||
|
export const fetchLists = pmem(
|
||||||
|
async () => {
|
||||||
|
const { masto } = api();
|
||||||
|
const lists = await masto.v1.lists.list();
|
||||||
|
lists.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
|
||||||
|
if (lists.length) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Save to local storage, with saved timestamp
|
||||||
|
store.account.set('lists', {
|
||||||
|
lists,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lists;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maxAge: FETCH_MAX_AGE,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export async function getLists() {
|
||||||
|
try {
|
||||||
|
const { lists, updatedAt } = store.account.get('lists') || {};
|
||||||
|
if (!lists?.length) return await fetchLists();
|
||||||
|
if (Date.now() - updatedAt > MAX_AGE) {
|
||||||
|
// Stale-while-revalidate
|
||||||
|
fetchLists();
|
||||||
|
return lists;
|
||||||
|
}
|
||||||
|
return lists;
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchList = pmem(
|
||||||
|
(id) => {
|
||||||
|
const { masto } = api();
|
||||||
|
return masto.v1.lists.$select(id).fetch();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maxAge: FETCH_MAX_AGE,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export async function getList(id) {
|
||||||
|
const { lists } = store.account.get('lists') || {};
|
||||||
|
console.log({ lists });
|
||||||
|
if (lists?.length) {
|
||||||
|
const theList = lists.find((l) => l.id === id);
|
||||||
|
if (theList) return theList;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return fetchList(id);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getListTitle(id) {
|
||||||
|
const list = await getList(id);
|
||||||
|
return list?.title || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addListStore(list) {
|
||||||
|
const { lists } = store.account.get('lists') || {};
|
||||||
|
if (lists?.length) {
|
||||||
|
lists.push(list);
|
||||||
|
lists.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
store.account.set('lists', {
|
||||||
|
lists,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateListStore(list) {
|
||||||
|
const { lists } = store.account.get('lists') || {};
|
||||||
|
if (lists?.length) {
|
||||||
|
const index = lists.findIndex((l) => l.id === list.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
lists[index] = list;
|
||||||
|
lists.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
store.account.set('lists', {
|
||||||
|
lists,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteListStore(listID) {
|
||||||
|
const { lists } = store.account.get('lists') || {};
|
||||||
|
if (lists?.length) {
|
||||||
|
const index = lists.findIndex((l) => l.id === listID);
|
||||||
|
if (index !== -1) {
|
||||||
|
lists.splice(index, 1);
|
||||||
|
store.account.set('lists', {
|
||||||
|
lists,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue