mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
Merge remote-tracking branch 'origin/stable'
# Conflicts: # src/public/app/services/note_content_renderer.js
This commit is contained in:
commit
1403acd808
File diff suppressed because it is too large
Load Diff
2
libraries/boxicons/css/boxicons.min.css
vendored
2
libraries/boxicons/css/boxicons.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 912 KiB After Width: | Height: | Size: 952 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,6 +3,7 @@ import utils from "../services/utils.js";
|
|||||||
import ws from "../services/ws.js";
|
import ws from "../services/ws.js";
|
||||||
import toastService from "../services/toast.js";
|
import toastService from "../services/toast.js";
|
||||||
import treeCache from "../services/tree_cache.js";
|
import treeCache from "../services/tree_cache.js";
|
||||||
|
import openService from "../services/open.js";
|
||||||
|
|
||||||
const $dialog = $("#export-dialog");
|
const $dialog = $("#export-dialog");
|
||||||
const $form = $("#export-form");
|
const $form = $("#export-form");
|
||||||
@ -73,9 +74,9 @@ $form.on('submit', () => {
|
|||||||
function exportBranch(branchId, type, format, version) {
|
function exportBranch(branchId, type, format, version) {
|
||||||
taskId = utils.randomString(10);
|
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 () {
|
$('input[name=export-type]').on('change', function () {
|
||||||
@ -133,4 +134,4 @@ ws.subscribeToMessages(async message => {
|
|||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import server from '../services/server.js';
|
|||||||
import toastService from "../services/toast.js";
|
import toastService from "../services/toast.js";
|
||||||
import appContext from "../services/app_context.js";
|
import appContext from "../services/app_context.js";
|
||||||
import libraryLoader from "../services/library_loader.js";
|
import libraryLoader from "../services/library_loader.js";
|
||||||
|
import openService from "../services/open.js";
|
||||||
|
|
||||||
const $dialog = $("#note-revisions-dialog");
|
const $dialog = $("#note-revisions-dialog");
|
||||||
const $list = $("#note-revision-list");
|
const $list = $("#note-revision-list");
|
||||||
@ -121,11 +122,7 @@ async function setContentPane() {
|
|||||||
|
|
||||||
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
|
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
|
||||||
|
|
||||||
$downloadButton.on('click', () => {
|
$downloadButton.on('click', () => openService.downloadNoteRevision(revisionItem.noteId, revisionItem.noteRevisionId));
|
||||||
const url = utils.getUrlForDownload(`api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`);
|
|
||||||
|
|
||||||
utils.download(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
$titleButtons.append($downloadButton);
|
$titleButtons.append($downloadButton);
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ export default class KeyboardShortcutsOptions {
|
|||||||
.filter(shortcut => !!shortcut);
|
.filter(shortcut => !!shortcut);
|
||||||
|
|
||||||
const opts = {};
|
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);
|
server.put('options', opts);
|
||||||
});
|
});
|
||||||
@ -138,4 +138,4 @@ export default class KeyboardShortcutsOptions {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ class NoteShort {
|
|||||||
/** @param {string} content-type, e.g. "application/json" */
|
/** @param {string} content-type, e.g. "application/json" */
|
||||||
this.mime = row.mime;
|
this.mime = row.mime;
|
||||||
/** @param {boolean} */
|
/** @param {boolean} */
|
||||||
this.isDeleted = row.isDeleted;
|
this.isDeleted = !!row.isDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
addParent(parentNoteId, branchId) {
|
addParent(parentNoteId, branchId) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import utils from "./utils.js";
|
|
||||||
import renderService from "./render.js";
|
import renderService from "./render.js";
|
||||||
import protectedSessionService from "./protected_session.js";
|
import protectedSessionService from "./protected_session.js";
|
||||||
import protectedSessionHolder from "./protected_session_holder.js";
|
import protectedSessionHolder from "./protected_session_holder.js";
|
||||||
import libraryLoader from "./library_loader.js";
|
import libraryLoader from "./library_loader.js";
|
||||||
|
import openService from "./open.js";
|
||||||
|
|
||||||
async function getRenderedContent(note, options = {}) {
|
async function getRenderedContent(note, options = {}) {
|
||||||
options = Object.assign({
|
options = Object.assign({
|
||||||
@ -36,24 +36,11 @@ async function getRenderedContent(note, options = {}) {
|
|||||||
.css("max-width", "100%");
|
.css("max-width", "100%");
|
||||||
}
|
}
|
||||||
else if (type === 'file' || type === 'pdf') {
|
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 $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>');
|
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
||||||
|
|
||||||
$downloadButton.on('click', () => utils.download(getFileUrl()));
|
$downloadButton.on('click', () => openService.downloadFileNote(note.noteId));
|
||||||
$openButton.on('click', () => {
|
$openButton.on('click', () => openService.openFileNote(note.noteId));
|
||||||
if (utils.isElectron()) {
|
|
||||||
const open = utils.dynamicRequire("open");
|
|
||||||
|
|
||||||
open(getFileUrl(), {url: true});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
window.location.href = getFileUrl();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||||
$openButton.toggle(!note.isProtected);
|
$openButton.toggle(!note.isProtected);
|
||||||
@ -62,7 +49,7 @@ async function getRenderedContent(note, options = {}) {
|
|||||||
|
|
||||||
if (type === 'pdf') {
|
if (type === 'pdf') {
|
||||||
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
|
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);
|
$rendered.append($pdfPreview);
|
||||||
}
|
}
|
||||||
|
71
src/public/app/services/open.js
Normal file
71
src/public/app/services/open.js
Normal 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
|
||||||
|
}
|
@ -105,24 +105,6 @@ function formatLabel(label) {
|
|||||||
return str;
|
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) {
|
function toObject(array, fn) {
|
||||||
const obj = {};
|
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() {
|
function copySelectionToClipboard() {
|
||||||
const text = window.getSelection().toString();
|
const text = window.getSelection().toString();
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
@ -366,7 +334,6 @@ export default {
|
|||||||
escapeHtml,
|
escapeHtml,
|
||||||
stopWatch,
|
stopWatch,
|
||||||
formatLabel,
|
formatLabel,
|
||||||
download,
|
|
||||||
toObject,
|
toObject,
|
||||||
randomString,
|
randomString,
|
||||||
bindGlobalShortcut,
|
bindGlobalShortcut,
|
||||||
@ -384,7 +351,6 @@ export default {
|
|||||||
focusSavedElement,
|
focusSavedElement,
|
||||||
isHtmlEmpty,
|
isHtmlEmpty,
|
||||||
clearBrowserCache,
|
clearBrowserCache,
|
||||||
getUrlForDownload,
|
|
||||||
normalizeShortcut,
|
normalizeShortcut,
|
||||||
copySelectionToClipboard,
|
copySelectionToClipboard,
|
||||||
isCKEditorInitialized,
|
isCKEditorInitialized,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
|
import openService from "../../services/open.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import toastService from "../../services/toast.js";
|
import toastService from "../../services/toast.js";
|
||||||
import TypeWidget from "./type_widget.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.$uploadNewRevisionButton = this.$widget.find(".file-upload-new-revision");
|
||||||
this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input");
|
this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input");
|
||||||
|
|
||||||
this.$downloadButton.on('click', () => utils.download(this.getFileUrl()));
|
this.$downloadButton.on('click', () => openService.downloadFileNote(this.noteId));
|
||||||
|
this.$openButton.on('click', () => openService.openFileNote(this.noteId));
|
||||||
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.$uploadNewRevisionButton.on("click", () => {
|
this.$uploadNewRevisionButton.on("click", () => {
|
||||||
this.$uploadNewRevisionInput.trigger("click");
|
this.$uploadNewRevisionInput.trigger("click");
|
||||||
@ -146,14 +131,10 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
else if (note.mime === 'application/pdf') {
|
else if (note.mime === 'application/pdf') {
|
||||||
this.$pdfPreview.show();
|
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
|
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||||
this.$openButton.toggle(!note.isProtected);
|
this.$openButton.toggle(!note.isProtected);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileUrl() {
|
|
||||||
return utils.getUrlForDownload("api/notes/" + this.noteId + "/download");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
import toastService from "../../services/toast.js";
|
import toastService from "../../services/toast.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
|
import openService from "../../services/open.js";
|
||||||
import TypeWidget from "./type_widget.js";
|
import TypeWidget from "./type_widget.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
@ -64,7 +65,7 @@ class ImageTypeWidget extends TypeWidget {
|
|||||||
this.$fileSize = this.$widget.find(".image-filesize");
|
this.$fileSize = this.$widget.find(".image-filesize");
|
||||||
|
|
||||||
this.$imageDownloadButton = this.$widget.find(".image-download");
|
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.$copyToClipboardButton.on('click',() => {
|
||||||
this.$imageWrapper.attr('contenteditable','true');
|
this.$imageWrapper.attr('contenteditable','true');
|
||||||
@ -145,10 +146,6 @@ class ImageTypeWidget extends TypeWidget {
|
|||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileUrl() {
|
|
||||||
return utils.getUrlForDownload(`api/notes/${this.noteId}/download`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageTypeWidget
|
export default ImageTypeWidget
|
||||||
|
@ -59,7 +59,7 @@ ul.fancytree-container {
|
|||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
content: "\ea1d";
|
content: "\e9b2";
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -2px;
|
top: -2px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
@ -72,7 +72,7 @@ ul.fancytree-container {
|
|||||||
|
|
||||||
.fancytree-node.fancytree-expanded .fancytree-expander:before {
|
.fancytree-node.fancytree-expanded .fancytree-expander:before {
|
||||||
font-family: 'boxicons' !important;
|
font-family: 'boxicons' !important;
|
||||||
content: "\ea17";
|
content: "\e9ac";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** some common text styling for cssClass label */
|
/** some common text styling for cssClass label */
|
||||||
|
@ -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')");
|
const attrs = repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
|
||||||
|
|
||||||
for (const attr of attrs) {
|
for (const attr of attrs) {
|
||||||
|
if (!attr.value.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const regex = new RegExp(attr.value);
|
const regex = new RegExp(attr.value);
|
||||||
let match;
|
let match;
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
actionName: "openDevTools",
|
actionName: "openDevTools",
|
||||||
defaultShortcuts: ["CommandOrControl+Shift+I"],
|
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+I"] : [],
|
||||||
scope: "window"
|
scope: "window"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -408,13 +408,7 @@ for (const action of DEFAULT_KEYBOARD_ACTIONS) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedActions = null;
|
|
||||||
|
|
||||||
function getKeyboardActions() {
|
function getKeyboardActions() {
|
||||||
if (cachedActions) {
|
|
||||||
return cachedActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = JSON.parse(JSON.stringify(DEFAULT_KEYBOARD_ACTIONS));
|
const actions = JSON.parse(JSON.stringify(DEFAULT_KEYBOARD_ACTIONS));
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
@ -442,8 +436,6 @@ function getKeyboardActions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedActions = actions;
|
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,7 +687,7 @@ function eraseDeletedNotes() {
|
|||||||
|
|
||||||
sql.executeMany(`
|
sql.executeMany(`
|
||||||
UPDATE notes
|
UPDATE notes
|
||||||
SET title = '[deleted]',
|
SET title = '[erased]',
|
||||||
isProtected = 0,
|
isProtected = 0,
|
||||||
isErased = 1
|
isErased = 1
|
||||||
WHERE noteId IN (???)`, noteIdsToErase);
|
WHERE noteId IN (???)`, noteIdsToErase);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user