summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorDouglas Barbosa Alexandre <dbalexandre@gmail.com>2019-07-09 14:45:46 -0300
committerDouglas Barbosa Alexandre <dbalexandre@gmail.com>2019-07-09 14:45:46 -0300
commit2615265ef82e07ea36df67f6d1a11ad4a731ad63 (patch)
tree92c1d3ab69f88e1dceed11ae886e1acb6ae65f1e /app/assets
parent203ef336392befd907c335a4a851ec0794d5e91a (diff)
parent48c19d9e296b2bec3663f46b8d936da2082171b1 (diff)
downloadgitlab-ce-2615265ef82e07ea36df67f6d1a11ad4a731ad63.tar.gz
Merge branch 'master' into sathieu/gitlab-ce-project_api
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/images/auth_buttons/salesforce_64.pngbin0 -> 8774 bytes
-rw-r--r--app/assets/javascripts/api.js6
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue8
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js3
-rw-r--r--app/assets/javascripts/boards/index.js8
-rw-r--r--app/assets/javascripts/boards/models/issue.js6
-rw-r--r--app/assets/javascripts/boards/models/list.js8
-rw-r--r--app/assets/javascripts/boards/models/milestone.js4
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js2
-rw-r--r--app/assets/javascripts/boards/services/board_service.js76
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js111
-rw-r--r--app/assets/javascripts/branches/divergence_graph.js4
-rw-r--r--app/assets/javascripts/commons/index.js3
-rw-r--r--app/assets/javascripts/commons/nav/user_merge_requests.js67
-rw-r--r--app/assets/javascripts/confidential_merge_request/components/dropdown.vue58
-rw-r--r--app/assets/javascripts/confidential_merge_request/components/project_form_group.vue132
-rw-r--r--app/assets/javascripts/confidential_merge_request/index.js30
-rw-r--r--app/assets/javascripts/confidential_merge_request/state.js5
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js47
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue6
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue1
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue12
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue2
-rw-r--r--app/assets/javascripts/diffs/mixins/draft_comments.js2
-rw-r--r--app/assets/javascripts/event_tracking/notes.js1
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue4
-rw-r--r--app/assets/javascripts/ide/lib/files.js5
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js26
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js4
-rw-r--r--app/assets/javascripts/issuable_index.js17
-rw-r--r--app/assets/javascripts/issuable_init_bulk_update_sidebar.js19
-rw-r--r--app/assets/javascripts/labels_select.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js8
-rw-r--r--app/assets/javascripts/main.js6
-rw-r--r--app/assets/javascripts/main_ee.js1
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue17
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue65
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js17
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue54
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue8
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue27
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue16
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue32
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_signed_out_widget.vue19
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue20
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue18
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue3
-rw-r--r--app/assets/javascripts/notes/index.js5
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js4
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js4
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue10
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees.vue29
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue19
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue19
-rw-r--r--app/assets/javascripts/sidebar/components/todo_toggle/todo.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue43
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue20
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue25
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue3
-rw-r--r--app/assets/javascripts/vue_shared/mixins/is_ee.js10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss15
-rw-r--r--app/assets/stylesheets/pages/wiki.scss4
100 files changed, 1016 insertions, 406 deletions
diff --git a/app/assets/images/auth_buttons/salesforce_64.png b/app/assets/images/auth_buttons/salesforce_64.png
new file mode 100644
index 00000000000..c8a86a0c515
--- /dev/null
+++ b/app/assets/images/auth_buttons/salesforce_64.png
Binary files differ
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 4f66a5d080c..a649c521405 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -24,6 +24,7 @@ const Api = {
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
+ userCountsPath: '/api/:version/user_counts',
usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status',
@@ -312,6 +313,11 @@ const Api = {
});
},
+ userCounts() {
+ const url = Api.buildUrl(this.userCountsPath);
+ return axios.get(url);
+ },
+
userStatus(id, options) {
const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index f58149c9f7b..d8b0b60c183 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -61,7 +61,7 @@ export default {
<div class="board-blank-state p-3">
<p>
{{
- __('BoardBlankState|Add the following default lists to your Issue Board with one click:')
+ s__('BoardBlankState|Add the following default lists to your Issue Board with one click:')
}}
</p>
<ul class="list-unstyled board-blank-state-list">
@@ -76,7 +76,7 @@ export default {
</ul>
<p>
{{
- __(
+ s__(
'BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board.',
)
}}
@@ -86,10 +86,10 @@ export default {
type="button"
@click.stop="addDefaultLists"
>
- {{ __('BoardBlankState|Add default lists') }}
+ {{ s__('BoardBlankState|Add default lists') }}
</button>
<button class="btn btn-default btn-block" type="button" @click.stop="clearBlankState">
- {{ __("BoardBlankState|Nevermind, I'll use my own") }}
+ {{ s__("BoardBlankState|Nevermind, I'll use my own") }}
</button>
</div>
</template>
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 6b54e8baefb..b1b4b1c5508 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -2,7 +2,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable
import FilteredSearchContainer from '../filtered_search/container';
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
import boardsStore from './stores/boards_store';
-import { isEE } from '~/lib/utils/common_utils';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
@@ -10,7 +9,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
page: 'boards',
isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
- isGroup: isEE(),
+ isGroup: IS_EE,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index a020765f335..23b107abefa 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import Vue from 'vue';
+import mountMultipleBoardsSwitcher from 'ee_else_ce/boards/mount_multiple_boards_switcher';
import Flash from '~/flash';
import { __ } from '~/locale';
import './models/label';
@@ -20,7 +21,7 @@ import modalMixin from './mixins/modal_mixins';
import './filters/due_date_filters';
import Board from './components/board';
import BoardSidebar from './components/board_sidebar';
-import initNewListDropdown from './components/new_list_dropdown';
+import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import {
@@ -78,13 +79,14 @@ export default () => {
},
},
created() {
- gl.boardService = new BoardService({
+ boardsStore.setEndpoints({
boardsEndpoint: this.boardsEndpoint,
recentBoardsEndpoint: this.recentBoardsEndpoint,
listsEndpoint: this.listsEndpoint,
bulkUpdatePath: this.bulkUpdatePath,
boardId: this.boardId,
});
+ gl.boardService = new BoardService();
boardsStore.rootPath = this.boardsEndpoint;
eventHub.$on('updateTokens', this.updateTokens);
@@ -278,4 +280,6 @@ export default () => {
`,
});
}
+
+ mountMultipleBoardsSwitcher();
};
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index f858b162c6b..9069b35db9a 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -5,7 +5,7 @@
import Vue from 'vue';
import './label';
-import { isEE, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssueProject from './project';
import boardsStore from '../stores/boards_store';
@@ -91,13 +91,13 @@ class ListIssue {
addMilestone(milestone) {
const miletoneId = this.milestone ? this.milestone.id : null;
- if (isEE && milestone.id !== miletoneId) {
+ if (IS_EE && milestone.id !== miletoneId) {
this.milestone = new ListMilestone(milestone);
}
}
removeMilestone(removeMilestone) {
- if (isEE && removeMilestone && removeMilestone.id === this.milestone.id) {
+ if (IS_EE && removeMilestone && removeMilestone.id === this.milestone.id) {
this.milestone = {};
}
}
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index cd553d0c4af..7e0ccb9bd2a 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -4,7 +4,7 @@
import { __ } from '~/locale';
import ListLabel from './label';
import ListAssignee from './assignee';
-import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
+import { urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
import ListMilestone from './milestone';
@@ -58,7 +58,7 @@ class List {
} else if (obj.user) {
this.assignee = new ListAssignee(obj.user);
this.title = this.assignee.name;
- } else if (isEE && obj.milestone) {
+ } else if (IS_EE && obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
this.title = this.milestone.title;
}
@@ -85,7 +85,7 @@ class List {
entityType = 'label_id';
} else if (this.assignee) {
entityType = 'assignee_id';
- } else if (isEE && this.milestone) {
+ } else if (IS_EE && this.milestone) {
entityType = 'milestone_id';
}
@@ -205,7 +205,7 @@ class List {
issue.addAssignee(this.assignee);
}
- if (isEE && this.milestone) {
+ if (IS_EE && this.milestone) {
if (listFrom && listFrom.type === 'milestone') {
issue.removeMilestone(listFrom.milestone);
}
diff --git a/app/assets/javascripts/boards/models/milestone.js b/app/assets/javascripts/boards/models/milestone.js
index 6f81d6bc6f8..7201b6e91f5 100644
--- a/app/assets/javascripts/boards/models/milestone.js
+++ b/app/assets/javascripts/boards/models/milestone.js
@@ -1,11 +1,9 @@
-import { isEE } from '~/lib/utils/common_utils';
-
export default class ListMilestone {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
- if (isEE) {
+ if (IS_EE) {
this.path = obj.path;
this.state = obj.state;
this.webUrl = obj.web_url || obj.webUrl;
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
new file mode 100644
index 00000000000..bdb14a7f2f2
--- /dev/null
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -0,0 +1,2 @@
+// this will be moved from EE to CE as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/53811
+export default () => {};
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index 7d463f17ab1..580d04a3649 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -1,106 +1,66 @@
-import axios from '../../lib/utils/axios_utils';
-import { mergeUrlParams } from '../../lib/utils/url_utility';
+/* eslint-disable class-methods-use-this */
-export default class BoardService {
- constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
- this.boardsEndpoint = boardsEndpoint;
- this.boardId = boardId;
- this.listsEndpoint = listsEndpoint;
- this.listsEndpointGenerate = `${listsEndpoint}/generate.json`;
- this.bulkUpdatePath = bulkUpdatePath;
- this.recentBoardsEndpoint = `${recentBoardsEndpoint}.json`;
- }
+import boardsStore from '~/boards/stores/boards_store';
+export default class BoardService {
generateBoardsPath(id) {
- return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`;
+ return boardsStore.generateBoardsPath(id);
}
generateIssuesPath(id) {
- return `${this.listsEndpoint}${id ? `/${id}` : ''}/issues`;
+ return boardsStore.generateIssuesPath(id);
}
static generateIssuePath(boardId, id) {
- return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
- id ? `/${id}` : ''
- }`;
+ return boardsStore.generateIssuePath(boardId, id);
}
all() {
- return axios.get(this.listsEndpoint);
+ return boardsStore.all();
}
generateDefaultLists() {
- return axios.post(this.listsEndpointGenerate, {});
+ return boardsStore.generateDefaultLists();
}
createList(entityId, entityType) {
- const list = {
- [entityType]: entityId,
- };
-
- return axios.post(this.listsEndpoint, {
- list,
- });
+ return boardsStore.createList(entityId, entityType);
}
updateList(id, position) {
- return axios.put(`${this.listsEndpoint}/${id}`, {
- list: {
- position,
- },
- });
+ return boardsStore.updateList(id, position);
}
destroyList(id) {
- return axios.delete(`${this.listsEndpoint}/${id}`);
+ return boardsStore.destroyList(id);
}
getIssuesForList(id, filter = {}) {
- const data = { id };
- Object.keys(filter).forEach(key => {
- data[key] = filter[key];
- });
-
- return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
+ return boardsStore.getIssuesForList(id, filter);
}
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
- return axios.put(BoardService.generateIssuePath(this.boardId, id), {
- from_list_id: fromListId,
- to_list_id: toListId,
- move_before_id: moveBeforeId,
- move_after_id: moveAfterId,
- });
+ return boardsStore.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId);
}
newIssue(id, issue) {
- return axios.post(this.generateIssuesPath(id), {
- issue,
- });
+ return boardsStore.newIssue(id, issue);
}
getBacklog(data) {
- return axios.get(
- mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`),
- );
+ return boardsStore.getBacklog(data);
}
bulkUpdate(issueIds, extraData = {}) {
- const data = {
- update: Object.assign(extraData, {
- issuable_ids: issueIds.join(','),
- }),
- };
-
- return axios.post(this.bulkUpdatePath, data);
+ return boardsStore.bulkUpdate(issueIds, extraData);
}
static getIssueInfo(endpoint) {
- return axios.get(endpoint);
+ return boardsStore.getIssueInfo(endpoint);
}
static toggleIssueSubscription(endpoint) {
- return axios.post(endpoint);
+ return boardsStore.toggleIssueSubscription(endpoint);
}
}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 4ba4cde6bae..b9cd4a143ef 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -8,6 +8,8 @@ import Cookies from 'js-cookie';
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../eventhub';
const boardsStore = {
@@ -28,6 +30,7 @@ const boardsStore = {
},
currentPage: '',
reload: false,
+ endpoints: {},
},
detail: {
issue: {},
@@ -36,6 +39,19 @@ const boardsStore = {
issue: {},
list: {},
},
+
+ setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
+ const listsEndpointGenerate = `${listsEndpoint}/generate.json`;
+ this.state.endpoints = {
+ boardsEndpoint,
+ boardId,
+ listsEndpoint,
+ listsEndpointGenerate,
+ bulkUpdatePath,
+ recentBoardsEndpoint: `${recentBoardsEndpoint}.json`,
+ };
+ },
+
create() {
this.state.lists = [];
this.filter.path = getUrlParamsArray().join('&');
@@ -229,6 +245,101 @@ const boardsStore = {
setTimeTrackingLimitToHours(limitToHours) {
this.timeTracking.limitToHours = parseBoolean(limitToHours);
},
+
+ generateBoardsPath(id) {
+ return `${this.state.endpoints.boardsEndpoint}${id ? `/${id}` : ''}.json`;
+ },
+
+ generateIssuesPath(id) {
+ return `${this.state.endpoints.listsEndpoint}${id ? `/${id}` : ''}/issues`;
+ },
+
+ generateIssuePath(boardId, id) {
+ return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
+ id ? `/${id}` : ''
+ }`;
+ },
+
+ all() {
+ return axios.get(this.state.endpoints.listsEndpoint);
+ },
+
+ generateDefaultLists() {
+ return axios.post(this.state.endpoints.listsEndpointGenerate, {});
+ },
+
+ createList(entityId, entityType) {
+ const list = {
+ [entityType]: entityId,
+ };
+
+ return axios.post(this.state.endpoints.listsEndpoint, {
+ list,
+ });
+ },
+
+ updateList(id, position) {
+ return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, {
+ list: {
+ position,
+ },
+ });
+ },
+
+ destroyList(id) {
+ return axios.delete(`${this.state.endpoints.listsEndpoint}/${id}`);
+ },
+
+ getIssuesForList(id, filter = {}) {
+ const data = { id };
+ Object.keys(filter).forEach(key => {
+ data[key] = filter[key];
+ });
+
+ return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
+ },
+
+ moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
+ return axios.put(this.generateIssuePath(this.state.endpoints.boardId, id), {
+ from_list_id: fromListId,
+ to_list_id: toListId,
+ move_before_id: moveBeforeId,
+ move_after_id: moveAfterId,
+ });
+ },
+
+ newIssue(id, issue) {
+ return axios.post(this.generateIssuesPath(id), {
+ issue,
+ });
+ },
+
+ getBacklog(data) {
+ return axios.get(
+ mergeUrlParams(
+ data,
+ `${gon.relative_url_root}/-/boards/${this.state.endpoints.boardId}/issues.json`,
+ ),
+ );
+ },
+
+ bulkUpdate(issueIds, extraData = {}) {
+ const data = {
+ update: Object.assign(extraData, {
+ issuable_ids: issueIds.join(','),
+ }),
+ };
+
+ return axios.post(this.state.endpoints.bulkUpdatePath, data);
+ },
+
+ getIssueInfo(endpoint) {
+ return axios.get(endpoint);
+ },
+
+ toggleIssueSubscription(endpoint) {
+ return axios.post(endpoint);
+ },
};
BoardsStoreEE.initEESpecific(boardsStore);
diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js
index 96bc6a5f8e8..7dbaf984acf 100644
--- a/app/assets/javascripts/branches/divergence_graph.js
+++ b/app/assets/javascripts/branches/divergence_graph.js
@@ -36,7 +36,9 @@ export default endpoint => {
}, 100);
Object.entries(data).forEach(([branchName, val]) => {
- const el = document.querySelector(`.js-branch-${branchName} .js-branch-divergence-graph`);
+ const el = document.querySelector(
+ `[data-name="${branchName}"] .js-branch-divergence-graph`,
+ );
if (!el) return;
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 0d2fe2925d8..ad0f6cc1496 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -4,3 +4,6 @@ import './jquery';
import './bootstrap';
import './vue';
import '../lib/utils/axios_utils';
+import { openUserCountsBroadcast } from './nav/user_merge_requests';
+
+openUserCountsBroadcast();
diff --git a/app/assets/javascripts/commons/nav/user_merge_requests.js b/app/assets/javascripts/commons/nav/user_merge_requests.js
new file mode 100644
index 00000000000..8e694cca6a1
--- /dev/null
+++ b/app/assets/javascripts/commons/nav/user_merge_requests.js
@@ -0,0 +1,67 @@
+import Api from '~/api';
+
+let channel;
+
+function broadcastCount(newCount) {
+ if (!channel) {
+ return;
+ }
+
+ channel.postMessage(newCount);
+}
+
+function updateUserMergeRequestCounts(newCount) {
+ const mergeRequestsCountEl = document.querySelector('.merge-requests-count');
+ mergeRequestsCountEl.textContent = newCount.toLocaleString();
+ mergeRequestsCountEl.classList.toggle('hidden', Number(newCount) === 0);
+}
+
+/**
+ * Refresh user counts (and broadcast if open)
+ */
+export function refreshUserMergeRequestCounts() {
+ return Api.userCounts()
+ .then(({ data }) => {
+ const count = data.merge_requests;
+
+ updateUserMergeRequestCounts(count);
+ broadcastCount(count);
+ })
+ .catch(ex => {
+ console.error(ex); // eslint-disable-line no-console
+ });
+}
+
+/**
+ * Close the broadcast channel for user counts
+ */
+export function closeUserCountsBroadcast() {
+ if (!channel) {
+ return;
+ }
+
+ channel.close();
+ channel = null;
+}
+
+/**
+ * Open the broadcast channel for user counts, adds user id so we only update
+ *
+ * **Please note:**
+ * Not supported in all browsers, but not polyfilling for now
+ * to keep bundle size small and
+ * no special functionality lost except cross tab notifications
+ */
+export function openUserCountsBroadcast() {
+ closeUserCountsBroadcast();
+
+ if (window.BroadcastChannel) {
+ const currentUserId = typeof gon !== 'undefined' && gon && gon.current_user_id;
+ if (currentUserId) {
+ channel = new BroadcastChannel(`mr_count_channel_${currentUserId}`);
+ channel.onmessage = ev => {
+ updateUserMergeRequestCounts(ev.data);
+ };
+ }
+ }
+}
diff --git a/app/assets/javascripts/confidential_merge_request/components/dropdown.vue b/app/assets/javascripts/confidential_merge_request/components/dropdown.vue
new file mode 100644
index 00000000000..444640980af
--- /dev/null
+++ b/app/assets/javascripts/confidential_merge_request/components/dropdown.vue
@@ -0,0 +1,58 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ Icon,
+ },
+ props: {
+ projects: {
+ type: Array,
+ required: true,
+ },
+ selectedProject: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ dropdownText() {
+ if (Object.keys(this.selectedProject).length) {
+ return this.selectedProject.name;
+ }
+
+ return __('Select private project');
+ },
+ },
+ methods: {
+ selectProject(project) {
+ this.$emit('click', project);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown toggle-class="d-flex align-items-center w-100" class="w-100">
+ <template slot="button-content">
+ <span class="str-truncated-100 mr-2">
+ <icon name="lock" />
+ {{ dropdownText }}
+ </span>
+ <icon name="chevron-down" class="ml-auto" />
+ </template>
+ <gl-dropdown-item v-for="project in projects" :key="project.id" @click="selectProject(project)">
+ <icon
+ name="mobile-issue-close"
+ :class="{ icon: project.id !== selectedProject.id }"
+ class="js-active-project-check"
+ />
+ <span class="ml-1">{{ project.name }}</span>
+ </gl-dropdown-item>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
new file mode 100644
index 00000000000..99d77a75c23
--- /dev/null
+++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
@@ -0,0 +1,132 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+import { __, sprintf } from '../../locale';
+import createFlash from '../../flash';
+import Api from '../../api';
+import state from '../state';
+import Dropdown from './dropdown.vue';
+
+export default {
+ components: {
+ GlLink,
+ Dropdown,
+ },
+ props: {
+ namespacePath: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ newForkPath: {
+ type: String,
+ required: true,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ projects: [],
+ };
+ },
+ computed: {
+ selectedProject() {
+ return state.selectedProject;
+ },
+ noForkText() {
+ return sprintf(
+ __(
+ "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.",
+ ),
+ { link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
+ false,
+ );
+ },
+ },
+ mounted() {
+ this.fetchProjects();
+ this.createBtn = document.querySelector('.js-create-target');
+ this.warningText = document.querySelector('.js-exposed-info-warning');
+ },
+ methods: {
+ selectProject(project) {
+ if (project) {
+ Object.assign(state, {
+ selectedProject: project,
+ });
+
+ if (project.namespaceFullPath !== this.namespacePath) {
+ this.showWarning();
+ }
+ } else if (this.createBtn) {
+ this.createBtn.setAttribute('disabled', 'disabled');
+ }
+ },
+ normalizeProjectData(data) {
+ return data.map(p => ({
+ id: p.id,
+ name: p.name_with_namespace,
+ pathWithNamespace: p.path_with_namespace,
+ namespaceFullpath: p.namespace.full_path,
+ }));
+ },
+ fetchProjects() {
+ Api.projectForks(this.projectPath, {
+ with_merge_requests_enabled: true,
+ min_access_level: 30,
+ visibility: 'private',
+ })
+ .then(({ data }) => {
+ this.projects = this.normalizeProjectData(data);
+ this.selectProject(this.projects[0]);
+ })
+ .catch(e => {
+ createFlash(__('Error fetching forked projects. Please try again.'));
+ throw e;
+ });
+ },
+ showWarning() {
+ if (this.warningText) {
+ this.warningText.classList.remove('hidden');
+ }
+
+ if (this.createBtn) {
+ this.createBtn.classList.add('btn-warning');
+ this.createBtn.classList.remove('btn-success');
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="form-group">
+ <label>{{ __('Project') }}</label>
+ <div>
+ <dropdown
+ v-if="projects.length"
+ :projects="projects"
+ :selected-project="selectedProject"
+ @click="selectProject"
+ />
+ <p class="text-muted mt-1 mb-0">
+ <template v-if="projects.length">
+ {{
+ __(
+ "To protect this issue's confidentiality, a private fork of this project was selected.",
+ )
+ }}
+ </template>
+ <template v-else>
+ {{ __('No forks available to you.') }}<br />
+ <span v-html="noForkText"></span>
+ </template>
+ </p>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/confidential_merge_request/index.js b/app/assets/javascripts/confidential_merge_request/index.js
new file mode 100644
index 00000000000..9672821d30e
--- /dev/null
+++ b/app/assets/javascripts/confidential_merge_request/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import { parseBoolean } from '../lib/utils/common_utils';
+import ProjectFormGroup from './components/project_form_group.vue';
+import state from './state';
+
+export function isConfidentialIssue() {
+ return parseBoolean(document.querySelector('.js-create-mr').dataset.isConfidential);
+}
+
+export function canCreateConfidentialMergeRequest() {
+ return isConfidentialIssue() && Object.keys(state.selectedProject).length > 0;
+}
+
+export function init() {
+ const el = document.getElementById('js-forked-project');
+
+ return new Vue({
+ el,
+ render(h) {
+ return h(ProjectFormGroup, {
+ props: {
+ namespacePath: el.dataset.namespacePath,
+ projectPath: el.dataset.projectPath,
+ newForkPath: el.dataset.newForkPath,
+ helpPagePath: el.dataset.helpPagePath,
+ },
+ });
+ },
+ });
+}
diff --git a/app/assets/javascripts/confidential_merge_request/state.js b/app/assets/javascripts/confidential_merge_request/state.js
new file mode 100644
index 00000000000..95b0580f4b9
--- /dev/null
+++ b/app/assets/javascripts/confidential_merge_request/state.js
@@ -0,0 +1,5 @@
+import Vue from 'vue';
+
+export default Vue.observable({
+ selectedProject: {},
+});
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index 8f5cece0788..052168bb21c 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -5,6 +5,12 @@ import Flash from './flash';
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
import { __, sprintf } from './locale';
+import {
+ init as initConfidentialMergeRequest,
+ isConfidentialIssue,
+ canCreateConfidentialMergeRequest,
+} from './confidential_merge_request';
+import confidentialMergeRequestState from './confidential_merge_request/state';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
@@ -12,6 +18,17 @@ const InputSetter = Object.assign({}, ISetter);
const CREATE_MERGE_REQUEST = 'create-mr';
const CREATE_BRANCH = 'create-branch';
+function createEndpoint(projectPath, endpoint) {
+ if (canCreateConfidentialMergeRequest()) {
+ return endpoint.replace(
+ projectPath,
+ confidentialMergeRequestState.selectedProject.pathWithNamespace,
+ );
+ }
+
+ return endpoint;
+}
+
export default class CreateMergeRequestDropdown {
constructor(wrapperEl) {
this.wrapperEl = wrapperEl;
@@ -42,6 +59,8 @@ export default class CreateMergeRequestDropdown {
this.refIsValid = true;
this.refsPath = this.wrapperEl.dataset.refsPath;
this.suggestedRef = this.refInput.value;
+ this.projectPath = this.wrapperEl.dataset.projectPath;
+ this.projectId = this.wrapperEl.dataset.projectId;
// These regexps are used to replace
// a backend generated new branch name and its source (ref)
@@ -58,6 +77,14 @@ export default class CreateMergeRequestDropdown {
};
this.init();
+
+ if (isConfidentialIssue()) {
+ this.createMergeRequestButton.setAttribute(
+ 'data-dropdown-trigger',
+ '#create-merge-request-dropdown',
+ );
+ initConfidentialMergeRequest();
+ }
}
available() {
@@ -113,7 +140,9 @@ export default class CreateMergeRequestDropdown {
this.isCreatingBranch = true;
return axios
- .post(this.createBranchPath)
+ .post(createEndpoint(this.projectPath, this.createBranchPath), {
+ confidential_issue_project_id: canCreateConfidentialMergeRequest() ? this.projectId : null,
+ })
.then(({ data }) => {
this.branchCreated = true;
window.location.href = data.url;
@@ -125,7 +154,11 @@ export default class CreateMergeRequestDropdown {
this.isCreatingMergeRequest = true;
return axios
- .post(this.createMrPath)
+ .post(this.createMrPath, {
+ target_project_id: canCreateConfidentialMergeRequest()
+ ? confidentialMergeRequestState.selectedProject.id
+ : null,
+ })
.then(({ data }) => {
this.mergeRequestCreated = true;
window.location.href = data.url;
@@ -149,6 +182,8 @@ export default class CreateMergeRequestDropdown {
}
enable() {
+ if (!canCreateConfidentialMergeRequest()) return;
+
this.createMergeRequestButton.classList.remove('disabled');
this.createMergeRequestButton.removeAttribute('disabled');
@@ -205,7 +240,7 @@ export default class CreateMergeRequestDropdown {
if (!ref) return false;
return axios
- .get(`${this.refsPath}${encodeURIComponent(ref)}`)
+ .get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`)
.then(({ data }) => {
const branches = data[Object.keys(data)[0]];
const tags = data[Object.keys(data)[1]];
@@ -325,6 +360,12 @@ export default class CreateMergeRequestDropdown {
let xhr = null;
event.preventDefault();
+ if (isConfidentialIssue() && !event.target.classList.contains('js-create-target')) {
+ this.droplab.hooks.forEach(hook => hook.list.toggle());
+
+ return;
+ }
+
if (this.isBusy()) {
return;
}
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index ca3285e9afd..a06dbd70ac5 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -24,6 +24,11 @@ export default {
required: false,
default: '',
},
+ hasDraft: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
className() {
@@ -55,6 +60,7 @@ export default {
:help-page-path="helpPagePath"
/>
<diff-discussion-reply
+ v-if="!hasDraft"
:has-form="line.hasForm"
:render-reply-placeholder="Boolean(line.discussions.length)"
@showNewDiscussionForm="
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index 8c76a555b62..b2bc3d9914f 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -57,6 +57,7 @@ export default {
:diff-file-hash="diffFile.file_hash"
:line="line"
:help-page-path="helpPagePath"
+ :has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false"
/>
<inline-draft-comment-row
v-if="shouldRenderDraftRow(diffFile.file_hash, line)"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index c00b0e010ff..65b41b0e456 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -28,6 +28,16 @@ export default {
required: false,
default: '',
},
+ hasDraftLeft: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ hasDraftRight: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
hasExpandedDiscussionOnLeft() {
@@ -121,6 +131,7 @@ export default {
/>
</div>
<diff-discussion-reply
+ v-if="!hasDraftLeft"
:has-form="showLeftSideCommentForm"
:render-reply-placeholder="shouldRenderReplyPlaceholderOnLeft"
@showNewDiscussionForm="showNewDiscussionForm"
@@ -145,6 +156,7 @@ export default {
/>
</div>
<diff-discussion-reply
+ v-if="!hasDraftRight"
:has-form="showRightSideCommentForm"
:render-reply-placeholder="shouldRenderReplyPlaceholderOnRight"
@showNewDiscussionForm="showNewDiscussionForm"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 41a80d99850..c477e68c33c 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -58,6 +58,8 @@ export default {
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
+ :has-draft-left="hasParallelDraftLeft(diffFile.file_hash, line) || false"
+ :has-draft-right="hasParallelDraftRight(diffFile.file_hash, line) || false"
/>
<parallel-draft-comment-row
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
diff --git a/app/assets/javascripts/diffs/mixins/draft_comments.js b/app/assets/javascripts/diffs/mixins/draft_comments.js
index dfb71bf38ce..b6c9b132aeb 100644
--- a/app/assets/javascripts/diffs/mixins/draft_comments.js
+++ b/app/assets/javascripts/diffs/mixins/draft_comments.js
@@ -6,5 +6,7 @@ export default {
imageDiscussions() {
return this.diffFile.discussions;
},
+ hasParallelDraftLeft: () => () => false,
+ hasParallelDraftRight: () => () => false,
},
};
diff --git a/app/assets/javascripts/event_tracking/notes.js b/app/assets/javascripts/event_tracking/notes.js
index 2d1ec238274..1f70290c397 100644
--- a/app/assets/javascripts/event_tracking/notes.js
+++ b/app/assets/javascripts/event_tracking/notes.js
@@ -1 +1,2 @@
+// Noop function which has a EE counter-part
export default () => {};
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 5fcb11a232e..03756a634d5 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -144,7 +144,9 @@ export default {
'triggerFilesChange',
]),
initEditor() {
- if (this.shouldHideEditor) return;
+ if (this.shouldHideEditor && (this.file.content || this.file.raw)) {
+ return;
+ }
this.editor.clearEditor();
diff --git a/app/assets/javascripts/ide/lib/files.js b/app/assets/javascripts/ide/lib/files.js
index b8abaa41f23..51278640b5b 100644
--- a/app/assets/javascripts/ide/lib/files.js
+++ b/app/assets/javascripts/ide/lib/files.js
@@ -77,6 +77,7 @@ export const decorateFiles = ({
const fileFolder = parent && insertParent(parent);
if (name) {
+ const previewMode = viewerInformationForPath(name);
parentPath = fileFolder && fileFolder.path;
file = decorateData({
@@ -92,9 +93,9 @@ export const decorateFiles = ({
changed: tempFile,
content,
base64,
- binary,
+ binary: (previewMode && previewMode.binary) || binary,
rawPath,
- previewMode: viewerInformationForPath(name),
+ previewMode,
parentPath,
});
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index c88244492e0..a52f1e235ed 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -1,6 +1,7 @@
import * as types from '../mutation_types';
import { sortTree } from '../utils';
import { diffModes } from '../../constants';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export default {
[types.SET_FILE_ACTIVE](state, { path, active }) {
@@ -35,19 +36,18 @@ export default {
}
},
[types.SET_FILE_DATA](state, { data, file }) {
- Object.assign(state.entries[file.path], {
- id: data.id,
- blamePath: data.blame_path,
- commitsPath: data.commits_path,
- permalink: data.permalink,
- rawPath: data.raw_path,
- binary: data.binary,
- renderError: data.render_error,
- raw: (state.entries[file.path] && state.entries[file.path].raw) || null,
- baseRaw: null,
- html: data.html,
- size: data.size,
- lastCommitSha: data.last_commit_sha,
+ const stateEntry = state.entries[file.path];
+ const stagedFile = state.stagedFiles.find(f => f.path === file.path);
+ const openFile = state.openFiles.find(f => f.path === file.path);
+ const changedFile = state.changedFiles.find(f => f.path === file.path);
+
+ [stateEntry, stagedFile, openFile, changedFile].forEach(f => {
+ if (f) {
+ Object.assign(f, convertObjectPropsToCamelCase(data, { dropKeys: ['raw', 'baseRaw'] }), {
+ raw: (stateEntry && stateEntry.raw) || null,
+ baseRaw: null,
+ });
+ }
});
},
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index bc9d7fcf30d..c855f3973b0 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,4 +1,4 @@
-/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback, no-unused-vars */
+/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback */
import $ from 'jquery';
import _ from 'underscore';
@@ -7,7 +7,7 @@ import Flash from './flash';
import { __ } from './locale';
export default {
- init({ container, form, issues, prefixId } = {}) {
+ init({ form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 16f88cddce3..f3f8b6ec715 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -2,26 +2,13 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { s__, __ } from './locale';
-import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
-import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex {
constructor(pagePrefix) {
- this.initBulkUpdate(pagePrefix);
+ issuableInitBulkUpdateSidebar.init(pagePrefix);
IssuableIndex.resetIncomingEmailToken();
}
- initBulkUpdate(pagePrefix) {
- const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
- const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
-
- if (userCanBulkUpdate && !alreadyInitialized) {
- IssuableBulkUpdateActions.init({
- prefixId: pagePrefix,
- });
-
- this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
- }
- }
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');
diff --git a/app/assets/javascripts/issuable_init_bulk_update_sidebar.js b/app/assets/javascripts/issuable_init_bulk_update_sidebar.js
new file mode 100644
index 00000000000..da8969c80f3
--- /dev/null
+++ b/app/assets/javascripts/issuable_init_bulk_update_sidebar.js
@@ -0,0 +1,19 @@
+import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
+import issuableBulkUpdateActions from './issuable_bulk_update_actions';
+
+export default {
+ bulkUpdateSidebar: null,
+
+ init(prefixId) {
+ const bulkUpdateEl = document.querySelector('.issues-bulk-update');
+ const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
+
+ if (bulkUpdateEl && !alreadyInitialized) {
+ issuableBulkUpdateActions.init({ prefixId });
+
+ this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
+ }
+
+ return this.bulkUpdateSidebar;
+ },
+};
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 3f954b43ee3..bea43430edc 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -11,7 +11,7 @@ import CreateLabelDropdown from './create_label';
import flash from './flash';
import ModalStore from './boards/stores/modal_store';
import boardsStore from './boards/stores/boards_store';
-import { isEE, isScopedLabel } from '~/lib/utils/common_utils';
+import { isScopedLabel } from '~/lib/utils/common_utils';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -140,7 +140,7 @@ export default class LabelsSelect {
labelCount = data.labels.length;
// EE Specific
- if (isEE) {
+ if (IS_EE) {
/**
* For Scoped labels, the last label selected with the
* same key will be applied to the current issueable.
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index cc5e12aa467..5e90893b684 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -727,14 +727,6 @@ export const NavigationType = {
};
/**
- * Returns the value of `gon.ee`
- * Used to check if it's the EE codebase or the CE one.
- *
- * @returns Boolean
- */
-export const isEE = () => window.gon && window.gon.ee;
-
-/**
* Checks if the given Label has a special syntax `::` in
* it's title.
*
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 9f30a989295..9e97f345717 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -33,6 +33,8 @@ import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import { __ } from './locale';
+import 'ee_else_ce/main_ee';
+
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
@@ -119,11 +121,15 @@ function deferredInitialisation() {
.catch(() => {});
}
+ const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
+ const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
+
// Initialize tooltips
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover',
boundary: 'viewport',
+ delay,
});
// Initialize popovers
diff --git a/app/assets/javascripts/main_ee.js b/app/assets/javascripts/main_ee.js
new file mode 100644
index 00000000000..84d74775163
--- /dev/null
+++ b/app/assets/javascripts/main_ee.js
@@ -0,0 +1 @@
+// This is an empty file to satisfy ee_else_ce import for the EE main entry point
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 2cbda8ea05d..ba79a697df2 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -4,7 +4,6 @@ import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import '~/vue_shared/mixins/is_ee';
import { getParameterValues } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import MonitorAreaChart from './charts/area.vue';
@@ -124,6 +123,11 @@ export default {
required: false,
default: '',
},
+ smallEmptyState: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -155,6 +159,12 @@ export default {
selectedDashboardText() {
return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name);
},
+ addingMetricsAvailable() {
+ return IS_EE && this.canAddMetrics && !this.showEmptyState;
+ },
+ alertWidgetAvailable() {
+ return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint;
+ },
},
created() {
this.setEndpoints({
@@ -308,7 +318,7 @@ export default {
</div>
</div>
<div class="d-flex">
- <div v-if="isEE && canAddMetrics && !showEmptyState">
+ <div v-if="addingMetricsAvailable">
<gl-button
v-gl-modal-directive="$options.addMetric.modalId"
class="js-add-metric-button text-success border-success"
@@ -367,7 +377,7 @@ export default {
group-id="monitor-area-chart"
>
<alert-widget
- v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData"
+ v-if="alertWidgetAvailable && graphData"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries"
:alerts-to-manage="getGraphAlerts(graphData.queries)"
@@ -386,6 +396,7 @@ export default {
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-no-data-svg-path="emptyNoDataSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
+ :compact="smallEmptyState"
/>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index a3c6de14aa4..1bb40447a3e 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -1,7 +1,11 @@
<script>
import { __ } from '~/locale';
+import { GlEmptyState } from '@gitlab/ui';
export default {
+ components: {
+ GlEmptyState,
+ },
props: {
documentationPath: {
type: String,
@@ -37,6 +41,11 @@ export default {
type: String,
required: true,
},
+ compact: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -58,6 +67,8 @@ export default {
If this takes a long time, ensure that data is available.`),
buttonText: __('View documentation'),
buttonPath: this.documentationPath,
+ secondaryButtonText: '',
+ secondaryButtonPath: '',
},
noData: {
svgUrl: this.emptyNoDataSvgPath,
@@ -66,13 +77,19 @@ export default {
no data to display.`),
buttonText: __('Configure Prometheus'),
buttonPath: this.settingsPath,
+ secondaryButtonText: '',
+ secondaryButtonPath: '',
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: __('Unable to connect to Prometheus server'),
- description: 'Ensure connectivity is available from the GitLab server to the ',
+ description: __(
+ 'Ensure connectivity is available from the GitLab server to the Prometheus server',
+ ),
buttonText: __('View documentation'),
buttonPath: this.documentationPath,
+ secondaryButtonText: __('Configure Prometheus'),
+ secondaryButtonPath: this.settingsPath,
},
},
};
@@ -81,45 +98,19 @@ export default {
currentState() {
return this.states[this.selectedState];
},
- showButtonDescription() {
- if (this.selectedState === 'unableToConnect') return true;
- return false;
- },
},
};
</script>
<template>
- <div class="row empty-state js-empty-state">
- <div class="col-12">
- <div class="state-svg svg-content">
- <img :src="currentState.svgUrl" />
- </div>
- </div>
-
- <div class="col-12">
- <div class="text-content">
- <h4 class="state-title text-center">{{ currentState.title }}</h4>
- <p class="state-description">
- {{ currentState.description }}
- <a v-if="showButtonDescription" :href="settingsPath">{{ __('Prometheus server') }}</a>
- </p>
-
- <div class="text-center">
- <a
- v-if="currentState.buttonPath"
- :href="currentState.buttonPath"
- class="btn btn-success"
- >{{ currentState.buttonText }}</a
- >
- <a
- v-if="currentState.secondaryButtonPath"
- :href="currentState.secondaryButtonPath"
- class="btn"
- >{{ currentState.secondaryButtonText }}</a
- >
- </div>
- </div>
- </div>
- </div>
+ <gl-empty-state
+ :title="currentState.title"
+ :description="currentState.description"
+ :primary-button-text="currentState.buttonText"
+ :primary-button-link="currentState.buttonPath"
+ :secondary-button-text="currentState.secondaryButtonText"
+ :secondary-button-link="currentState.secondaryButtonPath"
+ :svg-path="currentState.svgUrl"
+ :compact="compact"
+ />
</template>
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 84e1f1c4c20..721942f9d3b 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -36,15 +36,26 @@ function removeTimeSeriesNoData(queries) {
// { metricId: 2, ...query2Attrs }] },
// { title: 'new title', y_label: 'MB', queries: [{ metricId: 3, ...query3Attrs }]}
// ]
-function groupQueriesByChartInfo(metrics) {
+export function groupQueriesByChartInfo(metrics) {
const metricsByChart = metrics.reduce((accumulator, metric) => {
const { queries, ...chart } = metric;
- const metricId = chart.id ? chart.id.toString() : null;
const chartKey = `${chart.title}|${chart.y_label}`;
accumulator[chartKey] = accumulator[chartKey] || { ...chart, queries: [] };
- queries.forEach(queryAttrs => accumulator[chartKey].queries.push({ metricId, ...queryAttrs }));
+ queries.forEach(queryAttrs => {
+ let metricId;
+
+ if (chart.id) {
+ metricId = chart.id.toString();
+ } else if (queryAttrs.metric_id) {
+ metricId = queryAttrs.metric_id.toString();
+ } else {
+ metricId = null;
+ }
+
+ accumulator[chartKey].queries.push({ metricId, ...queryAttrs });
+ });
return accumulator;
}, {});
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 5a4b5f9398b..fda494fec07 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -13,6 +13,7 @@ import {
splitCamelCase,
slugifyWithUnderscore,
} from '../../lib/utils/text_utility';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@@ -65,14 +66,12 @@ export default {
return this.getUserData.id;
},
commentButtonTitle() {
- return this.noteType === constants.COMMENT ? 'Comment' : 'Start thread';
+ return this.noteType === constants.COMMENT ? __('Comment') : __('Start thread');
},
startDiscussionDescription() {
- let text = 'Discuss a specific suggestion or question';
- if (this.getNoteableData.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE) {
- text += ' that needs to be resolved';
- }
- return `${text}.`;
+ return this.getNoteableData.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
+ ? __('Discuss a specific suggestion or question that needs to be resolved.')
+ : __('Discuss a specific suggestion or question.');
},
isOpen() {
return this.openState === constants.OPENED || this.openState === constants.REOPENED;
@@ -127,8 +126,8 @@ export default {
},
issuableTypeTitle() {
return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
- ? 'merge request'
- : 'issue';
+ ? __('merge request')
+ : __('issue');
},
trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
@@ -203,7 +202,7 @@ export default {
this.discard();
} else {
Flash(
- 'Something went wrong while adding your comment. Please try again.',
+ __('Something went wrong while adding your comment. Please try again.'),
'alert',
this.$refs.commentForm,
);
@@ -219,8 +218,9 @@ export default {
.catch(() => {
this.enableButton();
this.discard(false);
- const msg = `Your comment could not be submitted!
-Please check your network connection and try again.`;
+ const msg = __(
+ 'Your comment could not be submitted! Please check your network connection and try again.',
+ );
Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes();
@@ -235,7 +235,10 @@ Please check your network connection and try again.`;
toggleIssueState() {
if (this.isOpen) {
this.closeIssue()
- .then(() => this.enableButton())
+ .then(() => {
+ this.enableButton();
+ refreshUserMergeRequestCounts();
+ })
.catch(() => {
this.enableButton();
this.toggleStateButtonLoading(false);
@@ -248,7 +251,10 @@ Please check your network connection and try again.`;
});
} else {
this.reopenIssue()
- .then(() => this.enableButton())
+ .then(() => {
+ this.enableButton();
+ refreshUserMergeRequestCounts();
+ })
.catch(({ data }) => {
this.enableButton();
this.toggleStateButtonLoading(false);
@@ -298,7 +304,7 @@ Please check your network connection and try again.`;
const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType));
this.autosave = new Autosave($(this.$refs.textarea), [
- 'Note',
+ __('Note'),
noteableType,
this.getNoteableData.id,
]);
@@ -359,8 +365,8 @@ Please check your network connection and try again.`;
class="note-textarea js-vue-comment-form js-note-text
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
- aria-label="Description"
- placeholder="Write a comment or drag your files here…"
+ :aria-label="__('Description')"
+ :placeholder="__('Write a comment or drag your files here…')"
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()"
@@ -381,7 +387,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
data-track-event="click_button"
@click.prevent="handleSave()"
>
- {{ __(commentButtonTitle) }}
+ {{ commentButtonTitle }}
</button>
<button
:disabled="isSubmitButtonDisabled"
@@ -390,7 +396,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-success note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
data-display="static"
data-toggle="dropdown"
- aria-label="Open comment type dropdown"
+ :aria-label="__('Open comment type dropdown')"
>
<i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
</button>
@@ -404,8 +410,14 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
- <strong>Comment</strong>
- <p>Add a general comment to this {{ noteableDisplayName }}.</p>
+ <strong>{{ __('Comment') }}</strong>
+ <p>
+ {{
+ sprintf(__('Add a general comment to this %{noteableDisplayName}.'), {
+ noteableDisplayName,
+ })
+ }}
+ </p>
</div>
</button>
</li>
@@ -418,7 +430,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
- <strong>Start thread</strong>
+ <strong>{{ __('Start thread') }}</strong>
<p>{{ startDiscussionDescription }}</p>
</div>
</button>
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 54c242b2fda..164e79c6294 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -100,7 +100,7 @@ export default {
class="btn-link btn-link-retry btn-no-padding js-toggle-lazy-diff-retry-button"
@click="fetchDiff"
>
- Try again
+ {{ __('Try again') }}
</button>
</td>
<td v-else class="line_content js-success-lazy-load">
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
index 2ff0fee62f3..0b136549c14 100644
--- a/app/assets/javascripts/notes/components/discussion_notes.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -8,12 +8,14 @@ import SystemNote from '~/vue_shared/components/notes/system_note.vue';
import NoteableNote from './noteable_note.vue';
import ToggleRepliesWidget from './toggle_replies_widget.vue';
import NoteEditedText from './note_edited_text.vue';
+import DiscussionNotesRepliesWrapper from './discussion_notes_replies_wrapper.vue';
export default {
name: 'DiscussionNotes',
components: {
ToggleRepliesWidget,
NoteEditedText,
+ DiscussionNotesRepliesWrapper,
},
props: {
discussion: {
@@ -119,9 +121,7 @@ export default {
/>
<slot slot="avatar-badge" name="avatar-badge"></slot>
</component>
- <div
- :class="discussion.diff_discussion ? 'discussion-collapsible bordered-box clearfix' : ''"
- >
+ <discussion-notes-replies-wrapper :is-diff-discussion="discussion.diff_discussion">
<toggle-replies-widget
v-if="hasReplies"
:collapsed="!isExpanded"
@@ -141,7 +141,7 @@ export default {
/>
</template>
<slot :show-replies="isExpanded || !hasReplies" name="footer"></slot>
- </div>
+ </discussion-notes-replies-wrapper>
</template>
<template v-else>
<component
diff --git a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
new file mode 100644
index 00000000000..2ddca56ddd5
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
@@ -0,0 +1,27 @@
+<script>
+/**
+ * Wrapper for discussion notes replies section.
+ *
+ * This is a functional component using the render method because in some cases
+ * the wrapper is not needed and we want to simply render along the children.
+ */
+export default {
+ functional: true,
+ props: {
+ isDiffDiscussion: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ render(h, { props, children }) {
+ if (props.isDiffDiscussion) {
+ return h('li', { class: 'discussion-collapsible bordered-box clearfix' }, [
+ h('ul', { class: 'notes' }, children),
+ ]);
+ }
+
+ return children;
+ },
+};
+</script>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 941b6d5cab3..d4a57d5d58d 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -4,6 +4,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import { glEmojiTag } from '../../emoji';
+import { __, sprintf } from '~/locale';
export default {
components: {
@@ -108,23 +109,26 @@ export default {
// Add myself to the beginning of the list so title will start with You.
if (hasReactionByCurrentUser) {
- namesToShow.unshift('You');
+ namesToShow.unshift(__('You'));
}
let title = '';
// We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) {
- title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`;
+ title = sprintf(__(`%{listToShow}, and %{awardsListLength} more.`), {
+ listToShow: namesToShow.join(', '),
+ awardsListLength: remainingAwardList.length,
+ });
} else if (namesToShow.length > 1) {
// Join all names with comma but not the last one, it will be added with and text.
title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
// If we have more than 2 users we need an extra comma before and text.
title += namesToShow.length > 2 ? ',' : '';
- title += ` and ${namesToShow.slice(-1)}`; // Append and text
+ title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }); // Append and text
} else {
// We have only 2 users so join them with and.
- title = namesToShow.join(' and ');
+ title = namesToShow.join(__(' and '));
}
return title;
@@ -155,7 +159,7 @@ export default {
awardName: parsedName,
};
- this.toggleAwardRequest(data).catch(() => Flash('Something went wrong on our end.'));
+ this.toggleAwardRequest(data).catch(() => Flash(__('Something went wrong on our end.')));
},
},
};
@@ -184,7 +188,7 @@ export default {
:class="{ 'js-user-authored': isAuthoredByMe }"
class="award-control btn js-add-award"
title="Add reaction"
- aria-label="Add reaction"
+ :aria-label="__('Add reaction')"
data-boundary="viewport"
type="button"
>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 01be4f2b094..3823861c0b9 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,14 +1,14 @@
<script>
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { mapGetters, mapActions } from 'vuex';
+import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import { getDraft, updateDraft } from '~/lib/utils/autosave';
-import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
export default {
name: 'NoteForm',
@@ -174,6 +174,18 @@ export default {
(this.line && this.line.can_receive_suggestion)
);
},
+ changedCommentText() {
+ return sprintf(
+ __(
+ 'This comment has changed since you started editing, please review the %{startTag}updated comment%{endTag} to ensure information is not lost.',
+ ),
+ {
+ startTag: `<a href="${this.noteHash}" target="_blank" rel="noopener noreferrer">`,
+ endTag: '</a>',
+ },
+ false,
+ );
+ },
},
watch: {
noteBody() {
@@ -228,11 +240,11 @@ export default {
<template>
<div ref="editNoteForm" class="note-edit-form current-note-edit-form js-discussion-note-form">
- <div v-if="conflictWhileEditing" class="js-conflict-edit-warning alert alert-danger">
- This comment has changed since you started editing, please review the
- <a :href="noteHash" target="_blank" rel="noopener noreferrer">updated comment</a> to ensure
- information is not lost.
- </div>
+ <div
+ v-if="conflictWhileEditing"
+ class="js-conflict-edit-warning alert alert-danger"
+ v-html="changedCommentText"
+ ></div>
<div class="flash-container timeline-content"></div>
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning
@@ -264,8 +276,8 @@ export default {
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
dir="auto"
- aria-label="Description"
- placeholder="Write a comment or drag your files here…"
+ :aria-label="__('Description')"
+ :placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="handleKeySubmit()"
@keydown.ctrl.enter="handleKeySubmit()"
@keydown.exact.up="editMyLastNote()"
@@ -339,7 +351,7 @@ export default {
type="button"
@click="cancelHandler()"
>
- Cancel
+ {{ __('Cancel') }}
</button>
</template>
</div>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 6466ab3acbe..3158e086f6c 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -103,7 +103,7 @@ export default {
</template>
<i
class="fa fa-spinner fa-spin editing-spinner"
- aria-label="Comment is being updated"
+ :aria-label="__('Comment is being updated')"
aria-hidden="true"
></i>
</span>
diff --git a/app/assets/javascripts/notes/components/note_signed_out_widget.vue b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
index e3eb92956b1..ccfe84ab098 100644
--- a/app/assets/javascripts/notes/components/note_signed_out_widget.vue
+++ b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
@@ -1,5 +1,6 @@
<script>
import { mapGetters } from 'vuex';
+import { __, sprintf } from '~/locale';
export default {
computed: {
@@ -10,12 +11,24 @@ export default {
signInLink() {
return this.getNotesDataByProp('newSessionPath');
},
+ signedOutText() {
+ return sprintf(
+ __(
+ 'Please %{startTagRegister}register%{endRegisterTag} or %{startTagSignIn}sign in%{endSignInTag} to reply',
+ ),
+ {
+ startTagRegister: `<a href="${this.registerLink}">`,
+ startTagSignIn: `<a href="${this.signInLink}">`,
+ endRegisterTag: '</a>',
+ endSignInTag: '</a>',
+ },
+ false,
+ );
+ },
},
};
</script>
<template>
- <div class="disabled-comment text-center">
- Please <a :href="registerLink">register</a> or <a :href="signInLink">sign in</a> to reply
- </div>
+ <div class="disabled-comment text-center" v-html="signedOutText"></div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index a71a89cfffc..ac743d9f4b8 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -144,15 +144,6 @@ export default {
return {};
},
- componentClassName() {
- if (this.shouldRenderDiffs) {
- if (!this.lastUpdatedAt && !this.discussion.resolved) {
- return 'unresolved';
- }
- }
-
- return '';
- },
isExpanded() {
return this.discussion.expanded || this.alwaysExpanded;
},
@@ -283,8 +274,9 @@ export default {
this.removePlaceholderNotes();
this.isReplying = true;
this.$nextTick(() => {
- const msg = `Your comment could not be submitted!
-Please check your network connection and try again.`;
+ const msg = __(
+ 'Your comment could not be submitted! Please check your network connection and try again.',
+ );
Flash(msg, 'alert', this.$el);
this.$refs.noteForm.note = noteText;
callback(err);
@@ -312,11 +304,11 @@ Please check your network connection and try again.`;
</script>
<template>
- <timeline-entry-item class="note note-discussion" :class="componentClassName">
+ <timeline-entry-item class="note note-discussion">
<div class="timeline-content">
<div :data-discussion-id="discussion.id" class="discussion js-discussion-container">
<div v-if="shouldRenderDiffs" class="discussion-header note-wrapper">
- <div v-once class="timeline-icon">
+ <div v-once class="timeline-icon align-self-start flex-shrink-0">
<user-avatar-link
v-if="author"
:link-href="author.path"
@@ -325,7 +317,7 @@ Please check your network connection and try again.`;
:img-size="40"
/>
</div>
- <div class="timeline-content">
+ <div class="timeline-content w-100">
<note-header
:author="author"
:created-at="firstNote.created_at"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index aa80e25a3e0..2f201839d45 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -5,7 +5,7 @@ import { escape } from 'underscore';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import draftMixin from 'ee_else_ce/notes/mixins/draft';
-import { s__, sprintf } from '../../locale';
+import { __, s__, sprintf } from '../../locale';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
@@ -128,9 +128,13 @@ export default {
this.$emit('handleEdit');
},
deleteHandler() {
- const typeOfComment = this.note.isDraft ? 'pending comment' : 'comment';
- // eslint-disable-next-line no-alert
- if (window.confirm(`Are you sure you want to delete this ${typeOfComment}?`)) {
+ const typeOfComment = this.note.isDraft ? __('pending comment') : __('comment');
+ if (
+ // eslint-disable-next-line no-alert
+ window.confirm(
+ sprintf(__('Are you sure you want to delete this %{typeOfComment}?'), { typeOfComment }),
+ )
+ ) {
this.isDeleting = true;
this.$emit('handleDeleteNote', this.note);
@@ -141,7 +145,7 @@ export default {
this.isDeleting = false;
})
.catch(() => {
- Flash('Something went wrong while deleting your note. Please try again.');
+ Flash(__('Something went wrong while deleting your note. Please try again.'));
this.isDeleting = false;
});
}
@@ -185,7 +189,7 @@ export default {
this.isRequesting = false;
this.isEditing = true;
this.$nextTick(() => {
- const msg = 'Something went wrong while editing your comment. Please try again.';
+ const msg = __('Something went wrong while editing your comment. Please try again.');
Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText);
callback();
@@ -195,7 +199,7 @@ export default {
formCancelHandler(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
// eslint-disable-next-line no-alert
- if (!window.confirm('Are you sure you want to cancel editing this comment?')) return;
+ if (!window.confirm(__('Are you sure you want to cancel editing this comment?'))) return;
}
this.$refs.noteBody.resetAutoSave();
if (this.oldContent) {
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 4d00e957973..a0695f9e191 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash';
@@ -170,7 +171,7 @@ export default {
.catch(() => {
this.setLoadingState(false);
this.setNotesFetchedState(true);
- Flash('Something went wrong while fetching comments. Please try again.');
+ Flash(__('Something went wrong while fetching comments. Please try again.'));
});
},
initPolling() {
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 57dd1c5cab2..c70c0e4095c 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import { isEE } from '~/lib/utils/common_utils';
import initNoteStats from 'ee_else_ce/event_tracking/notes';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
@@ -41,9 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
};
},
mounted() {
- if (isEE) {
- initNoteStats();
- }
+ initNoteStats();
},
render(createElement) {
return createElement('notes-app', {
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 237e70c0a4c..47a6f07cce2 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import Api from '~/api';
import VueResource from 'vue-resource';
import * as constants from '../constants';
@@ -45,7 +44,4 @@ export default {
toggleIssueState(endpoint, data) {
return Vue.http.put(endpoint, data);
},
- applySuggestion(id) {
- return Api.applySuggestion(id);
- },
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 9054b4779aa..fef962f008e 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -14,6 +14,7 @@ import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
import { __ } from '~/locale';
+import Api from '~/api';
let eTagPoll;
@@ -449,8 +450,7 @@ export const submitSuggestion = (
{ commit, dispatch },
{ discussionId, noteId, suggestionId, flashContainer },
) =>
- service
- .applySuggestion(suggestionId)
+ Api.applySuggestion(suggestionId)
.then(() => commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }))
.then(() => dispatch('resolveDiscussion', { discussionId }).catch(() => {}))
.catch(err => {
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 1b56b97f751..d51d411f3c6 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -82,7 +82,7 @@ export default class Todos {
})
.catch(() => {
this.updateRowState(target, true);
- return flash(__('Error updating todo status.'));
+ return flash(__('Error updating status of to-do item.'));
});
}
@@ -124,7 +124,7 @@ export default class Todos {
this.updateAllState(target, data);
this.updateBadges(data);
})
- .catch(() => flash(__('Error updating status for all todos.')));
+ .catch(() => flash(__('Error updating status for all to-do items.')));
}
updateAllState(target, data) {
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 23fb5656008..dcdee77a8ab 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,11 +1,15 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import initManualOrdering from '~/manual_ordering';
+const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
+
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
+ issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 941c4552579..2205a7bafe3 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -17,7 +17,5 @@ export default () => {
new MilestoneSelect();
new IssuableTemplateSelectors();
- if (gon.features.graphql) {
- initSuggestions();
- }
+ initSuggestions();
};
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 1e66ccbfa29..0d9e992e596 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -76,7 +76,7 @@ export default {
variables: {
projectPath: this.projectPath,
ref: this.ref,
- path: this.path,
+ path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
index 0ad2b3a73a2..fa6b6bfaef1 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -1,4 +1,6 @@
<script>
+import { n__ } from '~/locale';
+
export default {
name: 'AssigneeTitle',
props: {
@@ -24,7 +26,7 @@ export default {
computed: {
assigneeTitle() {
const assignees = this.numberOfAssignees;
- return assignees > 1 ? `${assignees} Assignees` : 'Assignee';
+ return n__('Assignee', `%d Assignees`, assignees);
},
},
};
@@ -32,18 +34,18 @@ export default {
<template>
<div class="title hide-collapsed">
{{ assigneeTitle }}
- <i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"> </i>
+ <i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"></i>
<a v-if="editable" class="js-sidebar-dropdown-toggle edit-link float-right" href="#">
{{ __('Edit') }}
</a>
<a
v-if="showToggle"
- aria-label="Toggle sidebar"
+ :aria-label="__('Toggle sidebar')"
class="gutter-toggle float-right js-sidebar-toggle"
href="#"
role="button"
>
- <i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"> </i>
+ <i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
</a>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
index 0074d7099dc..805c21d0965 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -1,5 +1,5 @@
<script>
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
@@ -62,7 +62,8 @@ export default {
return this.numberOfHiddenAssignees > 0;
},
hiddenAssigneesLabel() {
- return `+ ${this.numberOfHiddenAssignees} more`;
+ const { numberOfHiddenAssignees } = this;
+ return sprintf(__('+ %{numberOfHiddenAssignees} more'), { numberOfHiddenAssignees });
},
collapsedTooltipTitle() {
const maxRender = Math.min(this.defaultRenderCount, this.users.length);
@@ -103,12 +104,15 @@ export default {
// Everyone can merge
return null;
} else if (cannotMergeCount === assigneesCount && assigneesCount > 1) {
- return 'No one can merge';
+ return __('No one can merge');
} else if (assigneesCount === 1) {
- return 'Cannot merge';
+ return __('Cannot merge');
}
- return `${canMergeCount}/${assigneesCount} can merge`;
+ return sprintf(__('%{canMergeCount}/%{assigneesCount} can merge'), {
+ canMergeCount,
+ assigneesCount,
+ });
},
},
methods: {
@@ -128,7 +132,7 @@ export default {
return `${this.rootPath}${user.username}`;
},
assigneeAlt(user) {
- return `${user.name}'s avatar`;
+ return sprintf(__("%{userName}'s avatar"), { userName: user.name });
},
assigneeUsername(user) {
return `@${user.username}`;
@@ -153,7 +157,7 @@ export default {
data-placement="left"
data-boundary="viewport"
>
- <i v-if="hasNoUsers" aria-label="None" class="fa fa-user"> </i>
+ <i v-if="hasNoUsers" :aria-label="__('None')" class="fa fa-user"> </i>
<button
v-for="(user, index) in users"
v-if="shouldRenderCollapsedAssignee(index)"
@@ -185,9 +189,12 @@ export default {
</span>
<template v-if="hasNoUsers">
<span class="assign-yourself no-value qa-assign-yourself">
- None
+ {{ __('None') }}
<template v-if="editable">
- - <button type="button" class="btn-link" @click="assignSelf">assign yourself</button>
+ -
+ <button type="button" class="btn-link" @click="assignSelf">
+ {{ __('assign yourself') }}
+ </button>
</template>
</span>
</template>
@@ -232,9 +239,7 @@ export default {
<template v-if="showLess">
{{ hiddenAssigneesLabel }}
</template>
- <template v-else>
- - show less
- </template>
+ <template v-else>{{ __('- show less') }}</template>
</button>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index cfa7029b388..be1e4811856 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -2,8 +2,10 @@
import Flash from '~/flash';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import AssigneeTitle from './assignee_title.vue';
import Assignees from './assignees.vue';
+import { __ } from '~/locale';
export default {
name: 'SidebarAssignees',
@@ -72,9 +74,12 @@ export default {
this.mediator
.saveAssignees(this.field)
.then(setLoadingFalse.bind(this))
+ .then(() => {
+ refreshUserMergeRequestCounts();
+ })
.catch(() => {
setLoadingFalse();
- return new Flash('Error occurred when saving assignees');
+ return new Flash(__('Error occurred when saving assignees'));
});
},
},
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
index 4b9bb5c7b0e..5d0e39e8195 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
import eventHub from '../../event_hub';
+import { __ } from '~/locale';
export default {
props: {
@@ -15,7 +16,7 @@ export default {
},
computed: {
toggleButtonText() {
- return this.isConfidential ? 'Turn Off' : 'Turn On';
+ return this.isConfidential ? __('Turn Off') : __('Turn On');
},
updateConfidentialBool() {
return !this.isConfidential;
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
index 657ac837baf..24d5b14ded9 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
@@ -79,7 +79,7 @@ export default {
} else if (this.showSpentOnlyState) {
return `${this.timeSpent} / --`;
} else if (this.showNoTimeTrackingState) {
- return 'None';
+ return __('None');
}
return '';
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
index bc263bc36e4..06aca547183 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -2,6 +2,7 @@
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import tooltip from '../../../vue_shared/directives/tooltip';
import { GlProgressBar } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
export default {
name: 'TimeTrackingComparisonPane',
@@ -43,8 +44,14 @@ export default {
return stringifyTime(this.parsedTimeRemaining);
},
timeRemainingTooltip() {
- const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
- return `${prefix} ${this.timeRemainingHumanReadable}`;
+ const { timeRemainingHumanReadable, timeRemainingMinutes } = this;
+ return timeRemainingMinutes < 0
+ ? sprintf(s__('TimeTracking|Over by %{timeRemainingHumanReadable}'), {
+ timeRemainingHumanReadable,
+ })
+ : sprintf(s__('TimeTracking|Time remaining: %{timeRemainingHumanReadable}'), {
+ timeRemainingHumanReadable,
+ });
},
/* Diff values for comparison meter */
timeRemainingMinutes() {
@@ -74,12 +81,12 @@ export default {
<gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" />
<div class="compare-display-container">
<div class="compare-display float-left">
- <span class="compare-label"> {{ s__('TimeTracking|Spent') }} </span>
- <span class="compare-value spent"> {{ timeSpentHumanReadable }} </span>
+ <span class="compare-label">{{ s__('TimeTracking|Spent') }}</span>
+ <span class="compare-value spent">{{ timeSpentHumanReadable }}</span>
</div>
<div class="compare-display estimated float-right">
- <span class="compare-label"> {{ s__('TimeTrackingEstimated|Est') }} </span>
- <span class="compare-value"> {{ timeEstimateHumanReadable }} </span>
+ <span class="compare-label">{{ s__('TimeTrackingEstimated|Est') }}</span>
+ <span class="compare-value">{{ timeEstimateHumanReadable }}</span>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
index 7c7356e2afa..c2f30310e2e 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
@@ -1,4 +1,6 @@
<script>
+import { sprintf, s__ } from '~/locale';
+
export default {
name: 'TimeTrackingSpentOnlyPane',
props: {
@@ -7,11 +9,22 @@ export default {
required: true,
},
},
+ computed: {
+ timeSpent() {
+ return sprintf(
+ s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'),
+ {
+ startTag: '<span class="bold">',
+ endTag: '</span>',
+ timeSpentHumanReadable: this.timeSpentHumanReadable,
+ },
+ false,
+ );
+ },
+ },
};
</script>
<template>
- <div class="time-tracking-spend-only-pane">
- <span class="bold">Spent:</span> {{ timeSpentHumanReadable }}
- </div>
+ <div class="time-tracking-spend-only-pane" v-html="timeSpent"></div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
index 57125c78cf6..e6f2fe2b5fc 100644
--- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
@@ -5,8 +5,8 @@ import { GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
-const MARK_TEXT = __('Mark todo as done');
-const TODO_TEXT = __('Add todo');
+const MARK_TEXT = __('Mark as done');
+const TODO_TEXT = __('Add a To Do');
export default {
directives: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 34cdb70ce14..5c7859828d8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -125,7 +125,9 @@ export default {
this.isStopping = false;
})
.catch(() => {
- createFlash('Something went wrong while stopping this environment. Please try again.');
+ createFlash(
+ __('Something went wrong while stopping this environment. Please try again.'),
+ );
this.isStopping = false;
});
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index e20a16900d4..fb826be19f5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -139,7 +139,7 @@ export default {
type="button"
class="btn dropdown-toggle qa-dropdown-toggle"
data-toggle="dropdown"
- aria-label="Download as"
+ :aria-label="__('Download as')"
aria-haspopup="true"
aria-expanded="false"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
index 5958c2cf87e..8e8e67228ed 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
@@ -6,6 +6,7 @@ import statusIcon from '../mr_widget_status_icon.vue';
import MrWidgetAuthor from '../../components/mr_widget_author.vue';
import eventHub from '../../event_hub';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
+import { __ } from '~/locale';
export default {
name: 'MRWidgetAutoMergeEnabled',
@@ -55,7 +56,7 @@ export default {
})
.catch(() => {
this.isCancellingAutoMerge = false;
- Flash('Something went wrong. Please try again.');
+ Flash(__('Something went wrong. Please try again.'));
});
},
removeSourceBranch() {
@@ -76,7 +77,7 @@ export default {
})
.catch(() => {
this.isRemovingSourceBranch = false;
- Flash('Something went wrong. Please try again.');
+ Flash(__('Something went wrong. Please try again.'));
});
},
},
@@ -107,15 +108,15 @@ export default {
<section class="mr-info-list">
<p>
{{ s__('mrWidget|The changes will be merged into') }}
- <a :href="mr.targetBranchPath" class="label-branch"> {{ mr.targetBranch }} </a>
+ <a :href="mr.targetBranchPath" class="label-branch">{{ mr.targetBranch }}</a>
</p>
<p v-if="mr.shouldRemoveSourceBranch">
{{ s__('mrWidget|The source branch will be deleted') }}
</p>
<p v-else class="d-flex align-items-start">
- <span class="append-right-10">
- {{ s__('mrWidget|The source branch will not be deleted') }}
- </span>
+ <span class="append-right-10">{{
+ s__('mrWidget|The source branch will not be deleted')
+ }}</span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 0bcccc50eb2..c7b064b8506 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -4,6 +4,7 @@ import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
import Flash from '../../../flash';
+import { __, sprintf } from '~/locale';
export default {
name: 'MRWidgetRebase',
@@ -40,6 +41,17 @@ export default {
showDisabledButton() {
return ['failed', 'loading'].includes(this.status);
},
+ fastForwardMergeText() {
+ return sprintf(
+ __(
+ `Fast-forward merge is not possible. Rebase the source branch onto %{startTag}${this.mr.targetBranch}%{endTag} to allow this merge request to be merged.`,
+ ),
+ {
+ startTag: '<span class="label-branch">',
+ endTag: '</span>',
+ },
+ );
+ },
},
methods: {
rebase() {
@@ -54,7 +66,7 @@ export default {
.catch(error => {
this.rebasingError = error.merge_error;
this.isMakingRequest = false;
- Flash('Something went wrong. Please try again.');
+ Flash(__('Something went wrong. Please try again.'));
});
},
checkRebaseStatus(continuePolling, stopPolling) {
@@ -69,7 +81,7 @@ export default {
if (res.merge_error && res.merge_error.length) {
this.rebasingError = res.merge_error;
- Flash('Something went wrong. Please try again.');
+ Flash(__('Something went wrong. Please try again.'));
}
eventHub.$emit('MRWidgetRebaseSuccess');
@@ -78,7 +90,7 @@ export default {
})
.catch(() => {
this.isMakingRequest = false;
- Flash('Something went wrong. Please try again.');
+ Flash(__('Something went wrong. Please try again.'));
stopPolling();
});
},
@@ -91,19 +103,14 @@ export default {
<div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest">
- <span class="bold"> Rebase in progress </span>
+ <span class="bold">{{ __('Rebase in progress') }}</span>
</template>
<template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch">
- <span class="bold">
- Fast-forward merge is not possible. Rebase the source branch onto
- <span class="label-branch">{{ mr.targetBranch }}</span> to allow this merge request to be
- merged.
- </span>
+ <span class="bold" v-html="fastForwardMergeText"></span>
</template>
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest">
<div
- class="accept-merge-holder clearfix
-js-toggle-container accept-action media space-children"
+ class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
>
<button
:disabled="isMakingRequest"
@@ -111,14 +118,14 @@ js-toggle-container accept-action media space-children"
class="btn btn-sm btn-reopen btn-success qa-mr-rebase-button"
@click="rebase"
>
- <gl-loading-icon v-if="isMakingRequest" />
- Rebase
+ <gl-loading-icon v-if="isMakingRequest" />{{ __('Rebase') }}
</button>
- <span v-if="!rebasingError" class="bold">
- Fast-forward merge is not possible. Rebase the source branch onto the target branch or
- merge target branch into source branch to allow this merge request to be merged.
- </span>
- <span v-else class="bold danger"> {{ rebasingError }} </span>
+ <span v-if="!rebasingError" class="bold">{{
+ __(
+ 'Fast-forward merge is not possible. Rebase the source branch onto the target branch or merge target branch into source branch to allow this merge request to be merged.',
+ )
+ }}</span>
+ <span v-else class="bold danger">{{ rebasingError }}</span>
</div>
</template>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index a38495bb4cc..7312b31c01c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -22,19 +22,29 @@ export default {
<span v-html="emptyStateSVG"></span>
</div>
<div class="text col-md-7 order-md-first col-12">
- <span>
- Merge requests are a place to propose changes you have made to a project and discuss those
- changes with others.
- </span>
- <p>Interested parties can even contribute by pushing commits if they want to.</p>
+ <span>{{
+ s__(
+ 'mrWidgetNothingToMerge|Merge requests are a place to propose changes you have made to a project and discuss those changes with others.',
+ )
+ }}</span>
<p>
- Currently there are no changes in this merge request's source branch. Please push new
- commits or use a different branch.
+ {{
+ s__(
+ 'mrWidgetNothingToMerge|Interested parties can even contribute by pushing commits if they want to.',
+ )
+ }}
+ </p>
+ <p>
+ {{
+ s__(
+ "mrWidgetNothingToMerge|Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch.",
+ )
+ }}
</p>
<div>
- <a v-if="mr.newBlobPath" :href="mr.newBlobPath" class="btn btn-inverted btn-success">
- Create file
- </a>
+ <a v-if="mr.newBlobPath" :href="mr.newBlobPath" class="btn btn-inverted btn-success">{{
+ __('Create file')
+ }}</a>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index ca1b4a57717..d4514767912 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -6,6 +6,7 @@ import simplePoll from '~/lib/utils/simple_poll';
import { __ } from '~/locale';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
import MergeRequest from '../../../merge_request';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
@@ -174,6 +175,8 @@ export default {
MergeRequest.decreaseCounter();
stopPolling();
+ refreshUserMergeRequestCounts();
+
// If user checked remove source branch and we didn't remove the branch yet
// we should start another polling for source branch remove process
if (this.removeSourceBranch && data.source_branch_exists) {
@@ -248,7 +251,7 @@ export default {
type="button"
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
data-toggle="dropdown"
- aria-label="Select merge moment"
+ :aria-label="__('Select merge moment')"
>
<i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
</button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index 7c322388d30..91c0b40a0b5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -46,14 +46,20 @@ export default {
<status-icon :show-disabled-button="Boolean(mr.removeWIPPath)" status="warning" />
<div class="media-body space-children">
<span class="bold">
- This is a Work in Progress
+ {{ __('This is a Work in Progress') }}
<i
v-tooltip
class="fa fa-question-circle"
- title="When this merge request is ready,
- remove the WIP: prefix from the title to allow it to be merged"
- aria-label="When this merge request is ready,
- remove the WIP: prefix from the title to allow it to be merged"
+ :title="
+ s__(
+ 'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
+ )
+ "
+ :aria-label="
+ s__(
+ 'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
+ )
+ "
>
</i>
</span>
@@ -64,8 +70,8 @@ export default {
class="btn btn-default btn-sm js-remove-wip"
@click="removeWIP"
>
- <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"> </i> Resolve WIP
- status
+ <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
+ {{ s__('mrWidget|Resolve WIP status') }}
</button>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index a79da476890..8d415c1bbea 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -263,8 +263,11 @@ export default {
if (!data.pipeline) return;
const { label } = data.pipeline.details.status;
- const title = `Pipeline ${label}`;
- const message = `Pipeline ${label} for "${data.title}"`;
+ const title = sprintf(__('Pipeline %{label}'), { label });
+ const message = sprintf(__('Pipeline %{label} for "%{dataTitle}"'), {
+ dataTitle: data.title,
+ label,
+ });
notify.notifyMe(title, message, this.mr.gitlabLogo);
},
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index e9ab6f5ba7a..15cb0bd9792 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -1,7 +1,6 @@
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
-import { pluralize } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
import { getCommitIconMap } from '~/ide/utils';
@@ -69,7 +68,7 @@ export default {
});
} else if (this.file.changed && this.file.staged) {
return sprintf(__('Unstaged and staged %{type}'), {
- type: pluralize(type),
+ type,
});
}
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index a1168fa0f1e..ae9b013d980 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -1,6 +1,7 @@
<script>
import _ from 'underscore';
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
import Icon from '../../vue_shared/components/icon.vue';
@@ -129,7 +130,9 @@ export default {
* @returns {String}
*/
userImageAltDescription() {
- return this.author && this.author.username ? `${this.author.username}'s avatar` : null;
+ return this.author && this.author.username
+ ? sprintf(__("%{username}'s avatar"), { username: this.author.username })
+ : null;
},
},
};
@@ -180,7 +183,7 @@ export default {
{{ title }}
</gl-link>
</tooltip-on-truncate>
- <span v-else> Can't find HEAD commit for this branch </span>
+ <span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
index ba63683f5c0..da0b45110e2 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
@@ -3,6 +3,7 @@ import { __ } from '~/locale';
const viewers = {
image: {
id: 'image',
+ binary: true,
},
markdown: {
id: 'markdown',
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
index 2ca933a37d2..fc6a45b957e 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
@@ -91,7 +91,9 @@ export default {
|
</template>
<template v-if="hasDimensions">
- <strong>W</strong>: {{ width }} | <strong>H</strong>: {{ height }}
+ <strong>{{ s__('ImageViewerDimensions|W') }}</strong
+ >: {{ width }} | <strong>{{ s__('ImageViewerDimensions|H') }}</strong
+ >: {{ height }}
</template>
</p>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index 5fdc915fffb..655f0054887 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -40,7 +40,7 @@ export default {
this.fetchMarkdownPreview();
},
destroyed() {
- if (this.isLoading) axiosSource.cancel('Cancelling Preview');
+ if (this.isLoading) axiosSource.cancel(__('Cancelling Preview'));
},
methods: {
fetchMarkdownPreview() {
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
index 36b3ee05456..d5558d93219 100644
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -1,5 +1,7 @@
<script>
/* eslint-disable vue/require-default-prop */
+import { __ } from '~/locale';
+
export default {
name: 'DeprecatedModal', // use GlModal instead
@@ -39,7 +41,7 @@ export default {
closeButtonLabel: {
type: String,
required: false,
- default: 'Cancel',
+ default: __('Cancel'),
},
primaryButtonLabel: {
type: String,
@@ -94,7 +96,7 @@ export default {
type="button"
class="close float-right"
data-dismiss="modal"
- aria-label="Close"
+ :aria-label="__('Close')"
@click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
diff --git a/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
index 7d49c87271d..c35fee84771 100644
--- a/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
@@ -69,7 +69,7 @@ export default {
data-display="static"
data-toggle="dropdown"
>
- <icon name="arrow-down" aria-label="toggle dropdown" />
+ <icon name="arrow-down" :aria-label="__('toggle dropdown')" />
</button>
<ul :class="dropdownClass" class="dropdown-menu dropdown-open-top">
<template v-for="(action, index) in actions">
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
index 4e5dfbf3bf8..20bcceeb477 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
@@ -115,7 +115,7 @@ export default {
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
- aria-label="Expand dropdown"
+ :aria-label="__('Expand dropdown')"
>
<icon name="angle-down" :size="12" />
</button>
@@ -125,7 +125,7 @@ export default {
ref="searchInput"
v-model="filter"
type="search"
- placeholder="Filter"
+ :placeholder="__('Filter')"
class="js-filtered-dropdown-input dropdown-input-field"
/>
<icon class="dropdown-input-search" name="search" />
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 3f45dc7853b..c652a684d7c 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -1,5 +1,6 @@
<script>
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
import CiIconBadge from './ci_badge_link.vue';
import TimeagoTooltip from './time_ago_tooltip.vue';
import UserAvatarImage from './user_avatar/user_avatar_image.vue';
@@ -65,7 +66,7 @@ export default {
computed: {
userAvatarAltText() {
- return `${this.user.name}'s avatar`;
+ return sprintf(__(`%{username}'s avatar`), { username: this.user.name });
},
},
@@ -87,16 +88,12 @@ export default {
<strong> {{ itemName }} #{{ itemId }} </strong>
- <template v-if="shouldRenderTriggeredLabel">
- triggered
- </template>
- <template v-else>
- created
- </template>
+ <template v-if="shouldRenderTriggeredLabel">{{ __('triggered') }}</template>
+ <template v-else>{{ __('created') }}</template>
<timeago-tooltip :time="time" />
- by
+ {{ __('by') }}
<template v-if="user">
<gl-link
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
index e438ff16a41..47f0851f650 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,7 +1,7 @@
<script>
import { GlLink } from '@gitlab/ui';
import _ from 'underscore';
-import { sprintf } from '~/locale';
+import { __, sprintf } from '~/locale';
import icon from '../../../vue_shared/components/icon.vue';
function buildDocsLinkStart(path) {
@@ -47,7 +47,9 @@ export default {
},
confidentialAndLockedDiscussionText() {
return sprintf(
- 'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
+ __(
+ 'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
+ ),
{
confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
@@ -66,7 +68,7 @@ export default {
<span v-if="isLockedAndConfidential">
<span v-html="confidentialAndLockedDiscussionText"></span>
{{
- __(`People without permission will never get a notification and won't be able to comment.`)
+ __("People without permission will never get a notification and won't be able to comment.")
}}
</span>
diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
index eb0f666422f..b76679960ca 100644
--- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
@@ -160,8 +160,8 @@ export default {
:disabled="removeDisabled"
type="button"
class="btn btn-default btn-svg btn-item-remove js-issue-item-remove-button qa-remove-issue-button mr-xl-0 align-self-xl-center"
- title="Remove"
- aria-label="Remove"
+ :title="__('Remove')"
+ :aria-label="__('Remove')"
@click="onRemoveRequest"
>
<icon :size="16" class="btn-item-remove-icon" name="close" />
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 3bdc0bb8ebd..b520d302407 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,7 +1,7 @@
<script>
import $ from 'jquery';
import _ from 'underscore';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
@@ -118,6 +118,18 @@ export default {
lineType() {
return this.line ? this.line.type : '';
},
+ addMultipleToDiscussionWarning() {
+ return sprintf(
+ __(
+ '%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution.',
+ ),
+ {
+ icon: '<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>',
+ usersTag: `<strong><span class="js-referenced-users-count">${this.referencedUsers.length}</span></strong>`,
+ },
+ false,
+ );
+ },
},
mounted() {
/*
@@ -172,7 +184,7 @@ export default {
renderMarkdown(data = {}) {
this.markdownPreviewLoading = false;
- this.markdownPreview = data.body || 'Nothing to preview.';
+ this.markdownPreview = data.body || __('Nothing to preview.');
if (data.references) {
this.referencedCommands = data.references.commands;
@@ -207,7 +219,11 @@ export default {
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
<slot name="textarea"></slot>
- <a class="zen-control zen-control-leave js-zen-leave" href="#" aria-label="Enter zen mode">
+ <a
+ class="zen-control zen-control-leave js-zen-leave"
+ href="#"
+ :aria-label="__('Enter zen mode')"
+ >
<icon :size="32" name="screen-normal" />
</a>
<markdown-toolbar
@@ -246,13 +262,7 @@ export default {
<template v-if="previewMarkdown && !markdownPreviewLoading">
<div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div>
<div v-if="shouldShowReferencedUsers" class="referenced-users">
- <span>
- <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> You are about to add
- <strong>
- <span class="js-referenced-users-count">{{ referencedUsers.length }}</span>
- </strong>
- people to the discussion. Proceed with caution.
- </span>
+ <span v-html="addMultipleToDiscussionWarning"></span>
</div>
</template>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 8d3705e1e4a..7f0fcfac071 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -1,5 +1,6 @@
<script>
import Vue from 'vue';
+import { __ } from '~/locale';
import SuggestionDiff from './suggestion_diff.vue';
import Flash from '~/flash';
@@ -56,7 +57,7 @@ export default {
const suggestionElements = container.querySelectorAll('.js-render-suggestion');
if (this.lineType === 'old') {
- Flash('Unable to apply suggestions to a deleted line.', 'alert', this.$el);
+ Flash(__('Unable to apply suggestions to a deleted line.'), 'alert', this.$el);
}
suggestionElements.forEach((suggestionEl, i) => {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index d6c398c8946..8ce5b615795 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -33,13 +33,18 @@ export default {
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1"
- >Markdown is supported</gl-link
- >
+ <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">{{
+ __('Markdown is supported')
+ }}</gl-link>
</template>
<template v-if="hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">Markdown</gl-link> and
- <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">quick actions</gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">{{
+ __('Markdown')
+ }}</gl-link>
+ and
+ <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">{{
+ __('quick actions')
+ }}</gl-link>
are supported
</template>
</div>
@@ -57,15 +62,17 @@ export default {
<i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i>
</span>
<span class="uploading-error-message"></span>
- <button class="retry-uploading-link" type="button">Try again</button> or
- <button class="attach-new-file markdown-selector" type="button">attach a new file</button>
+ <button class="retry-uploading-link" type="button">{{ __('Try again') }}</button> or
+ <button class="attach-new-file markdown-selector" type="button">
+ {{ __('attach a new file') }}
+ </button>
</span>
<button class="markdown-selector button-attach-file btn-link" tabindex="-1" type="button">
<i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i
- ><span class="text-attach-file">Attach a file</span>
+ ><span class="text-attach-file">{{ __('Attach a file') }}</span>
</button>
<button class="btn btn-default btn-sm hide button-cancel-uploading-files" type="button">
- Cancel
+ {{ __('Cancel') }}
</button>
</span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.vue b/app/assets/javascripts/vue_shared/components/memory_graph.vue
index 16f4ff068f6..26d7d8e8866 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.vue
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue
@@ -1,4 +1,5 @@
<script>
+import { __, sprintf } from '~/locale';
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
@@ -20,7 +21,7 @@ export default {
computed: {
getFormattedMedian() {
const deployedSince = getTimeago().format(this.deploymentTime * 1000);
- return `Deployed ${deployedSince}`;
+ return sprintf(__('Deployed %{deployedSince}'), { deployedSince });
},
},
mounted() {
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 3c86b7e4c61..d6dfe9eded8 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -103,7 +103,7 @@ export default {
<div v-if="hasMoreCommits" class="flex-list">
<div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
<icon :name="toggleIcon" :size="8" class="append-right-5" />
- <span>Toggle commit list</span>
+ <span>{{ __('Toggle commit list') }}</span>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
index b9311d65360..43bbb756805 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -14,7 +14,7 @@
/>
*/
-
+import { __ } from '~/locale';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader';
@@ -39,7 +39,7 @@ export default {
imgAlt: {
type: String,
required: false,
- default: 'project avatar',
+ default: __('project avatar'),
},
size: {
type: Number,
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
index b5e43da401e..4dcc121496c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
@@ -85,7 +85,7 @@ export default {
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
- <span v-if="showFromText">From</span> <span>{{ dateText('min') }}</span>
+ <span v-if="showFromText">{{ __('From') }}</span> <span>{{ dateText('min') }}</span>
</span>
</collapsed-calendar-icon>
<div v-if="hasMinAndMaxDates" class="text-center sidebar-collapsed-divider">-</div>
@@ -96,7 +96,7 @@ export default {
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
- <span v-if="!minDate">Until</span> <span>{{ dateText('max') }}</span>
+ <span v-if="!minDate">{{ __('Until') }}</span> <span>{{ dateText('max') }}</span>
</span>
</collapsed-calendar-icon>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 45f01a6fced..6caf8bc92c2 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -74,7 +74,7 @@ export default {
return dateInWords(this.selectedDate, true);
},
collapsedText() {
- return this.selectedDateWords ? this.selectedDateWords : 'None';
+ return this.selectedDateWords ? this.selectedDateWords : __('None');
},
},
methods: {
@@ -112,7 +112,7 @@ export default {
class="btn-blank btn-link btn-primary-hover-link btn-sidebar-action"
@click="toggleDatePicker"
>
- Edit
+ {{ __('Edit') }}
</button>
<toggle-sidebar v-if="showToggleSidebar" :collapsed="collapsed" @toggle="toggleSidebar" />
</div>
@@ -137,11 +137,11 @@ export default {
class="btn-blank btn-link btn-secondary-hover-link"
@click="newDateSelected(null)"
>
- remove
+ {{ __('remove') }}
</button>
</span>
</template>
- <span v-else class="no-value"> None </span>
+ <span v-else class="no-value">{{ __('None') }}</span>
</span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
index 3b5ce0e9910..913c971a512 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
@@ -48,7 +48,7 @@ export default {
'fa-angle-double-right': !collapsed,
'fa-angle-double-left': collapsed,
}"
- aria-label="toggle collapse"
+ :aria-label="__('toggle collapse')"
class="fa"
>
</i>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index a6c1737dcab..ea483416c46 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -17,6 +17,7 @@
import { GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
+import { __ } from '~/locale';
import { placeholderImage } from '../../../lazy_loader';
export default {
@@ -43,7 +44,7 @@ export default {
imgAlt: {
type: String,
required: false,
- default: 'user avatar',
+ default: __('user avatar'),
},
size: {
type: Number,
diff --git a/app/assets/javascripts/vue_shared/mixins/is_ee.js b/app/assets/javascripts/vue_shared/mixins/is_ee.js
deleted file mode 100644
index 8e00d93ef18..00000000000
--- a/app/assets/javascripts/vue_shared/mixins/is_ee.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Vue from 'vue';
-import { isEE } from '~/lib/utils/common_utils';
-
-Vue.mixin({
- computed: {
- isEE() {
- return isEE();
- },
- },
-});
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index cd951f67293..e75c1379dfb 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -288,7 +288,7 @@
padding: 0 1px;
a,
- button,
+ button:not(.dropdown-toggle,.ci-action-icon-container),
.menu-item {
@include dropdown-link;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index f75e5b55506..975dca168d5 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -35,7 +35,8 @@
background-color: $modal-body-bg;
line-height: $line-height-base;
position: relative;
- padding: #{3 * $grid-size} #{2 * $grid-size};
+ min-height: $modal-body-height;
+ padding: #{2 * $grid-size} #{6 * $grid-size} #{2 * $grid-size} #{2 * $grid-size};
text-align: left;
white-space: normal;
@@ -85,9 +86,9 @@ body.modal-open {
.modal {
background-color: $black-transparent;
- @include media-breakpoint-up(md) {
+ @include media-breakpoint-up(sm) {
.modal-dialog {
- margin: 30px auto;
+ margin: 64px auto;
}
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b6a24247d40..406bcda418e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -805,7 +805,7 @@ $border-color-settings: #e1e1e1;
/*
Modals
*/
-$modal-body-height: 134px;
+$modal-body-height: 80px;
$modal-border-color: #e9ecef;
$priority-label-empty-state-width: 114px;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 623c44e062f..3ffe8ae304d 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1095,6 +1095,10 @@ table.code {
.discussion-collapsible {
margin: 0 $gl-padding $gl-padding 71px;
+
+ .notes {
+ border-radius: $border-radius-default;
+ }
}
.parallel {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 7bd1a4138e4..b9b8eabf909 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -139,7 +139,6 @@ $note-form-margin-left: 72px;
border-radius: 4px 4px 0 0;
&.collapsed {
- border: 0;
border-radius: 4px;
}
}
@@ -406,7 +405,7 @@ $note-form-margin-left: 72px;
border-radius: 0;
@media (min-width: map-get($grid-breakpoints, md)) {
- top: 91px;
+ top: $mr-tabs-height + $header-height;
.with-performance-bar & {
top: 126px;
@@ -598,7 +597,8 @@ $note-form-margin-left: 72px;
}
.discussion-header {
- min-height: 74px;
+ min-height: $line-height-base * 2em;
+ box-sizing: content-box;
.note-header-info {
padding-bottom: 0;
@@ -608,13 +608,10 @@ $note-form-margin-left: 72px;
overflow-x: auto;
overflow-y: hidden;
}
-}
-.unresolved {
- .discussion-header {
- .note-header-info {
- margin-top: $gl-padding-8;
- }
+ &.note-wrapper {
+ display: flex;
+ align-items: center;
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 10120a472d3..60400f10ca5 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -168,6 +168,10 @@
}
ul.wiki-pages-list.content-list {
+ a {
+ color: $blue-600;
+ }
+
ul {
list-style: none;
margin-left: 0;