From 7af4e527660761f65605ab5d0d65879d434e78e0 Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Sun, 16 Feb 2025 21:29:38 -0700 Subject: [PATCH 01/62] brought over changes from 879035d The last known good state before I got sidetracked into docker changes --- src/public/app/widgets/type_widgets/content_widget.ts | 4 +++- src/services/auth.ts | 2 +- src/services/options_init.ts | 6 +++++- src/share/routes.ts | 4 +++- src/views/share/page.ejs | 5 +++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index 1f19e2b81..d2ea5a971 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -35,6 +35,7 @@ import RibbonOptions from "./options/appearance/ribbon.js"; import LocalizationOptions from "./options/appearance/i18n.js"; import CodeBlockOptions from "./options/appearance/code_block.js"; import EditorOptions from "./options/text_notes/editor.js"; +import ShareSettingsOptions from "./options/other/share_settings.js"; // added import statement import type FNote from "../../entities/fnote.js"; import type NoteContextAwareWidget from "../note_context_aware_widget.js"; @@ -76,7 +77,8 @@ const CONTENT_WIDGETS: Record = { RevisionsSnapshotIntervalOptions, RevisionSnapshotsLimitOptions, NetworkConnectionsOptions, - HtmlImportTagsOptions + HtmlImportTagsOptions, + ShareSettingsOptions // moved to the end of the array ], _optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions], _backendLog: [BackendLogWidget] diff --git a/src/services/auth.ts b/src/services/auth.ts index 3e8957100..d89fad633 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -15,7 +15,7 @@ function checkAuth(req: Request, res: Response, next: NextFunction) { if (!sqlInit.isDbInitialized()) { res.redirect("setup"); } else if (!req.session.loggedIn && !isElectron && !noAuthentication) { - res.redirect("login"); + res.redirect("share"); } else { next(); } diff --git a/src/services/options_init.ts b/src/services/options_init.ts index bb962835d..0c738a759 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -252,7 +252,11 @@ const defaultOptions: DefaultOption[] = [ "tt" ]), isSynced: true - } + }, + + // Share settings + { name: 'redirectBareDomain', value: 'false', isSynced: true }, + { name: 'showLoginInShareTheme', value: 'false', isSynced: true } ]; /** diff --git a/src/share/routes.ts b/src/share/routes.ts index c77a41fc0..a1ea2ecf2 100644 --- a/src/share/routes.ts +++ b/src/share/routes.ts @@ -16,6 +16,7 @@ import type SNote from "./shaca/entities/snote.js"; import type SBranch from "./shaca/entities/sbranch.js"; import type SAttachment from "./shaca/entities/sattachment.js"; import utils from "../services/utils.js"; +import optionService from '../services/option_service.js'; function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { @@ -151,7 +152,8 @@ function register(router: Router) { const { header, content, isEmpty } = contentRenderer.getContent(note); const subRoot = getSharedSubTreeRoot(note); - const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath }; + const showLoginInShareTheme = optionService.getOption('showLoginInShareTheme'); + const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath, showLoginInShareTheme }; let useDefaultView = true; // Check if the user has their own template diff --git a/src/views/share/page.ejs b/src/views/share/page.ejs index 0ff04174d..35d01cd2c 100644 --- a/src/views/share/page.ejs +++ b/src/views/share/page.ejs @@ -88,5 +88,10 @@ <% } %> +
+ <% if (showLoginInShareTheme === 'true') { %> +

+ <% } %> +
From bc66e985331c00b1ccea3d6307a0f54ff714a252 Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Sun, 16 Feb 2025 22:17:59 -0700 Subject: [PATCH 02/62] okay, we can start npm server now, but new db redirects to share --- .../widgets/type_widgets/content_widget.ts | 2 +- .../options/other/share_settings.ts | 47 +++++++++++++++++++ src/services/options_interface.ts | 3 ++ src/share/routes.ts | 4 +- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/public/app/widgets/type_widgets/options/other/share_settings.ts diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index d2ea5a971..e7a0533ff 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -35,7 +35,7 @@ import RibbonOptions from "./options/appearance/ribbon.js"; import LocalizationOptions from "./options/appearance/i18n.js"; import CodeBlockOptions from "./options/appearance/code_block.js"; import EditorOptions from "./options/text_notes/editor.js"; -import ShareSettingsOptions from "./options/other/share_settings.js"; // added import statement +import ShareSettingsOptions from "./options/other/share_settings.js"; import type FNote from "../../entities/fnote.js"; import type NoteContextAwareWidget from "../note_context_aware_widget.js"; diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts new file mode 100644 index 000000000..d90840948 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -0,0 +1,47 @@ +import OptionsWidget from "../options_widget.js"; +import options from "../../../../services/options.js"; +import { t } from "../../../../services/i18n.js"; + +const TPL = ` +
+

${t('Share Settings')}

+ +
+ +

${t('When enabled, accessing the root URL will redirect to the Share page instead of Login')}

+
+ +
+ +

${t('Add a login link to the Share page footer')}

+
+
`; + +export default class ShareSettingsOptions extends OptionsWidget { + doRender() { + this.$widget = $(TPL); + this.contentSized(); + } + + async optionsLoaded(options: Record) { + this.$widget.find('input[name="redirectBareDomain"]').prop('checked', + options.redirectBareDomain === 'true'); + + this.$widget.find('input[name="showLoginInShareTheme"]').prop('checked', + options.showLoginInShareTheme === 'true'); + } + + async save() { + const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop('checked'); + await this.updateOption('redirectBareDomain', redirectBareDomain.toString()); + + const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop('checked'); + await this.updateOption('showLoginInShareTheme', showLoginInShareTheme.toString()); + } +} diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts index f3a92383b..ef55af6c5 100644 --- a/src/services/options_interface.ts +++ b/src/services/options_interface.ts @@ -29,6 +29,9 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Sun, 16 Feb 2025 22:47:50 -0700 Subject: [PATCH 03/62] feature complete; tested and working on local linux machine --- .../type_widgets/options/other/share_settings.ts | 10 +++++++--- src/routes/api/options.ts | 4 +++- src/services/auth.ts | 4 +++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index d90840948..d7743c63c 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -1,6 +1,7 @@ import OptionsWidget from "../options_widget.js"; import options from "../../../../services/options.js"; import { t } from "../../../../services/i18n.js"; +import type { OptionMap, OptionNames } from "../../../../../../services/options_interface.js"; const TPL = `
@@ -27,9 +28,12 @@ export default class ShareSettingsOptions extends OptionsWidget { doRender() { this.$widget = $(TPL); this.contentSized(); + + // Add change handlers for both checkboxes + this.$widget.find('input[type="checkbox"]').on('change', () => this.save()); } - async optionsLoaded(options: Record) { + async optionsLoaded(options: OptionMap) { this.$widget.find('input[name="redirectBareDomain"]').prop('checked', options.redirectBareDomain === 'true'); @@ -39,9 +43,9 @@ export default class ShareSettingsOptions extends OptionsWidget { async save() { const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop('checked'); - await this.updateOption('redirectBareDomain', redirectBareDomain.toString()); + await this.updateOption<'redirectBareDomain'>('redirectBareDomain', redirectBareDomain.toString()); const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop('checked'); - await this.updateOption('showLoginInShareTheme', showLoginInShareTheme.toString()); + await this.updateOption<'showLoginInShareTheme'>('showLoginInShareTheme', showLoginInShareTheme.toString()); } } diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 14cb7ec42..85272ea62 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -72,7 +72,9 @@ const ALLOWED_OPTIONS = new Set([ "textNoteEditorMultilineToolbar", "layoutOrientation", "backgroundEffects", - "allowedHtmlTags" // Allow configuring HTML import tags + "allowedHtmlTags", + "redirectBareDomain", + "showLoginInShareTheme" ]); function getOptions() { diff --git a/src/services/auth.ts b/src/services/auth.ts index d89fad633..5ba56e426 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -7,6 +7,7 @@ import { isElectron } from "./utils.js"; import passwordEncryptionService from "./encryption/password_encryption.js"; import config from "./config.js"; import passwordService from "./encryption/password.js"; +import options from "./options.js"; import type { NextFunction, Request, Response } from "express"; const noAuthentication = config.General && config.General.noAuthentication === true; @@ -15,7 +16,8 @@ function checkAuth(req: Request, res: Response, next: NextFunction) { if (!sqlInit.isDbInitialized()) { res.redirect("setup"); } else if (!req.session.loggedIn && !isElectron && !noAuthentication) { - res.redirect("share"); + const redirectToShare = options.getOption('redirectBareDomain') === 'true'; + res.redirect(redirectToShare ? "share" : "login"); } else { next(); } From 2ec2d784ec8f6f6f4fda00d9aef32b7b264885d8 Mon Sep 17 00:00:00 2001 From: Matt Wilkie Date: Mon, 17 Feb 2025 13:19:55 -0700 Subject: [PATCH 04/62] results of `npx prettier` --- .../widgets/type_widgets/content_widget.ts | 1 - .../options/other/share_settings.ts | 28 +++++++++---------- src/services/auth.ts | 2 +- src/services/options_init.ts | 4 +-- src/share/routes.ts | 4 +-- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index e7a0533ff..5bd577fd2 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -85,7 +85,6 @@ const CONTENT_WIDGETS: Record = { }; export default class ContentWidgetTypeWidget extends TypeWidget { - private $content!: JQuery; static getType() { diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index d7743c63c..9c7cd1b2d 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -5,22 +5,22 @@ import type { OptionMap, OptionNames } from "../../../../../../services/options_ const TPL = `
-

${t('Share Settings')}

+

${t("Share Settings")}

-

${t('When enabled, accessing the root URL will redirect to the Share page instead of Login')}

+

${t("When enabled, accessing the root URL will redirect to the Share page instead of Login")}

-

${t('Add a login link to the Share page footer')}

+

${t("Add a login link to the Share page footer")}

`; @@ -30,22 +30,20 @@ export default class ShareSettingsOptions extends OptionsWidget { this.contentSized(); // Add change handlers for both checkboxes - this.$widget.find('input[type="checkbox"]').on('change', () => this.save()); + this.$widget.find('input[type="checkbox"]').on("change", () => this.save()); } async optionsLoaded(options: OptionMap) { - this.$widget.find('input[name="redirectBareDomain"]').prop('checked', - options.redirectBareDomain === 'true'); - - this.$widget.find('input[name="showLoginInShareTheme"]').prop('checked', - options.showLoginInShareTheme === 'true'); + this.$widget.find('input[name="redirectBareDomain"]').prop("checked", options.redirectBareDomain === "true"); + + this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked", options.showLoginInShareTheme === "true"); } async save() { - const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop('checked'); - await this.updateOption<'redirectBareDomain'>('redirectBareDomain', redirectBareDomain.toString()); + const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop("checked"); + await this.updateOption<"redirectBareDomain">("redirectBareDomain", redirectBareDomain.toString()); - const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop('checked'); - await this.updateOption<'showLoginInShareTheme'>('showLoginInShareTheme', showLoginInShareTheme.toString()); + const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked"); + await this.updateOption<"showLoginInShareTheme">("showLoginInShareTheme", showLoginInShareTheme.toString()); } } diff --git a/src/services/auth.ts b/src/services/auth.ts index 5ba56e426..68c497b91 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -16,7 +16,7 @@ function checkAuth(req: Request, res: Response, next: NextFunction) { if (!sqlInit.isDbInitialized()) { res.redirect("setup"); } else if (!req.session.loggedIn && !isElectron && !noAuthentication) { - const redirectToShare = options.getOption('redirectBareDomain') === 'true'; + const redirectToShare = options.getOption("redirectBareDomain") === "true"; res.redirect(redirectToShare ? "share" : "login"); } else { next(); diff --git a/src/services/options_init.ts b/src/services/options_init.ts index 0c738a759..da81abc90 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -255,8 +255,8 @@ const defaultOptions: DefaultOption[] = [ }, // Share settings - { name: 'redirectBareDomain', value: 'false', isSynced: true }, - { name: 'showLoginInShareTheme', value: 'false', isSynced: true } + { name: "redirectBareDomain", value: "false", isSynced: true }, + { name: "showLoginInShareTheme", value: "false", isSynced: true } ]; /** diff --git a/src/share/routes.ts b/src/share/routes.ts index ae2b5d414..3b6258872 100644 --- a/src/share/routes.ts +++ b/src/share/routes.ts @@ -16,7 +16,7 @@ import type SNote from "./shaca/entities/snote.js"; import type SBranch from "./shaca/entities/sbranch.js"; import type SAttachment from "./shaca/entities/sattachment.js"; import utils from "../services/utils.js"; -import options from '../services/options.js'; +import options from "../services/options.js"; function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { @@ -152,7 +152,7 @@ function register(router: Router) { const { header, content, isEmpty } = contentRenderer.getContent(note); const subRoot = getSharedSubTreeRoot(note); - const showLoginInShareTheme = options.getOption('showLoginInShareTheme'); + const showLoginInShareTheme = options.getOption("showLoginInShareTheme"); const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath, showLoginInShareTheme }; let useDefaultView = true; From 657638ee54f93c5e278e2a65b68b98230de102a0 Mon Sep 17 00:00:00 2001 From: Matt Wilkie Date: Mon, 17 Feb 2025 13:46:03 -0700 Subject: [PATCH 05/62] responding to code review (thanks pano!) --- .../widgets/type_widgets/content_widget.ts | 2 +- .../options/other/share_settings.ts | 13 ++++++----- src/public/translations/en/translation.json | 22 +++++++++++++++++++ src/services/options_interface.ts | 7 +++--- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index 5bd577fd2..e4cb81c10 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -78,7 +78,7 @@ const CONTENT_WIDGETS: Record = { RevisionSnapshotsLimitOptions, NetworkConnectionsOptions, HtmlImportTagsOptions, - ShareSettingsOptions // moved to the end of the array + ShareSettingsOptions ], _optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions], _backendLog: [BackendLogWidget] diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index 9c7cd1b2d..c4c9d9421 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -5,22 +5,23 @@ import type { OptionMap, OptionNames } from "../../../../../../services/options_ const TPL = `
-

${t("Share Settings")}

- +

${t("share.title")}

+
-

${t("When enabled, accessing the root URL will redirect to the Share page instead of Login")}

+

${t("share.redirect_bare_domain_description")}

-

${t("Add a login link to the Share page footer")}

+

${t("share.show_login_link_description")}

+

 

`; diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index f8fdbc51a..7fed97206 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1656,5 +1656,27 @@ "minutes": "Minutes", "hours": "Hours", "days": "Days" + }, + "share": { + "title": "Share Settings", + "redirect_bare_domain": "Redirect bare domain to Share page", + "redirect_bare_domain_description": "When enabled, accessing the root URL will redirect to the Share page instead of Login", + "show_login_link": "Show Login link in Share theme", + "show_login_link_description": "Add a login link to the Share page footer" + }, + "sync_2": { + "config_title": "Sync Configuration", + "server_address": "Server instance address", + "timeout": "Sync timeout (milliseconds)", + "proxy_label": "Sync proxy server (optional)", + "note": "Note", + "note_description": "If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).", + "special_value_description": "Another special value is noproxy which forces ignoring even the system proxy and respects NODE_TLS_REJECT_UNAUTHORIZED.", + "save": "Save", + "help": "Help", + "test_title": "Sync Test", + "test_description": "This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.", + "test_button": "Test sync", + "handshake_failed": "Sync server handshake failed, error: {{message}}" } } diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts index ef55af6c5..9a89c9ac7 100644 --- a/src/services/options_interface.ts +++ b/src/services/options_interface.ts @@ -29,9 +29,6 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Mon, 17 Feb 2025 13:55:55 -0700 Subject: [PATCH 06/62] fix indent --- src/public/translations/en/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 027c2f269..8cbe3aa9a 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1677,7 +1677,7 @@ "test_button": "Test sync", "handshake_failed": "Sync server handshake failed, error: {{message}}" }, - "time_selector": { + "time_selector": { "invalid_input": "The entered time value is not a valid number." - } + } } From bcb40b531f718b7d2e52977ba88a80464263ee6d Mon Sep 17 00:00:00 2001 From: "Romain DEP." Date: Mon, 17 Feb 2025 22:20:20 +0100 Subject: [PATCH 07/62] feat(view/calendar): support coloring --- src/public/app/widgets/view_widgets/calendar_view.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/view_widgets/calendar_view.ts b/src/public/app/widgets/view_widgets/calendar_view.ts index 70b68e98f..fc1d2595f 100644 --- a/src/public/app/widgets/view_widgets/calendar_view.ts +++ b/src/public/app/widgets/view_widgets/calendar_view.ts @@ -192,6 +192,7 @@ export default class CalendarView extends ViewMode { for (const note of notes) { const startDate = note.getAttributeValue("label", "startDate"); const customTitle = note.getAttributeValue("label", "calendar:title"); + const color = note.getAttributeValue("label", "calendar:color") ?? note.getAttributeValue("label", "color") ?? undefined; if (!startDate) { continue; @@ -203,7 +204,8 @@ export default class CalendarView extends ViewMode { title: title, start: startDate, url: `#${note.noteId}`, - noteId: note.noteId + noteId: note.noteId, + color: color, }; const endDate = CalendarView.#offsetDate(note.getAttributeValue("label", "endDate") ?? startDate, 1); From aab35955bf6af74595a49e4737278709d8b907b8 Mon Sep 17 00:00:00 2001 From: Matt Wilkie Date: Mon, 17 Feb 2025 16:45:47 -0700 Subject: [PATCH 08/62] remove duplicated sync_2 --- src/public/translations/en/translation.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 8cbe3aa9a..f2978237d 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1662,21 +1662,6 @@ "show_login_link": "Show Login link in Share theme", "show_login_link_description": "Add a login link to the Share page footer" }, - "sync_2": { - "config_title": "Sync Configuration", - "server_address": "Server instance address", - "timeout": "Sync timeout (milliseconds)", - "proxy_label": "Sync proxy server (optional)", - "note": "Note", - "note_description": "If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).", - "special_value_description": "Another special value is noproxy which forces ignoring even the system proxy and respects NODE_TLS_REJECT_UNAUTHORIZED.", - "save": "Save", - "help": "Help", - "test_title": "Sync Test", - "test_description": "This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.", - "test_button": "Test sync", - "handshake_failed": "Sync server handshake failed, error: {{message}}" - }, "time_selector": { "invalid_input": "The entered time value is not a valid number." } From eeb99cf37c821887543fa7abf3972006c28e06ca Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Tue, 18 Feb 2025 08:57:58 +0100 Subject: [PATCH 09/62] feat: add revisionSnapshotTimeIntervalTimeScale option to be used in the revision_snapshot_interval widget, when it is ported to use TimeSelector --- src/routes/api/options.ts | 1 + src/services/options_init.ts | 1 + src/services/options_interface.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 33cd5fa8c..29cea6200 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -15,6 +15,7 @@ const ALLOWED_OPTIONS = new Set([ "eraseEntitiesAfterTimeScale", "protectedSessionTimeout", "revisionSnapshotTimeInterval", + "revisionSnapshotTimeIntervalTimeScale", "revisionSnapshotNumberLimit", "zoomFactor", "theme", diff --git a/src/services/options_init.ts b/src/services/options_init.ts index 3bb87703e..16eb28795 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -75,6 +75,7 @@ async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = */ const defaultOptions: DefaultOption[] = [ { name: "revisionSnapshotTimeInterval", value: "600", isSynced: true }, + { name: "revisionSnapshotTimeIntervalTimeScale", value: "60", isSynced: true }, // default to Minutes { name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true }, { name: "protectedSessionTimeout", value: "600", isSynced: true }, { name: "zoomFactor", value: isWindows ? "0.9" : "1.0", isSynced: false }, diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts index fb234240c..c8a49518f 100644 --- a/src/services/options_interface.ts +++ b/src/services/options_interface.ts @@ -49,6 +49,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Tue, 18 Feb 2025 09:00:57 +0100 Subject: [PATCH 10/62] feat: use TimeSelector for revision_snapshot_interval added one TODO that needs to be handled in TimeSelector: minimum allowed value (!in seconds!) --- .../other/revisions_snapshot_interval.ts | 32 +++++++++---------- .../type_widgets/options/time_selector.ts | 1 + 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts b/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts index 751f70653..db095fa81 100644 --- a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts +++ b/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts @@ -1,30 +1,30 @@ -import OptionsWidget from "../options_widget.js"; import { t } from "../../../../services/i18n.js"; -import type { OptionMap } from "../../../../../../services/options_interface.js"; +import TimeSelector from "../time_selector.js"; const TPL = `

${t("revisions_snapshot_interval.note_revisions_snapshot_interval_title")}

- -
- - -
+
`; +//TriliumNextTODO: add support for setting minimum number of entered seconds -> snapshot revision should not be less than 10 seconds +export default class RevisionsSnapshotIntervalOptions extends TimeSelector { -export default class RevisionsSnapshotIntervalOptions extends OptionsWidget { - - private $revisionsTimeInterval!: JQuery; + constructor() { + super({ + widgetId: "revision-snapshot-time-interval", + widgetLabelId: "revisions_snapshot_interval.snapshot_time_interval_label", + optionValueId: "revisionSnapshotTimeInterval", + optionTimeScaleId: "revisionSnapshotTimeIntervalTimeScale" + }); + super.doRender(); + } doRender() { + const $timeSelector = this.$widget; + // inject TimeSelector widget template this.$widget = $(TPL); - this.$revisionsTimeInterval = this.$widget.find(".revision-snapshot-time-interval-in-seconds"); - this.$revisionsTimeInterval.on("change", () => this.updateOption("revisionSnapshotTimeInterval", this.$revisionsTimeInterval.val())); - } - - async optionsLoaded(options: OptionMap) { - this.$revisionsTimeInterval.val(options.revisionSnapshotTimeInterval); + this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector) } } diff --git a/src/public/app/widgets/type_widgets/options/time_selector.ts b/src/public/app/widgets/type_widgets/options/time_selector.ts index a3ec7c9bb..74c85faa7 100644 --- a/src/public/app/widgets/type_widgets/options/time_selector.ts +++ b/src/public/app/widgets/type_widgets/options/time_selector.ts @@ -36,6 +36,7 @@ const TPL = (options: Omit`; +//TriliumNextTODO: add support for setting minimum number of entered seconds export default class TimeSelector extends OptionsWidget { private $timeValueInput!: JQuery; private $timeScaleSelect!: JQuery; From 28148b32d23d66957d3669714681a1778e9e8877 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Tue, 18 Feb 2025 09:07:37 +0100 Subject: [PATCH 11/62] i18n: adjust labels for update revision_snapshot_interval options we don't do seconds only anymore, so strings needed adjusting --- src/public/translations/cn/translation.json | 6 +++--- src/public/translations/de/translation.json | 4 ++-- src/public/translations/en/translation.json | 4 ++-- src/public/translations/es/translation.json | 4 ++-- src/public/translations/fr/translation.json | 4 ++-- src/public/translations/ro/translation.json | 4 ++-- src/public/translations/tw/translation.json | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 7deb0d59c..de278739b 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1138,9 +1138,9 @@ "deleted_notes_erased": "已删除的笔记已被清理。" }, "revisions_snapshot_interval": { - "note_revisions_snapshot_interval_title": "笔记修改快照间隔", - "note_revisions_snapshot_description": "笔记修改快照时间间隔是指经过多少秒后会为笔记创建新的修改历史。更多信息请参见wiki。", - "snapshot_time_interval_label": "笔记修改快照时间间隔(单位:秒)" + "note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval", + "note_revisions_snapshot_description": "The Note revision snapshot interval is the time 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:" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "笔记历史快照限制", diff --git a/src/public/translations/de/translation.json b/src/public/translations/de/translation.json index c450085e3..a38c09fcd 100644 --- a/src/public/translations/de/translation.json +++ b/src/public/translations/de/translation.json @@ -1107,8 +1107,8 @@ }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Snapshot-Intervall für Notizrevisionen", - "note_revisions_snapshot_description": "Das Snapshot-Zeitintervall für Notizrevisionen ist die Zeit in Sekunden, nach der eine neue Notizrevision erstellt wird. Weitere Informationen findest du im Wiki.", - "snapshot_time_interval_label": "Zeitintervall für Notiz-Revisions-Snapshot (in Sekunden)" + "note_revisions_snapshot_description": "Das Snapshot-Zeitintervall für Notizrevisionen ist die Zeit, nach der eine neue Notizrevision erstellt wird. Weitere Informationen findest du im Wiki.", + "snapshot_time_interval_label": "Zeitintervall für Notiz-Revisions-Snapshot:" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "Limit für Notizrevision-Snapshots", diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 32474d5c1..1dc443200 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1165,8 +1165,8 @@ }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval", - "note_revisions_snapshot_description": "The Note revision snapshot interval is the 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):" + "note_revisions_snapshot_description": "The Note revision snapshot interval is the time 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:" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "Note Revision Snapshot Limit", diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index 3810997c3..1902ba01a 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -1163,8 +1163,8 @@ }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Intervalo de instantáneas de revisiones de notas", - "note_revisions_snapshot_description": "El intervalo de tiempo de la instantánea de revisión de nota es el tiempo en segundos después de lo cual se creará una nueva revisión para la nota. Ver wiki para obtener más información.", - "snapshot_time_interval_label": "Intervalo de tiempo de la instantánea de revisión de notas (en segundos)" + "note_revisions_snapshot_description": "El intervalo de tiempo de la instantánea de revisión de nota es el tiempo después de lo cual se creará una nueva revisión para la nota. Ver wiki para obtener más información.", + "snapshot_time_interval_label": "Intervalo de tiempo de la instantánea de revisión de notas:" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "Límite de respaldos de revisiones de nota", diff --git a/src/public/translations/fr/translation.json b/src/public/translations/fr/translation.json index 891760da8..bb99773c7 100644 --- a/src/public/translations/fr/translation.json +++ b/src/public/translations/fr/translation.json @@ -1108,8 +1108,8 @@ }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Intervalle d'enregistrement automatique des versions des notes", - "note_revisions_snapshot_description": "L'intervalle d'enregistrement automatique des versions de note est le temps en secondes après lequel une nouvelle version de note est créée pour une note. Consultez le wiki pour plus d'informations.", - "snapshot_time_interval_label": "Intervalle de temps entre deux enregistrements de version de note (en secondes) :" + "note_revisions_snapshot_description": "L'intervalle d'enregistrement automatique des versions de note est le temps après lequel une nouvelle version de note est créée pour une note. Consultez le wiki pour plus d'informations.", + "snapshot_time_interval_label": "Intervalle de temps entre deux enregistrements de version de note :" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "Limite des enregistrements de version de note", diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 2d22c2c3c..b0a3ccf40 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1068,9 +1068,9 @@ "note_revisions": "Revizii ale notiței" }, "revisions_snapshot_interval": { - "note_revisions_snapshot_description": "Intervalul de salvare a reviziilor este timpul în secunde după care se crează o nouă revizie a unei notițe. Vedeți wiki-ul pentru mai multe informații.", + "note_revisions_snapshot_description": "Intervalul de salvare a reviziilor este timpul după care se crează o nouă revizie a unei notițe. Vedeți wiki-ul pentru mai multe informații.", "note_revisions_snapshot_interval_title": "Intervalul de salvare a reviziilor", - "snapshot_time_interval_label": "Intervalul de salvare a reviziilor (în secunde)" + "snapshot_time_interval_label": "Intervalul de salvare a reviziilor:" }, "ribbon": { "edited_notes_message": "Tab-ul panglicii „Notițe editate” se va deschide automat pentru notițele zilnice", diff --git a/src/public/translations/tw/translation.json b/src/public/translations/tw/translation.json index 654033580..830c76167 100644 --- a/src/public/translations/tw/translation.json +++ b/src/public/translations/tw/translation.json @@ -1116,9 +1116,9 @@ "deleted_notes_erased": "已刪除的筆記已被清理。" }, "revisions_snapshot_interval": { - "note_revisions_snapshot_interval_title": "筆記修改快照間隔", - "note_revisions_snapshot_description": "筆記修改快照時間間隔是指經過多少秒後會為筆記新增新的修改歷史。更多資訊請參見wiki。", - "snapshot_time_interval_label": "筆記修改快照時間間隔(單位:秒)" + "note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval", + "note_revisions_snapshot_description": "The Note revision snapshot interval is the time 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:" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "筆記歷史快照限制", From bf41c54bd05f56643223b706c5ca4c44a2ff95bc Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Tue, 18 Feb 2025 22:32:43 +0100 Subject: [PATCH 12/62] feat: set TimeSelector minimumSeconds for revision_snapshot_interval and remove TODO --- .../options/other/revisions_snapshot_interval.ts | 5 +++-- src/public/app/widgets/type_widgets/options/time_selector.ts | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts b/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts index db095fa81..13c86fc88 100644 --- a/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts +++ b/src/public/app/widgets/type_widgets/options/other/revisions_snapshot_interval.ts @@ -8,7 +8,7 @@ const TPL = `
`; -//TriliumNextTODO: add support for setting minimum number of entered seconds -> snapshot revision should not be less than 10 seconds + export default class RevisionsSnapshotIntervalOptions extends TimeSelector { constructor() { @@ -16,7 +16,8 @@ export default class RevisionsSnapshotIntervalOptions extends TimeSelector { widgetId: "revision-snapshot-time-interval", widgetLabelId: "revisions_snapshot_interval.snapshot_time_interval_label", optionValueId: "revisionSnapshotTimeInterval", - optionTimeScaleId: "revisionSnapshotTimeIntervalTimeScale" + optionTimeScaleId: "revisionSnapshotTimeIntervalTimeScale", + minimumSeconds: 10 }); super.doRender(); } diff --git a/src/public/app/widgets/type_widgets/options/time_selector.ts b/src/public/app/widgets/type_widgets/options/time_selector.ts index 74c85faa7..a3ec7c9bb 100644 --- a/src/public/app/widgets/type_widgets/options/time_selector.ts +++ b/src/public/app/widgets/type_widgets/options/time_selector.ts @@ -36,7 +36,6 @@ const TPL = (options: Omit`; -//TriliumNextTODO: add support for setting minimum number of entered seconds export default class TimeSelector extends OptionsWidget { private $timeValueInput!: JQuery; private $timeScaleSelect!: JQuery; From 9a1d26e129a192c064a6209a3a0427263eeedcff Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Tue, 18 Feb 2025 09:20:57 +0100 Subject: [PATCH 13/62] feat: add protectedSessionTimeoutScale option to be used in the protected_session_timeout widget, when it is ported to use TimeSelector --- src/routes/api/options.ts | 1 + src/services/options_init.ts | 1 + src/services/options_interface.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 33cd5fa8c..5cb103a31 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -14,6 +14,7 @@ const ALLOWED_OPTIONS = new Set([ "eraseEntitiesAfterTimeInSeconds", "eraseEntitiesAfterTimeScale", "protectedSessionTimeout", + "protectedSessionTimeoutTimeScale", "revisionSnapshotTimeInterval", "revisionSnapshotNumberLimit", "zoomFactor", diff --git a/src/services/options_init.ts b/src/services/options_init.ts index 3bb87703e..394808c99 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -77,6 +77,7 @@ const defaultOptions: DefaultOption[] = [ { name: "revisionSnapshotTimeInterval", value: "600", isSynced: true }, { name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true }, { name: "protectedSessionTimeout", value: "600", isSynced: true }, + { name: "protectedSessionTimeoutTimeScale", value: "60", isSynced: true }, { name: "zoomFactor", value: isWindows ? "0.9" : "1.0", isSynced: false }, { name: "overrideThemeFonts", value: "false", isSynced: false }, { name: "mainFontFamily", value: "theme", isSynced: false }, diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts index fb234240c..c2da7e1b5 100644 --- a/src/services/options_interface.ts +++ b/src/services/options_interface.ts @@ -51,6 +51,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Tue, 18 Feb 2025 09:24:22 +0100 Subject: [PATCH 14/62] feat: use TimeSelector for protected_session_timeout to be used in the Password options --- .../password/protected_session_timeout.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts diff --git a/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts b/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts new file mode 100644 index 000000000..ec40dcfac --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts @@ -0,0 +1,30 @@ +import { t } from "../../../../services/i18n.js"; +import TimeSelector from "../time_selector.js"; + +const TPL = ` +
+

${t("password.protected_session_timeout")}

+ +

${t("password.protected_session_timeout_description")} ${t("password.wiki")} ${t("password.for_more_info")}

+
+
`; + +// TriliumNextTODO: set minimum value to 60 seconds +export default class ProtectedSessionTimeoutOption extends TimeSelector { + constructor() { + super({ + widgetId: "protected-session-timeout", + widgetLabelId: "password.protected_session_timeout_label", + optionValueId: "protectedSessionTimeout", + optionTimeScaleId: "protectedSessionTimeoutTimeScale" + }); + super.doRender(); + } + + doRender() { + const $timeSelector = this.$widget; + // inject TimeSelector widget template + this.$widget = $(TPL); + this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector) + } +} From 5a6c3ae4263e289af0f241c9ca06df8eeb3a6a1d Mon Sep 17 00:00:00 2001 From: Matt Wilkie Date: Wed, 19 Feb 2025 08:33:03 -0700 Subject: [PATCH 15/62] use the standard classes per @pano9000 advice, https://github.com/TriliumNext/Notes/pull/1207#issuecomment-2667896424 --- .../options/other/share_settings.ts | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index c4c9d9421..199605082 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -4,25 +4,20 @@ import { t } from "../../../../services/i18n.js"; import type { OptionMap, OptionNames } from "../../../../../../services/options_interface.js"; const TPL = ` -
+

${t("share.title")}

-
- -

${t("share.redirect_bare_domain_description")}

-
+ +

${t("share.redirect_bare_domain_description")}

-
- -

${t("share.show_login_link_description")}

-

 

-
+ +

${t("share.show_login_link_description")}

`; export default class ShareSettingsOptions extends OptionsWidget { From 7f173b287aa85ebb463a98e3aa5342fb0d096325 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 19 Feb 2025 22:21:49 +0100 Subject: [PATCH 16/62] feat(protected_session_timeout): add minimumSeconds --- .../options/password/protected_session_timeout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts b/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts index ec40dcfac..9608b14e7 100644 --- a/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts +++ b/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts @@ -9,14 +9,14 @@ const TPL = `
`; -// TriliumNextTODO: set minimum value to 60 seconds export default class ProtectedSessionTimeoutOption extends TimeSelector { constructor() { super({ widgetId: "protected-session-timeout", widgetLabelId: "password.protected_session_timeout_label", optionValueId: "protectedSessionTimeout", - optionTimeScaleId: "protectedSessionTimeoutTimeScale" + optionTimeScaleId: "protectedSessionTimeoutTimeScale", + minimumSeconds: 60 }); super.doRender(); } From 739eaf9fc073a5b398d9ad2c72f83597bd51fa20 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 19 Feb 2025 22:27:35 +0100 Subject: [PATCH 17/62] refactor(password): remove protected_session_timeout TPL element -> now part of protected_session_timeout widget --- .../app/widgets/type_widgets/options/password.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/password.ts b/src/public/app/widgets/type_widgets/options/password.ts index 9a838f9aa..d3800f85e 100644 --- a/src/public/app/widgets/type_widgets/options/password.ts +++ b/src/public/app/widgets/type_widgets/options/password.ts @@ -32,17 +32,7 @@ const TPL = ` - -
-

${t("password.protected_session_timeout")}

- -

${t("password.protected_session_timeout_description")} ${t("password.wiki")} ${t("password.for_more_info")}

- -
- - -
-
`; +`; // TODO: Deduplicate interface ChangePasswordResponse { From 1a80a379dc9625a4e25168c7cdd8270df8976350 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 19 Feb 2025 22:32:48 +0100 Subject: [PATCH 18/62] refactor(password): move password.ts to password subfolder --- .../app/widgets/type_widgets/content_widget.ts | 2 +- .../type_widgets/options/{ => password}/password.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) rename src/public/app/widgets/type_widgets/options/{ => password}/password.ts (92%) diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index 1f19e2b81..96252d251 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -14,7 +14,7 @@ import CodeAutoReadOnlySizeOptions from "./options/code_notes/code_auto_read_onl import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js"; import ImageOptions from "./options/images/images.js"; import SpellcheckOptions from "./options/spellcheck.js"; -import PasswordOptions from "./options/password.js"; +import PasswordOptions from "./options/password/password.js"; import EtapiOptions from "./options/etapi.js"; import BackupOptions from "./options/backup.js"; import SyncOptions from "./options/sync.js"; diff --git a/src/public/app/widgets/type_widgets/options/password.ts b/src/public/app/widgets/type_widgets/options/password/password.ts similarity index 92% rename from src/public/app/widgets/type_widgets/options/password.ts rename to src/public/app/widgets/type_widgets/options/password/password.ts index d3800f85e..3813d2aee 100644 --- a/src/public/app/widgets/type_widgets/options/password.ts +++ b/src/public/app/widgets/type_widgets/options/password/password.ts @@ -1,9 +1,9 @@ -import { t } from "../../../services/i18n.js"; -import server from "../../../services/server.js"; -import protectedSessionHolder from "../../../services/protected_session_holder.js"; -import toastService from "../../../services/toast.js"; -import OptionsWidget from "./options_widget.js"; -import type { OptionMap } from "../../../../../services/options_interface.js"; +import { t } from "../../../../services/i18n.js"; +import server from "../../../../services/server.js"; +import protectedSessionHolder from "../../../../services/protected_session_holder.js"; +import toastService from "../../../../services/toast.js"; +import OptionsWidget from "../options_widget.js"; +import type { OptionMap } from "../../../../../../services/options_interface.js"; const TPL = `
From cecde349b7cf3c9e7a92b46ea458280bdf833b1c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 19 Feb 2025 22:35:02 +0100 Subject: [PATCH 19/62] feat: add ProtectedSessionTimeoutOption to content_widget --- src/public/app/widgets/type_widgets/content_widget.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index 96252d251..d99865648 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -15,6 +15,7 @@ import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js"; import ImageOptions from "./options/images/images.js"; import SpellcheckOptions from "./options/spellcheck.js"; import PasswordOptions from "./options/password/password.js"; +import ProtectedSessionTimeoutOption from "./options/password/protected_session_timeout.js" import EtapiOptions from "./options/etapi.js"; import BackupOptions from "./options/backup.js"; import SyncOptions from "./options/sync.js"; From 19816493d6c2b69bd73bd94326b8f73a1c3a7545 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 19 Feb 2025 22:37:08 +0100 Subject: [PATCH 20/62] chore: rename ProtectedSessionTimeoutOption to plural matches the remaining Options widgets --- src/public/app/widgets/type_widgets/content_widget.ts | 4 ++-- .../options/password/protected_session_timeout.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/type_widgets/content_widget.ts b/src/public/app/widgets/type_widgets/content_widget.ts index d99865648..15a4d23fa 100644 --- a/src/public/app/widgets/type_widgets/content_widget.ts +++ b/src/public/app/widgets/type_widgets/content_widget.ts @@ -15,7 +15,7 @@ import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js"; import ImageOptions from "./options/images/images.js"; import SpellcheckOptions from "./options/spellcheck.js"; import PasswordOptions from "./options/password/password.js"; -import ProtectedSessionTimeoutOption from "./options/password/protected_session_timeout.js" +import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js" import EtapiOptions from "./options/etapi.js"; import BackupOptions from "./options/backup.js"; import SyncOptions from "./options/sync.js"; @@ -65,7 +65,7 @@ const CONTENT_WIDGETS: Record = { _optionsCodeNotes: [VimKeyBindingsOptions, WrapLinesOptions, CodeAutoReadOnlySizeOptions, CodeMimeTypesOptions], _optionsImages: [ImageOptions], _optionsSpellcheck: [SpellcheckOptions], - _optionsPassword: [PasswordOptions], + _optionsPassword: [PasswordOptions, ProtectedSessionTimeoutOptions], _optionsEtapi: [EtapiOptions], _optionsBackup: [BackupOptions], _optionsSync: [SyncOptions], diff --git a/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts b/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts index 9608b14e7..20d84a1ad 100644 --- a/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts +++ b/src/public/app/widgets/type_widgets/options/password/protected_session_timeout.ts @@ -9,7 +9,7 @@ const TPL = `
`; -export default class ProtectedSessionTimeoutOption extends TimeSelector { +export default class ProtectedSessionTimeoutOptions extends TimeSelector { constructor() { super({ widgetId: "protected-session-timeout", From bf6c5dfb202d5d58eb3968020103d42052e90ded Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 19 Feb 2025 22:45:07 +0100 Subject: [PATCH 21/62] i18n: adjust protected_session_timeout_label we are not limited to seconds now anymore (at least in the UI :-)) --- src/public/translations/cn/translation.json | 2 +- src/public/translations/de/translation.json | 2 +- src/public/translations/en/translation.json | 2 +- src/public/translations/es/translation.json | 2 +- src/public/translations/fr/translation.json | 2 +- src/public/translations/ro/translation.json | 2 +- src/public/translations/tw/translation.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 7deb0d59c..5c4085fdc 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1259,7 +1259,7 @@ "protected_session_timeout_description": "保护会话超时是一个时间段,超时后保护会话会从浏览器内存中清除。这是从最后一次与保护笔记的交互开始计时的。更多信息请见", "wiki": "维基", "for_more_info": "更多信息。", - "protected_session_timeout_label": "保护会话超时(秒)", + "protected_session_timeout_label": "Protected session timeout:", "reset_confirmation": "重置密码将永久丧失对所有现受保护笔记的访问。您真的要重置密码吗?", "reset_success_message": "密码已重置。请设置新密码", "change_password_heading": "更改密码", diff --git a/src/public/translations/de/translation.json b/src/public/translations/de/translation.json index c450085e3..bc37422a1 100644 --- a/src/public/translations/de/translation.json +++ b/src/public/translations/de/translation.json @@ -1225,7 +1225,7 @@ "protected_session_timeout_description": "Das Zeitlimit für geschützte Sitzungen ist ein Zeitraum, nach dem die geschützte Sitzung aus dem Speicher des Browsers gelöscht wird. Dies wird ab der letzten Interaktion mit geschützten Notizen gemessen. Sehen", "wiki": "Wiki", "for_more_info": "für weitere Informationen.", - "protected_session_timeout_label": "Zeitüberschreitung der geschützten Sitzung (in Sekunden)", + "protected_session_timeout_label": "Zeitüberschreitung der geschützten Sitzung:", "reset_confirmation": "Durch das Zurücksetzen des Passworts verlierst du für immer den Zugriff auf alle Ihre bestehenden geschützten Notizen. Möchtest du das Passwort wirklich zurücksetzen?", "reset_success_message": "Das Passwort wurde zurückgesetzt. Bitte lege ein neues Passwort fest", "change_password_heading": "Kennwort ändern", diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 32474d5c1..bf2b94a3e 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1285,7 +1285,7 @@ "protected_session_timeout_description": "Protected session timeout is a time period after which the protected session is wiped from the browser's memory. This is measured from the last interaction with protected notes. See", "wiki": "wiki", "for_more_info": "for more info.", - "protected_session_timeout_label": "Protected session timeout (in seconds)", + "protected_session_timeout_label": "Protected session timeout:", "reset_confirmation": "By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?", "reset_success_message": "Password has been reset. Please set new password", "change_password_heading": "Change Password", diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index 3810997c3..65c15ad88 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -1283,7 +1283,7 @@ "protected_session_timeout_description": "El tiempo de espera de la sesión protegida es el período de tiempo después del cual la sesión protegida se borra de la memoria del navegador. Esto se mide desde la última interacción con notas protegidas. Ver", "wiki": "wiki", "for_more_info": "para más información.", - "protected_session_timeout_label": "Tiempo de espera de sesión protegida (en segundos)", + "protected_session_timeout_label": "Tiempo de espera de sesión protegida:", "reset_confirmation": "Al restablecer la contraseña, perderá para siempre el acceso a todas sus notas protegidas existentes. ¿Realmente quieres restablecer la contraseña?", "reset_success_message": "La contraseña ha sido restablecida. Por favor establezca una nueva contraseña", "change_password_heading": "Cambiar contraseña", diff --git a/src/public/translations/fr/translation.json b/src/public/translations/fr/translation.json index 891760da8..8e136200a 100644 --- a/src/public/translations/fr/translation.json +++ b/src/public/translations/fr/translation.json @@ -1226,7 +1226,7 @@ "protected_session_timeout_description": "Le délai d'expiration de la session protégée est une période de temps après laquelle la session protégée est effacée de la mémoire du navigateur. Il est mesuré à partir de la dernière interaction avec des notes protégées. Voir", "wiki": "wiki", "for_more_info": "pour plus d'informations.", - "protected_session_timeout_label": "Délai d'expiration de la session protégée (en secondes)", + "protected_session_timeout_label": "Délai d'expiration de la session protégée :", "reset_confirmation": "En réinitialisant le mot de passe, vous perdrez à jamais l'accès à toutes vos notes protégées existantes. Voulez-vous vraiment réinitialiser le mot de passe ?", "reset_success_message": "Le mot de passe a été réinitialisé. Veuillez définir un nouveau mot de passe", "change_password_heading": "Changer le mot de passe", diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 2d22c2c3c..442507833 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -923,7 +923,7 @@ "password_mismatch": "Noile parole nu coincid.", "protected_session_timeout": "Timpul de expirare a sesiunii protejate", "protected_session_timeout_description": "Timpul de expirare a sesiunii protejate este o perioadă de timp după care sesiunea protejată este ștearsă din memoria navigatorului. Aceasta este măsurată de la timpul ultimei interacțiuni cu notițele protejate. Vezi", - "protected_session_timeout_label": "Timpul de expirare a sesiunii protejate (în secunde)", + "protected_session_timeout_label": "Timpul de expirare a sesiunii protejate:", "reset_confirmation": "Prin resetarea parolei se va pierde pentru totdeauna accesul la notițele protejate existente. Sigur doriți resetarea parolei?", "reset_link": "click aici pentru a o reseta.", "reset_success_message": "Parola a fost resetată. Setați o nouă parolă", diff --git a/src/public/translations/tw/translation.json b/src/public/translations/tw/translation.json index 654033580..cbf60c297 100644 --- a/src/public/translations/tw/translation.json +++ b/src/public/translations/tw/translation.json @@ -1237,7 +1237,7 @@ "protected_session_timeout_description": "保護會話超時是一個時間段,超時後保護會話會從瀏覽器內存中清除。這是從最後一次與保護筆記的交互開始計時的。更多資訊請見", "wiki": "維基", "for_more_info": "更多資訊。", - "protected_session_timeout_label": "保護會話超時(秒)", + "protected_session_timeout_label": "Protected session timeout:", "reset_confirmation": "重置密碼將永久喪失對所有現受保護筆記的訪問。您真的要重置密碼嗎?", "reset_success_message": "密碼已重置。請設定新密碼", "change_password_heading": "更改密碼", From 2734e230ab07fcc802ca988ff92094ff4abf4f69 Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Thu, 20 Feb 2025 08:12:51 -0700 Subject: [PATCH 22/62] WIP: 1st step at verifying shareRoot is set --- .../options/other/share_settings.ts | 71 +++++++++++++++---- src/public/translations/en/translation.json | 5 +- src/services/auth.ts | 11 ++- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index 199605082..abc540b49 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -2,44 +2,91 @@ import OptionsWidget from "../options_widget.js"; import options from "../../../../services/options.js"; import { t } from "../../../../services/i18n.js"; import type { OptionMap, OptionNames } from "../../../../../../services/options_interface.js"; +import searchService from "../../../../services/search.js"; const TPL = `
-

${t("share.title")}

-

${t("share.redirect_bare_domain_description")}

+ + -

${t("share.show_login_link_description")}

`; export default class ShareSettingsOptions extends OptionsWidget { + private $shareRootCheck!: JQuery; + private $shareRootStatus!: JQuery; + doRender() { this.$widget = $(TPL); this.contentSized(); + this.$shareRootCheck = this.$widget.find('.share-root-check'); + this.$shareRootStatus = this.$widget.find('.share-root-status'); + // Add change handlers for both checkboxes - this.$widget.find('input[type="checkbox"]').on("change", () => this.save()); + this.$widget.find('input[type="checkbox"]').on("change", (e: JQuery.ChangeEvent) => { + this.save(); + + // Show/hide share root status section based on redirectBareDomain checkbox + const target = e.target as HTMLInputElement; + if (target.name === 'redirectBareDomain') { + this.$shareRootCheck.toggle(target.checked); + if (target.checked) { + this.checkShareRoot(); + } + } + }); + + // Add click handler for check share root button + this.$widget.find('.check-share-root').on("click", () => this.checkShareRoot()); } async optionsLoaded(options: OptionMap) { - this.$widget.find('input[name="redirectBareDomain"]').prop("checked", options.redirectBareDomain === "true"); + const redirectBareDomain = options.redirectBareDomain === "true"; + this.$widget.find('input[name="redirectBareDomain"]').prop("checked", redirectBareDomain); + this.$shareRootCheck.toggle(redirectBareDomain); + if (redirectBareDomain) { + await this.checkShareRoot(); + } - this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked", options.showLoginInShareTheme === "true"); + this.$widget.find('input[name="shareSubtree"]').prop("checked", options.shareSubtree === "true"); + } + + async checkShareRoot() { + const shareRootNotes = await searchService.searchNotes("#shareRoot", { + includeArchivedNotes: true, + ignoreHoistedNote: true + }); + + if (shareRootNotes.length > 0) { + this.$shareRootStatus + .removeClass('text-danger') + .addClass('text-success') + .text(t("share.share_root_found", {noteTitle: shareRootNotes[0].title})); + } else { + this.$shareRootStatus + .removeClass('text-success') + .addClass('text-danger') + .text(t("share.share_root_not_found")); + } } async save() { const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop("checked"); await this.updateOption<"redirectBareDomain">("redirectBareDomain", redirectBareDomain.toString()); - const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked"); - await this.updateOption<"showLoginInShareTheme">("showLoginInShareTheme", showLoginInShareTheme.toString()); + const showLoginInShareTheme = this.$widget.find('input[name="shareSubtree"]').prop("checked"); + await this.updateOption<"shareSubtree">("shareSubtree", showLoginInShareTheme.toString()); } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index f2978237d..c0ee99e4b 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1660,7 +1660,10 @@ "redirect_bare_domain": "Redirect bare domain to Share page", "redirect_bare_domain_description": "When enabled, accessing the root URL will redirect to the Share page instead of Login", "show_login_link": "Show Login link in Share theme", - "show_login_link_description": "Add a login link to the Share page footer" + "show_login_link_description": "Add a login link to the Share page footer", + "check_share_root": "Check Share Root Status", + "share_root_found": "Share root found: {{noteTitle}}", + "share_root_not_found": "No note with #shareRoot label found. Set up a note with #shareRoot label first." }, "time_selector": { "invalid_input": "The entered time value is not a valid number." diff --git a/src/services/auth.ts b/src/services/auth.ts index 68c497b91..03f40e6e7 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -8,6 +8,7 @@ import passwordEncryptionService from "./encryption/password_encryption.js"; import config from "./config.js"; import passwordService from "./encryption/password.js"; import options from "./options.js"; +import attributes from "./attributes.js"; import type { NextFunction, Request, Response } from "express"; const noAuthentication = config.General && config.General.noAuthentication === true; @@ -16,7 +17,15 @@ function checkAuth(req: Request, res: Response, next: NextFunction) { if (!sqlInit.isDbInitialized()) { res.redirect("setup"); } else if (!req.session.loggedIn && !isElectron && !noAuthentication) { - const redirectToShare = options.getOption("redirectBareDomain") === "true"; + const redirectToShare = options.getOptionBool("redirectBareDomain"); + if (redirectToShare) { + // Check if any note has the #shareRoot label + const shareRootNotes = attributes.getNotesWithLabel("shareRoot"); + if (shareRootNotes.length === 0) { + res.status(404).json({ message: "Share root not found. Please set up a note with #shareRoot label first." }); + return; + } + } res.redirect(redirectToShare ? "share" : "login"); } else { next(); From bdd6395a762f29b46e3fecadb5c7783af57e132a Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Thu, 20 Feb 2025 09:08:24 -0700 Subject: [PATCH 23/62] works! verify shareRoot is set and note is shared --- .../options/other/share_settings.ts | 47 +++++++++++-------- src/public/translations/en/translation.json | 7 +-- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index abc540b49..d2d77bc6d 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -6,11 +6,11 @@ import searchService from "../../../../services/search.js"; const TPL = `
+

${t("share.redirect_bare_domain_description")}

-

${t("share.redirect_bare_domain_description")}

`; @@ -60,25 +60,32 @@ export default class ShareSettingsOptions extends OptionsWidget { await this.checkShareRoot(); } - this.$widget.find('input[name="shareSubtree"]').prop("checked", options.shareSubtree === "true"); + this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked", options.showLoginInShareTheme === "true"); } async checkShareRoot() { - const shareRootNotes = await searchService.searchNotes("#shareRoot", { - includeArchivedNotes: true, - ignoreHoistedNote: true - }); + const $button = this.$widget.find('.check-share-root'); + $button.prop('disabled', true); + + try { + const shareRootNotes = await searchService.searchForNotes("#shareRoot"); + const sharedShareRootNote = shareRootNotes.find(note => note.isShared()); - if (shareRootNotes.length > 0) { - this.$shareRootStatus - .removeClass('text-danger') - .addClass('text-success') - .text(t("share.share_root_found", {noteTitle: shareRootNotes[0].title})); - } else { - this.$shareRootStatus - .removeClass('text-success') - .addClass('text-danger') - .text(t("share.share_root_not_found")); + if (sharedShareRootNote) { + this.$shareRootStatus + .removeClass('text-danger') + .addClass('text-success') + .text(t("share.share_root_found", {noteTitle: sharedShareRootNote.title})); + } else { + this.$shareRootStatus + .removeClass('text-success') + .addClass('text-danger') + .text(shareRootNotes.length > 0 + ? t("share.share_root_not_shared", {noteTitle: shareRootNotes[0].title}) + : t("share.share_root_not_found")); + } + } finally { + $button.prop('disabled', false); } } @@ -86,7 +93,7 @@ export default class ShareSettingsOptions extends OptionsWidget { const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop("checked"); await this.updateOption<"redirectBareDomain">("redirectBareDomain", redirectBareDomain.toString()); - const showLoginInShareTheme = this.$widget.find('input[name="shareSubtree"]').prop("checked"); - await this.updateOption<"shareSubtree">("shareSubtree", showLoginInShareTheme.toString()); + const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked"); + await this.updateOption<"showLoginInShareTheme">("showLoginInShareTheme", showLoginInShareTheme.toString()); } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index c0ee99e4b..506952623 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1658,12 +1658,13 @@ "share": { "title": "Share Settings", "redirect_bare_domain": "Redirect bare domain to Share page", - "redirect_bare_domain_description": "When enabled, accessing the root URL will redirect to the Share page instead of Login", + "redirect_bare_domain_description": "Redirect anonymous users to the Share page instead of showing Login", "show_login_link": "Show Login link in Share theme", "show_login_link_description": "Add a login link to the Share page footer", "check_share_root": "Check Share Root Status", - "share_root_found": "Share root found: {{noteTitle}}", - "share_root_not_found": "No note with #shareRoot label found. Set up a note with #shareRoot label first." + "share_root_found": "Share root note '{{noteTitle}}' is ready", + "share_root_not_found": "No note with #shareRoot label found", + "share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not Shared" }, "time_selector": { "invalid_input": "The entered time value is not a valid number." From 16b16927efda18a67e218431ab6fe6e2060000c4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 20 Feb 2025 22:08:04 +0200 Subject: [PATCH 24/62] feat(settings/share): add title to section --- .../widgets/type_widgets/options/other/share_settings.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/other/share_settings.ts b/src/public/app/widgets/type_widgets/options/other/share_settings.ts index d2d77bc6d..52d41552c 100644 --- a/src/public/app/widgets/type_widgets/options/other/share_settings.ts +++ b/src/public/app/widgets/type_widgets/options/other/share_settings.ts @@ -6,6 +6,8 @@ import searchService from "../../../../services/search.js"; const TPL = `
+

${t("share.title")}

+

${t("share.redirect_bare_domain_description")}