Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tags #339

Merged
merged 8 commits into from
Jan 29, 2024
Merged

Tags #339

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions source/core/Entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { generateUUID } from "../tools/uuid.js";
import { getEntryURLs, EntryURLType } from "../tools/entry.js";
import { Group } from "./Group.js";
import { Vault } from "./Vault.js";
import { isValidTag } from "../tools/tag.js";
import {
EntryChange,
EntryPropertyValueType,
Expand All @@ -20,7 +21,8 @@ export class Entry extends VaultItem {
static Attributes = Object.freeze({
AttachmentPrefix: "BC_ENTRY_ATTACHMENT:",
FacadeType: "BC_ENTRY_FACADE_TYPE",
FieldTypePrefix: "BC_ENTRY_FIELD_TYPE:"
FieldTypePrefix: "BC_ENTRY_FIELD_TYPE:",
Tags: "BC_ENTRY_TAGS"
});

/**
Expand All @@ -47,6 +49,24 @@ export class Entry extends VaultItem {
return vault.findEntryByID(id);
}

/**
* Add one or more tags to the entry
* @param tags A collection of tag strings
* @returns Self
*/
addTags(...tags: Array<string>): this {
const current = new Set(this.getTags());
for (const tag of tags) {
const tagLower = tag.toLowerCase();
if (!isValidTag(tagLower)) {
throw new Error(`Cannot add entry tag: Invalid format: ${tagLower}`);
}
current.add(tagLower);
}
this.setAttribute(Entry.Attributes.Tags, [...current].join(","));
return this;
}

/**
* Delete the entry - either trashes the entry, or removes it completely.
* If the entry is in the trash already, it is removed (including if there is no
Expand Down Expand Up @@ -108,6 +128,8 @@ export class Entry extends VaultItem {
* containing all attribute keys and their values if no attribute name
* is provided
*/
getAttribute(): PropertyKeyValueObject;
getAttribute(attribute: string): string | undefined;
getAttribute(attribute?: string): PropertyKeyValueObject | string | undefined {
const attributes = this.vault.format.getEntryAttributes(this._source) || {};
if (typeof attribute === "undefined") {
Expand Down Expand Up @@ -189,9 +211,22 @@ export class Entry extends VaultItem {
}, {});
}

/**
* Get entry tags
* @returns An array of tag strings
*/
getTags(): Array<string> {
const tags = this.getAttribute(Entry.Attributes.Tags);
return typeof tags === "string"
? tags.split(",").reduce((output, tag) => {
return isValidTag(tag) ? [...output, tag] : output;
}, [])
: [];
}

/**
* Get the entry type
* @returns
* @returns The entry type
*/
getType(): EntryType {
return (
Expand Down Expand Up @@ -233,6 +268,21 @@ export class Entry extends VaultItem {
return this;
}

/**
* Remove one or more tags
* @param tags Collection of tags to remove
* @returns Self
*/
removeTags(...tags: Array<string>): this {
const current = new Set(this.getTags());
for (const tag of tags) {
const tagLower = tag.toLowerCase();
current.delete(tagLower);
}
this.setAttribute(Entry.Attributes.Tags, [...current].join(","));
return this;
}

/**
* Set an attribute on the entry
* @param attribute The name of the attribute
Expand Down
53 changes: 53 additions & 0 deletions source/core/Vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export class Vault extends EventEmitter {

_onCommandExec: () => void;

_tagMap: Map<string, Array<EntryID>> = new Map();

/**
* The vault format
* @readonly
Expand Down Expand Up @@ -171,6 +173,31 @@ export class Vault extends EventEmitter {
return findEntriesByProperty(this._entries, property, value);
}

/**
* 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) : [];
console.log("SEARCHING", entryIDs, this._tagMap);
return entryIDs.map((id) => this.findEntryByID(id));
}

/**
* Find a group by its ID
* @param id The group ID to search for
Expand Down Expand Up @@ -209,6 +236,14 @@ export class Vault extends EventEmitter {
return [...this._groups];
}

/**
* Get all registered entry tags
* @returns An array of tag strings
*/
getAllTags(): Array<string> {
return [...this._tagMap.keys()];
}

/**
* Get the value of an attribute
* @param attributeName The attribute to get
Expand Down Expand Up @@ -275,6 +310,24 @@ export class Vault extends EventEmitter {
this._entries.push(new Entry(this, rawEntry));
}
});
this._rebuildTags();
}

_rebuildTags() {
this._tagMap = new Map();
this.getAllEntries().forEach((entry) => {
const tags = entry.getTags();
for (const tag of tags) {
const tagLower = tag.toLowerCase();
const existingIDs = this._tagMap.has(tagLower)
? [...this._tagMap.get(tagLower)]
: [];
if (!existingIDs.includes(entry.id)) {
existingIDs.push(entry.id);
}
this._tagMap.set(tagLower, existingIDs);
}
});
}

/**
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
21 changes: 21 additions & 0 deletions source/io/VaultFormatB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export class VaultFormatB extends VaultFormat {
this.cloneGroup(childGroup, newGroup.id);
}
});
this.emit("commandsExecuted");
}

createEntry(groupID: GroupID, entryID: EntryID) {
Expand All @@ -140,6 +141,7 @@ export class VaultFormatB extends VaultFormat {
a: {}
};
this.source.e.push(entry);
this.emit("commandsExecuted");
}

createGroup(parentID: GroupID, groupID: GroupID) {
Expand All @@ -150,45 +152,52 @@ export class VaultFormatB extends VaultFormat {
a: {}
};
this.source.g.push(group);
this.emit("commandsExecuted");
}

deleteEntry(entryID: EntryID) {
const ind = this.source.e.findIndex((entry) => entry.id === entryID);
if (ind >= 0) {
this.source.e.splice(ind, 1);
this.source.del.e[entryID] = Date.now();
this.emit("commandsExecuted");
}
}

deleteEntryAttribute(entryID: EntryID, attribute: string) {
const entry = this.source.e.find((e: FormatBEntry) => e.id === entryID);
if (!entry.a[attribute]) return;
entry.a[attribute].deleted = getTimestamp();
this.emit("commandsExecuted");
}

deleteEntryProperty(entryID: EntryID, property: string) {
const entry = this.source.e.find((e) => e.id === entryID);
if (!entry.p[property]) return;
entry.p[property].deleted = getTimestamp();
this.emit("commandsExecuted");
}

deleteGroup(groupID: GroupID) {
const ind = this.source.g.findIndex((group) => group.id === groupID);
if (ind >= 0) {
this.source.g.splice(ind, 1);
this.source.del.g[groupID] = Date.now();
this.emit("commandsExecuted");
}
}

deleteGroupAttribute(groupID: GroupID, attribute: string) {
const group = this.source.g.find((g) => g.id === groupID);
if (!group.a[attribute]) return;
group.a[attribute].deleted = getTimestamp();
this.emit("commandsExecuted");
}

deleteVaultAttribute(attribute: string) {
if (!this.source.a[attribute]) return;
this.source.a[attribute].deleted = getTimestamp();
this.emit("commandsExecuted");
}

erase() {
Expand Down Expand Up @@ -239,6 +248,7 @@ export class VaultFormatB extends VaultFormat {

generateID() {
this.source.id = generateUUID();
this.emit("commandsExecuted");
}

getAllEntries(parentID: GroupID = null): Array<FormatBEntry> {
Expand Down Expand Up @@ -338,18 +348,23 @@ export class VaultFormatB extends VaultFormat {
this.source.del.g = {};
}
if (!this.source.id) {
// Emits "commandsExecuted"
this.generateID();
} else {
this.emit("commandsExecuted");
}
}

moveEntry(entryID: EntryID, groupID: GroupID) {
const entry = this.source.e.find((e: FormatBEntry) => e.id === entryID);
entry.g = groupID;
this.emit("commandsExecuted");
}

moveGroup(groupID: GroupID, newParentID: GroupID) {
const group = this.source.g.find((g: FormatBGroup) => g.id === groupID);
group.g = newParentID;
this.emit("commandsExecuted");
}

optimise() {
Expand Down Expand Up @@ -393,6 +408,7 @@ export class VaultFormatB extends VaultFormat {
delete this.source.del.g[groupID];
}
}
this.emit("commandsExecuted");
}

prepareOrphansGroup(): FormatBGroup {
Expand All @@ -419,6 +435,7 @@ export class VaultFormatB extends VaultFormat {
item.value = value;
item.updated = getTimestamp();
}
this.emit("commandsExecuted");
}

setEntryProperty(entryID: EntryID, property: string, value: string) {
Expand All @@ -431,6 +448,7 @@ export class VaultFormatB extends VaultFormat {
item.value = value;
item.updated = getTimestamp();
}
this.emit("commandsExecuted");
}

setGroupAttribute(groupID: GroupID, attribute: string, value: string) {
Expand All @@ -443,11 +461,13 @@ export class VaultFormatB extends VaultFormat {
item.value = value;
item.updated = getTimestamp();
}
this.emit("commandsExecuted");
}

setGroupTitle(groupID: GroupID, title: string) {
const group = this.source.g.find((g: FormatBGroup) => g.id === groupID);
group.t = title;
this.emit("commandsExecuted");
}

setVaultAttribute(key: string, value: string) {
Expand All @@ -459,5 +479,6 @@ export class VaultFormatB extends VaultFormat {
item.value = value;
item.updated = getTimestamp();
}
this.emit("commandsExecuted");
}
}
Loading
Loading