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:
parent
fe41c153f7
commit
2ec09fdcca
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -287,6 +287,21 @@ export async function POST(request: NextRequest) {
|
|||||||
externalProvider = 'outlook';
|
externalProvider = 'outlook';
|
||||||
externalListId = somedayList.externalId;
|
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) {
|
} catch (syncError) {
|
||||||
console.error('Failed to sync new task to external provider:', syncError);
|
console.error('Failed to sync new task to external provider:', syncError);
|
||||||
|
|||||||
@ -1458,7 +1458,8 @@ h3 {
|
|||||||
color: var(--weekly-text, #222);
|
color: var(--weekly-text, #222);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: opacity 0.15s ease;
|
transition: opacity 0.15s ease;
|
||||||
|
|||||||
@ -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
|
* Fetch tasks (VTODOs) from a specific Synology list
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user