mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
note revisions changes WIP
This commit is contained in:
parent
cf53cbf1dd
commit
4e5e3e4675
@ -1,7 +1,7 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "note_revisions_mig" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
CREATE TABLE IF NOT EXISTS "note_revisions_mig" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
||||||
`noteId` TEXT NOT NULL,
|
`noteId` TEXT NOT NULL,
|
||||||
`title` TEXT,
|
`title` TEXT,
|
||||||
`content` TEXT,
|
`contentLength` INT NOT NULL,
|
||||||
`isProtected` INT NOT NULL DEFAULT 0,
|
`isProtected` INT NOT NULL DEFAULT 0,
|
||||||
`utcDateLastEdited` TEXT NOT NULL,
|
`utcDateLastEdited` TEXT NOT NULL,
|
||||||
`utcDateCreated` TEXT NOT NULL,
|
`utcDateCreated` TEXT NOT NULL,
|
||||||
@ -18,10 +18,10 @@ CREATE TABLE IF NOT EXISTS "note_revision_contents" (`noteRevisionId` TEXT NOT N
|
|||||||
`utcDateModified` TEXT NOT NULL);
|
`utcDateModified` TEXT NOT NULL);
|
||||||
|
|
||||||
INSERT INTO note_revision_contents (noteRevisionId, content, hash, utcDateModified)
|
INSERT INTO note_revision_contents (noteRevisionId, content, hash, utcDateModified)
|
||||||
SELECT noteRevisionId, content, hash, utcDateModified FROM note_revisions;
|
SELECT noteRevisionId, content, hash, utcDateModifiedTo FROM note_revisions;
|
||||||
|
|
||||||
INSERT INTO note_revisions_mig (noteRevisionId, noteId, title, isProtected, utcDateLastEdited, utcDateCreated, utcDateModified, dateLastEdited, dateCreated, type, mime, hash)
|
INSERT INTO note_revisions_mig (noteRevisionId, noteId, title, contentLength, isProtected, utcDateLastEdited, utcDateCreated, utcDateModified, dateLastEdited, dateCreated, type, mime, hash)
|
||||||
SELECT noteRevisionId, noteId, title, isProtected, utcDateModifiedFrom, utcDateModifiedTo, utcDateModifiedTo, dateModifiedFrom, dateModifiedTo, type, mime, hash FROM note_revisions;
|
SELECT noteRevisionId, noteId, title, LENGTH(content), isProtected, utcDateModifiedFrom, utcDateModifiedTo, utcDateModifiedTo, dateModifiedFrom, dateModifiedTo, type, mime, hash FROM note_revisions;
|
||||||
|
|
||||||
DROP TABLE note_revisions;
|
DROP TABLE note_revisions;
|
||||||
ALTER TABLE note_revisions_mig RENAME TO note_revisions;
|
ALTER TABLE note_revisions_mig RENAME TO note_revisions;
|
||||||
|
@ -112,7 +112,7 @@ class Note extends Entity {
|
|||||||
|
|
||||||
/** @returns {Promise} */
|
/** @returns {Promise} */
|
||||||
async setContent(content) {
|
async setContent(content) {
|
||||||
// force updating note itself so that dateChanged is represented correctly even for the content
|
// force updating note itself so that dateModified is represented correctly even for the content
|
||||||
this.forcedChange = true;
|
this.forcedChange = true;
|
||||||
await this.save();
|
await this.save();
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ const Entity = require('./entity');
|
|||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
const utils = require('../services/utils');
|
const utils = require('../services/utils');
|
||||||
|
const sql = require('../services/sql');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
const syncTableService = require('../services/sync_table');
|
const syncTableService = require('../services/sync_table');
|
||||||
|
|
||||||
@ -16,17 +17,18 @@ const syncTableService = require('../services/sync_table');
|
|||||||
* @param {string} mime
|
* @param {string} mime
|
||||||
* @param {string} title
|
* @param {string} title
|
||||||
* @param {string} isProtected
|
* @param {string} isProtected
|
||||||
* @param {string} dateModifiedFrom
|
* @param {string} dateLastEdited
|
||||||
* @param {string} dateModifiedTo
|
* @param {string} dateCreated
|
||||||
* @param {string} utcDateModifiedFrom
|
* @param {string} utcDateLastEdited
|
||||||
* @param {string} utcDateModifiedTo
|
* @param {string} utcDateCreated
|
||||||
|
* @param {string} utcDateModified
|
||||||
*
|
*
|
||||||
* @extends Entity
|
* @extends Entity
|
||||||
*/
|
*/
|
||||||
class NoteRevision extends Entity {
|
class NoteRevision extends Entity {
|
||||||
static get entityName() { return "note_revisions"; }
|
static get entityName() { return "note_revisions"; }
|
||||||
static get primaryKeyName() { return "noteRevisionId"; }
|
static get primaryKeyName() { return "noteRevisionId"; }
|
||||||
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isProtected", "dateModifiedFrom", "dateModifiedTo", "utcDateModifiedFrom", "utcDateModifiedTo"]; }
|
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "contentLength", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
|
||||||
|
|
||||||
constructor(row) {
|
constructor(row) {
|
||||||
super(row);
|
super(row);
|
||||||
@ -98,8 +100,9 @@ class NoteRevision extends Entity {
|
|||||||
|
|
||||||
/** @returns {Promise} */
|
/** @returns {Promise} */
|
||||||
async setContent(content) {
|
async setContent(content) {
|
||||||
// force updating note itself so that dateChanged is represented correctly even for the content
|
// force updating note itself so that utcDateModified is represented correctly even for the content
|
||||||
this.forcedChange = true;
|
this.forcedChange = true;
|
||||||
|
this.contentLength = content.length;
|
||||||
await this.save();
|
await this.save();
|
||||||
|
|
||||||
this.content = content;
|
this.content = content;
|
||||||
|
@ -25,12 +25,12 @@ export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
|
|||||||
$content.empty();
|
$content.empty();
|
||||||
|
|
||||||
note = noteDetailService.getActiveTabNote();
|
note = noteDetailService.getActiveTabNote();
|
||||||
revisionItems = await server.get('notes/' + noteId + '/revisions');
|
revisionItems = await server.get(`notes/${noteId}/revisions`);
|
||||||
|
|
||||||
for (const item of revisionItems) {
|
for (const item of revisionItems) {
|
||||||
$list.append($('<option>', {
|
$list.append($('<option>', {
|
||||||
value: item.noteRevisionId,
|
value: item.noteRevisionId,
|
||||||
text: item.dateModifiedFrom
|
text: item.dateLastEdited
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,18 +46,20 @@ export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$list.on('change', () => {
|
$list.on('change', async () => {
|
||||||
const optVal = $list.find(":selected").val();
|
const optVal = $list.find(":selected").val();
|
||||||
|
|
||||||
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
|
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
|
||||||
|
|
||||||
$title.html(revisionItem.title);
|
$title.html(revisionItem.title);
|
||||||
|
|
||||||
|
const fullNoteRevision = await server.get(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`);
|
||||||
|
|
||||||
if (note.type === 'text') {
|
if (note.type === 'text') {
|
||||||
$content.html(revisionItem.content);
|
$content.html(fullNoteRevision.content);
|
||||||
}
|
}
|
||||||
else if (note.type === 'code') {
|
else if (note.type === 'code') {
|
||||||
$content.html($("<pre>").text(revisionItem.content));
|
$content.html($("<pre>").text(fullNoteRevision.content));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$content.text("Preview isn't available for this note type.");
|
$content.text("Preview isn't available for this note type.");
|
||||||
|
@ -27,7 +27,7 @@ class NoteRevisionsWidget extends StandardWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doRenderBody() {
|
async doRenderBody() {
|
||||||
const revisionItems = await server.get(`notes/${this.ctx.note.noteId}/revision-list`);
|
const revisionItems = await server.get(`notes/${this.ctx.note.noteId}/revisions`);
|
||||||
|
|
||||||
if (revisionItems.length === 0) {
|
if (revisionItems.length === 0) {
|
||||||
this.$body.text("No revisions yet...");
|
this.$body.text("No revisions yet...");
|
||||||
@ -44,7 +44,7 @@ class NoteRevisionsWidget extends StandardWidget {
|
|||||||
'data-note-path': this.ctx.note.noteId,
|
'data-note-path': this.ctx.note.noteId,
|
||||||
'data-note-revision-id': item.noteRevisionId,
|
'data-note-revision-id': item.noteRevisionId,
|
||||||
href: 'javascript:'
|
href: 'javascript:'
|
||||||
}).text(item.dateModifiedFrom.substr(0, 16)));
|
}).text(item.dateLastEdited.substr(0, 16)));
|
||||||
|
|
||||||
if (item.contentLength !== null) {
|
if (item.contentLength !== null) {
|
||||||
$listItem.append($("<span>").text(` (${item.contentLength} characters)`))
|
$listItem.append($("<span>").text(` (${item.contentLength} characters)`))
|
||||||
|
@ -4,33 +4,21 @@ const repository = require('../../services/repository');
|
|||||||
const noteCacheService = require('../../services/note_cache');
|
const noteCacheService = require('../../services/note_cache');
|
||||||
|
|
||||||
async function getNoteRevisions(req) {
|
async function getNoteRevisions(req) {
|
||||||
const noteId = req.params.noteId;
|
const {noteId} = req.params;
|
||||||
|
|
||||||
return await repository.getEntities(`
|
return await repository.getEntities(`
|
||||||
SELECT note_revisions.*
|
SELECT note_revisions.*
|
||||||
FROM note_revisions
|
FROM note_revisions
|
||||||
WHERE noteId = ?
|
WHERE noteId = ?
|
||||||
ORDER BY utcDateModifiedTo DESC`, [noteId]);
|
ORDER BY utcDateCreated DESC`, [noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getNoteRevisionList(req) {
|
async function getNoteRevision(req) {
|
||||||
const noteId = req.params.noteId;
|
const {noteRevisionId} = req.params;
|
||||||
|
|
||||||
return await repository.getEntities(`
|
return await repository.getNote(`SELECT *
|
||||||
SELECT noteRevisionId,
|
|
||||||
noteId,
|
|
||||||
title,
|
|
||||||
isProtected,
|
|
||||||
utcDateModifiedFrom,
|
|
||||||
utcDateModifiedTo,
|
|
||||||
dateModifiedFrom,
|
|
||||||
dateModifiedTo,
|
|
||||||
type,
|
|
||||||
mime,
|
|
||||||
CASE isProtected WHEN 1 THEN null ELSE LENGTH(content) END AS contentLength
|
|
||||||
FROM note_revisions
|
FROM note_revisions
|
||||||
WHERE noteId = ?
|
WHERE noteRevisionId = ?`, [noteId]);
|
||||||
ORDER BY utcDateModifiedTo DESC`, [noteId]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getEditedNotesOnDate(req) {
|
async function getEditedNotesOnDate(req) {
|
||||||
@ -42,7 +30,7 @@ async function getEditedNotesOnDate(req) {
|
|||||||
left join note_revisions using (noteId)
|
left join note_revisions using (noteId)
|
||||||
where substr(notes.dateCreated, 0, 11) = ?
|
where substr(notes.dateCreated, 0, 11) = ?
|
||||||
or substr(notes.dateModified, 0, 11) = ?
|
or substr(notes.dateModified, 0, 11) = ?
|
||||||
or substr(note_revisions.dateModifiedFrom, 0, 11) = ?`, [date, date, date]);
|
or substr(note_revisions.dateLastEdited, 0, 11) = ?`, [date, date, date]);
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
const notePath = noteCacheService.getNotePath(note.noteId);
|
const notePath = noteCacheService.getNotePath(note.noteId);
|
||||||
@ -55,6 +43,6 @@ async function getEditedNotesOnDate(req) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNoteRevisions,
|
getNoteRevisions,
|
||||||
getNoteRevisionList,
|
getNoteRevision,
|
||||||
getEditedNotesOnDate
|
getEditedNotesOnDate
|
||||||
};
|
};
|
@ -13,12 +13,12 @@ async function getRecentChanges() {
|
|||||||
notes.title AS current_title,
|
notes.title AS current_title,
|
||||||
notes.isProtected AS current_isProtected,
|
notes.isProtected AS current_isProtected,
|
||||||
note_revisions.title,
|
note_revisions.title,
|
||||||
note_revisions.utcDateModifiedTo AS date
|
note_revisions.utcDateCreated AS date
|
||||||
FROM
|
FROM
|
||||||
note_revisions
|
note_revisions
|
||||||
JOIN notes USING(noteId)
|
JOIN notes USING(noteId)
|
||||||
ORDER BY
|
ORDER BY
|
||||||
utcDateModifiedTo DESC
|
utcDateCreated DESC
|
||||||
LIMIT 1000
|
LIMIT 1000
|
||||||
)
|
)
|
||||||
UNION ALL SELECT * FROM (
|
UNION ALL SELECT * FROM (
|
||||||
|
@ -132,7 +132,7 @@ function register(app) {
|
|||||||
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectSubtree);
|
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectSubtree);
|
||||||
apiRoute(PUT, /\/api\/notes\/(.*)\/type\/(.*)\/mime\/(.*)/, notesApiRoute.setNoteTypeMime);
|
apiRoute(PUT, /\/api\/notes\/(.*)\/type\/(.*)\/mime\/(.*)/, notesApiRoute.setNoteTypeMime);
|
||||||
apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions);
|
apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions);
|
||||||
apiRoute(GET, '/api/notes/:noteId/revision-list', noteRevisionsApiRoute.getNoteRevisionList);
|
apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision);
|
||||||
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
|
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
|
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
|
||||||
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote);
|
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote);
|
||||||
|
@ -329,24 +329,27 @@ async function saveNoteRevision(note) {
|
|||||||
const revisionCutoff = dateUtils.utcDateStr(new Date(now.getTime() - noteRevisionSnapshotTimeInterval * 1000));
|
const revisionCutoff = dateUtils.utcDateStr(new Date(now.getTime() - noteRevisionSnapshotTimeInterval * 1000));
|
||||||
|
|
||||||
const existingNoteRevisionId = await sql.getValue(
|
const existingNoteRevisionId = await sql.getValue(
|
||||||
"SELECT noteRevisionId FROM note_revisions WHERE noteId = ? AND utcDateModifiedTo >= ?", [note.noteId, revisionCutoff]);
|
"SELECT noteRevisionId FROM note_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 (!existingNoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) {
|
||||||
await new NoteRevision({
|
const noteRevision = await new NoteRevision({
|
||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
// title and text should be decrypted now
|
// title and text should be decrypted now
|
||||||
title: note.title,
|
title: note.title,
|
||||||
content: await note.getContent(),
|
contentLength: -1, // will be updated in .setContent()
|
||||||
type: note.type,
|
type: note.type,
|
||||||
mime: note.mime,
|
mime: note.mime,
|
||||||
isProtected: false, // will be fixed in the protectNoteRevisions() call
|
isProtected: false, // will be fixed in the protectNoteRevisions() call
|
||||||
utcDateModifiedFrom: note.utcDateModified,
|
utcDateLastEdited: note.utcDateModified,
|
||||||
utcDateModifiedTo: dateUtils.utcNowDateTime(),
|
utcDateCreated: dateUtils.utcNowDateTime(),
|
||||||
dateModifiedFrom: note.dateModified,
|
utcDateModified: dateUtils.utcNowDateTime(),
|
||||||
dateModifiedTo: dateUtils.localNowDateTime()
|
dateLastEdited: note.dateModified,
|
||||||
|
dateCreated: dateUtils.localNowDateTime()
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
|
await noteRevision.setContent(await note.getContent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,11 @@ async function getNotes(noteIds) {
|
|||||||
return notes;
|
return notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<NoteRevision|null>} */
|
||||||
|
async function getNoteRevision(noteRevisionId) {
|
||||||
|
return await getEntity("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
|
||||||
|
}
|
||||||
|
|
||||||
/** @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]);
|
||||||
|
@ -102,8 +102,8 @@ async function updateNoteRevision(entity, sourceId) {
|
|||||||
|
|
||||||
await sql.transactional(async () => {
|
await sql.transactional(async () => {
|
||||||
// we update note revision even if date modified to is the same because the only thing which might have changed
|
// we update note revision even if date modified to is the same because the only thing which might have changed
|
||||||
// is the protected status (and correnspondingly title and content) which doesn't affect the utcDateModifiedTo
|
// is the protected status (and correnspondingly title and content) which doesn't affect the utcDateCreated
|
||||||
if (orig === null || orig.utcDateModifiedTo <= entity.utcDateModifiedTo) {
|
if (orig === null || orig.utcDateCreated <= entity.utcDateCreated) {
|
||||||
entity.content = entity.content === null ? null : Buffer.from(entity.content, 'base64');
|
entity.content = entity.content === null ? null : Buffer.from(entity.content, 'base64');
|
||||||
|
|
||||||
await sql.replace('note_revisions', entity);
|
await sql.replace('note_revisions', entity);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user