mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/stable'
This commit is contained in:
		| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.50.1", |   "version": "0.50.2", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
|   | |||||||
| @@ -42,7 +42,9 @@ class EtapiToken extends AbstractEntity { | |||||||
|         /** @type {boolean} */ |         /** @type {boolean} */ | ||||||
|         this.isDeleted = !!row.isDeleted; |         this.isDeleted = !!row.isDeleted; | ||||||
|  |  | ||||||
|         this.becca.etapiTokens[this.etapiTokenId] = this; |         if (this.etapiTokenId) { | ||||||
|  |             this.becca.etapiTokens[this.etapiTokenId] = this; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     init() { |     init() { | ||||||
|   | |||||||
| @@ -62,12 +62,12 @@ export default class EtapiOptions { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const {token} = await server.post('etapi-tokens', {tokenName}); |             const {authToken} = await server.post('etapi-tokens', {tokenName}); | ||||||
|  |  | ||||||
|             await promptDialog.ask({ |             await promptDialog.ask({ | ||||||
|                 title: "ETAPI token created", |                 title: "ETAPI token created", | ||||||
|                 message: 'Copy the created token into clipboard. Trilium stores the token hashed and this is the last time you see it.', |                 message: 'Copy the created token into clipboard. Trilium stores the token hashed and this is the last time you see it.', | ||||||
|                 defaultValue: token  |                 defaultValue: authToken | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             this.refreshTokens(); |             this.refreshTokens(); | ||||||
|   | |||||||
| @@ -51,7 +51,8 @@ $dialog.on("hidden.bs.modal", () => { | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| $form.on('submit', () => { | $form.on('submit', e => { | ||||||
|  |     e.preventDefault(); | ||||||
|     resolve($answer.val()); |     resolve($answer.val()); | ||||||
|  |  | ||||||
|     $dialog.modal('hide'); |     $dialog.modal('hide'); | ||||||
|   | |||||||
| @@ -7,11 +7,14 @@ import treeService from "./tree.js"; | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import NoteContext from "./note_context.js"; | import NoteContext from "./note_context.js"; | ||||||
| import appContext from "./app_context.js"; | import appContext from "./app_context.js"; | ||||||
|  | import Mutex from "../utils/mutex.js"; | ||||||
|  |  | ||||||
| export default class TabManager extends Component { | export default class TabManager extends Component { | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
|  |  | ||||||
|  |         this.mutex = new Mutex(); | ||||||
|  |  | ||||||
|         this.activeNtxId = null; |         this.activeNtxId = null; | ||||||
|  |  | ||||||
|         // elements are arrays of note contexts for each tab (one main context + subcontexts [splits]) |         // elements are arrays of note contexts for each tab (one main context + subcontexts [splits]) | ||||||
| @@ -292,51 +295,55 @@ export default class TabManager extends Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async removeNoteContext(ntxId) { |     async removeNoteContext(ntxId) { | ||||||
|         const noteContextToRemove = this.getNoteContextById(ntxId); |         // removing note context is async process which can take some time, if users presses CTRL-W quickly, two | ||||||
|  |         // close events could interleave which would then lead to attempting to activate already removed context. | ||||||
|  |         await this.mutex.runExclusively(async () => { | ||||||
|  |             const noteContextToRemove = this.getNoteContextById(ntxId); | ||||||
|  |  | ||||||
|         if (noteContextToRemove.isMainContext()) { |             if (noteContextToRemove.isMainContext()) { | ||||||
|             // forbid removing last main note context |                 // forbid removing last main note context | ||||||
|             // this was previously allowed (was replaced with empty tab) but this proved to be prone to race conditions |                 // this was previously allowed (was replaced with empty tab) but this proved to be prone to race conditions | ||||||
|             const mainNoteContexts = this.getNoteContexts().filter(nc => nc.isMainContext()); |                 const mainNoteContexts = this.getNoteContexts().filter(nc => nc.isMainContext()); | ||||||
|  |  | ||||||
|             if (mainNoteContexts.length === 1) { |                 if (mainNoteContexts.length === 1) { | ||||||
|                 mainNoteContexts[0].setEmpty(); |                     mainNoteContexts[0].setEmpty(); | ||||||
|                 return; |                     return; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // close dangling autocompletes after closing the tab |             // close dangling autocompletes after closing the tab | ||||||
|         $(".aa-input").autocomplete("close"); |             $(".aa-input").autocomplete("close"); | ||||||
|  |  | ||||||
|         const noteContextsToRemove = noteContextToRemove.getSubContexts(); |             const noteContextsToRemove = noteContextToRemove.getSubContexts(); | ||||||
|         const ntxIdsToRemove = noteContextsToRemove.map(nc => nc.ntxId); |             const ntxIdsToRemove = noteContextsToRemove.map(nc => nc.ntxId); | ||||||
|  |  | ||||||
|         await this.triggerEvent('beforeTabRemove', { ntxIds: ntxIdsToRemove }); |             await this.triggerEvent('beforeTabRemove', { ntxIds: ntxIdsToRemove }); | ||||||
|  |  | ||||||
|         if (!noteContextToRemove.isMainContext()) { |             if (!noteContextToRemove.isMainContext()) { | ||||||
|             await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId); |                 await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId); | ||||||
|         } |  | ||||||
|         else if (this.mainNoteContexts.length <= 1) { |  | ||||||
|             await this.openAndActivateEmptyTab(); |  | ||||||
|         } |  | ||||||
|         else if (ntxIdsToRemove.includes(this.activeNtxId)) { |  | ||||||
|             const idx = this.mainNoteContexts.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId); |  | ||||||
|  |  | ||||||
|             if (idx === this.mainNoteContexts.length - 1) { |  | ||||||
|                 await this.activatePreviousTabCommand(); |  | ||||||
|             } |             } | ||||||
|             else { |             else if (this.mainNoteContexts.length <= 1) { | ||||||
|                 await this.activateNextTabCommand(); |                 await this.openAndActivateEmptyTab(); | ||||||
|             } |             } | ||||||
|         } |             else if (ntxIdsToRemove.includes(this.activeNtxId)) { | ||||||
|  |                 const idx = this.mainNoteContexts.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId); | ||||||
|  |  | ||||||
|         this.children = this.children.filter(nc => !ntxIdsToRemove.includes(nc.ntxId)); |                 if (idx === this.mainNoteContexts.length - 1) { | ||||||
|  |                     await this.activatePreviousTabCommand(); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     await this.activateNextTabCommand(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|         this.recentlyClosedTabs.push(noteContextsToRemove); |             this.children = this.children.filter(nc => !ntxIdsToRemove.includes(nc.ntxId)); | ||||||
|  |  | ||||||
|         this.triggerEvent('noteContextRemoved', {ntxIds: ntxIdsToRemove}); |             this.recentlyClosedTabs.push(noteContextsToRemove); | ||||||
|  |  | ||||||
|         this.tabsUpdate.scheduleUpdate(); |             this.triggerEvent('noteContextRemoved', {ntxIds: ntxIdsToRemove}); | ||||||
|  |  | ||||||
|  |             this.tabsUpdate.scheduleUpdate(); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     tabReorderEvent({ntxIdsInOrder}) { |     tabReorderEvent({ntxIdsInOrder}) { | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|  |  | ||||||
|             if (!parents.length) { |             if (!parents.length) { | ||||||
|                 if (logErrors) { |                 if (logErrors) { | ||||||
|                     ws.logError(`No parents found for ${childNoteId} (${child.title}) for path ${notePath}`); |                     ws.logError(`No parents found for note ${childNoteId} (${child.title}) for path ${notePath}`); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return; |                 return; | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								src/public/app/utils/mutex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/public/app/utils/mutex.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | export default class Mutex { | ||||||
|  |     constructor() { | ||||||
|  |         this.current = Promise.resolve(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** @returns {Promise} */ | ||||||
|  |     lock() { | ||||||
|  |         let resolveFun; | ||||||
|  |         const subPromise = new Promise(resolve => resolveFun = () => resolve()); | ||||||
|  |         // Caller gets a promise that resolves when the current outstanding lock resolves | ||||||
|  |         const newPromise = this.current.then(() => resolveFun); | ||||||
|  |         // Don't allow the next request until the new promise is done | ||||||
|  |         this.current = subPromise; | ||||||
|  |         // Return the new promise | ||||||
|  |         return newPromise; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     async runExclusively(cb) { | ||||||
|  |         const unlock = await this.lock(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             await cb(); | ||||||
|  |         } | ||||||
|  |         finally { | ||||||
|  |             unlock(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -91,6 +91,8 @@ body { | |||||||
|  |  | ||||||
|     --ck-color-table-focused-cell-background: var(--more-accented-background-color); |     --ck-color-table-focused-cell-background: var(--more-accented-background-color); | ||||||
|  |  | ||||||
|  |     --ck-color-labeled-field-label-background: var(--accented-background-color); | ||||||
|  |  | ||||||
|     /* todo lists */ |     /* todo lists */ | ||||||
|  |  | ||||||
|     --ck-color-todo-list-checkmark-border: var(--main-border-color); |     --ck-color-todo-list-checkmark-border: var(--main-border-color); | ||||||
|   | |||||||
| @@ -9,9 +9,7 @@ function getTokens() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function createToken(req) { | function createToken(req) { | ||||||
|     return { |     return etapiTokenService.createToken(req.body.tokenName); | ||||||
|         authToken: etapiTokenService.createToken(req.body.tokenName) |  | ||||||
|     }; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function patchToken(req) { | function patchToken(req) { | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2022-02-02T21:38:21+01:00", buildRevision: "0917fc8be171253449219cf29c0e603ac29eb26e" }; | module.exports = { buildDate:"2022-02-09T22:52:36+01:00", buildRevision: "23daaa2387a0655685377f0a541d154aeec2aae8" }; | ||||||
|   | |||||||
| @@ -93,8 +93,7 @@ function deleteToken(etapiTokenId) { | |||||||
|         return; // ok, already deleted |         return; // ok, already deleted | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     etapiToken.isDeleted = true; |     etapiToken.markAsDeletedSimple(); | ||||||
|     etapiToken.save(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ const fs = require("fs"); | |||||||
| const becca = require("../../becca/becca"); | const becca = require("../../becca/becca"); | ||||||
| const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | ||||||
| const archiver = require('archiver'); | const archiver = require('archiver'); | ||||||
|  | const log = require("../log"); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param {TaskContext} taskContext |  * @param {TaskContext} taskContext | ||||||
| @@ -254,7 +255,9 @@ ${content} | |||||||
| </html>`; | </html>`; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return html.prettyPrint(content, {indent_size: 2}); |             return content.length < 100000 | ||||||
|  |                 ? html.prettyPrint(content, {indent_size: 2}) | ||||||
|  |                 : content; | ||||||
|         } |         } | ||||||
|         else if (noteMeta.format === 'markdown') { |         else if (noteMeta.format === 'markdown') { | ||||||
|             let markdownContent = mdService.toMarkdown(content); |             let markdownContent = mdService.toMarkdown(content); | ||||||
| @@ -274,6 +277,8 @@ ${content} | |||||||
|     const notePaths = {}; |     const notePaths = {}; | ||||||
|  |  | ||||||
|     function saveNote(noteMeta, filePathPrefix) { |     function saveNote(noteMeta, filePathPrefix) { | ||||||
|  |         log.info(`Exporting note ${noteMeta.noteId}`); | ||||||
|  |  | ||||||
|         if (noteMeta.isClone) { |         if (noteMeta.isClone) { | ||||||
|             const targetUrl = getTargetUrl(noteMeta.noteId, noteMeta); |             const targetUrl = getTargetUrl(noteMeta.noteId, noteMeta); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -634,7 +634,10 @@ function undeleteBranch(branchId, deleteId, taskContext) { | |||||||
|     taskContext.increaseProgressCount(); |     taskContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|     if (note.isDeleted && note.deleteId === deleteId) { |     if (note.isDeleted && note.deleteId === deleteId) { | ||||||
|         new Note(note).save(); |         // becca entity was already created as skeleton in "new Branch()" above | ||||||
|  |         const noteEntity = becca.getNote(note.noteId); | ||||||
|  |         noteEntity.updateFromRow(note); | ||||||
|  |         noteEntity.save(); | ||||||
|  |  | ||||||
|         const attributes = sql.getRows(` |         const attributes = sql.getRows(` | ||||||
|                 SELECT * FROM attributes  |                 SELECT * FROM attributes  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <div id="prompt-dialog" class="modal mx-auto" tabindex="-1" role="dialog"> | <div id="prompt-dialog" class="modal mx-auto" tabindex="-1" role="dialog"> | ||||||
|     <div class="modal-dialog" role="document"> |     <div class="modal-dialog modal-lg" role="document"> | ||||||
|         <div class="modal-content"> |         <div class="modal-content"> | ||||||
|             <form id="prompt-dialog-form"> |             <form id="prompt-dialog-form"> | ||||||
|                 <div class="modal-header"> |                 <div class="modal-header"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user