From 2ec8779c6b4432200384e67fb7adc2451d1f2c74 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Sun, 28 Jul 2024 20:08:04 +0200 Subject: [PATCH] add tags --- src/components/TagInList.vue | 28 +++++ src/lib/db/entities/journalPosts.ts | 3 +- src/lib/db/entities/tags.ts | 38 +++--- src/lib/db/liveQueries.ts | 10 +- src/lib/util/filterQuery.ts | 43 ------- src/modals/MemberEdit.vue | 2 +- src/modals/TagEdit.vue | 179 ++++++++++++++++++++++++++++ src/views/options/TagManagement.vue | 39 +++++- translations/en/options.json | 20 ++++ 9 files changed, 291 insertions(+), 71 deletions(-) create mode 100644 src/components/TagInList.vue create mode 100644 src/modals/TagEdit.vue diff --git a/src/components/TagInList.vue b/src/components/TagInList.vue new file mode 100644 index 0000000..699c14b --- /dev/null +++ b/src/components/TagInList.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/lib/db/entities/journalPosts.ts b/src/lib/db/entities/journalPosts.ts index 77df51a..9f951b9 100644 --- a/src/lib/db/entities/journalPosts.ts +++ b/src/lib/db/entities/journalPosts.ts @@ -9,7 +9,8 @@ export type JournalPost = UUIDable & { title: string, body: string, cover?: File, - attachments?: Attachment[] + attachments?: Attachment[], + tags?: UUID[] // array of UUIDs } export type Attachment = UUIDable & { diff --git a/src/lib/db/entities/tags.ts b/src/lib/db/entities/tags.ts index 1960330..a0d94e5 100644 --- a/src/lib/db/entities/tags.ts +++ b/src/lib/db/entities/tags.ts @@ -1,13 +1,15 @@ import { db } from ".."; -import { parseTagFilterQuery } from "../../util/filterQuery"; import { makeUUIDv5 } from "../../util/uuid"; -import { UUIDable } from "../types"; +import { UUID, UUIDable } from "../types"; import { getSystemUUID } from "./system"; +import { getTable as getMembers } from "./members"; +import { getTable as getJournalPosts } from "./journalPosts"; + export type Tag = UUIDable & { name: string, type: "member" | "journal", - color: string + color?: string } export function getTable() { @@ -26,20 +28,20 @@ export async function newTag(tag: Omit) { }); } -export async function getTagFromName(name: string){ - return await getTable().get({ name }); +export async function removeTag(uuid: UUID){ + const tag = await getTable().get(uuid); + if(tag?.type === "member"){ + getMembers().toCollection().modify(member => { + member.tags = member.tags?.filter(tag => tag !== uuid) + }); + } else if(tag?.type === "journal") { + getJournalPosts().toCollection().modify(journalPost => { + journalPost.tags = journalPost.tags?.filter(tag => tag !== uuid) + }); + } + await getTable().delete(uuid); } -export async function getTagsFromFilterQuery(filterQuery: string){ - const parsed = await parseTagFilterQuery(filterQuery); - - return getTable().where("name").startsWithIgnoreCase(parsed.query).filter(x => { - - if (parsed.type) { - if (x.type !== parsed.type.toLowerCase()) - return false; - } - - return true; - }).toArray(); -} +export async function getTagFromName(name: string){ + return await getTable().get({ name }); +} \ No newline at end of file diff --git a/src/lib/db/liveQueries.ts b/src/lib/db/liveQueries.ts index 043871f..4f28586 100644 --- a/src/lib/db/liveQueries.ts +++ b/src/lib/db/liveQueries.ts @@ -2,7 +2,7 @@ import { from, useObservable } from "@vueuse/rxjs"; import { liveQuery } from "dexie"; import { Ref, ref, watch } from "vue"; import { Member, getMembersFromFilterQuery, getTable as getMembersTable } from "./entities/members"; -import { Tag, getTagsFromFilterQuery, getTable as getTagsTable } from "./entities/tags"; +import { Tag, getTable as getTagsTable } from "./entities/tags"; export function getFilteredMembers(search: Ref){ const members = ref(); @@ -17,14 +17,18 @@ export function getFilteredMembers(search: Ref){ return members; } -export function getFilteredTags(search: Ref) { +export function getFilteredTags(search: Ref, type: Ref) { const tags = ref(); watch([ search, + type, useObservable(from(liveQuery(() => getTagsTable().toArray()))) ], async () => { - tags.value = await getTagsFromFilterQuery(search.value); + if(search.value.length == 0) + tags.value = await getTagsTable().filter(x => x.type === type.value).toArray(); + else + tags.value = await getTagsTable().where("name").startsWithIgnoreCase(search.value).filter(x => x.type === type.value).toArray(); }, { immediate: true }); return tags; diff --git a/src/lib/util/filterQuery.ts b/src/lib/util/filterQuery.ts index b965acb..1305800 100644 --- a/src/lib/util/filterQuery.ts +++ b/src/lib/util/filterQuery.ts @@ -96,46 +96,3 @@ export async function parseMemberFilterQuery(search: string): Promise + import { + IonContent, + IonHeader, + IonToolbar, + IonTitle, + IonButton, + IonIcon, + IonList, + IonInput, + IonFab, + IonFabButton, + IonLabel, + IonItem, + modalController, + IonModal, + IonButtons, + IonSegment, + IonSegmentButton + } from "@ionic/vue"; + import Color from "../components/Color.vue"; + + import { + saveOutline as saveIOS, + chevronBack as backIOS, + trashBinOutline as trashIOS + } from "ionicons/icons"; + + import saveMD from "@material-design-icons/svg/outlined/save.svg"; + import backMD from "@material-design-icons/svg/outlined/arrow_back.svg"; + import trashMD from "@material-design-icons/svg/outlined/delete.svg"; + + import { Tag, getTable, newTag, removeTag } from '../lib/db/entities/tags'; + import { Ref, inject, ref } from "vue"; + import { addMaterialColors, unsetMaterialColors } from "../lib/theme"; + + type PartialBy = Omit & Partial>; + const isOpen = inject>("isOpen"); + const props = defineProps<{ + tag?: PartialBy, + add: boolean, + edit?: boolean + }>(); + + const tag = ref({ + name: props.tag?.name || "", + type: props.tag?.type || "member", + ...props.tag || {} + } as PartialBy); + + function dismiss(){ + if(isOpen) { + isOpen.value = false; + tag.value = { + name: props.tag?.name || "", + type: props.tag?.type || "member", + ...props.tag || {} + }; + } + } + + async function save(){ + const { uuid, ...tagWithoutUUID } = tag.value; + + if(!props.add){ + await getTable().update(tag.value.uuid, tagWithoutUUID); + + // update tag in props, since it's reactive + for(const prop in tag.value) + props.tag![prop] = tag.value[prop]; + + try{ + await modalController.dismiss(undefined, "modified"); + }catch(_){} + // catch an error because the type might get changed, causing the parent to be removed from DOM + // however it's safe for us to ignore + + } else { + await newTag(tagWithoutUUID); + await modalController.dismiss(undefined, "added"); + } + } + + async function deleteTag(){ + await removeTag(tag.value!.uuid!); + await modalController.dismiss(undefined, "deleted"); + } + + const self = ref(); + function setAccent() { + if(tag.value.color && tag.value.color !== "#000000"){ + addMaterialColors(tag.value.color, self.value.$el); + } else { + unsetMaterialColors(self.value.$el); + } + } + + + + + \ No newline at end of file diff --git a/src/views/options/TagManagement.vue b/src/views/options/TagManagement.vue index e92512b..cdfe935 100644 --- a/src/views/options/TagManagement.vue +++ b/src/views/options/TagManagement.vue @@ -1,10 +1,21 @@ diff --git a/translations/en/options.json b/translations/en/options.json index 7d85caa..8f1963f 100644 --- a/translations/en/options.json +++ b/translations/en/options.json @@ -16,9 +16,29 @@ }, "tagManagement": { "header": "Tag management", + "searchPlaceholder": "Search for tags...", "selector": { "member": "Member tags", "journal": "Journal tags" + }, + "edit": { + "header": { + "member": "Edit member tag", + "journal": "Edit journal tag" + }, + "name": "Tag name", + "color": "Tag color", + "type": { + "header": "Tag type", + "member": "Member", + "journal": "Journal" + }, + "actions": { + "delete": { + "title": "Delete tag", + "desc": "You won't be able to undo this action!" + } + } } }, "reminders": {