diff --git a/notes/!!!meta.json b/notes/!!!meta.json
new file mode 100644
index 0000000..863f4dc
--- /dev/null
+++ b/notes/!!!meta.json
@@ -0,0 +1,511 @@
+{
+ "formatVersion": 1,
+ "appVersion": "0.46.9",
+ "files": [
+ {
+ "isClone": false,
+ "noteId": "vP5DkrdHVvv0",
+ "notePath": [
+ "vP5DkrdHVvv0"
+ ],
+ "title": "Implementation",
+ "notePosition": 50,
+ "prefix": null,
+ "isExpanded": 1,
+ "type": "text",
+ "mime": "text/html",
+ "attributes": [],
+ "format": "html",
+ "dirFileName": "Implementation",
+ "children": [
+ {
+ "isClone": false,
+ "noteId": "dDCKmKnFWfrk",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "dDCKmKnFWfrk"
+ ],
+ "title": "attribute changed",
+ "notePosition": 0,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=backend",
+ "attributes": [],
+ "dataFileName": "attribute changed.js",
+ "dirFileName": "attribute changed",
+ "children": [
+ {
+ "isClone": false,
+ "noteId": "Xv1GdWl4UvVO",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "dDCKmKnFWfrk",
+ "Xv1GdWl4UvVO"
+ ],
+ "title": "reconcileAssignments",
+ "notePosition": 0,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=backend",
+ "attributes": [],
+ "dataFileName": "reconcileAssignments.js"
+ }
+ ]
+ },
+ {
+ "isClone": false,
+ "noteId": "1d9ya4XfzQP2",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "1d9ya4XfzQP2"
+ ],
+ "title": "button",
+ "notePosition": 10,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=frontend",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "run",
+ "value": "frontendStartup",
+ "isInheritable": false,
+ "position": 0
+ }
+ ],
+ "dataFileName": "button.js"
+ },
+ {
+ "isClone": false,
+ "noteId": "hvYOnJwamlAm",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "hvYOnJwamlAm"
+ ],
+ "title": "CSS",
+ "notePosition": 20,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "text/css",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "appCss",
+ "value": "",
+ "isInheritable": false,
+ "position": 0
+ }
+ ],
+ "dataFileName": "CSS.css"
+ },
+ {
+ "isClone": false,
+ "noteId": "wYT6EyWoF0Qg",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "wYT6EyWoF0Qg"
+ ],
+ "title": "task template",
+ "notePosition": 30,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "text",
+ "mime": "text/html",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "label:tag",
+ "value": "promoted,multi,text",
+ "isInheritable": false,
+ "position": 10
+ },
+ {
+ "type": "label",
+ "name": "cssClass",
+ "value": "todo",
+ "isInheritable": false,
+ "position": 20
+ },
+ {
+ "type": "label",
+ "name": "label:location",
+ "value": "promoted,single,text",
+ "isInheritable": false,
+ "position": 30
+ },
+ {
+ "type": "label",
+ "name": "label:todoDate",
+ "value": "promoted,single,date",
+ "isInheritable": false,
+ "position": 40
+ },
+ {
+ "type": "label",
+ "name": "label:todoTime",
+ "value": "promoted,single,text",
+ "isInheritable": false,
+ "position": 50
+ },
+ {
+ "type": "label",
+ "name": "label:doneDate",
+ "value": "promoted,single,date",
+ "isInheritable": false,
+ "position": 60
+ },
+ {
+ "type": "label",
+ "name": "label:canceled",
+ "value": "promoted,single,boolean",
+ "isInheritable": false,
+ "position": 70
+ }
+ ],
+ "format": "html",
+ "dataFileName": "task template.html"
+ },
+ {
+ "isClone": false,
+ "noteId": "zzJx2J4CLv9y",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "zzJx2J4CLv9y"
+ ],
+ "title": "new_reminder handler",
+ "notePosition": 40,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=backend",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "customRequestHandler",
+ "value": "new_reminder",
+ "isInheritable": false,
+ "position": 20
+ },
+ {
+ "type": "relation",
+ "name": "targetTemplate",
+ "value": "LBDjD6rGxw6I",
+ "isInheritable": false,
+ "position": 10
+ }
+ ],
+ "dataFileName": "new_reminder handler.js"
+ },
+ {
+ "isClone": false,
+ "noteId": "UNuCNmOMROgz",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "UNuCNmOMROgz"
+ ],
+ "title": "task_alerts handler",
+ "notePosition": 50,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=backend",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "customRequestHandler",
+ "value": "task_alerts",
+ "isInheritable": false,
+ "position": 10
+ },
+ {
+ "type": "relation",
+ "name": "targetTemplate",
+ "value": "wYT6EyWoF0Qg",
+ "isInheritable": false,
+ "position": 20
+ },
+ {
+ "type": "relation",
+ "name": "targetTemplateReminder",
+ "value": "LBDjD6rGxw6I",
+ "isInheritable": false,
+ "position": 30
+ },
+ {
+ "type": "relation",
+ "name": "targetTemplateReminderDaily",
+ "value": "7Wzjk70ySoKm",
+ "isInheritable": false,
+ "position": 40
+ }
+ ],
+ "dataFileName": "task_alerts handler.js"
+ },
+ {
+ "isClone": false,
+ "noteId": "LBDjD6rGxw6I",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "LBDjD6rGxw6I"
+ ],
+ "title": "reminder template",
+ "notePosition": 60,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "text",
+ "mime": "text/html",
+ "attributes": [
+ {
+ "type": "relation",
+ "name": "template",
+ "value": "wYT6EyWoF0Qg",
+ "isInheritable": false,
+ "position": 10
+ },
+ {
+ "type": "label",
+ "name": "reminder",
+ "value": "true",
+ "isInheritable": true,
+ "position": 20
+ },
+ {
+ "type": "label",
+ "name": "iconClass",
+ "value": "bx bx-alarm",
+ "isInheritable": true,
+ "position": 30
+ },
+ {
+ "type": "label",
+ "name": "cssClass",
+ "value": "reminder",
+ "isInheritable": false,
+ "position": 40
+ }
+ ],
+ "format": "html",
+ "dataFileName": "reminder template.html"
+ },
+ {
+ "isClone": false,
+ "noteId": "7Wzjk70ySoKm",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "7Wzjk70ySoKm"
+ ],
+ "title": "daily reminder template",
+ "notePosition": 61,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "text",
+ "mime": "text/html",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "iconClass",
+ "value": "bx bx-alarm",
+ "isInheritable": true,
+ "position": 10
+ },
+ {
+ "type": "label",
+ "name": "cssClass",
+ "value": "reminder",
+ "isInheritable": false,
+ "position": 20
+ },
+ {
+ "type": "label",
+ "name": "label:todoTime",
+ "value": "promoted,single,text",
+ "isInheritable": false,
+ "position": 30
+ },
+ {
+ "type": "label",
+ "name": "reminder",
+ "value": "true",
+ "isInheritable": true,
+ "position": 40
+ }
+ ],
+ "format": "html",
+ "dataFileName": "daily reminder template.html"
+ },
+ {
+ "isClone": false,
+ "noteId": "XE5kBjusNVkY",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "XE5kBjusNVkY"
+ ],
+ "title": "attribute sync button",
+ "notePosition": 80,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=frontend",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "run",
+ "value": "frontendStartup",
+ "isInheritable": false,
+ "position": 10
+ }
+ ],
+ "dataFileName": "attribute sync button.js",
+ "dirFileName": "attribute sync button",
+ "children": [
+ {
+ "isClone": true,
+ "noteId": "Xv1GdWl4UvVO",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "XE5kBjusNVkY",
+ "Xv1GdWl4UvVO"
+ ],
+ "title": "reconcileAssignments",
+ "prefix": "",
+ "dataFileName": "reconcileAssignments.clone.html",
+ "type": "text",
+ "format": "html"
+ }
+ ]
+ },
+ {
+ "isClone": false,
+ "noteId": "X5pzYZriILAz",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "X5pzYZriILAz"
+ ],
+ "title": "event template",
+ "notePosition": 90,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "text",
+ "mime": "text/html",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "label:location",
+ "value": "promoted,single,text",
+ "isInheritable": false,
+ "position": 10
+ },
+ {
+ "type": "label",
+ "name": "label:startTime",
+ "value": "promoted,single,text",
+ "isInheritable": false,
+ "position": 20
+ },
+ {
+ "type": "label",
+ "name": "label:endTime",
+ "value": "promoted,single,text",
+ "isInheritable": false,
+ "position": 30
+ },
+ {
+ "type": "label",
+ "name": "iconClass",
+ "value": "bx bx-calendar-event",
+ "isInheritable": true,
+ "position": 40
+ },
+ {
+ "type": "label",
+ "name": "label:uid",
+ "value": "single,text",
+ "isInheritable": false,
+ "position": 50
+ }
+ ],
+ "format": "html",
+ "dataFileName": "event template.html"
+ },
+ {
+ "isClone": false,
+ "noteId": "Qfv3H8TfLqzA",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "Qfv3H8TfLqzA"
+ ],
+ "title": "new_event handler",
+ "notePosition": 100,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=backend",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "customRequestHandler",
+ "value": "new_event",
+ "isInheritable": false,
+ "position": 10
+ },
+ {
+ "type": "relation",
+ "name": "targetTemplate",
+ "value": "X5pzYZriILAz",
+ "isInheritable": false,
+ "position": 20
+ }
+ ],
+ "dataFileName": "new_event handler.js"
+ },
+ {
+ "isClone": false,
+ "noteId": "rvu9Tix6GYYx",
+ "notePath": [
+ "vP5DkrdHVvv0",
+ "rvu9Tix6GYYx"
+ ],
+ "title": "event_alerts handler",
+ "notePosition": 110,
+ "prefix": null,
+ "isExpanded": 0,
+ "type": "code",
+ "mime": "application/javascript;env=backend",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "customRequestHandler",
+ "value": "event_alerts",
+ "isInheritable": false,
+ "position": 20
+ },
+ {
+ "type": "relation",
+ "name": "targetTemplateEvent",
+ "value": "X5pzYZriILAz",
+ "isInheritable": false,
+ "position": 10
+ }
+ ],
+ "dataFileName": "event_alerts handler.js"
+ }
+ ]
+ },
+ {
+ "noImport": true,
+ "dataFileName": "navigation.html"
+ },
+ {
+ "noImport": true,
+ "dataFileName": "index.html"
+ },
+ {
+ "noImport": true,
+ "dataFileName": "style.css"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/notes/Implementation/CSS.css b/notes/Implementation/CSS.css
new file mode 100644
index 0000000..4d4b43b
--- /dev/null
+++ b/notes/Implementation/CSS.css
@@ -0,0 +1,7 @@
+span.fancytree-node.todo .fancytree-title {
+ color: red !important;
+}
+
+span.fancytree-node.done .fancytree-title {
+ color: green !important;
+}
\ No newline at end of file
diff --git a/notes/Implementation/attribute changed.js b/notes/Implementation/attribute changed.js
new file mode 100644
index 0000000..a734304
--- /dev/null
+++ b/notes/Implementation/attribute changed.js
@@ -0,0 +1,48 @@
+if (!["task", "location", "tag", "todoDate", "doneDate"].includes(api.originEntity.name)) {
+ return;
+}
+
+const note = await api.originEntity.getNote();
+
+const attributes = await note.getAttributes();
+
+if (attributes.filter(attr => attr.type === 'label' && attr.name === 'reminder').map(attr => attr.value).length() > 0) {
+ api.log("ignoring");
+ return;
+}
+
+const todoDate = await note.getLabelValue('todoDate');
+const doneDate = await note.getLabelValue('doneDate');
+const canceled = !!(await note.getLabelValue('canceled'));
+const isTaskDone = !!doneDate;
+
+const canceledRootNote = await api.getNoteWithLabel('taskDoneRoot');
+await api.toggleNoteInParent(canceled, note.noteId, canceledRootNote.noteId);
+
+const doneRootNote = await api.getNoteWithLabel('taskDoneRoot');
+await api.toggleNoteInParent(isTaskDone && !canceled, note.noteId, doneRootNote.noteId);
+
+const todoRootNote = await api.getNoteWithLabel('taskTodoRoot');
+await api.toggleNoteInParent(!isTaskDone && !canceled, note.noteId, todoRootNote.noteId);
+
+const location = await note.getLabelValue('location');
+const locationRootNote = await api.getNoteWithLabel('taskLocationRoot');
+
+await reconcileAssignments(note, locationRootNote, location ? [location] : [], 'taskLocationNote', isTaskDone);
+
+const tags = attributes.filter(attr => attr.type === 'label' && attr.name === 'tag').map(attr => attr.value);
+const tagRootNote = await api.getNoteWithLabel('taskTagRoot');
+
+await reconcileAssignments(note, tagRootNote, tags, 'taskTagNote', isTaskDone);
+
+await note.toggleLabel(isTaskDone || canceled, "cssClass", "done");
+
+const doneTargetNoteId = isTaskDone ? (await api.getDateNote(doneDate)).noteId : null;
+await api.setNoteToParent(note.noteId, 'DONE', doneTargetNoteId);
+
+await note.toggleLabel(!isTaskDone && !canceled, "cssClass", "todo");
+
+const todoTargetNoteId = (!isTaskDone && !canceled && todoDate) ? (await api.getDateNote(todoDate)).noteId : null;
+await api.setNoteToParent(note.noteId, 'TODO', todoTargetNoteId);
+
+api.refreshTree();
\ No newline at end of file
diff --git a/notes/Implementation/attribute changed/reconcileAssignments.js b/notes/Implementation/attribute changed/reconcileAssignments.js
new file mode 100644
index 0000000..310f219
--- /dev/null
+++ b/notes/Implementation/attribute changed/reconcileAssignments.js
@@ -0,0 +1,25 @@
+module.exports = async function(note, categoryRootNote, assignedCategories, labelName, isTaskDone) {
+ const found = {};
+
+ for (const categoryNote of await categoryRootNote.getChildNotes()) {
+ const label = await categoryNote.getLabel(labelName);
+
+ if (label) {
+ found[label.value] = !isTaskDone && assignedCategories.includes(label.value);
+
+ await api.toggleNoteInParent(found[label.value], note.noteId, categoryNote.noteId);
+ }
+ }
+
+ if (!isTaskDone) {
+ for (const assignedCategory of assignedCategories) {
+ if (!found[assignedCategory]) {
+ const categoryNote = (await api.createNote(categoryRootNote.noteId, assignedCategory, "", {
+ attributes: [ { type: "label", name: labelName, value: assignedCategory } ]
+ })).note;
+
+ await api.ensureNoteIsPresentInParent(note.noteId, categoryNote.noteId);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/notes/Implementation/attribute sync button.js b/notes/Implementation/attribute sync button.js
new file mode 100644
index 0000000..807d4a7
--- /dev/null
+++ b/notes/Implementation/attribute sync button.js
@@ -0,0 +1,44 @@
+api.addButtonToToolbar({
+ title: 'Sync task',
+ icon: 'sync',
+ action: async () => {
+ await api.runOnBackend(async (noteId) => {
+const note = await api.getNote(noteId);
+const attributes = await note.getAttributes();
+const todoDate = await note.getLabelValue('todoDate');
+const doneDate = await note.getLabelValue('doneDate');
+const canceled = !!(await note.getLabelValue('canceled'));
+api.log(canceled);
+const isTaskDone = !!doneDate;
+
+const canceledRootNote = await api.getNoteWithLabel('taskCanceledRoot');
+await api.toggleNoteInParent(canceled, note.noteId, canceledRootNote.noteId);
+
+const doneRootNote = await api.getNoteWithLabel('taskDoneRoot');
+await api.toggleNoteInParent(isTaskDone && !canceled, note.noteId, doneRootNote.noteId);
+
+const todoRootNote = await api.getNoteWithLabel('taskTodoRoot');
+await api.toggleNoteInParent(!isTaskDone && !canceled, note.noteId, todoRootNote.noteId);
+
+const location = await note.getLabelValue('location');
+const locationRootNote = await api.getNoteWithLabel('taskLocationRoot');
+
+await reconcileAssignments(note, locationRootNote, location ? [location] : [], 'taskLocationNote', isTaskDone);
+
+const tags = attributes.filter(attr => attr.type === 'label' && attr.name === 'tag').map(attr => attr.value);
+const tagRootNote = await api.getNoteWithLabel('taskTagRoot');
+
+await reconcileAssignments(note, tagRootNote, tags, 'taskTagNote', isTaskDone);
+
+await note.toggleLabel(isTaskDone || canceled, "cssClass", "done");
+
+const doneTargetNoteId = (isTaskDone && !canceled) ? (await api.getDateNote(doneDate)).noteId : null;
+await api.setNoteToParent(note.noteId, 'DONE', doneTargetNoteId);
+
+await note.toggleLabel(!isTaskDone && !canceled, "cssClass", "todo");
+
+const todoTargetNoteId = ((!isTaskDone || canceled) && todoDate) ? (await api.getDateNote(todoDate)).noteId : null;
+await api.setNoteToParent(note.noteId, 'TODO', todoTargetNoteId);
+ }, [api.getActiveTabNote().noteId]);
+ }
+});
\ No newline at end of file
diff --git a/notes/Implementation/attribute sync button/reconcileAssignments.clone.html b/notes/Implementation/attribute sync button/reconcileAssignments.clone.html
new file mode 100644
index 0000000..f0e986b
--- /dev/null
+++ b/notes/Implementation/attribute sync button/reconcileAssignments.clone.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+ reconcileAssignments
+
+ This is a clone of a note. Go to its primary location.
+
+
+
\ No newline at end of file
diff --git a/notes/Implementation/button.js b/notes/Implementation/button.js
new file mode 100644
index 0000000..e81217d
--- /dev/null
+++ b/notes/Implementation/button.js
@@ -0,0 +1,19 @@
+api.addButtonToToolbar({
+ title: 'New task',
+ icon: 'check',
+ shortcut: 'alt+n',
+ action: async () => {
+ // creating notes is backend (server) responsibility so we need to pass
+ // the control there
+ const taskNoteId = await api.runOnServer(async () => {
+ const todoRootNote = await api.getNoteWithLabel('taskTodoRoot');
+ const resp = await api.createTextNote(todoRootNote.noteId, 'new task', '');
+
+ return resp.note.noteId;
+ });
+
+ await api.waitUntilSynced();
+ // we got an ID of newly created note and we want to immediatelly display it
+ await api.activateNewNote(taskNoteId);
+ }
+});
\ No newline at end of file
diff --git a/notes/Implementation/daily reminder template.html b/notes/Implementation/daily reminder template.html
new file mode 100644
index 0000000..85f51e1
--- /dev/null
+++ b/notes/Implementation/daily reminder template.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ daily reminder template
+
+
+
+
\ No newline at end of file
diff --git a/notes/Implementation/event template.html b/notes/Implementation/event template.html
new file mode 100644
index 0000000..368700d
--- /dev/null
+++ b/notes/Implementation/event template.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ event template
+
+
+
+
\ No newline at end of file
diff --git a/event_alerts.js b/notes/Implementation/event_alerts handler.js
similarity index 55%
rename from event_alerts.js
rename to notes/Implementation/event_alerts handler.js
index d90e406..a7cbd6f 100644
--- a/event_alerts.js
+++ b/notes/Implementation/event_alerts handler.js
@@ -1,11 +1,3 @@
-// Create as JS Backend note and add these attributes:
-// ~targetTemplateEvent=@event template (this note is described below)
-// #customRequestHandler=event_alerts
-// Create another note (event template) with this promoted attributes:
-// startTime
-// Optionally add:
-// endTime, location
-
const {req, res} = api;
const targetTemplate = await api.currentNote.getRelationValue("targetTemplateEvent");
@@ -21,4 +13,4 @@ for (const event of events) {
});
}
-res.send(eventsData);
+res.send(eventsData);
\ No newline at end of file
diff --git a/notes/Implementation/new_event handler.js b/notes/Implementation/new_event handler.js
new file mode 100644
index 0000000..80c9901
--- /dev/null
+++ b/notes/Implementation/new_event handler.js
@@ -0,0 +1,54 @@
+function formatTime(startTime) {
+ return startTime.getFullYear().toString() + "-" + (startTime.getMonth() + 1).toString().padStart(2, "0") + "-" + startTime.getDate().toString().padStart(2, "0") + "T" + startTime.getHours().toString().padStart(2, "0") + ":" + startTime.getMinutes().toString().padStart(2, "0") + ":" + startTime.getSeconds().toString().padStart(2, "0");
+}
+
+const {req, res} = api;
+
+const uid = req.body["uid"];
+const name = req.body["name"];
+const summary = req.body["summary"];
+const location = req.body["location"];
+const fileName = req.body["fileName"];
+const fileData = req.body["fileData"];
+const startTime = new Date(req.body["startTime"]);
+const endTime = new Date(req.body["endTime"]);
+
+const year = startTime.getFullYear();
+var month = startTime.getMonth() + 1;
+var day = startTime.getDate();
+
+if (month < 10) {
+ month = '0' + month;
+}
+if (day < 10) {
+ day = '0' + day;
+}
+
+const eventDateStr = year + "-" + month + "-" + day;
+const dayNote = await api.getDateNote(eventDateStr);
+
+const targetTemplate = await api.currentNote.getRelationValue('targetTemplate');
+const options = {
+ "parentNoteId": dayNote.noteId,
+ "title": name,
+ "content": summary,
+ "type": "text"
+};
+const resp = await api.createNewNote(options);
+const note = resp.note;
+await note.setAttribute("relation", "template", targetTemplate);
+await note.setAttribute("label", "uid", uid);
+await note.setAttribute("label", "location", location);
+const startTimeStr = formatTime(startTime);
+await note.setAttribute("label", "startTime", startTimeStr);
+await note.setAttribute("label", "endTime", formatTime(endTime));
+const fileOptions = {
+ "parentNoteId": note.noteId,
+ "title": fileName,
+ "content": fileData,
+ "type": "file",
+ "mime": "text/calendar"
+};
+await api.createNewNote(fileOptions);
+
+res.sendStatus(200);
diff --git a/new_reminder.js b/notes/Implementation/new_reminder handler.js
similarity index 81%
rename from new_reminder.js
rename to notes/Implementation/new_reminder handler.js
index ba46259..add3dbd 100644
--- a/new_reminder.js
+++ b/notes/Implementation/new_reminder handler.js
@@ -1,12 +1,3 @@
-// Create as JS Backend note with these attributes:
-// ~targetTemplate=@reminder template
-// #customRequestHandler=new_reminder
-// Create a new note (reminder template) with these promoted attributes:
-// todoDate: date
-// todoTime: text
-// doneDate: date
-// reminder = true
-
Date.prototype.addHours = function(h) {
this.setTime(this.getTime() + (h*60*60*1000));
return this;
@@ -46,6 +37,8 @@ if (day < 10) {
const todayDateStr = year + "-" + month + "-" + day;
const todayNote = await api.getDateNote(todayDateStr);
+//const templateNoteId = 'LBDjD6rGxw6I';
+//const templateNote = await api.getNote('LBDjD6rGxw6I');
const targetTemplate = await api.currentNote.getAttributeValue('relation', 'targetTemplate');
const resp = await api.createNote(
todayNote.noteId,
diff --git a/notes/Implementation/reminder template.html b/notes/Implementation/reminder template.html
new file mode 100644
index 0000000..f34e589
--- /dev/null
+++ b/notes/Implementation/reminder template.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ reminder template
+
+
+
+
\ No newline at end of file
diff --git a/notes/Implementation/task template.html b/notes/Implementation/task template.html
new file mode 100644
index 0000000..7ed37c9
--- /dev/null
+++ b/notes/Implementation/task template.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ task template
+
+
+
+
\ No newline at end of file
diff --git a/notes/Implementation/task_alerts handler.js b/notes/Implementation/task_alerts handler.js
new file mode 100644
index 0000000..85d91c8
--- /dev/null
+++ b/notes/Implementation/task_alerts handler.js
@@ -0,0 +1,56 @@
+const {req, res} = api;
+
+const today = new Date().toISOString().substr(0, 10);
+
+const targetTemplate = await api.currentNote.getRelationValue('targetTemplate');
+const tasks = await api.getNotesWithLabel("template", targetTemplate);
+
+let tasksData = [];
+
+for (const task of tasks) {
+ const todoDate = task.getAttribute("label", "todoDate");
+ if (!todoDate || todoDate["value"] < today) {
+ continue;
+ }
+ tasksData.push({
+ attributes: await task.getAttributes(),
+ ...task
+ });
+}
+
+const targetTemplateReminder = await api.currentNote.getRelationValue('targetTemplateReminder');
+const reminders = await api.getNotesWithLabel("template", targetTemplateReminder);
+for (const task of reminders) {
+ const todoDate = task.getAttribute("label", "todoDate");
+ if (!todoDate || todoDate["value"] < today) {
+ continue;
+ }
+ tasksData.push({
+ attributes: await task.getAttributes(),
+ ...task
+ });
+}
+
+const targetTemplateReminderDaily = await api.currentNote.getRelationValue('targetTemplateReminderDaily');
+const remindersDaily = await api.getNotesWithLabel("template", targetTemplateReminderDaily);
+for (const task of remindersDaily) {
+ const attributes = await task.getAttributes();
+ attributes.push({
+ "type": "label",
+ "name": "todoDate",
+ "value": today,
+ // API compliance
+ "attributeId": "",
+ "noteId": "",
+ "position": 0,
+ "utcDateModified": "2021-04-23 08:20:14.295Z",
+ "isDeleted": false,
+ "isInheritable": false
+ });
+ tasksData.push({
+ attributes: attributes,
+ ...task
+ });
+}
+
+res.send(tasksData);
diff --git a/notes/index.html b/notes/index.html
new file mode 100644
index 0000000..a3ecbc3
--- /dev/null
+++ b/notes/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/notes/navigation.html b/notes/navigation.html
new file mode 100644
index 0000000..f5a878e
--- /dev/null
+++ b/notes/navigation.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/notes/style.css b/notes/style.css
new file mode 100644
index 0000000..2ff1779
--- /dev/null
+++ b/notes/style.css
@@ -0,0 +1,339 @@
+/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
+
+.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
+ display: none;
+}
+
+/*
+ * CKEditor 5 (v24.0.0) content styles.
+ * Generated on Thu, 10 Dec 2020 08:15:26 GMT.
+ * For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html
+ */
+
+:root {
+ --ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
+ --ck-color-mention-text: hsl(341, 100%, 30%);
+ --ck-highlight-marker-blue: hsl(201, 97%, 72%);
+ --ck-highlight-marker-green: hsl(120, 93%, 68%);
+ --ck-highlight-marker-pink: hsl(345, 96%, 73%);
+ --ck-highlight-marker-yellow: hsl(60, 97%, 73%);
+ --ck-highlight-pen-green: hsl(112, 100%, 27%);
+ --ck-highlight-pen-red: hsl(0, 85%, 49%);
+ --ck-image-style-spacing: 1.5em;
+ --ck-todo-list-checkmark-size: 16px;
+}
+
+/* ckeditor5-image/theme/imageresize.css */
+.ck-content .image.image_resized {
+ max-width: 100%;
+ display: block;
+ box-sizing: border-box;
+}
+/* ckeditor5-image/theme/imageresize.css */
+.ck-content .image.image_resized img {
+ width: 100%;
+}
+/* ckeditor5-image/theme/imageresize.css */
+.ck-content .image.image_resized > figcaption {
+ display: block;
+}
+/* ckeditor5-image/theme/imagestyle.css */
+.ck-content .image-style-side {
+ float: right;
+ margin-left: var(--ck-image-style-spacing);
+ max-width: 50%;
+}
+/* ckeditor5-image/theme/imagestyle.css */
+.ck-content .image-style-align-left {
+ float: left;
+ margin-right: var(--ck-image-style-spacing);
+}
+/* ckeditor5-image/theme/imagestyle.css */
+.ck-content .image-style-align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+/* ckeditor5-image/theme/imagestyle.css */
+.ck-content .image-style-align-right {
+ float: right;
+ margin-left: var(--ck-image-style-spacing);
+}
+/* ckeditor5-image/theme/image.css */
+.ck-content .image {
+ display: table;
+ clear: both;
+ text-align: center;
+ margin: 1em auto;
+}
+/* ckeditor5-image/theme/image.css */
+.ck-content .image img {
+ display: block;
+ margin: 0 auto;
+ max-width: 100%;
+ min-width: 50px;
+}
+/* ckeditor5-image/theme/imagecaption.css */
+.ck-content .image > figcaption {
+ display: table-caption;
+ caption-side: bottom;
+ word-break: break-word;
+ color: hsl(0, 0%, 20%);
+ background-color: hsl(0, 0%, 97%);
+ padding: .6em;
+ font-size: .75em;
+ outline-offset: -1px;
+}
+/* ckeditor5-highlight/theme/highlight.css */
+.ck-content .marker-yellow {
+ background-color: var(--ck-highlight-marker-yellow);
+}
+/* ckeditor5-highlight/theme/highlight.css */
+.ck-content .marker-green {
+ background-color: var(--ck-highlight-marker-green);
+}
+/* ckeditor5-highlight/theme/highlight.css */
+.ck-content .marker-pink {
+ background-color: var(--ck-highlight-marker-pink);
+}
+/* ckeditor5-highlight/theme/highlight.css */
+.ck-content .marker-blue {
+ background-color: var(--ck-highlight-marker-blue);
+}
+/* ckeditor5-highlight/theme/highlight.css */
+.ck-content .pen-red {
+ color: var(--ck-highlight-pen-red);
+ background-color: transparent;
+}
+/* ckeditor5-highlight/theme/highlight.css */
+.ck-content .pen-green {
+ color: var(--ck-highlight-pen-green);
+ background-color: transparent;
+}
+/* ckeditor5-font/theme/fontsize.css */
+.ck-content .text-tiny {
+ font-size: .7em;
+}
+/* ckeditor5-font/theme/fontsize.css */
+.ck-content .text-small {
+ font-size: .85em;
+}
+/* ckeditor5-font/theme/fontsize.css */
+.ck-content .text-big {
+ font-size: 1.4em;
+}
+/* ckeditor5-font/theme/fontsize.css */
+.ck-content .text-huge {
+ font-size: 1.8em;
+}
+/* ckeditor5-block-quote/theme/blockquote.css */
+.ck-content blockquote {
+ overflow: hidden;
+ padding-right: 1.5em;
+ padding-left: 1.5em;
+ margin-left: 0;
+ margin-right: 0;
+ font-style: italic;
+ border-left: solid 5px hsl(0, 0%, 80%);
+}
+/* ckeditor5-block-quote/theme/blockquote.css */
+.ck-content[dir="rtl"] blockquote {
+ border-left: 0;
+ border-right: solid 5px hsl(0, 0%, 80%);
+}
+/* ckeditor5-basic-styles/theme/code.css */
+.ck-content code {
+ background-color: hsla(0, 0%, 78%, 0.3);
+ padding: .15em;
+ border-radius: 2px;
+}
+/* ckeditor5-table/theme/table.css */
+.ck-content .table {
+ margin: 1em auto;
+ display: table;
+}
+/* ckeditor5-table/theme/table.css */
+.ck-content .table table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ width: 100%;
+ height: 100%;
+ border: 1px double hsl(0, 0%, 70%);
+}
+/* ckeditor5-table/theme/table.css */
+.ck-content .table table td,
+.ck-content .table table th {
+ min-width: 2em;
+ padding: .4em;
+ border: 1px solid hsl(0, 0%, 75%);
+}
+/* ckeditor5-table/theme/table.css */
+.ck-content .table table th {
+ font-weight: bold;
+ background: hsla(0, 0%, 0%, 5%);
+}
+/* ckeditor5-table/theme/table.css */
+.ck-content[dir="rtl"] .table th {
+ text-align: right;
+}
+/* ckeditor5-table/theme/table.css */
+.ck-content[dir="ltr"] .table th {
+ text-align: left;
+}
+/* ckeditor5-page-break/theme/pagebreak.css */
+.ck-content .page-break {
+ position: relative;
+ clear: both;
+ padding: 5px 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+/* ckeditor5-page-break/theme/pagebreak.css */
+.ck-content .page-break::after {
+ content: '';
+ position: absolute;
+ border-bottom: 2px dashed hsl(0, 0%, 77%);
+ width: 100%;
+}
+/* ckeditor5-page-break/theme/pagebreak.css */
+.ck-content .page-break__label {
+ position: relative;
+ z-index: 1;
+ padding: .3em .6em;
+ display: block;
+ text-transform: uppercase;
+ border: 1px solid hsl(0, 0%, 77%);
+ border-radius: 2px;
+ font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
+ font-size: 0.75em;
+ font-weight: bold;
+ color: hsl(0, 0%, 20%);
+ background: hsl(0, 0%, 100%);
+ box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+/* ckeditor5-media-embed/theme/mediaembed.css */
+.ck-content .media {
+ clear: both;
+ margin: 1em 0;
+ display: block;
+ min-width: 15em;
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list {
+ list-style: none;
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list li {
+ margin-bottom: 5px;
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list li .todo-list {
+ margin-top: 5px;
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list .todo-list__label > input {
+ -webkit-appearance: none;
+ display: inline-block;
+ position: relative;
+ width: var(--ck-todo-list-checkmark-size);
+ height: var(--ck-todo-list-checkmark-size);
+ vertical-align: middle;
+ border: 0;
+ left: -25px;
+ margin-right: -15px;
+ right: 0;
+ margin-left: 0;
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list .todo-list__label > input::before {
+ display: block;
+ position: absolute;
+ box-sizing: border-box;
+ content: '';
+ width: 100%;
+ height: 100%;
+ border: 1px solid hsl(0, 0%, 20%);
+ border-radius: 2px;
+ transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border;
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list .todo-list__label > input::after {
+ display: block;
+ position: absolute;
+ box-sizing: content-box;
+ pointer-events: none;
+ content: '';
+ left: calc( var(--ck-todo-list-checkmark-size) / 3 );
+ top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
+ width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
+ height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
+ border-style: solid;
+ border-color: transparent;
+ border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
+ transform: rotate(45deg);
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list .todo-list__label > input[checked]::before {
+ background: hsl(126, 64%, 41%);
+ border-color: hsl(126, 64%, 41%);
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list .todo-list__label > input[checked]::after {
+ border-color: hsl(0, 0%, 100%);
+}
+/* ckeditor5-list/theme/todolist.css */
+.ck-content .todo-list .todo-list__label .todo-list__label__description {
+ vertical-align: middle;
+}
+/* ckeditor5-html-embed/theme/htmlembed.css */
+.ck-content .raw-html-embed {
+ margin: 1em auto;
+ min-width: 15em;
+ font-style: normal;
+}
+/* ckeditor5-horizontal-line/theme/horizontalline.css */
+.ck-content hr {
+ margin: 15px 0;
+ height: 4px;
+ background: hsl(0, 0%, 87%);
+ border: 0;
+}
+/* ckeditor5-code-block/theme/codeblock.css */
+.ck-content pre {
+ padding: 1em;
+ color: hsl(0, 0%, 20.8%);
+ background: hsla(0, 0%, 78%, 0.3);
+ border: 1px solid hsl(0, 0%, 77%);
+ border-radius: 2px;
+ text-align: left;
+ direction: ltr;
+ tab-size: 4;
+ white-space: pre-wrap;
+ font-style: normal;
+ min-width: 200px;
+}
+/* ckeditor5-code-block/theme/codeblock.css */
+.ck-content pre code {
+ background: unset;
+ padding: 0;
+ border-radius: 0;
+}
+/* ckeditor5-mention/theme/mention.css */
+.ck-content .mention {
+ background: var(--ck-color-mention-background);
+ color: var(--ck-color-mention-text);
+}
+@media print {
+ /* ckeditor5-page-break/theme/pagebreak.css */
+ .ck-content .page-break {
+ padding: 0;
+ }
+ /* ckeditor5-page-break/theme/pagebreak.css */
+ .ck-content .page-break::after {
+ display: none;
+ }
+}
diff --git a/task_alerts.js b/task_alerts.js
deleted file mode 100644
index 04218ea..0000000
--- a/task_alerts.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Create as JS Backend note with attributes:
-// ~targetTemplate=@task template (included in Trilium task manager)
-// #customRequestHandler=task_alerts
-// ~targetTemplateReminder=@reminder template (see new_reminder.js)
-
-const {req, res} = api;
-
-const targetTemplate = await api.currentNote.getRelationValue('targetTemplate');
-const tasks = await api.getNotesWithLabel("template", targetTemplate);
-
-let tasksData = [];
-
-for (const task of tasks) {
- tasksData.push({
- attributes: await task.getAttributes(),
- ...task
- });
-}
-
-const targetTemplateReminder = await api.currentNote.getRelationValue('targetTemplateReminder');
-const reminders = await api.getNotesWithLabel("template", targetTemplateReminder);
-for (const task of reminders) {
- tasksData.push({
- attributes: await task.getAttributes(),
- ...task
- });
-}
-
-res.send(tasksData);