[ci skip] Fix more rules

This commit is contained in:
Filipa Lacerda 2018-01-04 19:50:06 +00:00
parent 615f1927bb
commit 318d6f449e
No known key found for this signature in database
GPG key ID: 9CA3FDE4D1E2F1C8
18 changed files with 478 additions and 473 deletions

View file

@ -37,6 +37,16 @@
"import/no-commonjs": "error", "import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }], "no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error", "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"
}]
} }
} }

View file

@ -1,131 +1,133 @@
<script> <script>
export default { export default {
name: 'modal', 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,
},
},
props: { computed: {
title: { btnKindClass() {
type: String, return {
required: false, [`btn-${this.kind}`]: true,
};
},
btnCancelKindClass() {
return {
[`btn-${this.closeKind}`]: true,
};
},
}, },
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,
},
},
computed: { methods: {
btnKindClass() { close() {
return { this.$emit('toggle', false);
[`btn-${this.kind}`]: true, },
}; emitSubmit(status) {
this.$emit('submit', status);
},
}, },
btnCancelKindClass() { };
return {
[`btn-${this.closeKind}`]: true,
};
},
},
methods: {
close() {
this.$emit('toggle', false);
},
emitSubmit(status) {
this.$emit('submit', status);
},
},
};
</script> </script>
<template> <template>
<div class="modal-open"> <div class="modal-open">
<div
class="modal show"
role="dialog"
tabindex="-1"
>
<div <div
:class="modalDialogClass" class="modal show"
class="modal-dialog" role="dialog"
role="document" tabindex="-1"
> >
<div class="modal-content"> <div
<div class="modal-header"> :class="modalDialogClass"
<slot name="header"> class="modal-dialog"
<h4 class="modal-title pull-left"> role="document"
{{this.title}} >
</h4> <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">&times;</span>
</button>
</slot>
</div>
<div class="modal-body">
<slot name="body">
</slot>
</div>
<div
class="modal-footer"
v-if="!hideFooter"
>
<button <button
type="button" type="button"
class="close pull-right" class="btn pull-left"
@click="close" :class="btnCancelKindClass"
aria-label="Close" @click="close">
> {{ closeButtonLabel }}
<span aria-hidden="true">&times;</span>
</button> </button>
</slot> <button
</div> v-if="primaryButtonLabel"
<div class="modal-body"> type="button"
<slot name="body" :text="text"> class="btn pull-right js-primary-button"
<p>{{this.text}}</p> :disabled="submitDisabled"
</slot> :class="btnKindClass"
</div> @click="emitSubmit(true)">
<div class="modal-footer" v-if="!hideFooter"> {{ primaryButtonLabel }}
<button </button>
type="button" </div>
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>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-backdrop fade in"></div>
</div> </div>
<div class="modal-backdrop fade in" />
</div>
</template> </template>

View file

@ -45,7 +45,7 @@
this.$emit('onChangeTab', tab.scope); this.$emit('onChangeTab', tab.scope);
}, },
}, },
}; };
</script> </script>
<template> <template>
<ul class="nav-links scrolling-tabs"> <ul class="nav-links scrolling-tabs">
@ -55,21 +55,20 @@
:class="{ :class="{
active: tab.isActive, active: tab.isActive,
}" }"
> >
<a <a
role="button" role="button"
@click="onTabClick(tab)" @click="onTabClick(tab)"
:class="`js-${scope}-tab-${tab.scope}`" :class="`js-${scope}-tab-${tab.scope}`"
> >
{{ tab.name }} {{ tab.name }}
<span <span
v-if="shouldRenderBadge(tab.count)" v-if="shouldRenderBadge(tab.count)"
class="badge" class="badge"
> >
{{tab.count}} {{ tab.count }}
</span> </span>
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -20,16 +20,16 @@
import userAvatarLink from '../user_avatar/user_avatar_link.vue'; import userAvatarLink from '../user_avatar/user_avatar_link.vue';
export default { export default {
name: 'placeholderNote', name: 'PlaceholderNote',
components: {
userAvatarLink,
},
props: { props: {
note: { note: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
components: {
userAvatarLink,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getUserData', 'getUserData',
@ -46,7 +46,7 @@
:link-href="getUserData.path" :link-href="getUserData.path"
:img-src="getUserData.avatar_url" :img-src="getUserData.avatar_url"
:img-size="40" :img-size="40"
/> />
</div> </div>
<div <div
:class="{ discussion: !note.individual_note }" :class="{ discussion: !note.individual_note }"
@ -54,14 +54,14 @@
<div class="note-header"> <div class="note-header">
<div class="note-header-info"> <div class="note-header-info">
<a :href="getUserData.path"> <a :href="getUserData.path">
<span class="hidden-xs">{{getUserData.name}}</span> <span class="hidden-xs">{{ getUserData.name }}</span>
<span class="note-headline-light">@{{getUserData.username}}</span> <span class="note-headline-light">@{{ getUserData.username }}</span>
</a> </a>
</div> </div>
</div> </div>
<div class="note-body"> <div class="note-body">
<div class="note-text"> <div class="note-text">
<p>{{note.body}}</p> <p>{{ note.body }}</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -8,7 +8,7 @@
* /> * />
*/ */
export default { export default {
name: 'placeholderSystemNote', name: 'PlaceholderSystemNote',
props: { props: {
note: { note: {
type: Object, type: Object,
@ -20,10 +20,10 @@
<template> <template>
<li class="note system-note timeline-entry being-posted fade-in-half"> <li class="note system-note timeline-entry being-posted fade-in-half">
<div class="timeline-entry-inner"> <div class="timeline-entry-inner">
<div class="timeline-content"> <div class="timeline-content">
<em>{{note.body}}</em> <em>{{ note.body }}</em>
</div> </div>
</div> </div>
</li> </li>
</template> </template>

View file

@ -21,16 +21,16 @@
import { spriteIcon } from '../../../lib/utils/common_utils'; import { spriteIcon } from '../../../lib/utils/common_utils';
export default { export default {
name: 'systemNote', name: 'SystemNote',
components: {
noteHeader,
},
props: { props: {
note: { note: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
components: {
noteHeader,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'targetNoteHash', 'targetNoteHash',

View file

@ -1,87 +1,87 @@
<script> <script>
export default { export default {
props: { props: {
startSize: { startSize: {
type: Number, type: Number,
required: true, 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: { data() {
type: String, return {
required: true, size: this.startSize,
};
}, },
minSize: { computed: {
type: Number, className() {
required: false, return `drag${this.side}`;
default: 0, },
cursorStyle() {
if (this.enabled) {
return { cursor: 'ew-resize' };
}
return {};
},
}, },
maxSize: { methods: {
type: Number, resetSize(e) {
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) {
e.preventDefault(); e.preventDefault();
this.startPos = e.clientX; this.size = this.startSize;
this.currentStartSize = this.size; this.$emit('update:size', this.size);
document.addEventListener('mousemove', this.drag); },
document.addEventListener('mouseup', this.endDrag, { once: true }); startDrag(e) {
this.$emit('resize-start', this.size); if (this.enabled) {
} e.preventDefault();
}, this.startPos = e.clientX;
drag(e) { this.currentStartSize = this.size;
e.preventDefault(); document.addEventListener('mousemove', this.drag);
let moved = e.clientX - this.startPos; document.addEventListener('mouseup', this.endDrag, { once: true });
if (this.side === 'left') moved = -moved; this.$emit('resize-start', this.size);
let newSize = this.currentStartSize + moved; }
if (newSize < this.minSize) { },
newSize = this.minSize; drag(e) {
} else if (newSize > this.maxSize) { e.preventDefault();
newSize = this.maxSize; let moved = e.clientX - this.startPos;
} if (this.side === 'left') moved = -moved;
this.size = newSize; 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); this.$emit('update:size', newSize);
},
endDrag(e) {
e.preventDefault();
document.removeEventListener('mousemove', this.drag);
this.$emit('resize-end', this.size);
},
}, },
endDrag(e) { };
e.preventDefault();
document.removeEventListener('mousemove', this.drag);
this.$emit('resize-end', this.size);
},
},
};
</script> </script>
<template> <template>
<div <div
class="dragHandle" class="dragHandle"
:class="className" :class="className"
:style="cursorStyle" :style="cursorStyle"

View file

@ -3,7 +3,7 @@
import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix'; import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
export default { export default {
name: 'datePicker', name: 'DatePicker',
props: { props: {
label: { label: {
type: String, type: String,
@ -23,14 +23,6 @@
required: false, required: false,
}, },
}, },
methods: {
selected(dateText) {
this.$emit('newDateSelected', this.calendar.toString(dateText));
},
toggled() {
this.$emit('hidePicker');
},
},
mounted() { mounted() {
this.calendar = new Pikaday({ this.calendar = new Pikaday({
field: this.$el.querySelector('.dropdown-menu-toggle'), field: this.$el.querySelector('.dropdown-menu-toggle'),
@ -53,6 +45,14 @@
beforeDestroy() { beforeDestroy() {
this.calendar.destroy(); this.calendar.destroy();
}, },
methods: {
selected(dateText) {
this.$emit('newDateSelected', this.calendar.toString(dateText));
},
toggled() {
this.$emit('hidePicker');
},
},
}; };
</script> </script>
@ -66,7 +66,7 @@
@click="toggled" @click="toggled"
> >
<span class="dropdown-toggle-text"> <span class="dropdown-toggle-text">
{{label}} {{ label }}
</span> </span>
<i <i
class="fa fa-chevron-down" class="fa fa-chevron-down"

View file

@ -1,85 +1,85 @@
<script> <script>
/* This is a re-usable vue component for rendering a project avatar that /* 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 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. tooltip can be configured by props passed to this component.
Sample configuration: Sample configuration:
<project-avatar-image <project-avatar-image
:lazy="true" :lazy="true"
:img-src="projectAvatarSrc" :img-src="projectAvatarSrc"
:img-alt="tooltipText" :img-alt="tooltipText"
:tooltip-text="tooltipText" :tooltip-text="tooltipText"
tooltip-placement="top" tooltip-placement="top"
/> />
*/ */
import defaultAvatarUrl from 'images/no_avatar.png'; import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader'; import { placeholderImage } from '../../../lazy_loader';
import tooltip from '../../directives/tooltip'; import tooltip from '../../directives/tooltip';
export default { export default {
name: 'ProjectAvatarImage', name: 'ProjectAvatarImage',
props: { directives: {
lazy: { tooltip,
type: Boolean,
required: false,
default: false,
}, },
imgSrc: { props: {
type: String, lazy: {
required: false, type: Boolean,
default: defaultAvatarUrl, 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',
},
}, },
cssClasses: { computed: {
type: String, // API response sends null when gravatar is disabled and
required: false, // we provide an empty string when we use it inside project avatar link.
default: '', // 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}`;
},
}, },
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;
},
tooltipContainer() {
return this.tooltipText ? 'body' : null;
},
avatarSizeClass() {
return `s${this.size}`;
},
},
};
</script> </script>
<template> <template>
@ -87,7 +87,7 @@ export default {
v-tooltip v-tooltip
class="avatar" class="avatar"
:class="{ :class="{
lazy, lazy: lazy,
[avatarSizeClass]: true, [avatarSizeClass]: true,
[cssClasses]: true [cssClasses]: true
}" }"

View file

@ -2,8 +2,10 @@
import modal from './modal.vue'; import modal from './modal.vue';
export default { export default {
name: 'recaptcha-modal', name: 'RecaptchaModal',
components: {
modal,
},
props: { props: {
html: { html: {
type: String, type: String,
@ -18,11 +20,14 @@ export default {
scriptSrc: 'https://www.google.com/recaptcha/api.js', scriptSrc: 'https://www.google.com/recaptcha/api.js',
}; };
}, },
watch: {
components: { html() {
modal, this.appendRecaptchaScript();
},
},
mounted() {
window.recaptchaDialogCallback = this.submit.bind(this);
}, },
methods: { methods: {
appendRecaptchaScript() { appendRecaptchaScript() {
this.removeRecaptchaScript(); this.removeRecaptchaScript();
@ -51,35 +56,26 @@ export default {
this.$el.querySelector('form').submit(); this.$el.querySelector('form').submit();
}, },
}, },
watch: {
html() {
this.appendRecaptchaScript();
},
},
mounted() {
window.recaptchaDialogCallback = this.submit.bind(this);
},
}; };
</script> </script>
<template> <template>
<modal <modal
kind="warning" kind="warning"
class="recaptcha-modal js-recaptcha-modal" class="recaptcha-modal js-recaptcha-modal"
:hide-footer="true" :hide-footer="true"
:title="__('Please solve the reCAPTCHA')" :title="__('Please solve the reCAPTCHA')"
@toggle="close" @toggle="close"
> >
<div slot="body"> <div slot="body">
<p> <p>
{{__('We want to be sure it is you, please confirm you are not a robot.')}} {{ __('We want to be sure it is you, please confirm you are not a robot.') }}
</p> </p>
<div <div
ref="recaptcha" ref="recaptcha"
v-html="html" v-html="html"
></div> >
</div> </div>
</modal> </div>
</modal>
</template> </template>

View file

@ -1,6 +1,6 @@
<script> <script>
export default { export default {
name: 'collapsedCalendarIcon', name: 'CollapsedCalendarIcon',
props: { props: {
containerClass: { containerClass: {
type: String, type: String,

View file

@ -4,7 +4,11 @@
import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
export default { export default {
name: 'sidebarCollapsedGroupedDatePicker', name: 'SidebarCollapsedGroupedDatePicker',
components: {
toggleSidebar,
collapsedCalendarIcon,
},
props: { props: {
collapsed: { collapsed: {
type: Boolean, type: Boolean,
@ -30,10 +34,6 @@
default: false, default: false,
}, },
}, },
components: {
toggleSidebar,
collapsedCalendarIcon,
},
computed: { computed: {
hasMinAndMaxDates() { hasMinAndMaxDates() {
return this.minDate && this.maxDate; return this.minDate && this.maxDate;

View file

@ -6,7 +6,13 @@
import { dateInWords } from '../../../lib/utils/datetime_utility'; import { dateInWords } from '../../../lib/utils/datetime_utility';
export default { export default {
name: 'sidebarDatePicker', name: 'SidebarDatePicker',
components: {
datePicker,
toggleSidebar,
loadingIcon,
collapsedCalendarIcon,
},
props: { props: {
collapsed: { collapsed: {
type: Boolean, type: Boolean,
@ -51,12 +57,6 @@
editing: false, editing: false,
}; };
}, },
components: {
datePicker,
toggleSidebar,
loadingIcon,
collapsedCalendarIcon,
},
computed: { computed: {
selectedAndEditable() { selectedAndEditable() {
return this.selectedDate && this.editable; return this.selectedDate && this.editable;

View file

@ -1,6 +1,6 @@
<script> <script>
export default { export default {
name: 'toggleSidebar', name: 'ToggleSidebar',
props: { props: {
collapsed: { collapsed: {
type: Boolean, type: Boolean,
@ -25,6 +25,6 @@
aria-label="toggle collapse" aria-label="toggle collapse"
class="fa" class="fa"
:class="{ 'fa-angle-double-right': !collapsed, 'fa-angle-double-left': collapsed }" :class="{ 'fa-angle-double-right': !collapsed, 'fa-angle-double-left': collapsed }"
></i> />
</button> </button>
</template> </template>

View file

@ -1,132 +1,126 @@
<script> <script>
import { s__ } from '../../locale'; import { s__ } from '../../locale';
const PAGINATION_UI_BUTTON_LIMIT = 4; const PAGINATION_UI_BUTTON_LIMIT = 4;
const UI_LIMIT = 6; const UI_LIMIT = 6;
const SPREAD = '...'; const SPREAD = '...';
const PREV = s__('Pagination|Prev'); const PREV = s__('Pagination|Prev');
const NEXT = s__('Pagination|Next'); const NEXT = s__('Pagination|Next');
const FIRST = s__('Pagination|« First'); const FIRST = s__('Pagination|« First');
const LAST = s__('Pagination|Last »'); const LAST = s__('Pagination|Last »');
export default { export default {
props: { props: {
/** /**
This function will take the information given by the pagination component This function will take the information given by the pagination component
*/
Here is an example `change` method: change: {
type: Function,
change(pagenum) { required: true,
gl.utils.visitUrl(`?page=${pagenum}`); },
/**
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,
}, },
*/
change: {
type: Function,
required: true,
}, },
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) {
pageInfo will come from the headers of the API call items.push({ title: FIRST, first: true });
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: if (page > 1) {
items.push({ title: PREV, prev: true });
} else {
items.push({ title: PREV, disabled: true, prev: true });
}
const pageInfo = headers => ({ if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
perPage: +headers['X-Per-Page'],
page: +headers['X-Page'], const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
total: +headers['X-Total'], const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
totalPages: +headers['X-Total-Pages'],
nextPage: +headers['X-Next-Page'], for (let i = start; i <= end; i += 1) {
previousPage: +headers['X-Prev-Page'], const isActive = i === page;
}); items.push({ title: i, active: isActive, page: true });
*/ }
pageInfo: {
type: Object, if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
required: true, 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;
},
}, },
}, methods: {
methods: { changePage(e) {
changePage(e) { if (e.target.parentElement.classList.contains('disabled')) return;
if (e.target.parentElement.classList.contains('disabled')) return;
const text = e.target.innerText; const text = e.target.innerText;
const { totalPages, nextPage, previousPage } = this.pageInfo; const { totalPages, nextPage, previousPage } = this.pageInfo;
switch (text) { switch (text) {
case SPREAD: case SPREAD:
break; break;
case LAST: case LAST:
this.change(totalPages); this.change(totalPages);
break; break;
case NEXT: case NEXT:
this.change(nextPage); this.change(nextPage);
break; break;
case PREV: case PREV:
this.change(previousPage); this.change(previousPage);
break; break;
case FIRST: case FIRST:
this.change(1); this.change(1);
break; break;
default: default:
this.change(+text); this.change(+text);
break; break;
} }
},
}, },
}, };
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;
},
},
};
</script> </script>
<template> <template>
<div <div
@ -135,7 +129,8 @@ export default {
> >
<ul class="pagination clearfix"> <ul class="pagination clearfix">
<li <li
v-for="item in getItems" v-for="(item, index) in getItems"
:key="index"
:class="{ :class="{
page: item.page, page: item.page,
'js-previous-button': item.prev, 'js-previous-button': item.prev,
@ -145,8 +140,11 @@ export default {
separator: item.separator, separator: item.separator,
active: item.active, active: item.active,
disabled: item.disabled disabled: item.disabled
}"> }"
<a @click.prevent="changePage($event)">{{item.title}}</a> >
<a @click.prevent="changePage($event)">
{{ item.title }}
</a>
</li> </li>
</ul> </ul>
</div> </div>

View file

@ -40,6 +40,6 @@ export default {
:height="size" :height="size"
:width="size" :width="size"
v-html="svg" v-html="svg"
/> />
</template> </template>

View file

@ -90,11 +90,11 @@
"devDependencies": { "devDependencies": {
"@gitlab-org/gitlab-svgs": "^1.4.0", "@gitlab-org/gitlab-svgs": "^1.4.0",
"babel-plugin-istanbul": "^4.1.5", "babel-plugin-istanbul": "^4.1.5",
"eslint": "^3.10.1", "eslint": "3.18.0",
"eslint-config-airbnb-base": "^10.0.1", "eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.3", "eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-filenames": "^1.1.0", "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-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0", "eslint-plugin-jasmine": "^2.1.0",
"eslint-plugin-promise": "^3.5.0", "eslint-plugin-promise": "^3.5.0",

View file

@ -2372,9 +2372,9 @@ eslint-plugin-filenames@^1.1.0:
lodash.kebabcase "4.0.1" lodash.kebabcase "4.0.1"
lodash.snakecase "4.0.1" lodash.snakecase "4.0.1"
eslint-plugin-html@^4.0.1: eslint-plugin-html@2.0.1:
version "4.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-4.0.1.tgz#fc70072263cc938496fbbc9cf648660e41fa269a" resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-2.0.1.tgz#3a829510e82522f1e2e44d55d7661a176121fce1"
dependencies: dependencies:
htmlparser2 "^3.8.2" htmlparser2 "^3.8.2"
@ -2419,9 +2419,9 @@ eslint-visitor-keys@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
eslint@^3.10.1: eslint@3.18.0:
version "3.19.0" version "3.18.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.18.0.tgz#647e985c4ae71502d20ac62c109f66d5104c8a4b"
dependencies: dependencies:
babel-code-frame "^6.16.0" babel-code-frame "^6.16.0"
chalk "^1.1.3" chalk "^1.1.3"