diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_13_05_23,_16_28_[Changes]/Montserrat-Light.ttf b/.idea/shelf/Uncommitted_changes_before_Checkout_at_13_05_23,_16_28_[Changes]/Montserrat-Light.ttf deleted file mode 100644 index d1afa353e..000000000 Binary files a/.idea/shelf/Uncommitted_changes_before_Checkout_at_13_05_23,_16_28_[Changes]/Montserrat-Light.ttf and /dev/null differ diff --git a/package.json b/package.json index b0b420a54..5f16fefea 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.59.4", + "version": "0.60.0-beta", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { @@ -28,7 +28,7 @@ "test": "npm run test-jasmine && npm run test-es6", "postinstall": "rimraf ./node_modules/canvas", "lint": "eslint . --cache", - "prepare": "husky install" + "prepare": "husky install || echo 'Husky install failed, expected on flatpak build'" }, "dependencies": { "@braintree/sanitize-url": "6.0.2", @@ -45,8 +45,8 @@ "cookie-parser": "1.4.6", "csurf": "1.11.0", "dayjs": "1.11.7", - "dayjs-plugin-utc": "^0.1.2", - "debounce": "^1.2.1", + "dayjs-plugin-utc": "0.1.2", + "debounce": "1.2.1", "ejs": "3.1.9", "electron-debug": "3.2.0", "electron-dl": "3.5.0", diff --git a/src/public/app/components/root_command_executor.js b/src/public/app/components/root_command_executor.js index 2c524c735..98ff637f0 100644 --- a/src/public/app/components/root_command_executor.js +++ b/src/public/app/components/root_command_executor.js @@ -54,13 +54,11 @@ export default class RootCommandExecutor extends Component { openService.openNoteExternally(noteId, mime); } } - + openNoteCustomCommand() { const noteId = appContext.tabManager.getActiveContextNoteId(); - const mime = appContext.tabManager.getActiveContextNoteMime() - if (noteId) { - openService.openNoteCustom(noteId, mime); + openService.openNoteCustom(noteId); } } diff --git a/src/public/app/services/open.js b/src/public/app/services/open.js index 6c79d9417..29da3bac2 100644 --- a/src/public/app/services/open.js +++ b/src/public/app/services/open.js @@ -41,61 +41,66 @@ function downloadAttachment(attachmentId) { download(url); } -async function openNoteCustom(noteId, mime) { - if (utils.isElectron()) { - const resp = await server.post(`notes/${noteId}/save-to-tmp-dir`); - const filePath = resp.tmpFilePath; - const { exec } = utils.dynamicRequire('child_process'); - const platform = process.platform; - if (platform === 'linux') { - const terminals = ['gnome-terminal', 'konsole', 'xterm', 'xfce4-terminal', 'mate-terminal', 'rxvt', 'terminator', 'terminology']; +async function openNoteCustom(noteId) { + if (!utils.isElectron() || utils.isMac()) { + return; + } + + const resp = await server.post(`notes/${noteId}/save-to-tmp-dir`); + let filePath = resp.tmpFilePath; + const {exec} = utils.dynamicRequire('child_process'); + const platform = process.platform; + + if (platform === 'linux') { + // we don't know which terminal is available, try in succession + const terminals = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xterm', 'xfce4-terminal', 'mate-terminal', 'rxvt', 'terminator', 'terminology']; const openFileWithTerminal = (terminal) => { - const command = `${terminal} -e 'mimeopen -d "${filePath}"'`; - console.log(`Open Note custom: ${command} `); - exec(command, (error, stdout, stderr) => { - if (error) { - console.error(`Open Note custom: Failed to open file with ${terminal}: ${error}`); - searchTerminal(terminals.indexOf(terminal) + 1); - } else { - console.log(`Open Note custom: File opened with ${terminal}. ${stdout}`); - } - }); + const command = `${terminal} -e 'mimeopen -d "${filePath}"'`; + console.log(`Open Note custom: ${command} `); + exec(command, (error, stdout, stderr) => { + if (error) { + console.error(`Open Note custom: Failed to open file with ${terminal}: ${error}`); + searchTerminal(terminals.indexOf(terminal) + 1); + } else { + console.log(`Open Note custom: File opened with ${terminal}: ${stdout}`); + } + }); }; + const searchTerminal = (index) => { - const terminal = terminals[index]; - if (!terminal) { - console.error('Open Note custom: No terminal found!'); - open(getFileUrl(noteId), { url: true }); - return; - } - exec(`which ${terminal}`, (error, stdout, stderr) => { - if (stdout.trim()) { - openFileWithTerminal(terminal); - } else { - searchTerminal(index + 1); + const terminal = terminals[index]; + if (!terminal) { + console.error('Open Note custom: No terminal found!'); + open(getFileUrl(noteId), {url: true}); + return; } - }); + exec(`which ${terminal}`, (error, stdout, stderr) => { + if (stdout.trim()) { + openFileWithTerminal(terminal); + } else { + searchTerminal(index + 1); + } + }); }; searchTerminal(0); - } else if (platform === 'win32') { + } else if (platform === 'win32') { if (filePath.indexOf("/") !== -1) { - //Note that the path separator must be \ instead of / - filePath = filePath.replace(/\//g, "\\"); + // Note that the path separator must be \ instead of / + filePath = filePath.replace(/\//g, "\\"); } const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ` + filePath; exec(command, (err, stdout, stderr) => { - if (err) { - console.error("Open Note custom: ", err); - open(getFileUrl(noteId), { url: true }); - return; - } + if (err) { + console.error("Open Note custom: ", err); + open(getFileUrl(noteId), {url: true}); + return; + } }); - } else { + } else { console.log('Currently "Open Note custom" only supports linux and windows systems'); - open(getFileUrl(noteId), { url: true }); - } + open(getFileUrl(noteId), {url: true}); } - } +} function downloadNoteRevision(noteId, noteRevisionId) { const url = getUrlForDownload(`api/revisions/${noteRevisionId}/download`); diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index ac552f4b5..3f6bbf843 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -40,7 +40,7 @@ const TPL = ` Open note externally - Open note custom (beta) + Open note custom Import files Export note Delete note @@ -104,7 +104,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { this.$renderNoteButton.toggle(note.type === 'render'); this.$openNoteExternallyButton.toggle(utils.isElectron()); - this.$openNoteCustomButton.toggle(utils.isElectron()); + this.$openNoteCustomButton.toggle(utils.isElectron() && !utils.isMac()); // no implementation for Mac yet } async convertNoteIntoAttachmentCommand() { diff --git a/src/public/app/widgets/containers/scrolling_container.js b/src/public/app/widgets/containers/scrolling_container.js index 78c9a243a..f13994353 100644 --- a/src/public/app/widgets/containers/scrolling_container.js +++ b/src/public/app/widgets/containers/scrolling_container.js @@ -6,6 +6,7 @@ export default class ScrollingContainer extends Container { this.class("scrolling-container"); this.css('overflow', 'auto'); + this.css('scroll-behavior', 'smooth'); this.css('position', 'relative'); } diff --git a/src/public/app/widgets/find.js b/src/public/app/widgets/find.js index 8be5d2e41..53a8e2604 100644 --- a/src/public/app/widgets/find.js +++ b/src/public/app/widgets/find.js @@ -146,20 +146,31 @@ export default class FindWidget extends NoteContextAwareWidget { return; } + this.handler = await this.getHandler(); + + const selectedText = window.getSelection().toString() || ""; + this.$findBox.show(); this.$input.focus(); - this.handler = await this.getHandler(); const isAlreadyVisible = this.$findBox.is(":visible"); if (isAlreadyVisible) { + if (selectedText) { + this.$input.val(selectedText); + } + + if (this.$input.val()) { + await this.performFind(); + } + this.$input.select(); } else { this.$totalFound.text(0); this.$currentFound.text(0); - const searchTerm = await this.handler.getInitialSearchTerm(); - this.$input.val(searchTerm || ""); - if (searchTerm !== "") { + this.$input.val(selectedText); + + if (selectedText) { this.$input.select(); await this.performFind(); } diff --git a/src/public/app/widgets/find_in_code.js b/src/public/app/widgets/find_in_code.js index 19bbade7a..d1188b3cf 100644 --- a/src/public/app/widgets/find_in_code.js +++ b/src/public/app/widgets/find_in_code.js @@ -16,23 +16,6 @@ export default class FindInCode { return this.parent.noteContext.getCodeEditor(); } - async getInitialSearchTerm() { - const codeEditor = await this.getCodeEditor(); - - // highlightSelectionMatches is the overlay that highlights - // the words under the cursor. This occludes the search - // markers style, save it, disable it. It will be restored when - // the focus is back into the note - this.oldHighlightSelectionMatches = codeEditor.getOption("highlightSelectionMatches"); - codeEditor.setOption("highlightSelectionMatches", false); - - // Fill in the findbox with the current selection if any - const selectedText = codeEditor.getSelection() - if (selectedText !== "") { - return selectedText; - } - } - async performFind(searchTerm, matchCase, wholeWord) { let findResult = null; let totalFound = 0; diff --git a/src/public/app/widgets/find_in_html.js b/src/public/app/widgets/find_in_html.js index 2feb3d5fe..349c70d51 100644 --- a/src/public/app/widgets/find_in_html.js +++ b/src/public/app/widgets/find_in_html.js @@ -16,10 +16,6 @@ export default class FindInHtml { this.$results = null; } - async getInitialSearchTerm() { - return ""; // FIXME - } - async performFind(searchTerm, matchCase, wholeWord) { await libraryLoader.requireLibrary(libraryLoader.MARKJS); diff --git a/src/public/app/widgets/find_in_text.js b/src/public/app/widgets/find_in_text.js index 019b5d389..e7044eb18 100644 --- a/src/public/app/widgets/find_in_text.js +++ b/src/public/app/widgets/find_in_text.js @@ -8,19 +8,6 @@ export default class FindInText { return this.parent.noteContext.getTextEditor(); } - async getInitialSearchTerm() { - const textEditor = await this.getTextEditor(); - - const selection = textEditor.model.document.selection; - const range = selection.getFirstRange(); - - // FIXME - for (const item of range.getItems()) { - // Fill in the findbox with the current selection if any - return item.data; - } - } - async performFind(searchTerm, matchCase, wholeWord) { // Do this even if the searchTerm is empty so the markers are cleared and // the counters updated diff --git a/src/public/app/widgets/toc.js b/src/public/app/widgets/toc.js index b1b72f879..85d415be2 100644 --- a/src/public/app/widgets/toc.js +++ b/src/public/app/widgets/toc.js @@ -177,7 +177,7 @@ export default class TocWidget extends RightPanelWidget { const headingElement = $container.find(":header")[headingIndex]; if (headingElement != null) { - headingElement.scrollIntoView(); + headingElement.scrollIntoView({ behavior: "smooth" }); } } else { const textEditor = await this.noteContext.getTextEditor(); @@ -193,50 +193,9 @@ export default class TocWidget extends RightPanelWidget { // navigate (note that the TOC rendering and other TOC // entries' navigation could be wrong too) if (headingNode != null) { - // Setting the selection alone doesn't scroll to the - // caret, needs to be done explicitly and outside of - // the writer change callback so the scroll is - // guaranteed to happen after the selection is - // updated. - - // In addition, scrolling to a caret later in the - // document (ie "forward scrolls"), only scrolls - // barely enough to place the caret at the bottom of - // the screen, which is a usability issue, you would - // like the caret to be placed at the top or center - // of the screen. - - // To work around that issue, first scroll to the - // end of the document, then scroll to the desired - // point. This causes all the scrolls to be - // "backward scrolls" no matter the current caret - // position, which places the caret at the top of - // the screen. - - // XXX This could be fixed in another way by using - // the underlying CKEditor5 - // scrollViewportToShowTarget, which allows to - // provide a larger "viewportOffset", but that - // has coding complications (requires calling an - // internal CKEditor utils funcion and passing - // an HTML element, not a CKEditor node, and - // CKEditor5 doesn't seem to have a - // straightforward way to convert a node to an - // HTML element? (in CKEditor4 this was done - // with $(node.$) ) - - // Scroll to the end of the note to guarantee the - // next scroll is a backwards scroll that places the - // caret at the top of the screen - model.change(writer => { - writer.setSelection(root.getChild(root.childCount - 1), 0); + $(textEditor.editing.view.domRoots.values().next().value).find(':header')[headingIndex].scrollIntoView({ + behavior: 'smooth' }); - textEditor.editing.view.scrollToTheSelection(); - // Backwards scroll to the heading - model.change(writer => { - writer.setSelection(headingNode, 0); - }); - textEditor.editing.view.scrollToTheSelection(); } } } diff --git a/src/public/app/widgets/type_widgets/canvas.js b/src/public/app/widgets/type_widgets/canvas.js index f55247e67..168ad4e84 100644 --- a/src/public/app/widgets/type_widgets/canvas.js +++ b/src/public/app/widgets/type_widgets/canvas.js @@ -19,6 +19,7 @@ const TPL = ` display: block; } + .excalidraw-wrapper { height: 100%; } diff --git a/src/public/app/widgets/type_widgets/options/images/images.js b/src/public/app/widgets/type_widgets/options/images/images.js index 38eae0db8..c6ceb6e04 100644 --- a/src/public/app/widgets/type_widgets/options/images/images.js +++ b/src/public/app/widgets/type_widgets/options/images/images.js @@ -48,7 +48,7 @@ export default class ImageOptions extends OptionsWidget { this.updateOption('imageMaxWidthHeight', this.$imageMaxWidthHeight.val())); this.$imageJpegQuality.on('change', () => - this.updateOption('imageJpegQuality', this.$imageJpegQuality.val())); + this.updateOption('imageJpegQuality', this.$imageJpegQuality.val().trim() || "75")); this.$downloadImagesAutomatically = this.$widget.find(".download-images-automatically"); diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 852933d47..9a2150ecb 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -402,6 +402,42 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th .bs-tooltip-left .arrow::before { border-left-color: var(--main-border-color) !important; } .bs-tooltip-right .arrow::before { border-right-color: var(--main-border-color) !important; } +.bs-tooltip-bottom .arrow::after { border-bottom-color: var(--tooltip-background-color) !important; } +.bs-tooltip-top .arrow::after { border-top-color: var(--tooltip-background-color) !important; } +.bs-tooltip-left .arrow::after { border-left-color: var(--tooltip-background-color) !important; } +.bs-tooltip-right .arrow::after { border-right-color: var(--tooltip-background-color) !important; } + +.tooltip .arrow::after { + position: absolute; + content: ''; + border-color: transparent; + border-style: solid; +} + +.bs-tooltip-auto[x-placement^='left'] .arrow::after, +.bs-tooltip-left .arrow::after { + left: -1px; + border-width: 0.4rem 0 0.4rem 0.4rem; +} + +.bs-tooltip-auto[x-placement^='bottom'] .arrow::after, +.bs-tooltip-bottom .arrow::after { + bottom: -1px; + border-width: 0 0.4rem 0.4rem; +} + +.bs-tooltip-auto[x-placement^='right'] .arrow::after, +.bs-tooltip-right .arrow::after { + right: -1px; + border-width: 0.4rem 0.4rem 0.4rem 0; +} + +.bs-tooltip-auto[x-placement^='top'] .arrow::after, +.bs-tooltip-top .arrow::after { + top: -1px; + border-width: 0.4rem 0.4rem 0; +} + .note-tooltip.tooltip .arrow { display: none; } diff --git a/src/services/auth.js b/src/services/auth.js index 331f1d252..8081cd34c 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -110,8 +110,8 @@ function checkCredentials(req, res, next) { const header = req.headers['trilium-cred'] || ''; const auth = new Buffer.from(header, 'base64').toString(); - const [username, password] = auth.split(/:/); - + const colonIndex = auth.indexOf(':'); + const password = colonIndex === -1 ? "" : auth.substr(colonIndex + 1); // username is ignored if (!passwordEncryptionService.verifyPassword(password)) { diff --git a/src/services/build.js b/src/services/build.js index 42ec8bd89..f9256be1b 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2023-04-17T21:40:35+02:00", buildRevision: "1d3272e9f8c27106a66227fbb580677ae5d70427" }; +module.exports = { buildDate:"2023-05-18T23:31:57+02:00", buildRevision: "14dd2b882750ea5484d1aba1f2b57c931bc76e9c" }; diff --git a/src/services/image.js b/src/services/image.js index b26271c23..aafb33289 100644 --- a/src/services/image.js +++ b/src/services/image.js @@ -169,7 +169,7 @@ function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSw } async function shrinkImage(buffer, originalName) { - let jpegQuality = optionService.getOptionInt('imageJpegQuality'); + let jpegQuality = optionService.getOptionInt('imageJpegQuality', 0); if (jpegQuality < 10 || jpegQuality > 100) { jpegQuality = 75; diff --git a/src/services/options.js b/src/services/options.js index 480b1e142..8620d0c21 100644 --- a/src/services/options.js +++ b/src/services/options.js @@ -29,13 +29,17 @@ function getOption(name) { /** * @returns {integer} */ -function getOptionInt(name) { +function getOptionInt(name, defaultValue = undefined) { const val = getOption(name); const intVal = parseInt(val); if (isNaN(intVal)) { - throw new Error(`Could not parse '${val}' into integer for option '${name}'`); + if (defaultValue === undefined) { + throw new Error(`Could not parse '${val}' into integer for option '${name}'`); + } else { + return defaultValue; + } } return intVal;