2023-05-25 17:06:17 +02:00
|
|
|
<script>
|
|
|
|
import { createEventDispatcher } from "svelte";
|
|
|
|
import { fs_search } from "./FilesystemAPI";
|
2023-05-30 16:59:11 +02:00
|
|
|
import { fs_encode_path, fs_thumbnail_url } from "./FilesystemUtil";
|
2023-05-25 17:06:17 +02:00
|
|
|
|
|
|
|
export let state
|
|
|
|
export let fs_navigator
|
|
|
|
|
|
|
|
let dispatch = createEventDispatcher()
|
|
|
|
|
2023-05-25 17:32:59 +02:00
|
|
|
let error = ""
|
2023-05-25 17:06:17 +02:00
|
|
|
let search_term = ""
|
|
|
|
let search_results = []
|
|
|
|
let searching = false
|
|
|
|
let last_searched_term = ""
|
|
|
|
let last_limit = 10
|
|
|
|
|
|
|
|
const search = async (limit = 10) => {
|
|
|
|
if (search_term.length < 2 || search_term.length > 100) {
|
|
|
|
// These are the length limits defined by the API
|
|
|
|
search_results = []
|
|
|
|
return
|
|
|
|
} else if (search_term === last_searched_term && limit === last_limit) {
|
|
|
|
// If the term is the same we don't have to search
|
|
|
|
return
|
|
|
|
} else if (searching) {
|
|
|
|
// If a search is already running we also don't search
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
console.debug("Searching for", search_term)
|
|
|
|
|
2023-05-25 17:32:59 +02:00
|
|
|
error = ""
|
2023-05-25 17:06:17 +02:00
|
|
|
last_searched_term = search_term
|
|
|
|
last_limit = limit
|
|
|
|
searching = true
|
|
|
|
dispatch("loading", true)
|
|
|
|
|
|
|
|
try {
|
2023-05-30 15:51:10 +02:00
|
|
|
search_results = await fs_search(state.base.path, search_term, limit)
|
2023-05-25 17:06:17 +02:00
|
|
|
} catch (err) {
|
2023-05-25 17:32:59 +02:00
|
|
|
try {
|
|
|
|
error = JSON.parse(err).value
|
|
|
|
} catch {
|
|
|
|
alert(err)
|
|
|
|
console.error(err)
|
|
|
|
}
|
2023-05-25 17:06:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
searching = false
|
|
|
|
dispatch("loading", false)
|
|
|
|
|
|
|
|
// It's possible that the user entered another letter while we were
|
|
|
|
// performing the search reqeust. If this happens we run the search function
|
|
|
|
// again
|
|
|
|
if (last_searched_term !== search_term) {
|
|
|
|
console.debug("Search term changed during search. Searching again")
|
|
|
|
await search()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-29 12:33:22 +02:00
|
|
|
const keyup = e => {
|
|
|
|
if (e.key === "Escape") {
|
|
|
|
dispatch("done")
|
|
|
|
} else {
|
|
|
|
search()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-25 17:06:17 +02:00
|
|
|
// Submitting opens the first result
|
|
|
|
const submit_search = () => {
|
|
|
|
if (search_results.length !== 0) {
|
2023-05-29 12:33:22 +02:00
|
|
|
open_result(0)
|
2023-05-25 17:06:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-29 12:33:22 +02:00
|
|
|
const open_result = index => {
|
2023-05-25 17:06:17 +02:00
|
|
|
fs_navigator.navigate(search_results[index], true)
|
2023-05-29 12:33:22 +02:00
|
|
|
dispatch("done")
|
2023-05-25 17:06:17 +02:00
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
2023-05-25 17:32:59 +02:00
|
|
|
{#if error === "path_not_found" || error === "node_is_a_directory"}
|
|
|
|
<div class="highlight_yellow center">
|
|
|
|
Search index not found. The search index is a file called
|
|
|
|
'.search_index.gz' in your home directory. If you delete this file then
|
|
|
|
search will not work. The file is regenerated 10 minutes after modifying
|
|
|
|
a file in your filesystem.
|
|
|
|
</div>
|
|
|
|
{:else if error !== ""}
|
|
|
|
<div class="highlight_red center">
|
|
|
|
An error ocurred while executing the search request: {error}
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
|
2023-05-29 12:33:22 +02:00
|
|
|
|
|
|
|
<div class="search_bar highlight_shaded center">
|
|
|
|
<form class="search_form" on:submit|preventDefault={submit_search}>
|
|
|
|
<i class="icon">search</i>
|
|
|
|
<!-- svelte-ignore a11y-autofocus -->
|
2023-05-30 15:51:10 +02:00
|
|
|
<input
|
|
|
|
class="term"
|
|
|
|
type="text"
|
|
|
|
placeholder="Type to search in {state.base.name}"
|
|
|
|
style="width: 100%;"
|
|
|
|
bind:value={search_term}
|
|
|
|
on:keyup={keyup}
|
|
|
|
autofocus
|
|
|
|
/>
|
2023-05-29 12:33:22 +02:00
|
|
|
</form>
|
|
|
|
</div>
|
2023-05-25 17:06:17 +02:00
|
|
|
|
2023-05-25 17:32:59 +02:00
|
|
|
<div class="directory center">
|
2023-05-25 17:06:17 +02:00
|
|
|
<tr>
|
|
|
|
<td></td>
|
|
|
|
<td>Name</td>
|
|
|
|
</tr>
|
|
|
|
{#each search_results as result, index}
|
|
|
|
<a
|
2023-05-30 16:59:11 +02:00
|
|
|
href={"/d"+fs_encode_path(result)}
|
2023-05-29 12:33:22 +02:00
|
|
|
on:click|preventDefault={() => open_result(index)}
|
2023-05-25 17:06:17 +02:00
|
|
|
class="node"
|
|
|
|
>
|
|
|
|
<td>
|
2023-05-30 15:51:10 +02:00
|
|
|
<img src={fs_thumbnail_url(result, 32, 32)} class="node_icon" alt="icon"/>
|
2023-05-25 17:06:17 +02:00
|
|
|
</td>
|
|
|
|
<td class="node_name">
|
2023-05-30 15:51:10 +02:00
|
|
|
<!-- Remove the search directory from the result -->
|
|
|
|
{result.slice(state.base.path.length+1)}
|
2023-05-25 17:06:17 +02:00
|
|
|
</td>
|
|
|
|
</a>
|
|
|
|
{/each}
|
|
|
|
|
|
|
|
{#if search_results.length === last_limit}
|
|
|
|
<div class="node">
|
|
|
|
<td></td>
|
|
|
|
<td class="node_name">
|
|
|
|
<button on:click={() => {search(last_limit + 100)}}>
|
|
|
|
<i class="icon">expand_more</i>
|
|
|
|
More results
|
|
|
|
</button>
|
|
|
|
</td>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<style>
|
2023-05-25 17:32:59 +02:00
|
|
|
.center {
|
|
|
|
margin: auto;
|
|
|
|
width: 1000px;
|
|
|
|
max-width: 100%;
|
|
|
|
}
|
|
|
|
|
2023-05-25 17:06:17 +02:00
|
|
|
.search_bar {
|
2023-05-29 12:33:22 +02:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
margin-top: 10px;
|
|
|
|
}
|
|
|
|
.search_form {
|
2023-05-25 17:06:17 +02:00
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
.term {
|
|
|
|
flex: 1 1 auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.directory {
|
|
|
|
display: table;
|
|
|
|
position: relative;
|
|
|
|
overflow: hidden;
|
|
|
|
margin: 8px auto 16px auto;
|
|
|
|
text-align: left;
|
|
|
|
background: var(--body_color);
|
|
|
|
border-collapse: collapse;
|
|
|
|
border-radius: 8px;
|
|
|
|
}
|
|
|
|
.directory > * {
|
|
|
|
display: table-row;
|
|
|
|
}
|
|
|
|
.directory > * > * {
|
|
|
|
display: table-cell;
|
|
|
|
}
|
|
|
|
.node {
|
|
|
|
display: table-row;
|
|
|
|
text-decoration: none;
|
|
|
|
color: var(--text-color);
|
|
|
|
padding: 6px;
|
|
|
|
}
|
|
|
|
.node:not(:last-child) {
|
|
|
|
border-bottom: 1px solid var(--separator);
|
|
|
|
}
|
|
|
|
.node:hover:not(.node_selected) {
|
|
|
|
background: var(--input_background);
|
|
|
|
color: var(--input_text);
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
td {
|
|
|
|
padding: 4px;
|
|
|
|
vertical-align: middle;
|
|
|
|
}
|
|
|
|
.node_icon {
|
|
|
|
height: 32px;
|
|
|
|
width: 32px;
|
|
|
|
vertical-align: middle;
|
|
|
|
border-radius: 4px;
|
|
|
|
}
|
|
|
|
.node_name {
|
|
|
|
width: 100%;
|
|
|
|
overflow: hidden;
|
|
|
|
line-height: 1.2em;
|
|
|
|
word-break: break-all;
|
|
|
|
}
|
|
|
|
</style>
|