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 @@
-
+
+
+ {{ card.title }}
+
+
+
-
+
+
+ {{ card.title }}
+
+
+
@@ -98,6 +121,7 @@ export default {
return {
loading: true,
columnPropsList: COLUMN_PROPS_LIST,
+ simpleListMode: true,
}
},
computed: {
@@ -156,6 +180,28 @@ export default {