for a case when note.content is pure text (e.g. "[protected]") which
+ // surround with
for a case when note's content is pure text (e.g. "[protected]") which
// then fails the jquery non-empty text test
- content += '
' + note.content + '
';
+ content += '
' + note.noteContent.content + '
';
}
else if (note.type === 'code') {
content += $("
")
- .text(note.content)
+ .text(note.noteContent.content)
.prop('outerHTML');
}
else if (note.type === 'image') {
diff --git a/src/routes/api/file_upload.js b/src/routes/api/file_upload.js
index 73620b865..663432b2f 100644
--- a/src/routes/api/file_upload.js
+++ b/src/routes/api/file_upload.js
@@ -51,7 +51,7 @@ async function downloadNoteFile(noteId, res) {
res.setHeader('Content-Disposition', utils.getContentDisposition(fileName));
res.setHeader('Content-Type', note.mime);
- res.send(note.content);
+ res.send((await note.getNoteContent()).content);
}
async function downloadFile(req, res) {
diff --git a/src/routes/api/image.js b/src/routes/api/image.js
index 15b6236d9..f1f2ec8b8 100644
--- a/src/routes/api/image.js
+++ b/src/routes/api/image.js
@@ -21,7 +21,7 @@ async function returnImage(req, res) {
res.set('Content-Type', image.mime);
- res.send(image.content);
+ res.send((await note.getNoteContent()).content);
}
async function uploadImage(req) {
diff --git a/src/services/export/opml.js b/src/services/export/opml.js
index b00ed9340..5a9f164ed 100644
--- a/src/services/export/opml.js
+++ b/src/services/export/opml.js
@@ -17,7 +17,7 @@ async function exportToOpml(branch, res) {
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
const preparedTitle = prepareText(title);
- const preparedContent = prepareText(note.content);
+ const preparedContent = prepareText(await note.getContent());
res.write(`\n`);
diff --git a/src/services/export/single.js b/src/services/export/single.js
index c6cc979dd..e5f2b833e 100644
--- a/src/services/export/single.js
+++ b/src/services/export/single.js
@@ -18,30 +18,32 @@ async function exportSingleNote(branch, format, res) {
let payload, extension, mime;
+ const noteContent = await note.getNoteContent();
+
if (note.type === 'text') {
if (format === 'html') {
- if (!note.content.toLowerCase().includes("';
+ if (!noteContent.content.toLowerCase().includes("';
}
- payload = html.prettyPrint(note.content, {indent_size: 2});
+ payload = html.prettyPrint(noteContent.content, {indent_size: 2});
extension = 'html';
mime = 'text/html';
}
else if (format === 'markdown') {
const turndownService = new TurndownService();
- payload = turndownService.turndown(note.content);
+ payload = turndownService.turndown(noteContent.content);
extension = 'md';
mime = 'text/markdown'
}
}
else if (note.type === 'code') {
- payload = note.content;
+ payload = noteContent.content;
extension = mimeTypes.extension(note.mime) || 'code';
mime = note.mime;
}
else if (note.type === 'relation-map' || note.type === 'search') {
- payload = note.content;
+ payload = noteContent.content;
extension = 'json';
mime = 'application/json';
}
diff --git a/src/services/export/tar.js b/src/services/export/tar.js
index 9a42aedfb..7634f3cbb 100644
--- a/src/services/export/tar.js
+++ b/src/services/export/tar.js
@@ -123,7 +123,7 @@ async function exportToTar(branch, format, res) {
const childBranches = await note.getChildBranches();
// if it's a leaf then we'll export it even if it's empty
- if (note.content.length > 0 || childBranches.length === 0) {
+ if ((await note.getContent()).length > 0 || childBranches.length === 0) {
meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames);
}
@@ -147,19 +147,21 @@ async function exportToTar(branch, format, res) {
return meta;
}
- function prepareContent(note, format) {
+ async function prepareContent(note, format) {
+ const content = await note.getContent();
+
if (format === 'html') {
- if (!note.content.toLowerCase().includes("';
+ if (!content.toLowerCase().includes("';
}
- return html.prettyPrint(note.content, {indent_size: 2});
+ return html.prettyPrint(content, {indent_size: 2});
}
else if (format === 'markdown') {
- return turndownService.turndown(note.content);
+ return turndownService.turndown(content);
}
else {
- return note.content;
+ return content;
}
}
@@ -179,7 +181,7 @@ async function exportToTar(branch, format, res) {
notePaths[note.noteId] = path + (noteMeta.dataFileName || noteMeta.dirFileName);
if (noteMeta.dataFileName) {
- const content = prepareContent(note, noteMeta.format);
+ const content = await prepareContent(note, noteMeta.format);
pack.entry({name: path + noteMeta.dataFileName, size: content.length}, content);
}
diff --git a/src/services/import/enex.js b/src/services/import/enex.js
index 183e2b192..5793a1d05 100644
--- a/src/services/import/enex.js
+++ b/src/services/import/enex.js
@@ -218,6 +218,8 @@ async function importEnex(file, parentNote) {
mime: 'text/html'
})).note;
+ const noteContent = await noteEntity.getNoteContent();
+
for (const resource of resources) {
const hash = utils.md5(resource.content);
@@ -238,8 +240,8 @@ async function importEnex(file, parentNote) {
const resourceLink = `${utils.escapeHtml(resource.title)}`;
- noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink);
- }
+ noteContent.content = noteContent.content.replace(mediaRegex, resourceLink);
+ };
if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) {
try {
@@ -249,12 +251,12 @@ async function importEnex(file, parentNote) {
const imageLink = `
`;
- noteEntity.content = noteEntity.content.replace(mediaRegex, imageLink);
+ noteContent.content = noteContent.content.replace(mediaRegex, imageLink);
- if (!note.content.includes(imageLink)) {
+ if (!noteContent.content.includes(imageLink)) {
// if there wasn't any match for the reference, we'll add the image anyway
// otherwise image would be removed since no note would include it
- note.content += imageLink;
+ noteContent.content += imageLink;
}
} catch (e) {
log.error("error when saving image from ENEX file: " + e);
@@ -267,7 +269,7 @@ async function importEnex(file, parentNote) {
}
// save updated content with links to files/images
- await noteEntity.save();
+ await noteContent.save();
}
saxStream.on("closetag", async tag => {
diff --git a/src/services/import/tar.js b/src/services/import/tar.js
index 8170274a4..b631bbeb8 100644
--- a/src/services/import/tar.js
+++ b/src/services/import/tar.js
@@ -245,8 +245,10 @@ async function importTar(fileBuffer, importRootNote) {
let note = await repository.getNote(noteId);
if (note) {
- note.content = content;
- await note.save();
+ const noteContent = await note.getNoteContent();
+
+ noteContent.content = content;
+ await noteContent.save();
}
else {
const noteTitle = getNoteTitle(filePath, noteMeta);
diff --git a/src/services/notes.js b/src/services/notes.js
index 7cebb2dee..9e07260e0 100644
--- a/src/services/notes.js
+++ b/src/services/notes.js
@@ -8,6 +8,7 @@ const eventService = require('./events');
const repository = require('./repository');
const cls = require('../services/cls');
const Note = require('../entities/note');
+const NoteContent = require('../entities/note_content');
const Link = require('../entities/link');
const NoteRevision = require('../entities/note_revision');
const Branch = require('../entities/branch');
@@ -87,12 +88,16 @@ async function createNewNote(parentNoteId, noteData) {
const note = await new Note({
noteId: noteData.noteId, // optionally can force specific noteId
title: noteData.title,
- content: noteData.content,
isProtected: noteData.isProtected,
type: noteData.type || 'text',
mime: noteData.mime || 'text/html'
}).save();
+ note.noteContent = await new NoteContent({
+ noteId: note.noteId,
+ content: noteData.content
+ });
+
const branch = await new Branch({
noteId: note.noteId,
parentNoteId: parentNoteId,
@@ -284,6 +289,12 @@ async function saveLinks(note, content) {
}
async function saveNoteRevision(note) {
+ // files and images are immutable, they can't be updated
+ // but we don't even version titles which is probably not correct
+ if (note.type !== 'file' || note.type !== 'image' || await note.hasLabel('disableVersioning')) {
+ return;
+ }
+
const now = new Date();
const noteRevisionSnapshotTimeInterval = parseInt(await optionService.getOption('noteRevisionSnapshotTimeInterval'));
@@ -294,16 +305,12 @@ async function saveNoteRevision(note) {
const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.dateCreated).getTime();
- if (note.type !== 'file'
- && !await note.hasLabel('disableVersioning')
- && !existingNoteRevisionId
- && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) {
-
+ if (!existingNoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) {
await new NoteRevision({
noteId: note.noteId,
// title and text should be decrypted now
title: note.title,
- content: note.content,
+ content: note.noteContent.content,
type: note.type,
mime: note.mime,
isProtected: false, // will be fixed in the protectNoteRevisions() call
@@ -320,22 +327,23 @@ async function updateNote(noteId, noteUpdates) {
throw new Error(`Note ${noteId} is not available for change!`);
}
- if (note.type === 'file' || note.type === 'image') {
- // files and images are immutable, they can't be updated
- noteUpdates.content = note.content;
- }
-
await saveNoteRevision(note);
const noteTitleChanged = note.title !== noteUpdates.title;
- noteUpdates.content = await saveLinks(note, noteUpdates.content);
+ noteUpdates.noteContent.content = await saveLinks(note, noteUpdates.noteContent.content);
note.title = noteUpdates.title;
- note.setContent(noteUpdates.content);
note.isProtected = noteUpdates.isProtected;
await note.save();
+ if (note.type !== 'file' && note.type !== 'image') {
+ const noteContent = await note.getNoteContent();
+ noteContent.content = noteUpdates.noteContent.content;
+ noteContent.isProtected = noteUpdates.isProtected;
+ await noteContent.save();
+ }
+
if (noteTitleChanged) {
await triggerNoteTitleChanged(note);
}
@@ -394,7 +402,7 @@ async function cleanupDeletedNotes() {
// it's better to not use repository for this because it will complain about saving protected notes
// out of protected session
- await sql.execute("UPDATE notes SET content = NULL WHERE isDeleted = 1 AND content IS NOT NULL AND dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
+ await sql.execute("UPDATE note_contents SET content = NULL WHERE content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
}
diff --git a/src/services/repository.js b/src/services/repository.js
index e6cb4fc27..967bdd19d 100644
--- a/src/services/repository.js
+++ b/src/services/repository.js
@@ -42,6 +42,14 @@ async function getNote(noteId) {
return await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
}
+/** @returns {Promise} */
+async function getNoteWithContent(noteId) {
+ const note = await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
+ await note.getNoteContent();
+
+ return note;
+}
+
/** @returns {Promise} */
async function getNoteContent(noteContentId) {
return await getEntity("SELECT * FROM note_contents WHERE noteContentId = ?", [noteContentId]);
@@ -126,6 +134,7 @@ module.exports = {
getEntities,
getEntity,
getNote,
+ getNoteWithContent,
getNoteContent,
getBranch,
getAttribute,
diff --git a/src/services/script.js b/src/services/script.js
index 596213a84..11c96af3e 100644
--- a/src/services/script.js
+++ b/src/services/script.js
@@ -56,10 +56,10 @@ async function executeBundle(bundle, apiParams = {}) {
*/
async function executeScript(script, params, startNoteId, currentNoteId, originEntityName, originEntityId) {
const startNote = await repository.getNote(startNoteId);
- const currentNote = await repository.getNote(currentNoteId);
+ const currentNote = await repository.getNoteWithContent(currentNoteId);
const originEntity = await repository.getEntityFromName(originEntityName, originEntityId);
- currentNote.content = `return await (${script}\r\n)(${getParams(params)})`;
+ currentNote.noteContent.content = `return await (${script}\r\n)(${getParams(params)})`;
currentNote.type = 'code';
currentNote.mime = 'application/javascript;env=backend';
@@ -158,7 +158,7 @@ apiContext.modules['${note.noteId}'] = {};
${root ? 'return ' : ''}await ((async function(exports, module, require, api` + (modules.length > 0 ? ', ' : '') +
modules.map(child => sanitizeVariableName(child.title)).join(', ') + `) {
try {
-${note.content};
+${await note.getContent()};
} catch (e) { throw new Error("Load of script note \\"${note.title}\\" (${note.noteId}) failed with: " + e.message); }
if (!module.exports) module.exports = {};
for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];
@@ -167,7 +167,7 @@ for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];
`;
}
else if (note.isHtml()) {
- bundle.html += note.content;
+ bundle.html += await note.getContent();
}
return bundle;
diff --git a/src/services/sync_update.js b/src/services/sync_update.js
index 630f95ebc..a27304097 100644
--- a/src/services/sync_update.js
+++ b/src/services/sync_update.js
@@ -48,14 +48,16 @@ async function updateEntity(sync, entity, sourceId) {
}
}
-function deserializeNoteContentBuffer(note) {
- if (note.content !== null && (note.type === 'file' || note.type === 'image')) {
- note.content = Buffer.from(note.content, 'base64');
+async function deserializeNoteContentBuffer(note) {
+ const noteContent = await note.getNoteContent();
+
+ if (noteContent.content !== null && (note.type === 'file' || note.type === 'image')) {
+ noteContent.content = Buffer.from(noteContent.content, 'base64');
}
}
async function updateNote(entity, sourceId) {
- deserializeNoteContentBuffer(entity);
+ await deserializeNoteContentBuffer(entity);
const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]);