Skip to content

Commit

Permalink
Merge pull request #2232 from nextcloud/fix/ime-inputs
Browse files Browse the repository at this point in the history
fix: Add support for adding new entries with IME input
  • Loading branch information
Chartman123 authored Aug 6, 2024
2 parents 3a85a74 + d6433f3 commit f18c6f3
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 69 deletions.
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 })
}
},
},
}

0 comments on commit f18c6f3

Please sign in to comment.