diff --git a/source/core/Entry.ts b/source/core/Entry.ts index 555b65c0..fb441890 100644 --- a/source/core/Entry.ts +++ b/source/core/Entry.ts @@ -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, @@ -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" }); /** @@ -47,6 +49,23 @@ 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): this { + const current = new Set(this.getTags()); + for (const tag of tags) { + if (!isValidTag(tag)) { + throw new Error(`Cannot add entry tag: Invalid format: ${tag}`); + } + current.add(tag); + } + 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 @@ -108,6 +127,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") { @@ -189,9 +210,22 @@ export class Entry extends VaultItem { }, {}); } + /** + * Get entry tags + * @returns An array of tag strings + */ + getTags(): Array { + 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 ( @@ -233,6 +267,20 @@ export class Entry extends VaultItem { return this; } + /** + * Remove one or more tags + * @param tags Collection of tags to remove + * @returns Self + */ + removeTags(...tags: Array): this { + const current = new Set(this.getTags()); + for (const tag of tags) { + current.delete(tag); + } + this.setAttribute(Entry.Attributes.Tags, [...current].join(",")); + return this; + } + /** * Set an attribute on the entry * @param attribute The name of the attribute diff --git a/source/tools/tag.ts b/source/tools/tag.ts new file mode 100644 index 00000000..4b15d025 --- /dev/null +++ b/source/tools/tag.ts @@ -0,0 +1,3 @@ +export function isValidTag(tag: string): boolean { + return /^[a-zA-Z0-9_]+$/.test(tag); +}