feat: add local pomodoro focus timer
Frontend-only 25m focus / 5m break timer with pause-resume-reset, deadline-based absolute timing, versioned localStorage persistence, cross-tab conflict protection, midnight-completion attribution, accessible stage controls, and reduced-motion support. Adds 专注 entry to desktop sidebar; mobile bottom nav unchanged; no notifications, task association, history, or cross-device sync. Also fixes mobile E2E task isolation and the stale calendar 390px overflow assertion.
This commit is contained in:
@@ -3,7 +3,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import {
|
||||
ArchiveRestore, Bold, CalendarDays, CalendarHeart, Check, ChevronDown, ChevronLeft, ChevronRight, Code, Folder,
|
||||
Ellipsis, GripVertical, Heading2, Inbox, Italic, Link, List, ListChecks, ListOrdered, ListTodo, Menu, Pencil, Plus, Quote,
|
||||
Settings, Trash2, X, Repeat2, StickyNote,
|
||||
Settings, Trash2, X, Repeat2, StickyNote, TimerReset,
|
||||
} from 'lucide-vue-next'
|
||||
import { applyMarkdownFormat, buildTaskDueDraft, buildTaskRecurrencePayload, defaultTaskDueAt, fromDateTimeLocal, groupTaskTree, groupTrashTaskTree, isSameTaskSortTier, moveItemWithinScope, parseTaskDueDraft, parseTaskRecurrence, parseTaskRrule, renderMarkdown, type AfterCompletionUnit, type MarkdownFormat, type TaskRepeatConfig, type TaskRepeatOption } from './lib/task-utils'
|
||||
import { beginLatestRequest, createMutationReconciler, createTaskCompletionExitCoordinator, createTaskToggleCoordinator, formatApiErrorDetail, isLatestRequest, isTaskView, loadCountdownCache, mergeTaskToggleResponse, normalizeRequiredName, readStoredBoolean, readStoredNavigation, reconcileCurrentTaskView, runLatestRequest, shouldToggleRowSwipe, startPrimaryWithBackground, taskVersionedPatchPayload, writeStoredBoolean, writeStoredNavigation } from './lib/mvp-utils'
|
||||
@@ -17,6 +17,7 @@ import MvpPanel from './MvpPanel.vue'
|
||||
import CountdownPanel from './CountdownPanel.vue'
|
||||
import MemoPanel from './MemoPanel.vue'
|
||||
import CalendarPanel from './CalendarPanel.vue'
|
||||
import PomodoroPanel from './PomodoroPanel.vue'
|
||||
import FloatingAddButton from './components/FloatingAddButton.vue'
|
||||
import CompletedFilterPill from './components/CompletedFilterPill.vue'
|
||||
import CalendarPicker from './components/CalendarPicker.vue'
|
||||
@@ -33,7 +34,7 @@ type TaskList = { id: string; folder_id: string | null; name: string; is_inbox:
|
||||
type Task = { id: string; list_id: string; parent_id: string | null; title: string; description: string; priority: number; completed: boolean; completed_at: string | null; version: number; due_at: string | null; due_has_time: boolean; subtasks?: Task[] }
|
||||
type RepeatOption = TaskRepeatOption
|
||||
type Recurrence = { id: string; task_id: string; rrule: string | null; trigger_mode: 'scheduled' | 'after_completion'; after_completion_days: number | null; after_completion_unit: AfterCompletionUnit | null }
|
||||
type View = 'tasks' | 'today' | 'upcoming' | 'trash' | 'habits' | 'countdowns' | 'memos' | 'calendar' | 'settings'
|
||||
type View = 'tasks' | 'today' | 'upcoming' | 'trash' | 'habits' | 'focus' | 'countdowns' | 'memos' | 'calendar' | 'settings'
|
||||
|
||||
const initialized = ref<boolean | null>(null)
|
||||
const authReady = ref(false)
|
||||
@@ -403,6 +404,7 @@ const activeName = computed(() => {
|
||||
if (activeView.value === 'today') return '今天'
|
||||
if (activeView.value === 'upcoming') return '最近 7 天'
|
||||
if (activeView.value === 'habits') return '习惯'
|
||||
if (activeView.value === 'focus') return '专注'
|
||||
if (activeView.value === 'countdowns') return '倒数日'
|
||||
if (activeView.value === 'memos') return '备忘录'
|
||||
if (activeView.value === 'calendar') return '日历订阅'
|
||||
@@ -426,7 +428,7 @@ const sourceTasks = computed(() => activeView.value === 'trash' ? trash.value :
|
||||
const visibleTasks = computed(() => {
|
||||
const now = new Date()
|
||||
let result = sourceTasks.value
|
||||
if (['habits','settings','countdowns','memos','calendar'].includes(activeView.value)) return []
|
||||
if (['habits','settings','focus','countdowns','memos','calendar'].includes(activeView.value)) return []
|
||||
if (activeView.value === 'today') result = result.filter((task) => {
|
||||
const dueToday = task.due_at && new Date(task.due_at).toDateString() === now.toDateString()
|
||||
const completedToday = task.completed_at && new Date(task.completed_at).toDateString() === now.toDateString()
|
||||
@@ -1735,6 +1737,7 @@ onUnmounted(() => {
|
||||
<button :class="{ active: activeView==='tasks' && lists.find(l=>l.id===activeList)?.is_inbox }" @click="switchView('tasks', lists.find(l=>l.is_inbox)?.id)"><Inbox />收集箱</button>
|
||||
<button :class="{ active: activeView==='upcoming' }" @click="switchView('upcoming')"><CalendarDays />最近 7 天</button>
|
||||
<button :class="{ active: activeView==='habits' }" @click="switchView('habits')"><Repeat2 />习惯</button>
|
||||
<button :class="{ active: activeView==='focus' }" @click="switchView('focus')"><TimerReset />专注</button>
|
||||
<button :class="{ active: activeView==='countdowns' }" @click="switchView('countdowns')"><CalendarHeart />倒数日</button>
|
||||
<button :class="{ active: activeView==='memos' }" @click="switchView('memos')"><StickyNote />备忘录</button>
|
||||
<button :class="{ active: activeView==='calendar' }" @click="switchView('calendar')"><CalendarDays />日历订阅</button>
|
||||
@@ -1801,6 +1804,7 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<MemoPanel v-else-if="activeView==='memos'" ref="memoPanel" :request="api" @scope="memoTrash=$event==='trash'" @detail="memoDetailOpen=$event" @notice="toast" />
|
||||
<CalendarPanel v-else-if="activeView==='calendar'" :compact-layout="compactLayout" @detail="calendarDetailOpen=$event" @notice="toast" />
|
||||
<PomodoroPanel v-else-if="activeView==='focus'" />
|
||||
<CountdownPanel ref="countdownComposer" v-else-if="activeView==='countdowns'" @notice="toast" />
|
||||
<template v-else>
|
||||
<section v-if="activeView==='today'" class="today-context" aria-label="今日概览">
|
||||
|
||||
Reference in New Issue
Block a user