attachment improvements

This commit is contained in:
zadam 2023-04-11 17:45:51 +02:00
parent a5f0b2a81e
commit 54c0268593
10 changed files with 191 additions and 47 deletions

1
package-lock.json generated
View File

@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.59.3",
"hasInstallScript": true,
"license": "AGPL-3.0-only",

View File

@ -39,6 +39,8 @@ class NoteContext extends Component {
async setNote(inputNotePath, opts = {}) {
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);
@ -46,6 +48,10 @@ class NoteContext extends Component {
return;
}
if (this.notePath === resolvedNotePath && utils.areObjectsEqual(this.viewScope, opts.viewScope)) {
return;
}
await this.triggerEvent('beforeNoteSwitch', {noteContext: this});
utils.closeActiveDialog();
@ -53,8 +59,7 @@ class NoteContext extends Component {
this.notePath = resolvedNotePath;
({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
this.viewScope = opts.viewScope || {};
this.viewScope.viewMode = this.viewScope.viewMode || "default";
this.viewScope = opts.viewScope;
this.saveToRecentNotes(resolvedNotePath);
@ -137,10 +142,6 @@ class NoteContext extends Component {
return;
}
if (resolvedNotePath === this.notePath) {
return;
}
if (await hoistedNoteService.checkNoteAccess(resolvedNotePath, this) === false) {
return; // note is outside of hoisted subtree and user chose not to unhoist
}

View File

@ -31,3 +31,5 @@ class FAttachment {
return this.froca.notes[this.parentId];
}
}
export default FAttachment;

View File

@ -5,6 +5,7 @@ import options from "../services/options.js";
import froca from "../services/froca.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import cssClassManager from "../services/css_class_manager.js";
import FAttachment from "./fattachment.js";
const LABEL = 'label';
const RELATION = 'relation';

View File

@ -78,39 +78,39 @@ async function createNoteLink(notePath, options = {}) {
return $container;
}
function getNotePathFromLink($link) {
const notePathAttr = $link.attr("data-note-path");
function parseNotePathAndScope($link) {
let notePath = $link.attr("data-note-path");
if (notePathAttr) {
return notePathAttr;
if (!notePath) {
const url = $link.attr('href');
notePath = url ? getNotePathFromUrl(url) : null;
}
const url = $link.attr('href');
const notePath = url ? getNotePathFromUrl(url) : null;
const viewScope = {
viewMode: $link.attr('data-view-mode'),
viewMode: $link.attr('data-view-mode') || 'default',
attachmentId: $link.attr('data-attachment-id'),
};
return {
notePath,
noteId: treeService.getNoteIdFromNotePath(notePath),
viewScope
};
}
function goToLink(evt) {
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;
}
evt.preventDefault();
evt.stopPropagation();
const {notePath, viewScope} = getNotePathFromLink($link);
const { notePath, viewScope } = parseNotePathAndScope($link);
const ctrlKey = utils.isCtrlKey(evt);
const isLeftClick = evt.which === 1;
@ -135,20 +135,19 @@ function goToLink(evt) {
});
}
}
else {
if (openInNewTab
|| $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
|| $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
) {
if (address) {
if (address.toLowerCase().startsWith('http')) {
window.open(address, '_blank');
}
else if (address.toLowerCase().startsWith('file:') && utils.isElectron()) {
const electron = utils.dynamicRequire('electron');
else if (hrefLink) {
// this branch handles external links
const isWithinCKLinkDialog = $link.hasClass("ck-link-actions__preview");
const isOutsideCKEditor = $link.closest("[contenteditable]").length === 0;
electron.shell.openPath(address);
}
if (openInNewTab || isWithinCKLinkDialog || isOutsideCKEditor) {
if (hrefLink.toLowerCase().startsWith('http')) {
window.open(hrefLink, '_blank');
}
else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) {
const electron = utils.dynamicRequire('electron');
electron.shell.openPath(hrefLink);
}
}
}
@ -159,7 +158,7 @@ function goToLink(evt) {
function linkContextMenu(e) {
const $link = $(e.target).closest("a");
const {notePath, viewScope} = getNotePathFromLink($link);
const { notePath, viewScope } = parseNotePathAndScope($link);
if (!notePath) {
return;
@ -223,5 +222,6 @@ export default {
getNotePathFromUrl,
createNoteLink,
goToLink,
loadReferenceLinkTitle
loadReferenceLinkTitle,
parseNotePathAndScope
};

View File

@ -31,18 +31,12 @@ async function mouseEnterHandler() {
return;
}
let notePath = linkService.getNotePathFromUrl($link.attr("href"));
const { notePath, noteId, viewScope } = linkService.parseNotePathAndScope($link);
if (!notePath) {
notePath = $link.attr("data-note-path");
}
if (!notePath) {
if (!notePath || viewScope.viewMode !== 'default') {
return;
}
const noteId = treeService.getNoteIdFromNotePath(notePath);
const note = await froca.getNote(noteId);
const content = await renderTooltip(note);

View File

@ -365,6 +365,121 @@ function escapeRegExp(str) {
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 {
reloadFrontendApp,
parseDate,
@ -408,5 +523,6 @@ export default {
filterAttributeName,
isValidAttributeName,
sleep,
escapeRegExp
escapeRegExp,
areObjectsEqual
};

View File

@ -20,24 +20,35 @@ const TPL = `
}
.attachment-content pre {
max-height: 400px;
background: var(--accented-background-color);
padding: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
.attachment-detail-wrapper.list-view .attachment-content pre {
max-height: 400px;
}
.attachment-content img {
margin: 10px;
}
.attachment-detail-wrapper.list-view .attachment-content img {
max-height: 300px;
max-width: 90%;
object-fit: contain;
}
.attachment-detail-wrapper.full-detail .attachment-content img {
max-width: 90%;
object-fit: contain;
}
</style>
<div class="attachment-detail-wrapper">
<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 style="flex: 1 1;"></div>
<div class="attachment-actions-container"></div>
@ -54,6 +65,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
this.contentSized();
this.attachment = attachment;
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment);
this.isFullDetail = true;
this.child(this.attachmentActionsWidget);
}
@ -73,7 +85,21 @@ export default class AttachmentDetailWidget extends BasicWidget {
.html()
);
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')
.text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`);
this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render());

View File

@ -30,18 +30,19 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
this.children = [];
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) {
this.$list.html("<strong>This attachment has been deleted.</strong>");
this.$wrapper.html("<strong>This attachment has been deleted.</strong>");
return;
}
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
attachmentDetailWidget.isFullDetail = true;
this.child(attachmentDetailWidget);
this.$list.append(attachmentDetailWidget.render());
this.$wrapper.append(attachmentDetailWidget.render());
}
async entitiesReloadedEvent({loadResults}) {

View File

@ -40,6 +40,8 @@ export default class AttachmentListTypeWidget extends TypeWidget {
for (const attachment of attachments) {
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
attachmentDetailWidget.isFullDetail = false;
this.child(attachmentDetailWidget);
this.renderedAttachmentIds.add(attachment.attachmentId);