aboutsummaryrefslogtreecommitdiff
path: root/viewer/src/services/indexfactory.ts
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src/services/indexfactory.ts')
-rw-r--r--viewer/src/services/indexfactory.ts155
1 files changed, 0 insertions, 155 deletions
diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts
deleted file mode 100644
index 4b28a60..0000000
--- a/viewer/src/services/indexfactory.ts
+++ /dev/null
@@ -1,155 +0,0 @@
1/* ldgallery - A static generator which turns a collection of tagged
2-- pictures into a searchable web gallery.
3--
4-- Copyright (C) 2019-2020 Guillaume FOUET
5--
6-- This program is free software: you can redistribute it and/or modify
7-- it under the terms of the GNU Affero General Public License as
8-- published by the Free Software Foundation, either version 3 of the
9-- License, or (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14-- GNU Affero General Public License for more details.
15--
16-- You should have received a copy of the GNU Affero General Public License
17-- along with this program. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20import { Operation } from "@/@types/Operation";
21import { ItemType } from "@/@types/ItemType";
22import Navigation from "@/services/navigation";
23
24export default class IndexFactory {
25 public static generateTags(root: Gallery.Item | null): Tag.Index {
26 const tagsIndex: Tag.Index = {};
27 if (root) IndexFactory.pushTagsForItem(tagsIndex, root);
28 return tagsIndex;
29 }
30
31 // Pushes all tags for a root item (and its children) to the index
32 private static pushTagsForItem(tagsIndex: Tag.Index, item: Gallery.Item): void {
33 if (item.properties.type === ItemType.DIRECTORY) {
34 item.properties.items.forEach(item => this.pushTagsForItem(tagsIndex, item));
35 return; // Directories are not indexed
36 }
37 for (const tag of item.tags) {
38 const parts = tag.split(":");
39 let lastPart: string | null = null;
40 for (const part of parts) {
41 tagsIndex[part] = IndexFactory.pushPartToIndex(tagsIndex[part], part, item, !Boolean(lastPart));
42 if (lastPart) {
43 const children = tagsIndex[lastPart].children;
44 children[part] = IndexFactory.pushPartToIndex(children[part], part, item, false);
45 }
46 lastPart = part;
47 }
48 if (lastPart) tagsIndex[lastPart].childPart = true;
49 }
50 }
51
52 private static pushPartToIndex(index: Tag.Node, part: string, item: Gallery.Item, rootPart: boolean): Tag.Node {
53 if (!index)
54 index = {
55 tag: part,
56 tagfiltered: Navigation.normalize(part),
57 rootPart,
58 childPart: !rootPart,
59 items: [],
60 children: {},
61 };
62 else if (rootPart) index.rootPart = true;
63 else index.childPart = true;
64
65 if (!index.items.includes(item)) index.items.push(item);
66 return index;
67 }
68
69 // ---
70
71 public static searchTags(tagsIndex: Tag.Index, filter: string, strict: boolean): Tag.Search[] {
72 let search: Tag.Search[] = [];
73 if (tagsIndex && filter) {
74 const operation = IndexFactory.extractOperation(filter);
75 if (operation !== Operation.INTERSECTION) filter = filter.slice(1);
76 if (filter.includes(":")) {
77 const filterParts = filter.split(":");
78 search = this.searchTagsFromFilterWithCategory(tagsIndex, operation, filterParts[0], filterParts[1], strict);
79 } else {
80 search = this.searchTagsFromFilter(tagsIndex, operation, filter, strict);
81 }
82 }
83 return search;
84 }
85
86 private static extractOperation(filter: string): Operation {
87 const first = filter.slice(0, 1);
88 switch (first) {
89 case Operation.ADDITION:
90 case Operation.SUBSTRACTION:
91 return first;
92 default:
93 return Operation.INTERSECTION;
94 }
95 }
96
97 private static searchTagsFromFilterWithCategory(
98 tagsIndex: Tag.Index,
99 operation: Operation,
100 category: string,
101 disambiguation: string,
102 strict: boolean
103 ): Tag.Search[] {
104 category = Navigation.normalize(category);
105 disambiguation = Navigation.normalize(disambiguation);
106 return Object.values(tagsIndex)
107 .filter(node => IndexFactory.matches(node, category, strict))
108 .flatMap(node =>
109 Object.values(node.children)
110 .filter(child => IndexFactory.matches(child, disambiguation, strict))
111 .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` }))
112 );
113 }
114
115 private static searchTagsFromFilter(
116 tagsIndex: Tag.Index,
117 operation: Operation,
118 filter: string,
119 strict: boolean
120 ): Tag.Search[] {
121 filter = Navigation.normalize(filter);
122 return Object.values(tagsIndex)
123 .filter(node => IndexFactory.matches(node, filter, strict))
124 .map(node => ({ ...node, operation, display: `${operation}${node.tag}` }));
125 }
126
127 private static matches(node: Tag.Node, filter: string, strict: boolean): boolean {
128 if (strict) return node.tagfiltered === filter;
129 return node.tagfiltered.includes(filter);
130 }
131
132 // ---
133
134 public static generateCategories(tagsIndex: Tag.Index, categoryTags?: Gallery.RawTag[]): Tag.Category[] {
135 if (!categoryTags?.length) return [{ tag: "", index: tagsIndex }];
136
137 const tagsCategories: Tag.Category[] = [];
138 const tagsRemaining = new Map(Object.entries(tagsIndex));
139 categoryTags
140 .map(tag => ({ tag, index: tagsIndex[tag]?.children }))
141 .filter(category => category.index && Object.keys(category.index).length)
142 .forEach(category => {
143 tagsCategories.push(category);
144 [category.tag, ...Object.values(category.index).map(node => node.tag)]
145 .filter(tag => IndexFactory.isDiscriminantTagOnly(categoryTags, tagsIndex[tag]))
146 .forEach(tag => tagsRemaining.delete(tag));
147 });
148 tagsCategories.push({ tag: "", index: Object.fromEntries(tagsRemaining) });
149 return tagsCategories;
150 }
151
152 private static isDiscriminantTagOnly(tags: Gallery.RawTag[], node: Tag.Node): boolean {
153 return !tags.includes(node.tag) || !node.childPart;
154 }
155}