1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
<!-- ldgallery - A static generator which turns a collection of tagged
-- pictures into a searchable web gallery.
--
-- Copyright (C) 2019-2020 Guillaume FOUET
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<b-taginput
v-model="$uiStore.currentTags"
:placeholder="$t('tagInput.placeholder')"
autocomplete
ellipsis
attached
:data="filteredTags"
field="display"
type="is-black"
icon="tag"
size="is-medium"
class="panelTagInput"
@typing="searchTags"
@add="onAdd"
@remove="onRemove"
>
<template slot-scope="props">{{displayOption(props.option)}}</template>
<template slot="empty">{{$t('tagInput.nomatch')}}</template>
</b-taginput>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Operation } from "@/@types/tag/Operation";
import Tools from "@/tools";
@Component
export default class LdTagInput extends Vue {
filteredTags: Tag.Search[] = [];
onAdd(e: any) {
this.$uiStore.mode = "search";
}
onRemove() {
if (this.$uiStore.currentTags.length === 0) this.$uiStore.mode = "navigation";
}
displayOption(option: Tag.Search): string {
return `${option.display} (${option.items.length})`;
}
extractOperation(filter: string): Operation {
const first = filter.slice(0, 1);
switch (first) {
case Operation.ADDITION:
case Operation.SUBSTRACTION:
return first;
default:
return Operation.INTERSECTION;
}
}
searchTags(filter: string) {
const tags = this.$galleryStore.tags;
let search: Tag.Search[] = [];
if (tags && filter) {
const operation = this.extractOperation(filter);
if (operation !== Operation.INTERSECTION) filter = filter.slice(1);
if (filter.includes(":")) {
const filterParts = filter.split(":");
search = this.searchTagsFromFilterWithCategory(tags, operation, filterParts[0], filterParts[1]);
} else {
search = this.searchTagsFromFilter(tags, operation, filter);
}
}
this.filteredTags = this.cleanupAndSort(search);
}
searchTagsFromFilterWithCategory(
tags: Tag.Index,
operation: Operation,
category: string,
disambiguation: string
): Tag.Search[] {
disambiguation = Tools.normalize(disambiguation);
return Object.values(tags)
.filter(node => node.tag.includes(category))
.flatMap(node =>
Object.values(node.children)
.filter(child => child.tagfiltered.includes(disambiguation))
.map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` }))
);
}
searchTagsFromFilter(tags: Tag.Index, operation: Operation, filter: string): Tag.Search[] {
filter = Tools.normalize(filter);
return Object.values(tags)
.filter(node => node.tagfiltered.includes(filter))
.map(node => ({ ...node, operation, display: `${operation}${node.tag}` }));
}
cleanupAndSort(search: Tag.Search[]): Tag.Search[] {
const currentTags = this.$uiStore.currentTags;
return search
.filter(node => !currentTags.find(currentTag => currentTag.tag === node.tag))
.sort((a, b) => b.items.length - a.items.length);
}
}
</script>
<style lang="scss">
.panelTagInput .autocomplete .dropdown-content {
max-height: 300px;
}
</style>
|