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

fix: Add support for adding new entries with IME input #2232

Merged
merged 1 commit into from
Aug 6, 2024
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
38 changes: 6 additions & 32 deletions src/components/Questions/QuestionDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,15 @@
<li v-if="!isLastEmpty || hasNoAnswer" class="question__item">
<input
ref="pseudoInput"
v-model="inputValue"
class="question__input"
:aria-label="t('forms', 'Add a new answer')"
:placeholder="t('forms', 'Add a new answer')"
class="question__input"
:maxlength="maxStringLengths.optionText"
minlength="1"
type="text"
@input="addNewEntry" />
@input="addNewEntry"
@compositionstart="onCompositionStart"
@compositionend="onCompositionEnd" />
</li>
</ol>
</template>
Expand All @@ -112,7 +113,6 @@ import IconContentPaste from 'vue-material-design-icons/ContentPaste.vue'
import AnswerInput from './AnswerInput.vue'
import OptionInputDialog from '../OptionInputDialog.vue'
import QuestionMixin from '../../mixins/QuestionMixin.js'
import GenRandomId from '../../utils/GenRandomId.js'
import logger from '../../utils/Logger.js'

export default {
Expand Down Expand Up @@ -153,7 +153,7 @@ export default {

isLastEmpty() {
const value = this.options[this.options.length - 1]
return value?.text?.trim().length === 0
return value?.text?.trim?.().length === 0
},

isMultiple() {
Expand Down Expand Up @@ -239,39 +239,13 @@ export default {
* @param {object} answer the answer to update
*/
updateAnswer(id, answer) {
const options = this.options.slice()
const options = [...this.options]
const answerIndex = options.findIndex((option) => option.id === id)
options[answerIndex] = answer

this.updateOptions(options)
},

/**
* Add a new empty answer locally
*/
addNewEntry() {
// Add local entry
const options = this.options.slice()
options.push({
id: GenRandomId(),
questionId: this.id,
text: this.inputValue,
local: true,
})

this.inputValue = ''

// Update question
this.updateOptions(options)

this.$nextTick(() => {
this.focusIndex(options.length - 1)

// Trigger onInput on new AnswerInput for posting the new option to the API
this.$refs.input[options.length - 1].onInput()
})
},

/**
* Restore an option locally
*
Expand Down
41 changes: 4 additions & 37 deletions src/components/Questions/QuestionMultiple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@
:maxlength="maxStringLengths.optionText"
minlength="1"
type="text"
@input="addNewEntry" />
@input="addNewEntry"
@compositionstart="onCompositionStart"
@compositionend="onCompositionEnd" />
</li>
</ul>
</template>
Expand Down Expand Up @@ -205,7 +207,6 @@ import IconRadioboxBlank from 'vue-material-design-icons/RadioboxBlank.vue'

import AnswerInput from './AnswerInput.vue'
import QuestionMixin from '../../mixins/QuestionMixin.js'
import GenRandomId from '../../utils/GenRandomId.js'
import logger from '../../utils/Logger.js'
import OptionInputDialog from '../OptionInputDialog.vue'

Expand Down Expand Up @@ -485,10 +486,9 @@ export default {
* So we require the one that are checked or all
* if none are checked yet.
*
* @param {number} id the answer id
* @return {boolean}
*/
checkRequired(id) {
checkRequired() {
// false, if question not required
if (!this.isRequired) {
return false
Expand Down Expand Up @@ -553,39 +553,6 @@ export default {
this.updateOptions(options)
},

/**
* Add a new empty answer locally
* @param {InputEvent} event The input event that triggered adding a new entry
*/
addNewEntry({ target }) {
// Add local entry
const options = [
...this.options,
{
id: GenRandomId(),
questionId: this.id,
text: target?.value ?? '',
local: true,
},
]

// Reset the "new answer" input if needed
if (this.$refs.pseudoInput) {
this.$refs.pseudoInput.value = ''
}

// Update questions
this.updateOptions(options)

this.$nextTick(() => {
// Set focus to the created input element
this.focusIndex(options.length - 1)

// Trigger onInput on new AnswerInput for posting the new option to the API
this.$refs.input[options.length - 1].onInput()
})
},

/**
* Restore an option locally
*
Expand Down
67 changes: 67 additions & 0 deletions src/mixins/QuestionMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import axios from '@nextcloud/axios'
import debounce from 'debounce'

import logger from '../utils/Logger.js'
import GenRandomId from '../utils/GenRandomId.js'
import OcsResponse2Data from '../utils/OcsResponse2Data.js'
import Question from '../components/Questions/Question.vue'

Expand Down Expand Up @@ -180,6 +181,14 @@ export default {
type: Boolean,
default: false,
},

/**
* isComposing for IME handling
*/
isIMEComposing: {
type: Boolean,
default: false,
},
},

components: {
Expand Down Expand Up @@ -409,5 +418,63 @@ export default {
})
this.isLoading = false
},

/**
* Add a new empty answer locally
* @param {InputEvent} event The input event that triggered adding a new entry
*/
addNewEntry({ target, isComposing }) {
/*
* Check for !isComposing needed for languages using IME like Japanese or Chinese
* Check for !this.isComposing needed for IME inputs handled by CompositionEvents
* Check for target.value !== '' needed for Linux/Mac for characters like á or è
*/
if (!isComposing && !this.isIMEComposing && target.value !== '') {
// Add local entry
const options = [
...this.options,
{
id: GenRandomId(),
questionId: this.id,
text: target.value,
local: true,
},
]

// Reset the "new answer" input if needed
if (this.$refs.pseudoInput) {
this.$refs.pseudoInput.value = ''
}

// Update questions
this.updateOptions(options)

this.$nextTick(() => {
// Set focus to the created input element
this.focusIndex(options.length - 1)

// Trigger onInput on new AnswerInput for posting the new option to the API
this.$refs.input[options.length - 1].onInput()
})
}
},

/**
* Handle compostion start event for IME inputs
*/
onCompositionStart() {
this.isIMEComposing = true
},

/**
* Handle compostion end event for IME inputs
* @param {CompositionEvent} event The input event that triggered adding a new entry
*/
onCompositionEnd({ target, isComposing }) {
this.isIMEComposing = false
if (!isComposing) {
this.addNewEntry({ target, isComposing })
}
},
},
}
Loading