mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	refactored access to options on frontend
This commit is contained in:
		
							
								
								
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -2157,9 +2157,9 @@ | ||||
|       "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" | ||||
|     }, | ||||
|     "dayjs": { | ||||
|       "version": "1.8.19", | ||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.19.tgz", | ||||
|       "integrity": "sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg==" | ||||
|       "version": "1.8.20", | ||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.20.tgz", | ||||
|       "integrity": "sha512-mH0MCDxw6UCGJYxVN78h8ugWycZAO8thkj3bW6vApL5tS0hQplIDdAQcmbvl7n35H0AKdCJQaArTrIQw2xt4Qg==" | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "4.1.1", | ||||
| @@ -3782,9 +3782,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "file-type": { | ||||
|       "version": "14.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/file-type/-/file-type-14.0.0.tgz", | ||||
|       "integrity": "sha512-+gxNvurlwHfTohZC6gqf0ybMl+cXYB9f1x++kw9AgKItdFx20J0fV9wCVR38a5/jphL5EUcusJ9tLYkPRtGHaw==", | ||||
|       "version": "14.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/file-type/-/file-type-14.1.0.tgz", | ||||
|       "integrity": "sha512-HfxnzrPH+LLClSAsno88/0frRtamu1XfqEP4IP/8RqBmqQnBQkemv3Udde0t53wZmrdOtc70aaR9WUHyQhjCUQ==", | ||||
|       "requires": { | ||||
|         "readable-web-to-node-stream": "^2.0.0", | ||||
|         "strtok3": "^6.0.0", | ||||
| @@ -6859,9 +6859,9 @@ | ||||
|       "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" | ||||
|     }, | ||||
|     "node-abi": { | ||||
|       "version": "2.13.0", | ||||
|       "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.13.0.tgz", | ||||
|       "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", | ||||
|       "version": "2.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.14.0.tgz", | ||||
|       "integrity": "sha512-y54KGgEOHnRHlGQi7E5UiryRkH8bmksmQLj/9iLAjoje743YS+KaKB/sDYXgqtT0J16JT3c3AYJZNI98aU/kYg==", | ||||
|       "requires": { | ||||
|         "semver": "^5.4.1" | ||||
|       }, | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
|     "electron-window-state": "5.0.3", | ||||
|     "express": "4.17.1", | ||||
|     "express-session": "1.17.0", | ||||
|     "file-type": "14.0.0", | ||||
|     "file-type": "14.1.0", | ||||
|     "fs-extra": "8.1.0", | ||||
|     "helmet": "3.21.2", | ||||
|     "html": "1.0.0", | ||||
| @@ -53,7 +53,7 @@ | ||||
|     "jimp": "0.9.3", | ||||
|     "mime-types": "2.1.26", | ||||
|     "multer": "1.4.2", | ||||
|     "node-abi": "2.13.0", | ||||
|     "node-abi": "2.14.0", | ||||
|     "open": "7.0.2", | ||||
|     "pngjs": "3.4.0", | ||||
|     "portscanner": "2.2.0", | ||||
|   | ||||
| @@ -28,7 +28,7 @@ import dateNoteService from './services/date_notes.js'; | ||||
| import importService from './services/import.js'; | ||||
| import keyboardActionService from "./services/keyboard_actions.js"; | ||||
| import splitService from "./services/split.js"; | ||||
| import optionService from "./services/options.js"; | ||||
| import options from "./services/options.js"; | ||||
| import noteContentRenderer from "./services/note_content_renderer.js"; | ||||
| import appContext from "./services/app_context.js"; | ||||
|  | ||||
| @@ -140,42 +140,3 @@ appContext.start(); | ||||
| noteTooltipService.setupGlobalTooltip(); | ||||
|  | ||||
| noteAutocompleteService.init(); | ||||
|  | ||||
| if (utils.isElectron()) { | ||||
|     import("./services/spell_check.js").then(spellCheckService => spellCheckService.initSpellCheck()); | ||||
| } | ||||
|  | ||||
| optionService.waitForOptions().then(options => { | ||||
|     toggleSidebar('left', options.is('leftPaneVisible')); | ||||
|     toggleSidebar('right', options.is('rightPaneVisible')); | ||||
|  | ||||
|     splitService.setupSplit(paneVisible.left, paneVisible.right); | ||||
| }); | ||||
|  | ||||
| const paneVisible = {}; | ||||
|  | ||||
| function toggleSidebar(side, show) { | ||||
|     $(`#${side}-pane`).toggle(show); | ||||
|     $(`#show-${side}-pane-button`).toggle(!show); | ||||
|     $(`#hide-${side}-pane-button`).toggle(show); | ||||
|  | ||||
|     paneVisible[side] = show; | ||||
| } | ||||
|  | ||||
| async function toggleAndSave(side, show) { | ||||
|     toggleSidebar(side, show); | ||||
|  | ||||
|     await server.put(`options/${side}PaneVisible/` + show.toString()); | ||||
|  | ||||
|     await optionService.reloadOptions(); | ||||
|  | ||||
|     splitService.setupSplit(paneVisible.left, paneVisible.right); | ||||
|  | ||||
|     appContext.trigger('sidebarVisibilityChanged', {side, show}); | ||||
| } | ||||
|  | ||||
| $("#show-right-pane-button").on('click', () => toggleAndSave('right', true)); | ||||
| $("#hide-right-pane-button").on('click', () => toggleAndSave('right', false)); | ||||
|  | ||||
| $("#show-left-pane-button").on('click', () => toggleAndSave('left', true)); | ||||
| $("#hide-left-pane-button").on('click', () => toggleAndSave('left', false)); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import utils from "../../services/utils.js"; | ||||
| import cssLoader from "../../services/css_loader.js"; | ||||
| import zoomService from "../../services/zoom.js"; | ||||
| import optionsService from "../../services/options.js"; | ||||
| import appContext from "../../services/app_context.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <p><strong>Settings on this options tab are saved automatically after each change.</strong></p> | ||||
| @@ -107,7 +108,7 @@ export default class ApperanceOptions { | ||||
|             server.put('options/theme/' + newTheme); | ||||
|         }); | ||||
|  | ||||
|         this.$zoomFactorSelect.on('change', () => { zoomService.setZoomFactorAndSave(this.$zoomFactorSelect.val()); }); | ||||
|         this.$zoomFactorSelect.on('change', () => { appContext.trigger('setZoomFactorAndSave', {zoomFactor: this.$zoomFactorSelect.val()}); }); | ||||
|  | ||||
|         this.$nativeTitleBarSelect.on('change', () => { | ||||
|             const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false'; | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import server from "../../services/server.js"; | ||||
| import mimeTypesService from "../../services/mime_types.js"; | ||||
| import optionsService from "../../services/options.js"; | ||||
| import options from "../../services/options.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <h4>Available MIME types in the dropdown</h4> | ||||
| @@ -14,7 +13,7 @@ export default class CodeNotesOptions { | ||||
|         this.$mimeTypes = $("#options-mime-types"); | ||||
|     } | ||||
|  | ||||
|     async optionsLoaded(options) { | ||||
|     async optionsLoaded() { | ||||
|         this.$mimeTypes.empty(); | ||||
|  | ||||
|         let idCtr = 1; | ||||
| @@ -42,10 +41,8 @@ export default class CodeNotesOptions { | ||||
|         this.$mimeTypes.find("input:checked").each( | ||||
|             (i, el) => enabledMimeTypes.push($(el).attr("data-mime-type"))); | ||||
|  | ||||
|         const opts = { codeNotesMimeTypes: JSON.stringify(enabledMimeTypes) }; | ||||
|         await options.save('codeNotesMimeTypes', JSON.stringify(enabledMimeTypes)); | ||||
|  | ||||
|         await server.put('options', opts); | ||||
|  | ||||
|         await optionsService.reloadOptions(); | ||||
|         mimeTypesService.loadMimeTypes(); | ||||
|     } | ||||
| } | ||||
| @@ -101,8 +101,6 @@ export default class ProtectedSessionOptions { | ||||
|             const eraseNotesAfterTimeInSeconds = this.$eraseNotesAfterTimeInSeconds.val(); | ||||
|  | ||||
|             server.put('options', { 'eraseNotesAfterTimeInSeconds': eraseNotesAfterTimeInSeconds }).then(() => { | ||||
|                 optionsService.reloadOptions(); | ||||
|  | ||||
|                 toastService.showMessage("Options change have been saved."); | ||||
|             }); | ||||
|  | ||||
| @@ -115,8 +113,6 @@ export default class ProtectedSessionOptions { | ||||
|             const protectedSessionTimeout = this.$protectedSessionTimeout.val(); | ||||
|  | ||||
|             server.put('options', { 'protectedSessionTimeout': protectedSessionTimeout }).then(() => { | ||||
|                 optionsService.reloadOptions(); | ||||
|  | ||||
|                 toastService.showMessage("Options change have been saved."); | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -104,8 +104,6 @@ export default class SidebarOptions { | ||||
|         }); | ||||
|  | ||||
|         await server.put('options', opts); | ||||
|  | ||||
|         optionsService.reloadOptions(); | ||||
|     } | ||||
|  | ||||
|     parseJsonSafely(str) { | ||||
|   | ||||
| @@ -29,10 +29,12 @@ import bundleService from "./bundle.js"; | ||||
| import DialogEventComponent from "./dialog_events.js"; | ||||
| import Entrypoints from "./entrypoints.js"; | ||||
| import CalendarWidget from "../widgets/calendar.js"; | ||||
| import optionsService from "./options.js"; | ||||
| import options from "./options.js"; | ||||
| import utils from "./utils.js"; | ||||
| import treeService from "./tree.js"; | ||||
| import SidePaneContainer from "../widgets/side_pane_container.js"; | ||||
| import ZoomService from "./zoom.js"; | ||||
| import SidebarToggle from "../widgets/sidebar_toggle.js"; | ||||
|  | ||||
| class AppContext { | ||||
|     constructor() { | ||||
| @@ -45,7 +47,9 @@ class AppContext { | ||||
|         this.activeTabId = null; | ||||
|     } | ||||
|  | ||||
|     start() { | ||||
|     async start() { | ||||
|         options.load(await server.get('options')); | ||||
|  | ||||
|         this.showWidgets(); | ||||
|  | ||||
|         this.loadTabs(); | ||||
| @@ -54,8 +58,6 @@ class AppContext { | ||||
|     } | ||||
|  | ||||
|     async loadTabs() { | ||||
|         const options = await optionsService.waitForOptions(); | ||||
|  | ||||
|         const openTabs = options.getJson('openTabs') || []; | ||||
|  | ||||
|         await treeCache.initializedPromise; | ||||
| @@ -186,14 +188,25 @@ class AppContext { | ||||
|  | ||||
|         $centerPane.after(rightPaneContainer.render()); | ||||
|  | ||||
|         const sidebarToggleWidget = new SidebarToggle(this); | ||||
|  | ||||
|         $centerPane.after(sidebarToggleWidget.render()); | ||||
|  | ||||
|         this.components = [ | ||||
|             new Entrypoints(), | ||||
|             new DialogEventComponent(this), | ||||
|             ...topPaneWidgets, | ||||
|             leftPaneContainer, | ||||
|             ...centerPaneWidgets, | ||||
|             rightPaneContainer | ||||
|             rightPaneContainer, | ||||
|             sidebarToggleWidget | ||||
|         ]; | ||||
|  | ||||
|         if (utils.isElectron()) { | ||||
|             this.components.push(new ZoomService(this)); | ||||
|  | ||||
|             import("./spell_check.js").then(spellCheckService => spellCheckService.initSpellCheck()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     trigger(name, data, sync = false) { | ||||
|   | ||||
| @@ -70,7 +70,7 @@ function copy(nodes) { | ||||
|  | ||||
| function cut(nodes) { | ||||
|     clipboardBranchIds = nodes | ||||
|         .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) | ||||
|         .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteId()) | ||||
|         .filter(node => node.getParent().data.noteType !== 'search') | ||||
|         .map(node => node.key); | ||||
|  | ||||
|   | ||||
| @@ -57,13 +57,7 @@ export default class Entrypoints extends Component { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     zoomOutListener() { | ||||
|         zoomService.decreaseZoomFactor(); | ||||
|     } | ||||
|  | ||||
|     zoomInListener() { | ||||
|         zoomService.increaseZoomFactor(); | ||||
|     } | ||||
|  | ||||
|     async createNoteIntoDayNoteListener() { | ||||
|         const todayNote = await dateNoteService.getTodayNote(); | ||||
|   | ||||
| @@ -1,30 +1,16 @@ | ||||
| import optionsService from './options.js'; | ||||
| import server from "./server.js"; | ||||
| import options from './options.js'; | ||||
| import appContext from "./app_context.js"; | ||||
| import treeService from "./tree.js"; | ||||
|  | ||||
| let hoistedNoteId = 'root'; | ||||
|  | ||||
| optionsService.waitForOptions().then(options => { | ||||
|     hoistedNoteId = options.get('hoistedNoteId'); | ||||
| }); | ||||
|  | ||||
| function getHoistedNoteNoPromise() { | ||||
|     return hoistedNoteId; | ||||
| } | ||||
|  | ||||
| async function getHoistedNoteId() { | ||||
|     await optionsService.waitForOptions(); | ||||
|  | ||||
|     return hoistedNoteId; | ||||
| function getHoistedNoteId() { | ||||
|     return options.get('hoistedNoteId'); | ||||
| } | ||||
|  | ||||
| async function setHoistedNoteId(noteId) { | ||||
|     hoistedNoteId = noteId; | ||||
|     await options.save('hoistedNoteId', noteId); | ||||
|  | ||||
|     await server.put('options/hoistedNoteId/' + noteId); | ||||
|  | ||||
|     appContext.trigger('hoistedNoteChanged', {hoistedNoteId}); | ||||
|     // FIXME - just use option load event | ||||
|     appContext.trigger('hoistedNoteChanged', {noteId}); | ||||
| } | ||||
|  | ||||
| async function unhoist() { | ||||
| @@ -69,7 +55,6 @@ async function checkNoteAccess(notePath) { | ||||
|  | ||||
| export default { | ||||
|     getHoistedNoteId, | ||||
|     getHoistedNoteNoPromise, | ||||
|     setHoistedNoteId, | ||||
|     unhoist, | ||||
|     isTopLevelNode, | ||||
|   | ||||
| @@ -14,6 +14,8 @@ export class LoadResults { | ||||
|         this.noteRevisions = []; | ||||
|  | ||||
|         this.contentNoteIdToSourceId = []; | ||||
|  | ||||
|         this.options = []; | ||||
|     } | ||||
|  | ||||
|     addNote(noteId, sourceId) { | ||||
| @@ -90,4 +92,12 @@ export class LoadResults { | ||||
|  | ||||
|         return this.contentNoteIdToSourceId.find(l => l.noteId === noteId && l.sourceId !== sourceId); | ||||
|     } | ||||
|  | ||||
|     addOption(name) { | ||||
|         this.options.push(name); | ||||
|     } | ||||
|  | ||||
|     isOptionReloaded(name) { | ||||
|         this.options.includes(name); | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import optionsService from "./options.js"; | ||||
| import options from "./options.js"; | ||||
|  | ||||
| const MIME_TYPES_DICT = [ | ||||
|     { default: true, title: "Plain text", mime: "text/plain" }, | ||||
| @@ -161,7 +161,7 @@ const MIME_TYPES_DICT = [ | ||||
|  | ||||
| let mimeTypes = null; | ||||
|  | ||||
| function loadMimeTypes(options) { | ||||
| function loadMimeTypes() { | ||||
|     mimeTypes = JSON.parse(JSON.stringify(MIME_TYPES_DICT)); // clone | ||||
|  | ||||
|     const enabledMimeTypes = options.getJson('codeNotesMimeTypes') | ||||
| @@ -172,16 +172,15 @@ function loadMimeTypes(options) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| optionsService.addLoadListener(loadMimeTypes); | ||||
|  | ||||
| async function getMimeTypes() { | ||||
|     if (mimeTypes === null) { | ||||
|         loadMimeTypes(await options.waitForOptions()); | ||||
|         loadMimeTypes(); | ||||
|     } | ||||
|  | ||||
|     return mimeTypes; | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     getMimeTypes | ||||
|     getMimeTypes, | ||||
|     loadMimeTypes | ||||
| } | ||||
| @@ -1,11 +1,9 @@ | ||||
| import server from "./server.js"; | ||||
|  | ||||
| let optionsReady; | ||||
|  | ||||
| const loadListeners = []; | ||||
|  | ||||
| class Options { | ||||
|     constructor(arr) { | ||||
|     load(arr) { | ||||
|         this.arr = arr; | ||||
|     } | ||||
|  | ||||
| @@ -37,45 +35,21 @@ class Options { | ||||
|     is(key) { | ||||
|         return this.arr[key] === 'true'; | ||||
|     } | ||||
|  | ||||
|     set(key, value) { | ||||
|         this.arr[key] = value; | ||||
|     } | ||||
|  | ||||
| function reloadOptions() { | ||||
|     optionsReady = new Promise((resolve, reject) => { | ||||
|         server.get('options').then(optionArr => { | ||||
|             const options = new Options(optionArr); | ||||
|     async save(key, value) { | ||||
|         this.set(key, value); | ||||
|  | ||||
|             resolve(options); | ||||
|         const payload = {}; | ||||
|         payload[key] = value; | ||||
|  | ||||
|             for (const listener of loadListeners) { | ||||
|                 listener(options); | ||||
|         await server.put(`options`, payload); | ||||
|     } | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     return optionsReady; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * just waits for some options without triggering reload | ||||
|  * | ||||
|  * @return {Options} | ||||
|  */ | ||||
| async function waitForOptions() { | ||||
|     return await optionsReady; | ||||
| } | ||||
| const options = new Options(); | ||||
|  | ||||
| reloadOptions(); // initial load | ||||
|  | ||||
| function addLoadListener(listener) { | ||||
|     loadListeners.push(listener); | ||||
|  | ||||
|     // useful when listener has been added after the promise resolved, but can cause double emit if not yet | ||||
|     // that should not be an issue though | ||||
|     optionsReady.then(listener); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     addLoadListener, | ||||
|     reloadOptions, | ||||
|     waitForOptions | ||||
| } | ||||
| export default options; | ||||
| @@ -1,23 +1,19 @@ | ||||
| import utils from "./utils.js"; | ||||
| import optionsService from './options.js'; | ||||
| import options from './options.js'; | ||||
|  | ||||
| const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; | ||||
|  | ||||
| let lastProtectedSessionOperationDate = null; | ||||
| let protectedSessionTimeout = null; | ||||
|  | ||||
| optionsService.addLoadListener(options => setProtectedSessionTimeout(options.getInt('protectedSessionTimeout'))); | ||||
| let lastProtectedSessionOperationDate = 0; | ||||
|  | ||||
| setInterval(() => { | ||||
|     if (lastProtectedSessionOperationDate !== null && Date.now() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { | ||||
|     const protectedSessionTimeout = options.getInt('protectedSessionTimeout'); | ||||
|     if (lastProtectedSessionOperationDate | ||||
|         && Date.now() - lastProtectedSessionOperationDate > protectedSessionTimeout * 1000) { | ||||
|  | ||||
|         resetProtectedSession(); | ||||
|     } | ||||
| }, 5000); | ||||
|  | ||||
| function setProtectedSessionTimeout(encSessTimeout) { | ||||
|     protectedSessionTimeout = encSessTimeout; | ||||
| } | ||||
|  | ||||
| function setProtectedSessionId(id) { | ||||
|     // using session cookie so that it disappears after browser/tab is closed | ||||
|     utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, id); | ||||
| @@ -37,7 +33,7 @@ function isProtectedSessionAvailable() { | ||||
|  | ||||
| function touchProtectedSession() { | ||||
|     if (isProtectedSessionAvailable()) { | ||||
|         lastProtectedSessionOperationDate = new Date(); | ||||
|         lastProtectedSessionOperationDate = Date.now(); | ||||
|  | ||||
|         setProtectedSessionId(utils.getCookie(PROTECTED_SESSION_ID_KEY)); | ||||
|     } | ||||
| @@ -47,6 +43,5 @@ export default { | ||||
|     setProtectedSessionId, | ||||
|     resetProtectedSession, | ||||
|     isProtectedSessionAvailable, | ||||
|     setProtectedSessionTimeout, | ||||
|     touchProtectedSession | ||||
| }; | ||||
| @@ -1,9 +1,21 @@ | ||||
| import treeService from './tree.js'; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import server from './server.js'; | ||||
| import toastService from "./toast.js"; | ||||
| import appContext from "./app_context.js"; | ||||
|  | ||||
| const helpText = ` | ||||
| <strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="Search">complete help on search</button> | ||||
| <p> | ||||
| <ul> | ||||
|     <li>Just enter any text for full text search</li> | ||||
|     <li><code>@abc</code> - returns notes with label abc</li> | ||||
|     <li><code>@year=2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li> | ||||
|     <li><code>@rock @pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li> | ||||
|     <li><code>@rock or @pop</code> - only one of the labels must be present</li> | ||||
|     <li><code>@year<=2000</code> - numerical comparison (also >, >=, <).</li> | ||||
|     <li><code>@dateCreated>=MONTH-1</code> - notes created in the last month</li> | ||||
|     <li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li> | ||||
| </ul> | ||||
| </p>`; | ||||
|  | ||||
| async function refreshSearch() { | ||||
|     const activeNode = appContext.getMainNoteTree().getActiveNode(); | ||||
|  | ||||
| @@ -23,10 +35,6 @@ function init() { | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     // toggleSearch, | ||||
|     // resetSearch, | ||||
|     // showSearch, | ||||
|     // doSearch, | ||||
|     refreshSearch, | ||||
|     init, | ||||
|     getHelpText: () => helpText | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| import optionsService from "./options.js"; | ||||
| import options from "./options.js"; | ||||
|  | ||||
| export async function initSpellCheck() { | ||||
|     const options = await optionsService.waitForOptions(); | ||||
|  | ||||
|     const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker'); | ||||
|     const {remote, shell} = require('electron'); | ||||
|  | ||||
|   | ||||
| @@ -1,17 +1,7 @@ | ||||
| import server from "./server.js"; | ||||
| import optionService from "./options.js"; | ||||
| import options from "./options.js"; | ||||
|  | ||||
| let instance; | ||||
|  | ||||
| async function getPaneWidths() { | ||||
|     const options = await optionService.waitForOptions(); | ||||
|  | ||||
|     return { | ||||
|         leftPaneWidth: options.getInt('leftPaneWidth'), | ||||
|         rightPaneWidth: options.getInt('rightPaneWidth') | ||||
|     }; | ||||
| } | ||||
|  | ||||
| async function setupSplit(left, right) { | ||||
|     if (instance) { | ||||
|         instance.destroy(); | ||||
| @@ -24,15 +14,16 @@ async function setupSplit(left, right) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const {leftPaneWidth, rightPaneWidth} = await getPaneWidths(); | ||||
|     const leftPaneWidth = options.getInt('leftPaneWidth'); | ||||
|     const rightPaneWidth = options.getInt('rightPaneWidth'); | ||||
|  | ||||
|     if (left && right) { | ||||
|         instance = Split(['#left-pane', '#center-pane', '#right-pane'], { | ||||
|             sizes: [leftPaneWidth, 100 - leftPaneWidth - rightPaneWidth, rightPaneWidth], | ||||
|             gutterSize: 5, | ||||
|             onDragEnd: sizes => { | ||||
|                 server.put('options/leftPaneWidth/' + Math.round(sizes[0])); | ||||
|                 server.put('options/rightPaneWidth/' + Math.round(sizes[2])); | ||||
|                 options.save('leftPaneWidth', Math.round(sizes[0])); | ||||
|                 options.save('rightPaneWidth', Math.round(sizes[2])); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| @@ -41,7 +32,7 @@ async function setupSplit(left, right) { | ||||
|             sizes: [leftPaneWidth, 100 - leftPaneWidth], | ||||
|             gutterSize: 5, | ||||
|             onDragEnd: sizes => { | ||||
|                 server.put('options/leftPaneWidth/' + Math.round(sizes[0])); | ||||
|                 options.save('leftPaneWidth', Math.round(sizes[0])); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| @@ -50,7 +41,7 @@ async function setupSplit(left, right) { | ||||
|             sizes: [100 - rightPaneWidth, rightPaneWidth], | ||||
|             gutterSize: 5, | ||||
|             onDragEnd: sizes => { | ||||
|                 server.put('options/rightPaneWidth/' + Math.round(sizes[1])); | ||||
|                 options.save('rightPaneWidth', Math.round(sizes[1])); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -9,12 +9,6 @@ import Component from "../widgets/component.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import hoistedNoteService from "./hoisted_note.js"; | ||||
|  | ||||
| let showSidebarInNewTab = true; | ||||
|  | ||||
| optionsService.addLoadListener(options => { | ||||
|     showSidebarInNewTab = options.is('showSidebarInNewTab'); | ||||
| }); | ||||
|  | ||||
| class TabContext extends Component { | ||||
|     /** | ||||
|      * @param {AppContext} appContext | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import server from "./server.js"; | ||||
| import {LoadResults} from "./load_results.js"; | ||||
| import NoteComplement from "../entities/note_complement.js"; | ||||
| import appContext from "./app_context.js"; | ||||
| import options from "./options.js"; | ||||
|  | ||||
| /** | ||||
|  * TreeCache keeps a read only cache of note tree structure in frontend's memory. | ||||
| @@ -351,6 +352,12 @@ class TreeCache { | ||||
|             loadResults.addNoteRevision(sync.entityId, sync.noteId, sync.sourceId); | ||||
|         }); | ||||
|  | ||||
|         syncRows.filter(sync => sync.entityName === 'options').forEach(sync => { | ||||
|             options.set(sync.entity.name, sync.entity.value); | ||||
|  | ||||
|             loadResults.addOption(sync.entity.name); | ||||
|         }); | ||||
|  | ||||
|         appContext.trigger('entitiesReloaded', {loadResults}); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -130,7 +130,7 @@ async function consumeSyncData() { | ||||
|             await treeCache.processSyncRows(allSyncData); | ||||
|         } | ||||
|         catch (e) { | ||||
|             logError(`Encountered error ${e.message}, reloading frontend.`); | ||||
|             logError(`Encountered error ${e.message}: ${e.stack}, reloading frontend.`); | ||||
|  | ||||
|             // if there's an error in updating the frontend then the easy option to recover is to reload the frontend completely | ||||
|             utils.reloadApp(); | ||||
|   | ||||
| @@ -1,51 +1,47 @@ | ||||
| import server from "./server.js"; | ||||
| import utils from "./utils.js"; | ||||
| import optionsService from "./options.js"; | ||||
| import options from "./options.js"; | ||||
| import Component from "../widgets/component.js"; | ||||
|  | ||||
| const MIN_ZOOM = 0.5; | ||||
| const MAX_ZOOM = 2.0; | ||||
|  | ||||
| async function decreaseZoomFactor() { | ||||
|     await setZoomFactorAndSave(getCurrentZoom() - 0.1); | ||||
| export default class ZoomService extends Component { | ||||
|     constructor(appContext) { | ||||
|         super(appContext); | ||||
|  | ||||
|         this.setZoomFactor(options.getFloat('zoomFactor')); | ||||
|     } | ||||
|  | ||||
| async function increaseZoomFactor() { | ||||
|     await setZoomFactorAndSave(getCurrentZoom() + 0.1); | ||||
| } | ||||
|  | ||||
| function setZoomFactor(zoomFactor) { | ||||
|     setZoomFactor(zoomFactor) { | ||||
|         zoomFactor = parseFloat(zoomFactor); | ||||
|      | ||||
|         const webFrame = require('electron').webFrame; | ||||
|         webFrame.setZoomFactor(zoomFactor); | ||||
|     } | ||||
|      | ||||
| async function setZoomFactorAndSave(zoomFactor) { | ||||
|     if (!utils.isElectron()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     async setZoomFactorAndSave(zoomFactor) { | ||||
|         if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) { | ||||
|         setZoomFactor(zoomFactor); | ||||
|             this.setZoomFactor(zoomFactor); | ||||
|      | ||||
|         await server.put('options/zoomFactor/' + zoomFactor); | ||||
|             await options.save('zoomFactor', zoomFactor); | ||||
|         } | ||||
|         else { | ||||
|             console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`); | ||||
|         } | ||||
|     } | ||||
|      | ||||
| function getCurrentZoom() { | ||||
|     getCurrentZoom() { | ||||
|         return require('electron').webFrame.getZoomFactor(); | ||||
|     } | ||||
|  | ||||
| if (utils.isElectron()) { | ||||
|     optionsService.addLoadListener(options => setZoomFactor(options.getFloat('zoomFactor'))) | ||||
|     zoomOutListener() { | ||||
|         this.setZoomFactorAndSave(this.getCurrentZoom() - 0.1); | ||||
|     } | ||||
|  | ||||
| export default { | ||||
|     decreaseZoomFactor, | ||||
|     increaseZoomFactor, | ||||
|     setZoomFactor, | ||||
|     setZoomFactorAndSave | ||||
|     zoomInListener() { | ||||
|         this.setZoomFactorAndSave(this.getCurrentZoom() + 0.1); | ||||
|     } | ||||
|  | ||||
|     setZoomFactorAndSaveListener({zoomFactor}) { | ||||
|         this.setZoomFactorAndSave(zoomFactor); | ||||
|     } | ||||
| } | ||||
| @@ -118,7 +118,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|                 autoExpandMS: 600, | ||||
|                 dragStart: (node, data) => { | ||||
|                     // don't allow dragging root node | ||||
|                     if (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() | ||||
|                     if (node.data.noteId === hoistedNoteService.getHoistedNoteId() | ||||
|                         || node.getParent().data.noteType === 'search') { | ||||
|                         return false; | ||||
|                     } | ||||
| @@ -141,7 +141,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|                 dragDrop: async (node, data) => { | ||||
|                     if ((data.hitMode === 'over' && node.data.noteType === 'search') || | ||||
|                         (['after', 'before'].includes(data.hitMode) | ||||
|                             && (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() || node.getParent().data.noteType === 'search'))) { | ||||
|                             && (node.data.noteId === hoistedNoteService.getHoistedNoteId() || node.getParent().data.noteType === 'search'))) { | ||||
|  | ||||
|                         const infoDialog = await import('../dialogs/info.js'); | ||||
|  | ||||
|   | ||||
| @@ -1,25 +1,10 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import treeService from "../services/tree.js"; | ||||
| import searchService from "../services/search_notes.js"; | ||||
| import treeCache from "../services/tree_cache.js"; | ||||
| import toastService from "../services/toast.js"; | ||||
| import appContext from "../services/app_context.js"; | ||||
| import noteCreateService from "../services/note_create.js"; | ||||
|  | ||||
| const helpText = ` | ||||
| <strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="Search">complete help on search</button> | ||||
| <p> | ||||
| <ul> | ||||
|     <li>Just enter any text for full text search</li> | ||||
|     <li><code>@abc</code> - returns notes with label abc</li> | ||||
|     <li><code>@year=2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li> | ||||
|     <li><code>@rock @pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li> | ||||
|     <li><code>@rock or @pop</code> - only one of the labels must be present</li> | ||||
|     <li><code>@year<=2000</code> - numerical comparison (also >, >=, <).</li> | ||||
|     <li><code>@dateCreated>=MONTH-1</code> - notes created in the last month</li> | ||||
|     <li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li> | ||||
| </ul> | ||||
| </p>`; | ||||
|  | ||||
| const TPL = ` | ||||
| <div class="search-box"> | ||||
|     <style> | ||||
| @@ -145,7 +130,7 @@ export default class SearchBoxWidget extends BasicWidget { | ||||
|         this.$searchBox.tooltip({ | ||||
|             trigger: 'focus', | ||||
|             html: true, | ||||
|             title: helpText, | ||||
|             title: searchService.getHelpText(), | ||||
|             placement: 'right', | ||||
|             delay: { | ||||
|                 show: 500, // necessary because sliding out may cause wrong position | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import optionService from "../services/options.js"; | ||||
| import options from "../services/options.js"; | ||||
|  | ||||
| export default class SidePaneContainer extends BasicWidget { | ||||
|     constructor(appContext, side, widgets) { | ||||
| @@ -19,9 +19,7 @@ export default class SidePaneContainer extends BasicWidget { | ||||
|         return this.$widget; | ||||
|     } | ||||
|  | ||||
|     async eventReceived(name, data, sync = false) { | ||||
|         const options = await optionService.waitForOptions(); | ||||
|  | ||||
|     eventReceived(name, data, sync = false) { | ||||
|         if (options.is(this.side + 'PaneVisible')) { | ||||
|             super.eventReceived(name, data, sync); | ||||
|         } | ||||
|   | ||||
							
								
								
									
										72
									
								
								src/public/javascripts/widgets/sidebar_toggle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/public/javascripts/widgets/sidebar_toggle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| import options from "../services/options.js"; | ||||
| import splitService from "../services/split.js"; | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <div> | ||||
|     <style> | ||||
|     #hide-right-pane-button, #show-right-pane-button { | ||||
|         position: fixed; | ||||
|         bottom: 10px; | ||||
|         right: 10px; | ||||
|         z-index: 1000; | ||||
|     } | ||||
|      | ||||
|     #hide-left-pane-button, #show-left-pane-button { | ||||
|         position: fixed; | ||||
|         bottom: 10px; | ||||
|         left: 10px; | ||||
|         z-index: 1000; | ||||
|     } | ||||
|     </style> | ||||
|      | ||||
|     <button id="hide-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button> | ||||
|     <button id="show-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button> | ||||
|              | ||||
|     <button id="hide-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button> | ||||
|     <button id="show-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button> | ||||
| </div> | ||||
| `; | ||||
|  | ||||
| export default class SidebarToggle extends BasicWidget { | ||||
|     constructor(appContext) { | ||||
|         super(appContext); | ||||
|  | ||||
|         this.paneVisible = {}; | ||||
|     } | ||||
|  | ||||
|     doRender() { | ||||
|         this.$widget = $(TPL); | ||||
|  | ||||
|         this.toggleSidebar('left', options.is('leftPaneVisible')); | ||||
|         this.toggleSidebar('right', options.is('rightPaneVisible')); | ||||
|  | ||||
|         $("#show-right-pane-button").on('click', () => toggleAndSave('right', true)); | ||||
|         $("#hide-right-pane-button").on('click', () => toggleAndSave('right', false)); | ||||
|  | ||||
|         $("#show-left-pane-button").on('click', () => toggleAndSave('left', true)); | ||||
|         $("#hide-left-pane-button").on('click', () => toggleAndSave('left', false)); | ||||
|  | ||||
|         splitService.setupSplit(this.paneVisible.left, this.paneVisible.right); | ||||
|  | ||||
|         return this.$widget; | ||||
|     } | ||||
|  | ||||
|     toggleSidebar(side, show) { | ||||
|         $(`#${side}-pane`).toggle(show); | ||||
|         $(`#show-${side}-pane-button`).toggle(!show); | ||||
|         $(`#hide-${side}-pane-button`).toggle(show); | ||||
|  | ||||
|         this.paneVisible[side] = show; | ||||
|     } | ||||
|  | ||||
|     async toggleAndSave(side, show) { | ||||
|         this.toggleSidebar(side, show); | ||||
|  | ||||
|         await options.save(`${side}PaneVisible`, show.toString()); | ||||
|  | ||||
|         splitService.setupSplit(this.paneVisible.left, this.paneVisible.right); | ||||
|  | ||||
|         this.trigger('sidebarVisibilityChanged', {side, show}); | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import optionService from "../services/options.js"; | ||||
| import options from "../services/options.js"; | ||||
| import utils from "../services/utils.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| @@ -23,10 +23,8 @@ export default class TitleBarButtonsWidget extends BasicWidget { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.$widget = $(TPL); | ||||
|  | ||||
|         optionService.waitForOptions().then(options => { | ||||
|         if (!options.is('nativeTitleBarVisible')) { | ||||
|             this.$widget = $(TPL); | ||||
|             this.$widget.show(); | ||||
|  | ||||
|             const $minimizeBtn = this.$widget.find(".minimize-btn"); | ||||
| @@ -57,7 +55,9 @@ export default class TitleBarButtonsWidget extends BasicWidget { | ||||
|                 remote.BrowserWindow.getFocusedWindow().close(); | ||||
|             }); | ||||
|         } | ||||
|         }); | ||||
|         else { | ||||
|             this.$widget = $('<div>'); | ||||
|         } | ||||
|  | ||||
|         return this.$widget; | ||||
|     } | ||||
|   | ||||
| @@ -146,20 +146,6 @@ body { | ||||
|     border-color: var(--button-border-color); | ||||
| } | ||||
|  | ||||
| #hide-right-pane-button, #show-right-pane-button { | ||||
|     position: fixed; | ||||
|     bottom: 10px; | ||||
|     right: 10px; | ||||
|     z-index: 1000; | ||||
| } | ||||
|  | ||||
| #hide-left-pane-button, #show-left-pane-button { | ||||
|     position: fixed; | ||||
|     bottom: 10px; | ||||
|     left: 10px; | ||||
|     z-index: 1000; | ||||
| } | ||||
|  | ||||
| #right-pane { | ||||
|     overflow: auto; | ||||
|     padding-top: 4px; | ||||
|   | ||||
| @@ -110,9 +110,7 @@ async function updateEntity(entity) { | ||||
|         const primaryKey = entity[primaryKeyName]; | ||||
|  | ||||
|         if (entity.isChanged) { | ||||
|             if (entityName !== 'options' || entity.isSynced) { | ||||
|             await syncTableService.addEntitySync(entityName, primaryKey); | ||||
|             } | ||||
|  | ||||
|             if (!cls.isEntityEventsDisabled()) { | ||||
|                 const eventPayload = { | ||||
|   | ||||
| @@ -315,10 +315,13 @@ async function getSyncRecords(syncs) { | ||||
|     let length = 0; | ||||
|  | ||||
|     for (const sync of syncs) { | ||||
|         const record = { | ||||
|             sync: sync, | ||||
|             entity: await getEntityRow(sync.entityName, sync.entityId) | ||||
|         }; | ||||
|         const entity = await getEntityRow(sync.entityName, sync.entityId); | ||||
|  | ||||
|         if (sync.entityName === 'options' && !entity.isSynced) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const record = { sync, entity }; | ||||
|  | ||||
|         records.push(record); | ||||
|  | ||||
|   | ||||
| @@ -92,6 +92,9 @@ async function fillInAdditionalProperties(sync) { | ||||
|     } else if (sync.entityName === 'note_reordering') { | ||||
|         sync.positions = await sql.getMap(`SELECT branchId, notePosition FROM branches WHERE isDeleted = 0 AND parentNoteId = ?`, [sync.entityId]); | ||||
|     } | ||||
|     else if (sync.entityName === 'options') { | ||||
|         sync.entity = await sql.getRow(`SELECT * FROM options WHERE name = ?`, [sync.entityId]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function sendPing(client) { | ||||
|   | ||||
| @@ -14,13 +14,7 @@ | ||||
|     <div id="top-pane"></div> | ||||
|  | ||||
|     <div id="main-pane" style="display: flex; flex-grow: 1; flex-shrink: 1; min-height: 0;"> | ||||
|         <button id="hide-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button> | ||||
|         <button id="show-left-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button> | ||||
|  | ||||
|         <div id="center-pane"></div> | ||||
|  | ||||
|         <button id="hide-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button> | ||||
|         <button id="show-right-pane-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button> | ||||
|     </div> | ||||
|  | ||||
|     <div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user