Skip to content

Commit

Permalink
Add tag searching
Browse files Browse the repository at this point in the history
  • Loading branch information
perry-mitchell committed Jan 28, 2024
1 parent 44e57a3 commit 9a309ae
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 13 deletions.
20 changes: 19 additions & 1 deletion source/core/Vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,26 @@ export class Vault extends EventEmitter {
return findEntriesByProperty(this._entries, property, value);
}

findEntriesByTag(tag: string): Array<Entry> {
/**
* Find entries by a certain tag
* @param tag The case-insensitive tag name
* @param exact Whether to match exact tag names or use partial
* matching. Default is true (exact).
* @returns An array of entries
*/
findEntriesByTag(tag: string, exact: boolean = true): Array<Entry> {
const tagLower = tag.toLowerCase();
if (!exact) {
const entryIDs = new Set<string>();
for (const [currentTag, currentIDs] of this._tagMap.entries()) {
if (currentTag.toLowerCase().indexOf(tagLower) === 0) {
for (const id of currentIDs) {
entryIDs.add(id);
}
}
}
return [...entryIDs].map((id) => this.findEntryByID(id));
}
const entryIDs = this._tagMap.has(tagLower) ? this._tagMap.get(tagLower) : [];
return entryIDs.map((id) => this.findEntryByID(id));
}
Expand Down
18 changes: 17 additions & 1 deletion source/facades/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,22 @@ export function createEntryFromFacade(group: Group, facade: EntryFacade): Entry
return entry;
}

/**
* Convert an array of entry facade fields to a
* key-value object with only attributes
* @param facadeFields Array of fields
* @memberof module:Buttercup
*/
export function fieldsToAttributes(facadeFields: Array<EntryFacadeField>): {
[key: string]: string;
} {
return facadeFields.reduce((output, field) => {
if (field.propertyType !== EntryPropertyType.Attribute) return output;
output[field.property] = field.value;
return output;
}, {});
}

/**
* Convert an array of entry facade fields to a
* key-value object with only properties
Expand All @@ -207,7 +223,7 @@ export function fieldsToProperties(facadeFields: Array<EntryFacadeField>): {
[key: string]: string;
} {
return facadeFields.reduce((output, field) => {
if (field.propertyType !== "property") return output;
if (field.propertyType !== EntryPropertyType.Property) return output;
output[field.property] = field.value;
return output;
}, {});
Expand Down
17 changes: 15 additions & 2 deletions source/search/BaseSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { StorageInterface } from "../storage/StorageInterface.js";
import { buildSearcher } from "./searcher.js";
import { Vault } from "../core/Vault.js";
import { EntryID, EntryType, GroupID, VaultFacade, VaultID } from "../types.js";
import { extractTagsFromSearchTerm } from "./tags.js";

interface DomainScores {
[domain: string]: number;
Expand All @@ -22,10 +23,11 @@ export interface SearcherFactory {
}

export interface SearchResult {
id: EntryID;
entryType: EntryType;
groupID: GroupID;
id: EntryID;
properties: { [property: string]: string };
entryType: EntryType;
tags: Array<string>;
urls: Array<string>;
vaultID: VaultID;
}
Expand Down Expand Up @@ -137,6 +139,17 @@ export class BaseSearch {
if (!this._fuse) {
throw new Error("Searching interface not prepared");
}
const { tags, term: searchTerm } = extractTagsFromSearchTerm(term);
if (tags.length > 0) {
// Instantiate new searcher based on a subset of entries
const subset = this._entries.filter((entry) =>
entry.tags.some((entryTag) => tags.includes(entryTag))
);
this._fuse = this._searcherFactory(subset);
} else {
// Reset instance
this._fuse = this._searcherFactory(this._entries);
}
this._results = this._fuse.search(term).map((result) => result.item);
return this._results;
}
Expand Down
9 changes: 5 additions & 4 deletions source/search/VaultEntrySearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ async function extractEntries(
const properties = entry.getProperties();
const urls = getEntryURLs(properties, EntryURLType.General);
return {
domainScores: vaultScore[entry.id] || {},
entryType: entry.getType(),
groupID: entry.getGroup().id,
id: entry.id,
properties,
entryType: entry.getType(),
tags: entry.getTags(),
urls,
groupID: entry.getGroup().id,
vaultID: vault.id,
domainScores: vaultScore[entry.id] || {}
vaultID: vault.id
};
});
}
Expand Down
19 changes: 14 additions & 5 deletions source/search/VaultFacadeEntrySearch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Entry } from "../core/Entry.js";
import { BaseSearch, ProcessedSearchEntry, SearcherFactory } from "./BaseSearch.js";
import { EntryURLType, getEntryURLs } from "../tools/entry.js";
import { fieldsToProperties } from "../facades/entry.js";
import { fieldsToAttributes, fieldsToProperties } from "../facades/entry.js";
import { StorageInterface } from "../storage/StorageInterface.js";
import { EntryFacade, VaultFacade } from "../types.js";
import { isValidTag } from "../tools/tag.js";

async function extractEntries(
facade: VaultFacade,
Expand All @@ -20,16 +22,23 @@ async function extractEntries(
// Get entries
return facade.entries.reduce((entries: Array<ProcessedSearchEntry>, nextEntry: EntryFacade) => {
// @todo in trash
const attributes = fieldsToAttributes(nextEntry.fields);
const tags = attributes[Entry.Attributes.Tags]
? attributes[Entry.Attributes.Tags].split(",").reduce((output, tag) => {
return isValidTag(tag) ? [...output, tag] : output;
}, [])
: [];
const properties = fieldsToProperties(nextEntry.fields);
const urls = getEntryURLs(properties, EntryURLType.General);
entries.push({
domainScores: vaultScore[nextEntry.id] || {},
entryType: nextEntry.type,
groupID: nextEntry.parentID,
id: nextEntry.id,
properties,
entryType: nextEntry.type,
tags,
urls,
groupID: nextEntry.parentID,
vaultID: facade.id,
domainScores: vaultScore[nextEntry.id] || {}
vaultID: facade.id
});
return entries;
}, []);
Expand Down
20 changes: 20 additions & 0 deletions source/search/tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function extractTagsFromSearchTerm(term: string): {
tags: Array<string>;
term: string;
} {
const searchItems: Array<string> = [];
const tags = new Set<string>();
const parts = term.split(/\s+/g);
for (const part of parts) {
if (/^#.+/.test(part)) {
const raw = part.replace(/^#/, "");
tags.add(raw.toLowerCase());
} else if (part.length > 0) {
searchItems.push(part);
}
}
return {
tags: [...tags],
term: searchItems.join(" ").trim()
};
}

0 comments on commit 9a309ae

Please sign in to comment.