Merge remote-tracking branch 'origin/stable'

This commit is contained in:
zadam 2020-11-19 21:42:49 +01:00
commit c737a3adc9
9 changed files with 101 additions and 27 deletions

View File

@ -38,7 +38,7 @@ class Branch extends Entity {
} }
beforeSaving() { beforeSaving() {
if (this.notePosition === undefined) { if (this.notePosition === undefined || this.notePosition === null) {
const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]); const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10; this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10;
} }

View File

@ -88,7 +88,7 @@ function parseSelectedHtml(selectedHtml) {
} }
} }
async function duplicateNote(noteId, parentNoteId) { async function duplicateSubtree(noteId, parentNoteId) {
const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`); const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`);
await ws.waitForMaxKnownEntityChangeId(); await ws.waitForMaxKnownEntityChangeId();
@ -102,5 +102,5 @@ async function duplicateNote(noteId, parentNoteId) {
export default { export default {
createNote, createNote,
createNewTopLevelNote, createNewTopLevelNote,
duplicateNote duplicateSubtree
}; };

View File

@ -95,7 +95,7 @@ class TreeContextMenu {
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes }, enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste", { title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste",
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
{ title: "Duplicate note(s) here", command: "duplicateNote", uiIcon: "empty", { title: "Duplicate subtree(s) here", command: "duplicateSubtree", uiIcon: "empty",
enabled: parentNotSearch && isNotRoot && !isHoisted }, enabled: parentNotSearch && isNotRoot && !isHoisted },
{ title: "----" }, { title: "----" },
{ title: "Export", command: "exportNote", uiIcon: "empty", { title: "Export", command: "exportNote", uiIcon: "empty",

View File

@ -1341,7 +1341,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
protectedSessionService.protectNote(node.data.noteId, false, true); protectedSessionService.protectNote(node.data.noteId, false, true);
} }
duplicateNoteCommand({node}) { duplicateSubtreeCommand({node}) {
const nodesToDuplicate = this.getSelectedOrActiveNodes(node); const nodesToDuplicate = this.getSelectedOrActiveNodes(node);
for (const nodeToDuplicate of nodesToDuplicate) { for (const nodeToDuplicate of nodesToDuplicate) {
@ -1353,7 +1353,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
const branch = treeCache.getBranch(nodeToDuplicate.data.branchId); const branch = treeCache.getBranch(nodeToDuplicate.data.branchId);
noteCreateService.duplicateNote(nodeToDuplicate.data.noteId, branch.parentNoteId); noteCreateService.duplicateSubtree(nodeToDuplicate.data.noteId, branch.parentNoteId);
} }
} }
} }

View File

@ -187,10 +187,10 @@ function changeTitle(req) {
return note; return note;
} }
function duplicateNote(req) { function duplicateSubtree(req) {
const {noteId, parentNoteId} = req.params; const {noteId, parentNoteId} = req.params;
return noteService.duplicateNote(noteId, parentNoteId); return noteService.duplicateSubtree(noteId, parentNoteId);
} }
module.exports = { module.exports = {
@ -204,5 +204,5 @@ module.exports = {
setNoteTypeMime, setNoteTypeMime,
getRelationMap, getRelationMap,
changeTitle, changeTitle,
duplicateNote duplicateSubtree
}; };

View File

@ -154,7 +154,7 @@ function register(app) {
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision); apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote); apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree);
apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate);

View File

@ -1,7 +1,7 @@
const eventService = require('./events'); const eventService = require('./events');
const scriptService = require('./script'); const scriptService = require('./script');
const treeService = require('./tree'); const treeService = require('./tree');
const log = require('./log'); const noteService = require('./notes');
const repository = require('./repository'); const repository = require('./repository');
const Attribute = require('../entities/attribute'); const Attribute = require('../entities/attribute');
@ -58,17 +58,21 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
return; return;
} }
const targetNote = repository.getNote(entity.value); const templateNote = repository.getNote(entity.value);
if (!targetNote || !targetNote.isStringNote()) { if (!templateNote) {
return; return;
} }
const targetNoteContent = targetNote.getContent(); if (templateNote.isStringNote()) {
const templateNoteContent = templateNote.getContent();
if (targetNoteContent) { if (templateNoteContent) {
note.setContent(targetNoteContent); note.setContent(templateNoteContent);
}
} }
noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId);
} }
else if (entity.type === 'label' && entity.name === 'sorted') { else if (entity.type === 'label' && entity.name === 'sorted') {
treeService.sortNotesAlphabetically(entity.noteId); treeService.sortNotesAlphabetically(entity.noteId);

View File

@ -310,7 +310,13 @@ function importEnex(taskContext, file, parentNote) {
updateDates(noteEntity.noteId, utcDateCreated, utcDateModified); updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
} }
saxStream.on("closetag", tag => path.pop()); saxStream.on("closetag", tag => {
path.pop();
if (tag === 'note') {
saveNote();
}
});
saxStream.on("opencdata", () => { saxStream.on("opencdata", () => {
//console.log("opencdata"); //console.log("opencdata");

View File

@ -719,26 +719,68 @@ function eraseDeletedNotes() {
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
} }
function duplicateNote(noteId, parentNoteId) { // do a replace in str - all keys should be replaced by the corresponding values
const origNote = repository.getNote(noteId); function replaceByMap(str, mapObj) {
const re = new RegExp(Object.keys(mapObj).join("|"),"g");
return str.replace(re, matched => mapObj[matched]);
}
function duplicateSubtree(origNoteId, newParentNoteId) {
if (origNoteId === 'root') {
throw new Error('Duplicating root is not possible');
}
const origNote = repository.getNote(origNoteId);
// might be null if orig note is not in the target newParentNoteId
const origBranch = origNote.getBranches().find(branch => branch.parentNoteId === newParentNoteId);
const noteIdMapping = getNoteIdMapping(origNote);
const res = duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping);
res.note.title += " (dup)";
res.note.save();
return res;
}
function duplicateSubtreeWithoutRoot(origNoteId, newNoteId) {
if (origNoteId === 'root') {
throw new Error('Duplicating root is not possible');
}
const origNote = repository.getNote(origNoteId);
const noteIdMapping = getNoteIdMapping(origNote);
for (const childBranch of origNote.getChildBranches()) {
duplicateSubtreeInner(childBranch.getNote(), childBranch, newNoteId, noteIdMapping);
}
}
function duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping) {
if (origNote.isProtected && !protectedSessionService.isProtectedSessionAvailable()) { if (origNote.isProtected && !protectedSessionService.isProtectedSessionAvailable()) {
throw new Error(`Cannot duplicate note=${origNote.noteId} because it is protected and protected session is not available`); throw new Error(`Cannot duplicate note=${origNote.noteId} because it is protected and protected session is not available`);
} }
// might be null if orig note is not in the target parentNoteId
const origBranch = origNote.getBranches().find(branch => branch.parentNoteId === parentNoteId);
const newNote = new Note(origNote); const newNote = new Note(origNote);
newNote.noteId = undefined; // force creation of new note newNote.noteId = noteIdMapping[origNote.noteId];
newNote.title += " (dup)"; newNote.dateCreated = dateUtils.localNowDateTime();
newNote.utcDateCreated = dateUtils.utcNowDateTime();
newNote.save(); newNote.save();
newNote.setContent(origNote.getContent()); let content = origNote.getContent();
if (['text', 'relation-map', 'search'].includes(origNote.type)) {
// fix links in the content
content = replaceByMap(content, noteIdMapping);
}
newNote.setContent(content);
const newBranch = new Branch({ const newBranch = new Branch({
noteId: newNote.noteId, noteId: newNote.noteId,
parentNoteId: parentNoteId, parentNoteId: newParentNoteId,
// here increasing just by 1 to make sure it's directly after original // here increasing just by 1 to make sure it's directly after original
notePosition: origBranch ? origBranch.notePosition + 1 : null notePosition: origBranch ? origBranch.notePosition + 1 : null
}).save(); }).save();
@ -746,17 +788,38 @@ function duplicateNote(noteId, parentNoteId) {
for (const attribute of origNote.getOwnedAttributes()) { for (const attribute of origNote.getOwnedAttributes()) {
const attr = new Attribute(attribute); const attr = new Attribute(attribute);
attr.attributeId = undefined; // force creation of new attribute attr.attributeId = undefined; // force creation of new attribute
attr.utcDateCreated = dateUtils.utcNowDateTime();
attr.noteId = newNote.noteId; attr.noteId = newNote.noteId;
// if relation points to within the duplicated tree then replace the target to the duplicated note
// if it points outside of duplicated tree then keep the original target
if (attr.type === 'relation' && attr.value in noteIdMapping) {
attr.value = noteIdMapping[attr.value];
}
attr.save(); attr.save();
} }
for (const childBranch of origNote.getChildBranches()) {
duplicateSubtreeInner(childBranch.getNote(), childBranch, newNote.noteId, noteIdMapping);
}
return { return {
note: newNote, note: newNote,
branch: newBranch branch: newBranch
}; };
} }
function getNoteIdMapping(origNote) {
const noteIdMapping = {};
// pregenerate new noteIds since we'll need to fix relation references even for not yet created notes
for (const origNoteId of origNote.getDescendantNoteIds()) {
noteIdMapping[origNoteId] = utils.newEntityId();
}
return noteIdMapping;
}
sqlInit.dbReady.then(() => { sqlInit.dbReady.then(() => {
// first cleanup kickoff 5 minutes after startup // first cleanup kickoff 5 minutes after startup
setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000); setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
@ -772,7 +835,8 @@ module.exports = {
undeleteNote, undeleteNote,
protectNoteRecursively, protectNoteRecursively,
scanForLinks, scanForLinks,
duplicateNote, duplicateSubtree,
duplicateSubtreeWithoutRoot,
getUndeletedParentBranches, getUndeletedParentBranches,
triggerNoteTitleChanged triggerNoteTitleChanged
}; };