feat: improve memo editing workflow
This commit is contained in:
+20
-8
@@ -12,6 +12,7 @@ import { createCompletionPulse, shouldAnimateCompletionExit, waitForCompletionEx
|
||||
import { captureListDragPointer, getAdjacentListMove, hasExceededLongPressMovement, moveListToScope, snapshotListDragPointer, type ListDragPointer } from './lib/list-drag'
|
||||
import { clampSearchPullDistance, isAtSearchPullOrigin, isSearchShortcut, shouldHideSearchAfterSwipe, shouldRevealSearchAfterPull } from './lib/mobile-search'
|
||||
import { nextDialogFocusIndex } from './lib/list-purge'
|
||||
import { deriveMemoShellState } from './lib/app-shell-state'
|
||||
import { positionArchivedMenu, resolveArchivedMenuFocusTarget } from './lib/archived-list-menu'
|
||||
import MvpPanel from './MvpPanel.vue'
|
||||
import CountdownPanel from './CountdownPanel.vue'
|
||||
@@ -155,6 +156,11 @@ const habitComposer = ref<InstanceType<typeof MvpPanel> | null>(null)
|
||||
const countdownComposer = ref<InstanceType<typeof CountdownPanel> | null>(null)
|
||||
const memoPanel = ref<InstanceType<typeof MemoPanel> | null>(null)
|
||||
const memoTrash = ref(false)
|
||||
const memoDetailOpen = ref(false)
|
||||
const compactLayout = ref(window.innerWidth <= 930)
|
||||
const memoShellState = computed(() => deriveMemoShellState({ view: activeView.value, detailOpen: memoDetailOpen.value, compact: compactLayout.value, trash: memoTrash.value }))
|
||||
const memoBackgroundInert = computed(() => memoShellState.value.backgroundInert)
|
||||
const showFloatingAdd = computed(() => memoShellState.value.showFab)
|
||||
const composeOrigin = ref({ x: window.innerWidth - 43, y: window.innerHeight - 104 })
|
||||
let suppressTaskClickId = ''
|
||||
|
||||
@@ -618,6 +624,7 @@ async function switchView(view: View, listId?: string) {
|
||||
writeStoredNavigation(window.localStorage, NAVIGATION_STORAGE_KEY, view, activeList.value)
|
||||
page.value = 1
|
||||
selectedTask.value = null; mobileSidebar.value = false; mobileDetail.value = false; mobileMore.value = false; taskComposeOpen.value = false; sidebarCreateOpen.value = false; sidebarAction.value = null
|
||||
if (view !== 'memos') memoDetailOpen.value = false
|
||||
if (view === 'trash') await loadTrash()
|
||||
else if (view === 'today') await loadTodayView()
|
||||
else if (!isTaskView(view)) tasks.value = []
|
||||
@@ -1306,11 +1313,16 @@ function handleSearchBlur() {
|
||||
if (!query.value.trim()) mobileSearchOpen.value = false
|
||||
}
|
||||
|
||||
function handleViewportResize() {
|
||||
compactLayout.value = window.innerWidth <= 930
|
||||
handleArchivedListViewportChange()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('pointerdown', handleArchivedListOutsidePointer)
|
||||
document.addEventListener('keydown', handleArchivedListEscape)
|
||||
document.addEventListener('keydown', handleTaskSearchShortcut)
|
||||
window.addEventListener('resize', handleArchivedListViewportChange)
|
||||
window.addEventListener('resize', handleViewportResize)
|
||||
document.addEventListener('scroll', handleArchivedListViewportChange, true)
|
||||
void bootstrap()
|
||||
})
|
||||
@@ -1318,7 +1330,7 @@ onUnmounted(() => {
|
||||
document.removeEventListener('pointerdown', handleArchivedListOutsidePointer)
|
||||
document.removeEventListener('keydown', handleArchivedListEscape)
|
||||
document.removeEventListener('keydown', handleTaskSearchShortcut)
|
||||
window.removeEventListener('resize', handleArchivedListViewportChange)
|
||||
window.removeEventListener('resize', handleViewportResize)
|
||||
document.removeEventListener('scroll', handleArchivedListViewportChange, true)
|
||||
})
|
||||
</script>
|
||||
@@ -1334,9 +1346,9 @@ onUnmounted(() => {
|
||||
<small v-if="error" role="alert">{{ error }}</small>
|
||||
</section>
|
||||
</div>
|
||||
<div v-else class="shell" :class="{ 'sidebar-collapsed': sidebarCollapsed, 'detail-open': Boolean(selectedTask), 'mobile-sidebar-open': mobileSidebar }">
|
||||
<div v-else class="shell" :class="{ 'sidebar-collapsed': sidebarCollapsed, 'detail-open': Boolean(selectedTask), 'memo-detail-open': activeView==='memos' && memoDetailOpen, 'mobile-sidebar-open': mobileSidebar }">
|
||||
<div v-if="mobileSidebar || mobileDetail" class="scrim" @click="mobileSidebar=false;mobileDetail=false" />
|
||||
<aside class="sidebar" :class="{ open: mobileSidebar }" @keydown.esc="sidebarCreateOpen=false;closeArchivedListAction();closeSidebarAction()">
|
||||
<aside class="sidebar" :inert="memoBackgroundInert ? true : undefined" :class="{ open: mobileSidebar }" @keydown.esc="sidebarCreateOpen=false;closeArchivedListAction();closeSidebarAction()">
|
||||
<div class="brand-row"><div class="brand small brand-lockup"><img class="brand-logo" src="/dodo-logo.svg" alt=""><span class="brand-wordmark">dodo</span></div><button class="icon mobile-only" aria-label="关闭菜单" @click="mobileSidebar=false"><X /></button></div>
|
||||
<nav class="primary-nav">
|
||||
<button :class="{ active: activeView==='today' }" @click="switchView('today')"><ListTodo />今天</button>
|
||||
@@ -1407,7 +1419,7 @@ onUnmounted(() => {
|
||||
</aside>
|
||||
|
||||
<main @touchstart="startSearchPull" @touchmove="moveSearchPull" @touchend="finishSearchPull" @touchcancel="cancelSearchPull">
|
||||
<header class="topbar">
|
||||
<header class="topbar" :inert="memoBackgroundInert ? true : undefined">
|
||||
<button class="icon" :aria-label="(sidebarCollapsed ? '展开' : '收起') + '菜单'" :aria-expanded="sidebarCollapsed ? 'false' : 'true'" @click="toggleSidebar"><Menu /></button>
|
||||
<div class="topbar-title"><h1 :title="activeName">{{ activeName }}</h1></div>
|
||||
<div v-if="['today', 'tasks', 'upcoming', 'habits'].includes(activeView)" class="topbar-actions">
|
||||
@@ -1422,7 +1434,7 @@ onUnmounted(() => {
|
||||
<template v-if="['habits','settings'].includes(activeView)">
|
||||
<MvpPanel ref="habitComposer" :key="activeView" :view="activeView as 'habits'|'settings'" :show-completed="showCompleted" @changed="refreshAll" @notice="toast" />
|
||||
</template>
|
||||
<MemoPanel v-else-if="activeView==='memos'" ref="memoPanel" :request="api" @scope="memoTrash=$event==='trash'" @notice="toast" />
|
||||
<MemoPanel v-else-if="activeView==='memos'" ref="memoPanel" :request="api" @scope="memoTrash=$event==='trash'" @detail="memoDetailOpen=$event" @notice="toast" />
|
||||
<CountdownPanel ref="countdownComposer" v-else-if="activeView==='countdowns'" @notice="toast" />
|
||||
<template v-else>
|
||||
<section v-if="activeView==='today'" class="today-board" aria-label="今日进度">
|
||||
@@ -1505,8 +1517,8 @@ onUnmounted(() => {
|
||||
</aside>
|
||||
|
||||
<div v-if="mobileMore" class="more-mask app-sheet-mask" @click.self="mobileMore=false;switchView('settings')"><section id="mobile-more-menu" class="more-sheet app-sheet app-sheet--actions" role="dialog" aria-modal="true" aria-label="更多导航" @click.stop><div class="more-sheet-head app-sheet__header"><b>更多</b><button class="icon" aria-label="关闭更多菜单" @click="mobileMore=false"><X/></button></div><div class="app-sheet__body"><button @click="switchView('settings')"><Settings/>设置与数据</button></div></section></div>
|
||||
<nav class="bottom" aria-label="主要导航"><button :class="{active:activeView==='today'}" :aria-current="activeView==='today' ? 'page' : undefined" @click="switchView('today')"><ListTodo/><span>今天</span></button><button :class="{active:activeView==='habits'}" :aria-current="activeView==='habits' ? 'page' : undefined" @click="switchView('habits')"><Repeat2/><span>习惯</span></button><button :class="{active:activeView==='countdowns'}" :aria-current="activeView==='countdowns' ? 'page' : undefined" @click="switchView('countdowns')"><CalendarHeart/><span>倒数日</span></button><button :class="{active:activeView==='settings'}" :aria-current="activeView==='settings' ? 'page' : undefined" @click="switchView('settings')"><Settings/><span>设置</span></button></nav>
|
||||
<FloatingAddButton v-if="['tasks','today','upcoming','habits','countdowns','memos'].includes(activeView)" :show="activeView!=='memos' || !memoTrash" :label="activeView==='habits' ? '添加习惯' : activeView==='countdowns' ? '添加倒数日' : activeView==='memos' ? '添加备忘录' : '添加任务'" @activate="activateFloatingAdd" />
|
||||
<nav class="bottom" :inert="memoBackgroundInert ? true : undefined" aria-label="主要导航"><button :class="{active:activeView==='today'}" :aria-current="activeView==='today' ? 'page' : undefined" @click="switchView('today')"><ListTodo/><span>今天</span></button><button :class="{active:activeView==='habits'}" :aria-current="activeView==='habits' ? 'page' : undefined" @click="switchView('habits')"><Repeat2/><span>习惯</span></button><button :class="{active:activeView==='countdowns'}" :aria-current="activeView==='countdowns' ? 'page' : undefined" @click="switchView('countdowns')"><CalendarHeart/><span>倒数日</span></button><button :class="{active:activeView==='settings'}" :aria-current="activeView==='settings' ? 'page' : undefined" @click="switchView('settings')"><Settings/><span>设置</span></button></nav>
|
||||
<FloatingAddButton v-if="['tasks','today','upcoming','habits','countdowns','memos'].includes(activeView)" :show="showFloatingAdd" :label="activeView==='habits' ? '添加习惯' : activeView==='countdowns' ? '添加倒数日' : activeView==='memos' ? '添加备忘录' : '添加任务'" @activate="activateFloatingAdd" />
|
||||
<Transition name="task-compose">
|
||||
<div v-if="taskComposeOpen" class="task-compose-mask app-sheet-mask" @click.self="closeTaskCompose">
|
||||
<form class="task-compose-sheet app-sheet app-sheet--create" :style="taskComposeStyle" role="dialog" aria-modal="true" aria-labelledby="task-compose-title" @submit.prevent="submitTaskCompose">
|
||||
|
||||
Reference in New Issue
Block a user