Commit f8fe64a6 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 8998_skip_pending_commits_if_not_head

* upstream/master: (58 commits)
  Fix icon name error
  Rewrite system note helper
  Change edit icon
  Leave icon area blank if legacy note; remove diamond icon
  Fix positioning of note icons
  Fix newline errors
  Add remaining system note icons
  Add system notes icon helper; add icons
  Fixed specs Updated JS that was causing the hints to appear & then disappear
  Update tests
  Fix broken spinach test
  Reuse code
  Improve shortcuts code
  Adds ShortcutsDashboardNavigation to globals comment since its a global variable
  Fix shortcut specs
  Reorganize shortcut help menu
  Change todos shortcut to shift
  Change shortcuts
  Switch global shortcuts to shift; reuse key styles from help menu
  Map bindings to lowercase letters; only show key bindings when using keyboard shortcut
  ...
parents e28fc7b1 6ea35f98
...@@ -20,6 +20,7 @@ import eventHub from '../eventhub'; ...@@ -20,6 +20,7 @@ import eventHub from '../eventhub';
list: { list: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}),
}, },
rootPath: { rootPath: {
type: String, type: String,
...@@ -31,6 +32,26 @@ import eventHub from '../eventhub'; ...@@ -31,6 +32,26 @@ import eventHub from '../eventhub';
default: false, 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: { methods: {
showLabel(label) { showLabel(label) {
if (!this.list) return true; if (!this.list) return true;
...@@ -67,35 +88,41 @@ import eventHub from '../eventhub'; ...@@ -67,35 +88,41 @@ import eventHub from '../eventhub';
}, },
template: ` template: `
<div> <div>
<div class="card-header">
<h4 class="card-title"> <h4 class="card-title">
<i <i
class="fa fa-eye-slash confidential-icon" class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"></i> v-if="issue.confidential"
aria-hidden="true"
/>
<a <a
:href="issueLinkBase + '/' + issue.id" class="js-no-trigger"
:title="issue.title"> :href="cardUrl"
{{ issue.title }} :title="issue.title">{{ issue.title }}</a>
</a>
</h4>
<div class="card-footer">
<span <span
class="card-number" class="card-number"
v-if="issue.id"> v-if="issue.id"
#{{ issue.id }} >
{{ issueId }}
</span> </span>
</h4>
<a <a
class="card-assignee has-tooltip js-no-trigger" class="card-assignee has-tooltip js-no-trigger"
:href="rootPath + issue.assignee.username" :href="assigneeUrl"
:title="'Assigned to ' + issue.assignee.name" :title="assigneeUrlTitle"
v-if="issue.assignee" v-if="issue.assignee"
data-container="body"> data-container="body"
>
<img <img
class="avatar avatar-inline s20 js-no-trigger" class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar" :src="issue.assignee.avatar"
width="20" width="20"
height="20" height="20"
:alt="'Avatar for ' + issue.assignee.name" /> :alt="avatarUrlTitle"
/>
</a> </a>
</div>
<div class="card-footer" v-if="showLabelFooter">
<button <button
class="label color-label has-tooltip js-no-trigger" class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels" v-for="label in issue.labels"
......
...@@ -38,9 +38,35 @@ showTooltip = function(target, title) { ...@@ -38,9 +38,35 @@ showTooltip = function(target, title) {
}; };
$(function() { $(function() {
var clipboard; const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess); 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);
});
}); });
...@@ -13,10 +13,6 @@ class Diff { ...@@ -13,10 +13,6 @@ class Diff {
$diffFile.each((index, file) => new gl.ImageFile(file)); $diffFile.each((index, file) => new gl.ImageFile(file));
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
if (!isBound) { if (!isBound) {
$(document) $(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this)) .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
/* global NamespaceSelects */ /* global NamespaceSelects */
/* global ShortcutsDashboardNavigation */
/* global Project */ /* global Project */
/* global ProjectAvatar */ /* global ProjectAvatar */
/* global CompareAutocomplete */ /* global CompareAutocomplete */
...@@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
break; break;
case 'dashboard': case 'dashboard':
case 'root': case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout(); new UserCallout();
break; break;
case 'groups': case 'groups':
......
...@@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', { ...@@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', {
cssContainerClass: environmentsData.cssClass, cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment, canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment, canReadEnvironment: environmentsData.canReadEnvironment,
// svgs
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties, // Pagination Properties,
paginationInformation: {}, paginationInformation: {},
pageNumber: 1, pageNumber: 1,
...@@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', { ...@@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', {
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"
:service="service"/> :service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
/* 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 */ /* 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 Mousetrap */
/* global findFileURL */ /* global findFileURL */
import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -14,11 +15,33 @@ ...@@ -14,11 +15,33 @@
} }
Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (function(_this) { Mousetrap.bind('f', (e => this.focusFilter(e)));
return function(e) {
return _this.focusFilter(e); const $globalDropdownMenu = $('.global-dropdown-menu');
}; const $globalDropdownToggle = $('.global-dropdown-toggle');
})(this));
$('.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); Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) { if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() { Mousetrap.bind('t', function() {
......
/* 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 */ * Helper function that finds the href of the fiven selector and updates the location.
/* global Shortcuts */ *
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
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) { if (link) {
return window.location = link; window.location = link;
} }
}; };
return ShortcutsDashboardNavigation;
})(Shortcuts);
}).call(window);
/* 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 */ /* 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 Mousetrap */
/* global Shortcuts */ /* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts'); require('./shortcuts');
...@@ -13,59 +14,23 @@ require('./shortcuts'); ...@@ -13,59 +14,23 @@ require('./shortcuts');
function ShortcutsNavigation() { function ShortcutsNavigation() {
ShortcutsNavigation.__super__.constructor.call(this); ShortcutsNavigation.__super__.constructor.call(this);
Mousetrap.bind('g p', function() { Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project'); Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity'));
}); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g e', function() { Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
}); Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network'));
Mousetrap.bind('g f', function() { Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'); Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
}); Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
Mousetrap.bind('g c', function() { Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'); Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
}); Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind('g b', function() { Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
});
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');
});
this.enabledHelp.push('.hidden-shortcut.project'); this.enabledHelp.push('.hidden-shortcut.project');
} }
ShortcutsNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsNavigation; return ShortcutsNavigation;
})(Shortcuts); })(Shortcuts);
}).call(window); }).call(window);
...@@ -187,6 +187,15 @@ ...@@ -187,6 +187,15 @@
} }
} }
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul { ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
......
.timeline { .timeline {
@include basic-list; @include basic-list;
margin: 0; margin: 0;
padding: 0; padding: 0;
.timeline-entry { .timeline-entry {
padding: $gl-padding $gl-btn-padding 11px; padding: $gl-padding $gl-btn-padding 14px;
border-color: $white-normal; border-color: $white-normal;
color: $gl-text-color; color: $gl-text-color;
border-bottom: 1px solid $border-white-light; border-bottom: 1px solid $border-white-light;
.timeline-entry-inner {
position: relative;
}
&:target { &:target {
background: $line-target-blue; background: $line-target-blue;
} }
......
...@@ -197,7 +197,7 @@ ...@@ -197,7 +197,7 @@
.card { .card {
position: relative; position: relative;
padding: 10px $gl-padding; padding: 11px 10px 11px $gl-padding;
background: $white-light; background: $white-light;
border-radius: $border-radius-default; border-radius: $border-radius-default;
box-shadow: 0 1px 2px $issue-boards-card-shadow; box-shadow: 0 1px 2px $issue-boards-card-shadow;
...@@ -217,6 +217,8 @@ ...@@ -217,6 +217,8 @@
} }
.confidential-icon { .confidential-icon {
position: relative;
top: 1px;
margin-right: 5px; margin-right: 5px;
} }
} }
...@@ -224,34 +226,43 @@ ...@@ -224,34 +226,43 @@
.card-title { .card-title {
margin: 0; margin: 0;
font-size: 1em; font-size: 1em;
line-height: inherit;
a { a {
color: inherit; color: $gl-text-color;
word-wrap: break-word; word-wrap: break-word;
margin-right: 2px;
} }
} }
.card-footer { .card-header {
margin-top: 5px; display: flex;
line-height: 25px; min-height: 20px;
.label {
margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em;
}
.card-assignee { .card-assignee {
margin-left: auto;
margin-right: 5px; margin-right: 5px;
padding-left: 10px;
height: 20px;
} }
.avatar { .avatar {
margin-left: 0; margin: 0;
margin-right: 0; }
}
.card-footer {
margin: 0 0 5px;
.label {
margin-top: 5px;
margin-right: 6px;
} }
} }
.card-number { .card-number {
margin-right: 5px; font-size: 12px;
color: $gl-text-color-secondary;
} }
.issue-boards-search { .issue-boards-search {
......
...@@ -16,6 +16,15 @@ ul.notes { ...@@ -16,6 +16,15 @@ ul.notes {
.timeline-icon { .timeline-icon {
float: left; float: left;
svg {
width: 18px;
height: auto;
fill: $gray-darkest;
position: absolute;
left: 30px;
top: 15px;
}
} }
.timeline-content { .timeline-content {
...@@ -33,6 +42,103 @@ ul.notes { ...@@ -33,6 +42,103 @@ ul.notes {
white-space: nowrap; 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 { .system-note {
font-size: 14px; font-size: 14px;
padding: 0; padding: 0;
...@@ -68,6 +174,10 @@ ul.notes { ...@@ -68,6 +174,10 @@ ul.notes {
padding: 14px 10px; padding: 14px 10px;
} }
.note-header {
padding-bottom: 0;
}
.note-body { .note-body {
overflow: hidden; overflow: hidden;
...@@ -130,116 +240,6 @@ ul.notes { ...@@ -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;
}
}
}
} }
} }
......
...@@ -102,7 +102,7 @@ module BlobHelper ...@@ -102,7 +102,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename) if Gitlab::MarkupHelper.previewable?(filename)
'Preview' 'Preview'
else else
'Preview Changes' 'Preview changes'
end end
end end
...@@ -210,13 +210,13 @@ module BlobHelper ...@@ -210,13 +210,13 @@ module BlobHelper
end end
def copy_file_path_button(file_path) def copy_file_path_button(file_path)
clipboard_button(clipboard_text: file_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard') clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
end end
def copy_blob_content_button(blob) def copy_blob_content_button(blob)
return if markup?(blob.name) return if markup?(blob.name)
clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard") clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard")
end end
def open_raw_file_button(path) def open_raw_file_button(path)
......
module ButtonHelper module ButtonHelper
# Output a "Copy to Clipboard" button # Output a "Copy to Clipboard" button
# #
# data - Data attributes passed to `content_tag` # data - Data attributes passed to `content_tag` (default: {}):
# :text - Text to copy (optional)
# :gfm - GitLab Flavored Markdown to copy, if different from `text` (optional)
# :target - Selector for target element to copy from (optional)
# #
# Examples: # Examples:
# #
# # Define the clipboard's text # # Define the clipboard's text
# clipboard_button(clipboard_text: "Foo") # clipboard_button(text: "Foo")
# # => "<button class='...' data-clipboard-text='Foo'>...</button>" # # => "<button class='...' data-clipboard-text='Foo'>...</button>"
# #
# # Define the target element # # Define the target element
# clipboard_button(clipboard_target: "div#foo") # clipboard_button(target: "div#foo")
# # => "<button class='...' data-clipboard-target='div#foo'>...</button>" # # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
# #
# See http://clipboardjs.com/#usage # See http://clipboardjs.com/#usage
def clipboard_button(data = {}) def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent' css_class = data[:class] || 'btn-clipboard btn-transparent'
title = data[:title] || 'Copy to clipboard' title = data[:title] || 'Copy to clipboard'
# This supports code in app/assets/javascripts/copy_to_clipboard.js that
# works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
if text = data.delete(:text)
data[:clipboard_text] =
if gfm = data.delete(:gfm)
{ text: text, gfm: gfm }
else
text
end
end
target = data.delete(:target)
data[:clipboard_target] = target if target
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button, content_tag :button,
icon('clipboard', 'aria-hidden': 'true'), icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}", class: "btn #{css_class}",
......
module SystemNoteHelper
ICON_NAMES_BY_ACTION = {
'commit' => 'icon_commit',
'merge' => 'icon_merge',
'merged' => 'icon_merged',
'opened' => 'icon_status_open',
'closed' => 'icon_status_closed',
'time_tracking' => 'icon_stopwatch',
'assignee' => 'icon_user',
'title' => 'icon_pencil',
'task' => 'icon_check_square_o',
'label' => 'icon_tags',
'cross_reference' => 'icon_random',
'branch' => 'icon_code_fork',
'confidential' => 'icon_eye_slash',
'visible' => 'icon_eye',
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
'moved' => 'icon_arrow_circle_o_right'
}.freeze
def icon_for_system_note(note)
icon_name = ICON_NAMES_BY_ACTION[note.system_note_metadata&.action]
custom_icon(icon_name) if icon_name
end
end
...@@ -103,7 +103,6 @@ module Ci ...@@ -103,7 +103,6 @@ module Ci
end end
def playable? def playable?
project.builds_enabled? && has_commands? &&
action? && manual? action? && manual?
end end
...@@ -111,10 +110,6 @@ module Ci ...@@ -111,10 +110,6 @@ module Ci
self.when == 'manual' self.when == 'manual'
end end
def has_commands?
commands.present?
end
def play(current_user) def play(current_user)
# Try to queue a current build # Try to queue a current build
if self.enqueue if self.enqueue
...@@ -131,8 +126,7 @@ module Ci ...@@ -131,8 +126,7 @@ module Ci
end end
def retryable? def retryable?
project.builds_enabled? && has_commands? && success? || failed? || canceled?
(success? || failed? || canceled?)
end end
def retried? def retried?
......
...@@ -17,6 +17,12 @@ module Ci ...@@ -17,6 +17,12 @@ module Ci
has_many :builds, foreign_key: :commit_id has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
has_many :manual_actions, -> { latest.manual_actions }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :artifacts, -> { latest.with_artifacts_not_expired }, foreign_key: :commit_id, class_name: 'Ci::Build'
delegate :id, to: :project, prefix: true delegate :id, to: :project, prefix: true
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
...@@ -169,10 +175,6 @@ module Ci ...@@ -169,10 +175,6 @@ module Ci
end end
end end
def artifacts
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
def valid_commit_sha def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)") self.errors.add(:sha, " cant be 00000000 (branch removal)")
...@@ -209,20 +211,16 @@ module Ci ...@@ -209,20 +211,16 @@ module Ci
!tag? !tag?
end end
def manual_actions
builds.latest.manual_actions.includes(project: [:namespace])
end
def stuck? def stuck?
builds.pending.includes(:project).any?(&:stuck?) pending_builds.any?(&:stuck?)
end end
def retryable? def retryable?
builds.latest.failed_or_canceled.any?(&:retryable?) retryable_builds.any?
end end
def cancelable? def cancelable?
statuses.cancelable.any? cancelable_statuses.any?
end end
def auto_canceled? def auto_canceled?
......
...@@ -116,6 +116,7 @@ class Project < ActiveRecord::Base ...@@ -116,6 +116,7 @@ class Project < ActiveRecord::Base
has_one :mock_ci_service, dependent: :destroy has_one :mock_ci_service, dependent: :destroy
has_one :mock_deployment_service, dependent: :destroy has_one :mock_deployment_service, dependent: :destroy
has_one :mock_monitoring_service, dependent: :destroy has_one :mock_monitoring_service, dependent: :destroy
has_one :microsoft_teams_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
...@@ -171,6 +172,8 @@ class Project < ActiveRecord::Base ...@@ -171,6 +172,8 @@ class Project < ActiveRecord::Base
has_many :environments, dependent: :destroy has_many :environments, dependent: :destroy
has_many :deployments, dependent: :destroy has_many :deployments, dependent: :destroy
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature accepts_nested_attributes_for :project_feature
...@@ -1086,15 +1089,15 @@ class Project < ActiveRecord::Base ...@@ -1086,15 +1089,15 @@ class Project < ActiveRecord::Base
end end
def shared_runners def shared_runners
shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none @shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
end end
def any_runners?(&block) def active_shared_runners
if runners.active.any?(&block) @active_shared_runners ||= shared_runners.active
return true
end end
shared_runners.active.any?(&block) def any_runners?(&block)
active_runners.any?(&block) || active_shared_runners.any?(&block)
end end
def valid_runners_token?(token) def valid_runners_token?(token)
......
...@@ -2,11 +2,23 @@ require 'slack-notifier' ...@@ -2,11 +2,23 @@ require 'slack-notifier'
module ChatMessage module ChatMessage
class BaseMessage class BaseMessage
attr_reader :markdown
attr_reader :user_name
attr_reader :user_avatar
attr_reader :project_name
attr_reader :project_url
def initialize(params) def initialize(params)
raise NotImplementedError @markdown = params[:markdown] || false
@project_name = params.dig(:project, :path_with_namespace) || params[:project_name]
@project_url = params.dig(:project, :web_url) || params[:project_url]
@user_name = params.dig(:user, :username) || params[:user_name]
@user_avatar = params.dig(:user, :avatar_url) || params[:user_avatar]
end end
def pretext def pretext
return message if markdown
format(message) format(message)
end end
...@@ -17,6 +29,10 @@ module ChatMessage ...@@ -17,6 +29,10 @@ module ChatMessage
raise NotImplementedError raise NotImplementedError
end end
def activity
raise NotImplementedError
end
private private
def message def message
......
module ChatMessage module ChatMessage
class IssueMessage < BaseMessage class IssueMessage < BaseMessage
attr_reader :user_name
attr_reader :title attr_reader :title
attr_reader :project_name
attr_reader :project_url
attr_reader :issue_iid attr_reader :issue_iid
attr_reader :issue_url attr_reader :issue_url
attr_reader :action attr_reader :action
...@@ -11,9 +8,7 @@ module ChatMessage ...@@ -11,9 +8,7 @@ module ChatMessage
attr_reader :description attr_reader :description
def initialize(params) def initialize(params)
@user_name = params[:user][:username] super
@project_name = params[:project_name]
@project_url = params[:project_url]
obj_attr = params[:object_attributes] obj_attr = params[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr) obj_attr = HashWithIndifferentAccess.new(obj_attr)
...@@ -27,15 +22,24 @@ module ChatMessage ...@@ -27,15 +22,24 @@ module ChatMessage
def attachments def attachments
return [] unless opened_issue? return [] unless opened_issue?
return description if markdown
description_message description_message
end end
def activity
{
title: "Issue #{state} by #{user_name}",
subtitle: "in #{project_link}",
text: issue_link,
image: user_avatar
}
end
private private
def message def message
case state if state == 'opened'
when "opened"
"[#{project_link}] Issue #{state} by #{user_name}" "[#{project_link}] Issue #{state} by #{user_name}"
else else
"[#{project_link}] Issue #{issue_link} #{state} by #{user_name}" "[#{project_link}] Issue #{issue_link} #{state} by #{user_name}"
...@@ -64,7 +68,7 @@ module ChatMessage ...@@ -64,7 +68,7 @@ module ChatMessage
end end
def issue_title def issue_title
"##{issue_iid} #{title}" "#{Issue.reference_prefix}#{issue_iid} #{title}"
end end
end end
end end
module ChatMessage module ChatMessage
class MergeMessage < BaseMessage class MergeMessage < BaseMessage
attr_reader :user_name attr_reader :merge_request_iid
attr_reader :project_name
attr_reader :project_url
attr_reader :merge_request_id
attr_reader :source_branch attr_reader :source_branch
attr_reader :target_branch attr_reader :target_branch
attr_reader :state attr_reader :state
attr_reader :title attr_reader :title
def initialize(params) def initialize(params)
@user_name = params[:user][:username] super
@project_name = params[:project_name]
@project_url = params[:project_url]
obj_attr = params[:object_attributes] obj_attr = params[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr) obj_attr = HashWithIndifferentAccess.new(obj_attr)
@merge_request_id = obj_attr[:iid] @merge_request_iid = obj_attr[:iid]
@source_branch = obj_attr[:source_branch] @source_branch = obj_attr[:source_branch]
@target_branch = obj_attr[:target_branch] @target_branch = obj_attr[:target_branch]
@state = obj_attr[:state] @state = obj_attr[:state]
@title = format_title(obj_attr[:title]) @title = format_title(obj_attr[:title])
end end
def pretext
format(message)
end
def attachments def attachments
[] []
end end
def activity
{
title: "Merge Request #{state} by #{user_name}",
subtitle: "in #{project_link}",
text: merge_request_link,
image: user_avatar
}
end
private private
def format_title(title) def format_title(title)
...@@ -50,11 +50,15 @@ module ChatMessage ...@@ -50,11 +50,15 @@ module ChatMessage
end end
def merge_request_link def merge_request_link
link("merge request !#{merge_request_id}", merge_request_url) link(merge_request_title, merge_request_url)
end
def merge_request_title
"#{MergeRequest.reference_prefix}#{merge_request_iid} #{title}"
end end
def merge_request_url def merge_request_url
"#{project_url}/merge_requests/#{merge_request_id}" "#{project_url}/merge_requests/#{merge_request_iid}"
end end
end end
end end
module ChatMessage module ChatMessage
class NoteMessage < BaseMessage class NoteMessage < BaseMessage
attr_reader :message
attr_reader :user_name
attr_reader :project_name
attr_reader :project_url
attr_reader :note attr_reader :note
attr_reader :note_url attr_reader :note_url
attr_reader :title
attr_reader :target
def initialize(params) def initialize(params)
params = HashWithIndifferentAccess.new(params) super
@user_name = params[:user][:username]
@project_name = params[:project_name]
@project_url = params[:project_url]
params = HashWithIndifferentAccess.new(params)
obj_attr = params[:object_attributes] obj_attr = params[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
@note = obj_attr[:note] @note = obj_attr[:note]
@note_url = obj_attr[:url] @note_url = obj_attr[:url]
noteable_type = obj_attr[:noteable_type] @target, @title = case obj_attr[:noteable_type]
case noteable_type
when "Commit" when "Commit"
create_commit_note(HashWithIndifferentAccess.new(params[:commit])) create_commit_note(params[:commit])
when "Issue" when "Issue"
create_issue_note(HashWithIndifferentAccess.new(params[:issue])) create_issue_note(params[:issue])
when "MergeRequest" when "MergeRequest"
create_merge_note(HashWithIndifferentAccess.new(params[:merge_request])) create_merge_note(params[:merge_request])
when "Snippet" when "Snippet"
create_snippet_note(HashWithIndifferentAccess.new(params[:snippet])) create_snippet_note(params[:snippet])
end end
end end
def attachments def attachments
return note if markdown
description_message description_message
end end
def activity
{
title: "#{user_name} #{link('commented on ' + target, note_url)}",
subtitle: "in #{project_link}",
text: formatted_title,
image: user_avatar
}
end
private private
def message
"#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{formatted_title}*"
end
def format_title(title) def format_title(title)
title.lines.first.chomp title.lines.first.chomp
end end
def create_commit_note(commit) def formatted_title
commit_sha = commit[:id] format_title(title)
commit_sha = Commit.truncate_sha(commit_sha)
commented_on_message(
"commit #{commit_sha}",
format_title(commit[:message]))
end end
def create_issue_note(issue) def create_issue_note(issue)
commented_on_message( ["issue #{Issue.reference_prefix}#{issue[:iid]}", issue[:title]]
"issue ##{issue[:iid]}", end
format_title(issue[:title]))
def create_commit_note(commit)
commit_sha = Commit.truncate_sha(commit[:id])
["commit #{commit_sha}", commit[:message]]
end end
def create_merge_note(merge_request) def create_merge_note(merge_request)
commented_on_message( ["merge request #{MergeRequest.reference_prefix}#{merge_request[:iid]}", merge_request[:title]]
"merge request !#{merge_request[:iid]}",
format_title(merge_request[:title]))
end end
def create_snippet_note(snippet) def create_snippet_note(snippet)
commented_on_message( ["snippet #{Snippet.reference_prefix}#{snippet[:id]}", snippet[:title]]
"snippet ##{snippet[:id]}",
format_title(snippet[:title]))
end end
def description_message def description_message
...@@ -74,9 +78,5 @@ module ChatMessage ...@@ -74,9 +78,5 @@ module ChatMessage
def project_link def project_link
link(project_name, project_url) link(project_name, project_url)
end end
def commented_on_message(target, title)
@message = "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{title}*"
end
end end
end end
module ChatMessage module ChatMessage
class PipelineMessage < BaseMessage class PipelineMessage < BaseMessage
attr_reader :ref_type, :ref, :status, :project_name, :project_url, attr_reader :ref_type
:user_name, :duration, :pipeline_id attr_reader :ref
attr_reader :status
attr_reader :duration
attr_reader :pipeline_id
def initialize(data) def initialize(data)
super
@user_name = data.dig(:user, :name) || 'API'
pipeline_attributes = data[:object_attributes] pipeline_attributes = data[:object_attributes]
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch' @ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
@ref = pipeline_attributes[:ref] @ref = pipeline_attributes[:ref]
@status = pipeline_attributes[:status] @status = pipeline_attributes[:status]
@duration = pipeline_attributes[:duration] @duration = pipeline_attributes[:duration]
@pipeline_id = pipeline_attributes[:id] @pipeline_id = pipeline_attributes[:id]
@project_name = data[:project][:path_with_namespace]
@project_url = data[:project][:web_url]
@user_name = (data[:user] && data[:user][:name]) || 'API'
end end
def pretext def pretext
...@@ -25,17 +28,24 @@ module ChatMessage ...@@ -25,17 +28,24 @@ module ChatMessage
end end
def attachments def attachments
return message if markdown
[{ text: format(message), color: attachment_color }] [{ text: format(message), color: attachment_color }]
end end
def activity
{
title: "Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status}",
subtitle: "in #{project_link}",
text: "in #{duration} #{time_measure}",
image: user_avatar || ''
}
end
private private
def message def message
"#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}" "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{time_measure}"
end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end end
def humanized_status def humanized_status
...@@ -74,5 +84,9 @@ module ChatMessage ...@@ -74,5 +84,9 @@ module ChatMessage
def pipeline_link def pipeline_link
"[##{pipeline_id}](#{pipeline_url})" "[##{pipeline_id}](#{pipeline_url})"
end end
def time_measure
'second'.pluralize(duration)
end
end end
end end
...@@ -3,33 +3,43 @@ module ChatMessage ...@@ -3,33 +3,43 @@ module ChatMessage
attr_reader :after attr_reader :after
attr_reader :before attr_reader :before
attr_reader :commits attr_reader :commits
attr_reader :project_name
attr_reader :project_url
attr_reader :ref attr_reader :ref
attr_reader :ref_type attr_reader :ref_type
attr_reader :user_name
def initialize(params) def initialize(params)
super
@after = params[:after] @after = params[:after]
@before = params[:before] @before = params[:before]
@commits = params.fetch(:commits, []) @commits = params.fetch(:commits, [])
@project_name = params[:project_name]
@project_url = params[:project_url]
@ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch' @ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch'
@ref = Gitlab::Git.ref_name(params[:ref]) @ref = Gitlab::Git.ref_name(params[:ref])
@user_name = params[:user_name]
end
def pretext
format(message)
end end
def attachments def attachments
return [] if new_branch? || removed_branch? return [] if new_branch? || removed_branch?
return commit_messages if markdown
commit_message_attachments commit_message_attachments
end end
def activity
action = if new_branch?
"created"
elsif removed_branch?
"removed"
else
"pushed to"
end
{
title: "#{user_name} #{action} #{ref_type}",
subtitle: "in #{project_link}",
text: compare_link,
image: user_avatar
}
end
private private
def message def message
...@@ -59,7 +69,7 @@ module ChatMessage ...@@ -59,7 +69,7 @@ module ChatMessage
end end
def commit_messages def commit_messages
commits.map { |commit| compose_commit_message(commit) }.join("\n") commits.map { |commit| compose_commit_message(commit) }.join("\n\n")
end end
def commit_message_attachments def commit_message_attachments
......
module ChatMessage module ChatMessage
class WikiPageMessage < BaseMessage class WikiPageMessage < BaseMessage
attr_reader :user_name
attr_reader :title attr_reader :title
attr_reader :project_name
attr_reader :project_url
attr_reader :wiki_page_url attr_reader :wiki_page_url
attr_reader :action attr_reader :action
attr_reader :description attr_reader :description
def initialize(params) def initialize(params)
@user_name = params[:user][:username] super
@project_name = params[:project_name]
@project_url = params[:project_url]
obj_attr = params[:object_attributes] obj_attr = params[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr) obj_attr = HashWithIndifferentAccess.new(obj_attr)
...@@ -29,9 +24,20 @@ module ChatMessage ...@@ -29,9 +24,20 @@ module ChatMessage
end end
def attachments def attachments
return description if markdown
description_message description_message
end end
def activity
{
title: "#{user_name} #{action} #{wiki_page_link}",
subtitle: "in #{project_link}",
text: title,
image: user_avatar
}
end
private private
def message def message
......
...@@ -49,10 +49,7 @@ class ChatNotificationService < Service ...@@ -49,10 +49,7 @@ class ChatNotificationService < Service
object_kind = data[:object_kind] object_kind = data[:object_kind]
data = data.merge( data = custom_data(data)
project_url: project_url,
project_name: project_name
)
# WebHook events often have an 'update' event that follows a 'open' or # WebHook events often have an 'update' event that follows a 'open' or
# 'close' action. Ignore update events for now to prevent duplicate # 'close' action. Ignore update events for now to prevent duplicate
...@@ -68,8 +65,7 @@ class ChatNotificationService < Service ...@@ -68,8 +65,7 @@ class ChatNotificationService < Service
opts[:channel] = channel_name if channel_name opts[:channel] = channel_name if channel_name
opts[:username] = username if username opts[:username] = username if username
notifier = Slack::Notifier.new(webhook, opts) return false unless notify(message, opts)
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
true true
end end
...@@ -92,6 +88,18 @@ class ChatNotificationService < Service ...@@ -92,6 +88,18 @@ class ChatNotificationService < Service
private private
def notify(message, opts)
Slack::Notifier.new(webhook, opts).ping(
message.pretext,
attachments: message.attachments,
fallback: message.fallback
)
end
def custom_data(data)
data.merge(project_url: project_url, project_name: project_name)
end
def get_message(object_kind, data) def get_message(object_kind, data)
case object_kind case object_kind
when "push", "tag_push" when "push", "tag_push"
......
class MicrosoftTeamsService < ChatNotificationService
def title
'Microsoft Teams Notification'
end
def description
'Receive event notifications in Microsoft Teams'
end
def self.to_param
'microsoft_teams'
end
def help
'This service sends notifications about projects events to Microsoft Teams channels.<br />
To set up this service:
<ol>
<li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
end
def webhook_placeholder
'https://outlook.office.com/webhook/…'
end
def event_field(event)
end
def default_channel_placeholder
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'checkbox', name: 'notify_only_default_branch' },
]
end
private
def notify(message, opts)
MicrosoftTeams::Notifier.new(webhook).ping(
title: message.project_name,
pretext: message.pretext,
activity: message.activity,
attachments: message.attachments
)
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
...@@ -237,6 +237,7 @@ class Service < ActiveRecord::Base ...@@ -237,6 +237,7 @@ class Service < ActiveRecord::Base
slack_slash_commands slack_slash_commands
slack slack
teamcity teamcity
microsoft_teams
] ]
if Rails.env.development? if Rails.env.development?
service_names += %w[mock_ci mock_deployment mock_monitoring] service_names += %w[mock_ci mock_deployment mock_monitoring]
......
...@@ -69,13 +69,13 @@ class PipelineEntity < Grape::Entity ...@@ -69,13 +69,13 @@ class PipelineEntity < Grape::Entity
alias_method :pipeline, :object alias_method :pipeline, :object
def can_retry? def can_retry?
pipeline.retryable? && can?(request.user, :update_pipeline, pipeline) &&
can?(request.user, :update_pipeline, pipeline) pipeline.retryable?
end end
def can_cancel? def can_cancel?
pipeline.cancelable? && can?(request.user, :update_pipeline, pipeline) &&
can?(request.user, :update_pipeline, pipeline) pipeline.cancelable?
end end
def detailed_status def detailed_status
......
...@@ -13,7 +13,15 @@ class PipelineSerializer < BaseSerializer ...@@ -13,7 +13,15 @@ class PipelineSerializer < BaseSerializer
def represent(resource, opts = {}) def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation) if resource.is_a?(ActiveRecord::Relation)
resource = resource.includes(project: :namespace) resource = resource.preload([
:retryable_builds,
:cancelable_statuses,
:trigger_requests,
:project,
{ pending_builds: :project },
{ manual_actions: :project },
{ artifacts: :project }
])
end end
if paginated? if paginated?
......
...@@ -7,9 +7,7 @@ module Ci ...@@ -7,9 +7,7 @@ module Ci
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
end end
pipeline.builds.latest.failed_or_canceled.find_each do |build| pipeline.retryable_builds.find_each do |build|
next unless build.retryable?
Ci::RetryBuildService.new(project, current_user) Ci::RetryBuildService.new(project, current_user)
.reprocess(build) .reprocess(build)
end end
......
...@@ -30,5 +30,5 @@ ...@@ -30,5 +30,5 @@
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-block" = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-block"
- else - else
.btn.btn-sm.disabled.btn-block .btn.btn-sm.disabled.btn-block
Already Blocked Already blocked
= link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-sm btn-block btn-close js-remove-tr" = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-sm btn-block btn-close js-remove-tr"
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
Sign-in enabled Sign-in enabled
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group .form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2' = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
.btn-group{ data: { toggle: 'buttons' } } .btn-group{ data: { toggle: 'buttons' } }
- oauth_providers_checkboxes.each do |source| - oauth_providers_checkboxes.each do |source|
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%p.light %p.light
System OAuth applications don't belong to any user and can only be managed by admins System OAuth applications don't belong to any user and can only be managed by admins
%hr %hr
%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success' %p= link_to 'New application', new_admin_application_path, class: 'btn btn-success'
%table.table.table-striped %table.table.table-striped
%thead %thead
%tr %tr
......
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
= link_to admin_projects_path do = link_to admin_projects_path do
%h1= number_with_delimiter(Project.cached_count) %h1= number_with_delimiter(Project.cached_count)
%hr %hr
= link_to('New Project', new_project_path, class: "btn btn-new") = link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4 .col-sm-4
.light-well.well-centered .light-well.well-centered
%h4 Users %h4 Users
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
= link_to admin_users_path do = link_to admin_users_path do
%h1= number_with_delimiter(User.count) %h1= number_with_delimiter(User.count)
%hr %hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new" = link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4 .col-sm-4
.light-well.well-centered .light-well.well-centered
%h4 Groups %h4 Groups
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
= link_to admin_groups_path do = link_to admin_groups_path do
%h1= number_with_delimiter(Group.count) %h1= number_with_delimiter(Group.count)
%hr %hr
= link_to 'New Group', new_admin_group_path, class: "btn btn-new" = link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row.prepend-top-10 .row.prepend-top-10
.col-md-4 .col-md-4
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%h3.page-title.deploy-keys-title %h3.page-title.deploy-keys-title
Public deploy keys (#{@deploy_keys.count}) Public deploy keys (#{@deploy_keys.count})
.pull-right .pull-right
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted' = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted'
- if @deploy_keys.any? - if @deploy_keys.any?
.table-holder.deploy-keys-list .table-holder.deploy-keys-list
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
= link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do = link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
= sort_title_largest_group = sort_title_largest_group
= link_to new_admin_group_path, class: "btn btn-new" do = link_to new_admin_group_path, class: "btn btn-new" do
New Group New group
%ul.content-list %ul.content-list
= render @groups = render @groups
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
group members group members
%span.badge= @group.members.size %span.badge= @group.members.size
.pull-right .pull-right
= link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs" = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
%ul.well-list.group-users-list.content-list %ul.well-list.group-users-list.content-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
= f.check_box :enable_ssl_verification = f.check_box :enable_ssl_verification
%strong Enable SSL verification %strong Enable SSL verification
.form-actions .form-actions
= f.submit "Add System Hook", class: "btn btn-create" = f.submit "Add system hook", class: "btn btn-create"
%hr %hr
- if @hooks.any? - if @hooks.any?
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
- @hooks.each do |hook| - @hooks.each do |hook|
%li %li
.controls .controls
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" = link_to 'Test hook', admin_hook_test_path(hook), class: "btn btn-sm"
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
.monospace= hook.url .monospace= hook.url
%div %div
......
- page_title "Identities", @user.name, "Users" - page_title "Identities", @user.name, "Users"
= render 'admin/users/head' = render 'admin/users/head'
= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new' = link_to 'New identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
- if @identities.present? - if @identities.present?
.table-holder .table-holder
%table.table %table.table
......
...@@ -159,7 +159,7 @@ ...@@ -159,7 +159,7 @@
%span.badge= @group_members.size %span.badge= @group_members.size
.pull-right .pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do = link_to admin_group_path(@group), class: 'btn btn-xs' do
= icon('pencil-square-o', text: 'Manage Access') = icon('pencil-square-o', text: 'Manage access')
%ul.well-list.content-list %ul.well-list.content-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
...@@ -173,7 +173,7 @@ ...@@ -173,7 +173,7 @@
project members project members
%span.badge= @project.users.size %span.badge= @project.users.size
.pull-right .pull-right
= link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs" = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
%ul.well-list.project_members.content-list %ul.well-list.project_members.content-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
......
...@@ -35,5 +35,5 @@ ...@@ -35,5 +35,5 @@
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs" = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
- else - else
.btn.btn-xs.disabled .btn.btn-xs.disabled
Already Blocked Already blocked
= link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr" = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
- if user.can_be_removed? && can?(current_user, :destroy_user, @user) - if user.can_be_removed? && can?(current_user, :destroy_user, @user)
%li.divider %li.divider
%li %li
= link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" }, = link_to 'Delete user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
class: 'btn btn-remove btn-block', class: 'btn btn-remove btn-block',
method: :delete method: :delete
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
= sort_title_recently_updated = sort_title_recently_updated
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
= sort_title_oldest_updated = sort_title_oldest_updated
= link_to 'New User', new_admin_user_path, class: 'btn btn-new btn-search' = link_to 'New user', new_admin_user_path, class: 'btn btn-new btn-search'
.nav-block .nav-block
%ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs %ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs
......
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
- if current_user.can_create_group? - if current_user.can_create_group?
= link_to new_group_path, class: "btn btn-new" do = link_to new_group_path, class: "btn btn-new" do
New Group New group
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
New Project New project
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.nav-controls .nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
= render 'shared/issues' = render 'shared/issues'
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request"
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests' = render 'shared/merge_requests'
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls .nav-controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New Milestone', include_groups: true = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true
.milestones .milestones
%ul.content-list %ul.content-list
......
...@@ -10,5 +10,5 @@ ...@@ -10,5 +10,5 @@
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create Merge Request Create merge request
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= custom_icon("icon_status_closed") = custom_icon("icon_status_closed")
- else - else
.profile-icon.fork-icon .profile-icon.fork-icon
= custom_icon("code_fork") = custom_icon("icon_code_fork")
.event-title .event-title
%span{ class: event.action_name } %span{ class: event.action_name }
......
.profile-icon .profile-icon
= custom_icon("comment_o") = custom_icon("icon_comment_o")
.event-title .event-title
= event.action_name = event.action_name
......
...@@ -51,4 +51,4 @@ ...@@ -51,4 +51,4 @@
%strong Removed group can not be restored! %strong Removed group can not be restored!
.form-actions .form-actions
= link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" = link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.nav-controls .nav-controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do = link_to new_group_milestone_path(@group), class: "btn btn-new" do
New Milestone New milestone
.row-content-block .row-content-block
Only milestones from Only milestones from
......
...@@ -39,5 +39,5 @@ ...@@ -39,5 +39,5 @@
= render "shared/milestones/form_dates", f: f = render "shared/milestones/form_dates", f: f
.form-actions .form-actions
= f.submit 'Create Milestone', class: "btn-create btn" = f.submit 'Create milestone', class: "btn-create btn"
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel" = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if can? current_user, :admin_group, @group - if can? current_user, :admin_group, @group
.controls .controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
New Project New project
%ul.well-list %ul.well-list
- @projects.each do |project| - @projects.each do |project|
%li %li
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
%tr %tr
%th %th
%th Global Shortcuts %th Global Shortcuts
%tr
%td.shortcut
.key n
%td Main Navigation
%tr %tr
%td.shortcut %td.shortcut
.key s .key s
...@@ -39,24 +43,46 @@ ...@@ -39,24 +43,46 @@
.key .key
%i.fa.fa-arrow-up %i.fa.fa-arrow-up
%td Edit last comment (when focused on an empty textarea) %td Edit last comment (when focused on an empty textarea)
%tbody
%tr %tr
%th %td.shortcut
%th Project Files browsing .key shift t
%td
Go to todos
%tr %tr
%td.shortcut %td.shortcut
.key .key shift a
%i.fa.fa-arrow-up %td
%td Move selection up Go to the activity feed
%tr %tr
%td.shortcut %td.shortcut
.key .key shift p
%i.fa.fa-arrow-down %td
%td Move selection down Go to projects
%tr %tr
%td.shortcut %td.shortcut
.key enter .key shift i
%td Open Selection %td
Go to issues
%tr
%td.shortcut
.key shift m
%td
Go to merge requests
%tr
%td.shortcut
.key shift g
%td
Go to groups
%tr
%td.shortcut
.key shift l
%td
Go to milestones
%tr
%td.shortcut
.key shift s
%td
Go to snippets
%tbody %tbody
%tr %tr
%th %th
...@@ -79,51 +105,8 @@ ...@@ -79,51 +105,8 @@
%td.shortcut %td.shortcut
.key esc .key esc
%td Go back %td Go back
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody.hidden-shortcut.project{ style: 'display:none' }
%tr
%th
%th Global Dashboard
%tr
%td.shortcut
.key g
.key a
%td
Go to the activity feed
%tr
%td.shortcut
.key g
.key p
%td
Go to projects
%tr
%td.shortcut
.key g
.key i
%td
Go to issues
%tr
%td.shortcut
.key g
.key m
%td
Go to merge requests
%tr
%td.shortcut
.key g
.key t
%td
Go to todos
%tbody %tbody
%tr %tr
%th %th
...@@ -155,7 +138,7 @@ ...@@ -155,7 +138,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key b .key j
%td %td
Go to jobs Go to jobs
%tr %tr
...@@ -167,7 +150,7 @@ ...@@ -167,7 +150,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key g .key d
%td %td
Go to repository charts Go to repository charts
%tr %tr
...@@ -179,7 +162,7 @@ ...@@ -179,7 +162,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key l .key b
%td %td
Go to issue boards Go to issue boards
%tr %tr
...@@ -194,6 +177,12 @@ ...@@ -194,6 +177,12 @@
.key s .key s
%td %td
Go to snippets Go to snippets
%tr
%td.shortcut
.key g
.key w
%td
Go to wiki
%tr %tr
%td.shortcut %td.shortcut
.key t .key t
...@@ -202,6 +191,33 @@ ...@@ -202,6 +191,33 @@
%td.shortcut %td.shortcut
.key i .key i
%td New issue %td New issue
%tbody
%tr
%th
%th Project Files browsing
%tr
%td.shortcut
.key
%i.fa.fa-arrow-up
%td Move selection up
%tr
%td.shortcut
.key
%i.fa.fa-arrow-down
%td Move selection down
%tr
%td.shortcut
.key enter
%td Open Selection
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody.hidden-shortcut.network{ style: 'display:none' } %tbody.hidden-shortcut.network{ style: 'display:none' }
......
...@@ -225,7 +225,7 @@ ...@@ -225,7 +225,7 @@
%ul.dropdown-menu %ul.dropdown-menu
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.dropdown.inline.pull-right .dropdown.inline.pull-right
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
Dropdown Dropdown
...@@ -233,7 +233,7 @@ ...@@ -233,7 +233,7 @@
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.example .example
%div %div
.dropdown.inline .dropdown.inline
...@@ -243,7 +243,7 @@ ...@@ -243,7 +243,7 @@
%ul.dropdown-menu.dropdown-menu-selectable %ul.dropdown-menu.dropdown-menu-selectable
%li %li
%a.is-active{ href: "#" } %a.is-active{ href: "#" }
Dropdown Option Dropdown option
.example .example
%div %div
.dropdown.inline .dropdown.inline
...@@ -262,26 +262,26 @@ ...@@ -262,26 +262,26 @@
%ul %ul
%li %li
%a.is-active{ href: "#" } %a.is-active{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li.divider %li.divider
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.dropdown-footer .dropdown-footer
%strong Tip: %strong Tip:
If an author is not a member of this project, you can still filter by his name while using the search field. If an author is not a member of this project, you can still filter by his name while using the search field.
...@@ -301,26 +301,26 @@ ...@@ -301,26 +301,26 @@
%ul %ul
%li %li
%a.is-active{ href: "#" } %a.is-active{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li.divider %li.divider
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.dropdown-footer .dropdown-footer
%strong Tip: %strong Tip:
If an author is not a member of this project, you can still filter by his name while using the search field. If an author is not a member of this project, you can still filter by his name while using the search field.
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
To import a GitHub project, you first need to authorize GitLab to access To import a GitHub project, you first need to authorize GitLab to access
the list of your GitHub repositories: the list of your GitHub repositories:
= link_to 'List Your GitHub Repositories', status_import_github_path, class: 'btn btn-success' = link_to 'List your GitHub repositories', status_import_github_path, class: 'btn btn-success'
%hr %hr
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
= form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do = form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
.form-group .form-group
= text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40 = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40
= submit_tag 'List Your GitHub Repositories', class: 'btn btn-success' = submit_tag 'List your GitHub repositories', class: 'btn btn-success'
- unless github_import_configured? - unless github_import_configured?
%hr %hr
......
...@@ -29,11 +29,11 @@ ...@@ -29,11 +29,11 @@
- if current_user - if current_user
- if session[:impersonator_id] - if session[:impersonator_id]
%li.impersonation %li.impersonation
= link_to admin_impersonation_path, method: :delete, title: "Stop Impersonation", aria: { label: 'Stop Impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw') = icon('user-secret fw')
- if current_user.is_admin? - if current_user.is_admin?
%li %li
= link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw') = icon('wrench fw')
- if current_user.can_create_project? - if current_user.can_create_project?
%li %li
......
%ul %ul
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
P
%span %span
Projects Projects
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
A
%span %span
Activity Activity
- if koding_enabled? - if koding_enabled?
...@@ -13,25 +21,45 @@ ...@@ -13,25 +21,45 @@
%span %span
Koding Koding
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
G
%span %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
L
%span %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
I
%span %span
Issues Issues
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened)) .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
M
%span %span
Merge Requests Merge Requests
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened)) .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
= nav_link(controller: 'dashboard/snippets') do = nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
S
%span %span
Snippets Snippets
%li.divider %li.divider
......
...@@ -49,14 +49,14 @@ ...@@ -49,14 +49,14 @@
%p %p
Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
- if current_user.two_factor_enabled? - if current_user.two_factor_enabled?
= link_to 'Manage Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-info' = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info'
= link_to 'Disable', profile_two_factor_auth_path, = link_to 'Disable', profile_two_factor_auth_path,
method: :delete, method: :delete,
data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
class: 'btn btn-danger' class: 'btn btn-danger'
- else - else
.append-bottom-10 .append-bottom-10
= link_to 'Enable Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-success' = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success'
%hr %hr
- if button_based_providers.any? - if button_based_providers.any?
......
...@@ -33,17 +33,17 @@ ...@@ -33,17 +33,17 @@
%li %li
= @primary = @primary
%span.pull-right %span.pull-right
%span.label.label-success Primary Email %span.label.label-success Primary email
- if @primary === current_user.public_email - if @primary === current_user.public_email
%span.label.label-info Public Email %span.label.label-info Public email
- if @primary === current_user.notification_email - if @primary === current_user.notification_email
%span.label.label-info Notification Email %span.label.label-info Notification email
- @emails.each do |email| - @emails.each do |email|
%li %li
= email.email = email.email
%span.pull-right %span.pull-right
- if email.email === current_user.public_email - if email.email === current_user.public_email
%span.label.label-info Public Email %span.label.label-info Public email
- if email.email === current_user.notification_email - if email.email === current_user.notification_email
%span.label.label-info Notification Email %span.label.label-info Notification email
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10' = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
Your New Personal Access Token Your New Personal Access Token
.form-group .form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block" = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block"
= clipboard_button(clipboard_text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left") = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr %hr
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
= label_tag :pin_code, nil, class: "label-light" = label_tag :pin_code, nil, class: "label-light"
= text_field_tag :pin_code, nil, class: "form-control", required: true = text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default .prepend-top-default
= submit_tag 'Register with Two-Factor App', class: 'btn btn-success' = submit_tag 'Register with two-factor app', class: 'btn btn-success'
%hr %hr
......
.form-actions .form-actions
= button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create' = button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path, = link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message} class: 'btn btn-cancel', data: {confirm: leave_edit_message}
......
= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do = link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do
= icon('search') = icon('search')
%span Find File %span Find file
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
- if @project && event.project != @project - if @project && event.project != @project
%span at %span at
%strong= link_to_project event.project %strong= link_to_project event.project
= clipboard_button(clipboard_text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard') = clipboard_button(text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create Merge Request Create merge request
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
-# only show normal/blame view links for text files -# only show normal/blame view links for text files
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id) - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), = link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm' class: 'btn btn-sm'
- else - else
= link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id), = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.template-type-selector.js-template-type-selector-wrap.hidden .template-type-selector.js-template-type-selector-wrap.hidden
= dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } ) = dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden .license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a License template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) = dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden .gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
.controls.hidden-xs< .controls.hidden-xs<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name) - if merge_project && create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
Merge Request Merge request
- if branch.name != @repository.root_ref - if branch.name != @repository.root_ref
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do = link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint %span CI lint
.content-list.builds-content-list .content-list.builds-content-list
= render "table", builds: @builds, project: @project = render "table", builds: @builds, project: @project
.page-content-header .page-content-header
.header-main-content .header-main-content
%strong Commit #{@commit.short_id} %strong Commit #{@commit.short_id}
= clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") = clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard")
%span.hidden-xs authored %span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)} #{time_ago_with_tooltip(@commit.authored_date)}
%span by %span by
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= icon('comment') = icon('comment')
= @notes_count = @notes_count
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
Browse Files Browse files
.dropdown.inline .dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
%span Options %span Options
......
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
.commit-actions.flex-row.hidden-xs .commit-actions.flex-row.hidden-xs
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
= clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard") = clipboard_button(text: commit.id, title: "Copy commit SHA to clipboard")
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
...@@ -18,16 +18,16 @@ ...@@ -18,16 +18,16 @@
.block-controls.hidden-xs.hidden-sm .block-controls.hidden-xs.hidden-sm
- if @merge_request.present? - if @merge_request.present?
.control .control
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn' = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref) - elsif create_mr_button?(@repository.root_ref, @ref)
.control .control
= link_to "Create Merge Request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' = link_to "Create merge request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control .control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
= search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control .control
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits Feed", class: 'btn' do = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits feed", class: 'btn' do
= icon("rss") = icon("rss")
%div{ id: dom_id(@project) } %div{ id: dom_id(@project) }
......
...@@ -21,6 +21,6 @@ ...@@ -21,6 +21,6 @@
&nbsp; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present? - if @merge_request.present?
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn' = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button? - elsif create_mr_button?
= link_to "Create Merge Request", create_mr_path, class: 'prepend-left-10 btn' = link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn'
...@@ -8,7 +8,4 @@ ...@@ -8,7 +8,4 @@
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, #environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s, "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"css-class" => container_class, "css-class" => container_class } }
"commit-icon-svg" => custom_icon("icon_commit"),
"terminal-icon-svg" => custom_icon("icon_terminal"),
"play-icon-svg" => custom_icon("icon_play") } }
...@@ -22,4 +22,4 @@ ...@@ -22,4 +22,4 @@
%p %p
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork %i.fa.fa-code-fork
Try to Fork again Try to fork again
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.email-modal-input-group.input-group .email-modal-input-group.input-group
= text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#issue_email') = clipboard_button(target: '#issue_email')
%p %p
The subject will be used as the title of the new issue, and the message will be the description. The subject will be used as the title of the new issue, and the message will be the description.
......
...@@ -24,9 +24,9 @@ ...@@ -24,9 +24,9 @@
issue: { assignee_id: issues_finder.assignee.try(:id), issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }), milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new", class: "btn btn-new",
title: "New Issue", title: "New issue",
id: "new_issue_link" do id: "new_issue_link" do
New Issue New issue
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
.issues-holder .issues-holder
......
...@@ -53,5 +53,6 @@ ...@@ -53,5 +53,6 @@
:javascript :javascript
var merge_request = new MergeRequest({ var merge_request = new MergeRequest({
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}" action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
setUrl: false,
}); });
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%p %p
%strong Step 1. %strong Step 1.
Fetch and check out the branch for this merge request Fetch and check out the branch for this merge request
= clipboard_button(clipboard_target: "pre#merge-info-1", title: "Copy commands to clipboard") = clipboard_button(target: "pre#merge-info-1", title: "Copy commands to clipboard")
%pre.dark#merge-info-1 %pre.dark#merge-info-1
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%p %p
%strong Step 3. %strong Step 3.
Merge the branch and fix any conflicts that come up Merge the branch and fix any conflicts that come up
= clipboard_button(clipboard_target: "pre#merge-info-3", title: "Copy commands to clipboard") = clipboard_button(target: "pre#merge-info-3", title: "Copy commands to clipboard")
%pre.dark#merge-info-3 %pre.dark#merge-info-3
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
%p %p
%strong Step 4. %strong Step 4.
Push the result of the merge to GitLab Push the result of the merge to GitLab
= clipboard_button(clipboard_target: "pre#merge-info-4", title: "Copy commands to clipboard") = clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4 %pre.dark#merge-info-4
:preserve :preserve
git push origin #{h @merge_request.target_branch} git push origin #{h @merge_request.target_branch}
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if can_remove_source_branch - if can_remove_source_branch
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default remove_source_branch" do = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default remove_source_branch" do
= icon('trash-o') = icon('trash-o')
Remove Source Branch Remove source branch
- if mr_can_be_reverted - if mr_can_be_reverted
= revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "close") = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "close")
- if mr_can_be_cherry_picked - if mr_can_be_cherry_picked
......
...@@ -10,24 +10,24 @@ ...@@ -10,24 +10,24 @@
- if @pipeline && @pipeline.active? - if @pipeline && @pipeline.active?
%span.btn-group %span.btn-group
= button_tag class: "btn btn-info js-merge-when-pipeline-succeeds-button merge-when-pipeline-succeeds" do = button_tag class: "btn btn-info js-merge-when-pipeline-succeeds-button merge-when-pipeline-succeeds" do
Merge When Pipeline Succeeds Merge when pipeline succeeds
- unless @project.only_allow_merge_if_pipeline_succeeds? - unless @project.only_allow_merge_if_pipeline_succeeds?
= button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do = button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do
= icon('caret-down') = icon('caret-down')
%span.sr-only %span.sr-only
Select Merge Moment Select merge moment
%ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' } %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li %li
= link_to "#", class: "merge_when_pipeline_succeeds" do = link_to "#", class: "merge_when_pipeline_succeeds" do
= icon('check fw') = icon('check fw')
Merge When Pipeline Succeeds Merge when pipeline succeeds
%li %li
= link_to "#", class: "accept-merge-request" do = link_to "#", class: "accept-merge-request" do
= icon('warning fw') = icon('warning fw')
Merge Immediately Merge immediately
- else - else
= f.button class: "btn btn-grouped js-merge-button accept-merge-request" do = f.button class: "btn btn-grouped js-merge-button accept-merge-request" do
Accept Merge Request Accept merge request
- if @merge_request.force_remove_source_branch? - if @merge_request.force_remove_source_branch?
.accept-control .accept-control
The source branch will be removed. The source branch will be removed.
......
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
- if remove_source_branch_button - if remove_source_branch_button
= link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
= icon('times') = icon('times')
Remove Source Branch When Merged Remove source branch when merged
- if user_can_cancel_automatic_merge - if user_can_cancel_automatic_merge
= link_to cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-sm" do = link_to cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-sm" do
Cancel Automatic Merge Cancel automatic merge
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
.nav-controls .nav-controls
= render 'shared/milestones_sort_dropdown' = render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New Milestone' do = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do
New Milestone New milestone
.milestones .milestones
%ul.content-list %ul.content-list
......
...@@ -23,9 +23,9 @@ ...@@ -23,9 +23,9 @@
.milestone-buttons .milestone-buttons
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
- if @milestone.active? - if @milestone.active?
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" = link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
- else - else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" = link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
Edit Edit
......
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
.note-form-actions.clearfix .note-form-actions.clearfix
.settings-message.note-edit-warning.js-edit-warning .settings-message.note-edit-warning.js-edit-warning
Finish editing this message first! Finish editing this message first!
= submit_tag 'Save Comment', class: 'btn btn-nr btn-save js-comment-button' = submit_tag 'Save comment', class: 'btn btn-nr btn-save js-comment-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' } %button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
Cancel Cancel
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} } %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
- if note.system
= icon_for_system_note(note)
- else
%a{ href: user_path(note.author) } %a{ href: user_path(note.author) }
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
.timeline-content .timeline-content
......
...@@ -46,4 +46,4 @@ ...@@ -46,4 +46,4 @@
\... \...
%span.js-details-content.hide %span.js-details-content.hide
= link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace commit-hash-full" = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace commit-hash-full"
= clipboard_button(clipboard_text: @pipeline.sha, title: "Copy commit SHA to clipboard") = clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
%tr.tag %tr.tag
%td %td
= escape_once(tag.name) = escape_once(tag.name)
= clipboard_button(clipboard_text: "docker pull #{tag.path}") = clipboard_button(text: "docker pull #{tag.path}")
%td %td
- if tag.revision - if tag.revision
%span.has-tooltip{ title: "#{tag.revision}" } %span.has-tooltip{ title: "#{tag.revision}" }
......
...@@ -22,14 +22,14 @@ ...@@ -22,14 +22,14 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#display_name') = clipboard_button(target: '#display_name')
.form-group .form-group
= label_tag :description, 'Description', class: 'col-sm-2 col-xs-12 control-label' = label_tag :description, 'Description', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#description') = clipboard_button(target: '#description')
.form-group .form-group
= label_tag nil, 'Command trigger word', class: 'col-sm-2 col-xs-12 control-label' = label_tag nil, 'Command trigger word', class: 'col-sm-2 col-xs-12 control-label'
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :request_url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :request_url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#request_url') = clipboard_button(target: '#request_url')
.form-group .form-group
= label_tag nil, 'Request method', class: 'col-sm-2 col-xs-12 control-label' = label_tag nil, 'Request method', class: 'col-sm-2 col-xs-12 control-label'
...@@ -57,14 +57,14 @@ ...@@ -57,14 +57,14 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :response_username, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :response_username, 'GitLab', class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#response_username') = clipboard_button(target: '#response_username')
.form-group .form-group
= label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-xs-12 control-label' = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#response_icon') = clipboard_button(target: '#response_icon')
.form-group .form-group
= label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label'
...@@ -75,14 +75,14 @@ ...@@ -75,14 +75,14 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :autocomplete_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :autocomplete_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#autocomplete_hint') = clipboard_button(target: '#autocomplete_hint')
.form-group .form-group
= label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label' = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#autocomplete_description') = clipboard_button(target: '#autocomplete_description')
%hr %hr
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#url') = clipboard_button(target: '#url')
.form-group .form-group
= label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label' = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label'
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#customize_name') = clipboard_button(target: '#customize_name')
.form-group .form-group
= label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label'
...@@ -68,21 +68,21 @@ ...@@ -68,21 +68,21 @@
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#autocomplete_description') = clipboard_button(target: '#autocomplete_description')
.form-group .form-group
= label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label' = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#autocomplete_usage_hint') = clipboard_button(target: '#autocomplete_usage_hint')
.form-group .form-group
= label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label' = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#descriptive_label') = clipboard_button(target: '#descriptive_label')
%hr %hr
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%i.fa.fa-angle-right %i.fa.fa-angle-right
%small.light %small.light
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
= clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") = clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard")
= time_ago_with_tooltip(@commit.committed_date) = time_ago_with_tooltip(@commit.committed_date)
\- \-
= @commit.full_title = @commit.full_title
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%td %td
- if can?(current_user, :admin_trigger, trigger) - if can?(current_user, :admin_trigger, trigger)
%span= trigger.token %span= trigger.token
= clipboard_button(clipboard_text: trigger.token, title: "Copy trigger token to clipboard") = clipboard_button(text: trigger.token, title: "Copy trigger token to clipboard")
- else - else
%span= trigger.short_token %span= trigger.short_token
......
- if (@page && @page.persisted?) - if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New Page New page
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page History Page history
- if can?(current_user, :create_wiki, @project) && @page.latest? - if can?(current_user, :create_wiki, @project) && @page.latest?
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
Edit Edit
...@@ -18,4 +18,4 @@ ...@@ -18,4 +18,4 @@
Tip: You can specify the full path for the new file. Tip: You can specify the full path for the new file.
We will automatically create any missing directories. We will automatically create any missing directories.
.form-actions .form-actions
= button_tag 'Create Page', class: 'build-new-wiki btn btn-create' = button_tag 'Create page', class: 'build-new-wiki btn btn-create'
...@@ -22,10 +22,10 @@ ...@@ -22,10 +22,10 @@
.nav-controls .nav-controls
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New Page New page
- if @page.persisted? - if @page.persisted?
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page History Page history
- if can?(current_user, :admin_wiki, @project) - if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
Delete Delete
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#project_clone', title: "Copy URL to clipboard") = clipboard_button(target: '#project_clone', title: "Copy URL to clipboard")
:javascript :javascript
$('ul.clone-options-dropdown a').on('click',function(e){ $('ul.clone-options-dropdown a').on('click',function(e){
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment