diff --git a/.eslintrc.json b/.eslintrc.json index dcba84eef..91b96b8d1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,7 +18,7 @@ "sourceType": "module" }, "rules": { - "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unused-expressions": "error" }, "settings": { diff --git a/.gitignore b/.gitignore index 25fd1d63e..8418ee80c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,6 @@ typings/ # Built TypeScript files lib/*.js -webpack.config.js +lib/*.js.map +editor/**/*.js +editor/**/*.js.map diff --git a/.prettierignore b/.prettierignore index 02bf4afda..45a55222f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,5 +6,5 @@ node_modules # This can be removed after https://github.com/googleapis/release-please/issues/1802 is fixed CHANGELOG.md # TSC output: -webpack.config.js lib/*.js +editor/js/**/*.js diff --git a/editor/js/editable-css.js b/editor/js/editable-css.ts similarity index 66% rename from editor/js/editable-css.js rename to editor/js/editable-css.ts index 0e7d3c5c3..46e97d946 100644 --- a/editor/js/editable-css.js +++ b/editor/js/editable-css.ts @@ -11,20 +11,29 @@ import "../css/editor-libs/common.css"; import "../css/editable-css.css"; (function () { - const exampleChoiceList = document.getElementById("example-choice-list"); - const exampleChoices = exampleChoiceList.querySelectorAll(".example-choice"); + const exampleChoiceList = document.getElementById( + "example-choice-list", + ) as HTMLElement; + + const exampleChoices = exampleChoiceList.querySelectorAll( + ".example-choice", + ) as NodeListOf; const exampleDeclarations = Array.from( exampleChoices, - (choice) => choice.querySelector("code").textContent, + (choice) => choice.querySelector("code")?.textContent, ); - const editorWrapper = document.getElementById("editor-wrapper"); - const output = document.getElementById("output"); - const warningNoSupport = document.getElementById("warning-no-support"); - - const originalChoices = []; + const editorWrapper = document.getElementById( + "editor-wrapper", + ) as HTMLElement; + const output = document.getElementById("output") as HTMLElement; + const warningNoSupport = document.getElementById( + "warning-no-support", + ) as HTMLElement; + + const originalChoices: string[] = []; let initialChoice = 0; - function applyCodeMirror(target, code) { + function applyCodeMirror(target: HTMLElement, code: string) { return initCodeEditor(target, code, languageCSS(), { lineNumbers: false, }); @@ -42,14 +51,16 @@ import "../css/editable-css.css"; const exampleChoice = exampleChoices[i]; const choiceButton = document.createElement("button"); const choiceButtonText = document.createElement("span"); - const choiceCode = exampleChoice.querySelector("code"); - const copyButton = exampleChoice.getElementsByClassName("copy")[0]; + const choiceCode = exampleChoice.querySelector("code") as HTMLElement; + const copyButton = exampleChoice.getElementsByClassName( + "copy", + )[0] as HTMLButtonElement; - originalChoices.push(choiceCode.textContent); + originalChoices.push(choiceCode.textContent || ""); const codeMirrorEditor = applyCodeMirror( - exampleChoice.querySelector("pre"), - choiceCode.textContent, + exampleChoice.querySelector("pre") as HTMLElement, + choiceCode.textContent || "", ); choiceButton.setAttribute("type", "button"); @@ -82,11 +93,12 @@ import "../css/editable-css.css"; * reset all the CSS examples to their original state */ function handleResetEvents() { - const resetButton = document.getElementById("reset"); + const resetButton = document.getElementById("reset") as HTMLElement; resetButton.addEventListener("click", () => { exampleChoices.forEach((e, i) => { - const preEl = e.querySelector("pre"); + const preEl = e.querySelector("pre") as HTMLElement; + // Remove original codemirror for (const e of preEl.children) { e.remove(); @@ -100,16 +112,16 @@ import "../css/editable-css.css"; // if there is an initial choice set, set it as selected if (initialChoice) { - mceEvents.onChoose(exampleChoices[initialChoice]); - clippy.toggleClippy(exampleChoices[initialChoice]); + mceEvents.onChoose(exampleChoices[initialChoice] as HTMLElement); + clippy.toggleClippy(exampleChoices[initialChoice] as HTMLElement); } else { - mceEvents.onChoose(exampleChoices[0]); - clippy.toggleClippy(exampleChoices[0]); + mceEvents.onChoose(exampleChoices[0] as HTMLElement); + clippy.toggleClippy(exampleChoices[0] as HTMLElement); } }); } - function indexOf(exampleChoices, choice) { + function indexOf(exampleChoices: NodeListOf, choice: Element) { for (let i = 0, l = exampleChoices.length; i < l; i++) { if (exampleChoices[i] === choice) { return i; @@ -126,14 +138,15 @@ import "../css/editable-css.css"; function handleChoiceHover() { for (let i = 0, l = exampleChoices.length; i < l; i++) { const choice = exampleChoices[i]; - const copyBtn = choice.querySelector(".copy"); + const copyBtn = choice.querySelector(".copy") as HTMLElement; + copyBtn.setAttribute("aria-label", "Copy to clipboard"); choice.addEventListener("mouseover", () => { - copyBtn.setAttribute("aria-hidden", false); + copyBtn.setAttribute("aria-hidden", "false"); }); choice.addEventListener("mouseout", () => { - copyBtn.setAttribute("aria-hidden", true); + copyBtn.setAttribute("aria-hidden", "true"); }); } } @@ -141,8 +154,8 @@ import "../css/editable-css.css"; /* only show the live code view if JS is enabled and the property is supported. */ if (cssEditorUtils.isAnyDeclarationSetSupported(exampleDeclarations)) { enableLiveEditor(); - mceEvents.onChoose(exampleChoices[initialChoice]); - clippy.toggleClippy(exampleChoices[initialChoice]); + mceEvents.onChoose(exampleChoices[initialChoice] as HTMLElement); + clippy.toggleClippy(exampleChoices[initialChoice] as HTMLElement); } else { warningNoSupport.classList.remove("hidden"); } diff --git a/editor/js/editable-js.js b/editor/js/editable-js.ts similarity index 63% rename from editor/js/editable-js.js rename to editor/js/editable-js.ts index 38f5b9f6b..294cd69ec 100644 --- a/editor/js/editable-js.js +++ b/editor/js/editable-js.ts @@ -1,3 +1,4 @@ +import type { EditorView } from "codemirror"; import * as featureDetector from "./editor-libs/feature-detector.js"; import mceConsole from "./editor-libs/console.js"; import * as mceEvents from "./editor-libs/events.js"; @@ -12,15 +13,16 @@ import { } from "./editor-libs/codemirror-editor.js"; (function () { - const codeBlock = document.getElementById("static-js"); + const codeBlock = document.getElementById("static-js") as HTMLElement; + const exampleFeature = codeBlock.dataset["feature"]; - const execute = document.getElementById("execute"); - const output = document.querySelector("#console code"); - const reset = document.getElementById("reset"); + const execute = document.getElementById("execute") as HTMLElement; + const output = document.querySelector("#console code") as HTMLElement; + const reset = document.getElementById("reset") as HTMLElement; - let codeMirror; + let codeMirror: EditorView | null; let staticContainer; - let liveContainer = ""; + let liveContainer; /** * Reads the textContent from the interactiveCodeBlock, sends the @@ -28,7 +30,11 @@ import { * output container */ function applyCode() { - const currentValue = getEditorContent(codeMirror); + if (!codeMirror) { + initCodeMirror(); + // "as EditorView" on next line needed to trick TypeScript + } + const currentValue = getEditorContent(codeMirror as EditorView); updateOutput(currentValue); } @@ -36,11 +42,11 @@ import { * Initialize CodeMirror */ function initCodeMirror() { - const editorContainer = document.getElementById("editor"); + const editorContainer = document.getElementById("editor") as HTMLElement; codeMirror = initCodeEditor( editorContainer, - codeBlock.textContent, + codeBlock.textContent || "", languageJavaScript(), ); } @@ -51,15 +57,15 @@ import { function initInteractiveEditor() { /* If the `data-height` attribute is defined on the `codeBlock`, set the value of this attribute as a class on the editor element. */ - if (codeBlock.dataset["height"]) { - const editor = document.getElementById("editor"); + if (codeBlock?.dataset["height"]) { + const editor = document.getElementById("editor") as HTMLElement; editor.classList.add(codeBlock.dataset["height"]); } - staticContainer = document.getElementById("static"); + staticContainer = document.getElementById("static") as HTMLElement; staticContainer.classList.add("hidden"); - liveContainer = document.getElementById("live"); + liveContainer = document.getElementById("live") as HTMLElement; liveContainer.classList.remove("hidden"); mceConsole(); @@ -73,14 +79,14 @@ import { * to the output container. * @param {String} exampleCode - The code to execute */ - function updateOutput(exampleCode) { + function updateOutput(exampleCode: string) { output.classList.add("fade-in"); try { // Create a new Function from the code, and immediately execute it. new Function(exampleCode)(); - } catch (event) { - output.textContent = "Error: " + event.message; + } catch (event: unknown) { + output.textContent = "Error: " + (event as Error)?.message; } output.addEventListener("animationend", () => @@ -88,6 +94,7 @@ import { ); } + /* only execute code in supported browsers */ if (featureDetector.isDefined(exampleFeature)) { document.documentElement.classList.add("js"); @@ -99,5 +106,9 @@ import { }); reset.addEventListener("click", () => window.location.reload()); + } else { + console.warn( + `Feature ${exampleFeature} is not supported; code editor disabled.`, + ); } })(); diff --git a/editor/js/editable-wat.js b/editor/js/editable-wat.ts similarity index 67% rename from editor/js/editable-wat.js rename to editor/js/editable-wat.ts index 3fccdfcca..c31d1e2b5 100644 --- a/editor/js/editable-wat.js +++ b/editor/js/editable-wat.ts @@ -1,3 +1,4 @@ +import type { EditorView } from "codemirror"; import * as featureDetector from "./editor-libs/feature-detector.js"; import mceConsole from "./editor-libs/console.js"; import * as mceEvents from "./editor-libs/events.js"; @@ -15,21 +16,22 @@ import { } from "./editor-libs/codemirror-editor.js"; (async function () { - const watCodeBlock = document.getElementById("static-wat"); - const jsCodeBlock = document.getElementById("static-js"); + const watCodeBlock = document.getElementById("static-wat") as HTMLElement; + const jsCodeBlock = document.getElementById("static-js") as HTMLElement; const exampleFeature = watCodeBlock.dataset["feature"]; - const execute = document.getElementById("execute"); - const output = document.querySelector("#console code"); - const reset = document.getElementById("reset"); + const execute = document.getElementById("execute") as HTMLElement; + const output = document.querySelector("#console code") as HTMLElement; + const reset = document.getElementById("reset") as HTMLElement; const wabt = await wabtConstructor(); - const tabContainer = document.getElementById("tab-container"); - const tabs = tabContainer.querySelectorAll("button[role='tab']"); - const tabList = document.getElementById("tablist"); + const tabContainer = document.getElementById("tab-container") as HTMLElement; + const tabs = + tabContainer.querySelectorAll("button[role='tab']"); + const tabList = document.getElementById("tablist") as HTMLElement; - let watCodeMirror; - let jsCodeMirror; - let liveContainer = ""; + let watCodeMirror: EditorView | null; + let jsCodeMirror: EditorView | null; + let liveContainer; let staticContainer; /** @@ -47,21 +49,22 @@ import { function registerEventListeners() { tabList.addEventListener("click", (event) => { - const eventTarget = event.target; + const eventTarget = event.target as typeof tabList; const role = eventTarget.getAttribute("role"); if (role === "tab") { - const activeTab = tabList.querySelector("button[aria-selected='true']"); - const selectedPanel = document.getElementById( - eventTarget.getAttribute("aria-controls"), - ); + const activeTab = tabList.querySelector( + "button[aria-selected='true']", + ) as HTMLElement; + const controls = eventTarget.getAttribute("aria-controls") || ""; + const selectedPanel = document.getElementById(controls) as HTMLElement; hideTabPanels(); setActiveTab(eventTarget, activeTab); // now show the selected tabpanel selectedPanel.classList.remove("hidden"); - selectedPanel.setAttribute("aria-hidden", false); + selectedPanel.setAttribute("aria-hidden", "false"); } }); @@ -94,15 +97,15 @@ import { * @param {Object} nextActiveTab - The tab to activate * @param {Object} [activeTab] - The current active tab */ - function setActiveTab(nextActiveTab, activeTab) { + function setActiveTab(nextActiveTab: HTMLElement, activeTab?: HTMLElement) { if (activeTab) { // set the currentSelectedTab to false - activeTab.setAttribute("aria-selected", false); - activeTab.setAttribute("tabindex", -1); + activeTab.setAttribute("aria-selected", "false"); + activeTab.setAttribute("tabindex", "-1"); } // set the activated tab to selected - nextActiveTab.setAttribute("aria-selected", true); + nextActiveTab.setAttribute("aria-selected", "true"); nextActiveTab.removeAttribute("tabindex"); nextActiveTab.focus(); } @@ -113,8 +116,10 @@ import { * @param {String} direction - The direction in which to move tab focus * Must be either forward, or reverse. */ - function setNextActiveTab(direction) { - const activeTab = tabList.querySelector("button[aria-selected='true']"); + function setNextActiveTab(direction: string) { + const activeTab = tabList.querySelector( + "button[aria-selected='true']", + ) as HTMLElement; // if the direction specified is not valid, simply return if (direction !== "forward" && direction !== "reverse") { @@ -122,7 +127,7 @@ import { } if (direction === "forward") { - if (activeTab.nextElementSibling) { + if (activeTab.nextElementSibling instanceof HTMLElement) { setActiveTab(activeTab.nextElementSibling, activeTab); activeTab.nextElementSibling.click(); } else { @@ -131,7 +136,7 @@ import { tabs[0].click(); } } else if (direction === "reverse") { - if (activeTab.previousElementSibling) { + if (activeTab.previousElementSibling instanceof HTMLElement) { setActiveTab(activeTab.previousElementSibling, activeTab); activeTab.previousElementSibling.click(); } else { @@ -148,8 +153,12 @@ import { * output container */ function applyCode() { - const wat = getEditorContent(watCodeMirror); - const js = getEditorContent(jsCodeMirror); + if (!(watCodeMirror && jsCodeMirror)) { + initCodeMirror(); + // "as EditorView" on next lines needed to trick TypeScript + } + const wat = getEditorContent(watCodeMirror as EditorView); + const js = getEditorContent(jsCodeMirror as EditorView); updateOutput(wat, js); } @@ -157,17 +166,17 @@ import { * Initialize CodeMirror */ function initCodeMirror() { - const watContainer = document.getElementById("wat-editor"); + const watContainer = document.getElementById("wat-editor") as HTMLElement; watCodeMirror = initCodeEditor( watContainer, - watCodeBlock.textContent, + watCodeBlock.textContent || "", languageWAST(), ); - const jsContainer = document.getElementById("js-editor"); + const jsContainer = document.getElementById("js-editor") as HTMLElement; jsCodeMirror = initCodeEditor( jsContainer, - jsCodeBlock.textContent, + jsCodeBlock.textContent || "", languageJavaScript(), ); } @@ -179,16 +188,16 @@ import { /* If the `data-height` attribute is defined on the `codeBlock`, set the value of this attribute as a class on the editor element. */ if (watCodeBlock.dataset["height"]) { - const watEditor = document.getElementById("wat-panel"); + const watEditor = document.getElementById("wat-panel") as HTMLElement; watEditor.classList.add(watCodeBlock.dataset["height"]); - const jsEditor = document.getElementById("js-panel"); + const jsEditor = document.getElementById("js-panel") as HTMLElement; jsEditor.classList.add(watCodeBlock.dataset["height"]); } - staticContainer = document.getElementById("static"); + staticContainer = document.getElementById("static") as HTMLElement; staticContainer.classList.add("hidden"); - liveContainer = document.getElementById("live"); + liveContainer = document.getElementById("live") as HTMLElement; liveContainer.classList.remove("hidden"); mceConsole(); @@ -204,7 +213,7 @@ import { * @param {string} wat * @returns {Blob} a blob with the newly created wasm module */ - async function compileWat(wat) { + async function compileWat(wat: string): Promise { const encoder = new TextEncoder(); const watBuffer = encoder.encode(wat); const module = wabt.parseWat("", watBuffer, { @@ -231,7 +240,7 @@ import { * @param {String} wat - The wat code to execute * @param {String} js - The JavaScript code to execute */ - async function updateOutput(wat, js) { + async function updateOutput(wat: string, js: string) { output.classList.add("fade-in"); try { @@ -244,8 +253,10 @@ import { // Create an new async function from the code, and immediately execute it. // using an async function since WebAssembly.instantiate is async and // we need to await in order to capture errors - // eslint-disable-next-line @typescript-eslint/no-empty-function - const AsyncFunction = async function () {}.constructor; + const AsyncFunction = Object.getPrototypeOf( + // eslint-disable-next-line @typescript-eslint/no-empty-function + async function () {}, + ).constructor; await new AsyncFunction(exampleCode)(); } catch (error) { console.error(error); @@ -256,9 +267,7 @@ import { ); } - /* only execute JS in supported browsers. As `document.all` - is a non standard object available only in IE10 and older, - this will stop JS from executing in those versions. */ + /* only execute code in supported browsers */ if ("WebAssembly" in window && featureDetector.isDefined(exampleFeature)) { document.documentElement.classList.add("wat"); @@ -270,5 +279,11 @@ import { }); reset.addEventListener("click", () => window.location.reload()); + } else { + console.warn( + `Feature ${ + "WebAssembly" in window ? exampleFeature : "WebAssembly" + } is not supported; code editor disabled.`, + ); } })(); diff --git a/editor/js/editor-libs/clippy.js b/editor/js/editor-libs/clippy.ts similarity index 80% rename from editor/js/editor-libs/clippy.js rename to editor/js/editor-libs/clippy.ts index e52d8a73f..ec7faf39c 100644 --- a/editor/js/editor-libs/clippy.js +++ b/editor/js/editor-libs/clippy.ts @@ -1,3 +1,4 @@ +import type { EditorView } from "codemirror"; import { getEditorContent } from "./codemirror-editor.js"; /** @@ -6,9 +7,12 @@ import { getEditorContent } from "./codemirror-editor.js"; * @param {HTMLButtonElement} copyButton - Button which can trigger copy action * @param {HTMLElement} toastElement - The feedback message container */ -function setToastPosition(copyButton, toastElement) { +function setToastPosition( + copyButton: HTMLButtonElement, + toastElement: HTMLElement, +) { /** @var {HTMLElement} */ - const copyBtnParent = copyButton.offsetParent; + const copyBtnParent = copyButton.offsetParent as HTMLElement; /* calculate the base top offset by combining the top offset of the button's parent element, and the height of the button */ @@ -26,7 +30,10 @@ function setToastPosition(copyButton, toastElement) { * @param {HTMLButtonElement} copyButton * @param {EditorView} codeMirrorEditor */ -export function addClippy(copyButton, codeMirrorEditor) { +export function addClippy( + copyButton: HTMLButtonElement, + codeMirrorEditor: EditorView, +) { copyButton.addEventListener("click", () => { const currentText = getEditorContent(codeMirrorEditor); copyText(currentText); @@ -39,7 +46,7 @@ export function addClippy(copyButton, codeMirrorEditor) { * * @param {string} text */ -function copyText(text) { +function copyText(text: string) { try { // Available only in HTTPs & localhost navigator.clipboard.writeText(text); @@ -52,11 +59,10 @@ function copyText(text) { * Displays and adjusts position of the "Copied!" toast * @param {HTMLButtonElement} copyButton - Button which can trigger copy action */ -function showToastCopied(copyButton) { - /** @var {HTMLElement} */ - const toastElement = document.getElementById("user-message"); +function showToastCopied(copyButton: HTMLButtonElement) { + const toastElement = document.getElementById("user-message") as HTMLElement; - const toggleToast = (show) => { + const toggleToast = (show: boolean) => { toastElement.classList.toggle("show", show); toastElement.setAttribute("aria-hidden", JSON.stringify(!show)); }; @@ -75,7 +81,7 @@ function showToastCopied(copyButton) { * the button in the container element passed in * @param {HTMLElement} container - The container containing the button to show */ -export function toggleClippy(container) { +export function toggleClippy(container: HTMLElement) { /** @var {HTMLElement} */ const activeClippy = container.querySelector(".copy"); const clippyButtons = document.querySelectorAll(".copy"); diff --git a/editor/js/editor-libs/codemirror-editor.js b/editor/js/editor-libs/codemirror-editor.ts similarity index 90% rename from editor/js/editor-libs/codemirror-editor.js rename to editor/js/editor-libs/codemirror-editor.ts index 148353d72..f3982e3f0 100644 --- a/editor/js/editor-libs/codemirror-editor.js +++ b/editor/js/editor-libs/codemirror-editor.ts @@ -10,13 +10,14 @@ import { HighlightStyle, LRLanguage, } from "@codemirror/language"; +import type { TagStyle, Language } from "@codemirror/language"; import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete"; import { lintKeymap } from "@codemirror/lint"; import { javascript, javascriptLanguage } from "@codemirror/lang-javascript"; import { wast } from "@codemirror/lang-wast"; import { css, cssLanguage } from "@codemirror/lang-css"; -import { parseMixed } from "@lezer/common"; +import { NodeType, parseMixed } from "@lezer/common"; import { tags } from "@lezer/highlight"; import { parser as jsParser } from "@lezer/javascript"; import { parser as htmlParser } from "@lezer/html"; @@ -130,8 +131,11 @@ const WAST_HIGHLIGHT_STYLE_SPECS = [ * @param specs - an array that associates the given styles to the given tags * @return {Extension} - Code Mirror extension attaching class names to a given tags */ -function highlighting(specs) { - return syntaxHighlighting(HighlightStyle.define(specs), { fallback: false }); +function highlighting(specs: TagStyle[]) { + const style = HighlightStyle.define(specs); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore -- Due to a typedef bug, we have no choice but to ignore errors + return syntaxHighlighting(style, { fallback: false }); } /** @@ -141,11 +145,19 @@ function highlighting(specs) { * @param nodeName - optional name of Syntax Nodes for which extension will be active * @return {Extension} - Code Mirror extension attaching class names to a given tags, but only to a given language or node tree */ -function scopedHighlighting(specs, scope, nodeName = undefined) { +function scopedHighlighting( + specs: TagStyle[], + scope: Language | NodeType, + nodeName?: string, +) { const style = HighlightStyle.define(specs, { scope: scope }); if (nodeName) { - style.scope = (node) => node.name === nodeName; // This line overrides internal scope check, because alternative parsers don't attach chosen language to props + type Writeable = { -readonly [P in keyof T]: T[P] }; + (style as Writeable).scope = (node: NodeType) => + node.name === nodeName; // This line overrides internal scope check, because alternative parsers don't attach chosen language to props } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore -- Due to a typedef bug, we have no choice but to ignore errors return syntaxHighlighting(style, { fallback: false }); } @@ -153,8 +165,8 @@ function scopedHighlighting(specs, scope, nodeName = undefined) { * @param callback {function(): *} returning a value to memoize * @return {*} - first result of provided function */ -function memo(callback) { - let value; +function memo(callback: () => any) { + let value: any; return () => (value === undefined ? ((value = callback()), value) : value); } @@ -264,15 +276,15 @@ export const languageHTML = memo(() => { * @return {EditorView} - Code editor which should be saved, so it's content can be fetched by {getEditorContent} */ export function initCodeEditor( - editorContainer, - initialContent, - language, + editorContainer: HTMLElement, + initialContent: string, + language: { extensions: any }, options = { lineNumbers: true }, ) { const extensions = [...BASE_EXTENSIONS, ...language.extensions]; if (options.lineNumbers) { - extensions.push(view.lineNumbers(), view.gutter()); + extensions.push(view.lineNumbers(), view.gutter({})); } return new EditorView({ @@ -287,6 +299,6 @@ export function initCodeEditor( * @param editorView - code editor returned by {initCodeEditor} * @return {string} - current textual content of provided editor */ -export function getEditorContent(editorView) { +export function getEditorContent(editorView: EditorView) { return editorView.state.doc.toString(); } diff --git a/editor/js/editor-libs/console-utils.js b/editor/js/editor-libs/console-utils.ts similarity index 94% rename from editor/js/editor-libs/console-utils.js rename to editor/js/editor-libs/console-utils.ts index c2c3a8ef6..874deb629 100644 --- a/editor/js/editor-libs/console-utils.js +++ b/editor/js/editor-libs/console-utils.ts @@ -7,7 +7,7 @@ * @param {any} input - The output to log. * @returns Formatted output as a string. */ -export function formatArray(input) { +export function formatArray(input: any) { let output = ""; for (let i = 0, l = input.length; i < l; i++) { if (typeof input[i] === "string") { @@ -37,7 +37,7 @@ export function formatArray(input) { * @param {any} input - The output to log. * @returns Formatted output as a string. */ -export function formatObject(input) { +export function formatObject(input: any) { "use strict"; const bufferDataViewRegExp = /^(ArrayBuffer|SharedArrayBuffer|DataView)$/; const complexArrayRegExp = @@ -119,7 +119,7 @@ export function formatObject(input) { * @param {any} input - The output to log. * @returns Formatted output as a string. */ -export function formatOutput(input) { +export function formatOutput(input: any) { if (input === undefined || input === null || typeof input === "boolean") { return String(input); } else if (typeof input === "number") { @@ -149,8 +149,8 @@ export function formatOutput(input) { * Writes the provided content to the editor’s output area * @param {String} content - The content to write to output */ -export function writeOutput(content) { - const output = document.querySelector("#console code"); +export function writeOutput(content: string) { + const output = document.querySelector("#console code") as HTMLElement; const outputContent = output.textContent; const newLogItem = "> " + content + "\n"; output.textContent = outputContent + newLogItem; diff --git a/editor/js/editor-libs/console.js b/editor/js/editor-libs/console.ts similarity index 65% rename from editor/js/editor-libs/console.js rename to editor/js/editor-libs/console.ts index b7c144b8d..0e3b4565d 100644 --- a/editor/js/editor-libs/console.js +++ b/editor/js/editor-libs/console.ts @@ -1,29 +1,29 @@ import { writeOutput, formatOutput } from "./console-utils.js"; // Thanks in part to https://stackoverflow.com/questions/11403107/capturing-javascript-console-log -export default function (targetWindow) { +export default function (targetWindow?: Window & typeof globalThis) { /* Getting reference to console, either from current window or from the iframe window */ const console = targetWindow ? targetWindow.console : window.console; const originalConsoleLogger = console.log; // eslint-disable-line no-console const originalConsoleError = console.error; - console.error = function (loggedItem) { + console.error = function (loggedItem: any, ...otherArgs: any[]) { writeOutput(loggedItem); // do not swallow console.error - originalConsoleError.apply(console, arguments); + originalConsoleError.apply(console, [loggedItem, ...otherArgs]); }; // eslint-disable-next-line no-console - console.log = function () { + console.log = function (...args) { const formattedList = []; - for (let i = 0, l = arguments.length; i < l; i++) { - const formatted = formatOutput(arguments[i]); + for (let i = 0, l = args.length; i < l; i++) { + const formatted = formatOutput(args[i]); formattedList.push(formatted); } const output = formattedList.join(" "); writeOutput(output); // do not swallow console.log - originalConsoleLogger.apply(console, arguments); + originalConsoleLogger.apply(console, args); }; } diff --git a/editor/js/editor-libs/css-editor-utils.js b/editor/js/editor-libs/css-editor-utils.ts similarity index 81% rename from editor/js/editor-libs/css-editor-utils.js rename to editor/js/editor-libs/css-editor-utils.ts index 9d074234b..968c615ab 100644 --- a/editor/js/editor-libs/css-editor-utils.js +++ b/editor/js/editor-libs/css-editor-utils.ts @@ -1,9 +1,16 @@ -export let editTimer = undefined; - -export function applyCode(code, choice, targetElement, immediateInvalidChange) { +export let editTimer: NodeJS.Timeout | undefined; + +export function applyCode( + code: string, + choice: HTMLElement, + targetElement?: HTMLElement, + immediateInvalidChange: boolean = false, +) { // http://regexr.com/3fvik const cssCommentsMatch = /(\/\*)[\s\S]+(\*\/)/g; - const element = targetElement || document.getElementById("example-element"); + const element = + targetElement || + (document.getElementById("example-element") as HTMLElement); // strip out any CSS comments before applying the code code = code.replace(cssCommentsMatch, ""); @@ -41,7 +48,7 @@ export function applyCode(code, choice, targetElement, immediateInvalidChange) { * Creates a temporary element and tests whether any of the provided CSS sets of declarations are fully supported by the user's browser * @param {Array} declarationSets - Array in which every element is one or multiple declarations separated by semicolons */ -export function isAnyDeclarationSetSupported(declarationSets) { +export function isAnyDeclarationSetSupported(declarationSets: any[]) { const tmpElem = document.createElement("div"); return declarationSets.some(isCodeSupported.bind(null, tmpElem)); } @@ -54,7 +61,7 @@ export function isAnyDeclarationSetSupported(declarationSets) { * @param declarations - list of css declarations with no curly brackets. They need to be separated by ";" and declaration key-value needs to be separated by ":". Function expects no comments. * @returns {boolean} - true if every declaration is supported by the browser. Properties with vendor prefix are excluded. */ -export function isCodeSupported(element, declarations) { +export function isCodeSupported(element: HTMLElement, declarations: string) { const vendorPrefixMatch = /^-(?:webkit|moz|ms|o)-/; const style = element.style; // Expecting declarations to be separated by ";" @@ -67,7 +74,7 @@ export function isCodeSupported(element, declarations) { /** * @returns {boolean} - true if declaration starts with -webkit-, -moz-, -ms- or -o- */ - function hasVendorPrefix(declaration) { + function hasVendorPrefix(declaration: string) { return vendorPrefixMatch.test(declaration); } @@ -77,7 +84,7 @@ export function isCodeSupported(element, declarations) { * @param declaration - single css declaration, with not white space at the beginning * @returns {string} - property name without vendor prefix. */ - function getPropertyNameNoPrefix(declaration) { + function getPropertyNameNoPrefix(declaration: string) { const prefixMatch = vendorPrefixMatch.exec(declaration); const prefix = prefixMatch === null ? "" : prefixMatch[0]; const declarationNoPrefix = @@ -94,7 +101,7 @@ export function isCodeSupported(element, declarations) { // List of not applied properties - because of lack of support for its name or value const notAppliedProperties = new Set(); - for (let declaration of declarationsArray) { + for (const declaration of declarationsArray) { const previousCSSText = style.cssText; // Declarations are added one by one, because browsers sometimes combine multiple declarations into one // For example Chrome changes "column-count: auto;column-width: 8rem;" into "columns: 8rem auto;" @@ -129,10 +136,12 @@ export function isCodeSupported(element, declarations) { * This function will change styles of 'example-element', so it is important to apply them again. * @param choices - elements containing element code, containing css declarations to apply */ -export function applyInitialSupportWarningState(choices) { +export function applyInitialSupportWarningState( + choices: NodeListOf, +) { for (const choice of choices) { - const codeBlock = choice.querySelector(".cm-content"); - applyCode(codeBlock.textContent, choice, undefined, true); + const codeBlock = choice.querySelector(".cm-content") as HTMLElement; + applyCode(codeBlock.textContent || "", choice, undefined, true); } } @@ -140,20 +149,22 @@ export function applyInitialSupportWarningState(choices) { * Sets the choice to selected, changes the nested code element to be editable, * turns of spellchecking. Lastly, it applies the code to the example element * by calling applyCode. - * @param {Object} choice - The selected `example-choice` element + * @param {HTMLElement} choice - The selected `example-choice` element */ -export function choose(choice) { +export function choose(choice: HTMLElement) { choice.classList.add("selected"); - const codeBlock = choice.querySelector(".cm-content"); - applyCode(codeBlock.textContent, choice); + const codeBlock = choice.querySelector(".cm-content") as HTMLElement; + applyCode(codeBlock.textContent || "", choice); } /** * Resets the default example to visible but, only if it is currently hidden */ export function resetDefault() { - const defaultExample = document.getElementById("default-example"); - const output = document.getElementById("output"); + const defaultExample = document.getElementById( + "default-example", + ) as HTMLElement; + const output = document.getElementById("output") as HTMLElement; // only reset to default if the default example is hidden if (defaultExample.classList.contains("hidden")) { @@ -161,11 +172,11 @@ export function resetDefault() { // loop over all sections and set to hidden for (let i = 0, l = sections.length; i < l; i++) { sections[i].classList.add("hidden"); - sections[i].setAttribute("aria-hidden", true); + sections[i].setAttribute("aria-hidden", "true"); } // show the default example defaultExample.classList.remove("hidden"); - defaultExample.setAttribute("aria-hidden", false); + defaultExample.setAttribute("aria-hidden", "false"); } resetUIState(); @@ -175,7 +186,9 @@ export function resetDefault() { * Resets the UI state by deselecting all example choice */ export function resetUIState() { - const exampleChoiceList = document.getElementById("example-choice-list"); + const exampleChoiceList = document.getElementById( + "example-choice-list", + ) as HTMLElement; const exampleChoices = exampleChoiceList.querySelectorAll(".example-choice"); for (let i = 0, l = exampleChoices.length; i < l; i++) { diff --git a/editor/js/editor-libs/events.js b/editor/js/editor-libs/events.ts similarity index 71% rename from editor/js/editor-libs/events.js rename to editor/js/editor-libs/events.ts index 42d2e7065..6a600bbeb 100644 --- a/editor/js/editor-libs/events.js +++ b/editor/js/editor-libs/events.ts @@ -5,21 +5,26 @@ import { getStorageItem, storeItem } from "./utils.js"; /** * Adds listeners for events from the CSS live examples - * @param {Object} exampleChoiceList - The object to which events are added + * @param exampleChoiceList - The object to which events are added */ -export function addCSSEditorEventListeners(exampleChoiceList) { +export function addCSSEditorEventListeners(exampleChoiceList: HTMLElement) { exampleChoiceList.addEventListener("keyup", (event) => { - const exampleChoiceParent = event.target.parentElement; + const target = event.target as typeof exampleChoiceList; + const exampleChoiceParent = target.parentElement; - cssEditorUtils.applyCode( - exampleChoiceParent.textContent, - exampleChoiceParent.closest(".example-choice"), - ); + if (exampleChoiceParent) { + cssEditorUtils.applyCode( + exampleChoiceParent.textContent || "", + exampleChoiceParent.closest(".example-choice") as HTMLElement, + ); + } }); const exampleChoices = exampleChoiceList.querySelectorAll(".example-choice"); Array.from(exampleChoices).forEach((choice) => { - choice.addEventListener("click", handleChoiceEvent); + choice.addEventListener("click", (e) => + onChoose(e.currentTarget as HTMLElement), + ); }); } @@ -36,6 +41,9 @@ function addPostMessageListener() { // to be to allow for future themes to be added without a change here. if (event.data.theme !== undefined) { const body = document.querySelector("body"); + if (!body) { + return; + } for (let i = body.classList.length - 1; i >= 0; i--) { const className = body.classList[i]; if (className.startsWith("theme-")) { @@ -53,7 +61,10 @@ function addPostMessageListener() { document.addEventListener("DOMContentLoaded", () => { const theme = getStorageItem("theme"); if (theme !== null) { - document.querySelector("body").classList.add("theme-" + theme); + const body = document.querySelector("body"); + if (body) { + body.classList.add("theme-" + theme); + } } }); @@ -61,19 +72,18 @@ function sendOwnHeight() { postParentMessage("height", { height: document.body.scrollHeight }); } -export function postParentMessage(type, values) { +export function postParentMessage( + type: string, + values: Record, +) { parent?.postMessage({ type, url: window.location.href, ...values }, "*"); } -function handleChoiceEvent() { - onChoose(this); -} - /** * Called when a new `example-choice` has been selected. - * @param {Object} choice - The selected `example-choice` element + * @param choice - The selected `example-choice` element */ -export function onChoose(choice) { +export function onChoose(choice: HTMLElement) { const selected = document.querySelector(".selected"); // highlght the code we are leaving diff --git a/editor/js/editor-libs/feature-detector.js b/editor/js/editor-libs/feature-detector.ts similarity index 90% rename from editor/js/editor-libs/feature-detector.js rename to editor/js/editor-libs/feature-detector.ts index 696f7cf3d..196fc7be2 100644 --- a/editor/js/editor-libs/feature-detector.js +++ b/editor/js/editor-libs/feature-detector.ts @@ -3,7 +3,7 @@ * @param {String} feature - The string value to match against * @returns The matched feature as an Object */ -function getFeatureObject(feature) { +function getFeatureObject(feature: string) { let featureObj = undefined; switch (feature) { @@ -20,9 +20,9 @@ function getFeatureObject(feature) { /** * Tests whether the provided feature is supported. It * does this by checking the `typeof` the feature. - * @param {String} feature - The feature to test ex. 'array-entries' + * @param {String?} feature - The feature to test ex. 'array-entries' */ -export function isDefined(feature) { +export function isDefined(feature?: string | undefined) { // if the feature parameter is undefined, return true if (feature === undefined) { return true; diff --git a/editor/js/editor-libs/mce-utils.js b/editor/js/editor-libs/mce-utils.ts similarity index 72% rename from editor/js/editor-libs/mce-utils.js rename to editor/js/editor-libs/mce-utils.ts index 49c9ac522..7963aea6d 100644 --- a/editor/js/editor-libs/mce-utils.js +++ b/editor/js/editor-libs/mce-utils.ts @@ -1,16 +1,16 @@ /** * Find and return the `example-choice` parent of the provided element - * @param {Object} element - The child element for which to find the + * @param {HTMLElement} element - The child element for which to find the * `example-choice` parent * * @return The parent `example-choice` element */ -export function findParentChoiceElem(element) { - let parent = element.parentElement; +export function findParentChoiceElem(element: HTMLElement) { + let parent = element.parentElement as HTMLElement; let parentClassList = parent.classList; while (parent && !parentClassList.contains("example-choice")) { // get the next parent - parent = parent.parentElement; + parent = parent.parentElement as HTMLElement; // get the new parent's `classList` parentClassList = parent.classList; } @@ -21,7 +21,9 @@ export function findParentChoiceElem(element) { * the iframe and opens them in a new tab instead * @param {Array} externalLinks - all external links inside the iframe */ -export function openLinksInNewTab(externalLinks) { +export function openLinksInNewTab( + externalLinks: NodeListOf, +) { externalLinks.forEach((externalLink) => { externalLink.addEventListener("click", (event) => { event.preventDefault(); @@ -37,11 +39,15 @@ export function openLinksInNewTab(externalLinks) { * @param {Object} rootElement - root or body element, that contains referenced links * @param {Array} relativeLinks - all relative links inside the iframe */ -export function scrollToAnchors(contentWindow, rootElement, relativeLinks) { +export function scrollToAnchors( + contentWindow: Window, + rootElement: HTMLElement, + relativeLinks: NodeListOf, +) { relativeLinks.forEach((relativeLink) => { relativeLink.addEventListener("click", (event) => { event.preventDefault(); - let element = rootElement.querySelector(relativeLink.hash); + const element = rootElement.querySelector(relativeLink.hash); if (element) { element.scrollIntoView(); contentWindow.location.hash = relativeLink.hash; diff --git a/editor/js/editor-libs/tabby.js b/editor/js/editor-libs/tabby.ts similarity index 59% rename from editor/js/editor-libs/tabby.js rename to editor/js/editor-libs/tabby.ts index 27e9bf5e8..e96a0152c 100644 --- a/editor/js/editor-libs/tabby.js +++ b/editor/js/editor-libs/tabby.ts @@ -1,3 +1,5 @@ +import type { EditorView } from "codemirror"; + import { languageCSS, languageHTML, @@ -5,15 +7,16 @@ import { initCodeEditor, } from "./codemirror-editor.js"; -const cssEditor = document.getElementById("css-editor"); -const htmlEditor = document.getElementById("html-editor"); -const jsEditor = document.getElementById("js-editor"); -const staticHTMLCode = htmlEditor.querySelector("pre"); -const staticCSSCode = cssEditor.querySelector("pre"); -const staticJSCode = jsEditor.querySelector("pre"); -const tabContainer = document.getElementById("tab-container"); -const tabs = tabContainer.querySelectorAll('button[role="tab"]'); -const tabList = document.getElementById("tablist"); +const cssEditor = document.getElementById("css-editor") as HTMLElement; +const htmlEditor = document.getElementById("html-editor") as HTMLElement; +const jsEditor = document.getElementById("js-editor") as HTMLElement; +const staticHTMLCode = htmlEditor.querySelector("pre") as HTMLElement; +const staticCSSCode = cssEditor.querySelector("pre") as HTMLElement; +const staticJSCode = jsEditor.querySelector("pre") as HTMLElement; +const tabContainer = document.getElementById("tab-container") as HTMLElement; +const tabs = + tabContainer.querySelectorAll('button[role="tab"]'); +const tabList = document.getElementById("tablist") as HTMLElement; /** * Hides all tabpanels @@ -34,15 +37,15 @@ function hideTabPanels() { * @param {Object} nextActiveTab - The tab to activate * @param {Object} [activeTab] - The current active tab */ -function setActiveTab(nextActiveTab, activeTab) { +function setActiveTab(nextActiveTab: HTMLElement, activeTab?: HTMLElement) { if (activeTab) { // set the currentSelectedTab to false - activeTab.setAttribute("aria-selected", false); - activeTab.setAttribute("tabindex", -1); + activeTab.setAttribute("aria-selected", "false"); + activeTab.setAttribute("tabindex", "-1"); } // set the activated tab to selected - nextActiveTab.setAttribute("aria-selected", true); + nextActiveTab.setAttribute("aria-selected", "true"); nextActiveTab.removeAttribute("tabindex"); nextActiveTab.focus(); } @@ -51,14 +54,14 @@ function setActiveTab(nextActiveTab, activeTab) { * Set the default tab, and shows the relevant panel * @param {Object} tab - The tab to set as default */ -function setDefaultTab(tab) { - const panel = document.getElementById(tab.id + "-panel"); +function setDefaultTab(tab: HTMLElement) { + const panel = document.getElementById(tab.id + "-panel") as HTMLElement; - tab.setAttribute("aria-selected", true); + tab.setAttribute("aria-selected", "true"); tab.removeAttribute("tabindex"); panel.classList.remove("hidden"); - panel.setAttribute("aria-hidden", false); + panel.setAttribute("aria-hidden", "false"); tab.focus(); } @@ -69,8 +72,10 @@ function setDefaultTab(tab) { * @param {String} direction - The direction in which to move tab focus * Must be either forward, or reverse. */ -function setNextActiveTab(direction) { - const activeTab = tabList.querySelector('button[aria-selected="true"]'); +function setNextActiveTab(direction: string) { + const activeTab = tabList.querySelector( + 'button[aria-selected="true"]', + ) as HTMLElement; // if the direction specified is not valid, simply return if (direction !== "forward" && direction !== "reverse") { @@ -78,7 +83,7 @@ function setNextActiveTab(direction) { } if (direction === "forward") { - if (activeTab.nextElementSibling) { + if (activeTab.nextElementSibling instanceof HTMLElement) { setActiveTab(activeTab.nextElementSibling, activeTab); activeTab.nextElementSibling.click(); } else { @@ -87,7 +92,7 @@ function setNextActiveTab(direction) { tabs[0].click(); } } else if (direction === "reverse") { - if (activeTab.previousElementSibling) { + if (activeTab.previousElementSibling instanceof HTMLElement) { setActiveTab(activeTab.previousElementSibling, activeTab); activeTab.previousElementSibling.click(); } else { @@ -98,23 +103,30 @@ function setNextActiveTab(direction) { } } -export const editors = { +export const editors: { + [type: string]: { + editor: EditorView | undefined; + code: HTMLElement; + initialContent: string; + language: any; + }; +} = { html: { editor: undefined, code: htmlEditor, - initialContent: staticHTMLCode.querySelector("code").textContent, + initialContent: staticHTMLCode.querySelector("code")?.textContent || "", language: languageHTML(), }, css: { editor: undefined, code: cssEditor, - initialContent: staticCSSCode.querySelector("code").textContent, + initialContent: staticCSSCode.querySelector("code")?.textContent || "", language: languageCSS(), }, js: { editor: undefined, code: jsEditor, - initialContent: staticJSCode.querySelector("code").textContent, + initialContent: staticJSCode.querySelector("code")?.textContent || "", language: languageJavaScript(), }, }; @@ -124,14 +136,17 @@ export const editors = { * @param {Array} editorTypes - The editors to initialise * @param {Object} defaultTab - The deafult active tab */ -export function initEditor(editorTypes, defaultTab) { +export function initEditor(editorTypes: string[], defaultTab: HTMLElement) { if (defaultTab) { setDefaultTab(defaultTab); } for (const editorName of editorTypes) { + if (!(editorName in editors)) { + continue; + } // enable relevant tabs - const editorData = editors[editorName]; - document.getElementById(editorName).classList.remove("hidden"); + const editorData = editors[editorName as keyof typeof editors]; + document.getElementById(editorName)?.classList.remove("hidden"); editorData.editor = initCodeEditor( editorData.code, @@ -146,21 +161,22 @@ export function initEditor(editorTypes, defaultTab) { */ export function registerEventListeners() { tabList.addEventListener("click", (event) => { - const eventTarget = event.target; + const eventTarget = event.target as HTMLElement; const role = eventTarget.getAttribute("role"); if (role === "tab") { - const activeTab = tabList.querySelector('button[aria-selected="true"]'); - const selectedPanel = document.getElementById( - eventTarget.getAttribute("aria-controls"), - ); + const activeTab = tabList.querySelector( + 'button[aria-selected="true"]', + ) as HTMLElement; + const controls = eventTarget.getAttribute("aria-controls") || ""; + const selectedPanel = document.getElementById(controls) as HTMLElement; hideTabPanels(); setActiveTab(eventTarget, activeTab); // now show the selected tabpanel selectedPanel.classList.remove("hidden"); - selectedPanel.setAttribute("aria-hidden", false); + selectedPanel.setAttribute("aria-hidden", "false"); } }); diff --git a/editor/js/editor-libs/telemetry.js b/editor/js/editor-libs/telemetry.ts similarity index 88% rename from editor/js/editor-libs/telemetry.js rename to editor/js/editor-libs/telemetry.ts index f129271f9..293128a71 100644 --- a/editor/js/editor-libs/telemetry.js +++ b/editor/js/editor-libs/telemetry.ts @@ -3,6 +3,8 @@ import { getStorageItem, storeItem } from "./utils.js"; const ACTION_COUNTS_KEY = "action-counts"; +type ActionCounts = Record; + /** * Reads action counts from local storage. * Ignores action counts that belong to another example. @@ -29,7 +31,7 @@ function getActionsCounts() { * * @param {object} counts - The current action counts. */ -function storeActionCounts(counts) { +function storeActionCounts(counts: ActionCounts) { storeItem( ACTION_COUNTS_KEY, JSON.stringify({ @@ -43,13 +45,13 @@ function storeActionCounts(counts) { * Action counts by key. * Used to distinguish 1st/2nd/etc occurrences of the same event. */ -let actionCounts = {}; +let actionCounts: ActionCounts = {}; /** * Last observed action. * Used to ignore multiple input actions until another action happened. */ -let lastAction = null; +let lastAction: string | null = null; /** * Records an action, by counting it and forwarding it to the window parent. @@ -57,7 +59,7 @@ let lastAction = null; * @param {string} key - Distinct name of the type of action. * @param {boolean} deduplicate - Should multiple actions of the same type be ignored until another action occurred? */ -export function recordAction(key, deduplicate = false) { +export function recordAction(key: string, deduplicate = false) { if (deduplicate && key === lastAction) { return; } else { @@ -86,7 +88,7 @@ export function initTelemetry() { // User clicks on any element with an id. window.addEventListener("click", (event) => { - const id = event.target.id; + const id = (event.target as HTMLElement | null)?.id; if (id) { recordAction(`click@${id}`); } diff --git a/editor/js/editor-libs/utils.js b/editor/js/editor-libs/utils.ts similarity index 85% rename from editor/js/editor-libs/utils.js rename to editor/js/editor-libs/utils.ts index 36876c23e..772b5abe9 100644 --- a/editor/js/editor-libs/utils.js +++ b/editor/js/editor-libs/utils.ts @@ -1,7 +1,7 @@ /** * Adds key & value to {@link localStorage}, without throwing an exception when it is unavailable */ -export function storeItem(key, value) { +export function storeItem(key: string, value: any) { try { localStorage.setItem(key, value); } catch (err) { @@ -13,7 +13,7 @@ export function storeItem(key, value) { * @returns the value of a given key from {@link localStorage}, or null when the key wasn't found. * It doesn't throw an exception when {@link localStorage} is unavailable */ -export function getStorageItem(key) { +export function getStorageItem(key: string) { try { return localStorage.getItem(key); } catch (err) { diff --git a/editor/js/editor.js b/editor/js/editor.ts similarity index 74% rename from editor/js/editor.js rename to editor/js/editor.ts index 7d01e4a38..659ab2127 100644 --- a/editor/js/editor.js +++ b/editor/js/editor.ts @@ -12,31 +12,39 @@ import "../css/editor-libs/tabby.css"; import "../css/tabbed-editor.css"; (function () { - const cssEditor = document.getElementById("css-editor"); - const clearConsole = document.getElementById("clear"); - const editorContainer = document.getElementById("editor-container"); - const tabContainer = document.getElementById("tab-container"); - const iframeContainer = document.getElementById("output"); - const header = document.querySelector(".output-header"); - const htmlEditor = document.getElementById("html-editor"); - const jsEditor = document.getElementById("js-editor"); - const staticCSSCode = cssEditor.querySelector("pre"); - const staticHTMLCode = htmlEditor.querySelector("pre"); - const staticJSCode = jsEditor.querySelector("pre"); - const outputIFrame = document.getElementById("output-iframe"); + const clearConsole = document.getElementById("clear") as HTMLElement; + const editorContainer = document.getElementById( + "editor-container", + ) as HTMLElement; + const tabContainer = document.getElementById("tab-container") as HTMLElement; + const iframeContainer = document.getElementById("output") as HTMLElement; + const header = document.querySelector(".output-header") as HTMLElement; + const cssEditor = document.getElementById("css-editor") as HTMLElement; + const htmlEditor = document.getElementById("html-editor") as HTMLElement; + const jsEditor = document.getElementById("js-editor") as HTMLElement; + const staticCSSCode = cssEditor.querySelector("pre") as HTMLPreElement; + const staticHTMLCode = htmlEditor.querySelector("pre") as HTMLPreElement; + const staticJSCode = jsEditor.querySelector("pre") as HTMLPreElement; + const outputIFrame = document.getElementById( + "output-iframe", + ) as HTMLIFrameElement; const outputTemplate = getOutputTemplate(); const editorType = editorContainer.dataset.editorType; let appliedHeightAdjustment = false; - let timer; + let timer: NodeJS.Timeout | undefined; /** * @returns {string} - Interactive example output template, formed by joining together contents of #output-head and #output-body, found in live-tabbed-tmpl.html */ function getOutputTemplate() { /* Document is split into two templates, just because