fix: debouncing sync and other improvements

This commit is contained in:
2026-08-22 22:58:20 +02:00
parent e27e7a679e
commit c6e45cd067
17 changed files with 350 additions and 207 deletions
+6
View File
@@ -29,6 +29,7 @@ async function handleLogout() {
<span v-if="listsStore.pendingCount > 0" class="pending">
{{ listsStore.pendingCount }} pending
</span>
<span v-if="listsStore.error" class="sync-error" :title="listsStore.error"> sync error</span>
<button v-if="authStore.isAuthenticated" type="button" class="logout" @click="handleLogout">
Log out
</button>
@@ -85,6 +86,11 @@ async function handleLogout() {
background-color: var(--c-text-soft);
}
.sync-error {
color: var(--c-danger);
cursor: help;
}
.logout {
background: none;
border: 1px solid var(--c-border);
+4 -16
View File
@@ -1,29 +1,17 @@
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import type { LocalList } from '@/database/db'
import { useEscapeKey } from '@/composables/useEscapeKey'
defineProps<{
list: LocalList
}>()
const emit = defineEmits<{
(e: 'close'): void
(e: 'confirm'): void
close: []
confirm: []
}>()
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
})
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
handleClose()
}
}
useEscapeKey(handleClose)
function handleClose() {
emit('close')
+25 -28
View File
@@ -1,24 +1,42 @@
<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { computed, ref } from 'vue'
import { RouterLink } from 'vue-router'
import type { LocalList } from '@/database/db'
import { useListsStore } from '@/stores/lists'
import { useClickOutside } from '@/composables/useClickOutside'
import { useEscapeKey } from '@/composables/useEscapeKey'
const props = defineProps<{ list: LocalList }>()
const emit = defineEmits<{
(e: 'share', list: LocalList): void
(e: 'delete', list: LocalList): void
share: [list: LocalList]
delete: [list: LocalList]
}>()
const listsStore = useListsStore()
const isMenuOpen = ref(false)
const menuContainerRef = ref<HTMLElement | null>(null)
const items = computed(() => listsStore.itemsForList(props.list.id))
const totalCount = computed(() => props.list.total_items ?? items.value.length)
const completedCount = computed(
() => props.list.completed_items ?? items.value.filter((item) => item.is_completed).length,
// The server always reports total_items/completed_items once a list has
// synced at least once, so the common case never touches the store's full
// item list at all - only a list that hasn't synced yet falls back to
// scanning its own items.
const totalCount = computed(() =>
props.list.total_items !== undefined
? props.list.total_items
: listsStore.itemsForList(props.list.id).length,
)
const completedCount = computed(() =>
props.list.completed_items !== undefined
? props.list.completed_items
: listsStore.itemsForList(props.list.id).filter((item) => item.is_completed).length,
)
useClickOutside(menuContainerRef, () => {
isMenuOpen.value = false
})
useEscapeKey(() => {
if (isMenuOpen.value) isMenuOpen.value = false
})
function toggleMenu(event: Event) {
event.preventDefault()
@@ -40,27 +58,6 @@ function handleDelete(event: Event) {
emit('delete', props.list)
}
function handleClickOutside(event: MouseEvent) {
if (menuContainerRef.value && !menuContainerRef.value.contains(event.target as Node)) {
isMenuOpen.value = false
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && isMenuOpen.value) {
isMenuOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
document.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
document.removeEventListener('keydown', handleKeydown)
})
</script>
<template>
+11 -23
View File
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref } from 'vue'
import type { LocalListItem } from '@/database/db'
import { useListsStore } from '@/stores/lists'
import { useClickOutside } from '@/composables/useClickOutside'
import { useEscapeKey } from '@/composables/useEscapeKey'
const props = defineProps<{ item: LocalListItem }>()
const emit = defineEmits<{
(e: 'delete', item: LocalListItem): void
delete: [item: LocalListItem]
}>()
const listsStore = useListsStore()
@@ -14,6 +16,13 @@ const editedTitle = ref(props.item.title)
const isMenuOpen = ref(false)
const menuContainerRef = ref<HTMLElement | null>(null)
useClickOutside(menuContainerRef, () => {
isMenuOpen.value = false
})
useEscapeKey(() => {
if (isMenuOpen.value) isMenuOpen.value = false
})
function toggleCompleted() {
listsStore.setListItemCompleted(props.item.id, !props.item.is_completed)
}
@@ -45,27 +54,6 @@ function handleDelete(event: Event) {
listsStore.deleteListItem(props.item.id)
}
function handleClickOutside(event: MouseEvent) {
if (menuContainerRef.value && !menuContainerRef.value.contains(event.target as Node)) {
isMenuOpen.value = false
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && isMenuOpen.value) {
isMenuOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
document.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
document.removeEventListener('keydown', handleKeydown)
})
</script>
<template>
+5 -13
View File
@@ -1,14 +1,15 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ref, onMounted, nextTick } from 'vue'
import type { LocalList } from '@/database/db'
import { useListsStore } from '@/stores/lists'
import { useEscapeKey } from '@/composables/useEscapeKey'
const props = defineProps<{
list: LocalList
}>()
const emit = defineEmits<{
(e: 'close'): void
close: []
}>()
const listsStore = useListsStore()
@@ -19,23 +20,14 @@ const isSubmitting = ref(false)
const error = ref('')
const successMessage = ref('')
useEscapeKey(handleClose)
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
nextTick(() => {
emailInput.value?.focus()
})
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
})
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
handleClose()
}
}
function handleClose() {
emit('close')
}