mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Merge pull request #1207 from maphew/bare2share2
Bare2share - Redirect bare domain to defined #shareRoot
This commit is contained in:
		| @@ -36,6 +36,7 @@ import RibbonOptions from "./options/appearance/ribbon.js"; | |||||||
| import LocalizationOptions from "./options/appearance/i18n.js"; | import LocalizationOptions from "./options/appearance/i18n.js"; | ||||||
| import CodeBlockOptions from "./options/appearance/code_block.js"; | import CodeBlockOptions from "./options/appearance/code_block.js"; | ||||||
| import EditorOptions from "./options/text_notes/editor.js"; | import EditorOptions from "./options/text_notes/editor.js"; | ||||||
|  | import ShareSettingsOptions from "./options/other/share_settings.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import type NoteContextAwareWidget from "../note_context_aware_widget.js"; | import type NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||||
|  |  | ||||||
| @@ -77,14 +78,14 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = { | |||||||
|         RevisionsSnapshotIntervalOptions, |         RevisionsSnapshotIntervalOptions, | ||||||
|         RevisionSnapshotsLimitOptions, |         RevisionSnapshotsLimitOptions, | ||||||
|         NetworkConnectionsOptions, |         NetworkConnectionsOptions, | ||||||
|         HtmlImportTagsOptions |         HtmlImportTagsOptions, | ||||||
|  |         ShareSettingsOptions | ||||||
|     ], |     ], | ||||||
|     _optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions], |     _optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions], | ||||||
|     _backendLog: [BackendLogWidget] |     _backendLog: [BackendLogWidget] | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default class ContentWidgetTypeWidget extends TypeWidget { | export default class ContentWidgetTypeWidget extends TypeWidget { | ||||||
|  |  | ||||||
|     private $content!: JQuery<HTMLElement>; |     private $content!: JQuery<HTMLElement>; | ||||||
|  |  | ||||||
|     static getType() { |     static getType() { | ||||||
|   | |||||||
| @@ -0,0 +1,97 @@ | |||||||
|  | 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 = ` | ||||||
|  | <div class="options-section"> | ||||||
|  |     <h4>${t("share.title")}</h4> | ||||||
|  |  | ||||||
|  |     <label class="tn-checkbox"> | ||||||
|  |         <input class="form-check-input" type="checkbox" name="redirectBareDomain" value="true"> | ||||||
|  |         ${t("share.redirect_bare_domain")} | ||||||
|  |     </label> | ||||||
|  |     <p class="form-text">${t("share.redirect_bare_domain_description")}</p> | ||||||
|  |  | ||||||
|  |     <label class="tn-checkbox"> | ||||||
|  |         <input class="form-check-input" type="checkbox" name="showLoginInShareTheme" value="true"> | ||||||
|  |         ${t("share.show_login_link")} | ||||||
|  |     </label> | ||||||
|  |     <p class="form-text">${t("share.show_login_link_description")}</p> | ||||||
|  | </div>`; | ||||||
|  |  | ||||||
|  | export default class ShareSettingsOptions extends OptionsWidget { | ||||||
|  |     private $shareRootCheck!: JQuery<HTMLElement>; | ||||||
|  |     private $shareRootStatus!: JQuery<HTMLElement>; | ||||||
|  |  | ||||||
|  |     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", (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) { | ||||||
|  |         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"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async checkShareRoot() { | ||||||
|  |         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 (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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -199,6 +199,11 @@ input::selection, | |||||||
|     color: var(--input-selection-text-color); |     color: var(--input-selection-text-color); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .form-text { | ||||||
|  |     color: var(--main-text-color); | ||||||
|  |     opacity: 0.8; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Input groups */ | /* Input groups */ | ||||||
|  |  | ||||||
| .input-group { | .input-group { | ||||||
|   | |||||||
| @@ -1656,6 +1656,17 @@ | |||||||
|     "hours": "Hours", |     "hours": "Hours", | ||||||
|     "days": "Days" |     "days": "Days" | ||||||
|   }, |   }, | ||||||
|  |   "share": { | ||||||
|  |     "title": "Share Settings", | ||||||
|  |     "redirect_bare_domain": "Redirect bare domain to Share page", | ||||||
|  |     "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 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": { |   "time_selector": { | ||||||
|     "invalid_input": "The entered time value is not a valid number.", |     "invalid_input": "The entered time value is not a valid number.", | ||||||
|     "minimum_input": "The entered time value needs to be at least {{minimumSeconds}} seconds." |     "minimum_input": "The entered time value needs to be at least {{minimumSeconds}} seconds." | ||||||
|   | |||||||
| @@ -75,7 +75,9 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     "textNoteEditorMultilineToolbar", |     "textNoteEditorMultilineToolbar", | ||||||
|     "layoutOrientation", |     "layoutOrientation", | ||||||
|     "backgroundEffects", |     "backgroundEffects", | ||||||
|     "allowedHtmlTags" // Allow configuring HTML import tags |     "allowedHtmlTags", | ||||||
|  |     "redirectBareDomain", | ||||||
|  |     "showLoginInShareTheme" | ||||||
| ]); | ]); | ||||||
|  |  | ||||||
| function getOptions() { | function getOptions() { | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ import { isElectron } from "./utils.js"; | |||||||
| import passwordEncryptionService from "./encryption/password_encryption.js"; | import passwordEncryptionService from "./encryption/password_encryption.js"; | ||||||
| import config from "./config.js"; | import config from "./config.js"; | ||||||
| import passwordService from "./encryption/password.js"; | import passwordService from "./encryption/password.js"; | ||||||
|  | import options from "./options.js"; | ||||||
|  | import attributes from "./attributes.js"; | ||||||
| import type { NextFunction, Request, Response } from "express"; | import type { NextFunction, Request, Response } from "express"; | ||||||
|  |  | ||||||
| const noAuthentication = config.General && config.General.noAuthentication === true; | const noAuthentication = config.General && config.General.noAuthentication === true; | ||||||
| @@ -15,7 +17,16 @@ function checkAuth(req: Request, res: Response, next: NextFunction) { | |||||||
|     if (!sqlInit.isDbInitialized()) { |     if (!sqlInit.isDbInitialized()) { | ||||||
|         res.redirect("setup"); |         res.redirect("setup"); | ||||||
|     } else if (!req.session.loggedIn && !isElectron && !noAuthentication) { |     } else if (!req.session.loggedIn && !isElectron && !noAuthentication) { | ||||||
|         res.redirect("login"); |         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 { |     } else { | ||||||
|         next(); |         next(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -255,7 +255,11 @@ const defaultOptions: DefaultOption[] = [ | |||||||
|             "tt" |             "tt" | ||||||
|         ]), |         ]), | ||||||
|         isSynced: true |         isSynced: true | ||||||
|     } |     }, | ||||||
|  |  | ||||||
|  |     // Share settings | ||||||
|  |     { name: "redirectBareDomain", value: "false", isSynced: true }, | ||||||
|  |     { name: "showLoginInShareTheme", value: "false", isSynced: true } | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -97,6 +97,10 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | |||||||
|     codeBlockWordWrap: boolean; |     codeBlockWordWrap: boolean; | ||||||
|     textNoteEditorMultilineToolbar: boolean; |     textNoteEditorMultilineToolbar: boolean; | ||||||
|     backgroundEffects: boolean; |     backgroundEffects: boolean; | ||||||
|  |     // Share settings | ||||||
|  |     redirectBareDomain: boolean; | ||||||
|  |     showLoginInShareTheme: boolean; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export type OptionNames = keyof OptionDefinitions; | export type OptionNames = keyof OptionDefinitions; | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import type SNote from "./shaca/entities/snote.js"; | |||||||
| import type SBranch from "./shaca/entities/sbranch.js"; | import type SBranch from "./shaca/entities/sbranch.js"; | ||||||
| import type SAttachment from "./shaca/entities/sattachment.js"; | import type SAttachment from "./shaca/entities/sattachment.js"; | ||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
|  | import options from "../services/options.js"; | ||||||
|  |  | ||||||
| function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { | function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { | ||||||
|     if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { |     if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { | ||||||
| @@ -151,7 +152,8 @@ function register(router: Router) { | |||||||
|  |  | ||||||
|         const { header, content, isEmpty } = contentRenderer.getContent(note); |         const { header, content, isEmpty } = contentRenderer.getContent(note); | ||||||
|         const subRoot = getSharedSubTreeRoot(note); |         const subRoot = getSharedSubTreeRoot(note); | ||||||
|         const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath }; |         const showLoginInShareTheme = options.getOption("showLoginInShareTheme"); | ||||||
|  |         const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath, showLoginInShareTheme }; | ||||||
|         let useDefaultView = true; |         let useDefaultView = true; | ||||||
|  |  | ||||||
|         // Check if the user has their own template |         // Check if the user has their own template | ||||||
|   | |||||||
| @@ -88,5 +88,10 @@ | |||||||
|         </nav> |         </nav> | ||||||
|     <% } %> |     <% } %> | ||||||
| </div> | </div> | ||||||
|  | <footer> | ||||||
|  |     <% if (showLoginInShareTheme === 'true') { %> | ||||||
|  |         <p><a href="/login" class="login-link">Login</a></p> | ||||||
|  |     <% } %> | ||||||
|  | </footer> | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user