mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	note revisions changes WIP
This commit is contained in:
		| @@ -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); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user