diff --git a/__tests__/puppeteer-tabbed-editor.test.js b/__tests__/puppeteer-tabbed-editor.test.js index 11de59afc..434932149 100644 --- a/__tests__/puppeteer-tabbed-editor.test.js +++ b/__tests__/puppeteer-tabbed-editor.test.js @@ -5,30 +5,30 @@ describe("Tabbed Editor", () => { }); it("loads the expected HTML into the output element", async () => { - const expectedOutput = - "" + - "
' + - "
James Rockford
2354 Pacific Coast Highway
" + - "California
USA
+311-555-2368
Email: " + - 'j.rockford@example.com
' + - "
"; + const expectedCSS = "address { font-variant-caps: small-caps;}a { font-variant: normal;}"; + const expectedHTML = "
James Rockford
2354 Pacific Coast Highway
" + + "California
USA
+311-555-2368
Email: " + + 'j.rockford@example.com
' + + "
"; - await page.waitForSelector("#output"); + await page.waitForSelector("#output-iframe"); + const outputIframe = await page.$('#output-iframe'); + const iframeContent = await outputIframe.contentFrame(); - let outputContent = await page.$eval("#output shadow-output", (elem) => - /* trim new lines, then trim matches of two or more consecutive + await iframeContent.waitForSelector('#html-output'); + + const trimInnerHTML = (elem) => + /* trim new lines, then trim matches of two or more consecutive whitespace characters with a single whitespace character */ - elem.shadowRoot.innerHTML - .replace(/\r?\n|\r/g, "") - .replace(/\s{2,}/g, " ") - ); - await expect(outputContent).toBe(expectedOutput); + elem.innerHTML + .replace(/\r?\n|\r/g, "") + .replace(/\s{2,}/g, " "); + + let htmlOutputContent = await iframeContent.$eval("#html-output", trimInnerHTML); + await expect(htmlOutputContent).toBe(expectedHTML); + + let cssOutputContent = await iframeContent.$eval("#css-output", trimInnerHTML); + await expect(cssOutputContent).toBe(expectedCSS); }); it("should switch to CSS editor on tab click", async () => { diff --git a/editor/css/tabbed-editor.css b/editor/css/tabbed-editor.css index c87ef9f95..4c1184e26 100644 --- a/editor/css/tabbed-editor.css +++ b/editor/css/tabbed-editor.css @@ -25,22 +25,10 @@ border-bottom: 1px solid var(--border-primary); } -.shadow-container { +.output-container { position: relative; } -/* For non shadow-dom supporting browsers */ -.output.style-scope { - background-color: var(--background-primary); - border-left: 1px solid var(--border-primary); - font-size: 0.9rem; - height: 342px; - line-height: 1.5; - margin: 0; - overflow: scroll; - padding: 1rem; -} - .tabs { font-size: 0.9rem; flex: none; @@ -144,7 +132,7 @@ height: 580px; } - .shadow-container { + .output-container { width: 40%; height: 100%; border-left: 1px solid var(--border-primary); @@ -156,3 +144,24 @@ border-bottom: 0 none; } } + +#output-iframe { + width: 100%; + height: 100%; + border: none; +} + +/* Those rules apply to content inside iframe, where example is shown */ +#output-root body { + background-color: #fff; +} + +#html-output { + color: #15141aff; + font-size: 0.9rem; + overflow: auto; + line-height: 1.5; + padding: 2rem 1rem 1rem; + height: min-content; /* This value ensures correct calculation of clientHeight in /pages/tabbed/img */ + width: fit-content; /* This value moves horizontal scroll bar to the bottom of iframe */ +} diff --git a/editor/js/editor-libs/console.js b/editor/js/editor-libs/console.js index cfb3a64b4..a1319ccb7 100644 --- a/editor/js/editor-libs/console.js +++ b/editor/js/editor-libs/console.js @@ -1,7 +1,10 @@ import { writeOutput, formatOutput } from "./console-utils.js"; // Thanks in part to https://stackoverflow.com/questions/11403107/capturing-javascript-console-log -export default function () { +export default function (targetWindow) { + /* Getting reference to console, either from current window or from the iframe window */ + var console = targetWindow ? targetWindow.console : window.console; + var originalConsoleLogger = console.log; // eslint-disable-line no-console var originalConsoleError = console.error; diff --git a/editor/js/editor-libs/mce-utils.js b/editor/js/editor-libs/mce-utils.js index 81a223149..d2ed5d3fb 100644 --- a/editor/js/editor-libs/mce-utils.js +++ b/editor/js/editor-libs/mce-utils.js @@ -47,8 +47,8 @@ export function isPropertySupported(dataset) { /** * 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 + * the iframe and opens them in a new tab instead + * @param {Array} externalLinks - all external links inside the iframe */ export function openLinksInNewTab(externalLinks) { externalLinks.forEach(function (externalLink) { @@ -61,15 +61,15 @@ export function openLinksInNewTab(externalLinks) { /** * 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 + * the iframe and scrolls to the targeted anchor + * @param {Object} rootElement - root or body element, that contains referenced links + * @param {Array} relativeLinks - all relative links inside the iframe */ -export function scrollToAnchors(shadow, relativeLinks) { +export function scrollToAnchors(rootElement, relativeLinks) { relativeLinks.forEach(function (relativeLink) { relativeLink.addEventListener("click", function (event) { event.preventDefault(); - shadow.querySelector(relativeLink.hash).scrollIntoView(); + rootElement.querySelector(relativeLink.hash).scrollIntoView(); }); }); } diff --git a/editor/js/editor-libs/shadow-output.js b/editor/js/editor-libs/shadow-output.js deleted file mode 100644 index 71d89a95b..000000000 --- a/editor/js/editor-libs/shadow-output.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Initialise a custom output element - * wrapped in a ShadowDOM container. - */ -class ShadowOutput extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: "open" }); - } - - connectedCallback() { - ShadyCSS.styleElement(this); - } -} - -export default ShadowOutput; diff --git a/editor/js/editor-libs/template-utils.js b/editor/js/editor-libs/template-utils.js deleted file mode 100644 index 5ff8ac7d1..000000000 --- a/editor/js/editor-libs/template-utils.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Return the base style rules for the output class - * @returns base style rules for the output class - */ -export function getOutputBaseStyle() { - return ".output{background-color:#fff;color:#15141aff;font-size:0.9rem;line-height:1.5;overflow:scroll;padding:2rem 1rem 1rem;height:100%;}"; -} - -/** - * Return the base script to inject into the shadowDOM - * @returns base JavaScript util which return the `shadowRoot` - */ -export function getBaseJS() { - return "function getShadowRoot() { return document.querySelector('shadow-output').shadowRoot; }"; -} - -/** - * Get the template element and return its content - * @returns The .content of the template element - */ -export function getTemplateOutput() { - return document.getElementById("code_tmpl").content; -} - -/** - * Create a template element and populate it with the content of - * the editor panes. If native shadowDOM is not supported, it uses - * ShadyCSS to prepare the template before it is injected into - * the shadowDOM element. - * @param {Object} contents - The content from the editor panes - * Example - * -------- - * { - * cssContent: 'h1 { background-color: #333; }', - * htmlContent: '

Title

' - * } - */ -export function createTemplate(contents) { - var html = document.createElement("div"); - var output = document.getElementById("output"); - var previousTmpl = document.getElementById("code_tmpl"); - var outputStyleElem = document.createElement("style"); - var styleElem = document.createElement("style"); - var tmpl = document.createElement("template"); - - /* First remove the existing template if it exists. - This ensures that prepareTemplate will process - the template. */ - if (previousTmpl) { - output.removeChild(previousTmpl); - } - - tmpl.setAttribute("id", "code_tmpl"); - output.appendChild(tmpl); - - outputStyleElem.textContent = getOutputBaseStyle(); - styleElem.textContent = contents.cssContent; - html.classList.add("output"); - html.innerHTML = contents.htmlContent; - - tmpl.content.appendChild(outputStyleElem); - tmpl.content.appendChild(styleElem); - tmpl.content.appendChild(html); - - if (contents.jsContent) { - var jsUtilElem = document.createElement("script"); - var jsElem = document.createElement("script"); - - jsUtilElem.textContent = getBaseJS(); - /* wrap the example JS in an IIFE to avoid collisions with variables, - functions etc. in the larger page scope */ - jsElem.textContent = `(function() { 'use strict'; ${contents.jsContent} })();`; - - tmpl.content.appendChild(jsUtilElem); - tmpl.content.appendChild(jsElem); - } - - if (typeof ShadyDOM !== "undefined") { - ShadyCSS.prepareTemplate(tmpl, "shadow-output"); - } -} diff --git a/editor/js/editor.js b/editor/js/editor.js index 80747f1bb..a7f2c2ca2 100644 --- a/editor/js/editor.js +++ b/editor/js/editor.js @@ -2,8 +2,6 @@ import wcb from "@webcomponents/webcomponentsjs"; import mceConsole from "./editor-libs/console.js"; import * as mceEvents from "./editor-libs/events.js"; import * as mceUtils from "./editor-libs/mce-utils.js"; -import shadowOutput from "./editor-libs/shadow-output.js"; -import * as templateUtils from "./editor-libs/template-utils.js"; import * as tabby from "./editor-libs/tabby.js"; import "../css/editor-libs/ui-fonts.css"; @@ -16,14 +14,50 @@ import "../css/tabbed-editor.css"; var cssEditor = document.getElementById("css-editor"); var clearConsole = document.getElementById("clear"); var editorContainer = document.getElementById("editor-container"); + var tabContainer = document.getElementById("tab-container"); + var iframeContainer = document.getElementById("output"); 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 outputIFrame = document.getElementById("output-iframe"); + var outputTemplate = getOutputTemplate(); + var appliedHeightAdjustment = false; var timer; + /** + * @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