rename "note revision" to just "revision"

This commit is contained in:
zadam 2023-06-04 23:01:40 +02:00
parent cb9feab7b2
commit 779751a234
46 changed files with 275 additions and 248 deletions

View File

@ -2,7 +2,7 @@
UPDATE etapi_tokens SET tokenHash = 'API token hash value'; UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\'; UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\';
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL; UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title'; UPDATE revisions SET title = 'title';
UPDATE attributes SET name = 'name', value = 'value' UPDATE attributes SET name = 'name', value = 'value'
WHERE type = 'label' WHERE type = 'label'
@ -28,7 +28,7 @@ UPDATE attributes SET name = 'name', value = 'value'
'widget', 'widget',
'noteInfoWidgetDisabled', 'noteInfoWidgetDisabled',
'linkMapWidgetDisabled', 'linkMapWidgetDisabled',
'noteRevisionsWidgetDisabled', 'revisionsWidgetDisabled',
'whatLinksHereWidgetDisabled', 'whatLinksHereWidgetDisabled',
'similarNotesWidgetDisabled', 'similarNotesWidgetDisabled',
'workspace', 'workspace',
@ -103,7 +103,7 @@ UPDATE attributes SET name = 'name'
'widget', 'widget',
'noteInfoWidgetDisabled', 'noteInfoWidgetDisabled',
'linkMapWidgetDisabled', 'linkMapWidgetDisabled',
'noteRevisionsWidgetDisabled', 'revisionsWidgetDisabled',
'whatLinksHereWidgetDisabled', 'whatLinksHereWidgetDisabled',
'similarNotesWidgetDisabled', 'similarNotesWidgetDisabled',
'workspace', 'workspace',

View File

@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS "revisions" (`revisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL,
type TEXT DEFAULT '' NOT NULL,
mime TEXT DEFAULT '' NOT NULL,
`title` TEXT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
blobId TEXT DEFAULT NULL,
`utcDateLastEdited` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
`dateLastEdited` TEXT NOT NULL,
`dateCreated` TEXT NOT NULL);
INSERT INTO revisions (revisionId, noteId, type, mime, title, isProtected, utcDateLastEdited, utcDateCreated, utcDateModified, dateLastEdited, dateCreated, blobId)
SELECT noteRevisionId, noteId, type, mime, title, isProtected, utcDateLastEdited, utcDateCreated, utcDateModified, dateLastEdited, dateCreated, blobId FROM note_revisions;
DROP TABLE note_revisions;
CREATE INDEX `IDX_revisions_noteId` ON `revisions` (`noteId`);
CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`);
CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`);
CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`);
UPDATE entity_changes SET entity_name = 'revisions' WHERE entity_name = 'note_revisions';

View File

@ -35,24 +35,26 @@ CREATE TABLE IF NOT EXISTS "notes" (
`isProtected` INT NOT NULL DEFAULT 0, `isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text', `type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html', `mime` TEXT NOT NULL DEFAULT 'text/html',
blobId TEXT DEFAULT NULL,
`isDeleted` INT NOT NULL DEFAULT 0, `isDeleted` INT NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL, `deleteId` TEXT DEFAULT NULL,
`dateCreated` TEXT NOT NULL, `dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL, `dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL, `utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL, blobId TEXT DEFAULT NULL, `utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`)); PRIMARY KEY(`noteId`));
CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY, CREATE TABLE IF NOT EXISTS "revisions" (`revisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL, `noteId` TEXT NOT NULL,
type TEXT DEFAULT '' NOT NULL, type TEXT DEFAULT '' NOT NULL,
mime TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL,
`title` TEXT NOT NULL, `title` TEXT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0, `isProtected` INT NOT NULL DEFAULT 0,
blobId TEXT DEFAULT NULL,
`utcDateLastEdited` TEXT NOT NULL, `utcDateLastEdited` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL, `utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL, `utcDateModified` TEXT NOT NULL,
`dateLastEdited` TEXT NOT NULL, `dateLastEdited` TEXT NOT NULL,
`dateCreated` TEXT NOT NULL, blobId TEXT DEFAULT NULL); `dateCreated` TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS "options" CREATE TABLE IF NOT EXISTS "options"
( (
name TEXT not null PRIMARY KEY, name TEXT not null PRIMARY KEY,
@ -84,11 +86,11 @@ CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`); CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`); CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`); CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (`noteId`); CREATE INDEX `IDX_revisions_noteId` ON `revisions` (`noteId`);
CREATE INDEX `IDX_note_revisions_utcDateCreated` ON `note_revisions` (`utcDateCreated`); CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`);
CREATE INDEX `IDX_note_revisions_utcDateLastEdited` ON `note_revisions` (`utcDateLastEdited`); CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_note_revisions_dateCreated` ON `note_revisions` (`dateCreated`); CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`);
CREATE INDEX `IDX_note_revisions_dateLastEdited` ON `note_revisions` (`dateLastEdited`); CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`);
CREATE INDEX `IDX_entity_changes_changeId` ON `entity_changes` (`changeId`); CREATE INDEX `IDX_entity_changes_changeId` ON `entity_changes` (`changeId`);
CREATE INDEX IDX_attributes_name_value CREATE INDEX IDX_attributes_name_value
on attributes (name, value); on attributes (name, value);

View File

@ -144,12 +144,12 @@ class Becca {
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
} }
/** @returns {BNoteRevision|null} */ /** @returns {BRevision|null} */
getNoteRevision(noteRevisionId) { getRevision(revisionId) {
const row = sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]); const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]);
const BNoteRevision = require("./entities/bnote_revision"); // avoiding circular dependency problems const BRevision = require("./entities/brevision.js"); // avoiding circular dependency problems
return row ? new BNoteRevision(row) : null; return row ? new BRevision(row) : null;
} }
/** @returns {BAttachment|null} */ /** @returns {BAttachment|null} */
@ -213,8 +213,8 @@ class Becca {
return null; return null;
} }
if (entityName === 'note_revisions') { if (entityName === 'revisions') {
return this.getNoteRevision(entityId); return this.getRevision(entityId);
} else if (entityName === 'attachments') { } else if (entityName === 'attachments') {
return this.getAttachment(entityId); return this.getAttachment(entityId);
} else if (entityName === 'blobs') { } else if (entityName === 'blobs') {
@ -243,12 +243,12 @@ class Becca {
return rows.map(row => new BRecentNote(row)); return rows.map(row => new BRecentNote(row));
} }
/** @returns {BNoteRevision[]} */ /** @returns {BRevision[]} */
getNoteRevisionsFromQuery(query, params = []) { getRevisionsFromQuery(query, params = []) {
const rows = sql.getRows(query, params); const rows = sql.getRows(query, params);
const BNoteRevision = require("./entities/bnote_revision"); // avoiding circular dependency problems const BRevision = require("./entities/brevision.js"); // avoiding circular dependency problems
return rows.map(row => new BNoteRevision(row)); return rows.map(row => new BRevision(row));
} }
/** Should be called when the set of all non-skeleton notes changes (added/removed) */ /** Should be called when the set of all non-skeleton notes changes (added/removed) */

View File

@ -170,7 +170,7 @@ class AbstractBeccaEntity {
return; return;
} }
if (sql.getValue("SELECT 1 FROM note_revisions WHERE blobId = ? LIMIT 1", [blobId])) { if (sql.getValue("SELECT 1 FROM revisions WHERE blobId = ? LIMIT 1", [blobId])) {
return; return;
} }

View File

@ -38,7 +38,7 @@ class BAttachment extends AbstractBeccaEntity {
/** @type {string} */ /** @type {string} */
this.attachmentId = row.attachmentId; this.attachmentId = row.attachmentId;
/** @type {string} either noteId or noteRevisionId to which this attachment belongs */ /** @type {string} either noteId or revisionId to which this attachment belongs */
this.parentId = row.parentId; this.parentId = row.parentId;
/** @type {string} */ /** @type {string} */
this.role = row.role; this.role = row.role;

View File

@ -6,7 +6,7 @@ const sql = require('../../services/sql');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils'); const dateUtils = require('../../services/date_utils');
const AbstractBeccaEntity = require("./abstract_becca_entity"); const AbstractBeccaEntity = require("./abstract_becca_entity");
const BNoteRevision = require("./bnote_revision"); const BRevision = require("./brevision.js");
const BAttachment = require("./battachment"); const BAttachment = require("./battachment");
const TaskContext = require("../../services/task_context"); const TaskContext = require("../../services/task_context");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
@ -1102,10 +1102,10 @@ class BNote extends AbstractBeccaEntity {
return minDistance; return minDistance;
} }
/** @returns {BNoteRevision[]} */ /** @returns {BRevision[]} */
getNoteRevisions() { getRevisions() {
return sql.getRows("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]) return sql.getRows("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
.map(row => new BNoteRevision(row)); .map(row => new BRevision(row));
} }
/** @returns {BAttachment[]} */ /** @returns {BAttachment[]} */
@ -1571,14 +1571,14 @@ class BNote extends AbstractBeccaEntity {
} }
/** /**
* @returns {BNoteRevision|null} * @returns {BRevision|null}
*/ */
saveNoteRevision() { saveRevision() {
return sql.transactional(() => { return sql.transactional(() => {
let noteContent = this.getContent(); let noteContent = this.getContent();
const contentMetadata = this.getContentMetadata(); const contentMetadata = this.getContentMetadata();
const noteRevision = new BNoteRevision({ const revision = new BRevision({
noteId: this.noteId, noteId: this.noteId,
// title and text should be decrypted now // title and text should be decrypted now
title: this.title, title: this.title,
@ -1596,7 +1596,7 @@ class BNote extends AbstractBeccaEntity {
dateCreated: dateUtils.localNowDateTime() dateCreated: dateUtils.localNowDateTime()
}, true); }, true);
noteRevision.save(); // to generate noteRevisionId which is then used to save attachments revision.save(); // to generate revisionId which is then used to save attachments
for (const noteAttachment of this.getAttachments()) { for (const noteAttachment of this.getAttachments()) {
if (noteAttachment.utcDateScheduledForErasureSince) { if (noteAttachment.utcDateScheduledForErasureSince) {
@ -1604,16 +1604,16 @@ class BNote extends AbstractBeccaEntity {
} }
const revisionAttachment = noteAttachment.copy(); const revisionAttachment = noteAttachment.copy();
revisionAttachment.parentId = noteRevision.noteRevisionId; revisionAttachment.parentId = revision.revisionId;
revisionAttachment.setContent(noteAttachment.getContent(), { forceSave: true }); revisionAttachment.setContent(noteAttachment.getContent(), { forceSave: true });
// content is rewritten to point to the revision attachments // content is rewritten to point to the revision attachments
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, `attachments/${revisionAttachment.attachmentId}`); noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, `attachments/${revisionAttachment.attachmentId}`);
} }
noteRevision.setContent(noteContent, { forceSave: true }); revision.setContent(noteContent, { forceSave: true });
return noteRevision; return revision;
}); });
} }

View File

@ -9,21 +9,21 @@ const sql = require("../../services/sql");
const BAttachment = require("./battachment"); const BAttachment = require("./battachment");
/** /**
* NoteRevision represents a snapshot of note's title and content at some point in the past. * Revision represents a snapshot of note's title and content at some point in the past.
* It's used for seamless note versioning. * It's used for seamless note versioning.
* *
* @extends AbstractBeccaEntity * @extends AbstractBeccaEntity
*/ */
class BNoteRevision extends AbstractBeccaEntity { class BRevision extends AbstractBeccaEntity {
static get entityName() { return "note_revisions"; } static get entityName() { return "revisions"; }
static get primaryKeyName() { return "noteRevisionId"; } static get primaryKeyName() { return "revisionId"; }
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; } static get hashedProperties() { return ["revisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
constructor(row, titleDecrypted = false) { constructor(row, titleDecrypted = false) {
super(); super();
/** @type {string} */ /** @type {string} */
this.noteRevisionId = row.noteRevisionId; this.revisionId = row.revisionId;
/** @type {string} */ /** @type {string} */
this.noteId = row.noteId; this.noteId = row.noteId;
/** @type {string} */ /** @type {string} */
@ -66,14 +66,14 @@ class BNoteRevision extends AbstractBeccaEntity {
} }
isContentAvailable() { isContentAvailable() {
return !this.noteRevisionId // new note which was not encrypted yet return !this.revisionId // new note which was not encrypted yet
|| !this.isProtected || !this.isProtected
|| protectedSessionService.isProtectedSessionAvailable() || protectedSessionService.isProtectedSessionAvailable()
} }
/* /*
* Note revision content has quite special handling - it's not a separate entity, but a lazily loaded * Note revision content has quite special handling - it's not a separate entity, but a lazily loaded
* part of NoteRevision entity with its own sync. The reason behind this hybrid design is that * part of Revision entity with its own sync. The reason behind this hybrid design is that
* content can be quite large, and it's not necessary to load it / fill memory for any note access even * content can be quite large, and it's not necessary to load it / fill memory for any note access even
* if we don't need a content, especially for bulk operations like search. * if we don't need a content, especially for bulk operations like search.
* *
@ -88,7 +88,7 @@ class BNoteRevision extends AbstractBeccaEntity {
/** /**
* @param content * @param content
* @param {object} [opts] * @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BNoteRevision entity * @param {object} [opts.forceSave=false] - will also save this BRevision entity
*/ */
setContent(content, opts) { setContent(content, opts) {
this._setContent(content, opts); this._setContent(content, opts);
@ -100,7 +100,7 @@ class BNoteRevision extends AbstractBeccaEntity {
SELECT attachments.* SELECT attachments.*
FROM attachments FROM attachments
WHERE parentId = ? WHERE parentId = ?
AND isDeleted = 0`, [this.noteRevisionId]) AND isDeleted = 0`, [this.revisionId])
.map(row => new BAttachment(row)); .map(row => new BAttachment(row));
} }
@ -112,7 +112,7 @@ class BNoteRevision extends AbstractBeccaEntity {
getPojo() { getPojo() {
return { return {
noteRevisionId: this.noteRevisionId, revisionId: this.revisionId,
noteId: this.noteId, noteId: this.noteId,
type: this.type, type: this.type,
mime: this.mime, mime: this.mime,
@ -148,4 +148,4 @@ class BNoteRevision extends AbstractBeccaEntity {
} }
} }
module.exports = BNoteRevision; module.exports = BRevision;

View File

@ -1,5 +1,5 @@
const BNote = require('./entities/bnote'); const BNote = require('./entities/bnote');
const BNoteRevision = require('./entities/bnote_revision'); const BRevision = require('./entities/brevision.js');
const BAttachment = require("./entities/battachment"); const BAttachment = require("./entities/battachment");
const BBranch = require('./entities/bbranch'); const BBranch = require('./entities/bbranch');
const BAttribute = require('./entities/battribute'); const BAttribute = require('./entities/battribute');
@ -11,7 +11,7 @@ const ENTITY_NAME_TO_ENTITY = {
"attributes": BAttribute, "attributes": BAttribute,
"branches": BBranch, "branches": BBranch,
"notes": BNote, "notes": BNote,
"note_revisions": BNoteRevision, "revisions": BRevision,
"attachments": BAttachment, "attachments": BAttachment,
"recent_notes": BRecentNote, "recent_notes": BRecentNote,
"etapi_tokens": BEtapiToken, "etapi_tokens": BEtapiToken,

View File

@ -24,7 +24,7 @@ const IGNORED_ATTR_NAMES = [
"keyboardshortcut", "keyboardshortcut",
"noteinfowidgetdisabled", "noteinfowidgetdisabled",
"linkmapwidgetdisabled", "linkmapwidgetdisabled",
"noterevisionswidgetdisabled", "revisionswidgetdisabled",
"whatlinksherewidgetdisabled", "whatlinksherewidgetdisabled",
"similarnoteswidgetdisabled", "similarnoteswidgetdisabled",
"disableinclusion", "disableinclusion",

View File

@ -308,7 +308,7 @@ paths:
default: html default: html
post: post:
description: Create a note revision for the given note description: Create a note revision for the given note
operationId: createNoteRevision operationId: createRevision
responses: responses:
'204': '204':
description: revision has been created description: revision has been created

View File

@ -149,7 +149,7 @@ function register(router) {
eu.route(router, 'post' ,'/etapi/notes/:noteId/note-revision', (req, res, next) => { eu.route(router, 'post' ,'/etapi/notes/:noteId/note-revision', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId); const note = eu.getAndCheckNote(req.params.noteId);
note.saveNoteRevision(); note.saveRevision();
return res.sendStatus(204); return res.sendStatus(204);
}); });

View File

@ -197,7 +197,7 @@ export default class Entrypoints extends Component {
this.hideAllPopups(); this.hideAllPopups();
} }
async forceSaveNoteRevisionCommand() { async forceSaveRevisionCommand() {
const noteId = appContext.tabManager.getActiveContextNoteId(); const noteId = appContext.tabManager.getActiveContextNoteId();
await server.post(`notes/${noteId}/revision`); await server.post(`notes/${noteId}/revision`);

View File

@ -61,7 +61,7 @@ import ImportDialog from "../widgets/dialogs/import.js";
import ExportDialog from "../widgets/dialogs/export.js"; import ExportDialog from "../widgets/dialogs/export.js";
import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js"; import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js";
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js"; import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
import NoteRevisionsDialog from "../widgets/dialogs/note_revisions.js"; import RevisionsDialog from "../widgets/dialogs/revisions.js";
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js"; import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
import InfoDialog from "../widgets/dialogs/info.js"; import InfoDialog from "../widgets/dialogs/info.js";
import ConfirmDialog from "../widgets/dialogs/confirm.js"; import ConfirmDialog from "../widgets/dialogs/confirm.js";
@ -70,7 +70,7 @@ import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js"; import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js"; import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js";
import LauncherContainer from "../widgets/containers/launcher_container.js"; import LauncherContainer from "../widgets/containers/launcher_container.js";
import NoteRevisionsButton from "../widgets/buttons/note_revisions_button.js"; import RevisionsButton from "../widgets/buttons/revisions_button.js";
import CodeButtonsWidget from "../widgets/floating_buttons/code_buttons.js"; import CodeButtonsWidget from "../widgets/floating_buttons/code_buttons.js";
import ApiLogWidget from "../widgets/api_log.js"; import ApiLogWidget from "../widgets/api_log.js";
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js"; import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
@ -147,7 +147,7 @@ export default class DesktopLayout {
.ribbon(new NoteMapRibbonWidget()) .ribbon(new NoteMapRibbonWidget())
.ribbon(new SimilarNotesWidget()) .ribbon(new SimilarNotesWidget())
.ribbon(new NoteInfoWidget()) .ribbon(new NoteInfoWidget())
.button(new NoteRevisionsButton()) .button(new RevisionsButton())
.button(new NoteActionsWidget()) .button(new NoteActionsWidget())
) )
.child(new SharedInfoWidget()) .child(new SharedInfoWidget())
@ -204,7 +204,7 @@ export default class DesktopLayout {
.child(new UploadAttachmentsDialog()) .child(new UploadAttachmentsDialog())
.child(new MarkdownImportDialog()) .child(new MarkdownImportDialog())
.child(new ProtectedSessionPasswordDialog()) .child(new ProtectedSessionPasswordDialog())
.child(new NoteRevisionsDialog()) .child(new RevisionsDialog())
.child(new DeleteNotesDialog()) .child(new DeleteNotesDialog())
.child(new InfoDialog()) .child(new InfoDialog())
.child(new ConfirmDialog()) .child(new ConfirmDialog())

View File

@ -2,7 +2,7 @@ import server from "./server.js";
import ws from "./ws.js"; import ws from "./ws.js";
import MoveNoteBulkAction from "../widgets/bulk_actions/note/move_note.js"; import MoveNoteBulkAction from "../widgets/bulk_actions/note/move_note.js";
import DeleteNoteBulkAction from "../widgets/bulk_actions/note/delete_note.js"; import DeleteNoteBulkAction from "../widgets/bulk_actions/note/delete_note.js";
import DeleteNoteRevisionsBulkAction from "../widgets/bulk_actions/note/delete_note_revisions.js"; import DeleteRevisionsBulkAction from "../widgets/bulk_actions/note/delete_revisions.js";
import DeleteLabelBulkAction from "../widgets/bulk_actions/label/delete_label.js"; import DeleteLabelBulkAction from "../widgets/bulk_actions/label/delete_label.js";
import DeleteRelationBulkAction from "../widgets/bulk_actions/relation/delete_relation.js"; import DeleteRelationBulkAction from "../widgets/bulk_actions/relation/delete_relation.js";
import RenameLabelBulkAction from "../widgets/bulk_actions/label/rename_label.js"; import RenameLabelBulkAction from "../widgets/bulk_actions/label/rename_label.js";
@ -25,7 +25,7 @@ const ACTION_GROUPS = [
}, },
{ {
title: 'Notes', title: 'Notes',
actions: [RenameNoteBulkAction, MoveNoteBulkAction, DeleteNoteBulkAction, DeleteNoteRevisionsBulkAction], actions: [RenameNoteBulkAction, MoveNoteBulkAction, DeleteNoteBulkAction, DeleteRevisionsBulkAction],
}, },
{ {
title: 'Other', title: 'Other',
@ -37,7 +37,7 @@ const ACTION_CLASSES = [
RenameNoteBulkAction, RenameNoteBulkAction,
MoveNoteBulkAction, MoveNoteBulkAction,
DeleteNoteBulkAction, DeleteNoteBulkAction,
DeleteNoteRevisionsBulkAction, DeleteRevisionsBulkAction,
DeleteLabelBulkAction, DeleteLabelBulkAction,
DeleteRelationBulkAction, DeleteRelationBulkAction,
RenameLabelBulkAction, RenameLabelBulkAction,

View File

@ -30,8 +30,8 @@ async function processEntityChanges(entityChanges) {
} }
loadResults.addNoteContent(ec.noteIds, ec.componentId); loadResults.addNoteContent(ec.noteIds, ec.componentId);
} else if (ec.entityName === 'note_revisions') { } else if (ec.entityName === 'revisions') {
loadResults.addNoteRevision(ec.entityId, ec.noteId, ec.componentId); loadResults.addRevision(ec.entityId, ec.noteId, ec.componentId);
} else if (ec.entityName === 'options') { } else if (ec.entityName === 'options') {
if (ec.entity.name === 'openNoteContexts') { if (ec.entity.name === 'openNoteContexts') {
continue; // only noise continue; // only noise

View File

@ -18,7 +18,7 @@ export default class LoadResults {
this.noteReorderings = []; this.noteReorderings = [];
this.noteRevisions = []; this.revisions = [];
this.contentNoteIdToComponentId = []; this.contentNoteIdToComponentId = [];
@ -75,12 +75,12 @@ export default class LoadResults {
.filter(attr => !!attr); .filter(attr => !!attr);
} }
addNoteRevision(noteRevisionId, noteId, componentId) { addRevision(revisionId, noteId, componentId) {
this.noteRevisions.push({noteRevisionId, noteId, componentId}); this.revisions.push({revisionId, noteId, componentId});
} }
hasNoteRevisionForNote(noteId) { hasRevisionForNote(noteId) {
return !!this.noteRevisions.find(nr => nr.noteId === noteId); return !!this.revisions.find(nr => nr.noteId === noteId);
} }
getNoteIds() { getNoteIds() {
@ -140,7 +140,7 @@ export default class LoadResults {
&& this.branches.length === 0 && this.branches.length === 0
&& this.attributes.length === 0 && this.attributes.length === 0
&& this.noteReorderings.length === 0 && this.noteReorderings.length === 0
&& this.noteRevisions.length === 0 && this.revisions.length === 0
&& this.contentNoteIdToComponentId.length === 0 && this.contentNoteIdToComponentId.length === 0
&& this.options.length === 0 && this.options.length === 0
&& this.attachments.length === 0; && this.attachments.length === 0;

View File

@ -102,8 +102,8 @@ async function openNoteCustom(noteId) {
} }
} }
function downloadNoteRevision(noteId, noteRevisionId) { function downloadRevision(noteId, revisionId) {
const url = getUrlForDownload(`api/revisions/${noteRevisionId}/download`); const url = getUrlForDownload(`api/revisions/${revisionId}/download`);
download(url); download(url);
} }
@ -164,7 +164,7 @@ function getHost() {
export default { export default {
download, download,
downloadFileNote, downloadFileNote,
downloadNoteRevision, downloadRevision,
downloadAttachment, downloadAttachment,
getUrlForDownload, getUrlForDownload,
openNoteExternally, openNoteExternally,

View File

@ -19,8 +19,8 @@ const TPL = `
</td> </td>
</tr>`; </tr>`;
export default class DeleteNoteRevisionsBulkAction extends AbstractBulkAction { export default class DeleteRevisionsBulkAction extends AbstractBulkAction {
static get actionName() { return "deleteNoteRevisions"; } static get actionName() { return "deleteRevisions"; }
static get actionTitle() { return "Delete note revisions"; } static get actionTitle() { return "Delete note revisions"; }
doRender() { doRender() {

View File

@ -1,12 +1,12 @@
import CommandButtonWidget from "./command_button.js"; import CommandButtonWidget from "./command_button.js";
export default class NoteRevisionsButton extends CommandButtonWidget { export default class RevisionsButton extends CommandButtonWidget {
constructor() { constructor() {
super(); super();
this.icon('bx-history') this.icon('bx-history')
.title("Note Revisions") .title("Note Revisions")
.command("showNoteRevisions") .command("showRevisions")
.titlePlacement("bottom") .titlePlacement("bottom")
.class("icon-action"); .class("icon-action");
} }

View File

@ -72,13 +72,13 @@ const TPL = `
</div> </div>
</div>`; </div>`;
export default class NoteRevisionsDialog extends BasicWidget { export default class RevisionsDialog extends BasicWidget {
constructor() { constructor() {
super(); super();
this.revisionItems = []; this.revisionItems = [];
this.note = null; this.note = null;
this.noteRevisionId = null; this.revisionId = null;
} }
doRender() { doRender() {
@ -100,7 +100,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
}); });
this.$widget.on('shown.bs.modal', () => { this.$widget.on('shown.bs.modal', () => {
this.$list.find(`[data-note-revision-id="${this.noteRevisionId}"]`) this.$list.find(`[data-note-revision-id="${this.revisionId}"]`)
.trigger('focus'); .trigger('focus');
}); });
@ -130,13 +130,13 @@ export default class NoteRevisionsDialog extends BasicWidget {
}); });
} }
async showNoteRevisionsEvent({noteId = appContext.tabManager.getActiveContextNoteId()}) { async showRevisionsEvent({noteId = appContext.tabManager.getActiveContextNoteId()}) {
utils.openDialog(this.$widget); utils.openDialog(this.$widget);
await this.loadNoteRevisions(noteId); await this.loadRevisions(noteId);
} }
async loadNoteRevisions(noteId) { async loadRevisions(noteId) {
this.$list.empty(); this.$list.empty();
this.$content.empty(); this.$content.empty();
this.$titleButtons.empty(); this.$titleButtons.empty();
@ -148,7 +148,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
this.$list.append( this.$list.append(
$('<a class="dropdown-item" tabindex="0">') $('<a class="dropdown-item" tabindex="0">')
.text(`${item.dateLastEdited.substr(0, 16)} (${item.contentLength} bytes)`) .text(`${item.dateLastEdited.substr(0, 16)} (${item.contentLength} bytes)`)
.attr('data-note-revision-id', item.noteRevisionId) .attr('data-note-revision-id', item.revisionId)
.attr('title', `This revision was last edited on ${item.dateLastEdited}`) .attr('title', `This revision was last edited on ${item.dateLastEdited}`)
); );
} }
@ -156,21 +156,21 @@ export default class NoteRevisionsDialog extends BasicWidget {
this.$listDropdown.dropdown('show'); this.$listDropdown.dropdown('show');
if (this.revisionItems.length > 0) { if (this.revisionItems.length > 0) {
if (!this.noteRevisionId) { if (!this.revisionId) {
this.noteRevisionId = this.revisionItems[0].noteRevisionId; this.revisionId = this.revisionItems[0].revisionId;
} }
} else { } else {
this.$title.text("No revisions for this note yet..."); this.$title.text("No revisions for this note yet...");
this.noteRevisionId = null; this.revisionId = null;
} }
this.$eraseAllRevisionsButton.toggle(this.revisionItems.length > 0); this.$eraseAllRevisionsButton.toggle(this.revisionItems.length > 0);
} }
async setContentPane() { async setContentPane() {
const noteRevisionId = this.$list.find(".active").attr('data-note-revision-id'); const revisionId = this.$list.find(".active").attr('data-note-revision-id');
const revisionItem = this.revisionItems.find(r => r.noteRevisionId === noteRevisionId); const revisionItem = this.revisionItems.find(r => r.revisionId === revisionId);
this.$title.html(revisionItem.title); this.$title.html(revisionItem.title);
@ -188,7 +188,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.'; const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.';
if (await dialogService.confirm(text)) { if (await dialogService.confirm(text)) {
await server.post(`revisions/${revisionItem.noteRevisionId}/restore`); await server.post(`revisions/${revisionItem.revisionId}/restore`);
this.$widget.modal('hide'); this.$widget.modal('hide');
@ -202,9 +202,9 @@ export default class NoteRevisionsDialog extends BasicWidget {
const text = 'Do you want to delete this revision? This action will delete revision title and content, but still preserve revision metadata.'; const text = 'Do you want to delete this revision? This action will delete revision title and content, but still preserve revision metadata.';
if (await dialogService.confirm(text)) { if (await dialogService.confirm(text)) {
await server.remove(`revisions/${revisionItem.noteRevisionId}`); await server.remove(`revisions/${revisionItem.revisionId}`);
this.loadNoteRevisions(revisionItem.noteId); this.loadRevisions(revisionItem.noteId);
toastService.showMessage('Note revision has been deleted.'); toastService.showMessage('Note revision has been deleted.');
} }
@ -222,7 +222,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>'); const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
$downloadButton.on('click', () => openService.downloadNoteRevision(revisionItem.noteId, revisionItem.noteRevisionId)); $downloadButton.on('click', () => openService.downloadRevision(revisionItem.noteId, revisionItem.revisionId));
if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
this.$titleButtons.append($downloadButton); this.$titleButtons.append($downloadButton);
@ -232,10 +232,10 @@ export default class NoteRevisionsDialog extends BasicWidget {
async renderContent(revisionItem) { async renderContent(revisionItem) {
this.$content.empty(); this.$content.empty();
const fullNoteRevision = await server.get(`revisions/${revisionItem.noteRevisionId}`); const fullRevision = await server.get(`revisions/${revisionItem.revisionId}`);
if (revisionItem.type === 'text') { if (revisionItem.type === 'text') {
this.$content.html(fullNoteRevision.content); this.$content.html(fullRevision.content);
if (this.$content.find('span.math-tex').length > 0) { if (this.$content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX); await libraryLoader.requireLibrary(libraryLoader.KATEX);
@ -243,12 +243,12 @@ export default class NoteRevisionsDialog extends BasicWidget {
renderMathInElement(this.$content[0], {trust: true}); renderMathInElement(this.$content[0], {trust: true});
} }
} else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') { } else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
this.$content.html($("<pre>").text(fullNoteRevision.content)); this.$content.html($("<pre>").text(fullRevision.content));
} else if (revisionItem.type === 'image') { } else if (revisionItem.type === 'image') {
this.$content.html($("<img>") this.$content.html($("<img>")
// reason why we put this inline as base64 is that we do not want to let user copy this // reason why we put this inline as base64 is that we do not want to let user copy this
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note // as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note
.attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`) .attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
.css("max-width", "100%") .css("max-width", "100%")
.css("max-height", "100%")); .css("max-height", "100%"));
} else if (revisionItem.type === 'file') { } else if (revisionItem.type === 'file') {
@ -262,12 +262,12 @@ export default class NoteRevisionsDialog extends BasicWidget {
$("<td>").text(`${revisionItem.contentLength} bytes`) $("<td>").text(`${revisionItem.contentLength} bytes`)
)); ));
if (fullNoteRevision.content) { if (fullRevision.content) {
$table.append($("<tr>").append( $table.append($("<tr>").append(
$('<td colspan="2">').append( $('<td colspan="2">').append(
$('<div style="font-weight: bold;">').text("Preview:"), $('<div style="font-weight: bold;">').text("Preview:"),
$('<pre class="file-preview-content"></pre>') $('<pre class="file-preview-content"></pre>')
.text(fullNoteRevision.content) .text(fullRevision.content)
) )
)); ));
} }
@ -278,7 +278,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
* FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com * FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
* REMOVE external dependency!!!! This is defined in the svg in defs.style * REMOVE external dependency!!!! This is defined in the svg in defs.style
*/ */
const content = fullNoteRevision.content; const content = fullRevision.content;
try { try {
const data = JSON.parse(content) const data = JSON.parse(content)
@ -291,7 +291,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"}); const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
this.$content.html($('<div>').append($svgHtml)); this.$content.html($('<div>').append($svgHtml));
} catch (err) { } catch (err) {
console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err); console.error("error parsing fullRevision.content as JSON", fullRevision.content, err);
this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details.")); this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
} }
} else { } else {

View File

@ -21,7 +21,7 @@ import SyncOptions from "./options/sync.js";
import SearchEngineOptions from "./options/other/search_engine.js"; import SearchEngineOptions from "./options/other/search_engine.js";
import TrayOptions from "./options/other/tray.js"; import TrayOptions from "./options/other/tray.js";
import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js"; import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js";
import NoteRevisionsSnapshotIntervalOptions from "./options/other/note_revisions_snapshot_interval.js"; import RevisionsSnapshotIntervalOptions from "./options/other/revisions_snapshot_interval.js";
import NetworkConnectionsOptions from "./options/other/network_connections.js"; import NetworkConnectionsOptions from "./options/other/network_connections.js";
import AdvancedSyncOptions from "./options/advanced/sync.js"; import AdvancedSyncOptions from "./options/advanced/sync.js";
import DatabaseIntegrityCheckOptions from "./options/advanced/database_integrity_check.js"; import DatabaseIntegrityCheckOptions from "./options/advanced/database_integrity_check.js";
@ -81,7 +81,7 @@ const CONTENT_WIDGETS = {
TrayOptions, TrayOptions,
NoteErasureTimeoutOptions, NoteErasureTimeoutOptions,
AttachmentErasureTimeoutOptions, AttachmentErasureTimeoutOptions,
NoteRevisionsSnapshotIntervalOptions, RevisionsSnapshotIntervalOptions,
NetworkConnectionsOptions NetworkConnectionsOptions
], ],
_optionsAdvanced: [ _optionsAdvanced: [

View File

@ -12,15 +12,15 @@ const TPL = `
</div> </div>
</div>`; </div>`;
export default class NoteRevisionsSnapshotIntervalOptions extends OptionsWidget { export default class RevisionsSnapshotIntervalOptions extends OptionsWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$noteRevisionsTimeInterval = this.$widget.find(".note-revision-snapshot-time-interval-in-seconds"); this.$revisionsTimeInterval = this.$widget.find(".note-revision-snapshot-time-interval-in-seconds");
this.$noteRevisionsTimeInterval.on('change', () => this.$revisionsTimeInterval.on('change', () =>
this.updateOption('noteRevisionSnapshotTimeInterval', this.$noteRevisionsTimeInterval.val())); this.updateOption('revisionSnapshotTimeInterval', this.$revisionsTimeInterval.val()));
} }
async optionsLoaded(options) { async optionsLoaded(options) {
this.$noteRevisionsTimeInterval.val(options.noteRevisionSnapshotTimeInterval); this.$revisionsTimeInterval.val(options.revisionSnapshotTimeInterval);
} }
} }

View File

@ -16,7 +16,7 @@ function updateFile(req) {
const note = becca.getNoteOrThrow(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
const file = req.file; const file = req.file;
note.saveNoteRevision(); note.saveRevision();
note.mime = file.mimetype.toLowerCase(); note.mime = file.mimetype.toLowerCase();
note.save(); note.save();
@ -35,7 +35,7 @@ function updateFile(req) {
function updateAttachment(req) { function updateAttachment(req) {
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId); const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
const file = req.file; const file = req.file;
attachment.getNote().saveNoteRevision(); attachment.getNote().saveRevision();
attachment.mime = file.mimetype.toLowerCase(); attachment.mime = file.mimetype.toLowerCase();
attachment.setContent(file.buffer, {forceSave: true}); attachment.setContent(file.buffer, {forceSave: true});
@ -186,7 +186,7 @@ function uploadModifiedFileToNote(req) {
log.info(`Updating note '${noteId}' with content from '${filePath}'`); log.info(`Updating note '${noteId}' with content from '${filePath}'`);
note.saveNoteRevision(); note.saveRevision();
const fileContent = fs.readFileSync(filePath); const fileContent = fs.readFileSync(filePath);
@ -205,7 +205,7 @@ function uploadModifiedFileToAttachment(req) {
log.info(`Updating attachment '${attachmentId}' with content from '${filePath}'`); log.info(`Updating attachment '${attachmentId}' with content from '${filePath}'`);
attachment.getNote().saveNoteRevision(); attachment.getNote().saveRevision();
const fileContent = fs.readFileSync(filePath); const fileContent = fs.readFileSync(filePath);

View File

@ -131,7 +131,7 @@ function changeTitle(req) {
const noteTitleChanged = note.title !== title; const noteTitleChanged = note.title !== title;
if (noteTitleChanged) { if (noteTitleChanged) {
noteService.saveNoteRevisionIfNeeded(note); noteService.saveRevisionIfNeeded(note);
} }
note.title = title; note.title = title;
@ -216,7 +216,7 @@ function getDeleteNotesPreview(req) {
}; };
} }
function forceSaveNoteRevision(req) { function forceSaveRevision(req) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNoteOrThrow(noteId); const note = becca.getNoteOrThrow(noteId);
@ -224,7 +224,7 @@ function forceSaveNoteRevision(req) {
throw new ValidationError(`Note revision of a protected note cannot be created outside of a protected session.`); throw new ValidationError(`Note revision of a protected note cannot be created outside of a protected session.`);
} }
note.saveNoteRevision(); note.saveRevision();
} }
function convertNoteToAttachment(req) { function convertNoteToAttachment(req) {
@ -252,6 +252,6 @@ module.exports = {
eraseDeletedNotesNow, eraseDeletedNotesNow,
eraseUnusedAttachmentsNow, eraseUnusedAttachmentsNow,
getDeleteNotesPreview, getDeleteNotesPreview,
forceSaveNoteRevision, forceSaveRevision,
convertNoteToAttachment convertNoteToAttachment
}; };

View File

@ -9,7 +9,7 @@ const ValidationError = require("../../errors/validation_error");
const ALLOWED_OPTIONS = new Set([ const ALLOWED_OPTIONS = new Set([
'eraseEntitiesAfterTimeInSeconds', 'eraseEntitiesAfterTimeInSeconds',
'protectedSessionTimeout', 'protectedSessionTimeout',
'noteRevisionSnapshotTimeInterval', 'revisionSnapshotTimeInterval',
'zoomFactor', 'zoomFactor',
'theme', 'theme',
'syncServerHost', 'syncServerHost',
@ -28,7 +28,7 @@ const ALLOWED_OPTIONS = new Set([
'noteInfoWidget', 'noteInfoWidget',
'attributesWidget', 'attributesWidget',
'linkMapWidget', 'linkMapWidget',
'noteRevisionsWidget', 'revisionsWidget',
'whatLinksHereWidget', 'whatLinksHereWidget',
'similarNotesWidget', 'similarNotesWidget',
'editedNotesWidget', 'editedNotesWidget',

View File

@ -10,25 +10,25 @@ function getRecentChanges(req) {
let recentChanges = []; let recentChanges = [];
const noteRevisionRows = sql.getRows(` const revisionRows = sql.getRows(`
SELECT SELECT
notes.noteId, notes.noteId,
notes.isDeleted AS current_isDeleted, notes.isDeleted AS current_isDeleted,
notes.deleteId AS current_deleteId, notes.deleteId AS current_deleteId,
notes.title AS current_title, notes.title AS current_title,
notes.isProtected AS current_isProtected, notes.isProtected AS current_isProtected,
note_revisions.title, revisions.title,
note_revisions.utcDateCreated AS utcDate, revisions.utcDateCreated AS utcDate,
note_revisions.dateCreated AS date revisions.dateCreated AS date
FROM FROM
note_revisions revisions
JOIN notes USING(noteId)`); JOIN notes USING(noteId)`);
for (const noteRevisionRow of noteRevisionRows) { for (const revisionRow of revisionRows) {
const note = becca.getNote(noteRevisionRow.noteId); const note = becca.getNote(revisionRow.noteId);
if (note?.hasAncestor(ancestorNoteId)) { if (note?.hasAncestor(ancestorNoteId)) {
recentChanges.push(noteRevisionRow); recentChanges.push(revisionRow);
} }
} }

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
const beccaService = require('../../becca/becca_service'); const beccaService = require('../../becca/becca_service');
const noteRevisionService = require('../../services/note_revisions'); const revisionService = require('../../services/revisions.js');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const cls = require('../../services/cls'); const cls = require('../../services/cls');
@ -9,50 +9,50 @@ const path = require('path');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const blobService = require("../../services/blob.js"); const blobService = require("../../services/blob.js");
function getNoteRevisionBlob(req) { function getRevisionBlob(req) {
const preview = req.query.preview === 'true'; const preview = req.query.preview === 'true';
return blobService.getBlobPojo('note_revisions', req.params.noteRevisionId, { preview }); return blobService.getBlobPojo('revisions', req.params.revisionId, { preview });
} }
function getNoteRevisions(req) { function getRevisions(req) {
return becca.getNoteRevisionsFromQuery(` return becca.getRevisionsFromQuery(`
SELECT note_revisions.*, SELECT revisions.*,
LENGTH(blobs.content) AS contentLength LENGTH(blobs.content) AS contentLength
FROM note_revisions FROM revisions
JOIN blobs ON note_revisions.blobId = blobs.blobId JOIN blobs ON revisions.blobId = blobs.blobId
WHERE noteId = ? WHERE noteId = ?
ORDER BY utcDateCreated DESC`, [req.params.noteId]); ORDER BY utcDateCreated DESC`, [req.params.noteId]);
} }
function getNoteRevision(req) { function getRevision(req) {
const noteRevision = becca.getNoteRevision(req.params.noteRevisionId); const revision = becca.getRevision(req.params.revisionId);
if (noteRevision.type === 'file') { if (revision.type === 'file') {
if (noteRevision.hasStringContent()) { if (revision.hasStringContent()) {
noteRevision.content = noteRevision.getContent().substr(0, 10000); revision.content = revision.getContent().substr(0, 10000);
} }
} }
else { else {
noteRevision.content = noteRevision.getContent(); revision.content = revision.getContent();
if (noteRevision.content && noteRevision.type === 'image') { if (revision.content && revision.type === 'image') {
noteRevision.content = noteRevision.content.toString('base64'); revision.content = revision.content.toString('base64');
} }
} }
return noteRevision; return revision;
} }
/** /**
* @param {BNoteRevision} noteRevision * @param {BRevision} revision
* @returns {string} * @returns {string}
*/ */
function getRevisionFilename(noteRevision) { function getRevisionFilename(revision) {
let filename = utils.formatDownloadTitle(noteRevision.title, noteRevision.type, noteRevision.mime); let filename = utils.formatDownloadTitle(revision.title, revision.type, revision.mime);
const extension = path.extname(filename); const extension = path.extname(filename);
const date = noteRevision.dateCreated const date = revision.dateCreated
.substr(0, 19) .substr(0, 19)
.replace(' ', '_') .replace(' ', '_')
.replace(/[^0-9_]/g, ''); .replace(/[^0-9_]/g, '');
@ -67,50 +67,50 @@ function getRevisionFilename(noteRevision) {
return filename; return filename;
} }
function downloadNoteRevision(req, res) { function downloadRevision(req, res) {
const noteRevision = becca.getNoteRevision(req.params.noteRevisionId); const revision = becca.getRevision(req.params.revisionId);
if (!noteRevision.isContentAvailable()) { if (!revision.isContentAvailable()) {
return res.setHeader("Content-Type", "text/plain") return res.setHeader("Content-Type", "text/plain")
.status(401) .status(401)
.send("Protected session not available"); .send("Protected session not available");
} }
const filename = getRevisionFilename(noteRevision); const filename = getRevisionFilename(revision);
res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
res.setHeader('Content-Type', noteRevision.mime); res.setHeader('Content-Type', revision.mime);
res.send(noteRevision.getContent()); res.send(revision.getContent());
} }
function eraseAllNoteRevisions(req) { function eraseAllRevisions(req) {
const noteRevisionIdsToErase = sql.getColumn('SELECT noteRevisionId FROM note_revisions WHERE noteId = ?', const revisionIdsToErase = sql.getColumn('SELECT revisionId FROM revisions WHERE noteId = ?',
[req.params.noteId]); [req.params.noteId]);
noteRevisionService.eraseNoteRevisions(noteRevisionIdsToErase); revisionService.eraseRevisions(revisionIdsToErase);
} }
function eraseNoteRevision(req) { function eraseRevision(req) {
noteRevisionService.eraseNoteRevisions([req.params.noteRevisionId]); revisionService.eraseRevisions([req.params.revisionId]);
} }
function restoreNoteRevision(req) { function restoreRevision(req) {
const noteRevision = becca.getNoteRevision(req.params.noteRevisionId); const revision = becca.getRevision(req.params.revisionId);
if (noteRevision) { if (revision) {
const note = noteRevision.getNote(); const note = revision.getNote();
sql.transactional(() => { sql.transactional(() => {
note.saveNoteRevision(); note.saveRevision();
for (const oldNoteAttachment of note.getAttachments()) { for (const oldNoteAttachment of note.getAttachments()) {
oldNoteAttachment.markAsDeleted(); oldNoteAttachment.markAsDeleted();
} }
let revisionContent = noteRevision.getContent(); let revisionContent = revision.getContent();
for (const revisionAttachment of noteRevision.getAttachments()) { for (const revisionAttachment of revision.getAttachments()) {
const noteAttachment = revisionAttachment.copy(); const noteAttachment = revisionAttachment.copy();
noteAttachment.parentId = note.noteId; noteAttachment.parentId = note.noteId;
noteAttachment.setContent(revisionAttachment.getContent(), { forceSave: true }); noteAttachment.setContent(revisionAttachment.getContent(), { forceSave: true });
@ -119,7 +119,7 @@ function restoreNoteRevision(req) {
revisionContent = revisionContent.replaceAll(`attachments/${revisionAttachment.attachmentId}`, `attachments/${noteAttachment.attachmentId}`); revisionContent = revisionContent.replaceAll(`attachments/${revisionAttachment.attachmentId}`, `attachments/${noteAttachment.attachmentId}`);
} }
note.title = noteRevision.title; note.title = revision.title;
note.setContent(revisionContent, { forceSave: true }); note.setContent(revisionContent, { forceSave: true });
}); });
} }
@ -134,8 +134,8 @@ function getEditedNotesOnDate(req) {
WHERE notes.dateCreated LIKE :date WHERE notes.dateCreated LIKE :date
OR notes.dateModified LIKE :date OR notes.dateModified LIKE :date
UNION ALL UNION ALL
SELECT noteId FROM note_revisions SELECT noteId FROM revisions
WHERE note_revisions.dateLastEdited LIKE :date WHERE revisions.dateLastEdited LIKE :date
) )
ORDER BY isDeleted ORDER BY isDeleted
LIMIT 50`, {date: `${req.params.date}%`}); LIMIT 50`, {date: `${req.params.date}%`});
@ -186,12 +186,12 @@ function getNotePathData(note) {
} }
module.exports = { module.exports = {
getNoteRevisionBlob, getRevisionBlob,
getNoteRevisions, getRevisions,
getNoteRevision, getRevision,
downloadNoteRevision, downloadRevision,
getEditedNotesOnDate, getEditedNotesOnDate,
eraseAllNoteRevisions, eraseAllRevisions,
eraseNoteRevision, eraseRevision,
restoreNoteRevision restoreRevision
}; };

View File

@ -9,10 +9,10 @@ function getNoteSize(req) {
FROM blobs FROM blobs
LEFT JOIN notes ON notes.blobId = blobs.blobId AND notes.noteId = ? AND notes.isDeleted = 0 LEFT JOIN notes ON notes.blobId = blobs.blobId AND notes.noteId = ? AND notes.isDeleted = 0
LEFT JOIN attachments ON attachments.blobId = blobs.blobId AND attachments.parentId = ? AND attachments.isDeleted = 0 LEFT JOIN attachments ON attachments.blobId = blobs.blobId AND attachments.parentId = ? AND attachments.isDeleted = 0
LEFT JOIN note_revisions ON note_revisions.blobId = blobs.blobId AND note_revisions.noteId = ? LEFT JOIN revisions ON revisions.blobId = blobs.blobId AND revisions.noteId = ?
WHERE notes.noteId IS NOT NULL WHERE notes.noteId IS NOT NULL
OR attachments.attachmentId IS NOT NULL OR attachments.attachmentId IS NOT NULL
OR note_revisions.noteRevisionId IS NOT NULL`, [noteId, noteId, noteId]); OR revisions.revisionId IS NOT NULL`, [noteId, noteId, noteId]);
const noteSize = Object.values(blobSizes).reduce((acc, blobSize) => acc + blobSize, 0); const noteSize = Object.values(blobSizes).reduce((acc, blobSize) => acc + blobSize, 0);
@ -33,8 +33,8 @@ function getSubtreeSize(req) {
FROM param_list FROM param_list
JOIN notes ON notes.noteId = param_list.paramId AND notes.isDeleted = 0 JOIN notes ON notes.noteId = param_list.paramId AND notes.isDeleted = 0
LEFT JOIN attachments ON attachments.parentId = param_list.paramId AND attachments.isDeleted = 0 LEFT JOIN attachments ON attachments.parentId = param_list.paramId AND attachments.isDeleted = 0
LEFT JOIN note_revisions ON note_revisions.noteId = param_list.paramId LEFT JOIN revisions ON revisions.noteId = param_list.paramId
JOIN blobs ON blobs.blobId = notes.blobId OR blobs.blobId = attachments.blobId OR blobs.blobId = note_revisions.blobId`); JOIN blobs ON blobs.blobId = notes.blobId OR blobs.blobId = attachments.blobId OR blobs.blobId = revisions.blobId`);
const subTreeSize = Object.values(blobSizes).reduce((acc, blobSize) => acc + blobSize, 0); const subTreeSize = Object.values(blobSizes).reduce((acc, blobSize) => acc + blobSize, 0);

View File

@ -108,9 +108,9 @@ function forceNoteSync(req) {
entityChangesService.moveEntityChangeToTop('attributes', attributeId); entityChangesService.moveEntityChangeToTop('attributes', attributeId);
} }
for (const noteRevisionId of sql.getColumn("SELECT noteRevisionId FROM note_revisions WHERE noteId = ?", [noteId])) { for (const revisionId of sql.getColumn("SELECT revisionId FROM revisions WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE note_revisions SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]); sql.execute(`UPDATE revisions SET utcDateModified = ? WHERE revisionId = ?`, [now, revisionId]);
entityChangesService.moveEntityChangeToTop('note_revisions', noteRevisionId); entityChangesService.moveEntityChangeToTop('revisions', revisionId);
} }
for (const attachmentId of sql.getColumn("SELECT attachmentId FROM attachments WHERE noteId = ?", [noteId])) { for (const attachmentId of sql.getColumn("SELECT attachmentId FROM attachments WHERE noteId = ?", [noteId])) {

View File

@ -28,7 +28,7 @@ const branchesApiRoute = require('./api/branches');
const attachmentsApiRoute = require('./api/attachments'); const attachmentsApiRoute = require('./api/attachments');
const autocompleteApiRoute = require('./api/autocomplete'); const autocompleteApiRoute = require('./api/autocomplete');
const cloningApiRoute = require('./api/cloning'); const cloningApiRoute = require('./api/cloning');
const noteRevisionsApiRoute = require('./api/note_revisions'); const revisionsApiRoute = require('./api/revisions.js');
const recentChangesApiRoute = require('./api/recent_changes'); const recentChangesApiRoute = require('./api/recent_changes');
const optionsApiRoute = require('./api/options'); const optionsApiRoute = require('./api/options');
const passwordApiRoute = require('./api/password'); const passwordApiRoute = require('./api/password');
@ -117,7 +117,7 @@ function register(app) {
apiRoute(PUT, '/api/notes/:noteId/data', notesApiRoute.updateNoteData); apiRoute(PUT, '/api/notes/:noteId/data', notesApiRoute.updateNoteData);
apiRoute(DEL, '/api/notes/:noteId', notesApiRoute.deleteNote); apiRoute(DEL, '/api/notes/:noteId', notesApiRoute.deleteNote);
apiRoute(PUT, '/api/notes/:noteId/undelete', notesApiRoute.undeleteNote); apiRoute(PUT, '/api/notes/:noteId/undelete', notesApiRoute.undeleteNote);
apiRoute(PST, '/api/notes/:noteId/revision', notesApiRoute.forceSaveNoteRevision); apiRoute(PST, '/api/notes/:noteId/revision', notesApiRoute.forceSaveRevision);
apiRoute(PST, '/api/notes/:parentNoteId/children', notesApiRoute.createNote); apiRoute(PST, '/api/notes/:parentNoteId/children', notesApiRoute.createNote);
apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes); apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes);
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote); apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote);
@ -171,13 +171,13 @@ function register(app) {
route(PUT, '/api/attachments/:attachmentId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], route(PUT, '/api/attachments/:attachmentId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware],
filesRoute.updateAttachment, apiResultHandler); filesRoute.updateAttachment, apiResultHandler);
apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions); apiRoute(GET, '/api/notes/:noteId/revisions', revisionsApiRoute.getRevisions);
apiRoute(DEL, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions); apiRoute(DEL, '/api/notes/:noteId/revisions', revisionsApiRoute.eraseAllRevisions);
apiRoute(GET, '/api/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision); apiRoute(GET, '/api/revisions/:revisionId', revisionsApiRoute.getRevision);
apiRoute(GET, '/api/revisions/:noteRevisionId/blob', noteRevisionsApiRoute.getNoteRevisionBlob); apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
apiRoute(DEL, '/api/revisions/:noteRevisionId', noteRevisionsApiRoute.eraseNoteRevision); apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
apiRoute(PST, '/api/revisions/:noteRevisionId/restore', noteRevisionsApiRoute.restoreNoteRevision); apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision);
route(GET, '/api/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision); route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
route(GET, '/api/branches/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); route(GET, '/api/branches/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
@ -321,7 +321,7 @@ function register(app) {
route(GET, '/api/fonts', [auth.checkApiAuthOrElectron], fontsRoute.getFontCss); route(GET, '/api/fonts', [auth.checkApiAuthOrElectron], fontsRoute.getFontCss);
apiRoute(GET, '/api/other/icon-usage', otherRoute.getIconUsage); apiRoute(GET, '/api/other/icon-usage', otherRoute.getIconUsage);
apiRoute(GET, '/api/recent-changes/:ancestorNoteId', recentChangesApiRoute.getRecentChanges); apiRoute(GET, '/api/recent-changes/:ancestorNoteId', recentChangesApiRoute.getRecentChanges);
apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); apiRoute(GET, '/api/edited-notes/:date', revisionsApiRoute.getEditedNotesOnDate);
apiRoute(PST, '/api/note-map/:noteId/tree', noteMapRoute.getTreeMap); apiRoute(PST, '/api/note-map/:noteId/tree', noteMapRoute.getTreeMap);
apiRoute(PST, '/api/note-map/:noteId/link', noteMapRoute.getLinkMap); apiRoute(PST, '/api/note-map/:noteId/link', noteMapRoute.getLinkMap);

View File

@ -15,7 +15,7 @@ function getFullAnonymizationScript() {
UPDATE etapi_tokens SET tokenHash = 'API token hash value'; UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share'); UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL; UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title'; UPDATE revisions SET title = 'title';
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames}); UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames}); UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
@ -36,7 +36,7 @@ function getLightAnonymizationScript() {
return `UPDATE blobs SET content = 'text' WHERE content IS NOT NULL AND blobId NOT IN ( return `UPDATE blobs SET content = 'text' WHERE content IS NOT NULL AND blobId NOT IN (
SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend') SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
UNION ALL UNION ALL
SELECT blobId FROM note_revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend') SELECT blobId FROM revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
);`; );`;
} }

View File

@ -4,7 +4,7 @@ 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 = 220; const APP_DB_VERSION = 221;
const SYNC_VERSION = 30; const SYNC_VERSION = 30;
const CLIPPER_PROTOCOL_VERSION = "1.0"; const CLIPPER_PROTOCOL_VERSION = "1.0";

View File

@ -22,7 +22,7 @@ module.exports = [
{ type: 'label', name: 'widget', isDangerous: true }, { type: 'label', name: 'widget', isDangerous: true },
{ type: 'label', name: 'noteInfoWidgetDisabled' }, { type: 'label', name: 'noteInfoWidgetDisabled' },
{ type: 'label', name: 'linkMapWidgetDisabled' }, { type: 'label', name: 'linkMapWidgetDisabled' },
{ type: 'label', name: 'noteRevisionsWidgetDisabled' }, { type: 'label', name: 'revisionsWidgetDisabled' },
{ type: 'label', name: 'whatLinksHereWidgetDisabled' }, { type: 'label', name: 'whatLinksHereWidgetDisabled' },
{ type: 'label', name: 'similarNotesWidgetDisabled' }, { type: 'label', name: 'similarNotesWidgetDisabled' },
{ type: 'label', name: 'workspace' }, { type: 'label', name: 'workspace' },

View File

@ -1,5 +1,5 @@
const log = require("./log"); const log = require("./log");
const noteRevisionService = require("./note_revisions"); const revisionService = require("./revisions.js");
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const cloningService = require("./cloning"); const cloningService = require("./cloning");
const branchService = require("./branches"); const branchService = require("./branches");
@ -17,8 +17,8 @@ const ACTION_HANDLERS = {
note.deleteNote(deleteId); note.deleteNote(deleteId);
}, },
deleteNoteRevisions: (action, note) => { deleteRevisions: (action, note) => {
noteRevisionService.eraseNoteRevisions(note.getNoteRevisions().map(rev => rev.noteRevisionId)); revisionService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId));
}, },
deleteLabel: (action, note) => { deleteLabel: (action, note) => {
for (const label of note.getOwnedLabels(action.labelName)) { for (const label of note.getOwnedLabels(action.labelName)) {

View File

@ -9,7 +9,7 @@ const cls = require('./cls');
const entityChangesService = require('./entity_changes'); const entityChangesService = require('./entity_changes');
const optionsService = require('./options'); const optionsService = require('./options');
const BBranch = require('../becca/entities/bbranch'); const BBranch = require('../becca/entities/bbranch');
const noteRevisionService = require('./note_revisions'); const revisionService = require('./revisions.js');
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const utils = require("../services/utils"); const utils = require("../services/utils");
const {sanitizeAttributeName} = require("./sanitize_attribute_name"); const {sanitizeAttributeName} = require("./sanitize_attribute_name");
@ -221,7 +221,7 @@ class ConsistencyChecks {
WHERE attachments.parentId NOT IN ( WHERE attachments.parentId NOT IN (
SELECT noteId FROM notes SELECT noteId FROM notes
UNION ALL UNION ALL
SELECT noteRevisionId FROM note_revisions SELECT revisionId FROM revisions
) )
AND attachments.isDeleted = 0`, AND attachments.isDeleted = 0`,
({attachmentId, parentId}) => { ({attachmentId, parentId}) => {
@ -461,19 +461,19 @@ class ConsistencyChecks {
} }
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT note_revisions.noteRevisionId SELECT revisions.revisionId
FROM note_revisions FROM revisions
LEFT JOIN blobs USING (blobId) LEFT JOIN blobs USING (blobId)
WHERE blobs.blobId IS NULL`, WHERE blobs.blobId IS NULL`,
({noteRevisionId}) => { ({revisionId}) => {
if (this.autoFix) { if (this.autoFix) {
noteRevisionService.eraseNoteRevisions([noteRevisionId]); revisionService.eraseRevisions([revisionId]);
this.reloadNeeded = true; this.reloadNeeded = true;
logFix(`Note revision content '${noteRevisionId}' was set to erased since its content did not exist.`); logFix(`Note revision content '${revisionId}' was set to erased since its content did not exist.`);
} else { } else {
logError(`Note revision content '${noteRevisionId}' does not exist`); logError(`Note revision content '${revisionId}' does not exist`);
} }
}); });
@ -669,7 +669,7 @@ class ConsistencyChecks {
findEntityChangeIssues() { findEntityChangeIssues() {
this.runEntityChangeChecks("notes", "noteId"); this.runEntityChangeChecks("notes", "noteId");
this.runEntityChangeChecks("note_revisions", "noteRevisionId"); this.runEntityChangeChecks("revisions", "revisionId");
this.runEntityChangeChecks("attachments", "attachmentId"); this.runEntityChangeChecks("attachments", "attachmentId");
this.runEntityChangeChecks("blobs", "blobId"); this.runEntityChangeChecks("blobs", "blobId");
this.runEntityChangeChecks("branches", "branchId"); this.runEntityChangeChecks("branches", "branchId");
@ -767,7 +767,7 @@ class ConsistencyChecks {
return `${tableName}: ${count}`; return `${tableName}: ${count}`;
} }
const tables = [ "notes", "note_revisions", "attachments", "branches", "attributes", "etapi_tokens" ]; const tables = [ "notes", "revisions", "attachments", "branches", "attributes", "etapi_tokens" ];
log.info(`Table counts: ${tables.map(tableName => getTableRowCount(tableName)).join(", ")}`); log.info(`Table counts: ${tables.map(tableName => getTableRowCount(tableName)).join(", ")}`);
} }

View File

@ -148,7 +148,7 @@ function fillAllEntityChanges() {
fillEntityChanges("notes", "noteId"); fillEntityChanges("notes", "noteId");
fillEntityChanges("branches", "branchId"); fillEntityChanges("branches", "branchId");
fillEntityChanges("note_revisions", "noteRevisionId"); fillEntityChanges("revisions", "revisionId");
fillEntityChanges("attachments", "attachmentId"); fillEntityChanges("attachments", "attachmentId");
fillEntityChanges("blobs", "blobId"); fillEntityChanges("blobs", "blobId");
fillEntityChanges("attributes", "attributeId"); fillEntityChanges("attributes", "attributeId");

View File

@ -70,7 +70,7 @@ function updateImage(noteId, uploadBuffer, originalName) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
note.saveNoteRevision(); note.saveRevision();
note.setLabel('originalFileName', originalName); note.setLabel('originalFileName', originalName);

View File

@ -249,7 +249,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
scope: "window" scope: "window"
}, },
{ {
actionName: "showNoteRevisions", actionName: "showRevisions",
defaultShortcuts: [], defaultShortcuts: [],
description: "Shows Note Revisions dialog", description: "Shows Note Revisions dialog",
scope: "window" scope: "window"
@ -502,7 +502,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
scope: "text-detail" scope: "text-detail"
}, },
{ {
actionName: "forceSaveNoteRevision", actionName: "forceSaveRevision",
defaultShortcuts: [], defaultShortcuts: [],
description: "Force creating / saving new note revision of the active note", description: "Force creating / saving new note revision of the active note",
scope: "window" scope: "window"

View File

@ -8,7 +8,7 @@ const cls = require('../services/cls');
const protectedSessionService = require('../services/protected_session'); const protectedSessionService = require('../services/protected_session');
const log = require('../services/log'); const log = require('../services/log');
const utils = require('../services/utils'); const utils = require('../services/utils');
const noteRevisionService = require('../services/note_revisions'); const revisionService = require('./revisions.js');
const request = require('./request'); const request = require('./request');
const path = require('path'); const path = require('path');
const url = require('url'); const url = require('url');
@ -315,7 +315,7 @@ function protectNote(note, protect) {
note.setContent(content, { forceSave: true }); note.setContent(content, { forceSave: true });
} }
noteRevisionService.protectNoteRevisions(note); revisionService.protectRevisions(note);
for (const attachment of note.getAttachments()) { for (const attachment of note.getAttachments()) {
if (protect !== attachment.isProtected) { if (protect !== attachment.isProtected) {
@ -666,24 +666,24 @@ function saveLinks(note, content) {
} }
/** @param {BNote} note */ /** @param {BNote} note */
function saveNoteRevisionIfNeeded(note) { function saveRevisionIfNeeded(note) {
// files and images are versioned separately // files and images are versioned separately
if (note.type === 'file' || note.type === 'image' || note.hasLabel('disableVersioning')) { if (note.type === 'file' || note.type === 'image' || note.hasLabel('disableVersioning')) {
return; return;
} }
const now = new Date(); const now = new Date();
const noteRevisionSnapshotTimeInterval = parseInt(optionService.getOption('noteRevisionSnapshotTimeInterval')); const revisionSnapshotTimeInterval = parseInt(optionService.getOption('revisionSnapshotTimeInterval'));
const revisionCutoff = dateUtils.utcDateTimeStr(new Date(now.getTime() - noteRevisionSnapshotTimeInterval * 1000)); const revisionCutoff = dateUtils.utcDateTimeStr(new Date(now.getTime() - revisionSnapshotTimeInterval * 1000));
const existingNoteRevisionId = sql.getValue( const existingRevisionId = sql.getValue(
"SELECT noteRevisionId FROM note_revisions WHERE noteId = ? AND utcDateCreated >= ?", [note.noteId, revisionCutoff]); "SELECT revisionId FROM revisions WHERE noteId = ? AND utcDateCreated >= ?", [note.noteId, revisionCutoff]);
const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.utcDateCreated).getTime(); const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.utcDateCreated).getTime();
if (!existingNoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) { if (!existingRevisionId && msSinceDateCreated >= revisionSnapshotTimeInterval * 1000) {
note.saveNoteRevision(); note.saveRevision();
} }
} }
@ -694,7 +694,7 @@ function updateNoteData(noteId, content) {
throw new Error(`Note '${noteId}' is not available for change!`); throw new Error(`Note '${noteId}' is not available for change!`);
} }
saveNoteRevisionIfNeeded(note); saveRevisionIfNeeded(note);
const { forceFrontendReload, content: newContent } = saveLinks(note, content); const { forceFrontendReload, content: newContent } = saveLinks(note, content);
@ -839,10 +839,10 @@ function eraseNotes(noteIdsToErase) {
eraseAttributes(attributeIdsToErase); eraseAttributes(attributeIdsToErase);
const noteRevisionIdsToErase = sql.getManyRows(`SELECT noteRevisionId FROM note_revisions WHERE noteId IN (???)`, noteIdsToErase) const revisionIdsToErase = sql.getManyRows(`SELECT revisionId FROM revisions WHERE noteId IN (???)`, noteIdsToErase)
.map(row => row.noteRevisionId); .map(row => row.revisionId);
noteRevisionService.eraseNoteRevisions(noteRevisionIdsToErase); revisionService.eraseRevisions(revisionIdsToErase);
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
} }
@ -899,10 +899,10 @@ function eraseUnusedBlobs() {
FROM blobs FROM blobs
LEFT JOIN notes ON notes.blobId = blobs.blobId LEFT JOIN notes ON notes.blobId = blobs.blobId
LEFT JOIN attachments ON attachments.blobId = blobs.blobId LEFT JOIN attachments ON attachments.blobId = blobs.blobId
LEFT JOIN note_revisions ON note_revisions.blobId = blobs.blobId LEFT JOIN revisions ON revisions.blobId = blobs.blobId
WHERE notes.noteId IS NULL WHERE notes.noteId IS NULL
AND attachments.attachmentId IS NULL AND attachments.attachmentId IS NULL
AND note_revisions.noteRevisionId IS NULL`); AND revisions.revisionId IS NULL`);
if (unusedBlobIds.length === 0) { if (unusedBlobIds.length === 0) {
return; return;
@ -1136,7 +1136,7 @@ module.exports = {
eraseDeletedNotesNow, eraseDeletedNotesNow,
eraseUnusedAttachmentsNow, eraseUnusedAttachmentsNow,
eraseNotesWithDeleteId, eraseNotesWithDeleteId,
saveNoteRevisionIfNeeded, saveRevisionIfNeeded,
downloadImages, downloadImages,
asyncPostProcessContent asyncPostProcessContent
}; };

View File

@ -44,7 +44,7 @@ function initNotSyncedOptions(initialized, opts = {}) {
} }
const defaultOptions = [ const defaultOptions = [
{ name: 'noteRevisionSnapshotTimeInterval', value: '600', isSynced: true }, { name: 'revisionSnapshotTimeInterval', value: '600', isSynced: true },
{ name: 'protectedSessionTimeout', value: '600', isSynced: true }, { name: 'protectedSessionTimeout', value: '600', isSynced: true },
{ name: 'zoomFactor', value: process.platform === "win32" ? '0.9' : '1.0', isSynced: false }, { name: 'zoomFactor', value: process.platform === "win32" ? '0.9' : '1.0', isSynced: false },
{ name: 'overrideThemeFonts', value: 'false', isSynced: false }, { name: 'overrideThemeFonts', value: 'false', isSynced: false },

View File

@ -7,12 +7,12 @@ const protectedSessionService = require("./protected_session");
/** /**
* @param {BNote} note * @param {BNote} note
*/ */
function protectNoteRevisions(note) { function protectRevisions(note) {
if (!protectedSessionService.isProtectedSessionAvailable()) { if (!protectedSessionService.isProtectedSessionAvailable()) {
throw new Error(`Cannot (un)protect revisions of note '${note.noteId}' without active protected session`); throw new Error(`Cannot (un)protect revisions of note '${note.noteId}' without active protected session`);
} }
for (const revision of note.getNoteRevisions()) { for (const revision of note.getRevisions()) {
if (note.isProtected === revision.isProtected) { if (note.isProtected === revision.isProtected) {
continue; continue;
} }
@ -25,25 +25,25 @@ function protectNoteRevisions(note) {
// this will force de/encryption // this will force de/encryption
revision.setContent(content, {forceSave: true}); revision.setContent(content, {forceSave: true});
} catch (e) { } catch (e) {
log.error(`Could not un/protect note revision '${revision.noteRevisionId}'`); log.error(`Could not un/protect note revision '${revision.revisionId}'`);
throw e; throw e;
} }
} }
} }
function eraseNoteRevisions(noteRevisionIdsToErase) { function eraseRevisions(revisionIdsToErase) {
if (noteRevisionIdsToErase.length === 0) { if (revisionIdsToErase.length === 0) {
return; return;
} }
log.info(`Removing note revisions: ${JSON.stringify(noteRevisionIdsToErase)}`); log.info(`Removing note revisions: ${JSON.stringify(revisionIdsToErase)}`);
sql.executeMany(`DELETE FROM note_revisions WHERE noteRevisionId IN (???)`, noteRevisionIdsToErase); sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_revisions' AND entityId IN (???)`, noteRevisionIdsToErase); sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase);
} }
module.exports = { module.exports = {
protectNoteRevisions, protectRevisions,
eraseNoteRevisions eraseRevisions
}; };

View File

@ -116,16 +116,16 @@ function loadNeededInfoFromDatabase() {
becca.notes[noteId].noteSize = length; becca.notes[noteId].noteSize = length;
} }
const noteRevisionContentLengths = sql.getRows(` const revisionContentLengths = sql.getRows(`
SELECT SELECT
noteId, noteId,
LENGTH(content) AS length LENGTH(content) AS length
FROM notes FROM notes
JOIN note_revisions USING(noteId) JOIN revisions USING(noteId)
JOIN blobs USING(blobId) JOIN blobs USING(blobId)
WHERE notes.isDeleted = 0`); WHERE notes.isDeleted = 0`);
for (const {noteId, length} of noteRevisionContentLengths) { for (const {noteId, length} of revisionContentLengths) {
if (!(noteId in becca.notes)) { if (!(noteId in becca.notes)) {
log.error(`Note ${noteId} not found in becca.`); log.error(`Note ${noteId} not found in becca.`);
continue; continue;

View File

@ -113,7 +113,7 @@ function eraseEntity(entityChange, instanceId) {
"notes", "notes",
"branches", "branches",
"attributes", "attributes",
"note_revisions", "revisions",
"attachments", "attachments",
"blobs", "blobs",
]; ];

View File

@ -127,10 +127,10 @@ function fillInAdditionalProperties(entityChange) {
entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title); entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title);
} }
} }
} else if (entityChange.entityName === 'note_revisions') { } else if (entityChange.entityName === 'revisions') {
entityChange.noteId = sql.getValue(`SELECT noteId entityChange.noteId = sql.getValue(`SELECT noteId
FROM note_revisions FROM revisions
WHERE noteRevisionId = ?`, [entityChange.entityId]); WHERE revisionId = ?`, [entityChange.entityId]);
} else if (entityChange.entityName === 'note_reordering') { } else if (entityChange.entityName === 'note_reordering') {
entityChange.positions = {}; entityChange.positions = {};
@ -165,7 +165,7 @@ const ORDERING = {
"branches": 2, "branches": 2,
"blobs": 0, "blobs": 0,
"note_reordering": 2, "note_reordering": 2,
"note_revisions": 2, "revisions": 2,
"attachments": 3, "attachments": 3,
"notes": 1, "notes": 1,
"options": 0 "options": 0

View File

@ -82,7 +82,7 @@ async function start() {
isInheritable: Math.random() > 0.1 // 10% are inheritable isInheritable: Math.random() > 0.1 // 10% are inheritable
}); });
note.saveNoteRevision(); note.saveRevision();
notes.push(note.noteId); notes.push(note.noteId);
} }