mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
ui: add omni-search
This commit is contained in:
1 parent
c991062738
commit
d4cc5e9c5b
3 files changed
+259
No files matched your search
@@ -0,0 +1,125 @@
|
||||
import { debounce } from 'lodash';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { STATUS_CODES } from 'vj/constant/record';
|
||||
import { AutoloadPage } from 'vj/misc/Page';
|
||||
import {
|
||||
api, gql, i18n, request,
|
||||
} from 'vj/utils';
|
||||
|
||||
export default new AutoloadPage('omnibar', () => {
|
||||
const $search = $(`
|
||||
<div class='omnibar' data-hotkey="esc:click" style="opacity:0;display:none;">
|
||||
<div class='omnibar-main'>
|
||||
<div>
|
||||
<input placeholder="Search" />
|
||||
<span class="icon icon-search"></span>
|
||||
</div>
|
||||
<div id='omnibar-content'></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
const $entry = $(`
|
||||
<button class="omnibar-toggle" data-global-hotkey="ctrl+k:click">
|
||||
<span class="icon icon-search"></span>
|
||||
</button>
|
||||
`);
|
||||
$(document.body).append($search);
|
||||
$(document.body).append($entry);
|
||||
const $input = $('.omnibar input') as JQuery<HTMLInputElement>;
|
||||
|
||||
const prefix = window.location.href.startsWith('/d/') ? `/d/${UiContext.domainId}` : '';
|
||||
let setSearching;
|
||||
let pdocs = [];
|
||||
let psdict = {};
|
||||
let udocs = [];
|
||||
function SearchResult() {
|
||||
const [searching, set] = React.useState(false);
|
||||
setSearching = set;
|
||||
return (
|
||||
<>
|
||||
{searching && <div>Searching...</div>}
|
||||
{pdocs.length > 0 && <div className="omnibar-content-title">{i18n('Problems')}</div>}
|
||||
{pdocs.map(({
|
||||
domainId, docId, pid, title, nSubmit, nAccept,
|
||||
}) => (
|
||||
<a
|
||||
key={domainId + docId}
|
||||
className="omnibar-content-section omnibar-problem"
|
||||
href={`${domainId !== UiContext.domainId ? `/d/${domainId}` : prefix}/p/${pid || docId}`}
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
href={psdict[`${domainId}#${docId}`]?.rid && `/record/${psdict[`${domainId}#${docId}`]?.rid}`}
|
||||
className={`record-status--text ${STATUS_CODES[psdict[`${domainId}#${docId}`]?.status]}`}
|
||||
>
|
||||
<span className={`icon record-status--icon ${STATUS_CODES[psdict[`${domainId}#${docId}`]?.status]}`}></span>
|
||||
</a>
|
||||
<div>{title}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="icon icon-book" />{domainId !== UiContext.domainId ? `${domainId}#` : ''}{pid || docId}
|
||||
<span className="icon icon-pie-chart" />{nAccept}/{nSubmit}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
{udocs.length > 0 && <div className="omnibar-content-title">{i18n('Users')}</div>}
|
||||
{udocs.map(({
|
||||
_id, uname, avatarUrl,
|
||||
}) => (
|
||||
<a key={_id} className="omnibar-content-section omnibar-user" href={`${prefix}/user/${_id}`}>
|
||||
<div>
|
||||
<img src={avatarUrl} alt={uname} />
|
||||
<div className="omnibar-user-info">
|
||||
<span>UID {_id}</span><br />
|
||||
<div><span style={{ lineHeight: '20px' }}>{uname}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
ReactDOM.createRoot(document.getElementById('omnibar-content')!).render(<SearchResult />);
|
||||
|
||||
async function search(query: string) {
|
||||
if (!query?.trim()) {
|
||||
pdocs = [];
|
||||
setSearching?.(false);
|
||||
return;
|
||||
}
|
||||
setSearching?.(true);
|
||||
[{ pdocs, psdict }, udocs] = await Promise.all([
|
||||
request.get(`/d/${UiContext.domainId}/p?q=${encodeURIComponent(query)}&limit=10`),
|
||||
api(gql`
|
||||
users(search: ${query}) {
|
||||
_id
|
||||
uname
|
||||
displayName
|
||||
avatarUrl
|
||||
}
|
||||
`, ['data', 'users']),
|
||||
]);
|
||||
setSearching?.(false);
|
||||
}
|
||||
$input.on('input', debounce((ev) => search(ev.target.value), 1000));
|
||||
|
||||
let open = false;
|
||||
const show = () => {
|
||||
$search.show();
|
||||
setTimeout(() => {
|
||||
$search.css('opacity', '1');
|
||||
$input.focus();
|
||||
}, 20);
|
||||
open = true;
|
||||
};
|
||||
const hide = () => {
|
||||
$search.css('opacity', '0');
|
||||
setTimeout(() => $search.hide(), 200);
|
||||
open = false;
|
||||
};
|
||||
$entry.on('click', () => (open ? hide() : show()));
|
||||
$input.on('click', (ev) => ev.stopPropagation());
|
||||
$('.omnibar-main').on('click', '.omnibar-content-section', (ev) => ev.stopPropagation());
|
||||
$search.on('click', hide);
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
.omnibar {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(3px);
|
||||
z-index: 999;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.omnibar-main {
|
||||
height: min(600px, 100% - 20px);
|
||||
width: min(750px, 60%);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
>div:first-child {
|
||||
transition: 0.2s;
|
||||
margin-bottom: 10px;
|
||||
input {
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #aaa;
|
||||
padding-left: 42px;
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
span {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
position: absolute;
|
||||
display: grid;
|
||||
scale: 1.5;
|
||||
place-items: center;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
>div:last-child {
|
||||
flex: 1;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar { width: 0 !important; }
|
||||
}
|
||||
}
|
||||
.omnibar-toggle {
|
||||
z-index: 99;
|
||||
background: white;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 30px;
|
||||
transition: 0.2s;
|
||||
border-radius: 5px;
|
||||
&:hover {
|
||||
background: #ccc;
|
||||
}
|
||||
span {
|
||||
scale: 1.5;
|
||||
}
|
||||
}
|
||||
#omnibar-content {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
.omnibar-content-title {
|
||||
text-align: left;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.omnibar-content-section {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
line-height: 1;
|
||||
margin-bottom: 10px;
|
||||
border: 2px solid #888;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
text-decoration: none !important;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
>div {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.omnibar-user img {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.omnibar-user-info {
|
||||
>span {
|
||||
font-size: 14px;
|
||||
margin-bottom: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
>div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.omnibar-problem a {
|
||||
margin: 2px 0px 3px 0px;
|
||||
}
|
||||
.omnibar-problem .icon {
|
||||
margin-right: 3px;
|
||||
&:not(:first-child) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1000" width="1000"><path d="M437 542c-1.8-3.2-2.4-7-1.7-10.6l76.3-405c1.7-9-4.5-17.6-13.5-18.9-20.8-3.1-42.1-4.8-63.8-4.8C187.6 102.7-11 308.3.5 557.5c10.4 226 196.4 407.9 422.6 413.6 64.3 1.6 125.6-10.7 181-34.3 20.6-8.7 28.8-33.6 18.1-53.3L437 542zm533.8-161.7c-6.3-16.2-25-23.7-40.8-16.3L594.2 523.5c-15.5 7.4-21.6 26.2-13.5 41.2l177.2 326.8c8.3 15.4 28 20.3 42.7 10.8C920.6 825 1000 690.3 1000 537c0-55.3-10.4-108.1-29.2-156.7zM560.6 432 891 275.1c11.8-5.6 16.7-20.1 10.4-31.6C843.7 138 743.4 59 623.7 29.6c-12.7-3.1-25.3 5.3-27.7 18.1l-67.7 359.4c-3.5 18.4 15.4 32.9 32.3 24.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 639 B |
Reference in new issue
Block a user