diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 04:45:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 04:45:44 +0300 |
commit | 85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch) | |
tree | 9160f299afd8c80c038f08e1545be119f5e3f1e1 /app/assets/javascripts/related_issues/components/related_issuable_input.vue | |
parent | 15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff) |
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'app/assets/javascripts/related_issues/components/related_issuable_input.vue')
-rw-r--r-- | app/assets/javascripts/related_issues/components/related_issuable_input.vue | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/app/assets/javascripts/related_issues/components/related_issuable_input.vue b/app/assets/javascripts/related_issues/components/related_issuable_input.vue new file mode 100644 index 00000000000..1931cfb2c00 --- /dev/null +++ b/app/assets/javascripts/related_issues/components/related_issuable_input.vue @@ -0,0 +1,231 @@ +<script> +import $ from 'jquery'; +import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; +import issueToken from './issue_token.vue'; +import { + autoCompleteTextMap, + inputPlaceholderConfidentialTextMap, + inputPlaceholderTextMap, + issuableTypesMap, +} from '../constants'; + +const SPACE_FACTOR = 1; + +export default { + name: 'RelatedIssuableInput', + components: { + issueToken, + }, + props: { + inputId: { + type: String, + required: false, + default: '', + }, + references: { + type: Array, + required: false, + default: () => [], + }, + pathIdSeparator: { + type: String, + required: true, + }, + inputValue: { + type: String, + required: false, + default: '', + }, + focusOnMount: { + type: Boolean, + required: false, + default: false, + }, + autoCompleteSources: { + type: Object, + required: false, + default: () => ({}), + }, + autoCompleteOptions: { + type: Object, + required: false, + default: () => ({}), + }, + issuableType: { + type: String, + required: false, + default: issuableTypesMap.ISSUE, + }, + confidential: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + isInputFocused: false, + isAutoCompleteOpen: false, + areEventsAssigned: false, + }; + }, + computed: { + inputPlaceholder() { + const { issuableType, allowAutoComplete, confidential } = this; + const inputPlaceholderMapping = confidential + ? inputPlaceholderConfidentialTextMap + : inputPlaceholderTextMap; + const allowAutoCompleteText = autoCompleteTextMap[allowAutoComplete][issuableType]; + return `${inputPlaceholderMapping[issuableType]}${allowAutoCompleteText}`; + }, + allowAutoComplete() { + return Object.keys(this.autoCompleteSources).length > 0; + }, + }, + mounted() { + this.setupAutoComplete(); + if (this.focusOnMount) { + this.$nextTick() + .then(() => { + this.$refs.input.focus(); + }) + .catch(() => {}); + } + }, + beforeUpdate() { + this.setupAutoComplete(); + }, + beforeDestroy() { + const $input = $(this.$refs.input); + $input.off('shown-issues.atwho'); + $input.off('hidden-issues.atwho'); + $input.off('inserted-issues.atwho', this.onInput); + }, + methods: { + onAutoCompleteToggled(isOpen) { + this.isAutoCompleteOpen = isOpen; + }, + onInputWrapperClick() { + this.$refs.input.focus(); + }, + onInput() { + const { value } = this.$refs.input; + const caretPos = this.$refs.input.selectionStart; + const rawRefs = value.split(/\s/); + let touchedReference; + let position = 0; + + const untouchedRawRefs = rawRefs + .filter(ref => { + let isTouched = false; + + if (caretPos >= position && caretPos <= position + ref.length) { + touchedReference = ref; + isTouched = true; + } + + position = position + ref.length + SPACE_FACTOR; + + return !isTouched; + }) + .filter(ref => ref.trim().length > 0); + + this.$emit('addIssuableFormInput', { + newValue: value, + untouchedRawReferences: untouchedRawRefs, + touchedReference, + caretPos, + }); + }, + onBlur(event) { + // Early exit if this Blur event is caused by card header + const container = this.$root.$el.querySelector('.js-button-container'); + if (container && container.contains(event.relatedTarget)) { + return; + } + + this.isInputFocused = false; + + // Avoid tokenizing partial input when clicking an autocomplete item + if (!this.isAutoCompleteOpen) { + const { value } = this.$refs.input; + // Avoid event emission when only pathIdSeparator has been typed + if (value !== this.pathIdSeparator) { + this.$emit('addIssuableFormBlur', value); + } + } + }, + onFocus() { + this.isInputFocused = true; + }, + setupAutoComplete() { + const $input = $(this.$refs.input); + + if (this.allowAutoComplete) { + this.gfmAutoComplete = new GfmAutoComplete(this.autoCompleteSources); + this.gfmAutoComplete.setup($input, this.autoCompleteOptions); + } + + if (!this.areEventsAssigned) { + $input.on('shown-issues.atwho', this.onAutoCompleteToggled.bind(this, true)); + $input.on('hidden-issues.atwho', this.onAutoCompleteToggled.bind(this, true)); + } + this.areEventsAssigned = true; + }, + onIssuableFormWrapperClick() { + this.$refs.input.focus(); + }, + }, +}; +</script> + +<template> + <div + ref="issuableFormWrapper" + :class="{ focus: isInputFocused }" + class="add-issuable-form-input-wrapper form-control gl-field-error-outline" + role="button" + @click="onIssuableFormWrapperClick" + > + <ul class="add-issuable-form-input-token-list"> + <!-- + We need to ensure this key changes any time the pendingReferences array is updated + else two consecutive pending ref strings in an array with the same name will collide + and cause odd behavior when one is removed. + --> + <li + v-for="(reference, index) in references" + :key="`related-issues-token-${reference}`" + class="js-add-issuable-form-token-list-item add-issuable-form-token-list-item" + > + <issue-token + :id-key="index" + :display-reference="reference.text || reference" + :can-remove="true" + :is-condensed="true" + :path-id-separator="pathIdSeparator" + event-namespace="pendingIssuable" + @pendingIssuableRemoveRequest=" + params => { + $emit('pendingIssuableRemoveRequest', params); + } + " + /> + </li> + <li class="add-issuable-form-input-list-item"> + <input + :id="inputId" + ref="input" + :value="inputValue" + :placeholder="inputPlaceholder" + type="text" + class="js-add-issuable-form-input add-issuable-form-input qa-add-issue-input" + @input="onInput" + @focus="onFocus" + @blur="onBlur" + @keyup.escape.exact="$emit('addIssuableFormCancel')" + /> + </li> + </ul> + </div> +</template> |