ScrollSyncHelper.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * This class is copied from Microsoft/vscode repository
  3. * @see https://github.com/Microsoft/vscode/blob/0532a3429a18688a0c086a4212e7e5b4888b2a48/extensions/markdown/media/main.js
  4. */
  5. class ScrollSyncHelper {
  6. /**
  7. * @typedef {{ element: Element, line: number }} CodeLineElement
  8. */
  9. constructor() {
  10. }
  11. getCodeLineElements(parentElement) {
  12. /** @type {CodeLineElement[]} */
  13. let elements;
  14. if (!elements) {
  15. elements = Array.prototype.map.call(
  16. parentElement.getElementsByClassName('code-line'),
  17. element => {
  18. const line = +element.getAttribute('data-line');
  19. return { element, line }
  20. })
  21. .filter(x => !isNaN(x.line));
  22. }
  23. return elements;
  24. }
  25. /**
  26. * Find the html elements that map to a specific target line in the editor.
  27. *
  28. * If an exact match, returns a single element. If the line is between elements,
  29. * returns the element prior to and the element after the given line.
  30. *
  31. * @param {Element} parentElement
  32. * @param {number} targetLine
  33. *
  34. * @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
  35. */
  36. getElementsForSourceLine(parentElement, targetLine) {
  37. const lines = this.getCodeLineElements(parentElement);
  38. let previous = lines[0] || null;
  39. for (const entry of lines) {
  40. if (entry.line === targetLine) {
  41. return { previous: entry, next: null };
  42. } else if (entry.line > targetLine) {
  43. return { previous, next: entry };
  44. }
  45. previous = entry;
  46. }
  47. return { previous };
  48. }
  49. getParentElementOffset(parentElement) {
  50. // get paddingTop
  51. const style = window.getComputedStyle(parentElement, null);
  52. const paddingTop = +(style.paddingTop.replace('px', ''));
  53. return paddingTop + parentElement.getBoundingClientRect().top;
  54. }
  55. /**
  56. * Attempt to reveal the element for a source line in the editor.
  57. *
  58. * @param {Element} element
  59. * @param {number} line
  60. */
  61. scrollToRevealSourceLine(element, line) {
  62. const { previous, next } = this.getElementsForSourceLine(element, line);
  63. // marker.update(previous && previous.element);
  64. if (previous) {
  65. let scrollTo = 0;
  66. if (next) {
  67. // Between two elements. Go to percentage offset between them.
  68. const betweenProgress = (line - previous.line) / (next.line - previous.line);
  69. const elementOffset = next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top;
  70. scrollTo = previous.element.getBoundingClientRect().top + betweenProgress * elementOffset;
  71. } else {
  72. scrollTo = previous.element.getBoundingClientRect().top;
  73. }
  74. scrollTo -= this.getParentElementOffset(element);
  75. element.scroll(0, element.scrollTop + scrollTo);
  76. }
  77. }
  78. /**
  79. * Attempt to reveal the element that is overflowing from parent element.
  80. *
  81. * @param {Element} element
  82. * @param {number} line
  83. */
  84. scrollToRevealOverflowingSourceLine(element, line) {
  85. const { previous, next } = this.getElementsForSourceLine(element, line);
  86. // marker.update(previous && previous.element);
  87. if (previous) {
  88. const parentElementOffset = this.getParentElementOffset(element);
  89. const prevElmTop = previous.element.getBoundingClientRect().top - parentElementOffset;
  90. const prevElmBottom = previous.element.getBoundingClientRect().bottom - parentElementOffset;
  91. if (prevElmTop < 0) {
  92. // set the top of 'previous.element' to the top of 'element'
  93. const scrollTo = element.scrollTop + prevElmTop;
  94. element.scroll(0, scrollTo);
  95. }
  96. if (prevElmBottom > element.clientHeight) {
  97. // set the bottom of 'previous.element' to the bottom of 'element'
  98. const scrollTo = element.scrollTop + prevElmBottom - element.clientHeight + 20;
  99. element.scroll(0, scrollTo);
  100. }
  101. }
  102. }
  103. }
  104. // singleton pattern
  105. const instance = new ScrollSyncHelper();
  106. Object.freeze(instance);
  107. export default instance;