diff options
18 files changed, 483 insertions, 478 deletions
diff --git a/.eslintrc b/.eslintrc index 2c284ec6d94..5b1e5bfd530 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,6 +37,16 @@ "import/no-commonjs": "error", "no-multiple-empty-lines": ["error", { "max": 1 }], "promise/catch-or-return": "error", - "no-underscore-dangle": ["error", { "allow": ["__"]}] + "no-underscore-dangle": ["error", { "allow": ["__"]}], + "vue/html-self-closing": ["error", { + "html": { + "void": "always", + "normal": "any", + "component": "always" + }, + "svg": "always", + "math": "any" + }] + } } diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue index 55f466b7b41..b2573c2c9a4 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/modal.vue @@ -1,131 +1,133 @@ <script> -export default { - name: 'modal', - - props: { - title: { - type: String, - required: false, - }, - text: { - type: String, - required: false, - }, - hideFooter: { - type: Boolean, - required: false, - default: false, - }, - kind: { - type: String, - required: false, - default: 'primary', - }, - modalDialogClass: { - type: String, - required: false, - default: '', - }, - closeKind: { - type: String, - required: false, - default: 'default', - }, - closeButtonLabel: { - type: String, - required: false, - default: 'Cancel', - }, - primaryButtonLabel: { - type: String, - required: false, - default: '', - }, - submitDisabled: { - type: Boolean, - required: false, - default: false, + export default { + name: 'Modal', + props: { + title: { + type: String, + required: false, + default: '', + }, + text: { + type: String, + required: false, + default: '', + }, + hideFooter: { + type: Boolean, + required: false, + default: false, + }, + kind: { + type: String, + required: false, + default: 'primary', + }, + modalDialogClass: { + type: String, + required: false, + default: '', + }, + closeKind: { + type: String, + required: false, + default: 'default', + }, + closeButtonLabel: { + type: String, + required: false, + default: 'Cancel', + }, + primaryButtonLabel: { + type: String, + required: false, + default: '', + }, + submitDisabled: { + type: Boolean, + required: false, + default: false, + }, }, - }, - computed: { - btnKindClass() { - return { - [`btn-${this.kind}`]: true, - }; + computed: { + btnKindClass() { + return { + [`btn-${this.kind}`]: true, + }; + }, + btnCancelKindClass() { + return { + [`btn-${this.closeKind}`]: true, + }; + }, }, - btnCancelKindClass() { - return { - [`btn-${this.closeKind}`]: true, - }; - }, - }, - methods: { - close() { - this.$emit('toggle', false); - }, - emitSubmit(status) { - this.$emit('submit', status); + methods: { + close() { + this.$emit('toggle', false); + }, + emitSubmit(status) { + this.$emit('submit', status); + }, }, - }, -}; + }; </script> - <template> -<div class="modal-open"> - <div - class="modal show" - role="dialog" - tabindex="-1" - > + <div class="modal-open"> <div - :class="modalDialogClass" - class="modal-dialog" - role="document" + class="modal show" + role="dialog" + tabindex="-1" > - <div class="modal-content"> - <div class="modal-header"> - <slot name="header"> - <h4 class="modal-title pull-left"> - {{this.title}} - </h4> + <div + :class="modalDialogClass" + class="modal-dialog" + role="document" + > + <div class="modal-content"> + <div class="modal-header"> + <slot name="header"> + <h4 class="modal-title pull-left"> + {{ this.title }} + </h4> + <button + type="button" + class="close pull-right" + @click="close" + aria-label="Close" + > + <span aria-hidden="true">×</span> + </button> + </slot> + </div> + <div class="modal-body"> + <slot name="body"> + </slot> + </div> + <div + class="modal-footer" + v-if="!hideFooter" + > <button type="button" - class="close pull-right" - @click="close" - aria-label="Close" - > - <span aria-hidden="true">×</span> + class="btn pull-left" + :class="btnCancelKindClass" + @click="close"> + {{ closeButtonLabel }} </button> - </slot> - </div> - <div class="modal-body"> - <slot name="body" :text="text"> - <p>{{this.text}}</p> - </slot> - </div> - <div class="modal-footer" v-if="!hideFooter"> - <button - type="button" - class="btn pull-left" - :class="btnCancelKindClass" - @click="close"> - {{ closeButtonLabel }} - </button> - <button - v-if="primaryButtonLabel" - type="button" - class="btn pull-right js-primary-button" - :disabled="submitDisabled" - :class="btnKindClass" - @click="emitSubmit(true)"> - {{ primaryButtonLabel }} - </button> + <button + v-if="primaryButtonLabel" + type="button" + class="btn pull-right js-primary-button" + :disabled="submitDisabled" + :class="btnKindClass" + @click="emitSubmit(true)"> + {{ primaryButtonLabel }} + </button> + </div> </div> </div> </div> + <div class="modal-backdrop fade in"></div> </div> - <div class="modal-backdrop fade in" /> -</div> </template> diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue index a2ddd565170..cb8e6072a9b 100644 --- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue @@ -45,7 +45,7 @@ this.$emit('onChangeTab', tab.scope); }, }, -}; + }; </script> <template> <ul class="nav-links scrolling-tabs"> @@ -55,21 +55,20 @@ :class="{ active: tab.isActive, }" - > + > <a role="button" @click="onTabClick(tab)" :class="`js-${scope}-tab-${tab.scope}`" - > + > {{ tab.name }} <span v-if="shouldRenderBadge(tab.count)" class="badge" - > - {{tab.count}} + > + {{ tab.count }} </span> - </a> </li> </ul> diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue index e467ca56704..50b1508691b 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue @@ -20,16 +20,16 @@ import userAvatarLink from '../user_avatar/user_avatar_link.vue'; export default { - name: 'placeholderNote', + name: 'PlaceholderNote', + components: { + userAvatarLink, + }, props: { note: { type: Object, required: true, }, }, - components: { - userAvatarLink, - }, computed: { ...mapGetters([ 'getUserData', @@ -46,7 +46,7 @@ :link-href="getUserData.path" :img-src="getUserData.avatar_url" :img-size="40" - /> + /> </div> <div :class="{ discussion: !note.individual_note }" @@ -54,14 +54,14 @@ <div class="note-header"> <div class="note-header-info"> <a :href="getUserData.path"> - <span class="hidden-xs">{{getUserData.name}}</span> - <span class="note-headline-light">@{{getUserData.username}}</span> + <span class="hidden-xs">{{ getUserData.name }}</span> + <span class="note-headline-light">@{{ getUserData.username }}</span> </a> </div> </div> <div class="note-body"> <div class="note-text"> - <p>{{note.body}}</p> + <p>{{ note.body }}</p> </div> </div> </div> diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue index d805fea8006..95e2b38e292 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue @@ -8,7 +8,7 @@ * /> */ export default { - name: 'placeholderSystemNote', + name: 'PlaceholderSystemNote', props: { note: { type: Object, @@ -20,10 +20,10 @@ <template> <li class="note system-note timeline-entry being-posted fade-in-half"> - <div class="timeline-entry-inner"> - <div class="timeline-content"> - <em>{{note.body}}</em> - </div> - </div> + <div class="timeline-entry-inner"> + <div class="timeline-content"> + <em>{{ note.body }}</em> + </div> + </div> </li> </template> diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue index 2248699c399..aac10f84087 100644 --- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue @@ -21,16 +21,16 @@ import { spriteIcon } from '../../../lib/utils/common_utils'; export default { - name: 'systemNote', + name: 'SystemNote', + components: { + noteHeader, + }, props: { note: { type: Object, required: true, }, }, - components: { - noteHeader, - }, computed: { ...mapGetters([ 'targetNoteHash', diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue index 4371534d345..abbe9a22717 100644 --- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue +++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue @@ -1,87 +1,87 @@ <script> -export default { - props: { - startSize: { - type: Number, - required: true, + export default { + props: { + startSize: { + type: Number, + required: true, + }, + side: { + type: String, + required: true, + }, + minSize: { + type: Number, + required: false, + default: 0, + }, + maxSize: { + type: Number, + required: false, + default: Number.MAX_VALUE, + }, + enabled: { + type: Boolean, + required: false, + default: true, + }, }, - side: { - type: String, - required: true, + data() { + return { + size: this.startSize, + }; }, - minSize: { - type: Number, - required: false, - default: 0, + computed: { + className() { + return `drag${this.side}`; + }, + cursorStyle() { + if (this.enabled) { + return { cursor: 'ew-resize' }; + } + return {}; + }, }, - maxSize: { - type: Number, - required: false, - default: Number.MAX_VALUE, - }, - enabled: { - type: Boolean, - required: false, - default: true, - }, - }, - data() { - return { - size: this.startSize, - }; - }, - computed: { - className() { - return `drag${this.side}`; - }, - cursorStyle() { - if (this.enabled) { - return { cursor: 'ew-resize' }; - } - return {}; - }, - }, - methods: { - resetSize(e) { - e.preventDefault(); - this.size = this.startSize; - this.$emit('update:size', this.size); - }, - startDrag(e) { - if (this.enabled) { + methods: { + resetSize(e) { e.preventDefault(); - this.startPos = e.clientX; - this.currentStartSize = this.size; - document.addEventListener('mousemove', this.drag); - document.addEventListener('mouseup', this.endDrag, { once: true }); - this.$emit('resize-start', this.size); - } - }, - drag(e) { - e.preventDefault(); - let moved = e.clientX - this.startPos; - if (this.side === 'left') moved = -moved; - let newSize = this.currentStartSize + moved; - if (newSize < this.minSize) { - newSize = this.minSize; - } else if (newSize > this.maxSize) { - newSize = this.maxSize; - } - this.size = newSize; + this.size = this.startSize; + this.$emit('update:size', this.size); + }, + startDrag(e) { + if (this.enabled) { + e.preventDefault(); + this.startPos = e.clientX; + this.currentStartSize = this.size; + document.addEventListener('mousemove', this.drag); + document.addEventListener('mouseup', this.endDrag, { once: true }); + this.$emit('resize-start', this.size); + } + }, + drag(e) { + e.preventDefault(); + let moved = e.clientX - this.startPos; + if (this.side === 'left') moved = -moved; + let newSize = this.currentStartSize + moved; + if (newSize < this.minSize) { + newSize = this.minSize; + } else if (newSize > this.maxSize) { + newSize = this.maxSize; + } + this.size = newSize; - this.$emit('update:size', newSize); - }, - endDrag(e) { - e.preventDefault(); - document.removeEventListener('mousemove', this.drag); - this.$emit('resize-end', this.size); + this.$emit('update:size', newSize); + }, + endDrag(e) { + e.preventDefault(); + document.removeEventListener('mousemove', this.drag); + this.$emit('resize-end', this.size); + }, }, - }, -}; + }; </script> <template> - <div + <div class="dragHandle" :class="className" :style="cursorStyle" diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue index d8d974a2ff7..45ad278e5e2 100644 --- a/app/assets/javascripts/vue_shared/components/pikaday.vue +++ b/app/assets/javascripts/vue_shared/components/pikaday.vue @@ -3,7 +3,7 @@ import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix'; export default { - name: 'datePicker', + name: 'DatePicker', props: { label: { type: String, @@ -23,14 +23,6 @@ required: false, }, }, - methods: { - selected(dateText) { - this.$emit('newDateSelected', this.calendar.toString(dateText)); - }, - toggled() { - this.$emit('hidePicker'); - }, - }, mounted() { this.calendar = new Pikaday({ field: this.$el.querySelector('.dropdown-menu-toggle'), @@ -53,6 +45,14 @@ beforeDestroy() { this.calendar.destroy(); }, + methods: { + selected(dateText) { + this.$emit('newDateSelected', this.calendar.toString(dateText)); + }, + toggled() { + this.$emit('hidePicker'); + }, + }, }; </script> @@ -66,7 +66,7 @@ @click="toggled" > <span class="dropdown-toggle-text"> - {{label}} + {{ label }} </span> <i class="fa fa-chevron-down" diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue index dce23bd65f6..279cc1de5bb 100644 --- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue +++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue @@ -1,85 +1,85 @@ <script> -/* This is a re-usable vue component for rendering a project avatar that - does not need to link to the project's profile. The image and an optional - tooltip can be configured by props passed to this component. + /* This is a re-usable vue component for rendering a project avatar that + does not need to link to the project's profile. The image and an optional + tooltip can be configured by props passed to this component. - Sample configuration: + Sample configuration: - <project-avatar-image - :lazy="true" - :img-src="projectAvatarSrc" - :img-alt="tooltipText" - :tooltip-text="tooltipText" - tooltip-placement="top" - /> + <project-avatar-image + :lazy="true" + :img-src="projectAvatarSrc" + :img-alt="tooltipText" + :tooltip-text="tooltipText" + tooltip-placement="top" + /> -*/ + */ -import defaultAvatarUrl from 'images/no_avatar.png'; -import { placeholderImage } from '../../../lazy_loader'; -import tooltip from '../../directives/tooltip'; + import defaultAvatarUrl from 'images/no_avatar.png'; + import { placeholderImage } from '../../../lazy_loader'; + import tooltip from '../../directives/tooltip'; -export default { - name: 'ProjectAvatarImage', - props: { - lazy: { - type: Boolean, - required: false, - default: false, - }, - imgSrc: { - type: String, - required: false, - default: defaultAvatarUrl, - }, - cssClasses: { - type: String, - required: false, - default: '', - }, - imgAlt: { - type: String, - required: false, - default: 'project avatar', - }, - size: { - type: Number, - required: false, - default: 20, - }, - tooltipText: { - type: String, - required: false, - default: '', - }, - tooltipPlacement: { - type: String, - required: false, - default: 'top', - }, - }, - directives: { - tooltip, - }, - computed: { - // API response sends null when gravatar is disabled and - // we provide an empty string when we use it inside project avatar link. - // In both cases we should render the defaultAvatarUrl - sanitizedSource() { - return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc; - }, - resultantSrcAttribute() { - return this.lazy ? placeholderImage : this.sanitizedSource; + export default { + name: 'ProjectAvatarImage', + directives: { + tooltip, }, - tooltipContainer() { - return this.tooltipText ? 'body' : null; + props: { + lazy: { + type: Boolean, + required: false, + default: false, + }, + imgSrc: { + type: String, + required: false, + default: defaultAvatarUrl, + }, + cssClasses: { + type: String, + required: false, + default: '', + }, + imgAlt: { + type: String, + required: false, + default: 'project avatar', + }, + size: { + type: Number, + required: false, + default: 20, + }, + tooltipText: { + type: String, + required: false, + default: '', + }, + tooltipPlacement: { + type: String, + required: false, + default: 'top', + }, }, - avatarSizeClass() { - return `s${this.size}`; + computed: { + // API response sends null when gravatar is disabled and + // we provide an empty string when we use it inside project avatar link. + // In both cases we should render the defaultAvatarUrl + sanitizedSource() { + return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc; + }, + resultantSrcAttribute() { + return this.lazy ? placeholderImage : this.sanitizedSource; + }, + tooltipContainer() { + return this.tooltipText ? 'body' : null; + }, + avatarSizeClass() { + return `s${this.size}`; + }, }, - }, -}; + }; </script> <template> @@ -87,7 +87,7 @@ export default { v-tooltip class="avatar" :class="{ - lazy, + lazy: lazy, [avatarSizeClass]: true, [cssClasses]: true }" diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue index 8053c65d498..85ee71f3238 100644 --- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue +++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue @@ -2,8 +2,10 @@ import modal from './modal.vue'; export default { - name: 'recaptcha-modal', - + name: 'RecaptchaModal', + components: { + modal, + }, props: { html: { type: String, @@ -18,11 +20,14 @@ export default { scriptSrc: 'https://www.google.com/recaptcha/api.js', }; }, - - components: { - modal, + watch: { + html() { + this.appendRecaptchaScript(); + }, + }, + mounted() { + window.recaptchaDialogCallback = this.submit.bind(this); }, - methods: { appendRecaptchaScript() { this.removeRecaptchaScript(); @@ -51,35 +56,26 @@ export default { this.$el.querySelector('form').submit(); }, }, - - watch: { - html() { - this.appendRecaptchaScript(); - }, - }, - - mounted() { - window.recaptchaDialogCallback = this.submit.bind(this); - }, }; </script> <template> -<modal - kind="warning" - class="recaptcha-modal js-recaptcha-modal" - :hide-footer="true" - :title="__('Please solve the reCAPTCHA')" - @toggle="close" -> - <div slot="body"> - <p> - {{__('We want to be sure it is you, please confirm you are not a robot.')}} - </p> - <div - ref="recaptcha" - v-html="html" - ></div> - </div> -</modal> + <modal + kind="warning" + class="recaptcha-modal js-recaptcha-modal" + :hide-footer="true" + :title="__('Please solve the reCAPTCHA')" + @toggle="close" + > + <div slot="body"> + <p> + {{ __('We want to be sure it is you, please confirm you are not a robot.') }} + </p> + <div + ref="recaptcha" + v-html="html" + > + </div> + </div> + </modal> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue index a88e1310131..7f1eb6bcec4 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue @@ -1,6 +1,6 @@ <script> export default { - name: 'collapsedCalendarIcon', + name: 'CollapsedCalendarIcon', props: { containerClass: { type: String, diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue index 9ede5553bc5..364aea2f649 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue @@ -4,7 +4,11 @@ import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; export default { - name: 'sidebarCollapsedGroupedDatePicker', + name: 'SidebarCollapsedGroupedDatePicker', + components: { + toggleSidebar, + collapsedCalendarIcon, + }, props: { collapsed: { type: Boolean, @@ -30,10 +34,6 @@ default: false, }, }, - components: { - toggleSidebar, - collapsedCalendarIcon, - }, computed: { hasMinAndMaxDates() { return this.minDate && this.maxDate; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 9c3413377a3..0b2232d3339 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -6,7 +6,13 @@ import { dateInWords } from '../../../lib/utils/datetime_utility'; export default { - name: 'sidebarDatePicker', + name: 'SidebarDatePicker', + components: { + datePicker, + toggleSidebar, + loadingIcon, + collapsedCalendarIcon, + }, props: { collapsed: { type: Boolean, @@ -51,12 +57,6 @@ editing: false, }; }, - components: { - datePicker, - toggleSidebar, - loadingIcon, - collapsedCalendarIcon, - }, computed: { selectedAndEditable() { return this.selectedDate && this.editable; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue index 5ae76adad71..a5dfcd7ffee 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue @@ -1,6 +1,6 @@ <script> export default { - name: 'toggleSidebar', + name: 'ToggleSidebar', props: { collapsed: { type: Boolean, @@ -25,6 +25,6 @@ aria-label="toggle collapse" class="fa" :class="{ 'fa-angle-double-right': !collapsed, 'fa-angle-double-left': collapsed }" - ></i> + /> </button> </template> diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue index 33096b53cf8..5aa2aee8882 100644 --- a/app/assets/javascripts/vue_shared/components/table_pagination.vue +++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue @@ -1,132 +1,126 @@ <script> -import { s__ } from '../../locale'; - -const PAGINATION_UI_BUTTON_LIMIT = 4; -const UI_LIMIT = 6; -const SPREAD = '...'; -const PREV = s__('Pagination|Prev'); -const NEXT = s__('Pagination|Next'); -const FIRST = s__('Pagination|« First'); -const LAST = s__('Pagination|Last »'); - -export default { - props: { - /** - This function will take the information given by the pagination component - - Here is an example `change` method: - - change(pagenum) { - gl.utils.visitUrl(`?page=${pagenum}`); + import { s__ } from '../../locale'; + + const PAGINATION_UI_BUTTON_LIMIT = 4; + const UI_LIMIT = 6; + const SPREAD = '...'; + const PREV = s__('Pagination|Prev'); + const NEXT = s__('Pagination|Next'); + const FIRST = s__('Pagination|« First'); + const LAST = s__('Pagination|Last »'); + + export default { + props: { + /** + This function will take the information given by the pagination component + */ + change: { + type: Function, + required: true, }, - */ - change: { - type: Function, - required: true, - }, - /** - pageInfo will come from the headers of the API call - in the `.then` clause of the VueResource API call - there should be a function that contructs the pageInfo for this component - - This is an example: - - const pageInfo = headers => ({ - perPage: +headers['X-Per-Page'], - page: +headers['X-Page'], - total: +headers['X-Total'], - totalPages: +headers['X-Total-Pages'], - nextPage: +headers['X-Next-Page'], - previousPage: +headers['X-Prev-Page'], - }); - */ - pageInfo: { - type: Object, - required: true, - }, - }, - methods: { - changePage(e) { - if (e.target.parentElement.classList.contains('disabled')) return; - - const text = e.target.innerText; - const { totalPages, nextPage, previousPage } = this.pageInfo; - - switch (text) { - case SPREAD: - break; - case LAST: - this.change(totalPages); - break; - case NEXT: - this.change(nextPage); - break; - case PREV: - this.change(previousPage); - break; - case FIRST: - this.change(1); - break; - default: - this.change(+text); - break; - } - }, - }, - computed: { - prev() { - return this.pageInfo.previousPage; - }, - next() { - return this.pageInfo.nextPage; + /** + pageInfo will come from the headers of the API call + in the `.then` clause of the VueResource API call + there should be a function that contructs the pageInfo for this component + + This is an example: + + const pageInfo = headers => ({ + perPage: +headers['X-Per-Page'], + page: +headers['X-Page'], + total: +headers['X-Total'], + totalPages: +headers['X-Total-Pages'], + nextPage: +headers['X-Next-Page'], + previousPage: +headers['X-Prev-Page'], + }); + */ + pageInfo: { + type: Object, + required: true, + }, }, - getItems() { - const total = this.pageInfo.totalPages; - const page = this.pageInfo.page; - const items = []; - - if (page > 1) { - items.push({ title: FIRST, first: true }); - } - - if (page > 1) { - items.push({ title: PREV, prev: true }); - } else { - items.push({ title: PREV, disabled: true, prev: true }); - } - - if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); - - const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); - const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); - - for (let i = start; i <= end; i += 1) { - const isActive = i === page; - items.push({ title: i, active: isActive, page: true }); - } - - if (total - page > PAGINATION_UI_BUTTON_LIMIT) { - items.push({ title: SPREAD, separator: true, page: true }); - } - - if (page === total) { - items.push({ title: NEXT, disabled: true, next: true }); - } else if (total - page >= 1) { - items.push({ title: NEXT, next: true }); - } - - if (total - page >= 1) { - items.push({ title: LAST, last: true }); - } - - return items; + computed: { + prev() { + return this.pageInfo.previousPage; + }, + next() { + return this.pageInfo.nextPage; + }, + getItems() { + const total = this.pageInfo.totalPages; + const page = this.pageInfo.page; + const items = []; + + if (page > 1) { + items.push({ title: FIRST, first: true }); + } + + if (page > 1) { + items.push({ title: PREV, prev: true }); + } else { + items.push({ title: PREV, disabled: true, prev: true }); + } + + if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); + + const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); + const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); + + for (let i = start; i <= end; i += 1) { + const isActive = i === page; + items.push({ title: i, active: isActive, page: true }); + } + + if (total - page > PAGINATION_UI_BUTTON_LIMIT) { + items.push({ title: SPREAD, separator: true, page: true }); + } + + if (page === total) { + items.push({ title: NEXT, disabled: true, next: true }); + } else if (total - page >= 1) { + items.push({ title: NEXT, next: true }); + } + + if (total - page >= 1) { + items.push({ title: LAST, last: true }); + } + + return items; + }, + showPagination() { + return this.pageInfo.totalPages > 1; + }, }, - showPagination() { - return this.pageInfo.totalPages > 1; + methods: { + changePage(e) { + if (e.target.parentElement.classList.contains('disabled')) return; + + const text = e.target.innerText; + const { totalPages, nextPage, previousPage } = this.pageInfo; + + switch (text) { + case SPREAD: + break; + case LAST: + this.change(totalPages); + break; + case NEXT: + this.change(nextPage); + break; + case PREV: + this.change(previousPage); + break; + case FIRST: + this.change(1); + break; + default: + this.change(+text); + break; + } + }, }, - }, -}; + }; </script> <template> <div @@ -135,7 +129,8 @@ export default { > <ul class="pagination clearfix"> <li - v-for="item in getItems" + v-for="(item, index) in getItems" + :key="index" :class="{ page: item.page, 'js-previous-button': item.prev, @@ -145,8 +140,11 @@ export default { separator: item.separator, active: item.active, disabled: item.disabled - }"> - <a @click.prevent="changePage($event)">{{item.title}}</a> + }" + > + <a @click.prevent="changePage($event)"> + {{ item.title }} + </a> </li> </ul> </div> diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue index a775ae03ffa..ef3b16edf5f 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue @@ -40,6 +40,6 @@ export default { :height="size" :width="size" v-html="svg" - /> + /> </template> diff --git a/package.json b/package.json index 6b98e93df76..937f71b3d65 100644 --- a/package.json +++ b/package.json @@ -90,11 +90,11 @@ "devDependencies": { "@gitlab-org/gitlab-svgs": "^1.4.0", "babel-plugin-istanbul": "^4.1.5", - "eslint": "^3.10.1", + "eslint": "3.18.0", "eslint-config-airbnb-base": "^10.0.1", "eslint-import-resolver-webpack": "^0.8.3", "eslint-plugin-filenames": "^1.1.0", - "eslint-plugin-html": "^4.0.1", + "eslint-plugin-html": "2.0.1", "eslint-plugin-import": "^2.2.0", "eslint-plugin-jasmine": "^2.1.0", "eslint-plugin-promise": "^3.5.0", diff --git a/yarn.lock b/yarn.lock index e595e7a1116..0a5a184c2c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2372,9 +2372,9 @@ eslint-plugin-filenames@^1.1.0: lodash.kebabcase "4.0.1" lodash.snakecase "4.0.1" -eslint-plugin-html@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-4.0.1.tgz#fc70072263cc938496fbbc9cf648660e41fa269a" +eslint-plugin-html@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-2.0.1.tgz#3a829510e82522f1e2e44d55d7661a176121fce1" dependencies: htmlparser2 "^3.8.2" @@ -2419,9 +2419,9 @@ eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" -eslint@^3.10.1: - version "3.19.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" +eslint@3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.18.0.tgz#647e985c4ae71502d20ac62c109f66d5104c8a4b" dependencies: babel-code-frame "^6.16.0" chalk "^1.1.3" |