From 6fbf28b30dcc3dc41606b2e65b3b85e12faf34ce Mon Sep 17 00:00:00 2001 From: azivner Date: Tue, 11 Dec 2018 21:53:56 +0100 Subject: [PATCH] hoisting notes WIP --- db/migrations/0121__add_hoisting_option.sql | 2 ++ .../javascripts/services/context_menu.js | 4 +++ .../javascripts/services/hoisted_note.js | 25 +++++++++++++++++++ .../javascripts/services/tree_builder.js | 7 +++++- .../javascripts/services/tree_context_menu.js | 25 ++++++++++++++++++- src/routes/api/options.js | 2 +- src/routes/api/tree.js | 6 +++-- src/services/app_info.js | 2 +- 8 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 db/migrations/0121__add_hoisting_option.sql create mode 100644 src/public/javascripts/services/hoisted_note.js diff --git a/db/migrations/0121__add_hoisting_option.sql b/db/migrations/0121__add_hoisting_option.sql new file mode 100644 index 000000000..1b0458592 --- /dev/null +++ b/db/migrations/0121__add_hoisting_option.sql @@ -0,0 +1,2 @@ +INSERT INTO options (name, value, dateCreated, dateModified, isSynced) +VALUES ('hoistedNoteId', 'root', '2018-12-11T18:31:00.874Z', '2018-12-11T18:31:00.874Z', 0); \ No newline at end of file diff --git a/src/public/javascripts/services/context_menu.js b/src/public/javascripts/services/context_menu.js index 2ee0e552e..99416cf02 100644 --- a/src/public/javascripts/services/context_menu.js +++ b/src/public/javascripts/services/context_menu.js @@ -4,6 +4,10 @@ function initContextMenu(event, contextMenuItems, selectContextMenuItem) { $contextMenuContainer.empty(); for (const item of contextMenuItems) { + if (item.hidden) { + continue; + } + if (item.title === '----') { $contextMenuContainer.append($("
").addClass("dropdown-divider")); } else { diff --git a/src/public/javascripts/services/hoisted_note.js b/src/public/javascripts/services/hoisted_note.js new file mode 100644 index 000000000..26c863223 --- /dev/null +++ b/src/public/javascripts/services/hoisted_note.js @@ -0,0 +1,25 @@ +import optionsInit from './options_init.js'; +import server from "./server.js"; + +let hoistedNoteId; + +optionsInit.optionsReady.then(options => { + hoistedNoteId = options['hoistedNoteId']; +}); + +async function getHoistedNoteId() { + await optionsInit.optionsReady; + + return hoistedNoteId; +} + +async function setHoistedNoteId(noteId) { + hoistedNoteId = noteId; + + await server.put('options/hoistedNoteId/' + noteId); +} + +export default { + getHoistedNoteId, + setHoistedNoteId +} \ No newline at end of file diff --git a/src/public/javascripts/services/tree_builder.js b/src/public/javascripts/services/tree_builder.js index 09c564470..945ea33e7 100644 --- a/src/public/javascripts/services/tree_builder.js +++ b/src/public/javascripts/services/tree_builder.js @@ -4,13 +4,18 @@ import Branch from "../entities/branch.js"; import server from "./server.js"; import treeCache from "./tree_cache.js"; import messagingService from "./messaging.js"; +import hoistedNoteService from "./hoisted_note.js"; async function prepareTree(noteRows, branchRows, relations) { utils.assertArguments(noteRows, branchRows, relations); treeCache.load(noteRows, branchRows, relations); - return [ await prepareNode(await treeCache.getBranch('root')) ]; + const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); + const hoistedNote = await treeCache.getNote(hoistedNoteId); + const hoistedBranch = (await hoistedNote.getBranches())[0]; + + return [ await prepareNode(hoistedBranch) ]; } async function prepareBranch(note) { diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index ac21f13b2..fa44e2f46 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -10,7 +10,7 @@ import exportDialog from '../dialogs/export.js'; import infoService from "./info.js"; import treeCache from "./tree_cache.js"; import syncService from "./sync.js"; -import contextMenuService from "./context_menu.js"; +import hoistedNoteService from './hoisted_note.js'; const $tree = $("#tree"); @@ -83,6 +83,8 @@ const contextMenuItems = [ {title: "Insert child note Ctrl+P", cmd: "insertChildNote", uiIcon: "plus"}, {title: "Delete", cmd: "delete", uiIcon: "trash"}, {title: "----"}, + {title: "Hoist note", cmd: "hoist", uiIcon: "arrow-up"}, + {title: "Unhoist note", cmd: "unhoist", uiIcon: "arrow-up"}, {title: "Edit branch prefix F2", cmd: "editBranchPrefix", uiIcon: "pencil"}, {title: "----"}, {title: "Protect subtree", cmd: "protectSubtree", uiIcon: "shield-check"}, @@ -101,6 +103,16 @@ const contextMenuItems = [ {title: "Sort alphabetically Alt+S", cmd: "sortAlphabetically", uiIcon: "arrows-v"} ]; +function hideItem(cmd, hidden) { + const item = contextMenuItems.find(item => item.cmd === cmd); + + if (!item) { + throw new Error(`Command ${cmd} has not been found!`); + } + + item.hidden = hidden; +} + function enableItem(cmd, enabled) { const item = contextMenuItems.find(item => item.cmd === cmd); @@ -130,6 +142,11 @@ async function getContextMenuItems(event) { enableItem("export", note.type !== 'search'); enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search'); + const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); + + hideItem("hoist", note.noteId === hoistedNoteId); + hideItem("unhoist", note.noteId !== hoistedNoteId || !isNotRoot); + // Activate node on right-click node.setActive(); @@ -194,6 +211,12 @@ function selectContextMenuItem(event, cmd) { else if (cmd === "sortAlphabetically") { treeService.sortAlphabetically(node.data.noteId); } + else if (cmd === "hoist") { + hoistedNoteService.setHoistedNoteId(node.data.noteId); + } + else if (cmd === "unhoist") { + hoistedNoteService.setHoistedNoteId('root'); + } else { messagingService.logError("Unknown command: " + cmd); } diff --git a/src/routes/api/options.js b/src/routes/api/options.js index b2cb0c690..1f9a8c9aa 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -6,7 +6,7 @@ const log = require('../../services/log'); // options allowed to be updated directly in options dialog const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval', - 'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent']; + 'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId']; async function getOptions() { return await optionService.getOptionsMap(ALLOWED_OPTIONS); diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index cebc547b9..97dbb527c 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -50,18 +50,20 @@ async function getRelations(noteIds) { } async function getTree() { + const hoistedNoteId = await optionService.getOption('hoistedNoteId'); + // we fetch all branches of notes, even if that particular branch isn't visible // this allows us to e.g. detect and properly display clones const branches = await sql.getRows(` WITH RECURSIVE tree(branchId, noteId, isExpanded) AS ( - SELECT branchId, noteId, isExpanded FROM branches WHERE branchId = 'root' + SELECT branchId, noteId, isExpanded FROM branches WHERE noteId = ? UNION ALL SELECT branches.branchId, branches.noteId, branches.isExpanded FROM branches JOIN tree ON branches.parentNoteId = tree.noteId WHERE tree.isExpanded = 1 AND branches.isDeleted = 0 ) - SELECT branches.* FROM tree JOIN branches USING(noteId) WHERE branches.isDeleted = 0 ORDER BY branches.notePosition`); + SELECT branches.* FROM tree JOIN branches USING(noteId) WHERE branches.isDeleted = 0 ORDER BY branches.notePosition`, [hoistedNoteId]); const noteIds = Array.from(new Set(branches.map(b => b.noteId))); diff --git a/src/services/app_info.js b/src/services/app_info.js index a238a1645..5576b99ef 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -3,7 +3,7 @@ const build = require('./build'); const packageJson = require('../../package'); -const APP_DB_VERSION = 120; +const APP_DB_VERSION = 121; const SYNC_VERSION = 2; module.exports = {