Skip to content

Commit 2fe1354

Browse files
committed
fix(ui): safeguard attachment rendering and tiptap editor teardown
- Guard AttachmentList against missing extendedData and info properties - Guard CardSidebar currentCard watcher when card is deselected - Safeguard Description editor setup and teardown against unmounted states Signed-off-by: aquananu <AQUANANU@GMAIL.COM>
1 parent 623c56b commit 2fe1354

3 files changed

Lines changed: 67 additions & 32 deletions

File tree

‎src/components/card/AttachmentList.vue‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@
4646
<span class="extension">.{{ attachmentExtension(attachment) }}</span>
4747
</div>
4848
<div v-if="attachment.deletedAt === 0">
49-
<span class="filesize">{{ formattedFileSize(attachment.extendedData.filesize) }}</span>
49+
<span class="filesize">{{ formattedFileSize(attachment.extendedData?.filesize) }}</span>
5050
<span class="filedate">{{ relativeDate(attachment.createdAt*1000) }}</span>
51-
<span class="filedate">{{ attachment.extendedData.attachmentCreator.displayName }}</span>
51+
<span class="filedate">{{ attachment.extendedData?.attachmentCreator?.displayName }}</span>
5252
</div>
5353
<div v-else>
5454
<span class="attachment--info">{{ t('deck', 'Pending share') }}</span>
@@ -156,24 +156,24 @@ export default {
156156
if (!attachment) {
157157
return {}
158158
}
159-
const url = attachment.extendedData.hasPreview ? this.attachmentPreview(attachment) : OC.MimeType.getIconUrl(attachment.extendedData.mimetype)
159+
const url = attachment?.extendedData?.hasPreview ? this.attachmentPreview(attachment) : OC.MimeType.getIconUrl(attachment?.extendedData?.mimetype)
160160
const styles = {
161161
'background-image': `url("${url}")`,
162162
}
163163
return styles
164164
}
165165
},
166166
attachmentPreview() {
167-
return (attachment) => (attachment.extendedData.fileid ? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=64&y=64`) : null)
167+
return (attachment) => (attachment?.extendedData?.fileid ? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=64&y=64`) : null)
168168
},
169169
attachmentUrl() {
170-
return (attachment) => generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
170+
return (attachment) => (attachment?.cardId && attachment?.id ? generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`) : '#')
171171
},
172172
internalLink() {
173-
return (attachment) => generateUrl('/f/' + attachment.extendedData.fileid)
173+
return (attachment) => (attachment?.extendedData?.fileid ? generateUrl('/f/' + attachment.extendedData.fileid) : '#')
174174
},
175175
downloadLink() {
176-
return (attachment) => generateRemoteUrl(`dav/files/${getCurrentUser().uid}/${attachment.extendedData.path}`)
176+
return (attachment) => (attachment?.extendedData?.path ? generateRemoteUrl(`dav/files/${getCurrentUser().uid}/${attachment.extendedData.path}`) : '#')
177177
},
178178
formattedFileSize() {
179179
return (filesize) => formatFileSize(filesize)
@@ -194,11 +194,11 @@ export default {
194194
},
195195
attachmentBasename() {
196196
return (attachment) => attachment?.extendedData?.info?.filename
197-
?? (attachment?.name ?? attachment.data).replace(/\.[^/.]+$/, '')
197+
?? (attachment?.name ?? attachment?.data ?? '').replace(/\.[^/.]+$/, '')
198198
},
199199
attachmentExtension() {
200200
return (attachment) => attachment?.extendedData?.info?.extension
201-
?? (attachment?.name ?? attachment.data).split('.').pop()
201+
?? (attachment?.name ?? attachment?.data ?? '').split('.').pop()
202202
},
203203
cardDetailsInModal() {
204204
return this.$store.getters.config('cardDetailsInModal')

‎src/components/card/CardSidebar.vue‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ export default {
209209
watch: {
210210
currentCard(newCard, oldCard) {
211211
if (newCard?.id === oldCard?.id) return
212-
this.focusHeader()
212+
if (newCard) {
213+
this.focusHeader()
214+
}
213215
},
214216
'currentCard.title': {
215217
immediate: true,

‎src/components/card/Description.vue‎

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,12 @@ export default {
183183
184184
this.descriptionOld = newCard.description
185185
this.description = newCard.description
186-
if (this.editor) {
187-
this.editor.setContent(this.description)
186+
if (this.editor && typeof this.editor.setContent === 'function') {
187+
try {
188+
this.editor.setContent(this.description)
189+
} catch (e) {
190+
console.debug('Failed to set editor content', e)
191+
}
188192
}
189193
showWarning(t('deck', 'The description has been changed by another user.'), { timeout: 3000 })
190194
}
@@ -205,29 +209,56 @@ export default {
205209
this.descriptionLastEdit = 0
206210
this.descriptionOld = this.card.description
207211
this.description = this.card.description
208-
this.editor = await window.OCA.Text.createEditor({
209-
el: this.$refs.editor,
210-
content: this.card.description,
211-
readOnly: !this.canEdit,
212-
onLoaded: () => {
213-
this.descriptionLastEdit = 0
214-
},
215-
onUpdate: ({ markdown }) => {
216-
if (this.description === markdown) {
217-
return
212+
if (!window.OCA?.Text?.createEditor || !this.$refs.editor) {
213+
return
214+
}
215+
try {
216+
const editor = await window.OCA.Text.createEditor({
217+
el: this.$refs.editor,
218+
content: this.card.description,
219+
readOnly: !this.canEdit,
220+
onLoaded: () => {
221+
this.descriptionLastEdit = 0
222+
},
223+
onUpdate: ({ markdown }) => {
224+
if (this.description === markdown) {
225+
return
226+
}
227+
this.description = markdown
228+
this.updateDescription()
229+
},
230+
onFileInsert: () => {
231+
this.showAttachmentModal()
232+
},
233+
})
234+
if (this._isBeingDestroyed || this._isDestroyed) {
235+
if (editor && typeof editor.destroy === 'function') {
236+
try {
237+
await editor.destroy()
238+
} catch (e) {
239+
// Ignore teardown error on already destroyed component
240+
}
218241
}
219-
this.description = markdown
220-
this.updateDescription()
221-
},
222-
onFileInsert: () => {
223-
this.showAttachmentModal()
224-
},
225-
})
226-
242+
return
243+
}
244+
this.editor = editor
245+
} catch (e) {
246+
console.warn('Failed to initialize text editor', e)
247+
}
227248
},
228249
async destroyEditor() {
229250
await this.saveDescription()
230-
this?.editor?.destroy()
251+
if (this.editor) {
252+
const editor = this.editor
253+
this.editor = null
254+
try {
255+
if (typeof editor.destroy === 'function' && !editor.isDestroyed) {
256+
await editor.destroy()
257+
}
258+
} catch (e) {
259+
console.debug('Caught editor teardown error', e)
260+
}
261+
}
231262
},
232263
addKeyListeners() {
233264
this.$refs.markdownEditor.easymde.codemirror.on('keydown', (a, b) => {
@@ -268,7 +299,9 @@ export default {
268299
const asImage = (attachment.type === 'file' && attachment.extendedData.hasPreview) || attachment.extendedData.mimetype.includes('image')
269300
// We need to strip those as text does not support rtl yet, so we cannot insert them separately
270301
const stripRTLO = (text) => text.replaceAll('\u202e', '')
271-
const fileName = stripRTLO(attachment.extendedData.info.filename) + '.' + stripRTLO(attachment.extendedData.info.extension)
302+
const base = attachment?.extendedData?.info?.filename ?? attachment?.data ?? ''
303+
const ext = attachment?.extendedData?.info?.extension
304+
const fileName = stripRTLO(base) + (ext ? '.' + stripRTLO(ext) : '')
272305
if (this.editor) {
273306
this.editor.insertAtCursor(
274307
asImage

0 commit comments

Comments
 (0)