add cheap autocomplete for tags

This commit is contained in:
Zutatensuppe 2021-06-03 23:30:08 +02:00
parent f303a78631
commit df7584f19d
4 changed files with 135 additions and 13 deletions

View file

@ -23,7 +23,7 @@
<!-- TODO: autocomplete tags -->
<td><label>Tags</label></td>
<td>
<tags-input v-model="tags" />
<tags-input v-model="tags" :autocompleteTags="autocompleteTags" />
</td>
</tr>
</table>
@ -54,6 +54,9 @@ export default defineComponent({
type: Object as PropType<Image>,
required: true,
},
autocompleteTags: {
type: Function,
},
},
emits: {
bgclick: null,

View file

@ -36,7 +36,7 @@ gallery", if possible!
<!-- TODO: autocomplete tags -->
<td><label>Tags</label></td>
<td>
<tags-input v-model="tags" />
<tags-input v-model="tags" :autocompleteTags="autocompleteTags" />
</td>
</tr>
</table>
@ -62,6 +62,11 @@ export default defineComponent({
ResponsiveImage,
TagsInput,
},
props: {
autocompleteTags: {
type: Function,
},
},
emits: {
bgclick: null,
setupGameClick: null,

View file

@ -1,19 +1,42 @@
<template>
<div>
<input class="input" type="text" v-model="input" placeholder="Plants, People" @keydown.enter="add" @keyup="onKeyUp" />
<input
ref="input"
class="input"
type="text"
v-model="input"
placeholder="Plants, People"
@change="onChange"
@keydown.enter="add"
@keyup="onKeyUp"
/>
<div v-if="autocomplete.values" class="autocomplete">
<ul>
<li
v-for="(val,idx) in autocomplete.values"
:key="idx"
:class="{active: idx===autocomplete.idx}"
@click="addVal(val)"
>{{val}}</li>
</ul>
</div>
<span v-for="(tag,idx) in values" :key="idx" class="bit" @click="rm(tag)">{{tag}} </span>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { Tag } from '../../common/Types'
export default defineComponent({
name: 'tags-input',
props: {
modelValue: {
type: Array as PropType<Array<string>>,
type: Array as PropType<string[]>,
required: true,
},
autocompleteTags: {
type: Function,
},
},
emits: {
'update:modelValue': null,
@ -21,7 +44,11 @@ export default defineComponent({
data () {
return {
input: '',
values: [] as Array<string>,
values: [] as string[],
autocomplete: {
idx: -1,
values: [] as string[],
},
}
},
created () {
@ -29,14 +56,39 @@ export default defineComponent({
},
methods: {
onKeyUp (ev: KeyboardEvent) {
if (ev.key === 'ArrowDown' && this.autocomplete.values.length > 0) {
if (this.autocomplete.idx < this.autocomplete.values.length - 1) {
this.autocomplete.idx++
}
ev.stopPropagation()
return false
}
if (ev.key === 'ArrowUp' && this.autocomplete.values.length > 0) {
if (this.autocomplete.idx > 0) {
this.autocomplete.idx--
}
ev.stopPropagation()
return false
}
if (ev.key === ',') {
this.add()
ev.stopPropagation()
return false
}
if (this.input && this.autocompleteTags) {
this.autocomplete.values = this.autocompleteTags(
this.input,
this.values
)
this.autocomplete.idx = -1
} else {
this.autocomplete.values = []
this.autocomplete.idx = -1
}
},
add () {
const newval = this.input.replace(/,/g, '').trim()
addVal (value: string) {
const newval = value.replace(/,/g, '').trim()
if (!newval) {
return
}
@ -44,7 +96,16 @@ export default defineComponent({
this.values.push(newval)
}
this.input = ''
this.autocomplete.values = []
this.autocomplete.idx = -1
this.$emit('update:modelValue', this.values)
;(this.$refs.input as HTMLInputElement).focus()
},
add () {
const value = this.autocomplete.idx >= 0
? this.autocomplete.values[this.autocomplete.idx]
: this.input
this.addVal(value)
},
rm (val: string) {
this.values = this.values.filter(v => v !== val)
@ -57,4 +118,31 @@ export default defineComponent({
.input {
margin-bottom: .5em;
}
.autocomplete {
position: relative;
}
.autocomplete ul { list-style: none;
padding: 0;
margin: 0;
position: absolute;
left: 0;
right: 0;
background: #333230;
top: -.5em;
}
.autocomplete ul li {
position: relative;
padding: .5em .5em .5em 1.5em;
cursor: pointer;
}
.autocomplete ul li.active {
color: var(--link-hover-color);
background: var(--input-bg-color);
}
.autocomplete ul li.active:before {
content: '▶';
display: block;
position: absolute;
left: .5em;
}
</style>

View file

@ -31,15 +31,32 @@ in jigsawpuzzles.io
</select>
</label>
</div>
<image-library :images="images" @imageClicked="onImageClicked" @imageEditClicked="onImageEditClicked" />
<new-image-dialog v-if="dialog==='new-image'" @bgclick="dialog=''" @postToGalleryClick="postToGalleryClick" @setupGameClick="setupGameClick" />
<edit-image-dialog v-if="dialog==='edit-image'" @bgclick="dialog=''" @saveClick="onSaveImageClick" :image="image" />
<new-game-dialog v-if="image && dialog==='new-game'" @bgclick="dialog=''" @newGame="onNewGame" :image="image" />
<image-library
:images="images"
@imageClicked="onImageClicked"
@imageEditClicked="onImageEditClicked" />
<new-image-dialog
v-if="dialog==='new-image'"
:autocompleteTags="autocompleteTags"
@bgclick="dialog=''"
@postToGalleryClick="postToGalleryClick"
@setupGameClick="setupGameClick" />
<edit-image-dialog
v-if="dialog==='edit-image'"
:autocompleteTags="autocompleteTags"
@bgclick="dialog=''"
@saveClick="onSaveImageClick"
:image="image" />
<new-game-dialog
v-if="image && dialog==='new-game'"
@bgclick="dialog=''"
@newGame="onNewGame"
:image="image" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent, PropType } from 'vue'
import ImageLibrary from './../components/ImageLibrary.vue'
import NewImageDialog from './../components/NewImageDialog.vue'
@ -62,7 +79,7 @@ export default defineComponent({
tags: [] as string[],
},
images: [],
tags: [],
tags: [] as Tag[],
image: {
id: 0,
@ -81,6 +98,15 @@ export default defineComponent({
await this.loadImages()
},
methods: {
autocompleteTags (input: string, exclude: string[]): string[] {
return this.tags
.filter((tag: Tag) => {
return !exclude.includes(tag.title)
&& tag.title.toLowerCase().startsWith(input.toLowerCase())
})
.slice(0, 10)
.map((tag: Tag) => tag.title)
},
toggleTag (t: Tag) {
if (this.filters.tags.includes(t.slug)) {
this.filters.tags = this.filters.tags.filter(slug => slug !== t.slug)