Commit 3271c5f0 authored by Mike Greiling's avatar Mike Greiling

Prettify vue_shared modules

parent 8b7c86ea
...@@ -118,7 +118,9 @@ export default { ...@@ -118,7 +118,9 @@ export default {
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0; this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
const yCoord = this.vbHeight / 2 + rectWidth - 5; const yCoord = this.vbHeight / 2 + rectWidth - 5;
return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${this.yAxisTextRotation})`; return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${
this.yAxisTextRotation
})`;
}, },
}, },
mounted() { mounted() {
...@@ -207,8 +209,7 @@ export default { ...@@ -207,8 +209,7 @@ export default {
renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) { renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) {
if (i > 0) { if (i > 0) {
d3 d3.select(this)
.select(this)
.select('line') .select('line')
.attr('x2', width) .attr('x2', width)
.attr('class', 'axis-tick'); .attr('class', 'axis-tick');
...@@ -217,8 +218,7 @@ export default { ...@@ -217,8 +218,7 @@ export default {
// Add the panning capabilities // Add the panning capabilities
if (this.isPanAvailable) { if (this.isPanAvailable) {
d3 d3.select(this.$refs.baseSvg)
.select(this.$refs.baseSvg)
.call(this.zoom) .call(this.zoom)
.on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel .on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel
} }
......
<script> <script>
/* eslint-disable vue/require-default-prop */ /* eslint-disable vue/require-default-prop */
export default { export default {
name: 'DeprecatedModal', // use GlModal instead name: 'DeprecatedModal', // use GlModal instead
props: { props: {
id: { id: {
type: String, type: String,
required: false, required: false,
},
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: '',
},
secondaryButtonLabel: {
type: String,
required: false,
default: '',
},
submitDisabled: {
type: Boolean,
required: false,
default: false,
},
}, },
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: '',
},
secondaryButtonLabel: {
type: String,
required: false,
default: '',
},
submitDisabled: {
type: Boolean,
required: false,
default: false,
},
},
computed: { computed: {
btnKindClass() { btnKindClass() {
return { return {
[`btn-${this.kind}`]: true, [`btn-${this.kind}`]: true,
}; };
}, },
btnCancelKindClass() { btnCancelKindClass() {
return { return {
[`btn-${this.closeKind}`]: true, [`btn-${this.closeKind}`]: true,
}; };
},
}, },
},
methods: { methods: {
emitCancel(event) { emitCancel(event) {
this.$emit('cancel', event); this.$emit('cancel', event);
}, },
emitSubmit(event) { emitSubmit(event) {
this.$emit('submit', event); this.$emit('submit', event);
},
}, },
}; },
};
</script> </script>
<template> <template>
......
...@@ -583,7 +583,5 @@ const fileNameIcons = { ...@@ -583,7 +583,5 @@ const fileNameIcons = {
}; };
export default function getIconForFile(name) { export default function getIconForFile(name) {
return fileNameIcons[name] || return fileNameIcons[name] || fileExtensionIcons[name ? name.split('.').pop() : ''] || '';
fileExtensionIcons[name ? name.split('.').pop() : ''] ||
'';
} }
...@@ -41,10 +41,14 @@ export default { ...@@ -41,10 +41,14 @@ export default {
}, },
}, },
mounted() { mounted() {
$(this.$el).on('shown.bs.modal', this.opened).on('hidden.bs.modal', this.closed); $(this.$el)
.on('shown.bs.modal', this.opened)
.on('hidden.bs.modal', this.closed);
}, },
beforeDestroy() { beforeDestroy() {
$(this.$el).off('shown.bs.modal', this.opened).off('hidden.bs.modal', this.closed); $(this.$el)
.off('shown.bs.modal', this.opened)
.off('hidden.bs.modal', this.closed);
}, },
methods: { methods: {
emitCancel(event) { emitCancel(event) {
......
<script> <script>
// only allow classes in images.scss e.g. s12 // only allow classes in images.scss e.g. s12
const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72]; const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true; let iconValidator = () => true;
......
<script> <script>
import icon from '../../../vue_shared/components/icon.vue'; import icon from '../../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
icon, icon,
},
props: {
isLocked: {
type: Boolean,
default: false,
required: false,
}, },
props: { isConfidential: {
isLocked: { type: Boolean,
type: Boolean, default: false,
default: false, required: false,
required: false,
},
isConfidential: {
type: Boolean,
default: false,
required: false,
},
}, },
computed: { },
warningIcon() { computed: {
if (this.isConfidential) return 'eye-slash'; warningIcon() {
if (this.isLocked) return 'lock'; if (this.isConfidential) return 'eye-slash';
if (this.isLocked) return 'lock';
return ''; return '';
},
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
}, },
}; isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
},
};
</script> </script>
<template> <template>
<div class="issuable-note-warning"> <div class="issuable-note-warning">
......
<script> <script>
/* eslint-disable vue/require-default-prop */ /* eslint-disable vue/require-default-prop */
/* This is a re-usable vue component for rendering a button /* This is a re-usable vue component for rendering a button
that will probably be sending off ajax requests and need that will probably be sending off ajax requests and need
to show the loading status by setting the `loading` option. to show the loading status by setting the `loading` option.
This can also be used for initial page load when you don't This can also be used for initial page load when you don't
...@@ -17,34 +17,34 @@ ...@@ -17,34 +17,34 @@
*/ */
export default { export default {
props: { props: {
loading: { loading: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
},
disabled: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
},
containerClass: {
type: [String, Array, Object],
required: false,
default: 'btn btn-align-content',
},
}, },
methods: { disabled: {
onClick(e) { type: Boolean,
this.$emit('click', e); required: false,
}, default: false,
}, },
}; label: {
type: String,
required: false,
},
containerClass: {
type: [String, Array, Object],
required: false,
default: 'btn btn-align-content',
},
},
methods: {
onClick(e) {
this.$emit('click', e);
},
},
};
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Flash from '../../../flash'; import Flash from '../../../flash';
import GLForm from '../../../gl_form'; import GLForm from '../../../gl_form';
import markdownHeader from './header.vue'; import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue'; import markdownToolbar from './toolbar.vue';
import icon from '../icon.vue'; import icon from '../icon.vue';
export default { export default {
components: { components: {
markdownHeader, markdownHeader,
markdownToolbar, markdownToolbar,
icon, icon,
},
props: {
markdownPreviewPath: {
type: String,
required: false,
default: '',
}, },
props: { markdownDocsPath: {
markdownPreviewPath: { type: String,
type: String, required: true,
required: false,
default: '',
},
markdownDocsPath: {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
addSpacingClasses: {
type: Boolean,
required: false,
default: true,
},
quickActionsDocsPath: {
type: String,
required: false,
default: '',
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { markdownVersion: {
return { type: Number,
markdownPreview: '', required: false,
referencedCommands: '', default: 0,
referencedUsers: '',
markdownPreviewLoading: false,
previewMarkdown: false,
};
}, },
computed: { addSpacingClasses: {
shouldShowReferencedUsers() { type: Boolean,
const referencedUsersThreshold = 10; required: false,
return this.referencedUsers.length >= referencedUsersThreshold; default: true,
},
}, },
mounted() { quickActionsDocsPath: {
/* type: String,
GLForm class handles all the toolbar buttons required: false,
*/ default: '',
return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
members: this.enableAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
milestones: this.enableAutocomplete,
labels: this.enableAutocomplete,
snippets: this.enableAutocomplete,
});
}, },
beforeDestroy() { canAttachFile: {
const glForm = $(this.$refs['gl-form']).data('glForm'); type: Boolean,
if (glForm) { required: false,
glForm.destroy(); default: true,
} },
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
}, },
methods: { },
showPreviewTab() { data() {
if (this.previewMarkdown) return; return {
markdownPreview: '',
referencedCommands: '',
referencedUsers: '',
markdownPreviewLoading: false,
previewMarkdown: false,
};
},
computed: {
shouldShowReferencedUsers() {
const referencedUsersThreshold = 10;
return this.referencedUsers.length >= referencedUsersThreshold;
},
},
mounted() {
/*
GLForm class handles all the toolbar buttons
*/
return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
members: this.enableAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
milestones: this.enableAutocomplete,
labels: this.enableAutocomplete,
snippets: this.enableAutocomplete,
});
},
beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('glForm');
if (glForm) {
glForm.destroy();
}
},
methods: {
showPreviewTab() {
if (this.previewMarkdown) return;
this.previewMarkdown = true; this.previewMarkdown = true;
/* /*
Can't use `$refs` as the component is technically in the parent component Can't use `$refs` as the component is technically in the parent component
so we access the VNode & then get the element so we access the VNode & then get the element
*/ */
const text = this.$slots.textarea[0].elm.value; const text = this.$slots.textarea[0].elm.value;
if (text) { if (text) {
this.markdownPreviewLoading = true; this.markdownPreviewLoading = true;
this.$http this.$http
.post(this.versionedPreviewPath(), { text }) .post(this.versionedPreviewPath(), { text })
.then(resp => resp.json()) .then(resp => resp.json())
.then(data => this.renderMarkdown(data)) .then(data => this.renderMarkdown(data))
.catch(() => new Flash(s__('Error loading markdown preview'))); .catch(() => new Flash(s__('Error loading markdown preview')));
} else { } else {
this.renderMarkdown(); this.renderMarkdown();
} }
}, },
showWriteTab() { showWriteTab() {
this.markdownPreview = ''; this.markdownPreview = '';
this.previewMarkdown = false; this.previewMarkdown = false;
}, },
renderMarkdown(data = {}) { renderMarkdown(data = {}) {
this.markdownPreviewLoading = false; this.markdownPreviewLoading = false;
this.markdownPreview = data.body || 'Nothing to preview.'; this.markdownPreview = data.body || 'Nothing to preview.';
if (data.references) { if (data.references) {
this.referencedCommands = data.references.commands; this.referencedCommands = data.references.commands;
this.referencedUsers = data.references.users; this.referencedUsers = data.references.users;
} }
this.$nextTick(() => { this.$nextTick(() => {
$(this.$refs['markdown-preview']).renderGFM(); $(this.$refs['markdown-preview']).renderGFM();
}); });
}, },
versionedPreviewPath() { versionedPreviewPath() {
const { markdownPreviewPath, markdownVersion } = this; const { markdownPreviewPath, markdownVersion } = this;
return `${markdownPreviewPath}${ return `${markdownPreviewPath}${
markdownPreviewPath.indexOf('?') === -1 ? '?' : '&' markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
}markdown_version=${markdownVersion}`; }markdown_version=${markdownVersion}`;
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import tooltip from '../../directives/tooltip'; import tooltip from '../../directives/tooltip';
import toolbarButton from './toolbar_button.vue'; import toolbarButton from './toolbar_button.vue';
import icon from '../icon.vue'; import icon from '../icon.vue';
export default { export default {
directives: { directives: {
tooltip, tooltip,
},
components: {
toolbarButton,
icon,
},
props: {
previewMarkdown: {
type: Boolean,
required: true,
}, },
components: { },
toolbarButton, computed: {
icon, mdTable() {
return [
'| header | header |',
'| ------ | ------ |',
'| cell | cell |',
'| cell | cell |',
].join('\n');
}, },
props: { },
previewMarkdown: { mounted() {
type: Boolean, $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
required: true, $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
}, },
beforeDestroy() {
$(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
},
methods: {
isValid(form) {
return (
!form ||
(form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0])
);
}, },
computed: {
mdTable() {
return [
'| header | header |',
'| ------ | ------ |',
'| cell | cell |',
'| cell | cell |',
].join('\n');
},
},
mounted() {
$(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
$(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
},
beforeDestroy() {
$(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
},
methods: {
isValid(form) {
return !form ||
form.find('.js-vue-markdown-field').length &&
$(this.$el).closest('form')[0] === form[0];
},
previewMarkdownTab(event, form) { previewMarkdownTab(event, form) {
if (event.target.blur) event.target.blur(); if (event.target.blur) event.target.blur();
if (!this.isValid(form)) return; if (!this.isValid(form)) return;
this.$emit('preview-markdown'); this.$emit('preview-markdown');
}, },
writeMarkdownTab(event, form) { writeMarkdownTab(event, form) {
if (event.target.blur) event.target.blur(); if (event.target.blur) event.target.blur();
if (!this.isValid(form)) return; if (!this.isValid(form)) return;
this.$emit('write-markdown'); this.$emit('write-markdown');
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import { Link } from '@gitlab-org/gitlab-ui'; import { Link } from '@gitlab-org/gitlab-ui';
export default { export default {
components: { components: {
'gl-link': Link, 'gl-link': Link,
},
props: {
markdownDocsPath: {
type: String,
required: true,
}, },
props: { quickActionsDocsPath: {
markdownDocsPath: { type: String,
type: String, required: false,
required: true, default: '',
},
quickActionsDocsPath: {
type: String,
required: false,
default: '',
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { canAttachFile: {
hasQuickActionsDocsPath() { type: Boolean,
return this.quickActionsDocsPath !== ''; required: false,
}, default: true,
}, },
}; },
computed: {
hasQuickActionsDocsPath() {
return this.quickActionsDocsPath !== '';
},
},
};
</script> </script>
<template> <template>
......
<script> <script>
import tooltip from '../../directives/tooltip'; import tooltip from '../../directives/tooltip';
import icon from '../icon.vue'; import icon from '../icon.vue';
export default { export default {
components: { components: {
icon, icon,
},
directives: {
tooltip,
},
props: {
buttonTitle: {
type: String,
required: true,
}, },
directives: { icon: {
tooltip, type: String,
required: true,
}, },
props: { tag: {
buttonTitle: { type: String,
type: String, required: true,
required: true,
},
icon: {
type: String,
required: true,
},
tag: {
type: String,
required: true,
},
tagBlock: {
type: String,
required: false,
default: '',
},
tagSelect: {
type: String,
required: false,
default: '',
},
prepend: {
type: Boolean,
required: false,
default: false,
},
}, },
}; tagBlock: {
type: String,
required: false,
default: '',
},
tagSelect: {
type: String,
required: false,
default: '',
},
prepend: {
type: Boolean,
required: false,
default: false,
},
},
};
</script> </script>
<template> <template>
......
...@@ -41,7 +41,8 @@ export default { ...@@ -41,7 +41,8 @@ export default {
// Find metric timestamp which is closest to deploymentTime // Find metric timestamp which is closest to deploymentTime
timestampDiff = Math.abs(metricTimestamps[0] - median); timestampDiff = Math.abs(metricTimestamps[0] - median);
metricTimestamps.forEach((timestamp, index) => { metricTimestamps.forEach((timestamp, index) => {
if (index === 0) { // Skip first element if (index === 0) {
// Skip first element
return; return;
} }
......
<script> <script>
/** /**
* Common component to render a placeholder note and user information. * Common component to render a placeholder note and user information.
* *
* This component needs to be used with a vuex store. * This component needs to be used with a vuex store.
* That vuex store needs to have a `getUserData` getter that contains * That vuex store needs to have a `getUserData` getter that contains
* { * {
* path: String, * path: String,
* avatar_url: String, * avatar_url: String,
* name: String, * name: String,
* username: String, * username: String,
* } * }
* *
* @example * @example
* <placeholder-note * <placeholder-note
* :note="{body: 'This is a note'}" * :note="{body: 'This is a note'}"
* /> * />
*/ */
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
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: { components: {
userAvatarLink, userAvatarLink,
},
props: {
note: {
type: Object,
required: true,
}, },
props: { },
note: { computed: {
type: Object, ...mapGetters(['getUserData']),
required: true, },
}, };
},
computed: {
...mapGetters([
'getUserData',
]),
},
};
</script> </script>
<template> <template>
......
<script> <script>
/** /**
* Common component to render a placeholder system note. * Common component to render a placeholder system note.
* *
* @example * @example
* <placeholder-system-note * <placeholder-system-note
* :note="{ body: 'Commands are being applied'}" * :note="{ body: 'Commands are being applied'}"
* /> * />
*/ */
export default { export default {
name: 'PlaceholderSystemNote', name: 'PlaceholderSystemNote',
props: { props: {
note: { note: {
type: Object, type: Object,
required: true, required: true,
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<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,
},
}, },
data() { side: {
return { type: String,
size: this.startSize, required: true,
};
}, },
computed: { minSize: {
className() { type: Number,
return `drag-${this.side}`; required: false,
}, default: 0,
cursorStyle() {
if (this.enabled) {
return { cursor: 'ew-resize' };
}
return {};
},
}, },
methods: { maxSize: {
resetSize(e) { type: Number,
e.preventDefault(); required: false,
this.$emit('resize-start', this.size); 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.$emit('resize-start', this.size);
this.size = this.startSize; this.size = this.startSize;
this.$emit('update:size', this.size); this.$emit('update:size', this.size);
// End resizing on next tick so that listeners can react to DOM changes // End resizing on next tick so that listeners can react to DOM changes
this.$nextTick(() => { this.$nextTick(() => {
this.$emit('resize-end', this.size); this.$emit('resize-end', this.size);
}); });
}, },
startDrag(e) { startDrag(e) {
if (this.enabled) { 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(); e.preventDefault();
let moved = e.clientX - this.startPos; this.startPos = e.clientX;
if (this.side === 'left') moved = -moved; this.currentStartSize = this.size;
let newSize = this.currentStartSize + moved; document.addEventListener('mousemove', this.drag);
if (newSize < this.minSize) { document.addEventListener('mouseup', this.endDrag, { once: true });
newSize = this.minSize; this.$emit('resize-start', this.size);
} else if (newSize > this.maxSize) { }
newSize = this.maxSize; },
} drag(e) {
this.size = newSize; 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); this.$emit('update:size', newSize);
}, },
endDrag(e) { endDrag(e) {
e.preventDefault(); e.preventDefault();
document.removeEventListener('mousemove', this.drag); document.removeEventListener('mousemove', this.drag);
this.$emit('resize-end', this.size); this.$emit('resize-end', this.size);
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
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,
required: false, required: false,
default: 'Date picker', default: 'Date picker',
},
selectedDate: {
type: Date,
required: false,
default: null,
},
minDate: {
type: Date,
required: false,
default: null,
},
maxDate: {
type: Date,
required: false,
default: null,
},
}, },
mounted() { selectedDate: {
this.calendar = new Pikaday({ type: Date,
field: this.$el.querySelector('.dropdown-menu-toggle'), required: false,
theme: 'gitlab-theme animate-picker', default: null,
format: 'yyyy-mm-dd', },
container: this.$el, minDate: {
defaultDate: this.selectedDate, type: Date,
setDefaultDate: !!this.selectedDate, required: false,
minDate: this.minDate, default: null,
maxDate: this.maxDate,
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: this.selected.bind(this),
onClose: this.toggled.bind(this),
});
this.$el.append(this.calendar.el);
this.calendar.show();
}, },
beforeDestroy() { maxDate: {
this.calendar.destroy(); type: Date,
required: false,
default: null,
},
},
mounted() {
this.calendar = new Pikaday({
field: this.$el.querySelector('.dropdown-menu-toggle'),
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: this.$el,
defaultDate: this.selectedDate,
setDefaultDate: !!this.selectedDate,
minDate: this.minDate,
maxDate: this.maxDate,
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: this.selected.bind(this),
onClose: this.toggled.bind(this),
});
this.$el.append(this.calendar.el);
this.calendar.show();
},
beforeDestroy() {
this.calendar.destroy();
},
methods: {
selected(dateText) {
this.$emit('newDateSelected', this.calendar.toString(dateText));
}, },
methods: { toggled() {
selected(dateText) { this.$emit('hidePicker');
this.$emit('newDateSelected', this.calendar.toString(dateText));
},
toggled() {
this.$emit('hidePicker');
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<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.
...@@ -16,70 +15,70 @@ ...@@ -16,70 +15,70 @@
*/ */
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',
directives: { directives: {
tooltip, tooltip,
},
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',
},
},
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;
}, },
props: { tooltipContainer() {
lazy: { return this.tooltipText ? 'body' : null;
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',
},
}, },
computed: { avatarSizeClass() {
// API response sends null when gravatar is disabled and return `s${this.size}`;
// 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>
......
<script> <script>
import DeprecatedModal from './deprecated_modal.vue'; import DeprecatedModal from './deprecated_modal.vue';
export default { export default {
name: 'RecaptchaModal', name: 'RecaptchaModal',
components: { components: {
DeprecatedModal, DeprecatedModal,
}, },
props: { props: {
html: { html: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
},
}, },
},
data() { data() {
return { return {
script: {}, script: {},
scriptSrc: 'https://www.google.com/recaptcha/api.js', scriptSrc: 'https://www.google.com/recaptcha/api.js',
}; };
}, },
watch: { watch: {
html() { html() {
this.appendRecaptchaScript(); this.appendRecaptchaScript();
},
}, },
},
mounted() { mounted() {
window.recaptchaDialogCallback = this.submit.bind(this); window.recaptchaDialogCallback = this.submit.bind(this);
}, },
methods: { methods: {
appendRecaptchaScript() { appendRecaptchaScript() {
this.removeRecaptchaScript(); this.removeRecaptchaScript();
const script = document.createElement('script'); const script = document.createElement('script');
script.src = this.scriptSrc; script.src = this.scriptSrc;
script.classList.add('js-recaptcha-script'); script.classList.add('js-recaptcha-script');
script.async = true; script.async = true;
script.defer = true; script.defer = true;
this.script = script; this.script = script;
document.body.appendChild(script); document.body.appendChild(script);
}, },
removeRecaptchaScript() { removeRecaptchaScript() {
if (this.script instanceof Element) this.script.remove(); if (this.script instanceof Element) this.script.remove();
}, },
close() { close() {
this.removeRecaptchaScript(); this.removeRecaptchaScript();
this.$emit('close'); this.$emit('close');
}, },
submit() { submit() {
this.$el.querySelector('form').submit(); this.$el.querySelector('form').submit();
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import datePicker from '../pikaday.vue'; import datePicker from '../pikaday.vue';
import toggleSidebar from './toggle_sidebar.vue'; import toggleSidebar from './toggle_sidebar.vue';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
import { dateInWords } from '../../../lib/utils/datetime_utility'; import { dateInWords } from '../../../lib/utils/datetime_utility';
export default { export default {
name: 'SidebarDatePicker', name: 'SidebarDatePicker',
components: { components: {
datePicker, datePicker,
toggleSidebar, toggleSidebar,
collapsedCalendarIcon, collapsedCalendarIcon,
}, },
props: { props: {
blockClass: { blockClass: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
collapsed: { collapsed: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true, default: true,
}, },
showToggleSidebar: { showToggleSidebar: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
isLoading: { isLoading: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
editable: { editable: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
label: { label: {
type: String, type: String,
required: false, required: false,
default: 'Date picker', default: 'Date picker',
}, },
selectedDate: { selectedDate: {
type: Date, type: Date,
required: false, required: false,
default: null, default: null,
}, },
minDate: { minDate: {
type: Date, type: Date,
required: false, required: false,
default: null, default: null,
}, },
maxDate: { maxDate: {
type: Date, type: Date,
required: false, required: false,
default: null, default: null,
}, },
}, },
data() { data() {
return { return {
editing: false, editing: false,
}; };
}, },
computed: { computed: {
selectedAndEditable() { selectedAndEditable() {
return this.selectedDate && this.editable; return this.selectedDate && this.editable;
}, },
selectedDateWords() { selectedDateWords() {
return dateInWords(this.selectedDate, true); return dateInWords(this.selectedDate, true);
}, },
collapsedText() { collapsedText() {
return this.selectedDateWords ? this.selectedDateWords : 'None'; return this.selectedDateWords ? this.selectedDateWords : 'None';
}, },
}, },
methods: { methods: {
stopEditing() { stopEditing() {
this.editing = false; this.editing = false;
}, },
toggleDatePicker() { toggleDatePicker() {
this.editing = !this.editing; this.editing = !this.editing;
}, },
newDateSelected(date = null) { newDateSelected(date = null) {
this.date = date; this.date = date;
this.editing = false; this.editing = false;
this.$emit('saveDate', date); this.$emit('saveDate', date);
}, },
toggleSidebar() { toggleSidebar() {
this.$emit('toggleCollapse'); this.$emit('toggleCollapse');
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<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: Here is an example `change` method:
...@@ -20,12 +20,12 @@ ...@@ -20,12 +20,12 @@
gl.utils.visitUrl(`?page=${pagenum}`); gl.utils.visitUrl(`?page=${pagenum}`);
}, },
*/ */
change: { change: {
type: Function, type: Function,
required: true, required: true,
}, },
/** /**
pageInfo will come from the headers of the API call pageInfo will come from the headers of the API call
in the `.then` clause of the VueResource API call in the `.then` clause of the VueResource API call
there should be a function that contructs the pageInfo for this component there should be a function that contructs the pageInfo for this component
...@@ -41,94 +41,94 @@ ...@@ -41,94 +41,94 @@
previousPage: +headers['X-Prev-Page'], previousPage: +headers['X-Prev-Page'],
}); });
*/ */
pageInfo: { pageInfo: {
type: Object, type: Object,
required: true, required: true,
}, },
},
computed: {
prev() {
return this.pageInfo.previousPage;
},
next() {
return this.pageInfo.nextPage;
},
getItems() {
const total = this.pageInfo.totalPages;
const { page } = this.pageInfo;
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;
}, },
computed: { },
prev() { methods: {
return this.pageInfo.previousPage; changePage(text, isDisabled) {
}, if (isDisabled) return;
next() {
return this.pageInfo.nextPage; const { totalPages, nextPage, previousPage } = this.pageInfo;
},
getItems() { switch (text) {
const total = this.pageInfo.totalPages; case SPREAD:
const { page } = this.pageInfo; break;
const items = []; case LAST:
this.change(totalPages);
if (page > 1) { break;
items.push({ title: FIRST, first: true }); case NEXT:
} this.change(nextPage);
break;
if (page > 1) { case PREV:
items.push({ title: PREV, prev: true }); this.change(previousPage);
} else { break;
items.push({ title: PREV, disabled: true, prev: true }); case FIRST:
} this.change(1);
break;
if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); default:
this.change(+text);
const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); break;
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;
},
}, },
methods: { hideOnSmallScreen(item) {
changePage(text, isDisabled) { return !item.first && !item.last && !item.next && !item.prev && !item.active;
if (isDisabled) return;
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;
}
},
hideOnSmallScreen(item) {
return !item.first && !item.last && !item.next && !item.prev && !item.active;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div <div
......
...@@ -11,9 +11,7 @@ export default { ...@@ -11,9 +11,7 @@ export default {
directives: { directives: {
tooltip, tooltip,
}, },
mixins: [ mixins: [timeagoMixin],
timeagoMixin,
],
props: { props: {
time: { time: {
type: String, type: String,
......
<script> <script>
import { s__ } from '../../locale'; import { s__ } from '../../locale';
import icon from './icon.vue'; import icon from './icon.vue';
const ICON_ON = 'status_success_borderless'; const ICON_ON = 'status_success_borderless';
const ICON_OFF = 'status_failed_borderless'; const ICON_OFF = 'status_failed_borderless';
const LABEL_ON = s__('ToggleButton|Toggle Status: ON'); const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF'); const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
export default { export default {
components: { components: {
icon, icon,
}, },
model: { model: {
prop: 'value', prop: 'value',
event: 'change', event: 'change',
}, },
props: { props: {
name: { name: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
}, },
value: { value: {
type: Boolean, type: Boolean,
required: false, required: false,
default: null, default: null,
},
disabledInput: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
}, },
disabledInput: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
computed: { computed: {
toggleIcon() { toggleIcon() {
return this.value ? ICON_ON : ICON_OFF; return this.value ? ICON_ON : ICON_OFF;
}, },
ariaLabel() { ariaLabel() {
return this.value ? LABEL_ON : LABEL_OFF; return this.value ? LABEL_ON : LABEL_OFF;
},
}, },
},
methods: { methods: {
toggleFeature() { toggleFeature() {
if (!this.disabledInput) this.$emit('change', !this.value); if (!this.disabledInput) this.$emit('change', !this.value);
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
/* This is a re-usable vue component for rendering a user avatar wrapped in /* This is a re-usable vue component for rendering a user avatar wrapped in
a clickable link (likely to the user's profile). The link, image, and a clickable link (likely to the user's profile). The link, image, and
tooltip can be configured by props passed to this component. tooltip can be configured by props passed to this component.
......
<script> <script>
/* This is a re-usable vue component for rendering a user avatar svg (typically /* This is a re-usable vue component for rendering a user avatar svg (typically
for a blank state). It will receive styles comparable to the user avatar, for a blank state). It will receive styles comparable to the user avatar,
but no image is loaded, it isn't wrapped in a link, and tooltips aren't supported. but no image is loaded, it isn't wrapped in a link, and tooltips aren't supported.
...@@ -42,4 +41,3 @@ export default { ...@@ -42,4 +41,3 @@ export default {
v-html="svg" v-html="svg"
/> />
</template> </template>
...@@ -4,10 +4,7 @@ ...@@ -4,10 +4,7 @@
* *
* Components need to have `scope`, `page` and `requestData` * Components need to have `scope`, `page` and `requestData`
*/ */
import { import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/common_utils';
historyPushState,
buildUrlWithCurrentLocation,
} from '../../lib/utils/common_utils';
export default { export default {
methods: { methods: {
...@@ -24,12 +21,14 @@ export default { ...@@ -24,12 +21,14 @@ export default {
// stop polling // stop polling
this.poll.stop(); this.poll.stop();
const queryString = Object.keys(parameters).map((parameter) => { const queryString = Object.keys(parameters)
const value = parameters[parameter]; .map(parameter => {
// update internal state for UI const value = parameters[parameter];
this[parameter] = value; // update internal state for UI
return `${parameter}=${encodeURIComponent(value)}`; this[parameter] = value;
}).join('&'); return `${parameter}=${encodeURIComponent(value)}`;
})
.join('&');
// update polling parameters // update polling parameters
this.requestData = parameters; this.requestData = parameters;
......
...@@ -6,7 +6,7 @@ export default class ListLabel { ...@@ -6,7 +6,7 @@ export default class ListLabel {
this.color = obj.color; this.color = obj.color;
this.textColor = obj.text_color; this.textColor = obj.text_color;
this.description = obj.description; this.description = obj.description;
this.priority = (obj.priority !== null) ? obj.priority : Infinity; this.priority = obj.priority !== null ? obj.priority : Infinity;
} }
} }
......
import { import { __, n__, s__, sprintf } from '../locale';
__,
n__,
s__,
sprintf,
} from '../locale';
export default (Vue) => { export default Vue => {
Vue.mixin({ Vue.mixin({
methods: { methods: {
/** /**
......
...@@ -21,7 +21,7 @@ Vue.http.interceptors.push((request, next) => { ...@@ -21,7 +21,7 @@ Vue.http.interceptors.push((request, next) => {
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
request.headers.set(csrf.headerKey, csrf.token); request.headers.set(csrf.headerKey, csrf.token);
next((response) => { next(response => {
// Headers object has a `forEach` property that iterates through all values. // Headers object has a `forEach` property that iterates through all values.
const headers = {}; const headers = {};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment