feat: schedule Todo native reminders

This commit is contained in:
2026-07-14 02:47:05 +08:00
parent 7e34a73835
commit 7687f410c4
5 changed files with 63 additions and 4 deletions
+31 -3
View File
@@ -340,18 +340,45 @@
});
}
function notificationRequests() {
return sortTodos(todos).filter(function (todo) {
return todo.status === 'open' && todo.reminderAt;
}).map(function (todo) {
var dueAt = new Date(todo.reminderAt);
if (isNaN(dueAt.getTime())) return null;
var title = todo.title || tr('ui.untitled', null, 'Untitled todo');
return {
id: todo.id,
dueAt: dueAt.toISOString(),
title: tr('ui.notificationTitle', null, 'Todo reminder'),
body: tr('ui.notificationBody', { title: title }, title)
};
}).filter(function (item) { return item !== null; });
}
function syncNotifications() {
if (!api || !api.notifications || typeof api.notifications.replace !== 'function') return Promise.resolve();
return api.notifications.replace(notificationRequests()).catch(function (err) {
statusText = tr('ui.notificationError', { error: err && err.message ? err.message : String(err) }, 'Could not schedule reminders: ' + (err && err.message ? err.message : String(err)));
statusClass = 'error';
});
}
function persist() {
if (!api || !api.settings || typeof api.settings.write !== 'function') return Promise.resolve();
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).catch(function (err) {
if (!api || !api.settings || typeof api.settings.write !== 'function') return syncNotifications();
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).then(function () {
return syncNotifications();
}).catch(function (err) {
statusText = tr('ui.saveError', { error: err && err.message ? err.message : String(err) }, 'Could not save todos: ' + (err && err.message ? err.message : String(err)));
statusClass = 'error';
});
}
function loadStored() {
if (!api || !api.settings || typeof api.settings.read !== 'function') return Promise.resolve();
if (!api || !api.settings || typeof api.settings.read !== 'function') return syncNotifications();
return api.settings.read().then(function (settings) {
todos = sortTodos(normalizeTodos((settings || {})[GLOBAL_KEY]));
return syncNotifications();
}).catch(function (err) {
statusText = tr('ui.loadError', { error: err && err.message ? err.message : String(err) }, 'Could not load todos: ' + (err && err.message ? err.message : String(err)));
statusClass = 'error';
@@ -589,6 +616,7 @@
searchInput.setAttribute('placeholder', tr('ui.search', null, 'Search todos'));
addBtn.textContent = tr('ui.add', null, 'Add Todo');
render();
syncNotifications().then(render);
});
}
};
+3
View File
@@ -19,6 +19,9 @@
"ui.unassigned": "Unassigned",
"ui.saveError": "Could not save todos: {error}",
"ui.loadError": "Could not load todos: {error}",
"ui.notificationError": "Could not schedule reminders: {error}",
"ui.notificationTitle": "Todo reminder",
"ui.notificationBody": "{title}",
"ui.titlePlaceholder": "Todo title",
"ui.descriptionPlaceholder": "Optional description",
"ui.priority.low": "Low",
+3
View File
@@ -19,6 +19,9 @@
"ui.unassigned": "Не назначено",
"ui.saveError": "Не удалось сохранить задачи: {error}",
"ui.loadError": "Не удалось загрузить задачи: {error}",
"ui.notificationError": "Не удалось запланировать напоминания: {error}",
"ui.notificationTitle": "Напоминание о задаче",
"ui.notificationBody": "{title}",
"ui.titlePlaceholder": "Название задачи",
"ui.descriptionPlaceholder": "Необязательное описание",
"ui.priority.low": "Низкий",
+5 -1
View File
@@ -12,10 +12,14 @@
"todo.list",
"todo.workspace"
],
"requires": [
"verstak/core/notifications/v1"
],
"permissions": [
"files.read",
"storage.namespace",
"ui.register"
"ui.register",
"notifications.schedule"
],
"frontend": {
"entry": "frontend/src/index.js"