1
0
Fork 0

Experiment: allow Search in Shortcuts

This commit is contained in:
Lim Chee Aun 2023-12-22 18:01:41 +08:00
parent 6bcee318e4
commit da58336285
5 changed files with 125 additions and 23 deletions

View file

@ -9,6 +9,7 @@ import List from '../pages/list';
import Mentions from '../pages/mentions';
import Notifications from '../pages/notifications';
import Public from '../pages/public';
import Search from '../pages/search';
import Trending from '../pages/trending';
import states from '../utils/states';
import useTitle from '../utils/useTitle';
@ -33,8 +34,11 @@ function Columns() {
hashtag: Hashtag,
mentions: Mentions,
trending: Trending,
search: Search,
}[type];
if (!Component) return null;
// Don't show Search column with no query, for now
if (type === 'search' && !params.query) return null;
return (
<Component key={type + JSON.stringify(params)} {...params} columnMode />
);

View file

@ -123,6 +123,11 @@
min-width: 0;
max-width: 320px;
}
#shortcut-settings-form .form-note {
display: flex;
gap: 6px;
align-items: center;
}
#shortcut-settings-form form footer {
display: flex;
gap: 16px;

View file

@ -32,12 +32,12 @@ const TYPES = [
'list',
'public',
'trending',
// NOTE: Hide for now
// 'search', // Search on Mastodon ain't great
// 'account-statuses', // Need @acct search first
'search',
'hashtag',
'bookmarks',
'favourites',
// NOTE: Hide for now
// 'account-statuses', // Need @acct search first
];
const TYPE_TEXT = {
following: 'Home / Following',
@ -87,6 +87,8 @@ const TYPE_PARAMS = {
text: 'Search term',
name: 'query',
type: 'text',
placeholder: 'Optional, unless for multi-column mode',
notRequired: true,
},
],
'account-statuses': [
@ -168,9 +170,11 @@ export const SHORTCUTS_META = {
},
search: {
id: 'search',
title: ({ query }) => query,
path: ({ query }) => `/search?q=${query}`,
title: ({ query }) => (query ? `"${query}"` : 'Search'),
path: ({ query }) =>
query ? `/search?q=${query}&type=statuses` : '/search',
icon: 'search',
excludeViewMode: ({ query }) => (!query ? ['multi-column'] : []),
},
'account-statuses': {
id: 'account-statuses',
@ -279,7 +283,8 @@ function ShortcutsSettings({ onClose }) {
const key = Object.values(shortcut).join('-');
const { type } = shortcut;
if (!SHORTCUTS_META[type]) return null;
let { icon, title, subtitle } = SHORTCUTS_META[type];
let { icon, title, subtitle, excludeViewMode } =
SHORTCUTS_META[type];
if (typeof title === 'function') {
title = title(shortcut, i);
}
@ -289,6 +294,12 @@ function ShortcutsSettings({ onClose }) {
if (typeof icon === 'function') {
icon = icon(shortcut, i);
}
if (typeof excludeViewMode === 'function') {
excludeViewMode = excludeViewMode(shortcut, i);
}
const excludedViewMode = excludeViewMode?.includes(
snapStates.settings.shortcutsViewMode,
);
return (
<li key={key}>
<Icon icon={icon} />
@ -300,6 +311,11 @@ function ShortcutsSettings({ onClose }) {
<small class="ib insignificant">{subtitle}</small>
</>
)}
{excludedViewMode && (
<span class="tag">
Not available in current view mode
</span>
)}
</span>
<span class="shortcut-actions">
<button
@ -468,6 +484,11 @@ const fetchLists = pmem(
},
);
const FORM_NOTES = {
search: `For multi-column mode, search term is required, else the column will not be shown.`,
hashtag: 'Multiple hashtags are supported. Space-separated.',
};
function ShortcutForm({
onSubmit,
disabled,
@ -615,6 +636,7 @@ function ShortcutForm({
<span>{text}</span>{' '}
<input
type={type}
switch={type === 'checkbox' || undefined}
name={name}
placeholder={placeholder}
required={type === 'text' && !notRequired}
@ -642,6 +664,12 @@ function ShortcutForm({
);
},
)}
{!!FORM_NOTES[currentType] && (
<p class="form-note insignificant">
<Icon icon="info" />
{FORM_NOTES[currentType]}
</p>
)}
<footer>
<button
type="submit"

View file

@ -1,18 +1,47 @@
#search-page .deck > header .header-grid {
grid-template-columns: auto 1fr auto;
}
#search-page header input {
width: 100%;
padding: 8px 16px;
border: 0;
border-radius: 999px;
background-color: var(--bg-faded-color);
border: 2px solid transparent;
#search-page header {
input {
width: 100%;
padding: 8px 16px;
border: 0;
border-radius: 999px;
background-color: var(--bg-faded-color);
border: 2px solid transparent;
&:focus {
outline: 0;
background-color: var(--bg-color);
border-color: var(--link-color);
}
#columns & {
font-weight: bold;
background-color: transparent;
text-align: center;
padding-inline: 8px;
text-overflow: ellipsis;
}
}
}
#search-page header input:focus {
outline: 0;
background-color: var(--bg-color);
border-color: var(--link-color);
#columns #search-page {
.header-grid {
.header-side {
min-width: 40px;
&:last-of-type {
button {
display: block;
&:not(:hover, :focus) {
color: var(--text-insignificant-color);
}
}
}
}
}
}
#search-page ul.accounts-list {

View file

@ -16,21 +16,26 @@ import Status from '../components/status';
import { api } from '../utils/api';
import { fetchRelationships } from '../utils/relationships';
import shortenNumber from '../utils/shorten-number';
import usePageVisibility from '../utils/usePageVisibility';
import useScroll from '../utils/useScroll';
import useTitle from '../utils/useTitle';
const SHORT_LIMIT = 5;
const LIMIT = 40;
const emptySearchParams = new URLSearchParams();
function Search(props) {
const params = useParams();
function Search({ columnMode, ...props }) {
const params = columnMode ? {} : useParams();
const { masto, instance, authenticated } = api({
instance: params.instance,
});
const [uiState, setUIState] = useState('default');
const [searchParams] = useSearchParams();
const [searchParams] = columnMode ? [emptySearchParams] : useSearchParams();
const searchFormRef = useRef();
const q = props?.query || searchParams.get('q');
const type = props?.type || searchParams.get('type');
const type = columnMode
? 'statuses'
: props?.type || searchParams.get('type');
useTitle(
q
? `Search: ${q}${
@ -86,6 +91,10 @@ function Search(props) {
};
function loadResults(firstLoad) {
if (firstLoad) {
offsetRef.current = 0;
}
if (!firstLoad && !authenticated) {
// Search results pagination is only available to authenticated users
return;
@ -142,6 +151,22 @@ function Search(props) {
})();
}
const { reachStart } = useScroll({
scrollableRef,
});
const lastHiddenTime = useRef();
usePageVisibility((visible) => {
if (visible && reachStart) {
const timeDiff = Date.now() - lastHiddenTime.current;
if (!lastHiddenTime.current || timeDiff > 1000 * 3) {
// 3 seconds
loadResults(true);
} else {
lastHiddenTime.current = Date.now();
}
}
});
useEffect(() => {
if (q) {
searchFormRef.current?.setValue?.(q);
@ -172,11 +197,22 @@ function Search(props) {
<NavMenu />
</div>
<SearchForm ref={searchFormRef} />
<div class="header-side">&nbsp;</div>
<div class="header-side">
<button
type="button"
class="plain"
onClick={() => {
loadResults(true);
}}
disabled={uiState === 'loading'}
>
<Icon icon="search" size="l" />
</button>
</div>
</div>
</header>
<main>
{!!q && (
{!!q && !columnMode && (
<div
ref={filterBarParent}
class={`filter-bar ${uiState === 'loading' ? 'loading' : ''}`}