summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/dispatcher.js.es65
-rw-r--r--app/assets/javascripts/due_date_select.js107
-rw-r--r--app/assets/javascripts/due_date_select.js.es6161
-rw-r--r--app/assets/javascripts/merge_conflict_data_provider.js.es6347
-rw-r--r--app/assets/javascripts/merge_conflict_resolver.js.es682
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es693
-rw-r--r--app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es612
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es614
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es615
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es630
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6437
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es689
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es612
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es618
-rw-r--r--app/assets/javascripts/pipelines.js.es62
-rw-r--r--app/assets/javascripts/project_find_file.js9
-rw-r--r--app/assets/javascripts/username_validator.js.es62
-rw-r--r--app/assets/stylesheets/framework/typography.scss39
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss47
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss7
-rw-r--r--app/assets/stylesheets/pages/tree.scss4
23 files changed, 974 insertions, 565 deletions
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index f3957ed374b..73691f40c74 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -50,7 +50,7 @@
case 'projects:milestones:new':
case 'projects:milestones:edit':
new ZenMode();
- new DueDateSelect();
+ new gl.DueDateSelectors();
new GLForm($('.milestone-form'));
break;
case 'groups:milestones:new':
@@ -101,9 +101,6 @@
new ZenMode();
new MergedButtons();
break;
- case "projects:merge_requests:conflicts":
- window.mcui = new MergeConflictResolver()
- break;
case 'projects:merge_requests:index':
shortcut_handler = new ShortcutsNavigation();
Issuable.init();
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
deleted file mode 100644
index bf68b7e3a9b..00000000000
--- a/app/assets/javascripts/due_date_select.js
+++ /dev/null
@@ -1,107 +0,0 @@
-(function() {
- this.DueDateSelect = (function() {
- function DueDateSelect() {
- var $datePicker, $dueDate, $loading;
- // Milestone edit/new form
- $datePicker = $('.datepicker');
- if ($datePicker.length) {
- $dueDate = $('#milestone_due_date');
- $datePicker.datepicker({
- dateFormat: 'yy-mm-dd',
- onSelect: function(dateText, inst) {
- return $dueDate.val(dateText);
- }
- }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
- }
- $('.js-clear-due-date').on('click', function(e) {
- e.preventDefault();
- return $.datepicker._clearDate($datePicker);
- });
- // Issuable sidebar
- $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
- $('.js-due-date-select').each(function(i, dropdown) {
- var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
- $dropdown = $(dropdown);
- $dropdownParent = $dropdown.closest('.dropdown');
- $datePicker = $dropdownParent.find('.js-due-date-calendar');
- $block = $dropdown.closest('.block');
- $selectbox = $dropdown.closest('.selectbox');
- $value = $block.find('.value');
- $valueContent = $block.find('.value-content');
- $sidebarValue = $('.js-due-date-sidebar-value', $block);
- fieldName = $dropdown.data('field-name');
- abilityName = $dropdown.data('ability-name');
- issueUpdateURL = $dropdown.data('issue-update');
- $dropdown.glDropdown({
- hidden: function() {
- $selectbox.hide();
- return $value.css('display', '');
- }
- });
- addDueDate = function(isDropdown) {
- var data, date, mediumDate, value;
- // Create the post date
- value = $("input[name='" + fieldName + "']").val();
- if (value !== '') {
- date = new Date(value.replace(new RegExp('-', 'g'), ','));
- mediumDate = $.datepicker.formatDate('M d, yy', date);
- } else {
- mediumDate = 'No due date';
- }
- data = {};
- data[abilityName] = {};
- data[abilityName].due_date = value;
- return $.ajax({
- type: 'PUT',
- url: issueUpdateURL,
- data: data,
- dataType: 'json',
- beforeSend: function() {
- var cssClass;
- $loading.fadeIn();
- if (isDropdown) {
- $dropdown.trigger('loading.gl.dropdown');
- $selectbox.hide();
- }
- $value.css('display', '');
- cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value';
- $valueContent.html("<span class='" + cssClass + "'>" + mediumDate + "</span>");
- $sidebarValue.html(mediumDate);
- if (value !== '') {
- return $('.js-remove-due-date-holder').removeClass('hidden');
- } else {
- return $('.js-remove-due-date-holder').addClass('hidden');
- }
- }
- }).done(function(data) {
- if (isDropdown) {
- $dropdown.trigger('loaded.gl.dropdown');
- $dropdown.dropdown('toggle');
- }
- return $loading.fadeOut();
- });
- };
- $block.on('click', '.js-remove-due-date', function(e) {
- e.preventDefault();
- $("input[name='" + fieldName + "']").val('');
- return addDueDate(false);
- });
- return $datePicker.datepicker({
- dateFormat: 'yy-mm-dd',
- defaultDate: $("input[name='" + fieldName + "']").val(),
- altField: "input[name='" + fieldName + "']",
- onSelect: function() {
- return addDueDate(true);
- }
- });
- });
- $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) {
- return e.stopImmediatePropagation();
- });
- }
-
- return DueDateSelect;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6
new file mode 100644
index 00000000000..41925fcc8e3
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js.es6
@@ -0,0 +1,161 @@
+(function(global) {
+ class DueDateSelect {
+ constructor({ $dropdown, $loading } = {}) {
+ const $dropdownParent = $dropdown.closest('.dropdown');
+ const $block = $dropdown.closest('.block');
+ this.$loading = $loading;
+ this.$dropdown = $dropdown;
+ this.$dropdownParent = $dropdownParent;
+ this.$datePicker = $dropdownParent.find('.js-due-date-calendar');
+ this.$block = $block;
+ this.$selectbox = $dropdown.closest('.selectbox');
+ this.$value = $block.find('.value');
+ this.$valueContent = $block.find('.value-content');
+ this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
+ this.fieldName = $dropdown.data('field-name'),
+ this.abilityName = $dropdown.data('ability-name'),
+ this.issueUpdateURL = $dropdown.data('issue-update')
+
+ this.rawSelectedDate = null;
+ this.displayedDate = null;
+ this.datePayload = null;
+
+ this.initGlDropdown();
+ this.initRemoveDueDate();
+ this.initDatePicker();
+ this.initStopPropagation();
+ }
+
+ initGlDropdown() {
+ this.$dropdown.glDropdown({
+ hidden: () => {
+ this.$selectbox.hide();
+ this.$value.css('display', '');
+ }
+ });
+ }
+
+ initDatePicker() {
+ this.$datePicker.datepicker({
+ dateFormat: 'yy-mm-dd',
+ defaultDate: $("input[name='" + this.fieldName + "']").val(),
+ altField: "input[name='" + this.fieldName + "']",
+ onSelect: () => {
+ return this.saveDueDate(true);
+ }
+ });
+ }
+
+ initRemoveDueDate() {
+ this.$block.on('click', '.js-remove-due-date', (e) => {
+ e.preventDefault();
+ $("input[name='" + this.fieldName + "']").val('');
+ return this.saveDueDate(false);
+ });
+ }
+
+ initStopPropagation() {
+ $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => {
+ return e.stopImmediatePropagation();
+ });
+ }
+
+ saveDueDate(isDropdown) {
+ this.parseSelectedDate();
+ this.prepSelectedDate();
+ this.submitSelectedDate(isDropdown);
+ }
+
+ parseSelectedDate() {
+ this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val();
+ if (this.rawSelectedDate.length) {
+ let dateObj = new Date(this.rawSelectedDate);
+ this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
+ } else {
+ this.displayedDate = 'No due date';
+ }
+ }
+
+ prepSelectedDate() {
+ const datePayload = {};
+ datePayload[this.abilityName] = {};
+ datePayload[this.abilityName].due_date = this.rawSelectedDate;
+ this.datePayload = datePayload;
+ }
+
+ submitSelectedDate(isDropdown) {
+ return $.ajax({
+ type: 'PUT',
+ url: this.issueUpdateURL,
+ data: this.datePayload,
+ dataType: 'json',
+ beforeSend: () => {
+ const selectedDateValue = this.datePayload[this.abilityName].due_date;
+ const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
+
+ this.$loading.fadeIn();
+
+ if (isDropdown) {
+ this.$dropdown.trigger('loading.gl.dropdown');
+ this.$selectbox.hide();
+ }
+
+ this.$value.css('display', '');
+ this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
+ this.$sidebarValue.html(this.displayedDate);
+
+ return selectedDateValue.length ?
+ $('.js-remove-due-date-holder').removeClass('hidden') :
+ $('.js-remove-due-date-holder').addClass('hidden');
+
+ }
+ }).done((data) => {
+ if (isDropdown) {
+ this.$dropdown.trigger('loaded.gl.dropdown');
+ this.$dropdown.dropdown('toggle');
+ }
+ return this.$loading.fadeOut();
+ });
+ }
+ }
+
+ class DueDateSelectors {
+ constructor() {
+ this.initMilestoneDueDate();
+ this.initIssuableSelect();
+ }
+
+ initMilestoneDueDate() {
+ const $datePicker = $('.datepicker');
+
+ if ($datePicker.length) {
+ const $dueDate = $('#milestone_due_date');
+ $datePicker.datepicker({
+ dateFormat: 'yy-mm-dd',
+ onSelect: (dateText, inst) => {
+ $dueDate.val(dateText);
+ }
+ }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
+ }
+ $('.js-clear-due-date').on('click', (e) => {
+ e.preventDefault();
+ $.datepicker._clearDate($datePicker);
+ });
+ }
+
+ initIssuableSelect() {
+ const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
+
+ $('.js-due-date-select').each((i, dropdown) => {
+ const $dropdown = $(dropdown);
+ new DueDateSelect({
+ $dropdown,
+ $loading
+ });
+ });
+ }
+ }
+
+ global.DueDateSelectors = DueDateSelectors;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6
deleted file mode 100644
index 13ee794ba38..00000000000
--- a/app/assets/javascripts/merge_conflict_data_provider.js.es6
+++ /dev/null
@@ -1,347 +0,0 @@
-const HEAD_HEADER_TEXT = 'HEAD//our changes';
-const ORIGIN_HEADER_TEXT = 'origin//their changes';
-const HEAD_BUTTON_TITLE = 'Use ours';
-const ORIGIN_BUTTON_TITLE = 'Use theirs';
-
-
-class MergeConflictDataProvider {
-
- getInitialData() {
- // TODO: remove reliance on jQuery and DOM state introspection
- const diffViewType = $.cookie('diff_view');
- const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited');
-
- return {
- isLoading : true,
- hasError : false,
- isParallel : diffViewType === 'parallel',
- diffViewType : diffViewType,
- fixedLayout : fixedLayout,
- isSubmitting : false,
- conflictsData : {},
- resolutionData : {}
- }
- }
-
-
- decorateData(vueInstance, data) {
- this.vueInstance = vueInstance;
-
- if (data.type === 'error') {
- vueInstance.hasError = true;
- data.errorMessage = data.message;
- }
- else {
- data.shortCommitSha = data.commit_sha.slice(0, 7);
- data.commitMessage = data.commit_message;
-
- this.setParallelLines(data);
- this.setInlineLines(data);
- this.updateResolutionsData(data);
- }
-
- vueInstance.conflictsData = data;
- vueInstance.isSubmitting = false;
-
- const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict';
- vueInstance.conflictsData.conflictsText = conflictsText;
- }
-
-
- updateResolutionsData(data) {
- const vi = this.vueInstance;
-
- data.files.forEach( (file) => {
- file.sections.forEach( (section) => {
- if (section.conflict) {
- vi.$set(`resolutionData['${section.id}']`, false);
- }
- });
- });
- }
-
-
- setParallelLines(data) {
- data.files.forEach( (file) => {
- file.filePath = this.getFilePath(file);
- file.iconClass = `fa-${file.blob_icon}`;
- file.blobPath = file.blob_path;
- file.parallelLines = [];
- const linesObj = { left: [], right: [] };
-
- file.sections.forEach( (section) => {
- const { conflict, lines, id } = section;
-
- if (conflict) {
- linesObj.left.push(this.getOriginHeaderLine(id));
- linesObj.right.push(this.getHeadHeaderLine(id));
- }
-
- lines.forEach( (line) => {
- const { type } = line;
-
- if (conflict) {
- if (type === 'old') {
- linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
- }
- else if (type === 'new') {
- linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
- }
- }
- else {
- const lineType = type || 'context';
-
- linesObj.left.push (this.getLineForParallelView(line, id, lineType));
- linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
- }
- });
-
- this.checkLineLengths(linesObj);
- });
-
- for (let i = 0, len = linesObj.left.length; i < len; i++) {
- file.parallelLines.push([
- linesObj.right[i],
- linesObj.left[i]
- ]);
- }
-
- });
- }
-
-
- checkLineLengths(linesObj) {
- let { left, right } = linesObj;
-
- if (left.length !== right.length) {
- if (left.length > right.length) {
- const diff = left.length - right.length;
- for (let i = 0; i < diff; i++) {
- right.push({ lineType: 'emptyLine', richText: '' });
- }
- }
- else {
- const diff = right.length - left.length;
- for (let i = 0; i < diff; i++) {
- left.push({ lineType: 'emptyLine', richText: '' });
- }
- }
- }
- }
-
-
- setInlineLines(data) {
- data.files.forEach( (file) => {
- file.iconClass = `fa-${file.blob_icon}`;
- file.blobPath = file.blob_path;
- file.filePath = this.getFilePath(file);
- file.inlineLines = []
-
- file.sections.forEach( (section) => {
- let currentLineType = 'new';
- const { conflict, lines, id } = section;
-
- if (conflict) {
- file.inlineLines.push(this.getHeadHeaderLine(id));
- }
-
- lines.forEach( (line) => {
- const { type } = line;
-
- if ((type === 'new' || type === 'old') && currentLineType !== type) {
- currentLineType = type;
- file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
- }
-
- this.decorateLineForInlineView(line, id, conflict);
- file.inlineLines.push(line);
- })
-
- if (conflict) {
- file.inlineLines.push(this.getOriginHeaderLine(id));
- }
- });
- });
- }
-
-
- handleSelected(sectionId, selection) {
- const vi = this.vueInstance;
-
- vi.resolutionData[sectionId] = selection;
- vi.conflictsData.files.forEach( (file) => {
- file.inlineLines.forEach( (line) => {
- if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
- this.markLine(line, selection);
- }
- });
-
- file.parallelLines.forEach( (lines) => {
- const left = lines[0];
- const right = lines[1];
- const hasSameId = right.id === sectionId || left.id === sectionId;
- const isLeftMatch = left.hasConflict || left.isHeader;
- const isRightMatch = right.hasConflict || right.isHeader;
-
- if (hasSameId && (isLeftMatch || isRightMatch)) {
- this.markLine(left, selection);
- this.markLine(right, selection);
- }
- })
- });
- }
-
-
- updateViewType(newType) {
- const vi = this.vueInstance;
-
- if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) {
- return;
- }
-
- vi.diffViewType = newType;
- vi.isParallel = newType === 'parallel';
- $.cookie('diff_view', newType, {
- path: (gon && gon.relative_url_root) || '/'
- });
- $('.content-wrapper .container-fluid')
- .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout);
- }
-
-
- markLine(line, selection) {
- if (selection === 'head' && line.isHead) {
- line.isSelected = true;
- line.isUnselected = false;
- }
- else if (selection === 'origin' && line.isOrigin) {
- line.isSelected = true;
- line.isUnselected = false;
- }
- else {
- line.isSelected = false;
- line.isUnselected = true;
- }
- }
-
-
- getConflictsCount() {
- return Object.keys(this.vueInstance.resolutionData).length;
- }
-
-
- getResolvedCount() {
- let count = 0;
- const data = this.vueInstance.resolutionData;
-
- for (const id in data) {
- const resolution = data[id];
- if (resolution) {
- count++;
- }
- }
-
- return count;
- }
-
-
- isReadyToCommit() {
- const { conflictsData, isSubmitting } = this.vueInstance
- const allResolved = this.getConflictsCount() === this.getResolvedCount();
- const hasCommitMessage = $.trim(conflictsData.commitMessage).length;
-
- return !isSubmitting && hasCommitMessage && allResolved;
- }
-
-
- getCommitButtonText() {
- const initial = 'Commit conflict resolution';
- const inProgress = 'Committing...';
- const vue = this.vueInstance;
-
- return vue ? vue.isSubmitting ? inProgress : initial : initial;
- }
-
-
- decorateLineForInlineView(line, id, conflict) {
- const { type } = line;
- line.id = id;
- line.hasConflict = conflict;
- line.isHead = type === 'new';
- line.isOrigin = type === 'old';
- line.hasMatch = type === 'match';
- line.richText = line.rich_text;
- line.isSelected = false;
- line.isUnselected = false;
- }
-
- getLineForParallelView(line, id, lineType, isHead) {
- const { old_line, new_line, rich_text } = line;
- const hasConflict = lineType === 'conflict';
-
- return {
- id,
- lineType,
- hasConflict,
- isHead : hasConflict && isHead,
- isOrigin : hasConflict && !isHead,
- hasMatch : lineType === 'match',
- lineNumber : isHead ? new_line : old_line,
- section : isHead ? 'head' : 'origin',
- richText : rich_text,
- isSelected : false,
- isUnselected : false
- }
- }
-
-
- getHeadHeaderLine(id) {
- return {
- id : id,
- richText : HEAD_HEADER_TEXT,
- buttonTitle : HEAD_BUTTON_TITLE,
- type : 'new',
- section : 'head',
- isHeader : true,
- isHead : true,
- isSelected : false,
- isUnselected: false
- }
- }
-
-
- getOriginHeaderLine(id) {
- return {
- id : id,
- richText : ORIGIN_HEADER_TEXT,
- buttonTitle : ORIGIN_BUTTON_TITLE,
- type : 'old',
- section : 'origin',
- isHeader : true,
- isOrigin : true,
- isSelected : false,
- isUnselected: false
- }
- }
-
-
- handleFailedRequest(vueInstance, data) {
- vueInstance.hasError = true;
- vueInstance.conflictsData.errorMessage = 'Something went wrong!';
- }
-
-
- getCommitData() {
- return {
- commit_message: this.vueInstance.conflictsData.commitMessage,
- sections: this.vueInstance.resolutionData
- }
- }
-
-
- getFilePath(file) {
- const { old_path, new_path } = file;
- return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
- }
-
-}
diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6
deleted file mode 100644
index 7e756433bf5..00000000000
--- a/app/assets/javascripts/merge_conflict_resolver.js.es6
+++ /dev/null
@@ -1,82 +0,0 @@
-//= require vue
-
-class MergeConflictResolver {
-
- constructor() {
- this.dataProvider = new MergeConflictDataProvider()
- this.initVue()
- }
-
-
- initVue() {
- const that = this;
- this.vue = new Vue({
- el : '#conflicts',
- name : 'MergeConflictResolver',
- data : this.dataProvider.getInitialData(),
- created : this.fetchData(),
- computed : this.setComputedProperties(),
- methods : {
- handleSelected(sectionId, selection) {
- that.dataProvider.handleSelected(sectionId, selection);
- },
- handleViewTypeChange(newType) {
- that.dataProvider.updateViewType(newType);
- },
- commit() {
- that.commit();
- }
- }
- })
- }
-
-
- setComputedProperties() {
- const dp = this.dataProvider;
-
- return {
- conflictsCount() { return dp.getConflictsCount() },
- resolvedCount() { return dp.getResolvedCount() },
- readyToCommit() { return dp.isReadyToCommit() },
- commitButtonText() { return dp.getCommitButtonText() }
- }
- }
-
-
- fetchData() {
- const dp = this.dataProvider;
-
- $.get($('#conflicts').data('conflictsPath'))
- .done((data) => {
- dp.decorateData(this.vue, data);
- })
- .error((data) => {
- dp.handleFailedRequest(this.vue, data);
- })
- .always(() => {
- this.vue.isLoading = false;
-
- this.vue.$nextTick(() => {
- $('#conflicts .js-syntax-highlight').syntaxHighlight();
- });
-
- $('.content-wrapper .container-fluid')
- .toggleClass('container-limited', !this.vue.isParallel && this.vue.fixedLayout);
- })
- }
-
-
- commit() {
- this.vue.isSubmitting = true;
-
- $.post($('#conflicts').data('resolveConflictsPath'), this.dataProvider.getCommitData())
- .done((data) => {
- window.location.href = data.redirect_to;
- })
- .error(() => {
- this.vue.isSubmitting = false;
- new Flash('Something went wrong!');
- });
- }
-
-}
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
new file mode 100644
index 00000000000..5012bdfe997
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
@@ -0,0 +1,93 @@
+((global) => {
+
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ global.mergeConflicts.diffFileEditor = Vue.extend({
+ props: {
+ file: Object,
+ onCancelDiscardConfirmation: Function,
+ onAcceptDiscardConfirmation: Function
+ },
+ data() {
+ return {
+ saved: false,
+ loading: false,
+ fileLoaded: false,
+ originalContent: '',
+ }
+ },
+ computed: {
+ classObject() {
+ return {
+ 'saved': this.saved,
+ 'is-loading': this.loading
+ };
+ }
+ },
+ watch: {
+ ['file.showEditor'](val) {
+ this.resetEditorContent();
+
+ if (!val || this.fileLoaded || this.loading) {
+ return;
+ }
+
+ this.loadEditor();
+ }
+ },
+ ready() {
+ if (this.file.loadEditor) {
+ this.loadEditor();
+ }
+ },
+ methods: {
+ loadEditor() {
+ this.loading = true;
+
+ $.get(this.file.content_path)
+ .done((file) => {
+ let content = this.$el.querySelector('pre');
+ let fileContent = document.createTextNode(file.content);
+
+ content.textContent = fileContent.textContent;
+
+ this.originalContent = file.content;
+ this.fileLoaded = true;
+ this.editor = ace.edit(content);
+ this.editor.$blockScrolling = Infinity; // Turn off annoying warning
+ this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`);
+ this.editor.on('change', () => {
+ this.saveDiffResolution();
+ });
+ this.saveDiffResolution();
+ })
+ .fail(() => {
+ new Flash('Failed to load the file, please try again.');
+ })
+ .always(() => {
+ this.loading = false;
+ });
+ },
+ saveDiffResolution() {
+ this.saved = true;
+
+ // This probably be better placed in the data provider
+ this.file.content = this.editor.getValue();
+ this.file.resolveEditChanged = this.file.content !== this.originalContent;
+ this.file.promptDiscardConfirmation = false;
+ },
+ resetEditorContent() {
+ if (this.fileLoaded) {
+ this.editor.setValue(this.originalContent, -1);
+ }
+ },
+ cancelDiscardConfirmation(file) {
+ this.onCancelDiscardConfirmation(file);
+ },
+ acceptDiscardConfirmation(file) {
+ this.onAcceptDiscardConfirmation(file);
+ }
+ }
+ });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
new file mode 100644
index 00000000000..b4be1c8988d
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
@@ -0,0 +1,12 @@
+((global) => {
+
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ global.mergeConflicts.inlineConflictLines = Vue.extend({
+ props: {
+ file: Object
+ },
+ mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
+ });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6
new file mode 100644
index 00000000000..8b0a8ab2073
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6
@@ -0,0 +1,14 @@
+((global) => {
+
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ global.mergeConflicts.parallelConflictLine = Vue.extend({
+ props: {
+ file: Object,
+ line: Object
+ },
+ mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
+ template: '#parallel-conflict-line'
+ });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
new file mode 100644
index 00000000000..eb4cc6a9dac
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
@@ -0,0 +1,15 @@
+((global) => {
+
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ global.mergeConflicts.parallelConflictLines = Vue.extend({
+ props: {
+ file: Object
+ },
+ mixins: [global.mergeConflicts.utils],
+ components: {
+ 'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine
+ }
+ });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
new file mode 100644
index 00000000000..da2fb8b1323
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
@@ -0,0 +1,30 @@
+((global) => {
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ class mergeConflictsService {
+ constructor(options) {
+ this.conflictsPath = options.conflictsPath;
+ this.resolveConflictsPath = options.resolveConflictsPath;
+ }
+
+ fetchConflictsData() {
+ return $.ajax({
+ dataType: 'json',
+ url: this.conflictsPath
+ });
+ }
+
+ submitResolveConflicts(data) {
+ return $.ajax({
+ url: this.resolveConflictsPath,
+ data: JSON.stringify(data),
+ contentType: 'application/json',
+ dataType: 'json',
+ method: 'POST'
+ });
+ }
+ };
+
+ global.mergeConflicts.mergeConflictsService = mergeConflictsService;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
new file mode 100644
index 00000000000..5c5c65f29d4
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
@@ -0,0 +1,437 @@
+((global) => {
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ const diffViewType = $.cookie('diff_view');
+ const HEAD_HEADER_TEXT = 'HEAD//our changes';
+ const ORIGIN_HEADER_TEXT = 'origin//their changes';
+ const HEAD_BUTTON_TITLE = 'Use ours';
+ const ORIGIN_BUTTON_TITLE = 'Use theirs';
+ const INTERACTIVE_RESOLVE_MODE = 'interactive';
+ const EDIT_RESOLVE_MODE = 'edit';
+ const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
+ const VIEW_TYPES = {
+ INLINE: 'inline',
+ PARALLEL: 'parallel'
+ };
+ const CONFLICT_TYPES = {
+ TEXT: 'text',
+ TEXT_EDITOR: 'text-editor'
+ };
+
+ global.mergeConflicts.mergeConflictsStore = {
+ state: {
+ isLoading: true,
+ hasError: false,
+ isSubmitting: false,
+ isParallel: diffViewType === VIEW_TYPES.PARALLEL,
+ diffViewType: diffViewType,
+ conflictsData: {}
+ },
+
+ setConflictsData(data) {
+ this.decorateFiles(data.files);
+
+ this.state.conflictsData = {
+ files: data.files,
+ commitMessage: data.commit_message,
+ sourceBranch: data.source_branch,
+ targetBranch: data.target_branch,
+ commitMessage: data.commit_message,
+ shortCommitSha: data.commit_sha.slice(0, 7),
+ };
+ },
+
+ decorateFiles(files) {
+ files.forEach((file) => {
+ file.content = '';
+ file.resolutionData = {};
+ file.promptDiscardConfirmation = false;
+ file.resolveMode = DEFAULT_RESOLVE_MODE;
+ file.filePath = this.getFilePath(file);
+ file.iconClass = `fa-${file.blob_icon}`;
+ file.blobPath = file.blob_path;
+
+ if (file.type === CONFLICT_TYPES.TEXT) {
+ file.showEditor = false;
+ file.loadEditor = false;
+
+ this.setInlineLine(file);
+ this.setParallelLine(file);
+ } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
+ file.showEditor = true;
+ file.loadEditor = true;
+ }
+ });
+ },
+
+ setInlineLine(file) {
+ file.inlineLines = [];
+
+ file.sections.forEach((section) => {
+ let currentLineType = 'new';
+ const { conflict, lines, id } = section;
+
+ if (conflict) {
+ file.inlineLines.push(this.getHeadHeaderLine(id));
+ }
+
+ lines.forEach((line) => {
+ const { type } = line;
+
+ if ((type === 'new' || type === 'old') && currentLineType !== type) {
+ currentLineType = type;
+ file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
+ }
+
+ this.decorateLineForInlineView(line, id, conflict);
+ file.inlineLines.push(line);
+ })
+
+ if (conflict) {
+ file.inlineLines.push(this.getOriginHeaderLine(id));
+ }
+ });
+ },
+
+ setParallelLine(file) {
+ file.parallelLines = [];
+ const linesObj = { left: [], right: [] };
+
+ file.sections.forEach((section) => {
+ const { conflict, lines, id } = section;
+
+ if (conflict) {
+ linesObj.left.push(this.getOriginHeaderLine(id));
+ linesObj.right.push(this.getHeadHeaderLine(id));
+ }
+
+ lines.forEach((line) => {
+ const { type } = line;
+
+ if (conflict) {
+ if (type === 'old') {
+ linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
+ } else if (type === 'new') {
+ linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
+ }
+ } else {
+ const lineType = type || 'context';
+
+ linesObj.left.push (this.getLineForParallelView(line, id, lineType));
+ linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
+ }
+ });
+
+ this.checkLineLengths(linesObj);
+ });
+
+ for (let i = 0, len = linesObj.left.length; i < len; i++) {
+ file.parallelLines.push([
+ linesObj.right[i],
+ linesObj.left[i]
+ ]);
+ }
+ },
+
+ setLoadingState(state) {
+ this.state.isLoading = state;
+ },
+
+ setErrorState(state) {
+ this.state.hasError = state;
+ },
+
+ setFailedRequest(message) {
+ this.state.hasError = true;
+ this.state.conflictsData.errorMessage = message;
+ },
+
+ getConflictsCount() {
+ if (!this.state.conflictsData.files.length) {
+ return 0;
+ }
+
+ const files = this.state.conflictsData.files;
+ let count = 0;
+
+ files.forEach((file) => {
+ if (file.type === CONFLICT_TYPES.TEXT) {
+ file.sections.forEach((section) => {
+ if (section.conflict) {
+ count++;
+ }
+ });
+ } else {
+ count++;
+ }
+ });
+
+ return count;
+ },
+
+ getConflictsCountText() {
+ const count = this.getConflictsCount();
+ const text = count ? 'conflicts' : 'conflict';
+
+ return `${count} ${text}`;
+ },
+
+ setViewType(viewType) {
+ this.state.diffView = viewType;
+ this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;
+
+ $.cookie('diff_view', viewType, {
+ path: gon.relative_url_root || '/'
+ });
+ },
+
+ getHeadHeaderLine(id) {
+ return {
+ id: id,
+ richText: HEAD_HEADER_TEXT,
+ buttonTitle: HEAD_BUTTON_TITLE,
+ type: 'new',
+ section: 'head',
+ isHeader: true,
+ isHead: true,
+ isSelected: false,
+ isUnselected: false
+ };
+ },
+
+ decorateLineForInlineView(line, id, conflict) {
+ const { type } = line;
+ line.id = id;
+ line.hasConflict = conflict;
+ line.isHead = type === 'new';
+ line.isOrigin = type === 'old';
+ line.hasMatch = type === 'match';
+ line.richText = line.rich_text;
+ line.isSelected = false;
+ line.isUnselected = false;
+ },
+
+ getLineForParallelView(line, id, lineType, isHead) {
+ const { old_line, new_line, rich_text } = line;
+ const hasConflict = lineType === 'conflict';
+
+ return {
+ id,
+ lineType,
+ hasConflict,
+ isHead: hasConflict && isHead,
+ isOrigin: hasConflict && !isHead,
+ hasMatch: lineType === 'match',
+ lineNumber: isHead ? new_line : old_line,
+ section: isHead ? 'head' : 'origin',
+ richText: rich_text,
+ isSelected: false,
+ isUnselected: false
+ };
+ },
+
+ getOriginHeaderLine(id) {
+ return {
+ id: id,
+ richText: ORIGIN_HEADER_TEXT,
+ buttonTitle: ORIGIN_BUTTON_TITLE,
+ type: 'old',
+ section: 'origin',
+ isHeader: true,
+ isOrigin: true,
+ isSelected: false,
+ isUnselected: false
+ };
+ },
+
+ getFilePath(file) {
+ const { old_path, new_path } = file;
+ return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
+ },
+
+ checkLineLengths(linesObj) {
+ let { left, right } = linesObj;
+
+ if (left.length !== right.length) {
+ if (left.length > right.length) {
+ const diff = left.length - right.length;
+ for (let i = 0; i < diff; i++) {
+ right.push({ lineType: 'emptyLine', richText: '' });
+ }
+ } else {
+ const diff = right.length - left.length;
+ for (let i = 0; i < diff; i++) {
+ left.push({ lineType: 'emptyLine', richText: '' });
+ }
+ }
+ }
+ },
+
+ setPromptConfirmationState(file, state) {
+ file.promptDiscardConfirmation = state;
+ },
+
+ setFileResolveMode(file, mode) {
+ if (mode === INTERACTIVE_RESOLVE_MODE) {
+ file.showEditor = false;
+ } else if (mode === EDIT_RESOLVE_MODE) {
+ // Restore Interactive mode when switching to Edit mode
+ file.showEditor = true;
+ file.loadEditor = true;
+ file.resolutionData = {};
+
+ this.restoreFileLinesState(file);
+ }
+
+ file.resolveMode = mode;
+ },
+
+ restoreFileLinesState(file) {
+ file.inlineLines.forEach((line) => {
+ if (line.hasConflict || line.isHeader) {
+ line.isSelected = false;
+ line.isUnselected = false;
+ }
+ });
+
+ file.parallelLines.forEach((lines) => {
+ const left = lines[0];
+ const right = lines[1];
+ const isLeftMatch = left.hasConflict || left.isHeader;
+ const isRightMatch = right.hasConflict || right.isHeader;
+
+ if (isLeftMatch || isRightMatch) {
+ left.isSelected = false;
+ left.isUnselected = false;
+ right.isSelected = false;
+ right.isUnselected = false;
+ }
+ });
+ },
+
+ isReadyToCommit() {
+ const files = this.state.conflictsData.files;
+ const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
+ let unresolved = 0;
+
+ for (let i = 0, l = files.length; i < l; i++) {
+ let file = files[i];
+
+ if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
+ let numberConflicts = 0;
+ let resolvedConflicts = Object.keys(file.resolutionData).length
+
+ // We only check for conflicts type 'text'
+ // since conflicts `text_editor` can´t be resolved in interactive mode
+ if (file.type === CONFLICT_TYPES.TEXT) {
+ for (let j = 0, k = file.sections.length; j < k; j++) {
+ if (file.sections[j].conflict) {
+ numberConflicts++;
+ }
+ }
+
+ if (resolvedConflicts !== numberConflicts) {
+ unresolved++;
+ }
+ }
+ } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
+
+ // Unlikely to happen since switching to Edit mode saves content automatically.
+ // Checking anyway in case the save strategy changes in the future
+ if (!file.content) {
+ unresolved++;
+ continue;
+ }
+ }
+ }
+
+ return !this.state.isSubmitting && hasCommitMessage && !unresolved;
+ },
+
+ getCommitButtonText() {
+ const initial = 'Commit conflict resolution';
+ const inProgress = 'Committing...';
+
+ return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
+ },
+
+ getCommitData() {
+ let commitData = {};
+
+ commitData = {
+ commit_message: this.state.conflictsData.commitMessage,
+ files: []
+ };
+
+ this.state.conflictsData.files.forEach((file) => {
+ let addFile;
+
+ addFile = {
+ old_path: file.old_path,
+ new_path: file.new_path
+ };
+
+ if (file.type === CONFLICT_TYPES.TEXT) {
+
+ // Submit only one data for type of editing
+ if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
+ addFile.sections = file.resolutionData;
+ } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
+ addFile.content = file.content;
+ }
+ } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
+ addFile.content = file.content;
+ }
+
+ commitData.files.push(addFile);
+ });
+
+ return commitData;
+ },
+
+ handleSelected(file, sectionId, selection) {
+ Vue.set(file.resolutionData, sectionId, selection);
+
+ file.inlineLines.forEach((line) => {
+ if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
+ this.markLine(line, selection);
+ }
+ });
+
+ file.parallelLines.forEach((lines) => {
+ const left = lines[0];
+ const right = lines[1];
+ const hasSameId = right.id === sectionId || left.id === sectionId;
+ const isLeftMatch = left.hasConflict || left.isHeader;
+ const isRightMatch = right.hasConflict || right.isHeader;
+
+ if (hasSameId && (isLeftMatch || isRightMatch)) {
+ this.markLine(left, selection);
+ this.markLine(right, selection);
+ }
+ });
+ },
+
+ markLine(line, selection) {
+ if (selection === 'head' && line.isHead) {
+ line.isSelected = true;
+ line.isUnselected = false;
+ } else if (selection === 'origin' && line.isOrigin) {
+ line.isSelected = true;
+ line.isUnselected = false;
+ } else {
+ line.isSelected = false;
+ line.isUnselected = true;
+ }
+ },
+
+ setSubmitState(state) {
+ this.state.isSubmitting = state;
+ },
+
+ fileTextTypePresent() {
+ return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
+ }
+ };
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
new file mode 100644
index 00000000000..7fd3749b3e2
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
@@ -0,0 +1,89 @@
+//= require vue
+//= require ./merge_conflict_store
+//= require ./merge_conflict_service
+//= require ./mixins/line_conflict_utils
+//= require ./mixins/line_conflict_actions
+//= require ./components/diff_file_editor
+//= require ./components/inline_conflict_lines
+//= require ./components/parallel_conflict_line
+//= require ./components/parallel_conflict_lines
+
+$(() => {
+ const INTERACTIVE_RESOLVE_MODE = 'interactive';
+ const conflictsEl = document.querySelector('#conflicts');
+ const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore;
+ const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({
+ conflictsPath: conflictsEl.dataset.conflictsPath,
+ resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath
+ });
+
+ gl.MergeConflictsResolverApp = new Vue({
+ el: '#conflicts',
+ data: mergeConflictsStore.state,
+ components: {
+ 'diff-file-editor': gl.mergeConflicts.diffFileEditor,
+ 'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines,
+ 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines
+ },
+ computed: {
+ conflictsCountText() { return mergeConflictsStore.getConflictsCountText() },
+ readyToCommit() { return mergeConflictsStore.isReadyToCommit() },
+ commitButtonText() { return mergeConflictsStore.getCommitButtonText() },
+ showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() }
+ },
+ created() {
+ mergeConflictsService
+ .fetchConflictsData()
+ .done((data) => {
+ if (data.type === 'error') {
+ mergeConflictsStore.setFailedRequest(data.message);
+ } else {
+ mergeConflictsStore.setConflictsData(data);
+ }
+ })
+ .error(() => {
+ mergeConflictsStore.setFailedRequest();
+ })
+ .always(() => {
+ mergeConflictsStore.setLoadingState(false);
+
+ this.$nextTick(() => {
+ $(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight();
+ });
+ });
+ },
+ methods: {
+ handleViewTypeChange(viewType) {
+ mergeConflictsStore.setViewType(viewType);
+ },
+ onClickResolveModeButton(file, mode) {
+ if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) {
+ mergeConflictsStore.setPromptConfirmationState(file, true);
+ return;
+ }
+
+ mergeConflictsStore.setFileResolveMode(file, mode);
+ },
+ acceptDiscardConfirmation(file) {
+ mergeConflictsStore.setPromptConfirmationState(file, false);
+ mergeConflictsStore.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE);
+ },
+ cancelDiscardConfirmation(file) {
+ mergeConflictsStore.setPromptConfirmationState(file, false);
+ },
+ commit() {
+ mergeConflictsStore.setSubmitState(true);
+
+ mergeConflictsService
+ .submitResolveConflicts(mergeConflictsStore.getCommitData())
+ .done((data) => {
+ window.location.href = data.redirect_to;
+ })
+ .error(() => {
+ mergeConflictsStore.setSubmitState(false);
+ new Flash('Failed to save merge conflicts resolutions. Please try again!');
+ });
+ }
+ }
+ })
+});
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
new file mode 100644
index 00000000000..114a2c5b305
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
@@ -0,0 +1,12 @@
+((global) => {
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ global.mergeConflicts.actions = {
+ methods: {
+ handleSelected(file, sectionId, selection) {
+ gl.mergeConflicts.mergeConflictsStore.handleSelected(file, sectionId, selection);
+ }
+ }
+ };
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
new file mode 100644
index 00000000000..b846a90ab2a
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
@@ -0,0 +1,18 @@
+((global) => {
+ global.mergeConflicts = global.mergeConflicts || {};
+
+ global.mergeConflicts.utils = {
+ methods: {
+ lineCssClass(line) {
+ return {
+ 'head': line.isHead,
+ 'origin': line.isOrigin,
+ 'match': line.hasMatch,
+ 'selected': line.isSelected,
+ 'unselected': line.isUnselected
+ };
+ }
+ }
+ };
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index 6bf63ee6979..a7624de6089 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -15,7 +15,7 @@
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
- graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
+ graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand')
}
addMarginToBuildColumns() {
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 8e38ccf7e44..b8347367717 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -7,6 +7,7 @@
function ProjectFindFile(element1, options) {
this.element = element1;
this.options = options;
+ this.goToBlob = bind(this.goToBlob, this);
this.goToTree = bind(this.goToTree, this);
this.selectRowDown = bind(this.selectRowDown, this);
this.selectRowUp = bind(this.selectRowUp, this);
@@ -154,6 +155,14 @@
return location.href = this.options.treeUrl;
};
+ ProjectFindFile.prototype.goToBlob = function() {
+ var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
+
+ if ($link.length) {
+ $link.get(0).click();
+ }
+ };
+
return ProjectFindFile;
})();
diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6
index 2517f778365..bf4b2e320cd 100644
--- a/app/assets/javascripts/username_validator.js.es6
+++ b/app/assets/javascripts/username_validator.js.es6
@@ -76,7 +76,7 @@
this.renderState();
return $.ajax({
type: 'GET',
- url: `/u/${username}/exists`,
+ url: `/users/${username}/exists`,
dataType: 'json',
success: (res) => this.setAvailabilityState(res.exists)
});
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 8df0067fac1..55de9053be5 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -45,40 +45,38 @@
}
h1 {
- font-size: 2em;
+ font-size: 1.75em;
font-weight: 600;
- margin: 1em 0 10px;
+ margin: 16px 0 10px;
padding: 0 0 0.3em;
- border-bottom: 1px solid $btn-default-border;
+ border-bottom: 1px solid $white-dark;
color: $gl-gray-dark;
}
h2 {
- font-size: 1.6em;
+ font-size: 1.5em;
font-weight: 600;
- margin: 1em 0 10px;
- padding-bottom: 0.3em;
- border-bottom: 1px solid $btn-default-border;
+ margin: 16px 0 10px;
color: $gl-gray-dark;
}
h3 {
- margin: 1em 0 10px;
- font-size: 1.4em;
+ margin: 16px 0 10px;
+ font-size: 1.3em;
}
h4 {
- margin: 1em 0 10px;
- font-size: 1.25em;
+ margin: 16px 0 10px;
+ font-size: 1.2em;
}
h5 {
- margin: 1em 0 10px;
+ margin: 16px 0 10px;
font-size: 1em;
}
h6 {
- margin: 1em 0 10px;
+ margin: 16px 0 10px;
font-size: 0.95em;
}
@@ -87,7 +85,12 @@
font-size: inherit;
padding: 8px 21px;
margin: 12px 0;
- border-left: 3px solid #e7e9ed;
+ border-left: 3px solid $white-dark;
+ }
+
+ blockquote:dir(rtl) {
+ border-left: 0;
+ border-right: 3px solid $white-dark;
}
blockquote p {
@@ -112,6 +115,10 @@
}
}
+ table:dir(rtl) th {
+ text-align: right;
+ }
+
pre {
margin: 12px 0;
font-size: 13px;
@@ -129,6 +136,10 @@
margin: 3px 0 3px 28px !important;
}
+ ul:dir(rtl), ol:dir(rtl) {
+ margin: 3px 28px 3px 0 !important;
+ }
+
li {
line-height: 1.6em;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4c34ed3ebf7..7690d65de8e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -56,6 +56,7 @@ $border-gray-light: #dcdcdc;
$border-gray-normal: #d7d7d7;
$border-gray-dark: #c6cacf;
+$border-green-extra-light: #9adb84;
$border-green-light: #2faa60;
$border-green-normal: #2ca05b;
$border-green-dark: #279654;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 4d9c73c6840..2357671c2ae 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -20,9 +20,11 @@
.detail-page-description {
.title {
- margin: 0;
- font-size: 23px;
+ margin: 0 0 16px;
+ font-size: 2em;
color: $gl-gray-dark;
+ padding: 0 0 0.3em;
+ border-bottom: 1px solid $white-dark;
}
.description {
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 49013d7cac9..eed2b0ab7cc 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -237,4 +237,51 @@ $colors: (
.btn-success .fa-spinner {
color: #fff;
}
+
+ .editor-wrap {
+ &.is-loading {
+ .editor {
+ display: none;
+ }
+
+ .loading {
+ display: block;
+ }
+ }
+
+ &.saved {
+ .editor {
+ border-top: solid 2px $border-green-extra-light;
+ }
+ }
+
+ .editor {
+ pre {
+ height: 350px;
+ border: none;
+ border-radius: 0;
+ margin-bottom: 0;
+ }
+ }
+
+ .loading {
+ display: none;
+ }
+ }
+
+ .discard-changes-alert {
+ background-color: $background-color;
+ text-align: right;
+ padding: $gl-padding-top $gl-padding;
+ color: $gl-text-color;
+
+ .discard-actions {
+ display: inline-block;
+ margin-left: 10px;
+ }
+ }
+
+ .resolve-conflicts-form {
+ padding-top: $gl-padding;
+ }
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index afc4e517fde..101472278e2 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -429,13 +429,6 @@
}
}
-.merge-request-details {
-
- .title {
- margin-bottom: 20px;
- }
-}
-
.merge-request-tabs {
background-color: #fff;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 99c0f6362d0..6ea7a2b5498 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -169,4 +169,8 @@
margin-top: 11px;
position: relative;
z-index: 2;
+
+ .download-button {
+ margin-left: $btn-side-margin;
+ }
}