summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-05-02 10:40:10 +0200
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-05-02 10:40:10 +0200
commitd276ea93728e7972ed5b8275460929128d98fccd (patch)
treed6c8188cea2d25685f02c7043f7521f8b1f3509e /app/assets
parentc9def85844531ffdd2984707a1bc8cbca18f6742 (diff)
parent6068b863c66f785bea0a56881d60e8c23da08a0b (diff)
downloadgitlab-ce-d276ea93728e7972ed5b8275460929128d98fccd.tar.gz
Merge branch 'master' into feature/gb/manual-actions-protected-branches-permissions
* master: (314 commits) Better Explore Groups view Update Carrierwave and fog-core Add specs for Gitlab::RequestProfiler Add scripts/static-analysis to run all the static analysers in one go Shorten and improve some job names Group static-analysis jobs into a single job Don't blow up when email has no References header Update CHANGELOG.md for 9.1.2 Add changelog Add changelog Show Raw button as Download for binary files Use blob viewers for snippets Fix typo Fixed transient failure related to dropdown animations Revert "Merge branch 'tc-no-todo-service-select' into 'master'" fix link to MR 10416 Another change from .click -> .trigger('click') to make spec pass Change from .click -> .trigger('click') to make spec pass Disable AddColumnWithDefaultToLargeTable cop for pre-existing migrations Add AddColumnWithDefaultToLargeTable cop ... Conflicts: spec/requests/api/jobs_spec.rb
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/blob/blob_fork_suggestion.js53
-rw-r--r--app/assets/javascripts/blob/notebook/index.js6
-rw-r--r--app/assets/javascripts/blob/pdf/index.js12
-rw-r--r--app/assets/javascripts/blob/viewer/index.js120
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js9
-rw-r--r--app/assets/javascripts/dispatcher.js23
-rw-r--r--app/assets/javascripts/environments/components/environment.vue (renamed from app/assets/javascripts/environments/components/environment.js)153
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js21
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js21
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue (renamed from app/assets/javascripts/environments/folder/environments_folder_view.js)104
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js38
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js14
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js11
-rw-r--r--app/assets/javascripts/landing.js37
-rw-r--r--app/assets/javascripts/lib/utils/regexp.js10
-rw-r--r--app/assets/javascripts/line_highlighter.js21
-rw-r--r--app/assets/javascripts/merge_request_tabs.js12
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js7
-rw-r--r--app/assets/javascripts/monitoring/prometheus_graph.js63
-rw-r--r--app/assets/javascripts/notebook/cells/code.vue58
-rw-r--r--app/assets/javascripts/notebook/cells/code/index.vue57
-rw-r--r--app/assets/javascripts/notebook/cells/index.js2
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue98
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue22
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue27
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue83
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue30
-rw-r--r--app/assets/javascripts/notebook/index.vue75
-rw-r--r--app/assets/javascripts/notebook/lib/highlight.js22
-rw-r--r--app/assets/javascripts/pdf/assets/img/bg.gifbin0 -> 58 bytes
-rw-r--r--app/assets/javascripts/pdf/index.vue73
-rw-r--r--app/assets/javascripts/pdf/page/index.vue68
-rw-r--r--app/assets/javascripts/pipelines/components/stage.js49
-rw-r--r--app/assets/javascripts/pipelines/components/time_ago.js101
-rw-r--r--app/assets/javascripts/pipelines/pipelines.js10
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines_store.js31
-rw-r--r--app/assets/javascripts/users_select.js1
-rw-r--r--app/assets/javascripts/vue_realtime_listener/index.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js31
-rw-r--r--app/assets/stylesheets/framework/awards.scss7
-rw-r--r--app/assets/stylesheets/framework/blocks.scss58
-rw-r--r--app/assets/stylesheets/framework/common.scss7
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss1
-rw-r--r--app/assets/stylesheets/framework/files.scss14
-rw-r--r--app/assets/stylesheets/framework/filters.scss22
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss4
-rw-r--r--app/assets/stylesheets/framework/mixins.scss7
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/framework/timeline.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss40
-rw-r--r--app/assets/stylesheets/pages/builds.scss1
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss44
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss6
-rw-r--r--app/assets/stylesheets/pages/groups.scss23
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/issues.scss13
-rw-r--r--app/assets/stylesheets/pages/note_form.scss3
-rw-r--r--app/assets/stylesheets/pages/notes.scss17
-rw-r--r--app/assets/stylesheets/pages/projects.scss1
-rw-r--r--app/assets/stylesheets/pages/tree.scss1
-rw-r--r--app/assets/stylesheets/pages/wiki.scss6
61 files changed, 1435 insertions, 432 deletions
diff --git a/app/assets/javascripts/blob/blob_fork_suggestion.js b/app/assets/javascripts/blob/blob_fork_suggestion.js
index 3baf81905fe..47c431fb809 100644
--- a/app/assets/javascripts/blob/blob_fork_suggestion.js
+++ b/app/assets/javascripts/blob/blob_fork_suggestion.js
@@ -16,47 +16,44 @@ const defaults = {
class BlobForkSuggestion {
constructor(options) {
this.elementMap = Object.assign({}, defaults, options);
- this.onClickWrapper = this.onClick.bind(this);
-
- document.addEventListener('click', this.onClickWrapper);
+ this.onOpenButtonClick = this.onOpenButtonClick.bind(this);
+ this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
}
- showSuggestionSection(forkPath, action = 'edit') {
- [].forEach.call(this.elementMap.suggestionSections, (suggestionSection) => {
- suggestionSection.classList.remove('hidden');
- });
+ init() {
+ this.bindEvents();
- [].forEach.call(this.elementMap.forkButtons, (forkButton) => {
- forkButton.setAttribute('href', forkPath);
- });
+ return this;
+ }
- [].forEach.call(this.elementMap.actionTextPieces, (actionTextPiece) => {
- // eslint-disable-next-line no-param-reassign
- actionTextPiece.textContent = action;
- });
+ bindEvents() {
+ $(this.elementMap.openButtons).on('click', this.onOpenButtonClick);
+ $(this.elementMap.cancelButtons).on('click', this.onCancelButtonClick);
}
- hideSuggestionSection() {
- [].forEach.call(this.elementMap.suggestionSections, (suggestionSection) => {
- suggestionSection.classList.add('hidden');
- });
+ showSuggestionSection(forkPath, action = 'edit') {
+ $(this.elementMap.suggestionSections).removeClass('hidden');
+ $(this.elementMap.forkButtons).attr('href', forkPath);
+ $(this.elementMap.actionTextPieces).text(action);
}
- onClick(e) {
- const el = e.target;
+ hideSuggestionSection() {
+ $(this.elementMap.suggestionSections).addClass('hidden');
+ }
- if ([].includes.call(this.elementMap.openButtons, el)) {
- const { forkPath, action } = el.dataset;
- this.showSuggestionSection(forkPath, action);
- }
+ onOpenButtonClick(e) {
+ const forkPath = $(e.currentTarget).attr('data-fork-path');
+ const action = $(e.currentTarget).attr('data-action');
+ this.showSuggestionSection(forkPath, action);
+ }
- if ([].includes.call(this.elementMap.cancelButtons, el)) {
- this.hideSuggestionSection();
- }
+ onCancelButtonClick() {
+ this.hideSuggestionSection();
}
destroy() {
- document.removeEventListener('click', this.onClickWrapper);
+ $(this.elementMap.openButtons).off('click', this.onOpenButtonClick);
+ $(this.elementMap.cancelButtons).off('click', this.onCancelButtonClick);
}
}
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js
index 9b8bfbfc8c0..36fe8a7184f 100644
--- a/app/assets/javascripts/blob/notebook/index.js
+++ b/app/assets/javascripts/blob/notebook/index.js
@@ -1,10 +1,9 @@
/* eslint-disable no-new */
import Vue from 'vue';
import VueResource from 'vue-resource';
-import NotebookLab from 'vendor/notebooklab';
+import notebookLab from '../../notebook/index.vue';
Vue.use(VueResource);
-Vue.use(NotebookLab);
export default () => {
const el = document.getElementById('js-notebook-viewer');
@@ -19,6 +18,9 @@ export default () => {
json: {},
};
},
+ components: {
+ notebookLab,
+ },
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
<div
diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js
index a74c2db9a61..0ed915c1ac9 100644
--- a/app/assets/javascripts/blob/pdf/index.js
+++ b/app/assets/javascripts/blob/pdf/index.js
@@ -1,11 +1,6 @@
/* eslint-disable no-new */
import Vue from 'vue';
-import PDFLab from 'vendor/pdflab';
-import workerSrc from 'vendor/pdf.worker';
-
-Vue.use(PDFLab, {
- workerSrc,
-});
+import pdfLab from '../../pdf/index.vue';
export default () => {
const el = document.getElementById('js-pdf-viewer');
@@ -20,6 +15,9 @@ export default () => {
pdf: el.dataset.endpoint,
};
},
+ components: {
+ pdfLab,
+ },
methods: {
onLoad() {
this.loading = false;
@@ -31,7 +29,7 @@ export default () => {
},
},
template: `
- <div class="container-fluid md prepend-top-default append-bottom-default">
+ <div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default">
<div
class="text-center loading"
v-if="loading && !error">
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
new file mode 100644
index 00000000000..07d67d49aa5
--- /dev/null
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -0,0 +1,120 @@
+/* global Flash */
+export default class BlobViewer {
+ constructor() {
+ this.switcher = document.querySelector('.js-blob-viewer-switcher');
+ this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
+ this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
+ this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
+ this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
+ this.$fileHolder = $('.file-holder');
+
+ let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
+
+ this.initBindings();
+
+ if (this.switcher && location.hash.indexOf('#L') === 0) {
+ initialViewerName = 'simple';
+ }
+
+ this.switchToViewer(initialViewerName);
+ }
+
+ initBindings() {
+ if (this.switcherBtns.length) {
+ Array.from(this.switcherBtns)
+ .forEach((el) => {
+ el.addEventListener('click', this.switchViewHandler.bind(this));
+ });
+ }
+
+ if (this.copySourceBtn) {
+ this.copySourceBtn.addEventListener('click', () => {
+ if (this.copySourceBtn.classList.contains('disabled')) return;
+
+ this.switchToViewer('simple');
+ });
+ }
+ }
+
+ switchViewHandler(e) {
+ const target = e.currentTarget;
+
+ e.preventDefault();
+
+ this.switchToViewer(target.getAttribute('data-viewer'));
+ }
+
+ toggleCopyButtonState() {
+ if (!this.copySourceBtn) return;
+
+ if (this.simpleViewer.getAttribute('data-loaded')) {
+ this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
+ this.copySourceBtn.classList.remove('disabled');
+ } else if (this.activeViewer === this.simpleViewer) {
+ this.copySourceBtn.setAttribute('title', 'Wait for the source to load to copy it to the clipboard');
+ this.copySourceBtn.classList.add('disabled');
+ } else {
+ this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
+ this.copySourceBtn.classList.add('disabled');
+ }
+
+ $(this.copySourceBtn).tooltip('fixTitle');
+ }
+
+ loadViewer(viewerParam) {
+ const viewer = viewerParam;
+ const url = viewer.getAttribute('data-url');
+
+ if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
+ return;
+ }
+
+ viewer.setAttribute('data-loading', 'true');
+
+ $.ajax({
+ url,
+ dataType: 'JSON',
+ })
+ .fail(() => new Flash('Error loading source view'))
+ .done((data) => {
+ viewer.innerHTML = data.html;
+ $(viewer).syntaxHighlight();
+
+ viewer.setAttribute('data-loaded', 'true');
+
+ this.$fileHolder.trigger('highlight:line');
+
+ this.toggleCopyButtonState();
+ });
+ }
+
+ switchToViewer(name) {
+ const newViewer = document.querySelector(`.blob-viewer[data-type='${name}']`);
+ if (this.activeViewer === newViewer) return;
+
+ const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
+ const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`);
+ const oldViewer = document.querySelector(`.blob-viewer:not([data-type='${name}'])`);
+
+ if (oldButton) {
+ oldButton.classList.remove('active');
+ }
+
+ if (newButton) {
+ newButton.classList.add('active');
+ newButton.blur();
+ }
+
+ if (oldViewer) {
+ oldViewer.classList.add('hidden');
+ }
+
+ newViewer.classList.remove('hidden');
+
+ this.activeViewer = newViewer;
+
+ this.toggleCopyButtonState();
+
+ this.loadViewer(newViewer);
+ }
+}
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 68a1c1de1df..e704be8b53e 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -106,15 +106,6 @@ export default Vue.component('pipelines-table', {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
- beforeUpdate() {
- if (this.state.pipelines.length &&
- this.$children &&
- !this.isMakingRequest &&
- !this.isLoading) {
- this.store.startTimeAgoLoops.call(this, Vue);
- }
- },
-
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 20db2698ba8..0bdce52cc89 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -44,10 +44,12 @@ 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 Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
import ShortcutsWiki from './shortcuts_wiki';
+import BlobViewer from './blob/viewer/index';
const ShortcutsBlob = require('./shortcuts_blob');
@@ -97,7 +99,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
- });
+ })
+ .init();
}
switch (page) {
@@ -146,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob');
new ProjectsList();
break;
case 'dashboard:groups:index':
+ new GroupsList();
+ break;
case 'explore:groups:index':
new GroupsList();
+
+ const landingElement = document.querySelector('.js-explore-groups-landing');
+ if (!landingElement) break;
+ const exploreGroupsLanding = new Landing(
+ landingElement,
+ landingElement.querySelector('.dismiss-button'),
+ 'explore_groups_landing_dismissed',
+ );
+ exploreGroupsLanding.toggle();
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
@@ -298,6 +312,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:blob:show':
+ new BlobViewer();
gl.TargetBranchDropDown.bootstrap();
initBlob();
break;
@@ -353,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'users:show':
new UserCallout();
break;
+ case 'snippets:show':
+ new LineHighlighter();
+ new BlobViewer();
+ break;
}
switch (path.first()) {
case 'sessions':
@@ -431,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') {
new ZenMode();
+ new LineHighlighter();
+ new BlobViewer();
}
break;
case 'labels':
diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.vue
index f7175e412da..f319d6ca0c8 100644
--- a/app/assets/javascripts/environments/components/environment.js
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -1,6 +1,7 @@
+<script>
+
/* eslint-disable no-new */
/* global Flash */
-import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
@@ -8,7 +9,7 @@ import TablePaginationComponent from '../../vue_shared/components/table_paginati
import '../../lib/utils/common_utils';
import eventHub from '../event_hub';
-export default Vue.component('environment-component', {
+export default {
components: {
'environment-table': EnvironmentTable,
@@ -140,76 +141,90 @@ export default Vue.component('environment-component', {
});
},
},
-
- template: `
- <div :class="cssContainerClass">
- <div class="top-area">
- <ul v-if="!isLoading" class="nav-links">
- <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
- <a :href="projectEnvironmentsPath">
- Available
- <span class="badge js-available-environments-count">
- {{state.availableCounter}}
- </span>
- </a>
- </li>
- <li v-bind:class="{ 'active' : scope === 'stopped' }">
- <a :href="projectStoppedEnvironmentsPath">
- Stopped
- <span class="badge js-stopped-environments-count">
- {{state.stoppedCounter}}
- </span>
- </a>
- </li>
- </ul>
- <div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-create">
- New environment
+};
+</script>
+<template>
+ <div :class="cssContainerClass">
+ <div class="top-area">
+ <ul
+ v-if="!isLoading"
+ class="nav-links">
+ <li :class="{ active: scope === null || scope === 'available' }">
+ <a :href="projectEnvironmentsPath">
+ Available
+ <span class="badge js-available-environments-count">
+ {{state.availableCounter}}
+ </span>
+ </a>
+ </li>
+ <li :class="{ active : scope === 'stopped' }">
+ <a :href="projectStoppedEnvironmentsPath">
+ Stopped
+ <span class="badge js-stopped-environments-count">
+ {{state.stoppedCounter}}
+ </span>
</a>
- </div>
+ </li>
+ </ul>
+ <div
+ v-if="canCreateEnvironmentParsed && !isLoading"
+ class="nav-controls">
+ <a
+ :href="newEnvironmentPath"
+ class="btn btn-create">
+ New environment
+ </a>
</div>
+ </div>
+
+ <div class="content-list environments-container">
+ <div
+ class="environments-list-loading text-center"
+ v-if="isLoading">
- <div class="content-list environments-container">
- <div class="environments-list-loading text-center" v-if="isLoading">
- <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
- </div>
-
- <div class="blank-state blank-state-no-icon"
- v-if="!isLoading && state.environments.length === 0">
- <h2 class="blank-state-title js-blank-state-title">
- You don't have any environments right now.
- </h2>
- <p class="blank-state-text">
- Environments are places where code gets deployed, such as staging or production.
- <br />
- <a :href="helpPagePath">
- Read more about environments
- </a>
- </p>
-
- <a v-if="canCreateEnvironmentParsed"
- :href="newEnvironmentPath"
- class="btn btn-create js-new-environment-button">
- New Environment
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true" />
+ </div>
+
+ <div
+ class="blank-state blank-state-no-icon"
+ v-if="!isLoading && state.environments.length === 0">
+ <h2 class="blank-state-title js-blank-state-title">
+ You don't have any environments right now.
+ </h2>
+ <p class="blank-state-text">
+ Environments are places where code gets deployed, such as staging or production.
+ <br />
+ <a :href="helpPagePath">
+ Read more about environments
</a>
- </div>
-
- <div class="table-holder"
- v-if="!isLoading && state.environments.length > 0">
-
- <environment-table
- :environments="state.environments"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :service="service"
- :is-loading-folder-content="isLoadingFolderContent" />
- </div>
-
- <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
- :change="changePage"
- :pageInfo="state.paginationInformation">
- </table-pagination>
+ </p>
+
+ <a
+ v-if="canCreateEnvironmentParsed"
+ :href="newEnvironmentPath"
+ class="btn btn-create js-new-environment-button">
+ New Environment
+ </a>
</div>
+
+ <div
+ class="table-holder"
+ v-if="!isLoading && state.environments.length > 0">
+
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :service="service"
+ :is-loading-folder-content="isLoadingFolderContent" />
+ </div>
+
+ <table-pagination
+ v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation" />
</div>
- `,
-});
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/environments_bundle.js b/app/assets/javascripts/environments/environments_bundle.js
index 8d963b335cf..c0662125f28 100644
--- a/app/assets/javascripts/environments/environments_bundle.js
+++ b/app/assets/javascripts/environments/environments_bundle.js
@@ -1,13 +1,10 @@
-import EnvironmentsComponent from './components/environment';
+import Vue from 'vue';
+import EnvironmentsComponent from './components/environment.vue';
-$(() => {
- window.gl = window.gl || {};
-
- if (gl.EnvironmentsListApp) {
- gl.EnvironmentsListApp.$destroy(true);
- }
-
- gl.EnvironmentsListApp = new EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- });
-});
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#environments-list-view',
+ components: {
+ 'environments-table-app': EnvironmentsComponent,
+ },
+ render: createElement => createElement('environments-table-app'),
+}));
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index f939eccf246..9add8c3d721 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,13 +1,10 @@
-import EnvironmentsFolderComponent from './environments_folder_view';
+import Vue from 'vue';
+import EnvironmentsFolderComponent from './environments_folder_view.vue';
-$(() => {
- window.gl = window.gl || {};
-
- if (gl.EnvironmentsListFolderApp) {
- gl.EnvironmentsListFolderApp.$destroy(true);
- }
-
- gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({
- el: document.querySelector('#environments-folder-list-view'),
- });
-});
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#environments-folder-list-view',
+ components: {
+ 'environments-folder-app': EnvironmentsFolderComponent,
+ },
+ render: createElement => createElement('environments-folder-app'),
+}));
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 05d44f77d1d..d27b2acfcdf 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -1,6 +1,6 @@
+<script>
/* eslint-disable no-new */
/* global Flash */
-import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
@@ -8,7 +8,7 @@ import TablePaginationComponent from '../../vue_shared/components/table_paginati
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
-export default Vue.component('environment-folder-view', {
+export default {
components: {
'environment-table': EnvironmentTable,
'table-pagination': TablePaginationComponent,
@@ -116,54 +116,66 @@ export default Vue.component('environment-folder-view', {
return param;
},
},
+};
+</script>
+<template>
+ <div :class="cssContainerClass">
+ <div
+ class="top-area"
+ v-if="!isLoading">
+
+ <h4 class="js-folder-name environments-folder-name">
+ Environments / <b>{{folderName}}</b>
+ </h4>
+
+ <ul class="nav-links">
+ <li :class="{ active: scope === null || scope === 'available' }">
+ <a
+ :href="availablePath"
+ class="js-available-environments-folder-tab">
+ Available
+ <span class="badge js-available-environments-count">
+ {{state.availableCounter}}
+ </span>
+ </a>
+ </li>
+ <li :class="{ active : scope === 'stopped' }">
+ <a
+ :href="stoppedPath"
+ class="js-stopped-environments-folder-tab">
+ Stopped
+ <span class="badge js-stopped-environments-count">
+ {{state.stoppedCounter}}
+ </span>
+ </a>
+ </li>
+ </ul>
+ </div>
- template: `
- <div :class="cssContainerClass">
- <div class="top-area" v-if="!isLoading">
-
- <h4 class="js-folder-name environments-folder-name">
- Environments / <b>{{folderName}}</b>
- </h4>
-
- <ul class="nav-links">
- <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
- <a :href="availablePath" class="js-available-environments-folder-tab">
- Available
- <span class="badge js-available-environments-count">
- {{state.availableCounter}}
- </span>
- </a>
- </li>
- <li v-bind:class="{ 'active' : scope === 'stopped' }">
- <a :href="stoppedPath" class="js-stopped-environments-folder-tab">
- Stopped
- <span class="badge js-stopped-environments-count">
- {{state.stoppedCounter}}
- </span>
- </a>
- </li>
- </ul>
+ <div class="environments-container">
+ <div
+ class="environments-list-loading text-center"
+ v-if="isLoading">
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"/>
</div>
- <div class="environments-container">
- <div class="environments-list-loading text-center" v-if="isLoading">
- <i class="fa fa-spinner fa-spin"></i>
- </div>
-
- <div class="table-holder"
- v-if="!isLoading && state.environments.length > 0">
+ <div
+ class="table-holder"
+ v-if="!isLoading && state.environments.length > 0">
- <environment-table
- :environments="state.environments"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :service="service"/>
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :service="service"/>
- <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
- :change="changePage"
- :pageInfo="state.paginationInformation"/>
- </div>
+ <table-pagination
+ v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation"/>
</div>
</div>
- `,
-});
+ </div>
+</template>
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 68a832102a0..36af0674ac6 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -77,13 +77,14 @@ class FilteredSearchManager {
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
- this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
+ this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
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.removeTokenWrapper = this.removeToken.bind(this);
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
@@ -96,12 +97,13 @@ class FilteredSearchManager {
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.addEventListener('click', this.removeTokenWrapper);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
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);
+ document.addEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
@@ -117,12 +119,13 @@ class FilteredSearchManager {
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.removeEventListener('click', this.removeTokenWrapper);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
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);
+ document.removeEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
@@ -195,14 +198,28 @@ class FilteredSearchManager {
static selectToken(e) {
const button = e.target.closest('.selectable');
+ const removeButtonSelected = e.target.closest('.remove-token');
- if (button) {
+ if (!removeButtonSelected && button) {
e.preventDefault();
e.stopPropagation();
gl.FilteredSearchVisualTokens.selectToken(button);
}
}
+ removeToken(e) {
+ const removeButtonSelected = e.target.closest('.remove-token');
+
+ if (removeButtonSelected) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const button = e.target.closest('.selectable');
+ gl.FilteredSearchVisualTokens.selectToken(button, true);
+ this.removeSelectedToken();
+ }
+ }
+
unselectEditTokens(e) {
const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
@@ -248,16 +265,21 @@ class FilteredSearchManager {
}
}
- removeSelectedToken(e) {
+ removeSelectedTokenKeydown(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
- gl.FilteredSearchVisualTokens.removeSelectedToken();
- this.handleInputPlaceholder();
- this.toggleClearSearchButton();
+ this.removeSelectedToken();
}
}
+ removeSelectedToken() {
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+ this.handleInputPlaceholder();
+ this.toggleClearSearchButton();
+ this.dropdownManager.updateCurrentDropdownOffset();
+ }
+
onClearSearch(e) {
e.preventDefault();
this.clearSearch();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index a5657fc8720..453ecccc6fc 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -16,11 +16,11 @@ class FilteredSearchVisualTokens {
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
- static selectToken(tokenButton) {
+ static selectToken(tokenButton, forceSelection = false) {
const selected = tokenButton.classList.contains('selected');
FilteredSearchVisualTokens.unselectTokens();
- if (!selected) {
+ if (!selected || forceSelection) {
tokenButton.classList.add('selected');
}
}
@@ -38,7 +38,12 @@ class FilteredSearchVisualTokens {
return `
<div class="selectable" role="button">
<div class="name"></div>
- <div class="value"></div>
+ <div class="value-container">
+ <div class="value"></div>
+ <div class="remove-token" role="button">
+ <i class="fa fa-close"></i>
+ </div>
+ </div>
</div>
`;
}
@@ -122,7 +127,8 @@ class FilteredSearchVisualTokens {
if (value) {
const button = lastVisualToken.querySelector('.selectable');
- button.removeChild(value);
+ const valueContainer = lastVisualToken.querySelector('.value-container');
+ button.removeChild(valueContainer);
lastVisualToken.innerHTML = button.innerHTML;
} else {
lastVisualToken.closest('.tokens-container').removeChild(lastVisualToken);
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index b62b2cec4d8..687a462a0d4 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -3,6 +3,7 @@
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from '~/behaviors/gl_emoji';
+import glRegexp from '~/lib/utils/regexp';
// Creates the variables for setting up GFM auto-completion
window.gl = window.gl || {};
@@ -127,7 +128,15 @@ window.gl.GfmAutoComplete = {
callbacks: {
sorter: this.DefaultOptions.sorter,
beforeInsert: this.DefaultOptions.beforeInsert,
- filter: this.DefaultOptions.filter
+ filter: this.DefaultOptions.filter,
+
+ matcher: (flag, subtext) => {
+ const relevantText = subtext.trim().split(/\s/).pop();
+ const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
+ const match = regexp.exec(relevantText);
+
+ return match && match.length ? match[1] : null;
+ }
}
});
// Team Members
diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js
new file mode 100644
index 00000000000..8c0950ad5d5
--- /dev/null
+++ b/app/assets/javascripts/landing.js
@@ -0,0 +1,37 @@
+import Cookies from 'js-cookie';
+
+class Landing {
+ constructor(landingElement, dismissButton, cookieName) {
+ this.landingElement = landingElement;
+ this.cookieName = cookieName;
+ this.dismissButton = dismissButton;
+ this.eventWrapper = {};
+ }
+
+ toggle() {
+ const isDismissed = this.isDismissed();
+
+ this.landingElement.classList.toggle('hidden', isDismissed);
+ if (!isDismissed) this.addEvents();
+ }
+
+ addEvents() {
+ this.eventWrapper.dismissLanding = this.dismissLanding.bind(this);
+ this.dismissButton.addEventListener('click', this.eventWrapper.dismissLanding);
+ }
+
+ removeEvents() {
+ this.dismissButton.removeEventListener('click', this.eventWrapper.dismissLanding);
+ }
+
+ dismissLanding() {
+ this.landingElement.classList.add('hidden');
+ Cookies.set(this.cookieName, 'true', { expires: 365 });
+ }
+
+ isDismissed() {
+ return Cookies.get(this.cookieName) === 'true';
+ }
+}
+
+export default Landing;
diff --git a/app/assets/javascripts/lib/utils/regexp.js b/app/assets/javascripts/lib/utils/regexp.js
new file mode 100644
index 00000000000..baa0b51d59b
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/regexp.js
@@ -0,0 +1,10 @@
+/**
+ * Regexp utility for the convenience of working with regular expressions.
+ *
+ */
+
+// Inspired by https://github.com/mishoo/UglifyJS/blob/2bc1d02363db3798d5df41fb5059a19edca9b7eb/lib/parse-js.js#L203
+// Unicode 6.1
+const unicodeLetters = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
+
+export default { unicodeLetters };
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 1821ca18053..3ac6dedf131 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -41,7 +41,6 @@ require('vendor/jquery.scrollTo');
LineHighlighter.prototype._hash = '';
function LineHighlighter(hash) {
- var range;
if (hash == null) {
// Initialize a LineHighlighter object
//
@@ -51,10 +50,22 @@ require('vendor/jquery.scrollTo');
this.setHash = bind(this.setHash, this);
this.highlightLine = bind(this.highlightLine, this);
this.clickHandler = bind(this.clickHandler, this);
+ this.highlightHash = this.highlightHash.bind(this);
this._hash = hash;
this.bindEvents();
- if (hash !== '') {
- range = this.hashToRange(hash);
+ this.highlightHash();
+ }
+
+ LineHighlighter.prototype.bindEvents = function() {
+ const $fileHolder = $('.file-holder');
+ $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
+ $fileHolder.on('highlight:line', this.highlightHash);
+ };
+
+ LineHighlighter.prototype.highlightHash = function() {
+ var range;
+ if (this._hash !== '') {
+ range = this.hashToRange(this._hash);
if (range[0]) {
this.highlightRange(range);
$.scrollTo("#L" + range[0], {
@@ -64,10 +75,6 @@ require('vendor/jquery.scrollTo');
});
}
}
- }
-
- LineHighlighter.prototype.bindEvents = function() {
- $('#blob-content-holder').on('click', 'a[data-line-number]', this.clickHandler);
};
LineHighlighter.prototype.clickHandler = function(event) {
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index f7f6a773036..93c30c54a8e 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -5,6 +5,7 @@
import Cookies from 'js-cookie';
import './breakpoints';
import './flash';
+import BlobForkSuggestion from './blob/blob_fork_suggestion';
/* eslint-disable max-len */
// MergeRequestTabs
@@ -266,6 +267,17 @@ import './flash';
new gl.Diff();
this.scrollToElement('#diffs');
+
+ $('.diff-file').each((i, el) => {
+ new BlobForkSuggestion({
+ openButtons: $(el).find('.js-edit-blob-link-fork-toggler'),
+ forkButtons: $(el).find('.js-fork-suggestion-button'),
+ cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
+ suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
+ actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
+ })
+ .init();
+ });
},
});
}
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index 9c58c465001..64c1447f427 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -28,7 +28,9 @@ export default class MiniPipelineGraph {
* All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
- $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
+ $(document)
+ .off('shown.bs.dropdown', this.container)
+ .on('shown.bs.dropdown', this.container, this.getBuildsList);
}
/**
@@ -91,6 +93,9 @@ export default class MiniPipelineGraph {
},
error: () => {
this.toggleLoading(button);
+ if ($(button).parent().hasClass('open')) {
+ $(button).dropdown('toggle');
+ }
new Flash('An error occurred while fetching the builds.', 'alert');
},
});
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
index aff507abb91..78bb0e6fb47 100644
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -22,6 +22,7 @@ class PrometheusGraph {
const hasMetrics = $prometheusContainer.data('has-metrics');
this.docLink = $prometheusContainer.data('doc-link');
this.integrationLink = $prometheusContainer.data('prometheus-integration');
+ this.state = '';
$(document).ajaxError(() => {});
@@ -38,8 +39,9 @@ class PrometheusGraph {
this.configureGraph();
this.init();
} else {
+ const prevState = this.state;
this.state = '.js-getting-started';
- this.updateState();
+ this.updateState(prevState);
}
}
@@ -53,26 +55,26 @@ class PrometheusGraph {
}
init() {
- this.getData().then((metricsResponse) => {
+ return this.getData().then((metricsResponse) => {
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();
+ if (typeof metricsResponse === 'undefined') {
+ enoughData = false;
} else {
+ Object.keys(metricsResponse.metrics).forEach((key) => {
+ if (key === 'cpu_values' || key === 'memory_values') {
+ const currentData = (metricsResponse.metrics[key])[0];
+ if (currentData.values.length <= 2) {
+ enoughData = false;
+ }
+ }
+ });
+ }
+ if (enoughData) {
+ $(prometheusStatesContainer).hide();
+ $(prometheusParentGraphContainer).show();
this.transformData(metricsResponse);
this.createGraph();
}
- }).catch(() => {
- new Flash('An error occurred when trying to load metrics. Please try again.');
});
}
@@ -342,6 +344,8 @@ class PrometheusGraph {
getData() {
const maxNumberOfRequests = 3;
+ this.state = '.js-loading';
+ this.updateState();
return gl.utils.backOff((next, stop) => {
$.ajax({
url: metricsEndpoint,
@@ -352,12 +356,11 @@ class PrometheusGraph {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < maxNumberOfRequests) {
next();
- } else {
- stop({
- status: resp.status,
- metrics: data,
- });
+ } else if (this.backOffRequestCounter >= maxNumberOfRequests) {
+ stop(new Error('loading'));
}
+ } else if (!data.success) {
+ stop(new Error('loading'));
} else {
stop({
status: resp.status,
@@ -373,8 +376,9 @@ class PrometheusGraph {
return resp.metrics;
})
.catch(() => {
+ const prevState = this.state;
this.state = '.js-unable-to-connect';
- this.updateState();
+ this.updateState(prevState);
});
}
@@ -382,19 +386,20 @@ class PrometheusGraph {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
- if (metricValues !== undefined) {
- this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
- time: new Date(metric[0] * 1000),
- value: metric[1],
- }));
- }
+ this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
+ time: new Date(metric[0] * 1000),
+ value: metric[1],
+ }));
}
});
}
- updateState() {
+ updateState(prevState) {
const $statesContainer = $(prometheusStatesContainer);
$(prometheusParentGraphContainer).hide();
+ if (prevState) {
+ $(`${prevState}`, $statesContainer).addClass('hidden');
+ }
$(`${this.state}`, $statesContainer).removeClass('hidden');
$(prometheusStatesContainer).show();
}
diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue
new file mode 100644
index 00000000000..b8a16356576
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/code.vue
@@ -0,0 +1,58 @@
+<template>
+ <div class="cell">
+ <code-cell
+ type="input"
+ :raw-code="rawInputCode"
+ :count="cell.execution_count"
+ :code-css-class="codeCssClass" />
+ <output-cell
+ v-if="hasOutput"
+ :count="cell.execution_count"
+ :output="output"
+ :code-css-class="codeCssClass" />
+ </div>
+</template>
+
+<script>
+import CodeCell from './code/index.vue';
+import OutputCell from './output/index.vue';
+
+export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'output-cell': OutputCell,
+ },
+ props: {
+ cell: {
+ type: Object,
+ required: true,
+ },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ rawInputCode() {
+ if (this.cell.source) {
+ return this.cell.source.join('');
+ }
+
+ return '';
+ },
+ hasOutput() {
+ return this.cell.outputs.length;
+ },
+ output() {
+ return this.cell.outputs[0];
+ },
+ },
+};
+</script>
+
+<style scoped>
+.cell {
+ flex-direction: column;
+}
+</style>
diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue
new file mode 100644
index 00000000000..31b30f601e2
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/code/index.vue
@@ -0,0 +1,57 @@
+<template>
+ <div :class="type">
+ <prompt
+ :type="promptType"
+ :count="count" />
+ <pre
+ class="language-python"
+ :class="codeCssClass"
+ ref="code"
+ v-text="code">
+ </pre>
+ </div>
+</template>
+
+<script>
+ import Prism from '../../lib/highlight';
+ import Prompt from '../prompt.vue';
+
+ export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ type: {
+ type: String,
+ required: true,
+ },
+ rawCode: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ code() {
+ return this.rawCode;
+ },
+ promptType() {
+ const type = this.type.split('put')[0];
+
+ return type.charAt(0).toUpperCase() + type.slice(1);
+ },
+ },
+ mounted() {
+ Prism.highlightElement(this.$refs.code);
+ },
+ };
+</script>
diff --git a/app/assets/javascripts/notebook/cells/index.js b/app/assets/javascripts/notebook/cells/index.js
new file mode 100644
index 00000000000..e4c255609fe
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/index.js
@@ -0,0 +1,2 @@
+export { default as MarkdownCell } from './markdown.vue';
+export { default as CodeCell } from './code.vue';
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
new file mode 100644
index 00000000000..3e8240d10ec
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -0,0 +1,98 @@
+<template>
+ <div class="cell text-cell">
+ <prompt />
+ <div class="markdown" v-html="markdown"></div>
+ </div>
+</template>
+
+<script>
+ /* global katex */
+ import marked from 'marked';
+ import Prompt from './prompt.vue';
+
+ const renderer = new marked.Renderer();
+
+ /*
+ Regex to match KaTex blocks.
+
+ Supports the following:
+
+ \begin{equation}<math>\end{equation}
+ $$<math>$$
+ inline $<math>$
+
+ The matched text then goes through the KaTex renderer & then outputs the HTML
+ */
+ const katexRegexString = `(
+ ^\\\\begin{[a-zA-Z]+}\\s
+ |
+ ^\\$\\$
+ |
+ \\s\\$(?!\\$)
+ )
+ (.+?)
+ (
+ \\s\\\\end{[a-zA-Z]+}$
+ |
+ \\$\\$$
+ |
+ \\$
+ )
+ `.replace(/\s/g, '').trim();
+
+ renderer.paragraph = (t) => {
+ let text = t;
+ let inline = false;
+
+ if (typeof katex !== 'undefined') {
+ const katexString = text.replace(/\\/g, '\\');
+ const matches = new RegExp(katexRegexString, 'gi').exec(katexString);
+
+ if (matches && matches.length > 0) {
+ if (matches[1].trim() === '$' && matches[3].trim() === '$') {
+ inline = true;
+
+ text = `${katexString.replace(matches[0], '')} ${katex.renderToString(matches[2])}`;
+ } else {
+ text = katex.renderToString(matches[2]);
+ }
+ }
+ }
+
+ return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
+ };
+
+ marked.setOptions({
+ sanitize: true,
+ renderer,
+ });
+
+ export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ cell: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ markdown() {
+ return marked(this.cell.source.join(''));
+ },
+ },
+ };
+</script>
+
+<style>
+.markdown .katex {
+ display: block;
+ text-align: center;
+}
+
+.markdown .inline-katex .katex {
+ display: inline;
+ text-align: initial;
+}
+</style>
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
new file mode 100644
index 00000000000..0f39cd138df
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -0,0 +1,22 @@
+<template>
+ <div class="output">
+ <prompt />
+ <div v-html="rawCode"></div>
+ </div>
+</template>
+
+<script>
+import Prompt from '../prompt.vue';
+
+export default {
+ props: {
+ rawCode: {
+ type: String,
+ required: true,
+ },
+ },
+ components: {
+ prompt: Prompt,
+ },
+};
+</script>
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
new file mode 100644
index 00000000000..f3b873bbc0f
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -0,0 +1,27 @@
+<template>
+ <div class="output">
+ <prompt />
+ <img
+ :src="'data:' + outputType + ';base64,' + rawCode" />
+ </div>
+</template>
+
+<script>
+import Prompt from '../prompt.vue';
+
+export default {
+ props: {
+ outputType: {
+ type: String,
+ required: true,
+ },
+ rawCode: {
+ type: String,
+ required: true,
+ },
+ },
+ components: {
+ prompt: Prompt,
+ },
+};
+</script>
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
new file mode 100644
index 00000000000..23c9ea78939
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -0,0 +1,83 @@
+<template>
+ <component :is="componentName"
+ type="output"
+ :outputType="outputType"
+ :count="count"
+ :raw-code="rawCode"
+ :code-css-class="codeCssClass" />
+</template>
+
+<script>
+import CodeCell from '../code/index.vue';
+import Html from './html.vue';
+import Image from './image.vue';
+
+export default {
+ props: {
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ output: {
+ type: Object,
+ requred: true,
+ },
+ },
+ components: {
+ 'code-cell': CodeCell,
+ 'html-output': Html,
+ 'image-output': Image,
+ },
+ data() {
+ return {
+ outputType: '',
+ };
+ },
+ computed: {
+ componentName() {
+ if (this.output.text) {
+ return 'code-cell';
+ } else if (this.output.data['image/png']) {
+ this.outputType = 'image/png';
+
+ return 'image-output';
+ } else if (this.output.data['text/html']) {
+ this.outputType = 'text/html';
+
+ return 'html-output';
+ } else if (this.output.data['image/svg+xml']) {
+ this.outputType = 'image/svg+xml';
+
+ return 'html-output';
+ }
+
+ this.outputType = 'text/plain';
+ return 'code-cell';
+ },
+ rawCode() {
+ if (this.output.text) {
+ return this.output.text.join('');
+ }
+
+ return this.dataForType(this.outputType);
+ },
+ },
+ methods: {
+ dataForType(type) {
+ let data = this.output.data[type];
+
+ if (typeof data === 'object') {
+ data = data.join('');
+ }
+
+ return data;
+ },
+ },
+};
+</script>
diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue
new file mode 100644
index 00000000000..4540e4248d8
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/prompt.vue
@@ -0,0 +1,30 @@
+<template>
+ <div class="prompt">
+ <span v-if="type && count">
+ {{ type }} [{{ count }}]:
+ </span>
+ </div>
+</template>
+
+<script>
+ export default {
+ props: {
+ type: {
+ type: String,
+ required: false,
+ },
+ count: {
+ type: Number,
+ required: false,
+ },
+ },
+ };
+</script>
+
+<style scoped>
+.prompt {
+ padding: 0 10px;
+ min-width: 7em;
+ font-family: monospace;
+}
+</style>
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
new file mode 100644
index 00000000000..fd62c1231ef
--- /dev/null
+++ b/app/assets/javascripts/notebook/index.vue
@@ -0,0 +1,75 @@
+<template>
+ <div v-if="hasNotebook">
+ <component
+ v-for="(cell, index) in cells"
+ :is="cellType(cell.cell_type)"
+ :cell="cell"
+ :key="index"
+ :code-css-class="codeCssClass" />
+ </div>
+</template>
+
+<script>
+ import {
+ MarkdownCell,
+ CodeCell,
+ } from './cells';
+
+ export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'markdown-cell': MarkdownCell,
+ },
+ props: {
+ notebook: {
+ type: Object,
+ required: true,
+ },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ cellType(type) {
+ return `${type}-cell`;
+ },
+ },
+ computed: {
+ cells() {
+ if (this.notebook.worksheets) {
+ const data = {
+ cells: [],
+ };
+
+ return this.notebook.worksheets.reduce((cellData, sheet) => {
+ const cellDataCopy = cellData;
+ cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
+ return cellDataCopy;
+ }, data).cells;
+ }
+
+ return this.notebook.cells;
+ },
+ hasNotebook() {
+ return Object.keys(this.notebook).length;
+ },
+ },
+ };
+</script>
+
+<style>
+.cell,
+.input,
+.output {
+ display: flex;
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+.cell pre {
+ margin: 0;
+ width: 100%;
+}
+</style>
diff --git a/app/assets/javascripts/notebook/lib/highlight.js b/app/assets/javascripts/notebook/lib/highlight.js
new file mode 100644
index 00000000000..74ade6d2edf
--- /dev/null
+++ b/app/assets/javascripts/notebook/lib/highlight.js
@@ -0,0 +1,22 @@
+import Prism from 'prismjs';
+import 'prismjs/components/prism-python';
+import 'prismjs/plugins/custom-class/prism-custom-class';
+
+Prism.plugins.customClass.map({
+ comment: 'c',
+ error: 'err',
+ operator: 'o',
+ constant: 'kc',
+ namespace: 'kn',
+ keyword: 'k',
+ string: 's',
+ number: 'm',
+ 'attr-name': 'na',
+ builtin: 'nb',
+ entity: 'ni',
+ function: 'nf',
+ tag: 'nt',
+ variable: 'nv',
+});
+
+export default Prism;
diff --git a/app/assets/javascripts/pdf/assets/img/bg.gif b/app/assets/javascripts/pdf/assets/img/bg.gif
new file mode 100644
index 00000000000..c7e98e044f5
--- /dev/null
+++ b/app/assets/javascripts/pdf/assets/img/bg.gif
Binary files differ
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
new file mode 100644
index 00000000000..4603859d7b0
--- /dev/null
+++ b/app/assets/javascripts/pdf/index.vue
@@ -0,0 +1,73 @@
+<template>
+ <div class="pdf-viewer" v-if="hasPDF">
+ <page v-for="(page, index) in pages"
+ :key="index"
+ :v-if="!loading"
+ :page="page"
+ :number="index + 1" />
+ </div>
+</template>
+
+<script>
+ import pdfjsLib from 'pdfjs-dist';
+ import workerSrc from 'vendor/pdf.worker';
+
+ import page from './page/index.vue';
+
+ export default {
+ props: {
+ pdf: {
+ type: [String, Uint8Array],
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ pages: [],
+ };
+ },
+ components: { page },
+ watch: { pdf: 'load' },
+ computed: {
+ document() {
+ return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
+ },
+ hasPDF() {
+ return this.pdf && this.pdf.length > 0;
+ },
+ },
+ methods: {
+ load() {
+ this.pages = [];
+ return pdfjsLib.getDocument(this.document)
+ .then(this.renderPages)
+ .then(() => this.$emit('pdflabload'))
+ .catch(error => this.$emit('pdflaberror', error))
+ .then(() => { this.loading = false; });
+ },
+ renderPages(pdf) {
+ const pagePromises = [];
+ this.loading = true;
+ for (let num = 1; num <= pdf.numPages; num += 1) {
+ pagePromises.push(
+ pdf.getPage(num).then(p => this.pages.push(p)),
+ );
+ }
+ return Promise.all(pagePromises);
+ },
+ },
+ mounted() {
+ pdfjsLib.PDFJS.workerSrc = workerSrc;
+ if (this.hasPDF) this.load();
+ },
+ };
+</script>
+
+<style>
+ .pdf-viewer {
+ background: url('./assets/img/bg.gif');
+ display: flex;
+ flex-flow: column nowrap;
+ }
+</style>
diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue
new file mode 100644
index 00000000000..7b74ee4eb2e
--- /dev/null
+++ b/app/assets/javascripts/pdf/page/index.vue
@@ -0,0 +1,68 @@
+<template>
+ <canvas
+ class="pdf-page"
+ ref="canvas"
+ :data-page="number" />
+</template>
+
+<script>
+ export default {
+ props: {
+ page: {
+ type: Object,
+ required: true,
+ },
+ number: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ scale: 4,
+ rendering: false,
+ };
+ },
+ computed: {
+ viewport() {
+ return this.page.getViewport(this.scale);
+ },
+ context() {
+ return this.$refs.canvas.getContext('2d');
+ },
+ renderContext() {
+ return {
+ canvasContext: this.context,
+ viewport: this.viewport,
+ };
+ },
+ },
+ mounted() {
+ this.$refs.canvas.height = this.viewport.height;
+ this.$refs.canvas.width = this.viewport.width;
+ this.rendering = true;
+ this.page.render(this.renderContext)
+ .then(() => { this.rendering = false; })
+ .catch(error => this.$emit('pdflaberror', error));
+ },
+ };
+</script>
+
+<style>
+.pdf-page {
+ margin: 8px auto 0 auto;
+ border-top: 1px #ddd solid;
+ border-bottom: 1px #ddd solid;
+ width: 100%;
+}
+
+.pdf-page:first-child {
+ margin-top: 0px;
+ border-top: 0px;
+}
+
+.pdf-page:last-child {
+ margin-bottom: 0px;
+ border-bottom: 0px;
+}
+</style>
diff --git a/app/assets/javascripts/pipelines/components/stage.js b/app/assets/javascripts/pipelines/components/stage.js
index b8cc3630611..203485f2990 100644
--- a/app/assets/javascripts/pipelines/components/stage.js
+++ b/app/assets/javascripts/pipelines/components/stage.js
@@ -2,13 +2,6 @@
import StatusIconEntityMap from '../../ci_status_icons';
export default {
- data() {
- return {
- builds: '',
- spinner: '<span class="fa fa-spinner fa-spin"></span>',
- };
- },
-
props: {
stage: {
type: Object,
@@ -16,6 +9,13 @@ export default {
},
},
+ data() {
+ return {
+ builds: '',
+ spinner: '<span class="fa fa-spinner fa-spin"></span>',
+ };
+ },
+
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
@@ -31,7 +31,13 @@ export default {
return this.$http.get(this.stage.dropdown_path)
.then((response) => {
this.builds = JSON.parse(response.body).html;
- }, () => {
+ })
+ .catch(() => {
+ // If dropdown is opened we'll close it.
+ if (this.$el.classList.contains('open')) {
+ $(this.$refs.dropdown).dropdown('toggle');
+ }
+
const flash = new Flash('Something went wrong on our end.');
return flash;
});
@@ -46,9 +52,10 @@ export default {
* target the click event of this component.
*/
stopDropdownClickPropagation() {
- $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
- e.stopPropagation();
- });
+ $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
+ .on('click', (e) => {
+ e.stopPropagation();
+ });
},
},
computed: {
@@ -81,12 +88,22 @@ export default {
data-placement="top"
data-toggle="dropdown"
type="button"
- :aria-label="stage.title">
- <span v-html="svgHTML" aria-hidden="true"></span>
- <i class="fa fa-caret-down" aria-hidden="true"></i>
+ :aria-label="stage.title"
+ ref="dropdown">
+ <span
+ v-html="svgHTML"
+ aria-hidden="true">
+ </span>
+ <i
+ class="fa fa-caret-down"
+ aria-hidden="true" />
</button>
- <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
- <div class="arrow-up" aria-hidden="true"></div>
+ <ul
+ ref="dropdown-content"
+ class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+ <div
+ class="arrow-up"
+ aria-hidden="true"></div>
<div
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
diff --git a/app/assets/javascripts/pipelines/components/time_ago.js b/app/assets/javascripts/pipelines/components/time_ago.js
index 498d0715f54..188f74cc705 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.js
+++ b/app/assets/javascripts/pipelines/components/time_ago.js
@@ -2,68 +2,95 @@ import iconTimerSvg from 'icons/_icon_timer.svg';
import '../../lib/utils/datetime_utility';
export default {
+ props: {
+ finishedTime: {
+ type: String,
+ required: true,
+ },
+
+ duration: {
+ type: Number,
+ required: true,
+ },
+ },
+
data() {
return {
- currentTime: new Date(),
iconTimerSvg,
};
},
- props: ['pipeline'],
+
+ updated() {
+ $(this.$refs.tooltip).tooltip('fixTitle');
+ },
+
computed: {
- timeAgo() {
- return gl.utils.getTimeago();
+ hasDuration() {
+ return this.duration > 0;
},
- localTimeFinished() {
- return gl.utils.formatDate(this.pipeline.details.finished_at);
+
+ hasFinishedTime() {
+ return this.finishedTime !== '';
},
- timeStopped() {
- const changeTime = this.currentTime;
- const options = {
- weekday: 'long',
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- };
- options.timeZoneName = 'short';
- const finished = this.pipeline.details.finished_at;
- if (!finished && changeTime) return false;
- return ({ words: this.timeAgo.format(finished) });
+
+ localTimeFinished() {
+ return gl.utils.formatDate(this.finishedTime);
},
- duration() {
- const { duration } = this.pipeline.details;
- const date = new Date(duration * 1000);
+
+ durationFormated() {
+ const date = new Date(this.duration * 1000);
let hh = date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
- if (hh < 10) hh = `0${hh}`;
- if (mm < 10) mm = `0${mm}`;
- if (ss < 10) ss = `0${ss}`;
+ // left pad
+ if (hh < 10) {
+ hh = `0${hh}`;
+ }
+ if (mm < 10) {
+ mm = `0${mm}`;
+ }
+ if (ss < 10) {
+ ss = `0${ss}`;
+ }
- if (duration !== null) return `${hh}:${mm}:${ss}`;
- return false;
+ return `${hh}:${mm}:${ss}`;
},
- },
- methods: {
- changeTime() {
- this.currentTime = new Date();
+
+ finishedTimeFormated() {
+ const timeAgo = gl.utils.getTimeago();
+
+ return timeAgo.format(this.finishedTime);
},
},
+
template: `
<td class="pipelines-time-ago">
- <p class="duration" v-if='duration'>
- <span v-html="iconTimerSvg"></span>
- {{duration}}
+ <p
+ class="duration"
+ v-if="hasDuration">
+ <span
+ v-html="iconTimerSvg">
+ </span>
+ {{durationFormated}}
</p>
- <p class="finished-at" v-if='timeStopped'>
- <i class="fa fa-calendar"></i>
+
+ <p
+ class="finished-at"
+ v-if="hasFinishedTime">
+
+ <i
+ class="fa fa-calendar"
+ aria-hidden="true" />
+
<time
+ ref="tooltip"
data-toggle="tooltip"
data-placement="top"
data-container="body"
- :data-original-title='localTimeFinished'>
- {{timeStopped.words}}
+ :title="localTimeFinished">
+ {{finishedTimeFormated}}
</time>
</p>
</td>
diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js
index 6eea4812f33..93d4818231f 100644
--- a/app/assets/javascripts/pipelines/pipelines.js
+++ b/app/assets/javascripts/pipelines/pipelines.js
@@ -1,4 +1,3 @@
-import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
@@ -161,15 +160,6 @@ export default {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
- beforeUpdate() {
- if (this.state.pipelines.length &&
- this.$children &&
- !this.isMakingRequest &&
- !this.isLoading) {
- this.store.startTimeAgoLoops.call(this, Vue);
- }
- },
-
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
diff --git a/app/assets/javascripts/pipelines/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js
index 377ec8ba2cc..ffefe0192f2 100644
--- a/app/assets/javascripts/pipelines/stores/pipelines_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js
@@ -1,6 +1,3 @@
-/* eslint-disable no-underscore-dangle*/
-import VueRealtimeListener from '../../vue_realtime_listener';
-
export default class PipelinesStore {
constructor() {
this.state = {};
@@ -30,32 +27,4 @@ export default class PipelinesStore {
this.state.pageInfo = paginationInfo;
}
-
- /**
- * FIXME: Move this inside the component.
- *
- * Once the data is received we will start the time ago loops.
- *
- * Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
- * update the time to show how long as passed.
- *
- */
- startTimeAgoLoops() {
- const startTimeLoops = () => {
- this.timeLoopInterval = setInterval(() => {
- this.$children[0].$children.reduce((acc, component) => {
- const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
- acc.push(timeAgoComponent);
- return acc;
- }, []).forEach(e => e.changeTime());
- }, 10000);
- };
-
- startTimeLoops();
-
- const removeIntervals = () => clearInterval(this.timeLoopInterval);
- const startIntervals = () => startTimeLoops();
-
- VueRealtimeListener(removeIntervals, startIntervals);
- }
}
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 30902767705..0344ce9ffb4 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -33,6 +33,7 @@
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
$dropdown = $(dropdown);
options.projectId = $dropdown.data('project-id');
+ options.groupId = $dropdown.data('group-id');
options.showCurrentUser = $dropdown.data('current-user');
options.todoFilter = $dropdown.data('todo-filter');
options.todoStateFilter = $dropdown.data('todo-state-filter');
diff --git a/app/assets/javascripts/vue_realtime_listener/index.js b/app/assets/javascripts/vue_realtime_listener/index.js
deleted file mode 100644
index 4ddb2f975b0..00000000000
--- a/app/assets/javascripts/vue_realtime_listener/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default (removeIntervals, startIntervals) => {
- window.removeEventListener('focus', startIntervals);
- window.removeEventListener('blur', removeIntervals);
- window.removeEventListener('onbeforeload', removeIntervals);
-
- window.addEventListener('focus', startIntervals);
- window.addEventListener('blur', removeIntervals);
- window.addEventListener('onbeforeload', removeIntervals);
-};
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index 62b7131de51..79806bc7204 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
@@ -1,5 +1,4 @@
/* eslint-disable no-param-reassign */
-
import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
@@ -166,6 +165,32 @@ export default {
}
return undefined;
},
+
+ /**
+ * Timeago components expects a number
+ *
+ * @return {type} description
+ */
+ pipelineDuration() {
+ if (this.pipeline.details && this.pipeline.details.duration) {
+ return this.pipeline.details.duration;
+ }
+
+ return 0;
+ },
+
+ /**
+ * Timeago component expects a String.
+ *
+ * @return {String}
+ */
+ pipelineFinishedAt() {
+ if (this.pipeline.details && this.pipeline.details.finished_at) {
+ return this.pipeline.details.finished_at;
+ }
+
+ return '';
+ },
},
template: `
@@ -192,7 +217,9 @@ export default {
</div>
</td>
- <time-ago :pipeline="pipeline"/>
+ <time-ago
+ :duration="pipelineDuration"
+ :finished-time="pipelineFinishedAt" />
<td class="pipeline-actions">
<div class="pull-right btn-group">
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index f614f262316..9159927ed8b 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -108,8 +108,7 @@
}
.award-control {
- margin: 3px 5px 3px 0;
- padding: .35em .4em;
+ margin-right: 5px;
outline: 0;
&.disabled {
@@ -228,8 +227,8 @@
.award-control-icon-positive,
.award-control-icon-super-positive {
position: absolute;
- left: 7px;
- bottom: 9px;
+ left: 11px;
+ bottom: 7px;
opacity: 0;
@include transition(opacity, transform);
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 52425262925..ac1fc0eb8ae 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -230,7 +230,6 @@
float: right;
margin-top: 8px;
padding-bottom: 8px;
- border-bottom: 1px solid $border-color;
}
}
@@ -255,6 +254,63 @@
padding: 10px 0;
}
+.landing {
+ margin-bottom: $gl-padding;
+ overflow: hidden;
+ display: flex;
+ position: relative;
+ border: 1px solid $blue-300;
+ border-radius: $border-radius-default;
+ background-color: $blue-25;
+ justify-content: center;
+
+ .dismiss-button {
+ position: absolute;
+ right: 6px;
+ top: 6px;
+ cursor: pointer;
+ color: $blue-300;
+ z-index: 1;
+ border: none;
+ background-color: transparent;
+
+ &:hover,
+ &:focus {
+ border: none;
+ color: $blue-400;
+ }
+ }
+
+ .svg-container {
+ align-self: center;
+ }
+
+ .inner-content {
+ text-align: left;
+ white-space: nowrap;
+
+ h4 {
+ color: $gl-text-color;
+ font-size: 17px;
+ }
+
+ p {
+ color: $gl-text-color;
+ margin-bottom: $gl-padding;
+ }
+ }
+
+ @media (max-width: $screen-sm-min) {
+ flex-direction: column;
+
+ .inner-content {
+ white-space: normal;
+ padding: 0 28px;
+ text-align: center;
+ }
+ }
+}
+
.empty-state {
margin: 100px 0 0;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 0fd7203e72b..1a6f36d032d 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -70,7 +70,7 @@ pre {
}
hr {
- margin: $gl-padding 0;
+ margin: 24px 0;
border-top: 1px solid darken($gray-normal, 8%);
}
@@ -424,6 +424,11 @@ table {
}
}
+.bordered-box {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+}
+
.str-truncated {
&-60 {
@include str-truncated(60%);
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 30d785464ac..1313ea25c2a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -195,7 +195,6 @@
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
- overflow: hidden;
@include set-invisible;
@media (max-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index a5a8522739e..c197bf6b9f5 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -61,11 +61,13 @@
.file-content {
background: $white-light;
- &.image_file {
+ &.image_file,
+ &.video {
background: $file-image-bg;
text-align: center;
- img {
+ img,
+ video {
padding: 20px;
max-width: 80%;
}
@@ -73,14 +75,6 @@
&.wiki {
padding: 30px $gl-padding;
-
- .highlight {
- margin-bottom: 9px;
-
- > pre {
- margin: 0;
- }
- }
}
&.blob-no-preview {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 11d44df4867..0692f65043b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -104,6 +104,24 @@
padding: 2px 7px;
}
+ .value {
+ padding-right: 0;
+ }
+
+ .remove-token {
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 8px;
+
+ .fa-close {
+ color: $gl-text-color-disabled;
+ }
+
+ &:hover .fa-close {
+ color: $gl-text-color-secondary;
+ }
+ }
+
.name {
background-color: $filter-name-resting-color;
color: $filter-name-text-color;
@@ -112,7 +130,7 @@
text-transform: capitalize;
}
- .value {
+ .value-container {
background-color: $white-normal;
color: $filter-value-text-color;
border-radius: 0 2px 2px 0;
@@ -124,7 +142,7 @@
background-color: $filter-name-selected-color;
}
- .value {
+ .value-container {
background-color: $filter-value-selected-color;
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index a668a6c4c39..80691a234f8 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -120,6 +120,10 @@
// Ensure that image does not exceed viewport
max-height: calc(100vh - 100px);
}
+
+ table {
+ @include markdown-table;
+ }
}
.toolbar-group {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index b3340d41333..3a98332e46c 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -13,6 +13,13 @@
}
/*
+ * Mixin for markdown tables
+ */
+@mixin markdown-table {
+ width: auto;
+}
+
+/*
* Base mixin for lists in GitLab
*/
@mixin basic-list {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index e6d808717f3..b6cf5101d60 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -110,7 +110,7 @@
.top-area {
@include clearfix;
- border-bottom: 1px solid $white-normal;
+ border-bottom: 1px solid $border-color;
.nav-text {
padding-top: 16px;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index cd23deb6d75..d2164a1d333 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -4,7 +4,7 @@
padding: 0;
.timeline-entry {
- padding: $gl-padding $gl-btn-padding 14px;
+ padding: $gl-padding $gl-btn-padding 0;
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 1839cadcc10..96d8a812723 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -8,6 +8,13 @@
img {
max-width: 100%;
+ margin: 0 0 8px;
+ }
+
+ p a:not(.no-attachment-icon) img {
+ // Remove bottom padding because
+ // <p> already has $gl-padding bottom
+ margin-bottom: 0;
}
*:first-child:not(.katex-display) {
@@ -47,44 +54,50 @@
h1 {
font-size: 1.75em;
font-weight: 600;
- margin: 16px 0 10px;
- padding: 0 0 0.3em;
+ margin: 24px 0 16px;
+ padding-bottom: 0.3em;
border-bottom: 1px solid $white-dark;
color: $gl-text-color;
+
+ &:first-child {
+ margin-top: 0;
+ }
}
h2 {
font-size: 1.5em;
font-weight: 600;
- margin: 16px 0 10px;
+ margin: 24px 0 16px;
+ padding-bottom: 0.3em;
+ border-bottom: 1px solid $white-dark;
color: $gl-text-color;
}
h3 {
- margin: 16px 0 10px;
+ margin: 24px 0 16px;
font-size: 1.3em;
}
h4 {
- margin: 16px 0 10px;
+ margin: 24px 0 16px;
font-size: 1.2em;
}
h5 {
- margin: 16px 0 10px;
+ margin: 24px 0 16px;
font-size: 1em;
}
h6 {
- margin: 16px 0 10px;
+ margin: 24px 0 16px;
font-size: 0.95em;
}
blockquote {
color: $gl-grayish-blue;
font-size: inherit;
- padding: 8px 21px;
- margin: 12px 0;
+ padding: 8px 24px;
+ margin: 16px 0;
border-left: 3px solid $white-dark;
}
@@ -95,19 +108,20 @@
blockquote p {
color: $gl-grayish-blue !important;
+ margin: 0;
font-size: inherit;
line-height: 1.5;
}
p {
color: $gl-text-color;
- margin: 6px 0 0;
+ margin: 0 0 16px;
}
table {
@extend .table;
@extend .table-bordered;
- margin: 12px 0;
+ margin: 16px 0;
color: $gl-text-color;
th {
@@ -120,7 +134,7 @@
}
pre {
- margin: 12px 0;
+ margin-bottom: 16px;
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
@@ -134,7 +148,7 @@
ul,
ol {
padding: 0;
- margin: 3px 0 !important;
+ margin: 0 0 16px !important;
}
ul:dir(rtl),
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 411f1c4442b..724b4080ee0 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -200,6 +200,7 @@
.header-content {
flex: 1;
+ line-height: 1.8;
a {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index ad3dbc7ac48..403724cd68a 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -93,11 +93,6 @@
top: $gl-padding-top;
}
- .bordered-box {
- border: 1px solid $border-color;
- border-radius: $border-radius-default;
- }
-
.content-list {
li {
padding: 18px $gl-padding $gl-padding;
@@ -139,42 +134,9 @@
}
}
- .landing {
- margin-bottom: $gl-padding;
- overflow: hidden;
-
- .dismiss-icon {
- position: absolute;
- right: $cycle-analytics-box-padding;
- cursor: pointer;
- color: $cycle-analytics-dismiss-icon-color;
- }
-
- .svg-container {
- text-align: center;
-
- svg {
- width: 136px;
- height: 136px;
- }
- }
-
- .inner-content {
- @media (max-width: $screen-xs-max) {
- padding: 0 28px;
- text-align: center;
- }
-
- h4 {
- color: $gl-text-color;
- font-size: 17px;
- }
-
- p {
- color: $cycle-analytics-box-text-color;
- margin-bottom: $gl-padding;
- }
- }
+ .landing svg {
+ width: 136px;
+ height: 136px;
}
.fa-spinner {
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 46fd19c93f9..f3de05aa5f6 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -29,11 +29,5 @@
.description {
margin-top: 6px;
-
- p {
- &:last-child {
- margin-bottom: 0;
- }
- }
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 73a5889867a..72d73b89a2a 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -88,3 +88,26 @@
color: $gl-text-color-secondary;
margin-top: 10px;
}
+
+.explore-groups.landing {
+ margin-top: 10px;
+
+ .inner-content {
+ padding: 0;
+
+ p {
+ margin: 7px 0 0;
+ max-width: 480px;
+ padding: 0 $gl-padding;
+
+ @media (max-width: $screen-sm-min) {
+ margin: 0 auto;
+ }
+ }
+ }
+
+ svg {
+ width: 62px;
+ height: 50px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 8d3d1a72b9b..97fab513b01 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -52,7 +52,7 @@
.title {
padding: 0;
- margin: 0;
+ margin-bottom: 16px;
border-bottom: none;
}
@@ -357,6 +357,8 @@
}
.detail-page-description {
+ padding: 16px 0 0;
+
small {
color: $gray-darkest;
}
@@ -364,6 +366,8 @@
.edited-text {
color: $gray-darkest;
+ display: block;
+ margin: 0 0 16px;
.author_link {
color: $gray-darkest;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index b2f45625a2a..2aa52986e0a 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -101,11 +101,16 @@ ul.related-merge-requests > li {
}
}
-.merge-request-ci-status {
+.merge-request-ci-status,
+.related-merge-requests {
+ .ci-status-link {
+ display: block;
+ margin-top: 3px;
+ margin-right: 5px;
+ }
+
svg {
- margin-right: 4px;
- position: relative;
- top: 1px;
+ display: block;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index b637994adf8..62f654ed343 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -28,7 +28,7 @@
.note-edit-form {
.note-form-actions {
position: relative;
- margin-top: $gl-padding;
+ margin: $gl-padding 0;
}
.note-preview-holder {
@@ -387,6 +387,7 @@
@media (max-width: $screen-xs-max) {
display: flex;
width: 100%;
+ margin-bottom: 10px;
.comment-btn {
flex-grow: 1;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 2ea2ff8362b..7cf74502a3a 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -97,18 +97,21 @@ ul.notes {
padding-left: 1.3em;
}
}
+
+ table {
+ @include markdown-table;
+ }
}
}
.note-awards {
.js-awards-block {
- padding: 2px;
- margin-top: 10px;
+ margin-bottom: 16px;
}
}
.note-header {
- padding-bottom: 3px;
+ padding-bottom: 8px;
padding-right: 20px;
@media (min-width: $screen-sm-min) {
@@ -151,6 +154,10 @@ ul.notes {
margin-left: 65px;
}
+ .note-header {
+ padding-bottom: 0;
+ }
+
&.timeline-entry::after {
clear: none;
}
@@ -386,6 +393,10 @@ ul.notes {
.note-headline-meta {
display: inline-block;
white-space: nowrap;
+
+ .system-note-message {
+ white-space: normal;
+ }
}
/**
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 28a8f9cb335..c119f0c9b22 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -614,6 +614,7 @@ pre.light-well {
.controls {
margin-left: auto;
+ text-align: right;
}
.ci-status-link {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index f3916622b6f..03c75ce61f5 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -160,7 +160,6 @@
.tree-controls {
float: right;
- margin-top: 11px;
position: relative;
z-index: 2;
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 9bc47bbe173..04ff2d52b91 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -159,3 +159,9 @@ ul.wiki-pages-list.content-list {
padding: 5px 0;
}
}
+
+.wiki {
+ table {
+ @include markdown-table;
+ }
+}