Refine calendar filters and manual refresh behavior
This commit is contained in:
parent
6c1c65d5c7
commit
5b244ff766
@ -3,7 +3,6 @@
|
|||||||
export let onSelectedDateChange: (payload: { year: number; month: number; day: number; key: string }) => void =
|
export let onSelectedDateChange: (payload: { year: number; month: number; day: number; key: string }) => void =
|
||||||
() => {};
|
() => {};
|
||||||
export let onDateActivate: (payload: { year: number; month: number; day: number; key: string }) => void = () => {};
|
export let onDateActivate: (payload: { year: number; month: number; day: number; key: string }) => void = () => {};
|
||||||
export let signalsByDate: Record<string, { count: number; hasTrigger: boolean; hasMood: boolean; hasOpenTodos: boolean }> = {};
|
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
let currentYear = today.getFullYear();
|
let currentYear = today.getFullYear();
|
||||||
@ -33,11 +32,6 @@
|
|||||||
currentMonth = next.getMonth();
|
currentMonth = next.getMonth();
|
||||||
}
|
}
|
||||||
|
|
||||||
function signalFor(cell: CalendarCell): { count: number; hasTrigger: boolean; hasMood: boolean; hasOpenTodos: boolean } | null {
|
|
||||||
const key = getDateKey(cell.year, cell.month, cell.day);
|
|
||||||
return signalsByDate[key] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeMonth(offset: number) {
|
function changeMonth(offset: number) {
|
||||||
setViewDate(currentYear, currentMonth + offset);
|
setViewDate(currentYear, currentMonth + offset);
|
||||||
}
|
}
|
||||||
@ -154,16 +148,6 @@
|
|||||||
on:click={() => selectCell(cell)}
|
on:click={() => selectCell(cell)}
|
||||||
>
|
>
|
||||||
<span class="day-number">{cell.day}</span>
|
<span class="day-number">{cell.day}</span>
|
||||||
{#if signalFor(cell)}
|
|
||||||
{#if signalFor(cell)!.count > 0}
|
|
||||||
<span class="entry-count">{signalFor(cell)!.count}</span>
|
|
||||||
{/if}
|
|
||||||
<span class="signals" aria-hidden="true">
|
|
||||||
{#if signalFor(cell)!.hasMood}<i class="signal mood"></i>{/if}
|
|
||||||
{#if signalFor(cell)!.hasTrigger}<i class="signal trigger"></i>{/if}
|
|
||||||
{#if signalFor(cell)!.hasOpenTodos}<i class="signal todo"></i>{/if}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@ -253,49 +237,6 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-count {
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
right: 3px;
|
|
||||||
min-width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
padding: 0 3px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--surface-3);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.58rem;
|
|
||||||
line-height: 12px;
|
|
||||||
border: 1px solid var(--border-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.signals {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 3px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
display: inline-flex;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal {
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 999px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal.mood {
|
|
||||||
background: #6ba7ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal.trigger {
|
|
||||||
background: #f08c6c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal.todo {
|
|
||||||
background: #f2c266;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell:hover {
|
.calendar-cell:hover {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
background: var(--bg-hover);
|
background: var(--bg-hover);
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { listEntryTemplates, type EntryTemplateItemDto } from "$lib/backend/templates";
|
import { listEntryTemplates, type EntryTemplateItemDto } from "$lib/backend/templates";
|
||||||
|
import { listFragments, type FragmentDto } from "$lib/backend/fragments";
|
||||||
|
import { listLists, type ListDocumentDto } from "$lib/backend/lists";
|
||||||
|
import { listTodoLists, type TodoListDto } from "$lib/backend/todos";
|
||||||
import { sendCommand } from "$lib/backend/client";
|
import { sendCommand } from "$lib/backend/client";
|
||||||
import CalendarWidget from "$lib/components/CalendarWidget.svelte";
|
import CalendarWidget from "$lib/components/CalendarWidget.svelte";
|
||||||
import { entriesBusyStore, entriesStore, searchEntriesAsItems } from "$lib/stores/entries";
|
import { entriesBusyStore, entriesStore, searchEntriesAsItems } from "$lib/stores/entries";
|
||||||
import { createFragmentDraft, fragmentsStore } from "$lib/stores/fragments";
|
import { createFragmentDraft, fragmentsStore, serializeFragment } from "$lib/stores/fragments";
|
||||||
import { createListDraft, createListFromLabel, listsStore } from "$lib/stores/lists";
|
import { createListDraft, createListFromLabel, listsStore } from "$lib/stores/lists";
|
||||||
import { createTodoListDraft, createTodoListFromLabel, serializeTodoList, todoListsStore, todosStore } from "$lib/stores/todos";
|
import { createTodoListDraft, createTodoListFromLabel, serializeTodoList, todoListsStore, todosStore } from "$lib/stores/todos";
|
||||||
|
|
||||||
@ -24,15 +27,10 @@
|
|||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
initialContent: string;
|
initialContent: string;
|
||||||
|
sortDate?: string;
|
||||||
};
|
};
|
||||||
type CalendarViewMode = "day" | "week" | "month";
|
type CalendarViewMode = "day" | "week" | "month";
|
||||||
type CalendarSortMode = "asc" | "desc";
|
type CalendarSortMode = "asc" | "desc";
|
||||||
type CalendarSignal = {
|
|
||||||
count: number;
|
|
||||||
hasTrigger: boolean;
|
|
||||||
hasMood: boolean;
|
|
||||||
hasOpenTodos: boolean;
|
|
||||||
};
|
|
||||||
type SavedCalendarView = {
|
type SavedCalendarView = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -41,8 +39,6 @@
|
|||||||
query: string;
|
query: string;
|
||||||
tags: string;
|
tags: string;
|
||||||
types: string;
|
types: string;
|
||||||
checked: string;
|
|
||||||
unchecked: string;
|
|
||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
};
|
};
|
||||||
@ -73,12 +69,9 @@
|
|||||||
let calendarQuery = "";
|
let calendarQuery = "";
|
||||||
let calendarTags = "";
|
let calendarTags = "";
|
||||||
let calendarTypes = "";
|
let calendarTypes = "";
|
||||||
let calendarChecked = "";
|
|
||||||
let calendarUnchecked = "";
|
|
||||||
let calendarStartDate = "";
|
let calendarStartDate = "";
|
||||||
let calendarEndDate = "";
|
let calendarEndDate = "";
|
||||||
let calendarTimelineItems: SidePanelItem[] = [];
|
let calendarTimelineItems: SidePanelItem[] = [];
|
||||||
let calendarSignals: Record<string, CalendarSignal> = {};
|
|
||||||
let calendarBusy = false;
|
let calendarBusy = false;
|
||||||
let calendarError = "";
|
let calendarError = "";
|
||||||
let calendarSavedViews: SavedCalendarView[] = [];
|
let calendarSavedViews: SavedCalendarView[] = [];
|
||||||
@ -86,8 +79,9 @@
|
|||||||
let saveViewName = "";
|
let saveViewName = "";
|
||||||
let hasLoadedSavedViews = false;
|
let hasLoadedSavedViews = false;
|
||||||
let lastCalendarTimelineKey = "";
|
let lastCalendarTimelineKey = "";
|
||||||
let lastCalendarSignalsKey = "";
|
|
||||||
let calendarTimelineDebounce: ReturnType<typeof setTimeout> | null = null;
|
let calendarTimelineDebounce: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let lastActiveSection = "";
|
||||||
|
let calendarLastRefreshedAt = "";
|
||||||
const CALENDAR_SAVED_VIEWS_KEY = "journal.calendar.savedViews";
|
const CALENDAR_SAVED_VIEWS_KEY = "journal.calendar.savedViews";
|
||||||
|
|
||||||
let selectedCalendarDate: { year: number; month: number; day: number; key: string } | null = {
|
let selectedCalendarDate: { year: number; month: number; day: number; key: string } | null = {
|
||||||
@ -115,15 +109,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseDateLabel(value: string): Date | null {
|
function parseDateLabel(value: string): Date | null {
|
||||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
const token = value.trim().split(/\s*[|·]\s*/)[0].trim();
|
||||||
const date = new Date(`${value}T00:00:00`);
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(token)) return null;
|
||||||
|
const date = new Date(`${token}T00:00:00`);
|
||||||
return Number.isNaN(date.getTime()) ? null : date;
|
return Number.isNaN(date.getTime()) ? null : date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseIsoDate(value: string): Date | null {
|
||||||
|
if (!value) return null;
|
||||||
|
const parsed = new Date(value);
|
||||||
|
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
function toIsoDate(date: Date): string {
|
function toIsoDate(date: Date): string {
|
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWithinRange(date: Date, startDate: string, endDate: string): boolean {
|
||||||
|
const start = parseDateLabel(startDate);
|
||||||
|
const end = parseDateLabel(endDate);
|
||||||
|
if (!start || !end) return true;
|
||||||
|
const day = toIsoDate(date);
|
||||||
|
return day >= toIsoDate(start) && day <= toIsoDate(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesTextQuery(content: string, query: string): boolean {
|
||||||
|
const trimmed = query.trim();
|
||||||
|
if (!trimmed) return true;
|
||||||
|
return content.toLowerCase().includes(trimmed.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
function getActiveDateRange(): { startDate: string; endDate: string } {
|
function getActiveDateRange(): { startDate: string; endDate: string } {
|
||||||
if (calendarStartDate && calendarEndDate) {
|
if (calendarStartDate && calendarEndDate) {
|
||||||
return { startDate: calendarStartDate, endDate: calendarEndDate };
|
return { startDate: calendarStartDate, endDate: calendarEndDate };
|
||||||
@ -162,47 +177,60 @@
|
|||||||
return (config.dataDirectory ?? config.DataDirectory ?? "").trim();
|
return (config.dataDirectory ?? config.DataDirectory ?? "").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSignal(dateKey: string): CalendarSignal {
|
function toFragmentTimelineItem(fragment: FragmentDto): SidePanelItem {
|
||||||
const existing = calendarSignals[dateKey];
|
const split = fragment.description.split(/\n{2,}/);
|
||||||
if (existing) return existing;
|
const title = (split[0] ?? "").trim() || "Untitled Fragment";
|
||||||
return { count: 0, hasTrigger: false, hasMood: false, hasOpenTodos: false };
|
const body = split.slice(1).join("\n\n").trim() || "Add details for this fragment.";
|
||||||
|
const date = parseIsoDate(fragment.time) ?? new Date();
|
||||||
|
const dateKey = toIsoDate(date);
|
||||||
|
return {
|
||||||
|
id: `fragments/${fragment.id}`,
|
||||||
|
label: `${dateKey} | Fragment | ${title}`,
|
||||||
|
initialContent: serializeFragment({
|
||||||
|
title,
|
||||||
|
type: fragment.type,
|
||||||
|
tags: fragment.tags ?? [],
|
||||||
|
body
|
||||||
|
}),
|
||||||
|
sortDate: date.toISOString()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function upsertSignal(dateKey: string, next: CalendarSignal) {
|
function toListTimelineItem(list: ListDocumentDto): SidePanelItem {
|
||||||
calendarSignals = { ...calendarSignals, [dateKey]: next };
|
const created = parseIsoDate(list.createdAt) ?? parseIsoDate(list.updatedAt) ?? new Date();
|
||||||
|
const dateKey = toIsoDate(created);
|
||||||
|
return {
|
||||||
|
id: `lists/${list.id}`,
|
||||||
|
label: `${dateKey} | List | ${list.label}`,
|
||||||
|
initialContent: list.content || `# ${list.label}\n\n`,
|
||||||
|
sortDate: created.toISOString()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCalendarSignals(): Promise<void> {
|
function toTodoTimelineItem(list: TodoListDto): SidePanelItem {
|
||||||
const monthStart = toDateKey(calendarYear, calendarMonth, 1);
|
const created = parseIsoDate(list.createdAt) ?? new Date();
|
||||||
const monthEnd = toDateKey(calendarYear, calendarMonth, new Date(calendarYear, calendarMonth + 1, 0).getDate());
|
const dateKey = toIsoDate(created);
|
||||||
const dataDirectory = await getDataDirectory();
|
const items = list.items.map((item, index) => ({
|
||||||
if (!dataDirectory) {
|
id: Date.now() + index,
|
||||||
calendarSignals = {};
|
text: item.text,
|
||||||
return;
|
done: item.done
|
||||||
}
|
}));
|
||||||
|
return {
|
||||||
|
id: `todos/${list.id}`,
|
||||||
|
label: `${dateKey} | To-Do | ${list.label}`,
|
||||||
|
initialContent: serializeTodoList(list.label, items),
|
||||||
|
sortDate: created.toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const results = await searchEntriesAsItems({
|
function splitFilterTokens(input: string): string[] {
|
||||||
dataDirectory,
|
return parseCsv(input).map((token) => token.toLowerCase());
|
||||||
startDate: monthStart,
|
}
|
||||||
endDate: monthEnd
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextSignals: Record<string, CalendarSignal> = {};
|
function matchesAnyToken(text: string, tokens: string[]): boolean {
|
||||||
for (const item of results) {
|
if (tokens.length === 0) return true;
|
||||||
const dateKey = item.label.trim();
|
const lower = text.toLowerCase();
|
||||||
const date = parseDateLabel(dateKey);
|
return tokens.some((token) => lower.includes(token));
|
||||||
if (!date) continue;
|
|
||||||
const signal = nextSignals[dateKey] ?? { count: 0, hasTrigger: false, hasMood: false, hasOpenTodos: false };
|
|
||||||
signal.count += 1;
|
|
||||||
const content = item.initialContent ?? "";
|
|
||||||
const lower = content.toLowerCase();
|
|
||||||
signal.hasTrigger = signal.hasTrigger || lower.includes("!trigger") || lower.includes("#trigger") || lower.includes("#stress");
|
|
||||||
signal.hasMood = signal.hasMood || lower.includes("mental / emotional snapshot") || lower.includes("cognitive state");
|
|
||||||
signal.hasOpenTodos = signal.hasOpenTodos || /-\s*\[\s\]/.test(content);
|
|
||||||
nextSignals[dateKey] = signal;
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarSignals = nextSignals;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCalendarTimeline(): Promise<void> {
|
async function refreshCalendarTimeline(): Promise<void> {
|
||||||
@ -216,23 +244,88 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { startDate, endDate } = getActiveDateRange();
|
const { startDate, endDate } = getActiveDateRange();
|
||||||
const items = await searchEntriesAsItems({
|
const entryItems = await searchEntriesAsItems({
|
||||||
dataDirectory,
|
dataDirectory,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
query: calendarQuery.trim() || undefined,
|
query: calendarQuery.trim() || undefined,
|
||||||
tags: parseCsv(calendarTags),
|
tags: parseCsv(calendarTags),
|
||||||
types: parseCsv(calendarTypes),
|
types: parseCsv(calendarTypes)
|
||||||
checked: parseCsv(calendarChecked),
|
|
||||||
unchecked: parseCsv(calendarUnchecked)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const sorted = [...items].sort((a, b) => {
|
const [fragmentDtos, listDtos, todoDtos] = await Promise.all([
|
||||||
const aDate = parseDateLabel(a.label)?.getTime() ?? 0;
|
listFragments(),
|
||||||
const bDate = parseDateLabel(b.label)?.getTime() ?? 0;
|
listLists(),
|
||||||
|
listTodoLists()
|
||||||
|
]);
|
||||||
|
|
||||||
|
const query = calendarQuery.trim();
|
||||||
|
const tagTokens = splitFilterTokens(calendarTags);
|
||||||
|
const typeTokens = splitFilterTokens(calendarTypes);
|
||||||
|
const hasTypeFilter = typeTokens.length > 0;
|
||||||
|
|
||||||
|
const fragmentItems = fragmentDtos
|
||||||
|
.map(toFragmentTimelineItem)
|
||||||
|
.filter((item) => {
|
||||||
|
const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
|
||||||
|
if (!date || !isWithinRange(date, startDate, endDate)) return false;
|
||||||
|
if (!matchesTextQuery(item.initialContent, query)) return false;
|
||||||
|
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||||
|
if (hasTypeFilter && !matchesAnyToken(`${item.label}\n${item.initialContent}`, typeTokens)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const listItems = listDtos
|
||||||
|
.map(toListTimelineItem)
|
||||||
|
.filter((item) => {
|
||||||
|
if (hasTypeFilter) return false;
|
||||||
|
const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
|
||||||
|
if (!date || !isWithinRange(date, startDate, endDate)) return false;
|
||||||
|
if (!matchesTextQuery(item.initialContent, query) && !matchesTextQuery(item.label, query)) return false;
|
||||||
|
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const todoItems = todoDtos
|
||||||
|
.map(toTodoTimelineItem)
|
||||||
|
.filter((item) => {
|
||||||
|
if (hasTypeFilter) return false;
|
||||||
|
const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
|
||||||
|
if (!date || !isWithinRange(date, startDate, endDate)) return false;
|
||||||
|
if (!matchesTextQuery(item.initialContent, query) && !matchesTextQuery(item.label, query)) return false;
|
||||||
|
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const entriesWithKind: SidePanelItem[] = entryItems
|
||||||
|
.map((item) => {
|
||||||
|
const date = parseDateLabel(item.label);
|
||||||
|
const dateKey = date ? toIsoDate(date) : item.label;
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
label: `${dateKey} | Entry | ${item.label}`,
|
||||||
|
initialContent: item.initialContent,
|
||||||
|
sortDate: date ? date.toISOString() : undefined
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item) => {
|
||||||
|
if (hasTypeFilter) return false;
|
||||||
|
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const merged = [...entriesWithKind, ...fragmentItems, ...listItems, ...todoItems];
|
||||||
|
const sorted = merged.sort((a, b) => {
|
||||||
|
const aDate = a.sortDate ? parseIsoDate(a.sortDate)?.getTime() ?? 0 : parseDateLabel(a.label)?.getTime() ?? 0;
|
||||||
|
const bDate = b.sortDate ? parseIsoDate(b.sortDate)?.getTime() ?? 0 : parseDateLabel(b.label)?.getTime() ?? 0;
|
||||||
return calendarSortMode === "asc" ? aDate - bDate : bDate - aDate;
|
return calendarSortMode === "asc" ? aDate - bDate : bDate - aDate;
|
||||||
});
|
});
|
||||||
calendarTimelineItems = sorted;
|
calendarTimelineItems = sorted;
|
||||||
|
calendarLastRefreshedAt = new Date().toLocaleTimeString(undefined, {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
calendarError = String(error);
|
calendarError = String(error);
|
||||||
calendarTimelineItems = [];
|
calendarTimelineItems = [];
|
||||||
@ -241,6 +334,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function forceRefreshCalendar(options?: { allowWhileBusy?: boolean }): Promise<void> {
|
||||||
|
if (activeSection !== "calendar") return;
|
||||||
|
if (calendarBusy && !options?.allowWhileBusy) return;
|
||||||
|
if (calendarTimelineDebounce) {
|
||||||
|
clearTimeout(calendarTimelineDebounce);
|
||||||
|
calendarTimelineDebounce = null;
|
||||||
|
}
|
||||||
|
await refreshCalendarTimeline();
|
||||||
|
}
|
||||||
|
|
||||||
function loadSavedViews() {
|
function loadSavedViews() {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const raw = window.localStorage.getItem(CALENDAR_SAVED_VIEWS_KEY);
|
const raw = window.localStorage.getItem(CALENDAR_SAVED_VIEWS_KEY);
|
||||||
@ -268,8 +371,6 @@
|
|||||||
calendarQuery = view.query;
|
calendarQuery = view.query;
|
||||||
calendarTags = view.tags;
|
calendarTags = view.tags;
|
||||||
calendarTypes = view.types;
|
calendarTypes = view.types;
|
||||||
calendarChecked = view.checked;
|
|
||||||
calendarUnchecked = view.unchecked;
|
|
||||||
calendarStartDate = view.startDate;
|
calendarStartDate = view.startDate;
|
||||||
calendarEndDate = view.endDate;
|
calendarEndDate = view.endDate;
|
||||||
}
|
}
|
||||||
@ -290,8 +391,6 @@
|
|||||||
query: calendarQuery,
|
query: calendarQuery,
|
||||||
tags: calendarTags,
|
tags: calendarTags,
|
||||||
types: calendarTypes,
|
types: calendarTypes,
|
||||||
checked: calendarChecked,
|
|
||||||
unchecked: calendarUnchecked,
|
|
||||||
startDate: calendarStartDate,
|
startDate: calendarStartDate,
|
||||||
endDate: calendarEndDate
|
endDate: calendarEndDate
|
||||||
};
|
};
|
||||||
@ -315,8 +414,6 @@
|
|||||||
query: "",
|
query: "",
|
||||||
tags: "",
|
tags: "",
|
||||||
types: "",
|
types: "",
|
||||||
checked: "",
|
|
||||||
unchecked: "",
|
|
||||||
startDate: "",
|
startDate: "",
|
||||||
endDate: ""
|
endDate: ""
|
||||||
},
|
},
|
||||||
@ -328,24 +425,10 @@
|
|||||||
query: "",
|
query: "",
|
||||||
tags: "stress, trigger",
|
tags: "stress, trigger",
|
||||||
types: "!TRIGGER",
|
types: "!TRIGGER",
|
||||||
checked: "",
|
|
||||||
unchecked: "",
|
|
||||||
startDate: "",
|
startDate: "",
|
||||||
endDate: ""
|
endDate: ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "builtin-open-todos",
|
|
||||||
name: "Open Checklist",
|
|
||||||
viewMode: "month",
|
|
||||||
sortMode: "desc",
|
|
||||||
query: "",
|
|
||||||
tags: "",
|
|
||||||
types: "",
|
|
||||||
checked: "",
|
|
||||||
unchecked: "todo, follow up, check",
|
|
||||||
startDate: "",
|
|
||||||
endDate: ""
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function openOrCreateDailyEntry(dateKey: string) {
|
function openOrCreateDailyEntry(dateKey: string) {
|
||||||
@ -432,6 +515,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRefreshClick() {
|
||||||
|
void forceRefreshCalendar();
|
||||||
|
}
|
||||||
|
|
||||||
function handleAddTemplate() {
|
function handleAddTemplate() {
|
||||||
if (activeSection !== "entries") return;
|
if (activeSection !== "entries") return;
|
||||||
createTemplateMode = true;
|
createTemplateMode = true;
|
||||||
@ -582,10 +669,19 @@
|
|||||||
calendarQuery,
|
calendarQuery,
|
||||||
calendarTags,
|
calendarTags,
|
||||||
calendarTypes,
|
calendarTypes,
|
||||||
calendarChecked,
|
|
||||||
calendarUnchecked,
|
|
||||||
calendarStartDate,
|
calendarStartDate,
|
||||||
calendarEndDate
|
calendarEndDate,
|
||||||
|
entriesSig: $entriesStore.map((item) => `${item.id}:${item.label}`).join("|"),
|
||||||
|
fragmentsSig: $fragmentsStore.map((item) => `${item.id}:${item.label}`).join("|"),
|
||||||
|
listsSig: $listsStore.map((item) => `${item.id}:${item.label}`).join("|"),
|
||||||
|
todosSig: $todoListsStore
|
||||||
|
.map((item) => {
|
||||||
|
const todos = ($todosStore[item.id] ?? [])
|
||||||
|
.map((todo) => `${todo.text}:${todo.done ? "1" : "0"}`)
|
||||||
|
.join("~");
|
||||||
|
return `${item.id}:${item.label}:${todos}`;
|
||||||
|
})
|
||||||
|
.join("|")
|
||||||
});
|
});
|
||||||
$: if (activeSection === "calendar" && calendarTimelineRefreshKey !== lastCalendarTimelineKey) {
|
$: if (activeSection === "calendar" && calendarTimelineRefreshKey !== lastCalendarTimelineKey) {
|
||||||
lastCalendarTimelineKey = calendarTimelineRefreshKey;
|
lastCalendarTimelineKey = calendarTimelineRefreshKey;
|
||||||
@ -597,15 +693,14 @@
|
|||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: calendarSignalsRefreshKey = JSON.stringify({
|
$: if (activeSection === "calendar" && lastActiveSection !== "calendar") {
|
||||||
activeSection,
|
void forceRefreshCalendar({ allowWhileBusy: true });
|
||||||
calendarYear,
|
setTimeout(() => {
|
||||||
calendarMonth
|
void forceRefreshCalendar({ allowWhileBusy: true });
|
||||||
});
|
}, 500);
|
||||||
$: if (activeSection === "calendar" && calendarSignalsRefreshKey !== lastCalendarSignalsKey) {
|
|
||||||
lastCalendarSignalsKey = calendarSignalsRefreshKey;
|
|
||||||
void refreshCalendarSignals();
|
|
||||||
}
|
}
|
||||||
|
$: lastActiveSection = activeSection;
|
||||||
|
|
||||||
$: if (activeSection === "calendar") {
|
$: if (activeSection === "calendar") {
|
||||||
onCalendarStateChange({
|
onCalendarStateChange({
|
||||||
items: calendarTimelineItems,
|
items: calendarTimelineItems,
|
||||||
@ -619,6 +714,11 @@
|
|||||||
<header class="panel-header">
|
<header class="panel-header">
|
||||||
<h2>{panelTitle}</h2>
|
<h2>{panelTitle}</h2>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
|
{#if activeSection === "calendar"}
|
||||||
|
<button type="button" class="panel-action" aria-label="Refresh calendar" title="Refresh calendar" on:click={handleRefreshClick}>
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
{#if activeSection === "entries"}
|
{#if activeSection === "entries"}
|
||||||
<button type="button" class="panel-action" aria-label="Add template" title="Add template" on:click={handleAddTemplate}>
|
<button type="button" class="panel-action" aria-label="Add template" title="Add template" on:click={handleAddTemplate}>
|
||||||
<span class="material-symbols-outlined">palette</span>
|
<span class="material-symbols-outlined">palette</span>
|
||||||
@ -671,11 +771,6 @@
|
|||||||
|
|
||||||
<div class="calendar-control-row">
|
<div class="calendar-control-row">
|
||||||
<input type="text" bind:value={calendarTypes} placeholder="Fragment types (comma)" />
|
<input type="text" bind:value={calendarTypes} placeholder="Fragment types (comma)" />
|
||||||
<input type="text" bind:value={calendarChecked} placeholder="Checked todos (comma)" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="calendar-control-row">
|
|
||||||
<input type="text" bind:value={calendarUnchecked} placeholder="Unchecked todos (comma)" />
|
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -723,12 +818,11 @@
|
|||||||
onVisibleMonthChange={handleVisibleMonthChange}
|
onVisibleMonthChange={handleVisibleMonthChange}
|
||||||
onSelectedDateChange={handleSelectedDateChange}
|
onSelectedDateChange={handleSelectedDateChange}
|
||||||
onDateActivate={handleDateActivate}
|
onDateActivate={handleDateActivate}
|
||||||
signalsByDate={calendarSignals}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="calendar-entries">
|
<div class="calendar-entries">
|
||||||
<h3>{calendarMonthLabel} {calendarYear} Timeline</h3>
|
<h3>{calendarMonthLabel} {calendarYear} Timeline</h3>
|
||||||
<p class="section-copy">Filtered items are shown in the main panel.</p>
|
<p class="section-copy">Last refreshed: {calendarLastRefreshedAt || "Not yet"}</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="panel-search">
|
<div class="panel-search">
|
||||||
@ -1181,3 +1275,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user