From 3f71c43e88c56bb5310c8814cd9f95cafb4f53ef Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 13:59:10 +0100 Subject: Allow setting content for resolutions When reading conflicts: 1. Add a `type` field. `text` works as before, and has `sections`; `text-editor` is a file with ambiguous conflict markers that can only be resolved in an editor. 2. Add a `content_path` field pointing to a JSON representation of the file's content for a single file. 3. Hitting `content_path` returns a similar datastructure to the `file`, but without the `content_path` and `sections` fields, and with a `content` field containing the full contents of the file (with conflict markers). When writing conflicts: 1. Instead of `sections` being at the top level, they are now in a `files` array. This matches the read format better. 2. The `files` array contains file hashes, each of which must contain: a. `new_path` b. `old_path` c. EITHER `sections` (which works as before) or `content` (with the full content of the resolved file). --- app/controllers/application_controller.rb | 7 ++++++- .../projects/merge_requests_controller.rb | 20 +++++++++++++----- app/models/merge_request.rb | 2 +- app/services/merge_requests/resolve_service.rb | 24 ++++++++++++++++++---- 4 files changed, 42 insertions(+), 11 deletions(-) (limited to 'app') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b3455e04c29..bf37421771f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -114,7 +114,12 @@ class ApplicationController < ActionController::Base end def render_404 - render file: Rails.root.join("public", "404"), layout: false, status: "404" + respond_to do |format| + format.json { head :not_found } + format.any do + render file: Rails.root.join("public", "404"), layout: false, status: "404" + end + end end def no_cache_headers diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 869d96b86f4..2c7a0062f2b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -9,15 +9,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ - :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, + :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines, :merge, :merge_check, :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] - before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] + before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] - before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] + before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :apply_diff_view_cookie!, only: [:new_diffs] before_action :build_merge_request, only: [:new, :new_diffs] @@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :authenticate_user!, only: [:assign_related_issues] - before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts] def index @merge_requests = merge_requests_collection @@ -170,6 +170,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def conflict_for_path + return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? + + file = @merge_request.conflicts.file_for_path(params[:old_path], params[:new_path]) + + return render_404 unless file + + render json: file, full_content: true + end + def resolve_conflicts return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? @@ -184,7 +194,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } - rescue Gitlab::Conflict::File::MissingResolution => e + rescue Gitlab::Conflict::ResolutionError => e render status: :bad_request, json: { message: e.message } end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..1fb0371fe06 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -868,7 +868,7 @@ class MergeRequest < ActiveRecord::Base # files. conflicts.files.each(&:lines) @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 - rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing + rescue Rugged::OdbError, Gitlab::Conflict::Parser::UnresolvableError, Gitlab::Conflict::FileCollection::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end end diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index 19caa038c44..d22a1d3e0ad 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -1,5 +1,8 @@ module MergeRequests class ResolveService < MergeRequests::BaseService + class MissingFiles < Gitlab::Conflict::ResolutionError + end + attr_accessor :conflicts, :rugged, :merge_index, :merge_request def execute(merge_request) @@ -10,8 +13,16 @@ module MergeRequests fetch_their_commit! - conflicts.files.each do |file| - write_resolved_file_to_index(file, params[:sections]) + params[:files].each do |file_params| + conflict_file = merge_request.conflicts.file_for_path(file_params[:old_path], file_params[:new_path]) + + write_resolved_file_to_index(conflict_file, file_params) + end + + unless merge_index.conflicts.empty? + missing_files = merge_index.conflicts.map { |file| file[:ours][:path] } + + raise MissingFiles, "Missing resolutions for the following files: #{missing_files.join(', ')}" end commit_params = { @@ -23,8 +34,13 @@ module MergeRequests project.repository.resolve_conflicts(current_user, merge_request.source_branch, commit_params) end - def write_resolved_file_to_index(file, resolutions) - new_file = file.resolve_lines(resolutions).map(&:text).join("\n") + def write_resolved_file_to_index(file, params) + new_file = if params[:sections] + file.resolve_lines(params[:sections]).map(&:text).join("\n") + elsif params[:content] + file.resolve_content(params[:content]) + end + our_path = file.our_path merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) -- cgit v1.2.1 From 241cca011f9a35c03e8f5fae1381a4af8a8f26bb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:20:29 +0100 Subject: Fix specs --- app/controllers/application_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf37421771f..81c0fa26d18 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -115,10 +115,10 @@ class ApplicationController < ActionController::Base def render_404 respond_to do |format| - format.json { head :not_found } - format.any do + format.html do render file: Rails.root.join("public", "404"), layout: false, status: "404" end + format.any { head :not_found } end end -- cgit v1.2.1 From 26f658decd3943bbc66c567ea91e7b859b32e0e6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 8 Sep 2016 11:44:04 -0500 Subject: Implement editor to manually resolve merge conflicts --- .../merge_conflict_data_provider.js.es6 | 165 ++++++++++++++++----- .../javascripts/merge_conflict_resolver.js.es6 | 60 ++++++-- .../components/diff_file_editor.js.es6 | 63 ++++++++ app/assets/stylesheets/pages/merge_conflicts.scss | 26 ++++ .../projects/merge_requests/conflicts.html.haml | 3 + .../conflicts/_diff_file_editor.html.haml | 9 ++ .../conflicts/_file_actions.html.haml | 12 ++ .../conflicts/_inline_view.html.haml | 10 +- .../conflicts/_parallel_view.html.haml | 10 +- .../conflicts/_submit_form.html.haml | 9 +- .../components/_diff_file_editor.html.haml | 6 + 11 files changed, 300 insertions(+), 73 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 create mode 100644 app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_file_actions.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml (limited to 'app') diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index 13ee794ba38..bfed78ff99c 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -2,7 +2,9 @@ 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; class MergeConflictDataProvider { @@ -18,8 +20,7 @@ class MergeConflictDataProvider { diffViewType : diffViewType, fixedLayout : fixedLayout, isSubmitting : false, - conflictsData : {}, - resolutionData : {} + conflictsData : {} } } @@ -35,9 +36,9 @@ class MergeConflictDataProvider { data.shortCommitSha = data.commit_sha.slice(0, 7); data.commitMessage = data.commit_message; + this.decorateFiles(data); this.setParallelLines(data); this.setInlineLines(data); - this.updateResolutionsData(data); } vueInstance.conflictsData = data; @@ -47,16 +48,12 @@ class MergeConflictDataProvider { 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); - } - }); + decorateFiles(data) { + data.files.forEach((file) => { + file.content = ''; + file.resolutionData = {}; + file.promptDiscardConfirmation = false; + file.resolveMode = DEFAULT_RESOLVE_MODE; }); } @@ -165,11 +162,14 @@ class MergeConflictDataProvider { } - handleSelected(sectionId, selection) { + handleSelected(file, sectionId, selection) { const vi = this.vueInstance; + let files = vi.conflictsData.files; + + vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection); + - vi.resolutionData[sectionId] = selection; - vi.conflictsData.files.forEach( (file) => { + files.forEach( (file) => { file.inlineLines.forEach( (line) => { if (line.id === sectionId && (line.hasConflict || line.isHeader)) { this.markLine(line, selection); @@ -208,6 +208,48 @@ class MergeConflictDataProvider { .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); } + setFileResolveMode(file, mode) { + const vi = this.vueInstance; + + // Restore Interactive mode when switching to Edit mode + if (mode === EDIT_RESOLVE_MODE) { + 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; + } + }); + } + + + setPromptConfirmationState(file, state) { + file.promptDiscardConfirmation = state; + } + markLine(line, selection) { if (selection === 'head' && line.isHead) { @@ -226,31 +268,54 @@ class MergeConflictDataProvider { getConflictsCount() { - return Object.keys(this.vueInstance.resolutionData).length; - } - - - getResolvedCount() { - let count = 0; - const data = this.vueInstance.resolutionData; + const files = this.vueInstance.conflictsData.files; + let count = 0; - for (const id in data) { - const resolution = data[id]; - if (resolution) { - count++; - } - } + files.forEach((file) => { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + }); return count; } isReadyToCommit() { - const { conflictsData, isSubmitting } = this.vueInstance - const allResolved = this.getConflictsCount() === this.getResolvedCount(); - const hasCommitMessage = $.trim(conflictsData.commitMessage).length; + const vi = this.vueInstance; + const files = this.vueInstance.conflictsData.files; + const hasCommitMessage = $.trim(this.vueInstance.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 + + 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 !isSubmitting && hasCommitMessage && allResolved; + return !vi.isSubmitting && hasCommitMessage && !unresolved; } @@ -332,10 +397,33 @@ class MergeConflictDataProvider { getCommitData() { - return { - commit_message: this.vueInstance.conflictsData.commitMessage, - sections: this.vueInstance.resolutionData - } + let conflictsData = this.vueInstance.conflictsData; + let commitData = {}; + + commitData = { + commitMessage: conflictsData.commitMessage, + files: [] + }; + + conflictsData.files.forEach((file) => { + let addFile; + + addFile = { + old_path: file.old_path, + new_path: file.new_path + }; + + // 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; + } + + commitData.files.push(addFile); + }); + + return commitData; } @@ -343,5 +431,4 @@ class MergeConflictDataProvider { 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 index 7e756433bf5..c317a6521b5 100644 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ b/app/assets/javascripts/merge_conflict_resolver.js.es6 @@ -1,13 +1,16 @@ //= require vue +//= require ./merge_conflicts/components/diff_file_editor + +const INTERACTIVE_RESOLVE_MODE = 'interactive'; +const EDIT_RESOLVE_MODE = 'edit'; class MergeConflictResolver { constructor() { - this.dataProvider = new MergeConflictDataProvider() - this.initVue() + this.dataProvider = new MergeConflictDataProvider(); + this.initVue(); } - initVue() { const that = this; this.vue = new Vue({ @@ -17,15 +20,28 @@ class MergeConflictResolver { created : this.fetchData(), computed : this.setComputedProperties(), methods : { - handleSelected(sectionId, selection) { - that.dataProvider.handleSelected(sectionId, selection); + handleSelected(file, sectionId, selection) { + that.dataProvider.handleSelected(file, sectionId, selection); }, handleViewTypeChange(newType) { that.dataProvider.updateViewType(newType); }, commit() { that.commit(); - } + }, + onClickResolveModeButton(file, mode) { + that.toggleResolveMode(file, mode); + }, + acceptDiscardConfirmation(file) { + that.dataProvider.setPromptConfirmationState(file, false); + that.dataProvider.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); + }, + cancelDiscardConfirmation(file) { + that.dataProvider.setPromptConfirmationState(file, false); + }, + }, + components: { + 'diff-file-editor': window.gl.diffFileEditor } }) } @@ -36,7 +52,6 @@ class MergeConflictResolver { return { conflictsCount() { return dp.getConflictsCount() }, - resolvedCount() { return dp.getResolvedCount() }, readyToCommit() { return dp.isReadyToCommit() }, commitButtonText() { return dp.getCommitButtonText() } } @@ -69,14 +84,29 @@ class MergeConflictResolver { 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!'); - }); + $.ajax({ + url: $('#conflicts').data('resolveConflictsPath'), + data: JSON.stringify(this.dataProvider.getCommitData()), + contentType: "application/json", + dataType: 'json', + method: 'POST' + }) + .done((data) => { + window.location.href = data.redirect_to; + }) + .error(() => { + this.vue.isSubmitting = false; + new Flash('Something went wrong!'); + }); } + + toggleResolveMode(file, mode) { + if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { + this.dataProvider.setPromptConfirmationState(file, true); + return; + } + + this.dataProvider.setFileResolveMode(file, mode); + } } 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..570d9ff877c --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -0,0 +1,63 @@ +((global) => { + global.diffFileEditor = Vue.extend({ + props: ['file', 'loadFile'], + template: '#diff-file-editor', + data() { + return { + originalState: '', + saved: false, + loading: false, + fileLoaded: false + } + }, + computed: { + classObject() { + return { + 'load-file': this.loadFile, + 'saved': this.saved, + 'is-loading': this.loading + }; + } + }, + watch: { + loadFile(val) { + const self = this; + + if (!val || this.fileLoaded || this.loading) { + return + } + + this.loading = true; + + $.get(this.file.content_path) + .done((file) => { + $(self.$el).find('textarea').val(file.content); + + self.originalState = file.content; + self.fileLoaded = true; + self.saveDiffResolution(); + }) + .fail(() => { + console.log('error'); + }) + .always(() => { + self.loading = false; + }); + } + }, + methods: { + saveDiffResolution() { + this.saved = true; + + // This probably be better placed in the data provider + this.file.content = this.$el.querySelector('textarea').value; + this.file.resolveEditChanged = this.file.content !== this.originalState; + this.file.promptDiscardConfirmation = false; + }, + onInput() { + this.saveDiffResolution(); + } + } + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5ec660799e3..577a97e8c0e 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -235,4 +235,30 @@ $colors: ( .btn-success .fa-spinner { color: #fff; } + + .editor-wrap { + &.is-loading { + .editor { + display: none; + } + + .loading-text { + display: block; + } + } + + &.saved { + .editor { + border-top: solid 1px green; + } + } + + .editor { + border-top: solid 1px yellow; + } + + .loading-text { + display: none; + } + } } diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index a524936f73c..e3add1f799f 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -27,3 +27,6 @@ = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/submit_form" + +-# Components += render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml new file mode 100644 index 00000000000..d0c518e5249 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -0,0 +1,9 @@ +- if_condition = local_assigns.fetch(:if_condition, '') + +.diff-editor-wrap{ "v-show" => if_condition } + .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } + %p + Are you sure to discard your changes? + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel + %diff-file-editor{":file" => "file", ":load-file" => if_condition } diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml new file mode 100644 index 00000000000..124dfde7b22 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -0,0 +1,12 @@ +.file-actions + .btn-group + %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", + '@click' => "onClickResolveModeButton(file, 'interactive')", + type: 'button' } + Interactive mode + %button.btn{ ':class' => "{ 'active': file.resolveMode == 'edit' }", + '@click' => "onClickResolveModeButton(file, 'edit')", + type: 'button' } + Edit inline + %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} + View file @{{conflictsData.shortCommitSha}} diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml index 19c7da4b5e3..7120b6ff48d 100644 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml @@ -3,12 +3,9 @@ .file-title %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} - .file-actions - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} - View file @{{conflictsData.shortCommitSha}} - + = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } %table %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} %template{"v-if" => "!line.isHeader"} @@ -24,5 +21,6 @@ %td.diff-line-num.header{":class" => class_bindings} %td.line_content.header{":class" => class_bindings} %strong {{{line.richText}}} - %button.btn{"@click" => "handleSelected(line.id, line.section)"} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } {{line.buttonTitle}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && !isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml index 2e6f67c2eaf..18c830ddb17 100644 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml @@ -3,12 +3,9 @@ .file-title %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} - .file-actions - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} - View file @{{conflictsData.shortCommitSha}} - + = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } %table %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} %template{"v-for" => "line in section"} @@ -17,7 +14,7 @@ %td.diff-line-num.header{":class" => class_bindings} %td.line_content.header{":class" => class_bindings} %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(line.id, line.section)"} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} {{line.buttonTitle}} %template{"v-if" => "!line.isHeader"} @@ -25,3 +22,4 @@ {{line.lineNumber}} %td.line_content.parallel{":class" => class_bindings} {{{line.richText}}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 78bd4133ea2..380f8722186 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,15 +1,10 @@ -.content-block.oneline-block.files-changed - %strong.resolved-count {{resolvedCount}} - of - %strong.total-count {{conflictsCount}} - conflicts have been resolved - +.content-block .commit-message-container.form-group .max-width-marker %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"} {{{conflictsData.commitMessage}}} - %button{type: "button", class: "btn btn-success js-submit-button", ":disabled" => "!readyToCommit", "@click" => "commit()"} + %button{type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } %span {{commitButtonText}} = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml new file mode 100644 index 00000000000..0556341fd64 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -0,0 +1,6 @@ +%template{ id: "diff-file-editor" } + %div + .editor-wrap{ ":class" => "classObject" } + %p.loading-text Loading... + .editor + %textarea{ "@input" => "onInput", cols: '80', rows: '20' } -- cgit v1.2.1 From a3eb39a1068df93d732585272c5eaff380801430 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 16 Sep 2016 19:41:15 -0500 Subject: Replace textarea with Ace editor --- .../components/diff_file_editor.js.es6 | 27 ++++++++++++++++------ app/assets/stylesheets/pages/merge_conflicts.scss | 7 ++++++ .../projects/merge_requests/conflicts.html.haml | 2 ++ .../components/_diff_file_editor.html.haml | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) (limited to 'app') 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 index 570d9ff877c..abdf73febb4 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -4,7 +4,7 @@ template: '#diff-file-editor', data() { return { - originalState: '', + originalContent: '', saved: false, loading: false, fileLoaded: false @@ -23,6 +23,8 @@ loadFile(val) { const self = this; + this.resetEditorContent(); + if (!val || this.fileLoaded || this.loading) { return } @@ -31,10 +33,19 @@ $.get(this.file.content_path) .done((file) => { - $(self.$el).find('textarea').val(file.content); - self.originalState = file.content; + let content = self.$el.querySelector('pre'); + let fileContent = document.createTextNode(file.content); + + content.textContent = fileContent.textContent; + + self.originalContent = file.content; self.fileLoaded = true; + self.editor = ace.edit(content); + self.editor.$blockScrolling = Infinity; // Turn off annoying warning + self.editor.on('change', () => { + self.saveDiffResolution(); + }); self.saveDiffResolution(); }) .fail(() => { @@ -50,12 +61,14 @@ this.saved = true; // This probably be better placed in the data provider - this.file.content = this.$el.querySelector('textarea').value; - this.file.resolveEditChanged = this.file.content !== this.originalState; + this.file.content = this.editor.getValue(); + this.file.resolveEditChanged = this.file.content !== this.originalContent; this.file.promptDiscardConfirmation = false; }, - onInput() { - this.saveDiffResolution(); + resetEditorContent() { + if (this.fileLoaded) { + this.editor.setValue(this.originalContent, -1); + } } } }); diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 577a97e8c0e..2a8c693b2a8 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -255,6 +255,13 @@ $colors: ( .editor { border-top: solid 1px yellow; + + pre { + height: 350px; + border: none; + border-radius: 0; + margin-bottom: 0; + } } .loading-text { diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index e3add1f799f..ff641b90b86 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -6,6 +6,8 @@ 'unselected': line.isUnselected }" - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') = render "projects/merge_requests/show/mr_title" .merge-request-details.issuable-details diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 0556341fd64..940558e02e0 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -3,4 +3,4 @@ .editor-wrap{ ":class" => "classObject" } %p.loading-text Loading... .editor - %textarea{ "@input" => "onInput", cols: '80', rows: '20' } + %pre{ "style" => "height: 350px" } -- cgit v1.2.1 From 17830296a6143c4d76155449ff4da4377e263657 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 10:10:17 -0500 Subject: Styles for discard alert --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/merge_conflicts.scss | 16 +++++++++++++--- .../merge_requests/conflicts/_diff_file_editor.html.haml | 7 ++++--- 3 files changed, 18 insertions(+), 6 deletions(-) (limited to 'app') 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/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 2a8c693b2a8..ea848b2c2c0 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -249,13 +249,11 @@ $colors: ( &.saved { .editor { - border-top: solid 1px green; + border-top: solid 2px $border-green-extra-light; } } .editor { - border-top: solid 1px yellow; - pre { height: 350px; border: none; @@ -268,4 +266,16 @@ $colors: ( 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; + } + } } diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml index d0c518e5249..a335470de75 100644 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -2,8 +2,9 @@ .diff-editor-wrap{ "v-show" => if_condition } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } - %p + .discard-changes-alert Are you sure to discard your changes? - %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes - %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel + .discard-actions + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel %diff-file-editor{":file" => "file", ":load-file" => if_condition } -- cgit v1.2.1 From 02bfb0ff1bf53abf44c50d8310ad0856d26860cf Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 10:51:09 -0500 Subject: Style for resolve conflicts form --- app/assets/stylesheets/pages/merge_conflicts.scss | 4 ++++ .../conflicts/_submit_form.html.haml | 27 ++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index ea848b2c2c0..c851bd52b1a 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -278,4 +278,8 @@ $colors: ( margin-left: 10px; } } + + .resolve-conflicts-form { + padding-top: $gl-padding; + } } diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 380f8722186..fbb15c307c4 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,10 +1,17 @@ -.content-block - .commit-message-container.form-group - .max-width-marker - %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"} - {{{conflictsData.commitMessage}}} - - %button{type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } - %span {{commitButtonText}} - - = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" +.form-horizontal.resolve-conflicts-form + .form-group + %label.col-sm-2.control-label{ "for" => "commit-message" } + Commit message + .col-sm-10 + .commit-message-container + .max-width-marker + %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } + {{{conflictsData.commitMessage}}} + .form-group + .col-sm-offset-2.col-sm-10 + .row + .col-xs-6 + %button{ type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } + %span {{commitButtonText}} + .col-xs-6.text-right + = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" -- cgit v1.2.1 From 0402a18367a7a08d3f40f8b63b961a0e1abb345a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 11:04:52 -0500 Subject: Replace loading text with spinner --- app/assets/stylesheets/pages/merge_conflicts.scss | 4 ++-- .../merge_requests/conflicts/components/_diff_file_editor.html.haml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index c851bd52b1a..d447ca53c16 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -242,7 +242,7 @@ $colors: ( display: none; } - .loading-text { + .loading { display: block; } } @@ -262,7 +262,7 @@ $colors: ( } } - .loading-text { + .loading { display: none; } } diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 940558e02e0..9965bdf9028 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,6 +1,7 @@ %template{ id: "diff-file-editor" } %div .editor-wrap{ ":class" => "classObject" } - %p.loading-text Loading... + .loading + %i.fa.fa-spinner.fa-spin .editor %pre{ "style" => "height: 350px" } -- cgit v1.2.1 From 12ca16080b86b2b0cee8037800952347c2dcd8c4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 20 Sep 2016 12:37:53 -0500 Subject: check if files is set before counting --- app/assets/javascripts/merge_conflict_data_provider.js.es6 | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index bfed78ff99c..6d1d3f36b33 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -268,6 +268,10 @@ class MergeConflictDataProvider { getConflictsCount() { + if (!this.vueInstance.conflictsData.files) { + return 0; + } + const files = this.vueInstance.conflictsData.files; let count = 0; -- cgit v1.2.1 From a8ac9089afb664e569b34c61dc6782d20d1019d1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 28 Sep 2016 05:12:13 -0500 Subject: Refactor JS code - Use a store base object to manage application state. - Add a service to handle ajax requests. - Load code only when needed --- app/assets/javascripts/dispatcher.js | 3 - .../components/diff_file_editor.js.es6 | 5 +- .../merge_conflicts/merge_conflict_service.js.es6 | 30 ++ .../merge_conflicts/merge_conflict_store.js.es6 | 419 +++++++++++++++++++++ .../merge_conflicts/merge_conflicts_bundle.js.es6 | 84 +++++ .../projects/merge_requests/conflicts.html.haml | 3 +- .../conflicts/_commit_stats.html.haml | 6 +- .../conflicts/_submit_form.html.haml | 1 - 8 files changed, 542 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f3ef13ce20e..2c277766d2d 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -97,9 +97,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/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index abdf73febb4..49d30f6e9e8 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -1,5 +1,8 @@ ((global) => { - global.diffFileEditor = Vue.extend({ + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.diffFileEditor = Vue.extend({ props: ['file', 'loadFile'], template: '#diff-file-editor', data() { 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..d35e6d8aed6 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -0,0 +1,419 @@ +((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' + }; + + 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.setInlineLines(data.files); + this.setParallelLines(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; + }); + }, + + setInlineLines(files) { + 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)); + } + }); + }); + }, + + setParallelLines(files) { + 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] + ]); + } + + return file; + }); + }, + + setLoadingState(state) { + this.state.isLoading = state; + }, + + setErrorState(state) { + this.state.hasError = state; + }, + + setFailedRequest(message) { + console.log('setFailedRequest'); + this.state.hasError = true; + this.state.conflictsData.errorMessage = message; + }, + + getConflictsCount() { + if (!this.state.conflictsData.files) { + return 0; + } + + const files = this.state.conflictsData.files; + let count = 0; + + files.forEach((file) => { + file.sections.forEach((section) => { + if (section.conflict) { + 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) { + // Restore Interactive mode when switching to Edit mode + if (mode === EDIT_RESOLVE_MODE) { + 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 + + 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 + }; + + // 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; + } + + commitData.files.push(addFile); + }); + + return commitData; + }, + + handleSelected(file, sectionId, selection) { + Vue.set(file.resolutionData, sectionId, selection); + + this.state.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); + } + }) + }); + }, + + 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; + } + }; + +})(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..b5123e22f7a --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -0,0 +1,84 @@ +//= require vue +//= require ./merge_conflict_store +//= require ./merge_conflict_service +//= require ./components/diff_file_editor + +$(() => { + const INTERACTIVE_RESOLVE_MODE = 'interactive'; + const $conflicts = $(document.getElementById('conflicts')); + const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; + const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({ + conflictsPath: $conflicts.data('conflictsPath'), + resolveConflictsPath: $conflicts.data('resolveConflictsPath') + }); + + gl.MergeConflictsResolverApp = new Vue({ + el: '#conflicts', + data: mergeConflictsStore.state, + components: { + 'diff-file-editor': gl.mergeConflicts.diffFileEditor + }, + computed: { + conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, + readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, + commitButtonText() { return mergeConflictsStore.getCommitButtonText() } + }, + 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(() => { + $conflicts.find('.js-syntax-highlight').syntaxHighlight(); + }); + }); + }, + methods: { + handleSelected(file, sectionId, selection) { + mergeConflictsStore.handleSelected(file, sectionId, selection); + }, + 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/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index ff641b90b86..997f40c0588 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -7,6 +7,7 @@ - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - content_for :page_specific_javascripts do + = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js') = page_specific_javascript_tag('lib/ace.js') = render "projects/merge_requests/show/mr_title" @@ -26,8 +27,8 @@ = render partial: "projects/merge_requests/conflicts/commit_stats" .files-wrapper{"v-if" => "!isLoading && !hasError"} - = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } + = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/submit_form" -# Components diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 457c467fba9..a3831d5a34e 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -13,8 +13,8 @@ .js-toggle-container .commit-stat-summary Showing - %strong.cred {{conflictsCount}} {{conflictsData.conflictsText}} + %strong.cred {{conflictsCountText}} between - %strong {{conflictsData.source_branch}} + %strong {{conflictsData.sourceBranch}} and - %strong {{conflictsData.target_branch}} + %strong {{conflictsData.targetBranch}} diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index fbb15c307c4..6ffaa9ad4d2 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -6,7 +6,6 @@ .commit-message-container .max-width-marker %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } - {{{conflictsData.commitMessage}}} .form-group .col-sm-offset-2.col-sm-10 .row -- cgit v1.2.1 From 197ac5ebbfd8ca7fbcb79776fd25ca0e213376c4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 29 Sep 2016 20:26:01 -0500 Subject: Ability to resolve conflicts for files with `text-editor` as conflict type --- .../components/diff_file_editor.js.es6 | 43 ++-- .../merge_conflicts/merge_conflict_store.js.es6 | 240 +++++++++++---------- .../conflicts/_diff_file_editor.html.haml | 6 +- .../conflicts/_file_actions.html.haml | 2 +- .../conflicts/_inline_view.html.haml | 41 ++-- .../conflicts/_parallel_view.html.haml | 2 +- 6 files changed, 176 insertions(+), 158 deletions(-) (limited to 'app') 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 index 49d30f6e9e8..46aad9fe33c 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -7,10 +7,10 @@ template: '#diff-file-editor', data() { return { - originalContent: '', saved: false, loading: false, - fileLoaded: false + fileLoaded: false, + originalContent: '', } }, computed: { @@ -23,43 +23,48 @@ } }, watch: { - loadFile(val) { - const self = this; - + ['file.showEditor'](val) { this.resetEditorContent(); if (!val || this.fileLoaded || this.loading) { - return + return; } + this.loadEditor(); + } + }, + ready() { + if (this.file.loadEditor) { + this.loadEditor(); + } + }, + methods: { + loadEditor() { this.loading = true; $.get(this.file.content_path) .done((file) => { - - let content = self.$el.querySelector('pre'); + let content = this.$el.querySelector('pre'); let fileContent = document.createTextNode(file.content); content.textContent = fileContent.textContent; - self.originalContent = file.content; - self.fileLoaded = true; - self.editor = ace.edit(content); - self.editor.$blockScrolling = Infinity; // Turn off annoying warning - self.editor.on('change', () => { - self.saveDiffResolution(); + this.originalContent = file.content; + this.fileLoaded = true; + this.editor = ace.edit(content); + this.editor.$blockScrolling = Infinity; // Turn off annoying warning + this.editor.on('change', () => { + this.saveDiffResolution(); }); - self.saveDiffResolution(); + this.saveDiffResolution(); }) .fail(() => { console.log('error'); }) .always(() => { - self.loading = false; + this.loading = false; }); - } - }, - methods: { + }, saveDiffResolution() { this.saved = true; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index d35e6d8aed6..e1f862797f5 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -13,6 +13,10 @@ INLINE: 'inline', PARALLEL: 'parallel' }; + const CONFLICT_TYPES = { + TEXT: 'text', + TEXT_EDITOR: 'text-editor' + }; global.mergeConflicts.mergeConflictsStore = { state: { @@ -26,8 +30,6 @@ setConflictsData(data) { this.decorateFiles(data.files); - this.setInlineLines(data.files); - this.setParallelLines(data.files); this.state.conflictsData = { files: data.files, @@ -45,90 +47,90 @@ file.resolutionData = {}; file.promptDiscardConfirmation = false; file.resolveMode = DEFAULT_RESOLVE_MODE; - }); - }, - - setInlineLines(files) { - files.forEach((file) => { + file.filePath = this.getFilePath(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 (file.type === CONFLICT_TYPES.TEXT) { + file.showEditor = false; + file.loadEditor = false; - if (conflict) { - file.inlineLines.push(this.getHeadHeaderLine(id)); - } + this.setInlineLine(file); + this.setParallelLine(file); + } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) { + file.showEditor = true; + file.loadEditor = true; + } + }); + }, - lines.forEach((line) => { - const { type } = line; + setInlineLine(file) { + file.inlineLines = []; - if ((type === 'new' || type === 'old') && currentLineType !== type) { - currentLineType = type; - file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); - } + file.sections.forEach((section) => { + let currentLineType = 'new'; + const { conflict, lines, id } = section; + + if (conflict) { + file.inlineLines.push(this.getHeadHeaderLine(id)); + } - this.decorateLineForInlineView(line, id, conflict); - file.inlineLines.push(line); - }) + lines.forEach((line) => { + const { type } = line; - if (conflict) { - file.inlineLines.push(this.getOriginHeaderLine(id)); + 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)); + } }); }, - setParallelLines(files) { - 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; + setParallelLine(file) { + file.parallelLines = []; + const linesObj = { left: [], right: [] }; - if (conflict) { - linesObj.left.push(this.getOriginHeaderLine(id)); - linesObj.right.push(this.getHeadHeaderLine(id)); - } + file.sections.forEach((section) => { + const { conflict, lines, id } = section; - lines.forEach((line) => { - const { type } = line; + if (conflict) { + linesObj.left.push(this.getOriginHeaderLine(id)); + linesObj.right.push(this.getHeadHeaderLine(id)); + } - 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'; + lines.forEach((line) => { + const { type } = line; - linesObj.left.push (this.getLineForParallelView(line, id, lineType)); - linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + 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'; - this.checkLineLengths(linesObj); + linesObj.left.push (this.getLineForParallelView(line, id, lineType)); + linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + } }); - for (let i = 0, len = linesObj.left.length; i < len; i++) { - file.parallelLines.push([ - linesObj.right[i], - linesObj.left[i] - ]); - } - - return file; + 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) { @@ -140,13 +142,12 @@ }, setFailedRequest(message) { - console.log('setFailedRequest'); this.state.hasError = true; this.state.conflictsData.errorMessage = message; }, getConflictsCount() { - if (!this.state.conflictsData.files) { + if (!this.state.conflictsData.files.length) { return 0; } @@ -154,11 +155,15 @@ let count = 0; files.forEach((file) => { - file.sections.forEach((section) => { - if (section.conflict) { - count++; - } - }); + if (file.type === CONFLICT_TYPES.TEXT) { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + } else { + count++; + } }); return count; @@ -172,7 +177,7 @@ }, setViewType(viewType) { - this.state.diffView = viewType; + this.state.diffView = viewType; this.state.isParallel = viewType === VIEW_TYPES.PARALLEL; $.cookie('diff_view', viewType, { @@ -253,8 +258,7 @@ for (let i = 0; i < diff; i++) { right.push({ lineType: 'emptyLine', richText: '' }); } - } - else { + } else { const diff = right.length - left.length; for (let i = 0; i < diff; i++) { left.push({ lineType: 'emptyLine', richText: '' }); @@ -268,8 +272,12 @@ }, setFileResolveMode(file, mode) { - // Restore Interactive mode when switching to Edit mode - if (mode === EDIT_RESOLVE_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); @@ -287,9 +295,9 @@ }); file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const isLeftMatch = left.hasConflict || left.isHeader; + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; const isRightMatch = right.hasConflict || right.isHeader; if (isLeftMatch || isRightMatch) { @@ -313,14 +321,17 @@ let numberConflicts = 0; let resolvedConflicts = Object.keys(file.resolutionData).length - for (let j = 0, k = file.sections.length; j < k; j++) { - if (file.sections[j].conflict) { - numberConflicts++; + // We only check if + 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++; + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } } } else if (file.resolveMode === EDIT_RESOLVE_MODE) { // Unlikely to happen since switching to Edit mode saves content automatically. @@ -358,10 +369,15 @@ new_path: file.new_path }; - // 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) { + 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; } @@ -374,39 +390,35 @@ handleSelected(file, sectionId, selection) { Vue.set(file.resolutionData, sectionId, selection); - this.state.conflictsData.files.forEach((file) => { - file.inlineLines.forEach((line) => { - if (line.id === sectionId && (line.hasConflict || line.isHeader)) { - this.markLine(line, 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; + 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); - } - }) + if (hasSameId && (isLeftMatch || isRightMatch)) { + this.markLine(left, selection); + this.markLine(right, selection); + } }); }, markLine(line, selection) { if (selection === 'head' && line.isHead) { - line.isSelected = true; + line.isSelected = true; line.isUnselected = false; - } - else if (selection === 'origin' && line.isOrigin) { - line.isSelected = true; + } else if (selection === 'origin' && line.isOrigin) { + line.isSelected = true; line.isUnselected = false; - } - else { - line.isSelected = false; + } else { + line.isSelected = false; line.isUnselected = true; } }, diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml index a335470de75..497db7e3f9b 100644 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -1,10 +1,8 @@ -- if_condition = local_assigns.fetch(:if_condition, '') - -.diff-editor-wrap{ "v-show" => if_condition } +.diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert Are you sure to discard your changes? .discard-actions %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel - %diff-file-editor{":file" => "file", ":load-file" => if_condition } + %diff-file-editor{":file" => "file"} diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml index 124dfde7b22..05af57acf03 100644 --- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -1,5 +1,5 @@ .file-actions - .btn-group + .btn-group{"v-if" => "file.type === 'text'"} %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", '@click' => "onClickResolveModeButton(file, 'interactive')", type: 'button' } diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml index 7120b6ff48d..60ac21d26c3 100644 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml @@ -4,23 +4,26 @@ %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} + %template{"v-if" => "file.type === 'text'"} + .diff-content.diff-wrap-lines + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.new_line{":class" => class_bindings} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => class_bindings} + %a {{line.old_line}} + %td.line_content{":class" => class_bindings} + {{{line.richText}}} - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && !isParallel" } + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor' + %template{"v-else" => true} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml index 18c830ddb17..7ed1485fc01 100644 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml @@ -22,4 +22,4 @@ {{line.lineNumber}} %td.line_content.parallel{":class" => class_bindings} {{{line.richText}}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && isParallel" } + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } -- cgit v1.2.1 From cebad0fb60c90ff64cf16b022d262381aff5bf4f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 30 Sep 2016 16:23:38 -0500 Subject: Do not show Diff view switcher if all files are can be only resolved with an editor --- .../javascripts/merge_conflicts/merge_conflict_store.js.es6 | 12 ++++++++++++ .../merge_conflicts/merge_conflicts_bundle.js.es6 | 3 ++- .../merge_requests/conflicts/_commit_stats.html.haml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index e1f862797f5..e31cac49d44 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -425,6 +425,18 @@ setSubmitState(state) { this.state.isSubmitting = state; + }, + + fileTextTypePresent() { + let found = false; + + this.state.conflictsData.files.find((f) => { + if (f.type === CONFLICT_TYPES.TEXT) { + return found = true; + } + }); + + return found; } }; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index b5123e22f7a..449b4d2209a 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -21,7 +21,8 @@ $(() => { computed: { conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, - commitButtonText() { return mergeConflictsStore.getCommitButtonText() } + commitButtonText() { return mergeConflictsStore.getCommitButtonText() }, + showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() } }, created() { mergeConflictsService diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index a3831d5a34e..7222b8ae4d3 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,5 +1,5 @@ .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} - .inline-parallel-buttons + .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} .btn-group %a.btn{ | ":class" => "{'active': !isParallel}", | -- cgit v1.2.1 From f947972dede2f8d68215555e4b4fd6e90a6bc437 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 30 Sep 2016 17:42:36 -0500 Subject: Improve diff view switching and components --- .../projects/merge_requests/conflicts.html.haml | 17 +++++++++++-- .../conflicts/_inline_view.html.haml | 29 ---------------------- .../conflicts/_parallel_view.html.haml | 25 ------------------- .../_resolve_mode_interactive_inline.html.haml | 17 +++++++++++++ .../_resolve_mode_interactive_parallel.html.haml | 17 +++++++++++++ 5 files changed, 49 insertions(+), 56 deletions(-) delete mode 100644 app/views/projects/merge_requests/conflicts/_inline_view.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_parallel_view.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml (limited to 'app') diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 997f40c0588..5ff7befe0ca 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -27,9 +27,22 @@ = render partial: "projects/merge_requests/conflicts/commit_stats" .files-wrapper{"v-if" => "!isLoading && !hasError"} - = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } - = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } + .files + .diff-file.file-holder.conflict{"v-for" => "file in conflictsData.files"} + .file-title + %i.fa.fa-fw{":class" => "file.iconClass"} + %strong {{file.filePath}} + = render partial: 'projects/merge_requests/conflicts/file_actions' + .diff-content.diff-wrap-lines + %div{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_inline", locals: { class_bindings: class_bindings } + %div{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_parallel", locals: { class_bindings: class_bindings } + %div{"v-show" => " file.resolveMode === 'edit' || file.type === 'text-editor'"} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } + = render partial: "projects/merge_requests/conflicts/submit_form" -# Components = render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' + \ No newline at end of file diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml deleted file mode 100644 index 60ac21d26c3..00000000000 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -.files{"v-show" => "!isParallel"} - .diff-file.file-holder.conflict.inline-view{"v-for" => "file in conflictsData.files"} - .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - %template{"v-if" => "file.type === 'text'"} - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor' - %template{"v-else" => true} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml deleted file mode 100644 index 7ed1485fc01..00000000000 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -.files{"v-show" => "isParallel"} - .diff-file.file-holder.conflict.parallel-view{"v-for" => "file in conflictsData.files"} - .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} - %template{"v-for" => "line in section"} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} - {{line.buttonTitle}} - - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.old_line{":class" => class_bindings} - {{line.lineNumber}} - %td.line_content.parallel{":class" => class_bindings} - {{{line.richText}}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml new file mode 100644 index 00000000000..b1d7a90eb74 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml @@ -0,0 +1,17 @@ +.diff-wrap-lines.code.file-content.js-syntax-highlight + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.new_line{":class" => class_bindings} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => class_bindings} + %a {{line.old_line}} + %td.line_content{":class" => class_bindings} + {{{line.richText}}} + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml new file mode 100644 index 00000000000..f5af347f59b --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml @@ -0,0 +1,17 @@ +.diff-wrap-lines.code.file-content.js-syntax-highlight + %table + %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} + %template{"v-for" => "line in section"} + + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{line.richText}} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} + {{line.buttonTitle}} + + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.old_line{":class" => class_bindings} + {{line.lineNumber}} + %td.line_content.parallel{":class" => class_bindings} + {{{line.richText}}} -- cgit v1.2.1 From 4f76ff0ecd0d3b278f9722042b66d1767078ddd0 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 4 Oct 2016 03:24:44 -0500 Subject: Use .some instead of .find for phantomjs compatibility --- .../javascripts/merge_conflicts/merge_conflict_store.js.es6 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index e31cac49d44..bdd6753f66a 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -428,15 +428,7 @@ }, fileTextTypePresent() { - let found = false; - - this.state.conflictsData.files.find((f) => { - if (f.type === CONFLICT_TYPES.TEXT) { - return found = true; - } - }); - - return found; + return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT); } }; -- cgit v1.2.1 From c4142cf9c0c0b217034c60a0a973d2e96b17a428 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 07:57:22 -0500 Subject: Improve components for PhantomJs compatibility --- .../components/diff_file_editor.js.es6 | 14 ++++++++++--- .../components/inline_conflict_lines.js.es6 | 12 +++++++++++ .../components/parallel_conflict_line.js.es6 | 14 +++++++++++++ .../components/parallel_conflict_lines.js.es6 | 15 ++++++++++++++ .../merge_conflicts/merge_conflict_store.js.es6 | 6 +++--- .../merge_conflicts/merge_conflicts_bundle.js.es6 | 12 +++++++---- .../mixins/line_conflict_actions.js.es6 | 12 +++++++++++ .../mixins/line_conflict_utils.js.es6 | 18 ++++++++++++++++ .../projects/merge_requests/conflicts.html.haml | 24 ++++++++-------------- .../conflicts/_commit_stats.html.haml | 8 ++------ .../conflicts/_diff_file_editor.html.haml | 8 -------- .../_resolve_mode_interactive_inline.html.haml | 17 --------------- .../_resolve_mode_interactive_parallel.html.haml | 17 --------------- .../components/_diff_file_editor.html.haml | 10 +++++++-- .../components/_inline_conflict_lines.html.haml | 15 ++++++++++++++ .../components/_parallel_conflict_line.html.haml | 10 +++++++++ .../components/_parallel_conflict_lines.html.haml | 4 ++++ 17 files changed, 140 insertions(+), 76 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 delete mode 100644 app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml (limited to 'app') 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 index 46aad9fe33c..417095a8db7 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -3,8 +3,11 @@ global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts.diffFileEditor = Vue.extend({ - props: ['file', 'loadFile'], - template: '#diff-file-editor', + props: { + file: Object, + onCancelDiscardConfirmation: Function, + onAcceptDiscardConfirmation: Function + }, data() { return { saved: false, @@ -16,7 +19,6 @@ computed: { classObject() { return { - 'load-file': this.loadFile, 'saved': this.saved, 'is-loading': this.loading }; @@ -77,6 +79,12 @@ if (this.fileLoaded) { this.editor.setValue(this.originalContent, -1); } + }, + cancelDiscardConfirmation(file) { + this.onCancelDiscardConfirmation(file); + }, + acceptDiscardConfirmation(file) { + this.onAcceptDiscardConfirmation(file); } } }); 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_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index bdd6753f66a..83bcc3f51aa 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -196,7 +196,7 @@ isHead: true, isSelected: false, isUnselected: false - } + }; }, decorateLineForInlineView(line, id, conflict) { @@ -227,7 +227,7 @@ richText: rich_text, isSelected: false, isUnselected: false - } + }; }, getOriginHeaderLine(id) { @@ -241,7 +241,7 @@ isOrigin: true, isSelected: false, isUnselected: false - } + }; }, getFilePath(file) { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 449b4d2209a..12688365f19 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -1,7 +1,12 @@ //= 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'; @@ -16,7 +21,9 @@ $(() => { el: '#conflicts', data: mergeConflictsStore.state, components: { - 'diff-file-editor': gl.mergeConflicts.diffFileEditor + 'diff-file-editor': gl.mergeConflicts.diffFileEditor, + 'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines, + 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines }, computed: { conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, @@ -46,9 +53,6 @@ $(() => { }); }, methods: { - handleSelected(file, sectionId, selection) { - mergeConflictsStore.handleSelected(file, sectionId, selection); - }, handleViewTypeChange(viewType) { mergeConflictsStore.setViewType(viewType); }, 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/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 5ff7befe0ca..d9f74d2cbfb 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -1,10 +1,3 @@ -- class_bindings = "{ | - 'head': line.isHead, | - 'origin': line.isOrigin, | - 'match': line.hasMatch, | - 'selected': line.isSelected, | - 'unselected': line.isUnselected }" - - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - content_for :page_specific_javascripts do = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js') @@ -34,15 +27,14 @@ %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - %div{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_inline", locals: { class_bindings: class_bindings } - %div{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_parallel", locals: { class_bindings: class_bindings } - %div{"v-show" => " file.resolveMode === 'edit' || file.type === 'text-editor'"} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } + .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" + .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/parallel_conflict_lines" + %div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"} + = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" = render partial: "projects/merge_requests/conflicts/submit_form" --# Components -= render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' - \ No newline at end of file +-# Components += render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line' diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 7222b8ae4d3..5ab3cd96163 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,13 +1,9 @@ .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} .btn-group - %a.btn{ | - ":class" => "{'active': !isParallel}", | - "@click" => "handleViewTypeChange('inline')"} + %button.btn{":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')"} Inline - %a.btn{ | - ":class" => "{'active': isParallel}", | - "@click" => "handleViewTypeChange('parallel')"} + %button.btn{":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')"} Side-by-side .js-toggle-container diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml deleted file mode 100644 index 497db7e3f9b..00000000000 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.diff-editor-wrap{ "v-show" => "file.showEditor" } - .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } - .discard-changes-alert - Are you sure to discard your changes? - .discard-actions - %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes - %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel - %diff-file-editor{":file" => "file"} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml deleted file mode 100644 index b1d7a90eb74..00000000000 --- a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.diff-wrap-lines.code.file-content.js-syntax-highlight - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml deleted file mode 100644 index f5af347f59b..00000000000 --- a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.diff-wrap-lines.code.file-content.js-syntax-highlight - %table - %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} - %template{"v-for" => "line in section"} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} - {{line.buttonTitle}} - - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.old_line{":class" => class_bindings} - {{line.lineNumber}} - %td.line_content.parallel{":class" => class_bindings} - {{{line.richText}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 9965bdf9028..943ae6ee129 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,5 +1,11 @@ -%template{ id: "diff-file-editor" } - %div +%diff-file-editor{"inline-template" => "true", ":file" => "file", ":on-cancel-discard-confirmation" => "cancelDiscardConfirmation", ":on-accept-discard-confirmation" => "acceptDiscardConfirmation"} + .diff-editor-wrap{ "v-show" => "file.showEditor" } + .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } + .discard-changes-alert + Are you sure to discard your changes? + .discard-actions + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel .editor-wrap{ ":class" => "classObject" } .loading %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml new file mode 100644 index 00000000000..f094df7fcaa --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml @@ -0,0 +1,15 @@ +%inline-conflict-lines{ "inline-template" => "true", ":file" => "file"} + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %td.diff-line-num.new_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %a {{line.old_line}} + %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{{line.richText}}} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml new file mode 100644 index 00000000000..5690bf7419c --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml @@ -0,0 +1,10 @@ +%script{"id" => 'parallel-conflict-line', "type" => "text/x-template"} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %strong {{line.richText}} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} + {{line.buttonTitle}} + %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{line.lineNumber}} + %td.line_content.parallel{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{{line.richText}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml new file mode 100644 index 00000000000..a8ecdf59393 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml @@ -0,0 +1,4 @@ +%parallel-conflict-lines{"inline-template" => "true", ":file" => "file"} + %table + %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} + %td{"is"=>"parallel-conflict-line", "v-for" => "line in section", ":line" => "line", ":file" => "file"} -- cgit v1.2.1 From 54bfe70795e289b86485b2a57d72b6711e4994bd Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 07:57:57 -0500 Subject: Add more tests to check conflicts resolution --- app/assets/javascripts/merge_conflict_data_provider.js.es6 | 2 +- app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index 6d1d3f36b33..5877d2f1896 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -290,7 +290,7 @@ class MergeConflictDataProvider { isReadyToCommit() { const vi = this.vueInstance; const files = this.vueInstance.conflictsData.files; - const hasCommitMessage = $.trim(this.vueInstance.conflictsData.commitMessage).length; + const hasCommitMessage = this.vueInstance.conflictsData.commitMessage.trim(); let unresolved = 0; for (let i = 0, l = files.length; i < l; i++) { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index 83bcc3f51aa..5c5c65f29d4 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -321,7 +321,8 @@ let numberConflicts = 0; let resolvedConflicts = Object.keys(file.resolutionData).length - // We only check if + // 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) { @@ -334,6 +335,7 @@ } } } 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) { -- cgit v1.2.1 From 8fd7a5942286d69eb34406e06ba5263c3944012e Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 20:49:16 -0500 Subject: Remove unused files --- .../merge_conflict_data_provider.js.es6 | 438 --------------------- .../javascripts/merge_conflict_resolver.js.es6 | 112 ------ 2 files changed, 550 deletions(-) delete mode 100644 app/assets/javascripts/merge_conflict_data_provider.js.es6 delete mode 100644 app/assets/javascripts/merge_conflict_resolver.js.es6 (limited to 'app') 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 5877d2f1896..00000000000 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ /dev/null @@ -1,438 +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'; -const INTERACTIVE_RESOLVE_MODE = 'interactive'; -const EDIT_RESOLVE_MODE = 'edit'; -const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; - -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 : {} - } - } - - - 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.decorateFiles(data); - this.setParallelLines(data); - this.setInlineLines(data); - } - - vueInstance.conflictsData = data; - vueInstance.isSubmitting = false; - - const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict'; - vueInstance.conflictsData.conflictsText = conflictsText; - } - - decorateFiles(data) { - data.files.forEach((file) => { - file.content = ''; - file.resolutionData = {}; - file.promptDiscardConfirmation = false; - file.resolveMode = DEFAULT_RESOLVE_MODE; - }); - } - - - 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(file, sectionId, selection) { - const vi = this.vueInstance; - let files = vi.conflictsData.files; - - vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection); - - - 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); - } - - setFileResolveMode(file, mode) { - const vi = this.vueInstance; - - // Restore Interactive mode when switching to Edit mode - if (mode === EDIT_RESOLVE_MODE) { - 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; - } - }); - } - - - setPromptConfirmationState(file, state) { - file.promptDiscardConfirmation = state; - } - - - 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() { - if (!this.vueInstance.conflictsData.files) { - return 0; - } - - const files = this.vueInstance.conflictsData.files; - let count = 0; - - files.forEach((file) => { - file.sections.forEach((section) => { - if (section.conflict) { - count++; - } - }); - }); - - return count; - } - - - isReadyToCommit() { - const vi = this.vueInstance; - const files = this.vueInstance.conflictsData.files; - const hasCommitMessage = this.vueInstance.conflictsData.commitMessage.trim(); - 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 - - 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 !vi.isSubmitting && hasCommitMessage && !unresolved; - } - - - 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() { - let conflictsData = this.vueInstance.conflictsData; - let commitData = {}; - - commitData = { - commitMessage: conflictsData.commitMessage, - files: [] - }; - - conflictsData.files.forEach((file) => { - let addFile; - - addFile = { - old_path: file.old_path, - new_path: file.new_path - }; - - // 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; - } - - commitData.files.push(addFile); - }); - - return commitData; - } - - - 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 c317a6521b5..00000000000 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ /dev/null @@ -1,112 +0,0 @@ -//= require vue -//= require ./merge_conflicts/components/diff_file_editor - -const INTERACTIVE_RESOLVE_MODE = 'interactive'; -const EDIT_RESOLVE_MODE = 'edit'; - -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(file, sectionId, selection) { - that.dataProvider.handleSelected(file, sectionId, selection); - }, - handleViewTypeChange(newType) { - that.dataProvider.updateViewType(newType); - }, - commit() { - that.commit(); - }, - onClickResolveModeButton(file, mode) { - that.toggleResolveMode(file, mode); - }, - acceptDiscardConfirmation(file) { - that.dataProvider.setPromptConfirmationState(file, false); - that.dataProvider.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); - }, - cancelDiscardConfirmation(file) { - that.dataProvider.setPromptConfirmationState(file, false); - }, - }, - components: { - 'diff-file-editor': window.gl.diffFileEditor - } - }) - } - - - setComputedProperties() { - const dp = this.dataProvider; - - return { - conflictsCount() { return dp.getConflictsCount() }, - 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; - - $.ajax({ - url: $('#conflicts').data('resolveConflictsPath'), - data: JSON.stringify(this.dataProvider.getCommitData()), - contentType: "application/json", - dataType: 'json', - method: 'POST' - }) - .done((data) => { - window.location.href = data.redirect_to; - }) - .error(() => { - this.vue.isSubmitting = false; - new Flash('Something went wrong!'); - }); - } - - - toggleResolveMode(file, mode) { - if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { - this.dataProvider.setPromptConfirmationState(file, true); - return; - } - - this.dataProvider.setFileResolveMode(file, mode); - } -} -- cgit v1.2.1 From 6141816c233d87888d1450d3bc48a28fc6044ec1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 20:49:43 -0500 Subject: Use plain JS to get elements and data and values --- .../javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 12688365f19..7fd3749b3e2 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -10,14 +10,14 @@ $(() => { const INTERACTIVE_RESOLVE_MODE = 'interactive'; - const $conflicts = $(document.getElementById('conflicts')); + const conflictsEl = document.querySelector('#conflicts'); const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({ - conflictsPath: $conflicts.data('conflictsPath'), - resolveConflictsPath: $conflicts.data('resolveConflictsPath') + conflictsPath: conflictsEl.dataset.conflictsPath, + resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath }); - gl.MergeConflictsResolverApp = new Vue({ + gl.MergeConflictsResolverApp = new Vue({ el: '#conflicts', data: mergeConflictsStore.state, components: { @@ -48,7 +48,7 @@ $(() => { mergeConflictsStore.setLoadingState(false); this.$nextTick(() => { - $conflicts.find('.js-syntax-highlight').syntaxHighlight(); + $(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight(); }); }); }, -- cgit v1.2.1 From cdd0dd507d1d5a226f1f438c4bfa602a115caac3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 12 Oct 2016 22:52:44 -0500 Subject: Fix discard message --- .../merge_requests/conflicts/components/_diff_file_editor.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 943ae6ee129..3c927d362c2 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -2,7 +2,7 @@ .diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert - Are you sure to discard your changes? + Are you sure you want to discard your changes? .discard-actions %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel -- cgit v1.2.1 From f5de2ce3fd74af214556f677f54efa3f4da13ed3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 13 Oct 2016 12:32:35 -0500 Subject: Add syntax highlighting to files --- .../javascripts/merge_conflicts/components/diff_file_editor.js.es6 | 1 + 1 file changed, 1 insertion(+) (limited to 'app') 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 index 417095a8db7..3379414343f 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -55,6 +55,7 @@ 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(); }); -- cgit v1.2.1 From 7e3ff1852340c33f5b8853190d6a24813d5920a2 Mon Sep 17 00:00:00 2001 From: Ebrahim Byagowi Date: Sun, 11 Sep 2016 20:38:09 +0430 Subject: Add RTL support to markdown renderer --- app/assets/stylesheets/framework/typography.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8df0067fac1..287653beac5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -90,6 +90,11 @@ border-left: 3px solid #e7e9ed; } + blockquote:dir(rtl) { + border-left: 0; + border-right: 3px solid #e7e9ed; + } + blockquote p { color: #7f8fa4 !important; font-size: inherit; @@ -112,6 +117,10 @@ } } + table:dir(rtl) th { + text-align: right; + } + pre { margin: 12px 0; font-size: 13px; @@ -129,6 +138,10 @@ margin: 3px 0 3px 28px !important; } + ul:dir(rtl), ol:dir(rtl) { + margin: 3px 28px 3px 0 !important; + } + li { line-height: 1.6em; } -- cgit v1.2.1 From 9c6c5c79f8d3a555ded0539e06a922bc058d5c20 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 19:08:48 +0200 Subject: Add Pipeline metrics worker --- app/models/ci/pipeline.rb | 10 ++++++---- app/workers/pipeline_metrics_worker.rb | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 app/workers/pipeline_metrics_worker.rb (limited to 'app') diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4fdb5fef4fb..c7b9d6cc223 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -49,6 +49,10 @@ module Ci transition any => :canceled end + # IMPORTANT + # Do not add any operations to this state_machine + # Create a separate worker for each new operation + before_transition [:created, :pending] => :running do |pipeline| pipeline.started_at = Time.now end @@ -62,13 +66,11 @@ module Ci end after_transition [:created, :pending] => :running do |pipeline| - MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). - update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) + pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end after_transition any => [:success] do |pipeline| - MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). - update_all(latest_build_finished_at: pipeline.finished_at) + pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end after_transition [:created, :pending, :running] => :success do |pipeline| diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb new file mode 100644 index 00000000000..3d1cd770515 --- /dev/null +++ b/app/workers/pipeline_metrics_worker.rb @@ -0,0 +1,15 @@ +class PipelineMetricsWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + merge_requests = pipeline.merge_requests.map(&:id) + + metrics = MergeRequest::Metrics.where(merge_request_id: merge_requests) + metrics.update_all(latest_build_started_at: pipeline.started_at) if pipeline.active? + metrics.update_all(latest_build_finished_at: pipeline.finished_at) if pipeline.success? + end + end +end -- cgit v1.2.1 From a497803072edb4c8edbf9f4daf3832c122ee50e2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 09:55:47 +0200 Subject: Improve spec for pipeline metrics worker --- app/workers/pipeline_metrics_worker.rb | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb index 3d1cd770515..7bb92df3bbd 100644 --- a/app/workers/pipeline_metrics_worker.rb +++ b/app/workers/pipeline_metrics_worker.rb @@ -5,11 +5,26 @@ class PipelineMetricsWorker def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - merge_requests = pipeline.merge_requests.map(&:id) - - metrics = MergeRequest::Metrics.where(merge_request_id: merge_requests) - metrics.update_all(latest_build_started_at: pipeline.started_at) if pipeline.active? - metrics.update_all(latest_build_finished_at: pipeline.finished_at) if pipeline.success? + update_metrics_for_active_pipeline(pipeline) if pipeline.active? + update_metrics_for_succeeded_pipeline(pipeline) if pipeline.success? end end + + private + + def update_metrics_for_active_pipeline(pipeline) + metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) + end + + def update_metrics_for_succeeded_pipeline(pipeline) + metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: pipeline.finished_at) + end + + def metrics(pipeline) + MergeRequest::Metrics.where(merge_request_id: merge_requests(pipeline)) + end + + def merge_requests(pipeline) + pipeline.merge_requests.map(&:id) + end end -- cgit v1.2.1 From 89bb889c4541a477aff4202fd6dad23ad3fe38d6 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 10:18:36 +0200 Subject: Stop directly parsing due_date with Date.parse, prefer parsing implicitly. --- app/assets/javascripts/due_date_select.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index bf68b7e3a9b..0fcf0823d4e 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -43,7 +43,7 @@ // Create the post date value = $("input[name='" + fieldName + "']").val(); if (value !== '') { - date = new Date(value.replace(new RegExp('-', 'g'), ',')); + date = new Date(value); mediumDate = $.datepicker.formatDate('M d, yy', date); } else { mediumDate = 'No due date'; @@ -64,7 +64,7 @@ $selectbox.hide(); } $value.css('display', ''); - cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value'; + cssClass = new Date(mediumDate) ? 'bold' : 'no-value'; $valueContent.html("" + mediumDate + ""); $sidebarValue.html(mediumDate); if (value !== '') { -- cgit v1.2.1 From 039ccc169a4114a53acb16c0b031f704748c3cae Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 10:19:18 +0200 Subject: Convert due_date_select.js filetype to es6. --- app/assets/javascripts/dispatcher.js.es6 | 2 +- app/assets/javascripts/due_date_select.js | 107 ----------------- app/assets/javascripts/due_date_select.js.es6 | 161 ++++++++++++++++++++++++++ app/views/shared/issuable/_sidebar.html.haml | 2 +- 4 files changed, 163 insertions(+), 109 deletions(-) delete mode 100644 app/assets/javascripts/due_date_select.js create mode 100644 app/assets/javascripts/due_date_select.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index f3957ed374b..5be35cf4b41 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': diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js deleted file mode 100644 index 0fcf0823d4e..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); - 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 = new Date(mediumDate) ? 'bold' : 'no-value'; - $valueContent.html("" + mediumDate + ""); - $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(`${this.displayedDate}`); + 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/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f8059988038..ba9f0c27661 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -171,5 +171,5 @@ new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') - new DueDateSelect(); + new gl.DueDateSelectors(); sidebar = new Sidebar(); -- cgit v1.2.1 From 6aca8b570c929e10f08a599f7bd4f3c7004f96d3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 14:01:58 +0100 Subject: Added download-button class and applied button margin --- app/assets/stylesheets/pages/tree.scss | 4 ++++ app/views/projects/buttons/_download.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'app') 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; + } } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 9089586a89d..7e83a88913a 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,5 +1,5 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - %span{class: 'hidden-xs hidden-sm'} + %span{class: 'hidden-xs hidden-sm download-button'} .dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') -- cgit v1.2.1 From f91df171c53f230937b538ffdec04d12f7726ffc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 17 Oct 2016 16:55:34 +0100 Subject: Fixed find file keyboard navigation Closes #23423 --- app/assets/javascripts/project_find_file.js | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'app') 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; })(); -- cgit v1.2.1 From 9ff64b299bdcca767464f26e25c7e0017ec0fc38 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Oct 2016 18:13:03 +0100 Subject: Swapped button text manipulation outcomes for the toggle query --- app/assets/javascripts/pipelines.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') 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() { -- cgit v1.2.1 From 501d1485edf321d9af242af4721dec604b8dfbcc Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 14 Oct 2016 13:55:10 -0700 Subject: Apply better hierarchy to markdown headers and issue/mr titles --- app/assets/stylesheets/framework/typography.scss | 22 ++++++++++------------ app/assets/stylesheets/pages/detail_page.scss | 6 ++++-- app/assets/stylesheets/pages/merge_requests.scss | 7 ------- 3 files changed, 14 insertions(+), 21 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 287653beac5..8f8ad728269 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; 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; } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 4d9c73c6840..7aa156bc47a 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 #e7e9ed; } .description { 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; -- cgit v1.2.1 From 28dbfd5c4c7793be4147ba217e7d9e162905088b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Oct 2016 12:43:17 -0500 Subject: Provide better error message to the user --- .../javascripts/merge_conflicts/components/diff_file_editor.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') 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 index 3379414343f..5012bdfe997 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -62,7 +62,7 @@ this.saveDiffResolution(); }) .fail(() => { - console.log('error'); + new Flash('Failed to load the file, please try again.'); }) .always(() => { this.loading = false; -- cgit v1.2.1 From eb574e4a0ad19a2848abff56a0ce7c44d3f6780e Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Mon, 17 Oct 2016 13:23:28 -0700 Subject: change border color to variable --- app/assets/stylesheets/framework/typography.scss | 6 +++--- app/assets/stylesheets/pages/detail_page.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8f8ad728269..55de9053be5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -49,7 +49,7 @@ font-weight: 600; 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; } @@ -85,12 +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 #e7e9ed; + border-right: 3px solid $white-dark; } blockquote p { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 7aa156bc47a..2357671c2ae 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -24,7 +24,7 @@ font-size: 2em; color: $gl-gray-dark; padding: 0 0 0.3em; - border-bottom: 1px solid #e7e9ed; + border-bottom: 1px solid $white-dark; } .description { -- cgit v1.2.1 From 73988dd994d6c0d7f5cec24ca5c4c20137cc3844 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Oct 2016 17:47:07 -0500 Subject: Update endpoint to username validator --- app/assets/javascripts/username_validator.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') 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) }); -- cgit v1.2.1