aboutsummaryrefslogtreecommitdiff
path: root/viewer/src/components/LdTagInput.vue
blob: 71131e67caf927493dcc378610639a382b5e8cef (plain)
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
<!-- 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";

@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[] {
    return Object.values(tags)
      .filter(node => node.tag.includes(category))
      .flatMap(node =>
        Object.values(node.children)
          .filter(child => child.tag.includes(disambiguation))
          .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` }))
      );
  }

  searchTagsFromFilter(tags: Tag.Index, operation: Operation, filter: string): Tag.Search[] {
    return Object.values(tags)
      .filter(node => node.tag.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>