Commit cdc49388 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Fix more rules

parent 318d6f44
...@@ -91,18 +91,21 @@ ...@@ -91,18 +91,21 @@
<template> <template>
<div class="cell text-cell"> <div class="cell text-cell">
<prompt /> <prompt />
<div class="markdown" v-html="markdown"></div> <div
class="markdown"
v-html="markdown">
</div>
</div> </div>
</template> </template>
<style> <style>
.markdown .katex { .markdown .katex {
display: block; display: block;
text-align: center; text-align: center;
} }
.markdown .inline-katex .katex { .markdown .inline-katex .katex {
display: inline; display: inline;
text-align: initial; text-align: initial;
} }
</style> </style>
<script> <script>
import Prompt from '../prompt.vue'; import Prompt from '../prompt.vue';
export default { export default {
props: { components: {
rawCode: { prompt: Prompt,
type: String,
required: true,
}, },
}, props: {
components: { rawCode: {
prompt: Prompt, type: String,
}, required: true,
}; },
},
};
</script> </script>
<template> <template>
......
<script> <script>
import Prompt from '../prompt.vue'; import Prompt from '../prompt.vue';
export default { export default {
props: { components: {
outputType: { prompt: Prompt,
type: String,
required: true,
}, },
rawCode: { props: {
type: String, outputType: {
required: true, type: String,
required: true,
},
rawCode: {
type: String,
required: true,
},
}, },
}, };
components: {
prompt: Prompt,
},
};
</script> </script>
<template> <template>
<div class="output"> <div class="output">
<prompt /> <prompt />
<img <img :src="'data:' + outputType + ';base64,' + rawCode" />
:src="'data:' + outputType + ';base64,' + rawCode" />
</div> </div>
</template> </template>
<script> <script>
import CodeCell from '../code/index.vue'; import CodeCell from '../code/index.vue';
import Html from './html.vue'; import Html from './html.vue';
import Image from './image.vue'; import Image from './image.vue';
export default { export default {
props: { components: {
codeCssClass: { 'code-cell': CodeCell,
type: String, 'html-output': Html,
required: false, 'image-output': Image,
default: '',
}, },
count: { props: {
type: Number, codeCssClass: {
required: false, type: String,
default: 0, required: false,
default: '',
},
count: {
type: Number,
required: false,
default: 0,
},
output: {
type: Object,
requred: true,
default: () => ({}),
},
}, },
output: { data() {
type: Object, return {
requred: true, outputType: '',
};
}, },
}, computed: {
components: { componentName() {
'code-cell': CodeCell, if (this.output.text) {
'html-output': Html, return 'code-cell';
'image-output': Image, } else if (this.output.data['image/png']) {
}, this.outputType = 'image/png';
data() {
return {
outputType: '',
};
},
computed: {
componentName() {
if (this.output.text) {
return 'code-cell';
} else if (this.output.data['image/png']) {
this.outputType = 'image/png';
return 'image-output'; return 'image-output';
} else if (this.output.data['text/html']) { } else if (this.output.data['text/html']) {
this.outputType = 'text/html'; this.outputType = 'text/html';
return 'html-output'; return 'html-output';
} else if (this.output.data['image/svg+xml']) { } else if (this.output.data['image/svg+xml']) {
this.outputType = 'image/svg+xml'; this.outputType = 'image/svg+xml';
return 'html-output'; return 'html-output';
} }
this.outputType = 'text/plain'; this.outputType = 'text/plain';
return 'code-cell'; return 'code-cell';
}, },
rawCode() { rawCode() {
if (this.output.text) { if (this.output.text) {
return this.output.text.join(''); return this.output.text.join('');
} }
return this.dataForType(this.outputType); return this.dataForType(this.outputType);
},
}, },
}, methods: {
methods: { dataForType(type) {
dataForType(type) { let data = this.output.data[type];
let data = this.output.data[type];
if (typeof data === 'object') { if (typeof data === 'object') {
data = data.join(''); data = data.join('');
} }
return data; return data;
},
}, },
}, };
};
</script> </script>
<template> <template>
<component :is="componentName" <component
:is="componentName"
type="output" type="output"
:outputType="outputType" :output-type="outputType"
:count="count" :count="count"
:raw-code="rawCode" :raw-code="rawCode"
:code-css-class="codeCssClass" /> :code-css-class="codeCssClass"
/>
</template> </template>
...@@ -4,10 +4,17 @@ ...@@ -4,10 +4,17 @@
type: { type: {
type: String, type: String,
required: false, required: false,
default: '',
}, },
count: { count: {
type: Number, type: Number,
required: false, required: false,
default: 0,
},
},
computed: {
hasKeys() {
return this.type !== '' && this.count;
}, },
}, },
}; };
...@@ -15,16 +22,16 @@ ...@@ -15,16 +22,16 @@
<template> <template>
<div class="prompt"> <div class="prompt">
<span v-if="type && count"> <span v-if="hasKeys">
{{ type }} [{{ count }}]: {{ type }} [{{ count }}]:
</span> </span>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.prompt { .prompt {
padding: 0 10px; padding: 0 10px;
min-width: 7em; min-width: 7em;
font-family: monospace; font-family: monospace;
} }
</style> </style>
...@@ -20,11 +20,6 @@ ...@@ -20,11 +20,6 @@
default: '', default: '',
}, },
}, },
methods: {
cellType(type) {
return `${type}-cell`;
},
},
computed: { computed: {
cells() { cells() {
if (this.notebook.worksheets) { if (this.notebook.worksheets) {
...@@ -45,6 +40,11 @@ ...@@ -45,6 +40,11 @@
return Object.keys(this.notebook).length; return Object.keys(this.notebook).length;
}, },
}, },
methods: {
cellType(type) {
return `${type}-cell`;
},
},
}; };
</script> </script>
......
...@@ -15,7 +15,17 @@ ...@@ -15,7 +15,17 @@
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'commentForm', name: 'CommentForm',
components: {
issueWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
},
mixins: [
issuableStateMixin,
],
data() { data() {
return { return {
note: '', note: '',
...@@ -27,21 +37,6 @@ ...@@ -27,21 +37,6 @@
isSubmitButtonDisabled: true, isSubmitButtonDisabled: true,
}; };
}, },
components: {
issueWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
},
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
},
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getCurrentUserLastNote', 'getCurrentUserLastNote',
...@@ -99,6 +94,23 @@ ...@@ -99,6 +94,23 @@
return this.getNoteableData.create_note_path; return this.getNoteableData.create_note_path;
}, },
}, },
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
},
},
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
});
this.initAutoSave();
this.initTaskList();
},
methods: { methods: {
...mapActions([ ...mapActions([
'saveNote', 'saveNote',
...@@ -231,18 +243,6 @@ Please check your network connection and try again.`; ...@@ -231,18 +243,6 @@ Please check your network connection and try again.`;
}); });
}, },
}, },
mixins: [
issuableStateMixin,
],
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
});
this.initAutoSave();
this.initTaskList();
},
}; };
</script> </script>
...@@ -266,7 +266,7 @@ Please check your network connection and try again.`; ...@@ -266,7 +266,7 @@ Please check your network connection and try again.`;
:img-src="author.avatar_url" :img-src="author.avatar_url"
:img-alt="author.name" :img-alt="author.name"
:img-size="40" :img-size="40"
/> />
</div> </div>
<div class="timeline-content timeline-content-form"> <div class="timeline-content timeline-content-form">
<form <form
...@@ -310,7 +310,7 @@ Please check your network connection and try again.`; ...@@ -310,7 +310,7 @@ Please check your network connection and try again.`;
:disabled="isSubmitButtonDisabled" :disabled="isSubmitButtonDisabled"
class="btn btn-create comment-btn js-comment-button js-comment-submit-button" class="btn btn-create comment-btn js-comment-button js-comment-submit-button"
type="submit"> type="submit">
{{commentButtonTitle}} {{ commentButtonTitle }}
</button> </button>
<button <button
:disabled="isSubmitButtonDisabled" :disabled="isSubmitButtonDisabled"
...@@ -352,7 +352,7 @@ Please check your network connection and try again.`; ...@@ -352,7 +352,7 @@ Please check your network connection and try again.`;
<i <i
aria-hidden="true" aria-hidden="true"
class="fa fa-check icon"> class="fa fa-check icon">
</i> </i>
<div class="description"> <div class="description">
<strong>Start discussion</strong> <strong>Start discussion</strong>
<p> <p>
...@@ -370,7 +370,7 @@ Please check your network connection and try again.`; ...@@ -370,7 +370,7 @@ Please check your network connection and try again.`;
:class="actionButtonClassNames" :class="actionButtonClassNames"
:disabled="isSubmitting" :disabled="isSubmitting"
class="btn btn-comment btn-comment-and-close js-action-button"> class="btn btn-comment btn-comment-and-close js-action-button">
{{issueActionButtonTitle}} {{ issueActionButtonTitle }}
</button> </button>
<button <button
type="button" type="button"
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
import Issuable from '~/vue_shared/mixins/issuable'; import Issuable from '~/vue_shared/mixins/issuable';
export default { export default {
mixins: [
Issuable,
],
components: { components: {
Icon, Icon,
}, },
mixins: [
Issuable,
],
}; };
</script> </script>
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
<icon <icon
name="lock" name="lock"
:size="16" :size="16"
class="icon"> class="icon"
</icon> />
<span>This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.</span> <span>
</span> This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.
</span>
</span>
</div> </div>
</template> </template>
...@@ -9,7 +9,13 @@ ...@@ -9,7 +9,13 @@
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
name: 'noteActions', name: 'NoteActions',
directives: {
tooltip,
},
components: {
loadingIcon,
},
props: { props: {
authorId: { authorId: {
type: Number, type: Number,
...@@ -41,12 +47,6 @@ ...@@ -41,12 +47,6 @@
required: true, required: true,
}, },
}, },
directives: {
tooltip,
},
components: {
loadingIcon,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getUserDataByProp', 'getUserDataByProp',
...@@ -98,20 +98,21 @@ ...@@ -98,20 +98,21 @@
data-placement="bottom" data-placement="bottom"
data-container="body" data-container="body"
href="#" href="#"
title="Add reaction"> title="Add reaction"
<loading-icon :inline="true" /> >
<span <loading-icon :inline="true" />
v-html="emojiSmiling" <span
class="link-highlight award-control-icon-neutral"> v-html="emojiSmiling"
</span> class="link-highlight award-control-icon-neutral">
<span </span>
v-html="emojiSmiley" <span
class="link-highlight award-control-icon-positive"> v-html="emojiSmiley"
</span> class="link-highlight award-control-icon-positive">
<span </span>
v-html="emojiSmile" <span
class="link-highlight award-control-icon-super-positive"> v-html="emojiSmile"
</span> class="link-highlight award-control-icon-super-positive">
</span>
</a> </a>
</div> </div>
<div <div
...@@ -127,7 +128,8 @@ ...@@ -127,7 +128,8 @@
data-placement="bottom"> data-placement="bottom">
<span <span
v-html="editSvg" v-html="editSvg"
class="link-highlight"></span> class="link-highlight">
</span>
</button> </button>
</div> </div>
<div <div
...@@ -143,7 +145,8 @@ ...@@ -143,7 +145,8 @@
data-placement="bottom"> data-placement="bottom">
<span <span
class="icon" class="icon"
v-html="ellipsisSvg"></span> v-html="ellipsisSvg">
</span>
</button> </button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left"> <ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse"> <li v-if="canReportAsAbuse">
......
<script> <script>
export default { export default {
name: 'noteAttachment', name: 'NoteAttachment',
props: { props: {
attachment: { attachment: {
type: Object, type: Object,
...@@ -19,7 +19,8 @@ ...@@ -19,7 +19,8 @@
rel="noopener noreferrer"> rel="noopener noreferrer">
<img <img
:src="attachment.url" :src="attachment.url"
class="note-image-attach" /> class="note-image-attach"
/>
</a> </a>
<div class="attachment"> <div class="attachment">
<a <a
...@@ -29,8 +30,9 @@ ...@@ -29,8 +30,9 @@
rel="noopener noreferrer"> rel="noopener noreferrer">
<i <i
class="fa fa-paperclip" class="fa fa-paperclip"
aria-hidden="true"></i> aria-hidden="true">
{{attachment.filename}} </i>
{{ attachment.filename }}
</a> </a>
</div> </div>
</div> </div>
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
directives: {
tooltip,
},
props: { props: {
awards: { awards: {
type: Array, type: Array,
...@@ -26,9 +29,6 @@ ...@@ -26,9 +29,6 @@
required: true, required: true,
}, },
}, },
directives: {
tooltip,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getUserData', 'getUserData',
...@@ -73,6 +73,11 @@ ...@@ -73,6 +73,11 @@
return this.getUserData.id; return this.getUserData.id;
}, },
}, },
created() {
this.emojiSmiling = emojiSmiling;
this.emojiSmile = emojiSmile;
this.emojiSmiley = emojiSmiley;
},
methods: { methods: {
...mapActions([ ...mapActions([
'toggleAwardRequest', 'toggleAwardRequest',
...@@ -168,11 +173,6 @@ ...@@ -168,11 +173,6 @@
.catch(() => Flash('Something went wrong on our end.')); .catch(() => Flash('Something went wrong on our end.'));
}, },
}, },
created() {
this.emojiSmiling = emojiSmiling;
this.emojiSmile = emojiSmile;
this.emojiSmiley = emojiSmiley;
},
}; };
</script> </script>
...@@ -191,7 +191,7 @@ ...@@ -191,7 +191,7 @@
type="button"> type="button">
<span v-html="getAwardHTML(awardName)"></span> <span v-html="getAwardHTML(awardName)"></span>
<span class="award-control-text js-counter"> <span class="award-control-text js-counter">
{{awardList.length}} {{ awardList.length }}
</span> </span>
</button> </button>
<div <div
......
...@@ -7,6 +7,15 @@ ...@@ -7,6 +7,15 @@
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
export default { export default {
components: {
noteEditedText,
noteAwardsList,
noteAttachment,
noteForm,
},
mixins: [
autosave,
],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -22,40 +31,11 @@ ...@@ -22,40 +31,11 @@
default: false, default: false,
}, },
}, },
mixins: [
autosave,
],
components: {
noteEditedText,
noteAwardsList,
noteAttachment,
noteForm,
},
computed: { computed: {
noteBody() { noteBody() {
return this.note.note; return this.note.note;
}, },
}, },
methods: {
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
initTaskList() {
if (this.canEdit) {
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
}
},
handleFormUpdate(note, parentElement, callback) {
this.$emit('handleFormUpdate', note, parentElement, callback);
},
formCancelHandler(shouldConfirm, isDirty) {
this.$emit('cancelFormEdition', shouldConfirm, isDirty);
},
},
mounted() { mounted() {
this.renderGFM(); this.renderGFM();
this.initTaskList(); this.initTaskList();
...@@ -76,6 +56,26 @@ ...@@ -76,6 +56,26 @@
} }
} }
}, },
methods: {
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
initTaskList() {
if (this.canEdit) {
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
}
},
handleFormUpdate(note, parentElement, callback) {
this.$emit('handleFormUpdate', note, parentElement, callback);
},
formCancelHandler(shouldConfirm, isDirty) {
this.$emit('cancelFormEdition', shouldConfirm, isDirty);
},
},
}; };
</script> </script>
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
:is-editing="isEditing" :is-editing="isEditing"
:note-body="noteBody" :note-body="noteBody"
:note-id="note.id" :note-id="note.id"
/> />
<textarea <textarea
v-if="canEdit" v-if="canEdit"
v-model="note.note" v-model="note.note"
...@@ -106,17 +106,17 @@ ...@@ -106,17 +106,17 @@
:edited-at="note.last_edited_at" :edited-at="note.last_edited_at"
:edited-by="note.last_edited_by" :edited-by="note.last_edited_by"
action-text="Edited" action-text="Edited"
/> />
<note-awards-list <note-awards-list
v-if="note.award_emoji.length" v-if="note.award_emoji.length"
:note-id="note.id" :note-id="note.id"
:note-author-id="note.author.id" :note-author-id="note.author.id"
:awards="note.award_emoji" :awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path" :toggle-award-path="note.toggle_award_path"
/> />
<note-attachment <note-attachment
v-if="note.attachment" v-if="note.attachment"
:attachment="note.attachment" :attachment="note.attachment"
/> />
</div> </div>
</template> </template>
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default { export default {
name: 'editedNoteText', name: 'EditedNoteText',
components: {
timeAgoTooltip,
},
props: { props: {
actionText: { actionText: {
type: String, type: String,
...@@ -15,6 +18,7 @@ ...@@ -15,6 +18,7 @@
editedBy: { editedBy: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}),
}, },
className: { className: {
type: String, type: String,
...@@ -22,25 +26,22 @@ ...@@ -22,25 +26,22 @@
default: 'edited-text', default: 'edited-text',
}, },
}, },
components: {
timeAgoTooltip,
},
}; };
</script> </script>
<template> <template>
<div :class="className"> <div :class="className">
{{actionText}} {{ actionText }}
<time-ago-tooltip <time-ago-tooltip
:time="editedAt" :time="editedAt"
tooltip-placement="bottom" tooltip-placement="bottom"
/> />
<template v-if="editedBy"> <template v-if="editedBy">
by by
<a <a
:href="editedBy.path" :href="editedBy.path"
class="js-vue-author author_link"> class="js-vue-author author_link">
{{editedBy.name}} {{ editedBy.name }}
</a> </a>
</template> </template>
</div> </div>
......
...@@ -6,7 +6,14 @@ ...@@ -6,7 +6,14 @@
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueNoteForm', name: 'IssueNoteForm',
components: {
issueWarning,
markdownField,
},
mixins: [
issuableStateMixin,
],
props: { props: {
noteBody: { noteBody: {
type: String, type: String,
...@@ -16,6 +23,7 @@ ...@@ -16,6 +23,7 @@
noteId: { noteId: {
type: Number, type: Number,
required: false, required: false,
default: 0,
}, },
saveButtonTitle: { saveButtonTitle: {
type: String, type: String,
...@@ -39,10 +47,6 @@ ...@@ -39,10 +47,6 @@
isSubmitting: false, isSubmitting: false,
}; };
}, },
components: {
issueWarning,
markdownField,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getDiscussionLastNote', 'getDiscussionLastNote',
...@@ -70,6 +74,18 @@ ...@@ -70,6 +74,18 @@
return !this.note.length || this.isSubmitting; return !this.note.length || this.isSubmitting;
}, },
}, },
watch: {
noteBody() {
if (this.note === this.noteBody) {
this.note = this.noteBody;
} else {
this.conflictWhileEditing = true;
}
},
},
mounted() {
this.$refs.textarea.focus();
},
methods: { methods: {
handleUpdate() { handleUpdate() {
this.isSubmitting = true; this.isSubmitting = true;
...@@ -94,26 +110,13 @@ ...@@ -94,26 +110,13 @@
this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note); this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note);
}, },
}, },
mixins: [
issuableStateMixin,
],
mounted() {
this.$refs.textarea.focus();
},
watch: {
noteBody() {
if (this.note === this.noteBody) {
this.note = this.noteBody;
} else {
this.conflictWhileEditing = true;
}
},
},
}; };
</script> </script>
<template> <template>
<div ref="editNoteForm" class="note-edit-form current-note-edit-form"> <div
ref="editNoteForm"
class="note-edit-form current-note-edit-form">
<div <div
v-if="conflictWhileEditing" v-if="conflictWhileEditing"
class="js-conflict-edit-warning alert alert-danger"> class="js-conflict-edit-warning alert alert-danger">
...@@ -121,12 +124,13 @@ ...@@ -121,12 +124,13 @@
<a <a
:href="noteHash" :href="noteHash"
target="_blank" target="_blank"
rel="noopener noreferrer">updated comment</a> rel="noopener noreferrer">
to ensure information is not lost. updated comment
</a>
to ensure information is not lost.
</div> </div>
<div class="flash-container timeline-content"></div> <div class="flash-container timeline-content"></div>
<form <form class="edit-note common-note-form js-quick-submit gfm-form">
class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning <issue-warning
v-if="hasWarning(getNoteableData)" v-if="hasWarning(getNoteableData)"
...@@ -160,7 +164,7 @@ ...@@ -160,7 +164,7 @@
@click="handleUpdate()" @click="handleUpdate()"
:disabled="isDisabled" :disabled="isDisabled"
class="js-vue-issue-save btn btn-save"> class="js-vue-issue-save btn btn-save">
{{saveButtonTitle}} {{ saveButtonTitle }}
</button> </button>
<button <button
@click="cancelHandler()" @click="cancelHandler()"
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default { export default {
components: {
timeAgoTooltip,
},
props: { props: {
author: { author: {
type: Object, type: Object,
...@@ -37,9 +40,6 @@ ...@@ -37,9 +40,6 @@
isExpanded: true, isExpanded: true,
}; };
}, },
components: {
timeAgoTooltip,
},
computed: { computed: {
toggleChevronClass() { toggleChevronClass() {
return this.isExpanded ? 'fa-chevron-up' : 'fa-chevron-down'; return this.isExpanded ? 'fa-chevron-up' : 'fa-chevron-down';
...@@ -67,16 +67,16 @@ ...@@ -67,16 +67,16 @@
<div class="note-header-info"> <div class="note-header-info">
<a :href="author.path"> <a :href="author.path">
<span class="note-header-author-name"> <span class="note-header-author-name">
{{author.name}} {{ author.name }}
</span> </span>
<span class="note-headline-light"> <span class="note-headline-light">
@{{author.username}} @{{ author.username }}
</span> </span>
</a> </a>
<span class="note-headline-light"> <span class="note-headline-light">
<span class="note-headline-meta"> <span class="note-headline-meta">
<template v-if="actionText"> <template v-if="actionText">
{{actionText}} {{ actionText }}
</template> </template>
<span <span
v-if="actionTextHtml" v-if="actionTextHtml"
...@@ -90,12 +90,13 @@ ...@@ -90,12 +90,13 @@
<time-ago-tooltip <time-ago-tooltip
:time="createdAt" :time="createdAt"
tooltip-placement="bottom" tooltip-placement="bottom"
/> />
</a> </a>
<i <i
class="fa fa-spinner fa-spin editing-spinner" class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated" aria-label="Comment is being updated"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</span> </span>
</span> </span>
...@@ -106,12 +107,12 @@ ...@@ -106,12 +107,12 @@
@click="handleToggle" @click="handleToggle"
class="note-action-button discussion-toggle-button js-vue-toggle-button" class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button"> type="button">
<i <i
:class="toggleChevronClass" :class="toggleChevronClass"
class="fa" class="fa"
aria-hidden="true"> aria-hidden="true">
</i> </i>
Toggle discussion Toggle discussion
</button> </button>
</div> </div>
</div> </div>
......
...@@ -13,17 +13,6 @@ ...@@ -13,17 +13,6 @@
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
export default { export default {
props: {
note: {
type: Object,
required: true,
},
},
data() {
return {
isReplying: false,
};
},
components: { components: {
noteableNote, noteableNote,
userAvatarLink, userAvatarLink,
...@@ -37,6 +26,17 @@ ...@@ -37,6 +26,17 @@
mixins: [ mixins: [
autosave, autosave,
], ],
props: {
note: {
type: Object,
required: true,
},
},
data() {
return {
isReplying: false,
};
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getNoteableData', 'getNoteableData',
...@@ -72,6 +72,20 @@ ...@@ -72,6 +72,20 @@
return null; return null;
}, },
}, },
mounted() {
if (this.isReplying) {
this.initAutoSave();
}
},
updated() {
if (this.isReplying) {
if (!this.autosave) {
this.initAutoSave();
} else {
this.setAutoSave();
}
}
},
methods: { methods: {
...mapActions([ ...mapActions([
'saveNote', 'saveNote',
...@@ -139,20 +153,6 @@ Please check your network connection and try again.`; ...@@ -139,20 +153,6 @@ Please check your network connection and try again.`;
}); });
}, },
}, },
mounted() {
if (this.isReplying) {
this.initAutoSave();
}
},
updated() {
if (this.isReplying) {
if (!this.autosave) {
this.initAutoSave();
} else {
this.setAutoSave();
}
}
},
}; };
</script> </script>
...@@ -165,7 +165,7 @@ Please check your network connection and try again.`; ...@@ -165,7 +165,7 @@ Please check your network connection and try again.`;
:img-src="author.avatar_url" :img-src="author.avatar_url"
:img-alt="author.name" :img-alt="author.name"
:img-size="40" :img-size="40"
/> />
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div class="discussion"> <div class="discussion">
...@@ -185,42 +185,43 @@ Please check your network connection and try again.`; ...@@ -185,42 +185,43 @@ Please check your network connection and try again.`;
:edited-by="lastUpdatedBy" :edited-by="lastUpdatedBy"
action-text="Last updated" action-text="Last updated"
class-name="discussion-headline-light js-discussion-headline" class-name="discussion-headline-light js-discussion-headline"
/> />
</div>
</div> </div>
<div </div>
v-if="note.expanded" <div
class="discussion-body"> v-if="note.expanded"
<div class="panel panel-default"> class="discussion-body">
<div class="discussion-notes"> <div class="panel panel-default">
<ul class="notes"> <div class="discussion-notes">
<component <ul class="notes">
v-for="note in note.notes" <component
:is="componentName(note)" v-for="note in note.notes"
:note="componentData(note)" :is="componentName(note)"
:key="note.id" :note="componentData(note)"
/> :key="note.id"
</ul> />
<div </ul>
:class="{ 'is-replying': isReplying }" <div
class="discussion-reply-holder"> :class="{ 'is-replying': isReplying }"
<button class="discussion-reply-holder">
v-if="canReply && !isReplying" <button
@click="showReplyForm" v-if="canReply && !isReplying"
type="button" @click="showReplyForm"
class="js-vue-discussion-reply btn btn-text-field" type="button"
title="Add a reply">Reply...</button> class="js-vue-discussion-reply btn btn-text-field"
<note-form title="Add a reply">
v-if="isReplying" Reply...
save-button-title="Comment" </button>
:discussion="note" <note-form
:is-editing="false" v-if="isReplying"
@handleFormUpdate="saveReply" save-button-title="Comment"
@cancelFormEdition="cancelReplyForm" :discussion="note"
ref="noteForm" :is-editing="false"
/> @handleFormUpdate="saveReply"
<note-signed-out-widget v-if="!canReply" /> @cancelFormEdition="cancelReplyForm"
</div> ref="noteForm"
/>
<note-signed-out-widget v-if="!canReply" />
</div> </div>
</div> </div>
</div> </div>
......
...@@ -9,6 +9,12 @@ ...@@ -9,6 +9,12 @@
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: {
userAvatarLink,
noteHeader,
noteActions,
noteBody,
},
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -22,12 +28,6 @@ ...@@ -22,12 +28,6 @@
isRequesting: false, isRequesting: false,
}; };
}, },
components: {
userAvatarLink,
noteHeader,
noteActions,
noteBody,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'targetNoteHash', 'targetNoteHash',
...@@ -51,6 +51,16 @@ ...@@ -51,6 +51,16 @@
return `note_${this.note.id}`; return `note_${this.note.id}`;
}, },
}, },
created() {
eventHub.$on('enterEditMode', ({ noteId }) => {
if (noteId === this.note.id) {
this.isEditing = true;
this.scrollToNoteIfNeeded($(this.$el));
}
});
},
methods: { methods: {
...mapActions([ ...mapActions([
'deleteNote', 'deleteNote',
...@@ -126,14 +136,6 @@ ...@@ -126,14 +136,6 @@
this.$refs.noteBody.$refs.noteForm.note = noteText; this.$refs.noteBody.$refs.noteForm.note = noteText;
}, },
}, },
created() {
eventHub.$on('enterEditMode', ({ noteId }) => {
if (noteId === this.note.id) {
this.isEditing = true;
this.scrollToNoteIfNeeded($(this.$el));
}
});
},
}; };
</script> </script>
...@@ -150,7 +152,7 @@ ...@@ -150,7 +152,7 @@
:img-src="author.avatar_url" :img-src="author.avatar_url"
:img-alt="author.name" :img-alt="author.name"
:img-size="40" :img-size="40"
/> />
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div class="note-header"> <div class="note-header">
...@@ -159,7 +161,7 @@ ...@@ -159,7 +161,7 @@
:created-at="note.created_at" :created-at="note.created_at"
:note-id="note.id" :note-id="note.id"
action-text="commented" action-text="commented"
/> />
<note-actions <note-actions
:author-id="author.id" :author-id="author.id"
:note-id="note.id" :note-id="note.id"
...@@ -170,7 +172,7 @@ ...@@ -170,7 +172,7 @@
:report-abuse-path="note.report_abuse_path" :report-abuse-path="note.report_abuse_path"
@handleEdit="editHandler" @handleEdit="editHandler"
@handleDelete="deleteHandler" @handleDelete="deleteHandler"
/> />
</div> </div>
<note-body <note-body
:note="note" :note="note"
...@@ -179,7 +181,7 @@ ...@@ -179,7 +181,7 @@
@handleFormUpdate="formUpdateHandler" @handleFormUpdate="formUpdateHandler"
@cancelFormEdition="formCancelHandler" @cancelFormEdition="formCancelHandler"
ref="noteBody" ref="noteBody"
/> />
</div> </div>
</div> </div>
</li> </li>
......
...@@ -13,7 +13,16 @@ ...@@ -13,7 +13,16 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
name: 'notesApp', name: 'NotesApp',
components: {
noteableNote,
noteableDiscussion,
systemNote,
commentForm,
loadingIcon,
placeholderNote,
placeholderSystemNote,
},
props: { props: {
noteableData: { noteableData: {
type: Object, type: Object,
...@@ -26,7 +35,7 @@ ...@@ -26,7 +35,7 @@
userData: { userData: {
type: Object, type: Object,
required: false, required: false,
default: {}, default: () => ({}),
}, },
}, },
store, store,
...@@ -35,21 +44,30 @@ ...@@ -35,21 +44,30 @@
isLoading: true, isLoading: true,
}; };
}, },
components: {
noteableNote,
noteableDiscussion,
systemNote,
commentForm,
loadingIcon,
placeholderNote,
placeholderSystemNote,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'notes', 'notes',
'getNotesDataByProp', 'getNotesDataByProp',
]), ]),
}, },
created() {
this.setNotesData(this.notesData);
this.setNoteableData(this.noteableData);
this.setUserData(this.userData);
},
mounted() {
this.fetchNotes();
const parentElement = this.$el.parentElement;
if (parentElement &&
parentElement.classList.contains('js-vue-notes-event')) {
parentElement.addEventListener('toggleAward', (event) => {
const { awardName, noteId } = event.detail;
this.actionToggleAward({ awardName, noteId });
});
}
},
methods: { methods: {
...mapActions({ ...mapActions({
actionFetchNotes: 'fetchNotes', actionFetchNotes: 'fetchNotes',
...@@ -105,24 +123,6 @@ ...@@ -105,24 +123,6 @@
} }
}, },
}, },
created() {
this.setNotesData(this.notesData);
this.setNoteableData(this.noteableData);
this.setUserData(this.userData);
},
mounted() {
this.fetchNotes();
const parentElement = this.$el.parentElement;
if (parentElement &&
parentElement.classList.contains('js-vue-notes-event')) {
parentElement.addEventListener('toggleAward', (event) => {
const { awardName, noteId } = event.detail;
this.actionToggleAward({ awardName, noteId });
});
}
},
}; };
</script> </script>
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
:is="getComponentName(note)" :is="getComponentName(note)"
:note="getComponentData(note)" :note="getComponentData(note)"
:key="note.id" :key="note.id"
/> />
</ul> </ul>
<comment-form /> <comment-form />
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import page from './page/index.vue'; import page from './page/index.vue';
export default { export default {
components: { page },
props: { props: {
pdf: { pdf: {
type: [String, Uint8Array], type: [String, Uint8Array],
...@@ -17,8 +18,6 @@ ...@@ -17,8 +18,6 @@
pages: [], pages: [],
}; };
}, },
components: { page },
watch: { pdf: 'load' },
computed: { computed: {
document() { document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf }; return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
...@@ -27,6 +26,11 @@ ...@@ -27,6 +26,11 @@
return this.pdf && this.pdf.length > 0; return this.pdf && this.pdf.length > 0;
}, },
}, },
watch: { pdf: 'load' },
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
methods: { methods: {
load() { load() {
this.pages = []; this.pages = [];
...@@ -47,20 +51,20 @@ ...@@ -47,20 +51,20 @@
return Promise.all(pagePromises); return Promise.all(pagePromises);
}, },
}, },
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
}; };
</script> </script>
<template> <template>
<div class="pdf-viewer" v-if="hasPDF"> <div
<page v-for="(page, index) in pages" class="pdf-viewer"
v-if="hasPDF">
<page
v-for="(page, index) in pages"
:key="index" :key="index"
:v-if="!loading" :v-if="!loading"
:page="page" :page="page"
:number="index + 1" /> :number="index + 1"
/>
</div> </div>
</template> </template>
......
...@@ -32,6 +32,20 @@ ...@@ -32,6 +32,20 @@
return !!(this.customInputEnabled || !this.intervalIsPreset); return !!(this.customInputEnabled || !this.intervalIsPreset);
}, },
}, },
watch: {
cronInterval() {
// updates field validation state when model changes, as
// glFieldError only updates on input.
this.$nextTick(() => {
gl.pipelineScheduleFieldErrors.updateFormValidityState();
});
},
},
created() {
if (this.intervalIsPreset) {
this.enableCustomInput = false;
}
},
methods: { methods: {
toggleCustomInput(shouldEnable) { toggleCustomInput(shouldEnable) {
this.customInputEnabled = shouldEnable; this.customInputEnabled = shouldEnable;
...@@ -43,20 +57,6 @@ ...@@ -43,20 +57,6 @@
} }
}, },
}, },
created() {
if (this.intervalIsPreset) {
this.enableCustomInput = false;
}
},
watch: {
cronInterval() {
// updates field validation state when model changes, as
// glFieldError only updates on input.
this.$nextTick(() => {
gl.pipelineScheduleFieldErrors.updateFormValidityState();
});
},
},
}; };
</script> </script>
...@@ -78,7 +78,12 @@ ...@@ -78,7 +78,12 @@
</label> </label>
<span class="cron-syntax-link-wrap"> <span class="cron-syntax-link-wrap">
(<a :href="cronSyntaxUrl" target="_blank">{{ __('Cron syntax') }}</a>) (<a
:href="cronSyntaxUrl"
target="_blank"
>
{{ __('Cron syntax') }}
</a>)
</span> </span>
</div> </div>
...@@ -93,7 +98,10 @@ ...@@ -93,7 +98,10 @@
@click="toggleCustomInput(false)" @click="toggleCustomInput(false)"
/> />
<label class="label-light" for="every-day"> <label
class="label-light"
for="every-day"
>
{{ __('Every day (at 4:00am)') }} {{ __('Every day (at 4:00am)') }}
</label> </label>
</div> </div>
...@@ -109,7 +117,10 @@ ...@@ -109,7 +117,10 @@
@click="toggleCustomInput(false)" @click="toggleCustomInput(false)"
/> />
<label class="label-light" for="every-week"> <label
class="label-light"
for="every-week"
>
{{ __('Every week (Sundays at 4:00am)') }} {{ __('Every week (Sundays at 4:00am)') }}
</label> </label>
</div> </div>
...@@ -125,7 +136,10 @@ ...@@ -125,7 +136,10 @@
@click="toggleCustomInput(false)" @click="toggleCustomInput(false)"
/> />
<label class="label-light" for="every-month"> <label
class="label-light"
for="every-month"
>
{{ __('Every month (on the 1st at 4:00am)') }} {{ __('Every month (on the 1st at 4:00am)') }}
</label> </label>
</div> </div>
......
...@@ -16,15 +16,15 @@ ...@@ -16,15 +16,15 @@
calloutDismissed: Cookies.get(cookieKey) === 'true', calloutDismissed: Cookies.get(cookieKey) === 'true',
}; };
}, },
created() {
this.illustrationSvg = illustrationSvg;
},
methods: { methods: {
dismissCallout() { dismissCallout() {
this.calloutDismissed = true; this.calloutDismissed = true;
Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 }); Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
}, },
}, },
created() {
this.illustrationSvg = illustrationSvg;
},
}; };
</script> </script>
<template> <template>
...@@ -41,17 +41,23 @@ ...@@ -41,17 +41,23 @@
class="fa fa-times"> class="fa fa-times">
</i> </i>
</button> </button>
<div class="svg-container" v-html="illustrationSvg"></div> <div
class="svg-container"
v-html="illustrationSvg">
</div>
<div class="user-callout-copy"> <div class="user-callout-copy">
<h4>{{ __('Scheduling Pipelines') }}</h4> <h4>{{ __('Scheduling Pipelines') }}</h4>
<p> <p>
{{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }} {{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }}
</p> </p>
<p> {{ __('Learn more in the') }} <p> {{ __('Learn more in the') }}
<a <a
:href="docsUrl" :href="docsUrl"
target="_blank" target="_blank"
rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period --> rel="nofollow"
>
{{ s__('Learn more in the|pipeline schedules documentation') }}</a>.
<!-- oneline to prevent extra space before period -->
</p> </p>
</div> </div>
</div> </div>
......
<script> <script>
/* eslint-disable no-new, no-alert */ /* eslint-disable no-alert */
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { directives: {
endpoint: { tooltip,
type: String,
required: true,
}, },
title: { components: {
type: String, loadingIcon,
required: true,
}, },
icon: { props: {
type: String, endpoint: {
required: true, type: String,
required: true,
},
title: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
cssClass: {
type: String,
required: true,
},
confirmActionMessage: {
type: String,
required: false,
default: '',
},
}, },
cssClass: { data() {
type: String, return {
required: true, isLoading: false,
};
}, },
confirmActionMessage: { computed: {
type: String, iconClass() {
required: false, return `fa fa-${this.icon}`;
},
buttonClass() {
return `btn ${this.cssClass}`;
},
}, },
}, methods: {
directives: { onClick() {
tooltip, if (this.confirmActionMessage !== '' && confirm(this.confirmActionMessage)) {
}, this.makeRequest();
components: { } else if (this.confirmActionMessage === '') {
loadingIcon, this.makeRequest();
}, }
data() { },
return { makeRequest() {
isLoading: false, this.isLoading = true;
};
},
computed: {
iconClass() {
return `fa fa-${this.icon}`;
},
buttonClass() {
return `btn ${this.cssClass}`;
},
},
methods: {
onClick() {
if (this.confirmActionMessage && confirm(this.confirmActionMessage)) {
this.makeRequest();
} else if (!this.confirmActionMessage) {
this.makeRequest();
}
},
makeRequest() {
this.isLoading = true;
eventHub.$emit('postAction', this.endpoint); eventHub.$emit('postAction', this.endpoint);
},
}, },
}, };
};
</script> </script>
<template> <template>
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<a <a
:href="helpPagePath" :href="helpPagePath"
class="btn btn-info" class="btn btn-info"
> >
{{ s__("Pipelines|Get started with Pipelines") }} {{ s__("Pipelines|Get started with Pipelines") }}
</a> </a>
</div> </div>
......
...@@ -7,6 +7,14 @@ ...@@ -7,6 +7,14 @@
* TODO: Remove UJS from here and use an async request instead. * TODO: Remove UJS from here and use an async request instead.
*/ */
export default { export default {
components: {
icon,
},
directives: {
tooltip,
},
props: { props: {
tooltipText: { tooltipText: {
type: String, type: String,
...@@ -29,14 +37,6 @@ ...@@ -29,14 +37,6 @@
}, },
}, },
components: {
icon,
},
directives: {
tooltip,
},
computed: { computed: {
cssClass() { cssClass() {
const actionIconDash = dasherize(this.actionIcon); const actionIconDash = dasherize(this.actionIcon);
...@@ -53,7 +53,8 @@ ...@@ -53,7 +53,8 @@
:href="link" :href="link"
class="ci-action-icon-container ci-action-icon-wrapper" class="ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass" :class="cssClass"
data-container="body"> data-container="body"
<icon :name="actionIcon"/> >
<icon :name="actionIcon" />
</a> </a>
</template> </template>
...@@ -7,6 +7,13 @@ ...@@ -7,6 +7,13 @@
* TODO: Remove UJS from here and use an async request instead. * TODO: Remove UJS from here and use an async request instead.
*/ */
export default { export default {
components: {
icon,
},
directives: {
tooltip,
},
props: { props: {
tooltipText: { tooltipText: {
type: String, type: String,
...@@ -28,14 +35,6 @@ ...@@ -28,14 +35,6 @@
required: true, required: true,
}, },
}, },
components: {
icon,
},
directives: {
tooltip,
},
}; };
</script> </script>
<template> <template>
...@@ -47,7 +46,8 @@ ...@@ -47,7 +46,8 @@
rel="nofollow" rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon" class="ci-action-icon-wrapper js-ci-status-icon"
data-container="body" data-container="body"
aria-label="Job's action"> aria-label="Job's action"
<icon :name="actionIcon"/> >
<icon :name="actionIcon" />
</a> </a>
</template> </template>
...@@ -27,13 +27,6 @@ ...@@ -27,13 +27,6 @@
* } * }
*/ */
export default { export default {
props: {
job: {
type: Object,
required: true,
},
},
directives: { directives: {
tooltip, tooltip,
}, },
...@@ -43,12 +36,23 @@ ...@@ -43,12 +36,23 @@
jobNameComponent, jobNameComponent,
}, },
props: {
job: {
type: Object,
required: true,
},
},
computed: { computed: {
tooltipText() { tooltipText() {
return `${this.job.name} - ${this.job.status.label}`; return `${this.job.name} - ${this.job.status.label}`;
}, },
}, },
mounted() {
this.stopDropdownClickPropagation();
},
methods: { methods: {
/** /**
* When the user right clicks or cmd/ctrl + click in the job name * When the user right clicks or cmd/ctrl + click in the job name
...@@ -66,10 +70,6 @@ ...@@ -66,10 +70,6 @@
}); });
}, },
}, },
mounted() {
this.stopDropdownClickPropagation();
},
}; };
</script> </script>
<template> <template>
...@@ -84,22 +84,25 @@ ...@@ -84,22 +84,25 @@
<job-name-component <job-name-component
:name="job.name" :name="job.name"
:status="job.status" /> :status="job.status"
/>
<span class="dropdown-counter-badge"> <span class="dropdown-counter-badge">
{{job.size}} {{ job.size }}
</span> </span>
</button> </button>
<ul class="dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown"> <ul class="dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown">
<li class="scrollable-menu"> <li class="scrollable-menu">
<ul> <ul>
<li v-for="item in job.jobs"> <li
v-for="(item, i) in job.jobs"
:key="i">
<job-component <job-component
:job="item" :job="item"
:is-dropdown="true" :is-dropdown="true"
css-class-job-name="mini-pipeline-graph-dropdown-item" css-class-job-name="mini-pipeline-graph-dropdown-item"
/> />
</li> </li>
</ul> </ul>
</li> </li>
......
<script> <script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import '~/flash';
import stageColumnComponent from './stage_column_component.vue'; import stageColumnComponent from './stage_column_component.vue';
export default { export default {
components: {
stageColumnComponent,
loadingIcon,
},
props: { props: {
isLoading: { isLoading: {
type: Boolean, type: Boolean,
...@@ -15,11 +19,6 @@ ...@@ -15,11 +19,6 @@
}, },
}, },
components: {
stageColumnComponent,
loadingIcon,
},
computed: { computed: {
graph() { graph() {
return this.pipeline.details && this.pipeline.details.stages; return this.pipeline.details && this.pipeline.details.stages;
...@@ -58,7 +57,7 @@ ...@@ -58,7 +57,7 @@
<loading-icon <loading-icon
v-if="isLoading" v-if="isLoading"
size="3" size="3"
/> />
</div> </div>
<ul <ul
...@@ -70,7 +69,8 @@ ...@@ -70,7 +69,8 @@
:jobs="stage.groups" :jobs="stage.groups"
:key="stage.name" :key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)" :stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"/> :is-first-column="isFirstColumn(index)"
/>
</ul> </ul>
</div> </div>
</div> </div>
......
...@@ -29,6 +29,15 @@ ...@@ -29,6 +29,15 @@
*/ */
export default { export default {
components: {
actionComponent,
dropdownActionComponent,
jobNameComponent,
},
directives: {
tooltip,
},
props: { props: {
job: { job: {
type: Object, type: Object,
...@@ -48,16 +57,6 @@ ...@@ -48,16 +57,6 @@
}, },
}, },
components: {
actionComponent,
dropdownActionComponent,
jobNameComponent,
},
directives: {
tooltip,
},
computed: { computed: {
status() { status() {
return this.job && this.job.status ? this.job.status : {}; return this.job && this.job.status ? this.job.status : {};
...@@ -102,12 +101,12 @@ ...@@ -102,12 +101,12 @@
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
class="js-pipeline-graph-job-link" class="js-pipeline-graph-job-link"
> >
<job-name-component <job-name-component
:name="job.name" :name="job.name"
:status="job.status" :status="job.status"
/> />
</a> </a>
<div <div
...@@ -117,12 +116,12 @@ ...@@ -117,12 +116,12 @@
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
> >
<job-name-component <job-name-component
:name="job.name" :name="job.name"
:status="job.status" :status="job.status"
/> />
</div> </div>
<action-component <action-component
...@@ -131,7 +130,7 @@ ...@@ -131,7 +130,7 @@
:link="status.action.path" :link="status.action.path"
:action-icon="status.action.icon" :action-icon="status.action.icon"
:action-method="status.action.method" :action-method="status.action.method"
/> />
<dropdown-action-component <dropdown-action-component
v-if="hasAction && isDropdown" v-if="hasAction && isDropdown"
...@@ -139,6 +138,6 @@ ...@@ -139,6 +138,6 @@
:link="status.action.path" :link="status.action.path"
:action-icon="status.action.icon" :action-icon="status.action.icon"
:action-method="status.action.method" :action-method="status.action.method"
/> />
</div> </div>
</template> </template>
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
* - Dropdown badge components * - Dropdown badge components
*/ */
export default { export default {
components: {
ciIcon,
},
props: { props: {
name: { name: {
type: String, type: String,
...@@ -19,19 +22,14 @@ ...@@ -19,19 +22,14 @@
required: true, required: true,
}, },
}, },
components: {
ciIcon,
},
}; };
</script> </script>
<template> <template>
<span class="ci-job-name-component"> <span class="ci-job-name-component">
<ci-icon <ci-icon :status="status" />
:status="status" />
<span class="ci-status-text"> <span class="ci-status-text">
{{name}} {{ name }}
</span> </span>
</span> </span>
</template> </template>
<script> <script>
import jobComponent from './job_component.vue'; import jobComponent from './job_component.vue';
import dropdownJobComponent from './dropdown_job_component.vue'; import dropdownJobComponent from './dropdown_job_component.vue';
export default { export default {
props: { components: {
title: { jobComponent,
type: String, dropdownJobComponent,
required: true,
}, },
jobs: { props: {
type: Array, title: {
required: true, type: String,
}, required: true,
},
isFirstColumn: {
type: Boolean,
required: false,
default: false,
},
stageConnectorClass: { jobs: {
type: String, type: Array,
required: false, required: true,
default: '', },
},
},
components: { isFirstColumn: {
jobComponent, type: Boolean,
dropdownJobComponent, required: false,
}, default: false,
},
methods: { stageConnectorClass: {
firstJob(list) { type: String,
return list[0]; required: false,
default: '',
},
}, },
jobId(job) { methods: {
return `ci-badge-${job.name}`; firstJob(list) {
}, return list[0];
},
jobId(job) {
return `ci-badge-${job.name}`;
},
buildConnnectorClass(index) { buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
}, },
}, };
};
</script> </script>
<template> <template>
<li <li
class="stage-column" class="stage-column"
:class="stageConnectorClass"> :class="stageConnectorClass">
<div class="stage-name"> <div class="stage-name">
{{title}} {{ title }}
</div> </div>
<div class="builds-container"> <div class="builds-container">
<ul> <ul>
...@@ -61,7 +61,8 @@ export default { ...@@ -61,7 +61,8 @@ export default {
:key="job.id" :key="job.id"
class="build" class="build"
:class="buildConnnectorClass(index)" :class="buildConnnectorClass(index)"
:id="jobId(job)"> :id="jobId(job)"
>
<div class="curve"></div> <div class="curve"></div>
...@@ -69,12 +70,12 @@ export default { ...@@ -69,12 +70,12 @@ export default {
v-if="job.size === 1" v-if="job.size === 1"
:job="job" :job="job"
css-class-job-name="build-content" css-class-job-name="build-content"
/> />
<dropdown-job-component <dropdown-job-component
v-if="job.size > 1" v-if="job.size > 1"
:job="job" :job="job"
/> />
</li> </li>
</ul> </ul>
......
<script> <script>
import ciHeader from '../../vue_shared/components/header_ci_component.vue'; import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
name: 'PipelineHeaderSection', name: 'PipelineHeaderSection',
props: { components: {
pipeline: { ciHeader,
type: Object, loadingIcon,
required: true,
}, },
isLoading: { props: {
type: Boolean, pipeline: {
required: true, type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
}, },
}, data() {
components: { return {
ciHeader, actions: this.getActions(),
loadingIcon, };
},
data() {
return {
actions: this.getActions(),
};
},
computed: {
status() {
return this.pipeline.details && this.pipeline.details.status;
}, },
shouldRenderContent() {
return !this.isLoading && Object.keys(this.pipeline).length; computed: {
status() {
return this.pipeline.details && this.pipeline.details.status;
},
shouldRenderContent() {
return !this.isLoading && Object.keys(this.pipeline).length;
},
}, },
},
methods: { watch: {
postAction(action) { pipeline() {
const index = this.actions.indexOf(action); this.actions = this.getActions();
},
},
this.$set(this.actions[index], 'isLoading', true); methods: {
postAction(action) {
const index = this.actions.indexOf(action);
eventHub.$emit('headerPostAction', action); this.$set(this.actions[index], 'isLoading', true);
},
getActions() { eventHub.$emit('headerPostAction', action);
const actions = []; },
if (this.pipeline.retry_path) { getActions() {
actions.push({ const actions = [];
label: 'Retry',
path: this.pipeline.retry_path,
cssClass: 'js-retry-button btn btn-inverted-secondary',
type: 'button',
isLoading: false,
});
}
if (this.pipeline.cancel_path) { if (this.pipeline.retry_path) {
actions.push({ actions.push({
label: 'Cancel running', label: 'Retry',
path: this.pipeline.cancel_path, path: this.pipeline.retry_path,
cssClass: 'js-btn-cancel-pipeline btn btn-danger', cssClass: 'js-retry-button btn btn-inverted-secondary',
type: 'button', type: 'button',
isLoading: false, isLoading: false,
}); });
} }
return actions; if (this.pipeline.cancel_path) {
}, actions.push({
}, label: 'Cancel running',
path: this.pipeline.cancel_path,
cssClass: 'js-btn-cancel-pipeline btn btn-danger',
type: 'button',
isLoading: false,
});
}
watch: { return actions;
pipeline() { },
this.actions = this.getActions();
}, },
}, };
};
</script> </script>
<template> <template>
<div class="pipeline-header-container"> <div class="pipeline-header-container">
...@@ -89,9 +88,10 @@ export default { ...@@ -89,9 +88,10 @@ export default {
:user="pipeline.user" :user="pipeline.user"
:actions="actions" :actions="actions"
@actionClicked="postAction" @actionClicked="postAction"
/> />
<loading-icon <loading-icon
v-if="isLoading" v-if="isLoading"
size="2"/> size="2"
/>
</div> </div>
</template> </template>
...@@ -4,6 +4,13 @@ ...@@ -4,6 +4,13 @@
import popover from '../../vue_shared/directives/popover'; import popover from '../../vue_shared/directives/popover';
export default { export default {
components: {
userAvatarLink,
},
directives: {
tooltip,
popover,
},
props: { props: {
pipeline: { pipeline: {
type: Object, type: Object,
...@@ -14,13 +21,6 @@ ...@@ -14,13 +21,6 @@
required: true, required: true,
}, },
}, },
components: {
userAvatarLink,
},
directives: {
tooltip,
popover,
},
computed: { computed: {
user() { user() {
return this.pipeline.user; return this.pipeline.user;
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<a <a
:href="pipeline.path" :href="pipeline.path"
class="js-pipeline-url-link"> class="js-pipeline-url-link">
<span class="pipeline-id">#{{pipeline.id}}</span> <span class="pipeline-id">#{{ pipeline.id }}</span>
</a> </a>
<span>by</span> <span>by</span>
<user-avatar-link <user-avatar-link
......
...@@ -13,6 +13,15 @@ ...@@ -13,6 +13,15 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default { export default {
components: {
tablePagination,
navigationTabs,
navigationControls,
},
mixins: [
pipelinesMixin,
CIPaginationMixin,
],
props: { props: {
store: { store: {
type: Object, type: Object,
...@@ -28,15 +37,6 @@ ...@@ -28,15 +37,6 @@
default: 'root', default: 'root',
}, },
}, },
components: {
tablePagination,
navigationTabs,
navigationControls,
},
mixins: [
pipelinesMixin,
CIPaginationMixin,
],
data() { data() {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
...@@ -214,7 +214,7 @@ ...@@ -214,7 +214,7 @@
:tabs="tabs" :tabs="tabs"
@onChangeTab="onChangeTab" @onChangeTab="onChangeTab"
scope="pipelines" scope="pipelines"
/> />
<navigation-controls <navigation-controls
:new-pipeline-path="newPipelinePath" :new-pipeline-path="newPipelinePath"
...@@ -222,7 +222,7 @@ ...@@ -222,7 +222,7 @@
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:ci-lint-path="ciLintPath" :ci-lint-path="ciLintPath"
:can-create-pipeline="canCreatePipelineParsed " :can-create-pipeline="canCreatePipelineParsed "
/> />
</div> </div>
<div class="content-list pipelines"> <div class="content-list pipelines">
...@@ -232,22 +232,23 @@ ...@@ -232,22 +232,23 @@
size="3" size="3"
v-if="isLoading" v-if="isLoading"
class="prepend-top-20" class="prepend-top-20"
/> />
<empty-state <empty-state
v-if="shouldRenderEmptyState" v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath" :empty-state-svg-path="emptyStateSvgPath"
/> />
<error-state <error-state
v-if="shouldRenderErrorState" v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath" :error-state-svg-path="errorStateSvgPath"
/> />
<div <div
class="blank-state-row" class="blank-state-row"
v-if="shouldRenderNoPipelinesMessage"> v-if="shouldRenderNoPipelinesMessage"
>
<div class="blank-state-center"> <div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2> <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div> </div>
...@@ -255,21 +256,22 @@ ...@@ -255,21 +256,22 @@
<div <div
class="table-holder" class="table-holder"
v-if="shouldRenderTable"> v-if="shouldRenderTable"
>
<pipelines-table-component <pipelines-table-component
:pipelines="state.pipelines" :pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsPath" :auto-devops-help-path="autoDevopsPath"
:view-type="viewType" :view-type="viewType"
/> />
</div> </div>
<table-pagination <table-pagination
v-if="shouldRenderPagination" v-if="shouldRenderPagination"
:change="onChangePage" :change="onChangePage"
:page-info="state.pageInfo" :page-info="state.pageInfo"
/> />
</div> </div>
</div> </div>
</template> </template>
...@@ -5,18 +5,18 @@ ...@@ -5,18 +5,18 @@
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: {
actions: {
type: Array,
required: true,
},
},
directives: { directives: {
tooltip, tooltip,
}, },
components: { components: {
loadingIcon, loadingIcon,
}, },
props: {
actions: {
type: Array,
required: true,
},
},
data() { data() {
return { return {
playIconSvg, playIconSvg,
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
data-toggle="dropdown" data-toggle="dropdown"
data-placement="top" data-placement="top"
aria-label="Manual job" aria-label="Manual job"
:disabled="isLoading"> :disabled="isLoading"
>
<span v-html="playIconSvg"></span> <span v-html="playIconSvg"></span>
<i <i
class="fa fa-caret-down" class="fa fa-caret-down"
...@@ -60,14 +61,18 @@ ...@@ -60,14 +61,18 @@
</button> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li
v-for="(action, i) in actions"
:key="i"
>
<button <button
type="button" type="button"
class="js-pipeline-action-link no-btn btn" class="js-pipeline-action-link no-btn btn"
@click="onClickAction(action.path)" @click="onClickAction(action.path)"
:class="{ disabled: isActionDisabled(action) }" :class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"> :disabled="isActionDisabled(action)"
{{action.name}} >
{{ action.name }}
</button> </button>
</li> </li>
</ul> </ul>
......
...@@ -3,46 +3,50 @@ ...@@ -3,46 +3,50 @@
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
export default { export default {
props: {
artifacts: {
type: Array,
required: true,
},
},
directives: { directives: {
tooltip, tooltip,
}, },
components: { components: {
icon, icon,
}, },
props: {
artifacts: {
type: Array,
required: true,
},
},
}; };
</script> </script>
<template> <template>
<div <div
class="btn-group" class="btn-group"
role="group"> role="group"
>
<button <button
v-tooltip v-tooltip
class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download"
title="Artifacts" title="Artifacts"
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
aria-label="Artifacts"> aria-label="Artifacts"
<icon >
name="download"> <icon name="download" />
</icon>
<i <i
class="fa fa-caret-down" class="fa fa-caret-down"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="artifact in artifacts"> <li
v-for="(artifact, i) in artifacts"
:key="i">
<a <a
rel="nofollow" rel="nofollow"
download download
:href="artifact.path"> :href="artifact.path"
Download {{artifact.name}} artifacts >
Download {{ artifact.name }} artifacts
</a> </a>
</li> </li>
</ul> </ul>
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
* Given an array of objects, renders a table. * Given an array of objects, renders a table.
*/ */
export default { export default {
components: {
pipelinesTableRowComponent,
},
props: { props: {
pipelines: { pipelines: {
type: Array, type: Array,
...@@ -26,34 +29,36 @@ ...@@ -26,34 +29,36 @@
required: true, required: true,
}, },
}, },
components: {
pipelinesTableRowComponent,
},
}; };
</script> </script>
<template> <template>
<div class="ci-table"> <div class="ci-table">
<div <div
class="gl-responsive-table-row table-row-header" class="gl-responsive-table-row table-row-header"
role="row"> role="row"
>
<div <div
class="table-section section-10 js-pipeline-status pipeline-status" class="table-section section-10 js-pipeline-status pipeline-status"
role="rowheader"> role="rowheader"
>
Status Status
</div> </div>
<div <div
class="table-section section-15 js-pipeline-info pipeline-info" class="table-section section-15 js-pipeline-info pipeline-info"
role="rowheader"> role="rowheader"
>
Pipeline Pipeline
</div> </div>
<div <div
class="table-section section-25 js-pipeline-commit pipeline-commit" class="table-section section-25 js-pipeline-commit pipeline-commit"
role="rowheader"> role="rowheader"
>
Commit Commit
</div> </div>
<div <div
class="table-section section-15 js-pipeline-stages pipeline-stages" class="table-section section-15 js-pipeline-stages pipeline-stages"
role="rowheader"> role="rowheader"
>
Stages Stages
</div> </div>
</div> </div>
......
<script> <script>
/** /**
* Renders each stage of the pipeline mini graph. * Renders each stage of the pipeline mini graph.
* *
* Given the provided endpoint will make a request to * Given the provided endpoint will make a request to
* fetch the dropdown data when the stage is clicked. * fetch the dropdown data when the stage is clicked.
* *
* Request is made inside this component to make it reusable between: * Request is made inside this component to make it reusable between:
* 1. Pipelines main table * 1. Pipelines main table
* 2. Pipelines table in commit and Merge request views * 2. Pipelines table in commit and Merge request views
* 3. Merge request widget * 3. Merge request widget
* 4. Commit widget * 4. Commit widget
*/ */
import Flash from '../../flash'; import Flash from '../../flash';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { components: {
stage: { loadingIcon,
type: Object, icon,
required: true,
}, },
updateDropdown: { directives: {
type: Boolean, tooltip,
required: false,
default: false,
}, },
},
directives: {
tooltip,
},
data() {
return {
isLoading: false,
dropdownContent: '',
};
},
components: {
loadingIcon,
icon,
},
updated() {
if (this.dropdownContent.length > 0) {
this.stopDropdownClickPropagation();
}
},
watch: {
updateDropdown() {
if (this.updateDropdown &&
this.isDropdownOpen() &&
!this.isLoading) {
this.fetchJobs();
}
},
},
methods: { props: {
onClickStage() { stage: {
if (!this.isDropdownOpen()) { type: Object,
this.isLoading = true; required: true,
this.fetchJobs(); },
}
updateDropdown: {
type: Boolean,
required: false,
default: false,
},
}, },
fetchJobs() { data() {
this.$http.get(this.stage.dropdown_path) return {
.then(response => response.json()) isLoading: false,
.then((data) => { dropdownContent: '',
this.dropdownContent = data.html; };
this.isLoading = false;
})
.catch(() => {
this.closeDropdown();
this.isLoading = false;
const flash = new Flash('Something went wrong on our end.');
return flash;
});
}, },
/** computed: {
* When the user right clicks or cmd/ctrl + click in the job name dropdownClass() {
* the dropdown should not be closed and the link should open in another tab, return this.dropdownContent.length > 0 ?
* so we stop propagation of the click event inside the dropdown. 'js-builds-dropdown-container' :
* 'js-builds-dropdown-loading';
* Since this component is rendered multiple times per page we need to guarantee we only },
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
.on('click', (e) => {
e.stopPropagation();
});
},
closeDropdown() { triggerButtonClass() {
if (this.isDropdownOpen()) { return `ci-status-icon-${this.stage.status.group}`;
$(this.$refs.dropdown).dropdown('toggle'); },
}
},
isDropdownOpen() { borderlessIcon() {
return this.$el.classList.contains('open'); return `${this.stage.status.icon}_borderless`;
},
}, },
},
computed: { watch: {
dropdownClass() { updateDropdown() {
return this.dropdownContent.length > 0 ? if (this.updateDropdown &&
'js-builds-dropdown-container' : this.isDropdownOpen() &&
'js-builds-dropdown-loading'; !this.isLoading) {
this.fetchJobs();
}
},
}, },
triggerButtonClass() { updated() {
return `ci-status-icon-${this.stage.status.group}`; if (this.dropdownContent.length > 0) {
this.stopDropdownClickPropagation();
}
}, },
borderlessIcon() { methods: {
return `${this.stage.status.icon}_borderless`; onClickStage() {
if (!this.isDropdownOpen()) {
this.isLoading = true;
this.fetchJobs();
}
},
fetchJobs() {
this.$http.get(this.stage.dropdown_path)
.then(response => response.json())
.then((data) => {
this.dropdownContent = data.html;
this.isLoading = false;
})
.catch(() => {
this.closeDropdown();
this.isLoading = false;
const flash = new Flash('Something went wrong on our end.');
return flash;
});
},
/**
* When the user right clicks or cmd/ctrl + click in the job name
* the dropdown should not be closed and the link should open in another tab,
* so we stop propagation of the click event inside the dropdown.
*
* Since this component is rendered multiple times per page we need to guarantee we only
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
.on('click', (e) => {
e.stopPropagation();
});
},
closeDropdown() {
if (this.isDropdownOpen()) {
$(this.$refs.dropdown).dropdown('toggle');
}
},
isDropdownOpen() {
return this.$el.classList.contains('open');
},
}, },
}, };
};
</script> </script>
<template> <template>
...@@ -145,36 +145,41 @@ export default { ...@@ -145,36 +145,41 @@ export default {
type="button" type="button"
id="stageDropdown" id="stageDropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"> aria-expanded="false"
>
<span <span
aria-hidden="true" aria-hidden="true"
:aria-label="stage.title"> :aria-label="stage.title"
<icon >
:name="borderlessIcon"/> <icon :name="borderlessIcon" />
</span> </span>
<i <i
class="fa fa-caret-down" class="fa fa-caret-down"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</button> </button>
<ul <ul
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container" class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
aria-labelledby="stageDropdown"> aria-labelledby="stageDropdown"
>
<li <li
:class="dropdownClass" :class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"> class="js-builds-dropdown-list scrollable-menu"
>
<loading-icon v-if="isLoading"/> <loading-icon v-if="isLoading"/>
<ul <ul
v-else v-else
v-html="dropdownContent"> v-html="dropdownContent"
>
</ul> </ul>
</li> </li>
</ul> </ul>
</div> </div>
</script> </template>
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
import timeagoMixin from '../../vue_shared/mixins/timeago'; import timeagoMixin from '../../vue_shared/mixins/timeago';
export default { export default {
directives: {
tooltip,
},
mixins: [
timeagoMixin,
],
props: { props: {
finishedTime: { finishedTime: {
type: String, type: String,
...@@ -15,12 +21,6 @@ ...@@ -15,12 +21,6 @@
required: true, required: true,
}, },
}, },
mixins: [
timeagoMixin,
],
directives: {
tooltip,
},
data() { data() {
return { return {
iconTimerSvg, iconTimerSvg,
...@@ -60,26 +60,29 @@ ...@@ -60,26 +60,29 @@
<div class="table-section section-15 pipelines-time-ago"> <div class="table-section section-15 pipelines-time-ago">
<div <div
class="table-mobile-header" class="table-mobile-header"
role="rowheader"> role="rowheader"
>
Duration Duration
</div> </div>
<div class="table-mobile-content"> <div class="table-mobile-content">
<p <p
class="duration" class="duration"
v-if="hasDuration"> v-if="hasDuration"
<span >
v-html="iconTimerSvg"> <span v-html="iconTimerSvg">
</span> </span>
{{durationFormated}} {{ durationFormated }}
</p> </p>
<p <p
class="finished-at hidden-xs hidden-sm" class="finished-at hidden-xs hidden-sm"
v-if="hasFinishedTime"> v-if="hasFinishedTime"
>
<i <i
class="fa fa-calendar" class="fa fa-calendar"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
<time <time
...@@ -87,9 +90,9 @@ ...@@ -87,9 +90,9 @@
data-placement="top" data-placement="top"
data-container="body" data-container="body"
:title="tooltipTitle(finishedTime)"> :title="tooltipTitle(finishedTime)">
{{timeFormated(finishedTime)}} {{ timeFormated(finishedTime) }}
</time> </time>
</p> </p>
</div> </div>
</div> </div>
</script> </template>
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
import csrf from '../../../lib/utils/csrf'; import csrf from '../../../lib/utils/csrf';
export default { export default {
components: {
modal,
},
props: { props: {
actionUrl: { actionUrl: {
type: String, type: String,
...@@ -25,9 +28,6 @@ ...@@ -25,9 +28,6 @@
isOpen: false, isOpen: false,
}; };
}, },
components: {
modal,
},
computed: { computed: {
csrfToken() { csrfToken() {
return csrf.token; return csrf.token;
...@@ -99,7 +99,9 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -99,7 +99,9 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
@toggle="toggleOpen" @toggle="toggleOpen"
@submit="onSubmit"> @submit="onSubmit">
<template slot="body" slot-scope="props"> <template
slot="body"
slot-scope="props">
<p v-html="props.text"></p> <p v-html="props.text"></p>
<form <form
...@@ -110,13 +112,19 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -110,13 +112,19 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
<input <input
type="hidden" type="hidden"
name="_method" name="_method"
value="delete" /> value="delete"
/>
<input <input
type="hidden" type="hidden"
name="authenticity_token" name="authenticity_token"
:value="csrfToken" /> :value="csrfToken"
/>
<p id="input-label" v-html="inputLabel"></p> <p
id="input-label"
v-html="inputLabel"
>
</p>
<input <input
v-if="confirmWithPassword" v-if="confirmWithPassword"
...@@ -124,14 +132,16 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -124,14 +132,16 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
class="form-control" class="form-control"
type="password" type="password"
v-model="enteredPassword" v-model="enteredPassword"
aria-labelledby="input-label" /> aria-labelledby="input-label"
/>
<input <input
v-else v-else
name="username" name="username"
class="form-control" class="form-control"
type="text" type="text"
v-model="enteredUsername" v-model="enteredUsername"
aria-labelledby="input-label" /> aria-labelledby="input-label"
/>
</form> </form>
</template> </template>
...@@ -140,7 +150,8 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -140,7 +150,8 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
<button <button
type="button" type="button"
class="btn btn-danger" class="btn btn-danger"
@click="toggleOpen(true)"> @click="toggleOpen(true)"
>
{{ s__('Profiles|Delete account') }} {{ s__('Profiles|Delete account') }}
</button> </button>
</div> </div>
......
<script> <script>
import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue'; import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
export default { export default {
props: { components: {
name: { projectFeatureToggle,
type: String,
required: false,
default: '',
}, },
options: {
type: Array,
required: false,
default: () => [],
},
value: {
type: Number,
required: false,
default: 0,
},
disabledInput: {
type: Boolean,
required: false,
default: false,
},
},
components: {
projectFeatureToggle,
},
computed: { model: {
featureEnabled() { prop: 'value',
return this.value !== 0; event: 'change',
}, },
displayOptions() { props: {
if (this.featureEnabled) { name: {
return this.options; type: String,
} required: false,
return [ default: '',
[0, 'Enable feature to choose access level'], },
]; options: {
type: Array,
required: false,
default: () => [],
},
value: {
type: Number,
required: false,
default: 0,
},
disabledInput: {
type: Boolean,
required: false,
default: false,
},
}, },
displaySelectInput() { computed: {
return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2; featureEnabled() {
}, return this.value !== 0;
}, },
model: { displayOptions() {
prop: 'value', if (this.featureEnabled) {
event: 'change', return this.options;
}, }
return [
[0, 'Enable feature to choose access level'],
];
},
methods: { displaySelectInput() {
toggleFeature(featureEnabled) { return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2;
if (featureEnabled === false || this.options.length < 1) { },
this.$emit('change', 0);
} else {
const [firstOptionValue] = this.options[this.options.length - 1];
this.$emit('change', firstOptionValue);
}
}, },
selectOption(e) { methods: {
this.$emit('change', Number(e.target.value)); toggleFeature(featureEnabled) {
if (featureEnabled === false || this.options.length < 1) {
this.$emit('change', 0);
} else {
const [firstOptionValue] = this.options[this.options.length - 1];
this.$emit('change', firstOptionValue);
}
},
selectOption(e) {
this.$emit('change', Number(e.target.value));
},
}, },
}, };
};
</script> </script>
<template> <template>
<div class="project-feature-controls" :data-for="name"> <div
class="project-feature-controls"
:data-for="name"
>
<input <input
v-if="name" v-if="name"
type="hidden" type="hidden"
...@@ -81,7 +84,7 @@ export default { ...@@ -81,7 +84,7 @@ export default {
<project-feature-toggle <project-feature-toggle
:value="featureEnabled" :value="featureEnabled"
@change="toggleFeature" @change="toggleFeature"
:disabledInput="disabledInput" :disabled-input="disabledInput"
/> />
<div class="select-wrapper"> <div class="select-wrapper">
<select <select
...@@ -95,10 +98,14 @@ export default { ...@@ -95,10 +98,14 @@ export default {
:value="optionValue" :value="optionValue"
:selected="optionValue === value" :selected="optionValue === value"
> >
{{optionName}} {{ optionName }}
</option> </option>
</select> </select>
<i aria-hidden="true" class="fa fa-chevron-down"></i> <i
aria-hidden="true"
class="fa fa-chevron-down"
>
</i>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
label: { label: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
},
helpPath: {
type: String,
required: false,
default: null,
},
helpText: {
type: String,
required: false,
default: null,
},
}, },
helpPath: { };
type: String,
required: false,
default: null,
},
helpText: {
type: String,
required: false,
default: null,
},
},
};
</script> </script>
<template> <template>
<div class="project-feature-row"> <div class="project-feature-row">
<label v-if="label" class="label-light"> <label
{{label}} v-if="label"
<a v-if="helpPath" :href="helpPath" target="_blank"> class="label-light"
<i aria-hidden="true" data-hidden="true" class="fa fa-question-circle"></i> >
{{ label }}
<a
v-if="helpPath"
:href="helpPath"
target="_blank"
>
<i
aria-hidden="true"
data-hidden="true"
class="fa fa-question-circle"
>
</i>
</a> </a>
</label> </label>
<span v-if="helpText" class="help-block"> <span
{{helpText}} v-if="helpText"
class="help-block"
>
{{ helpText }}
</span> </span>
<slot /> <slot />
</div> </div>
......
...@@ -47,6 +47,22 @@ export default { ...@@ -47,6 +47,22 @@ export default {
return this.store.getSearchedProjects(); return this.store.getSearchedProjects();
}, },
}, },
created() {
if (this.currentProject.id) {
this.logCurrentProjectAccess();
}
eventHub.$on('dropdownOpen', this.fetchFrequentProjects);
eventHub.$on('searchProjects', this.fetchSearchedProjects);
eventHub.$on('searchCleared', this.handleSearchClear);
eventHub.$on('searchFailed', this.handleSearchFailure);
},
beforeDestroy() {
eventHub.$off('dropdownOpen', this.fetchFrequentProjects);
eventHub.$off('searchProjects', this.fetchSearchedProjects);
eventHub.$off('searchCleared', this.handleSearchClear);
eventHub.$off('searchFailed', this.handleSearchFailure);
},
methods: { methods: {
toggleFrequentProjectsList(state) { toggleFrequentProjectsList(state) {
this.isLoadingProjects = !state; this.isLoadingProjects = !state;
...@@ -108,22 +124,6 @@ export default { ...@@ -108,22 +124,6 @@ export default {
this.toggleSearchProjectsList(true); this.toggleSearchProjectsList(true);
}, },
}, },
created() {
if (this.currentProject.id) {
this.logCurrentProjectAccess();
}
eventHub.$on('dropdownOpen', this.fetchFrequentProjects);
eventHub.$on('searchProjects', this.fetchSearchedProjects);
eventHub.$on('searchCleared', this.handleSearchClear);
eventHub.$on('searchFailed', this.handleSearchFailure);
},
beforeDestroy() {
eventHub.$off('dropdownOpen', this.fetchFrequentProjects);
eventHub.$off('searchProjects', this.fetchSearchedProjects);
eventHub.$off('searchCleared', this.handleSearchClear);
eventHub.$off('searchFailed', this.handleSearchFailure);
},
}; };
</script> </script>
......
<script> <script>
import { s__ } from '../../locale'; import { s__ } from '../../locale';
import projectsListItem from './projects_list_item.vue'; import projectsListItem from './projects_list_item.vue';
export default { export default {
components: { components: {
projectsListItem, projectsListItem,
},
props: {
projects: {
type: Array,
required: true,
}, },
localStorageFailed: { props: {
type: Boolean, projects: {
required: true, type: Array,
required: true,
},
localStorageFailed: {
type: Boolean,
required: true,
},
}, },
}, computed: {
computed: { isListEmpty() {
isListEmpty() { return this.projects.length === 0;
return this.projects.length === 0; },
listEmptyMessage() {
return this.localStorageFailed ?
s__('ProjectsDropdown|This feature requires browser localStorage support') :
s__('ProjectsDropdown|Projects you visit often will appear here');
},
}, },
listEmptyMessage() { };
return this.localStorageFailed ?
s__('ProjectsDropdown|This feature requires browser localStorage support') :
s__('ProjectsDropdown|Projects you visit often will appear here');
},
},
};
</script> </script>
<template> <template>
...@@ -40,7 +40,7 @@ export default { ...@@ -40,7 +40,7 @@ export default {
class="section-empty" class="section-empty"
v-if="isListEmpty" v-if="isListEmpty"
> >
{{listEmptyMessage}} {{ listEmptyMessage }}
</li> </li>
<projects-list-item <projects-list-item
v-else v-else
......
<script> <script>
import identicon from '../../vue_shared/components/identicon.vue'; import identicon from '../../vue_shared/components/identicon.vue';
export default { export default {
components: { components: {
identicon, identicon,
},
props: {
matcher: {
type: String,
required: false,
}, },
projectId: { props: {
type: Number, matcher: {
required: true, type: String,
}, required: false,
projectName: { },
type: String, projectId: {
required: true, type: Number,
}, required: true,
namespace: { },
type: String, projectName: {
required: true, type: String,
}, required: true,
webUrl: { },
type: String, namespace: {
required: true, type: String,
}, required: true,
avatarUrl: { },
required: true, webUrl: {
validator(value) { type: String,
return value === null || typeof value === 'string'; required: true,
},
avatarUrl: {
required: true,
validator(value) {
return value === null || typeof value === 'string';
},
}, },
}, },
}, computed: {
computed: { hasAvatar() {
hasAvatar() { return this.avatarUrl !== null;
return this.avatarUrl !== null; },
}, highlightedProjectName() {
highlightedProjectName() { if (this.matcher) {
if (this.matcher) { const matcherRegEx = new RegExp(this.matcher, 'gi');
const matcherRegEx = new RegExp(this.matcher, 'gi'); const matches = this.projectName.match(matcherRegEx);
const matches = this.projectName.match(matcherRegEx);
if (matches && matches.length > 0) { if (matches && matches.length > 0) {
return this.projectName.replace(matches[0], `<b>${matches[0]}</b>`); return this.projectName.replace(matches[0], `<b>${matches[0]}</b>`);
}
} }
} return this.projectName;
return this.projectName; },
}, /**
/** * Smartly truncates project namespace by doing two things;
* Smartly truncates project namespace by doing two things; * 1. Only include Group names in path by removing project name
* 1. Only include Group names in path by removing project name * 2. Only include first and last group names in the path
* 2. Only include first and last group names in the path * when namespace has more than 2 groups present
* when namespace has more than 2 groups present *
* * First part (removal of project name from namespace) can be
* First part (removal of project name from namespace) can be * done from backend but doing so involves migration of
* done from backend but doing so involves migration of * existing project namespaces which is not wise thing to do.
* existing project namespaces which is not wise thing to do. */
*/ truncatedNamespace() {
truncatedNamespace() { const namespaceArr = this.namespace.split(' / ');
const namespaceArr = this.namespace.split(' / '); namespaceArr.splice(-1, 1);
namespaceArr.splice(-1, 1); let namespace = namespaceArr.join(' / ');
let namespace = namespaceArr.join(' / ');
if (namespaceArr.length > 2) { if (namespaceArr.length > 2) {
namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`; namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`;
} }
return namespace; return namespace;
},
}, },
}, };
};
</script> </script>
<template> <template>
...@@ -92,7 +92,7 @@ export default { ...@@ -92,7 +92,7 @@ export default {
<identicon <identicon
v-else v-else
size-class="s32" size-class="s32"
:entity-id=projectId :entity-id="projectId"
:entity-name="projectName" :entity-name="projectName"
/> />
</div> </div>
...@@ -108,7 +108,7 @@ export default { ...@@ -108,7 +108,7 @@ export default {
<div <div
class="project-namespace" class="project-namespace"
:title="namespace" :title="namespace"
>{{truncatedNamespace}}</div> >{{ truncatedNamespace }}</div>
</div> </div>
</a> </a>
</li> </li>
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
data() { data() {
return { return {
searchQuery: '', searchQuery: '',
}; };
},
watch: {
searchQuery() {
this.handleInput();
}, },
}, watch: {
methods: { searchQuery() {
setFocus() { this.handleInput();
this.$refs.search.focus(); },
}, },
emitSearchEvents() { mounted() {
if (this.searchQuery) { eventHub.$on('dropdownOpen', this.setFocus);
eventHub.$emit('searchProjects', this.searchQuery);
} else {
eventHub.$emit('searchCleared');
}
}, },
/** beforeDestroy() {
* Callback function within _.debounce is intentionally eventHub.$off('dropdownOpen', this.setFocus);
* kept as ES5 `function() {}` instead of ES6 `() => {}` },
* as it otherwise messes up function context methods: {
* and component reference is no longer accessible via `this` setFocus() {
*/ this.$refs.search.focus();
// eslint-disable-next-line func-names },
handleInput: _.debounce(function () { emitSearchEvents() {
this.emitSearchEvents(); if (this.searchQuery) {
}, 500), eventHub.$emit('searchProjects', this.searchQuery);
}, } else {
mounted() { eventHub.$emit('searchCleared');
eventHub.$on('dropdownOpen', this.setFocus); }
}, },
beforeDestroy() { /**
eventHub.$off('dropdownOpen', this.setFocus); * Callback function within _.debounce is intentionally
}, * kept as ES5 `function() {}` instead of ES6 `() => {}`
}; * as it otherwise messes up function context
* and component reference is no longer accessible via `this`
*/
// eslint-disable-next-line func-names
handleInput: _.debounce(function () {
this.emitSearchEvents();
}, 500),
},
};
</script> </script>
<template> <template>
......
<script> <script>
/* globals Flash */
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import '../../flash'; import Flash from '../../flash';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import store from '../stores'; import store from '../stores';
import collapsibleContainer from './collapsible_container.vue'; import collapsibleContainer from './collapsible_container.vue';
import { errorMessages, errorMessagesTypes } from '../constants'; import { errorMessages, errorMessagesTypes } from '../constants';
export default { export default {
name: 'registryListApp', name: 'RegistryListApp',
components: {
collapsibleContainer,
loadingIcon,
},
props: { props: {
endpoint: { endpoint: {
type: String, type: String,
...@@ -16,22 +19,12 @@ ...@@ -16,22 +19,12 @@
}, },
}, },
store, store,
components: {
collapsibleContainer,
loadingIcon,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'isLoading', 'isLoading',
'repos', 'repos',
]), ]),
}, },
methods: {
...mapActions([
'setMainEndpoint',
'fetchRepos',
]),
},
created() { created() {
this.setMainEndpoint(this.endpoint); this.setMainEndpoint(this.endpoint);
}, },
...@@ -39,6 +32,12 @@ ...@@ -39,6 +32,12 @@
this.fetchRepos() this.fetchRepos()
.catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS])); .catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS]));
}, },
methods: {
...mapActions([
'setMainEndpoint',
'fetchRepos',
]),
},
}; };
</script> </script>
<template> <template>
...@@ -46,17 +45,17 @@ ...@@ -46,17 +45,17 @@
<loading-icon <loading-icon
v-if="isLoading" v-if="isLoading"
size="3" size="3"
/> />
<collapsible-container <collapsible-container
v-else-if="!isLoading && repos.length" v-else-if="!isLoading && repos.length"
v-for="(item, index) in repos" v-for="(item, index) in repos"
:key="index" :key="index"
:repo="item" :repo="item"
/> />
<p v-else-if="!isLoading && !repos.length"> <p v-else-if="!isLoading && !repos.length">
{{__("No container images stored for this project. Add one by following the instructions above.")}} {{ __("No container images stored for this project. Add one by following the instructions above.") }}
</p> </p>
</div> </div>
</template> </template>
<script> <script>
/* globals Flash */
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import '../../flash'; import Flash from '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -9,13 +8,7 @@ ...@@ -9,13 +8,7 @@
import { errorMessages, errorMessagesTypes } from '../constants'; import { errorMessages, errorMessagesTypes } from '../constants';
export default { export default {
name: 'collapsibeContainerRegisty', name: 'CollapsibeContainerRegisty',
props: {
repo: {
type: Object,
required: true,
},
},
components: { components: {
clipboardButton, clipboardButton,
loadingIcon, loadingIcon,
...@@ -24,6 +17,12 @@ ...@@ -24,6 +17,12 @@
directives: { directives: {
tooltip, tooltip,
}, },
props: {
repo: {
type: Object,
required: true,
},
},
data() { data() {
return { return {
isOpen: false, isOpen: false,
...@@ -65,28 +64,29 @@ ...@@ -65,28 +64,29 @@
<template> <template>
<div class="container-image"> <div class="container-image">
<div <div class="container-image-head">
class="container-image-head">
<button <button
type="button" type="button"
@click="toggleRepo" @click="toggleRepo"
class="js-toggle-repo btn-link"> class="js-toggle-repo btn-link"
>
<i <i
class="fa" class="fa"
:class="{ :class="{
'fa-chevron-right': !isOpen, 'fa-chevron-right': !isOpen,
'fa-chevron-up': isOpen, 'fa-chevron-up': isOpen,
}" }"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
{{repo.name}} {{ repo.name }}
</button> </button>
<clipboard-button <clipboard-button
v-if="repo.location" v-if="repo.location"
:text="clipboardText" :text="clipboardText"
:title="repo.location" :title="repo.location"
/> />
<div class="controls hidden-xs pull-right"> <div class="controls hidden-xs pull-right">
<button <button
...@@ -96,35 +96,38 @@ ...@@ -96,35 +96,38 @@
:title="s__('ContainerRegistry|Remove repository')" :title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')" :aria-label="s__('ContainerRegistry|Remove repository')"
v-tooltip v-tooltip
@click="handleDeleteRepository"> @click="handleDeleteRepository"
>
<i <i
class="fa fa-trash" class="fa fa-trash"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</button> </button>
</div> </div>
</div> </div>
<loading-icon <loading-icon
v-if="repo.isLoading" v-if="repo.isLoading"
class="append-bottom-20" class="append-bottom-20"
size="2" size="2"
/> />
<div <div
v-else-if="!repo.isLoading && isOpen" v-else-if="!repo.isLoading && isOpen"
class="container-image-tags"> class="container-image-tags"
>
<table-registry <table-registry
v-if="repo.list.length" v-if="repo.list.length"
:repo="repo" :repo="repo"
/> />
<div <div
v-else v-else
class="nothing-here-block"> class="nothing-here-block"
{{s__("ContainerRegistry|No tags in Container Registry for this container image.")}} >
{{ s__("ContainerRegistry|No tags in Container Registry for this container image.") }}
</div> </div>
</div> </div>
</div> </div>
......
<script> <script>
/* globals Flash */
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { n__ } from '../../locale'; import { n__ } from '../../locale';
import '../../flash'; import Flash from '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -11,21 +10,21 @@ ...@@ -11,21 +10,21 @@
import { numberToHumanSize } from '../../lib/utils/number_utils'; import { numberToHumanSize } from '../../lib/utils/number_utils';
export default { export default {
props: {
repo: {
type: Object,
required: true,
},
},
components: { components: {
clipboardButton, clipboardButton,
tablePagination, tablePagination,
}, },
directives: {
tooltip,
},
mixins: [ mixins: [
timeagoMixin, timeagoMixin,
], ],
directives: { props: {
tooltip, repo: {
type: Object,
required: true,
},
}, },
computed: { computed: {
shouldRenderPagination() { shouldRenderPagination() {
...@@ -68,75 +67,78 @@ ...@@ -68,75 +67,78 @@
}; };
</script> </script>
<template> <template>
<div> <div>
<table class="table tags"> <table class="table tags">
<thead> <thead>
<tr> <tr>
<th>{{s__('ContainerRegistry|Tag')}}</th> <th>{{ s__('ContainerRegistry|Tag') }}</th>
<th>{{s__('ContainerRegistry|Tag ID')}}</th> <th>{{ s__('ContainerRegistry|Tag ID') }}</th>
<th>{{s__("ContainerRegistry|Size")}}</th> <th>{{ s__("ContainerRegistry|Size") }}</th>
<th>{{s__("ContainerRegistry|Created")}}</th> <th>{{ s__("ContainerRegistry|Created") }}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr
v-for="(item, i) in repo.list" v-for="(item, i) in repo.list"
:key="i"> :key="i">
<td> <td>
{{item.tag}} {{ item.tag }}
<clipboard-button <clipboard-button
v-if="item.location" v-if="item.location"
:title="item.location" :title="item.location"
:text="clipboardText(item.location)" :text="clipboardText(item.location)"
/> />
</td> </td>
<td> <td>
<span <span
v-tooltip v-tooltip
:title="item.revision" :title="item.revision"
data-placement="bottom"> data-placement="bottom"
{{item.shortRevision}} >
{{ item.shortRevision }}
</span> </span>
</td> </td>
<td> <td>
{{formatSize(item.size)}} {{ formatSize(item.size) }}
<template v-if="item.size && item.layers"> <template v-if="item.size && item.layers">
&middot; &middot;
</template> </template>
{{layers(item)}} {{ layers(item) }}
</td> </td>
<td> <td>
{{timeFormated(item.createdAt)}} {{ timeFormated(item.createdAt) }}
</td> </td>
<td class="content"> <td class="content">
<button <button
v-if="item.canDelete" v-if="item.canDelete"
type="button" type="button"
class="js-delete-registry btn btn-danger hidden-xs pull-right" class="js-delete-registry btn btn-danger hidden-xs pull-right"
:title="s__('ContainerRegistry|Remove tag')" :title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')" :aria-label="s__('ContainerRegistry|Remove tag')"
data-container="body" data-container="body"
v-tooltip v-tooltip
@click="handleDeleteRegistry(item)"> @click="handleDeleteRegistry(item)"
<i >
class="fa fa-trash" <i
aria-hidden="true"> class="fa fa-trash"
</i> aria-hidden="true"
</button> >
</td> </i>
</tr> </button>
</tbody> </td>
</table> </tr>
</tbody>
</table>
<table-pagination <table-pagination
v-if="shouldRenderPagination" v-if="shouldRenderPagination"
:change="onPageChange" :change="onPageChange"
:page-info="repo.pagination" :page-info="repo.pagination"
/> />
</div> </div>
</template> </template>
<script> <script>
import Flash from '../../../flash'; import Flash from '../../../flash';
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
import Icon from '../../../vue_shared/components/icon.vue'; import Icon from '../../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
editForm, editForm,
Icon, Icon,
},
props: {
isConfidential: {
required: true,
type: Boolean,
}, },
isEditable: { props: {
required: true, isConfidential: {
type: Boolean, required: true,
type: Boolean,
},
isEditable: {
required: true,
type: Boolean,
},
service: {
required: true,
type: Object,
},
}, },
service: { data() {
required: true, return {
type: Object, edit: false,
};
}, },
}, computed: {
data() { confidentialityIcon() {
return { return this.isConfidential ? 'eye-slash' : 'eye';
edit: false, },
};
},
computed: {
confidentialityIcon() {
return this.isConfidential ? 'eye-slash' : 'eye';
}, },
}, methods: {
methods: { toggleForm() {
toggleForm() { this.edit = !this.edit;
this.edit = !this.edit; },
updateConfidentialAttribute(confidential) {
this.service.update('issue', { confidential })
.then(() => location.reload())
.catch(() => {
Flash(`Something went wrong trying to
change the confidentiality of this issue`);
});
},
}, },
updateConfidentialAttribute(confidential) { };
this.service.update('issue', { confidential })
.then(() => location.reload())
.catch(() => {
Flash(`Something went wrong trying to
change the confidentiality of this issue`);
});
},
},
};
</script> </script>
<template> <template>
...@@ -54,8 +54,8 @@ change the confidentiality of this issue`); ...@@ -54,8 +54,8 @@ change the confidentiality of this issue`);
<icon <icon
:name="confidentialityIcon" :name="confidentialityIcon"
:size="16" :size="16"
aria-hidden="true"> aria-hidden="true"
</icon> />
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
Confidentiality Confidentiality
...@@ -75,22 +75,26 @@ change the confidentiality of this issue`); ...@@ -75,22 +75,26 @@ change the confidentiality of this issue`);
:is-confidential="isConfidential" :is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute" :update-confidential-attribute="updateConfidentialAttribute"
/> />
<div v-if="!isConfidential" class="no-value sidebar-item-value"> <div
v-if="!isConfidential"
class="no-value sidebar-item-value">
<icon <icon
name="eye" name="eye"
:size="16" :size="16"
aria-hidden="true" aria-hidden="true"
class="sidebar-item-icon inline"> class="sidebar-item-icon inline"
</icon> />
Not confidential Not confidential
</div> </div>
<div v-else class="value sidebar-item-value hide-collapsed"> <div
v-else
class="value sidebar-item-value hide-collapsed">
<icon <icon
name="eye-slash" name="eye-slash"
:size="16" :size="16"
aria-hidden="true" aria-hidden="true"
class="sidebar-item-icon inline is-active"> class="sidebar-item-icon inline is-active"
</icon> />
This issue is confidential This issue is confidential
</div> </div>
</div> </div>
......
<script> <script>
import editFormButtons from './edit_form_buttons.vue'; import editFormButtons from './edit_form_buttons.vue';
export default { export default {
props: { components: {
isConfidential: { editFormButtons,
required: true,
type: Boolean,
}, },
toggleForm: { props: {
required: true, isConfidential: {
type: Function, required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: {
required: true,
type: Function,
},
}, },
updateConfidentialAttribute: { };
required: true,
type: Function,
},
},
components: {
editFormButtons,
},
};
</script> </script>
<template> <template>
......
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import participants from './participants.vue'; import participants from './participants.vue';
export default { export default {
data() { components: {
return { participants,
store: new Store(),
};
},
props: {
mediator: {
type: Object,
required: true,
}, },
}, props: {
components: { mediator: {
participants, type: Object,
}, required: true,
}; },
},
data() {
return {
store: new Store(),
};
},
};
</script> </script>
<template> <template>
......
...@@ -46,6 +46,6 @@ export default { ...@@ -46,6 +46,6 @@ export default {
class="avatar identicon" class="avatar identicon"
:class="sizeClass" :class="sizeClass"
:style="identiconStyles"> :style="identiconStyles">
{{identiconTitle}} {{ identiconTitle }}
</div> </div>
</template> </template>
...@@ -38,7 +38,8 @@ ...@@ -38,7 +38,8 @@
class="fa fa-spin fa-spinner" class="fa fa-spin fa-spinner"
:class="cssClass" :class="cssClass"
aria-hidden="true" aria-hidden="true"
:aria-label="label"> :aria-label="label"
>
</i> </i>
</component> </component>
</template> </template>
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