diff options
Diffstat (limited to 'app/assets/javascripts/diffs/components/diff_row.vue')
-rw-r--r-- | app/assets/javascripts/diffs/components/diff_row.vue | 556 |
1 files changed, 256 insertions, 300 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue index 37dd7941b2e..c310bd9f31a 100644 --- a/app/assets/javascripts/diffs/components/diff_row.vue +++ b/app/assets/javascripts/diffs/components/diff_row.vue @@ -1,13 +1,9 @@ <script> /* eslint-disable vue/no-v-html */ -import { GlTooltipDirective } from '@gitlab/ui'; -import { mapActions, mapGetters, mapState } from 'vuex'; -import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { memoize } from 'lodash'; +import { isLoggedIn } from '~/lib/utils/common_utils'; import { - CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE, - CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR, CONFLICT_OUR, CONFLICT_THEIR, @@ -22,15 +18,8 @@ import DiffGutterAvatars from './diff_gutter_avatars.vue'; import * as utils from './diff_row_utils'; export default { - components: { - DiffGutterAvatars, - CodeQualityGutterIcon: () => - import('ee_component/diffs/components/code_quality_gutter_icon.vue'), - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [glFeatureFlagsMixin()], + DiffGutterAvatars, + CodeQualityGutterIcon: () => import('ee_component/diffs/components/code_quality_gutter_icon.vue'), props: { fileHash: { type: String, @@ -58,148 +47,109 @@ export default { type: Number, required: true, }, + isHighlighted: { + type: Boolean, + required: true, + }, + fileLineCoverage: { + type: Function, + required: true, + }, }, - data() { - return { - dragging: false, - }; - }, - computed: { - ...mapGetters('diffs', ['fileLineCoverage']), - ...mapGetters(['isLoggedIn']), - ...mapState({ - isHighlighted(state) { - const line = this.line.left?.line_code ? this.line.left : this.line.right; - return utils.isHighlighted(state, line, false); - }, - }), - classNameMap() { + classNameMap: memoize( + (props) => { return { - [CONTEXT_LINE_CLASS_NAME]: this.line.isContextLineLeft, - [PARALLEL_DIFF_VIEW_TYPE]: !this.inline, - commented: this.isCommented, + [PARALLEL_DIFF_VIEW_TYPE]: !props.inline, + commented: props.isCommented, }; }, - parallelViewLeftLineType() { - return utils.parallelViewLeftLineType(this.line, this.isHighlighted || this.isCommented); + (props) => [!props.inline, props.isCommented].join(':'), + ), + parallelViewLeftLineType: memoize( + (props) => { + return utils.parallelViewLeftLineType(props.line, props.isHighlighted || props.isCommented); }, - coverageStateLeft() { - if (!this.inline || !this.line.left) return {}; - return this.fileLineCoverage(this.filePath, this.line.left.new_line); + (props) => + [props.line.left?.type, props.line.right?.type, props.isHighlighted, props.isCommented].join( + ':', + ), + ), + coverageStateLeft: memoize( + (props) => { + if (!props.inline || !props.line.left) return {}; + return props.fileLineCoverage(props.filePath, props.line.left.new_line); }, - coverageStateRight() { - if (!this.line.right) return {}; - return this.fileLineCoverage(this.filePath, this.line.right.new_line); + (props) => [props.inline, props.filePath, props.line.left?.new_line].join(':'), + ), + coverageStateRight: memoize( + (props) => { + if (!props.line.right) return {}; + return props.fileLineCoverage(props.filePath, props.line.right.new_line); }, - showCodequalityLeft() { - return ( - this.glFeatures.codequalityMrDiffAnnotations && - this.inline && - this.line.left?.codequality?.length > 0 - ); + (props) => [props.line.right?.new_line, props.filePath].join(':'), + ), + showCodequalityLeft: memoize( + (props) => { + return props.inline && props.line.left?.codequality?.length > 0; }, - showCodequalityRight() { - return ( - this.glFeatures.codequalityMrDiffAnnotations && - !this.inline && - this.line.right?.codequality?.length > 0 - ); + (props) => [props.inline, props.line.left?.codequality?.length].join(':'), + ), + showCodequalityRight: memoize( + (props) => { + return !props.inline && props.line.right?.codequality?.length > 0; }, - classNameMapCellLeft() { + (props) => [props.inline, props.line.right?.codequality?.length].join(':'), + ), + classNameMapCellLeft: memoize( + (props) => { return utils.classNameMapCell({ - line: this.line.left, - hll: this.isHighlighted || this.isCommented, - isLoggedIn: this.isLoggedIn, + line: props.line.left, + hll: props.isHighlighted || props.isCommented, }); }, - classNameMapCellRight() { + (props) => [props.line.left.type, props.isHighlighted, props.isCommented].join(':'), + ), + classNameMapCellRight: memoize( + (props) => { return utils.classNameMapCell({ - line: this.line.right, - hll: this.isHighlighted || this.isCommented, - isLoggedIn: this.isLoggedIn, + line: props.line.right, + hll: props.isHighlighted || props.isCommented, }); }, - addCommentTooltipLeft() { - return utils.addCommentTooltip(this.line.left, this.glFeatures.dragCommentSelection); - }, - addCommentTooltipRight() { - return utils.addCommentTooltip(this.line.right, this.glFeatures.dragCommentSelection); + (props) => [props.line.right.type, props.isHighlighted, props.isCommented].join(':'), + ), + shouldRenderCommentButton: memoize( + (props) => { + return isLoggedIn() && !props.line.isContextLineLeft && !props.line.isMetaLineLeft; }, - emptyCellRightClassMap() { - return { conflict_their: this.line.left?.type === CONFLICT_OUR }; - }, - emptyCellLeftClassMap() { - return { conflict_our: this.line.right?.type === CONFLICT_THEIR }; - }, - shouldRenderCommentButton() { - return this.isLoggedIn && !this.line.isContextLineLeft && !this.line.isMetaLineLeft; - }, - isLeftConflictMarker() { - return [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(this.line.left?.type); - }, - interopLeftAttributes() { - if (this.inline) { - return getInteropInlineAttributes(this.line.left); - } + (props) => [props.line.isContextLineLeft, props.line.isMetaLineLeft].join(':'), + ), + interopLeftAttributes(props) { + if (props.inline) { + return getInteropInlineAttributes(props.line.left); + } - return getInteropOldSideAttributes(this.line.left); - }, - interopRightAttributes() { - return getInteropNewSideAttributes(this.line.right); - }, + return getInteropOldSideAttributes(props.line.left); }, - mounted() { - this.scrollToLineIfNeededParallel(this.line); + interopRightAttributes(props) { + return getInteropNewSideAttributes(props.line.right); }, - methods: { - ...mapActions('diffs', [ - 'scrollToLineIfNeededParallel', - 'showCommentForm', - 'setHighlightedRow', - 'toggleLineDiscussions', - ]), - // Prevent text selecting on both sides of parallel diff view - // Backport of the same code from legacy diff notes. - handleParallelLineMouseDown(e) { - const line = e.currentTarget; - const table = line.closest('.diff-table'); - - table.classList.remove('left-side-selected', 'right-side-selected'); - const [lineClass] = ['left-side', 'right-side'].filter((name) => - line.classList.contains(name), - ); - - if (lineClass) { - table.classList.add(`${lineClass}-selected`); - } - }, - handleCommentButton(line) { - this.showCommentForm({ lineCode: line.line_code, fileHash: this.fileHash }); - }, - conflictText(line) { - return line.type === CONFLICT_MARKER_THEIR - ? this.$options.THEIR_CHANGES - : this.$options.OUR_CHANGES; - }, - onDragEnd() { - this.dragging = false; - if (!this.glFeatures.dragCommentSelection) return; - - this.$emit('stopdragging'); + conflictText: memoize( + (line) => { + return line.type === CONFLICT_MARKER_THEIR ? 'HEAD//our changes' : 'origin//their changes'; }, - onDragEnter(line, index) { - if (!this.glFeatures.dragCommentSelection) return; + (line) => line.type, + ), + lineContent: memoize( + (line) => { + if (line.isConflictMarker) { + return line.type === CONFLICT_MARKER_THEIR ? 'HEAD//our changes' : 'origin//their changes'; + } - this.$emit('enterdragging', { ...line, index }); - }, - onDragStart(line) { - this.$root.$emit(BV_HIDE_TOOLTIP); - this.dragging = true; - this.$emit('startdragging', line); + return line.rich_text; }, - }, - OUR_CHANGES: 'HEAD//our changes', - THEIR_CHANGES: 'origin//their changes', + (line) => line.line_code, + ), CONFLICT_MARKER, CONFLICT_MARKER_THEIR, CONFLICT_OUR, @@ -207,250 +157,256 @@ export default { }; </script> -<template> - <div :class="classNameMap" class="diff-grid-row diff-tr line_holder"> +<!-- eslint-disable-next-line vue/no-deprecated-functional-template --> +<template functional> + <div :class="$options.classNameMap(props)" class="diff-grid-row diff-tr line_holder"> <div + :id="props.line.left && props.line.left.line_code" data-testid="left-side" class="diff-grid-left left-side" - v-bind="interopLeftAttributes" + v-bind="$options.interopLeftAttributes(props)" @dragover.prevent - @dragenter="onDragEnter(line.left, index)" - @dragend="onDragEnd" + @dragenter="listeners.enterdragging({ ...props.line.left, index: props.index })" + @dragend="listeners.stopdragging" > - <template v-if="line.left && line.left.type !== $options.CONFLICT_MARKER"> + <template v-if="props.line.left && props.line.left.type !== $options.CONFLICT_MARKER"> <div - :class="classNameMapCellLeft" + :class="$options.classNameMapCellLeft(props)" data-testid="left-line-number" class="diff-td diff-line-num" data-qa-selector="new_diff_line_link" > - <template v-if="!isLeftConflictMarker"> - <span - v-if="shouldRenderCommentButton && !line.hasDiscussionsLeft" - v-gl-tooltip - class="add-diff-note tooltip-wrapper" - :title="addCommentTooltipLeft" - > - <div - data-testid="left-comment-button" - role="button" - tabindex="0" - :draggable="!line.left.commentsDisabled && glFeatures.dragCommentSelection" - type="button" - class="add-diff-note unified-diff-components-diff-note-button note-button js-add-diff-note-button" - data-qa-selector="diff_comment_button" - :class="{ 'gl-cursor-grab': dragging }" - :disabled="line.left.commentsDisabled" - :aria-disabled="line.left.commentsDisabled" - @click="!line.left.commentsDisabled && handleCommentButton(line.left)" - @keydown.enter="!line.left.commentsDisabled && handleCommentButton(line.left)" - @keydown.space="!line.left.commentsDisabled && handleCommentButton(line.left)" - @dragstart="!line.left.commentsDisabled && onDragStart({ ...line.left, index })" - ></div> - </span> - </template> + <span + v-if=" + !props.line.left.isConflictMarker && + $options.shouldRenderCommentButton(props) && + !props.line.hasDiscussionsLeft + " + class="add-diff-note tooltip-wrapper has-tooltip" + :title="props.line.left.addCommentTooltip" + > + <div + data-testid="left-comment-button" + role="button" + tabindex="0" + :draggable="!props.line.left.commentsDisabled" + type="button" + class="add-diff-note unified-diff-components-diff-note-button note-button js-add-diff-note-button" + data-qa-selector="diff_comment_button" + :disabled="props.line.left.commentsDisabled" + :aria-disabled="props.line.left.commentsDisabled" + @click=" + !props.line.left.commentsDisabled && + listeners.showCommentForm(props.line.left.line_code) + " + @keydown.enter=" + !props.line.left.commentsDisabled && + listeners.showCommentForm(props.line.left.line_code) + " + @keydown.space=" + !props.line.left.commentsDisabled && + listeners.showCommentForm(props.line.left.line_code) + " + @dragstart=" + !props.line.left.commentsDisabled && + listeners.startdragging({ + event: $event, + line: { ...props.line.left, index: props.index }, + }) + " + ></div> + </span> <a - v-if="line.left.old_line && line.left.type !== $options.CONFLICT_THEIR" - :data-linenumber="line.left.old_line" - :href="line.lineHrefOld" - @click="setHighlightedRow(line.lineCode)" + v-if="props.line.left.old_line && props.line.left.type !== $options.CONFLICT_THEIR" + :data-linenumber="props.line.left.old_line" + :href="props.line.lineHrefOld" + @click="listeners.setHighlightedRow(props.line.lineCode)" > </a> - <diff-gutter-avatars - v-if="line.hasDiscussionsLeft" - :discussions="line.left.discussions" - :discussions-expanded="line.left.discussionsExpanded" + <component + :is="$options.DiffGutterAvatars" + v-if="props.line.hasDiscussionsLeft" + :discussions="props.line.left.discussions" + :discussions-expanded="props.line.left.discussionsExpanded" data-testid="left-discussions" @toggleLineDiscussions=" - toggleLineDiscussions({ - lineCode: line.left.line_code, - fileHash, - expanded: !line.left.discussionsExpanded, + listeners.toggleLineDiscussions({ + lineCode: props.line.left.line_code, + expanded: !props.line.left.discussionsExpanded, }) " /> </div> - <div v-if="inline" :class="classNameMapCellLeft" class="diff-td diff-line-num"> + <div + v-if="props.inline" + :class="$options.classNameMapCellLeft(props)" + class="diff-td diff-line-num" + > <a - v-if="line.left.new_line && line.left.type !== $options.CONFLICT_OUR" - :data-linenumber="line.left.new_line" - :href="line.lineHrefOld" - @click="setHighlightedRow(line.lineCode)" + v-if="props.line.left.new_line && props.line.left.type !== $options.CONFLICT_OUR" + :data-linenumber="props.line.left.new_line" + :href="props.line.lineHrefOld" + @click="listeners.setHighlightedRow(props.line.lineCode)" > </a> </div> <div - v-gl-tooltip.hover - :title="coverageStateLeft.text" - :class="[...parallelViewLeftLineType, coverageStateLeft.class]" - class="diff-td line-coverage left-side" + :title="$options.coverageStateLeft(props).text" + :class="[ + $options.parallelViewLeftLineType(props), + $options.coverageStateLeft(props).class, + ]" + class="diff-td line-coverage left-side has-tooltip" ></div> - <div class="diff-td line-codequality left-side" :class="[...parallelViewLeftLineType]"> - <code-quality-gutter-icon - v-if="showCodequalityLeft" - :file-path="filePath" - :codequality="line.left.codequality" + <div + class="diff-td line-codequality left-side" + :class="$options.parallelViewLeftLineType(props)" + > + <component + :is="$options.CodeQualityGutterIcon" + v-if="$options.showCodequalityLeft(props)" + :codequality="props.line.left.codequality" + :file-path="props.filePath" /> </div> <div - :id="line.left.line_code" - :key="line.left.line_code" - :class="[parallelViewLeftLineType, { parallel: !inline }]" + :key="props.line.left.line_code" + :class="[ + $options.parallelViewLeftLineType(props), + { parallel: !props.inline, 'gl-font-weight-bold': props.line.left.isConflictMarker }, + ]" class="diff-td line_content with-coverage left-side" data-testid="left-content" - @mousedown="handleParallelLineMouseDown" - > - <strong v-if="isLeftConflictMarker">{{ conflictText(line.left) }}</strong> - <span v-else v-html="line.left.rich_text"></span> - </div> + v-html="$options.lineContent(props.line.left)" + ></div> </template> - <template v-else-if="!inline || (line.left && line.left.type === $options.CONFLICT_MARKER)"> - <div - data-testid="left-empty-cell" - class="diff-td diff-line-num old_line empty-cell" - :class="emptyCellLeftClassMap" - > + <template + v-else-if=" + !props.inline || (props.line.left && props.line.left.type === $options.CONFLICT_MARKER) + " + > + <div data-testid="left-empty-cell" class="diff-td diff-line-num old_line empty-cell"> </div> - <div - v-if="inline" - class="diff-td diff-line-num old_line empty-cell" - :class="emptyCellLeftClassMap" - ></div> - <div - class="diff-td line-coverage left-side empty-cell" - :class="emptyCellLeftClassMap" - ></div> - <div - v-if="inline" - class="diff-td line-codequality left-side empty-cell" - :class="emptyCellLeftClassMap" - ></div> + <div v-if="props.inline" class="diff-td diff-line-num old_line empty-cell"></div> + <div class="diff-td line-coverage left-side empty-cell"></div> + <div v-if="props.inline" class="diff-td line-codequality left-side empty-cell"></div> <div class="diff-td line_content with-coverage left-side empty-cell" - :class="[emptyCellLeftClassMap, { parallel: !inline }]" + :class="[{ parallel: !props.inline }]" ></div> </template> </div> <div - v-if="!inline" + v-if="!props.inline" + :id="props.line.right && props.line.right.line_code" data-testid="right-side" class="diff-grid-right right-side" - v-bind="interopRightAttributes" + v-bind="$options.interopRightAttributes(props)" @dragover.prevent - @dragenter="onDragEnter(line.right, index)" - @dragend="onDragEnd" + @dragenter="listeners.enterdragging({ ...props.line.right, index: props.index })" + @dragend="listeners.stopdragging" > - <template v-if="line.right"> - <div :class="classNameMapCellRight" class="diff-td diff-line-num new_line"> - <template v-if="line.right.type !== $options.CONFLICT_MARKER_THEIR"> + <template v-if="props.line.right"> + <div :class="$options.classNameMapCellRight(props)" class="diff-td diff-line-num new_line"> + <template v-if="props.line.right.type !== $options.CONFLICT_MARKER_THEIR"> <span - v-if="shouldRenderCommentButton && !line.hasDiscussionsRight" - v-gl-tooltip - class="add-diff-note tooltip-wrapper" - :title="addCommentTooltipRight" + v-if="$options.shouldRenderCommentButton(props) && !props.line.hasDiscussionsRight" + class="add-diff-note tooltip-wrapper has-tooltip" + :title="props.line.right.addCommentTooltip" > <div data-testid="right-comment-button" role="button" tabindex="0" - :draggable="!line.right.commentsDisabled && glFeatures.dragCommentSelection" + :draggable="!props.line.right.commentsDisabled" type="button" class="add-diff-note unified-diff-components-diff-note-button note-button js-add-diff-note-button" - :class="{ 'gl-cursor-grab': dragging }" - :disabled="line.right.commentsDisabled" - :aria-disabled="line.right.commentsDisabled" - @click="!line.right.commentsDisabled && handleCommentButton(line.right)" - @keydown.enter="!line.right.commentsDisabled && handleCommentButton(line.right)" - @keydown.space="!line.right.commentsDisabled && handleCommentButton(line.right)" - @dragstart="!line.right.commentsDisabled && onDragStart({ ...line.right, index })" + :disabled="props.line.right.commentsDisabled" + :aria-disabled="props.line.right.commentsDisabled" + @click=" + !props.line.right.commentsDisabled && + listeners.showCommentForm(props.line.right.line_code) + " + @keydown.enter=" + !props.line.right.commentsDisabled && + listeners.showCommentForm(props.line.right.line_code) + " + @keydown.space=" + !props.line.right.commentsDisabled && + listeners.showCommentForm(props.line.right.line_code) + " + @dragstart=" + !props.line.right.commentsDisabled && + listeners.startdragging({ + event: $event, + line: { ...props.line.right, index: props.index }, + }) + " ></div> </span> </template> <a - v-if="line.right.new_line" - :data-linenumber="line.right.new_line" - :href="line.lineHrefNew" - @click="setHighlightedRow(line.lineCode)" + v-if="props.line.right.new_line" + :data-linenumber="props.line.right.new_line" + :href="props.line.lineHrefNew" + @click="listeners.setHighlightedRow(props.line.lineCode)" > </a> - <diff-gutter-avatars - v-if="line.hasDiscussionsRight" - :discussions="line.right.discussions" - :discussions-expanded="line.right.discussionsExpanded" + <component + :is="$options.DiffGutterAvatars" + v-if="props.line.hasDiscussionsRight" + :discussions="props.line.right.discussions" + :discussions-expanded="props.line.right.discussionsExpanded" data-testid="right-discussions" @toggleLineDiscussions=" - toggleLineDiscussions({ - lineCode: line.right.line_code, - fileHash, - expanded: !line.right.discussionsExpanded, + listeners.toggleLineDiscussions({ + lineCode: props.line.right.line_code, + expanded: !props.line.right.discussionsExpanded, }) " /> </div> <div - v-gl-tooltip.hover - :title="coverageStateRight.text" + :title="$options.coverageStateRight(props).text" :class="[ - line.right.type, - coverageStateRight.class, - { hll: isHighlighted, hll: isCommented }, + props.line.right.type, + $options.coverageStateRight(props).class, + { hll: props.isHighlighted, hll: props.isCommented }, ]" - class="diff-td line-coverage right-side" + class="diff-td line-coverage right-side has-tooltip" ></div> <div class="diff-td line-codequality right-side" - :class="[line.right.type, { hll: isHighlighted, hll: isCommented }]" + :class="[props.line.right.type, { hll: props.isHighlighted, hll: props.isCommented }]" > - <code-quality-gutter-icon - v-if="showCodequalityRight" - :file-path="filePath" - :codequality="line.right.codequality" + <component + :is="$options.CodeQualityGutterIcon" + v-if="$options.showCodequalityRight(props)" + :codequality="props.line.right.codequality" + :file-path="props.filePath" + data-testid="codeQualityIcon" /> </div> <div - :id="line.right.line_code" - :key="line.right.rich_text" + :key="props.line.right.rich_text" :class="[ - line.right.type, + props.line.right.type, { - hll: isHighlighted, - hll: isCommented, - parallel: !inline, + hll: props.isHighlighted, + hll: props.isCommented, + 'gl-font-weight-bold': props.line.right.type === $options.CONFLICT_MARKER_THEIR, }, ]" - class="diff-td line_content with-coverage right-side" - @mousedown="handleParallelLineMouseDown" - > - <strong v-if="line.right.type === $options.CONFLICT_MARKER_THEIR">{{ - conflictText(line.right) - }}</strong> - <span v-else v-html="line.right.rich_text"></span> - </div> + class="diff-td line_content with-coverage right-side parallel" + v-html="$options.lineContent(props.line.right)" + ></div> </template> <template v-else> - <div - data-testid="right-empty-cell" - class="diff-td diff-line-num old_line empty-cell" - :class="emptyCellRightClassMap" - ></div> - <div - v-if="inline" - class="diff-td diff-line-num old_line empty-cell" - :class="emptyCellRightClassMap" - ></div> - <div - class="diff-td line-coverage right-side empty-cell" - :class="emptyCellRightClassMap" - ></div> - <div - class="diff-td line-codequality right-side empty-cell" - :class="emptyCellRightClassMap" - ></div> - <div - class="diff-td line_content with-coverage right-side empty-cell" - :class="[emptyCellRightClassMap, { parallel: !inline }]" - ></div> + <div data-testid="right-empty-cell" class="diff-td diff-line-num old_line empty-cell"></div> + <div class="diff-td line-coverage right-side empty-cell"></div> + <div class="diff-td line-codequality right-side empty-cell"></div> + <div class="diff-td line_content with-coverage right-side empty-cell parallel"></div> </template> </div> </div> |