196 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| import $ from 'jquery';
 | |
| import { merge } from 'lodash';
 | |
| import createFlash from '~/flash';
 | |
| import axios from '~/lib/utils/axios_utils';
 | |
| import { __ } from '~/locale';
 | |
| import FilesCommentButton from './files_comment_button';
 | |
| import initImageDiffHelper from './image_diff/helpers/init_image_diff';
 | |
| import { getLocationHash } from './lib/utils/url_utility';
 | |
| import SingleFileDiff from './single_file_diff';
 | |
| 
 | |
| const UNFOLD_COUNT = 20;
 | |
| let isBound = false;
 | |
| 
 | |
| export default class Diff {
 | |
|   constructor() {
 | |
|     const $diffFile = $('.files .diff-file');
 | |
| 
 | |
|     $diffFile.each((index, file) => {
 | |
|       if (!$.data(file, 'singleFileDiff')) {
 | |
|         $.data(file, 'singleFileDiff', new SingleFileDiff(file));
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     const tab = document.getElementById('diffs');
 | |
|     if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== ''))
 | |
|       FilesCommentButton.init($diffFile);
 | |
| 
 | |
|     const firstFile = $('.files').first().get(0);
 | |
|     const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note');
 | |
|     $diffFile.each((index, file) => initImageDiffHelper.initImageDiff(file, canCreateNote));
 | |
| 
 | |
|     if (!isBound) {
 | |
|       $(document)
 | |
|         .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
 | |
|         .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this))
 | |
|         .on('mousedown', 'td.line_content.parallel', this.handleParallelLineDown.bind(this));
 | |
|       isBound = true;
 | |
|     }
 | |
| 
 | |
|     if (getLocationHash()) {
 | |
|       this.highlightSelectedLine();
 | |
|     }
 | |
| 
 | |
|     this.openAnchoredDiff();
 | |
| 
 | |
|     this.prepareRenderedDiff();
 | |
|   }
 | |
| 
 | |
|   handleClickUnfold(e) {
 | |
|     const $target = $(e.target);
 | |
|     const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent());
 | |
|     const offset = newLineNumber - oldLineNumber;
 | |
|     const bottom = $target.hasClass('js-unfold-bottom');
 | |
|     let since;
 | |
|     let to;
 | |
|     let unfold = true;
 | |
| 
 | |
|     if (bottom) {
 | |
|       const lineNumber = newLineNumber + 1;
 | |
|       since = lineNumber;
 | |
|       to = lineNumber + UNFOLD_COUNT;
 | |
|     } else {
 | |
|       const lineNumber = newLineNumber - 1;
 | |
|       since = lineNumber - UNFOLD_COUNT;
 | |
|       to = lineNumber;
 | |
| 
 | |
|       // make sure we aren't loading more than we need
 | |
|       const prevNewLine = this.lineNumbers($target.parent().prev())[1];
 | |
|       if (since <= prevNewLine + 1) {
 | |
|         since = prevNewLine + 1;
 | |
|         unfold = false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const file = $target.parents('.diff-file');
 | |
|     const link = file.data('blobDiffPath');
 | |
|     const view = file.data('view');
 | |
| 
 | |
|     const params = { since, to, bottom, offset, unfold, view };
 | |
|     axios
 | |
|       .get(link, { params })
 | |
|       .then(({ data }) => $target.parent().replaceWith(data))
 | |
|       .catch(() =>
 | |
|         createFlash({
 | |
|           message: __('An error occurred while loading diff'),
 | |
|         }),
 | |
|       );
 | |
|   }
 | |
| 
 | |
|   openAnchoredDiff(cb) {
 | |
|     const locationHash = getLocationHash();
 | |
|     const anchoredDiff = locationHash && locationHash.split('_')[0];
 | |
| 
 | |
|     if (!anchoredDiff) return;
 | |
| 
 | |
|     const diffTitle = $(`#${anchoredDiff}`);
 | |
|     const diffFile = diffTitle.closest('.diff-file');
 | |
|     const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
 | |
|     if (nothingHereBlock.length) {
 | |
|       const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
 | |
|       diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
 | |
|         this.highlightSelectedLine();
 | |
|         if (cb) cb();
 | |
|       });
 | |
|     } else if (cb) {
 | |
|       cb();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   handleClickLineNum(e) {
 | |
|     const hash = $(e.currentTarget).attr('href');
 | |
|     e.preventDefault();
 | |
|     if (window.history.pushState) {
 | |
|       window.history.pushState(null, null, hash);
 | |
|     } else {
 | |
|       window.location.hash = hash;
 | |
|     }
 | |
|     this.highlightSelectedLine();
 | |
|   }
 | |
|   // eslint-disable-next-line class-methods-use-this
 | |
|   handleParallelLineDown(e) {
 | |
|     const line = $(e.currentTarget);
 | |
|     const table = line.closest('table');
 | |
| 
 | |
|     table.removeClass('left-side-selected right-side-selected');
 | |
| 
 | |
|     const lineClass = ['left-side', 'right-side'].filter((name) => line.hasClass(name))[0];
 | |
|     if (lineClass) {
 | |
|       table.addClass(`${lineClass}-selected`);
 | |
|     }
 | |
|   }
 | |
|   // eslint-disable-next-line class-methods-use-this
 | |
|   diffViewType() {
 | |
|     return $('.inline-parallel-buttons a.active').data('viewType');
 | |
|   }
 | |
|   // eslint-disable-next-line class-methods-use-this
 | |
|   lineNumbers(line) {
 | |
|     const children = line.find('.diff-line-num').toArray();
 | |
|     if (children.length !== 2) {
 | |
|       return [0, 0];
 | |
|     }
 | |
|     return children.map((elm) => parseInt($(elm).data('linenumber'), 10) || 0);
 | |
|   }
 | |
|   // eslint-disable-next-line class-methods-use-this
 | |
|   highlightSelectedLine() {
 | |
|     const hash = getLocationHash();
 | |
|     const $diffFiles = $('.diff-file');
 | |
|     $diffFiles.find('.hll').removeClass('hll');
 | |
| 
 | |
|     if (hash) {
 | |
|       $diffFiles
 | |
|         .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
 | |
|         .addClass('hll');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   prepareRenderedDiff() {
 | |
|     const $elements = $('[data-diff-toggle-entity]');
 | |
| 
 | |
|     if ($elements.length === 0) return;
 | |
| 
 | |
|     const diff = this;
 | |
| 
 | |
|     const elements = $elements.toArray().map(this.formatElementToObject).reduce(merge);
 | |
| 
 | |
|     Object.values(elements).forEach((e) => {
 | |
|       e.toShowBtn.onclick = () => diff.showOneHideAnother('rendered', e); // eslint-disable-line no-param-reassign
 | |
|       e.toHideBtn.onclick = () => diff.showOneHideAnother('raw', e); // eslint-disable-line no-param-reassign
 | |
| 
 | |
|       diff.showOneHideAnother('rendered', e);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   formatElementToObject = (element) => {
 | |
|     const key = element.attributes['data-file-hash'].value;
 | |
|     const name = element.attributes['data-diff-toggle-entity'].value;
 | |
| 
 | |
|     return { [key]: { [name]: element } };
 | |
|   };
 | |
| 
 | |
|   showOneHideAnother = (mode, elements) => {
 | |
|     let { toShowBtn, toHideBtn, toShow, toHide } = elements;
 | |
| 
 | |
|     if (mode === 'raw') {
 | |
|       [toShowBtn, toHideBtn] = [toHideBtn, toShowBtn];
 | |
|       [toShow, toHide] = [toHide, toShow];
 | |
|     }
 | |
| 
 | |
|     toShowBtn.classList.add('selected');
 | |
|     toHideBtn.classList.remove('selected');
 | |
| 
 | |
|     toHide.classList.add('hidden');
 | |
|     toShow.classList.remove('hidden');
 | |
|   };
 | |
| }
 |