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,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trilium",
|
||||
"version": "0.59.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -31,3 +31,5 @@ class FAttachment {
|
||||
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 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';
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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());
|
||||
|
@ -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}) {
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user