mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
refactoring clipboard WIP
This commit is contained in:
parent
0f8a7bad06
commit
d1f679ab90
@ -34,7 +34,7 @@ import keyboardActionService from "./services/keyboard_actions.js";
|
|||||||
import splitService from "./services/split.js";
|
import splitService from "./services/split.js";
|
||||||
import optionService from "./services/options.js";
|
import optionService from "./services/options.js";
|
||||||
import noteContentRenderer from "./services/note_content_renderer.js";
|
import noteContentRenderer from "./services/note_content_renderer.js";
|
||||||
import AppContext from "./services/app_context.js";
|
import appContext from "./services/app_context.js";
|
||||||
|
|
||||||
window.glob.isDesktop = utils.isDesktop;
|
window.glob.isDesktop = utils.isDesktop;
|
||||||
window.glob.isMobile = utils.isMobile;
|
window.glob.isMobile = utils.isMobile;
|
||||||
@ -183,7 +183,6 @@ macInit.init();
|
|||||||
|
|
||||||
searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash
|
searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash
|
||||||
|
|
||||||
const appContext = new AppContext();
|
|
||||||
appContext.showWidgets();
|
appContext.showWidgets();
|
||||||
|
|
||||||
entrypoints.registerEntrypoints();
|
entrypoints.registerEntrypoints();
|
||||||
|
@ -21,6 +21,11 @@ class Branch {
|
|||||||
return this.treeCache.getNote(this.noteId);
|
return this.treeCache.getNote(this.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {NoteShort} */
|
||||||
|
async getParentNote() {
|
||||||
|
return this.treeCache.getNote(this.parentNoteId);
|
||||||
|
}
|
||||||
|
|
||||||
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
||||||
isTopLevel() {
|
isTopLevel() {
|
||||||
return this.parentNoteId === 'root';
|
return this.parentNoteId === 'root';
|
||||||
|
@ -3,7 +3,7 @@ import SearchBoxWidget from "../widgets/search_box.js";
|
|||||||
import SearchResultsWidget from "../widgets/search_results.js";
|
import SearchResultsWidget from "../widgets/search_results.js";
|
||||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||||
|
|
||||||
export default class AppContext {
|
class AppContext {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.widgets = [];
|
this.widgets = [];
|
||||||
}
|
}
|
||||||
@ -31,3 +31,7 @@ export default class AppContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appContext = new AppContext();
|
||||||
|
|
||||||
|
export default appContext;
|
@ -8,76 +8,63 @@ import hoistedNoteService from "./hoisted_note.js";
|
|||||||
import noteDetailService from "./note_detail.js";
|
import noteDetailService from "./note_detail.js";
|
||||||
import ws from "./ws.js";
|
import ws from "./ws.js";
|
||||||
|
|
||||||
async function moveBeforeNode(nodesToMove, beforeNode) {
|
async function moveBeforeNode(branchIdsToMove, beforeBranchId) {
|
||||||
nodesToMove = await filterRootNote(nodesToMove);
|
branchIdsToMove = await filterRootNote(branchIdsToMove);
|
||||||
|
|
||||||
if (beforeNode.data.noteId === 'root') {
|
if (beforeBranchId === 'root') {
|
||||||
alert('Cannot move notes before root note.');
|
alert('Cannot move notes before root note.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const nodeToMove of nodesToMove) {
|
for (const branchIdToMove of branchIdsToMove) {
|
||||||
const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-before/' + beforeNode.data.branchId);
|
const resp = await server.put(`branches/${branchIdToMove}/move-before/${beforeBranchId}`);
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
alert(resp.message);
|
alert(resp.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await changeNode(
|
|
||||||
node => node.moveTo(beforeNode, 'before'),
|
|
||||||
nodeToMove);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moveAfterNode(nodesToMove, afterNode) {
|
async function moveAfterNode(branchIdsToMove, afterBranchId) {
|
||||||
nodesToMove = await filterRootNote(nodesToMove);
|
branchIdsToMove = await filterRootNote(branchIdsToMove);
|
||||||
|
|
||||||
if (afterNode.data.noteId === 'root' || afterNode.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
|
const afterNote = await treeCache.getBranch(afterBranchId).getNote();
|
||||||
|
|
||||||
|
if (afterNote.noteId === 'root' || afterNote.noteId === await hoistedNoteService.getHoistedNoteId()) {
|
||||||
alert('Cannot move notes after root note.');
|
alert('Cannot move notes after root note.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodesToMove.reverse(); // need to reverse to keep the note order
|
branchIdsToMove.reverse(); // need to reverse to keep the note order
|
||||||
|
|
||||||
for (const nodeToMove of nodesToMove) {
|
for (const branchIdToMove of branchIdsToMove) {
|
||||||
const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-after/' + afterNode.data.branchId);
|
const resp = await server.put(`branches/${branchIdToMove}/move-after/${afterBranchId}`);
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
alert(resp.message);
|
alert(resp.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await changeNode(
|
|
||||||
node => node.moveTo(afterNode, 'after'),
|
|
||||||
nodeToMove);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moveToNode(nodesToMove, toNode) {
|
async function moveToNode(branchIdsToMove, newParentNoteId) {
|
||||||
nodesToMove = await filterRootNote(nodesToMove);
|
branchIdsToMove = await filterRootNote(branchIdsToMove);
|
||||||
|
|
||||||
for (const nodeToMove of nodesToMove) {
|
for (const branchIdToMove of branchIdsToMove) {
|
||||||
if (nodeToMove.data.noteId === await hoistedNoteService.getHoistedNoteId()
|
const branchToMove = treeCache.getBranch(branchIdToMove);
|
||||||
|| nodeToMove.getParent().data.noteType === 'search') {
|
|
||||||
|
if (branchToMove.noteId === await hoistedNoteService.getHoistedNoteId()
|
||||||
|
|| (await branchToMove.getParentNote()).type === 'search') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-to/' + toNode.data.noteId);
|
const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentNoteId}`);
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
alert(resp.message);
|
alert(resp.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await changeNode(async node => {
|
|
||||||
// first expand which will force lazy load and only then move the node
|
|
||||||
// if this is not expanded before moving, then lazy load won't happen because it already contains node
|
|
||||||
// this doesn't work if this isn't a folder yet, that's why we expand second time below
|
|
||||||
await toNode.setExpanded(true);
|
|
||||||
|
|
||||||
node.moveTo(toNode);
|
|
||||||
}, nodeToMove);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,72 +157,21 @@ async function moveNodeUpInHierarchy(node) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hoistedNoteService.isTopLevelNode(node) && node.getParent().getChildren().length <= 1) {
|
if (!await hoistedNoteService.isTopLevelNode(node) && node.getParent().getChildren().length <= 1) {
|
||||||
node.getParent().folder = false;
|
node.getParent().folder = false;
|
||||||
node.getParent().renderTitle();
|
node.getParent().renderTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
await changeNode(
|
|
||||||
node => node.moveTo(node.getParent(), 'after'),
|
|
||||||
node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeNode(func, node) {
|
async function filterRootNote(branchIds) {
|
||||||
utils.assertArguments(func, node);
|
|
||||||
|
|
||||||
const childNoteId = node.data.noteId;
|
|
||||||
const thisOldParentNode = node.getParent();
|
|
||||||
|
|
||||||
// this will move the node the user is directly operating on to the desired location
|
|
||||||
// note that there might be other instances of this note in the tree and those are updated through
|
|
||||||
// force reloading. We could simplify our lives by just reloading this one as well, but that leads
|
|
||||||
// to flickering and not good user experience. Current solution leads to no-flicker experience in most
|
|
||||||
// cases (since cloning is not used that often) and correct for multi-clone use cases
|
|
||||||
await func(node);
|
|
||||||
|
|
||||||
const thisNewParentNode = node.getParent();
|
|
||||||
|
|
||||||
node.data.parentNoteId = thisNewParentNode.data.noteId;
|
|
||||||
|
|
||||||
await treeCache.reloadNotes([childNoteId]);
|
|
||||||
|
|
||||||
await treeService.checkFolderStatus(thisOldParentNode);
|
|
||||||
await treeService.checkFolderStatus(thisNewParentNode);
|
|
||||||
|
|
||||||
if (!thisNewParentNode.isExpanded()) {
|
|
||||||
// this expands the note in case it become the folder only after the move
|
|
||||||
await thisNewParentNode.setExpanded(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const newParentNode of treeService.getNodesByNoteId(thisNewParentNode.data.noteId)) {
|
|
||||||
if (newParentNode.key === thisNewParentNode.key) {
|
|
||||||
// this one has been handled above specifically
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newParentNode.load(true); // force reload to show up new note
|
|
||||||
|
|
||||||
await treeService.checkFolderStatus(newParentNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const oldParentNode of treeService.getNodesByNoteId(thisOldParentNode.data.noteId)) {
|
|
||||||
if (oldParentNode.key === thisOldParentNode.key) {
|
|
||||||
// this one has been handled above specifically
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await oldParentNode.load(true); // force reload to show up new note
|
|
||||||
|
|
||||||
await treeService.checkFolderStatus(oldParentNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function filterRootNote(nodes) {
|
|
||||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
return nodes.filter(node =>
|
return branchIds.filter(branchId => {
|
||||||
node.data.noteId !== 'root'
|
const branch = treeCache.getBranch(branchId);
|
||||||
&& node.data.noteId !== hoistedNoteId);
|
|
||||||
|
return branch.noteId !== 'root'
|
||||||
|
&& branch.noteId !== hoistedNoteId;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeToast(id, message) {
|
function makeToast(id, message) {
|
||||||
|
@ -1,70 +1,60 @@
|
|||||||
import treeService from "./tree.js";
|
|
||||||
import treeChangesService from "./branches.js";
|
import treeChangesService from "./branches.js";
|
||||||
import cloningService from "./cloning.js";
|
import cloningService from "./cloning.js";
|
||||||
import toastService from "./toast.js";
|
import toastService from "./toast.js";
|
||||||
import hoistedNoteService from "./hoisted_note.js";
|
import hoistedNoteService from "./hoisted_note.js";
|
||||||
|
import treeCache from "./tree_cache.js";
|
||||||
|
|
||||||
/*
|
let clipboardBranchIds = [];
|
||||||
* Clipboard contains node keys which are not stable. If a (part of the) tree is reloaded,
|
|
||||||
* node keys in the clipboard might not exist anymore. Code here should then be ready to deal
|
|
||||||
* with this.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let clipboardNodeKeys = [];
|
|
||||||
let clipboardMode = null;
|
let clipboardMode = null;
|
||||||
|
|
||||||
async function pasteAfter(afterNode) {
|
async function pasteAfter(afterBranchId) {
|
||||||
if (isClipboardEmpty()) {
|
if (isClipboardEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipboardMode === 'cut') {
|
if (clipboardMode === 'cut') {
|
||||||
const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey));
|
await treeChangesService.moveAfterNode(clipboardBranchIds, afterBranchId);
|
||||||
|
|
||||||
await treeChangesService.moveAfterNode(nodes, afterNode);
|
clipboardBranchIds = [];
|
||||||
|
|
||||||
clipboardNodeKeys = [];
|
|
||||||
clipboardMode = null;
|
clipboardMode = null;
|
||||||
}
|
}
|
||||||
else if (clipboardMode === 'copy') {
|
else if (clipboardMode === 'copy') {
|
||||||
for (const nodeKey of clipboardNodeKeys) {
|
const clipboardBranches = clipboardBranchIds.map(branchId => treeCache.getBranch(branchId));
|
||||||
const clipNode = treeService.getNodeByKey(nodeKey);
|
|
||||||
|
|
||||||
await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId);
|
for (const clipboardBranch of clipboardBranches) {
|
||||||
|
const clipboardNote = await clipboardBranch.getNote();
|
||||||
|
|
||||||
|
await cloningService.cloneNoteAfter(clipboardNote.noteId, afterBranchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
|
// copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toastService.throwError("Unrecognized clipboard mode=" + clipboardMode);
|
toastService.throwError("Unrecognized clipboard mode=" + clipboardMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pasteInto(parentNode) {
|
async function pasteInto(parentNoteId) {
|
||||||
if (isClipboardEmpty()) {
|
if (isClipboardEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipboardMode === 'cut') {
|
if (clipboardMode === 'cut') {
|
||||||
const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey));
|
await treeChangesService.moveToNode(clipboardBranchIds, parentNoteId);
|
||||||
|
|
||||||
await treeChangesService.moveToNode(nodes, parentNode);
|
clipboardBranchIds = [];
|
||||||
|
|
||||||
await parentNode.setExpanded(true);
|
|
||||||
|
|
||||||
clipboardNodeKeys = [];
|
|
||||||
clipboardMode = null;
|
clipboardMode = null;
|
||||||
}
|
}
|
||||||
else if (clipboardMode === 'copy') {
|
else if (clipboardMode === 'copy') {
|
||||||
for (const nodeKey of clipboardNodeKeys) {
|
const clipboardBranches = clipboardBranchIds.map(branchId => treeCache.getBranch(branchId));
|
||||||
const clipNode = treeService.getNodeByKey(nodeKey);
|
|
||||||
|
|
||||||
await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId);
|
for (const clipboardBranch of clipboardBranches) {
|
||||||
|
const clipboardNote = await clipboardBranch.getNote();
|
||||||
|
|
||||||
|
await cloningService.cloneNoteTo(clipboardNote.noteId, parentNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await parentNode.setExpanded(true);
|
// copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places
|
||||||
|
|
||||||
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toastService.throwError("Unrecognized clipboard mode=" + mode);
|
toastService.throwError("Unrecognized clipboard mode=" + mode);
|
||||||
@ -72,19 +62,19 @@ async function pasteInto(parentNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copy(nodes) {
|
function copy(nodes) {
|
||||||
clipboardNodeKeys = nodes.map(node => node.key);
|
clipboardBranchIds = nodes.map(node => node.data.branchId);
|
||||||
clipboardMode = 'copy';
|
clipboardMode = 'copy';
|
||||||
|
|
||||||
toastService.showMessage("Note(s) have been copied into clipboard.");
|
toastService.showMessage("Note(s) have been copied into clipboard.");
|
||||||
}
|
}
|
||||||
|
|
||||||
function cut(nodes) {
|
function cut(nodes) {
|
||||||
clipboardNodeKeys = nodes
|
clipboardBranchIds = nodes
|
||||||
.filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise())
|
.filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise())
|
||||||
.filter(node => node.getParent().data.noteType !== 'search')
|
.filter(node => node.getParent().data.noteType !== 'search')
|
||||||
.map(node => node.key);
|
.map(node => node.key);
|
||||||
|
|
||||||
if (clipboardNodeKeys.length > 0) {
|
if (clipboardBranchIds.length > 0) {
|
||||||
clipboardMode = 'cut';
|
clipboardMode = 'cut';
|
||||||
|
|
||||||
toastService.showMessage("Note(s) have been cut into clipboard.");
|
toastService.showMessage("Note(s) have been cut into clipboard.");
|
||||||
@ -92,9 +82,9 @@ function cut(nodes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isClipboardEmpty() {
|
function isClipboardEmpty() {
|
||||||
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeService.getNodeByKey(key));
|
clipboardBranchIds = clipboardBranchIds.filter(branchId => !!treeCache.getBranch(branchId));
|
||||||
|
|
||||||
return clipboardNodeKeys.length === 0;
|
return clipboardBranchIds.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -4,11 +4,10 @@ const $contextMenuContainer = $("#context-menu-container");
|
|||||||
let dateContextMenuOpenedMs = 0;
|
let dateContextMenuOpenedMs = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {NoteTreeWidget} treeWidget
|
|
||||||
* @param event - originating click event (used to get coordinates to display menu at position)
|
* @param event - originating click event (used to get coordinates to display menu at position)
|
||||||
* @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd)
|
* @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd)
|
||||||
*/
|
*/
|
||||||
async function initContextMenu(treeWidget, event, contextMenu) {
|
async function initContextMenu(event, contextMenu) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
$contextMenuContainer.empty();
|
$contextMenuContainer.empty();
|
||||||
|
@ -150,7 +150,7 @@ class TreeContextMenu {
|
|||||||
import("../dialogs/move_to.js").then(d => d.showDialog(nodes));
|
import("../dialogs/move_to.js").then(d => d.showDialog(nodes));
|
||||||
}
|
}
|
||||||
else if (cmd === "pasteAfter") {
|
else if (cmd === "pasteAfter") {
|
||||||
clipboard.pasteAfter(this.node);
|
clipboard.pasteAfter(this.treeWidget, this.node);
|
||||||
}
|
}
|
||||||
else if (cmd === "pasteInto") {
|
else if (cmd === "pasteInto") {
|
||||||
clipboard.pasteInto(this.node);
|
clipboard.pasteInto(this.node);
|
||||||
|
@ -157,7 +157,7 @@ export default class NoteTreeWidget extends BasicWidget {
|
|||||||
$tree.on('contextmenu', '.fancytree-node', e => {
|
$tree.on('contextmenu', '.fancytree-node', e => {
|
||||||
const node = $.ui.fancytree.getNode(e);
|
const node = $.ui.fancytree.getNode(e);
|
||||||
|
|
||||||
contextMenuWidget.initContextMenu(this, e, new TreeContextMenu(this, node));
|
contextMenuWidget.initContextMenu(e, new TreeContextMenu(this, node));
|
||||||
|
|
||||||
return false; // blocks default browser right click menu
|
return false; // blocks default browser right click menu
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user