import './show-whitespace.css'; import React from 'dom-chef'; import select from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '.'; import getTextNodes from '../helpers/get-text-nodes'; import onPrFileLoad from '../github-events/on-pr-file-load'; import onNewComments from '../github-events/on-new-comments'; // `splitText` is used before and after each whitespace group so a new whitespace-only text node is created. This new node is then wrapped in a function showWhiteSpacesOn(line: Element): void { const shouldAvoidSurroundingSpaces = Boolean(line.closest('.blob-wrapper-embedded')); // #2285 const textNodesOnThisLine = getTextNodes(line); for (const [nodeIndex, textNode] of textNodesOnThisLine.entries()) { // `textContent` reads must be cached #2737 let text = textNode.textContent!; const startingCharacter = shouldAvoidSurroundingSpaces && nodeIndex === 0 ? 1 : 0; const skipLastCharacter = shouldAvoidSurroundingSpaces && nodeIndex === textNodesOnThisLine.length - 1; const endingCharacter = text.length - 1 - Number(skipLastCharacter); // Loop goes in reverse otherwise `splitText`'s `index` parameter needs to keep track of the previous split for (let i = endingCharacter; i >= startingCharacter; i--) { const thisCharacter = text[i]; // Exclude irrelevant characters if (thisCharacter !== ' ' && thisCharacter !== '\t') { continue; } if (i < text.length - 1) { textNode.splitText(i + 1); } // Find the same character so they can be wrapped together, but stop at `startingCharacter` while (text[i - 1] === thisCharacter && !(i === startingCharacter)) { i--; } textNode.splitText(i); // Update cached variable here because it just changed text = textNode.textContent!; textNode.after( {textNode.nextSibling} ); } } } const viewportObserver = new IntersectionObserver(changes => { for (const change of changes) { if (change.isIntersecting) { showWhiteSpacesOn(change.target); viewportObserver.unobserve(change.target); } } }); function init(): void { for (const line of select.all('.blob-code-inner:not(.rgh-observing-whitespace)')) { line.classList.add('rgh-observing-whitespace'); viewportObserver.observe(line); } } void features.add(__filebasename, { include: [ pageDetect.hasCode ], additionalListeners: [ onNewComments, onPrFileLoad ], init });