feat: full Synology bidirectional sync and fix someday list delete button

- Add createSynologyTask: new tasks in synced lists push to Synology CalDAV
- Wire Synology create into POST /api/tasks when somedayList is synced
- Fix someday list title input using width:100% instead of flex:1, which
  pushed the delete button off-screen making lists undeletable
- Slightly improved provider icon styling

v1.16.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-08 02:00:55 +01:00
parent fe41c153f7
commit 2ec09fdcca
4 changed files with 71 additions and 2 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.16.0",
"version": "1.16.1",
"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": {

View File

@ -287,6 +287,21 @@ export async function POST(request: NextRequest) {
externalProvider = 'outlook';
externalListId = somedayList.externalId;
}
} else if (somedayList.externalProvider === 'synology') {
const { createSynologyTask } = await import('@/lib/synology-tasks');
const synoConnection = await prisma.calendarConnection.findFirst({
where: { userId, provider: 'synology' }
});
if (synoConnection?.accessToken && synoConnection?.refreshToken) {
const [synoUsername, synoPassword] = synoConnection.accessToken.split(':');
const synoServerUrl = synoConnection.refreshToken;
if (synoUsername && synoPassword && synoServerUrl) {
const synoTask = await createSynologyTask(synoServerUrl, synoUsername, synoPassword, somedayList.externalId, { title });
externalId = synoTask.id;
externalProvider = 'synology';
externalListId = somedayList.externalId;
}
}
}
} catch (syncError) {
console.error('Failed to sync new task to external provider:', syncError);

View File

@ -1458,7 +1458,8 @@ h3 {
color: var(--weekly-text, #222);
background: transparent;
border: none;
width: 100%;
flex: 1;
min-width: 0;
padding: 4px 0;
outline: none;
transition: opacity 0.15s ease;

View File

@ -104,6 +104,59 @@ export const createSynologyReminderList = async (serverUrl: string, username: st
}
};
/**
* Create a new task (VTODO) in a Synology list via CalDAV
*/
export const createSynologyTask = async (
serverUrl: string, username: string, password: string,
listId: string, task: { title: string; notes?: string; due?: string | null }
): Promise<{ id: string }> => {
const client = createClient(serverUrl, username, password);
await client.login();
const calendars = await client.fetchCalendars();
const getPath = (url: string) => {
try { return new URL(url).pathname; } catch { return url; }
};
const targetCalendar = calendars.find(c => getPath(c.url) === getPath(listId));
if (!targetCalendar) throw new Error(`List not found: ${listId}`);
const uid = crypto.randomUUID();
const now = ICAL.Time.now();
const vcalendar = new ICAL.Component(['vcalendar', [], []]);
vcalendar.updatePropertyWithValue('prodid', '-//My Weekly ToDo List//EN');
vcalendar.updatePropertyWithValue('version', '2.0');
const vtodo = new ICAL.Component('vtodo');
vtodo.updatePropertyWithValue('uid', uid);
vtodo.updatePropertyWithValue('summary', task.title);
vtodo.updatePropertyWithValue('created', now);
vtodo.updatePropertyWithValue('dtstamp', now);
vtodo.updatePropertyWithValue('status', 'NEEDS-ACTION');
if (task.notes) {
vtodo.updatePropertyWithValue('description', task.notes);
}
if (task.due) {
const dueTime = ICAL.Time.fromJSDate(new Date(task.due), false);
vtodo.updatePropertyWithValue('due', dueTime);
}
vcalendar.addSubcomponent(vtodo);
const calendarUrl = targetCalendar.url.replace(/\/$/, '');
const objectUrl = `${calendarUrl}/${uid}.ics`;
await client.createCalendarObject({
calendar: targetCalendar,
filename: `${uid}.ics`,
iCalString: vcalendar.toString(),
});
return { id: `synology::${uid}` };
};
/**
* Fetch tasks (VTODOs) from a specific Synology list
*/