Skip to content

Commit

Permalink
Merge pull request #6530 from getkirby/fix/search-js-abort
Browse files Browse the repository at this point in the history
Panel search improvements
  • Loading branch information
bastianallgeier authored Aug 2, 2024
2 parents a2a57a6 + 5fcd5f5 commit 4fd365c
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 230 deletions.
1 change: 1 addition & 0 deletions i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@

"save": "Save",
"search": "Search",
"searching": "Searching",
"search.min": "Enter {min} characters to search",
"search.all": "Show all {count} results",
"search.results.none": "No results",
Expand Down
5 changes: 5 additions & 0 deletions panel/lab/components/search-bar/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'docs' => 'k-search-bar',
];
60 changes: 60 additions & 0 deletions panel/lab/components/search-bar/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<k-lab-examples>
<k-lab-example label="Default">
<k-search-bar />
</k-lab-example>
<k-lab-example label="Types">
<k-search-bar :types="types" />
</k-lab-example>
<k-lab-example label="Default type">
<k-search-bar :types="types" default-type="files" />
</k-lab-example>
<k-lab-example label="No results">
<k-search-bar :results="[]" />
</k-lab-example>
<k-lab-example label="Results">
<k-search-bar :results="results" />
</k-lab-example>
<k-lab-example label="More results">
<k-search-bar :results="results" :pagination="{ total: 20 }" />
</k-lab-example>
<k-lab-example label="Is Loading">
<k-search-bar :is-loading="true" :results="results" :types="types" />
</k-lab-example>
</k-lab-examples>
</template>

<script>
export default {
computed: {
results() {
return [
{ text: "Result A" },
{ text: "Result B" },
{ text: "Result C" },
{ text: "Result D" }
];
},
types() {
return {
pages: {
id: "pages",
icon: "page",
label: "Pages"
},
files: {
id: "files",
icon: "file",
label: "Files"
}
};
}
}
};
</script>

<style>
.k-lab-example .k-search-bar {
background: var(--color-gray-100);
}
</style>
4 changes: 1 addition & 3 deletions panel/src/components/Dialogs/Elements/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
<k-input
:autofocus="autofocus"
:placeholder="placeholder"
:spellcheck="false"
:value="value"
class="k-dialog-search"
icon="search"
type="text"
type="search"
@input="$emit('search', $event)"
/>
</template>
Expand All @@ -23,7 +22,6 @@ export default {
type: Boolean
},
placeholder: {
default: () => window.panel.$t("search") + "",
type: String
},
value: {
Expand Down
208 changes: 24 additions & 184 deletions panel/src/components/Dialogs/SearchDialog.vue
Original file line number Diff line number Diff line change
@@ -1,177 +1,60 @@
<template>
<k-dialog
v-bind="$props"
:cancel-button="false"
:submit-button="false"
:visible="true"
class="k-search-dialog"
role="search"
size="medium"
@cancel="$emit('cancel')"
@submit="submit"
>
<div class="k-search-dialog-input">
<!-- Type select -->
<k-button
:dropdown="true"
:icon="currentType.icon"
:text="currentType.label"
variant="dimmed"
class="k-search-dialog-types"
@click="$refs.types.toggle()"
/>
<k-dropdown-content ref="types" :options="types" />

<!-- Input -->
<k-search-input
ref="input"
:aria-label="$t('search')"
:autofocus="true"
:value="query"
@input="query = $event"
@keydown.native.down.prevent="onDown"
@keydown.native.up.prevent="onUp"
@keydown.native.enter="onEnter"
/>
<k-button
:icon="isLoading ? 'loader' : 'cancel'"
:title="$t('close')"
class="k-search-dialog-close"
@click="close"
/>
</div>

<div v-if="query?.length > 1" class="k-search-dialog-results">
<!-- Results -->
<k-collection
v-if="items.length"
ref="items"
:items="items"
@mouseout.native="select(-1)"
/>

<!-- No results -->
<footer class="k-search-dialog-footer">
<p v-if="!items.length">
{{ $t("search.results.none") }}
</p>

<k-button
v-else-if="items.length < pagination.total"
icon="search"
variant="dimmed"
@click="$go('search', { query: { current, query } })"
>
{{ $t("search.all", { count: pagination.total }) }}
</k-button>
</footer>
</div>
<k-search-bar
ref="search"
:default-type="type ?? $panel.view.search"
:is-loading="$panel.searcher.isLoading"
:pagination="pagination"
:results="results"
:types="$panel.searches"
@close="close"
@more="$go('search', { query: $event })"
@navigate="navigate"
@search="search"
/>
</k-dialog>
</template>

<script>
import Dialog from "@/mixins/dialog.js";
import Search from "@/mixins/search.js";
export default {
mixins: [Dialog, Search],
mixins: [Dialog],
props: {
type: String
},
emits: ["cancel"],
data() {
return {
isLoading: false,
items: [],
pagination: {},
selected: -1,
current:
this.type ??
(this.$panel.searches[this.$panel.view.search]
? this.$panel.view.search
: Object.keys(this.$panel.searches)[0])
results: null,
pagination: {}
};
},
computed: {
currentType() {
return this.$panel.searches[this.current] ?? this.types[0];
},
types() {
return Object.values(this.$panel.searches).map((search) => ({
...search,
current: this.current === search.id,
click: () => {
this.current = search.id;
this.focus();
}
}));
}
},
watch: {
current() {
this.search();
}
},
methods: {
clear() {
this.items = [];
this.query = null;
},
focus() {
this.$refs.input?.focus();
this.$refs.search?.focus();
},
navigate(item) {
if (item) {
this.$go(item.link);
navigate(result) {
if (result) {
this.$go(result.link);
this.close();
}
},
onDown() {
if (this.selected < this.items.length - 1) {
this.select(this.selected + 1);
}
},
onEnter() {
this.navigate(this.items[this.selected] ?? this.items[0]);
},
onTab() {
this.navigate(this.items[this.selected]);
},
onUp() {
if (this.selected >= 0) {
this.select(this.selected - 1);
}
},
async search() {
this.isLoading = true;
this.$refs.types?.close();
this.select?.(-1);
async search({ type, query }) {
const response = await this.$panel.search(type, query);
try {
// Skip API call if query empty
if (this.query === null || this.query.length < 2) {
throw Error("Empty query");
}
const response = await this.$search(this.current, this.query);
this.items = response.results;
if (response) {
this.results = response.results;
this.pagination = response.pagination;
} catch {
this.items = [];
this.pagination = {};
} finally {
this.isLoading = false;
}
},
select(index) {
this.selected = index;
const items = this.$refs.items?.$el.querySelectorAll(".k-item") ?? [];
for (const item of items) {
delete item.dataset.selected;
}
if (index >= 0) {
items[index].dataset.selected = true;
}
}
}
Expand All @@ -187,47 +70,4 @@ export default {
.k-overlay[open][data-type="dialog"] > .k-portal > .k-search-dialog {
margin-top: 0;
}
.k-search-dialog-input {
--button-height: var(--input-height);
display: flex;
align-items: center;
}
.k-search-dialog-types {
flex-shrink: 0;
}
.k-search-dialog-input input {
flex-grow: 1;
padding-inline: 0.75rem;
height: var(--input-height);
border-left: 1px solid var(--color-border);
line-height: var(--input-height);
border-radius: var(--rounded);
font-size: var(--input-font-size);
}
.k-search-dialog-input input:focus {
outline: 0;
}
.k-search-dialog-input .k-search-dialog-close {
flex-shrink: 0;
}
.k-search-dialog-results {
border-top: 1px solid var(--color-border);
padding: 1rem;
}
.k-search-dialog-results .k-item[data-selected="true"] {
outline: var(--outline);
}
.k-search-dialog-footer {
text-align: center;
}
.k-search-dialog-footer p {
color: var(--color-text-dimmed);
}
.k-search-dialog-footer .k-button {
margin-top: var(--spacing-4);
}
</style>
Loading

0 comments on commit 4fd365c

Please sign in to comment.