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:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user