mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
attachment improvements
This commit is contained in:
parent
a5f0b2a81e
commit
54c0268593
1
package-lock.json
generated
1
package-lock.json
generated
@ -5,6 +5,7 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "trilium",
|
||||||
"version": "0.59.3",
|
"version": "0.59.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
|
@ -39,6 +39,8 @@ class NoteContext extends Component {
|
|||||||
|
|
||||||
async setNote(inputNotePath, opts = {}) {
|
async setNote(inputNotePath, opts = {}) {
|
||||||
opts.triggerSwitchEvent = opts.triggerSwitchEvent !== undefined ? opts.triggerSwitchEvent : true;
|
opts.triggerSwitchEvent = opts.triggerSwitchEvent !== undefined ? opts.triggerSwitchEvent : true;
|
||||||
|
opts.viewScope = opts.viewScope || {};
|
||||||
|
opts.viewScope.viewMode = opts.viewScope.viewMode || "default";
|
||||||
|
|
||||||
const resolvedNotePath = await this.getResolvedNotePath(inputNotePath);
|
const resolvedNotePath = await this.getResolvedNotePath(inputNotePath);
|
||||||
|
|
||||||
@ -46,6 +48,10 @@ class NoteContext extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.notePath === resolvedNotePath && utils.areObjectsEqual(this.viewScope, opts.viewScope)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.triggerEvent('beforeNoteSwitch', {noteContext: this});
|
await this.triggerEvent('beforeNoteSwitch', {noteContext: this});
|
||||||
|
|
||||||
utils.closeActiveDialog();
|
utils.closeActiveDialog();
|
||||||
@ -53,8 +59,7 @@ class NoteContext extends Component {
|
|||||||
this.notePath = resolvedNotePath;
|
this.notePath = resolvedNotePath;
|
||||||
({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
|
({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
|
||||||
|
|
||||||
this.viewScope = opts.viewScope || {};
|
this.viewScope = opts.viewScope;
|
||||||
this.viewScope.viewMode = this.viewScope.viewMode || "default";
|
|
||||||
|
|
||||||
this.saveToRecentNotes(resolvedNotePath);
|
this.saveToRecentNotes(resolvedNotePath);
|
||||||
|
|
||||||
@ -137,10 +142,6 @@ class NoteContext extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolvedNotePath === this.notePath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await hoistedNoteService.checkNoteAccess(resolvedNotePath, this) === false) {
|
if (await hoistedNoteService.checkNoteAccess(resolvedNotePath, this) === false) {
|
||||||
return; // note is outside of hoisted subtree and user chose not to unhoist
|
return; // note is outside of hoisted subtree and user chose not to unhoist
|
||||||
}
|
}
|
||||||
|
@ -31,3 +31,5 @@ class FAttachment {
|
|||||||
return this.froca.notes[this.parentId];
|
return this.froca.notes[this.parentId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default FAttachment;
|
||||||
|
@ -5,6 +5,7 @@ import options from "../services/options.js";
|
|||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||||
import cssClassManager from "../services/css_class_manager.js";
|
import cssClassManager from "../services/css_class_manager.js";
|
||||||
|
import FAttachment from "./fattachment.js";
|
||||||
|
|
||||||
const LABEL = 'label';
|
const LABEL = 'label';
|
||||||
const RELATION = 'relation';
|
const RELATION = 'relation';
|
||||||
|
@ -78,39 +78,39 @@ async function createNoteLink(notePath, options = {}) {
|
|||||||
return $container;
|
return $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNotePathFromLink($link) {
|
function parseNotePathAndScope($link) {
|
||||||
const notePathAttr = $link.attr("data-note-path");
|
let notePath = $link.attr("data-note-path");
|
||||||
|
|
||||||
if (notePathAttr) {
|
|
||||||
return notePathAttr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!notePath) {
|
||||||
const url = $link.attr('href');
|
const url = $link.attr('href');
|
||||||
|
|
||||||
const notePath = url ? getNotePathFromUrl(url) : null;
|
notePath = url ? getNotePathFromUrl(url) : null;
|
||||||
|
}
|
||||||
|
|
||||||
const viewScope = {
|
const viewScope = {
|
||||||
viewMode: $link.attr('data-view-mode'),
|
viewMode: $link.attr('data-view-mode') || 'default',
|
||||||
attachmentId: $link.attr('data-attachment-id'),
|
attachmentId: $link.attr('data-attachment-id'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notePath,
|
notePath,
|
||||||
|
noteId: treeService.getNoteIdFromNotePath(notePath),
|
||||||
viewScope
|
viewScope
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToLink(evt) {
|
function goToLink(evt) {
|
||||||
const $link = $(evt.target).closest("a,.block-link");
|
const $link = $(evt.target).closest("a,.block-link");
|
||||||
const address = $link.attr('href');
|
const hrefLink = $link.attr('href');
|
||||||
|
|
||||||
if (address?.startsWith("data:")) {
|
if (hrefLink?.startsWith("data:")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
const {notePath, viewScope} = getNotePathFromLink($link);
|
const { notePath, viewScope } = parseNotePathAndScope($link);
|
||||||
|
|
||||||
const ctrlKey = utils.isCtrlKey(evt);
|
const ctrlKey = utils.isCtrlKey(evt);
|
||||||
const isLeftClick = evt.which === 1;
|
const isLeftClick = evt.which === 1;
|
||||||
@ -135,20 +135,19 @@ function goToLink(evt) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (hrefLink) {
|
||||||
if (openInNewTab
|
// this branch handles external links
|
||||||
|| $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
|
const isWithinCKLinkDialog = $link.hasClass("ck-link-actions__preview");
|
||||||
|| $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
|
const isOutsideCKEditor = $link.closest("[contenteditable]").length === 0;
|
||||||
) {
|
|
||||||
if (address) {
|
if (openInNewTab || isWithinCKLinkDialog || isOutsideCKEditor) {
|
||||||
if (address.toLowerCase().startsWith('http')) {
|
if (hrefLink.toLowerCase().startsWith('http')) {
|
||||||
window.open(address, '_blank');
|
window.open(hrefLink, '_blank');
|
||||||
}
|
}
|
||||||
else if (address.toLowerCase().startsWith('file:') && utils.isElectron()) {
|
else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) {
|
||||||
const electron = utils.dynamicRequire('electron');
|
const electron = utils.dynamicRequire('electron');
|
||||||
|
|
||||||
electron.shell.openPath(address);
|
electron.shell.openPath(hrefLink);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +158,7 @@ function goToLink(evt) {
|
|||||||
function linkContextMenu(e) {
|
function linkContextMenu(e) {
|
||||||
const $link = $(e.target).closest("a");
|
const $link = $(e.target).closest("a");
|
||||||
|
|
||||||
const {notePath, viewScope} = getNotePathFromLink($link);
|
const { notePath, viewScope } = parseNotePathAndScope($link);
|
||||||
|
|
||||||
if (!notePath) {
|
if (!notePath) {
|
||||||
return;
|
return;
|
||||||
@ -223,5 +222,6 @@ export default {
|
|||||||
getNotePathFromUrl,
|
getNotePathFromUrl,
|
||||||
createNoteLink,
|
createNoteLink,
|
||||||
goToLink,
|
goToLink,
|
||||||
loadReferenceLinkTitle
|
loadReferenceLinkTitle,
|
||||||
|
parseNotePathAndScope
|
||||||
};
|
};
|
||||||
|
@ -31,18 +31,12 @@ async function mouseEnterHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notePath = linkService.getNotePathFromUrl($link.attr("href"));
|
const { notePath, noteId, viewScope } = linkService.parseNotePathAndScope($link);
|
||||||
|
|
||||||
if (!notePath) {
|
if (!notePath || viewScope.viewMode !== 'default') {
|
||||||
notePath = $link.attr("data-note-path");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!notePath) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
|
||||||
|
|
||||||
const note = await froca.getNote(noteId);
|
const note = await froca.getNote(noteId);
|
||||||
const content = await renderTooltip(note);
|
const content = await renderTooltip(note);
|
||||||
|
|
||||||
|
@ -365,6 +365,121 @@ function escapeRegExp(str) {
|
|||||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areObjectsEqual () {
|
||||||
|
var i, l, leftChain, rightChain;
|
||||||
|
|
||||||
|
function compare2Objects (x, y) {
|
||||||
|
var p;
|
||||||
|
|
||||||
|
// remember that NaN === NaN returns false
|
||||||
|
// and isNaN(undefined) returns true
|
||||||
|
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare primitives and functions.
|
||||||
|
// Check if both arguments link to the same object.
|
||||||
|
// Especially useful on the step where we compare prototypes
|
||||||
|
if (x === y) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Works in case when functions are created in constructor.
|
||||||
|
// Comparing dates is a common scenario. Another built-ins?
|
||||||
|
// We can even handle functions passed across iframes
|
||||||
|
if ((typeof x === 'function' && typeof y === 'function') ||
|
||||||
|
(x instanceof Date && y instanceof Date) ||
|
||||||
|
(x instanceof RegExp && y instanceof RegExp) ||
|
||||||
|
(x instanceof String && y instanceof String) ||
|
||||||
|
(x instanceof Number && y instanceof Number)) {
|
||||||
|
return x.toString() === y.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// At last checking prototypes as good as we can
|
||||||
|
if (!(x instanceof Object && y instanceof Object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.constructor !== y.constructor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.prototype !== y.prototype) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for infinitive linking loops
|
||||||
|
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick checking of one object being a subset of another.
|
||||||
|
// todo: cache the structure of arguments[0] for performance
|
||||||
|
for (p in y) {
|
||||||
|
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (typeof y[p] !== typeof x[p]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (p in x) {
|
||||||
|
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (typeof y[p] !== typeof x[p]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (typeof (x[p])) {
|
||||||
|
case 'object':
|
||||||
|
case 'function':
|
||||||
|
|
||||||
|
leftChain.push(x);
|
||||||
|
rightChain.push(y);
|
||||||
|
|
||||||
|
if (!compare2Objects (x[p], y[p])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
leftChain.pop();
|
||||||
|
rightChain.pop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (x[p] !== y[p]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.length < 1) {
|
||||||
|
return true; //Die silently? Don't know how to handle such case, please help...
|
||||||
|
// throw "Need two or more arguments to compare";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 1, l = arguments.length; i < l; i++) {
|
||||||
|
|
||||||
|
leftChain = []; //Todo: this can be cached
|
||||||
|
rightChain = [];
|
||||||
|
|
||||||
|
if (!compare2Objects(arguments[0], arguments[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
reloadFrontendApp,
|
reloadFrontendApp,
|
||||||
parseDate,
|
parseDate,
|
||||||
@ -408,5 +523,6 @@ export default {
|
|||||||
filterAttributeName,
|
filterAttributeName,
|
||||||
isValidAttributeName,
|
isValidAttributeName,
|
||||||
sleep,
|
sleep,
|
||||||
escapeRegExp
|
escapeRegExp,
|
||||||
|
areObjectsEqual
|
||||||
};
|
};
|
||||||
|
@ -20,24 +20,35 @@ const TPL = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attachment-content pre {
|
.attachment-content pre {
|
||||||
max-height: 400px;
|
|
||||||
background: var(--accented-background-color);
|
background: var(--accented-background-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachment-detail-wrapper.list-view .attachment-content pre {
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
.attachment-content img {
|
.attachment-content img {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-detail-wrapper.list-view .attachment-content img {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachment-detail-wrapper.full-detail .attachment-content img {
|
||||||
|
max-width: 90%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="attachment-detail-wrapper">
|
<div class="attachment-detail-wrapper">
|
||||||
<div class="attachment-title-line">
|
<div class="attachment-title-line">
|
||||||
<h4 class="attachment-title"><a href="javascript:" data-trigger-command="openAttachmentDetail"></a></h4>
|
<h4 class="attachment-title"></h4>
|
||||||
<div class="attachment-details"></div>
|
<div class="attachment-details"></div>
|
||||||
<div style="flex: 1 1;"></div>
|
<div style="flex: 1 1;"></div>
|
||||||
<div class="attachment-actions-container"></div>
|
<div class="attachment-actions-container"></div>
|
||||||
@ -54,6 +65,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
|||||||
this.contentSized();
|
this.contentSized();
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment);
|
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment);
|
||||||
|
this.isFullDetail = true;
|
||||||
this.child(this.attachmentActionsWidget);
|
this.child(this.attachmentActionsWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +85,21 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
|||||||
.html()
|
.html()
|
||||||
);
|
);
|
||||||
this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
|
this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
|
||||||
this.$wrapper.find('.attachment-title a').text(this.attachment.title);
|
this.$wrapper.addClass(this.isFullDetail ? "full-detail" : "list-view");
|
||||||
|
|
||||||
|
if (!this.isFullDetail) {
|
||||||
|
this.$wrapper.find('.attachment-title').append(
|
||||||
|
$('<a href="javascript:">')
|
||||||
|
.attr("data-note-path", this.attachment.parentId)
|
||||||
|
.attr("data-view-mode", "attachments")
|
||||||
|
.attr("data-attachment-id", this.attachment.attachmentId)
|
||||||
|
.text(this.attachment.title)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$wrapper.find('.attachment-title')
|
||||||
|
.text(this.attachment.title);
|
||||||
|
}
|
||||||
|
|
||||||
this.$wrapper.find('.attachment-details')
|
this.$wrapper.find('.attachment-details')
|
||||||
.text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`);
|
.text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`);
|
||||||
this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render());
|
this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render());
|
||||||
|
@ -30,18 +30,19 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
|||||||
this.children = [];
|
this.children = [];
|
||||||
this.renderedAttachmentIds = new Set();
|
this.renderedAttachmentIds = new Set();
|
||||||
|
|
||||||
const attachment = await server.get(`notes/${this.noteId}/attachments/${this.noteContext.viewScope.attachment.attachmentId}/?includeContent=true`);
|
const attachment = await server.get(`notes/${this.noteId}/attachments/${this.noteContext.viewScope.attachmentId}/?includeContent=true`);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
this.$list.html("<strong>This attachment has been deleted.</strong>");
|
this.$wrapper.html("<strong>This attachment has been deleted.</strong>");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
||||||
|
attachmentDetailWidget.isFullDetail = true;
|
||||||
this.child(attachmentDetailWidget);
|
this.child(attachmentDetailWidget);
|
||||||
|
|
||||||
this.$list.append(attachmentDetailWidget.render());
|
this.$wrapper.append(attachmentDetailWidget.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({loadResults}) {
|
async entitiesReloadedEvent({loadResults}) {
|
||||||
|
@ -40,6 +40,8 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
for (const attachment of attachments) {
|
for (const attachment of attachments) {
|
||||||
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
||||||
|
attachmentDetailWidget.isFullDetail = false;
|
||||||
|
|
||||||
this.child(attachmentDetailWidget);
|
this.child(attachmentDetailWidget);
|
||||||
|
|
||||||
this.renderedAttachmentIds.add(attachment.attachmentId);
|
this.renderedAttachmentIds.add(attachment.attachmentId);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user