summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/merge_conflicts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/merge_conflicts')
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue32
-rw-r--r--app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue25
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue17
-rw-r--r--app/assets/javascripts/merge_conflicts/constants.js1
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue129
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_service.js16
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js432
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js92
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js7
-rw-r--r--app/assets/javascripts/merge_conflicts/store/actions.js5
-rw-r--r--app/assets/javascripts/merge_conflicts/store/getters.js2
11 files changed, 137 insertions, 621 deletions
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue
index 2c7c8038af5..7649c363daa 100644
--- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue
@@ -1,8 +1,10 @@
<script>
import { debounce } from 'lodash';
+import { mapActions } from 'vuex';
import { deprecatedCreateFlash as flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
+import { INTERACTIVE_RESOLVE_MODE } from '../constants';
export default {
props: {
@@ -10,14 +12,6 @@ export default {
type: Object,
required: true,
},
- onCancelDiscardConfirmation: {
- type: Function,
- required: true,
- },
- onAcceptDiscardConfirmation: {
- type: Function,
- required: true,
- },
},
data() {
return {
@@ -50,6 +44,7 @@ export default {
}
},
methods: {
+ ...mapActions(['setFileResolveMode', 'setPromptConfirmationState', 'updateFile']),
loadEditor() {
const EditorPromise = import(/* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite');
const DataPromise = axios.get(this.file.content_path);
@@ -82,23 +77,24 @@ export default {
saveDiffResolution() {
this.saved = true;
- // This probably be better placed in the data provider
- /* eslint-disable vue/no-mutating-props */
- this.file.content = this.editor.getValue();
- this.file.resolveEditChanged = this.file.content !== this.originalContent;
- this.file.promptDiscardConfirmation = false;
- /* eslint-enable vue/no-mutating-props */
+ this.updateFile({
+ ...this.file,
+ content: this.editor.getValue(),
+ resolveEditChanged: this.file.content !== this.originalContent,
+ promptDiscardConfirmation: false,
+ });
},
resetEditorContent() {
if (this.fileLoaded) {
this.editor.setValue(this.originalContent);
}
},
- cancelDiscardConfirmation(file) {
- this.onCancelDiscardConfirmation(file);
- },
acceptDiscardConfirmation(file) {
- this.onAcceptDiscardConfirmation(file);
+ this.setPromptConfirmationState({ file, promptDiscardConfirmation: false });
+ this.setFileResolveMode({ file, mode: INTERACTIVE_RESOLVE_MODE });
+ },
+ cancelDiscardConfirmation(file) {
+ this.setPromptConfirmationState({ file, promptDiscardConfirmation: false });
},
},
};
diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue
index 519fd53af1e..9721481e6be 100644
--- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue
+++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.vue
@@ -1,34 +1,41 @@
<script>
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
-import actionsMixin from '../mixins/line_conflict_actions';
+import { mapActions } from 'vuex';
+import syntaxHighlight from '~/syntax_highlight';
+import { SYNTAX_HIGHLIGHT_CLASS } from '../constants';
import utilsMixin from '../mixins/line_conflict_utils';
export default {
directives: {
SafeHtml,
},
- mixins: [utilsMixin, actionsMixin],
+ mixins: [utilsMixin],
+ SYNTAX_HIGHLIGHT_CLASS,
props: {
file: {
type: Object,
required: true,
},
},
+ mounted() {
+ syntaxHighlight(document.querySelectorAll(`.${SYNTAX_HIGHLIGHT_CLASS}`));
+ },
+ methods: {
+ ...mapActions(['handleSelected']),
+ },
};
</script>
<template>
- <table class="diff-wrap-lines code code-commit js-syntax-highlight">
- <tr
- v-for="line in file.inlineLines"
- :key="(line.isHeader ? line.id : line.new_line) + line.richText"
- class="line_holder diff-inline"
- >
+ <table :class="['diff-wrap-lines code code-commit', $options.SYNTAX_HIGHLIGHT_CLASS]">
+ <!-- Unfortunately there isn't a good key for these sections -->
+ <!-- eslint-disable vue/require-v-for-key -->
+ <tr v-for="line in file.inlineLines" class="line_holder diff-inline">
<template v-if="line.isHeader">
<td :class="lineCssClass(line)" class="diff-line-num header"></td>
<td :class="lineCssClass(line)" class="diff-line-num header"></td>
<td :class="lineCssClass(line)" class="line_content header">
<strong>{{ line.richText }}</strong>
- <button class="btn" @click="handleSelected(file, line.id, line.section)">
+ <button class="btn" @click="handleSelected({ file, line })">
{{ line.buttonTitle }}
</button>
</td>
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue
index e66f641f70d..7b1d947ccff 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.vue
@@ -1,32 +1,41 @@
<script>
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
-import actionsMixin from '../mixins/line_conflict_actions';
+import { mapActions } from 'vuex';
+import syntaxHighlight from '~/syntax_highlight';
+import { SYNTAX_HIGHLIGHT_CLASS } from '../constants';
import utilsMixin from '../mixins/line_conflict_utils';
export default {
directives: {
SafeHtml,
},
- mixins: [utilsMixin, actionsMixin],
+ mixins: [utilsMixin],
+ SYNTAX_HIGHLIGHT_CLASS,
props: {
file: {
type: Object,
required: true,
},
},
+ mounted() {
+ syntaxHighlight(document.querySelectorAll(`.${SYNTAX_HIGHLIGHT_CLASS}`));
+ },
+ methods: {
+ ...mapActions(['handleSelected']),
+ },
};
</script>
<template>
<!-- Unfortunately there isn't a good key for these sections -->
<!-- eslint-disable vue/require-v-for-key -->
- <table class="diff-wrap-lines code js-syntax-highlight">
+ <table :class="['diff-wrap-lines code', $options.SYNTAX_HIGHLIGHT_CLASS]">
<tr v-for="section in file.parallelLines" class="line_holder parallel">
<template v-for="line in section">
<template v-if="line.isHeader">
<td class="diff-line-num header" :class="lineCssClass(line)"></td>
<td class="line_content header" :class="lineCssClass(line)">
<strong>{{ line.richText }}</strong>
- <button class="btn" @click="handleSelected(file, line.id, line.section)">
+ <button class="btn" @click="handleSelected({ file, line })">
{{ line.buttonTitle }}
</button>
</td>
diff --git a/app/assets/javascripts/merge_conflicts/constants.js b/app/assets/javascripts/merge_conflicts/constants.js
index 6f3ee339e36..dddcc891e81 100644
--- a/app/assets/javascripts/merge_conflicts/constants.js
+++ b/app/assets/javascripts/merge_conflicts/constants.js
@@ -13,6 +13,7 @@ export const VIEW_TYPES = {
export const EDIT_RESOLVE_MODE = 'edit';
export const INTERACTIVE_RESOLVE_MODE = 'interactive';
export const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
+export const SYNTAX_HIGHLIGHT_CLASS = 'js-syntax-highlight';
export const HEAD_HEADER_TEXT = s__('MergeConflict|HEAD//our changes');
export const ORIGIN_HEADER_TEXT = s__('MergeConflict|origin//their changes');
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue b/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
index 16a7cfb2ba8..0509cf0afa1 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
@@ -1,14 +1,15 @@
<script>
import { GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState, mapActions } from 'vuex';
import { __ } from '~/locale';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import DiffFileEditor from './components/diff_file_editor.vue';
import InlineConflictLines from './components/inline_conflict_lines.vue';
import ParallelConflictLines from './components/parallel_conflict_lines.vue';
+import { INTERACTIVE_RESOLVE_MODE } from './constants';
/**
- * NOTE: Most of this component is directly using $root, rather than props or a better data store.
- * This is BAD and one shouldn't copy that behavior. Similarly a lot of the classes below should
+ * A lot of the classes below should
* be replaced with GitLab UI components.
*
* We are just doing it temporarily in order to migrate the template from HAML => Vue in an iterative manner
@@ -25,60 +26,88 @@ export default {
InlineConflictLines,
ParallelConflictLines,
},
- inject: ['mergeRequestPath', 'sourceBranchPath'],
+ inject: ['mergeRequestPath', 'sourceBranchPath', 'resolveConflictsPath'],
i18n: {
commitStatSummary: __('Showing %{conflict} between %{sourceBranch} and %{targetBranch}'),
resolveInfo: __(
'You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}',
),
},
+ computed: {
+ ...mapGetters([
+ 'getConflictsCountText',
+ 'isReadyToCommit',
+ 'getCommitButtonText',
+ 'fileTextTypePresent',
+ ]),
+ ...mapState(['isLoading', 'hasError', 'isParallel', 'conflictsData']),
+ commitMessage: {
+ get() {
+ return this.conflictsData.commitMessage;
+ },
+ set(value) {
+ this.updateCommitMessage(value);
+ },
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'setViewType',
+ 'submitResolvedConflicts',
+ 'setFileResolveMode',
+ 'setPromptConfirmationState',
+ 'updateCommitMessage',
+ ]),
+ onClickResolveModeButton(file, mode) {
+ if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) {
+ this.setPromptConfirmationState({ file, promptDiscardConfirmation: true });
+ } else {
+ this.setFileResolveMode({ file, mode });
+ }
+ },
+ },
};
</script>
<template>
<div id="conflicts">
- <div v-if="$root.isLoading" class="loading">
+ <div v-if="isLoading" class="loading">
<div class="spinner spinner-md"></div>
</div>
- <div v-if="$root.hasError" class="nothing-here-block">
- {{ $root.conflictsData.errorMessage }}
+ <div v-if="hasError" class="nothing-here-block">
+ {{ conflictsData.errorMessage }}
</div>
- <template v-if="!$root.isLoading && !$root.hasError">
+ <template v-if="!isLoading && !hasError">
<div class="content-block oneline-block files-changed">
- <div v-if="$root.showDiffViewTypeSwitcher" class="inline-parallel-buttons">
+ <div v-if="fileTextTypePresent" class="inline-parallel-buttons">
<div class="btn-group">
<button
- :class="{ active: !$root.isParallel }"
+ :class="{ active: !isParallel }"
class="btn gl-button"
- @click="$root.handleViewTypeChange('inline')"
+ @click="setViewType('inline')"
>
{{ __('Inline') }}
</button>
<button
- :class="{ active: $root.isParallel }"
+ :class="{ active: isParallel }"
class="btn gl-button"
- @click="$root.handleViewTypeChange('parallel')"
+ data-testid="side-by-side"
+ @click="setViewType('parallel')"
>
{{ __('Side-by-side') }}
</button>
</div>
</div>
<div class="js-toggle-container">
- <div class="commit-stat-summary">
+ <div class="commit-stat-summary" data-testid="conflicts-count">
<gl-sprintf :message="$options.i18n.commitStatSummary">
<template #conflict>
- <strong class="cred">
- {{ $root.conflictsCountText }}
- </strong>
+ <strong class="cred">{{ getConflictsCountText }}</strong>
</template>
<template #sourceBranch>
- <strong class="ref-name">
- {{ $root.conflictsData.sourceBranch }}
- </strong>
+ <strong class="ref-name">{{ conflictsData.sourceBranch }}</strong>
</template>
<template #targetBranch>
- <strong class="ref-name">
- {{ $root.conflictsData.targetBranch }}
- </strong>
+ <strong class="ref-name">{{ conflictsData.targetBranch }}</strong>
</template>
</gl-sprintf>
</div>
@@ -87,12 +116,13 @@ export default {
<div class="files-wrapper">
<div class="files">
<div
- v-for="file in $root.conflictsData.files"
+ v-for="file in conflictsData.files"
:key="file.blobPath"
class="diff-file file-holder conflict"
+ data-testid="files"
>
<div class="js-file-title file-title file-title-flex-parent cursor-default">
- <div class="file-header-content">
+ <div class="file-header-content" data-testid="file-name">
<file-icon :file-name="file.filePath" :size="18" css-classes="gl-mr-2" />
<strong class="file-title-name">{{ file.filePath }}</strong>
</div>
@@ -102,7 +132,8 @@ export default {
:class="{ active: file.resolveMode === 'interactive' }"
class="btn gl-button"
type="button"
- @click="$root.onClickResolveModeButton(file, 'interactive')"
+ data-testid="interactive-button"
+ @click="onClickResolveModeButton(file, 'interactive')"
>
{{ __('Interactive mode') }}
</button>
@@ -110,7 +141,8 @@ export default {
:class="{ active: file.resolveMode === 'edit' }"
class="btn gl-button"
type="button"
- @click="$root.onClickResolveModeButton(file, 'edit')"
+ data-testid="inline-button"
+ @click="onClickResolveModeButton(file, 'edit')"
>
{{ __('Edit inline') }}
</button>
@@ -118,35 +150,23 @@ export default {
<a :href="file.blobPath" class="btn gl-button view-file">
<gl-sprintf :message="__('View file @ %{commitSha}')">
<template #commitSha>
- {{ $root.conflictsData.shortCommitSha }}
+ {{ conflictsData.shortCommitSha }}
</template>
</gl-sprintf>
</a>
</div>
</div>
<div class="diff-content diff-wrap-lines">
- <div
- v-show="
- !$root.isParallel && file.resolveMode === 'interactive' && file.type === 'text'
- "
- class="file-content"
- >
- <inline-conflict-lines :file="file" />
- </div>
- <div
- v-show="
- $root.isParallel && file.resolveMode === 'interactive' && file.type === 'text'
- "
- class="file-content"
- >
- <parallel-conflict-lines :file="file" />
- </div>
- <div v-show="file.resolveMode === 'edit' || file.type === 'text-editor'">
- <diff-file-editor
- :file="file"
- :on-accept-discard-confirmation="$root.acceptDiscardConfirmation"
- :on-cancel-discard-confirmation="$root.cancelDiscardConfirmation"
- />
+ <template v-if="file.resolveMode === 'interactive' && file.type === 'text'">
+ <div v-if="!isParallel" class="file-content">
+ <inline-conflict-lines :file="file" />
+ </div>
+ <div v-if="isParallel" class="file-content">
+ <parallel-conflict-lines :file="file" />
+ </div>
+ </template>
+ <div v-if="file.resolveMode === 'edit' || file.type === 'text-editor'">
+ <diff-file-editor :file="file" />
</div>
</div>
</div>
@@ -169,7 +189,7 @@ export default {
</template>
<template #branch_name>
<a class="ref-name" :href="sourceBranchPath">
- {{ $root.conflictsData.sourceBranch }}
+ {{ conflictsData.sourceBranch }}
</a>
</template>
</gl-sprintf>
@@ -183,7 +203,8 @@ export default {
<div class="max-width-marker"></div>
<textarea
id="commit-message"
- v-model="$root.conflictsData.commitMessage"
+ v-model="commitMessage"
+ data-testid="commit-message"
class="form-control js-commit-message"
rows="5"
></textarea>
@@ -195,12 +216,12 @@ export default {
<div class="row">
<div class="col-6">
<button
- :disabled="!$root.readyToCommit"
+ :disabled="!isReadyToCommit"
class="btn gl-button btn-success js-submit-button"
type="button"
- @click="$root.commit()"
+ @click="submitResolvedConflicts(resolveConflictsPath)"
>
- <span>{{ $root.commitButtonText }}</span>
+ <span>{{ getCommitButtonText }}</span>
</button>
</div>
<div class="col-6 text-right">
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
deleted file mode 100644
index 64d69159222..00000000000
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import axios from '../lib/utils/axios_utils';
-
-export default class MergeConflictsService {
- constructor(options) {
- this.conflictsPath = options.conflictsPath;
- this.resolveConflictsPath = options.resolveConflictsPath;
- }
-
- fetchConflictsData() {
- return axios.get(this.conflictsPath);
- }
-
- submitResolveConflicts(data) {
- return axios.post(this.resolveConflictsPath, data);
- }
-}
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
deleted file mode 100644
index fb3444262ea..00000000000
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ /dev/null
@@ -1,432 +0,0 @@
-/* eslint-disable no-param-reassign, babel/camelcase, no-nested-ternary, no-continue */
-
-import $ from 'jquery';
-import Cookies from 'js-cookie';
-import Vue from 'vue';
-import { s__ } from '~/locale';
-
-((global) => {
- global.mergeConflicts = global.mergeConflicts || {};
-
- const diffViewType = Cookies.get('diff_view');
- const HEAD_HEADER_TEXT = s__('MergeConflict|HEAD//our changes');
- const ORIGIN_HEADER_TEXT = s__('MergeConflict|origin//their changes');
- const HEAD_BUTTON_TITLE = s__('MergeConflict|Use ours');
- const ORIGIN_BUTTON_TITLE = s__('MergeConflict|Use theirs');
- const INTERACTIVE_RESOLVE_MODE = 'interactive';
- const EDIT_RESOLVE_MODE = 'edit';
- const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
- const VIEW_TYPES = {
- INLINE: 'inline',
- PARALLEL: 'parallel',
- };
- const CONFLICT_TYPES = {
- TEXT: 'text',
- TEXT_EDITOR: 'text-editor',
- };
-
- global.mergeConflicts.mergeConflictsStore = {
- state: {
- isLoading: true,
- hasError: false,
- isSubmitting: false,
- isParallel: diffViewType === VIEW_TYPES.PARALLEL,
- diffViewType,
- conflictsData: {},
- },
-
- setConflictsData(data) {
- this.decorateFiles(data.files);
-
- this.state.conflictsData = {
- files: data.files,
- commitMessage: data.commit_message,
- sourceBranch: data.source_branch,
- targetBranch: data.target_branch,
- shortCommitSha: data.commit_sha.slice(0, 7),
- };
- },
-
- decorateFiles(files) {
- files.forEach((file) => {
- file.content = '';
- file.resolutionData = {};
- file.promptDiscardConfirmation = false;
- file.resolveMode = DEFAULT_RESOLVE_MODE;
- file.filePath = this.getFilePath(file);
- file.blobPath = file.blob_path;
-
- if (file.type === CONFLICT_TYPES.TEXT) {
- file.showEditor = false;
- file.loadEditor = false;
-
- this.setInlineLine(file);
- this.setParallelLine(file);
- } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
- file.showEditor = true;
- file.loadEditor = true;
- }
- });
- },
-
- setInlineLine(file) {
- file.inlineLines = [];
-
- file.sections.forEach((section) => {
- let currentLineType = 'new';
- const { conflict, lines, id } = section;
-
- if (conflict) {
- file.inlineLines.push(this.getHeadHeaderLine(id));
- }
-
- lines.forEach((line) => {
- const { type } = line;
-
- if ((type === 'new' || type === 'old') && currentLineType !== type) {
- currentLineType = type;
- file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
- }
-
- this.decorateLineForInlineView(line, id, conflict);
- file.inlineLines.push(line);
- });
-
- if (conflict) {
- file.inlineLines.push(this.getOriginHeaderLine(id));
- }
- });
- },
-
- setParallelLine(file) {
- file.parallelLines = [];
- const linesObj = { left: [], right: [] };
-
- file.sections.forEach((section) => {
- const { conflict, lines, id } = section;
-
- if (conflict) {
- linesObj.left.push(this.getOriginHeaderLine(id));
- linesObj.right.push(this.getHeadHeaderLine(id));
- }
-
- lines.forEach((line) => {
- const { type } = line;
-
- if (conflict) {
- if (type === 'old') {
- linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
- } else if (type === 'new') {
- linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
- }
- } else {
- const lineType = type || 'context';
-
- linesObj.left.push(this.getLineForParallelView(line, id, lineType));
- linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
- }
- });
-
- this.checkLineLengths(linesObj);
- });
-
- for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
- file.parallelLines.push([linesObj.right[i], linesObj.left[i]]);
- }
- },
-
- setLoadingState(state) {
- this.state.isLoading = state;
- },
-
- setErrorState(state) {
- this.state.hasError = state;
- },
-
- setFailedRequest(message) {
- this.state.hasError = true;
- this.state.conflictsData.errorMessage = message;
- },
-
- getConflictsCount() {
- if (!this.state.conflictsData.files.length) {
- return 0;
- }
-
- const { files } = this.state.conflictsData;
- let count = 0;
-
- files.forEach((file) => {
- if (file.type === CONFLICT_TYPES.TEXT) {
- file.sections.forEach((section) => {
- if (section.conflict) {
- count += 1;
- }
- });
- } else {
- count += 1;
- }
- });
-
- return count;
- },
-
- getConflictsCountText() {
- const count = this.getConflictsCount();
- const text = count > 1 ? s__('MergeConflict|conflicts') : s__('MergeConflict|conflict');
-
- return `${count} ${text}`;
- },
-
- setViewType(viewType) {
- this.state.diffView = viewType;
- this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;
-
- Cookies.set('diff_view', viewType);
- },
-
- getHeadHeaderLine(id) {
- return {
- 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,
- 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) {
- const { 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 += 1) {
- right.push({ lineType: 'emptyLine', richText: '' });
- }
- } else {
- const diff = right.length - left.length;
- for (let i = 0; i < diff; i += 1) {
- left.push({ lineType: 'emptyLine', richText: '' });
- }
- }
- }
- },
-
- setPromptConfirmationState(file, state) {
- file.promptDiscardConfirmation = state;
- },
-
- setFileResolveMode(file, mode) {
- if (mode === INTERACTIVE_RESOLVE_MODE) {
- file.showEditor = false;
- } else if (mode === EDIT_RESOLVE_MODE) {
- // Restore Interactive mode when switching to Edit mode
- file.showEditor = true;
- file.loadEditor = true;
- file.resolutionData = {};
-
- this.restoreFileLinesState(file);
- }
-
- file.resolveMode = mode;
- },
-
- restoreFileLinesState(file) {
- file.inlineLines.forEach((line) => {
- if (line.hasConflict || line.isHeader) {
- line.isSelected = false;
- line.isUnselected = false;
- }
- });
-
- file.parallelLines.forEach((lines) => {
- const left = lines[0];
- const right = lines[1];
- const isLeftMatch = left.hasConflict || left.isHeader;
- const isRightMatch = right.hasConflict || right.isHeader;
-
- if (isLeftMatch || isRightMatch) {
- left.isSelected = false;
- left.isUnselected = false;
- right.isSelected = false;
- right.isUnselected = false;
- }
- });
- },
-
- isReadyToCommit() {
- const { files } = this.state.conflictsData;
- const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
- let unresolved = 0;
-
- for (let i = 0, l = files.length; i < l; i += 1) {
- const file = files[i];
-
- if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
- let numberConflicts = 0;
- const resolvedConflicts = Object.keys(file.resolutionData).length;
-
- // We only check for conflicts type 'text'
- // since conflicts `text_editor` can´t be resolved in interactive mode
- if (file.type === CONFLICT_TYPES.TEXT) {
- for (let j = 0, k = file.sections.length; j < k; j += 1) {
- if (file.sections[j].conflict) {
- numberConflicts += 1;
- }
- }
-
- if (resolvedConflicts !== numberConflicts) {
- unresolved += 1;
- }
- }
- } 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 += 1;
- continue;
- }
- }
- }
-
- return !this.state.isSubmitting && hasCommitMessage && !unresolved;
- },
-
- getCommitButtonText() {
- const initial = s__('MergeConflict|Commit to source branch');
- const inProgress = s__('MergeConflict|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) => {
- const addFile = {
- old_path: file.old_path,
- new_path: file.new_path,
- };
-
- if (file.type === CONFLICT_TYPES.TEXT) {
- // Submit only one data for type of editing
- if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
- addFile.sections = file.resolutionData;
- } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
- addFile.content = file.content;
- }
- } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
- addFile.content = file.content;
- }
-
- commitData.files.push(addFile);
- });
-
- return commitData;
- },
-
- handleSelected(file, sectionId, selection) {
- Vue.set(file.resolutionData, sectionId, selection);
-
- file.inlineLines.forEach((line) => {
- if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
- this.markLine(line, selection);
- }
- });
-
- file.parallelLines.forEach((lines) => {
- const left = lines[0];
- const right = lines[1];
- const hasSameId = right.id === sectionId || left.id === sectionId;
- const isLeftMatch = left.hasConflict || left.isHeader;
- const isRightMatch = right.hasConflict || right.isHeader;
-
- if (hasSameId && (isLeftMatch || isRightMatch)) {
- this.markLine(left, selection);
- this.markLine(right, selection);
- }
- });
- },
-
- markLine(line, selection) {
- if (selection === 'head' && line.isHead) {
- line.isSelected = true;
- line.isUnselected = false;
- } else if (selection === 'origin' && line.isOrigin) {
- line.isSelected = true;
- line.isUnselected = false;
- } else {
- line.isSelected = false;
- line.isUnselected = true;
- }
- },
-
- setSubmitState(state) {
- this.state.isSubmitting = state;
- },
-
- fileTextTypePresent() {
- return this.state.conflictsData.files.some((f) => f.type === CONFLICT_TYPES.TEXT);
- },
- };
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 4b73dd317cd..cf02c6fbd6b 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -1,100 +1,32 @@
-import $ from 'jquery';
import Vue from 'vue';
-import { __ } from '~/locale';
-import { deprecatedCreateFlash as createFlash } from '../flash';
import initIssuableSidebar from '../init_issuable_sidebar';
-import './merge_conflict_store';
-import syntaxHighlight from '../syntax_highlight';
import MergeConflictsResolverApp from './merge_conflict_resolver_app.vue';
-import MergeConflictsService from './merge_conflict_service';
+import { createStore } from './store';
export default function initMergeConflicts() {
- const INTERACTIVE_RESOLVE_MODE = 'interactive';
const conflictsEl = document.querySelector('#conflicts');
- const { mergeConflictsStore } = gl.mergeConflicts;
- const mergeConflictsService = new MergeConflictsService({
- conflictsPath: conflictsEl.dataset.conflictsPath,
- resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath,
- });
- const { sourceBranchPath, mergeRequestPath } = conflictsEl.dataset;
+ const {
+ sourceBranchPath,
+ mergeRequestPath,
+ conflictsPath,
+ resolveConflictsPath,
+ } = conflictsEl.dataset;
initIssuableSidebar();
+ const store = createStore();
+
return new Vue({
el: conflictsEl,
+ store,
provide: {
sourceBranchPath,
mergeRequestPath,
- },
- data: mergeConflictsStore.state,
- computed: {
- conflictsCountText() {
- return mergeConflictsStore.getConflictsCountText();
- },
- readyToCommit() {
- return mergeConflictsStore.isReadyToCommit();
- },
- commitButtonText() {
- return mergeConflictsStore.getCommitButtonText();
- },
- showDiffViewTypeSwitcher() {
- return mergeConflictsStore.fileTextTypePresent();
- },
+ resolveConflictsPath,
},
created() {
- mergeConflictsService
- .fetchConflictsData()
- .then(({ data }) => {
- if (data.type === 'error') {
- mergeConflictsStore.setFailedRequest(data.message);
- } else {
- mergeConflictsStore.setConflictsData(data);
- }
-
- mergeConflictsStore.setLoadingState(false);
-
- this.$nextTick(() => {
- syntaxHighlight($('.js-syntax-highlight'));
- });
- })
- .catch(() => {
- mergeConflictsStore.setLoadingState(false);
- mergeConflictsStore.setFailedRequest();
- });
- },
- methods: {
- handleViewTypeChange(viewType) {
- mergeConflictsStore.setViewType(viewType);
- },
- onClickResolveModeButton(file, mode) {
- if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) {
- mergeConflictsStore.setPromptConfirmationState(file, true);
- return;
- }
-
- mergeConflictsStore.setFileResolveMode(file, mode);
- },
- acceptDiscardConfirmation(file) {
- mergeConflictsStore.setPromptConfirmationState(file, false);
- mergeConflictsStore.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE);
- },
- cancelDiscardConfirmation(file) {
- mergeConflictsStore.setPromptConfirmationState(file, false);
- },
- commit() {
- mergeConflictsStore.setSubmitState(true);
-
- mergeConflictsService
- .submitResolveConflicts(mergeConflictsStore.getCommitData())
- .then(({ data }) => {
- window.location.href = data.redirect_to;
- })
- .catch(() => {
- mergeConflictsStore.setSubmitState(false);
- createFlash(__('Failed to save merge conflicts resolutions. Please try again!'));
- });
- },
+ store.dispatch('fetchConflictsData', conflictsPath);
},
render(createElement) {
return createElement(MergeConflictsResolverApp);
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js
deleted file mode 100644
index 364ae2b2688..00000000000
--- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default {
- methods: {
- handleSelected(file, sectionId, selection) {
- gl.mergeConflicts.mergeConflictsStore.handleSelected(file, sectionId, selection);
- },
- },
-};
diff --git a/app/assets/javascripts/merge_conflicts/store/actions.js b/app/assets/javascripts/merge_conflicts/store/actions.js
index 8036e90c58c..df515c4ac1a 100644
--- a/app/assets/javascripts/merge_conflicts/store/actions.js
+++ b/app/assets/javascripts/merge_conflicts/store/actions.js
@@ -118,3 +118,8 @@ export const handleSelected = ({ commit, state, getters }, { file, line: { id, s
commit(types.UPDATE_FILE, { file: updated, index });
};
+
+export const updateFile = ({ commit, getters }, file) => {
+ const index = getters.getFileIndex(file);
+ commit(types.UPDATE_FILE, { file, index });
+};
diff --git a/app/assets/javascripts/merge_conflicts/store/getters.js b/app/assets/javascripts/merge_conflicts/store/getters.js
index 03e425fb478..54f3d6ec4bc 100644
--- a/app/assets/javascripts/merge_conflicts/store/getters.js
+++ b/app/assets/javascripts/merge_conflicts/store/getters.js
@@ -67,7 +67,7 @@ export const isReadyToCommit = (state) => {
}
}
- return !state.isSubmitting && hasCommitMessage && !unresolved;
+ return Boolean(!state.isSubmitting && hasCommitMessage && !unresolved);
};
export const getCommitButtonText = (state) => {