Compare commits

...

23 Commits

Author SHA1 Message Date
zadam
47d61c416d release 0.45.4 2020-11-12 22:15:23 +01:00
zadam
6c57b2220f fix export download, fixes #1411 2020-11-12 22:13:59 +01:00
zadam
99f01b9ccf fix overwriting / deleting auto links, closes #1406 2020-11-11 23:15:48 +01:00
zadam
d5a9abd911 fix duplicating relations after change, closes #1405 2020-11-11 23:02:14 +01:00
zadam
a3a2bc0a74 fix "reviving" deleted attributes, closes #1404 2020-11-11 22:44:13 +01:00
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
zadam
af41e5d115 release 0.45.2 2020-10-29 22:57:25 +01:00
zadam
4f75b6aaaf fix removing stale branches from saved search after refresh, fixes #1354 2020-10-29 22:41:33 +01:00
zadam
82f410f695 fix math rendering in included note and note tooltip, fixes #1340 2020-10-29 21:06:30 +01:00
zadam
2bc06959c3 add a warning to change password dialog, fixes #1344 2020-10-29 20:57:26 +01:00
zadam
b898973ee6 fixed update ckeditor to 23.1.0 2020-10-29 20:09:25 +01:00
zadam
a2b0d8a379 update ckeditor to 23.1.0 2020-10-29 20:02:38 +01:00
zadam
06a4eab7d5 improved detection of image notes in ENEX import, fixes #1348 2020-10-28 23:36:45 +01:00
zadam
25df1a054c fix triggering change event when item is chosen from autocomplete, fixes #1345 2020-10-28 21:48:34 +01:00
zadam
8c4ff7ed2a fix using smart values with .dateCreated, closes #1338 2020-10-27 22:45:22 +01:00
zadam
609829653e fix docker build 2020-10-27 19:39:54 +01:00
44 changed files with 6224 additions and 6075 deletions

View File

@@ -9,23 +9,5 @@
<JSCodeStyleSettings version="0">
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
</JSCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
</JetCodeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,4 +1,4 @@
FROM node:12.16.3-alpine
FROM node:12.19.0-alpine
# Create app directory
WORKDIR /usr/src/app

View File

@@ -5,7 +5,7 @@ SERIES=${VERSION:0:4}-latest
cat package.json | grep -v electron > server-package.json
sudo docker build -t zadam/trilium:$VERSION -t zadam/trilium:$SERIES .
sudo docker build -t zadam/trilium:$VERSION --network host -t zadam/trilium:$SERIES .
if [[ $VERSION != *"beta"* ]]; then
sudo docker tag zadam/trilium:$VERSION zadam/trilium:latest

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.0-beta",
"version": "0.45.3",
"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.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.3.2.tgz",
"integrity": "sha512-0lleEf9msAXGDi2GukAuiGdw3VDgSTlONOnJgqDEz1fuSEVsXz5RX+hNPKDsVDerLTFg/C34RuJf4LwHvkKcBA==",
"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.1",
"version": "0.45.4",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -40,7 +40,6 @@
"electron-window-state": "5.0.3",
"express": "4.17.1",
"express-session": "1.17.1",
"file-type": "16.0.0",
"fs-extra": "9.0.1",
"helmet": "4.1.1",
"html": "1.0.0",
@@ -77,7 +76,7 @@
},
"devDependencies": {
"cross-env": "7.0.2",
"electron": "9.3.2",
"electron": "9.3.4",
"electron-builder": "22.9.1",
"electron-packager": "15.1.0",
"electron-rebuild": "2.3.2",

View File

@@ -57,8 +57,15 @@ function id() {
return randtoken.generate(10);
}
function note(title, type = 'text', mime = 'text/html') {
const note = new Note(noteCache, {noteId: id(), title, type, mime});
function note(title, extraParams = {}) {
const row = Object.assign({
noteId: id(),
title: title,
type: 'text',
mime: 'text/html'
}, extraParams);
const note = new Note(noteCache, row);
return new NoteBuilder(note);
}

View File

@@ -247,6 +247,6 @@ describe("Invalid expressions", () => {
searchContext
});
expect(searchContext.error).toEqual('Misplaced or incomplete expression "="')
expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""')
});
});

View File

@@ -53,8 +53,8 @@ describe("Search", () => {
it("normal search looks also at type and mime", () => {
rootNote
.child(note("Effective Java", 'book', ''))
.child(note("Hello World.java", 'code', 'text/x-java'));
.child(note("Effective Java", {type: 'book', mime:''}))
.child(note("Hello World.java", {type: 'code', mime: 'text/x-java'}));
const searchContext = new SearchContext();
let searchResults = searchService.findNotesWithQuery('book', searchContext);
@@ -178,7 +178,7 @@ describe("Search", () => {
// dates should not be coerced into numbers which would then give wrong numbers
rootNote
.child(note("My note")
.child(note("My note", {dateCreated: dateUtils.localNowDateTime()})
.label('year', new Date().getFullYear().toString())
.label('month', dateUtils.localNowDate().substr(0, 7))
.label('date', dateUtils.localNowDate())
@@ -209,6 +209,8 @@ describe("Search", () => {
test("#month = month", 1);
test("#month = 'MONTH'", 0);
test("note.dateCreated =* month", 1);
test("#date = TODAY", 1);
test("#date = today", 1);
test("#date = 'today'", 0);
@@ -586,7 +588,7 @@ describe("Search", () => {
const searchContext = new SearchContext();
let searchResults = searchService.findNotesWithQuery('# note.text *=* rati and note.noteId != root', searchContext);
let searchResults = searchService.findNotesWithQuery('# note.text *=* vaki and note.noteId != root', searchContext);
expect(searchResults.length).toEqual(1);
expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Slovakia");
});

View File

@@ -34,6 +34,10 @@ class Attribute extends Entity {
this.isInheritable = !!this.isInheritable;
}
isAutoLink() {
return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name);
}
/**
* @returns {Note|null}
*/

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

@@ -8,6 +8,11 @@ const TPL = `
<p>Your username is <strong id="credentials-username"></strong>.</p>
<h3>Change password</h3>
<div class="alert alert-warning" role="alert" style="font-weight: bold; color: red !important;">
Please take care to remember your new password. Password is used to encrypt protected notes. If you forget your password, then all your protected notes are forever lost with no recovery options.
</div>
<form id="change-password-form">
<div class="form-group">
<label for="old-password">Old password</label>

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,8 +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);
@@ -13,6 +14,12 @@ async function getRenderedContent(note) {
const fullNote = await server.get('notes/' + note.noteId);
$rendered = $('<div class="ck-content">').html(fullNote.content);
if ($rendered.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement($rendered[0], {});
}
}
else if (type === 'code') {
const fullNote = await server.get('notes/' + note.noteId);
@@ -25,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);
@@ -51,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

@@ -3,6 +3,7 @@ import linkService from "./link.js";
import treeCache from "./tree_cache.js";
import utils from "./utils.js";
import attributeRenderer from "./attribute_renderer.js";
import libraryLoader from "./library_loader.js";
function setupGlobalTooltip() {
$(document).on("mouseenter", "a", mouseEnterHandler);
@@ -101,7 +102,15 @@ async function renderTooltip(note, noteComplement) {
}
if (note.type === 'text' && !utils.isHtmlEmpty(noteComplement.content)) {
content += '<div class="ck-content">' + noteComplement.content + '</div>';
const $content = $('<div class="ck-content">').append(noteComplement.content);
if ($content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement($content[0], {});
}
content += $content[0].outerHTML;
}
else if (note.type === 'code' && noteComplement.content && noteComplement.content.trim()) {
content += $("<pre>")

View File

@@ -0,0 +1,72 @@
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 {
download,
downloadFileNote,
openFileNote,
downloadNoteRevision,
getUrlForDownload
}

View File

@@ -54,7 +54,7 @@ function closePersistent(id) {
}
function showMessage(message, delay = 2000) {
console.debug(utils.now(), "message: ", message);
console.debug(utils.now(), "message:", message);
toast({
title: "Info",

View File

@@ -203,6 +203,11 @@ class TreeCache {
// force to load all the notes at once instead of one by one
await this.getNotes(searchResultNoteIds);
// reset all the virtual branches from old search results
if (note.noteId in treeCache.notes) {
treeCache.notes[note.noteId].children = [];
}
const branches = resp.branches.filter(b => b.noteId === note.noteId || b.parentNoteId === note.noteId);
searchResultNoteIds.forEach((resultNoteId, index) => branches.push({

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

@@ -154,7 +154,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
}
}]);
$input.on('autocomplete:noteselected', e => this.promotedAttributeChanged(e))
$input.on('autocomplete:selected', e => this.promotedAttributeChanged(e))
});
}
else if (definition.labelType === 'number') {

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

@@ -28,8 +28,10 @@ function updateNoteAttribute(req) {
|| body.name !== attribute.name
|| (body.type === 'relation' && body.value !== attribute.value)) {
let newAttribute;
if (body.type !== 'relation' || !!body.value.trim()) {
const newAttribute = attribute.createClone(body.type, body.name, body.value);
newAttribute = attribute.createClone(body.type, body.name, body.value);
newAttribute.save();
}
@@ -37,7 +39,7 @@ function updateNoteAttribute(req) {
attribute.save();
return {
attributeId: attribute.attributeId
attributeId: newAttribute ? newAttribute.attributeId : null
};
}
}
@@ -52,8 +54,9 @@ function updateNoteAttribute(req) {
attribute.type = body.type;
}
if (body.value.trim()) {
if (body.type !== 'relation' || body.value.trim()) {
attribute.value = body.value;
attribute.isDeleted = false;
}
else {
// relations should never have empty target
@@ -144,8 +147,10 @@ function updateNoteAttributes(req) {
// all the remaining existing attributes are not defined anymore and should be deleted
for (const toDeleteAttr of existingAttrs) {
toDeleteAttr.isDeleted = true;
toDeleteAttr.save();
if (!toDeleteAttr.isAutoLink()) {
toDeleteAttr.isDeleted = true;
toDeleteAttr.save();
}
}
}

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-26T22:51:10+01:00", buildRevision: "93d0324177a34cb79caf410ff7fc14a18c4f2633" };
module.exports = { buildDate:"2020-11-12T22:15:23+01:00", buildRevision: "6c57b2220ff05059d7460369b195d281fcd9cbb6" };

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

@@ -1,5 +1,4 @@
const sax = require("sax");
const FileType = require('file-type');
const stream = require('stream');
const log = require("../log");
const utils = require("../utils");
@@ -138,17 +137,6 @@ function importEnex(taskContext, file, parentNote) {
}
else if (currentTag === 'mime') {
resource.mime = text.toLowerCase();
if (text.startsWith("image/")) {
resource.title = "image";
// images don't have "file-name" tag so we'll create attribute here
resource.attributes.push({
type: 'label',
name: 'originalFileName',
value: resource.title + "." + text.substr(6) // extension from mime type
});
}
}
}
else if (previousTag === 'note') {
@@ -243,11 +231,7 @@ function importEnex(taskContext, file, parentNote) {
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
const fileTypeFromBuffer = FileType.fromBuffer(resource.content);
if (fileTypeFromBuffer) {
// If fileType returns something for buffer, then set the mime given
resource.mime = fileTypeFromBuffer.mime;
}
resource.mime = resource.mime || "application/octet-stream";
const createFileNote = () => {
const resourceNote = noteService.createNewNote({
@@ -260,7 +244,7 @@ function importEnex(taskContext, file, parentNote) {
}).note;
for (const attr of resource.attributes) {
noteEntity.addAttribute(attr.type, attr.name, attr.value);
resourceNote.addAttribute(attr.type, attr.name, attr.value);
}
updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
@@ -274,10 +258,18 @@ function importEnex(taskContext, file, parentNote) {
if (resource.mime && resource.mime.startsWith('image/')) {
try {
const originalName = "image." + resource.mime.substr(6);
const originalName = (resource.title && resource.title !== 'resource')
? resource.title
: `image.${resource.mime.substr(6)}`; // default if real name is not present
const {url, note: imageNote} = imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
for (const attr of resource.attributes) {
if (attr.name !== 'originalFileName') { // this one is already saved in imageService
imageNote.addAttribute(attr.type, attr.name, attr.value);
}
}
updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
const imageLink = `<img src="${url}">`;

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);

View File

@@ -5,9 +5,9 @@ const stringComparators = {
">=": comparedValue => (val => val >= comparedValue),
"<": comparedValue => (val => val < comparedValue),
"<=": comparedValue => (val => val <= comparedValue),
"*=": comparedValue => (val => val.endsWith(comparedValue)),
"=*": comparedValue => (val => val.startsWith(comparedValue)),
"*=*": comparedValue => (val => val.includes(comparedValue)),
"*=": comparedValue => (val => val && val.endsWith(comparedValue)),
"=*": comparedValue => (val => val && val.startsWith(comparedValue)),
"*=*": comparedValue => (val => val && val.includes(comparedValue)),
};
const numericComparators = {

View File

@@ -80,10 +80,14 @@ function getExpression(tokens, searchContext, level = 0) {
if (i + 2 < tokens.length) {
if (tokens[i + 1].token === '+') {
delta += parseInt(tokens[i + 2].token);
i += 2;
delta += parseInt(tokens[i].token);
}
else if (tokens[i + 1].token === '-') {
delta -= parseInt(tokens[i + 2].token);
i += 2;
delta -= parseInt(tokens[i].token);
}
}
@@ -196,16 +200,18 @@ function getExpression(tokens, searchContext, level = 0) {
if (PropertyComparisonExp.isProperty(tokens[i].token)) {
const propertyName = tokens[i].token;
const operator = tokens[i + 1].token;
const comparedValue = tokens[i + 2].token;
i += 2;
const comparedValue = resolveConstantOperand();
const comparator = buildComparator(operator, comparedValue);
if (!comparator) {
searchContext.addError(`Can't find operator '${operator}' in ${context(i)}`);
searchContext.addError(`Can't find operator '${operator}' in ${context(i - 2)}`);
return;
}
i += 2;
return new PropertyComparisonExp(propertyName, comparator);
}