feat: subtask indicators, drag & drop fixes, font layout, someday scroll & Google Tasks sync
- Move subtask indicator badge outside text span for visibility - Prevent parent task from hijacking subtask drag events - Restructure font settings to 2-row layout (font dropdown + size/weight) - Fix someday horizontal scroll with callback ref wheel handler - Sync new tasks in Google-linked someday lists to Google Tasks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> v1.14.0
This commit is contained in:
parent
3ddfc6af79
commit
3d75d7b39b
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.13.1",
|
||||
"version": "1.14.0",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -252,6 +252,49 @@ export async function POST(request: NextRequest) {
|
||||
isRolling = user.autoRolling || false;
|
||||
}
|
||||
|
||||
// Check if someday list is linked to an external provider
|
||||
let externalId: string | null = null;
|
||||
let externalProvider: string | null = null;
|
||||
let externalListId: string | null = null;
|
||||
|
||||
if (somedayListId) {
|
||||
const somedayList = await prisma.somedayList.findUnique({
|
||||
where: { id: somedayListId },
|
||||
select: { externalId: true, externalProvider: true },
|
||||
});
|
||||
|
||||
if (somedayList?.externalId && somedayList?.externalProvider) {
|
||||
try {
|
||||
if (somedayList.externalProvider === 'google') {
|
||||
const { createGoogleClient, createGoogleTask } = await import('@/lib/google-tasks');
|
||||
const account = await prisma.account.findFirst({
|
||||
where: { userId, provider: { in: ['google-calendar', 'google'] } },
|
||||
});
|
||||
if (account?.access_token) {
|
||||
const client = createGoogleClient(account.access_token, account.refresh_token || undefined);
|
||||
const gTask = await createGoogleTask(client, somedayList.externalId, { title });
|
||||
externalId = gTask.id;
|
||||
externalProvider = 'google';
|
||||
externalListId = somedayList.externalId;
|
||||
}
|
||||
} else if (somedayList.externalProvider === 'outlook') {
|
||||
const { createMsTodoTask } = await import('@/lib/microsoft-todo');
|
||||
const { getOutlookAccessToken } = await import('@/lib/outlook-token');
|
||||
const accessToken = await getOutlookAccessToken(userId);
|
||||
if (accessToken) {
|
||||
const msTask = await createMsTodoTask(accessToken, somedayList.externalId, { title });
|
||||
externalId = msTask.id;
|
||||
externalProvider = 'outlook';
|
||||
externalListId = somedayList.externalId;
|
||||
}
|
||||
}
|
||||
} catch (syncError) {
|
||||
console.error('Failed to sync new task to external provider:', syncError);
|
||||
// Continue creating locally even if sync fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const task = await prisma.task.create({
|
||||
data: {
|
||||
title,
|
||||
@ -273,6 +316,7 @@ export async function POST(request: NextRequest) {
|
||||
somedaySlotIndex: somedaySlotIndex !== undefined ? parseInt(somedaySlotIndex) : null,
|
||||
parentTaskId: parentTaskId || null,
|
||||
...(projectId !== undefined && { projectId: projectId || null }),
|
||||
...(externalId && { externalId, externalProvider, externalListId }),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -1453,7 +1453,6 @@ h3 {
|
||||
.weekly-someday-list-title-input {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--weekly-text, #222);
|
||||
background: transparent;
|
||||
|
||||
@ -1012,6 +1012,7 @@ export default function WeeklyView() {
|
||||
const isScrollSyncing = useRef(false);
|
||||
const dayHeaderRef = useRef<HTMLElement>(null);
|
||||
const somedayGridRef = useRef<HTMLDivElement>(null);
|
||||
const somedaySectionRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
// Scroll sync handler
|
||||
const handleTimeColumnScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
@ -1477,40 +1478,42 @@ export default function WeeklyView() {
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const el = somedayGridRef.current;
|
||||
if (!el) return;
|
||||
// Horizontal scroll: convert vertical wheel to horizontal in someday area
|
||||
// Callback ref ensures handler is attached as soon as element mounts
|
||||
const somedayWheelCleanup = useRef<(() => void) | null>(null);
|
||||
const somedaySectionRefCb = useCallback((node: HTMLElement | null) => {
|
||||
// Cleanup previous
|
||||
if (somedayWheelCleanup.current) {
|
||||
somedayWheelCleanup.current();
|
||||
somedayWheelCleanup.current = null;
|
||||
}
|
||||
somedaySectionRef.current = node;
|
||||
if (!node) return;
|
||||
|
||||
const handler = (e: WheelEvent) => {
|
||||
// Ignore if scrolling horizontally natively (trackpad)
|
||||
const grid = somedayGridRef.current;
|
||||
if (!grid) return;
|
||||
|
||||
// Let native horizontal scroll (trackpad) pass through
|
||||
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
|
||||
if (e.deltaY === 0) return;
|
||||
|
||||
// Check if hovering over a vertically scrollable list that isn't at its boundary
|
||||
let target = e.target as HTMLElement | null;
|
||||
let canScrollVertically = false;
|
||||
// Only convert if grid has horizontal overflow
|
||||
if (grid.scrollWidth <= grid.clientWidth + 1) return;
|
||||
|
||||
while (target && target !== el) {
|
||||
if (target.scrollHeight > target.clientHeight) {
|
||||
const style = window.getComputedStyle(target);
|
||||
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
|
||||
const atTop = target.scrollTop <= 0;
|
||||
const atBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 1;
|
||||
if (!(e.deltaY < 0 && atTop) && !(e.deltaY > 0 && atBottom)) {
|
||||
canScrollVertically = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
// Check boundaries - allow page scroll when at edges
|
||||
const atLeft = grid.scrollLeft <= 0;
|
||||
const atRight = grid.scrollLeft + grid.clientWidth >= grid.scrollWidth - 1;
|
||||
if (e.deltaY < 0 && atLeft) return;
|
||||
if (e.deltaY > 0 && atRight) return;
|
||||
|
||||
if (!canScrollVertically && e.deltaY !== 0) {
|
||||
e.preventDefault();
|
||||
el.scrollLeft += e.deltaY;
|
||||
}
|
||||
grid.scrollLeft += e.deltaY;
|
||||
};
|
||||
el.addEventListener("wheel", handler, { passive: false });
|
||||
return () => el.removeEventListener("wheel", handler);
|
||||
}, [showSomeday, somedayExpanded]);
|
||||
|
||||
node.addEventListener("wheel", handler, { passive: false });
|
||||
somedayWheelCleanup.current = () => node.removeEventListener("wheel", handler);
|
||||
}, []);
|
||||
const saveSetting = async (key: string, value: any) => {
|
||||
// Per-device settings: save to cookie ONLY (not DB) so each device keeps its own value
|
||||
if (DEVICE_SETTINGS_KEYS.includes(key)) {
|
||||
@ -3511,31 +3514,6 @@ export default function WeeklyView() {
|
||||
setDropPreview(null);
|
||||
};
|
||||
|
||||
// Added: Horizontal scroll with mouse wheel for someday area
|
||||
useEffect(() => {
|
||||
const somedayGrid = somedayGridRef.current;
|
||||
if (!somedayGrid) return;
|
||||
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
// Only handle if there is horizontal overflow
|
||||
if (somedayGrid.scrollWidth > somedayGrid.clientWidth) {
|
||||
// Dominant vertical scroll -> horizontal scroll
|
||||
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
||||
// Check if we can scroll further in the requested direction
|
||||
const isAtLeft = somedayGrid.scrollLeft === 0;
|
||||
const isAtRight = Math.abs(somedayGrid.scrollWidth - somedayGrid.clientWidth - somedayGrid.scrollLeft) < 1;
|
||||
|
||||
if ((e.deltaY < 0 && !isAtLeft) || (e.deltaY > 0 && !isAtRight)) {
|
||||
e.preventDefault();
|
||||
somedayGrid.scrollLeft += e.deltaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
somedayGrid.addEventListener("wheel", handleWheel, { passive: false });
|
||||
return () => somedayGrid.removeEventListener("wheel", handleWheel);
|
||||
}, [somedayExpanded]);
|
||||
|
||||
const handleSomedayDragOver = (e: React.DragEvent, listId: string, slotIdx: number) => {
|
||||
e.preventDefault();
|
||||
@ -5345,6 +5323,7 @@ export default function WeeklyView() {
|
||||
{/* Someday Section */}
|
||||
{showSomeday && (
|
||||
<section
|
||||
ref={somedaySectionRefCb}
|
||||
className={`weekly-someday ${somedayExpanded ? "expanded" : "collapsed"} transition-colors duration-200`}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "row", maxWidth: "100%", width: "100%" }}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user