mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
yet another refactoring of working with note's payload/content
This commit is contained in:
parent
4bdcf32475
commit
29c60581a6
13
db/migrations/0130__cleanup_note_contents.sql
Normal file
13
db/migrations/0130__cleanup_note_contents.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "note_contents_mig" (
|
||||||
|
`noteId` TEXT NOT NULL,
|
||||||
|
`content` TEXT NULL DEFAULT NULL,
|
||||||
|
`hash` TEXT DEFAULT "" NOT NULL,
|
||||||
|
`utcDateModified` TEXT NOT NULL,
|
||||||
|
PRIMARY KEY(`noteId`)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO note_contents_mig (noteId, content, hash, utcDateModified)
|
||||||
|
SELECT noteId, content, hash, utcDateModified FROM note_contents;
|
||||||
|
|
||||||
|
DROP TABLE note_contents;
|
||||||
|
ALTER TABLE note_contents_mig RENAME TO note_contents;
|
@ -26,7 +26,13 @@ class Entity {
|
|||||||
|
|
||||||
this.hash = this.generateHash();
|
this.hash = this.generateHash();
|
||||||
|
|
||||||
this.isChanged = origHash !== this.hash;
|
if (this.forcedChange) {
|
||||||
|
this.isChanged = true;
|
||||||
|
delete this.forcedChange;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.isChanged = origHash !== this.hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateIdIfNecessary() {
|
generateIdIfNecessary() {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
const Note = require('../entities/note');
|
const Note = require('../entities/note');
|
||||||
const NoteContent = require('../entities/note_content');
|
|
||||||
const NoteRevision = require('../entities/note_revision');
|
const NoteRevision = require('../entities/note_revision');
|
||||||
const Link = require('../entities/link');
|
const Link = require('../entities/link');
|
||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
@ -13,7 +12,6 @@ const ENTITY_NAME_TO_ENTITY = {
|
|||||||
"attributes": Attribute,
|
"attributes": Attribute,
|
||||||
"branches": Branch,
|
"branches": Branch,
|
||||||
"notes": Note,
|
"notes": Note,
|
||||||
"note_contents": NoteContent,
|
|
||||||
"note_revisions": NoteRevision,
|
"note_revisions": NoteRevision,
|
||||||
"recent_notes": RecentNote,
|
"recent_notes": RecentNote,
|
||||||
"options": Option,
|
"options": Option,
|
||||||
@ -50,9 +48,6 @@ function createEntityFromRow(row) {
|
|||||||
else if (row.branchId) {
|
else if (row.branchId) {
|
||||||
entity = new Branch(row);
|
entity = new Branch(row);
|
||||||
}
|
}
|
||||||
else if (row.noteContentId) {
|
|
||||||
entity = new NoteContent(row);
|
|
||||||
}
|
|
||||||
else if (row.noteId) {
|
else if (row.noteId) {
|
||||||
entity = new Note(row);
|
entity = new Note(row);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
const Attribute = require('./attribute');
|
const Attribute = require('./attribute');
|
||||||
const NoteContent = require('./note_content');
|
|
||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
const sql = require('../services/sql');
|
const sql = require('../services/sql');
|
||||||
|
const utils = require('../services/utils');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
const noteFulltextService = require('../services/note_fulltext');
|
const noteFulltextService = require('../services/note_fulltext');
|
||||||
|
const syncTableService = require('../services/sync_table');
|
||||||
|
|
||||||
const LABEL = 'label';
|
const LABEL = 'label';
|
||||||
const LABEL_DEFINITION = 'label-definition';
|
const LABEL_DEFINITION = 'label-definition';
|
||||||
@ -56,37 +57,33 @@ class Note extends Entity {
|
|||||||
protectedSessionService.decryptNote(this);
|
protectedSessionService.decryptNote(this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// saving ciphertexts in case we do want to update protected note outside of protected session
|
|
||||||
// (which is allowed)
|
|
||||||
this.titleCipherText = this.title;
|
|
||||||
this.title = "[protected]";
|
this.title = "[protected]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<NoteContent>} */
|
/** @returns {Promise<*>} */
|
||||||
async getNoteContent() {
|
async getContent() {
|
||||||
if (!this.noteContent) {
|
if (this.content === undefined) {
|
||||||
this.noteContent = await repository.getEntity(`SELECT * FROM note_contents WHERE noteId = ?`, [this.noteId]);
|
this.content = await sql.getValue(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]);
|
||||||
|
|
||||||
if (!this.noteContent) {
|
if (this.isProtected) {
|
||||||
throw new Error("Note content not found for noteId=" + this.noteId);
|
if (this.isContentAvailable) {
|
||||||
|
protectedSessionService.decryptNoteContent(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.content = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isStringNote()) {
|
if (this.isStringNote()) {
|
||||||
this.noteContent.content = this.noteContent.content === null
|
this.content = this.content === null
|
||||||
? "" : this.noteContent.content.toString("UTF-8");
|
? ""
|
||||||
|
: this.content.toString("UTF-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.noteContent;
|
return this.content;
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {Promise<*>} */
|
|
||||||
async getContent() {
|
|
||||||
const noteContent = await this.getNoteContent();
|
|
||||||
|
|
||||||
return noteContent.content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<*>} */
|
/** @returns {Promise<*>} */
|
||||||
@ -98,14 +95,31 @@ class Note extends Entity {
|
|||||||
|
|
||||||
/** @returns {Promise} */
|
/** @returns {Promise} */
|
||||||
async setContent(content) {
|
async setContent(content) {
|
||||||
if (!this.noteContent) {
|
this.content = content;
|
||||||
// make sure it is loaded
|
|
||||||
await this.getNoteContent();
|
const pojo = {
|
||||||
|
noteId: this.noteId,
|
||||||
|
content: content,
|
||||||
|
utcDateModified: dateUtils.utcNowDateTime(),
|
||||||
|
hash: utils.hash(this.noteId + "|" + content)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isProtected) {
|
||||||
|
if (this.isContentAvailable) {
|
||||||
|
protectedSessionService.encryptNoteContent(pojo);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`Cannot update content of noteId=${this.noteId} since we're out of protected session.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.noteContent.content = content;
|
await sql.upsert("note_contents", "noteId", pojo);
|
||||||
|
|
||||||
await this.noteContent.save();
|
await syncTableService.addNoteContentSync(this.noteId);
|
||||||
|
|
||||||
|
this.forcedChange = true;
|
||||||
|
|
||||||
|
await this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise} */
|
/** @returns {Promise} */
|
||||||
@ -687,14 +701,13 @@ class Note extends Entity {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// updating protected note outside of protected session means we will keep original ciphertexts
|
// updating protected note outside of protected session means we will keep original ciphertexts
|
||||||
pojo.title = pojo.titleCipherText;
|
delete pojo.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete pojo.isContentAvailable;
|
delete pojo.isContentAvailable;
|
||||||
delete pojo.__attributeCache;
|
delete pojo.__attributeCache;
|
||||||
delete pojo.titleCipherText;
|
delete pojo.content;
|
||||||
delete pojo.noteContent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterSaving() {
|
async afterSaving() {
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Entity = require('./entity');
|
|
||||||
const protectedSessionService = require('../services/protected_session');
|
|
||||||
const repository = require('../services/repository');
|
|
||||||
const dateUtils = require('../services/date_utils');
|
|
||||||
const noteFulltextService = require('../services/note_fulltext');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This represents a Note which is a central object in the Trilium Notes project.
|
|
||||||
*
|
|
||||||
* @property {string} noteContentId - primary key
|
|
||||||
* @property {string} noteId - reference to owning note
|
|
||||||
* @property {boolean} isProtected - true if note content is protected
|
|
||||||
* @property {blob} content - note content - e.g. HTML text for text notes, file payload for files
|
|
||||||
* @property {string} utcDateCreated
|
|
||||||
* @property {string} utcDateModified
|
|
||||||
*
|
|
||||||
* @extends Entity
|
|
||||||
*/
|
|
||||||
class NoteContent extends Entity {
|
|
||||||
static get entityName() {
|
|
||||||
return "note_contents";
|
|
||||||
}
|
|
||||||
|
|
||||||
static get primaryKeyName() {
|
|
||||||
return "noteContentId";
|
|
||||||
}
|
|
||||||
|
|
||||||
static get hashedProperties() {
|
|
||||||
return ["noteContentId", "noteId", "isProtected", "content"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param row - object containing database row from "note_contents" table
|
|
||||||
*/
|
|
||||||
constructor(row) {
|
|
||||||
super(row);
|
|
||||||
|
|
||||||
this.isProtected = !!this.isProtected;
|
|
||||||
/* true if content (meaning any kind of potentially encrypted content) is either not encrypted
|
|
||||||
* or encrypted, but with available protected session (so effectively decrypted) */
|
|
||||||
this.isContentAvailable = true;
|
|
||||||
|
|
||||||
// check if there's noteContentId, otherwise this is a new entity which wasn't encrypted yet
|
|
||||||
if (this.isProtected && this.noteContentId) {
|
|
||||||
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
|
|
||||||
|
|
||||||
if (this.isContentAvailable) {
|
|
||||||
protectedSessionService.decryptNoteContent(this);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// saving ciphertexts in case we do want to update protected note outside of protected session
|
|
||||||
// (which is allowed)
|
|
||||||
this.contentCipherText = this.content;
|
|
||||||
this.content = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<Note>}
|
|
||||||
*/
|
|
||||||
async getNote() {
|
|
||||||
return await repository.getNote(this.noteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeSaving() {
|
|
||||||
if (!this.utcDateCreated) {
|
|
||||||
this.utcDateCreated = dateUtils.utcNowDateTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (this.isChanged) {
|
|
||||||
this.utcDateModified = dateUtils.utcNowDateTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cannot be static!
|
|
||||||
updatePojo(pojo) {
|
|
||||||
if (pojo.isProtected) {
|
|
||||||
if (this.isContentAvailable) {
|
|
||||||
protectedSessionService.encryptNoteContent(pojo);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// updating protected note outside of protected session means we will keep original ciphertext
|
|
||||||
pojo.content = pojo.contentCipherText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete pojo.isContentAvailable;
|
|
||||||
delete pojo.contentCipherText;
|
|
||||||
}
|
|
||||||
|
|
||||||
async afterSaving() {
|
|
||||||
noteFulltextService.triggerNoteFulltextUpdate(this.noteId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = NoteContent;
|
|
@ -8,7 +8,7 @@ function showDialog() {
|
|||||||
|
|
||||||
$dialog.modal();
|
$dialog.modal();
|
||||||
|
|
||||||
const noteText = noteDetailService.getActiveNote().noteContent.content;
|
const noteText = noteDetailService.getActiveNote().content;
|
||||||
|
|
||||||
$noteSource.text(formatHtml(noteText));
|
$noteSource.text(formatHtml(noteText));
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ class NoteFull extends NoteShort {
|
|||||||
super(treeCache, row);
|
super(treeCache, row);
|
||||||
|
|
||||||
/** @param {string} */
|
/** @param {string} */
|
||||||
this.noteContent = row.noteContent;
|
this.content = row.content;
|
||||||
|
|
||||||
/** @param {string} */
|
/** @param {string} */
|
||||||
this.utcDateCreated = row.utcDateCreated;
|
this.utcDateCreated = row.utcDateCreated;
|
||||||
|
@ -117,10 +117,7 @@ async function saveNote() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
note.title = $noteTitle.val();
|
note.title = $noteTitle.val();
|
||||||
|
note.content = getActiveNoteContent(note);
|
||||||
if (note.noteContent != null) { // might be null for file/image
|
|
||||||
note.noteContent.content = getActiveNoteContent(note);
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's important to set the flag back to false immediatelly after retrieving title and content
|
// it's important to set the flag back to false immediatelly after retrieving title and content
|
||||||
// otherwise we might overwrite another change (especially async code)
|
// otherwise we might overwrite another change (especially async code)
|
||||||
|
@ -49,7 +49,7 @@ async function show() {
|
|||||||
// this needs to happen after the element is shown, otherwise the editor won't be refreshed
|
// this needs to happen after the element is shown, otherwise the editor won't be refreshed
|
||||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||||
// we provide fallback
|
// we provide fallback
|
||||||
codeEditor.setValue(activeNote.noteContent.content || "");
|
codeEditor.setValue(activeNote.content || "");
|
||||||
|
|
||||||
const info = CodeMirror.findModeByMIME(activeNote.mime);
|
const info = CodeMirror.findModeByMIME(activeNote.mime);
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ async function show() {
|
|||||||
$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
||||||
$fileType.text(activeNote.mime);
|
$fileType.text(activeNote.mime);
|
||||||
|
|
||||||
if (activeNote.noteContent && activeNote.noteContent.content) {
|
if (activeNote.content) {
|
||||||
$previewRow.show();
|
$previewRow.show();
|
||||||
$previewContent.text(activeNote.noteContent.content);
|
$previewContent.text(activeNote.content);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$previewRow.hide();
|
$previewRow.hide();
|
||||||
|
@ -92,9 +92,9 @@ function loadMapData() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (activeNote.noteContent.content) {
|
if (activeNote.content) {
|
||||||
try {
|
try {
|
||||||
mapData = JSON.parse(activeNote.noteContent.content);
|
mapData = JSON.parse(activeNote.content);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Could not parse content: ", e);
|
console.log("Could not parse content: ", e);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ function show() {
|
|||||||
$component.show();
|
$component.show();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(noteDetailService.getActiveNote().noteContent.content);
|
const json = JSON.parse(noteDetailService.getActiveNote().content);
|
||||||
|
|
||||||
$searchString.val(json.searchString);
|
$searchString.val(json.searchString);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ async function show() {
|
|||||||
|
|
||||||
$component.show();
|
$component.show();
|
||||||
|
|
||||||
textEditor.setData(noteDetailService.getActiveNote().noteContent.content);
|
textEditor.setData(noteDetailService.getActiveNote().content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContent() {
|
function getContent() {
|
||||||
|
@ -113,11 +113,11 @@ async function renderTooltip(note, attributes) {
|
|||||||
if (note.type === 'text') {
|
if (note.type === 'text') {
|
||||||
// surround with <div> for a case when note's content is pure text (e.g. "[protected]") which
|
// surround with <div> for a case when note's content is pure text (e.g. "[protected]") which
|
||||||
// then fails the jquery non-empty text test
|
// then fails the jquery non-empty text test
|
||||||
content += '<div>' + note.noteContent.content + '</div>';
|
content += '<div>' + note.content + '</div>';
|
||||||
}
|
}
|
||||||
else if (note.type === 'code') {
|
else if (note.type === 'code') {
|
||||||
content += $("<pre>")
|
content += $("<pre>")
|
||||||
.text(note.noteContent.content)
|
.text(note.content)
|
||||||
.prop('outerHTML');
|
.prop('outerHTML');
|
||||||
}
|
}
|
||||||
else if (note.type === 'image') {
|
else if (note.type === 'image') {
|
||||||
|
@ -51,7 +51,7 @@ async function downloadNoteFile(noteId, res) {
|
|||||||
res.setHeader('Content-Disposition', utils.getContentDisposition(fileName));
|
res.setHeader('Content-Disposition', utils.getContentDisposition(fileName));
|
||||||
res.setHeader('Content-Type', note.mime);
|
res.setHeader('Content-Type', note.mime);
|
||||||
|
|
||||||
res.send((await note.getNoteContent()).content);
|
res.send(await note.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadFile(req, res) {
|
async function downloadFile(req, res) {
|
||||||
|
@ -13,10 +13,10 @@ async function getNote(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (note.isStringNote()) {
|
if (note.isStringNote()) {
|
||||||
const noteContent = await note.getNoteContent();
|
await note.getContent();
|
||||||
|
|
||||||
if (note.type === 'file') {
|
if (note.type === 'file') {
|
||||||
noteContent.content = noteContent.content.substr(0, 10000);
|
note.content = note.content.substr(0, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,11 @@ async function searchNotes(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveSearchToNote(req) {
|
async function saveSearchToNote(req) {
|
||||||
const noteContent = {
|
const content = {
|
||||||
searchString: req.params.searchString
|
searchString: req.params.searchString
|
||||||
};
|
};
|
||||||
|
|
||||||
const {note} = await noteService.createNote('root', req.params.searchString, noteContent, {
|
const {note} = await noteService.createNote('root', req.params.searchString, content, {
|
||||||
json: true,
|
json: true,
|
||||||
type: 'search',
|
type: 'search',
|
||||||
mime: "application/json"
|
mime: "application/json"
|
||||||
|
@ -4,8 +4,8 @@ const build = require('./build');
|
|||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||||
|
|
||||||
const APP_DB_VERSION = 129;
|
const APP_DB_VERSION = 130;
|
||||||
const SYNC_VERSION = 7;
|
const SYNC_VERSION = 8;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
appVersion: packageJson.version,
|
appVersion: packageJson.version,
|
||||||
|
@ -8,17 +8,16 @@ const messagingService = require('./messaging');
|
|||||||
const ApiToken = require('../entities/api_token');
|
const ApiToken = require('../entities/api_token');
|
||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
const Note = require('../entities/note');
|
const Note = require('../entities/note');
|
||||||
const NoteContent = require('../entities/note_content');
|
|
||||||
const Attribute = require('../entities/attribute');
|
const Attribute = require('../entities/attribute');
|
||||||
const NoteRevision = require('../entities/note_revision');
|
const NoteRevision = require('../entities/note_revision');
|
||||||
const RecentNote = require('../entities/recent_note');
|
const RecentNote = require('../entities/recent_note');
|
||||||
const Option = require('../entities/option');
|
const Option = require('../entities/option');
|
||||||
const Link = require('../entities/link');
|
const Link = require('../entities/link');
|
||||||
|
|
||||||
async function getHash(entityConstructor, whereBranch) {
|
async function getHash(tableName, primaryKeyName, whereBranch) {
|
||||||
// subselect is necessary to have correct ordering in GROUP_CONCAT
|
// subselect is necessary to have correct ordering in GROUP_CONCAT
|
||||||
const query = `SELECT GROUP_CONCAT(hash) FROM (SELECT hash FROM ${entityConstructor.entityName} `
|
const query = `SELECT GROUP_CONCAT(hash) FROM (SELECT hash FROM ${tableName} `
|
||||||
+ (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${entityConstructor.primaryKeyName})`;
|
+ (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${primaryKeyName})`;
|
||||||
|
|
||||||
let contentToHash = await sql.getValue(query);
|
let contentToHash = await sql.getValue(query);
|
||||||
|
|
||||||
@ -33,15 +32,15 @@ async function getHashes() {
|
|||||||
const startTime = new Date();
|
const startTime = new Date();
|
||||||
|
|
||||||
const hashes = {
|
const hashes = {
|
||||||
notes: await getHash(Note),
|
notes: await getHash(Note.entityName, Note.primaryKeyName),
|
||||||
note_contents: await getHash(NoteContent),
|
note_contents: await getHash("note_contents", "noteId"),
|
||||||
branches: await getHash(Branch),
|
branches: await getHash(Branch.entityName, Branch.primaryKeyName),
|
||||||
note_revisions: await getHash(NoteRevision),
|
note_revisions: await getHash(NoteRevision.entityName, NoteRevision.primaryKeyName),
|
||||||
recent_notes: await getHash(RecentNote),
|
recent_notes: await getHash(RecentNote.entityName, RecentNote.primaryKeyName),
|
||||||
options: await getHash(Option, "isSynced = 1"),
|
options: await getHash(Option.entityName, Option.primaryKeyName, "isSynced = 1"),
|
||||||
attributes: await getHash(Attribute),
|
attributes: await getHash(Attribute.entityName, Attribute.primaryKeyName),
|
||||||
api_tokens: await getHash(ApiToken),
|
api_tokens: await getHash(ApiToken.entityName, ApiToken.primaryKeyName),
|
||||||
links: await getHash(Link)
|
links: await getHash(Link.entityName, Link.primaryKeyName)
|
||||||
};
|
};
|
||||||
|
|
||||||
const elapseTimeMs = Date.now() - startTime.getTime();
|
const elapseTimeMs = Date.now() - startTime.getTime();
|
||||||
|
@ -18,32 +18,32 @@ async function exportSingleNote(exportContext, branch, format, res) {
|
|||||||
|
|
||||||
let payload, extension, mime;
|
let payload, extension, mime;
|
||||||
|
|
||||||
const noteContent = await note.getNoteContent();
|
let content = await note.getContent();
|
||||||
|
|
||||||
if (note.type === 'text') {
|
if (note.type === 'text') {
|
||||||
if (format === 'html') {
|
if (format === 'html') {
|
||||||
if (!noteContent.content.toLowerCase().includes("<html")) {
|
if (!content.toLowerCase().includes("<html")) {
|
||||||
noteContent.content = '<html><head><meta charset="utf-8"></head><body>' + noteContent.content + '</body></html>';
|
content = '<html><head><meta charset="utf-8"></head><body>' + content + '</body></html>';
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = html.prettyPrint(noteContent.content, {indent_size: 2});
|
payload = html.prettyPrint(content, {indent_size: 2});
|
||||||
extension = 'html';
|
extension = 'html';
|
||||||
mime = 'text/html';
|
mime = 'text/html';
|
||||||
}
|
}
|
||||||
else if (format === 'markdown') {
|
else if (format === 'markdown') {
|
||||||
const turndownService = new TurndownService();
|
const turndownService = new TurndownService();
|
||||||
payload = turndownService.turndown(noteContent.content);
|
payload = turndownService.turndown(content);
|
||||||
extension = 'md';
|
extension = 'md';
|
||||||
mime = 'text/x-markdown'
|
mime = 'text/x-markdown'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (note.type === 'code') {
|
else if (note.type === 'code') {
|
||||||
payload = noteContent.content;
|
payload = content;
|
||||||
extension = mimeTypes.extension(note.mime) || 'code';
|
extension = mimeTypes.extension(note.mime) || 'code';
|
||||||
mime = note.mime;
|
mime = note.mime;
|
||||||
}
|
}
|
||||||
else if (note.type === 'relation-map' || note.type === 'search') {
|
else if (note.type === 'relation-map' || note.type === 'search') {
|
||||||
payload = noteContent.content;
|
payload = content;
|
||||||
extension = 'json';
|
extension = 'json';
|
||||||
mime = 'application/json';
|
mime = 'application/json';
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ async function importEnex(importContext, file, parentNote) {
|
|||||||
|
|
||||||
importContext.increaseProgressCount();
|
importContext.increaseProgressCount();
|
||||||
|
|
||||||
const noteContent = await noteEntity.getNoteContent();
|
let noteContent = await noteEntity.getContent();
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
const hash = utils.md5(resource.content);
|
const hash = utils.md5(resource.content);
|
||||||
@ -248,7 +248,7 @@ async function importEnex(importContext, file, parentNote) {
|
|||||||
|
|
||||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||||
|
|
||||||
noteContent.content = noteContent.content.replace(mediaRegex, resourceLink);
|
noteContent = noteContent.replace(mediaRegex, resourceLink);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) {
|
if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) {
|
||||||
@ -259,12 +259,12 @@ async function importEnex(importContext, file, parentNote) {
|
|||||||
|
|
||||||
const imageLink = `<img src="${url}">`;
|
const imageLink = `<img src="${url}">`;
|
||||||
|
|
||||||
noteContent.content = noteContent.content.replace(mediaRegex, imageLink);
|
noteContent = noteContent.replace(mediaRegex, imageLink);
|
||||||
|
|
||||||
if (!noteContent.content.includes(imageLink)) {
|
if (!noteContent.includes(imageLink)) {
|
||||||
// if there wasn't any match for the reference, we'll add the image anyway
|
// 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
|
// otherwise image would be removed since no note would include it
|
||||||
noteContent.content += imageLink;
|
noteContent += imageLink;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("error when saving image from ENEX file: " + e);
|
log.error("error when saving image from ENEX file: " + e);
|
||||||
@ -276,7 +276,7 @@ async function importEnex(importContext, file, parentNote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// save updated content with links to files/images
|
// save updated content with links to files/images
|
||||||
await noteContent.save();
|
await noteEntity.setContent(noteContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
saxStream.on("closetag", async tag => {
|
saxStream.on("closetag", async tag => {
|
||||||
|
@ -259,10 +259,7 @@ async function importTar(importContext, fileBuffer, importRootNote) {
|
|||||||
let note = await repository.getNote(noteId);
|
let note = await repository.getNote(noteId);
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
const noteContent = await note.getNoteContent();
|
await note.setContent(content);
|
||||||
|
|
||||||
noteContent.content = content;
|
|
||||||
await noteContent.save();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const noteTitle = getNoteTitle(filePath, noteMeta);
|
const noteTitle = getNoteTitle(filePath, noteMeta);
|
||||||
|
@ -14,14 +14,14 @@ async function updateNoteFulltext(note) {
|
|||||||
let contentHash = null;
|
let contentHash = null;
|
||||||
|
|
||||||
if (['text', 'code'].includes(note.type)) {
|
if (['text', 'code'].includes(note.type)) {
|
||||||
const noteContent = await note.getNoteContent();
|
content = await note.getContent();
|
||||||
content = noteContent.content;
|
|
||||||
|
|
||||||
if (note.type === 'text' && note.mime === 'text/html') {
|
if (note.type === 'text' && note.mime === 'text/html') {
|
||||||
content = html2plaintext(content);
|
content = html2plaintext(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHash = noteContent.hash;
|
// FIXME
|
||||||
|
//contentHash = noteContent.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// optimistically try to update first ...
|
// optimistically try to update first ...
|
||||||
|
@ -8,7 +8,6 @@ const eventService = require('./events');
|
|||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
const cls = require('../services/cls');
|
const cls = require('../services/cls');
|
||||||
const Note = require('../entities/note');
|
const Note = require('../entities/note');
|
||||||
const NoteContent = require('../entities/note_content');
|
|
||||||
const Link = require('../entities/link');
|
const Link = require('../entities/link');
|
||||||
const NoteRevision = require('../entities/note_revision');
|
const NoteRevision = require('../entities/note_revision');
|
||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
@ -93,10 +92,7 @@ async function createNewNote(parentNoteId, noteData) {
|
|||||||
noteData.content = noteData.content || "";
|
noteData.content = noteData.content || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
note.noteContent = await new NoteContent({
|
await note.setContent(noteData.content);
|
||||||
noteId: note.noteId,
|
|
||||||
content: noteData.content
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
const branch = await new Branch({
|
const branch = await new Branch({
|
||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
@ -338,16 +334,11 @@ async function updateNote(noteId, noteUpdates) {
|
|||||||
note.isProtected = noteUpdates.isProtected;
|
note.isProtected = noteUpdates.isProtected;
|
||||||
await note.save();
|
await note.save();
|
||||||
|
|
||||||
const noteContent = await note.getNoteContent();
|
|
||||||
|
|
||||||
if (!['file', 'image'].includes(note.type)) {
|
if (!['file', 'image'].includes(note.type)) {
|
||||||
noteUpdates.noteContent.content = await saveLinks(note, noteUpdates.noteContent.content);
|
noteUpdates.content = await saveLinks(note, noteUpdates.content);
|
||||||
|
|
||||||
noteContent.content = noteUpdates.noteContent.content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
noteContent.isProtected = noteUpdates.isProtected;
|
await note.setContent(noteUpdates.content);
|
||||||
await noteContent.save();
|
|
||||||
|
|
||||||
if (noteTitleChanged) {
|
if (noteTitleChanged) {
|
||||||
await triggerNoteTitleChanged(note);
|
await triggerNoteTitleChanged(note);
|
||||||
|
@ -56,18 +56,14 @@ function decryptNote(note) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptNoteContent(noteContent) {
|
function decryptNoteContent(note) {
|
||||||
if (!noteContent.isProtected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (noteContent.content != null) {
|
if (note.content != null) {
|
||||||
noteContent.content = dataEncryptionService.decrypt(getDataKey(), noteContent.content.toString());
|
note.content = dataEncryptionService.decrypt(getDataKey(), note.content.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
e.message = `Cannot decrypt note content for noteContentId=${noteContent.noteContentId}: ` + e.message;
|
e.message = `Cannot decrypt content for noteId=${note.noteId}: ` + e.message;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,8 +94,8 @@ function encryptNote(note) {
|
|||||||
note.title = dataEncryptionService.encrypt(getDataKey(), note.title);
|
note.title = dataEncryptionService.encrypt(getDataKey(), note.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptNoteContent(noteContent) {
|
function encryptNoteContent(note) {
|
||||||
noteContent.content = dataEncryptionService.encrypt(getDataKey(), noteContent.content);
|
note.content = dataEncryptionService.encrypt(getDataKey(), note.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptNoteRevision(revision) {
|
function encryptNoteRevision(revision) {
|
||||||
|
@ -42,19 +42,6 @@ async function getNote(noteId) {
|
|||||||
return await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
return await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<Note|null>} */
|
|
||||||
async function getNoteWithContent(noteId) {
|
|
||||||
const note = await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
|
||||||
await note.getNoteContent();
|
|
||||||
|
|
||||||
return note;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {Promise<NoteContent|null>} */
|
|
||||||
async function getNoteContent(noteContentId) {
|
|
||||||
return await getEntity("SELECT * FROM note_contents WHERE noteContentId = ?", [noteContentId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {Promise<Branch|null>} */
|
/** @returns {Promise<Branch|null>} */
|
||||||
async function getBranch(branchId) {
|
async function getBranch(branchId) {
|
||||||
return await getEntity("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
return await getEntity("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
||||||
@ -138,8 +125,6 @@ module.exports = {
|
|||||||
getEntities,
|
getEntities,
|
||||||
getEntity,
|
getEntity,
|
||||||
getNote,
|
getNote,
|
||||||
getNoteWithContent,
|
|
||||||
getNoteContent,
|
|
||||||
getBranch,
|
getBranch,
|
||||||
getAttribute,
|
getAttribute,
|
||||||
getOption,
|
getOption,
|
||||||
|
@ -58,10 +58,10 @@ async function executeBundle(bundle, apiParams = {}) {
|
|||||||
*/
|
*/
|
||||||
async function executeScript(script, params, startNoteId, currentNoteId, originEntityName, originEntityId) {
|
async function executeScript(script, params, startNoteId, currentNoteId, originEntityName, originEntityId) {
|
||||||
const startNote = await repository.getNote(startNoteId);
|
const startNote = await repository.getNote(startNoteId);
|
||||||
const currentNote = await repository.getNoteWithContent(currentNoteId);
|
const currentNote = await repository.getNote(currentNoteId);
|
||||||
const originEntity = await repository.getEntityFromName(originEntityName, originEntityId);
|
const originEntity = await repository.getEntityFromName(originEntityName, originEntityId);
|
||||||
|
|
||||||
currentNote.noteContent.content = `return await (${script}\r\n)(${getParams(params)})`;
|
currentNote.content = `return await (${script}\r\n)(${getParams(params)})`;
|
||||||
currentNote.type = 'code';
|
currentNote.type = 'code';
|
||||||
currentNote.mime = 'application/javascript;env=backend';
|
currentNote.mime = 'application/javascript;env=backend';
|
||||||
|
|
||||||
|
@ -78,7 +78,6 @@ async function createInitialDatabase(username, password) {
|
|||||||
await sql.executeScript(schema);
|
await sql.executeScript(schema);
|
||||||
|
|
||||||
const Note = require("../entities/note");
|
const Note = require("../entities/note");
|
||||||
const NoteContent = require("../entities/note_content");
|
|
||||||
const Branch = require("../entities/branch");
|
const Branch = require("../entities/branch");
|
||||||
|
|
||||||
const rootNote = await new Note({
|
const rootNote = await new Note({
|
||||||
@ -88,10 +87,7 @@ async function createInitialDatabase(username, password) {
|
|||||||
mime: 'text/html'
|
mime: 'text/html'
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
const rootNoteContent = await new NoteContent({
|
await rootNote.setContent('');
|
||||||
noteId: rootNote.noteId,
|
|
||||||
content: ''
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
await new Branch({
|
await new Branch({
|
||||||
branchId: 'root',
|
branchId: 'root',
|
||||||
|
@ -239,7 +239,7 @@ async function syncRequest(syncContext, method, requestPath, body) {
|
|||||||
|
|
||||||
const primaryKeys = {
|
const primaryKeys = {
|
||||||
"notes": "noteId",
|
"notes": "noteId",
|
||||||
"note_contents": "noteContentId",
|
"note_contents": "noteId",
|
||||||
"branches": "branchId",
|
"branches": "branchId",
|
||||||
"note_revisions": "noteRevisionId",
|
"note_revisions": "noteRevisionId",
|
||||||
"recent_notes": "branchId",
|
"recent_notes": "branchId",
|
||||||
|
@ -8,8 +8,8 @@ async function addNoteSync(noteId, sourceId) {
|
|||||||
await addEntitySync("notes", noteId, sourceId)
|
await addEntitySync("notes", noteId, sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addNoteContentSync(noteContentId, sourceId) {
|
async function addNoteContentSync(noteId, sourceId) {
|
||||||
await addEntitySync("note_contents", noteContentId, sourceId)
|
await addEntitySync("note_contents", noteId, sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addBranchSync(branchId, sourceId) {
|
async function addBranchSync(branchId, sourceId) {
|
||||||
|
@ -77,12 +77,12 @@ async function updateNoteContent(entity, sourceId) {
|
|||||||
await sql.transactional(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace("note_contents", entity);
|
await sql.replace("note_contents", entity);
|
||||||
|
|
||||||
await syncTableService.addNoteContentSync(entity.noteContentId, sourceId);
|
await syncTableService.addNoteContentSync(entity.noteId, sourceId);
|
||||||
|
|
||||||
noteFulltextService.triggerNoteFulltextUpdate(entity.noteId);
|
noteFulltextService.triggerNoteFulltextUpdate(entity.noteId);
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("Update/sync note content " + entity.noteContentId);
|
log.info("Update/sync note content for noteId=" + entity.noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user