diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..78c6ddee2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_style = space
+indent_size = 2
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 000000000..24ae98e9d
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,29 @@
+{
+ "extends": [
+ "eslint:recommended",
+ "plugin:import/errors",
+ "prettier"
+ ],
+ "rules": {
+ "no-console": [
+ "error",
+ {
+ "allow": [
+ "info",
+ "error"
+ ]
+ }
+ ]
+ },
+ "plugins": [
+ "import"
+ ],
+ "parserOptions": {
+ "ecmaVersion": 2021
+ },
+ "env": {
+ "es6": true,
+ "browser": true,
+ "node": true
+ }
+}
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..1b8ac8894
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+# Ignore artifacts:
+build
+coverage
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 6e90746cc..000000000
--- a/.prettierrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- tabWidth: 4,
- singleQuote: true
-}
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1 @@
+{}
diff --git a/editor/js/editable-css.js b/editor/js/editable-css.js
index 0e2f616d3..4920d1fef 100644
--- a/editor/js/editable-css.js
+++ b/editor/js/editable-css.js
@@ -1,133 +1,111 @@
-(function() {
- 'use strict';
+(function () {
+ "use strict";
- var clippy = require('./editor-libs/clippy');
- var mceEvents = require('./editor-libs/events');
- var mceUtils = require('./editor-libs/mce-utils');
+ var clippy = require("./editor-libs/clippy");
+ var mceEvents = require("./editor-libs/events");
+ var mceUtils = require("./editor-libs/mce-utils");
- var exampleChoiceList = document.getElementById('example-choice-list');
- var exampleChoices = exampleChoiceList.querySelectorAll('.example-choice');
- var header = document.querySelector('header');
- var initialChoice = 0;
- var originalChoices = [];
- var output = document.getElementById('output');
+ var exampleChoiceList = document.getElementById("example-choice-list");
+ var exampleChoices = exampleChoiceList.querySelectorAll(".example-choice");
+ var header = document.querySelector("header");
+ var initialChoice = 0;
+ var originalChoices = [];
+ var output = document.getElementById("output");
- /**
- * Enables and initializes the live code editor
- */
- function enableLiveEditor() {
- header.classList.remove('hidden');
- exampleChoiceList.classList.add('live');
- output.classList.remove('hidden');
+ /**
+ * Enables and initializes the live code editor
+ */
+ function enableLiveEditor() {
+ header.classList.remove("hidden");
+ exampleChoiceList.classList.add("live");
+ output.classList.remove("hidden");
- for (var i = 0, l = exampleChoices.length; i < l; i++) {
- var exampleChoice = exampleChoices[i];
+ for (var i = 0, l = exampleChoices.length; i < l; i++) {
+ var exampleChoice = exampleChoices[i];
- originalChoices.push(
- exampleChoice.querySelector('code').textContent
- );
+ originalChoices.push(exampleChoice.querySelector("code").textContent);
- if (exampleChoice.getAttribute('initial-choice')) {
- initialChoice = indexOf(exampleChoices, exampleChoice);
- }
- }
-
- mceEvents.register();
- handleResetEvents();
- handleChoiceHover();
-
- clippy.addClippy();
+ if (exampleChoice.getAttribute("initial-choice")) {
+ initialChoice = indexOf(exampleChoices, exampleChoice);
+ }
}
- /**
- * Attached an event handler on the reset button, and handles
- * reset all the CSS examples to their original state
- */
- function handleResetEvents() {
- var resetButton = document.getElementById('reset');
+ mceEvents.register();
+ handleResetEvents();
+ handleChoiceHover();
- resetButton.addEventListener('click', function() {
- for (var i = 0, l = exampleChoices.length; i < l; i++) {
- var highlighted = Prism.highlight(
- originalChoices[i],
- Prism.languages.css
- );
- // IE11 does not support multiple selectors in `remove`
- exampleChoices[i].classList.remove('invalid');
- exampleChoices[i].classList.remove('selected');
- exampleChoices[i].querySelector('code').innerHTML = highlighted;
- }
+ clippy.addClippy();
+ }
- // if there is an initial choice set, set it as selected
- if (initialChoice) {
- mceEvents.onChoose(exampleChoices[initialChoice]);
- clippy.toggleClippy(exampleChoices[initialChoice]);
- } else {
- mceEvents.onChoose(exampleChoices[0]);
- clippy.toggleClippy(exampleChoices[0]);
- }
- });
- }
+ /**
+ * Attached an event handler on the reset button, and handles
+ * reset all the CSS examples to their original state
+ */
+ function handleResetEvents() {
+ var resetButton = document.getElementById("reset");
+
+ resetButton.addEventListener("click", function () {
+ for (var i = 0, l = exampleChoices.length; i < l; i++) {
+ var highlighted = Prism.highlight(
+ originalChoices[i],
+ Prism.languages.css
+ );
+ // IE11 does not support multiple selectors in `remove`
+ exampleChoices[i].classList.remove("invalid");
+ exampleChoices[i].classList.remove("selected");
+ exampleChoices[i].querySelector("code").innerHTML = highlighted;
+ }
+
+ // if there is an initial choice set, set it as selected
+ if (initialChoice) {
+ mceEvents.onChoose(exampleChoices[initialChoice]);
+ clippy.toggleClippy(exampleChoices[initialChoice]);
+ } else {
+ mceEvents.onChoose(exampleChoices[0]);
+ clippy.toggleClippy(exampleChoices[0]);
+ }
+ });
+ }
- function indexOf(exampleChoices, choice) {
- for (var i = 0, l = exampleChoices.length; i < l; i++) {
- if (exampleChoices[i] === choice) {
- return i;
- }
- }
- return -1;
+ function indexOf(exampleChoices, choice) {
+ for (var i = 0, l = exampleChoices.length; i < l; i++) {
+ if (exampleChoices[i] === choice) {
+ return i;
+ }
}
+ return -1;
+ }
- /**
- * Attach mouse events to example choices
- * for allowing clippy button to display on hover
- * and otherwise return to intial hidden state
- */
- function handleChoiceHover() {
- for (var i = 0, l = exampleChoices.length; i < l; i++) {
- var choice = exampleChoices[i];
- var copyBtn = choice.querySelector('.copy');
- copyBtn.setAttribute('aria-label', 'Copy to clipboard');
-
- choice.addEventListener('mouseover', () => {
- copyBtn.setAttribute('aria-hidden', false);
- });
- choice.addEventListener('mouseout', () => {
- copyBtn.setAttribute('aria-hidden', true);
- });
- }
+ /**
+ * Attach mouse events to example choices
+ * for allowing clippy button to display on hover
+ * and otherwise return to intial hidden state
+ */
+ function handleChoiceHover() {
+ for (var i = 0, l = exampleChoices.length; i < l; i++) {
+ var choice = exampleChoices[i];
+ var copyBtn = choice.querySelector(".copy");
+ copyBtn.setAttribute("aria-label", "Copy to clipboard");
+
+ choice.addEventListener("mouseover", () => {
+ copyBtn.setAttribute("aria-hidden", false);
+ });
+ choice.addEventListener("mouseout", () => {
+ copyBtn.setAttribute("aria-hidden", true);
+ });
}
+ }
- /* only show the live code view if JS is enabled and the property is supported.
+ /* only show the live code view if JS is enabled and the property is supported.
Also, only execute JS in our 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. */
- if (
- mceUtils.isPropertySupported(exampleChoiceList.dataset) &&
- !document.all
- ) {
- enableLiveEditor();
- mceEvents.onChoose(exampleChoices[initialChoice]);
- clippy.toggleClippy(exampleChoices[initialChoice]);
- }
-
- /* Ensure that performance is supported before
- gathering the performance metric */
- if (performance !== undefined) {
- document.addEventListener('readystatechange', function(event) {
- if (event.target.readyState === 'complete') {
- /* loadEventEnd happens a split second after we
- reached complete. So we wait an additional
- 100ms before getting it’ value */
- setTimeout(function() {
- mceEvents.trackloadEventEnd(
- 'CSS editor load time',
- performance.timing.loadEventEnd
- );
- // Posts mark to set on the Kuma side and used in measure
- mceUtils.postToKuma({ markName: 'css-ie-load-event-end' });
- }, 100);
- }
- });
- }
+ if (
+ mceUtils.isPropertySupported(exampleChoiceList.dataset) &&
+ !document.all
+ ) {
+ enableLiveEditor();
+ mceEvents.onChoose(exampleChoices[initialChoice]);
+ clippy.toggleClippy(exampleChoices[initialChoice]);
+ }
})();
diff --git a/editor/js/editable-js.js b/editor/js/editable-js.js
index 09b5fd733..10d6456a2 100644
--- a/editor/js/editable-js.js
+++ b/editor/js/editable-js.js
@@ -1,126 +1,105 @@
(function () {
- 'use strict';
-
- var featureDetector = require('./editor-libs/feature-detector.js');
- var mceConsole = require('./editor-libs/console');
- var mceEvents = require('./editor-libs/events.js');
- var mceUtils = require('./editor-libs/mce-utils');
-
- var codeBlock = document.getElementById('static-js');
- var exampleFeature = codeBlock.dataset['feature'];
- var execute = document.getElementById('execute');
- var liveContainer = '';
- var output = document.querySelector('#console code');
- var reset = document.getElementById('reset');
-
- var codeMirror;
- var staticContainer;
-
- /**
- * Reads the textContent from the interactiveCodeBlock, sends the
- * textContent to executeLiveExample, and logs the output to the
- * output container
- */
- function applyCode() {
- var codeMirrorDoc = codeMirror.getDoc();
- updateOutput(codeMirrorDoc.getValue());
+ "use strict";
+
+ var featureDetector = require("./editor-libs/feature-detector.js");
+ var mceConsole = require("./editor-libs/console");
+ var mceEvents = require("./editor-libs/events.js");
+ var mceUtils = require("./editor-libs/mce-utils");
+
+ var codeBlock = document.getElementById("static-js");
+ var exampleFeature = codeBlock.dataset["feature"];
+ var execute = document.getElementById("execute");
+ var liveContainer = "";
+ var output = document.querySelector("#console code");
+ var reset = document.getElementById("reset");
+
+ var codeMirror;
+ var staticContainer;
+
+ /**
+ * Reads the textContent from the interactiveCodeBlock, sends the
+ * textContent to executeLiveExample, and logs the output to the
+ * output container
+ */
+ function applyCode() {
+ var codeMirrorDoc = codeMirror.getDoc();
+ updateOutput(codeMirrorDoc.getValue());
+ }
+
+ /**
+ * Initialize CodeMirror
+ */
+ function initCodeMirror() {
+ var editorContainer = document.getElementById("editor");
+ // eslint-disable-next-line new-cap
+ codeMirror = CodeMirror(editorContainer, {
+ inputStyle: "contenteditable",
+ lineNumbers: true,
+ mode: "javascript",
+ undoDepth: 5,
+ tabindex: 0,
+ value: codeBlock.textContent,
+ });
+ }
+
+ /**
+ * Initialize the interactive editor
+ */
+ 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"]) {
+ var editor = document.getElementById("editor");
+ editor.classList.add(codeBlock.dataset["height"]);
}
- /**
- * Initialize CodeMirror
- */
- function initCodeMirror() {
- var editorContainer = document.getElementById('editor');
- // eslint-disable-next-line new-cap
- codeMirror = CodeMirror(editorContainer, {
- inputStyle: 'contenteditable',
- lineNumbers: true,
- mode: 'javascript',
- undoDepth: 5,
- tabindex: 0,
- value: codeBlock.textContent,
- });
- }
+ staticContainer = document.getElementById("static");
+ staticContainer.classList.add("hidden");
- /**
- * Initialize the interactive editor
- */
- 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']) {
- var editor = document.getElementById('editor');
- editor.classList.add(codeBlock.dataset['height']);
- }
+ liveContainer = document.getElementById("live");
+ liveContainer.classList.remove("hidden");
- staticContainer = document.getElementById('static');
- staticContainer.classList.add('hidden');
+ mceConsole();
+ mceEvents.register();
- liveContainer = document.getElementById('live');
- liveContainer.classList.remove('hidden');
+ initCodeMirror();
+ }
- mceConsole();
- mceEvents.register();
+ /**
+ * Executes the provided code snippet and logs the result
+ * to the output container.
+ * @param {String} exampleCode - The code to execute
+ */
+ function updateOutput(exampleCode) {
+ output.classList.add("fade-in");
- initCodeMirror();
+ try {
+ // Create a new Function from the code, and immediately execute it.
+ new Function(exampleCode)();
+ } catch (event) {
+ output.textContent = "Error: " + event.message;
}
- /**
- * Executes the provided code snippet and logs the result
- * to the output container.
- * @param {String} exampleCode - The code to execute
- */
- function updateOutput(exampleCode) {
- 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;
- }
-
- output.addEventListener('animationend', function () {
- output.classList.remove('fade-in');
- });
- }
+ output.addEventListener("animationend", function () {
+ output.classList.remove("fade-in");
+ });
+ }
- /* only execute JS in supported browsers. As `document.all`
+ /* 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. */
- if (!document.all && featureDetector.isDefined(exampleFeature)) {
- document.documentElement.classList.add('js');
+ if (!document.all && featureDetector.isDefined(exampleFeature)) {
+ document.documentElement.classList.add("js");
- initInteractiveEditor();
+ initInteractiveEditor();
- execute.addEventListener('click', function () {
- output.textContent = '';
- applyCode();
- });
+ execute.addEventListener("click", function () {
+ output.textContent = "";
+ applyCode();
+ });
- reset.addEventListener('click', function () {
- window.location.reload();
- });
- }
-
- /* Ensure that performance is supported before
- gathering the performance metric */
- if (performance !== undefined) {
- document.addEventListener('readystatechange', function (event) {
- if (event.target.readyState === 'complete') {
- /* loadEventEnd happens a split second after we
- reached complete. So we wait an additional
- 100ms before getting it’s value */
- setTimeout(function () {
- mceEvents.trackloadEventEnd(
- 'JS editor load time',
- performance.timing.loadEventEnd
- );
- // Posts mark to set on the Kuma side and used in measure
- mceUtils.postToKuma({ markName: 'js-ie-load-event-end' });
- codeMirror.refresh();
- }, 200);
- }
- });
- }
+ reset.addEventListener("click", function () {
+ window.location.reload();
+ });
+ }
})();
diff --git a/editor/js/editor-libs/analytics.js b/editor/js/editor-libs/analytics.js
deleted file mode 100644
index 7decf3d96..000000000
--- a/editor/js/editor-libs/analytics.js
+++ /dev/null
@@ -1,38 +0,0 @@
-module.exports = {
- /**
- * Posts a message to the parent with the data object to be sent
- * to GA
- */
- trackEvent: function(eventDetails) {
- 'use strict';
- // We use '*' as the origin so that we can post messages to
- // developer.mozilla.org or wiki.developer.mozilla.org or the
- // staging site. There is no confidential data being sent so
- // this is not a security risk.
- window.parent.postMessage(eventDetails, '*');
- },
- /**
- * Creates an object that is passed to trackEvent, recording
- * the users selecting a different CSS example
- */
- trackCSSExampleSelection: function() {
- 'use strict';
- this.trackEvent({
- category: 'Interactive Example - CSS',
- action: 'New CSS example selected',
- label: 'Interaction Events'
- });
- },
- /**
- * Creates an object that is passed to trackEvent, recording
- * the clicks on the JS examples run button.
- */
- trackRunClicks: function() {
- 'use strict';
- this.trackEvent({
- category: 'Interactive Example - JS',
- action: 'Clicked run',
- label: 'Interaction Events'
- });
- }
-};
diff --git a/editor/js/editor-libs/events.js b/editor/js/editor-libs/events.js
index b82731430..e4cdfa1e0 100644
--- a/editor/js/editor-libs/events.js
+++ b/editor/js/editor-libs/events.js
@@ -1,69 +1,48 @@
-var clippy = require('./clippy');
-var cssEditorUtils = require('./css-editor-utils');
-var mceAnalytics = require('./analytics');
+var clippy = require("./clippy");
+var cssEditorUtils = require("./css-editor-utils");
/**
* Adds listeners for events from the CSS live examples
* @param {Object} exampleChoiceList - The object to which events are added
*/
function addCSSEditorEventListeners(exampleChoiceList) {
- 'use strict';
- exampleChoiceList.addEventListener('cut', copyTextOnly);
- exampleChoiceList.addEventListener('copy', copyTextOnly);
- exampleChoiceList.addEventListener('paste', handlePasteEvents);
-
- exampleChoiceList.addEventListener('keyup', function(event) {
- var exampleChoiceParent = event.target.parentElement;
-
- cssEditorUtils.applyCode(
- exampleChoiceParent.textContent,
- exampleChoiceParent
- );
- });
-
- var exampleChoices = exampleChoiceList.querySelectorAll('.example-choice');
- Array.from(exampleChoices).forEach((choice) => {
- choice.addEventListener('click', handleChoiceEvent);
- });
-}
+ "use strict";
+ exampleChoiceList.addEventListener("cut", copyTextOnly);
+ exampleChoiceList.addEventListener("copy", copyTextOnly);
+ exampleChoiceList.addEventListener("paste", handlePasteEvents);
-/**
- * Adds listeners for events from the JS live examples
- * @param {Object} liveEditor - The object to which events are added
- */
-function addJSEditorEventListeners(liveEditor) {
- 'use strict';
+ exampleChoiceList.addEventListener("keyup", function (event) {
+ var exampleChoiceParent = event.target.parentElement;
- liveEditor.addEventListener('click', function(event) {
- if (event.target.id === 'execute') {
- mceAnalytics.trackRunClicks();
- }
- });
+ cssEditorUtils.applyCode(
+ exampleChoiceParent.textContent,
+ exampleChoiceParent
+ );
+ });
+
+ var exampleChoices = exampleChoiceList.querySelectorAll(".example-choice");
+ Array.from(exampleChoices).forEach((choice) => {
+ choice.addEventListener("click", handleChoiceEvent);
+ });
}
/**
* Adds listener for JavaScript errors, and logs them to GA
*/
function addJSErrorListener() {
- 'use strict';
- /**
- * Catches JavaScript errors from the editor that bubble up to the
- * window and passes them on to GA
- */
- window.onerror = function(msg, url, lineNo, columnNo, error) {
- var errorDetails = [
- 'URL: ' + url,
- 'Line: ' + lineNo,
- 'Column: ' + columnNo,
- 'Error object: ' + JSON.stringify(error)
- ].join(' - ');
-
- mceAnalytics.trackEvent({
- category: 'Interactive Example - JavaScript Errors',
- action: errorDetails,
- label: msg
- });
- };
+ "use strict";
+ /**
+ * Catches JavaScript errors from the editor that bubble up to the
+ * window and passes them on to GA
+ */
+ window.onerror = function (msg, url, lineNo, columnNo, error) {
+ var errorDetails = [
+ "URL: " + url,
+ "Line: " + lineNo,
+ "Column: " + columnNo,
+ "Error object: " + JSON.stringify(error),
+ ].join(" - ");
+ };
}
/**
@@ -71,29 +50,32 @@ function addJSErrorListener() {
* Currently only used by the CSS editor.
*/
function addPostMessageListener() {
- 'use strict';
- // listens for post message from Kuma
- window.addEventListener(
- 'message',
- function(event) {
- // Ignore any events that don't define the smallViewport property.
- // Note that we are not checking the origin property to verify
- // the source of the message. This is because we can't know if
- // we're on developer.mozilla.org or wiki.developer.mozilla.org.
- // Since we're just setting a CSS style based on the message
- // there is no security risk.
- if (event.data.smallViewport !== undefined) {
- var editorWrapper = document.querySelector('.editor-wrapper');
-
- if (event.data.smallViewport) {
- editorWrapper.classList.add('small-desktop-and-below');
- } else {
- editorWrapper.classList.remove('small-desktop-and-below');
- }
- }
- },
- false
- );
+ "use strict";
+
+ window.addEventListener(
+ "message",
+ function (event) {
+ // Note that we are not checking the origin property to verify
+ // the source of the message. This is because we can't know if
+ // we're on developer.mozilla.org or wiki.developer.mozilla.org.
+ // Since we're just setting a CSS style based on the message
+ // there is no security risk.
+ if (event.data.smallViewport !== undefined) {
+ var editorWrapper = document.querySelector(".editor-wrapper");
+
+ if (event.data.smallViewport) {
+ editorWrapper.classList.add("small-desktop-and-below");
+ } else {
+ editorWrapper.classList.remove("small-desktop-and-below");
+ }
+ }
+
+ if (event.data.theme !== undefined) {
+ document.querySelector("body").classList.add(event.data.theme);
+ }
+ },
+ false
+ );
}
/**
@@ -103,15 +85,15 @@ function addPostMessageListener() {
* @param {Object} event - The copy event
*/
function copyTextOnly(event) {
- 'use strict';
- var selection = window.getSelection();
- var range = selection.getRangeAt(0);
+ "use strict";
+ var selection = window.getSelection();
+ var range = selection.getRangeAt(0);
- event.preventDefault();
- event.stopPropagation();
+ event.preventDefault();
+ event.stopPropagation();
- event.clipboardData.setData('text/plain', range.toString());
- event.clipboardData.setData('text/html', range.toString());
+ event.clipboardData.setData("text/plain", range.toString());
+ event.clipboardData.setData("text/html", range.toString());
}
/**
@@ -121,89 +103,60 @@ function copyTextOnly(event) {
* @param {Object} event - The paste event object
*/
function handlePasteEvents(event) {
- 'use strict';
- var clipboardText = event.clipboardData.getData('text/plain');
- var parentPre = event.target.offsetParent;
- var parentCodeElem = parentPre.querySelector('code');
- var startValue = parentCodeElem.textContent;
+ "use strict";
+ var clipboardText = event.clipboardData.getData("text/plain");
+ var parentPre = event.target.offsetParent;
+ var parentCodeElem = parentPre.querySelector("code");
+ var startValue = parentCodeElem.textContent;
- event.preventDefault();
- event.stopPropagation();
+ event.preventDefault();
+ event.stopPropagation();
- parentCodeElem.innerText = startValue + '\n' + clipboardText;
+ parentCodeElem.innerText = startValue + "\n" + clipboardText;
- Prism.highlightElement(parentCodeElem);
+ Prism.highlightElement(parentCodeElem);
}
function handleChoiceEvent() {
- if (this.classList.contains('copy')) {
- mceAnalytics.trackEvent({
- category: 'Interactive Example - CSS',
- action: 'Copy to clipboard clicked',
- label: 'Interaction Events',
- });
- }
-
- module.exports.onChoose(this);
+ module.exports.onChoose(this);
}
module.exports = {
- /**
- * Called when a new `example-choice` has been selected.
- * @param {Object} choice - The selected `example-choice` element
- */
- onChoose: function(choice) {
- var selected = document.querySelector('.selected');
-
- // highlght the code we are leaving
- if (selected && !choice.classList.contains('selected')) {
- var highlighted = Prism.highlight(
- selected.firstChild.textContent,
- Prism.languages.css
- );
- selected.firstChild.innerHTML = highlighted;
-
- mceAnalytics.trackCSSExampleSelection();
-
- cssEditorUtils.resetDefault();
- }
-
- cssEditorUtils.choose(choice);
- clippy.toggleClippy(choice);
- },
- /**
- * Called by the main JS file after all other initialization
- * has been completed.
- */
- register: function() {
- 'use strict';
- var exampleChoiceList = document.getElementById('example-choice-list');
- var liveEditor = document.getElementById('editor');
-
- addJSErrorListener();
-
- // only bind events if the `exampleChoiceList` container exist
- if (exampleChoiceList) {
- addPostMessageListener();
- addCSSEditorEventListeners(exampleChoiceList);
- }
+ /**
+ * Called when a new `example-choice` has been selected.
+ * @param {Object} choice - The selected `example-choice` element
+ */
+ onChoose: function (choice) {
+ var selected = document.querySelector(".selected");
+
+ // highlght the code we are leaving
+ if (selected && !choice.classList.contains("selected")) {
+ var highlighted = Prism.highlight(
+ selected.firstChild.textContent,
+ Prism.languages.css
+ );
+ selected.firstChild.innerHTML = highlighted;
+
+ cssEditorUtils.resetDefault();
+ }
- if (liveEditor) {
- addJSEditorEventListeners(liveEditor);
- }
- },
- /**
- * Calls trackEvent and sends the loadEventEnd time for
- * the iframe to the parent page via postMessage
- * @param {String} action - The action that took place
- * @param {Number} loadTime - The loadEventEnd time in milliseconds
- */
- trackloadEventEnd: function(action, loadTime) {
- mceAnalytics.trackEvent({
- category: 'Interactive Examples',
- action: action,
- label: 'Performance Events',
- value: loadTime
- });
+ cssEditorUtils.choose(choice);
+ clippy.toggleClippy(choice);
+ },
+ /**
+ * Called by the main JS file after all other initialization
+ * has been completed.
+ */
+ register: function () {
+ "use strict";
+ var exampleChoiceList = document.getElementById("example-choice-list");
+
+ addJSErrorListener();
+ addPostMessageListener();
+
+ // only bind events if the `exampleChoiceList` container exist
+ if (exampleChoiceList) {
+ addCSSEditorEventListeners(exampleChoiceList);
}
+ },
};
diff --git a/editor/js/editor-libs/mce-utils.js b/editor/js/editor-libs/mce-utils.js
index a981e2ac6..2be27edf5 100644
--- a/editor/js/editor-libs/mce-utils.js
+++ b/editor/js/editor-libs/mce-utils.js
@@ -1,102 +1,90 @@
module.exports = {
- /**
- * Find and return the `example-choice` parent of the provided element
- * @param {Object} element - The child element for which to find the
- * `example-choice` parent
- *
- * @return The parent `example-choice` element
- */
- findParentChoiceElem: function(element) {
- 'use strict';
- var parent = element.parentElement;
- var parentClassList = parent.classList;
- while (parent && !parentClassList.contains('example-choice')) {
- // get the next parent
- parent = parent.parentElement;
- // get the new parent's `classList`
- parentClassList = parent.classList;
- }
- return parent;
- },
- /**
- * Creates a temporary element and tests whether the passed
- * property exists on the `style` property of the element.
- * @param {Object} dataset = The dataset from which to get the property
- */
- isPropertySupported: function(dataset) {
- 'use strict';
+ /**
+ * Find and return the `example-choice` parent of the provided element
+ * @param {Object} element - The child element for which to find the
+ * `example-choice` parent
+ *
+ * @return The parent `example-choice` element
+ */
+ findParentChoiceElem: function (element) {
+ "use strict";
+ var parent = element.parentElement;
+ var parentClassList = parent.classList;
+ while (parent && !parentClassList.contains("example-choice")) {
+ // get the next parent
+ parent = parent.parentElement;
+ // get the new parent's `classList`
+ parentClassList = parent.classList;
+ }
+ return parent;
+ },
+ /**
+ * Creates a temporary element and tests whether the passed
+ * property exists on the `style` property of the element.
+ * @param {Object} dataset = The dataset from which to get the property
+ */
+ isPropertySupported: function (dataset) {
+ "use strict";
- /* If there are no 'property' attributes,
+ /* If there are no 'property' attributes,
there is nothing to test, so return true. */
- if (dataset['property'] === undefined) {
- return true;
- }
+ if (dataset["property"] === undefined) {
+ return true;
+ }
- // `property` may be a space-separated list of properties.
- var properties = dataset['property'].split(' ');
- /* Iterate through properties: if any of them apply,
+ // `property` may be a space-separated list of properties.
+ var properties = dataset["property"].split(" ");
+ /* Iterate through properties: if any of them apply,
the browser supports this example. */
- var supported = false;
- var tmpElem = document.createElement('div');
+ var supported = false;
+ var tmpElem = document.createElement("div");
- for (var i = 0, l = properties.length; i < l; i++) {
- if (tmpElem.style[properties[i]] !== undefined) {
- supported = true;
- }
- }
+ for (var i = 0, l = properties.length; i < l; i++) {
+ if (tmpElem.style[properties[i]] !== undefined) {
+ supported = true;
+ }
+ }
- return supported;
- },
- /**
- * Interrupts the default click event on external links inside
- * the shadow dom and opens them in a new tab instead
- * @param {Array} externalLinks - all external links inside the shadow dom
- */
- openLinksInNewTab: function(externalLinks) {
- externalLinks.forEach(function(externalLink) {
- externalLink.addEventListener('click', function(event) {
- event.preventDefault();
- window.open(externalLink.href);
- });
- });
- },
- /**
- * Posts a name to set as a mark to Kuma for
- * processing and beaconing to GA
- * @param {Object} perf - The performance object sent to Kuma
- */
- postToKuma: function(perf) {
- // We use '*' as the origin so that we can post messages to
- // developer.mozilla.org or wiki.developer.mozilla.org or the
- // staging site. There is no confidential data being sent so
- // this is not a security risk.
- window.parent.postMessage(perf, '*');
- },
- /**
- * Interrupts the default click event on relative links inside
- * the shadow dom and scrolls to the targeted anchor
- * @param {Object} shadow - the shadow dom root
- * @param {Array} relativeLinks - all relative links inside the shadow dom
- */
- scrollToAnchors: function(shadow, relativeLinks) {
- relativeLinks.forEach(function(relativeLink) {
- relativeLink.addEventListener('click', function(event) {
- event.preventDefault();
- shadow.querySelector(relativeLink.hash).scrollIntoView();
- });
- });
- },
- /**
- * Hides the default example and shows the custom block
- * @param {object} customBlock - The HTML section to show
- */
- showCustomExampleHTML: function(customBlock) {
- 'use strict';
- var defaultExample = document.getElementById('default-example');
- defaultExample.classList.add('hidden');
- defaultExample.setAttribute('aria-hidden', true);
+ return supported;
+ },
+ /**
+ * Interrupts the default click event on external links inside
+ * the shadow dom and opens them in a new tab instead
+ * @param {Array} externalLinks - all external links inside the shadow dom
+ */
+ openLinksInNewTab: function (externalLinks) {
+ externalLinks.forEach(function (externalLink) {
+ externalLink.addEventListener("click", function (event) {
+ event.preventDefault();
+ window.open(externalLink.href);
+ });
+ });
+ },
+ /**
+ * Interrupts the default click event on relative links inside
+ * the shadow dom and scrolls to the targeted anchor
+ * @param {Object} shadow - the shadow dom root
+ * @param {Array} relativeLinks - all relative links inside the shadow dom
+ */
+ scrollToAnchors: function (shadow, relativeLinks) {
+ relativeLinks.forEach(function (relativeLink) {
+ relativeLink.addEventListener("click", function (event) {
+ event.preventDefault();
+ shadow.querySelector(relativeLink.hash).scrollIntoView();
+ });
+ });
+ },
+ /**
+ * Hides the default example and shows the custom block
+ * @param {object} customBlock - The HTML section to show
+ */
+ showCustomExampleHTML: function (customBlock) {
+ "use strict";
+ var defaultExample = document.getElementById("default-example");
+ defaultExample.classList.add("hidden");
+ defaultExample.setAttribute("aria-hidden", true);
- customBlock.classList.remove('hidden');
- customBlock.setAttribute('aria-hidden', false);
- }
+ customBlock.classList.remove("hidden");
+ customBlock.setAttribute("aria-hidden", false);
+ },
};
diff --git a/editor/js/editor-libs/perf.js b/editor/js/editor-libs/perf.js
deleted file mode 100644
index 22ea452aa..000000000
--- a/editor/js/editor-libs/perf.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict';
-
-/**
- * Posts a name to set as a mark to Kuma for
- * processing and beaconing to GA
- * @param {Object} perf - The performance object sent to Kuma
- */
-function postToKuma(perf) {
- // We use '*' as the origin so that we can post messages to
- // developer.mozilla.org or wiki.developer.mozilla.org or the
- // staging site. There is no confidential data being sent so
- // this is not a security risk.
- window.parent.postMessage(perf, '*');
-}
-
-postToKuma({ markName: 'interactive-editor-loading' });
-
-/**
- * Posts marks to set on the Kuma side, based on certain
- * events during document loading. These will then be made
- * available in performance tools, and beaconed to GA
- */
-document.addEventListener('readystatechange', function(event) {
- switch (event.target.readyState) {
- case 'interactive':
- postToKuma({
- markName: 'interactive-editor-interactive',
- measureName: 'ie-time-to-interactive',
- startMark: 'interactive-editor-loading',
- endMark: 'interactive-editor-interactive'
- });
- break;
- case 'complete':
- postToKuma({
- markName: 'interactive-editor-complete',
- measureName: 'ie-time-to-complete',
- startMark: 'interactive-editor-loading',
- endMark: 'interactive-editor-complete'
- });
- break;
- }
-});
diff --git a/editor/js/editor.js b/editor/js/editor.js
index 710903491..a303e42c0 100644
--- a/editor/js/editor.js
+++ b/editor/js/editor.js
@@ -1,190 +1,169 @@
-(function() {
- 'use strict';
-
- var mceConsole = require('./editor-libs/console');
- var mceEvents = require('./editor-libs/events.js');
- var mceUtils = require('./editor-libs/mce-utils');
- var shadowOutput = require('./editor-libs/shadow-output');
- var templateUtils = require('./editor-libs/template-utils');
- var tabby = require('./editor-libs/tabby');
-
- var cssEditor = document.getElementById('css-editor');
- var clearConsole = document.getElementById('clear');
- var editorContainer = document.getElementById('editor-container');
- var header = document.querySelector('.output-header');
- var htmlEditor = document.getElementById('html-editor');
- var jsEditor = document.getElementById('js-editor');
- var staticCSSCode = cssEditor.querySelector('pre');
- var staticHTMLCode = htmlEditor.querySelector('pre');
- var staticJSCode = jsEditor.querySelector('pre');
- var timer;
-
- /**
- * Called by the tabbed editor to combine code from all tabs in an Object
- * @returns Object with code from each tab panel
- * Example
- * --------
- * {
- * cssContent: 'h1 { background-color: #333; }',
- * htmlContent: '
Title
'
- * }
- */
- function getOutput() {
- var editorContents = {
- htmlContent: tabby.editors.html.editor.getValue(),
- cssContent: tabby.editors.css.editor.getValue()
- };
-
- // not all editor instances have a JS panel
- if (tabby.editors.js.editor) {
- editorContents.jsContent = tabby.editors.js.editor.getValue();
- }
-
- return editorContents;
+(function () {
+ "use strict";
+
+ var mceConsole = require("./editor-libs/console");
+ var mceEvents = require("./editor-libs/events.js");
+ var mceUtils = require("./editor-libs/mce-utils");
+ var shadowOutput = require("./editor-libs/shadow-output");
+ var templateUtils = require("./editor-libs/template-utils");
+ var tabby = require("./editor-libs/tabby");
+
+ var cssEditor = document.getElementById("css-editor");
+ var clearConsole = document.getElementById("clear");
+ var editorContainer = document.getElementById("editor-container");
+ var header = document.querySelector(".output-header");
+ var htmlEditor = document.getElementById("html-editor");
+ var jsEditor = document.getElementById("js-editor");
+ var staticCSSCode = cssEditor.querySelector("pre");
+ var staticHTMLCode = htmlEditor.querySelector("pre");
+ var staticJSCode = jsEditor.querySelector("pre");
+ var timer;
+
+ /**
+ * Called by the tabbed editor to combine code from all tabs in an Object
+ * @returns Object with code from each tab panel
+ * Example
+ * --------
+ * {
+ * cssContent: 'h1 { background-color: #333; }',
+ * htmlContent: '
Title
'
+ * }
+ */
+ function getOutput() {
+ var editorContents = {
+ htmlContent: tabby.editors.html.editor.getValue(),
+ cssContent: tabby.editors.css.editor.getValue(),
+ };
+
+ // not all editor instances have a JS panel
+ if (tabby.editors.js.editor) {
+ editorContents.jsContent = tabby.editors.js.editor.getValue();
}
- /**
- * Sets the height of the output container inside the shadow dom
- * based on the class present on the editor container
- * @param {Object} outputContainer - the output container inside the shadow dom
- */
- function setOutputHeight(outputContainer) {
- // styling for the polyfilled shadow is different
- if (typeof ShadyDOM !== 'undefined' && ShadyDOM.inUse) {
- outputContainer.style.height = '92%';
- } else if (editorContainer.classList.contains('tabbed-shorter')) {
- outputContainer.style.height = '62%';
- } else if (editorContainer.classList.contains('tabbed-standard')) {
- outputContainer.style.height = '67%';
- } else if (editorContainer.classList.contains('tabbed-taller')) {
- outputContainer.style.height = '76%';
- }
+ return editorContents;
+ }
+
+ /**
+ * Sets the height of the output container inside the shadow dom
+ * based on the class present on the editor container
+ * @param {Object} outputContainer - the output container inside the shadow dom
+ */
+ function setOutputHeight(outputContainer) {
+ // styling for the polyfilled shadow is different
+ if (typeof ShadyDOM !== "undefined" && ShadyDOM.inUse) {
+ outputContainer.style.height = "92%";
+ } else if (editorContainer.classList.contains("tabbed-shorter")) {
+ outputContainer.style.height = "62%";
+ } else if (editorContainer.classList.contains("tabbed-standard")) {
+ outputContainer.style.height = "67%";
+ } else if (editorContainer.classList.contains("tabbed-taller")) {
+ outputContainer.style.height = "76%";
}
-
- /**
- * Set or update the CSS and HTML in the output pane.
- * @param {Object} content - The content of the template element.
- */
- function render(content) {
- let shadow = document.querySelector('shadow-output').shadowRoot;
- let shadowChildren = shadow.children;
-
- if (shadowChildren.length) {
- if (typeof ShadyDOM !== 'undefined' && ShadyDOM.inUse) {
- shadow.innerHTML = '';
- } else {
- var output = shadow.querySelector('.output');
- output && shadow.removeChild(output);
- var styleElements = shadow.querySelectorAll('style');
-
- for (var styleElement in styleElements) {
- if (styleElements.hasOwnProperty(styleElement) && styleElements[styleElement]) {
- shadow.removeChild(styleElements[styleElement]);
- }
- }
- }
+ }
+
+ /**
+ * Set or update the CSS and HTML in the output pane.
+ * @param {Object} content - The content of the template element.
+ */
+ function render(content) {
+ let shadow = document.querySelector("shadow-output").shadowRoot;
+ let shadowChildren = shadow.children;
+
+ if (shadowChildren.length) {
+ if (typeof ShadyDOM !== "undefined" && ShadyDOM.inUse) {
+ shadow.innerHTML = "";
+ } else {
+ var output = shadow.querySelector(".output");
+ output && shadow.removeChild(output);
+ var styleElements = shadow.querySelectorAll("style");
+
+ for (var styleElement in styleElements) {
+ if (
+ styleElements.hasOwnProperty(styleElement) &&
+ styleElements[styleElement]
+ ) {
+ shadow.removeChild(styleElements[styleElement]);
+ }
}
-
- shadow.appendChild(document.importNode(content, true));
- setOutputHeight(shadow.querySelector('div'));
- mceUtils.openLinksInNewTab(shadow.querySelectorAll('a[href^="http"]'));
- mceUtils.scrollToAnchors(
- shadow,
- shadow.querySelectorAll('a[href^="#"]')
- );
+ }
}
- /**
- * Called from the editors on keyup events. Starts a 500 millisecond timer.
- * If no other keyup events happens before the 500 millisecond have elapsed,
- * update the output
- */
- function autoUpdate() {
- // clear the existing timer
- clearTimeout(timer);
-
- timer = setTimeout(function() {
- templateUtils.createTemplate(getOutput());
- render(templateUtils.getTemplateOutput());
- }, 500);
+ shadow.appendChild(document.importNode(content, true));
+ setOutputHeight(shadow.querySelector("div"));
+ mceUtils.openLinksInNewTab(shadow.querySelectorAll('a[href^="http"]'));
+ mceUtils.scrollToAnchors(shadow, shadow.querySelectorAll('a[href^="#"]'));
+ }
+
+ /**
+ * Called from the editors on keyup events. Starts a 500 millisecond timer.
+ * If no other keyup events happens before the 500 millisecond have elapsed,
+ * update the output
+ */
+ function autoUpdate() {
+ // clear the existing timer
+ clearTimeout(timer);
+
+ timer = setTimeout(function () {
+ templateUtils.createTemplate(getOutput());
+ render(templateUtils.getTemplateOutput());
+ }, 500);
+ }
+
+ header.addEventListener("click", function (event) {
+ if (event.target.classList.contains("reset")) {
+ window.location.reload();
}
-
- header.addEventListener('click', function(event) {
- if (event.target.classList.contains('reset')) {
- window.location.reload();
- }
- });
-
- htmlEditor.addEventListener('keyup', function() {
- autoUpdate();
- });
-
- cssEditor.addEventListener('keyup', function() {
- autoUpdate();
- });
-
- jsEditor.addEventListener('keyup', function() {
- autoUpdate();
- });
-
- clearConsole.addEventListener('click', function() {
- var webapiConsole = document.querySelector('#console code');
- webapiConsole.textContent = '';
- });
-
- // hide the static example when JS enabled
- staticHTMLCode.classList.add('hidden');
- // hide the static CSS example
- staticCSSCode.classList.add('hidden');
- // hide the static JS example
- staticJSCode.classList.add('hidden');
- // show the header
- header.classList.remove('hidden');
-
- /* Initialise the editors. If there is a `dataset` property
+ });
+
+ htmlEditor.addEventListener("keyup", function () {
+ autoUpdate();
+ });
+
+ cssEditor.addEventListener("keyup", function () {
+ autoUpdate();
+ });
+
+ jsEditor.addEventListener("keyup", function () {
+ autoUpdate();
+ });
+
+ clearConsole.addEventListener("click", function () {
+ var webapiConsole = document.querySelector("#console code");
+ webapiConsole.textContent = "";
+ });
+
+ // hide the static example when JS enabled
+ staticHTMLCode.classList.add("hidden");
+ // hide the static CSS example
+ staticCSSCode.classList.add("hidden");
+ // hide the static JS example
+ staticJSCode.classList.add("hidden");
+ // show the header
+ header.classList.remove("hidden");
+
+ /* Initialise the editors. If there is a `dataset` property
of type `tabs` on the `editorContainer`, pass its value
to `initEditor` */
- if (editorContainer.dataset && editorContainer.dataset.tabs) {
- tabby.initEditor(
- editorContainer.dataset.tabs.split(','),
- document.getElementById('js')
- );
- } else {
- tabby.initEditor(['html', 'css'], document.getElementById('html'));
- }
+ if (editorContainer.dataset && editorContainer.dataset.tabs) {
+ tabby.initEditor(
+ editorContainer.dataset.tabs.split(","),
+ document.getElementById("js")
+ );
+ } else {
+ tabby.initEditor(["html", "css"], document.getElementById("html"));
+ }
- mceConsole();
-
- tabby.registerEventListeners();
-
- // register the custom output element
- customElements.define('shadow-output', shadowOutput);
-
- templateUtils.createTemplate(getOutput());
-
- document.addEventListener('WebComponentsReady', function() {
- render(templateUtils.getTemplateOutput());
- });
-
- /* Ensure that performance is supported before
- gathering the performance metric */
- if (performance !== undefined) {
- document.addEventListener('readystatechange', function(event) {
- if (event.target.readyState === 'complete') {
- /* loadEventEnd happens a split second after we
- reached complete. So we wait an additional
- 100ms before getting it’ value */
- setTimeout(function() {
- mceEvents.trackloadEventEnd(
- 'Tabbed editor load time',
- performance.timing.loadEventEnd
- );
- // Posts mark to set on the Kuma side and used in measure
- mceUtils.postToKuma({
- markName: 'tabbed-ie-load-event-end'
- });
- }, 100);
- }
- });
- }
+ mceConsole();
+
+ tabby.registerEventListeners();
+ mceEvents.register();
+
+ // register the custom output element
+ customElements.define("shadow-output", shadowOutput);
+
+ templateUtils.createTemplate(getOutput());
+
+ document.addEventListener("WebComponentsReady", function () {
+ render(templateUtils.getTemplateOutput());
+ });
})();
diff --git a/editor/tmpl/live-css-tmpl.html b/editor/tmpl/live-css-tmpl.html
index 4d34e5ebd..01d5cfef0 100644
--- a/editor/tmpl/live-css-tmpl.html
+++ b/editor/tmpl/live-css-tmpl.html
@@ -9,7 +9,6 @@
%example-css-src%
-
diff --git a/editor/tmpl/live-js-tmpl.html b/editor/tmpl/live-js-tmpl.html
index 627f62d6d..5afe69df2 100644
--- a/editor/tmpl/live-js-tmpl.html
+++ b/editor/tmpl/live-js-tmpl.html
@@ -1,36 +1,40 @@
-
-
-
- %title%
-
-
-
- %example-css-src%
-
-
-
-
- %example-code%
-
-
-