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;