mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into next61
# Conflicts: # package-lock.json # package.json # src/public/app/components/note_context.js # src/public/app/services/open.js # src/public/app/widgets/buttons/note_actions.js # src/public/app/widgets/find_in_code.js # src/public/app/widgets/type_widgets/canvas.js # src/services/options.js
This commit is contained in:
commit
9e9fb2979f
Binary file not shown.
@ -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",
|
||||
|
@ -57,10 +57,8 @@ export default class RootCommandExecutor extends Component {
|
||||
|
||||
openNoteCustomCommand() {
|
||||
const noteId = appContext.tabManager.getActiveContextNoteId();
|
||||
const mime = appContext.tabManager.getActiveContextNoteMime()
|
||||
|
||||
if (noteId) {
|
||||
openService.openNoteCustom(noteId, mime);
|
||||
openService.openNoteCustom(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,14 +41,19 @@ function downloadAttachment(attachmentId) {
|
||||
download(url);
|
||||
}
|
||||
|
||||
async function openNoteCustom(noteId, mime) {
|
||||
if (utils.isElectron()) {
|
||||
async function openNoteCustom(noteId) {
|
||||
if (!utils.isElectron() || utils.isMac()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = await server.post(`notes/${noteId}/save-to-tmp-dir`);
|
||||
const filePath = resp.tmpFilePath;
|
||||
const { exec } = utils.dynamicRequire('child_process');
|
||||
let 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'];
|
||||
// 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} `);
|
||||
@ -57,15 +62,16 @@ async function openNoteCustom(noteId, mime) {
|
||||
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}`);
|
||||
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 });
|
||||
open(getFileUrl(noteId), {url: true});
|
||||
return;
|
||||
}
|
||||
exec(`which ${terminal}`, (error, stdout, stderr) => {
|
||||
@ -79,23 +85,22 @@ async function openNoteCustom(noteId, mime) {
|
||||
searchTerminal(0);
|
||||
} else if (platform === 'win32') {
|
||||
if (filePath.indexOf("/") !== -1) {
|
||||
//Note that the path separator must be \ instead of /
|
||||
// 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 });
|
||||
open(getFileUrl(noteId), {url: true});
|
||||
return;
|
||||
}
|
||||
});
|
||||
} 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`);
|
||||
|
@ -40,7 +40,7 @@ const TPL = `
|
||||
<kbd data-command="openNoteExternally"></kbd>
|
||||
Open note externally
|
||||
</a>
|
||||
<a data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"><kbd data-command="openNoteCustom"></kbd> Open note custom (beta)</a>
|
||||
<a data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"><kbd data-command="openNoteCustom"></kbd> Open note custom</a>
|
||||
<a class="dropdown-item import-files-button">Import files</a>
|
||||
<a class="dropdown-item export-note-button">Export note</a>
|
||||
<a class="dropdown-item delete-note-button">Delete note</a>
|
||||
@ -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() {
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ const TPL = `
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.excalidraw-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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" };
|
||||
|
@ -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;
|
||||
|
@ -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)) {
|
||||
if (defaultValue === undefined) {
|
||||
throw new Error(`Could not parse '${val}' into integer for option '${name}'`);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return intVal;
|
||||
|
Loading…
x
Reference in New Issue
Block a user