mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08: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, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|  |       "name": "trilium", | ||||||
|       "version": "0.59.3", |       "version": "0.59.3", | ||||||
|       "hasInstallScript": true, |       "hasInstallScript": true, | ||||||
|       "license": "AGPL-3.0-only", |       "license": "AGPL-3.0-only", | ||||||
|  | |||||||
| @ -39,6 +39,8 @@ class NoteContext extends Component { | |||||||
| 
 | 
 | ||||||
|     async setNote(inputNotePath, opts = {}) { |     async setNote(inputNotePath, opts = {}) { | ||||||
|         opts.triggerSwitchEvent = opts.triggerSwitchEvent !== undefined ? opts.triggerSwitchEvent : true; |         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); |         const resolvedNotePath = await this.getResolvedNotePath(inputNotePath); | ||||||
| 
 | 
 | ||||||
| @ -46,6 +48,10 @@ class NoteContext extends Component { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (this.notePath === resolvedNotePath && utils.areObjectsEqual(this.viewScope, opts.viewScope)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         await this.triggerEvent('beforeNoteSwitch', {noteContext: this}); |         await this.triggerEvent('beforeNoteSwitch', {noteContext: this}); | ||||||
| 
 | 
 | ||||||
|         utils.closeActiveDialog(); |         utils.closeActiveDialog(); | ||||||
| @ -53,8 +59,7 @@ class NoteContext extends Component { | |||||||
|         this.notePath = resolvedNotePath; |         this.notePath = resolvedNotePath; | ||||||
|         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); |         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); | ||||||
| 
 | 
 | ||||||
|         this.viewScope = opts.viewScope || {}; |         this.viewScope = opts.viewScope; | ||||||
|         this.viewScope.viewMode = this.viewScope.viewMode || "default"; |  | ||||||
| 
 | 
 | ||||||
|         this.saveToRecentNotes(resolvedNotePath); |         this.saveToRecentNotes(resolvedNotePath); | ||||||
| 
 | 
 | ||||||
| @ -137,10 +142,6 @@ class NoteContext extends Component { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (resolvedNotePath === this.notePath) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (await hoistedNoteService.checkNoteAccess(resolvedNotePath, this) === false) { |         if (await hoistedNoteService.checkNoteAccess(resolvedNotePath, this) === false) { | ||||||
|             return; // note is outside of hoisted subtree and user chose not to unhoist
 |             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]; |         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 froca from "../services/froca.js"; | ||||||
| import protectedSessionHolder from "../services/protected_session_holder.js"; | import protectedSessionHolder from "../services/protected_session_holder.js"; | ||||||
| import cssClassManager from "../services/css_class_manager.js"; | import cssClassManager from "../services/css_class_manager.js"; | ||||||
|  | import FAttachment from "./fattachment.js"; | ||||||
| 
 | 
 | ||||||
| const LABEL = 'label'; | const LABEL = 'label'; | ||||||
| const RELATION = 'relation'; | const RELATION = 'relation'; | ||||||
|  | |||||||
| @ -78,39 +78,39 @@ async function createNoteLink(notePath, options = {}) { | |||||||
|     return $container; |     return $container; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getNotePathFromLink($link) { | function parseNotePathAndScope($link) { | ||||||
|     const notePathAttr = $link.attr("data-note-path"); |     let notePath = $link.attr("data-note-path"); | ||||||
| 
 |  | ||||||
|     if (notePathAttr) { |  | ||||||
|         return notePathAttr; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|  |     if (!notePath) { | ||||||
|         const url = $link.attr('href'); |         const url = $link.attr('href'); | ||||||
| 
 | 
 | ||||||
|     const notePath = url ? getNotePathFromUrl(url) : null; |         notePath = url ? getNotePathFromUrl(url) : null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const viewScope = { |     const viewScope = { | ||||||
|         viewMode: $link.attr('data-view-mode'), |         viewMode: $link.attr('data-view-mode') || 'default', | ||||||
|         attachmentId: $link.attr('data-attachment-id'), |         attachmentId: $link.attr('data-attachment-id'), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|         notePath, |         notePath, | ||||||
|  |         noteId: treeService.getNoteIdFromNotePath(notePath), | ||||||
|         viewScope |         viewScope | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function goToLink(evt) { | function goToLink(evt) { | ||||||
|     const $link = $(evt.target).closest("a,.block-link"); |     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; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     evt.preventDefault(); |     evt.preventDefault(); | ||||||
|     evt.stopPropagation(); |     evt.stopPropagation(); | ||||||
| 
 | 
 | ||||||
|     const {notePath, viewScope} = getNotePathFromLink($link); |     const { notePath, viewScope } = parseNotePathAndScope($link); | ||||||
| 
 | 
 | ||||||
|     const ctrlKey = utils.isCtrlKey(evt); |     const ctrlKey = utils.isCtrlKey(evt); | ||||||
|     const isLeftClick = evt.which === 1; |     const isLeftClick = evt.which === 1; | ||||||
| @ -135,20 +135,19 @@ function goToLink(evt) { | |||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else { |     else if (hrefLink) { | ||||||
|         if (openInNewTab |         // this branch handles external links
 | ||||||
|             || $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
 |         const isWithinCKLinkDialog = $link.hasClass("ck-link-actions__preview"); | ||||||
|             || $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
 |         const isOutsideCKEditor = $link.closest("[contenteditable]").length === 0; | ||||||
|         ) { | 
 | ||||||
|             if (address) { |         if (openInNewTab || isWithinCKLinkDialog || isOutsideCKEditor) { | ||||||
|                 if (address.toLowerCase().startsWith('http')) { |             if (hrefLink.toLowerCase().startsWith('http')) { | ||||||
|                     window.open(address, '_blank'); |                 window.open(hrefLink, '_blank'); | ||||||
|             } |             } | ||||||
|                 else if (address.toLowerCase().startsWith('file:') && utils.isElectron()) { |             else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) { | ||||||
|                 const electron = utils.dynamicRequire('electron'); |                 const electron = utils.dynamicRequire('electron'); | ||||||
| 
 | 
 | ||||||
|                     electron.shell.openPath(address); |                 electron.shell.openPath(hrefLink); | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -159,7 +158,7 @@ function goToLink(evt) { | |||||||
| function linkContextMenu(e) { | function linkContextMenu(e) { | ||||||
|     const $link = $(e.target).closest("a"); |     const $link = $(e.target).closest("a"); | ||||||
| 
 | 
 | ||||||
|     const {notePath, viewScope} = getNotePathFromLink($link); |     const { notePath, viewScope } = parseNotePathAndScope($link); | ||||||
| 
 | 
 | ||||||
|     if (!notePath) { |     if (!notePath) { | ||||||
|         return; |         return; | ||||||
| @ -223,5 +222,6 @@ export default { | |||||||
|     getNotePathFromUrl, |     getNotePathFromUrl, | ||||||
|     createNoteLink, |     createNoteLink, | ||||||
|     goToLink, |     goToLink, | ||||||
|     loadReferenceLinkTitle |     loadReferenceLinkTitle, | ||||||
|  |     parseNotePathAndScope | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -31,18 +31,12 @@ async function mouseEnterHandler() { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let notePath = linkService.getNotePathFromUrl($link.attr("href")); |     const { notePath, noteId, viewScope } = linkService.parseNotePathAndScope($link); | ||||||
| 
 | 
 | ||||||
|     if (!notePath) { |     if (!notePath || viewScope.viewMode !== 'default') { | ||||||
|         notePath = $link.attr("data-note-path"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!notePath) { |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const noteId = treeService.getNoteIdFromNotePath(notePath); |  | ||||||
| 
 |  | ||||||
|     const note = await froca.getNote(noteId); |     const note = await froca.getNote(noteId); | ||||||
|     const content = await renderTooltip(note); |     const content = await renderTooltip(note); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -365,6 +365,121 @@ function escapeRegExp(str) { | |||||||
|     return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); |     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 { | export default { | ||||||
|     reloadFrontendApp, |     reloadFrontendApp, | ||||||
|     parseDate, |     parseDate, | ||||||
| @ -408,5 +523,6 @@ export default { | |||||||
|     filterAttributeName, |     filterAttributeName, | ||||||
|     isValidAttributeName, |     isValidAttributeName, | ||||||
|     sleep, |     sleep, | ||||||
|     escapeRegExp |     escapeRegExp, | ||||||
|  |     areObjectsEqual | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -20,24 +20,35 @@ const TPL = ` | |||||||
|         } |         } | ||||||
|          |          | ||||||
|         .attachment-content pre { |         .attachment-content pre { | ||||||
|             max-height: 400px; |  | ||||||
|             background: var(--accented-background-color); |             background: var(--accented-background-color); | ||||||
|             padding: 10px; |             padding: 10px; | ||||||
|             margin-top: 10px; |             margin-top: 10px; | ||||||
|             margin-bottom: 10px; |             margin-bottom: 10px; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|  |         .attachment-detail-wrapper.list-view .attachment-content pre { | ||||||
|  |             max-height: 400px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|         .attachment-content img { |         .attachment-content img { | ||||||
|             margin: 10px; |             margin: 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .attachment-detail-wrapper.list-view .attachment-content img { | ||||||
|             max-height: 300px;  |             max-height: 300px;  | ||||||
|             max-width: 90%;  |             max-width: 90%;  | ||||||
|             object-fit: contain; |             object-fit: contain; | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         .attachment-detail-wrapper.full-detail .attachment-content img { | ||||||
|  |             max-width: 90%;  | ||||||
|  |             object-fit: contain; | ||||||
|  |         } | ||||||
|     </style> |     </style> | ||||||
| 
 | 
 | ||||||
|     <div class="attachment-detail-wrapper"> |     <div class="attachment-detail-wrapper"> | ||||||
|         <div class="attachment-title-line"> |         <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 class="attachment-details"></div> | ||||||
|             <div style="flex: 1 1;"></div> |             <div style="flex: 1 1;"></div> | ||||||
|             <div class="attachment-actions-container"></div> |             <div class="attachment-actions-container"></div> | ||||||
| @ -54,6 +65,7 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|         this.contentSized(); |         this.contentSized(); | ||||||
|         this.attachment = attachment; |         this.attachment = attachment; | ||||||
|         this.attachmentActionsWidget = new AttachmentActionsWidget(attachment); |         this.attachmentActionsWidget = new AttachmentActionsWidget(attachment); | ||||||
|  |         this.isFullDetail = true; | ||||||
|         this.child(this.attachmentActionsWidget); |         this.child(this.attachmentActionsWidget); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -73,7 +85,21 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|                     .html() |                     .html() | ||||||
|             ); |             ); | ||||||
|         this.$wrapper = this.$widget.find('.attachment-detail-wrapper'); |         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') |         this.$wrapper.find('.attachment-details') | ||||||
|             .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`); |             .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`); | ||||||
|         this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render()); |         this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render()); | ||||||
|  | |||||||
| @ -30,18 +30,19 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { | |||||||
|         this.children = []; |         this.children = []; | ||||||
|         this.renderedAttachmentIds = new Set(); |         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) { |         if (!attachment) { | ||||||
|             this.$list.html("<strong>This attachment has been deleted.</strong>"); |             this.$wrapper.html("<strong>This attachment has been deleted.</strong>"); | ||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const attachmentDetailWidget = new AttachmentDetailWidget(attachment); |         const attachmentDetailWidget = new AttachmentDetailWidget(attachment); | ||||||
|  |         attachmentDetailWidget.isFullDetail = true; | ||||||
|         this.child(attachmentDetailWidget); |         this.child(attachmentDetailWidget); | ||||||
| 
 | 
 | ||||||
|         this.$list.append(attachmentDetailWidget.render()); |         this.$wrapper.append(attachmentDetailWidget.render()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async entitiesReloadedEvent({loadResults}) { |     async entitiesReloadedEvent({loadResults}) { | ||||||
|  | |||||||
| @ -40,6 +40,8 @@ export default class AttachmentListTypeWidget extends TypeWidget { | |||||||
| 
 | 
 | ||||||
|         for (const attachment of attachments) { |         for (const attachment of attachments) { | ||||||
|             const attachmentDetailWidget = new AttachmentDetailWidget(attachment); |             const attachmentDetailWidget = new AttachmentDetailWidget(attachment); | ||||||
|  |             attachmentDetailWidget.isFullDetail = false; | ||||||
|  | 
 | ||||||
|             this.child(attachmentDetailWidget); |             this.child(attachmentDetailWidget); | ||||||
| 
 | 
 | ||||||
|             this.renderedAttachmentIds.add(attachment.attachmentId); |             this.renderedAttachmentIds.add(attachment.attachmentId); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam