luoxu-api-pub/src/App.svelte
2021-10-28 13:27:43 +08:00

275 lines
6.5 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script>
import { onMount, setContext } from 'svelte'
import Message from './Message.svelte'
import Name from './Name.svelte'
import { sleep } from './util.js'
const LUOXU_URL = 'https://lab.lilydjwg.me/luoxu'
const islocal = LUOXU_URL.indexOf('http://localhost') === 0
let groups = []
let group
let query
let error
let result
let now = new Date()
let loading = false
let need_update_title = false
let sender
let selected_init
let our_hash_change = false
setContext('LUOXU_URL', LUOXU_URL)
function parse_hash() {
const hash = location.hash
if(hash) {
return new URLSearchParams(hash.substring(1))
}
}
onMount(async () => {
do_hash_search()
while(true) {
try{
const res = await fetch(`${LUOXU_URL}/groups`)
groups = (await res.json()).groups
need_update_title = true
if(!group) {
group = ''
}
break
}catch(e){
console.error('failed to fetch group info, will retry', e)
await sleep(1000)
}
}
})
$: {
// only update title on hash change (doing a search)
if(need_update_title && groups) {
let group_name
for(const g of groups) {
if(g.group_id == group) {
group_name = g.name
}
}
if(query && group_name) {
document.title = `搜索:${query}${group_name} - 落絮`
}else if(query) {
document.title = `搜索:${query} - 落絮`
}else if(group_name) {
document.title = `搜索 ${group_name} - 落絮`
}else{
document.title = '落絮'
}
need_update_title = false
}
}
function do_hash_search() {
const info = parse_hash()
if(info) {
query = ''
group = ''
result = null
if(info.has('g')) {
group = info.get('g')|0
}
if(info.has('q')) {
query = info.get('q')
}
if(info.has('sender')) {
sender = info.get('sender')
selected_init = sender
}
if((group || islocal) && query) {
result = null
do_search()
}
}
}
async function do_search(more) {
if(!group && !islocal) {
error = '请选择要搜索的群组'
return
}
if(!query && !islocal) {
error = '请输入搜索关键字'
return
}
error = ''
our_hash_change = true
console.log(`searching ${query} for group ${group}, older than ${more}, from ${sender}`)
const q = new URLSearchParams()
if(group) {
q.append('g', group)
}
if(query) {
q.append('q', query)
}
if(sender) {
q.append('sender', sender)
}
let url
const qstr = q.toString()
if(!more) {
location.hash = `#${qstr}`
need_update_title = true
if(result) {
result.messages = []
}
url = `${LUOXU_URL}/search?${qstr}`
}else{
url = `${LUOXU_URL}/search?${q}&end=${more}`
}
now = new Date()
loading = true
try{
const res = await fetch(url)
const r = await res.json()
loading = false
if(more) {
return r
}else{
result = r
}
}catch(e){
error = e
loading = false
}
our_hash_change = false;
}
async function on_group_change() {
error = ''
if(query) {
await do_search()
}
}
async function do_search_more() {
const more = result.messages[result.messages.length-1].t
const old_msgs = result.messages
const new_result = await do_search(more)
result.messages = [...old_msgs, ...new_result.messages]
result.has_more = new_result.has_more
}
</script>
<svelte:window on:hashchange={() => {if(!our_hash_change) do_hash_search()}}/>
<main>
<div id="searchbox">
{#if groups.length == 0}
<select>
<option selected>正在加载群组信息...</option>
</select>
{:else}
<select bind:value={group} on:change={on_group_change}>
{#if islocal}
<option value="">全部</option>
{/if}
{#each groups as group}
<option value={group.group_id}>{group.name}</option>
{/each}
</select>
{/if}
<input type="search" bind:value={query}
on:input={() => error = ''}
on:keydown={e => {if(e.key == 'Enter'){do_search()}}}
/>
<Name group={group} bind:selected={sender} selected_init={selected_init}/>
<button on:click={() => do_search()}>搜索</button>
</div>
{#if result}
{#each result.messages as message}
<Message msg={message} groupinfo={result.groupinfo} now={now} />
{/each}
{:else if !loading && !error}
<div>
<p>搜索消息时,搜索字符串不区分简繁(会使用 OpenCC 自动转换),也不进行分词(请手动将可能不连在一起的词语以空格分开)。</p>
<p>搜索字符串支持以下功能:</p>
<ul>
<li>以空格分开的多个搜索词是「与」的关系</li>
<li>使用 OR全大写来表达「或」条件</li>
<li>使用 - 来表达排除,如 落絮 - 测试</li>
<li>使用小括号来分组</li>
</ul>
<p>人名补全支持上下方向键和 Alt+N/P 进行选择。</p>
</div>
{/if}
{#if loading}
<div class="info"><p>正在加载...</p></div>
{:else}
{#if error}
<p class="error">{error}</p>
{:else if result && result.messages.length == 0}
<div class="info"><p>没有匹配的消息。</p></div>
{:else if result && !result.has_more}
<div class="info"><p>到底了。</p></div>
{/if}
{#if result && result.has_more}
<div class="info"><button on:click={do_search_more}>加载更多</button></div>
{/if}
{/if}
</main>
<style>
main {
margin: 1em;
}
button {
white-space: nowrap;
}
#searchbox {
display: flex;
}
#searchbox input[type=search] {
flex-grow: 1;
}
@media (max-width: 700px) {
#searchbox {
flex-direction: column;
}
}
.error {
color: red;
text-align: center;
}
.info {
display: flex;
justify-content: center;
}
.info > * {
padding: 0.5em 1em;
margin: 1em 0;
}
.info > p {
border: 1px #bfbfbf solid;
border-radius: 2em;
}
:global(input), :global(button), :global(select) {
border-radius: 0;
border: 1px solid var(--color-inactive);
height: 2.3em;
}
:global(input:focus), :global(button:focus), :global(select:focus) {
border-color: var(--color-active);
outline: 1px solid var(--color-active);
}
:global(:root) {
--color-inactive: #bfbfbf;
--color-active: #add8e6;
}
</style>