Compare commits

..

8 Commits

Author SHA1 Message Date
zadam
402e5c4d81 release 0.45.3 2020-11-10 22:54:40 +01:00
zadam
5157fc15e9 electron update 2020-11-10 22:54:03 +01:00
zadam
4bd87b1796 update ckeditor5-math which fixes focus and placeholder issues 2020-11-07 21:28:12 +01:00
zadam
ce33eb3abd fix wrong behavior when customResourceProvider has empty value, fixes #1393 2020-11-06 21:52:57 +01:00
zadam
f988935a33 fixed & refactored opening/downloading file notes 2020-11-05 21:26:24 +01:00
zadam
9b05d30b47 update boxicons to 2.0.7 2020-11-03 22:44:50 +01:00
zadam
9e97fdcc49 convert H1 to H2 also during import 2020-11-01 20:38:39 +01:00
zadam
8e8148ce42 fix math rendering in note revisions, fixes #1359 2020-10-30 15:06:11 +01:00
29 changed files with 6136 additions and 6014 deletions

View File

@@ -6,9 +6,6 @@
<option name="TAB_SIZE" value="2" />
</value>
</option>
<H2CodeStyleSettings version="5">
<option name="USE_GENERAL_STYLE" value="false" />
</H2CodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
</JSCodeStyleSettings>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 912 KiB

After

Width:  |  Height:  |  Size: 952 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

56
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.45.1",
"version": "0.45.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -586,11 +586,6 @@
"defer-to-connect": "^1.0.1"
}
},
"@tokenizer/token": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz",
"integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w=="
},
"@tootallnate/once": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz",
@@ -611,7 +606,8 @@
"@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
"integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
"integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==",
"dev": true
},
"@types/eslint": {
"version": "7.2.4",
@@ -2658,9 +2654,9 @@
}
},
"electron": {
"version": "9.3.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.3.3.tgz",
"integrity": "sha512-xghKeUY1qgnEcJ5w2rXo/toH+8NT2Dktx2aAxBNPV7CIJr3mejJJAPwLbycwtddzr37tgKxHeHlc8ivfKtMkJQ==",
"version": "9.3.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.3.4.tgz",
"integrity": "sha512-OHP8qMKgW8D8GtH+altB22WJw/lBOyyVdoz5e8D0/iPBmJU3Jm93vO4z4Eh/9DvdSXlH8bMHUCMLL9PVW6f+tw==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@@ -3680,17 +3676,6 @@
"pend": "~1.2.0"
}
},
"file-type": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.0.0.tgz",
"integrity": "sha512-0u4D11yDu0NXF+8qp1eiQ/6cOwPI7sbu9/bGzUOhlwzKbB9FYFivgTtsVPtlkXAakfByWjOsLp2/Jx927OVetg==",
"requires": {
"readable-web-to-node-stream": "^2.0.0",
"strtok3": "^6.0.3",
"token-types": "^2.0.0",
"typedarray-to-buffer": "^3.1.5"
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -5882,11 +5867,6 @@
"pify": "^2.0.0"
}
},
"peek-readable": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.0.tgz",
"integrity": "sha512-KGuODSTV6hcgdZvDrIDBUkN0utcAVj1LL7FfGbM0viKTtCHmtZcuEJ+lGqsp0fTFkGqesdtemV2yUSMeyy3ddA=="
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -6229,11 +6209,6 @@
"util-deprecate": "~1.0.1"
}
},
"readable-web-to-node-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-2.0.0.tgz",
"integrity": "sha512-+oZJurc4hXpaaqsN68GoZGQAQIA3qr09Or4fqEsargABnbe5Aau8hFn6ISVleT3cpY/0n/8drn7huyyEvTbghA=="
},
"rechoir": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz",
@@ -7005,16 +6980,6 @@
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
"integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
},
"strtok3": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.0.4.tgz",
"integrity": "sha512-rqWMKwsbN9APU47bQTMEYTPcwdpKDtmf1jVhHzNW2cL1WqAxaM9iBb9t5P2fj+RV2YsErUWgQzHD5JwV0uCTEQ==",
"requires": {
"@tokenizer/token": "^0.1.1",
"@types/debug": "^4.1.5",
"peek-readable": "^3.1.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -7293,15 +7258,6 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"token-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-2.0.0.tgz",
"integrity": "sha512-WWvu8sGK8/ZmGusekZJJ5NM6rRVTTDO7/bahz4NGiSDb/XsmdYBn6a1N/bymUHuWYTWeuLUg98wUzvE4jPdCZw==",
"requires": {
"@tokenizer/token": "^0.1.0",
"ieee754": "^1.1.13"
}
},
"tough-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.45.2",
"version": "0.45.3",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -76,7 +76,7 @@
},
"devDependencies": {
"cross-env": "7.0.2",
"electron": "9.3.3",
"electron": "9.3.4",
"electron-builder": "22.9.1",
"electron-packager": "15.1.0",
"electron-rebuild": "2.3.2",

View File

@@ -3,6 +3,7 @@ import utils from "../services/utils.js";
import ws from "../services/ws.js";
import toastService from "../services/toast.js";
import treeCache from "../services/tree_cache.js";
import openService from "../services/open.js";
const $dialog = $("#export-dialog");
const $form = $("#export-form");
@@ -73,9 +74,9 @@ $form.on('submit', () => {
function exportBranch(branchId, type, format, version) {
taskId = utils.randomString(10);
const url = utils.getUrlForDownload(`api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`);
const url = openService.getUrlForDownload(`api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`);
utils.download(url);
openService.download(url);
}
$('input[name=export-type]').on('change', function () {
@@ -133,4 +134,4 @@ ws.subscribeToMessages(async message => {
toastService.showPersistent(toast);
}
});
});

View File

@@ -2,6 +2,8 @@ import utils from '../services/utils.js';
import server from '../services/server.js';
import toastService from "../services/toast.js";
import appContext from "../services/app_context.js";
import libraryLoader from "../services/library_loader.js";
import openService from "../services/open.js";
const $dialog = $("#note-revisions-dialog");
const $list = $("#note-revision-list");
@@ -120,11 +122,7 @@ async function setContentPane() {
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
$downloadButton.on('click', () => {
const url = utils.getUrlForDownload(`api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`);
utils.download(url);
});
$downloadButton.on('click', () => openService.downloadNoteRevision(revisionItem.noteId, revisionItem.noteRevisionId));
$titleButtons.append($downloadButton);
@@ -132,6 +130,12 @@ async function setContentPane() {
if (revisionItem.type === 'text') {
$content.html(fullNoteRevision.content);
if ($content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement($content[0], {});
}
}
else if (revisionItem.type === 'code') {
$content.html($("<pre>").text(fullNoteRevision.content));

View File

@@ -81,7 +81,7 @@ export default class KeyboardShortcutsOptions {
.filter(shortcut => !!shortcut);
const opts = {};
opts['keyboardShortcuts' + actionName] = JSON.stringify(shortcuts);
opts['keyboardShortcuts' + actionName.substr(0, 1).toUpperCase() + actionName.substr(1)] = JSON.stringify(shortcuts);
server.put('options', opts);
});
@@ -138,4 +138,4 @@ export default class KeyboardShortcutsOptions {
});
});
}
}
}

View File

@@ -60,7 +60,7 @@ class NoteShort {
/** @param {string} content-type, e.g. "application/json" */
this.mime = row.mime;
/** @param {boolean} */
this.isDeleted = row.isDeleted;
this.isDeleted = !!row.isDeleted;
}
addParent(parentNoteId, branchId) {

View File

@@ -1,9 +1,9 @@
import server from "./server.js";
import utils from "./utils.js";
import renderService from "./render.js";
import protectedSessionService from "./protected_session.js";
import protectedSessionHolder from "./protected_session_holder.js";
import libraryLoader from "./library_loader.js";
import openService from "./open.js";
async function getRenderedContent(note) {
const type = getRenderingType(note);
@@ -32,24 +32,11 @@ async function getRenderedContent(note) {
.css("max-width", "100%");
}
else if (type === 'file' || type === 'pdf') {
function getFileUrl() {
return utils.getUrlForDownload("api/notes/" + note.noteId + "/download");
}
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
$downloadButton.on('click', () => utils.download(getFileUrl()));
$openButton.on('click', () => {
if (utils.isElectron()) {
const open = utils.dynamicRequire("open");
open(getFileUrl(), {url: true});
}
else {
window.location.href = getFileUrl();
}
});
$downloadButton.on('click', () => openService.downloadFileNote(note.noteId));
$openButton.on('click', () => openService.openFileNote(note.noteId));
// open doesn't work for protected notes since it works through browser which isn't in protected session
$openButton.toggle(!note.isProtected);
@@ -58,7 +45,7 @@ async function getRenderedContent(note) {
if (type === 'pdf') {
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
$pdfPreview.attr("src", utils.getUrlForDownload("api/notes/" + note.noteId + "/open"));
$pdfPreview.attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open"));
$rendered.append($pdfPreview);
}

View File

@@ -0,0 +1,71 @@
import utils from "./utils.js";
import server from "./server.js";
function getFileUrl(noteId) {
return getUrlForDownload("api/notes/" + noteId + "/download");
}
function download(url) {
if (utils.isElectron()) {
const remote = utils.dynamicRequire('electron').remote;
remote.getCurrentWebContents().downloadURL(url);
} else {
window.location.href = url;
}
}
function downloadFileNote(noteId) {
const url = getFileUrl(noteId) + '?' + Date.now(); // don't use cache
download(url);
}
async function openFileNote(noteId) {
if (utils.isElectron()) {
const resp = await server.post("notes/" + noteId + "/saveToTmpDir");
const electron = utils.dynamicRequire('electron');
const res = await electron.shell.openPath(resp.tmpFilePath);
if (res) {
// fallback in case there's no default application for this file
open(getFileUrl(noteId), {url: true});
}
}
else {
window.location.href = getFileUrl(noteId);
}
}
function downloadNoteRevision(noteId, noteRevisionId) {
const url = getUrlForDownload(`api/notes/${noteId}/revisions/${noteRevisionId}/download`);
download(url);
}
/**
* @param url - should be without initial slash!!!
*/
function getUrlForDownload(url) {
if (utils.isElectron()) {
// electron needs absolute URL so we extract current host, port, protocol
return getHost() + '/' + url;
}
else {
// web server can be deployed on subdomain so we need to use relative path
return url;
}
}
function getHost() {
const url = new URL(window.location.href);
return url.protocol + "//" + url.hostname + ":" + url.port;
}
export default {
downloadFileNote,
openFileNote,
downloadNoteRevision,
getUrlForDownload
}

View File

@@ -105,24 +105,6 @@ function formatLabel(label) {
return str;
}
function getHost() {
const url = new URL(window.location.href);
return url.protocol + "//" + url.hostname + ":" + url.port;
}
function download(url) {
url += '?' + Date.now(); // don't use cache
if (isElectron()) {
const remote = dynamicRequire('electron').remote;
remote.getCurrentWebContents().downloadURL(url);
}
else {
window.location.href = url;
}
}
function toObject(array, fn) {
const obj = {};
@@ -294,20 +276,6 @@ async function clearBrowserCache() {
}
}
/**
* @param url - should be without initial slash!!!
*/
function getUrlForDownload(url) {
if (isElectron()) {
// electron needs absolute URL so we extract current host, port, protocol
return getHost() + '/' + url;
}
else {
// web server can be deployed on subdomain so we need to use relative path
return url;
}
}
function copySelectionToClipboard() {
const text = window.getSelection().toString();
if (navigator.clipboard) {
@@ -366,7 +334,6 @@ export default {
escapeHtml,
stopWatch,
formatLabel,
download,
toObject,
randomString,
bindGlobalShortcut,
@@ -384,7 +351,6 @@ export default {
focusSavedElement,
isHtmlEmpty,
clearBrowserCache,
getUrlForDownload,
normalizeShortcut,
copySelectionToClipboard,
isCKEditorInitialized,

View File

@@ -1,4 +1,5 @@
import utils from "../../services/utils.js";
import openService from "../../services/open.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import TypeWidget from "./type_widget.js";
@@ -73,24 +74,8 @@ export default class FileTypeWidget extends TypeWidget {
this.$uploadNewRevisionButton = this.$widget.find(".file-upload-new-revision");
this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input");
this.$downloadButton.on('click', () => utils.download(this.getFileUrl()));
this.$openButton.on('click', async () => {
if (utils.isElectron()) {
const resp = await server.post("notes/" + this.noteId + "/saveToTmpDir");
const electron = utils.dynamicRequire('electron');
const res = await electron.shell.openPath(resp.tmpFilePath);
if (res) {
// fallback in case there's no default application for this file
open(this.getFileUrl(), {url: true});
}
}
else {
window.location.href = this.getFileUrl();
}
});
this.$downloadButton.on('click', () => openService.downloadFileNote(this.noteId));
this.$openButton.on('click', () => openService.openFileNote(this.noteId));
this.$uploadNewRevisionButton.on("click", () => {
this.$uploadNewRevisionInput.trigger("click");
@@ -146,14 +131,10 @@ export default class FileTypeWidget extends TypeWidget {
}
else if (note.mime === 'application/pdf') {
this.$pdfPreview.show();
this.$pdfPreview.attr("src", utils.getUrlForDownload("api/notes/" + this.noteId + "/open"));
this.$pdfPreview.attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open"));
}
// open doesn't work for protected notes since it works through browser which isn't in protected session
this.$openButton.toggle(!note.isProtected);
}
getFileUrl() {
return utils.getUrlForDownload("api/notes/" + this.noteId + "/download");
}
}

View File

@@ -1,6 +1,7 @@
import utils from "../../services/utils.js";
import toastService from "../../services/toast.js";
import server from "../../services/server.js";
import openService from "../../services/open.js";
import TypeWidget from "./type_widget.js";
const TPL = `
@@ -64,7 +65,7 @@ class ImageTypeWidget extends TypeWidget {
this.$fileSize = this.$widget.find(".image-filesize");
this.$imageDownloadButton = this.$widget.find(".image-download");
this.$imageDownloadButton.on('click', () => utils.download(this.getFileUrl()));
this.$imageDownloadButton.on('click', () => openService.downloadFileNote(this.noteId));
this.$copyToClipboardButton.on('click',() => {
this.$imageWrapper.attr('contenteditable','true');
@@ -145,10 +146,6 @@ class ImageTypeWidget extends TypeWidget {
selection.removeAllRanges();
selection.addRange(range);
}
getFileUrl() {
return utils.getUrlForDownload(`api/notes/${this.noteId}/download`);
}
}
export default ImageTypeWidget

View File

@@ -59,7 +59,7 @@ ul.fancytree-container {
font-size: x-large;
text-transform: none;
line-height: 1;
content: "\ea1d";
content: "\e9b2";
position: relative;
top: -2px;
margin-right: 5px;
@@ -72,7 +72,7 @@ ul.fancytree-container {
.fancytree-node.fancytree-expanded .fancytree-expander:before {
font-family: 'boxicons' !important;
content: "\ea17";
content: "\e9ac";
}
/** some common text styling for cssClass label */

View File

@@ -12,6 +12,10 @@ async function handleRequest(req, res) {
const attrs = repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
for (const attr of attrs) {
if (!attr.value.trim()) {
continue;
}
const regex = new RegExp(attr.value);
let match;

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-10-29T22:57:25+01:00", buildRevision: "4f75b6aaafef8144080fd17f403a605f61f5590d" };
module.exports = { buildDate:"2020-11-10T22:54:39+01:00", buildRevision: "5157fc15e9f7fa960ee35685426868d5599933dc" };

View File

@@ -5,8 +5,7 @@ const sanitizeHtml = require('sanitize-html');
function sanitize(dirtyHtml) {
return sanitizeHtml(dirtyHtml, {
allowedTags: [
// h1 is removed since that should be note's title
'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'li', 'b', 'i', 'strong', 'em', 'strike', 'abbr', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'span', 'label', 'input'

View File

@@ -117,6 +117,8 @@ function convertTextToHtml(text) {
}
function importMarkdown(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const markdownContent = file.buffer.toString("UTF-8");
const reader = new commonmark.Parser();
@@ -127,7 +129,7 @@ function importMarkdown(taskContext, file, parentNote) {
htmlContent = htmlSanitizer.sanitize(htmlContent);
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
htmlContent = handleH1(htmlContent, title);
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
@@ -143,12 +145,25 @@ function importMarkdown(taskContext, file, parentNote) {
return note;
}
function handleH1(content, title) {
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
if (title.trim() === text.trim()) {
return ""; // remove whole H1 tag
} else {
return `<h2>${text}</h2>`;
}
});
return content;
}
function importHtml(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
let content = file.buffer.toString("UTF-8");
content = htmlSanitizer.sanitize(content);
content = handleH1(content, title);
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,

View File

@@ -275,7 +275,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
return ""; // remove whole H1 tag
}
else {
return match;
return `<h2>${text}</h2>`;
}
});

View File

@@ -365,7 +365,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
},
{
actionName: "openDevTools",
defaultShortcuts: ["CommandOrControl+Shift+I"],
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+I"] : [],
scope: "window"
},
{
@@ -408,13 +408,7 @@ for (const action of DEFAULT_KEYBOARD_ACTIONS) {
}
}
let cachedActions = null;
function getKeyboardActions() {
if (cachedActions) {
return cachedActions;
}
const actions = JSON.parse(JSON.stringify(DEFAULT_KEYBOARD_ACTIONS));
for (const action of actions) {
@@ -442,8 +436,6 @@ function getKeyboardActions() {
}
}
cachedActions = actions;
return actions;
}

View File

@@ -687,7 +687,7 @@ function eraseDeletedNotes() {
sql.executeMany(`
UPDATE notes
SET title = '[deleted]',
SET title = '[erased]',
isProtected = 0,
isErased = 1
WHERE noteId IN (???)`, noteIdsToErase);