mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge remote-tracking branch 'origin/master' into m43
This commit is contained in:
commit
29e6b63f82
8
libraries/bootstrap/css/bootstrap.min.css
vendored
8
libraries/bootstrap/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
libraries/jquery.min.js
vendored
4
libraries/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
libraries/jquery.min.map
Normal file
1
libraries/jquery.min.map
Normal file
File diff suppressed because one or more lines are too long
6
package-lock.json
generated
6
package-lock.json
generated
@ -3345,9 +3345,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "9.0.0-beta.22",
|
"version": "9.0.0-beta.24",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.22.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.24.tgz",
|
||||||
"integrity": "sha512-dfqAf+CXXTKcNDj7DU7mYsmx+oZQcXOvJnZ8ZsgAHjrE9Tv8zsYUgCP3JlO4Z8CIazgleKXYmgh6H2stdK7fEA==",
|
"integrity": "sha512-25L3XMqm/1CCaV5CgU5ZkhKXw9830WeipJrTW0+VC5XTKp/3xHwhxyQ5G1kQnOTJd7IGwOamvw237D6e1YKnng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@electron/get": "^1.0.1",
|
"@electron/get": "^1.0.1",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.42.1",
|
"version": "0.42.2",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -78,7 +78,7 @@
|
|||||||
"yazl": "^2.5.1"
|
"yazl": "^2.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "9.0.0-beta.22",
|
"electron": "9.0.0-beta.24",
|
||||||
"electron-builder": "22.6.0",
|
"electron-builder": "22.6.0",
|
||||||
"electron-packager": "14.2.1",
|
"electron-packager": "14.2.1",
|
||||||
"electron-rebuild": "1.10.1",
|
"electron-rebuild": "1.10.1",
|
||||||
|
@ -105,7 +105,6 @@ class Attribute extends Entity {
|
|||||||
|
|
||||||
// cannot be static!
|
// cannot be static!
|
||||||
updatePojo(pojo) {
|
updatePojo(pojo) {
|
||||||
delete pojo.isOwned;
|
|
||||||
delete pojo.__note;
|
delete pojo.__note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,10 +411,6 @@ class Note extends Entity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const attr of filteredAttributes) {
|
|
||||||
attr.isOwned = attr.noteId === this.noteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__attributeCache = filteredAttributes;
|
this.__attributeCache = filteredAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import contextMenu from "./services/context_menu.js";
|
|||||||
import DesktopMainWindowLayout from "./layouts/desktop_main_window_layout.js";
|
import DesktopMainWindowLayout from "./layouts/desktop_main_window_layout.js";
|
||||||
import glob from "./services/glob.js";
|
import glob from "./services/glob.js";
|
||||||
import DesktopExtraWindowLayout from "./layouts/desktop_extra_window_layout.js";
|
import DesktopExtraWindowLayout from "./layouts/desktop_extra_window_layout.js";
|
||||||
|
import zoomService from './services/zoom.js';
|
||||||
|
|
||||||
glob.setupGlobs();
|
glob.setupGlobs();
|
||||||
|
|
||||||
@ -133,9 +134,11 @@ if (utils.isElectron()) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomLevel = zoomService.getCurrentZoom();
|
||||||
|
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
x: params.x,
|
x: params.x / zoomLevel,
|
||||||
y: params.y,
|
y: params.y / zoomLevel,
|
||||||
items,
|
items,
|
||||||
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
||||||
if (command === 'replaceMisspelling') {
|
if (command === 'replaceMisspelling') {
|
||||||
|
@ -59,8 +59,8 @@ function AttributesModel() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function showAttributes(attributes) {
|
async function showAttributes(noteId, attributes) {
|
||||||
const ownedAttributes = attributes.filter(attr => attr.isOwned);
|
const ownedAttributes = attributes.filter(attr => attr.noteId === noteId);
|
||||||
|
|
||||||
for (const attr of ownedAttributes) {
|
for (const attr of ownedAttributes) {
|
||||||
attr.labelValue = attr.type === 'label' ? attr.value : '';
|
attr.labelValue = attr.type === 'label' ? attr.value : '';
|
||||||
@ -86,7 +86,7 @@ function AttributesModel() {
|
|||||||
|
|
||||||
addLastEmptyRow();
|
addLastEmptyRow();
|
||||||
|
|
||||||
const inheritedAttributes = attributes.filter(attr => !attr.isOwned);
|
const inheritedAttributes = attributes.filter(attr => attr.noteId !== noteId);
|
||||||
|
|
||||||
self.inheritedAttributes(inheritedAttributes);
|
self.inheritedAttributes(inheritedAttributes);
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ function AttributesModel() {
|
|||||||
|
|
||||||
const attributes = await server.get('notes/' + noteId + '/attributes');
|
const attributes = await server.get('notes/' + noteId + '/attributes');
|
||||||
|
|
||||||
await showAttributes(attributes);
|
await showAttributes(noteId, attributes);
|
||||||
|
|
||||||
// attribute might not be rendered immediatelly so could not focus
|
// attribute might not be rendered immediatelly so could not focus
|
||||||
setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000);
|
setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000);
|
||||||
@ -166,7 +166,7 @@ function AttributesModel() {
|
|||||||
|
|
||||||
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
|
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
|
||||||
|
|
||||||
await showAttributes(attributes);
|
await showAttributes(noteId, attributes);
|
||||||
|
|
||||||
toastService.showMessage("Attributes have been saved.");
|
toastService.showMessage("Attributes have been saved.");
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,6 @@ import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.j
|
|||||||
import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
|
import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
|
||||||
import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
|
import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
|
||||||
import SidePaneToggles from "../widgets/side_pane_toggles.js";
|
import SidePaneToggles from "../widgets/side_pane_toggles.js";
|
||||||
import appContext from "../services/app_context.js";
|
|
||||||
|
|
||||||
const RIGHT_PANE_CSS = `
|
const RIGHT_PANE_CSS = `
|
||||||
<style>
|
<style>
|
||||||
@ -117,6 +116,7 @@ export default class DesktopMainWindowLayout {
|
|||||||
.hideInZenMode())
|
.hideInZenMode())
|
||||||
.child(new FlexContainer('row')
|
.child(new FlexContainer('row')
|
||||||
.collapsible()
|
.collapsible()
|
||||||
|
.filling()
|
||||||
.child(new SidePaneContainer('left')
|
.child(new SidePaneContainer('left')
|
||||||
.hideInZenMode()
|
.hideInZenMode()
|
||||||
.child(new GlobalButtonsWidget())
|
.child(new GlobalButtonsWidget())
|
||||||
|
@ -4,7 +4,7 @@ import DialogCommandExecutor from "./dialog_command_executor.js";
|
|||||||
import Entrypoints from "./entrypoints.js";
|
import Entrypoints from "./entrypoints.js";
|
||||||
import options from "./options.js";
|
import options from "./options.js";
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import ZoomService from "./zoom.js";
|
import zoomService from "./zoom.js";
|
||||||
import TabManager from "./tab_manager.js";
|
import TabManager from "./tab_manager.js";
|
||||||
import treeService from "./tree.js";
|
import treeService from "./tree.js";
|
||||||
import Component from "../widgets/component.js";
|
import Component from "../widgets/component.js";
|
||||||
@ -73,7 +73,7 @@ class AppContext extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
this.child(new ZoomService());
|
this.child(zoomService);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.triggerEvent('initialRenderComplete');
|
this.triggerEvent('initialRenderComplete');
|
||||||
|
@ -81,24 +81,29 @@ function goToLink(e) {
|
|||||||
}
|
}
|
||||||
else if (e.which === 1) {
|
else if (e.which === 1) {
|
||||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||||
activeTabContext.setNote(notePath)
|
activeTabContext.setNote(notePath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const address = $link.attr('href');
|
if (e.which === 1) {
|
||||||
|
const address = $link.attr('href');
|
||||||
|
|
||||||
if (address && address.startsWith('http')) {
|
if (address && address.startsWith('http')) {
|
||||||
window.open(address, '_blank');
|
window.open(address, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newTabContextMenu(e) {
|
function linkContextMenu(e) {
|
||||||
const $link = $(e.target).closest("a");
|
const $link = $(e.target).closest("a");
|
||||||
|
|
||||||
const notePath = getNotePathFromLink($link);
|
const notePath = getNotePathFromLink($link);
|
||||||
@ -113,7 +118,7 @@ function newTabContextMenu(e) {
|
|||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
y: e.pageY,
|
y: e.pageY,
|
||||||
items: [
|
items: [
|
||||||
{title: "Open note in new tab", command: "openNoteInNewTab", uiIcon: "arrow-up-right"},
|
{title: "Open note in new tab", command: "openNoteInNewTab", uiIcon: "empty"},
|
||||||
{title: "Open note in new window", command: "openNoteInNewWindow", uiIcon: "window-open"}
|
{title: "Open note in new window", command: "openNoteInNewWindow", uiIcon: "window-open"}
|
||||||
],
|
],
|
||||||
selectMenuItemHandler: ({command}) => {
|
selectMenuItemHandler: ({command}) => {
|
||||||
@ -155,18 +160,20 @@ $(document).on('mousedown', '.note-detail-text a', function (e) {
|
|||||||
|
|
||||||
$(document).on('mousedown', '.note-detail-book a', goToLink);
|
$(document).on('mousedown', '.note-detail-book a', goToLink);
|
||||||
$(document).on('mousedown', '.note-detail-render a', goToLink);
|
$(document).on('mousedown', '.note-detail-render a', goToLink);
|
||||||
$(document).on('mousedown', '.note-detail-text.ck-read-only a,.note-detail-text a.reference-link', goToLink);
|
$(document).on('mousedown', '.note-detail-text a.reference-link', goToLink);
|
||||||
|
$(document).on('mousedown', '.note-detail-readonly-text a', goToLink);
|
||||||
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
|
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
|
||||||
$(document).on('click', 'a.ck-link-actions__preview', e => {
|
$(document).on('click', 'a.ck-link-actions__preview', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('contextmenu', 'a.ck-link-actions__preview', newTabContextMenu);
|
$(document).on('contextmenu', 'a.ck-link-actions__preview', linkContextMenu);
|
||||||
$(document).on('contextmenu', '.note-detail-text a', newTabContextMenu);
|
$(document).on('contextmenu', '.note-detail-text a', linkContextMenu);
|
||||||
$(document).on('contextmenu', "a[data-action='note']", newTabContextMenu);
|
$(document).on('contextmenu', '.note-detail-readonly-text a', linkContextMenu);
|
||||||
$(document).on('contextmenu', ".note-detail-render a", newTabContextMenu);
|
$(document).on('contextmenu', "a[data-action='note']", linkContextMenu);
|
||||||
$(document).on('contextmenu', ".note-paths-widget a", newTabContextMenu);
|
$(document).on('contextmenu', ".note-detail-render a", linkContextMenu);
|
||||||
|
$(document).on('contextmenu', ".note-paths-widget a", linkContextMenu);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getNotePathFromUrl,
|
getNotePathFromUrl,
|
||||||
|
@ -37,6 +37,10 @@ function subscribeToMessages(messageHandler) {
|
|||||||
// used to serialize sync operations
|
// used to serialize sync operations
|
||||||
let consumeQueuePromise = null;
|
let consumeQueuePromise = null;
|
||||||
|
|
||||||
|
// most sync events are sent twice - once immediatelly after finishing the transaction and once during the scheduled ping
|
||||||
|
// but we want to process only once
|
||||||
|
const receivedSyncIds = new Set();
|
||||||
|
|
||||||
async function handleMessage(event) {
|
async function handleMessage(event) {
|
||||||
const message = JSON.parse(event.data);
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
@ -52,14 +56,19 @@ async function handleMessage(event) {
|
|||||||
|
|
||||||
if (syncRows.length > 0) {
|
if (syncRows.length > 0) {
|
||||||
const filteredRows = syncRows.filter(row =>
|
const filteredRows = syncRows.filter(row =>
|
||||||
row.entityName !== 'recent_notes'
|
!receivedSyncIds.has(row.id)
|
||||||
|
&& row.entityName !== 'recent_notes'
|
||||||
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
|
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
|
||||||
|
|
||||||
if (filteredRows.length > 0) {
|
if (filteredRows.length > 0) {
|
||||||
console.debug(utils.now(), "Sync data: ", filteredRows);
|
console.debug(utils.now(), "Sync data: ", filteredRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
syncDataQueue.push(...syncRows);
|
for (const row of filteredRows) {
|
||||||
|
receivedSyncIds.add(row.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncDataQueue.push(...filteredRows);
|
||||||
|
|
||||||
// we set lastAcceptedSyncId even before sync processing and send ping so that backend can start sending more updates
|
// we set lastAcceptedSyncId even before sync processing and send ping so that backend can start sending more updates
|
||||||
lastAcceptedSyncId = Math.max(lastAcceptedSyncId, syncRows[syncRows.length - 1].id);
|
lastAcceptedSyncId = Math.max(lastAcceptedSyncId, syncRows[syncRows.length - 1].id);
|
||||||
@ -170,7 +179,7 @@ function connectWebSocket() {
|
|||||||
|
|
||||||
async function sendPing() {
|
async function sendPing() {
|
||||||
if (Date.now() - lastPingTs > 30000) {
|
if (Date.now() - lastPingTs > 30000) {
|
||||||
console.log(utils.now(), "Lost websocket connection to the backend");
|
console.log(utils.now(), "Lost websocket connection to the backend. If you keep having this issue repeatedly, you might want to check your reverse proxy (nginx, apache) configuration and allow/unblock WebSocket.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ws.readyState === ws.OPEN) {
|
if (ws.readyState === ws.OPEN) {
|
||||||
|
@ -5,11 +5,13 @@ import utils from "../services/utils.js";
|
|||||||
const MIN_ZOOM = 0.5;
|
const MIN_ZOOM = 0.5;
|
||||||
const MAX_ZOOM = 2.0;
|
const MAX_ZOOM = 2.0;
|
||||||
|
|
||||||
export default class ZoomService extends Component {
|
class ZoomService extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.setZoomFactor(options.getFloat('zoomFactor'));
|
options.initializedPromise.then(() => {
|
||||||
|
this.setZoomFactor(options.getFloat('zoomFactor'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setZoomFactor(zoomFactor) {
|
setZoomFactor(zoomFactor) {
|
||||||
@ -46,3 +48,7 @@ export default class ZoomService extends Component {
|
|||||||
this.setZoomFactorAndSave(zoomFactor);
|
this.setZoomFactorAndSave(zoomFactor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomService = new ZoomService();
|
||||||
|
|
||||||
|
export default zoomService;
|
||||||
|
@ -30,6 +30,11 @@ class BasicWidget extends Component {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filling() {
|
||||||
|
this.css('flex-grow', '1');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
hideInZenMode() {
|
hideInZenMode() {
|
||||||
this.class('hide-in-zen-mode');
|
this.class('hide-in-zen-mode');
|
||||||
return this;
|
return this;
|
||||||
|
@ -251,8 +251,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
this.triggerCommand('setActiveScreen', {screen:'detail'});
|
this.triggerCommand('setActiveScreen', {screen:'detail'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expand: (event, data) => this.setExpandedToServer(data.node.data.branchId, true),
|
expand: (event, data) => this.setExpanded(data.node.data.branchId, true),
|
||||||
collapse: (event, data) => this.setExpandedToServer(data.node.data.branchId, false),
|
collapse: (event, data) => this.setExpanded(data.node.data.branchId, false),
|
||||||
hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() },
|
hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() },
|
||||||
dnd5: {
|
dnd5: {
|
||||||
autoExpandMS: 600,
|
autoExpandMS: 600,
|
||||||
@ -807,7 +807,9 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
async entitiesReloadedEvent({loadResults}) {
|
async entitiesReloadedEvent({loadResults}) {
|
||||||
const activeNode = this.getActiveNode();
|
const activeNode = this.getActiveNode();
|
||||||
|
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
|
||||||
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||||
|
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
|
||||||
const activeNoteId = activeNode ? activeNode.data.noteId : null;
|
const activeNoteId = activeNode ? activeNode.data.noteId : null;
|
||||||
|
|
||||||
const noteIdsToUpdate = new Set();
|
const noteIdsToUpdate = new Set();
|
||||||
@ -929,15 +931,27 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
if (node) {
|
if (node) {
|
||||||
node.setActive(true, {noEvents: true});
|
node.setActive(true, {noEvents: true});
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
||||||
|
node = await this.expandToNote(nextNotePath);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
this.tree.setFocus();
|
||||||
|
node.setFocus(true);
|
||||||
|
|
||||||
|
await appContext.tabManager.getActiveTabContext().setNote(nextNotePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExpandedToServer(branchId, isExpanded) {
|
async setExpanded(branchId, isExpanded) {
|
||||||
utils.assertArguments(branchId);
|
utils.assertArguments(branchId);
|
||||||
|
|
||||||
const expandedNum = isExpanded ? 1 : 0;
|
const branch = treeCache.getBranch(branchId);
|
||||||
|
branch.isExpanded = isExpanded;
|
||||||
|
|
||||||
await server.put('branches/' + branchId + '/expanded/' + expandedNum);
|
await server.put(`branches/${branchId}/expanded/${isExpanded ? 1 : 0}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadTreeFromCache() {
|
async reloadTreeFromCache() {
|
||||||
|
@ -19,6 +19,7 @@ const TPL = `
|
|||||||
|
|
||||||
.promoted-attributes td, .promoted-attributes th {
|
.promoted-attributes td, .promoted-attributes th {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
min-width: 50px; /* otherwise checkboxes can collapse into 0 width (if there are only checkboxes) */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
|||||||
const $labelCell = $("<th>").append(valueAttr.name);
|
const $labelCell = $("<th>").append(valueAttr.name);
|
||||||
const $input = $("<input>")
|
const $input = $("<input>")
|
||||||
.prop("tabindex", definitionAttr.position)
|
.prop("tabindex", definitionAttr.position)
|
||||||
.prop("attribute-id", valueAttr.isOwned ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
.prop("attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||||
.prop("attribute-type", valueAttr.type)
|
.prop("attribute-type", valueAttr.type)
|
||||||
.prop("attribute-name", valueAttr.name)
|
.prop("attribute-name", valueAttr.name)
|
||||||
.prop("value", valueAttr.value)
|
.prop("value", valueAttr.value)
|
||||||
|
@ -602,18 +602,23 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTab($tab, note) {
|
updateTab($tab, note) {
|
||||||
if (!note || !$tab.length) {
|
if (!$tab.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTitle($tab, note.title);
|
|
||||||
|
|
||||||
for (const clazz of Array.from($tab[0].classList)) { // create copy to safely iterate over while removing classes
|
for (const clazz of Array.from($tab[0].classList)) { // create copy to safely iterate over while removing classes
|
||||||
if (clazz !== 'note-tab') {
|
if (clazz !== 'note-tab') {
|
||||||
$tab.removeClass(clazz);
|
$tab.removeClass(clazz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
this.updateTitle($tab, 'New tab');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTitle($tab, note.title);
|
||||||
|
|
||||||
$tab.addClass(note.getCssClass());
|
$tab.addClass(note.getCssClass());
|
||||||
$tab.addClass(utils.getNoteTypeClass(note.type));
|
$tab.addClass(utils.getNoteTypeClass(note.type));
|
||||||
$tab.addClass(utils.getMimeTypeClass(note.mime));
|
$tab.addClass(utils.getMimeTypeClass(note.mime));
|
||||||
|
@ -22,6 +22,10 @@ const TPL = `
|
|||||||
.note-detail-readonly-text p:first-child, .note-detail-text::before {
|
.note-detail-readonly-text p:first-child, .note-detail-text::before {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-detail-readonly-text img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="alert alert-warning no-print">
|
<div class="alert alert-warning no-print">
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const noteService = require('../../services/notes');
|
|
||||||
const protectedSessionService = require('../../services/protected_session');
|
const protectedSessionService = require('../../services/protected_session');
|
||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
@ -45,7 +44,9 @@ async function downloadNoteFile(noteId, res, contentDisposition = true) {
|
|||||||
if (contentDisposition) {
|
if (contentDisposition) {
|
||||||
// (one) reason we're not using the originFileName (available as label) is that it's not
|
// (one) reason we're not using the originFileName (available as label) is that it's not
|
||||||
// available for older note revisions and thus would be inconsistent
|
// available for older note revisions and thus would be inconsistent
|
||||||
res.setHeader('Content-Disposition', utils.getContentDisposition(note.title || "untitled"));
|
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader('Content-Type', note.mime);
|
res.setHeader('Content-Type', note.mime);
|
||||||
|
@ -38,13 +38,7 @@ async function getNoteRevision(req) {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
function getRevisionFilename(noteRevision) {
|
function getRevisionFilename(noteRevision) {
|
||||||
let filename = noteRevision.title || "untitled";
|
let filename = utils.formatDownloadTitle(noteRevision.title, noteRevision.type, noteRevision.mime);
|
||||||
|
|
||||||
if (noteRevision.type === 'text') {
|
|
||||||
filename += '.html';
|
|
||||||
} else if (['relation-map', 'search'].includes(noteRevision.type)) {
|
|
||||||
filename += '.json';
|
|
||||||
}
|
|
||||||
|
|
||||||
const extension = path.extname(filename);
|
const extension = path.extname(filename);
|
||||||
const date = noteRevision.dateCreated
|
const date = noteRevision.dateCreated
|
||||||
|
@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2020-05-06T23:24:13+02:00", buildRevision: "54ecd2ee75d1177cedadf9fee10319687feee5f0" };
|
module.exports = { buildDate:"2020-05-12T16:46:45+02:00", buildRevision: "4f50864ec8346a12d7845cb4c91a3de3b1043d34" };
|
||||||
|
@ -13,6 +13,7 @@ const Attribute = require('../entities/attribute');
|
|||||||
const hoistedNoteService = require('../services/hoisted_note');
|
const hoistedNoteService = require('../services/hoisted_note');
|
||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const log = require('../services/log');
|
const log = require('../services/log');
|
||||||
|
const utils = require('../services/utils');
|
||||||
const noteRevisionService = require('../services/note_revisions');
|
const noteRevisionService = require('../services/note_revisions');
|
||||||
const attributeService = require('../services/attributes');
|
const attributeService = require('../services/attributes');
|
||||||
const request = require('./request');
|
const request = require('./request');
|
||||||
@ -276,9 +277,9 @@ async function downloadImage(noteId, imageUrl) {
|
|||||||
const downloadImagePromises = {};
|
const downloadImagePromises = {};
|
||||||
|
|
||||||
function replaceUrl(content, url, imageNote) {
|
function replaceUrl(content, url, imageNote) {
|
||||||
const quoted = url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
const quotedUrl = utils.quoteRegex(url);
|
||||||
|
|
||||||
return content.replace(new RegExp(`\\s+src=[\"']${quoted}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
|
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadImages(noteId, content) {
|
async function downloadImages(noteId, content) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*]+|"[^"]+"))?/igu;
|
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*"]+|"[^"]+"))?/igu;
|
||||||
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
|
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
|
||||||
|
|
||||||
function calculateSmartValue(v) {
|
function calculateSmartValue(v) {
|
||||||
|
@ -221,6 +221,7 @@ async function transactional(func) {
|
|||||||
|
|
||||||
await commit();
|
await commit();
|
||||||
|
|
||||||
|
// note that sync rows sent from this action will be sent again by scheduled periodic ping
|
||||||
require('./ws.js').sendPingToAllClients();
|
require('./ws.js').sendPingToAllClients();
|
||||||
|
|
||||||
transactionActive = false;
|
transactionActive = false;
|
||||||
|
@ -5,6 +5,7 @@ const randtoken = require('rand-token').generator({source: 'crypto'});
|
|||||||
const unescape = require('unescape');
|
const unescape = require('unescape');
|
||||||
const escape = require('escape-html');
|
const escape = require('escape-html');
|
||||||
const sanitize = require("sanitize-filename");
|
const sanitize = require("sanitize-filename");
|
||||||
|
const mimeTypes = require('mime-types');
|
||||||
|
|
||||||
function newEntityId() {
|
function newEntityId() {
|
||||||
return randomString(12);
|
return randomString(12);
|
||||||
@ -166,10 +167,46 @@ function isStringNote(type, mime) {
|
|||||||
|| STRING_MIME_TYPES.includes(mime);
|
|| STRING_MIME_TYPES.includes(mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceAll(string, replaceWhat, replaceWith) {
|
function quoteRegex(url) {
|
||||||
const escapedWhat = replaceWhat.replace(/([\/,!\\^${}\[\]().*+?|<>\-&])/g, "\\$&");
|
return url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
return string.replace(new RegExp(escapedWhat, "g"), replaceWith);
|
function replaceAll(string, replaceWhat, replaceWith) {
|
||||||
|
const quotedReplaceWhat = quoteRegex(replaceWhat);
|
||||||
|
|
||||||
|
return string.replace(new RegExp(quotedReplaceWhat, "g"), replaceWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDownloadTitle(filename, type, mime) {
|
||||||
|
if (!filename) {
|
||||||
|
filename = "untitled";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'text') {
|
||||||
|
return filename + '.html';
|
||||||
|
} else if (['relation-map', 'search'].includes(type)) {
|
||||||
|
return filename + '.json';
|
||||||
|
} else {
|
||||||
|
if (!mime) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
mime = mime.toLowerCase();
|
||||||
|
const filenameLc = filename.toLowerCase();
|
||||||
|
const extensions = mimeTypes.extensions[mime];
|
||||||
|
|
||||||
|
if (!extensions || extensions.length === 0) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ext of extensions) {
|
||||||
|
if (filenameLc.endsWith('.' + ext)) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename + '.' + extensions[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -198,5 +235,7 @@ module.exports = {
|
|||||||
sanitizeFilenameForHeader,
|
sanitizeFilenameForHeader,
|
||||||
getContentDisposition,
|
getContentDisposition,
|
||||||
isStringNote,
|
isStringNote,
|
||||||
replaceAll
|
quoteRegex,
|
||||||
|
replaceAll,
|
||||||
|
formatDownloadTitle
|
||||||
};
|
};
|
@ -5,7 +5,7 @@
|
|||||||
<link rel="shortcut icon" href="favicon.ico">
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
<title>Trilium Notes</title>
|
<title>Trilium Notes</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="desktop theme-<%= theme %>" style="display: none; --main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">
|
<body class="desktop theme-<%= theme %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">
|
||||||
<noscript>Trilium requires JavaScript to be enabled.</noscript>
|
<noscript>Trilium requires JavaScript to be enabled.</noscript>
|
||||||
|
|
||||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
||||||
@ -82,9 +82,5 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css">
|
<link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css">
|
||||||
|
|
||||||
<script>
|
|
||||||
$("body").show();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user