diff --git a/appinfo/routes.php b/appinfo/routes.php index 51dfba5c5f..e09b88276d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -161,8 +161,8 @@ ['name' => 'attachment_ocs#getAll', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments', 'verb' => 'GET'], ['name' => 'attachment_ocs#create', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachment', 'verb' => 'POST'], - ['name' => 'attachment_ocs#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT'], - ['name' => 'attachment_ocs#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{type}:{attachmentId}', 'verb' => 'DELETE'], + ['name' => 'attachment_ocs#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{type}:{attachmentId}', 'verb' => 'DELETE', 'requirements' => ['type' => '[a-z_]+', 'attachmentId' => '[0-9]+']], + ['name' => 'attachment_ocs#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT', 'requirements' => ['attachmentId' => '[0-9]+']], ['name' => 'attachment_ocs#restore', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}/restore', 'verb' => 'PUT'], ['name' => 'Config#get', 'url' => '/api/v{apiVersion}/config', 'verb' => 'GET'], diff --git a/src/components/card/AttachmentList.vue b/src/components/card/AttachmentList.vue index 394320b68d..a011c5b291 100644 --- a/src/components/card/AttachmentList.vue +++ b/src/components/card/AttachmentList.vue @@ -46,9 +46,9 @@ .{{ attachmentExtension(attachment) }}
- {{ formattedFileSize(attachment.extendedData.filesize) }} + {{ formattedFileSize(attachment.extendedData?.filesize) }} {{ relativeDate(attachment.createdAt*1000) }} - {{ attachment.extendedData.attachmentCreator.displayName }} + {{ attachment.extendedData?.attachmentCreator?.displayName }}
{{ t('deck', 'Pending share') }} @@ -154,7 +154,7 @@ export default { if (!attachment) { return {} } - const url = attachment.extendedData.hasPreview ? this.attachmentPreview(attachment) : OC.MimeType.getIconUrl(attachment.extendedData.mimetype) + const url = attachment?.extendedData?.hasPreview ? this.attachmentPreview(attachment) : OC.MimeType.getIconUrl(attachment?.extendedData?.mimetype) const styles = { 'background-image': `url("${url}")`, } @@ -162,16 +162,16 @@ export default { } }, attachmentPreview() { - return (attachment) => (attachment.extendedData.fileid ? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=64&y=64`) : null) + return (attachment) => (attachment?.extendedData?.fileid ? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=64&y=64`) : null) }, attachmentUrl() { - return (attachment) => generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`) + return (attachment) => (attachment?.cardId && attachment?.id ? generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`) : '#') }, internalLink() { - return (attachment) => generateUrl('/f/' + attachment.extendedData.fileid) + return (attachment) => (attachment?.extendedData?.fileid ? generateUrl('/f/' + attachment.extendedData.fileid) : '#') }, downloadLink() { - return (attachment) => generateRemoteUrl(`dav/files/${getCurrentUser().uid}/${attachment.extendedData.path}`) + return (attachment) => (attachment?.extendedData?.path ? generateRemoteUrl(`dav/files/${getCurrentUser().uid}/${attachment.extendedData.path}`) : '#') }, formattedFileSize() { return (filesize) => formatFileSize(filesize) @@ -190,12 +190,12 @@ export default { } }, attachmentBasename() { - return (attachment) => attachment?.extendedData?.info.filename - ?? (attachment?.name ?? attachment.data).replace(/\.[^/.]+$/, '') + return (attachment) => attachment?.extendedData?.info?.filename + ?? (attachment?.name ?? attachment?.data ?? '').replace(/\.[^/.]+$/, '') }, attachmentExtension() { return (attachment) => attachment?.extendedData?.info?.extension - ?? (attachment?.name ?? attachment.data).split('.').pop() + ?? (attachment?.name ?? attachment?.data ?? '').split('.').pop() }, cardDetailsInModal() { return this.$store.getters.config('cardDetailsInModal') diff --git a/src/components/card/CardSidebar.vue b/src/components/card/CardSidebar.vue index 346aa6cb97..756ae258cb 100644 --- a/src/components/card/CardSidebar.vue +++ b/src/components/card/CardSidebar.vue @@ -205,8 +205,10 @@ export default { }, watch: { currentCard(newCard, oldCard) { - if (newCard.id === oldCard.id) return - this.focusHeader() + if (newCard?.id === oldCard?.id) return + if (newCard) { + this.focusHeader() + } }, 'currentCard.title': { immediate: true, diff --git a/src/components/card/Description.vue b/src/components/card/Description.vue index 1bd3457b91..8235dd2677 100644 --- a/src/components/card/Description.vue +++ b/src/components/card/Description.vue @@ -181,8 +181,12 @@ export default { this.descriptionOld = newCard.description this.description = newCard.description - if (this.editor) { - this.editor.setContent(this.description) + if (this.editor && typeof this.editor.setContent === 'function') { + try { + this.editor.setContent(this.description) + } catch (e) { + console.debug('Failed to set editor content', e) + } } showWarning(t('deck', 'The description has been changed by another user.'), { timeout: 3000 }) } @@ -200,29 +204,56 @@ export default { this.descriptionLastEdit = 0 this.descriptionOld = this.card.description this.description = this.card.description - this.editor = await window.OCA.Text.createEditor({ - el: this.$refs.editor, - content: this.card.description, - readOnly: !this.canEdit, - onLoaded: () => { - this.descriptionLastEdit = 0 - }, - onUpdate: ({ markdown }) => { - if (this.description === markdown) { - return + if (!window.OCA?.Text?.createEditor || !this.$refs.editor) { + return + } + try { + const editor = await window.OCA.Text.createEditor({ + el: this.$refs.editor, + content: this.card.description, + readOnly: !this.canEdit, + onLoaded: () => { + this.descriptionLastEdit = 0 + }, + onUpdate: ({ markdown }) => { + if (this.description === markdown) { + return + } + this.description = markdown + this.updateDescription() + }, + onFileInsert: () => { + this.showAttachmentModal() + }, + }) + if (this._isBeingDestroyed || this._isDestroyed) { + if (editor && typeof editor.destroy === 'function') { + try { + await editor.destroy() + } catch (e) { + // Ignore teardown error on already destroyed component + } } - this.description = markdown - this.updateDescription() - }, - onFileInsert: () => { - this.showAttachmentModal() - }, - }) - + return + } + this.editor = editor + } catch (e) { + console.warn('Failed to initialize text editor', e) + } }, async destroyEditor() { await this.saveDescription() - this?.editor?.destroy() + if (this.editor) { + const editor = this.editor + this.editor = null + try { + if (typeof editor.destroy === 'function' && !editor.isDestroyed) { + await editor.destroy() + } + } catch (e) { + console.debug('Caught editor teardown error', e) + } + } }, addKeyListeners() { this.$refs.markdownEditor.easymde.codemirror.on('keydown', (a, b) => { @@ -263,7 +294,9 @@ export default { const asImage = (attachment.type === 'file' && attachment.extendedData.hasPreview) || attachment.extendedData.mimetype.includes('image') // We need to strip those as text does not support rtl yet, so we cannot insert them separately const stripRTLO = (text) => text.replaceAll('\u202e', '') - const fileName = stripRTLO(attachment.extendedData.info.filename) + '.' + stripRTLO(attachment.extendedData.info.extension) + const base = attachment?.extendedData?.info?.filename ?? attachment?.data ?? '' + const ext = attachment?.extendedData?.info?.extension + const fileName = stripRTLO(base) + (ext ? '.' + stripRTLO(ext) : '') if (this.editor) { this.editor.insertAtCursor( asImage diff --git a/src/components/overview/Overview.vue b/src/components/overview/Overview.vue index b86e35b13d..299ce5fced 100644 --- a/src/components/overview/Overview.vue +++ b/src/components/overview/Overview.vue @@ -7,6 +7,13 @@
+
+ +
+

{{ t('deck', 'Loading filtered view') }}

@@ -24,14 +31,30 @@
@@ -98,6 +121,7 @@ export default { return { loading: true, columnPropsList: COLUMN_PROPS_LIST, + simpleListMode: true, } }, computed: { @@ -156,6 +180,28 @@ export default {