Commit 011ffcfd authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 38869-datetime

* master: (85 commits)
  Export old code into es6 modules
  Use relative URL for projects to avoid storing domains
  Fix spec by avoiding monkeypatching
  Return the noteable in Note#touch_noteable
  add CHANGELOG.md entry for !15889
  fix broken empty state assets for environment monitoring page
  removed tab indexes from tag form
  Refactor entrypoint override docs
  Present member collection at the controller level
  Adds i18n
  check the import_status field before doing SQL operations to check the import url
  Fix rubocop offence
  Fix entrypoint overriding documentation
  Remove unnecessary js-issuable-edit
  Use memoization for commits on diffs
  Move Repository#write_ref to Git::Repository#write_ref
  Treat empty markdown and html strings as valid cached text, not missing cache that needs to be updated
  Center cluster title
  Remove noisy notification from QA base page
  Remove unused page from hashed storage QA scenario
  ...
parents 3c19c971 eacf99af
<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
{"iconCount":180,"spriteSize":82176,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} {"iconCount":181,"spriteSize":81482,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
...@@ -15,7 +15,7 @@ import GroupLabelSubscription from './group_label_subscription'; ...@@ -15,7 +15,7 @@ import GroupLabelSubscription from './group_label_subscription';
import BuildArtifacts from './build_artifacts'; import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor'; import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select'; import groupsSelect from './groups_select';
/* global Search */ import Search from './search';
/* global Admin */ /* global Admin */
import NamespaceSelect from './namespace_select'; import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form'; import NewCommitForm from './new_commit_form';
...@@ -24,7 +24,7 @@ import projectAvatar from './project_avatar'; ...@@ -24,7 +24,7 @@ import projectAvatar from './project_avatar';
/* global MergeRequest */ /* global MergeRequest */
import Compare from './compare'; import Compare from './compare';
import initCompareAutocomplete from './compare_autocomplete'; import initCompareAutocomplete from './compare_autocomplete';
/* global ProjectFindFile */ import ProjectFindFile from './project_find_file';
import ProjectNew from './project_new'; import ProjectNew from './project_new';
import projectImport from './project_import'; import projectImport from './project_import';
import Labels from './labels'; import Labels from './labels';
...@@ -91,6 +91,7 @@ import DueDateSelectors from './due_date_select'; ...@@ -91,6 +91,7 @@ import DueDateSelectors from './due_date_select';
import Diff from './diff'; import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription'; import ProjectLabelSubscription from './project_label_subscription';
import ProjectVariables from './project_variables'; import ProjectVariables from './project_variables';
import SearchAutocomplete from './search_autocomplete';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -683,7 +684,7 @@ import ProjectVariables from './project_variables'; ...@@ -683,7 +684,7 @@ import ProjectVariables from './project_variables';
Dispatcher.prototype.initSearch = function() { Dispatcher.prototype.initSearch = function() {
// Only when search form is present // Only when search form is present
if ($('.search').length) { if ($('.search').length) {
return new gl.SearchAutocomplete(); return new SearchAutocomplete();
} }
}; };
......
...@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { ...@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
} }
export function addImageCommentBadge(containerEl, { coordinate, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']); const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge']);
const iconEl = document.createElement('i'); buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
iconEl.className = 'fa fa-comment-o';
iconEl.setAttribute('aria-label', 'comment');
buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl); containerEl.appendChild(buttonEl);
} }
......
...@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"')); const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => { $('.js-issuable-edit').on('click', (e) => {
e.preventDefault(); e.preventDefault();
eventHub.$emit('open.form'); eventHub.$emit('open.form');
......
...@@ -60,15 +60,10 @@ import './notifications_dropdown'; ...@@ -60,15 +60,10 @@ import './notifications_dropdown';
import './notifications_form'; import './notifications_form';
import './pager'; import './pager';
import './preview_markdown'; import './preview_markdown';
import './project_find_file';
import './project_import'; import './project_import';
import './projects_dropdown'; import './projects_dropdown';
import './projects_list';
import './syntax_highlight';
import './render_gfm'; import './render_gfm';
import './right_sidebar'; import './right_sidebar';
import './search';
import './search_autocomplete';
import initBreadcrumbs from './breadcrumb'; import initBreadcrumbs from './breadcrumb';
import './dispatcher'; import './dispatcher';
......
...@@ -10,6 +10,7 @@ import './mixins/line_conflict_actions'; ...@@ -10,6 +10,7 @@ import './mixins/line_conflict_actions';
import './components/diff_file_editor'; import './components/diff_file_editor';
import './components/inline_conflict_lines'; import './components/inline_conflict_lines';
import './components/parallel_conflict_lines'; import './components/parallel_conflict_lines';
import syntaxHighlight from '../syntax_highlight';
$(() => { $(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive'; const INTERACTIVE_RESOLVE_MODE = 'interactive';
...@@ -53,7 +54,7 @@ $(() => { ...@@ -53,7 +54,7 @@ $(() => {
mergeConflictsStore.setLoadingState(false); mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => { this.$nextTick(() => {
$('.js-syntax-highlight').syntaxHighlight(); syntaxHighlight($('.js-syntax-highlight'));
}); });
}); });
}, },
......
...@@ -17,6 +17,7 @@ import Diff from './diff'; ...@@ -17,6 +17,7 @@ import Diff from './diff';
import { import {
localTimeAgo, localTimeAgo,
} from './lib/utils/datetime_utility'; } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight';
/* eslint-disable max-len */ /* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
...@@ -298,7 +299,7 @@ import { ...@@ -298,7 +299,7 @@ import {
} }
localTimeAgo($('.js-timeago', 'div#diffs')); localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight(); syntaxHighlight($('#diffs .js-syntax-highlight'));
if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) { if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
this.expandViewContainer(); this.expandViewContainer();
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueCommentForm', name: 'commentForm',
data() { data() {
return { return {
note: '', note: '',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue'; import noteAttachment from './note_attachment.vue';
import issueNoteForm from './issue_note_form.vue'; import noteForm from './note_form.vue';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
noteEditedText, noteEditedText,
noteAwardsList, noteAwardsList,
noteAttachment, noteAttachment,
issueNoteForm, noteForm,
}, },
computed: { computed: {
noteBody() { noteBody() {
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<div <div
v-html="note.note_html" v-html="note.note_html"
class="note-text md"></div> class="note-text md"></div>
<issue-note-form <note-form
v-if="isEditing" v-if="isEditing"
ref="noteForm" ref="noteForm"
@handleFormUpdate="handleFormUpdate" @handleFormUpdate="handleFormUpdate"
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import Flash from '../../flash'; import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import issueNote from './issue_note.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import issueNoteForm from './issue_note_form.vue'; import noteForm from './note_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -25,12 +25,12 @@ ...@@ -25,12 +25,12 @@
}; };
}, },
components: { components: {
issueNote, noteableNote,
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteSignedOutWidget, noteSignedOutWidget,
noteEditedText, noteEditedText,
issueNoteForm, noteForm,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
}, },
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
return placeholderNote; return placeholderNote;
} }
return issueNote; return noteableNote;
}, },
componentData(note) { componentData(note) {
return note.isPlaceholderNote ? note.notes[0] : note; return note.isPlaceholderNote ? note.notes[0] : note;
...@@ -209,7 +209,7 @@ ...@@ -209,7 +209,7 @@
type="button" type="button"
class="js-vue-discussion-reply btn btn-text-field" class="js-vue-discussion-reply btn btn-text-field"
title="Add a reply">Reply...</button> title="Add a reply">Reply...</button>
<issue-note-form <note-form
v-if="isReplying" v-if="isReplying"
save-button-title="Comment" save-button-title="Comment"
:discussion="note" :discussion="note"
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue'; import noteActions from './note_actions.vue';
import issueNoteBody from './issue_note_body.vue'; import noteBody from './note_body.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteActions, noteActions,
issueNoteBody, noteBody,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -123,9 +123,7 @@ ...@@ -123,9 +123,7 @@
// we need to do this to prevent noteForm inconsistent content warning // we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content // this is something we intentionally do so we need to recover the content
this.note.note = noteText; this.note.note = noteText;
if (this.$refs.noteBody) { this.$refs.noteBody.$refs.noteForm.note = noteText;
this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
}
}, },
}, },
created() { created() {
...@@ -174,7 +172,7 @@ ...@@ -174,7 +172,7 @@
@handleDelete="deleteHandler" @handleDelete="deleteHandler"
/> />
</div> </div>
<issue-note-body <note-body
:note="note" :note="note"
:can-edit="note.current_user.can_edit" :can-edit="note.current_user.can_edit"
:is-editing="isEditing" :is-editing="isEditing"
......
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
import Flash from '../../flash'; import Flash from '../../flash';
import store from '../stores/'; import store from '../stores/';
import * as constants from '../constants'; import * as constants from '../constants';
import issueNote from './issue_note.vue'; import noteableNote from './noteable_note.vue';
import issueDiscussion from './issue_discussion.vue'; import noteableDiscussion from './noteable_discussion.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue'; import systemNote from '../../vue_shared/components/notes/system_note.vue';
import issueCommentForm from './issue_comment_form.vue'; import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
name: 'issueNotesApp', name: 'notesApp',
props: { props: {
noteableData: { noteableData: {
type: Object, type: Object,
...@@ -36,10 +36,10 @@ ...@@ -36,10 +36,10 @@
}; };
}, },
components: { components: {
issueNote, noteableNote,
issueDiscussion, noteableDiscussion,
systemNote, systemNote,
issueCommentForm, commentForm,
loadingIcon, loadingIcon,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
...@@ -69,10 +69,10 @@ ...@@ -69,10 +69,10 @@
} }
return placeholderNote; return placeholderNote;
} else if (note.individual_note) { } else if (note.individual_note) {
return note.notes[0].system ? systemNote : issueNote; return note.notes[0].system ? systemNote : noteableNote;
} }
return issueDiscussion; return noteableDiscussion;
}, },
getComponentData(note) { getComponentData(note) {
return note.individual_note ? note.notes[0] : note; return note.individual_note ? note.notes[0] : note;
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
.then(() => this.checkLocationHash()) .then(() => this.checkLocationHash())
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
Flash('Something went wrong while fetching issue comments. Please try again.'); Flash('Something went wrong while fetching comments. Please try again.');
}); });
}, },
initPolling() { initPolling() {
...@@ -147,6 +147,6 @@ ...@@ -147,6 +147,6 @@
/> />
</ul> </ul>
<issue-comment-form /> <comment-form />
</div> </div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import issueNotesApp from './components/issue_notes_app.vue'; import notesApp from './components/notes_app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-notes', el: '#js-vue-notes',
components: { components: {
issueNotesApp, notesApp,
}, },
data() { data() {
const notesDataset = document.getElementById('js-vue-notes').dataset; const notesDataset = document.getElementById('js-vue-notes').dataset;
...@@ -32,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -32,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}; };
}, },
render(createElement) { render(createElement) {
return createElement('issue-notes-app', { return createElement('notes-app', {
props: { props: {
noteableData: this.noteableData, noteableData: this.noteableData,
notesData: this.notesData, notesData: this.notesData,
......
<script> <script>
export default { export default {
props: { props: {
helpPagePath: { helpPagePath: {
type: String, type: String,
...@@ -10,28 +10,33 @@ export default { ...@@ -10,28 +10,33 @@ export default {
required: true, required: true,
}, },
}, },
}; };
</script> </script>
<template> <template>
<div class="row empty-state js-empty-state"> <div class="row empty-state js-empty-state">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="svg-content"> <div class="svg-content svg-250">
<img :src="emptyStateSvgPath"/> <img :src="emptyStateSvgPath" />
</div> </div>
</div> </div>
<div class="col-xs-12 text-center"> <div class="col-xs-12">
<div class="text-content"> <div class="text-content">
<h4>Build with confidence</h4> <h4 class="text-center">
{{ s__("Pipelines|Build with confidence") }}
</h4>
<p> <p>
Continous Integration can help catch bugs by running your tests automatically, {{ s__("Pipelines|Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment.") }}
while Continuous Deployment can help you deliver code to your product environment.
</p> </p>
<a :href="helpPagePath" class="btn btn-info"> <div class="text-center">
Get started with Pipelines <a
:href="helpPagePath"
class="btn btn-info"
>
{{ s__("Pipelines|Get started with Pipelines") }}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
...@@ -59,8 +59,26 @@ ...@@ -59,8 +59,26 @@
}, },
computed: { computed: {
status() {
return this.job && this.job.status ? this.job.status : {};
},
tooltipText() { tooltipText() {
return `${this.job.name} - ${this.job.status.label}`; const textBuilder = [];
if (this.job.name) {
textBuilder.push(this.job.name);
}
if (this.job.name && this.status.label) {
textBuilder.push('-');
}
if (this.status.label) {
textBuilder.push(`${this.job.status.label}`);
}
return textBuilder.join(' ');
}, },
/** /**
...@@ -78,8 +96,8 @@ ...@@ -78,8 +96,8 @@
<div class="ci-job-component"> <div class="ci-job-component">
<a <a
v-tooltip v-tooltip
v-if="job.status.has_details" v-if="status.has_details"
:href="job.status.details_path" :href="status.details_path"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
...@@ -95,6 +113,7 @@ ...@@ -95,6 +113,7 @@
<div <div
v-else v-else
v-tooltip v-tooltip
class="js-job-component-tooltip"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
...@@ -108,18 +127,18 @@ ...@@ -108,18 +127,18 @@
<action-component <action-component
v-if="hasAction && !isDropdown" v-if="hasAction && !isDropdown"
:tooltip-text="job.status.action.title" :tooltip-text="status.action.title"
:link="job.status.action.path" :link="status.action.path"
:action-icon="job.status.action.icon" :action-icon="status.action.icon"
:action-method="job.status.action.method" :action-method="status.action.method"
/> />
<dropdown-action-component <dropdown-action-component
v-if="hasAction && isDropdown" v-if="hasAction && isDropdown"
:tooltip-text="job.status.action.title" :tooltip-text="status.action.title"
:link="job.status.action.path" :link="status.action.path"
:action-icon="job.status.action.icon" :action-icon="status.action.icon"
:action-method="job.status.action.method" :action-method="status.action.method"
/> />
</div> </div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import PipelinesStore from './stores/pipelines_store'; import PipelinesStore from './stores/pipelines_store';
import pipelinesComponent from './components/pipelines.vue'; import pipelinesComponent from './components/pipelines.vue';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipelines-list-vue', el: '#pipelines-list-vue',
......
...@@ -2,11 +2,33 @@ ...@@ -2,11 +2,33 @@
import fuzzaldrinPlus from 'fuzzaldrin-plus'; import fuzzaldrinPlus from 'fuzzaldrin-plus';
(function() { // highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
this.ProjectFindFile = (function() { const highlighter = function(element, text, matches) {
var highlighter; var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
lastIndex = 0;
highlightText = "";
matchedChars = [];
for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex);
if (unmatched) {
if (matchedChars.length) {
element.append(matchedChars.join("").bold());
}
matchedChars = [];
element.append(document.createTextNode(unmatched));
}
matchedChars.push(text[matchIndex]);
lastIndex = matchIndex + 1;
}
if (matchedChars.length) {
element.append(matchedChars.join("").bold());
}
return element.append(document.createTextNode(text.substring(lastIndex)));
};
function ProjectFindFile(element1, options) { export default class ProjectFindFile {
constructor(element1, options) {
this.element = element1; this.element = element1;
this.options = options; this.options = options;
this.goToBlob = this.goToBlob.bind(this); this.goToBlob = this.goToBlob.bind(this);
...@@ -23,7 +45,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -23,7 +45,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
this.load(this.options.url); this.load(this.options.url);
} }
ProjectFindFile.prototype.initEvent = function() { initEvent() {
this.inputElement.off("keyup"); this.inputElement.off("keyup");
this.inputElement.on("keyup", (function(_this) { this.inputElement.on("keyup", (function(_this) {
return function(event) { return function(event) {
...@@ -38,18 +60,18 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -38,18 +60,18 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
} }
}; };
})(this)); })(this));
}; }
ProjectFindFile.prototype.findFile = function() { findFile() {
var result, searchText; var result, searchText;
searchText = this.inputElement.val(); searchText = this.inputElement.val();
result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths; result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
return this.renderList(result, searchText); return this.renderList(result, searchText);
// find file // find file
}; }
// files pathes load // files pathes load
ProjectFindFile.prototype.load = function(url) { load(url) {
return $.ajax({ return $.ajax({
url: url, url: url,
method: "get", method: "get",
...@@ -63,10 +85,10 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -63,10 +85,10 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
}; };
})(this) })(this)
}); });
}; }
// render result // render result
ProjectFindFile.prototype.renderList = function(filePaths, searchText) { renderList(filePaths, searchText) {
var blobItemUrl, filePath, html, i, j, len, matches, results; var blobItemUrl, filePath, html, i, j, len, matches, results;
this.element.find(".tree-table > tbody").empty(); this.element.find(".tree-table > tbody").empty();
results = []; results = [];
...@@ -79,39 +101,14 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -79,39 +101,14 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
matches = fuzzaldrinPlus.match(filePath, searchText); matches = fuzzaldrinPlus.match(filePath, searchText);
} }
blobItemUrl = this.options.blobUrlTemplate + "/" + filePath; blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
html = this.makeHtml(filePath, matches, blobItemUrl); html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find(".tree-table > tbody").append(html)); results.push(this.element.find(".tree-table > tbody").append(html));
} }
return results; return results;
};
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
highlighter = function(element, text, matches) {
var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
lastIndex = 0;
highlightText = "";
matchedChars = [];
for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex);
if (unmatched) {
if (matchedChars.length) {
element.append(matchedChars.join("").bold());
}
matchedChars = [];
element.append(document.createTextNode(unmatched));
}
matchedChars.push(text[matchIndex]);
lastIndex = matchIndex + 1;
}
if (matchedChars.length) {
element.append(matchedChars.join("").bold());
} }
return element.append(document.createTextNode(text.substring(lastIndex)));
};
// make tbody row html // make tbody row html
ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) { static makeHtml(filePath, matches, blobItemUrl) {
var $tr; var $tr;
$tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>"); $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
if (matches) { if (matches) {
...@@ -121,9 +118,9 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -121,9 +118,9 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
$tr.find(".str-truncated").text(filePath); $tr.find(".str-truncated").text(filePath);
} }
return $tr; return $tr;
}; }
ProjectFindFile.prototype.selectRow = function(type) { selectRow(type) {
var next, rows, selectedRow; var next, rows, selectedRow;
rows = this.element.find(".files-slider tr.tree-item"); rows = this.element.find(".files-slider tr.tree-item");
selectedRow = this.element.find(".files-slider tr.tree-item.selected"); selectedRow = this.element.find(".files-slider tr.tree-item.selected");
...@@ -143,28 +140,25 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -143,28 +140,25 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
} }
return selectedRow.addClass("selected").focus(); return selectedRow.addClass("selected").focus();
} }
}; }
ProjectFindFile.prototype.selectRowUp = function() { selectRowUp() {
return this.selectRow("UP"); return this.selectRow("UP");
}; }
ProjectFindFile.prototype.selectRowDown = function() { selectRowDown() {
return this.selectRow("DOWN"); return this.selectRow("DOWN");
}; }
ProjectFindFile.prototype.goToTree = function() { goToTree() {
return location.href = this.options.treeUrl; return location.href = this.options.treeUrl;
}; }
ProjectFindFile.prototype.goToBlob = function() { goToBlob() {
var $link = this.element.find(".tree-item.selected .tree-item-file-name a"); var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
if ($link.length) { if ($link.length) {
$link.get(0).click(); $link.get(0).click();
} }
}; }
}
return ProjectFindFile;
})();
}).call(window);
import renderMath from './render_math'; import renderMath from './render_math';
import renderMermaid from './render_mermaid'; import renderMermaid from './render_mermaid';
import syntaxHighlight from './syntax_highlight';
// Render Gitlab flavoured Markdown // Render Gitlab flavoured Markdown
// //
// Delegates to syntax highlight and render math & mermaid diagrams. // Delegates to syntax highlight and render math & mermaid diagrams.
// //
$.fn.renderGFM = function renderGFM() { $.fn.renderGFM = function renderGFM() {
this.find('.js-syntax-highlight').syntaxHighlight(); syntaxHighlight(this.find('.js-syntax-highlight'));
renderMath(this.find('.js-render-math')); renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid')); renderMermaid(this.find('.js-render-mermaid'));
return this; return this;
......
<script> <script>
/* global LineHighlighter */ /* global LineHighlighter */
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import syntaxHighlight from '../../syntax_highlight';
export default { export default {
computed: { computed: {
...@@ -13,7 +14,7 @@ export default { ...@@ -13,7 +14,7 @@ export default {
}, },
methods: { methods: {
highlightFile() { highlightFile() {
$(this.$el).find('.file-content').syntaxHighlight(); syntaxHighlight($(this.$el).find('.file-content'));
}, },
}, },
mounted() { mounted() {
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
import Flash from './flash'; import Flash from './flash';
import Api from './api'; import Api from './api';
(function() { export default class Search {
this.Search = (function() { constructor() {
function Search() { const $groupDropdown = $('.js-search-group-dropdown');
var $groupDropdown, $projectDropdown; const $projectDropdown = $('.js-search-project-dropdown');
$groupDropdown = $('.js-search-group-dropdown');
$projectDropdown = $('.js-search-project-dropdown'); this.searchInput = '.js-search-input';
this.searchClear = '.js-search-clear';
this.groupId = $groupDropdown.data('group-id'); this.groupId = $groupDropdown.data('group-id');
this.eventListeners(); this.eventListeners();
$groupDropdown.glDropdown({ $groupDropdown.glDropdown({
selectable: true, selectable: true,
filterable: true, filterable: true,
fieldName: 'group_id', fieldName: 'group_id',
search: { search: {
fields: ['full_name'] fields: ['full_name'],
}, },
data: function(term, callback) { data(term, callback) {
return Api.groups(term, {}, function(data) { return Api.groups(term, {}, (data) => {
data.unshift({ data.unshift({
full_name: 'Any' full_name: 'Any',
}); });
data.splice(1, 0, 'divider'); data.splice(1, 0, 'divider');
return callback(data); return callback(data);
}); });
}, },
id: function(obj) { id(obj) {
return obj.id; return obj.id;
}, },
text: function(obj) { text(obj) {
return obj.full_name; return obj.full_name;
}, },
toggleLabel: function(obj) { toggleLabel(obj) {
return ($groupDropdown.data('default-label')) + " " + obj.full_name; return `${($groupDropdown.data('default-label'))} ${obj.full_name}`;
}, },
clicked: (function(_this) { clicked: () => Search.submitSearch(),
return function() {
return _this.submitSearch();
};
})(this)
}); });
$projectDropdown.glDropdown({ $projectDropdown.glDropdown({
selectable: true, selectable: true,
filterable: true, filterable: true,
fieldName: 'project_id', fieldName: 'project_id',
search: { search: {
fields: ['name'] fields: ['name'],
}, },
data: (term, callback) => { data: (term, callback) => {
this.getProjectsData(term) this.getProjectsData(term)
.then((data) => { .then((data) => {
data.unshift({ data.unshift({
name_with_namespace: 'Any' name_with_namespace: 'Any',
}); });
data.splice(1, 0, 'divider'); data.splice(1, 0, 'divider');
...@@ -61,47 +60,46 @@ import Api from './api'; ...@@ -61,47 +60,46 @@ import Api from './api';
.then(data => callback(data)) .then(data => callback(data))
.catch(() => new Flash('Error fetching projects')); .catch(() => new Flash('Error fetching projects'));
}, },
id: function(obj) { id(obj) {
return obj.id; return obj.id;
}, },
text: function(obj) { text(obj) {
return obj.name_with_namespace; return obj.name_with_namespace;
}, },
toggleLabel: function(obj) { toggleLabel(obj) {
return ($projectDropdown.data('default-label')) + " " + obj.name_with_namespace; return `${($projectDropdown.data('default-label'))} ${obj.name_with_namespace}`;
}, },
clicked: (function(_this) { clicked: () => Search.submitSearch(),
return function() {
return _this.submitSearch();
};
})(this)
}); });
} }
Search.prototype.eventListeners = function() { eventListeners() {
$(document).off('keyup', '.js-search-input').on('keyup', '.js-search-input', this.searchKeyUp); $(document)
return $(document).off('click', '.js-search-clear').on('click', '.js-search-clear', this.clearSearchField); .off('keyup', this.searchInput)
}; .on('keyup', this.searchInput, this.searchKeyUp);
$(document)
.off('click', this.searchClear)
.on('click', this.searchClear, this.clearSearchField.bind(this));
}
Search.prototype.submitSearch = function() { static submitSearch() {
return $('.js-search-form').submit(); return $('.js-search-form').submit();
}; }
Search.prototype.searchKeyUp = function() { searchKeyUp() {
var $input; const $input = $(this);
$input = $(this);
if ($input.val() === '') { if ($input.val() === '') {
return $('.js-search-clear').addClass('hidden'); $('.js-search-clear').addClass('hidden');
} else { } else {
return $('.js-search-clear').removeClass('hidden'); $('.js-search-clear').removeClass('hidden');
}
} }
};
Search.prototype.clearSearchField = function() { clearSearchField() {
return $('.js-search-input').val('').trigger('keyup').focus(); return $(this.searchInput).val('').trigger('keyup').focus();
}; }
Search.prototype.getProjectsData = function(term) { getProjectsData(term) {
return new Promise((resolve) => { return new Promise((resolve) => {
if (this.groupId) { if (this.groupId) {
Api.groupProjects(this.groupId, term, resolve); Api.groupProjects(this.groupId, term, resolve);
...@@ -111,8 +109,5 @@ import Api from './api'; ...@@ -111,8 +109,5 @@ import Api from './api';
}, resolve); }, resolve);
} }
}); });
}; }
}
return Search;
})();
}).call(window);
...@@ -8,17 +8,55 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -8,17 +8,55 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
* When the user clicks `x` button it cleans the input and closes the dropdown. * When the user clicks `x` button it cleans the input and closes the dropdown.
*/ */
((global) => { const KEYCODE = {
const KEYCODE = {
ESCAPE: 27, ESCAPE: 27,
BACKSPACE: 8, BACKSPACE: 8,
ENTER: 13, ENTER: 13,
UP: 38, UP: 38,
DOWN: 40, DOWN: 40,
};
function setSearchOptions() {
var $projectOptionsDataEl = $('.js-search-project-options');
var $groupOptionsDataEl = $('.js-search-group-options');
var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
if ($projectOptionsDataEl.length) {
gl.projectOptions = gl.projectOptions || {};
var projectPath = $projectOptionsDataEl.data('project-path');
gl.projectOptions[projectPath] = {
name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'),
issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
mrPath: $projectOptionsDataEl.data('mr-path'),
}; };
}
if ($groupOptionsDataEl.length) {
gl.groupOptions = gl.groupOptions || {};
class SearchAutocomplete { var groupPath = $groupOptionsDataEl.data('group-path');
gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'),
issuesPath: $groupOptionsDataEl.data('issues-path'),
mrPath: $groupOptionsDataEl.data('mr-path'),
};
}
if ($dashboardOptionsDataEl.length) {
gl.dashboardOptions = {
issuesPath: $dashboardOptionsDataEl.data('issues-path'),
mrPath: $dashboardOptionsDataEl.data('mr-path'),
};
}
}
export default class SearchAutocomplete {
constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) { constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
setSearchOptions();
this.bindEventContext(); this.bindEventContext();
this.wrap = wrap || $('.search'); this.wrap = wrap || $('.search');
this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts'); this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
...@@ -411,45 +449,4 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -411,45 +449,4 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
return this.searchInput.val('').focus(); return this.searchInput.val('').focus();
} }
} }
} }
global.SearchAutocomplete = SearchAutocomplete;
$(function() {
var $projectOptionsDataEl = $('.js-search-project-options');
var $groupOptionsDataEl = $('.js-search-group-options');
var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
if ($projectOptionsDataEl.length) {
gl.projectOptions = gl.projectOptions || {};
var projectPath = $projectOptionsDataEl.data('project-path');
gl.projectOptions[projectPath] = {
name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'),
issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
mrPath: $projectOptionsDataEl.data('mr-path'),
};
}
if ($groupOptionsDataEl.length) {
gl.groupOptions = gl.groupOptions || {};
var groupPath = $groupOptionsDataEl.data('group-path');
gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'),
issuesPath: $groupOptionsDataEl.data('issues-path'),
mrPath: $groupOptionsDataEl.data('mr-path'),
};
}
if ($dashboardOptionsDataEl.length) {
gl.dashboardOptions = {
issuesPath: $dashboardOptionsDataEl.data('issues-path'),
mrPath: $dashboardOptionsDataEl.data('mr-path'),
};
}
});
})(window.gl || (window.gl = {}));
...@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation { ...@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
super(); super();
this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form'); this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form');
this.editBtn = document.querySelector('.issuable-edit'); this.editBtn = document.querySelector('.js-issuable-edit');
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee')); Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone')); Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone'));
......
import Flash from '../../../flash'; import Flash from '../../../flash';
import AssigneeTitle from './assignee_title'; import AssigneeTitle from './assignee_title';
import Assignees from './assignees'; import Assignees from './assignees';
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'SidebarAssignees', name: 'SidebarAssignees',
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
loading: false, loading: false,
field: '',
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
field: {
type: String,
required: true,
},
signedIn: {
type: Boolean,
required: false,
default: false,
},
},
components: { components: {
'assignee-title': AssigneeTitle, 'assignee-title': AssigneeTitle,
assignees: Assignees, assignees: Assignees,
...@@ -61,10 +71,6 @@ export default { ...@@ -61,10 +71,6 @@ export default {
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees); eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
}, },
beforeMount() {
this.field = this.$el.dataset.field;
this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
},
template: ` template: `
<div> <div>
<assignee-title <assignee-title
......
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import participants from './participants.vue'; import participants from './participants.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
participants, participants,
}, },
...@@ -21,6 +25,7 @@ export default { ...@@ -21,6 +25,7 @@ export default {
<participants <participants
:loading="store.isFetching.participants" :loading="store.isFetching.participants"
:participants="store.participants" :participants="store.participants"
:number-of-less-participants="7" /> :number-of-less-participants="7"
/>
</div> </div>
</template> </template>
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue'; ...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
subscriptions, subscriptions,
}, },
......
...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate'; ...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
function mountAssigneesComponent(mediator) {
const el = document.getElementById('js-vue-sidebar-assignees');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarAssignees,
},
render: createElement => createElement('sidebar-assignees', {
props: {
mediator,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
},
}),
});
}
function mountConfidentialComponent(mediator) { function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point'); const el = document.getElementById('js-confidential-entry-point');
...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) { ...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) {
}).$mount(el); }).$mount(el);
} }
function mountParticipantsComponent() { function mountParticipantsComponent(mediator) {
const el = document.querySelector('.js-sidebar-participants-entry-point'); const el = document.querySelector('.js-sidebar-participants-entry-point');
// eslint-disable-next-line no-new
if (!el) return; if (!el) return;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -60,11 +82,15 @@ function mountParticipantsComponent() { ...@@ -60,11 +82,15 @@ function mountParticipantsComponent() {
components: { components: {
sidebarParticipants, sidebarParticipants,
}, },
render: createElement => createElement('sidebar-participants', {}), render: createElement => createElement('sidebar-participants', {
props: {
mediator,
},
}),
}); });
} }
function mountSubscriptionsComponent() { function mountSubscriptionsComponent(mediator) {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point'); const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return; if (!el) return;
...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() { ...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() {
components: { components: {
sidebarSubscriptions, sidebarSubscriptions,
}, },
render: createElement => createElement('sidebar-subscriptions', {}), render: createElement => createElement('sidebar-subscriptions', {
props: {
mediator,
},
}),
}); });
} }
function mount(mediator) { function mountTimeTrackingComponent() {
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees'); const el = document.getElementById('issuable-time-tracker');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page if (!el) return;
if (sidebarAssigneesEl) {
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl); // eslint-disable-next-line no-new
} new Vue({
el,
components: {
SidebarTimeTracking,
},
render: createElement => createElement('sidebar-time-tracking', {}),
});
}
export function mountSidebar(mediator) {
mountAssigneesComponent(mediator);
mountConfidentialComponent(mediator); mountConfidentialComponent(mediator);
mountLockComponent(mediator); mountLockComponent(mediator);
mountParticipantsComponent(); mountParticipantsComponent(mediator);
mountSubscriptionsComponent(); mountSubscriptionsComponent(mediator);
new SidebarMoveIssue( new SidebarMoveIssue(
mediator, mediator,
...@@ -98,7 +137,9 @@ function mount(mediator) { ...@@ -98,7 +137,9 @@ function mount(mediator) {
$('.js-move-issue-confirmation-button'), $('.js-move-issue-confirmation-button'),
).init(); ).init();
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker'); mountTimeTrackingComponent();
} }
export default mount; export function getSidebarOptions() {
return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
}
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
import mountSidebar from './mount_sidebar'; import { mountSidebar, getSidebarOptions } from './mount_sidebar';
function domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const mediator = new Mediator(getSidebarOptions());
const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
mountSidebar(mediator); mountSidebar(mediator);
......
...@@ -8,7 +8,6 @@ export default class SidebarMediator { ...@@ -8,7 +8,6 @@ export default class SidebarMediator {
if (!SidebarMediator.singleton) { if (!SidebarMediator.singleton) {
this.initSingleton(options); this.initSingleton(options);
} }
return SidebarMediator.singleton; return SidebarMediator.singleton;
} }
......
export default class SidebarStore { export default class SidebarStore {
constructor(store) { constructor(options) {
if (!SidebarStore.singleton) { if (!SidebarStore.singleton) {
const { currentUser, rootPath, editable } = store; this.initSingleton(options);
}
return SidebarStore.singleton;
}
initSingleton(options) {
const { currentUser, rootPath, editable } = options;
this.currentUser = currentUser; this.currentUser = currentUser;
this.rootPath = rootPath; this.rootPath = rootPath;
this.editable = editable; this.editable = editable;
...@@ -25,9 +32,6 @@ export default class SidebarStore { ...@@ -25,9 +32,6 @@ export default class SidebarStore {
SidebarStore.singleton = this; SidebarStore.singleton = this;
} }
return SidebarStore.singleton;
}
setAssigneeData(data) { setAssigneeData(data) {
this.isFetching.assignees = false; this.isFetching.assignees = false;
if (data.assignees) { if (data.assignees) {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import FilesCommentButton from './files_comment_button'; import FilesCommentButton from './files_comment_button';
import imageDiffHelper from './image_diff/helpers/index'; import imageDiffHelper from './image_diff/helpers/index';
import syntaxHighlight from './syntax_highlight';
const WRAPPER = '<div class="diff-content"></div>'; const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
...@@ -64,7 +65,7 @@ export default class SingleFileDiff { ...@@ -64,7 +65,7 @@ export default class SingleFileDiff {
_this.loadingContent.hide(); _this.loadingContent.hide();
if (data.html) { if (data.html) {
_this.content = $(data.html); _this.content = $(data.html);
_this.content.syntaxHighlight(); syntaxHighlight(_this.content);
} else { } else {
_this.hasError = true; _this.hasError = true;
_this.content = $(ERROR_HTML); _this.content = $(ERROR_HTML);
......
...@@ -10,17 +10,15 @@ ...@@ -10,17 +10,15 @@
// <div class="js-syntax-highlight"></div> // <div class="js-syntax-highlight"></div>
// //
$.fn.syntaxHighlight = function() { export default function syntaxHighlight(el) {
var $children; if ($(el).hasClass('js-syntax-highlight')) {
if ($(this).hasClass('js-syntax-highlight')) {
// Given the element itself, apply highlighting // Given the element itself, apply highlighting
return $(this).addClass(gon.user_color_scheme); return $(el).addClass(gon.user_color_scheme);
} else { } else {
// Given a parent element, recurse to any of its applicable children // Given a parent element, recurse to any of its applicable children
$children = $(this).find('.js-syntax-highlight'); const $children = $(el).find('.js-syntax-highlight');
if ($children.length) { if ($children.length) {
return $children.syntaxHighlight(); return syntaxHighlight($children);
} }
} }
}; }
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
padding: 1px 5px; padding: 1px 5px;
font-size: 12px; font-size: 12px;
color: $blue-500; color: $blue-500;
width: 23px; width: 24px;
height: 23px; height: 24px;
border: 1px solid $blue-500; border: 1px solid $blue-500;
&:hover, &:hover,
......
...@@ -143,20 +143,48 @@ ...@@ -143,20 +143,48 @@
} }
} }
@mixin dropdown-item-hover {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
outline: 0;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
.avatar {
border-color: $white-light;
}
}
@mixin dropdown-link { @mixin dropdown-link {
background: transparent;
border: 0;
border-radius: 0;
box-shadow: none;
display: block; display: block;
font-weight: $gl-font-weight-normal;
position: relative; position: relative;
padding: 5px 8px; padding: 8px 16px;
color: $gl-text-color; color: $gl-text-color;
line-height: initial; line-height: normal;
border-radius: 2px; white-space: normal;
white-space: nowrap;
overflow: hidden; overflow: hidden;
text-align: left;
width: 100%;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
&:hover, &:hover,
&:active,
&:focus, &:focus,
&.is-focused { &.is-focused {
background-color: $dropdown-link-hover-bg; @include dropdown-item-hover;
text-decoration: none; text-decoration: none;
.badge { .badge {
...@@ -166,6 +194,13 @@ ...@@ -166,6 +194,13 @@
&.dropdown-menu-user-link { &.dropdown-menu-user-link {
line-height: 16px; line-height: 16px;
padding-top: 10px;
padding-bottom: 7px;
white-space: nowrap;
.dropdown-menu-user-username {
display: block;
}
} }
.icon-play { .icon-play {
...@@ -187,8 +222,8 @@ ...@@ -187,8 +222,8 @@
z-index: 300; z-index: 300;
min-width: 240px; min-width: 240px;
max-width: 500px; max-width: 500px;
margin-top: 2px; margin-top: $dropdown-vertical-offset;
margin-bottom: 2px; margin-bottom: 24px;
font-size: 14px; font-size: 14px;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
padding: 8px 0; padding: 8px 0;
...@@ -197,6 +232,10 @@ ...@@ -197,6 +232,10 @@
border-radius: $border-radius-base; border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
&.dropdown-open-top {
margin-bottom: $dropdown-vertical-offset;
}
&.dropdown-open-left { &.dropdown-open-left {
right: 0; right: 0;
left: auto; left: auto;
...@@ -227,16 +266,27 @@ ...@@ -227,16 +266,27 @@
} }
li { li {
display: block;
text-align: left; text-align: left;
list-style: none; list-style: none;
padding: 0 10px; padding: 0 1px;
a,
button,
.menu-item {
@include dropdown-link;
}
} }
.divider { .divider {
height: 1px; height: 1px;
margin: 6px 10px; margin: 6px 0;
padding: 0; padding: 0;
background-color: $dropdown-divider-color; background-color: $dropdown-divider-color;
&:hover {
background-color: $dropdown-divider-color;
}
} }
.separator { .separator {
...@@ -247,10 +297,6 @@ ...@@ -247,10 +297,6 @@
background-color: $dropdown-divider-color; background-color: $dropdown-divider-color;
} }
a {
@include dropdown-link;
}
.dropdown-menu-empty-item a { .dropdown-menu-empty-item a {
&:hover, &:hover,
&:focus { &:focus {
...@@ -262,7 +308,7 @@ ...@@ -262,7 +308,7 @@
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
font-size: 13px; font-size: 13px;
line-height: 22px; line-height: 22px;
padding: 0 16px; padding: 8px 16px;
} }
&.capitalize-header .dropdown-header { &.capitalize-header .dropdown-header {
...@@ -277,7 +323,7 @@ ...@@ -277,7 +323,7 @@
.separator + .dropdown-header, .separator + .dropdown-header,
.separator + .dropdown-bold-header { .separator + .dropdown-bold-header {
padding-top: 2px; padding-top: 10px;
} }
.unclickable { .unclickable {
...@@ -298,48 +344,28 @@ ...@@ -298,48 +344,28 @@
} }
.dropdown-menu li { .dropdown-menu li {
padding: $gl-btn-padding;
cursor: pointer; cursor: pointer;
&.droplab-item-active button {
@include dropdown-item-hover;
}
> a, > a,
> button { > button {
display: flex; display: flex;
margin: 0; margin: 0;
padding: 0;
border-radius: 0;
text-overflow: inherit; text-overflow: inherit;
background-color: inherit;
color: inherit;
border: inherit;
text-align: left; text-align: left;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
&.btn .fa:not(:last-child) { &.btn .fa:not(:last-child) {
margin-left: 5px; margin-left: 5px;
} }
} }
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
}
&.droplab-item-selected i { &.droplab-item-selected i {
visibility: visible; visibility: visible;
} }
&.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
.icon { .icon {
visibility: hidden; visibility: hidden;
} }
...@@ -431,11 +457,6 @@ ...@@ -431,11 +457,6 @@
} }
} }
.dropdown-menu-user-link {
padding-top: 10px;
padding-bottom: 7px;
}
.dropdown-menu-user-full-name { .dropdown-menu-user-full-name {
display: block; display: block;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
...@@ -464,23 +485,22 @@ ...@@ -464,23 +485,22 @@
.dropdown-menu-align-right { .dropdown-menu-align-right {
left: auto; left: auto;
right: 0; right: 0;
margin-top: -5px;
} }
.dropdown-menu-selectable { .dropdown-menu-selectable {
li {
a { a {
padding-left: 26px; padding: 8px 40px;
position: relative; position: relative;
&.is-indeterminate, &.is-indeterminate,
&.is-active { &.is-active {
font-weight: $gl-font-weight-bold;
color: $gl-text-color; color: $gl-text-color;
&::before { &::before {
position: absolute; position: absolute;
left: 6px; left: 16px;
top: 50%; top: 16px;
transform: translateY(-50%); transform: translateY(-50%);
font: normal normal normal 14px/1 FontAwesome; font: normal normal normal 14px/1 FontAwesome;
font-size: inherit; font-size: inherit;
...@@ -488,6 +508,12 @@ ...@@ -488,6 +508,12 @@
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
&.dropdown-menu-user-link {
&::before {
top: 50%;
}
}
} }
&.is-indeterminate::before { &.is-indeterminate::before {
...@@ -496,9 +522,7 @@ ...@@ -496,9 +522,7 @@
&.is-active::before { &.is-active::before {
content: "\f00c"; content: "\f00c";
position: absolute; }
top: 50%;
transform: translateY(-50%);
} }
} }
} }
...@@ -735,136 +759,6 @@ ...@@ -735,136 +759,6 @@
} }
} }
@mixin dropdown-item-hover {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
}
// TODO: change global style and remove mixin
@mixin new-style-dropdown($selector: '') {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
margin-bottom: 24px;
&.dropdown-open-top {
margin-bottom: $dropdown-vertical-offset;
}
li {
display: block;
padding: 0 1px;
&:hover {
background-color: transparent;
}
&.divider {
margin: 6px 0;
&:hover {
background-color: $dropdown-divider-color;
}
}
&.dropdown-header {
padding: 8px 16px;
}
&.droplab-item-active button {
@include dropdown-item-hover;
}
a,
button,
.menu-item {
margin-bottom: 0;
border-radius: 0;
box-shadow: none;
padding: 8px 16px;
text-align: left;
white-space: normal;
width: 100%;
font-weight: $gl-font-weight-normal;
line-height: normal;
&.dropdown-menu-user-link {
white-space: nowrap;
.dropdown-menu-user-username {
display: block;
}
}
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
&.is-focused,
&:hover,
&:active,
&:focus {
@include dropdown-item-hover;
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
}
&.is-active {
font-weight: inherit;
&::before {
top: 16px;
}
&.dropdown-menu-user-link::before {
top: 50%;
transform: translateY(-50%);
}
}
}
&.dropdown-menu-empty-item a {
&:hover,
&:focus {
background-color: transparent;
}
}
}
&.dropdown-menu-selectable {
li {
a {
padding: 8px 40px;
&.is-indeterminate::before,
&.is-active::before {
left: 16px;
}
}
}
}
}
#{$selector}.dropdown-menu-align-right {
margin-top: 2px;
}
.open {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
@media (max-width: $screen-xs-max) {
max-width: 100%;
}
}
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.navbar-gitlab { .navbar-gitlab {
li.header-projects, li.header-projects,
...@@ -891,9 +785,6 @@ ...@@ -891,9 +785,6 @@
} }
} }
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
@include new-style-dropdown('.js-namespace-select + ');
header.header-content .dropdown-menu.projects-dropdown-menu { header.header-content .dropdown-menu.projects-dropdown-menu {
padding: 0; padding: 0;
} }
......
...@@ -50,8 +50,6 @@ ...@@ -50,8 +50,6 @@
} }
.filtered-search-wrapper { .filtered-search-wrapper {
@include new-style-dropdown;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
...@@ -165,16 +163,6 @@ ...@@ -165,16 +163,6 @@
} }
} }
.droplab-dropdown li.filtered-search-token {
padding: 0;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
}
.filtered-search-term { .filtered-search-term {
.name { .name {
background-color: inherit; background-color: inherit;
...@@ -336,21 +324,12 @@ ...@@ -336,21 +324,12 @@
.filtered-search-history-dropdown-content { .filtered-search-history-dropdown-content {
max-height: none; max-height: none;
}
.filtered-search-history-dropdown-item,
.filtered-search-history-clear-button {
@include dropdown-link;
overflow: hidden;
width: 100%;
margin: 0.5em 0;
background-color: transparent; .filtered-search-history-dropdown-item,
border: 0; .filtered-search-history-clear-button {
text-align: left;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
}
} }
.filtered-search-history-dropdown-token { .filtered-search-history-dropdown-token {
...@@ -402,24 +381,9 @@ ...@@ -402,24 +381,9 @@
} }
} }
%filter-dropdown-item-btn-hover {
text-decoration: none;
outline: 0;
.avatar {
border-color: $white-light;
}
}
.droplab-dropdown .dropdown-menu .filter-dropdown-item { .droplab-dropdown .dropdown-menu .filter-dropdown-item {
.btn { .btn {
border: 0;
width: 100%;
text-align: left;
padding: 8px 16px;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden;
border-radius: 0;
.fa { .fa {
width: 15px; width: 15px;
...@@ -434,11 +398,6 @@ ...@@ -434,11 +398,6 @@
height: 17px; height: 17px;
top: 0; top: 0;
} }
&:hover,
&:focus {
@extend %filter-dropdown-item-btn-hover;
}
} }
.dropdown-light-content { .dropdown-light-content {
...@@ -459,17 +418,9 @@ ...@@ -459,17 +418,9 @@
word-break: break-all; word-break: break-all;
} }
} }
&.droplab-item-active .btn {
@extend %filter-dropdown-item-btn-hover;
}
} }
.filter-dropdown-loading { .filter-dropdown-loading {
padding: 8px 16px; padding: 8px 16px;
text-align: center; text-align: center;
} }
.issues-details-filters {
@include new-style-dropdown;
}
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
} }
.navbar-gitlab { .navbar-gitlab {
@include new-style-dropdown;
&.navbar-gitlab { &.navbar-gitlab {
padding: 0 16px; padding: 0 16px;
z-index: 1000; z-index: 1000;
......
...@@ -19,6 +19,13 @@ ...@@ -19,6 +19,13 @@
max-width: 425px; max-width: 425px;
width: 100%; width: 100%;
} }
&.svg-250 {
img,
svg {
width: 250px;
}
}
} }
@mixin svg-size($size) { @mixin svg-size($size) {
......
...@@ -132,8 +132,6 @@ ul.content-list { ...@@ -132,8 +132,6 @@ ul.content-list {
} }
.controls { .controls {
@include new-style-dropdown;
float: right; float: right;
> .control-text { > .control-text {
......
...@@ -86,8 +86,6 @@ ...@@ -86,8 +86,6 @@
} }
.nav-controls { .nav-controls {
@include new-style-dropdown;
display: inline-block; display: inline-block;
float: right; float: right;
text-align: right; text-align: right;
......
...@@ -50,6 +50,11 @@ ...@@ -50,6 +50,11 @@
&:not(.disabled) { &:not(.disabled) {
cursor: pointer; cursor: pointer;
} }
svg {
width: $gl-padding;
height: $gl-padding;
}
} }
} }
...@@ -139,10 +144,6 @@ ...@@ -139,10 +144,6 @@
} }
} }
.issuable-sidebar {
@include new-style-dropdown;
}
.pikaday-container { .pikaday-container {
.pika-single { .pika-single {
margin-top: 2px; margin-top: 2px;
......
...@@ -343,8 +343,6 @@ a > code { ...@@ -343,8 +343,6 @@ a > code {
@extend .ref-name; @extend .ref-name;
} }
@include new-style-dropdown('.git-revision-dropdown');
/** /**
* Apply Markdown typography * Apply Markdown typography
* *
......
...@@ -721,7 +721,7 @@ $issuable-warning-icon-margin: 4px; ...@@ -721,7 +721,7 @@ $issuable-warning-icon-margin: 4px;
Image Commenting cursor Image Commenting cursor
*/ */
$image-comment-cursor-left-offset: 12; $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 30; $image-comment-cursor-top-offset: 12;
/* /*
Popup Popup
......
...@@ -323,8 +323,6 @@ ...@@ -323,8 +323,6 @@
} }
.build-dropdown { .build-dropdown {
@include new-style-dropdown;
margin: $gl-padding 0; margin: $gl-padding 0;
padding: 0; padding: 0;
......
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
max-width: 100%; max-width: 100%;
} }
@include new-style-dropdown('.clusters-dropdown ');
.clusters-container { .clusters-container {
.nav-bar-right { .nav-bar-right {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
......
#cycle-analytics { #cycle-analytics {
@include new-style-dropdown;
max-width: 1000px; max-width: 1000px;
margin: 24px auto 0; margin: 24px auto 0;
position: relative; position: relative;
......
...@@ -32,8 +32,6 @@ ...@@ -32,8 +32,6 @@
} }
.detail-page-header-actions { .detail-page-header-actions {
@include new-style-dropdown;
align-self: center; align-self: center;
flex-shrink: 0; flex-shrink: 0;
flex: 0 0 auto; flex: 0 0 auto;
......
...@@ -581,8 +581,6 @@ ...@@ -581,8 +581,6 @@
} }
.commit-stat-summary { .commit-stat-summary {
@include new-style-dropdown;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
margin-left: -$gl-padding; margin-left: -$gl-padding;
padding-left: $gl-padding; padding-left: $gl-padding;
...@@ -732,18 +730,18 @@ ...@@ -732,18 +730,18 @@
.frame.click-to-comment { .frame.click-to-comment {
position: relative; position: relative;
cursor: image-url('icon_image_comment.svg') cursor: image-url('illustrations/image_comment_light_cursor.svg')
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
// Retina cursor // Retina cursor
cursor: -webkit-image-set(image-url('icon_image_comment.svg') 1x, image-url('icon_image_comment@2x.svg') 2x) cursor: -webkit-image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x, image-url('illustrations/image_comment_light_cursor@2x.svg') 2x)
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
.comment-indicator { .comment-indicator {
position: absolute; position: absolute;
padding: 0; padding: 0;
width: (2px * $image-comment-cursor-left-offset); width: (2px * $image-comment-cursor-left-offset);
height: (1px * $image-comment-cursor-top-offset); height: (2px * $image-comment-cursor-top-offset);
// center the indicator to match the top left click region // center the indicator to match the top left click region
margin-top: (-1px * $image-comment-cursor-top-offset) + 2; margin-top: (-1px * $image-comment-cursor-top-offset) + 2;
margin-left: (-1px * $image-comment-cursor-left-offset) + 1; margin-left: (-1px * $image-comment-cursor-left-offset) + 1;
...@@ -778,15 +776,20 @@ ...@@ -778,15 +776,20 @@
.frame .badge, .frame .badge,
.frame .image-comment-badge { .frame .image-comment-badge {
// Center align badges on the frame // Center align badges on the frame
transform: translate3d(-50%, -50%, 0); transform: translate(-50%, -50%);
} }
.image-comment-badge { .image-comment-badge {
@include btn-comment-icon;
position: absolute; position: absolute;
width: 24px;
height: 24px;
padding: 0;
background: none;
border: 0;
&.inverted { > svg {
border-color: $white-light; width: 100%;
height: 100%;
} }
} }
......
...@@ -204,8 +204,6 @@ ...@@ -204,8 +204,6 @@
.gitlab-ci-yml-selector, .gitlab-ci-yml-selector,
.dockerfile-selector, .dockerfile-selector,
.template-type-selector { .template-type-selector {
@include new-style-dropdown;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
font-family: $regular_font; font-family: $regular_font;
......
...@@ -12,8 +12,6 @@ ...@@ -12,8 +12,6 @@
.environments-container { .environments-container {
.ci-table { .ci-table {
@include new-style-dropdown;
.deployment-column { .deployment-column {
> span { > span {
word-break: break-all; word-break: break-all;
......
...@@ -470,7 +470,8 @@ ...@@ -470,7 +470,8 @@
} }
} }
.milestone-title span { .milestone-title span,
.collapse-truncated-title {
@include str-truncated(100%); @include str-truncated(100%);
display: block; display: block;
margin: 0 4px; margin: 0 4px;
...@@ -487,12 +488,6 @@ ...@@ -487,12 +488,6 @@
} }
} }
.dropdown-content {
a:hover {
color: inherit;
}
}
.dropdown-menu-toggle { .dropdown-menu-toggle {
width: 100%; width: 100%;
padding-top: 6px; padding-top: 6px;
...@@ -511,10 +506,6 @@ ...@@ -511,10 +506,6 @@
} }
} }
.sidebar-move-issue-dropdown {
@include new-style-dropdown;
}
.sidebar-move-issue-confirmation-button { .sidebar-move-issue-confirmation-button {
width: 100%; width: 100%;
......
...@@ -142,8 +142,6 @@ ul.related-merge-requests > li { ...@@ -142,8 +142,6 @@ ul.related-merge-requests > li {
} }
.issue-form { .issue-form {
@include new-style-dropdown;
.select2-container { .select2-container {
width: 250px !important; width: 250px !important;
} }
......
...@@ -116,8 +116,6 @@ ...@@ -116,8 +116,6 @@
} }
.manage-labels-list { .manage-labels-list {
@include new-style-dropdown;
> li:not(.empty-message):not(.is-not-draggable) { > li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light; background-color: $white-light;
cursor: move; cursor: move;
......
...@@ -58,8 +58,6 @@ ...@@ -58,8 +58,6 @@
} }
.member-form-control { .member-form-control {
@include new-style-dropdown;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding-bottom: 5px; padding-bottom: 5px;
margin-left: 0; margin-left: 0;
...@@ -73,8 +71,6 @@ ...@@ -73,8 +71,6 @@
} }
.member-search-form { .member-search-form {
@include new-style-dropdown;
position: relative; position: relative;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
......
...@@ -485,8 +485,6 @@ ...@@ -485,8 +485,6 @@
} }
.mr-source-target { .mr-source-target {
@include new-style-dropdown;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
...@@ -608,8 +606,6 @@ ...@@ -608,8 +606,6 @@
} }
.mr-version-controls { .mr-version-controls {
@include new-style-dropdown;
position: relative; position: relative;
background: $gray-light; background: $gray-light;
color: $gl-text-color; color: $gl-text-color;
...@@ -727,7 +723,3 @@ ...@@ -727,7 +723,3 @@
font-size: 16px; font-size: 16px;
} }
} }
.merge-request-form {
@include new-style-dropdown;
}
...@@ -23,8 +23,6 @@ ...@@ -23,8 +23,6 @@
.new-note, .new-note,
.note-edit-form { .note-edit-form {
.note-form-actions { .note-form-actions {
@include new-style-dropdown;
position: relative; position: relative;
margin: $gl-padding 0 0; margin: $gl-padding 0 0;
} }
......
...@@ -490,8 +490,6 @@ ul.notes { ...@@ -490,8 +490,6 @@ ul.notes {
} }
.note-actions { .note-actions {
@include new-style-dropdown;
align-self: flex-start; align-self: flex-start;
flex-shrink: 0; flex-shrink: 0;
display: inline-flex; display: inline-flex;
......
...@@ -14,7 +14,3 @@ ...@@ -14,7 +14,3 @@
font-size: 18px; font-size: 18px;
} }
} }
.notification-form {
@include new-style-dropdown;
}
...@@ -286,8 +286,6 @@ ...@@ -286,8 +286,6 @@
// Pipeline visualization // Pipeline visualization
.pipeline-actions { .pipeline-actions {
@include new-style-dropdown;
border-bottom: 0; border-bottom: 0;
} }
...@@ -703,9 +701,6 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -703,9 +701,6 @@ button.mini-pipeline-graph-dropdown-toggle {
} }
} }
@include new-style-dropdown('.big-pipeline-graph-dropdown-menu');
@include new-style-dropdown('.mini-pipeline-graph-dropdown-menu');
// dropdown content for big and mini pipeline // dropdown content for big and mini pipeline
.big-pipeline-graph-dropdown-menu, .big-pipeline-graph-dropdown-menu,
.mini-pipeline-graph-dropdown-menu { .mini-pipeline-graph-dropdown-menu {
...@@ -804,7 +799,6 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -804,7 +799,6 @@ button.mini-pipeline-graph-dropdown-toggle {
font-weight: normal; font-weight: normal;
line-height: $line-height-base; line-height: $line-height-base;
white-space: nowrap; white-space: nowrap;
border-radius: 3px;
.ci-job-name-component { .ci-job-name-component {
align-items: center; align-items: center;
......
...@@ -323,8 +323,6 @@ ...@@ -323,8 +323,6 @@
} }
.project-repo-buttons { .project-repo-buttons {
@include new-style-dropdown;
.project-action-button .dropdown-menu { .project-action-button .dropdown-menu {
max-height: 250px; max-height: 250px;
overflow-y: auto; overflow-y: auto;
...@@ -898,8 +896,6 @@ pre.light-well { ...@@ -898,8 +896,6 @@ pre.light-well {
.new-protected-branch, .new-protected-branch,
.new-protected-tag { .new-protected-tag {
@include new-style-dropdown;
label { label {
margin-top: 6px; margin-top: 6px;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
...@@ -919,8 +915,6 @@ pre.light-well { ...@@ -919,8 +915,6 @@ pre.light-well {
.protected-branches-list, .protected-branches-list,
.protected-tags-list { .protected-tags-list {
@include new-style-dropdown;
margin-bottom: 30px; margin-bottom: 30px;
.settings-message { .settings-message {
......
...@@ -116,11 +116,6 @@ input[type="checkbox"]:hover { ...@@ -116,11 +116,6 @@ input[type="checkbox"]:hover {
opacity: 0; opacity: 0;
display: block; display: block;
left: -5px; left: -5px;
padding: 0;
ul {
padding: 10px 0;
}
} }
.dropdown-content { .dropdown-content {
...@@ -185,8 +180,6 @@ input[type="checkbox"]:hover { ...@@ -185,8 +180,6 @@ input[type="checkbox"]:hover {
} }
.search-holder { .search-holder {
@include new-style-dropdown;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
......
...@@ -265,7 +265,3 @@ ...@@ -265,7 +265,3 @@
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
} }
.todos-filters {
@include new-style-dropdown;
}
.tree-holder { .tree-holder {
@include new-style-dropdown;
.nav-block { .nav-block {
margin: 10px 0; margin: 10px 0;
......
class Admin::GroupsController < Admin::ApplicationController class Admin::GroupsController < Admin::ApplicationController
include MembersPresentation
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update] before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index def index
...@@ -10,8 +12,10 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -10,8 +12,10 @@ class Admin::GroupsController < Admin::ApplicationController
def show def show
@group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id]) @group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
@members = @group.members.order("access_level DESC").page(params[:members_page]) @members = present_members(
@requesters = AccessRequestsFinder.new(@group).execute(current_user) @group.members.order("access_level DESC").page(params[:members_page]))
@requesters = present_members(
AccessRequestsFinder.new(@group).execute(current_user))
@projects = @group.projects.with_statistics.page(params[:projects_page]) @projects = @group.projects.with_statistics.page(params[:projects_page])
end end
......
class Admin::ProjectsController < Admin::ApplicationController class Admin::ProjectsController < Admin::ApplicationController
include MembersPresentation
before_action :project, only: [:show, :transfer, :repository_check] before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
...@@ -19,11 +21,14 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -19,11 +21,14 @@ class Admin::ProjectsController < Admin::ApplicationController
def show def show
if @group if @group
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]) @group_members = present_members(
@group.members.order("access_level DESC").page(params[:group_members_page]))
end end
@project_members = @project.members.page(params[:project_members_page]) @project_members = present_members(
@requesters = AccessRequestsFinder.new(@project).execute(current_user) @project.members.page(params[:project_members_page]))
@requesters = present_members(
AccessRequestsFinder.new(@project).execute(current_user))
end end
def transfer def transfer
......
module MembersPresentation
extend ActiveSupport::Concern
def present_members(members)
Gitlab::View::Presenter::Factory.new(
members,
current_user: current_user,
presenter_class: MembersPresenter
).fabricate!
end
end
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
include MembershipActions include MembershipActions
include MembersPresentation
include SortingHelper include SortingHelper
# Authorize # Authorize
...@@ -14,15 +15,17 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -14,15 +15,17 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = @members.search(params[:search]) if params[:search].present? @members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort) @members = @members.sort(@sort)
@members = @members.page(params[:page]).per(50) @members = @members.page(params[:page]).per(50)
@members.includes(:user) @members = present_members(@members.includes(:user))
@requesters = AccessRequestsFinder.new(@group).execute(current_user) @requesters = present_members(
AccessRequestsFinder.new(@group).execute(current_user))
@group_member = @group.group_members.new @group_member = @group.group_members.new
end end
def update def update
@group_member = @group.members_and_requesters.find(params[:id]) @group_member = @group.members_and_requesters.find(params[:id])
.present(current_user: current_user)
return render_403 unless can?(current_user, :update_group_member, @group_member) return render_403 unless can?(current_user, :update_group_member, @group_member)
......
...@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController
STATUS_POLLING_INTERVAL = 10_000 STATUS_POLLING_INTERVAL = 10_000
def index def index
@scope = params[:scope] || 'all' clusters = ClustersFinder.new(project, current_user, :all).execute
@clusters = ClustersFinder.new(project, current_user, @scope).execute.page(params[:page]) @clusters = clusters.page(params[:page]).per(20)
@active_count = ClustersFinder.new(project, current_user, :active).execute.count
@inactive_count = ClustersFinder.new(project, current_user, :inactive).execute.count
@all_count = @active_count + @inactive_count
end end
def new def new
......
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
include MembershipActions include MembershipActions
include MembersPresentation
include SortingHelper include SortingHelper
# Authorize # Authorize
...@@ -20,13 +21,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -20,13 +21,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end end
@project_members = @project_members.sort(@sort).page(params[:page]) @project_members = present_members(@project_members.sort(@sort).page(params[:page]))
@requesters = AccessRequestsFinder.new(@project).execute(current_user) @requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new @project_member = @project.project_members.new
end end
def update def update
@project_member = @project.members_and_requesters.find(params[:id]) @project_member = @project.members_and_requesters.find(params[:id])
.present(current_user: current_user)
return render_403 unless can?(current_user, :update_project_member, @project_member) return render_403 unless can?(current_user, :update_project_member, @project_member)
......
...@@ -118,18 +118,22 @@ module BlobHelper ...@@ -118,18 +118,22 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw") icon("#{file_type_icon_class('file', mode, name)} fw")
end end
def blob_raw_path def blob_raw_url(only_path: false)
if @build && @entry if @build && @entry
raw_project_job_artifacts_path(@project, @build, path: @entry.path) raw_project_job_artifacts_url(@project, @build, path: @entry.path, only_path: only_path)
elsif @snippet elsif @snippet
if @snippet.project_id if @snippet.project_id
raw_project_snippet_path(@project, @snippet) raw_project_snippet_url(@project, @snippet, only_path: only_path)
else else
raw_snippet_path(@snippet) raw_snippet_url(@snippet, only_path: only_path)
end end
elsif @blob elsif @blob
project_raw_path(@project, @id) project_raw_url(@project, @id, only_path: only_path)
end
end end
def blob_raw_path
blob_raw_url(only_path: true)
end end
# SVGs can contain malicious JavaScript; only include whitelisted # SVGs can contain malicious JavaScript; only include whitelisted
......
...@@ -104,15 +104,23 @@ module DiffHelper ...@@ -104,15 +104,23 @@ module DiffHelper
].join(' ').html_safe ].join(' ').html_safe
end end
def diff_file_blob_raw_path(diff_file) def diff_file_blob_raw_url(diff_file, only_path: false)
project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path)) project_raw_url(@project, tree_join(diff_file.content_sha, diff_file.file_path), only_path: only_path)
end end
def diff_file_old_blob_raw_path(diff_file) def diff_file_old_blob_raw_url(diff_file, only_path: false)
sha = diff_file.old_content_sha sha = diff_file.old_content_sha
return unless sha return unless sha
project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) project_raw_url(@project, tree_join(diff_file.old_content_sha, diff_file.old_path), only_path: only_path)
end
def diff_file_blob_raw_path(diff_file)
diff_file_blob_raw_url(diff_file, only_path: true)
end
def diff_file_old_blob_raw_path(diff_file)
diff_file_old_blob_raw_url(diff_file, only_path: true)
end end
def diff_file_html_data(project, diff_file_path, diff_commit_id) def diff_file_html_data(project, diff_file_path, diff_commit_id)
......
module LabelsHelper module LabelsHelper
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
return true if label.is_a?(GroupLabel)
return true unless project
project.feature_available?(issuables_type, current_user)
end
# Link to a Label # Link to a Label
# #
# label - Label object to link to # label - Label object to link to
......
module MembersHelper module MembersHelper
# Returns a `<action>_<source>_member` association, e.g.:
# - admin_project_member, update_project_member, destroy_project_member
# - admin_group_member, update_group_member, destroy_group_member
def action_member_permission(action, member)
"#{action}_#{member.type.underscore}".to_sym
end
def remove_member_message(member, user: nil) def remove_member_message(member, user: nil)
user = current_user if defined?(current_user) user = current_user if defined?(current_user)
......
...@@ -85,8 +85,7 @@ module CacheMarkdownField ...@@ -85,8 +85,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field) def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field) html_field = cached_markdown_fields.html_field(markdown_field)
cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false markdown_changed = attribute_changed?(markdown_field) || false
html_changed = attribute_changed?(html_field) || false html_changed = attribute_changed?(html_field) || false
......
...@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base ...@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base
include ThrottledTouch include ThrottledTouch
include IgnorableColumn include IgnorableColumn
ignore_column :assignee_id ignore_column :assignee_id, :branch_name
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
...@@ -4,6 +4,7 @@ class Member < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class Member < ActiveRecord::Base
include Importable include Importable
include Expirable include Expirable
include Gitlab::Access include Gitlab::Access
include Presentable
attr_accessor :raw_invite_token attr_accessor :raw_invite_token
......
...@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include ManualInverseAssociation include ManualInverseAssociation
include EachBatch include EachBatch
include ThrottledTouch include ThrottledTouch
include Gitlab::Utils::StrongMemoize
ignore_column :locked_at, ignore_column :locked_at,
:ref_fetched :ref_fetched
...@@ -53,6 +54,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -53,6 +54,7 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
after_update :clear_memoized_shas
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -387,14 +389,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -387,14 +389,18 @@ class MergeRequest < ActiveRecord::Base
end end
def source_branch_head def source_branch_head
return unless source_project strong_memoize(:source_branch_head) do
if source_project && source_branch_ref
source_project.repository.commit(source_branch_ref) if source_branch_ref source_project.repository.commit(source_branch_ref)
end
end
end end
def target_branch_head def target_branch_head
strong_memoize(:target_branch_head) do
target_project.repository.commit(target_branch_ref) target_project.repository.commit(target_branch_ref)
end end
end
def branch_merge_base_commit def branch_merge_base_commit
start_sha = target_branch_sha start_sha = target_branch_sha
...@@ -525,6 +531,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -525,6 +531,13 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def clear_memoized_shas
@target_branch_sha = @source_branch_sha = nil
clear_memoization(:source_branch_head)
clear_memoization(:target_branch_head)
end
def reload_diff_if_branch_changed def reload_diff_if_branch_changed
if (source_branch_changed? || target_branch_changed?) && if (source_branch_changed? || target_branch_changed?) &&
(source_branch_head && target_branch_head) (source_branch_head && target_branch_head)
......
...@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base
def base_commit def base_commit
return unless base_commit_sha return unless base_commit_sha
project.commit(base_commit_sha) project.commit_by(oid: base_commit_sha)
end end
def start_commit def start_commit
return unless start_commit_sha return unless start_commit_sha
project.commit(start_commit_sha) project.commit_by(oid: start_commit_sha)
end end
def head_commit def head_commit
return unless head_commit_sha return unless head_commit_sha
project.commit(head_commit_sha) project.commit_by(oid: head_commit_sha)
end end
def commit_shas def commit_shas
......
...@@ -401,6 +401,9 @@ class Note < ActiveRecord::Base ...@@ -401,6 +401,9 @@ class Note < ActiveRecord::Base
end end
noteable_object&.touch noteable_object&.touch
# We return the noteable object so we can re-use it in EE for ElasticSearch.
noteable_object
end end
def banzai_render_context(field) def banzai_render_context(field)
......
...@@ -659,7 +659,8 @@ class Project < ActiveRecord::Base ...@@ -659,7 +659,8 @@ class Project < ActiveRecord::Base
end end
def import_started? def import_started?
import? && import_status == 'started' # import? does SQL work so only run it if it looks like there's an import running
import_status == 'started' && import?
end end
def import_scheduled? def import_scheduled?
...@@ -1147,7 +1148,7 @@ class Project < ActiveRecord::Base ...@@ -1147,7 +1148,7 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
if repository.branch_exists?(branch) if repository.branch_exists?(branch)
repository.before_change_head repository.before_change_head
repository.write_ref('HEAD', "refs/heads/#{branch}") repository.write_ref('HEAD', "refs/heads/#{branch}", force: true)
repository.copy_gitattributes(branch) repository.copy_gitattributes(branch)
repository.after_change_head repository.after_change_head
reload_default_branch reload_default_branch
......
...@@ -19,6 +19,7 @@ class Repository ...@@ -19,6 +19,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository delegate :ref_name_for_sha, to: :raw_repository
delegate :write_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError) CreateTreeError = Class.new(StandardError)
...@@ -237,11 +238,10 @@ class Repository ...@@ -237,11 +238,10 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes) # This will still fail if the file is corrupted (e.g. 0 bytes)
begin begin
write_ref(keep_around_ref_name(sha), sha) write_ref(keep_around_ref_name(sha), sha, force: true)
rescue Rugged::ReferenceError => ex rescue Gitlab::Git::Repository::GitError => ex
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" # Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156
rescue Rugged::OSError => ex return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
end end
...@@ -251,10 +251,6 @@ class Repository ...@@ -251,10 +251,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha)) ref_exists?(keep_around_ref_name(sha))
end end
def write_ref(ref_path, sha)
rugged.references.create(ref_path, sha, force: true)
end
def diverging_commit_counts(branch) def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do cache.fetch(:"diverging_commit_counts_#{branch.name}") do
...@@ -971,8 +967,7 @@ class Repository ...@@ -971,8 +967,7 @@ class Repository
tmp_remote_name = true tmp_remote_name = true
end end
add_remote(remote_name, url) add_remote(remote_name, url, mirror_refmap: refmap)
set_remote_as_mirror(remote_name, refmap: refmap)
fetch_remote(remote_name, forced: forced) fetch_remote(remote_name, forced: forced)
ensure ensure
remove_remote(remote_name) if tmp_remote_name remove_remote(remote_name) if tmp_remote_name
...@@ -995,7 +990,7 @@ class Repository ...@@ -995,7 +990,7 @@ class Repository
end end
def create_ref(ref, ref_path) def create_ref(ref, ref_path)
raw_repository.write_ref(ref_path, ref) write_ref(ref_path, ref)
end end
def ls_files(ref) def ls_files(ref)
......
class GroupMemberPresenter < MemberPresenter
private
def admin_member_permission
:admin_group_member
end
def update_member_permission
:update_group_member
end
def destroy_member_permission
:destroy_group_member
end
end
class MemberPresenter < Gitlab::View::Presenter::Delegated
presents :member
def access_level_roles
member.class.access_level_roles
end
def can_resend_invite?
invite? &&
can?(current_user, admin_member_permission, source)
end
def can_update?
can?(current_user, update_member_permission, member)
end
def can_remove?
can?(current_user, destroy_member_permission, member)
end
def can_approve?
request? && can_update?
end
private
def admin_member_permission
raise NotImplementedError
end
def update_member_permission
raise NotImplementedError
end
def destroy_member_permission
raise NotImplementedError
end
end
class MembersPresenter < Gitlab::View::Presenter::Delegated
include Enumerable
presents :members
def to_ary
to_a
end
def each
members.each do |member|
yield member.present(current_user: current_user)
end
end
end
class ProjectMemberPresenter < MemberPresenter
private
def admin_member_permission
:admin_project_member
end
def update_member_permission
:update_project_member
end
def destroy_member_permission
:destroy_project_member
end
end
...@@ -38,11 +38,15 @@ module Ci ...@@ -38,11 +38,15 @@ module Ci
begin begin
# In case when 2 runners try to assign the same build, second runner will be declined # In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
begin
build.runner_id = runner.id build.runner_id = runner.id
build.run! build.run!
register_success(build) register_success(build)
return Result.new(build, true) return Result.new(build, true)
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
end
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
# We are looping to find another build that is not conflicting # We are looping to find another build that is not conflicting
# It also indicates that this build can be picked and passed to runner. # It also indicates that this build can be picked and passed to runner.
...@@ -54,9 +58,6 @@ module Ci ...@@ -54,9 +58,6 @@ module Ci
# we still have to return 409 in the end, # we still have to return 409 in the end,
# to make sure that this is properly handled by runner. # to make sure that this is properly handled by runner.
valid = false valid = false
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
valid = false
end end
end end
......
...@@ -35,8 +35,17 @@ module Members ...@@ -35,8 +35,17 @@ module Members
def can_update_access_requester?(access_requester, opts = {}) def can_update_access_requester?(access_requester, opts = {})
access_requester && ( access_requester && (
opts[:force] || opts[:force] ||
can?(current_user, action_member_permission(:update, access_requester), access_requester) can?(current_user, update_member_permission(access_requester), access_requester)
) )
end end
def update_member_permission(member)
case member
when GroupMember
:update_group_member
when ProjectMember
:update_project_member
end
end
end end
end end
...@@ -36,7 +36,16 @@ module Members ...@@ -36,7 +36,16 @@ module Members
end end
def can_destroy_member?(member) def can_destroy_member?(member)
member && can?(current_user, action_member_permission(:destroy, member), member) member && can?(current_user, destroy_member_permission(member), member)
end
def destroy_member_permission(member)
case member
when GroupMember
:destroy_group_member
when ProjectMember
:destroy_project_member
end
end end
end end
end end
...@@ -83,12 +83,12 @@ ...@@ -83,12 +83,12 @@
You're all done! You're all done!
- elsif current_user.todos.any? - elsif current_user.todos.any?
.todos-all-done .todos-all-done
.svg-content .svg-content.svg-250
= image_tag 'illustrations/todos_all_done.svg' = image_tag 'illustrations/todos_all_done.svg'
- if todos_filter_empty? - if todos_filter_empty?
%h4.text-center %h4.text-center
= Gitlab.config.gitlab.no_todos_messages.sample = Gitlab.config.gitlab.no_todos_messages.sample
%p.text-center %p
Are you looking for things to do? Take a look at Are you looking for things to do? Take a look at
= succeed "," do = succeed "," do
= link_to "the opened issues", issues_dashboard_path = link_to "the opened issues", issues_dashboard_path
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
= image_tag 'illustrations/todos_empty.svg' = image_tag 'illustrations/todos_empty.svg'
.todos-empty-content .todos-empty-content
%h4 %h4
Todos let you see what you should do next. Todos let you see what you should do next
%p %p
When an issue or merge request is assigned to you, or when you When an issue or merge request is assigned to you, or when you
%strong %strong
......
- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: @project.web_url, avatar_url: @project.avatar_url } if @project&.persisted? - project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
.projects-dropdown-container .projects-dropdown-container
.project-dropdown-sidebar .project-dropdown-sidebar
%ul %ul
......
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
Template Template
.template-selector-dropdowns-wrap .template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden .template-type-selector.js-template-type-selector-wrap.hidden
= dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } ) = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden .license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden .gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden .dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } ) = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
.template-selectors-undo-menu.hidden .template-selectors-undo-menu.hidden
%span.text-info Template applied %span.text-info Template applied
%button.btn.btn-sm.btn-info Undo %button.btn.btn-sm.btn-info Undo
.file-content.image_file .file-content.image_file
= image_tag(blob_raw_path, alt: viewer.blob.name) -# Uses the full URL rather than the path, to prevent it from getting prefixed with the asset host.
= image_tag(blob_raw_url, alt: viewer.blob.name)
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.col-sm-10.create-from .col-sm-10.create-from
.dropdown .dropdown
= hidden_field_tag :ref, default_ref = hidden_field_tag :ref, default_ref
= button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref .text-left.dropdown-toggle-text= default_ref
= icon('chevron-down') = icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide' = render 'shared/ref_dropdown', dropdown_class: 'wide'
......
.row.empty-state .row.empty-state
.col-xs-12 .col-xs-12
.svg-content= image_tag 'illustrations/clusters_empty.svg' .svg-content= image_tag 'illustrations/clusters_empty.svg'
.col-xs-12.text-center .col-xs-12
.text-content .text-content
%h4= s_('ClusterIntegration|Integrate cluster automation') %h4.text-center= s_('ClusterIntegration|Integrate cluster automation')
- link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page} %p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
%p .text-center
= link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success' = link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon("angle-left")
.fade-right= icon("angle-right")
%ul.nav-links.scrolling-tabs
%li{ class: ('active' if @scope == 'active') }>
= link_to project_clusters_path(@project, scope: :active), class: "js-active-tab" do
= s_("ClusterIntegration|Active")
%span.badge= @active_count
%li{ class: ('active' if @scope == 'inactive') }>
= link_to project_clusters_path(@project, scope: :inactive), class: "js-inactive-tab" do
= s_("ClusterIntegration|Inactive")
%span.badge= @inactive_count
%li{ class: ('active' if @scope.nil? || @scope == 'all') }>
= link_to project_clusters_path(@project), class: "js-all-tab" do
= s_("ClusterIntegration|All")
%span.badge= @all_count
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
- page_title "Clusters" - page_title "Clusters"
.clusters-container .clusters-container
- if !@clusters.empty? - if @clusters.empty?
= render "tabs" = render "empty_state"
- else
.top-area.adjust
.nav-text
= s_("ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project")
.ci-table.js-clusters-list .ci-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" } .gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
...@@ -16,9 +20,3 @@ ...@@ -16,9 +20,3 @@
- @clusters.each do |cluster| - @clusters.each do |cluster|
= render "cluster", cluster: cluster.present(current_user: current_user) = render "cluster", cluster: cluster.present(current_user: current_user)
= paginate @clusters, theme: "gitlab" = paginate @clusters, theme: "gitlab"
- elsif @scope == 'all'
= render "empty_state"
- else
= render "tabs"
.prepend-top-20.text-center
= s_("ClusterIntegration|There are no clusters to show")
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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