| // @ts-check |
| |
| /** |
| * @typedef {{ attr: string, title: string }} FerrisType |
| */ |
| |
| /** @type {Array<FerrisType>} */ |
| const FERRIS_TYPES = [ |
| { |
| attr: 'does_not_compile', |
| title: 'This code does not compile!' |
| }, |
| { |
| attr: 'panics', |
| title: 'This code panics!' |
| }, |
| { |
| attr: 'not_desired_behavior', |
| title: 'This code does not produce the desired behavior.' |
| } |
| ] |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| for (let ferrisType of FERRIS_TYPES) { |
| attachFerrises(ferrisType) |
| } |
| }) |
| |
| /** |
| * @param {FerrisType} type |
| */ |
| function attachFerrises(type) { |
| let elements = document.getElementsByClassName(type.attr) |
| |
| for (let codeBlock of elements) { |
| // Skip SVG etc.: in principle, these should never be attached to those, but |
| // this means if someone happens to have a browser extension which *is* |
| // attaching them, it will not break the code. |
| if (!(codeBlock instanceof HTMLElement)) { |
| continue |
| } |
| |
| let lines = codeBlock.innerText.replace(/\n$/, '').split(/\n/).length |
| |
| /** @type {'small' | 'large'} */ |
| let size = lines < 4 ? 'small' : 'large' |
| |
| let container = prepareFerrisContainer(codeBlock, size == 'small') |
| if (!container) { |
| continue |
| } |
| |
| container.appendChild(createFerris(type, size)) |
| } |
| } |
| |
| /** |
| * @param {HTMLElement} element - Code block element to attach a Ferris to. |
| * @param {boolean} useButtons - Whether to attach to existing buttons. |
| * @returns {Element | null} - The container element to use. |
| */ |
| function prepareFerrisContainer(element, useButtons) { |
| let foundButtons = element.parentElement?.querySelector('.buttons') |
| if (useButtons && foundButtons) { |
| return foundButtons |
| } |
| |
| let div = document.createElement('div') |
| div.classList.add('ferris-container') |
| |
| if (!element.parentElement) { |
| console.error(`Could not install Ferris on ${element}, which is missing a parent`); |
| return null; |
| } |
| |
| element.parentElement.insertBefore(div, element) |
| |
| return div |
| } |
| |
| /** |
| * @param {FerrisType} type |
| * @param {'small' | 'large'} size |
| * @returns {HTMLAnchorElement} - The generated anchor element. |
| */ |
| function createFerris(type, size) { |
| let a = document.createElement('a') |
| a.setAttribute('href', 'ch00-00-introduction.html#ferris') |
| a.setAttribute('target', '_blank') |
| |
| let img = document.createElement('img') |
| img.setAttribute('src', 'img/ferris/' + type.attr + '.svg') |
| img.setAttribute('title', type.title) |
| img.classList.add('ferris') |
| img.classList.add('ferris-' + size) |
| |
| a.appendChild(img) |
| |
| return a |
| } |