summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorLuke "Jared" Bennett <lbennett@gitlab.com>2017-04-07 11:30:58 +0100
committerLuke "Jared" Bennett <lbennett@gitlab.com>2017-04-07 12:03:26 +0100
commitfb7b0d6e710d462f5bef1df3195fa8b051668bfc (patch)
tree1934efe739a32d6da5c88a58b7420a33c1c916a2 /app/assets
parentf2f44579a3ae944104d76681e75a85e6d0ceb756 (diff)
parent15e87cea3a374e8d929c28f6843170b182fe159a (diff)
downloadgitlab-ce-fb7b0d6e710d462f5bef1df3195fa8b051668bfc.tar.gz
Merge branch 'master' into update-droplab-to-webpack-versionupdate-droplab-to-webpack-version
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/awards_handler.js3
-rw-r--r--app/assets/javascripts/blob/blob_fork_suggestion.js15
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js67
-rw-r--r--app/assets/javascripts/build.js4
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js7
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js72
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js34
-rw-r--r--app/assets/javascripts/diff.js4
-rw-r--r--app/assets/javascripts/dispatcher.js9
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.js9
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js87
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js3
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js4
-rw-r--r--app/assets/javascripts/filtered_search/event_hub.js3
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js89
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_root.js59
-rw-r--r--app/assets/javascripts/filtered_search/services/recent_searches_service.js26
-rw-r--r--app/assets/javascripts/filtered_search/stores/recent_searches_store.js23
-rw-r--r--app/assets/javascripts/lib/utils/poll.js11
-rw-r--r--app/assets/javascripts/merge_request_tabs.js28
-rw-r--r--app/assets/javascripts/monitoring/prometheus_graph.js67
-rw-r--r--app/assets/javascripts/shortcuts.js33
-rw-r--r--app/assets/javascripts/shortcuts_dashboard_navigation.js55
-rw-r--r--app/assets/javascripts/shortcuts_navigation.js65
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js86
-rw-r--r--app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js3
-rw-r--r--app/assets/stylesheets/framework/awards.scss58
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss18
-rw-r--r--app/assets/stylesheets/framework/files.scss13
-rw-r--r--app/assets/stylesheets/framework/filters.scss141
-rw-r--r--app/assets/stylesheets/framework/modal.scss6
-rw-r--r--app/assets/stylesheets/framework/timeline.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss37
-rw-r--r--app/assets/stylesheets/pages/container_registry.scss16
-rw-r--r--app/assets/stylesheets/pages/environments.scss9
-rw-r--r--app/assets/stylesheets/pages/events.scss34
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss261
-rw-r--r--app/assets/stylesheets/pages/profile.scss8
-rw-r--r--app/assets/stylesheets/pages/settings_ci_cd.scss2
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
42 files changed, 1090 insertions, 392 deletions
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 4f63c7988f5..67106e85a37 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -263,7 +263,8 @@ AwardsHandler.prototype.addAward = function addAward(
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined;
});
- return $('.emoji-menu').removeClass('is-visible');
+ $('.emoji-menu').removeClass('is-visible');
+ $('.js-add-award.is-active').removeClass('is-active');
};
AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar(
diff --git a/app/assets/javascripts/blob/blob_fork_suggestion.js b/app/assets/javascripts/blob/blob_fork_suggestion.js
new file mode 100644
index 00000000000..aa9a4e1c99a
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_fork_suggestion.js
@@ -0,0 +1,15 @@
+function BlobForkSuggestion(openButton, cancelButton, suggestionSection) {
+ if (openButton) {
+ openButton.addEventListener('click', () => {
+ suggestionSection.classList.remove('hidden');
+ });
+ }
+
+ if (cancelButton) {
+ cancelButton.addEventListener('click', () => {
+ suggestionSection.classList.add('hidden');
+ });
+ }
+}
+
+export default BlobForkSuggestion;
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index a4629b092bf..e48d3344a2b 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -20,6 +20,7 @@ import eventHub from '../eventhub';
list: {
type: Object,
required: false,
+ default: () => ({}),
},
rootPath: {
type: String,
@@ -31,6 +32,26 @@ import eventHub from '../eventhub';
default: false,
},
},
+ computed: {
+ cardUrl() {
+ return `${this.issueLinkBase}/${this.issue.id}`;
+ },
+ assigneeUrl() {
+ return `${this.rootPath}${this.issue.assignee.username}`;
+ },
+ assigneeUrlTitle() {
+ return `Assigned to ${this.issue.assignee.name}`;
+ },
+ avatarUrlTitle() {
+ return `Avatar for ${this.issue.assignee.name}`;
+ },
+ issueId() {
+ return `#${this.issue.id}`;
+ },
+ showLabelFooter() {
+ return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
+ },
+ },
methods: {
showLabel(label) {
if (!this.list) return true;
@@ -67,35 +88,41 @@ import eventHub from '../eventhub';
},
template: `
<div>
- <h4 class="card-title">
- <i
- class="fa fa-eye-slash confidential-icon"
- v-if="issue.confidential"></i>
- <a
- :href="issueLinkBase + '/' + issue.id"
- :title="issue.title">
- {{ issue.title }}
- </a>
- </h4>
- <div class="card-footer">
- <span
- class="card-number"
- v-if="issue.id">
- #{{ issue.id }}
- </span>
+ <div class="card-header">
+ <h4 class="card-title">
+ <i
+ class="fa fa-eye-slash confidential-icon"
+ v-if="issue.confidential"
+ aria-hidden="true"
+ />
+ <a
+ class="js-no-trigger"
+ :href="cardUrl"
+ :title="issue.title">{{ issue.title }}</a>
+ <span
+ class="card-number"
+ v-if="issue.id"
+ >
+ {{ issueId }}
+ </span>
+ </h4>
<a
class="card-assignee has-tooltip js-no-trigger"
- :href="rootPath + issue.assignee.username"
- :title="'Assigned to ' + issue.assignee.name"
+ :href="assigneeUrl"
+ :title="assigneeUrlTitle"
v-if="issue.assignee"
- data-container="body">
+ data-container="body"
+ >
<img
class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar"
width="20"
height="20"
- :alt="'Avatar for ' + issue.assignee.name" />
+ :alt="avatarUrlTitle"
+ />
</a>
+ </div>
+ <div class="card-footer" v-if="showLabelFooter">
<button
class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels"
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index fe54ecffdfe..7cb022c848a 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -84,10 +84,10 @@ window.Build = (function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
return $.ajax({
- url: this.buildUrl,
+ url: this.pageUrl + "/trace.json",
dataType: 'json',
success: function(buildData) {
- $('.js-build-output').html(buildData.trace_html);
+ $('.js-build-output').html(buildData.html);
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height());
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index a92e068ca5a..5f3ed9374bf 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -8,10 +8,8 @@ Vue.use(VueResource);
/**
* Commits View > Pipelines Tab > Pipelines Table.
- * Merge Request View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
- * Renders Pipelines table in pipelines tab in the merge request show view.
*/
$(() => {
@@ -20,13 +18,14 @@ $(() => {
gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
+ document.querySelector('#commit-pipeline-table-view').removeChild(this.pipelinesTableBundle.$el);
gl.commits.PipelinesTableBundle.$destroy(true);
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
- gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
- gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
+ gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
+ document.querySelector('#commit-pipeline-table-view').appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
}
});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 4d5a857d705..da9707549f9 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
@@ -7,6 +8,7 @@ import EmptyState from '../../vue_pipelines_index/components/empty_state';
import ErrorState from '../../vue_pipelines_index/components/error_state';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
+import Poll from '../../lib/utils/poll';
/**
*
@@ -20,6 +22,7 @@ import '../../vue_shared/vue_resource_interceptor';
*/
export default Vue.component('pipelines-table', {
+
components: {
'pipelines-table-component': PipelinesTableComponent,
'error-state': ErrorState,
@@ -42,6 +45,7 @@ export default Vue.component('pipelines-table', {
state: store.state,
isLoading: false,
hasError: false,
+ isMakingRequest: false,
};
},
@@ -64,17 +68,41 @@ export default Vue.component('pipelines-table', {
*
*/
beforeMount() {
- this.endpoint = this.$el.dataset.endpoint;
- this.helpPagePath = this.$el.dataset.helpPagePath;
+ const element = document.querySelector('#commit-pipeline-table-view');
+
+ this.endpoint = element.dataset.endpoint;
+ this.helpPagePath = element.dataset.helpPagePath;
this.service = new PipelinesService(this.endpoint);
- this.fetchPipelines();
+ this.poll = new Poll({
+ resource: this.service,
+ method: 'getPipelines',
+ successCallback: this.successCallback,
+ errorCallback: this.errorCallback,
+ notificationCallback: this.setIsMakingRequest,
+ });
+
+ if (!Visibility.hidden()) {
+ this.isLoading = true;
+ this.poll.makeRequest();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ this.poll.restart();
+ } else {
+ this.poll.stop();
+ }
+ });
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
- if (this.state.pipelines.length && this.$children) {
+ if (this.state.pipelines.length &&
+ this.$children &&
+ !this.isMakingRequest &&
+ !this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
@@ -83,21 +111,35 @@ export default Vue.component('pipelines-table', {
eventHub.$off('refreshPipelines');
},
+ destroyed() {
+ this.poll.stop();
+ },
+
methods: {
fetchPipelines() {
this.isLoading = true;
+
return this.service.getPipelines()
- .then(response => response.json())
- .then((json) => {
- // depending of the endpoint the response can either bring a `pipelines` key or not.
- const pipelines = json.pipelines || json;
- this.store.storePipelines(pipelines);
- this.isLoading = false;
- })
- .catch(() => {
- this.hasError = true;
- this.isLoading = false;
- });
+ .then(response => this.successCallback(response))
+ .catch(() => this.errorCallback());
+ },
+
+ successCallback(resp) {
+ const response = resp.json();
+
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = response.pipelines || response;
+ this.store.storePipelines(pipelines);
+ this.isLoading = false;
+ },
+
+ errorCallback() {
+ this.hasError = true;
+ this.isLoading = false;
+ },
+
+ setIsMakingRequest(isMakingRequest) {
+ this.isMakingRequest = isMakingRequest;
},
},
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index 6dbec50b890..ab9a8e43dd1 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -38,9 +38,35 @@ showTooltip = function(target, title) {
};
$(function() {
- var clipboard;
-
- clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
+ const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
- return clipboard.on('error', genericError);
+ clipboard.on('error', genericError);
+
+ // This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
+ // The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and `gfm` keys into the `data-clipboard-text`
+ // attribute that ClipboardJS reads from.
+ // When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly` attribute`), sets its value
+ // to the value of this data attribute, focusses on it, and finally programmatically issues the 'Copy' command,
+ // this code intercepts the copy command/event at the last minute to deconstruct this JSON hash and set the
+ // `text/plain` and `text/x-gfm` copy data types to the intended values.
+ $(document).on('copy', 'body > textarea[readonly]', function(e) {
+ const clipboardData = e.originalEvent.clipboardData;
+ if (!clipboardData) return;
+
+ const text = e.target.value;
+
+ let json;
+ try {
+ json = JSON.parse(text);
+ } catch (ex) {
+ return;
+ }
+
+ if (!json.text || !json.gfm) return;
+
+ e.preventDefault();
+
+ clipboardData.setData('text/plain', json.text);
+ clipboardData.setData('text/x-gfm', json.gfm);
+ });
});
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 88180149715..5aa3eb46a69 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -13,10 +13,6 @@ class Diff {
$diffFile.each((index, file) => new gl.ImageFile(file));
- if (this.diffViewType() === 'parallel') {
- $('.content-wrapper .container-fluid').removeClass('container-limited');
- }
-
if (!isBound) {
$(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 9c7acc903d1..0a1a62fb012 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -24,7 +24,6 @@
/* global Search */
/* global Admin */
/* global NamespaceSelects */
-/* global ShortcutsDashboardNavigation */
/* global Project */
/* global ProjectAvatar */
/* global CompareAutocomplete */
@@ -43,6 +42,7 @@ import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
+import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
const ShortcutsBlob = require('./shortcuts_blob');
@@ -86,6 +86,12 @@ const ShortcutsBlob = require('./shortcuts_blob');
skipResetBindings: true,
fileBlobPermalinkUrl,
});
+
+ new BlobForkSuggestion(
+ document.querySelector('.js-edit-blob-link-fork-toggler'),
+ document.querySelector('.js-cancel-fork-suggestion'),
+ document.querySelector('.js-file-fork-suggestion-section'),
+ );
}
switch (page) {
@@ -371,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'dashboard':
case 'root':
- shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break;
case 'groups':
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.js
index 8abbcf0c227..d2514593e3a 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js
@@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', {
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
-
- // svgs
- commitIconSvg: environmentsData.commitIconSvg,
- playIconSvg: environmentsData.playIconSvg,
- terminalIconSvg: environmentsData.terminalIconSvg,
-
// Pagination Properties,
paginationInformation: {},
pageNumber: 1,
@@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
- :play-icon-svg="playIconSvg"
- :terminal-icon-svg="terminalIconSvg"
- :commit-icon-svg="commitIconSvg"
:service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js
new file mode 100644
index 00000000000..9126422b335
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js
@@ -0,0 +1,87 @@
+import eventHub from '../event_hub';
+
+export default {
+ name: 'RecentSearchesDropdownContent',
+
+ props: {
+ items: {
+ type: Array,
+ required: true,
+ },
+ },
+
+ computed: {
+ processedItems() {
+ return this.items.map((item) => {
+ const { tokens, searchToken }
+ = gl.FilteredSearchTokenizer.processTokens(item);
+
+ const resultantTokens = tokens.map(token => ({
+ prefix: `${token.key}:`,
+ suffix: `${token.symbol}${token.value}`,
+ }));
+
+ return {
+ text: item,
+ tokens: resultantTokens,
+ searchToken,
+ };
+ });
+ },
+ hasItems() {
+ return this.items.length > 0;
+ },
+ },
+
+ methods: {
+ onItemActivated(text) {
+ eventHub.$emit('recentSearchesItemSelected', text);
+ },
+ onRequestClearRecentSearches(e) {
+ // Stop the dropdown from closing
+ e.stopPropagation();
+
+ eventHub.$emit('requestClearRecentSearches');
+ },
+ },
+
+ template: `
+ <div>
+ <ul v-if="hasItems">
+ <li
+ v-for="(item, index) in processedItems"
+ :key="index">
+ <button
+ type="button"
+ class="filtered-search-history-dropdown-item"
+ @click="onItemActivated(item.text)">
+ <span>
+ <span
+ v-for="(token, tokenIndex) in item.tokens"
+ class="filtered-search-history-dropdown-token">
+ <span class="name">{{ token.prefix }}</span><span class="value">{{ token.suffix }}</span>
+ </span>
+ </span>
+ <span class="filtered-search-history-dropdown-search-token">
+ {{ item.searchToken }}
+ </span>
+ </button>
+ </li>
+ <li class="divider"></li>
+ <li>
+ <button
+ type="button"
+ class="filtered-search-history-clear-button"
+ @click="onRequestClearRecentSearches($event)">
+ Clear recent searches
+ </button>
+ </li>
+ </ul>
+ <div
+ v-else
+ class="dropdown-info-note">
+ You don't have any recent searches
+ </div>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index ede5d65ce12..381c40c03d8 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -55,7 +55,8 @@ require('./filtered_search_dropdown');
renderContent() {
const dropdownData = [];
- [].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
+
+ [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 432b0c0dfd2..6c5c20447f7 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -129,7 +129,9 @@ import FilteredSearchContainer from './container';
}
});
- return values.join(' ');
+ return values
+ .map(value => value.trim())
+ .join(' ');
}
static getSearchInput(filteredSearchInput) {
diff --git a/app/assets/javascripts/filtered_search/event_hub.js b/app/assets/javascripts/filtered_search/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 36090b49651..b93a8f1d322 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -1,18 +1,56 @@
+/* global Flash */
+
import FilteredSearchContainer from './container';
+import RecentSearchesRoot from './recent_searches_root';
+import RecentSearchesStore from './stores/recent_searches_store';
+import RecentSearchesService from './services/recent_searches_service';
+import eventHub from './event_hub';
(() => {
class FilteredSearchManager {
constructor(page) {
this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
+ this.filteredSearchInputForm = this.filteredSearchInput.form;
this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
+ this.recentSearchesStore = new RecentSearchesStore();
+ let recentSearchesKey = 'issue-recent-searches';
+ if (page === 'merge_requests') {
+ recentSearchesKey = 'merge-request-recent-searches';
+ }
+ this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
+
+ // Fetch recent searches from localStorage
+ this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
+ .catch(() => {
+ // eslint-disable-next-line no-new
+ new Flash('An error occured while parsing recent searches');
+ // Gracefully fail to empty array
+ return [];
+ })
+ .then((searches) => {
+ // Put any searches that may have come in before
+ // we fetched the saved searches ahead of the already saved ones
+ const resultantSearches = this.recentSearchesStore.setRecentSearches(
+ this.recentSearchesStore.state.recentSearches.concat(searches),
+ );
+ this.recentSearchesService.save(resultantSearches);
+ });
+
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
+ this.recentSearchesRoot = new RecentSearchesRoot(
+ this.recentSearchesStore,
+ this.recentSearchesService,
+ document.querySelector('.js-filtered-search-history-dropdown'),
+ );
+ this.recentSearchesRoot.init();
+
this.bindEvents();
this.loadSearchParamsFromURL();
this.dropdownManager.setDropdown();
@@ -25,6 +63,10 @@ import FilteredSearchContainer from './container';
cleanup() {
this.unbindEvents();
document.removeEventListener('beforeunload', this.cleanupWrapper);
+
+ if (this.recentSearchesRoot) {
+ this.recentSearchesRoot.destroy();
+ }
}
bindEvents() {
@@ -34,7 +76,7 @@ import FilteredSearchContainer from './container';
this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this);
this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this);
- this.clearSearchWrapper = this.clearSearch.bind(this);
+ this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
@@ -42,8 +84,8 @@ import FilteredSearchContainer from './container';
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
+ this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
- this.filteredSearchInputForm = this.filteredSearchInput.form;
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
@@ -56,11 +98,12 @@ import FilteredSearchContainer from './container';
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
- this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
+ this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
document.addEventListener('keydown', this.removeSelectedTokenWrapper);
+ eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
unbindEvents() {
@@ -76,11 +119,12 @@ import FilteredSearchContainer from './container';
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
- this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
+ this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
+ eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
checkForBackspace(e) {
@@ -131,7 +175,7 @@ import FilteredSearchContainer from './container';
}
addInputContainerFocus() {
- const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container');
+ const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
if (inputContainer) {
inputContainer.classList.add('focus');
@@ -139,7 +183,7 @@ import FilteredSearchContainer from './container';
}
removeInputContainerFocus(e) {
- const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container');
+ const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null;
@@ -161,7 +205,7 @@ import FilteredSearchContainer from './container';
}
unselectEditTokens(e) {
- const inputContainer = this.container.querySelector('.filtered-search-input-container');
+ const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementTokensContainer = e.target.classList.contains('tokens-container');
@@ -215,9 +259,12 @@ import FilteredSearchContainer from './container';
}
}
- clearSearch(e) {
+ onClearSearch(e) {
e.preventDefault();
+ this.clearSearch();
+ }
+ clearSearch() {
this.filteredSearchInput.value = '';
const removeElements = [];
@@ -289,6 +336,17 @@ import FilteredSearchContainer from './container';
this.search();
}
+ saveCurrentSearchQuery() {
+ // Don't save before we have fetched the already saved searches
+ this.fetchingRecentSearchesPromise.then(() => {
+ const searchQuery = gl.DropdownUtils.getSearchQuery();
+ if (searchQuery.length > 0) {
+ const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
+ this.recentSearchesService.save(resultantSearches);
+ }
+ });
+ }
+
loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray();
const usernameParams = this.getUsernameParams();
@@ -343,6 +401,8 @@ import FilteredSearchContainer from './container';
}
});
+ this.saveCurrentSearchQuery();
+
if (hasFilteredSearch) {
this.clearSearchButton.classList.remove('hidden');
this.handleInputPlaceholder();
@@ -351,8 +411,12 @@ import FilteredSearchContainer from './container';
search() {
const paths = [];
+ const searchQuery = gl.DropdownUtils.getSearchQuery();
+
+ this.saveCurrentSearchQuery();
+
const { tokens, searchToken }
- = this.tokenizer.processTokens(gl.DropdownUtils.getSearchQuery());
+ = this.tokenizer.processTokens(searchQuery);
const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
@@ -416,6 +480,13 @@ import FilteredSearchContainer from './container';
currentDropdownRef.dispatchInputEvent();
}
}
+
+ onrecentSearchesItemSelected(text) {
+ this.clearSearch();
+ this.filteredSearchInput.value = text;
+ this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
+ this.search();
+ }
}
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/filtered_search/recent_searches_root.js b/app/assets/javascripts/filtered_search/recent_searches_root.js
new file mode 100644
index 00000000000..4e38409e12a
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/recent_searches_root.js
@@ -0,0 +1,59 @@
+import Vue from 'vue';
+import RecentSearchesDropdownContent from './components/recent_searches_dropdown_content';
+import eventHub from './event_hub';
+
+class RecentSearchesRoot {
+ constructor(
+ recentSearchesStore,
+ recentSearchesService,
+ wrapperElement,
+ ) {
+ this.store = recentSearchesStore;
+ this.service = recentSearchesService;
+ this.wrapperElement = wrapperElement;
+ }
+
+ init() {
+ this.bindEvents();
+ this.render();
+ }
+
+ bindEvents() {
+ this.onRequestClearRecentSearchesWrapper = this.onRequestClearRecentSearches.bind(this);
+
+ eventHub.$on('requestClearRecentSearches', this.onRequestClearRecentSearchesWrapper);
+ }
+
+ unbindEvents() {
+ eventHub.$off('requestClearRecentSearches', this.onRequestClearRecentSearchesWrapper);
+ }
+
+ render() {
+ this.vm = new Vue({
+ el: this.wrapperElement,
+ data: this.store.state,
+ template: `
+ <recent-searches-dropdown-content
+ :items="recentSearches" />
+ `,
+ components: {
+ 'recent-searches-dropdown-content': RecentSearchesDropdownContent,
+ },
+ });
+ }
+
+ onRequestClearRecentSearches() {
+ const resultantSearches = this.store.setRecentSearches([]);
+ this.service.save(resultantSearches);
+ }
+
+ destroy() {
+ this.unbindEvents();
+ if (this.vm) {
+ this.vm.$destroy();
+ }
+ }
+
+}
+
+export default RecentSearchesRoot;
diff --git a/app/assets/javascripts/filtered_search/services/recent_searches_service.js b/app/assets/javascripts/filtered_search/services/recent_searches_service.js
new file mode 100644
index 00000000000..3e402d5aed0
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/services/recent_searches_service.js
@@ -0,0 +1,26 @@
+class RecentSearchesService {
+ constructor(localStorageKey = 'issuable-recent-searches') {
+ this.localStorageKey = localStorageKey;
+ }
+
+ fetch() {
+ const input = window.localStorage.getItem(this.localStorageKey);
+
+ let searches = [];
+ if (input && input.length > 0) {
+ try {
+ searches = JSON.parse(input);
+ } catch (err) {
+ return Promise.reject(err);
+ }
+ }
+
+ return Promise.resolve(searches);
+ }
+
+ save(searches = []) {
+ window.localStorage.setItem(this.localStorageKey, JSON.stringify(searches));
+ }
+}
+
+export default RecentSearchesService;
diff --git a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
new file mode 100644
index 00000000000..066be69766a
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
@@ -0,0 +1,23 @@
+import _ from 'underscore';
+
+class RecentSearchesStore {
+ constructor(initialState = {}) {
+ this.state = Object.assign({
+ recentSearches: [],
+ }, initialState);
+ }
+
+ addRecentSearch(newSearch) {
+ this.setRecentSearches([newSearch].concat(this.state.recentSearches));
+
+ return this.state.recentSearches;
+ }
+
+ setRecentSearches(searches = []) {
+ const trimmedSearches = searches.map(search => search.trim());
+ this.state.recentSearches = _.uniq(trimmedSearches).slice(0, 5);
+ return this.state.recentSearches;
+ }
+}
+
+export default RecentSearchesStore;
diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js
index 5c22aea51cd..e31cc5fbabe 100644
--- a/app/assets/javascripts/lib/utils/poll.js
+++ b/app/assets/javascripts/lib/utils/poll.js
@@ -65,7 +65,6 @@ export default class Poll {
this.makeRequest();
}, pollInterval);
}
-
this.options.successCallback(response);
}
@@ -76,8 +75,14 @@ export default class Poll {
notificationCallback(true);
return resource[method](data)
- .then(response => this.checkConditions(response))
- .catch(error => errorCallback(error));
+ .then((response) => {
+ this.checkConditions(response);
+ notificationCallback(false);
+ })
+ .catch((error) => {
+ notificationCallback(false);
+ errorCallback(error);
+ });
}
/**
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 3c4e6102469..8528e0800ae 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -90,6 +90,7 @@ import './flash';
.on('click', this.clickTab);
}
+ // Used in tests
unbindEvents() {
$(document)
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
@@ -99,9 +100,11 @@ import './flash';
.off('click', this.clickTab);
}
- destroy() {
- this.unbindEvents();
+ destroyPipelinesView() {
if (this.commitPipelinesTable) {
+ document.querySelector('#commit-pipeline-table-view')
+ .removeChild(this.commitPipelinesTable.$el);
+
this.commitPipelinesTable.$destroy();
}
}
@@ -128,6 +131,7 @@ import './flash';
this.loadCommits($target.attr('href'));
this.expandView();
this.resetViewContainer();
+ this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
if (Breakpoints.get().getBreakpointSize() !== 'lg') {
@@ -136,12 +140,14 @@ import './flash';
if (this.diffViewType() === 'parallel') {
this.expandViewContainer();
}
+ this.destroyPipelinesView();
} else if (action === 'pipelines') {
this.resetViewContainer();
- this.loadPipelines();
+ this.mountPipelinesView();
} else {
this.expandView();
this.resetViewContainer();
+ this.destroyPipelinesView();
}
if (this.setUrl) {
this.setCurrentAction(action);
@@ -227,16 +233,12 @@ import './flash';
});
}
- loadPipelines() {
- if (this.pipelinesLoaded) {
- return;
- }
- const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
- // Could already be mounted from the `pipelines_bundle`
- if (pipelineTableViewEl) {
- this.commitPipelinesTable = new CommitPipelinesTable().$mount(pipelineTableViewEl);
- }
- this.pipelinesLoaded = true;
+ mountPipelinesView() {
+ this.commitPipelinesTable = new CommitPipelinesTable().$mount();
+ // $mount(el) replaces the el with the new rendered component. We need it in order to mount
+ // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
+ document.querySelector('#commit-pipeline-table-view')
+ .appendChild(this.commitPipelinesTable.$el);
}
loadDiff(source) {
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
index a6ffa0f59de..d82a4eb9642 100644
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -6,7 +6,10 @@ import statusCodes from '~/lib/utils/http_status';
import { formatRelevantDigits } from '~/lib/utils/number_utils';
import '../flash';
+const prometheusContainer = '.prometheus-container';
+const prometheusParentGraphContainer = '.prometheus-graphs';
const prometheusGraphsContainer = '.prometheus-graph';
+const prometheusStatesContainer = '.prometheus-state';
const metricsEndpoint = 'metrics.json';
const timeFormat = d3.time.format('%H:%M');
const dayFormat = d3.time.format('%b %e, %a');
@@ -14,19 +17,30 @@ const bisectDate = d3.bisector(d => d.time).left;
const extraAddedWidthParent = 100;
class PrometheusGraph {
-
constructor() {
- this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
- this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
- const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
- extraAddedWidthParent;
- this.originalWidth = parentContainerWidth;
- this.originalHeight = 330;
- this.width = parentContainerWidth - this.margin.left - this.margin.right;
- this.height = this.originalHeight - this.margin.top - this.margin.bottom;
- this.backOffRequestCounter = 0;
- this.configureGraph();
- this.init();
+ const $prometheusContainer = $(prometheusContainer);
+ const hasMetrics = $prometheusContainer.data('has-metrics');
+ this.docLink = $prometheusContainer.data('doc-link');
+ this.integrationLink = $prometheusContainer.data('prometheus-integration');
+
+ $(document).ajaxError(() => {});
+
+ if (hasMetrics) {
+ this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
+ this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
+ const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
+ extraAddedWidthParent;
+ this.originalWidth = parentContainerWidth;
+ this.originalHeight = 330;
+ this.width = parentContainerWidth - this.margin.left - this.margin.right;
+ this.height = this.originalHeight - this.margin.top - this.margin.bottom;
+ this.backOffRequestCounter = 0;
+ this.configureGraph();
+ this.init();
+ } else {
+ this.state = '.js-getting-started';
+ this.updateState();
+ }
}
createGraph() {
@@ -40,8 +54,19 @@ class PrometheusGraph {
init() {
this.getData().then((metricsResponse) => {
- if (Object.keys(metricsResponse).length === 0) {
- new Flash('Empty metrics', 'alert');
+ let enoughData = true;
+ Object.keys(metricsResponse.metrics).forEach((key) => {
+ let currentKey;
+ if (key === 'cpu_values' || key === 'memory_values') {
+ currentKey = metricsResponse.metrics[key];
+ if (Object.keys(currentKey).length === 0) {
+ enoughData = false;
+ }
+ }
+ });
+ if (!enoughData) {
+ this.state = '.js-loading';
+ this.updateState();
} else {
this.transformData(metricsResponse);
this.createGraph();
@@ -345,14 +370,17 @@ class PrometheusGraph {
}
return resp.metrics;
})
- .catch(() => new Flash('An error occurred while fetching metrics.', 'alert'));
+ .catch(() => {
+ this.state = '.js-unable-to-connect';
+ this.updateState();
+ });
}
transformData(metricsResponse) {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
- if (typeof metricValues !== 'undefined') {
+ if (metricValues !== undefined) {
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
@@ -361,6 +389,13 @@ class PrometheusGraph {
}
});
}
+
+ updateState() {
+ const $statesContainer = $(prometheusStatesContainer);
+ $(prometheusParentGraphContainer).hide();
+ $(`${this.state}`, $statesContainer).removeClass('hidden');
+ $(prometheusStatesContainer).show();
+ }
}
export default PrometheusGraph;
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index fd5097696ad..5b6bb2bf3f5 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */
/* global findFileURL */
+import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -14,11 +15,33 @@
}
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
- Mousetrap.bind('f', (function(_this) {
- return function(e) {
- return _this.focusFilter(e);
- };
- })(this));
+ Mousetrap.bind('f', (e => this.focusFilter(e)));
+
+ const $globalDropdownMenu = $('.global-dropdown-menu');
+ const $globalDropdownToggle = $('.global-dropdown-toggle');
+
+ $('.global-dropdown').on('hide.bs.dropdown', () => {
+ $globalDropdownMenu.removeClass('shortcuts');
+ });
+
+ Mousetrap.bind('n', () => {
+ $globalDropdownMenu.toggleClass('shortcuts');
+ $globalDropdownToggle.trigger('click');
+
+ if (!$globalDropdownMenu.is(':visible')) {
+ $globalDropdownToggle.blur();
+ }
+ });
+
+ Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos'));
+ Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity'));
+ Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues'));
+ Mousetrap.bind('shift+m', () => findAndFollowLink('.dashboard-shortcuts-merge_requests'));
+ Mousetrap.bind('shift+p', () => findAndFollowLink('.dashboard-shortcuts-projects'));
+ Mousetrap.bind('shift+g', () => findAndFollowLink('.dashboard-shortcuts-groups'));
+ Mousetrap.bind('shift+l', () => findAndFollowLink('.dashboard-shortcuts-milestones'));
+ Mousetrap.bind('shift+s', () => findAndFollowLink('.dashboard-shortcuts-snippets'));
+
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() {
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js
index 4f1a19924a4..25f39e4fdb6 100644
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js
@@ -1,43 +1,12 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
-/* global Mousetrap */
-/* global Shortcuts */
-
-require('./shortcuts');
-
-(function() {
- var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
- this.ShortcutsDashboardNavigation = (function(superClass) {
- extend(ShortcutsDashboardNavigation, superClass);
-
- function ShortcutsDashboardNavigation() {
- ShortcutsDashboardNavigation.__super__.constructor.call(this);
- Mousetrap.bind('g a', function() {
- return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity');
- });
- Mousetrap.bind('g i', function() {
- return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues');
- });
- Mousetrap.bind('g m', function() {
- return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests');
- });
- Mousetrap.bind('g t', function() {
- return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos');
- });
- Mousetrap.bind('g p', function() {
- return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects');
- });
- }
-
- ShortcutsDashboardNavigation.findAndFollowLink = function(selector) {
- var link;
- link = $(selector).attr('href');
- if (link) {
- return window.location = link;
- }
- };
-
- return ShortcutsDashboardNavigation;
- })(Shortcuts);
-}).call(window);
+/**
+ * Helper function that finds the href of the fiven selector and updates the location.
+ *
+ * @param {String} selector
+ */
+export default (selector) => {
+ const link = document.querySelector(selector).getAttribute('href');
+
+ if (link) {
+ window.location = link;
+ }
+};
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index 3f5d6724417..c74ab0afd0c 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
+import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts');
@@ -13,59 +14,23 @@ require('./shortcuts');
function ShortcutsNavigation() {
ShortcutsNavigation.__super__.constructor.call(this);
- Mousetrap.bind('g p', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
- });
- Mousetrap.bind('g e', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
- });
- Mousetrap.bind('g f', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
- });
- Mousetrap.bind('g c', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits');
- });
- Mousetrap.bind('g b', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds');
- });
- Mousetrap.bind('g n', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
- });
- Mousetrap.bind('g g', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-repository-charts');
- });
- Mousetrap.bind('g i', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
- });
- Mousetrap.bind('g l', function() {
- ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
- });
- Mousetrap.bind('g m', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
- });
- Mousetrap.bind('g t', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos');
- });
- Mousetrap.bind('g w', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki');
- });
- Mousetrap.bind('g s', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets');
- });
- Mousetrap.bind('i', function() {
- return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue');
- });
+ Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
+ Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity'));
+ Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
+ Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
+ Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
+ Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network'));
+ Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts'));
+ Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
+ Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
+ Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
+ Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
+ Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
+ Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
+ Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
this.enabledHelp.push('.hidden-shortcut.project');
}
- ShortcutsNavigation.findAndFollowLink = function(selector) {
- var link;
- link = $(selector).attr('href');
- if (link) {
- return window.location = link;
- }
- };
-
return ShortcutsNavigation;
})(Shortcuts);
}).call(window);
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js
index 9bdc232b7da..5575aa72d5e 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
import PipelinesTableComponent from '../vue_shared/components/pipelines_table';
@@ -7,6 +8,7 @@ import EmptyState from './components/empty_state';
import ErrorState from './components/error_state';
import NavigationTabs from './components/navigation_tabs';
import NavigationControls from './components/nav_controls';
+import Poll from '../lib/utils/poll';
export default {
props: {
@@ -47,6 +49,7 @@ export default {
pagenum: 1,
isLoading: false,
hasError: false,
+ isMakingRequest: false,
};
},
@@ -120,18 +123,49 @@ export default {
tagsPath: this.tagsPath,
};
},
+
+ pageParameter() {
+ return gl.utils.getParameterByName('page') || this.pagenum;
+ },
+
+ scopeParameter() {
+ return gl.utils.getParameterByName('scope') || this.apiScope;
+ },
},
created() {
this.service = new PipelinesService(this.endpoint);
- this.fetchPipelines();
+ const poll = new Poll({
+ resource: this.service,
+ method: 'getPipelines',
+ data: { page: this.pageParameter, scope: this.scopeParameter },
+ successCallback: this.successCallback,
+ errorCallback: this.errorCallback,
+ notificationCallback: this.setIsMakingRequest,
+ });
+
+ if (!Visibility.hidden()) {
+ this.isLoading = true;
+ poll.makeRequest();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ poll.restart();
+ } else {
+ poll.stop();
+ }
+ });
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
- if (this.state.pipelines.length && this.$children) {
+ if (this.state.pipelines.length &&
+ this.$children &&
+ !this.isMakingRequest &&
+ !this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
@@ -154,27 +188,35 @@ export default {
},
fetchPipelines() {
- const pageNumber = gl.utils.getParameterByName('page') || this.pagenum;
- const scope = gl.utils.getParameterByName('scope') || this.apiScope;
+ if (!this.isMakingRequest) {
+ this.isLoading = true;
- this.isLoading = true;
- return this.service.getPipelines(scope, pageNumber)
- .then(resp => ({
- headers: resp.headers,
- body: resp.json(),
- }))
- .then((response) => {
- this.store.storeCount(response.body.count);
- this.store.storePipelines(response.body.pipelines);
- this.store.storePagination(response.headers);
- })
- .then(() => {
- this.isLoading = false;
- })
- .catch(() => {
- this.hasError = true;
- this.isLoading = false;
- });
+ this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter })
+ .then(response => this.successCallback(response))
+ .catch(() => this.errorCallback());
+ }
+ },
+
+ successCallback(resp) {
+ const response = {
+ headers: resp.headers,
+ body: resp.json(),
+ };
+
+ this.store.storeCount(response.body.count);
+ this.store.storePipelines(response.body.pipelines);
+ this.store.storePagination(response.headers);
+
+ this.isLoading = false;
+ },
+
+ errorCallback() {
+ this.hasError = true;
+ this.isLoading = false;
+ },
+
+ setIsMakingRequest(isMakingRequest) {
+ this.isMakingRequest = isMakingRequest;
},
},
diff --git a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js b/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js
index 708f5068dd3..255cd513490 100644
--- a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js
+++ b/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js
@@ -26,7 +26,8 @@ export default class PipelinesService {
this.pipelines = Vue.resource(endpoint);
}
- getPipelines(scope, page) {
+ getPipelines(data = {}) {
+ const { scope, page } = data;
return this.pipelines.get({ scope, page });
}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 1ae144fb471..b849cc2d853 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -91,7 +91,7 @@
.award-menu-holder {
display: inline-block;
- position: relative;
+ position: absolute;
.tooltip {
white-space: nowrap;
@@ -117,11 +117,41 @@
&.active,
&:hover,
- &:active {
+ &:active,
+ &.is-active {
background-color: $row-hover;
border-color: $row-hover-border;
box-shadow: none;
outline: 0;
+
+ .award-control-icon svg {
+ background: $award-emoji-positive-add-bg;
+
+ path {
+ fill: $award-emoji-positive-add-lines;
+ }
+ }
+
+ .award-control-icon-neutral {
+ opacity: 0;
+ }
+
+ .award-control-icon-positive {
+ opacity: 1;
+ transform: scale(1.15);
+ }
+ }
+
+ &.is-active {
+ .award-control-icon-positive {
+ opacity: 0;
+ transform: scale(1);
+ }
+
+ .award-control-icon-super-positive {
+ opacity: 1;
+ transform: scale(1);
+ }
}
&.btn {
@@ -162,9 +192,33 @@
color: $border-gray-normal;
margin-top: 1px;
padding: 0 2px;
+
+ svg {
+ margin-bottom: 1px;
+ height: 18px;
+ width: 18px;
+ border-radius: 50%;
+
+ path {
+ fill: $border-gray-normal;
+ }
+ }
+ }
+
+ .award-control-icon-positive,
+ .award-control-icon-super-positive {
+ position: absolute;
+ left: 7px;
+ bottom: 9px;
+ opacity: 0;
+ @include transition(opacity, transform);
}
.award-control-text {
vertical-align: middle;
}
}
+
+.note-awards .award-control-icon-positive {
+ left: 6px;
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 2ede47e9de6..7767826b033 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -177,10 +177,6 @@
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
- .filtered-search-input-container & {
- max-width: 280px;
- }
-
&.is-loading {
.dropdown-content {
display: none;
@@ -191,6 +187,15 @@
}
}
+ .shortcut-mappings {
+ display: none;
+ }
+
+ &.shortcuts .shortcut-mappings {
+ display: inline-block;
+ margin-right: 5px;
+ }
+
ul {
margin: 0;
padding: 0;
@@ -467,6 +472,11 @@
overflow-y: auto;
}
+.dropdown-info-note {
+ color: $gl-text-color-secondary;
+ text-align: center;
+}
+
.dropdown-footer {
padding-top: 10px;
margin-top: 10px;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index ddea1cf540b..a5a8522739e 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -281,3 +281,16 @@ span.idiff {
display: none;
}
}
+
+.file-fork-suggestion {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ background-color: $gray-light;
+ border-bottom: 1px solid $border-color;
+ padding: 5px $gl-padding;
+}
+
+.file-fork-suggestion-note {
+ margin-right: 1.5em;
+}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5a034e11206..12465d4a70b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -22,7 +22,6 @@
}
@media (min-width: $screen-sm-min) {
- .issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
width: 132px;
@@ -56,7 +55,7 @@
}
}
-.filtered-search-container {
+.filtered-search-wrapper {
display: -webkit-flex;
display: flex;
@@ -151,11 +150,13 @@
width: 100%;
}
-.filtered-search-input-container {
+.filtered-search-box {
+ position: relative;
+ flex: 1;
display: -webkit-flex;
display: flex;
- position: relative;
width: 100%;
+ min-width: 0;
border: 1px solid $border-color;
background-color: $white-light;
@@ -163,14 +164,6 @@
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
margin-bottom: 10px;
-
- .dropdown-menu {
- width: auto;
- left: 0;
- right: 0;
- max-width: none;
- min-width: 100%;
- }
}
&:hover {
@@ -229,6 +222,118 @@
}
}
+.filtered-search-box-input-container {
+ flex: 1;
+ position: relative;
+ // Fix PhantomJS not supporting `flex: 1;` properly.
+ // This is important because it can change the expected `e.target` when clicking things in tests.
+ // See https://gitlab.com/gitlab-org/gitlab-ce/blob/b54acba8b732688c59fe2f38510c469dc86ee499/spec/features/issues/filtered_search/visual_tokens_spec.rb#L61
+ // - With `width: 100%`: `e.target` = `.tokens-container`, https://i.imgur.com/jGq7wbx.png
+ // - Without `width: 100%`: `e.target` = `.filtered-search`, https://i.imgur.com/cNI2CyT.png
+ width: 100%;
+ min-width: 0;
+}
+
+.filtered-search-input-dropdown-menu {
+ max-width: 280px;
+
+ @media (max-width: $screen-xs-min) {
+ width: auto;
+ left: 0;
+ right: 0;
+ max-width: none;
+ min-width: 100%;
+ }
+}
+
+.filtered-search-history-dropdown-toggle-button {
+ display: flex;
+ align-items: center;
+ width: auto;
+ height: 100%;
+ padding-top: 0;
+ padding-left: 0.75em;
+ padding-bottom: 0;
+ padding-right: 0.5em;
+
+ background-color: transparent;
+ border-radius: 0;
+ border-top: 0;
+ border-left: 0;
+ border-bottom: 0;
+ border-right: 1px solid $border-color;
+
+ color: $gl-text-color-secondary;
+
+ transition: color 0.1s linear;
+
+ &:hover,
+ &:focus {
+ color: $gl-text-color;
+ border-color: $dropdown-input-focus-border;
+ outline: none;
+ }
+
+ .dropdown-toggle-text {
+ color: inherit;
+
+ .fa {
+ color: inherit;
+ }
+ }
+
+ .fa {
+ position: initial;
+ }
+
+}
+
+.filtered-search-history-dropdown-wrapper {
+ position: initial;
+ flex-shrink: 0;
+}
+
+.filtered-search-history-dropdown {
+ width: 40%;
+
+ @media (max-width: $screen-xs-min) {
+ left: 0;
+ right: 0;
+ max-width: none;
+ }
+}
+
+.filtered-search-history-dropdown-content {
+ max-height: none;
+}
+
+.filtered-search-history-dropdown-item,
+.filtered-search-history-clear-button {
+ @include dropdown-link;
+
+ overflow: hidden;
+ width: 100%;
+ margin: 0.5em 0;
+
+ background-color: transparent;
+ border: 0;
+ text-align: left;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.filtered-search-history-dropdown-token {
+ display: inline;
+
+ &:not(:last-child) {
+ margin-right: 0.3em;
+ }
+
+ & > .value {
+ font-weight: 600;
+ }
+}
+
.filter-dropdown-container {
display: -webkit-flex;
display: flex;
@@ -248,10 +353,8 @@
}
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
- .issues-details-filters {
- .dropdown-menu-toggle {
- width: 100px;
- }
+ .issue-bulk-update-dropdown-toggle {
+ width: 100px;
}
}
@@ -343,10 +446,8 @@
}
}
-.filter-dropdown-item.droplab-item-active {
- .btn {
- @extend %filter-dropdown-item-btn-hover;
- }
+.filter-dropdown-item.droplab-item-active .btn {
+ @extend %filter-dropdown-item-btn-hover;
}
.filter-dropdown-loading {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 8cd49280e1c..7098203321d 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -16,6 +16,8 @@ body.modal-open {
overflow: hidden;
}
-.modal .modal-dialog {
- width: 860px;
+@media (min-width: $screen-md-min) {
+ .modal-dialog {
+ width: 860px;
+ }
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index ff185cd8767..cd23deb6d75 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -1,15 +1,18 @@
.timeline {
@include basic-list;
-
margin: 0;
padding: 0;
.timeline-entry {
- padding: $gl-padding $gl-btn-padding 11px;
+ padding: $gl-padding $gl-btn-padding 14px;
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
+ .timeline-entry-inner {
+ position: relative;
+ }
+
&:target {
background: $line-target-blue;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 97794a47df8..712eb7caf33 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -293,6 +293,8 @@ $badge-color: $gl-text-color-secondary;
* Award emoji
*/
$award-emoji-menu-shadow: rgba(0,0,0,.175);
+$award-emoji-positive-add-bg: #fed159;
+$award-emoji-positive-add-lines: #bb9c13;
/*
* Search Box
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 7c0fc1008d0..0be1c215959 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -197,7 +197,7 @@
.card {
position: relative;
- padding: 10px $gl-padding;
+ padding: 11px 10px 11px $gl-padding;
background: $white-light;
border-radius: $border-radius-default;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
@@ -217,6 +217,8 @@
}
.confidential-icon {
+ position: relative;
+ top: 1px;
margin-right: 5px;
}
}
@@ -224,34 +226,43 @@
.card-title {
margin: 0;
font-size: 1em;
+ line-height: inherit;
a {
- color: inherit;
+ color: $gl-text-color;
word-wrap: break-word;
+ margin-right: 2px;
}
}
-.card-footer {
- margin-top: 5px;
- line-height: 25px;
-
- .label {
- margin-right: 5px;
- font-size: (14px / $issue-boards-font-size) * 1em;
- }
+.card-header {
+ display: flex;
+ min-height: 20px;
.card-assignee {
+ margin-left: auto;
margin-right: 5px;
+ padding-left: 10px;
+ height: 20px;
}
.avatar {
- margin-left: 0;
- margin-right: 0;
+ margin: 0;
+ }
+}
+
+.card-footer {
+ margin: 0 0 5px;
+
+ .label {
+ margin-top: 5px;
+ margin-right: 6px;
}
}
.card-number {
- margin-right: 5px;
+ font-size: 12px;
+ color: $gl-text-color-secondary;
}
.issue-boards-search {
diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss
new file mode 100644
index 00000000000..3266714396e
--- /dev/null
+++ b/app/assets/stylesheets/pages/container_registry.scss
@@ -0,0 +1,16 @@
+/**
+ * Container Registry
+ */
+
+.container-image {
+ border-bottom: 1px solid $white-normal;
+}
+
+.container-image-head {
+ padding: 0 16px;
+ line-height: 4em;
+}
+
+.table.tags {
+ margin-bottom: 0;
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 6faa3794c83..72e7d42858d 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -233,6 +233,15 @@
stroke-width: 1;
}
+.prometheus-state {
+ margin-top: 10px;
+ display: none;
+
+ .state-button-section {
+ margin-top: 10px;
+ }
+}
+
.environments-actions {
.external-url,
.monitoring-url,
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 08398bb43a2..e7f9bbbc62f 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -4,14 +4,14 @@
*/
.event-item {
font-size: $gl-font-size;
- padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
+ padding: $gl-padding-top 0 $gl-padding-top 40px;
border-bottom: 1px solid $white-normal;
color: $list-text-color;
+ position: relative;
&.event-inline {
- .avatar {
- position: relative;
- top: -2px;
+ .profile-icon {
+ top: 20px;
}
.event-title,
@@ -24,8 +24,28 @@
color: $gl-text-color;
}
- .avatar {
- margin-left: -($gl-avatar-size + $gl-padding-top);
+ .profile-icon {
+ position: absolute;
+ left: 0;
+ top: 14px;
+
+ svg {
+ width: 20px;
+ height: auto;
+ fill: $gl-text-color-secondary;
+ }
+
+ &.open-icon svg {
+ fill: $green-300;
+ }
+
+ &.closed-icon svg {
+ fill: $red-300;
+ }
+
+ &.fork-icon svg {
+ fill: $blue-300;
+ }
}
.event-title {
@@ -163,7 +183,7 @@
max-width: 100%;
}
- .avatar {
+ .profile-icon {
display: none;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 566dcc64802..2f946ab2f59 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -329,8 +329,6 @@
}
#modal_merge_info .modal-dialog {
- width: 600px;
-
.dark {
margin-right: 40px;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 57cf8e136e2..12ca20a1420 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -16,6 +16,15 @@ ul.notes {
.timeline-icon {
float: left;
+
+ svg {
+ width: 18px;
+ height: auto;
+ fill: $gray-darkest;
+ position: absolute;
+ left: 30px;
+ top: 15px;
+ }
}
.timeline-content {
@@ -33,6 +42,103 @@ ul.notes {
white-space: nowrap;
}
+ .discussion-body {
+ padding-top: 15px;
+ }
+
+ .discussion {
+ overflow: hidden;
+ display: block;
+ position: relative;
+ }
+
+ .note {
+ display: block;
+ position: relative;
+ border-bottom: 1px solid $white-normal;
+
+ &.note-discussion {
+ &.timeline-entry {
+ padding: 14px 10px;
+ }
+
+ .system-note {
+ padding: 0;
+ }
+ }
+
+ &.is-editting {
+ .note-header,
+ .note-text,
+ .edited-text {
+ display: none;
+ }
+
+ .note-edit-form {
+ display: block;
+
+ &.current-note-edit-form + .note-awards {
+ display: none;
+ }
+ }
+ }
+
+ .note-body {
+ overflow-x: auto;
+ overflow-y: hidden;
+
+ .note-text {
+ word-wrap: break-word;
+ @include md-typography;
+ // Reset ul style types since we're nested inside a ul already
+ @include bulleted-list;
+ ul.task-list {
+ ul:not(.task-list) {
+ padding-left: 1.3em;
+ }
+ }
+ }
+ }
+
+ .note-awards {
+ .js-awards-block {
+ padding: 2px;
+ margin-top: 10px;
+ }
+ }
+
+ .note-header {
+ padding-bottom: 3px;
+ padding-right: 20px;
+
+ @media (min-width: $screen-sm-min) {
+ padding-right: 0;
+ }
+
+ @media (max-width: $screen-xs-min) {
+ .inline {
+ display: block;
+ }
+ }
+ }
+
+ .note-emoji-button {
+ .fa-spinner {
+ display: none;
+ }
+
+ &.is-loading {
+ .fa-smile-o {
+ display: none;
+ }
+
+ .fa-spinner {
+ display: inline-block;
+ }
+ }
+ }
+ }
+
.system-note {
font-size: 14px;
padding: 0;
@@ -68,6 +174,10 @@ ul.notes {
padding: 14px 10px;
}
+ .note-header {
+ padding-bottom: 0;
+ }
+
.note-body {
overflow: hidden;
@@ -130,116 +240,6 @@ ul.notes {
}
}
}
-
- .timeline-icon {
- display: none;
-
- .avatar {
- visibility: hidden;
-
- .discussion-body & {
- visibility: visible;
- }
- }
- }
- }
-
- .discussion-body {
- padding-top: 15px;
- }
-
- .discussion {
- overflow: hidden;
- display: block;
- position: relative;
- }
-
- .note {
- display: block;
- position: relative;
- border-bottom: 1px solid $white-normal;
-
- &.note-discussion {
- &.timeline-entry {
- padding: 14px 10px;
- }
-
- .system-note {
- padding: 0;
- }
- }
-
- &.is-editting {
- .note-header,
- .note-text,
- .edited-text {
- display: none;
- }
-
- .note-edit-form {
- display: block;
-
- &.current-note-edit-form + .note-awards {
- display: none;
- }
- }
- }
-
- .note-body {
- overflow-x: auto;
- overflow-y: hidden;
-
- .note-text {
- word-wrap: break-word;
- @include md-typography;
- // Reset ul style types since we're nested inside a ul already
- @include bulleted-list;
- ul.task-list {
- ul:not(.task-list) {
- padding-left: 1.3em;
- }
- }
- }
- }
-
- .note-awards {
- .js-awards-block {
- padding: 2px;
- margin-top: 10px;
- }
- }
-
- .note-header {
- padding-bottom: 3px;
- padding-right: 20px;
-
- @media (min-width: $screen-sm-min) {
- padding-right: 0;
- }
-
- @media (max-width: $screen-xs-min) {
- .inline {
- display: block;
- }
- }
- }
-
- .note-emoji-button {
- .fa-spinner {
- display: none;
- }
-
- &.is-loading {
- .fa-smile-o {
- display: none;
- }
-
- .fa-spinner {
- display: inline-block;
- }
- }
- }
-
}
}
@@ -398,13 +398,50 @@ ul.notes {
font-size: 17px;
}
- &:hover {
+ svg {
+ height: 16px;
+ width: 16px;
+ fill: $gray-darkest;
+ vertical-align: text-top;
+ }
+
+ .award-control-icon-positive,
+ .award-control-icon-super-positive {
+ position: absolute;
+ margin-left: -20px;
+ opacity: 0;
+ }
+
+ &:hover,
+ &.is-active {
.danger-highlight {
color: $gl-text-red;
}
.link-highlight {
color: $gl-link-color;
+
+ svg {
+ fill: $gl-link-color;
+ }
+ }
+
+ .award-control-icon-neutral {
+ opacity: 0;
+ }
+
+ .award-control-icon-positive {
+ opacity: 1;
+ }
+ }
+
+ &.is-active {
+ .award-control-icon-positive {
+ opacity: 0;
+ }
+
+ .award-control-icon-super-positive {
+ opacity: 1;
}
}
}
@@ -508,7 +545,6 @@ ul.notes {
}
.line-resolve-all-container {
-
.btn-group {
margin-left: -4px;
}
@@ -537,7 +573,6 @@ ul.notes {
fill: $gray-darkest;
}
}
-
}
.line-resolve-all {
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 703c5fc8869..8c6dd392865 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -230,6 +230,14 @@
font-size: 0;
}
+ .fade-right {
+ right: 0;
+ }
+
+ .fade-left {
+ left: 0;
+ }
+
@media (max-width: $screen-xs-max) {
.cover-block {
padding-top: 20px;
diff --git a/app/assets/stylesheets/pages/settings_ci_cd.scss b/app/assets/stylesheets/pages/settings_ci_cd.scss
index b97a29cd1a0..fe22d186af1 100644
--- a/app/assets/stylesheets/pages/settings_ci_cd.scss
+++ b/app/assets/stylesheets/pages/settings_ci_cd.scss
@@ -6,6 +6,8 @@
}
.trigger-actions {
+ white-space: nowrap;
+
.btn {
margin-left: 10px;
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index fc4da4c495f..f3916622b6f 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -145,8 +145,6 @@
margin: 0;
}
-#modal-remove-blob > .modal-dialog { width: 850px; }
-
.blob-upload-dropzone-previews {
text-align: center;
border: 2px;