From e143becb7ac151991918f519d8c11cf3f1abd336 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 14 Nov 2019 20:27:28 +0100 Subject: [PATCH 01/13] styling changes --- package-lock.json | 21 ++++++++++++++------- package.json | 4 ++-- src/public/stylesheets/desktop.css | 15 +++++++++++---- src/public/stylesheets/themes.css | 17 ++++++++++------- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63c583348..5dc48fd44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3112,9 +3112,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.1.tgz", - "integrity": "sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.2.tgz", + "integrity": "sha512-rHGwtpl67oih3xAHbZlpw5rQAt+YV1mSCu2fUZ9XNrfaGEhom7E+AUiMci+ByP4aSfuAWx7hE0BPuJLMrpXwOw==" }, "electron": { "version": "6.0.12", @@ -8130,11 +8130,18 @@ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", "requires": { - "mime-db": "1.40.0" + "mime-db": "1.42.0" + }, + "dependencies": { + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + } } }, "mimic-fn": { diff --git a/package.json b/package.json index 4a13e862c..e762d8c37 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "csurf": "1.10.0", "dayjs": "1.8.17", "debug": "4.1.1", - "ejs": "2.7.1", + "ejs": "2.7.2", "electron-debug": "3.0.1", "electron-dl": "1.14.0", "electron-find": "1.0.6", @@ -51,7 +51,7 @@ "imagemin-pngquant": "8.0.0", "ini": "1.3.5", "jimp": "0.8.5", - "mime-types": "2.1.24", + "mime-types": "2.1.25", "moment": "2.24.0", "multer": "1.4.2", "node-abi": "2.12.0", diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 9622e07da..5e52a1cb3 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -145,7 +145,7 @@ body { ::-webkit-scrollbar-thumb { border-radius: 3px; - border: 1px solid var(--main-border-color); + border: 1px solid var(--scrollbar-border-color); } ::-webkit-scrollbar-corner { @@ -256,11 +256,14 @@ body { border-top-right-radius: 8px; overflow: hidden; pointer-events: all; - background-image: linear-gradient(to bottom, var(--accented-background-color), var(--main-background-color)); + background-color: var(--accented-background-color); + border-bottom: 1px solid var(--button-border-color); } .note-tab-row .note-tab[active] .note-tab-wrapper { - background-image: linear-gradient(to bottom, var(--more-accented-background-color), var(--main-background-color)); + background-color: var(--main-background-color); + border: 1px solid var(--button-border-color); + border-bottom: 0; font-weight: bold; } @@ -380,6 +383,8 @@ body { border: 0; background: inherit; font-weight: bold; + text-transform: uppercase; + color: var(--muted-text-color) !important; } .note-detail-sidebar .widget-header-action { @@ -388,7 +393,9 @@ body { } .note-detail-sidebar .widget-help { - color: var(--main-text-color); + color: var(--muted-text-color); + position: relative; + top: 2px; } .note-detail-sidebar .widget-help.no-link:hover { diff --git a/src/public/stylesheets/themes.css b/src/public/stylesheets/themes.css index b769d7362..032a38086 100644 --- a/src/public/stylesheets/themes.css +++ b/src/public/stylesheets/themes.css @@ -10,26 +10,27 @@ --main-text-color: black; --main-border-color: #ccc; --accented-background-color: #f5f5f5; - --more-accented-background-color: #ccc; - --header-background-color: #f8f8f8; - --button-background-color: #eee; - --button-disabled-background-color: #ccc; + --more-accented-background-color: #ddd; + --header-background-color: #fff; + --button-background-color: #fff; + --button-disabled-background-color: #ddd; --button-border-color: #ddd; --button-text-color: black; --button-border-radius: 5px; - --muted-text-color: #444; + --muted-text-color: #666; --input-text-color: black; --input-background-color: white; --hover-item-text-color: black; --hover-item-background-color: #eee; --active-item-text-color: black; - --active-item-background-color: #ccc; + --active-item-background-color: #ddd; --menu-text-color: black; --menu-background-color: white; --tooltip-background-color: #f8f8f8; --link-color: blue; --modal-background-color: white; --modal-backdrop-color: black; + --scrollbar-border-color: #ddd; } body.theme-black { @@ -56,6 +57,7 @@ body.theme-black { --link-color: lightskyblue; --modal-background-color: black; --modal-backdrop-color: #444; + --scrollbar-border-color: #888; } body.theme-black .CodeMirror { @@ -65,7 +67,7 @@ body.theme-black .CodeMirror { body.theme-dark { --main-background-color: #333; --main-text-color: white; - --main-border-color: #ddd; + --main-border-color: #aaa; --accented-background-color: #555; --more-accented-background-color: #777; --header-background-color: #333; @@ -86,6 +88,7 @@ body.theme-dark { --link-color: lightskyblue; --modal-background-color: #333; --modal-backdrop-color: #444; + --scrollbar-border-color: #888; } body.theme-dark .CodeMirror { From de02e9e889a5a2b22b5490a71311b51efcbc36ec Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 14 Nov 2019 23:10:56 +0100 Subject: [PATCH 02/13] redesign of createNote APIs, WIP --- src/entities/attribute.js | 4 + src/entities/note.js | 26 ++++++ src/services/notes.js | 184 +++++++++++++++++++++----------------- 3 files changed, 131 insertions(+), 83 deletions(-) diff --git a/src/entities/attribute.js b/src/entities/attribute.js index 0bc68afa2..6755f113f 100644 --- a/src/entities/attribute.js +++ b/src/entities/attribute.js @@ -79,6 +79,10 @@ class Attribute extends Entity { async beforeSaving() { if (!this.value) { + if (this.type === 'relation') { + throw new Error(`Cannot save relation ${this.name} since it does not target any note.`); + } + // null value isn't allowed this.value = ""; } diff --git a/src/entities/note.js b/src/entities/note.js index 60b603cf2..233246e90 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -472,6 +472,32 @@ class Note extends Entity { } } + /** + * @return {Promise} + */ + async createAttribute(type, name, value = "") { + const attr = new Attribute({ + noteId: this.noteId, + type: type, + name: name, + value: value + }); + + await attr.save(); + + this.invalidateAttributeCache(); + + return attr; + } + + async createLabel(name, value = "") { + return await this.createAttribute(LABEL, name, value); + } + + async createRelation(name, targetNoteId) { + return await this.createAttribute(RELATION, name, targetNoteId); + } + /** * @param {string} name - label name * @returns {Promise} true if label exists (including inherited) diff --git a/src/services/notes.js b/src/services/notes.js index cd516eaab..f5b225039 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -16,29 +16,14 @@ const protectedSessionService = require('../services/protected_session'); const log = require('../services/log'); const noteRevisionService = require('../services/note_revisions'); -async function getNewNotePosition(parentNoteId, noteData) { - let newNotePos = 0; +async function getNewNotePosition(parentNoteId) { + const maxNotePos = await sql.getValue(` + SELECT MAX(notePosition) + FROM branches + WHERE parentNoteId = ? + AND isDeleted = 0`, [parentNoteId]); - if (noteData.target === 'into') { - const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]); - - newNotePos = maxNotePos === null ? 0 : maxNotePos + 10; - } - else if (noteData.target === 'after') { - const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [noteData.target_branchId]); - - newNotePos = afterNote.notePosition + 10; - - // not updating utcDateModified to avoig having to sync whole rows - await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0', - [parentNoteId, afterNote.notePosition]); - - await syncTableService.addNoteReorderingSync(parentNoteId); - } - else { - throw new Error('Unknown target: ' + noteData.target); - } - return newNotePos; + return maxNotePos === null ? 0 : maxNotePos + 10; } async function triggerChildNoteCreated(childNote, parentNote) { @@ -49,31 +34,12 @@ async function triggerNoteTitleChanged(note) { await eventService.emit(eventService.NOTE_TITLE_CHANGED, note); } -/** - * FIXME: noteData has mandatory property "target", it might be better to add it as parameter to reflect this - */ -async function createNewNote(parentNoteId, noteData) { - let newNotePos; - - if (noteData.notePosition !== undefined) { - newNotePos = noteData.notePosition; - } - else { - newNotePos = await getNewNotePosition(parentNoteId, noteData); - } - - const parentNote = await repository.getNote(parentNoteId); - - if (!parentNote) { - throw new Error(`Parent note ${parentNoteId} not found.`); - } - +function deriveTypeAndMime(noteData, parentNote) { if (!noteData.type) { if (parentNote.type === 'text' || parentNote.type === 'code') { noteData.type = parentNote.type; noteData.mime = parentNote.mime; - } - else { + } else { // inheriting note type makes sense only for text and code noteData.type = 'text'; noteData.mime = 'text/html'; @@ -83,54 +49,83 @@ async function createNewNote(parentNoteId, noteData) { if (!noteData.mime) { if (noteData.type === 'text') { noteData.mime = 'text/html'; - } - else if (noteData.type === 'code') { + } else if (noteData.type === 'code') { noteData.mime = 'text/plain'; - } - else if (noteData.type === 'relation-map' || noteData.type === 'search') { + } else if (noteData.type === 'relation-map' || noteData.type === 'search') { noteData.mime = 'application/json'; } } noteData.type = noteData.type || parentNote.type; noteData.mime = noteData.mime || parentNote.mime; +} - const note = await new Note({ - noteId: noteData.noteId, // optionally can force specific noteId - title: noteData.title, - isProtected: noteData.isProtected, - type: noteData.type || 'text', - mime: noteData.mime || 'text/html' - }).save(); - - if (note.isStringNote() || this.type === 'render') { // render to just make sure it's not null - noteData.content = noteData.content || ""; - } - - await note.setContent(noteData.content); - - const branch = await new Branch({ - noteId: note.noteId, - parentNoteId: parentNoteId, - notePosition: newNotePos, - prefix: noteData.prefix, - isExpanded: !!noteData.isExpanded - }).save(); - +async function copyChildAttributes(parentNote, childNote) { for (const attr of await parentNote.getAttributes()) { if (attr.name.startsWith("child:")) { await new Attribute({ - noteId: note.noteId, - type: attr.type, - name: attr.name.substr(6), - value: attr.value, - position: attr.position, - isInheritable: attr.isInheritable + noteId: childNote.noteId, + type: attr.type, + name: attr.name.substr(6), + value: attr.value, + position: attr.position, + isInheritable: attr.isInheritable }).save(); - note.invalidateAttributeCache(); + childNote.invalidateAttributeCache(); } } +} + +/** + * Following object properties are mandatory: + * - {string} parentNoteId + * - {string} title + * - {*} content + * - {string} type + * + * Following are optional (have defaults) + * - {string} mime - value is derived from default mimes for type + * - {boolean} isProtected - default is false + * - {boolean} isExpanded - default is false + * - {string} prefix - default is empty string + * - {integer} notePosition - default is last existing notePosition in a parent + 10 + * + * @param params + * @return {Promise<{note: Entity, branch: Entity}>} + */ +async function createNewNote(params) { + const parentNote = await repository.getNote(params.parentNoteId); + + if (!parentNote) { + throw new Error(`Parent note ${params.parentNoteId} not found.`); + } + + if (!params.title || params.title.trim().length === 0) { + throw new Error(`Note title must not be empty`); + } + + deriveTypeAndMime(params, parentNote); + + const note = await new Note({ + noteId: params.noteId, // optionally can force specific noteId + title: params.title, + isProtected: !!params.isProtected, + type: params.type, + mime: params.mime + }).save(); + + await note.setContent(params.content); + + const branch = await new Branch({ + noteId: note.noteId, + parentNoteId: params.parentNoteId, + notePosition: params.notePosition !== undefined ? params.notePosition : await getNewNotePosition(params.parentNoteId), + prefix: params.prefix, + isExpanded: !!params.isExpanded + }).save(); + + await copyChildAttributes(parentNote, note); await triggerNoteTitleChanged(note); await triggerChildNoteCreated(note, parentNote); @@ -141,13 +136,41 @@ async function createNewNote(parentNoteId, noteData) { }; } +// methods below should be probably just backend API methods +async function createJsonNote(parentNoteId, title, content = {}, params = {}) { + params.parentNoteId = parentNoteId; + params.title = title; + + params.type = "code"; + params.mime = "application/json"; + + params.content = JSON.stringify(content, null, '\t'); + + return await createNewNote(params); +} + +async function createTextNote(parentNoteId, title, content = "", params = {}) { + params.parentNoteId = parentNoteId; + params.title = title; + + params.type = "text"; + params.mime = "text/html"; + + params.content = content; + + return await createNewNote(params); +} + +/** + * @deprecated + */ async function createNote(parentNoteId, title, content = "", extraOptions = {}) { if (!parentNoteId) throw new Error("Empty parentNoteId"); if (!title) throw new Error("Empty title"); const noteData = { title: title, - content: extraOptions.json ? JSON.stringify(content, null, '\t') : content, + content: content, target: 'into', noteId: extraOptions.noteId, isProtected: !!extraOptions.isProtected, @@ -158,12 +181,7 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) notePosition: extraOptions.notePosition }; - if (extraOptions.json && !noteData.type) { - noteData.type = "code"; - noteData.mime = "application/json"; - } - - const {note, branch} = await createNewNote(parentNoteId, noteData); + const {note, branch} = await createNewNote(parentNoteId, title, noteData); for (const attr of extraOptions.attributes || []) { await attributeService.createAttribute({ From 13c0411533bf6e06f3124b5978706b035d1c2f66 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 16 Nov 2019 11:09:52 +0100 Subject: [PATCH 03/13] refactoring of note creation APIs WIP --- src/entities/note.js | 10 ++-- src/routes/api/clipper.js | 12 ++++- src/routes/api/notes.js | 6 +-- src/routes/api/sender.js | 4 +- src/services/backend_script_api.js | 13 ++--- src/services/date_notes.js | 7 +-- src/services/image.js | 11 +++-- src/services/import/enex.js | 25 ++++++++-- src/services/import/opml.js | 7 ++- src/services/import/single.js | 32 +++++++++---- src/services/import/tar.js | 12 +++-- src/services/notes.js | 76 +++++++----------------------- src/tools/generate_document.js | 7 ++- 13 files changed, 115 insertions(+), 107 deletions(-) diff --git a/src/entities/note.js b/src/entities/note.js index 233246e90..7a3e89aa4 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -475,7 +475,7 @@ class Note extends Entity { /** * @return {Promise} */ - async createAttribute(type, name, value = "") { + async addAttribute(type, name, value = "") { const attr = new Attribute({ noteId: this.noteId, type: type, @@ -490,12 +490,12 @@ class Note extends Entity { return attr; } - async createLabel(name, value = "") { - return await this.createAttribute(LABEL, name, value); + async addLabel(name, value = "") { + return await this.addAttribute(LABEL, name, value); } - async createRelation(name, targetNoteId) { - return await this.createAttribute(RELATION, name, targetNoteId); + async addRelation(name, targetNoteId) { + return await this.addAttribute(RELATION, name, targetNoteId); } /** diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index 9bf806950..0344037a5 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -31,7 +31,11 @@ async function addClipping(req) { let clippingNote = await findClippingNote(todayNote, pageUrl); if (!clippingNote) { - clippingNote = (await noteService.createNote(todayNote.noteId, title, '')).note; + clippingNote = (await noteService.createNewNote({ + parentNoteId: todayNote.noteId, + title: title, + content: '' + })).note; await clippingNote.setLabel('clipType', 'clippings'); await clippingNote.setLabel('pageUrl', pageUrl); @@ -51,7 +55,11 @@ async function createNote(req) { const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate()); - const {note} = await noteService.createNote(todayNote.noteId, title, content); + const {note} = await noteService.createNewNote({ + parentNoteId: todayNote.noteId, + title, + content + }); await note.setLabel('clipType', clipType); diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index c06542111..210776074 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -53,10 +53,10 @@ async function getChildren(req) { } async function createNote(req) { - const parentNoteId = req.params.parentNoteId; - const newNote = req.body; + const params = Object.assign({}, req.body); // clone + params.parentNoteId = req.params.parentNoteId; - const { note, branch } = await noteService.createNewNote(parentNoteId, newNote, req); + const { note, branch } = await noteService.createNewNote(params); note.cssClass = (await note.getLabels("cssClass")).map(label => label.value).join(" "); diff --git a/src/routes/api/sender.js b/src/routes/api/sender.js index 06d87d7b4..c60cab4ac 100644 --- a/src/routes/api/sender.js +++ b/src/routes/api/sender.js @@ -26,10 +26,10 @@ async function uploadImage(req) { async function saveNote(req) { const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']); - const {note, branch} = await noteService.createNewNote(parentNote.noteId, { + const {note, branch} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, title: req.body.title, content: req.body.content, - target: 'into', isProtected: false, type: 'text', mime: 'text/html' diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index c430c9f88..505bd9721 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -178,7 +178,7 @@ function BackendScriptApi(currentNote, apiParams) { */ /** - * @typedef {object} CreateNoteExtraOptions + * @typedef {object} CreateNoteParams * @property {boolean} [json=false] - should the note be JSON * @property {boolean} [isProtected=false] - should the note be protected * @property {string} [type='text'] - note type @@ -189,13 +189,10 @@ function BackendScriptApi(currentNote, apiParams) { /** * @method * - * @param {string} parentNoteId - create new note under this parent - * @param {string} title - * @param {string} [content=""] - * @param {CreateNoteExtraOptions} [extraOptions={}] + * @param {CreateNoteParams} [extraOptions={}] * @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch */ - this.createNote = noteService.createNote; + this.createNote = noteService.createNewNote; /** * Creates new note according to given params and force all connected clients to refresh their tree. @@ -205,11 +202,11 @@ function BackendScriptApi(currentNote, apiParams) { * @param {string} parentNoteId - create new note under this parent * @param {string} title * @param {string} [content=""] - * @param {CreateNoteExtraOptions} [extraOptions={}] + * @param {CreateNoteParams} [extraOptions={}] * @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch */ this.createNoteAndRefresh = async function(parentNoteId, title, content, extraOptions) { - const ret = await noteService.createNote(parentNoteId, title, content, extraOptions); + const ret = await noteService.createNewNote(parentNoteId, title, content, extraOptions); ws.refreshTree(); diff --git a/src/services/date_notes.js b/src/services/date_notes.js index 7887b4b40..270258576 100644 --- a/src/services/date_notes.js +++ b/src/services/date_notes.js @@ -14,10 +14,10 @@ const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Satur const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; async function createNote(parentNoteId, noteTitle, noteText) { - return (await noteService.createNewNote(parentNoteId, { + return (await noteService.createNewNote({ + parentNoteId: parentNoteId, title: noteTitle, content: noteText, - target: 'into', isProtected: false })).note; } @@ -35,7 +35,8 @@ async function getRootCalendarNote() { let rootNote = await attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL); if (!rootNote) { - rootNote = (await noteService.createNewNote('root', { + rootNote = (await noteService.createNewNote({ + parentNoteId: 'root', title: 'Calendar', target: 'into', isProtected: false diff --git a/src/services/image.js b/src/services/image.js index 34cdb6ec7..21a3c3a3b 100644 --- a/src/services/image.js +++ b/src/services/image.js @@ -57,14 +57,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw const parentNote = await repository.getNote(parentNoteId); - const {note} = await noteService.createNote(parentNoteId, fileName, buffer, { - target: 'into', + const {note} = await noteService.createNewNote({ + parentNoteId, + title: fileName, + content: buffer, type: 'image', - isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), mime: 'image/' + imageFormat.ext.toLowerCase(), - attributes: [{ type: 'label', name: 'originalFileName', value: originalName }] + isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() }); + await note.addLabel('originalFileName', originalName); + return { fileName, note, diff --git a/src/services/import/enex.js b/src/services/import/enex.js index c898498d9..2d9bd57cc 100644 --- a/src/services/import/enex.js +++ b/src/services/import/enex.js @@ -30,7 +30,10 @@ async function importEnex(taskContext, file, parentNote) { : file.originalname; // root note is new note into all ENEX/notebook's notes will be imported - const rootNote = (await noteService.createNote(parentNote.noteId, rootNoteTitle, "", { + const rootNote = (await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title: rootNoteTitle, + content: "", type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), @@ -207,14 +210,20 @@ async function importEnex(taskContext, file, parentNote) { // following is workaround for this issue: https://github.com/Leonidas-from-XIV/node-xml2js/issues/484 content = extractContent(xmlObject['en-note']); - const noteEntity = (await noteService.createNote(rootNote.noteId, title, content, { - attributes, + const noteEntity = (await noteService.createNewNote({ + parentNoteId: rootNote.noteId, + title, + content, utcDateCreated, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), })).note; + for (const attr of resource.attributes) { + await note.addAttribute(attr.type, attr.name, attr.value); + } + taskContext.increaseProgressCount(); let noteContent = await noteEntity.getContent(); @@ -231,13 +240,19 @@ async function importEnex(taskContext, file, parentNote) { } const createFileNote = async () => { - const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, { - attributes: resource.attributes, + const resourceNote = (await noteService.createNewNote({ + parentNoteId: noteEntity.noteId, + title: resource.title, + content: resource.content, type: 'file', mime: resource.mime, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), })).note; + for (const attr of resource.attributes) { + await note.addAttribute(attr.type, attr.name, attr.value); + } + taskContext.increaseProgressCount(); const resourceLink = `${utils.escapeHtml(resource.title)}`; diff --git a/src/services/import/opml.js b/src/services/import/opml.js index 3b9e4f852..b4bd1a70b 100644 --- a/src/services/import/opml.js +++ b/src/services/import/opml.js @@ -44,8 +44,11 @@ async function importOpml(taskContext, fileBuffer, parentNote) { throw new Error("Unrecognized OPML version " + opmlVersion); } - const {note} = await noteService.createNote(parentNoteId, title, content, { - isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), + const {note} = await noteService.createNewNote({ + parentNoteId, + title, + content, + isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() }); taskContext.increaseProgressCount(); diff --git a/src/services/import/single.js b/src/services/import/single.js index 18c871fd0..983cca355 100644 --- a/src/services/import/single.js +++ b/src/services/import/single.js @@ -41,16 +41,18 @@ async function importImage(file, parentNote, taskContext) { async function importFile(taskContext, file, parentNote) { const originalName = file.originalname; - const size = file.size; - const {note} = await noteService.createNote(parentNote.noteId, originalName, file.buffer, { - target: 'into', + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title: originalName, + content: file.buffer, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), type: 'file', - mime: mimeService.getMime(originalName) || file.mimetype, - attributes: [{ type: "label", name: "originalFileName", value: originalName }] + mime: mimeService.getMime(originalName) || file.mimetype }); + await note.addLabel("originalFileName", originalName); + taskContext.increaseProgressCount(); return note; @@ -62,7 +64,10 @@ async function importCodeNote(taskContext, file, parentNote) { const detectedMime = mimeService.getMime(file.originalname) || file.mimetype; const mime = mimeService.normalizeMimeType(detectedMime); - const {note} = await noteService.createNote(parentNote.noteId, title, content, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content, type: 'code', mime: mime, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() @@ -78,7 +83,10 @@ async function importPlainText(taskContext, file, parentNote) { const plainTextContent = file.buffer.toString("UTF-8"); const htmlContent = convertTextToHtml(plainTextContent); - const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content: htmlContent, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), @@ -118,7 +126,10 @@ async function importMarkdown(taskContext, file, parentNote) { const title = getFileNameWithoutExtension(file.originalname); - const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content: htmlContent, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), @@ -133,7 +144,10 @@ async function importHtml(taskContext, file, parentNote) { const title = getFileNameWithoutExtension(file.originalname); const content = file.buffer.toString("UTF-8"); - const {note} = await noteService.createNote(parentNote.noteId, title, content, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), diff --git a/src/services/import/tar.js b/src/services/import/tar.js index b459d8c17..d71a6c55b 100644 --- a/src/services/import/tar.js +++ b/src/services/import/tar.js @@ -177,8 +177,11 @@ async function importTar(taskContext, fileBuffer, importRootNote) { return; } - ({note} = await noteService.createNote(parentNoteId, noteTitle, '', { - noteId, + ({note} = await noteService.createNewNote({ + parentNoteId: parentNoteId, + title: noteTitle, + content: '', + noteId: noteId, type: noteMeta ? noteMeta.type : 'text', mime: noteMeta ? noteMeta.mime : 'text/html', prefix: noteMeta ? noteMeta.prefix : '', @@ -324,7 +327,10 @@ async function importTar(taskContext, fileBuffer, importRootNote) { await note.setContent(content); } else { - ({note} = await noteService.createNote(parentNoteId, noteTitle, content, { + ({note} = await noteService.createNewNote({ + parentNoteId: parentNoteId, + title: noteTitle, + content: content, noteId, type, mime, diff --git a/src/services/notes.js b/src/services/notes.js index f5b225039..4cd3e081d 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -34,30 +34,24 @@ async function triggerNoteTitleChanged(note) { await eventService.emit(eventService.NOTE_TITLE_CHANGED, note); } -function deriveTypeAndMime(noteData, parentNote) { - if (!noteData.type) { - if (parentNote.type === 'text' || parentNote.type === 'code') { - noteData.type = parentNote.type; - noteData.mime = parentNote.mime; - } else { - // inheriting note type makes sense only for text and code - noteData.type = 'text'; - noteData.mime = 'text/html'; - } +function deriveMime(type, mime) { + if (!type) { + throw new Error(`Note type is a required param`); } - if (!noteData.mime) { - if (noteData.type === 'text') { - noteData.mime = 'text/html'; - } else if (noteData.type === 'code') { - noteData.mime = 'text/plain'; - } else if (noteData.type === 'relation-map' || noteData.type === 'search') { - noteData.mime = 'application/json'; - } + if (mime) { + return mime; } - noteData.type = noteData.type || parentNote.type; - noteData.mime = noteData.mime || parentNote.mime; + if (type === 'text') { + mime = 'text/html'; + } else if (type === 'code') { + mime = 'text/plain'; + } else if (['relation-map', 'search'].includes(type)) { + mime = 'application/json'; + } + + return mime; } async function copyChildAttributes(parentNote, childNote) { @@ -92,7 +86,7 @@ async function copyChildAttributes(parentNote, childNote) { * - {integer} notePosition - default is last existing notePosition in a parent + 10 * * @param params - * @return {Promise<{note: Entity, branch: Entity}>} + * @return {Promise<{note: Note, branch: Branch}>} */ async function createNewNote(params) { const parentNote = await repository.getNote(params.parentNoteId); @@ -105,14 +99,12 @@ async function createNewNote(params) { throw new Error(`Note title must not be empty`); } - deriveTypeAndMime(params, parentNote); - const note = await new Note({ noteId: params.noteId, // optionally can force specific noteId title: params.title, isProtected: !!params.isProtected, type: params.type, - mime: params.mime + mime: deriveMime(params.type, params.mime) }).save(); await note.setContent(params.content); @@ -161,41 +153,6 @@ async function createTextNote(parentNoteId, title, content = "", params = {}) { return await createNewNote(params); } -/** - * @deprecated - */ -async function createNote(parentNoteId, title, content = "", extraOptions = {}) { - if (!parentNoteId) throw new Error("Empty parentNoteId"); - if (!title) throw new Error("Empty title"); - - const noteData = { - title: title, - content: content, - target: 'into', - noteId: extraOptions.noteId, - isProtected: !!extraOptions.isProtected, - type: extraOptions.type, - mime: extraOptions.mime, - dateCreated: extraOptions.dateCreated, - isExpanded: extraOptions.isExpanded, - notePosition: extraOptions.notePosition - }; - - const {note, branch} = await createNewNote(parentNoteId, title, noteData); - - for (const attr of extraOptions.attributes || []) { - await attributeService.createAttribute({ - noteId: note.noteId, - type: attr.type, - name: attr.name, - value: attr.value, - isInheritable: !!attr.isInheritable - }); - } - - return {note, branch}; -} - async function protectNoteRecursively(note, protect, taskContext) { await protectNote(note, protect); @@ -555,7 +512,6 @@ sqlInit.dbReady.then(() => { module.exports = { createNewNote, - createNote, updateNote, deleteBranch, protectNoteRecursively, diff --git a/src/tools/generate_document.js b/src/tools/generate_document.js index 7c2fb6b1e..bf250ca77 100644 --- a/src/tools/generate_document.js +++ b/src/tools/generate_document.js @@ -50,7 +50,12 @@ async function start() { const content = loremIpsum({ count: paragraphCount, units: 'paragraphs', sentenceLowerBound: 1, sentenceUpperBound: 15, paragraphLowerBound: 3, paragraphUpperBound: 10, format: 'html' }); - const {note} = await noteService.createNote(getRandomParentNoteId(), title, content); + const {note} = await noteService.createNewNote({ + parentNoteId: getRandomParentNoteId(), + title, + content, + type: 'text' + }); console.log(`Created note ${i}: ${title}`); From 60231de0edea8a7c1eba386eee54c0f4bad88fe7 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 16 Nov 2019 12:28:47 +0100 Subject: [PATCH 04/13] refactoring of note creation APIs WIP --- src/public/javascripts/services/tree.js | 4 +--- src/routes/api/notes.js | 4 +++- src/services/notes.js | 25 ++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index dbbaa84be..e938f188a 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -649,11 +649,9 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { const newNoteName = extraOptions.title || "new note"; - const {note, branch} = await server.post('notes/' + parentNoteId + '/children', { + const {note, branch} = await server.post(`notes/${parentNoteId}/children?target=${target}&targetBranchId=${node.data.branchId}`, { title: newNoteName, content: extraOptions.content, - target: target, - target_branchId: node.data.branchId, isProtected: extraOptions.isProtected, type: extraOptions.type }); diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 210776074..330bf8182 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -56,7 +56,9 @@ async function createNote(req) { const params = Object.assign({}, req.body); // clone params.parentNoteId = req.params.parentNoteId; - const { note, branch } = await noteService.createNewNote(params); + const { target, targetBranchId } = req.query; + + const { note, branch } = await noteService.createNewNoteWithTarget(target, targetBranchId, params); note.cssClass = (await note.getLabels("cssClass")).map(label => label.value).join(" "); diff --git a/src/services/notes.js b/src/services/notes.js index 4cd3e081d..7f2f89045 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -76,7 +76,7 @@ async function copyChildAttributes(parentNote, childNote) { * - {string} parentNoteId * - {string} title * - {*} content - * - {string} type + * - {string} type - text, code, file, image, search, book, relation-map * * Following are optional (have defaults) * - {string} mime - value is derived from default mimes for type @@ -128,6 +128,28 @@ async function createNewNote(params) { }; } +async function createNewNoteWithTarget(target, targetBranchId, params) { + if (target === 'into') { + return await createNewNote(params); + } + else if (target === 'after') { + const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [noteData.target_branchId]); + + // not updating utcDateModified to avoig having to sync whole rows + await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0', + [params.parentNoteId, afterNote.notePosition]); + + params.notePosition = afterNote.notePosition + 10; + + await createNewNote(params); + + await syncTableService.addNoteReorderingSync(params.parentNoteId); + } + else { + throw new Error(`Unknown target ${target}`); + } +} + // methods below should be probably just backend API methods async function createJsonNote(parentNoteId, title, content = {}, params = {}) { params.parentNoteId = parentNoteId; @@ -512,6 +534,7 @@ sqlInit.dbReady.then(() => { module.exports = { createNewNote, + createNewNoteWithTarget, updateNote, deleteBranch, protectNoteRecursively, From 3d294c51637c508d1eda98e840b1bd8edb268495 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 16 Nov 2019 17:00:22 +0100 Subject: [PATCH 05/13] refactoring of note creation APIs WIP --- src/entities/note.js | 4 ++++ src/public/javascripts/services/tree.js | 2 +- src/services/notes.js | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/entities/note.js b/src/entities/note.js index 7a3e89aa4..9c85cc9d0 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -114,6 +114,10 @@ class Note extends Entity { /** @returns {Promise} */ async setContent(content) { + if (content === null || content === undefined) { + throw new Error(`Cannot set null content to note ${this.noteId}`); + } + // force updating note itself so that dateModified is represented correctly even for the content this.forcedChange = true; this.contentLength = content.length; diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index e938f188a..58aface6e 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -651,7 +651,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { const {note, branch} = await server.post(`notes/${parentNoteId}/children?target=${target}&targetBranchId=${node.data.branchId}`, { title: newNoteName, - content: extraOptions.content, + content: extraOptions.content || "", isProtected: extraOptions.isProtected, type: extraOptions.type }); diff --git a/src/services/notes.js b/src/services/notes.js index 7f2f89045..08d148291 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -129,6 +129,14 @@ async function createNewNote(params) { } async function createNewNoteWithTarget(target, targetBranchId, params) { + if (!params.type) { + const parentNote = await repository.getNote(params.parentNoteId); + + // code note type can be inherited, otherwise text is default + params.type = parentNote.type === 'code' ? 'code' : 'text'; + params.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html'; + } + if (target === 'into') { return await createNewNote(params); } From 8a7228146c87c4db305fa272bae3447a91b3669f Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 16 Nov 2019 17:56:49 +0100 Subject: [PATCH 06/13] simpler ENEX parsing --- package.json | 3 +-- src/services/import/enex.js | 44 +++++++++++++------------------------ 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index e762d8c37..3d0016a46 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,7 @@ "turndown": "5.0.3", "turndown-plugin-gfm": "1.0.2", "unescape": "1.0.1", - "ws": "7.2.0", - "xml2js": "0.4.22" + "ws": "7.2.0" }, "devDependencies": { "electron": "6.0.12", diff --git a/src/services/import/enex.js b/src/services/import/enex.js index 2d9bd57cc..0bf1b56a2 100644 --- a/src/services/import/enex.js +++ b/src/services/import/enex.js @@ -1,7 +1,6 @@ const sax = require("sax"); const fileType = require('file-type'); const stream = require('stream'); -const xml2js = require('xml2js'); const log = require("../log"); const utils = require("../utils"); const noteService = require("../notes"); @@ -22,8 +21,6 @@ let resource; async function importEnex(taskContext, file, parentNote) { const saxStream = sax.createStream(true); - const xmlBuilder = new xml2js.Builder({ headless: true }); - const parser = new xml2js.Parser({ explicitArray: true }); const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex") ? file.originalname.substr(0, file.originalname.length - 5) @@ -43,28 +40,20 @@ async function importEnex(taskContext, file, parentNote) { // when we finish parsing. We use this to be sure that all saving has been finished before returning successfully. const saveNotePromises = []; - async function parseXml(text) { - return new Promise(function(resolve, reject) - { - parser.parseString(text, function (err, result) { - if (err) { - reject(err); - } - else { - resolve(result); - } - }); - }); - } + function extractContent(content) { + const openingNoteIndex = content.indexOf(''); - function extractContent(enNote) { - // [] thing is workaround for https://github.com/Leonidas-from-XIV/node-xml2js/issues/484 - let content = xmlBuilder.buildObject([enNote]); + if (openingNoteIndex !== -1) { + content = content.substr(openingNoteIndex + 9); + } - const endOfFirstTagIndex = content.indexOf('>'); + const closingNoteIndex = content.lastIndexOf(''); - // strip the <0> and tags - content = content.substr(endOfFirstTagIndex + 1, content.length - endOfFirstTagIndex - 5).trim(); + if (closingNoteIndex !== -1) { + content = content.substr(0, closingNoteIndex); + } + + content = content.trim(); // workaround for https://github.com/ckeditor/ckeditor5-list/issues/116 content = content.replace(/
  • \s+
    /g, "
  • "); @@ -205,10 +194,7 @@ async function importEnex(taskContext, file, parentNote) { // make a copy because stream continues with the next async call and note gets overwritten let {title, content, attributes, resources, utcDateCreated} = note; - const xmlObject = await parseXml(content); - - // following is workaround for this issue: https://github.com/Leonidas-from-XIV/node-xml2js/issues/484 - content = extractContent(xmlObject['en-note']); + content = extractContent(content); const noteEntity = (await noteService.createNewNote({ parentNoteId: rootNote.noteId, @@ -220,8 +206,8 @@ async function importEnex(taskContext, file, parentNote) { isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), })).note; - for (const attr of resource.attributes) { - await note.addAttribute(attr.type, attr.name, attr.value); + for (const attr of attributes) { + await noteEntity.addAttribute(attr.type, attr.name, attr.value); } taskContext.increaseProgressCount(); @@ -250,7 +236,7 @@ async function importEnex(taskContext, file, parentNote) { })).note; for (const attr of resource.attributes) { - await note.addAttribute(attr.type, attr.name, attr.value); + await noteEntity.addAttribute(attr.type, attr.name, attr.value); } taskContext.increaseProgressCount(); From 767aaa18f4834dc2fbd92208b6b55d569aad01ab Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 16 Nov 2019 18:04:13 +0100 Subject: [PATCH 07/13] fix OPML import --- package.json | 3 ++- src/services/import/opml.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d0016a46..e762d8c37 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,8 @@ "turndown": "5.0.3", "turndown-plugin-gfm": "1.0.2", "unescape": "1.0.1", - "ws": "7.2.0" + "ws": "7.2.0", + "xml2js": "0.4.22" }, "devDependencies": { "electron": "6.0.12", diff --git a/src/services/import/opml.js b/src/services/import/opml.js index b4bd1a70b..fc993dbf7 100644 --- a/src/services/import/opml.js +++ b/src/services/import/opml.js @@ -48,6 +48,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) { parentNoteId, title, content, + type: 'text', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() }); From c141f4b2c02debe16f0b583e2d73eba7254bd98f Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 17 Nov 2019 10:22:26 +0100 Subject: [PATCH 08/13] tab row styling change --- src/public/javascripts/services/tab_row.js | 24 +++++++++++++++---- src/public/javascripts/services/tree.js | 2 +- src/public/javascripts/services/tree_cache.js | 2 +- src/public/stylesheets/desktop.css | 22 ++++++++++++++++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/public/javascripts/services/tab_row.js b/src/public/javascripts/services/tab_row.js index 1d5b4048b..870da284a 100644 --- a/src/public/javascripts/services/tab_row.js +++ b/src/public/javascripts/services/tab_row.js @@ -29,6 +29,7 @@ const tabTemplate = ` `; const newTabButtonTemplate = `
    +
    `; +const fillerTemplate = `
    `; class TabRow { constructor(el) { @@ -40,9 +41,10 @@ class TabRow { this.setupStyleEl(); this.setupEvents(); - this.layoutTabs(); this.setupDraggabilly(); this.setupNewButton(); + this.setupFiller(); + this.layoutTabs(); this.setVisibility(); } @@ -109,12 +111,17 @@ class TabRow { const widths = []; let extraWidthRemaining = totalExtraWidthDueToFlooring; + for (let i = 0; i < numberOfTabs; i += 1) { const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0; widths.push(flooredClampedTargetWidth + extraWidth); if (extraWidthRemaining > 0) extraWidthRemaining -= 1; } + if (this.fillerEl) { + this.fillerEl.style.width = extraWidthRemaining + "px"; + } + return widths; } @@ -129,8 +136,9 @@ class TabRow { }); const newTabPosition = position; + const fillerPosition = position + 32; - return {tabPositions, newTabPosition}; + return {tabPositions, newTabPosition, fillerPosition}; } layoutTabs() { @@ -151,13 +159,14 @@ class TabRow { let styleHTML = ''; - const {tabPositions, newTabPosition} = this.getTabPositions(); + const {tabPositions, newTabPosition, fillerPosition} = this.getTabPositions(); tabPositions.forEach((position, i) => { styleHTML += `.note-tab:nth-child(${ i + 1 }) { transform: translate3d(${ position }px, 0, 0)} `; }); styleHTML += `.note-new-tab { transform: translate3d(${ newTabPosition }px, 0, 0) } `; + styleHTML += `.tab-row-filler { transform: translate3d(${ fillerPosition }px, 0, 0) } `; this.styleEl.innerHTML = styleHTML; } @@ -387,11 +396,18 @@ class TabRow { this.newTabEl = div.firstElementChild; this.tabContentEl.appendChild(this.newTabEl); - this.layoutTabs(); this.newTabEl.addEventListener('click', _ => this.emit('newTab')); } + setupFiller() { + const div = document.createElement('div'); + div.innerHTML = fillerTemplate; + this.fillerEl = div.firstElementChild; + + this.tabContentEl.appendChild(this.fillerEl); + } + closest(value, array) { let closest = Infinity; let closestIndex = -1; diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 58aface6e..d56f79108 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -368,7 +368,7 @@ async function treeInitialized() { const notePath = location.hash.substr(1); const noteId = treeUtils.getNoteIdFromNotePath(notePath); - if (await treeCache.noteExists(noteId)) { + if (noteId && await treeCache.noteExists(noteId)) { for (const tab of openTabs) { tab.active = false; } diff --git a/src/public/javascripts/services/tree_cache.js b/src/public/javascripts/services/tree_cache.js index 02bfcbfe4..b72617eea 100644 --- a/src/public/javascripts/services/tree_cache.js +++ b/src/public/javascripts/services/tree_cache.js @@ -144,7 +144,7 @@ class TreeCache { else { return this.notes[noteId]; } - }).filter(note => note !== null); + }).filter(note => !!note); } /** @return {Promise} */ diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 5e52a1cb3..1a041df1b 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -68,6 +68,11 @@ body { margin-bottom: 2px; margin-top: 2px; margin-right: 8px; + border-color: transparent !important; +} + +#header button:hover { + border-color: var(--button-border-color) !important; } #history-navigation { @@ -215,7 +220,7 @@ body { .note-new-tab { position: absolute; left: 0; - height: 32px; + height: 33px; width: 32px; border: 0; margin: 0; @@ -223,6 +228,7 @@ body { text-align: center; font-size: 24px; cursor: pointer; + border-bottom: 1px solid var(--button-border-color); } .note-new-tab:hover { @@ -230,6 +236,14 @@ body { border-radius: 5px; } +.tab-row-filler { + position: absolute; + left: 0; + background: linear-gradient(to right, var(--button-border-color), transparent); + height: 1px; + margin-top: 32px; +} + .note-tab-row .note-tab[active] { z-index: 5; } @@ -244,6 +258,7 @@ body { top: 10px; animation: note-tab-was-just-added 120ms forwards ease-in-out; } + .note-tab-row .note-tab .note-tab-wrapper { position: absolute; display: flex; @@ -271,6 +286,7 @@ body { padding-left: 2px; padding-right: 2px; } + .note-tab-row .note-tab .note-tab-title { flex: 1; vertical-align: top; @@ -278,12 +294,15 @@ body { white-space: nowrap; color: var(--muted-text-color); } + .note-tab-row .note-tab[is-small] .note-tab-title { margin-left: 0; } + .note-tab-row .note-tab[active] .note-tab-title { color: var(--main-text-color); } + .note-tab-row .note-tab .note-tab-drag-handle { position: absolute; top: 0; @@ -293,6 +312,7 @@ body { border-top-left-radius: 8px; border-top-right-radius: 8px; } + .note-tab-row .note-tab .note-tab-close { flex-grow: 0; flex-shrink: 0; From 1d5daa8dfd2f8d038ff2a076d43b9a808fc091d0 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 17 Nov 2019 11:30:11 +0100 Subject: [PATCH 09/13] action icons now have hover border as well --- src/public/stylesheets/desktop.css | 2 +- src/public/stylesheets/mobile.css | 2 +- src/public/stylesheets/style.css | 4 ++++ src/services/backend_script_api.js | 19 ------------------- src/views/desktop.ejs | 2 -- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 1a041df1b..9ad1b4ace 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -84,7 +84,7 @@ body { #global-buttons { display: flex; justify-content: space-around; - padding: 10px 0 10px 0; + padding: 3px 0 3px 0; border: 1px solid var(--main-border-color); border-radius: 7px; margin: 5px 15px 5px 5px; diff --git a/src/public/stylesheets/mobile.css b/src/public/stylesheets/mobile.css index ab1a78969..b97088011 100644 --- a/src/public/stylesheets/mobile.css +++ b/src/public/stylesheets/mobile.css @@ -19,7 +19,7 @@ html, body { display: flex; flex-shrink: 0; justify-content: space-around; - padding: 10px 0 10px 0; + padding: 3px 0 3px 0; margin: 0 10px 0 16px; } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 97f0f6eac..a2b87e2cb 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -227,9 +227,13 @@ span.fancytree-node.archived { .icon-action:hover { text-decoration: none; + border-color: var(--button-border-color); } .icon-action { + border: 1px solid transparent; + border-radius: 3px; + padding: 5px; cursor: pointer; font-size: 1.5em; } diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index 505bd9721..2e1f639fe 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -194,25 +194,6 @@ function BackendScriptApi(currentNote, apiParams) { */ this.createNote = noteService.createNewNote; - /** - * Creates new note according to given params and force all connected clients to refresh their tree. - * - * @method - * - * @param {string} parentNoteId - create new note under this parent - * @param {string} title - * @param {string} [content=""] - * @param {CreateNoteParams} [extraOptions={}] - * @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch - */ - this.createNoteAndRefresh = async function(parentNoteId, title, content, extraOptions) { - const ret = await noteService.createNewNote(parentNoteId, title, content, extraOptions); - - ws.refreshTree(); - - return ret; - }; - /** * Log given message to trilium logs. * diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index e6a078476..3b92481b0 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -14,8 +14,6 @@ From 73a6c66379b3cd8b570d3b452dad818821f443c8 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 17 Nov 2019 11:39:06 +0100 Subject: [PATCH 10/13] header styling changes --- src/public/stylesheets/desktop.css | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 9ad1b4ace..cb5430304 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -59,7 +59,7 @@ body { background-color: var(--header-background-color); display: flex; align-items: center; - padding: 4px; + padding-top: 4px; } #header button { @@ -71,14 +71,17 @@ body { border-color: transparent !important; } +#header button.btn-sm .bx { + position: relative; + top: 1px; +} + #header button:hover { border-color: var(--button-border-color) !important; } #history-navigation { margin: 0 15px 0 5px; - position: relative; - top: 2px; } #global-buttons { @@ -170,6 +173,13 @@ body { cursor: pointer; position: relative; top: -1px; + border: 1px solid transparent; + padding: 2px; + border-radius: 2px; +} + +.refresh-search-button:hover { + border-color: var(--button-border-color); } .note-title-row { From a1181623b7bb7998be11259f2d57c1567e4f0ccd Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 17 Nov 2019 11:58:05 +0100 Subject: [PATCH 11/13] hide sidebar button styling --- src/public/stylesheets/desktop.css | 8 ++++++++ src/views/sidebar.ejs | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index cb5430304..eada71e04 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -381,6 +381,14 @@ body { .hide-sidebar-button { color: var(--main-text-color); + background: none; + border: 1px solid transparent; + padding: 2px 8px 2px 8px; + border-radius: 2px; +} + +.hide-sidebar-button:hover { + border-color: var(--button-border-color); } .note-detail-sidebar { diff --git a/src/views/sidebar.ejs b/src/views/sidebar.ejs index 8849711c8..24c83e684 100644 --- a/src/views/sidebar.ejs +++ b/src/views/sidebar.ejs @@ -1,6 +1,6 @@
    -
    - +
    +
    From b4709e8ee5ca212ca1f9b52bb2ad85ea5da0a469 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 18 Nov 2019 19:32:27 +0100 Subject: [PATCH 12/13] "distraction free mode" renamed to more standard "zen mode" --- package-lock.json | 19 ++++++++++--------- package.json | 2 +- .../javascripts/services/entrypoints.js | 6 +++--- src/public/stylesheets/desktop.css | 2 +- src/public/stylesheets/style.css | 4 ++-- src/views/desktop.ejs | 4 ++-- src/views/dialogs/help.ejs | 2 +- src/views/sidebar.ejs | 2 +- src/views/tabs.ejs | 2 +- src/views/title.ejs | 6 +++--- 10 files changed, 25 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5dc48fd44..506e6c846 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5990,9 +5990,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6546,12 +6546,13 @@ } }, "imagemin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.0.tgz", - "integrity": "sha512-TXvCSSIYl4KQUASur9S0+E4olVECzvxvZABU9rNqsza7vzIrUQMRTjyczGf8OmtcgvZ9jOYyinXW3epOpd/04A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz", + "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", "requires": { "file-type": "^12.0.0", "globby": "^10.0.0", + "graceful-fs": "^4.2.2", "junk": "^3.1.0", "make-dir": "^3.0.0", "p-pipe": "^3.0.0", @@ -9903,9 +9904,9 @@ "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", + "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==" }, "pify": { "version": "4.0.1", diff --git a/package.json b/package.json index e762d8c37..e69cb2386 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "http-proxy-agent": "2.1.0", "https-proxy-agent": "3.0.1", "image-type": "4.1.0", - "imagemin": "7.0.0", + "imagemin": "7.0.1", "imagemin-giflossy": "5.1.10", "imagemin-mozjpeg": "8.0.0", "imagemin-pngquant": "8.0.0", diff --git a/src/public/javascripts/services/entrypoints.js b/src/public/javascripts/services/entrypoints.js index 8256ae96c..d0142e55d 100644 --- a/src/public/javascripts/services/entrypoints.js +++ b/src/public/javascripts/services/entrypoints.js @@ -92,12 +92,12 @@ function registerEntrypoints() { } } + // hide (toggle) everything except for the note content for zen mode utils.bindGlobalShortcut('alt+m', e => { - $(".hide-toggle").toggle(); - $("#container").toggleClass("distraction-free-mode"); + $(".hide-in-zen-mode").toggle(); + $("#container").toggleClass("zen-mode"); }); - // hide (toggle) everything except for the note content for distraction free writing utils.bindGlobalShortcut('alt+t', e => { const date = new Date(); const dateString = utils.formatDateTime(date); diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index eada71e04..206be72b1 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -18,7 +18,7 @@ body { grid-gap: 0; } -#container.distraction-free-mode { +#container.zen-mode { grid-template-areas: "tab-container" !important; grid-template-rows: auto diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index a2b87e2cb..00d4d2be6 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -144,9 +144,9 @@ ul.fancytree-container { margin-top: 0; } -/** we disable shield background when in distraction free mode because I couldn't get it to stay static +/** we disable shield background when in zen mode because I couldn't get it to stay static (it kept growing with content) */ -#container:not(.distraction-free-mode) .note-tab-content.protected { +#container:not(.zen-mode) .note-tab-content.protected { /* DON'T COLLAPSE THE RULES INTO SINGLE ONE, BACKGROUND WON'T DISPLAY */ background: url('../images/shield.svg') no-repeat; background-size: contain; diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index 3b92481b0..09b1527dd 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -10,7 +10,7 @@