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; |     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) { | function toObject(array, fn) { | ||||||
|     const obj = {}; |     const obj = {}; | ||||||
|  |  | ||||||
| @@ -363,6 +374,7 @@ export default { | |||||||
|     formatDate, |     formatDate, | ||||||
|     formatDateISO, |     formatDateISO, | ||||||
|     formatDateTime, |     formatDateTime, | ||||||
|  |     formatSize, | ||||||
|     localNowDateTime, |     localNowDateTime, | ||||||
|     now, |     now, | ||||||
|     isElectron, |     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) { |     async refreshWithNote(note) { | ||||||
|         this.clearItems(); |         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 |         // can't use froca since that would count only relations from loaded notes | ||||||
|         const resp = await server.get(`note-map/${this.noteId}/backlink-count`); |         const resp = await server.get(`note-map/${this.noteId}/backlink-count`); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -70,10 +70,16 @@ export default class NoteTitleWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshWithNote(note) { |     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()) |         this.$noteTitle.prop("readonly", | ||||||
|                                         || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)); |             (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) | ||||||
|  |             || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId) | ||||||
|  |             || viewMode !== 'default' | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         this.setProtectedStatus(note); |         this.setProtectedStatus(note); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import NoteContextAwareWidget from "../note_context_aware_widget.js"; | import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
|  | import utils from "../../services/utils.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="note-info-widget"> | <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>')); |             this.$subTreeSize.empty().append($('<span class="bx bx-loader bx-spin"></span>')); | ||||||
|  |  | ||||||
|             const noteSizeResp = await server.get(`stats/note-size/${this.noteId}`); |             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}`); |             const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`); | ||||||
|  |  | ||||||
|             if (subTreeResp.subTreeNoteCount > 1) { |             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 { |             else { | ||||||
|                 this.$subTreeSize.text(""); |                 this.$subTreeSize.text(""); | ||||||
| @@ -143,17 +144,6 @@ export default class NoteInfoWidget extends NoteContextAwareWidget { | |||||||
|         this.$noteSizesWrapper.hide(); |         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}) { |     entitiesReloadedEvent({loadResults}) { | ||||||
|         if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) { |         if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) { | ||||||
|             this.refresh(); |             this.refresh(); | ||||||
|   | |||||||
| @@ -1,14 +1,29 @@ | |||||||
| import TypeWidget from "./type_widget.js"; | import TypeWidget from "./type_widget.js"; | ||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
|  | import utils from "../../services/utils.js"; | ||||||
|  | import AttachmentActionsWidget from "../buttons/attachments_actions.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="note-attachments note-detail-printable"> | <div class="attachments note-detail-printable"> | ||||||
|     <style> |     <style> | ||||||
|         .note-attachments { |         .attachments { | ||||||
|             padding: 15px; |             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; |             max-height: 400px; | ||||||
|             background: var(--accented-background-color); |             background: var(--accented-background-color); | ||||||
|             padding: 10px; |             padding: 10px; | ||||||
| @@ -16,18 +31,15 @@ const TPL = ` | |||||||
|             margin-bottom: 10px; |             margin-bottom: 10px; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         .attachment-details th { |         .attachment-content img { | ||||||
|             padding-left: 10px; |             margin: 10px; | ||||||
|             padding-right: 10px; |             max-height: 300px;  | ||||||
|  |             max-width: 90%;  | ||||||
|  |             object-fit: contain; | ||||||
|         } |         } | ||||||
|     </style> |     </style> | ||||||
|  |  | ||||||
|     <div class="alert alert-info" style="margin: 10px 0 10px 0; padding: 20px;"> |     <div class="attachment-list"></div> | ||||||
|         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>`; | </div>`; | ||||||
|  |  | ||||||
| export default class AttachmentsTypeWidget extends TypeWidget { | export default class AttachmentsTypeWidget extends TypeWidget { | ||||||
| @@ -35,13 +47,14 @@ export default class AttachmentsTypeWidget extends TypeWidget { | |||||||
|  |  | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$list = this.$widget.find('.note-attachment-list'); |         this.$list = this.$widget.find('.attachment-list'); | ||||||
|  |  | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async doRefresh(note) { |     async doRefresh(note) { | ||||||
|         this.$list.empty(); |         this.$list.empty(); | ||||||
|  |         this.children = []; | ||||||
|  |  | ||||||
|         const attachments = await server.get(`notes/${this.noteId}/attachments?includeContent=true`); |         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) { |         for (const attachment of attachments) { | ||||||
|  |             const attachmentActionsWidget = new AttachmentActionsWidget(); | ||||||
|  |             this.child(attachmentActionsWidget); | ||||||
|  |  | ||||||
|             this.$list.append( |             this.$list.append( | ||||||
|                 $('<div class="note-attachment-wrapper">') |                 $('<div class="attachment-wrapper">') | ||||||
|                     .append( |                     .append( | ||||||
|                         $('<h4>').append($('<span class="attachment-name">').text(attachment.name)) |                         $('<div class="attachment-title-line">') | ||||||
|                     ) |                             .append($('<h4>').append($('<span class="attachment-title">').text(attachment.title))) | ||||||
|                     .append( |  | ||||||
|                         $('<table class="attachment-details">') |  | ||||||
|                             .append( |                             .append( | ||||||
|                                 $('<tr>') |                                 $('<div class="attachment-details">') | ||||||
|                                     .append($('<th>').text('Length:')) |                                     .text(`Role: ${attachment.role}, Size: ${utils.formatSize(attachment.contentLength)}`) | ||||||
|                                     .append($('<td>').text(attachment.contentLength)) |  | ||||||
|                                     .append($('<th>').text('MIME:')) |  | ||||||
|                                     .append($('<td>').text(attachment.mime)) |  | ||||||
|                                     .append($('<th>').text('Date modified:')) |  | ||||||
|                                     .append($('<td>').text(attachment.utcDateModified)) |  | ||||||
|                             ) |                             ) | ||||||
|  |                             .append($('<div style="flex: 1 1;">')) // spacer | ||||||
|  |                             .append(attachmentActionsWidget.render()) | ||||||
|                     ) |                     ) | ||||||
|                     .append( |                     .append( | ||||||
|                         $('<pre class="attachment-content">') |                         $('<div class="attachment-content">') | ||||||
|                             .text(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); |     border-radius: var(--button-border-radius); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .icon-action-always-border { | ||||||
|  |     border-color: var(--button-border-color); | ||||||
|  | } | ||||||
|  |  | ||||||
| .icon-action:hover:not(.disabled) { | .icon-action:hover:not(.disabled) { | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     border-color: var(--button-border-color); |     border-color: var(--button-border-color); | ||||||
|   | |||||||
| @@ -142,14 +142,19 @@ function getAttachments(req) { | |||||||
|     return attachments.map(attachment => { |     return attachments.map(attachment => { | ||||||
|        const pojo = attachment.getPojo(); |        const pojo = attachment.getPojo(); | ||||||
|  |  | ||||||
|        if (includeContent && utils.isStringNote(null, attachment.mime)) { |        if (includeContent) { | ||||||
|            pojo.content = attachment.getContent()?.toString(); |            if (utils.isStringNote(null, attachment.mime)) { | ||||||
|            pojo.contentLength = pojo.content.length; |                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) { |                if (pojo.content.length > MAX_ATTACHMENT_LENGTH) { | ||||||
|                pojo.content = pojo.content.substring(0, 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