feat: Move to contenteditable for inline title editing

fix #2563
fix #4522

Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
Julius Härtl
2023-11-12 11:05:43 +01:00
parent 2703f7111a
commit a6b561acb7

View File

@@ -26,7 +26,7 @@
<template> <template>
<AttachmentDragAndDrop v-if="card" :card-id="card.id" class="drop-upload--card"> <AttachmentDragAndDrop v-if="card" :card-id="card.id" class="drop-upload--card">
<div :class="{'compact': compactMode, 'current-card': currentCard, 'has-labels': card.labels && card.labels.length > 0, 'is-editing': editing, 'card__editable': canEdit, 'card__archived': card.archived }" <div :class="{'compact': compactMode, 'current-card': currentCard, 'has-labels': card.labels && card.labels.length > 0, 'card__editable': canEdit, 'card__archived': card.archived }"
tag="div" tag="div"
class="card" class="card"
@click="openCard"> @click="openCard">
@@ -39,29 +39,19 @@
<h3 v-if="inlineEditingBlocked" dir="auto"> <h3 v-if="inlineEditingBlocked" dir="auto">
{{ card.title }} {{ card.title }}
</h3> </h3>
<h3 v-else-if="!editing" <h3 v-else
dir="auto" dir="auto"
tabindex="0"
class="editable" class="editable"
:aria-label="t('deck', 'Edit card title')" :aria-label="t('deck', 'Edit card title')">
@keydown.enter.stop.prevent="startEditing(card)"> <span ref="titleContentEditable"
<span @click.stop="startEditing(card)">{{ card.title }}</span> tabindex="0"
</h3> contenteditable="true"
<form v-else-if="editing" role="textbox"
v-click-outside="cancelEdit" @blur="blurTitle"
class="dragDisabled"
@click.stop @click.stop
@keyup.esc="cancelEdit" @keyup.esc="cancelEdit"
@submit.prevent="finishedEdit(card)"> @keyup.stop>{{ card.title }}</span>
<input v-model="copiedCard.title" </h3>
v-focus
dir="auto"
type="text"
autocomplete="off"
required
pattern=".*\S+.*">
<input type="submit" value="" class="icon-confirm">
</form>
<CardMenu v-if="showMenuAtTitle" :card="card" class="right card-menu" /> <CardMenu v-if="showMenuAtTitle" :card="card" class="right card-menu" />
</div> </div>
@@ -126,12 +116,6 @@ export default {
default: false, default: false,
}, },
}, },
data() {
return {
editing: false,
copiedCard: null,
}
},
computed: { computed: {
...mapState({ ...mapState({
compactMode: state => state.compactMode, compactMode: state => state.compactMode,
@@ -183,9 +167,6 @@ export default {
return this.$store.getters.config('cardIdBadge') return this.$store.getters.config('cardIdBadge')
}, },
showMenuAtTitle() { showMenuAtTitle() {
if (this.editing) {
return false
}
return this.compactMode || (!this.compactMode && !this.hasBadges && !this.hasLabels) return this.compactMode || (!this.compactMode && !this.hasBadges && !this.hasLabels)
}, },
showMenuAtLabels() { showMenuAtLabels() {
@@ -207,6 +188,12 @@ export default {
this.$nextTick(() => this.$el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' })) this.$nextTick(() => this.$el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }))
} }
}, },
'card.title'(value) {
if (document.activeElement === this.$refs.titleContentEditable || this.$refs.titleContentEditable.textContent === value) {
return
}
this.$refs.titleContentEditable.textContent = value
},
}, },
methods: { methods: {
openCard() { openCard() {
@@ -216,18 +203,17 @@ export default {
const boardId = this.card && this.card.boardId ? this.card.boardId : this.$route.params.id const boardId = this.card && this.card.boardId ? this.card.boardId : this.$route.params.id
this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {}) this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
}, },
startEditing(card) { blurTitle(e) {
this.copiedCard = Object.assign({}, card) // TODO Handle empty title
this.editing = true if (e.target.innerText !== this.card.title) {
}, this.$store.dispatch('updateCardTitle', {
finishedEdit(card) { ...this.card,
if (this.copiedCard.title !== card.title) { title: e.target.innerText,
this.$store.dispatch('updateCardTitle', this.copiedCard) })
} }
this.editing = false
}, },
cancelEdit() { cancelEdit() {
this.editing = false this.$refs.titleContentEditable.textContent = this.card.title
}, },
applyLabelFilter(label) { applyLabelFilter(label) {
if (this.dragging) { if (this.dragging) {
@@ -272,22 +258,13 @@ export default {
border: 2px solid var(--color-primary-element); border: 2px solid var(--color-primary-element);
} }
&:focus, &:focus-visible, &:focus-within, &:hover { &:focus, &:focus-visible, &:focus-within {
outline: none; outline: none;
border: 2px solid var(--color-primary-element); border: 2px solid var(--color-primary-element);
} }
.card-upper { .card-upper {
display: flex; display: flex;
form {
display: flex;
padding: 3px 5px;
width: 100%;
input[type=text] {
flex-grow: 1;
}
}
h3 { h3 {
margin: 0; margin: 0;
padding: 6px; padding: 6px;
@@ -300,17 +277,21 @@ export default {
&.editable { &.editable {
span { span {
cursor: text; cursor: text;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
&:focus, &:focus-visible {
outline: none;
}
} }
&:focus { &:focus-within {
outline: 2px solid var(--color-border-dark); outline: 2px solid var(--color-border-dark);
border-radius: 3px; border-radius: 3px;
} }
} }
} }
input[type=text] {
font-size: 100%;
}
.card-menu { .card-menu {
height: 44px; height: 44px;
align-self: end; align-self: end;