implemented search

This commit is contained in:
2025-06-19 14:47:42 +02:00
parent 62ddc33290
commit 25f63d30be
24 changed files with 604 additions and 175 deletions

View File

@ -1,5 +1,5 @@
<template>
<div class="p-2 min-h-full">
<div class="h-full overflow-auto p-2">
<NuxtPage />
</div>
</template>

View File

@ -1,11 +1,29 @@
<template>
<div>
{{ currentGroupId }}
<HaexPassGroup
v-model="group"
mode="create"
@close="onClose"
@submit="createAsync"
/>
<HaexPassMenuBottom
@close="onClose"
@save="createAsync"
show-close-button
show-save-button
:has-changes
>
</HaexPassMenuBottom>
<HaexPassDialogUnsavedChanges
:has-changes
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
v-model:ignore-changes="ignoreChanges"
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
@ -34,7 +52,14 @@ const errors = ref({
description: [],
})
const ignoreChanges = ref(false)
const onClose = () => {
if (showUnsavedChangesDialog.value) return
if (hasChanges.value && !ignoreChanges.value) {
return (showUnsavedChangesDialog.value = true)
}
useRouter().back()
}
@ -45,11 +70,11 @@ const createAsync = async () => {
const newGroup = await addGroupAsync(group.value)
console.log('newGroup', newGroup)
if (!newGroup.id) {
return
}
ignoreChanges.value = true
await navigateTo(
useLocalePath()({
name: 'passwordGroupItems',
@ -65,4 +90,20 @@ const createAsync = async () => {
console.log(error)
}
}
const hasChanges = computed(() => {
return !!(
group.value.color ||
group.value.description ||
group.value.icon ||
group.value.name
)
})
const showUnsavedChangesDialog = ref(false)
const onConfirmIgnoreChanges = () => {
showUnsavedChangesDialog.value = false
ignoreChanges.value = true
onClose()
}
</script>

View File

@ -1,11 +1,43 @@
<template>
<div>
currentGroup{{ currentGroup }}
<HaexPassGroup
v-model="group"
mode="edit"
:read_only
@close="onClose"
@submit="onSaveAsync"
mode="edit"
v-model="group"
/>
<HaexPassMenuBottom
:show-edit-button="read_only && !hasChanges"
:show-readonly-button="!read_only && !hasChanges"
:show-save-button="hasChanges"
:has-changes
@close="onClose()"
@delete="showConfirmDeleteDialog = true"
@edit="read_only = false"
@readonly="read_only = true"
@save="onSaveAsync"
show-close-button
show-delete-button
>
</HaexPassMenuBottom>
<HaexPassDialogDeleteItem
v-model:open="showConfirmDeleteDialog"
@abort="showConfirmDeleteDialog = false"
@confirm="onDeleteAsync"
:item-name="group.name"
:final="inTrashGroup"
>
</HaexPassDialogDeleteItem>
<HaexPassDialogUnsavedChanges
:has-changes="hasChanges"
v-model:ignore-changes="ignoreChanges"
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
@ -19,42 +51,79 @@ definePageMeta({
const { t } = useI18n()
const check = ref(false)
const { currentGroup, inTrashGroup, currentGroupId } = storeToRefs(
usePasswordGroupStore(),
)
const { currentGroup } = storeToRefs(usePasswordGroupStore())
const group = ref<SelectHaexPasswordsGroups>({
color: null,
createdAt: null,
description: null,
icon: null,
id: '',
name: null,
order: null,
parentId: null,
updateAt: null,
})
const group = ref<SelectHaexPasswordsGroups>()
const original = ref<string>('')
const ignoreChanges = ref(false)
const { readGroupAsync } = usePasswordGroupStore()
watch(
currentGroup,
() => {
group.value = JSON.parse(JSON.stringify(currentGroup.value))
currentGroupId,
async () => {
if (!currentGroupId.value) return
ignoreChanges.value = false
try {
const foundGroup = await readGroupAsync(currentGroupId.value)
if (foundGroup) {
original.value = JSON.stringify(foundGroup)
group.value = foundGroup
}
} catch (error) {
console.error(error)
}
},
{ immediate: true },
)
/* watch(
currentGroup,
(n, o) => {
console.log('currentGroup', currentGroup.value, n, o)
original.value = JSON.stringify(currentGroup.value)
group.value = JSON.parse(original.value)
ignoreChanges.value = false
},
{ immediate: true },
) */
const errors = ref({
name: [],
description: [],
})
const read_only = ref(true)
const hasChanges = computed(
() => JSON.stringify(group.value) !== original.value,
)
const onClose = () => {
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
read_only.value = true
useRouter().back()
}
const { add } = useSnackbar()
const { updateAsync, syncGroupItemsAsync } = usePasswordGroupStore()
const { updateAsync, syncGroupItemsAsync, deleteGroupAsync } =
usePasswordGroupStore()
const onSaveAsync = async () => {
try {
check.value = true
if (!group.value) return
if (errors.value.name.length || errors.value.description.length) return
ignoreChanges.value = true
await updateAsync(group.value)
syncGroupItemsAsync()
await syncGroupItemsAsync(group.value.id)
add({ type: 'success', text: t('change.success') })
onClose()
} catch (error) {
@ -62,6 +131,34 @@ const onSaveAsync = async () => {
console.log(error)
}
}
const showConfirmDeleteDialog = ref(false)
const showUnsavedChangesDialog = ref(false)
const onConfirmIgnoreChanges = () => {
showUnsavedChangesDialog.value = false
onClose()
}
const onDeleteAsync = async () => {
try {
const parentId = group.value.parentId
await deleteGroupAsync(group.value.id, inTrashGroup.value)
await syncGroupItemsAsync(parentId)
showConfirmDeleteDialog.value = false
ignoreChanges.value = true
await navigateTo(
useLocalePath()({
name: 'passwordGroupItems',
params: {
...useRouter().currentRoute.value.params,
groupId: parentId,
},
}),
)
} catch (error) {
console.error(error)
}
}
</script>
<i18n lang="yaml">

View File

@ -1,12 +1,12 @@
<template>
<div class="h-full relative">
<div class="h-full">
<div class="h-full flex flex-col">
<HaexPassGroupBreadcrumbs
:items="breadCrumbs"
class="px-2 z-10 bg-base-200"
class="px-2"
v-show="breadCrumbs.length"
/>
<div class="h-full overflow-auto flex flex-col">
<div class="flex-1 overflow-auto">
<HaexPassMobileMenu
:menu-items="groupItems"
ref="listRef"
@ -76,40 +76,77 @@ definePageMeta({
name: 'passwordGroupItems',
})
const { t } = useI18n()
const { add } = useSnackbar()
const selectedItems = ref<Set<IPasswordMenuItem>>(new Set())
const { menu } = storeToRefs(usePasswordsActionMenuStore())
const { syncItemsAsync } = usePasswordItemStore()
const { syncGroupItemsAsync } = usePasswordGroupStore()
onMounted(async () => {
try {
await Promise.allSettled([syncItemsAsync(), syncGroupItemsAsync()])
} catch (error) {}
})
const {
breadCrumbs,
currentGroupId,
currentGroupItems,
inTrashGroup,
selectedGroupItems,
groups,
} = storeToRefs(usePasswordGroupStore())
const { insertGroupItemsAsync } = usePasswordGroupStore()
const { items } = storeToRefs(usePasswordItemStore())
const { search } = storeToRefs(useSearchStore())
const groupItems = computed<IPasswordMenuItem[]>(() => {
const items: IPasswordMenuItem[] = []
const menuItems: IPasswordMenuItem[] = []
items.push(
...currentGroupItems.value.groups.map<IPasswordMenuItem>((group) => ({
color: group.color,
icon: group.icon,
id: group.id,
name: group.name,
type: 'group',
})),
menuItems.push(
...groups.value
.filter((group) => {
if (!search.value) return group.parentId == currentGroupId.value
return (
group.name?.includes(search.value) ||
group.description?.includes(search.value)
)
})
.map<IPasswordMenuItem>((group) => ({
color: group.color,
icon: group.icon,
id: group.id,
name: group.name,
type: 'group',
})),
)
items.push(
...currentGroupItems.value.items.map<IPasswordMenuItem>((item) => ({
icon: item.icon,
id: item.id,
name: item.title,
type: 'item',
})),
menuItems.push(
...items.value
.filter((item) => {
if (!search.value)
return item.haex_passwords_group_items.groupId == currentGroupId.value
return (
item.haex_passwords_item_details.title?.includes(search.value) ||
item.haex_passwords_item_details.note?.includes(search.value) ||
item.haex_passwords_item_details.password?.includes(search.value) ||
item.haex_passwords_item_details.tags?.includes(search.value) ||
item.haex_passwords_item_details.url?.includes(search.value) ||
item.haex_passwords_item_details.username?.includes(search.value)
)
})
.map<IPasswordMenuItem>((item) => ({
icon: item.haex_passwords_item_details.icon,
id: item.haex_passwords_item_details.id,
name: item.haex_passwords_item_details.title,
type: 'item',
})),
)
return items
return menuItems
})
const { isVisible } = storeToRefs(useSidebarStore())
@ -150,8 +187,7 @@ onKeyStroke('x', (event) => {
}
})
const { t } = useI18n()
const { add } = useSnackbar()
const { insertGroupItemsAsync } = usePasswordGroupStore()
const onPasteAsync = async () => {
if (!selectedGroupItems.value?.length) return
@ -190,7 +226,8 @@ onKeyStroke('a', (event) => {
})
const { deleteAsync } = usePasswordItemStore()
const { deleteGroupAsync, syncGroupItemsAsync } = usePasswordGroupStore()
const { deleteGroupAsync } = usePasswordGroupStore()
const onDeleteAsync = async () => {
for (const item of selectedItems.value) {
if (item.type === 'group') {

View File

@ -3,7 +3,7 @@
<HaexPassItem
:history="item.history"
:read_only
@close="onClose"
@close="onClose()"
@submit="onUpdateAsync"
v-model:details="item.details"
v-model:key-values-add="item.keyValuesAdd"
@ -11,7 +11,7 @@
v-model:key-values="item.keyValues"
/>
<div
<!-- <div
class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end"
:class="[isVisible ? 'left-15 ' : 'left-0']"
>
@ -85,27 +85,35 @@
</UiButton>
</UiTooltip>
</div>
</div>
</div> -->
<UiDialogConfirm
<HaexPassMenuBottom
:show-edit-button="read_only && !hasChanges"
:show-readonly-button="!read_only && !hasChanges"
:show-save-button="!read_only && hasChanges"
@close="onClose"
@delete="showConfirmDeleteDialog = true"
@edit="read_only = false"
@readonly="read_only = true"
@save="onUpdateAsync"
show-close-button
show-delete-button
>
</HaexPassMenuBottom>
<HaexPassDialogDeleteItem
v-model:open="showConfirmDeleteDialog"
:confirm-label="t('dialog.delete.label')"
:title="t('dialog.delete.title')"
@abort="showConfirmDeleteDialog = false"
@confirm="deleteItemAsync"
>
{{ t('dialog.delete.question') }}
</UiDialogConfirm>
</HaexPassDialogDeleteItem>
<UiDialogConfirm
v-model:open="showUnsavedChangesDialog"
:confirm-label="t('dialog.unsavedChanges.label')"
:title="t('dialog.unsavedChanges.title')"
<HaexPassDialogUnsavedChanges
:has-changes="hasChanges"
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
>
{{ t('dialog.unsavedChanges.question') }}
</UiDialogConfirm>
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
@ -126,7 +134,6 @@ defineProps({
withCopyButton: Boolean,
})
const { isVisible } = storeToRefs(useSidebarStore())
const read_only = ref(true)
const showConfirmDeleteDialog = ref(false)
const { t } = useI18n()
@ -165,7 +172,6 @@ const { currentItem } = storeToRefs(usePasswordItemStore())
watch(
currentItem,
() => {
console.log('watch currentItem', currentItem.value)
if (!currentItem.value) return
item.details = JSON.parse(JSON.stringify(currentItem.value?.details))
item.keyValues = JSON.parse(JSON.stringify(currentItem.value?.keyValues))
@ -174,15 +180,14 @@ watch(
item.keyValuesDelete = []
item.originalDetails = JSON.stringify(currentItem.value?.details)
item.originalKeyValues = JSON.stringify(currentItem.value?.keyValues)
ignoreChanges.value = false
},
{ immediate: true },
)
const { add } = useSnackbar()
const { deleteAsync, updateAsync } = usePasswordItemStore()
const { syncGroupItemsAsync, trashId } = usePasswordGroupStore()
const { currentGroupId } = storeToRefs(usePasswordGroupStore())
const { syncGroupItemsAsync } = usePasswordGroupStore()
const { currentGroupId, inTrashGroup } = storeToRefs(usePasswordGroupStore())
const onUpdateAsync = async () => {
try {
@ -195,31 +200,29 @@ const onUpdateAsync = async () => {
})
if (newId) add({ type: 'success', text: t('success.update') })
syncGroupItemsAsync(currentGroupId.value)
ignoreChanges.value = true
onClose()
onClose(true)
} catch (error) {
add({ type: 'error', text: t('error.update') })
}
}
const onClose = () => {
const onClose = (ignoreChanges?: boolean) => {
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
if (hasChanges.value && !ignoreChanges.value)
if (hasChanges.value && !ignoreChanges)
return (showUnsavedChangesDialog.value = true)
ignoreChanges.value = false
read_only.value = true
useRouter().back()
}
const deleteItemAsync = async () => {
try {
await deleteAsync(item.details.id, currentGroupId.value === trashId)
await deleteAsync(item.details.id, inTrashGroup.value)
showConfirmDeleteDialog.value = false
add({ type: 'success', text: t('success.delete') })
syncGroupItemsAsync(currentGroupId.value)
onClose()
await syncGroupItemsAsync(currentGroupId.value)
onClose(true)
} catch (errro) {
add({
type: 'error',
@ -239,22 +242,14 @@ const hasChanges = computed(
)
const showUnsavedChangesDialog = ref(false)
const ignoreChanges = ref(false)
const onConfirmIgnoreChanges = () => {
ignoreChanges.value = true
showUnsavedChangesDialog.value = false
onClose()
onClose(true)
}
</script>
<i18n lang="yaml">
de:
save: Speichern
abort: Abbrechen
edit: Bearbeiten
noEdit: Lesemodus
delete: Löschen
success:
update: Eintrag erfolgreich aktualisiert
delete: Eintrag wurde gelöscht
@ -266,21 +261,7 @@ de:
keyValue: Extra
history: Verlauf
dialog:
delete:
title: Eintrag löschen
question: Soll der Eintrag wirklich gelöscht werden?
label: Löschen
unsavedChanges:
title: Nicht gespeicherte Änderungen
question: Sollen die Änderungen verworfen werden?
label: Verwerfen
en:
save: Save
abort: Abort
edit: Edit
noEdit: Read Mode
delete: Delete
success:
update: Entry successfully updated
delete: Entry successfully removed
@ -291,14 +272,4 @@ en:
details: Details
keyValue: Extra
history: History
dialog:
delete:
title: Delete Entry
question: Should the entry really be deleted?
label: Delete
unsavedChanges:
title: Unsaved changes
question: Should the changes be discarded?
label: discard
</i18n>

View File

@ -9,7 +9,15 @@
v-model:key-values-add="item.keyValuesAdd"
/>
<div
<HaexPassMenuBottom
@close="onClose"
@save="onCreateAsync"
show-close-button
show-save-button
>
</HaexPassMenuBottom>
<!-- <div
class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end"
:class="[isVisible ? 'left-15 ' : 'left-0']"
>
@ -35,7 +43,7 @@
</UiButton>
</UiTooltip>
<div class="flex items-center justify-center flex-1"></div>
</div>
</div> -->
</div>
</template>