Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow reordering questions using the keyboard #1532

Merged
merged 3 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 74 additions & 6 deletions src/components/Questions/Question.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,41 @@

<template>
<li v-click-outside="disableEdit"
:class="{ 'question--edit': edit }"
:class="{
'question': true,
'question--edit': edit,
'question--editable': !readOnly
}"
:aria-label="t('forms', 'Question number {index}', {index})"
class="question"
@click="enableEdit">
<!-- Drag handle -->
<!-- TODO: implement arrow key mapping to reorder question -->
<div v-if="!readOnly"
class="question__drag-handle"
:class="{'question__drag-handle--shiftup': shiftDragHandle}"
:aria-label="t('forms', 'Drag to reorder the questions')">
:class="{
'question__drag-handle': true,
'question__drag-handle--shiftup': shiftDragHandle
}">
<NcButton ref="buttonUp"
:aria-label="t('forms', 'Move question up')"
:disabled="!canMoveUp"
class="question__drag-handle-button"
type="tertiary-no-background"
@click.stop="onMoveUp">
<template #icon>
<IconArrowUp :size="20" />
</template>
</NcButton>
<IconDragHorizontalVariant :size="20" />
<NcButton ref="buttonDown"
:aria-label="t('forms', 'Move question down')"
:disabled="!canMoveDown"
class="question__drag-handle-button"
type="tertiary-no-background"
@click.stop="onMoveDown">
<template #icon>
<IconArrowDown :size="20" />
</template>
</NcButton>
</div>

<!-- Header -->
Expand Down Expand Up @@ -113,8 +137,11 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'

import IconAlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
import IconArrowDown from 'vue-material-design-icons/ArrowDown.vue'
import IconArrowUp from 'vue-material-design-icons/ArrowUp.vue'
import IconDelete from 'vue-material-design-icons/Delete.vue'
import IconDragHorizontalVariant from 'vue-material-design-icons/DragHorizontalVariant.vue'
import IconIdentifier from 'vue-material-design-icons/Identifier.vue'
Expand All @@ -128,13 +155,16 @@ export default {

components: {
IconAlertCircleOutline,
IconArrowDown,
IconArrowUp,
IconDelete,
IconDragHorizontalVariant,
IconIdentifier,
NcActions,
NcActionButton,
NcActionCheckbox,
NcActionInput,
NcButton,
},

inject: ['$markdownit'],
Expand Down Expand Up @@ -188,6 +218,14 @@ export default {
type: String,
default: t('forms', 'This question needs a title!'),
},
canMoveDown: {
type: Boolean,
default: false,
},
canMoveUp: {
type: Boolean,
default: false,
},
},

computed: {
Expand Down Expand Up @@ -269,6 +307,18 @@ export default {
})
},

/**
* Reorder question but keep focus on the button
*/
onMoveDown() {
this.$emit('move-down')
this.$nextTick(() => this.$refs.buttonDown.$el.focus())
},
onMoveUp() {
this.$emit('move-up')
this.$nextTick(() => this.$refs.buttonUp.$el.focus())
},

/**
* Enable the edit mode
*/
Expand Down Expand Up @@ -311,6 +361,10 @@ export default {
user-select: none;
background-color: var(--color-main-background);

&--editable {
padding-inline-start: 56px; // add 12px for the title input box
}

> * {
cursor: pointer;
}
Expand All @@ -319,19 +373,33 @@ export default {
position: absolute;
display: flex;
inset-inline-start: 0;
flex-direction: column;
justify-content: center;
gap: 12px;
width: 44px;
height: 100%;
opacity: .5;
cursor: grab;

&-button {
position: absolute;
inset-block-start: -9999px;
}

// Avoid moving drag-handle due to newAnswer-input on multiple-Questions
&--shiftup {
height: calc(100% - 44px);
}

&:hover,
&:focus {
&:focus,
&:focus-within {
opacity: 1;

.question__drag-handle-button {
position: initial;
inset-block-start: initial;
}
}

&:active {
Expand Down
6 changes: 1 addition & 5 deletions src/components/Questions/QuestionDate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@
:max-string-lengths="maxStringLengths"
:title-placeholder="answerType.titlePlaceholder"
:warning-invalid="answerType.warningInvalid"
@update:text="onTitleChange"
@update:description="onDescriptionChange"
@update:isRequired="onRequiredChange"
@update:name="onNameChange"
@delete="onDelete">
susnux marked this conversation as resolved.
Show resolved Hide resolved
v-on="commonListeners">
<div class="question__content">
<NcDatetimePicker v-model="time"
:disabled="!readOnly"
Expand Down
6 changes: 1 addition & 5 deletions src/components/Questions/QuestionDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@
:warning-invalid="answerType.warningInvalid"
:content-valid="contentValid"
:shift-drag-handle="shiftDragHandle"
@update:text="onTitleChange"
@update:description="onDescriptionChange"
@update:isRequired="onRequiredChange"
@update:name="onNameChange"
@delete="onDelete">
v-on="commonListeners">
<template #actions>
<NcActionCheckbox :checked="extraSettings?.shuffleOptions"
@update:checked="onShuffleOptionsChange">
Expand Down
6 changes: 1 addition & 5 deletions src/components/Questions/QuestionLong.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@
:max-string-lengths="maxStringLengths"
:title-placeholder="answerType.titlePlaceholder"
:warning-invalid="answerType.warningInvalid"
@update:text="onTitleChange"
@update:description="onDescriptionChange"
@update:isRequired="onRequiredChange"
@update:name="onNameChange"
@delete="onDelete">
v-on="commonListeners">
<div class="question__content">
<textarea ref="textarea"
:aria-label="t('forms', 'A long answer for the question “{text}”', { text })"
Expand Down
6 changes: 1 addition & 5 deletions src/components/Questions/QuestionMultiple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@
:warning-invalid="answerType.warningInvalid"
:content-valid="contentValid"
:shift-drag-handle="shiftDragHandle"
@update:text="onTitleChange"
@update:description="onDescriptionChange"
@update:isRequired="onRequiredChange"
@update:name="onNameChange"
@delete="onDelete">
v-on="commonListeners">
<template #actions>
<NcActionCheckbox :checked="extraSettings?.shuffleOptions"
@update:checked="onShuffleOptionsChange">
Expand Down
6 changes: 1 addition & 5 deletions src/components/Questions/QuestionShort.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@
:max-string-lengths="maxStringLengths"
:title-placeholder="answerType.titlePlaceholder"
:warning-invalid="answerType.warningInvalid"
@update:text="onTitleChange"
@update:description="onDescriptionChange"
@update:isRequired="onRequiredChange"
@update:name="onNameChange"
@delete="onDelete">
v-on="commonListeners">
<div class="question__content">
<input ref="input"
:aria-label="t('forms', 'A short answer for the question “{text}”', { text })"
Expand Down
39 changes: 39 additions & 0 deletions src/mixins/QuestionMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ export default {
required: true,
},

/**
* ID of the form
*/
formId: {
type: Number,
default: null,
},

/**
* The question title
*/
Expand Down Expand Up @@ -90,6 +98,22 @@ export default {
required: true,
},

/**
* Order of the question
*/
order: {
type: Number,
default: -1,
},

/**
* Question type
*/
type: {
type: String,
default: null,
},

/**
* Answer type model object
*/
Expand Down Expand Up @@ -144,6 +168,21 @@ export default {
// Ensure order of options always is the same
return [...this.options].sort((a, b) => a.id - b.id)
},

/**
* Listeners for all questions to forward
*/
commonListeners() {
return {
delete: this.onDelete,
'update:text': this.onTitleChange,
'update:description': this.onDescriptionChange,
'update:isRequired': this.onRequiredChange,
'update:name': this.onNameChange,
'move-down': (...args) => this.$emit('move-down', ...args),
'move-up': (...args) => this.$emit('move-up', ...args),
}
},
},

methods: {
Expand Down
45 changes: 35 additions & 10 deletions src/views/Create.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,21 @@
@change="onQuestionOrderChange"
@start="isDragging = true"
@end="isDragging = false">
<Questions :is="answerTypes[question.type].component"
v-for="(question, index) in form.questions"
ref="questions"
:key="question.id"
:answer-type="answerTypes[question.type]"
:index="index + 1"
:max-string-lengths="maxStringLengths"
v-bind.sync="form.questions[index]"
@delete="deleteQuestion(question)" />
<transition-group :name="isDragging ? 'no-external-transition-on-drag' : 'question-list'">
<component :is="answerTypes[question.type].component"
v-for="(question, index) in form.questions"
ref="questions"
:key="question.id"
:can-move-down="index < (form.questions.length - 1)"
:can-move-up="index > 0"
:answer-type="answerTypes[question.type]"
:index="index + 1"
:max-string-lengths="maxStringLengths"
v-bind.sync="form.questions[index]"
@delete="deleteQuestion(question)"
@move-down="onMoveDown(index)"
@move-up="onMoveUp(index)" />
</transition-group>
</Draggable>

<!-- Add new questions menu -->
Expand Down Expand Up @@ -278,6 +284,18 @@ export default {
},

methods: {
onMoveUp(index) {
if (index > 0) {
[this.form.questions[index - 1], this.form.questions[index]] = [this.form.questions[index], this.form.questions[index - 1]]
this.onQuestionOrderChange()
}
},
onMoveDown(index) {
// only if not the last one
if (index < (this.form.questions.length - 1)) {
this.onMoveUp(index + 1)
}
},
onTitleChange() {
this.resizeTitle()
this.saveTitle()
Expand Down Expand Up @@ -451,6 +469,12 @@ export default {
}
</script>

<style lang="scss">
.question-list-move {
transition: all 0.2s ease;
}
</style>

<style lang="scss" scoped>
@import '../scssmixins/markdownOutput';

Expand All @@ -469,8 +493,9 @@ export default {
header {
display: flex;
flex-direction: column;
margin: 0;
margin-block-end: 24px;
margin-inline-start: 56px;
padding-inline-start: 40px;

.form-title {
font-size: 28px;
Expand Down
Loading