fix: Fix title selection and editing

Signed-off-by: Julius Knorr <jus@bitgrid.net>
This commit is contained in:
Julius Knorr
2024-09-18 21:25:16 +02:00
parent 047fcb6584
commit c5c8a6ef71
3 changed files with 61 additions and 15 deletions

View File

@@ -19,12 +19,12 @@
</div> </div>
<CardCover v-if="showCardCover" :card-id="card.id" /> <CardCover v-if="showCardCover" :card-id="card.id" />
<div class="card-upper"> <div class="card-upper">
<h4 v-if="inlineEditingBlocked" dir="auto"> <h4 v-if="!editingTitle" dir="auto">
{{ displayTitle }} <span v-auto-link class="dragDisabled">{{ displayTitle }}</span>
</h4> </h4>
<h4 v-else <h4 v-else
dir="auto" dir="auto"
class="editable" class="editable dragDisabled"
:aria-label="t('deck', 'Edit card title')"> :aria-label="t('deck', 'Edit card title')">
<span ref="titleContentEditable" <span ref="titleContentEditable"
tabindex="0" tabindex="0"
@@ -38,7 +38,10 @@
</h4> </h4>
<DueDate v-if="compactMode" :card="card" /> <DueDate v-if="compactMode" :card="card" />
<CardMenu v-if="showMenuAtTitle" :card="card" class="right card-menu" /> <CardMenu v-if="showMenuAtTitle"
:card="card"
class="right card-menu"
@edit-title="triggerEditTitle" />
</div> </div>
<div v-if="hasLabels" class="card-labels"> <div v-if="hasLabels" class="card-labels">
@@ -51,7 +54,10 @@
<span @click.stop="applyLabelFilter(label)">{{ label.title }}</span> <span @click.stop="applyLabelFilter(label)">{{ label.title }}</span>
</li> </li>
</transition-group> </transition-group>
<CardMenu v-if="showMenuAtLabels" :card="card" class="right" /> <CardMenu v-if="showMenuAtLabels"
:card="card"
class="right"
@edit-title="triggerEditTitle" />
</div> </div>
<div v-if="hasBadges" <div v-if="hasBadges"
@@ -59,7 +65,10 @@
class="card-controls compact-item" class="card-controls compact-item"
@click="openCard"> @click="openCard">
<CardBadges :card="card"> <CardBadges :card="card">
<CardMenu v-if="showMenuAtBadges" :card="card" class="right" /> <CardMenu v-if="showMenuAtBadges"
:card="card"
class="right"
@edit-title="triggerEditTitle" />
</CardBadges> </CardBadges>
</div> </div>
</div> </div>
@@ -78,6 +87,12 @@ import CardCover from './CardCover.vue'
import DueDate from './badges/DueDate.vue' import DueDate from './badges/DueDate.vue'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
const TITLE_EDITING_STATE = {
OFF: 0,
PENDING: 1,
ON: 2,
}
export default { export default {
name: 'CardItem', name: 'CardItem',
components: { CardBadges, AttachmentDragAndDrop, CardMenu, CardCover, DueDate }, components: { CardBadges, AttachmentDragAndDrop, CardMenu, CardCover, DueDate },
@@ -106,6 +121,7 @@ export default {
data() { data() {
return { return {
highlight: false, highlight: false,
editingTitle: TITLE_EDITING_STATE.OFF,
} }
}, },
computed: { computed: {
@@ -132,9 +148,6 @@ export default {
const board = this.$store.getters.boards.find((item) => item.id === this.card.boardId) const board = this.$store.getters.boards.find((item) => item.id === this.card.boardId)
return board ? !board.archived && board.permissions.PERMISSION_EDIT : false return board ? !board.archived && board.permissions.PERMISSION_EDIT : false
}, },
inlineEditingBlocked() {
return this.card.referenceData || this.isArchived || this.showArchived || !this.canEdit || this.standalone
},
card() { card() {
return this.item ? this.item : this.$store.getters.cardById(this.id) return this.item ? this.item : this.$store.getters.cardById(this.id)
}, },
@@ -193,15 +206,19 @@ export default {
}, },
}, },
methods: { methods: {
hasSelection() {
const selection = window.getSelection()
return selection.toString() !== ''
},
focus(card) { focus(card) {
if (this.shortcutLock) { if (this.shortcutLock || this.hasSelection()) {
return return
} }
card = this.$refs[`card${card}`] card = this.$refs[`card${card}`]
card.focus() card.focus()
}, },
openCard() { openCard() {
if (this.dragging) { if (this.dragging || this.hasSelection()) {
return return
} }
const boardId = this.card && this.card.boardId ? this.card.boardId : (this.$route?.params.id ?? this.currentBoard.id) const boardId = this.card && this.card.boardId ? this.card.boardId : (this.$route?.params.id ?? this.currentBoard.id)
@@ -213,8 +230,19 @@ export default {
this.$root.$emit('open-card', this.card.id) this.$root.$emit('open-card', this.card.id)
}, },
triggerEditTitle() {
this.editingTitle = TITLE_EDITING_STATE.PENDING
this.$store.dispatch('toggleShortcutLock', true)
setTimeout(() => {
this.$refs.titleContentEditable.focus()
this.editingTitle = TITLE_EDITING_STATE.ON
}, 0)
},
onTitleBlur(e) { onTitleBlur(e) {
// TODO Handle empty title if (this.editingTitle !== TITLE_EDITING_STATE.ON || e.target.innerText === '') {
return
}
this.editingTitle = TITLE_EDITING_STATE.OFF
if (e.target.innerText !== this.card.title) { if (e.target.innerText !== this.card.title) {
this.$store.dispatch('updateCardTitle', { this.$store.dispatch('updateCardTitle', {
...this.card, ...this.card,

View File

@@ -5,13 +5,16 @@
<template> <template>
<div v-if="card" class="card-menu" @click.stop.prevent> <div v-if="card" class="card-menu" @click.stop.prevent>
<NcButton v-if="card.referenceData" type="tertiary" @click="openLink"> <NcButton v-if="card.referenceData"
type="tertiary"
:title="t('deck','Open link')"
@click="openLink">
<template #icon> <template #icon>
<LinkIcon :size="20" /> <LinkIcon :size="20" />
</template> </template>
</NcButton> </NcButton>
<NcActions> <NcActions>
<CardMenuEntries :card="card" /> <CardMenuEntries :card="card" @edit-title="editTitle" />
</NcActions> </NcActions>
</div> </div>
</template> </template>
@@ -29,11 +32,15 @@ export default {
default: null, default: null,
}, },
}, },
emits: ['edit-title'],
methods: { methods: {
openLink() { openLink() {
window.open(this.card?.referenceData?.openGraphObject?.link) window.open(this.card?.referenceData?.openGraphObject?.link)
return false return false
}, },
editTitle(id) {
this.$emit('edit-title', id)
},
}, },
} }
</script> </script>

View File

@@ -9,6 +9,12 @@
<CardBulletedIcon slot="icon" :size="20" decorative /> <CardBulletedIcon slot="icon" :size="20" decorative />
{{ t('deck', 'Card details') }} {{ t('deck', 'Card details') }}
</NcActionButton> </NcActionButton>
<NcActionButton v-if="canEdit" :close-after-click="true" @click="editTitle">
<template #icon>
<PencilIcon :size="20" decorative />
</template>
{{ t('deck', 'Edit title') }}
</NcActionButton>
<NcActionButton v-if="canEdit && !isCurrentUserAssigned" <NcActionButton v-if="canEdit && !isCurrentUserAssigned"
icon="icon-user" icon="icon-user"
:close-after-click="true" :close-after-click="true"
@@ -59,6 +65,7 @@ import { NcActionButton } from '@nextcloud/vue'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue' import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import CardBulletedIcon from 'vue-material-design-icons/CardBulleted.vue' import CardBulletedIcon from 'vue-material-design-icons/CardBulleted.vue'
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
import { showUndo } from '@nextcloud/dialogs' import { showUndo } from '@nextcloud/dialogs'
@@ -68,7 +75,7 @@ import { emit } from '@nextcloud/event-bus'
export default { export default {
name: 'CardMenuEntries', name: 'CardMenuEntries',
components: { NcActionButton, ArchiveIcon, CardBulletedIcon }, components: { NcActionButton, ArchiveIcon, CardBulletedIcon, PencilIcon },
props: { props: {
card: { card: {
type: Object, type: Object,
@@ -79,6 +86,7 @@ export default {
default: false, default: false,
}, },
}, },
emits: ['edit-title'],
data() { data() {
return { return {
modalShow: false, modalShow: false,
@@ -136,6 +144,9 @@ export default {
this.$root.$emit('open-card', this.card.id) this.$root.$emit('open-card', this.card.id)
}, },
editTitle() {
this.$emit('edit-title', this.card.id)
},
deleteCard() { deleteCard() {
this.$store.dispatch('deleteCard', this.card) this.$store.dispatch('deleteCard', this.card)
const undoCard = { ...this.card, deletedAt: 0 } const undoCard = { ...this.card, deletedAt: 0 }