@@ -369,7 +369,6 @@ function updateTodayHabitSummary(value: { total: number; completed: number }) {
const sourceTasks = computed ( ( ) => activeView . value === 'trash' ? trash . value : tasks . value )
const visibleTasks = computed ( ( ) => {
const now = new Date ( )
const end = new Date ( now ) ; end . setDate ( end . getDate ( ) + 7 )
let result = sourceTasks . value
if ( [ 'habits' , 'settings' , 'countdowns' , 'memos' ] . includes ( activeView . value ) ) return [ ]
if ( activeView . value === 'today' ) result = result . filter ( ( task ) => {
@@ -377,7 +376,7 @@ const visibleTasks = computed(() => {
const completedToday = task . completed _at && new Date ( task . completed _at ) . toDateString ( ) === now . toDateString ( )
return Boolean ( dueToday || completedToday )
} )
if ( activeView . value === 'upcoming' ) result = result . filter ( ( task ) => task . due _at && new Date ( task . due _at ) >= now && new Date ( task . due _at ) <= end )
if ( activeView . value === 'upcoming' ) result = result . filter ( ( task ) => task . due _at && new Date ( task . due _at ) >= startOfLocalDay ( 0 ) && new Date ( task . due _at ) < startOfLocalDay ( 8 ) )
return query . value . trim ( ) ? filterTasks ( result , query . value ) : result
} )
const selectedTaskSubtasks = computed ( ( ) => selectedTask . value ? . subtasks ? ? [ ] )
@@ -505,11 +504,14 @@ async function loadTaskPages(path: string) {
} while ( cursor )
return rows
}
function isoAtLocalDayOffset ( offset : number ) {
function startOfLocalDay ( offset : number ) {
const day = new Date ( )
day . setDate ( day . getDate ( ) + offset )
day . setHours ( 0 , 0 , 0 , 0 )
return day . toISOString ( )
return day
}
function isoAtLocalDayOffset ( offset : number ) {
return startOfLocalDay ( offset ) . toISOString ( )
}
async function loadTodayTaskSummary ( ) {
const token = ++ todaySummaryLoadToken
@@ -585,15 +587,18 @@ async function loadTasksPage(request = beginLatestRequest('tasks')) {
if ( ! isLatestRequest ( 'tasks' , request ) ) return
tasks . value = data . items ? ? [ ]
totalTasks . value = data . total ? ? tasks . value . length
if ( activeView . value === 'upcoming' && ! showCompleted . value && ! query . value ) taskOpenTotal . value = totalTasks . value
if ( activeView . value === 'tasks' && ! showCompleted . value && ! query . value ) taskOpenTotal . value = totalTasks . value
if ( activeView . value === 'tasks' && ( showCompleted . value || query . value ) ) {
if ( ( activeView . value === 'tasks' || activeView . value === 'upcoming' ) && ( showCompleted . value || query . value ) ) {
const token = ++ taskOpenTotalLoadToken
const listId = activeList . value
const openParams = new URLSearchParams ( { page : '1' , page _size : '1' , completed : 'false' } )
if ( listId ) openParams . set ( 'list_id' , listId )
if ( activeView . value === 'tasks' && listId ) openParams . set ( 'list_id' , listId )
if ( activeView . value === 'upcoming' ) { openParams . set ( 'due_from' , isoAtLocalDayOffset ( 0 ) ) ; openParams . set ( 'due_to' , isoAtLocalDayOffset ( 8 ) ) }
const openView = activeView . value
loadTaskOpenTotal (
( ) => api ( ` /tasks? ${ openParams } ` ) ,
( ) => token === taskOpenTotalLoadToken && isLatestRequest ( 'tasks' , request ) && activeView . value === 'tasks' && activeList . value === listId ,
( ) => token === taskOpenTotalLoadToken && isLatestRequest ( 'tasks' , request ) && activeView . value === openView && ( openView !== 'tasks' || activeList . value === listId ) ,
value => { taskOpenTotal . value = value } ,
)
}
@@ -1685,16 +1690,15 @@ onUnmounted(() => {
</AppSheet>
</aside>
<main :class=" { 'today-main' : activeView === 'today' , 'list-main' : activeView === 'tasks' || activeView === 'habits' } " @touchstart=" startSearchPull " @touchmove=" moveSearchPull " @touchend=" finishSearchPull " @touchcancel=" cancelSearchPull ">
<main :class=" { 'today-main' : activeView === 'today' , 'list-main' : activeView === 'tasks' || activeView === 'upcoming' || activeView === ' habits'} " @touchstart=" startSearchPull " @touchmove=" moveSearchPull " @touchend=" finishSearchPull " @touchcancel=" cancelSearchPull ">
<header class=" topbar " :class=" { 'settings-topbar' : activeView === 'settings' } " :inert=" memoBackgroundInert ? true : undefined ">
<button class=" icon " :aria-label=" ( sidebarCollapsed ? '展开' : '收起' ) + '菜单' " :aria-expanded=" sidebarCollapsed ? 'false' : 'true' " @click=" toggleSidebar "><Menu /></button>
<div v-if=" ! [ 'today' , 'tasks' , 'habits' , 'settings' ] . includes ( activeView ) " class=" topbar - title "><h1 :title=" activeName ">{{ activeName }}</h1></div>
<div v-if=" ! [ 'today' , 'tasks' , 'upcoming' , ' habits', 'settings' ] . includes ( activeView ) " class=" topbar - title "><h1 :title=" activeName ">{{ activeName }}</h1></div>
<div v-if=" [ 'today' , 'tasks' , 'upcoming' , 'habits' ] . includes ( activeView ) " class=" topbar - actions ">
<CompletedFilterPill v-if=" activeView === 'upcoming' " v-model=" showCompleted " class=" topbar - filter " />
<button class=" icon topbar - refresh " :class=" { spinning : refreshing } " type=" button " aria-label=" 刷新当前页面 " title=" 刷新 " :disabled=" refreshing || loading " @click=" refreshCurrentView "><RefreshCw /></button>
</div>
<button v-if=" activeView === 'settings' " class=" icon topbar - refresh settings - refresh " :class=" { spinning : refreshing } " type=" button " aria-label=" 刷新设置 " title=" 刷新 " :disabled=" refreshing || loading " @click=" refreshCurrentView "><RefreshCw /></button>
<div v-if=" taskSearchAvailable && activeView !== 'tasks' " class=" search - reveal " :class=" { 'mobile-search-open' : mobileSearchOpen , 'mobile-search-pulling' : searchPullDistance > 0 } " :style=" searchRevealStyle ">
<div v-if=" taskSearchAvailable && ! [ 'tasks' , 'upcoming' ] . includes ( activeView ) " class=" search - reveal " :class=" { 'mobile-search-open' : mobileSearchOpen , 'mobile-search-pulling' : searchPullDistance > 0 } " :style=" searchRevealStyle ">
<button ref=" taskSearchToggle " class=" task - search - toggle " type=" button " :aria-label=" mobileSearchOpen ? '收起搜索任务' : '展开搜索任务' " :aria-expanded=" mobileSearchOpen " aria-controls=" task - search - panel " @click=" toggleTaskSearch "><Search/></button>
<span class=" search - pull - hint " aria-hidden=" true ">{{searchPullDistance >= 56 ? '松开搜索' : '下拉搜索'}}</span>
<label id=" task - search - panel " class=" search "><Search/><input ref=" searchInput " v-model=" query " placeholder=" 搜索任务 … " aria-label=" 搜索任务 " @keydown=" handleTaskSearchKeydown "><kbd>⌘ K</kbd></label>
@@ -1713,11 +1717,11 @@ onUnmounted(() => {
<CompletedFilterPill v-model=" showCompleted " class=" today - inline - filter " />
</div>
</section>
<section v-if=" activeView === 'tasks' " class=" list - page - context ">
<section v-if=" activeView === 'tasks' || activeView === 'upcoming' " class=" list - page - context ">
<div><h1 class=" list - page - title " :title=" activeName ">{{ activeName }}</h1><p class=" list - page - summary ">{{ taskOpenTotal === null ? '待完成统计暂不可用' : `还有 ${taskOpenTotal} 项待完成` }}</p></div>
<CompletedFilterPill v-model=" showCompleted " class=" list - inline - filter " />
</section>
<div v-if=" activeView === 'tasks' " class=" list - search - reveal " :class=" { 'mobile-search-open' : mobileSearchOpen } ">
<div v-if=" activeView === 'tasks' || activeView === 'upcoming' " class=" list - search - reveal " :class=" { 'mobile-search-open' : mobileSearchOpen } ">
<label id=" task - list - search - panel " class=" list - search "><Search/><input ref=" searchInput " v-model=" query " placeholder=" 搜索任务 … " aria-label=" 搜索任务 " @keydown=" handleTaskSearchKeydown "><kbd>⌘ K</kbd></label>
<button v-if=" query " class=" list - search - clear " type=" button " @click=" query = '' ">清除搜索</button>
<button ref=" taskSearchToggle " class=" task - search - toggle " type=" button " :aria-label=" mobileSearchOpen ? '收起搜索任务' : '展开搜索任务' " :aria-expanded=" mobileSearchOpen " aria-controls=" task - list - search - panel " @click=" toggleTaskSearch "><Search/></button>
@@ -1733,11 +1737,11 @@ onUnmounted(() => {
</section>
<button id=" today - tasks - heading " class=" today - section - toggle today - section - anchor " type=" button " :aria-expanded=" ! todaySectionCollapse . tasks " aria-controls=" today - tasks " @click=" toggleTodaySection ( 'tasks' ) "><span class=" today - section - title ">今天</span><span class=" today - section - summary ">{{totalTasks}}</span><span class=" today - section - chevron " aria-hidden=" true ">{{ todaySectionCollapse.tasks ? '› ' : '⌄' }}</span></button>
</template>
<div v-if=" activeView === 'tasks' " id=" task - list - heading " class=" list - section - heading "><span id=" task - list - title " class=" list - section - title ">任务</span><span class=" list - section - count ">{{ totalTasks }}</span><button v-if=" taskReorderAvailable " class=" list - section - action " type=" button " :aria-pressed=" taskReorderMode " @click=" taskReorderMode = ! taskReorderMode ; cancelTaskReorder ( ) ">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></div>
<div v-if=" activeView ! =='today' && activeView != ='tasks ' " class=" list - toolbar "><span v-if=" activeView === 'trash' || totalPages > 1 || totalTasks > 0 ">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><span class=" list - toolbar - actions "><button v-if=" query " class=" link " @click=" query = '' ">清除搜索</button><button v-if=" taskReorderAvailable " class=" soft - button reorder - mode - toggle task - reorder - toggle " type=" button " :aria-pressed=" taskReorderMode " @click=" taskReorderMode = ! taskReorderMode ; cancelTaskReorder ( ) ">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
<div v-if=" activeView === 'tasks' || activeView === 'upcoming' " id=" task - list - heading " class=" list - section - heading "><span id=" task - list - title " class=" list - section - title ">任务</span><span class=" list - section - count ">{{ totalTasks }}</span><button v-if=" activeView === 'tasks' && taskReorderAvailable " class=" list - section - action " type=" button " :aria-pressed=" taskReorderMode " @click=" taskReorderMode = ! taskReorderMode ; cancelTaskReorder ( ) ">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></div>
<div v-if=" activeView === 'tr ash ' " class=" list - toolbar "><span v-if=" activeView === 'trash' || totalPages > 1 || totalTasks > 0 ">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><span class=" list - toolbar - actions "><button v-if=" query " class=" link " @click=" query = '' ">清除搜索</button><button v-if=" taskReorderAvailable " class=" soft - button reorder - mode - toggle task - reorder - toggle " type=" button " :aria-pressed=" taskReorderMode " @click=" taskReorderMode = ! taskReorderMode ; cancelTaskReorder ( ) ">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
<div v-if=" activeView === 'tasks' && totalPages > 1 " class=" list - page - meta "><span>第 {{ page }} / {{ totalPages }} 页 · 共 {{ totalTasks }} 项</span></div>
<div v-if=" activeView !== 'trash' && totalPages > 1 " class=" pager "><button class=" secondary " :disabled=" page <= 1 || loading " @click=" previousPage ">上一页</button><span>{{page}} / {{totalPages}}</span><button class=" secondary " :disabled=" page >= totalPages || loading " @click=" nextPage ">下一页</button></div>
<section :id=" activeView === 'today' ? 'today-tasks' : undefined " class=" task - list plain - list " :class=" { loading } " v-show=" activeView !== 'today' || ! todaySectionCollapse . tasks " :role=" activeView === 'today' ? 'region' : undefined " :aria-labelledby=" activeView === 'today' ? 'today-tasks-heading' : activeView === 'tasks' ? 'task-list-title' : undefined ">
<section :id=" activeView === 'today' ? 'today-tasks' : undefined " class=" task - list plain - list " :class=" { loading } " v-show=" activeView !== 'today' || ! todaySectionCollapse . tasks " :role=" activeView === 'today' ? 'region' : undefined " :aria-labelledby=" activeView === 'today' ? 'today-tasks-heading' : ( activeView === 'tasks' || activeView === 'upcoming' ) ? 'task-list-title' : undefined ">
<template v-for=" node in taskTree " :key=" node . task . id ">
<article :data-task-id=" node . task . id " class=" task - row swipeable " :class=" { done : node . task . completed , 'task-row--trash' : activeView === 'trash' , 'just-completed' : justCompletedTaskIds . has ( node . task . id ) , 'completion-exiting' : completionExitingTaskIds . has ( node . task . id ) , selected : selectedTask ? . id === node . task . id , ready : Math . abs ( taskSwipeOffsets [ node . task . id ] ? ? 0 ) >= 64 , reordering : taskReorder ? . id === node . task . id , 'reorder-target' : taskReorderTarget === node . task . id } " :style=" { '--swipe-x' : ` ${ taskSwipeOffsets [ node . task . id ] ? ? 0 } px ` , '--reorder-y' : ` ${ taskReorder ? . id === node . task . id ? taskReorder . offsetY : 0 } px ` } " @pointerdown=" startTaskPointer ( node . task , $event ) " @pointermove=" moveTaskPointer ( node . task , $event ) " @pointerup=" finishTaskPointer ( node . task , $event ) " @pointercancel=" cancelTaskPointer ( node . task ) " @touchstart.passive=" startTaskSwipe ( node . task , $event ) " @touchmove.passive=" moveTaskSwipe ( node . task , $event ) " @touchend=" finishTaskSwipe ( node . task , $event ) " @touchcancel=" cancelTaskSwipe ( node . task ) ">
<button v-if=" taskReorderMode " class=" drag - handle task - drag - handle " :disabled=" Boolean ( query ) || totalPages > 1 " aria-label=" 上下拖动任务排序 " title=" 上下拖动排序 " @pointerdown.stop=" startTaskReorder ( node . task , $event ) " @pointermove.stop=" moveTaskReorder ( node . task , $event ) " @pointerup.stop=" finishTaskReorder ( node . task , $event ) " @pointercancel.stop=" cancelTaskReorder "><GripVertical/></button>