From 898afb7ed777136f7e1f4a4e9e201114fe238041 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 4 Sep 2024 08:41:17 +0000 Subject: [PATCH 01/10] Add revision number limit --- src/becca/entities/bnote.ts | 25 +++++++++-- .../widgets/type_widgets/content_widget.js | 2 + .../options/other/revisions_snapshot_limit.js | 42 +++++++++++++++++++ src/public/translations/en/translation.json | 9 +++- src/routes/api/options.ts | 1 + src/routes/api/revisions.ts | 9 ++++ src/routes/routes.ts | 1 + src/services/options_init.ts | 1 + 8 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js diff --git a/src/becca/entities/bnote.ts b/src/becca/entities/bnote.ts index 3ae4d60b5..2c4384eb1 100644 --- a/src/becca/entities/bnote.ts +++ b/src/becca/entities/bnote.ts @@ -3,6 +3,8 @@ import protectedSessionService from "../../services/protected_session.js"; import log from "../../services/log.js"; import sql from "../../services/sql.js"; +import optionService from "../../services/options.js"; +import eraseService from "../../services/erase.js"; import utils from "../../services/utils.js"; import dateUtils from "../../services/date_utils.js"; import AbstractBeccaEntity from "./abstract_becca_entity.js"; @@ -68,7 +70,7 @@ class BNote extends AbstractBeccaEntity { /** set during the deletion operation, before it is completed (removed from becca completely). */ isBeingDeleted!: boolean; isDecrypted!: boolean; - + ownedAttributes!: BAttribute[]; parentBranches!: BBranch[]; parents!: BNote[]; @@ -455,8 +457,8 @@ class BNote extends AbstractBeccaEntity { return this.getAttributes().find( attr => attr.name.toLowerCase() === name - && (!value || attr.value.toLowerCase() === value) - && attr.type === type); + && (!value || attr.value.toLowerCase() === value) + && attr.type === type); } getRelationTarget(name: string) { @@ -1612,10 +1614,27 @@ class BNote extends AbstractBeccaEntity { revision.setContent(noteContent); + this.eraseExcessRevisions() return revision; }); } + // Limit the number of Snapshots to revisionSnapshotNumberLimit + // Delete older Snapshots that exceed the limit + eraseExcessRevisions() { + const revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit')); + if (revisionSnapshotNumberLimit >= 0) { + const revisions = this.getRevisions(); + if (revisions.length - revisionSnapshotNumberLimit > 0) { + const revisionIds = revisions + .slice(0, revisions.length - revisionSnapshotNumberLimit) + .map(revision => revision.revisionId) + .filter((id): id is string => id !== undefined); + eraseService.eraseRevisions(revisionIds); + } + } + } + /** * @param matchBy - choose by which property we detect if to update an existing attachment. * Supported values are either 'attachmentId' (default) or 'title' diff --git a/src/public/app/widgets/type_widgets/content_widget.js b/src/public/app/widgets/type_widgets/content_widget.js index 6c2ddc0cb..e4eced3ef 100644 --- a/src/public/app/widgets/type_widgets/content_widget.js +++ b/src/public/app/widgets/type_widgets/content_widget.js @@ -23,6 +23,7 @@ import SearchEngineOptions from "./options/other/search_engine.js"; import TrayOptions from "./options/other/tray.js"; import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js"; import RevisionsSnapshotIntervalOptions from "./options/other/revisions_snapshot_interval.js"; +import RevisionsSnapshotLimitOptions from "./options/other/revisions_snapshot_limit.js"; import NetworkConnectionsOptions from "./options/other/network_connections.js"; import AdvancedSyncOptions from "./options/advanced/sync.js"; import DatabaseIntegrityCheckOptions from "./options/advanced/database_integrity_check.js"; @@ -88,6 +89,7 @@ const CONTENT_WIDGETS = { NoteErasureTimeoutOptions, AttachmentErasureTimeoutOptions, RevisionsSnapshotIntervalOptions, + RevisionsSnapshotLimitOptions, NetworkConnectionsOptions ], _optionsAdvanced: [ diff --git a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js b/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js new file mode 100644 index 000000000..5d25c99a9 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js @@ -0,0 +1,42 @@ +import OptionsWidget from "../options_widget.js"; +import { t } from "../../../../services/i18n.js"; +import server from "../../../../services/server.js"; +import toastService from "../../../../services/toast.js"; + +const TPL = ` +
+

${t("revisions_snapshot_limit.note_revisions_snapshot_limit_title")}

+ +

${t("revisions_snapshot_limit.note_revisions_snapshot_limit_description")}

+ +
+ + +
+ + +
`; + +export default class RevisionsSnapshotLimitOptions extends OptionsWidget { + doRender() { + this.$widget = $(TPL); + this.$revisionsNumberLimit = this.$widget.find(".revision-snapshot-number-limit"); + this.$revisionsNumberLimit.on('change', () => { + let revisionSnapshotNumberLimit = this.$revisionsNumberLimit.val(); + if (!isNaN(revisionSnapshotNumberLimit) && revisionSnapshotNumberLimit >= -1) { + this.updateOption('revisionSnapshotNumberLimit', revisionSnapshotNumberLimit) + } + }); + this.$eraseExcessRevisionSnapshotsButton = this.$widget.find(".erase-excess-revision-snapshots-now-button"); + this.$eraseExcessRevisionSnapshotsButton.on('click', () => { + server.post('revisions/erase-all-excess-revisions').then(() => { + toastService.showMessage(t("revisions_snapshot_limit.erase_excess_revision_snapshots_prompt")); + }); + }); + } + + async optionsLoaded(options) { + this.$revisionsNumberLimit.val(options.revisionSnapshotNumberLimit); + } +} diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index d92f2fe60..61e7af1f9 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1064,7 +1064,14 @@ "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Note Revisions Snapshot Interval", "note_revisions_snapshot_description": "Note revision snapshot time interval is time in seconds after which a new note revision will be created for the note. See wiki for more info.", - "snapshot_time_interval_label": "Note revision snapshot time interval (in seconds)" + "snapshot_time_interval_label": "Note revision snapshot time interval (in seconds):" + }, + "revisions_snapshot_limit": { + "note_revisions_snapshot_limit_title": "Note Revisions Snapshot Limit", + "note_revisions_snapshot_limit_description": "The note revision snapshot number limit refers to the maximum number of revisions that can be saved for each note. Where -1 means no limit, 0 means delete all revisions.", + "snapshot_number_limit_label": "Note revision snapshot number limit:", + "erase_excess_revision_snapshots": "Erase excess revision snapshots now", + "erase_excess_revision_snapshots_prompt": "Excess revision snapshots have been erased." }, "search_engine": { "title": "Search Engine", diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 4c7b06d2a..ddba39d4a 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -11,6 +11,7 @@ const ALLOWED_OPTIONS = new Set([ 'eraseEntitiesAfterTimeInSeconds', 'protectedSessionTimeout', 'revisionSnapshotTimeInterval', + 'revisionSnapshotNumberLimit', 'zoomFactor', 'theme', 'syncServerHost', diff --git a/src/routes/api/revisions.ts b/src/routes/api/revisions.ts index 7e98318d5..f791896e4 100644 --- a/src/routes/api/revisions.ts +++ b/src/routes/api/revisions.ts @@ -2,6 +2,7 @@ import beccaService from "../../becca/becca_service.js"; import revisionService from "../../services/revisions.js"; +import optionService from "../../services/options.js"; import utils from "../../services/utils.js"; import sql from "../../services/sql.js"; import cls from "../../services/cls.js"; @@ -112,6 +113,13 @@ function eraseRevision(req: Request) { eraseService.eraseRevisions([req.params.revisionId]); } +function eraseAllExcessRevisions() { + let allNoteIds = sql.getRows('SELECT noteId FROM notes') as { noteId: string }[]; + allNoteIds.forEach(row => { + becca.getNote(row.noteId)?.eraseExcessRevisions() + }); +} + function restoreRevision(req: Request) { const revision = becca.getRevision(req.params.revisionId); @@ -211,6 +219,7 @@ export default { downloadRevision, getEditedNotesOnDate, eraseAllRevisions, + eraseAllExcessRevisions, eraseRevision, restoreRevision }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index fd440804d..f1845eb1f 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -184,6 +184,7 @@ function register(app: express.Application) { apiRoute(GET, '/api/notes/:noteId/revisions', revisionsApiRoute.getRevisions); apiRoute(DEL, '/api/notes/:noteId/revisions', revisionsApiRoute.eraseAllRevisions); + apiRoute(PST, '/api/revisions/erase-all-excess-revisions', revisionsApiRoute.eraseAllExcessRevisions); apiRoute(GET, '/api/revisions/:revisionId', revisionsApiRoute.getRevision); apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob); apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision); diff --git a/src/services/options_init.ts b/src/services/options_init.ts index 2adbadd2b..e32787dc9 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -49,6 +49,7 @@ async function initNotSyncedOptions(initialized: boolean, theme: string, opts: N const defaultOptions: DefaultOption[] = [ { name: 'revisionSnapshotTimeInterval', value: '600', isSynced: true }, + { name: 'revisionSnapshotNumberLimit', value: '-1', isSynced: true }, { name: 'protectedSessionTimeout', value: '600', isSynced: true }, { name: 'zoomFactor', value: process.platform === "win32" ? '0.9' : '1.0', isSynced: false }, { name: 'overrideThemeFonts', value: 'false', isSynced: false }, From 78bfc3341b4f646fd22b49f8c9b33f3d70bf3c3b Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 4 Sep 2024 09:04:40 +0000 Subject: [PATCH 02/10] Add versioningLimit label support --- src/becca/entities/bnote.ts | 10 +++++++--- src/routes/api/revisions.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/becca/entities/bnote.ts b/src/becca/entities/bnote.ts index 2c4384eb1..9896567e8 100644 --- a/src/becca/entities/bnote.ts +++ b/src/becca/entities/bnote.ts @@ -1614,15 +1614,19 @@ class BNote extends AbstractBeccaEntity { revision.setContent(noteContent); - this.eraseExcessRevisions() + this.eraseExcessRevisionSnapshots() return revision; }); } // Limit the number of Snapshots to revisionSnapshotNumberLimit // Delete older Snapshots that exceed the limit - eraseExcessRevisions() { - const revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit')); + eraseExcessRevisionSnapshots() { + // lable has a higher priority + let revisionSnapshotNumberLimit = parseInt(this.getLabelValue("versioningLimit") ?? ""); + if (!Number.isInteger(revisionSnapshotNumberLimit)) { + revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit')); + } if (revisionSnapshotNumberLimit >= 0) { const revisions = this.getRevisions(); if (revisions.length - revisionSnapshotNumberLimit > 0) { diff --git a/src/routes/api/revisions.ts b/src/routes/api/revisions.ts index f791896e4..9e1e4ffac 100644 --- a/src/routes/api/revisions.ts +++ b/src/routes/api/revisions.ts @@ -116,7 +116,7 @@ function eraseRevision(req: Request) { function eraseAllExcessRevisions() { let allNoteIds = sql.getRows('SELECT noteId FROM notes') as { noteId: string }[]; allNoteIds.forEach(row => { - becca.getNote(row.noteId)?.eraseExcessRevisions() + becca.getNote(row.noteId)?.eraseExcessRevisionSnapshots() }); } From 2a273836827b8dfc6a3204dfdaa4918ab7940137 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 4 Sep 2024 14:27:33 +0000 Subject: [PATCH 03/10] revision snapshots number limit --- src/public/app/widgets/type_widgets/content_widget.js | 4 ++-- ...s_snapshot_limit.js => revision_snapshots_limit.js} | 10 +++++----- src/public/translations/en/translation.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/public/app/widgets/type_widgets/options/other/{revisions_snapshot_limit.js => revision_snapshots_limit.js} (79%) diff --git a/src/public/app/widgets/type_widgets/content_widget.js b/src/public/app/widgets/type_widgets/content_widget.js index e4eced3ef..d50252289 100644 --- a/src/public/app/widgets/type_widgets/content_widget.js +++ b/src/public/app/widgets/type_widgets/content_widget.js @@ -23,7 +23,7 @@ import SearchEngineOptions from "./options/other/search_engine.js"; import TrayOptions from "./options/other/tray.js"; import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js"; import RevisionsSnapshotIntervalOptions from "./options/other/revisions_snapshot_interval.js"; -import RevisionsSnapshotLimitOptions from "./options/other/revisions_snapshot_limit.js"; +import RevisionSnapshotsLimitOptions from "./options/other/revision_snapshots_limit.js"; import NetworkConnectionsOptions from "./options/other/network_connections.js"; import AdvancedSyncOptions from "./options/advanced/sync.js"; import DatabaseIntegrityCheckOptions from "./options/advanced/database_integrity_check.js"; @@ -89,7 +89,7 @@ const CONTENT_WIDGETS = { NoteErasureTimeoutOptions, AttachmentErasureTimeoutOptions, RevisionsSnapshotIntervalOptions, - RevisionsSnapshotLimitOptions, + RevisionSnapshotsLimitOptions, NetworkConnectionsOptions ], _optionsAdvanced: [ diff --git a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js b/src/public/app/widgets/type_widgets/options/other/revision_snapshots_limit.js similarity index 79% rename from src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js rename to src/public/app/widgets/type_widgets/options/other/revision_snapshots_limit.js index 5d25c99a9..58cb409ac 100644 --- a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_limit.js +++ b/src/public/app/widgets/type_widgets/options/other/revision_snapshots_limit.js @@ -18,12 +18,12 @@ const TPL = ` ${t('revisions_snapshot_limit.erase_excess_revision_snapshots')} `; -export default class RevisionsSnapshotLimitOptions extends OptionsWidget { +export default class RevisionSnapshotsLimitOptions extends OptionsWidget { doRender() { this.$widget = $(TPL); - this.$revisionsNumberLimit = this.$widget.find(".revision-snapshot-number-limit"); - this.$revisionsNumberLimit.on('change', () => { - let revisionSnapshotNumberLimit = this.$revisionsNumberLimit.val(); + this.$revisionSnapshotsNumberLimit = this.$widget.find(".revision-snapshot-number-limit"); + this.$revisionSnapshotsNumberLimit.on('change', () => { + let revisionSnapshotNumberLimit = this.$revisionSnapshotsNumberLimit.val(); if (!isNaN(revisionSnapshotNumberLimit) && revisionSnapshotNumberLimit >= -1) { this.updateOption('revisionSnapshotNumberLimit', revisionSnapshotNumberLimit) } @@ -37,6 +37,6 @@ export default class RevisionsSnapshotLimitOptions extends OptionsWidget { } async optionsLoaded(options) { - this.$revisionsNumberLimit.val(options.revisionSnapshotNumberLimit); + this.$revisionSnapshotsNumberLimit.val(options.revisionSnapshotNumberLimit); } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 61e7af1f9..26097493f 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1067,7 +1067,7 @@ "snapshot_time_interval_label": "Note revision snapshot time interval (in seconds):" }, "revisions_snapshot_limit": { - "note_revisions_snapshot_limit_title": "Note Revisions Snapshot Limit", + "note_revisions_snapshot_limit_title": "Note Revision Snapshots Limit", "note_revisions_snapshot_limit_description": "The note revision snapshot number limit refers to the maximum number of revisions that can be saved for each note. Where -1 means no limit, 0 means delete all revisions.", "snapshot_number_limit_label": "Note revision snapshot number limit:", "erase_excess_revision_snapshots": "Erase excess revision snapshots now", From 3e085e5caeeff35f6ad478327b471de7ab169f61 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 14 Sep 2024 14:32:43 +0800 Subject: [PATCH 04/10] Show revision information --- src/public/app/widgets/dialogs/revisions.js | 27 +++++++++++++++++++-- src/public/translations/en/translation.json | 5 +++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/dialogs/revisions.js b/src/public/app/widgets/dialogs/revisions.js index 4e074cfcd..51cd5a83e 100644 --- a/src/public/app/widgets/dialogs/revisions.js +++ b/src/public/app/widgets/dialogs/revisions.js @@ -8,7 +8,8 @@ import openService from "../../services/open.js"; import protectedSessionHolder from "../../services/protected_session_holder.js"; import BasicWidget from "../basic_widget.js"; import dialogService from "../../services/dialog.js"; - +import OnClickButtonWidget from "../buttons/onclick_button.js"; +import options from "../../services/options.js"; const TPL = `