mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	attachment improvements
This commit is contained in:
		
							parent
							
								
									fd8a2d4d92
								
							
						
					
					
						commit
						9be524ef89
					
				@ -114,6 +114,17 @@ function formatLabel(label) {
 | 
			
		||||
    return str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function formatSize(size) {
 | 
			
		||||
    size = Math.max(Math.round(size / 1024), 1);
 | 
			
		||||
 | 
			
		||||
    if (size < 1024) {
 | 
			
		||||
        return `${size} KiB`;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return `${Math.round(size / 102.4) / 10} MiB`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toObject(array, fn) {
 | 
			
		||||
    const obj = {};
 | 
			
		||||
 | 
			
		||||
@ -363,6 +374,7 @@ export default {
 | 
			
		||||
    formatDate,
 | 
			
		||||
    formatDateISO,
 | 
			
		||||
    formatDateTime,
 | 
			
		||||
    formatSize,
 | 
			
		||||
    localNowDateTime,
 | 
			
		||||
    now,
 | 
			
		||||
    isElectron,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										95
									
								
								src/public/app/widgets/buttons/attachments_actions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/public/app/widgets/buttons/attachments_actions.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
import BasicWidget from "../basic_widget.js";
 | 
			
		||||
 | 
			
		||||
const TPL = `
 | 
			
		||||
<div class="dropdown attachment-actions">
 | 
			
		||||
    <style>
 | 
			
		||||
    .attachment-actions {
 | 
			
		||||
        width: 35px;
 | 
			
		||||
        height: 35px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .attachment-actions .dropdown-menu {
 | 
			
		||||
        width: 15em;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover {
 | 
			
		||||
        color: var(--muted-text-color) !important;
 | 
			
		||||
        background-color: transparent !important;
 | 
			
		||||
        pointer-events: none; /* makes it unclickable */
 | 
			
		||||
    }
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <button type="button" data-toggle="dropdown" aria-haspopup="true" 
 | 
			
		||||
        aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button>
 | 
			
		||||
 | 
			
		||||
    <div class="dropdown-menu dropdown-menu-right">
 | 
			
		||||
        <a data-trigger-command="deleteAttachment" class="dropdown-item delete-attachment-button">Delete attachment</a>
 | 
			
		||||
        <a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Pull attachment into note</a>
 | 
			
		||||
        <a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>`;
 | 
			
		||||
 | 
			
		||||
export default class AttachmentActionsWidget extends BasicWidget {
 | 
			
		||||
    constructor(attachment) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.attachment = attachment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doRender() {
 | 
			
		||||
        this.$widget = $(TPL);
 | 
			
		||||
 | 
			
		||||
        // this.$findInTextButton = this.$widget.find('.find-in-text-button');
 | 
			
		||||
        // this.$printActiveNoteButton = this.$widget.find('.print-active-note-button');
 | 
			
		||||
        // this.$showSourceButton = this.$widget.find('.show-source-button');
 | 
			
		||||
        // this.$renderNoteButton = this.$widget.find('.render-note-button');
 | 
			
		||||
        //
 | 
			
		||||
        // this.$exportNoteButton = this.$widget.find('.export-note-button');
 | 
			
		||||
        // this.$exportNoteButton.on("click", () => {
 | 
			
		||||
        //     if (this.$exportNoteButton.hasClass("disabled")) {
 | 
			
		||||
        //         return;
 | 
			
		||||
        //     }
 | 
			
		||||
        //
 | 
			
		||||
        //     this.triggerCommand("showExportDialog", {
 | 
			
		||||
        //         notePath: this.noteContext.notePath,
 | 
			
		||||
        //         defaultType: "single"
 | 
			
		||||
        //     });
 | 
			
		||||
        // });
 | 
			
		||||
        //
 | 
			
		||||
        // this.$importNoteButton = this.$widget.find('.import-files-button');
 | 
			
		||||
        // this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
 | 
			
		||||
        //
 | 
			
		||||
        // this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
 | 
			
		||||
        //
 | 
			
		||||
        // this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
 | 
			
		||||
        //
 | 
			
		||||
        // this.$deleteNoteButton = this.$widget.find(".delete-note-button");
 | 
			
		||||
        // this.$deleteNoteButton.on("click", () => {
 | 
			
		||||
        //     if (this.note.noteId === 'root') {
 | 
			
		||||
        //         return;
 | 
			
		||||
        //     }
 | 
			
		||||
        //
 | 
			
		||||
        //     branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
 | 
			
		||||
        // });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    refreshWithNote(note) {
 | 
			
		||||
        // this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book', 'search'].includes(note.type));
 | 
			
		||||
        //
 | 
			
		||||
        // this.toggleDisabled(this.$showSourceButton, ['text', 'relationMap', 'mermaid'].includes(note.type));
 | 
			
		||||
        //
 | 
			
		||||
        // this.toggleDisabled(this.$printActiveNoteButton, ['text', 'code'].includes(note.type));
 | 
			
		||||
        //
 | 
			
		||||
        // this.$renderNoteButton.toggle(note.type === 'render');
 | 
			
		||||
        //
 | 
			
		||||
        // this.$openNoteExternallyButton.toggle(utils.isElectron());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toggleDisabled($el, enable) {
 | 
			
		||||
        if (enable) {
 | 
			
		||||
            $el.removeAttr('disabled');
 | 
			
		||||
        } else {
 | 
			
		||||
            $el.attr('disabled', 'disabled');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -85,6 +85,11 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
 | 
			
		||||
    async refreshWithNote(note) {
 | 
			
		||||
        this.clearItems();
 | 
			
		||||
 | 
			
		||||
        if (this.noteContext?.viewScope?.viewMode !== 'default') {
 | 
			
		||||
            this.toggle(false);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // can't use froca since that would count only relations from loaded notes
 | 
			
		||||
        const resp = await server.get(`note-map/${this.noteId}/backlink-count`);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -70,10 +70,16 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async refreshWithNote(note) {
 | 
			
		||||
        this.$noteTitle.val(note.title);
 | 
			
		||||
        const viewMode = this.noteContext.viewScope.viewMode;
 | 
			
		||||
        this.$noteTitle.val(viewMode === 'default'
 | 
			
		||||
            ? note.title
 | 
			
		||||
            : `${viewMode}: ${note.title}`);
 | 
			
		||||
 | 
			
		||||
        this.$noteTitle.prop("readonly", (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
 | 
			
		||||
                                        || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId));
 | 
			
		||||
        this.$noteTitle.prop("readonly",
 | 
			
		||||
            (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
 | 
			
		||||
            || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)
 | 
			
		||||
            || viewMode !== 'default'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this.setProtectedStatus(note);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
 | 
			
		||||
import server from "../../services/server.js";
 | 
			
		||||
import utils from "../../services/utils.js";
 | 
			
		||||
 | 
			
		||||
const TPL = `
 | 
			
		||||
<div class="note-info-widget">
 | 
			
		||||
@ -105,12 +106,12 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
 | 
			
		||||
            this.$subTreeSize.empty().append($('<span class="bx bx-loader bx-spin"></span>'));
 | 
			
		||||
 | 
			
		||||
            const noteSizeResp = await server.get(`stats/note-size/${this.noteId}`);
 | 
			
		||||
            this.$noteSize.text(this.formatSize(noteSizeResp.noteSize));
 | 
			
		||||
            this.$noteSize.text(utils.formatSize(noteSizeResp.noteSize));
 | 
			
		||||
 | 
			
		||||
            const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`);
 | 
			
		||||
 | 
			
		||||
            if (subTreeResp.subTreeNoteCount > 1) {
 | 
			
		||||
                this.$subTreeSize.text(`(subtree size: ${this.formatSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`);
 | 
			
		||||
                this.$subTreeSize.text(`(subtree size: ${utils.formatSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                this.$subTreeSize.text("");
 | 
			
		||||
@ -143,17 +144,6 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
 | 
			
		||||
        this.$noteSizesWrapper.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    formatSize(size) {
 | 
			
		||||
        size = Math.max(Math.round(size / 1024), 1);
 | 
			
		||||
 | 
			
		||||
        if (size < 1024) {
 | 
			
		||||
            return `${size} KiB`;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            return `${Math.round(size / 102.4) / 10} MiB`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    entitiesReloadedEvent({loadResults}) {
 | 
			
		||||
        if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) {
 | 
			
		||||
            this.refresh();
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,29 @@
 | 
			
		||||
import TypeWidget from "./type_widget.js";
 | 
			
		||||
import server from "../../services/server.js";
 | 
			
		||||
import utils from "../../services/utils.js";
 | 
			
		||||
import AttachmentActionsWidget from "../buttons/attachments_actions.js";
 | 
			
		||||
 | 
			
		||||
const TPL = `
 | 
			
		||||
<div class="note-attachments note-detail-printable">
 | 
			
		||||
<div class="attachments note-detail-printable">
 | 
			
		||||
    <style>
 | 
			
		||||
        .note-attachments {
 | 
			
		||||
        .attachments {
 | 
			
		||||
            padding: 15px;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        .attachment-content {
 | 
			
		||||
        .attachment-wrapper {
 | 
			
		||||
            margin-bottom: 20px;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        .attachment-title-line {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: baseline;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        .attachment-details {
 | 
			
		||||
            margin-left: 10px;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        .attachment-content pre {
 | 
			
		||||
            max-height: 400px;
 | 
			
		||||
            background: var(--accented-background-color);
 | 
			
		||||
            padding: 10px;
 | 
			
		||||
@ -16,18 +31,15 @@ const TPL = `
 | 
			
		||||
            margin-bottom: 10px;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        .attachment-details th {
 | 
			
		||||
            padding-left: 10px;
 | 
			
		||||
            padding-right: 10px;
 | 
			
		||||
        .attachment-content img {
 | 
			
		||||
            margin: 10px;
 | 
			
		||||
            max-height: 300px; 
 | 
			
		||||
            max-width: 90%; 
 | 
			
		||||
            object-fit: contain;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <div class="alert alert-info" style="margin: 10px 0 10px 0; padding: 20px;">
 | 
			
		||||
        Note attachments are pieces of data attached to a given note, providing attachment support. 
 | 
			
		||||
        This view is useful for diagnostics.
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <div class="note-attachment-list"></div>
 | 
			
		||||
    <div class="attachment-list"></div>
 | 
			
		||||
</div>`;
 | 
			
		||||
 | 
			
		||||
export default class AttachmentsTypeWidget extends TypeWidget {
 | 
			
		||||
@ -35,13 +47,14 @@ export default class AttachmentsTypeWidget extends TypeWidget {
 | 
			
		||||
 | 
			
		||||
    doRender() {
 | 
			
		||||
        this.$widget = $(TPL);
 | 
			
		||||
        this.$list = this.$widget.find('.note-attachment-list');
 | 
			
		||||
        this.$list = this.$widget.find('.attachment-list');
 | 
			
		||||
 | 
			
		||||
        super.doRender();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async doRefresh(note) {
 | 
			
		||||
        this.$list.empty();
 | 
			
		||||
        this.children = [];
 | 
			
		||||
 | 
			
		||||
        const attachments = await server.get(`notes/${this.noteId}/attachments?includeContent=true`);
 | 
			
		||||
 | 
			
		||||
@ -52,28 +65,36 @@ export default class AttachmentsTypeWidget extends TypeWidget {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const attachment of attachments) {
 | 
			
		||||
            const attachmentActionsWidget = new AttachmentActionsWidget();
 | 
			
		||||
            this.child(attachmentActionsWidget);
 | 
			
		||||
 | 
			
		||||
            this.$list.append(
 | 
			
		||||
                $('<div class="note-attachment-wrapper">')
 | 
			
		||||
                $('<div class="attachment-wrapper">')
 | 
			
		||||
                    .append(
 | 
			
		||||
                        $('<h4>').append($('<span class="attachment-name">').text(attachment.name))
 | 
			
		||||
                    )
 | 
			
		||||
                    .append(
 | 
			
		||||
                        $('<table class="attachment-details">')
 | 
			
		||||
                        $('<div class="attachment-title-line">')
 | 
			
		||||
                            .append($('<h4>').append($('<span class="attachment-title">').text(attachment.title)))
 | 
			
		||||
                            .append(
 | 
			
		||||
                                $('<tr>')
 | 
			
		||||
                                    .append($('<th>').text('Length:'))
 | 
			
		||||
                                    .append($('<td>').text(attachment.contentLength))
 | 
			
		||||
                                    .append($('<th>').text('MIME:'))
 | 
			
		||||
                                    .append($('<td>').text(attachment.mime))
 | 
			
		||||
                                    .append($('<th>').text('Date modified:'))
 | 
			
		||||
                                    .append($('<td>').text(attachment.utcDateModified))
 | 
			
		||||
                                $('<div class="attachment-details">')
 | 
			
		||||
                                    .text(`Role: ${attachment.role}, Size: ${utils.formatSize(attachment.contentLength)}`)
 | 
			
		||||
                            )
 | 
			
		||||
                            .append($('<div style="flex: 1 1;">')) // spacer
 | 
			
		||||
                            .append(attachmentActionsWidget.render())
 | 
			
		||||
                    )
 | 
			
		||||
                    .append(
 | 
			
		||||
                        $('<pre class="attachment-content">')
 | 
			
		||||
                            .text(attachment.content)
 | 
			
		||||
                        $('<div class="attachment-content">')
 | 
			
		||||
                            .append(this.renderContent(attachment))
 | 
			
		||||
                    )
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderContent(attachment) {
 | 
			
		||||
        if (attachment.content) {
 | 
			
		||||
            return $("<pre>").text(attachment.content);
 | 
			
		||||
        } else if (attachment.role === 'image') {
 | 
			
		||||
            return `<img src="api/notes/${attachment.parentId}/images/${attachment.attachmentId}/${encodeURIComponent(attachment.title)}?${attachment.utcDateModified}">`;
 | 
			
		||||
        } else {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -119,6 +119,10 @@ button.close:hover {
 | 
			
		||||
    border-radius: var(--button-border-radius);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-action-always-border {
 | 
			
		||||
    border-color: var(--button-border-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-action:hover:not(.disabled) {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    border-color: var(--button-border-color);
 | 
			
		||||
 | 
			
		||||
@ -142,14 +142,19 @@ function getAttachments(req) {
 | 
			
		||||
    return attachments.map(attachment => {
 | 
			
		||||
       const pojo = attachment.getPojo();
 | 
			
		||||
 | 
			
		||||
       if (includeContent && utils.isStringNote(null, attachment.mime)) {
 | 
			
		||||
           pojo.content = attachment.getContent()?.toString();
 | 
			
		||||
           pojo.contentLength = pojo.content.length;
 | 
			
		||||
       if (includeContent) {
 | 
			
		||||
           if (utils.isStringNote(null, attachment.mime)) {
 | 
			
		||||
               pojo.content = attachment.getContent()?.toString();
 | 
			
		||||
               pojo.contentLength = pojo.content.length;
 | 
			
		||||
 | 
			
		||||
           const MAX_ATTACHMENT_LENGTH = 1_000_000;
 | 
			
		||||
               const MAX_ATTACHMENT_LENGTH = 1_000_000;
 | 
			
		||||
 | 
			
		||||
           if (pojo.content.length > MAX_ATTACHMENT_LENGTH) {
 | 
			
		||||
               pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH);
 | 
			
		||||
               if (pojo.content.length > MAX_ATTACHMENT_LENGTH) {
 | 
			
		||||
                   pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH);
 | 
			
		||||
               }
 | 
			
		||||
           } else {
 | 
			
		||||
               const content = attachment.getContent();
 | 
			
		||||
               pojo.contentLength = content?.length;
 | 
			
		||||
           }
 | 
			
		||||
       }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user