implemented autocomplete for filter in kanban
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -4,13 +4,15 @@
|
|||||||
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
autofocus = false,
|
||||||
getCandidates = dummyGetCandidates,
|
getCandidates = dummyGetCandidates,
|
||||||
onCommit = dummyOnCommit,
|
onCommit = dummyOnCommit,
|
||||||
onSelect = dummyOnSelect,
|
onSelect = dummyOnSelect,
|
||||||
|
candidate = $bindable({ display : '' })
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const ignore = ['ArrowLeft','ArrowRight'];
|
const ignore = ['ArrowLeft','ArrowRight'];
|
||||||
let candidate = $state({ display : '' });
|
//let candidate = $state({ display : '' });
|
||||||
let selected = $state([]);
|
let selected = $state([]);
|
||||||
let candidates = $derived(getCandidates(candidate.display));
|
let candidates = $derived(getCandidates(candidate.display));
|
||||||
|
|
||||||
@@ -36,6 +38,8 @@
|
|||||||
// either the selected candidate or
|
// either the selected candidate or
|
||||||
// an anonymous object with the entered text in the display field
|
// an anonymous object with the entered text in the display field
|
||||||
console.warn(`onCommit(${JSON.stringify(candidate)}) not overridden!`);
|
console.warn(`onCommit(${JSON.stringify(candidate)}) not overridden!`);
|
||||||
|
// if the method returns true, the field will be cleared after committing
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dummyOnSelect(candidate){
|
function dummyOnSelect(candidate){
|
||||||
@@ -78,8 +82,7 @@
|
|||||||
if (ev.key == 'Enter') {
|
if (ev.key == 'Enter') {
|
||||||
candidates = [];
|
candidates = [];
|
||||||
selected = [];
|
selected = [];
|
||||||
onCommit(candidate);
|
if (onCommit(candidate)) candidate = { display : '' };
|
||||||
candidate = { display : '' };
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -97,15 +100,15 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div { position : relative }
|
span { position : relative }
|
||||||
select { position : absolute; top: 30px; left: 3px; }
|
select { position : absolute; top: 30px; left: 3px; }
|
||||||
|
|
||||||
select { background: black; color: orange; border: 1px solid orange; border-radius: 5px; }
|
select { background: black; color: orange; border: 1px solid orange; border-radius: 5px; }
|
||||||
option:checked { background: orange; color: black; }
|
option:checked { background: orange; color: black; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
<span>
|
||||||
<input type="text" bind:value={candidate.display} {onkeyup} />
|
<input type="text" bind:value={candidate.display} {onkeyup} autofocus={autofocus} />
|
||||||
{#if candidates && candidates.length > 0}
|
{#if candidates && candidates.length > 0}
|
||||||
<select bind:value={selected} {ondblclick} multiple tabindex="-1">
|
<select bind:value={selected} {ondblclick} multiple tabindex="-1">
|
||||||
{#each candidates as candidate,i}
|
{#each candidates as candidate,i}
|
||||||
@@ -113,4 +116,4 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</span>
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
|
||||||
import { api, patch, post, eventStream, target } from '../../urls.svelte.js';
|
import { api, get, patch, post, eventStream, target } from '../../urls.svelte.js';
|
||||||
import { error, messages, yikes } from '../../warn.svelte';
|
import { error, messages, yikes } from '../../warn.svelte';
|
||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte.js';
|
||||||
import { user } from '../../user.svelte.js';
|
import { user } from '../../user.svelte.js';
|
||||||
|
|
||||||
|
import Autocomplete from '../../Components/Autocomplete.svelte';
|
||||||
import Card from './KanbanCard.svelte';
|
import Card from './KanbanCard.svelte';
|
||||||
import LineEditor from '../../Components/LineEditor.svelte';
|
import LineEditor from '../../Components/LineEditor.svelte';
|
||||||
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
||||||
@@ -16,12 +17,12 @@
|
|||||||
let connectionStatus = 'disconnected';
|
let connectionStatus = 'disconnected';
|
||||||
let { id } = $props();
|
let { id } = $props();
|
||||||
let descr = $state(false);
|
let descr = $state(false);
|
||||||
let filter_input = $state('');
|
let filter_input = $state({display:''});
|
||||||
let router = useTinyRouter();
|
let router = useTinyRouter();
|
||||||
if (router.hasQueryParam('filter')) filter_input = router.getQueryParam('filter');
|
if (router.hasQueryParam('filter')) filter_input = {display:router.getQueryParam('filter')};
|
||||||
let dragged = null;
|
let dragged = null;
|
||||||
let highlight = $state({});
|
let highlight = $state({});
|
||||||
let filter = $derived(filter_input.toLowerCase());
|
let filter = $derived(filter_input.display.toLowerCase());
|
||||||
let project = $state(null);
|
let project = $state(null);
|
||||||
let tasks = $state({});
|
let tasks = $state({});
|
||||||
let users = [];
|
let users = [];
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
let info = $state(null);
|
let info = $state(null);
|
||||||
let task_form = $state(false);
|
let task_form = $state(false);
|
||||||
let stateList = {};
|
let stateList = {};
|
||||||
$effect(() => updateUrl(filter_input));
|
$effect(() => updateUrl(filter_input.display));
|
||||||
|
|
||||||
function byName(a,b) {
|
function byName(a,b) {
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
@@ -92,6 +93,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCandidates(input){
|
||||||
|
if (!input || input.length <3) return [];
|
||||||
|
const url = api(`tags/search/${encodeURI(input)}`);
|
||||||
|
const res = await get(url);
|
||||||
|
if (res.ok){
|
||||||
|
yikes();
|
||||||
|
const list = await res.json();
|
||||||
|
return list.map(elem => {return {display:elem}});
|
||||||
|
} else error(res);
|
||||||
|
}
|
||||||
|
|
||||||
function handleCreateEvent(evt){
|
function handleCreateEvent(evt){
|
||||||
handleEvent(evt,'create');
|
handleEvent(evt,'create');
|
||||||
}
|
}
|
||||||
@@ -191,6 +203,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onCommit(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function openTask(task_id){
|
function openTask(task_id){
|
||||||
window.open(`/task/${task_id}/view`, '_blank').focus();
|
window.open(`/task/${task_id}/view`, '_blank').focus();
|
||||||
}
|
}
|
||||||
@@ -219,8 +235,8 @@
|
|||||||
const user_ids = Object.values(project.members).map(member => member.user.id);
|
const user_ids = Object.values(project.members).map(member => member.user.id);
|
||||||
const data = {
|
const data = {
|
||||||
url : location.href,
|
url : location.href,
|
||||||
tags : ['Kanban', project.name, filter_input],
|
tags : ['Kanban', project.name, filter_input.display],
|
||||||
comment : `${project.name}: ${filter_input}`,
|
comment : `${project.name}: ${filter_input.display}`,
|
||||||
share : user_ids
|
share : user_ids
|
||||||
}
|
}
|
||||||
const url = api('bookmark');
|
const url = api('bookmark');
|
||||||
@@ -239,7 +255,7 @@
|
|||||||
|
|
||||||
function updateUrl(){
|
function updateUrl(){
|
||||||
let url = window.location.origin + window.location.pathname;
|
let url = window.location.origin + window.location.pathname;
|
||||||
if (filter_input) url += '?filter=' + encodeURI(filter_input);
|
if (filter_input.display) url += '?filter=' + encodeURI(filter_input.display);
|
||||||
window.history.replaceState(window.history.state, '', url);
|
window.history.replaceState(window.history.state, '', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +288,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="kanban" style="display: grid; grid-template-columns: {`repeat(${columns}, auto)`}">
|
<div class="kanban" style="display: grid; grid-template-columns: {`repeat(${columns}, auto)`}">
|
||||||
<span class="filter">
|
<span class="filter">
|
||||||
<input type="text" bind:value={filter_input} autofocus />
|
<Autocomplete {getCandidates} bind:candidate={filter_input} {onCommit} autofocus={true} />
|
||||||
{t('filter')}
|
{t('filter')}
|
||||||
<button style="visibility:{filter_input ? 'visible' : 'hidden'}" onclick={save_bookmark}>
|
<button style="visibility:{filter_input ? 'visible' : 'hidden'}" onclick={save_bookmark}>
|
||||||
<span class="symbol"></span> {t('save_object',{object:t('bookmark')})}
|
<span class="symbol"></span> {t('save_object',{object:t('bookmark')})}
|
||||||
|
|||||||
Reference in New Issue
Block a user