Welcome to mirror list, hosted at ThFree Co, Russian Federation.

source_editor_extension_base.js « extensions « editor « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 03c68fed3b183d46390ee4d8b483f7f8410ff69c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { Range } from 'monaco-editor';
import { waitForCSSLoaded } from '~/helpers/startup_css_helper';
import { ERROR_INSTANCE_REQUIRED_FOR_EXTENSION, EDITOR_TYPE_CODE } from '../constants';

const hashRegexp = new RegExp('#?L', 'g');

const createAnchor = (href) => {
  const fragment = new DocumentFragment();
  const el = document.createElement('a');
  el.classList.add('link-anchor');
  el.href = href;
  fragment.appendChild(el);
  el.addEventListener('contextmenu', (e) => {
    e.stopPropagation();
  });
  return fragment;
};

export class SourceEditorExtension {
  constructor({ instance, ...options } = {}) {
    if (instance) {
      Object.assign(instance, options);
      SourceEditorExtension.highlightLines(instance);
      if (instance.getEditorType && instance.getEditorType() === EDITOR_TYPE_CODE) {
        SourceEditorExtension.setupLineLinking(instance);
      }
      SourceEditorExtension.deferRerender(instance);
    } else if (Object.entries(options).length) {
      throw new Error(ERROR_INSTANCE_REQUIRED_FOR_EXTENSION);
    }
  }

  static deferRerender(instance) {
    waitForCSSLoaded(() => {
      instance.layout();
    });
  }

  static removeHighlights(instance) {
    Object.assign(instance, {
      lineDecorations: instance.deltaDecorations(instance.lineDecorations || [], []),
    });
  }

  /**
   * Returns a function that can only be invoked once between
   * each browser screen repaint.
   * @param {Object} instance - The Source Editor instance
   * @param {Array} bounds - The [start, end] array with start
   * and end coordinates for highlighting
   */
  static highlightLines(instance, bounds = null) {
    const [start, end] =
      bounds && Array.isArray(bounds)
        ? bounds
        : window.location.hash?.replace(hashRegexp, '').split('-');
    let startLine = start ? parseInt(start, 10) : null;
    let endLine = end ? parseInt(end, 10) : startLine;
    if (endLine < startLine) {
      [startLine, endLine] = [endLine, startLine];
    }
    if (startLine) {
      window.requestAnimationFrame(() => {
        instance.revealLineInCenter(startLine);
        Object.assign(instance, {
          lineDecorations: instance.deltaDecorations(instance.lineDecorations || [], [
            {
              range: new Range(startLine, 1, endLine, 1),
              options: { isWholeLine: true, className: 'active-line-text' },
            },
          ]),
        });
      });
    }
  }

  static onMouseMoveHandler(e) {
    const target = e.target.element;
    if (target.classList.contains('line-numbers')) {
      const lineNum = e.target.position.lineNumber;
      const hrefAttr = `#L${lineNum}`;
      let el = target.querySelector('a');
      if (!el) {
        el = createAnchor(hrefAttr);
        target.appendChild(el);
      }
    }
  }

  static setupLineLinking(instance) {
    instance.onMouseMove(SourceEditorExtension.onMouseMoveHandler);
    instance.onMouseDown((e) => {
      const isCorrectAnchor = e.target.element.classList.contains('link-anchor');
      if (!isCorrectAnchor) {
        return;
      }
      if (instance.lineDecorations) {
        instance.deltaDecorations(instance.lineDecorations, []);
      }
    });
  }
}