Allow instance-based hashtags

Also change design a little
This commit is contained in:
Lim Chee Aun 2023-04-08 20:42:38 +08:00
parent eebb55ba38
commit 959ac468d8
5 changed files with 117 additions and 40 deletions

View file

@ -26,6 +26,8 @@
#shortcuts-settings-container .shortcuts-list li .shortcut-text { #shortcuts-settings-container .shortcuts-list li .shortcut-text {
flex-grow: 1; flex-grow: 1;
min-width: 0; min-width: 0;
line-height: 1;
word-break: break-word;
} }
#shortcuts-settings-container .shortcuts-list li .shortcut-actions { #shortcuts-settings-container .shortcuts-list li .shortcut-actions {
flex-shrink: 0; flex-shrink: 0;

View file

@ -94,6 +94,13 @@ const TYPE_PARAMS = {
placeholder: 'e.g. PixelArt (Max 5, space-separated)', placeholder: 'e.g. PixelArt (Max 5, space-separated)',
pattern: '[^#]+', pattern: '[^#]+',
}, },
{
text: 'Instance',
name: 'instance',
type: 'text',
placeholder: 'Optional, e.g. mastodon.social',
notRequired: true,
},
], ],
}; };
export const SHORTCUTS_META = { export const SHORTCUTS_META = {
@ -131,14 +138,15 @@ export const SHORTCUTS_META = {
}, },
public: { public: {
id: 'public', id: 'public',
title: ({ local, instance }) => title: ({ local }) => (local ? 'Local' : 'Federated'),
`${local ? 'Local' : 'Federated'} (${instance})`, subtitle: ({ instance }) => instance,
path: ({ local, instance }) => `/${instance}/p${local ? '/l' : ''}`, path: ({ local, instance }) => `/${instance}/p${local ? '/l' : ''}`,
icon: ({ local }) => (local ? 'group' : 'earth'), icon: ({ local }) => (local ? 'group' : 'earth'),
}, },
trending: { trending: {
id: 'trending', id: 'trending',
title: 'Trending', title: 'Trending',
subtitle: ({ instance }) => instance,
path: ({ instance }) => `/${instance}/trending`, path: ({ instance }) => `/${instance}/trending`,
icon: 'chart', icon: 'chart',
}, },
@ -177,6 +185,7 @@ export const SHORTCUTS_META = {
hashtag: { hashtag: {
id: 'hashtag', id: 'hashtag',
title: ({ hashtag }) => hashtag, title: ({ hashtag }) => hashtag,
subtitle: ({ instance }) => instance,
path: ({ hashtag }) => `/t/${hashtag.split(/\s+/).join('+')}`, path: ({ hashtag }) => `/t/${hashtag.split(/\s+/).join('+')}`,
icon: 'hashtag', icon: 'hashtag',
}, },
@ -307,10 +316,13 @@ function ShortcutsSettings() {
const key = i + Object.values(shortcut); const key = i + Object.values(shortcut);
const { type } = shortcut; const { type } = shortcut;
if (!SHORTCUTS_META[type]) return null; if (!SHORTCUTS_META[type]) return null;
let { icon, title } = SHORTCUTS_META[type]; let { icon, title, subtitle } = SHORTCUTS_META[type];
if (typeof title === 'function') { if (typeof title === 'function') {
title = title(shortcut, i); title = title(shortcut, i);
} }
if (typeof subtitle === 'function') {
subtitle = subtitle(shortcut, i);
}
if (typeof icon === 'function') { if (typeof icon === 'function') {
icon = icon(shortcut, i); icon = icon(shortcut, i);
} }
@ -319,6 +331,12 @@ function ShortcutsSettings() {
<Icon icon={icon} /> <Icon icon={icon} />
<span class="shortcut-text"> <span class="shortcut-text">
<AsyncText>{title}</AsyncText> <AsyncText>{title}</AsyncText>
{subtitle && (
<>
{' '}
<small class="ib insignificant">{subtitle}</small>
</>
)}
</span> </span>
<span class="shortcut-actions"> <span class="shortcut-actions">
<button <button
@ -468,13 +486,17 @@ function ShortcutForm({
</label> </label>
</p> </p>
{TYPE_PARAMS[currentType]?.map?.( {TYPE_PARAMS[currentType]?.map?.(
({ text, name, type, placeholder, pattern }) => { ({ text, name, type, placeholder, pattern, notRequired }) => {
if (currentType === 'list') { if (currentType === 'list') {
return ( return (
<p> <p>
<label> <label>
<span>List</span> <span>List</span>
<select name="id" required disabled={disabled}> <select
name="id"
required={!notRequired}
disabled={disabled}
>
{lists.map((list) => ( {lists.map((list) => (
<option value={list.id}>{list.title}</option> <option value={list.id}>{list.title}</option>
))} ))}
@ -492,7 +514,7 @@ function ShortcutForm({
type={type} type={type}
name={name} name={name}
placeholder={placeholder} placeholder={placeholder}
required={type === 'text'} required={type === 'text' && !notRequired}
disabled={disabled} disabled={disabled}
list={ list={
currentType === 'hashtag' currentType === 'hashtag'

View file

@ -134,6 +134,14 @@ shortcuts .tab-bar[hidden] {
#app[data-shortcuts-view-mode='tab-menu-bar'] .deck-container { #app[data-shortcuts-view-mode='tab-menu-bar'] .deck-container {
padding-bottom: 52px; padding-bottom: 52px;
} }
#shortcuts .tab-bar li a.has-subtitle .icon,
#shortcuts .tab-bar li a.has-subtitle .icon svg {
width: 14px !important;
height: 14px !important;
}
#shortcuts .tab-bar li a span {
line-height: 1;
}
} }
@media (min-width: 40em) { @media (min-width: 40em) {
@ -172,6 +180,10 @@ shortcuts .tab-bar[hidden] {
height: 44px; height: 44px;
gap: 4px; gap: 4px;
} }
#shortcuts .tab-bar li a span {
text-align: left;
line-height: 1;
}
#app:has(#home-page):not(:has(#home-page ~ .deck-container)):has( #app:has(#home-page):not(:has(#home-page ~ .deck-container)):has(
header[hidden] header[hidden]
) )

View file

@ -30,7 +30,7 @@ function 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, 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);
@ -41,6 +41,9 @@ function Shortcuts() {
if (typeof title === 'function') { if (typeof title === 'function') {
title = title(data, i); title = title(data, i);
} }
if (typeof subtitle === 'function') {
subtitle = subtitle(data, i);
}
if (typeof icon === 'function') { if (typeof icon === 'function') {
icon = icon(data, i); icon = icon(data, i);
} }
@ -49,6 +52,7 @@ function Shortcuts() {
id, id,
path, path,
title, title,
subtitle,
icon, icon,
}; };
}) })
@ -73,10 +77,12 @@ function Shortcuts() {
{snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? ( {snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? (
<nav class="tab-bar"> <nav class="tab-bar">
<ul> <ul>
{formattedShortcuts.map(({ id, path, title, icon }, i) => { {formattedShortcuts.map(
({ id, path, title, subtitle, icon }, i) => {
return ( return (
<li key={i + title}> <li key={i + title}>
<Link <Link
class={subtitle ? 'has-subtitle' : ''}
to={path} to={path}
onClick={(e) => { onClick={(e) => {
if (e.target.classList.contains('is-active')) { if (e.target.classList.contains('is-active')) {
@ -97,11 +103,18 @@ function Shortcuts() {
<Icon icon={icon} size="xl" alt={title} /> <Icon icon={icon} size="xl" alt={title} />
<span> <span>
<AsyncText>{title}</AsyncText> <AsyncText>{title}</AsyncText>
{subtitle && (
<>
<br />
<small>{subtitle}</small>
</>
)}
</span> </span>
</Link> </Link>
</li> </li>
); );
})} },
)}
</ul> </ul>
</nav> </nav>
) : ( ) : (
@ -132,13 +145,21 @@ function Shortcuts() {
</button> </button>
} }
> >
{formattedShortcuts.map(({ path, title, icon }, i) => { {formattedShortcuts.map(({ path, title, subtitle, icon }, i) => {
return ( return (
<MenuLink to={path} key={i + title} class="glass-menu-item"> <MenuLink to={path} key={i + title} class="glass-menu-item">
<Icon icon={icon} size="l" />{' '} <Icon icon={icon} size="l" />{' '}
<span class="menu-grow"> <span class="menu-grow">
<span>
<AsyncText>{title}</AsyncText> <AsyncText>{title}</AsyncText>
</span> </span>
{subtitle && (
<>
{' '}
<small class="more-insignificant">{subtitle}</small>
</>
)}
</span>
<span class="menu-shortcut hide-until-focus-visible"> <span class="menu-shortcut hide-until-focus-visible">
{i + 1} {i + 1}
</span> </span>

View file

@ -32,8 +32,10 @@ function Hashtags(props) {
hashtags.sort(); hashtags.sort();
hashtag = hashtags[0]; hashtag = hashtags[0];
const { masto, instance } = api({ instance: params.instance }); const { masto, instance, authenticated } = api({
const { authenticated } = api(); instance: props?.instance || params.instance,
});
const { authenticated: currentAuthenticated } = api();
const hashtagTitle = hashtags.map((t) => `#${t}`).join(' '); const hashtagTitle = hashtags.map((t) => `#${t}`).join(' ');
const title = instance ? `${hashtagTitle} on ${instance}` : hashtagTitle; const title = instance ? `${hashtagTitle} on ${instance}` : hashtagTitle;
useTitle(title, `/:instance?/t/:hashtag`); useTitle(title, `/:instance?/t/:hashtag`);
@ -99,7 +101,7 @@ function Hashtags(props) {
return ( return (
<Timeline <Timeline
key={hashtagTitle} key={instance + hashtagTitle}
title={title} title={title}
titleComponent={ titleComponent={
!!instance && ( !!instance && (
@ -232,6 +234,7 @@ function Hashtags(props) {
{hashtags.map((t, i) => ( {hashtags.map((t, i) => (
<MenuItem <MenuItem
key={t} key={t}
disabled={hashtags.length === 1}
onClick={(e) => { onClick={(e) => {
hashtags.splice(i, 1); hashtags.splice(i, 1);
hashtags.sort(); hashtags.sort();
@ -252,7 +255,7 @@ function Hashtags(props) {
</MenuGroup> </MenuGroup>
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
disabled={!authenticated} disabled={!currentAuthenticated}
onClick={() => { onClick={() => {
const shortcut = { const shortcut = {
type: 'hashtag', type: 'hashtag',
@ -281,6 +284,23 @@ function Hashtags(props) {
> >
<Icon icon="shortcut" /> <span>Add to Shorcuts</span> <Icon icon="shortcut" /> <span>Add to Shorcuts</span>
</MenuItem> </MenuItem>
<MenuItem
onClick={() => {
let newInstance = prompt(
'Enter a new instance e.g. "mastodon.social"',
);
if (!/\./.test(newInstance)) {
if (newInstance) alert('Invalid instance');
return;
}
if (newInstance) {
newInstance = newInstance.toLowerCase().trim();
navigate(`/${newInstance}/t/${hashtags.join('+')}`);
}
}}
>
<Icon icon="bus" /> <span>Go to another instance</span>
</MenuItem>
</Menu> </Menu>
} }
/> />