mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-02-25 09:18:51 +01:00
Experiment: allow Search in Shortcuts
This commit is contained in:
parent
6bcee318e4
commit
da58336285
5 changed files with 125 additions and 23 deletions
|
@ -9,6 +9,7 @@ import List from '../pages/list';
|
||||||
import Mentions from '../pages/mentions';
|
import Mentions from '../pages/mentions';
|
||||||
import Notifications from '../pages/notifications';
|
import Notifications from '../pages/notifications';
|
||||||
import Public from '../pages/public';
|
import Public from '../pages/public';
|
||||||
|
import Search from '../pages/search';
|
||||||
import Trending from '../pages/trending';
|
import Trending from '../pages/trending';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
@ -33,8 +34,11 @@ function Columns() {
|
||||||
hashtag: Hashtag,
|
hashtag: Hashtag,
|
||||||
mentions: Mentions,
|
mentions: Mentions,
|
||||||
trending: Trending,
|
trending: Trending,
|
||||||
|
search: Search,
|
||||||
}[type];
|
}[type];
|
||||||
if (!Component) return null;
|
if (!Component) return null;
|
||||||
|
// Don't show Search column with no query, for now
|
||||||
|
if (type === 'search' && !params.query) return null;
|
||||||
return (
|
return (
|
||||||
<Component key={type + JSON.stringify(params)} {...params} columnMode />
|
<Component key={type + JSON.stringify(params)} {...params} columnMode />
|
||||||
);
|
);
|
||||||
|
|
|
@ -123,6 +123,11 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
}
|
}
|
||||||
|
#shortcut-settings-form .form-note {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
#shortcut-settings-form form footer {
|
#shortcut-settings-form form footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
|
@ -32,12 +32,12 @@ const TYPES = [
|
||||||
'list',
|
'list',
|
||||||
'public',
|
'public',
|
||||||
'trending',
|
'trending',
|
||||||
// NOTE: Hide for now
|
'search',
|
||||||
// 'search', // Search on Mastodon ain't great
|
|
||||||
// 'account-statuses', // Need @acct search first
|
|
||||||
'hashtag',
|
'hashtag',
|
||||||
'bookmarks',
|
'bookmarks',
|
||||||
'favourites',
|
'favourites',
|
||||||
|
// NOTE: Hide for now
|
||||||
|
// 'account-statuses', // Need @acct search first
|
||||||
];
|
];
|
||||||
const TYPE_TEXT = {
|
const TYPE_TEXT = {
|
||||||
following: 'Home / Following',
|
following: 'Home / Following',
|
||||||
|
@ -87,6 +87,8 @@ const TYPE_PARAMS = {
|
||||||
text: 'Search term',
|
text: 'Search term',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
placeholder: 'Optional, unless for multi-column mode',
|
||||||
|
notRequired: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'account-statuses': [
|
'account-statuses': [
|
||||||
|
@ -168,9 +170,11 @@ export const SHORTCUTS_META = {
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
id: 'search',
|
id: 'search',
|
||||||
title: ({ query }) => query,
|
title: ({ query }) => (query ? `"${query}"` : 'Search'),
|
||||||
path: ({ query }) => `/search?q=${query}`,
|
path: ({ query }) =>
|
||||||
|
query ? `/search?q=${query}&type=statuses` : '/search',
|
||||||
icon: 'search',
|
icon: 'search',
|
||||||
|
excludeViewMode: ({ query }) => (!query ? ['multi-column'] : []),
|
||||||
},
|
},
|
||||||
'account-statuses': {
|
'account-statuses': {
|
||||||
id: 'account-statuses',
|
id: 'account-statuses',
|
||||||
|
@ -279,7 +283,8 @@ function ShortcutsSettings({ onClose }) {
|
||||||
const key = Object.values(shortcut).join('-');
|
const key = Object.values(shortcut).join('-');
|
||||||
const { type } = shortcut;
|
const { type } = shortcut;
|
||||||
if (!SHORTCUTS_META[type]) return null;
|
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') {
|
if (typeof title === 'function') {
|
||||||
title = title(shortcut, i);
|
title = title(shortcut, i);
|
||||||
}
|
}
|
||||||
|
@ -289,6 +294,12 @@ function ShortcutsSettings({ onClose }) {
|
||||||
if (typeof icon === 'function') {
|
if (typeof icon === 'function') {
|
||||||
icon = icon(shortcut, i);
|
icon = icon(shortcut, i);
|
||||||
}
|
}
|
||||||
|
if (typeof excludeViewMode === 'function') {
|
||||||
|
excludeViewMode = excludeViewMode(shortcut, i);
|
||||||
|
}
|
||||||
|
const excludedViewMode = excludeViewMode?.includes(
|
||||||
|
snapStates.settings.shortcutsViewMode,
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<li key={key}>
|
<li key={key}>
|
||||||
<Icon icon={icon} />
|
<Icon icon={icon} />
|
||||||
|
@ -300,6 +311,11 @@ function ShortcutsSettings({ onClose }) {
|
||||||
<small class="ib insignificant">{subtitle}</small>
|
<small class="ib insignificant">{subtitle}</small>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{excludedViewMode && (
|
||||||
|
<span class="tag">
|
||||||
|
Not available in current view mode
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span class="shortcut-actions">
|
<span class="shortcut-actions">
|
||||||
<button
|
<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({
|
function ShortcutForm({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -615,6 +636,7 @@ function ShortcutForm({
|
||||||
<span>{text}</span>{' '}
|
<span>{text}</span>{' '}
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
|
switch={type === 'checkbox' || undefined}
|
||||||
name={name}
|
name={name}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
required={type === 'text' && !notRequired}
|
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>
|
<footer>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
@ -1,20 +1,49 @@
|
||||||
#search-page .deck > header .header-grid {
|
#search-page .deck > header .header-grid {
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
}
|
}
|
||||||
#search-page header input {
|
#search-page header {
|
||||||
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background-color: var(--bg-faded-color);
|
background-color: var(--bg-faded-color);
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
}
|
|
||||||
#search-page header input:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
border-color: var(--link-color);
|
border-color: var(--link-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#columns & {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
padding-inline: 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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 {
|
#search-page ul.accounts-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
@ -16,21 +16,26 @@ import Status from '../components/status';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import { fetchRelationships } from '../utils/relationships';
|
import { fetchRelationships } from '../utils/relationships';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
|
import usePageVisibility from '../utils/usePageVisibility';
|
||||||
|
import useScroll from '../utils/useScroll';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const SHORT_LIMIT = 5;
|
const SHORT_LIMIT = 5;
|
||||||
const LIMIT = 40;
|
const LIMIT = 40;
|
||||||
|
const emptySearchParams = new URLSearchParams();
|
||||||
|
|
||||||
function Search(props) {
|
function Search({ columnMode, ...props }) {
|
||||||
const params = useParams();
|
const params = columnMode ? {} : useParams();
|
||||||
const { masto, instance, authenticated } = api({
|
const { masto, instance, authenticated } = api({
|
||||||
instance: params.instance,
|
instance: params.instance,
|
||||||
});
|
});
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = columnMode ? [emptySearchParams] : useSearchParams();
|
||||||
const searchFormRef = useRef();
|
const searchFormRef = useRef();
|
||||||
const q = props?.query || searchParams.get('q');
|
const q = props?.query || searchParams.get('q');
|
||||||
const type = props?.type || searchParams.get('type');
|
const type = columnMode
|
||||||
|
? 'statuses'
|
||||||
|
: props?.type || searchParams.get('type');
|
||||||
useTitle(
|
useTitle(
|
||||||
q
|
q
|
||||||
? `Search: ${q}${
|
? `Search: ${q}${
|
||||||
|
@ -86,6 +91,10 @@ function Search(props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadResults(firstLoad) {
|
function loadResults(firstLoad) {
|
||||||
|
if (firstLoad) {
|
||||||
|
offsetRef.current = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!firstLoad && !authenticated) {
|
if (!firstLoad && !authenticated) {
|
||||||
// Search results pagination is only available to authenticated users
|
// Search results pagination is only available to authenticated users
|
||||||
return;
|
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(() => {
|
useEffect(() => {
|
||||||
if (q) {
|
if (q) {
|
||||||
searchFormRef.current?.setValue?.(q);
|
searchFormRef.current?.setValue?.(q);
|
||||||
|
@ -172,11 +197,22 @@ function Search(props) {
|
||||||
<NavMenu />
|
<NavMenu />
|
||||||
</div>
|
</div>
|
||||||
<SearchForm ref={searchFormRef} />
|
<SearchForm ref={searchFormRef} />
|
||||||
<div class="header-side"> </div>
|
<div class="header-side">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain"
|
||||||
|
onClick={() => {
|
||||||
|
loadResults(true);
|
||||||
|
}}
|
||||||
|
disabled={uiState === 'loading'}
|
||||||
|
>
|
||||||
|
<Icon icon="search" size="l" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{!!q && (
|
{!!q && !columnMode && (
|
||||||
<div
|
<div
|
||||||
ref={filterBarParent}
|
ref={filterBarParent}
|
||||||
class={`filter-bar ${uiState === 'loading' ? 'loading' : ''}`}
|
class={`filter-bar ${uiState === 'loading' ? 'loading' : ''}`}
|
||||||
|
|
Loading…
Reference in a new issue