mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
implemented search
This commit is contained in:
@ -12,7 +12,6 @@ pub fn run() {
|
|||||||
let protocol_name = "haex-extension";
|
let protocol_name = "haex-extension";
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_notification::init())
|
|
||||||
.register_uri_scheme_protocol(protocol_name, move |context, request| {
|
.register_uri_scheme_protocol(protocol_name, move |context, request| {
|
||||||
match extension::core::extension_protocol_handler(&context, &request) {
|
match extension::core::extension_protocol_handler(&context, &request) {
|
||||||
Ok(response) => response, // Wenn der Handler Ok ist, gib die Response direkt zurück
|
Ok(response) => response, // Wenn der Handler Ok ist, gib die Response direkt zurück
|
||||||
@ -45,6 +44,7 @@ pub fn run() {
|
|||||||
})
|
})
|
||||||
.manage(DbConnection(Mutex::new(None)))
|
.manage(DbConnection(Mutex::new(None)))
|
||||||
.manage(ExtensionState::default())
|
.manage(ExtensionState::default())
|
||||||
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
@ -57,10 +57,10 @@ pub fn run() {
|
|||||||
database::open_encrypted_database,
|
database::open_encrypted_database,
|
||||||
database::sql_execute,
|
database::sql_execute,
|
||||||
database::sql_select,
|
database::sql_select,
|
||||||
|
database::test,
|
||||||
|
extension::copy_directory,
|
||||||
extension::database::extension_sql_execute,
|
extension::database::extension_sql_execute,
|
||||||
extension::database::extension_sql_select,
|
extension::database::extension_sql_select,
|
||||||
extension::copy_directory,
|
|
||||||
database::test
|
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
44
src/components/haex/pass/dialog/deleteItem.vue
Normal file
44
src/components/haex/pass/dialog/deleteItem.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<UiDialogConfirm
|
||||||
|
v-model:open="showConfirmDeleteDialog"
|
||||||
|
:confirm-label="final ? t('final.label') : t('label')"
|
||||||
|
:title="final ? t('final.title') : t('title', { itemName })"
|
||||||
|
@abort="$emit('abort')"
|
||||||
|
@confirm="$emit('confirm')"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
final ? t('final.question', { itemName }) : t('question', { itemName })
|
||||||
|
}}
|
||||||
|
</UiDialogConfirm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const showConfirmDeleteDialog = defineModel<boolean>('open')
|
||||||
|
defineProps<{ final?: boolean; itemName?: string | null }>()
|
||||||
|
|
||||||
|
defineEmits(['confirm', 'abort'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
de:
|
||||||
|
title: Eintrag löschen
|
||||||
|
question: Soll der Eintrag "{itemName}" in den Papierkorb verschoben werden?
|
||||||
|
label: Verschieben
|
||||||
|
|
||||||
|
final:
|
||||||
|
title: Eintrag endgültig löschen
|
||||||
|
question: Soll der Eintrag "{itemName}" endgültig gelöscht werden?
|
||||||
|
label: Löschen
|
||||||
|
|
||||||
|
en:
|
||||||
|
title: Delete Entry
|
||||||
|
question: Should the “{itemName}” entry be moved to the recycle bin?
|
||||||
|
label: Move
|
||||||
|
|
||||||
|
final:
|
||||||
|
title: Delete entry permanently
|
||||||
|
question: Should the entry “{itemName}” be permanently deleted?
|
||||||
|
label: Delete
|
||||||
|
</i18n>
|
||||||
47
src/components/haex/pass/dialog/unsavedChanges.vue
Normal file
47
src/components/haex/pass/dialog/unsavedChanges.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<UiDialogConfirm
|
||||||
|
:confirm-label="t('label')"
|
||||||
|
:title="t('title')"
|
||||||
|
@abort="$emit('abort')"
|
||||||
|
@confirm="onConfirm"
|
||||||
|
v-model:open="showUnsavedChangesDialog"
|
||||||
|
>
|
||||||
|
{{ t('question') }}
|
||||||
|
</UiDialogConfirm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const showUnsavedChangesDialog = defineModel<boolean>('open')
|
||||||
|
const ignoreChanges = defineModel<boolean>('ignoreChanges')
|
||||||
|
const { hasChanges } = defineProps<{ hasChanges: boolean }>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm', 'abort'])
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
ignoreChanges.value = true
|
||||||
|
emit('confirm')
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
if (hasChanges && !ignoreChanges.value) {
|
||||||
|
showUnsavedChangesDialog.value = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
de:
|
||||||
|
title: Nicht gespeicherte Änderungen
|
||||||
|
question: Sollen die Änderungen verworfen werden?
|
||||||
|
label: Verwerfen
|
||||||
|
|
||||||
|
en:
|
||||||
|
title: Unsaved changes
|
||||||
|
question: Should the changes be discarded?
|
||||||
|
label: discard
|
||||||
|
</i18n>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="breadcrumbs sticky top-0">
|
<div class="breadcrumbs">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<NuxtLinkLocale :to="{ name: 'passwordGroupItems' }">
|
<NuxtLinkLocale :to="{ name: 'passwordGroupItems' }">
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<UiCard
|
<UiCard
|
||||||
v-if="group"
|
v-if="group"
|
||||||
:title="mode === 'create' ? t('title.create') : t('title.edit')"
|
:title="mode === 'edit' ? t('title.edit') : t('title.create')"
|
||||||
icon="mdi:folder-plus-outline"
|
icon="mdi:folder-plus-outline"
|
||||||
@close="$emit('close')"
|
@close="$emit('close')"
|
||||||
body-class="px-0"
|
body-class="px-0"
|
||||||
@ -12,9 +12,9 @@
|
|||||||
@submit.prevent="$emit('submit')"
|
@submit.prevent="$emit('submit')"
|
||||||
>
|
>
|
||||||
<UiInput
|
<UiInput
|
||||||
:check-input="check"
|
|
||||||
:label="t('name')"
|
:label="t('name')"
|
||||||
:placeholder="t('name')"
|
:placeholder="t('name')"
|
||||||
|
:read_only
|
||||||
autofocus
|
autofocus
|
||||||
v-model="group.name"
|
v-model="group.name"
|
||||||
ref="nameRef"
|
ref="nameRef"
|
||||||
@ -23,9 +23,9 @@
|
|||||||
|
|
||||||
<UiInput
|
<UiInput
|
||||||
v-model="group.description"
|
v-model="group.description"
|
||||||
:check-input="check"
|
|
||||||
:label="t('description')"
|
:label="t('description')"
|
||||||
:placeholder="t('description')"
|
:placeholder="t('description')"
|
||||||
|
:read_only
|
||||||
@keyup.enter="$emit('submit')"
|
@keyup.enter="$emit('submit')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -33,12 +33,16 @@
|
|||||||
<UiSelectIcon
|
<UiSelectIcon
|
||||||
v-model="group.icon"
|
v-model="group.icon"
|
||||||
default-icon="mdi:folder-outline"
|
default-icon="mdi:folder-outline"
|
||||||
|
:read_only
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UiSelectColor v-model="group.color" />
|
<UiSelectColor
|
||||||
|
v-model="group.color"
|
||||||
|
:read_only
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap justify-end gap-4">
|
<!-- <div class="flex flex-wrap justify-end gap-4">
|
||||||
<UiButton
|
<UiButton
|
||||||
class="btn-error btn-outline flex-1"
|
class="btn-error btn-outline flex-1"
|
||||||
@click="$emit('close')"
|
@click="$emit('close')"
|
||||||
@ -54,7 +58,7 @@
|
|||||||
{{ mode === 'create' ? t('create') : t('save') }}
|
{{ mode === 'create' ? t('create') : t('save') }}
|
||||||
<Icon name="mdi:check" />
|
<Icon name="mdi:check" />
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</div>
|
</div> -->
|
||||||
</form>
|
</form>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
</div>
|
</div>
|
||||||
@ -64,13 +68,14 @@
|
|||||||
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
|
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
|
||||||
|
|
||||||
const group = defineModel<SelectHaexPasswordsGroups | null>()
|
const group = defineModel<SelectHaexPasswordsGroups | null>()
|
||||||
defineEmits(['close', 'submit', 'back'])
|
const { read_only = false } = defineProps<{
|
||||||
defineProps<{ mode: 'create' | 'edit' }>()
|
read_only?: boolean
|
||||||
|
mode: 'create' | 'edit'
|
||||||
|
}>()
|
||||||
|
defineEmits(['close', 'submit'])
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const check = ref<boolean>(false)
|
|
||||||
|
|
||||||
const nameRef = useTemplateRef('nameRef')
|
const nameRef = useTemplateRef('nameRef')
|
||||||
onStartTyping(() => {
|
onStartTyping(() => {
|
||||||
nameRef.value?.inputRef?.focus()
|
nameRef.value?.inputRef?.focus()
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
:with-copy-button
|
:with-copy-button
|
||||||
:read_only
|
:read_only
|
||||||
v-model.trim="itemDetails.username"
|
v-model.trim="itemDetails.username"
|
||||||
|
@keyup.enter="$emit('submit')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UiInputPassword
|
<UiInputPassword
|
||||||
@ -33,6 +34,7 @@
|
|||||||
:read_only
|
:read_only
|
||||||
:with-copy-button
|
:with-copy-button
|
||||||
v-model.trim="itemDetails.password"
|
v-model.trim="itemDetails.password"
|
||||||
|
@keyup.enter="$emit('submit')"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<UiDialogPasswordGenerator
|
<UiDialogPasswordGenerator
|
||||||
@ -51,6 +53,7 @@
|
|||||||
:read_only
|
:read_only
|
||||||
:with-copy-button
|
:with-copy-button
|
||||||
v-model="itemDetails.url"
|
v-model="itemDetails.url"
|
||||||
|
@keyup.enter="$emit('submit')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UiSelectIcon
|
<UiSelectIcon
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<UiCard
|
<UiCard
|
||||||
body-class="rounded overflow-auto px-0 h-full"
|
body-class="rounded overflow-auto p-0 h-full"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
>
|
>
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|||||||
102
src/components/haex/pass/menu/bottom.vue
Normal file
102
src/components/haex/pass/menu/bottom.vue
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<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']"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center flex-1">
|
||||||
|
<UiButton
|
||||||
|
v-show="showCloseButton"
|
||||||
|
:tooltip="t('abort')"
|
||||||
|
@click="$emit('close')"
|
||||||
|
class="btn-accent btn-square"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:close" />
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<UiButton
|
||||||
|
v-show="showEditButton"
|
||||||
|
:tooltip="t('edit')"
|
||||||
|
@click="$emit('edit')"
|
||||||
|
class="btn-xl btn-square btn-primary"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="mdi:pencil-outline"
|
||||||
|
class="size-11 shrink-0"
|
||||||
|
/>
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<UiButton
|
||||||
|
v-show="showReadonlyButton"
|
||||||
|
:tooltip="t('readonly')"
|
||||||
|
class="btn-xl btn-square btn-primary"
|
||||||
|
@click="$emit('readonly')"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="mdi:pencil-off-outline"
|
||||||
|
class="size-11 shrink-0"
|
||||||
|
/>
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<UiButton
|
||||||
|
v-show="showSaveButton"
|
||||||
|
:tooltip="t('save')"
|
||||||
|
class="btn-xl btn-square btn-primary motion-duration-2000"
|
||||||
|
:class="{ 'motion-preset-pulse-sm': hasChanges }"
|
||||||
|
@click="$emit('save')"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="mdi:content-save-outline"
|
||||||
|
class="size-11 shrink-0"
|
||||||
|
/>
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center flex-1">
|
||||||
|
<UiButton
|
||||||
|
v-show="showDeleteButton"
|
||||||
|
:tooltip="t('delete')"
|
||||||
|
class="btn-square btn-error"
|
||||||
|
@click="$emit('delete')"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="mdi:trash-outline"
|
||||||
|
class="shrink-0"
|
||||||
|
/>
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { isVisible } = storeToRefs(useSidebarStore())
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
showCloseButton?: boolean
|
||||||
|
showDeleteButton?: boolean
|
||||||
|
showEditButton?: boolean
|
||||||
|
showReadonlyButton?: boolean
|
||||||
|
showSaveButton?: boolean
|
||||||
|
hasChanges?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits(['close', 'edit', 'readonly', 'save', 'delete'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
de:
|
||||||
|
save: Speichern
|
||||||
|
abort: Abbrechen
|
||||||
|
edit: Bearbeiten
|
||||||
|
readonly: Lesemodus
|
||||||
|
delete: Löschen
|
||||||
|
|
||||||
|
en:
|
||||||
|
save: Save
|
||||||
|
abort: Abort
|
||||||
|
edit: Edit
|
||||||
|
readonly: Read Mode
|
||||||
|
delete: Delete
|
||||||
|
</i18n>
|
||||||
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="menuItems?.length">
|
<div
|
||||||
|
v-if="menuItems?.length"
|
||||||
|
class="h-full"
|
||||||
|
>
|
||||||
<ul
|
<ul
|
||||||
class="flex flex-col w-full h-full gap-y-2 first:rounded-t-md last:rounded-b-md p-1"
|
class="flex flex-col w-full h-full gap-y-2 first:rounded-t-md last:rounded-b-md p-1"
|
||||||
ref="listRef"
|
ref="listRef"
|
||||||
@ -40,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex justify-center items-center px-20 h-full"
|
class="flex justify-center items-center px-20 flex-1 bg-red-100"
|
||||||
>
|
>
|
||||||
<UiIconNoData class="text-primary size-24 shrink-0" />
|
<UiIconNoData class="text-primary size-24 shrink-0" />
|
||||||
<!-- <p>{{ t('empty') }}</p> -->
|
<!-- <p>{{ t('empty') }}</p> -->
|
||||||
@ -78,6 +81,7 @@ watch(selectedItems, () => {
|
|||||||
|
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
const { ctrl } = useMagicKeys()
|
const { ctrl } = useMagicKeys()
|
||||||
|
const { search } = storeToRefs(useSearchStore())
|
||||||
|
|
||||||
const onClickItemAsync = async (item: IPasswordMenuItem) => {
|
const onClickItemAsync = async (item: IPasswordMenuItem) => {
|
||||||
currentSelectedItem.value = null
|
currentSelectedItem.value = null
|
||||||
@ -91,6 +95,7 @@ const onClickItemAsync = async (item: IPasswordMenuItem) => {
|
|||||||
|
|
||||||
if (!selectedItems.value.size) longPressedHook.value = false
|
if (!selectedItems.value.size) longPressedHook.value = false
|
||||||
} else {
|
} else {
|
||||||
|
search.value = ''
|
||||||
if (item.type === 'group')
|
if (item.type === 'group')
|
||||||
await navigateTo(
|
await navigateTo(
|
||||||
localePath({
|
localePath({
|
||||||
|
|||||||
@ -13,53 +13,55 @@
|
|||||||
</slot>
|
</slot>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Teleport to="body">
|
<div class="hidden">
|
||||||
<div
|
<Teleport to="body">
|
||||||
:id
|
|
||||||
ref="modalRef"
|
|
||||||
class="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden modal-middle p-0 xs:p-2 --prevent-on-load-init pointer-events-auto max-w-none"
|
|
||||||
role="dialog"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog"
|
:id
|
||||||
|
ref="modalRef"
|
||||||
|
class="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden modal-middle p-0 xs:p-2 --prevent-on-load-init pointer-events-auto max-w-none"
|
||||||
|
role="dialog"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div class="modal-content justify-between">
|
<div
|
||||||
<div class="modal-header py-0 sm:py-4">
|
class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog"
|
||||||
<div
|
>
|
||||||
v-if="title || $slots.title"
|
<div class="modal-content justify-between">
|
||||||
class="modal-title py-4 break-all"
|
<div class="modal-header py-0 sm:py-4">
|
||||||
>
|
<div
|
||||||
<slot name="title">
|
v-if="title || $slots.title"
|
||||||
{{ title }}
|
class="modal-title py-4 break-all"
|
||||||
</slot>
|
>
|
||||||
|
<slot name="title">
|
||||||
|
{{ title }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
|
||||||
|
:aria-label="t('close')"
|
||||||
|
tabindex="1"
|
||||||
|
@click="open = false"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="mdi:close"
|
||||||
|
size="18"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div class="modal-body text-sm sm:text-base grow mt-0 pt-0">
|
||||||
type="button"
|
<slot />
|
||||||
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
|
</div>
|
||||||
:aria-label="t('close')"
|
|
||||||
tabindex="1"
|
|
||||||
@click="open = false"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="mdi:close"
|
|
||||||
size="18"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body text-sm sm:text-base grow mt-0 pt-0">
|
<div class="modal-footer flex-col sm:flex-row">
|
||||||
<slot />
|
<slot name="buttons" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer flex-col sm:flex-row">
|
|
||||||
<slot name="buttons" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Teleport>
|
||||||
</Teleport>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
:is="itemIs"
|
:is="itemIs"
|
||||||
@click="$emit('select', item)"
|
@click="read_only ? '' : $emit('select', item)"
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
>
|
>
|
||||||
@ -59,6 +59,7 @@ const { itemIs = 'li', offset = '[--offset:0]' } = defineProps<{
|
|||||||
itemIs?: string
|
itemIs?: string
|
||||||
activatorClass?: string
|
activatorClass?: string
|
||||||
offset?: string
|
offset?: string
|
||||||
|
read_only?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{ select: [T] }>()
|
defineEmits<{ select: [T] }>()
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
:rules
|
:rules
|
||||||
:type="type"
|
:type="type"
|
||||||
:with-copy-button
|
:with-copy-button
|
||||||
|
@keyup="(e) => $emit('keyup', e)"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<slot name="append" />
|
<slot name="append" />
|
||||||
@ -38,6 +39,10 @@ defineProps<{
|
|||||||
withCopyButton?: boolean
|
withCopyButton?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
keyup: [KeyboardEvent]
|
||||||
|
}>()
|
||||||
|
|
||||||
const type = ref<'password' | 'text'>('password')
|
const type = ref<'password' | 'text'>('password')
|
||||||
|
|
||||||
const tooglePasswordType = () => {
|
const tooglePasswordType = () => {
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
:rules
|
:rules
|
||||||
:with-copy-button
|
:with-copy-button
|
||||||
v-model.trim="value"
|
v-model.trim="value"
|
||||||
|
@keyup="(e) => $emit('keyup', e)"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<UiButton
|
<UiButton
|
||||||
@ -38,6 +39,10 @@ defineProps({
|
|||||||
withCopyButton: Boolean,
|
withCopyButton: Boolean,
|
||||||
read_only: Boolean,
|
read_only: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
keyup: [KeyboardEvent]
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i18n lang="json">
|
<i18n lang="json">
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
:items="icons"
|
:items="icons"
|
||||||
class="btn"
|
class="btn"
|
||||||
@select="(newIcon) => (iconName = newIcon)"
|
@select="(newIcon) => (iconName = newIcon)"
|
||||||
|
:read_only
|
||||||
>
|
>
|
||||||
<template #activator>
|
<template #activator>
|
||||||
<Icon :name="iconName ? iconName : defaultIcon || icons.at(0)" />
|
<Icon :name="iconName ? iconName : defaultIcon || icons.at(0)" />
|
||||||
@ -13,7 +14,7 @@
|
|||||||
<li
|
<li
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
@click="iconName = item"
|
@click="read_only ? '' : (iconName = item)"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
:name="item"
|
:name="item"
|
||||||
@ -73,5 +74,5 @@ const icons = [
|
|||||||
|
|
||||||
const iconName = defineModel<string | undefined | null>()
|
const iconName = defineModel<string | undefined | null>()
|
||||||
|
|
||||||
defineProps<{ defaultIcon?: string }>()
|
defineProps<{ defaultIcon?: string; read_only?: boolean }>()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
/* import de from '@/stores/sidebar/de.json';
|
import passwordActionMenuDe from '@/stores/passwords/actionMenu/de.json'
|
||||||
import en from '@/stores/sidebar/en.json'; */
|
import passwordActionMenuEn from '@/stores/passwords/actionMenu/en.json'
|
||||||
|
|
||||||
export default defineI18nConfig(() => {
|
export default defineI18nConfig(() => {
|
||||||
return {
|
return {
|
||||||
legacy: false,
|
legacy: false,
|
||||||
messages: {
|
messages: {
|
||||||
de: {
|
de: {
|
||||||
//sidebar: de,
|
passwordActionMenu: passwordActionMenuDe,
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
//sidebar: en,
|
passwordActionMenu: passwordActionMenuEn,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|||||||
@ -35,7 +35,25 @@
|
|||||||
</NuxtLinkLocale>
|
</NuxtLinkLocale>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<UiInput
|
||||||
|
v-model="search"
|
||||||
|
:label="t('search.label')"
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<UiButton class="btn-square btn-primary">
|
||||||
|
<Icon
|
||||||
|
name="mdi:magnify"
|
||||||
|
class="size-full p-1"
|
||||||
|
/>
|
||||||
|
</UiButton>
|
||||||
|
</template>
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
<HaexMenuNotifications />
|
<HaexMenuNotifications />
|
||||||
<HaexMenuMain />
|
<HaexMenuMain />
|
||||||
</div>
|
</div>
|
||||||
@ -78,26 +96,34 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const { currentVaultName } = storeToRefs(useVaultStore())
|
const { currentVaultName } = storeToRefs(useVaultStore())
|
||||||
|
|
||||||
const { menu, isVisible } = storeToRefs(useSidebarStore())
|
|
||||||
|
|
||||||
const { extensionLinks } = storeToRefs(useExtensionsStore())
|
const { extensionLinks } = storeToRefs(useExtensionsStore())
|
||||||
|
|
||||||
|
const { menu, isVisible } = storeToRefs(useSidebarStore())
|
||||||
const toogleSidebar = () => {
|
const toogleSidebar = () => {
|
||||||
isVisible.value = !isVisible.value
|
isVisible.value = !isVisible.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { search } = storeToRefs(useSearchStore())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i18n lang="yaml">
|
<i18n lang="yaml">
|
||||||
de:
|
de:
|
||||||
vault:
|
vault:
|
||||||
close: Vault schließen
|
close: Vault schließen
|
||||||
|
|
||||||
sidebar:
|
sidebar:
|
||||||
close: Sidebar ausblenden
|
close: Sidebar ausblenden
|
||||||
show: Sidebar anzeigen
|
show: Sidebar anzeigen
|
||||||
|
|
||||||
|
search:
|
||||||
|
label: Suche
|
||||||
en:
|
en:
|
||||||
vault:
|
vault:
|
||||||
close: Close vault
|
close: Close vault
|
||||||
sidebar:
|
sidebar:
|
||||||
close: close sidebar
|
close: close sidebar
|
||||||
show: show sidebar
|
show: show sidebar
|
||||||
|
|
||||||
|
search:
|
||||||
|
label: Search
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-2 min-h-full">
|
<div class="h-full overflow-auto p-2">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,11 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
{{ currentGroupId }}
|
||||||
<HaexPassGroup
|
<HaexPassGroup
|
||||||
v-model="group"
|
v-model="group"
|
||||||
mode="create"
|
mode="create"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@submit="createAsync"
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,7 +52,14 @@ const errors = ref({
|
|||||||
description: [],
|
description: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ignoreChanges = ref(false)
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
|
if (showUnsavedChangesDialog.value) return
|
||||||
|
|
||||||
|
if (hasChanges.value && !ignoreChanges.value) {
|
||||||
|
return (showUnsavedChangesDialog.value = true)
|
||||||
|
}
|
||||||
useRouter().back()
|
useRouter().back()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,11 +70,11 @@ const createAsync = async () => {
|
|||||||
|
|
||||||
const newGroup = await addGroupAsync(group.value)
|
const newGroup = await addGroupAsync(group.value)
|
||||||
|
|
||||||
console.log('newGroup', newGroup)
|
|
||||||
if (!newGroup.id) {
|
if (!newGroup.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignoreChanges.value = true
|
||||||
await navigateTo(
|
await navigateTo(
|
||||||
useLocalePath()({
|
useLocalePath()({
|
||||||
name: 'passwordGroupItems',
|
name: 'passwordGroupItems',
|
||||||
@ -65,4 +90,20 @@ const createAsync = async () => {
|
|||||||
console.log(error)
|
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>
|
</script>
|
||||||
|
|||||||
@ -1,11 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
currentGroup{{ currentGroup }}
|
|
||||||
<HaexPassGroup
|
<HaexPassGroup
|
||||||
v-model="group"
|
:read_only
|
||||||
mode="edit"
|
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@submit="onSaveAsync"
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -19,42 +51,79 @@ definePageMeta({
|
|||||||
|
|
||||||
const { t } = useI18n()
|
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(
|
watch(
|
||||||
currentGroup,
|
currentGroupId,
|
||||||
() => {
|
async () => {
|
||||||
group.value = JSON.parse(JSON.stringify(currentGroup.value))
|
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 },
|
{ 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({
|
const read_only = ref(true)
|
||||||
name: [],
|
|
||||||
description: [],
|
const hasChanges = computed(
|
||||||
})
|
() => JSON.stringify(group.value) !== original.value,
|
||||||
|
)
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
|
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
|
||||||
|
|
||||||
|
read_only.value = true
|
||||||
useRouter().back()
|
useRouter().back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { add } = useSnackbar()
|
const { add } = useSnackbar()
|
||||||
|
|
||||||
const { updateAsync, syncGroupItemsAsync } = usePasswordGroupStore()
|
const { updateAsync, syncGroupItemsAsync, deleteGroupAsync } =
|
||||||
|
usePasswordGroupStore()
|
||||||
|
|
||||||
const onSaveAsync = async () => {
|
const onSaveAsync = async () => {
|
||||||
try {
|
try {
|
||||||
check.value = true
|
|
||||||
if (!group.value) return
|
if (!group.value) return
|
||||||
|
|
||||||
if (errors.value.name.length || errors.value.description.length) return
|
ignoreChanges.value = true
|
||||||
|
|
||||||
await updateAsync(group.value)
|
await updateAsync(group.value)
|
||||||
syncGroupItemsAsync()
|
await syncGroupItemsAsync(group.value.id)
|
||||||
add({ type: 'success', text: t('change.success') })
|
add({ type: 'success', text: t('change.success') })
|
||||||
onClose()
|
onClose()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -62,6 +131,34 @@ const onSaveAsync = async () => {
|
|||||||
console.log(error)
|
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>
|
</script>
|
||||||
|
|
||||||
<i18n lang="yaml">
|
<i18n lang="yaml">
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-full relative">
|
<div class="h-full relative">
|
||||||
<div class="h-full">
|
<div class="h-full flex flex-col">
|
||||||
<HaexPassGroupBreadcrumbs
|
<HaexPassGroupBreadcrumbs
|
||||||
:items="breadCrumbs"
|
:items="breadCrumbs"
|
||||||
class="px-2 z-10 bg-base-200"
|
class="px-2"
|
||||||
v-show="breadCrumbs.length"
|
v-show="breadCrumbs.length"
|
||||||
/>
|
/>
|
||||||
<div class="h-full overflow-auto flex flex-col">
|
<div class="flex-1 overflow-auto">
|
||||||
<HaexPassMobileMenu
|
<HaexPassMobileMenu
|
||||||
:menu-items="groupItems"
|
:menu-items="groupItems"
|
||||||
ref="listRef"
|
ref="listRef"
|
||||||
@ -76,40 +76,77 @@ definePageMeta({
|
|||||||
name: 'passwordGroupItems',
|
name: 'passwordGroupItems',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { add } = useSnackbar()
|
||||||
|
|
||||||
const selectedItems = ref<Set<IPasswordMenuItem>>(new Set())
|
const selectedItems = ref<Set<IPasswordMenuItem>>(new Set())
|
||||||
const { menu } = storeToRefs(usePasswordsActionMenuStore())
|
const { menu } = storeToRefs(usePasswordsActionMenuStore())
|
||||||
|
|
||||||
|
const { syncItemsAsync } = usePasswordItemStore()
|
||||||
|
const { syncGroupItemsAsync } = usePasswordGroupStore()
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await Promise.allSettled([syncItemsAsync(), syncGroupItemsAsync()])
|
||||||
|
} catch (error) {}
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
breadCrumbs,
|
breadCrumbs,
|
||||||
currentGroupId,
|
currentGroupId,
|
||||||
currentGroupItems,
|
|
||||||
inTrashGroup,
|
inTrashGroup,
|
||||||
selectedGroupItems,
|
selectedGroupItems,
|
||||||
|
groups,
|
||||||
} = storeToRefs(usePasswordGroupStore())
|
} = storeToRefs(usePasswordGroupStore())
|
||||||
const { insertGroupItemsAsync } = usePasswordGroupStore()
|
|
||||||
|
const { items } = storeToRefs(usePasswordItemStore())
|
||||||
|
const { search } = storeToRefs(useSearchStore())
|
||||||
|
|
||||||
const groupItems = computed<IPasswordMenuItem[]>(() => {
|
const groupItems = computed<IPasswordMenuItem[]>(() => {
|
||||||
const items: IPasswordMenuItem[] = []
|
const menuItems: IPasswordMenuItem[] = []
|
||||||
|
|
||||||
items.push(
|
menuItems.push(
|
||||||
...currentGroupItems.value.groups.map<IPasswordMenuItem>((group) => ({
|
...groups.value
|
||||||
color: group.color,
|
.filter((group) => {
|
||||||
icon: group.icon,
|
if (!search.value) return group.parentId == currentGroupId.value
|
||||||
id: group.id,
|
|
||||||
name: group.name,
|
return (
|
||||||
type: 'group',
|
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(
|
menuItems.push(
|
||||||
...currentGroupItems.value.items.map<IPasswordMenuItem>((item) => ({
|
...items.value
|
||||||
icon: item.icon,
|
.filter((item) => {
|
||||||
id: item.id,
|
if (!search.value)
|
||||||
name: item.title,
|
return item.haex_passwords_group_items.groupId == currentGroupId.value
|
||||||
type: 'item',
|
|
||||||
})),
|
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())
|
const { isVisible } = storeToRefs(useSidebarStore())
|
||||||
@ -150,8 +187,7 @@ onKeyStroke('x', (event) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { insertGroupItemsAsync } = usePasswordGroupStore()
|
||||||
const { add } = useSnackbar()
|
|
||||||
|
|
||||||
const onPasteAsync = async () => {
|
const onPasteAsync = async () => {
|
||||||
if (!selectedGroupItems.value?.length) return
|
if (!selectedGroupItems.value?.length) return
|
||||||
@ -190,7 +226,8 @@ onKeyStroke('a', (event) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { deleteAsync } = usePasswordItemStore()
|
const { deleteAsync } = usePasswordItemStore()
|
||||||
const { deleteGroupAsync, syncGroupItemsAsync } = usePasswordGroupStore()
|
const { deleteGroupAsync } = usePasswordGroupStore()
|
||||||
|
|
||||||
const onDeleteAsync = async () => {
|
const onDeleteAsync = async () => {
|
||||||
for (const item of selectedItems.value) {
|
for (const item of selectedItems.value) {
|
||||||
if (item.type === 'group') {
|
if (item.type === 'group') {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<HaexPassItem
|
<HaexPassItem
|
||||||
:history="item.history"
|
:history="item.history"
|
||||||
:read_only
|
:read_only
|
||||||
@close="onClose"
|
@close="onClose()"
|
||||||
@submit="onUpdateAsync"
|
@submit="onUpdateAsync"
|
||||||
v-model:details="item.details"
|
v-model:details="item.details"
|
||||||
v-model:key-values-add="item.keyValuesAdd"
|
v-model:key-values-add="item.keyValuesAdd"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
v-model:key-values="item.keyValues"
|
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="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end"
|
||||||
:class="[isVisible ? 'left-15 ' : 'left-0']"
|
:class="[isVisible ? 'left-15 ' : 'left-0']"
|
||||||
>
|
>
|
||||||
@ -85,27 +85,35 @@
|
|||||||
</UiButton>
|
</UiButton>
|
||||||
</UiTooltip>
|
</UiTooltip>
|
||||||
</div>
|
</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"
|
v-model:open="showConfirmDeleteDialog"
|
||||||
:confirm-label="t('dialog.delete.label')"
|
|
||||||
:title="t('dialog.delete.title')"
|
|
||||||
@abort="showConfirmDeleteDialog = false"
|
@abort="showConfirmDeleteDialog = false"
|
||||||
@confirm="deleteItemAsync"
|
@confirm="deleteItemAsync"
|
||||||
>
|
>
|
||||||
{{ t('dialog.delete.question') }}
|
</HaexPassDialogDeleteItem>
|
||||||
</UiDialogConfirm>
|
|
||||||
|
|
||||||
<UiDialogConfirm
|
<HaexPassDialogUnsavedChanges
|
||||||
v-model:open="showUnsavedChangesDialog"
|
:has-changes="hasChanges"
|
||||||
:confirm-label="t('dialog.unsavedChanges.label')"
|
|
||||||
:title="t('dialog.unsavedChanges.title')"
|
|
||||||
@abort="showUnsavedChangesDialog = false"
|
@abort="showUnsavedChangesDialog = false"
|
||||||
@confirm="onConfirmIgnoreChanges"
|
@confirm="onConfirmIgnoreChanges"
|
||||||
>
|
v-model:open="showUnsavedChangesDialog"
|
||||||
{{ t('dialog.unsavedChanges.question') }}
|
/>
|
||||||
</UiDialogConfirm>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -126,7 +134,6 @@ defineProps({
|
|||||||
withCopyButton: Boolean,
|
withCopyButton: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { isVisible } = storeToRefs(useSidebarStore())
|
|
||||||
const read_only = ref(true)
|
const read_only = ref(true)
|
||||||
const showConfirmDeleteDialog = ref(false)
|
const showConfirmDeleteDialog = ref(false)
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -165,7 +172,6 @@ const { currentItem } = storeToRefs(usePasswordItemStore())
|
|||||||
watch(
|
watch(
|
||||||
currentItem,
|
currentItem,
|
||||||
() => {
|
() => {
|
||||||
console.log('watch currentItem', currentItem.value)
|
|
||||||
if (!currentItem.value) return
|
if (!currentItem.value) return
|
||||||
item.details = JSON.parse(JSON.stringify(currentItem.value?.details))
|
item.details = JSON.parse(JSON.stringify(currentItem.value?.details))
|
||||||
item.keyValues = JSON.parse(JSON.stringify(currentItem.value?.keyValues))
|
item.keyValues = JSON.parse(JSON.stringify(currentItem.value?.keyValues))
|
||||||
@ -174,15 +180,14 @@ watch(
|
|||||||
item.keyValuesDelete = []
|
item.keyValuesDelete = []
|
||||||
item.originalDetails = JSON.stringify(currentItem.value?.details)
|
item.originalDetails = JSON.stringify(currentItem.value?.details)
|
||||||
item.originalKeyValues = JSON.stringify(currentItem.value?.keyValues)
|
item.originalKeyValues = JSON.stringify(currentItem.value?.keyValues)
|
||||||
ignoreChanges.value = false
|
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
const { add } = useSnackbar()
|
const { add } = useSnackbar()
|
||||||
const { deleteAsync, updateAsync } = usePasswordItemStore()
|
const { deleteAsync, updateAsync } = usePasswordItemStore()
|
||||||
const { syncGroupItemsAsync, trashId } = usePasswordGroupStore()
|
const { syncGroupItemsAsync } = usePasswordGroupStore()
|
||||||
const { currentGroupId } = storeToRefs(usePasswordGroupStore())
|
const { currentGroupId, inTrashGroup } = storeToRefs(usePasswordGroupStore())
|
||||||
|
|
||||||
const onUpdateAsync = async () => {
|
const onUpdateAsync = async () => {
|
||||||
try {
|
try {
|
||||||
@ -195,31 +200,29 @@ const onUpdateAsync = async () => {
|
|||||||
})
|
})
|
||||||
if (newId) add({ type: 'success', text: t('success.update') })
|
if (newId) add({ type: 'success', text: t('success.update') })
|
||||||
syncGroupItemsAsync(currentGroupId.value)
|
syncGroupItemsAsync(currentGroupId.value)
|
||||||
ignoreChanges.value = true
|
onClose(true)
|
||||||
onClose()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
add({ type: 'error', text: t('error.update') })
|
add({ type: 'error', text: t('error.update') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = (ignoreChanges?: boolean) => {
|
||||||
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
|
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
|
||||||
|
|
||||||
if (hasChanges.value && !ignoreChanges.value)
|
if (hasChanges.value && !ignoreChanges)
|
||||||
return (showUnsavedChangesDialog.value = true)
|
return (showUnsavedChangesDialog.value = true)
|
||||||
|
|
||||||
ignoreChanges.value = false
|
|
||||||
read_only.value = true
|
read_only.value = true
|
||||||
useRouter().back()
|
useRouter().back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteItemAsync = async () => {
|
const deleteItemAsync = async () => {
|
||||||
try {
|
try {
|
||||||
await deleteAsync(item.details.id, currentGroupId.value === trashId)
|
await deleteAsync(item.details.id, inTrashGroup.value)
|
||||||
showConfirmDeleteDialog.value = false
|
showConfirmDeleteDialog.value = false
|
||||||
add({ type: 'success', text: t('success.delete') })
|
add({ type: 'success', text: t('success.delete') })
|
||||||
syncGroupItemsAsync(currentGroupId.value)
|
await syncGroupItemsAsync(currentGroupId.value)
|
||||||
onClose()
|
onClose(true)
|
||||||
} catch (errro) {
|
} catch (errro) {
|
||||||
add({
|
add({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
@ -239,22 +242,14 @@ const hasChanges = computed(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const showUnsavedChangesDialog = ref(false)
|
const showUnsavedChangesDialog = ref(false)
|
||||||
const ignoreChanges = ref(false)
|
|
||||||
|
|
||||||
const onConfirmIgnoreChanges = () => {
|
const onConfirmIgnoreChanges = () => {
|
||||||
ignoreChanges.value = true
|
|
||||||
showUnsavedChangesDialog.value = false
|
showUnsavedChangesDialog.value = false
|
||||||
onClose()
|
onClose(true)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i18n lang="yaml">
|
<i18n lang="yaml">
|
||||||
de:
|
de:
|
||||||
save: Speichern
|
|
||||||
abort: Abbrechen
|
|
||||||
edit: Bearbeiten
|
|
||||||
noEdit: Lesemodus
|
|
||||||
delete: Löschen
|
|
||||||
success:
|
success:
|
||||||
update: Eintrag erfolgreich aktualisiert
|
update: Eintrag erfolgreich aktualisiert
|
||||||
delete: Eintrag wurde gelöscht
|
delete: Eintrag wurde gelöscht
|
||||||
@ -266,21 +261,7 @@ de:
|
|||||||
keyValue: Extra
|
keyValue: Extra
|
||||||
history: Verlauf
|
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:
|
en:
|
||||||
save: Save
|
|
||||||
abort: Abort
|
|
||||||
edit: Edit
|
|
||||||
noEdit: Read Mode
|
|
||||||
delete: Delete
|
|
||||||
success:
|
success:
|
||||||
update: Entry successfully updated
|
update: Entry successfully updated
|
||||||
delete: Entry successfully removed
|
delete: Entry successfully removed
|
||||||
@ -291,14 +272,4 @@ en:
|
|||||||
details: Details
|
details: Details
|
||||||
keyValue: Extra
|
keyValue: Extra
|
||||||
history: History
|
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>
|
</i18n>
|
||||||
|
|||||||
@ -9,7 +9,15 @@
|
|||||||
v-model:key-values-add="item.keyValuesAdd"
|
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="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end"
|
||||||
:class="[isVisible ? 'left-15 ' : 'left-0']"
|
:class="[isVisible ? 'left-15 ' : 'left-0']"
|
||||||
>
|
>
|
||||||
@ -35,7 +43,7 @@
|
|||||||
</UiButton>
|
</UiButton>
|
||||||
</UiTooltip>
|
</UiTooltip>
|
||||||
<div class="flex items-center justify-center flex-1"></div>
|
<div class="flex items-center justify-center flex-1"></div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
haexPasswordsItemKeyValues,
|
haexPasswordsItemKeyValues,
|
||||||
type InsertHaexPasswordsItemDetails,
|
type InsertHaexPasswordsItemDetails,
|
||||||
type InserthaexPasswordsItemKeyValues,
|
type InserthaexPasswordsItemKeyValues,
|
||||||
|
type SelectHaexPasswordsGroupItems,
|
||||||
type SelectHaexPasswordsGroups,
|
type SelectHaexPasswordsGroups,
|
||||||
type SelectHaexPasswordsItemDetails,
|
type SelectHaexPasswordsItemDetails,
|
||||||
type SelectHaexPasswordsItemKeyValues,
|
type SelectHaexPasswordsItemKeyValues,
|
||||||
@ -23,6 +24,25 @@ export const usePasswordItemStore = defineStore('passwordItemStore', () => {
|
|||||||
|
|
||||||
const currentItem = computedAsync(() => readAsync(currentItemId.value))
|
const currentItem = computedAsync(() => readAsync(currentItemId.value))
|
||||||
|
|
||||||
|
const items = ref<
|
||||||
|
{
|
||||||
|
haex_passwords_item_details: SelectHaexPasswordsItemDetails
|
||||||
|
haex_passwords_group_items: SelectHaexPasswordsGroupItems
|
||||||
|
}[]
|
||||||
|
>([])
|
||||||
|
|
||||||
|
const syncItemsAsync = async () => {
|
||||||
|
const { currentVault } = useVaultStore()
|
||||||
|
|
||||||
|
items.value = await currentVault.drizzle
|
||||||
|
.select()
|
||||||
|
.from(haexPasswordsItemDetails)
|
||||||
|
.innerJoin(
|
||||||
|
haexPasswordsGroupItems,
|
||||||
|
eq(haexPasswordsItemDetails.id, haexPasswordsGroupItems.itemId),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentItemId,
|
currentItemId,
|
||||||
currentItem,
|
currentItem,
|
||||||
@ -31,9 +51,11 @@ export const usePasswordItemStore = defineStore('passwordItemStore', () => {
|
|||||||
addKeyValuesAsync,
|
addKeyValuesAsync,
|
||||||
deleteAsync,
|
deleteAsync,
|
||||||
deleteKeyValueAsync,
|
deleteKeyValueAsync,
|
||||||
|
items,
|
||||||
readByGroupIdAsync,
|
readByGroupIdAsync,
|
||||||
readAsync,
|
readAsync,
|
||||||
readKeyValuesAsync,
|
readKeyValuesAsync,
|
||||||
|
syncItemsAsync,
|
||||||
updateAsync,
|
updateAsync,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
7
src/stores/vault/search.ts
Normal file
7
src/stores/vault/search.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const useSearchStore = defineStore('searchStore', () => {
|
||||||
|
const search = ref()
|
||||||
|
|
||||||
|
return {
|
||||||
|
search,
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user