mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	attachment improvements
This commit is contained in:
		| @@ -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; | ||||
|            } | ||||
|        } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user