From 119d0858569abc6b488d1692fdb6ed66a6d38a92 Mon Sep 17 00:00:00 2001 From: azivner Date: Sat, 2 Dec 2017 21:48:22 -0500 Subject: [PATCH 1/8] export subtree to filesystem --- package.json | 1 + routes/api/export.js | 49 ++++++++++++++++++++++++++++++++++++++++++++ routes/routes.js | 2 ++ services/data_dir.js | 4 +++- 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 routes/api/export.js diff --git a/package.json b/package.json index b0dae4057..8433c09fa 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "ini": "^1.3.4", "request": "^2.83.0", "request-promise": "^4.2.2", + "rimraf": "^2.6.2", "scrypt": "^6.0.3", "serve-favicon": "~2.4.5", "session-file-store": "^1.1.2", diff --git a/routes/api/export.js b/routes/api/export.js new file mode 100644 index 000000000..d0770eae0 --- /dev/null +++ b/routes/api/export.js @@ -0,0 +1,49 @@ +"use strict"; + +const express = require('express'); +const router = express.Router(); +const rimraf = require('rimraf'); +const fs = require('fs'); +const sql = require('../../services/sql'); +const data_dir = require('../../services/data_dir'); + +router.get('/:noteId/to/:directory', async (req, res, next) => { + const noteId = req.params.noteId; + const directory = req.params.directory.replace(/[^0-9a-zA-Z_-]/gi, ''); + + if (!fs.existsSync(data_dir.EXPORT_DIR)) { + fs.mkdirSync(data_dir.EXPORT_DIR); + } + + const completeExportDir = data_dir.EXPORT_DIR + '/' + directory; + + if (fs.existsSync(completeExportDir)) { + rimraf.sync(completeExportDir); + } + + fs.mkdirSync(completeExportDir); + + await exportNote(noteId, completeExportDir); + + res.send({}); +}); + +async function exportNote(noteId, dir) { + const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]); + + fs.writeFileSync(dir + '/' + note.note_title + '.html', note.note_text); + + const children = await sql.getResults("SELECT * FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [noteId]); + + if (children.length > 0) { + const childrenDir = dir + '/' + note.note_title; + + fs.mkdirSync(childrenDir); + + for (const child of children) { + await exportNote(child.note_id, childrenDir); + } + } +} + +module.exports = router; \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 2801a32b0..9b689a3f8 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -17,6 +17,7 @@ const loginApiRoute = require('./api/login'); const eventLogRoute = require('./api/event_log'); const recentNotesRoute = require('./api/recent_notes'); const appInfoRoute = require('./api/app_info'); +const exportRoute = require('./api/export'); function register(app) { app.use('/', indexRoute); @@ -37,6 +38,7 @@ function register(app) { app.use('/api/event-log', eventLogRoute); app.use('/api/recent-notes', recentNotesRoute); app.use('/api/app-info', appInfoRoute); + app.use('/api/export', exportRoute); } module.exports = { diff --git a/services/data_dir.js b/services/data_dir.js index afeef7c29..44e295042 100644 --- a/services/data_dir.js +++ b/services/data_dir.js @@ -12,10 +12,12 @@ if (!fs.existsSync(TRILIUM_DATA_DIR)) { const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db"; const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup"; const LOG_DIR = TRILIUM_DATA_DIR + "/log"; +const EXPORT_DIR = TRILIUM_DATA_DIR + "/export"; module.exports = { TRILIUM_DATA_DIR, DOCUMENT_PATH, BACKUP_DIR, - LOG_DIR + LOG_DIR, + EXPORT_DIR }; \ No newline at end of file From cd3c6d7e3b1065c796f925fb478a28d12d73ad3f Mon Sep 17 00:00:00 2001 From: azivner Date: Sat, 2 Dec 2017 23:41:18 -0500 Subject: [PATCH 2/8] note import from directory --- routes/api/import.js | 86 ++++++++++++++++++++++++++++++++++++++++++++ routes/routes.js | 2 ++ 2 files changed, 88 insertions(+) create mode 100644 routes/api/import.js diff --git a/routes/api/import.js b/routes/api/import.js new file mode 100644 index 000000000..fd32cf4bf --- /dev/null +++ b/routes/api/import.js @@ -0,0 +1,86 @@ +"use strict"; + +const express = require('express'); +const router = express.Router(); +const rimraf = require('rimraf'); +const fs = require('fs'); +const sql = require('../../services/sql'); +const data_dir = require('../../services/data_dir'); +const utils = require('../../services/utils'); +const sync_table = require('../../services/sync_table'); + +router.get('/:directory/to/:parentNoteId', async (req, res, next) => { + const directory = req.params.directory.replace(/[^0-9a-zA-Z_-]/gi, ''); + const parentNoteId = req.params.parentNoteId; + + const dir = data_dir.EXPORT_DIR + '/' + directory; + + await sql.doInTransaction(async () => await importNotes(dir, parentNoteId)); + + res.send({}); +}); + +async function importNotes(dir, parentNoteId) { + const parent = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [parentNoteId]); + + if (!parent) { + return; + } + + const fileList = fs.readdirSync(dir); + + for (const file of fileList) { + const path = dir + '/' + file; + + if (fs.lstatSync(path).isDirectory()) { + continue; + } + + if (!file.endsWith('.html')) { + continue; + } + + const noteTitle = file.substr(0, file.length - 5); + const noteText = fs.readFileSync(path, "utf8"); + + let maxPos = await sql.getSingleValue("SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [parentNoteId]); + if (!maxPos) { + maxPos = 1; + } + + const noteId = utils.newNoteId(); + const noteTreeId = utils.newNoteHistoryId(); + + await sql.insert('notes_tree', { + note_tree_id: noteTreeId, + note_id: noteId, + note_pid: parentNoteId, + note_pos: maxPos + 1, + is_expanded: 0, + is_deleted: 0, + date_modified: utils.nowTimestamp() + }); + + await sync_table.addNoteTreeSync(noteTreeId); + + await sql.insert('notes', { + note_id: noteId, + note_title: noteTitle, + note_text: noteText, + is_deleted: 0, + is_protected: 0, + date_created: utils.nowTimestamp(), + date_modified: utils.nowTimestamp() + }); + + await sync_table.addNoteSync(noteId); + + const noteDir = dir + '/' + noteTitle; + + if (fs.existsSync(noteDir) && fs.lstatSync(noteDir).isDirectory()) { + await importNotes(noteDir, noteId); + } + } +} + +module.exports = router; \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 9b689a3f8..37aa816b4 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -18,6 +18,7 @@ const eventLogRoute = require('./api/event_log'); const recentNotesRoute = require('./api/recent_notes'); const appInfoRoute = require('./api/app_info'); const exportRoute = require('./api/export'); +const importRoute = require('./api/import'); function register(app) { app.use('/', indexRoute); @@ -39,6 +40,7 @@ function register(app) { app.use('/api/recent-notes', recentNotesRoute); app.use('/api/app-info', appInfoRoute); app.use('/api/export', exportRoute); + app.use('/api/import', importRoute); } module.exports = { From ddd216c92e0b77bae8e36f62a4f2e1d7e90a4eaa Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 00:10:43 -0500 Subject: [PATCH 3/8] export with pos in filename and import so we preserve note order --- routes/api/export.js | 19 ++++++++++++------- routes/api/import.js | 30 +++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/routes/api/export.js b/routes/api/export.js index d0770eae0..7a26ce8af 100644 --- a/routes/api/export.js +++ b/routes/api/export.js @@ -23,25 +23,30 @@ router.get('/:noteId/to/:directory', async (req, res, next) => { fs.mkdirSync(completeExportDir); - await exportNote(noteId, completeExportDir); + const noteTreeId = await sql.getSingleValue('SELECT note_tree_id FROM notes_tree WHERE note_id = ?', [noteId]); + + await exportNote(noteTreeId, completeExportDir); res.send({}); }); -async function exportNote(noteId, dir) { - const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]); +async function exportNote(noteTreeId, dir) { + const noteTree = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]); + const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteTree.note_id]); - fs.writeFileSync(dir + '/' + note.note_title + '.html', note.note_text); + const pos = (noteTree.note_pos + '').padStart(4, '0'); - const children = await sql.getResults("SELECT * FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [noteId]); + fs.writeFileSync(dir + '/' + pos + '-' + note.note_title + '.html', note.note_text); + + const children = await sql.getResults("SELECT * FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [note.note_id]); if (children.length > 0) { - const childrenDir = dir + '/' + note.note_title; + const childrenDir = dir + '/' + pos + '-' + note.note_title; fs.mkdirSync(childrenDir); for (const child of children) { - await exportNote(child.note_id, childrenDir); + await exportNote(child.note_tree_id, childrenDir); } } } diff --git a/routes/api/import.js b/routes/api/import.js index fd32cf4bf..f9e251a6f 100644 --- a/routes/api/import.js +++ b/routes/api/import.js @@ -40,13 +40,29 @@ async function importNotes(dir, parentNoteId) { continue; } - const noteTitle = file.substr(0, file.length - 5); - const noteText = fs.readFileSync(path, "utf8"); + const fileNameWithoutExt = file.substr(0, file.length - 5); - let maxPos = await sql.getSingleValue("SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [parentNoteId]); - if (!maxPos) { - maxPos = 1; + let noteTitle; + let notePos; + + const match = fileNameWithoutExt.match(/^([0-9]{4})-(.*)$/); + if (match) { + notePos = parseInt(match[1]); + noteTitle = match[2]; } + else { + let maxPos = await sql.getSingleValue("SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [parentNoteId]); + if (maxPos) { + notePos = maxPos + 1; + } + else { + notePos = 0; + } + + noteTitle = fileNameWithoutExt; + } + + const noteText = fs.readFileSync(path, "utf8"); const noteId = utils.newNoteId(); const noteTreeId = utils.newNoteHistoryId(); @@ -55,7 +71,7 @@ async function importNotes(dir, parentNoteId) { note_tree_id: noteTreeId, note_id: noteId, note_pid: parentNoteId, - note_pos: maxPos + 1, + note_pos: notePos, is_expanded: 0, is_deleted: 0, date_modified: utils.nowTimestamp() @@ -75,7 +91,7 @@ async function importNotes(dir, parentNoteId) { await sync_table.addNoteSync(noteId); - const noteDir = dir + '/' + noteTitle; + const noteDir = dir + '/' + fileNameWithoutExt; if (fs.existsSync(noteDir) && fs.lstatSync(noteDir).isDirectory()) { await importNotes(noteDir, noteId); From 41f089b3f412f5823b10c37023f19400c09774b1 Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 09:19:48 -0500 Subject: [PATCH 4/8] pretty printing HTML output (for nice git diffs) --- package-lock.json | 8 ++++++++ package.json | 1 + routes/api/export.js | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 76e20980e..5ec4549df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4264,6 +4264,14 @@ "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.1.0.tgz", "integrity": "sha512-zXhh/DqgrTXJ7erTN6Fh5k/xjMhDGXCqdYN3wvxUvGUQvnxcFfUd8E+6vLg/nk3ss1TYMb+DhRl25fYABioTvA==" }, + "html": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz", + "integrity": "sha1-pUT6nqVJK/s6LMqCEKEL57WvH2E=", + "requires": { + "concat-stream": "1.6.0" + } + }, "html-comment-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", diff --git a/package.json b/package.json index 8433c09fa..2476f4942 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "express-session": "^1.15.6", "fs-extra": "^4.0.2", "helmet": "^3.9.0", + "html": "^1.0.0", "ini": "^1.3.4", "request": "^2.83.0", "request-promise": "^4.2.2", diff --git a/routes/api/export.js b/routes/api/export.js index 7a26ce8af..cfa4d539e 100644 --- a/routes/api/export.js +++ b/routes/api/export.js @@ -6,6 +6,7 @@ const rimraf = require('rimraf'); const fs = require('fs'); const sql = require('../../services/sql'); const data_dir = require('../../services/data_dir'); +const html = require('html'); router.get('/:noteId/to/:directory', async (req, res, next) => { const noteId = req.params.noteId; @@ -36,7 +37,7 @@ async function exportNote(noteTreeId, dir) { const pos = (noteTree.note_pos + '').padStart(4, '0'); - fs.writeFileSync(dir + '/' + pos + '-' + note.note_title + '.html', note.note_text); + fs.writeFileSync(dir + '/' + pos + '-' + note.note_title + '.html', html.prettyPrint(note.note_text, {indent_size: 2})); const children = await sql.getResults("SELECT * FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [note.note_id]); From 15faefe8a3bf011fc838c49c49fd9cf55972ed2b Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 10:06:53 -0500 Subject: [PATCH 5/8] recent notes are now keyed by note tree id which simplifies things --- ...0048__add_note_tree_id_to_recent_notes.sql | 10 ++++++++++ public/javascripts/dialogs/recent_notes.js | 17 +++++----------- public/javascripts/note_tree.js | 9 +++++---- public/javascripts/tree_changes.js | 20 ++++--------------- routes/api/recent_notes.js | 18 ++++------------- routes/api/sync.js | 6 +++--- services/content_hash.js | 2 +- services/migration.js | 2 +- services/sync.js | 2 +- services/sync_update.js | 4 ++-- 10 files changed, 36 insertions(+), 54 deletions(-) create mode 100644 migrations/0048__add_note_tree_id_to_recent_notes.sql diff --git a/migrations/0048__add_note_tree_id_to_recent_notes.sql b/migrations/0048__add_note_tree_id_to_recent_notes.sql new file mode 100644 index 000000000..3fbc03066 --- /dev/null +++ b/migrations/0048__add_note_tree_id_to_recent_notes.sql @@ -0,0 +1,10 @@ +DROP TABLE recent_notes; + +CREATE TABLE `recent_notes` ( + 'note_tree_id'TEXT NOT NULL PRIMARY KEY, + `note_path` TEXT NOT NULL, + `date_accessed` INTEGER NOT NULL , + is_deleted INT +); + +DELETE FROM sync WHERE entity_name = 'recent_notes'; \ No newline at end of file diff --git a/public/javascripts/dialogs/recent_notes.js b/public/javascripts/dialogs/recent_notes.js index 1c1e8d29e..372288129 100644 --- a/public/javascripts/dialogs/recent_notes.js +++ b/public/javascripts/dialogs/recent_notes.js @@ -8,30 +8,24 @@ const recentNotes = (function() { const addCurrentAsChildEl = $("#recent-notes-add-current-as-child"); const addRecentAsChildEl = $("#recent-notes-add-recent-as-child"); const noteDetailEl = $('#note-detail'); + // list of recent note paths let list = []; server.get('recent-notes').then(result => { - list = result.map(r => r.note_tree_id); + list = result.map(r => r.note_path); }); - function addRecentNote(notePath) { + function addRecentNote(noteTreeId, notePath) { setTimeout(async () => { // we include the note into recent list only if the user stayed on the note at least 5 seconds if (notePath && notePath === noteTree.getCurrentNotePath()) { - const result = await server.put('recent-notes/' + encodeURIComponent(notePath)); + const result = await server.put('recent-notes/' + noteTreeId + '/' + encodeURIComponent(notePath)); list = result.map(r => r.note_path); } }, 1500); } - // FIXME: this should be probably just refresh upon deletion, not explicit delete - async function removeRecentNote(notePathIdToRemove) { - const result = await server.remove('recent-notes/' + encodeURIComponent(notePathIdToRemove)); - - list = result.map(r => r.note_path); - } - function showDialog() { glob.activeDialog = dialogEl; @@ -146,7 +140,6 @@ const recentNotes = (function() { return { showDialog, - addRecentNote, - removeRecentNote + addRecentNote }; })(); \ No newline at end of file diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js index 4ef6e0756..edd23cf10 100644 --- a/public/javascripts/note_tree.js +++ b/public/javascripts/note_tree.js @@ -319,10 +319,11 @@ const noteTree = (function() { function setCurrentNotePathToHash(node) { const currentNotePath = treeUtils.getNotePath(node); + const currentNoteTreeId = node.data.note_tree_id; document.location.hash = currentNotePath; - recentNotes.addRecentNote(currentNotePath); + recentNotes.addRecentNote(currentNoteTreeId, currentNotePath); } function initFancyTree(noteTree) { @@ -343,13 +344,13 @@ const noteTree = (function() { const beforeNode = node.getPrevSibling(); if (beforeNode !== null) { - treeChanges.moveBeforeNode(node, beforeNode, false); + treeChanges.moveBeforeNode(node, beforeNode); } }, "shift+down": node => { let afterNode = node.getNextSibling(); if (afterNode !== null) { - treeChanges.moveAfterNode(node, afterNode, false); + treeChanges.moveAfterNode(node, afterNode); } }, "shift+left": node => { @@ -625,6 +626,6 @@ const noteTree = (function() { createNewTopLevelNote, createNote, setPrefix, - + getNotePathTitle }; })(); \ No newline at end of file diff --git a/public/javascripts/tree_changes.js b/public/javascripts/tree_changes.js index 94e8dc0cb..c18e35d97 100644 --- a/public/javascripts/tree_changes.js +++ b/public/javascripts/tree_changes.js @@ -1,28 +1,20 @@ "use strict"; const treeChanges = (function() { - async function moveBeforeNode(node, beforeNode, changeInPath = true) { + async function moveBeforeNode(node, beforeNode) { await server.put('notes/' + node.data.note_tree_id + '/move-before/' + beforeNode.data.note_tree_id); node.moveTo(beforeNode, 'before'); - if (changeInPath) { - recentNotes.removeRecentNote(noteTree.getCurrentNotePath()); - - noteTree.setCurrentNotePathToHash(node); - } + noteTree.setCurrentNotePathToHash(node); } - async function moveAfterNode(node, afterNode, changeInPath = true) { + async function moveAfterNode(node, afterNode) { await server.put('notes/' + node.data.note_tree_id + '/move-after/' + afterNode.data.note_tree_id); node.moveTo(afterNode, 'after'); - if (changeInPath) { - recentNotes.removeRecentNote(noteTree.getCurrentNotePath()); - - noteTree.setCurrentNotePathToHash(node); - } + noteTree.setCurrentNotePathToHash(node); } // beware that first arg is noteId and second is noteTreeId! @@ -47,8 +39,6 @@ const treeChanges = (function() { toNode.folder = true; toNode.renderTitle(); - recentNotes.removeRecentNote(noteTree.getCurrentNotePath()); - noteTree.setCurrentNotePathToHash(node); } @@ -75,8 +65,6 @@ const treeChanges = (function() { node.getParent().renderTitle(); } - recentNotes.removeRecentNote(noteTree.getCurrentNotePath()); - let next = node.getNextSibling(); if (!next) { next = node.getParent(); diff --git a/routes/api/recent_notes.js b/routes/api/recent_notes.js index a3c6a807a..1b78c8151 100644 --- a/routes/api/recent_notes.js +++ b/routes/api/recent_notes.js @@ -12,17 +12,19 @@ router.get('', auth.checkApiAuth, async (req, res, next) => { res.send(await getRecentNotes()); }); -router.put('/:notePath', auth.checkApiAuth, async (req, res, next) => { +router.put('/:noteTreeId/:notePath', auth.checkApiAuth, async (req, res, next) => { + const noteTreeId = req.params.noteTreeId; const notePath = req.params.notePath; await sql.doInTransaction(async () => { await sql.replace('recent_notes', { + note_tree_id: noteTreeId, note_path: notePath, date_accessed: utils.nowTimestamp(), is_deleted: 0 }); - await sync_table.addRecentNoteSync(notePath); + await sync_table.addRecentNoteSync(noteTreeId); await options.setOption('start_note_tree_id', notePath); }); @@ -30,18 +32,6 @@ router.put('/:notePath', auth.checkApiAuth, async (req, res, next) => { res.send(await getRecentNotes()); }); -router.delete('/:notePath', auth.checkApiAuth, async (req, res, next) => { - const notePath = req.params.notePath; - - await sql.doInTransaction(async () => { - await sql.execute('UPDATE recent_notes SET is_deleted = 1 WHERE note_path = ?', [notePath]); - - await sync_table.addRecentNoteSync(notePath); - }); - - res.send(await getRecentNotes()); -}); - async function getRecentNotes() { await deleteOld(); diff --git a/routes/api/sync.js b/routes/api/sync.js index 18fc9c640..ed9a2e361 100644 --- a/routes/api/sync.js +++ b/routes/api/sync.js @@ -66,10 +66,10 @@ router.get('/notes_reordering/:noteTreeParentId', auth.checkApiAuth, async (req, }); }); -router.get('/recent_notes/:notePath', auth.checkApiAuth, async (req, res, next) => { - const notePath = req.params.notePath; +router.get('/recent_notes/:noteTreeId', auth.checkApiAuth, async (req, res, next) => { + const noteTreeId = req.params.noteTreeId; - res.send(await sql.getSingleResult("SELECT * FROM recent_notes WHERE note_path = ?", [notePath])); + res.send(await sql.getSingleResult("SELECT * FROM recent_notes WHERE note_tree_id = ?", [noteTreeId])); }); router.put('/notes', auth.checkApiAuth, async (req, res, next) => { diff --git a/services/content_hash.js b/services/content_hash.js index 0bf118761..e0494d34b 100644 --- a/services/content_hash.js +++ b/services/content_hash.js @@ -22,7 +22,7 @@ async function getContentHash() { hash = updateHash(hash, await sql.getResults("SELECT note_history_id, note_id, note_title, note_text, " + "date_modified_from, date_modified_to FROM notes_history ORDER BY note_history_id")); - hash = updateHash(hash, await sql.getResults("SELECT note_path, date_accessed, is_deleted FROM recent_notes " + + hash = updateHash(hash, await sql.getResults("SELECT note_tree_id, note_path, date_accessed, is_deleted FROM recent_notes " + "ORDER BY note_path")); const questionMarks = Array(options.SYNCED_OPTIONS.length).fill('?').join(','); diff --git a/services/migration.js b/services/migration.js index 1ab2ed09b..39771eac4 100644 --- a/services/migration.js +++ b/services/migration.js @@ -4,7 +4,7 @@ const options = require('./options'); const fs = require('fs-extra'); const log = require('./log'); -const APP_DB_VERSION = 47; +const APP_DB_VERSION = 48; const MIGRATIONS_DIR = "migrations"; async function migrate() { diff --git a/services/sync.js b/services/sync.js index cd1fb0a7c..d59a74986 100644 --- a/services/sync.js +++ b/services/sync.js @@ -224,7 +224,7 @@ async function readAndPushEntity(sync, syncContext) { entity = await sql.getSingleResult('SELECT * FROM options WHERE opt_name = ?', [sync.entity_id]); } else if (sync.entity_name === 'recent_notes') { - entity = await sql.getSingleResult('SELECT * FROM recent_notes WHERE note_path = ?', [sync.entity_id]); + entity = await sql.getSingleResult('SELECT * FROM recent_notes WHERE note_tree_id = ?', [sync.entity_id]); } else { throw new Error("Unrecognized entity type " + sync.entity_name); diff --git a/services/sync_update.js b/services/sync_update.js index 034fe03f1..5b486cd28 100644 --- a/services/sync_update.js +++ b/services/sync_update.js @@ -92,13 +92,13 @@ async function updateOptions(entity, sourceId) { } async function updateRecentNotes(entity, sourceId) { - const orig = await sql.getSingleResultOrNull("SELECT * FROM recent_notes WHERE note_path = ?", [entity.note_path]); + const orig = await sql.getSingleResultOrNull("SELECT * FROM recent_notes WHERE note_tree_id = ?", [entity.note_tree_id]); if (orig === null || orig.date_accessed < entity.date_accessed) { await sql.doInTransaction(async () => { await sql.replace('recent_notes', entity); - await sync_table.addRecentNoteSync(entity.note_path, sourceId); + await sync_table.addRecentNoteSync(entity.note_tree_id, sourceId); }); } } From 28bc44391466a7e59590731d29e5c1716b530e06 Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 10:12:16 -0500 Subject: [PATCH 6/8] using WSS protocol when on HTTPS instead of WS --- public/javascripts/messaging.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/javascripts/messaging.js b/public/javascripts/messaging.js index f0977a40c..3e8948174 100644 --- a/public/javascripts/messaging.js +++ b/public/javascripts/messaging.js @@ -39,9 +39,11 @@ const messaging = (function() { } function connectWebSocket() { + const protocol = document.location.protocol === 'https:' ? 'wss' : 'ws'; + // use wss for secure messaging - ws = new WebSocket("ws://" + location.host); - ws.onopen = function (event) {}; + ws = new WebSocket(protocol + "://" + location.host); + ws.onopen = event => console.log("Connected to server with WebSocket"); ws.onmessage = messageHandler; ws.onclose = function(){ // Try to reconnect in 5 seconds From 34f1eb930c01b6c34eaa18b0a514bc6da77acbf8 Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 10:42:23 -0500 Subject: [PATCH 7/8] recent notes are reloaded if synced --- public/javascripts/dialogs/recent_notes.js | 11 ++++++++--- public/javascripts/messaging.js | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/public/javascripts/dialogs/recent_notes.js b/public/javascripts/dialogs/recent_notes.js index 372288129..014f070bc 100644 --- a/public/javascripts/dialogs/recent_notes.js +++ b/public/javascripts/dialogs/recent_notes.js @@ -11,9 +11,11 @@ const recentNotes = (function() { // list of recent note paths let list = []; - server.get('recent-notes').then(result => { + async function reload() { + const result = await server.get('recent-notes'); + list = result.map(r => r.note_path); - }); + } function addRecentNote(noteTreeId, notePath) { setTimeout(async () => { @@ -127,6 +129,8 @@ const recentNotes = (function() { e.preventDefault(); }); + reload(); + $(document).bind('keydown', 'alt+q', showDialog); selectBoxEl.dblclick(e => { @@ -140,6 +144,7 @@ const recentNotes = (function() { return { showDialog, - addRecentNote + addRecentNote, + reload }; })(); \ No newline at end of file diff --git a/public/javascripts/messaging.js b/public/javascripts/messaging.js index 3e8948174..af3888e16 100644 --- a/public/javascripts/messaging.js +++ b/public/javascripts/messaging.js @@ -33,6 +33,12 @@ const messaging = (function() { noteEditor.reload(); } + if (data.recent_notes) { + console.log("Reloading recent notes because of background changes"); + + recentNotes.reload(); + } + const changesToPushCountEl = $("#changesToPushCount"); changesToPushCountEl.html(message.changesToPushCount); } From 3a26054619732f8e567bbec4e053d6ba7b61bcf4 Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 17:46:56 -0500 Subject: [PATCH 8/8] fixes to recent changes --- public/javascripts/dialogs/recent_changes.js | 11 ++++- public/javascripts/note_tree.js | 48 +++++++++++++------- routes/api/recent_changes.js | 12 ++++- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/public/javascripts/dialogs/recent_changes.js b/public/javascripts/dialogs/recent_changes.js index d2547dddb..8c58afd7a 100644 --- a/public/javascripts/dialogs/recent_changes.js +++ b/public/javascripts/dialogs/recent_changes.js @@ -33,9 +33,18 @@ const recentChanges = (function() { .attr('note-path', change.note_id) .attr('note-history-id', change.note_history_id); + let noteLink; + + if (change.current_is_deleted) { + noteLink = change.current_note_title; + } + else { + noteLink = link.createNoteLink(change.note_id, change.note_title); + } + changesListEl.append($('
  • ') .append(formattedTime + ' - ') - .append(link.createNoteLink(change.note_id)) + .append(noteLink) .append(' (').append(revLink).append(')')); } diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js index edd23cf10..8fa2d537d 100644 --- a/public/javascripts/note_tree.js +++ b/public/javascripts/note_tree.js @@ -184,7 +184,32 @@ const noteTree = (function() { } async function activateNode(notePath) { + const runPath = getRunPath(notePath); + const noteId = treeUtils.getNoteIdFromNotePath(notePath); + + let parentNoteId = 'root'; + + for (const childNoteId of runPath) { + const node = getNodesByNoteId(childNoteId).find(node => node.data.note_pid === parentNoteId); + + if (childNoteId === noteId) { + await node.setActive(); + } + else { + await node.setExpanded(); + } + + parentNoteId = childNoteId; + } + } + + /** + * Accepts notePath and tries to resolve it. Part of the path might not be valid because of note moving (which causes + * path change) or other corruption, in that case this will try to get some other valid path to the correct note. + */ + function getRunPath(notePath) { const path = notePath.split("/").reverse(); + path.push('root'); const effectivePath = []; let childNoteId = null; @@ -224,27 +249,16 @@ const noteTree = (function() { } } - effectivePath.push(parentNoteId); - childNoteId = parentNoteId; - } - - const noteId = treeUtils.getNoteIdFromNotePath(notePath); - - const runPath = effectivePath.reverse(); - let parentNoteId = 'root'; - - for (const childNoteId of runPath) { - const node = getNodesByNoteId(childNoteId).find(node => node.data.note_pid === parentNoteId); - - if (childNoteId === noteId) { - await node.setActive(); + if (parentNoteId === 'root') { + break; } else { - await node.setExpanded(); + effectivePath.push(parentNoteId); + childNoteId = parentNoteId; } - - parentNoteId = childNoteId; } + + return effectivePath.reverse(); } function showParentList(noteId, node) { diff --git a/routes/api/recent_changes.js b/routes/api/recent_changes.js index 018e163da..873587700 100644 --- a/routes/api/recent_changes.js +++ b/routes/api/recent_changes.js @@ -6,7 +6,17 @@ const sql = require('../../services/sql'); const auth = require('../../services/auth'); router.get('/', auth.checkApiAuth, async (req, res, next) => { - const recentChanges = await sql.getResults("SELECT * FROM notes_history order by date_modified_to desc limit 1000"); + const recentChanges = await sql.getResults( + `SELECT + notes.is_deleted AS current_is_deleted, + notes.note_title AS current_note_title, + notes_history.* + FROM + notes_history + JOIN notes USING(note_id) + ORDER BY + date_modified_to DESC + LIMIT 1000`); res.send(recentChanges); });