Skip to content

Commit

Permalink
Use height of editor from height-data.json
Browse files Browse the repository at this point in the history
  • Loading branch information
NiedziolkaMichal committed Jan 18, 2023
1 parent 93aa399 commit 363f73f
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 95 deletions.
33 changes: 30 additions & 3 deletions build/extract-sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as cheerio from "cheerio";
import { ProseSection, Section } from "../libs/types";
import { extractSpecifications } from "./extract-specifications";
import { extractBCD } from "./extract-bcd";
import { InteractiveEditorHeights } from "../client/src/document/ingredients/interactive-example";

type SectionsAndFlaws = [Section[], string[]];

Expand All @@ -21,7 +22,9 @@ export function extractSections($: cheerio.CheerioAPI): [Section[], string[]] {
iterable.forEach((child) => {
if (
(child as cheerio.Element).tagName === "h2" ||
(child as cheerio.Element).tagName === "h3"
(child as cheerio.Element).tagName === "h3" ||
(child.tagName === "iframe" &&
child.attribs.class?.includes("interactive")) // Interactive Example
) {
if (c) {
const [subSections, subFlaws] = addSections(section.clone());
Expand Down Expand Up @@ -164,7 +167,9 @@ export function extractSections($: cheerio.CheerioAPI): [Section[], string[]] {
function addSections($: cheerio.Cheerio<cheerio.Element>): SectionsAndFlaws {
const flaws: string[] = [];

const countPotentialSpecialDivs = $.find("div.bc-data, div.bc-specs").length;
const countPotentialSpecialDivs = $.find(
"div.bc-data, div.bc-specs, iframe.interactive"
).length;
if (countPotentialSpecialDivs) {
/** If there's exactly 1 special table the only section to add is something
* like this:
Expand Down Expand Up @@ -252,7 +257,7 @@ function addSections($: cheerio.Cheerio<cheerio.Element>): SectionsAndFlaws {
}
if (countSpecialDivsFound !== countPotentialSpecialDivs) {
const leftoverCount = countPotentialSpecialDivs - countSpecialDivsFound;
const explanation = `${leftoverCount} 'div.bc-data' or 'div.bc-specs' element${
const explanation = `${leftoverCount} 'div.bc-data', 'div.bc-specs' or 'iframe.interactive' element${
leftoverCount > 1 ? "s" : ""
} found but deeply nested.`;
flaws.push(explanation);
Expand All @@ -267,6 +272,7 @@ function addSections($: cheerio.Cheerio<cheerio.Element>): SectionsAndFlaws {
// section underneath.
$.find("div.bc-data, h2, h3").remove();
$.find("div.bc-specs, h2, h3").remove();
$.find("iframe.interactive").remove();
const [proseSections, proseFlaws] = _addSectionProse($);
specialSections.push(...proseSections);
flaws.push(...proseFlaws);
Expand Down Expand Up @@ -317,6 +323,9 @@ function _addSingleSpecialSection(
specialSectionType = "specifications";
dataQuery = $.find("div.bc-specs").attr("data-bcd-query") ?? "";
specURLsString = $.find("div.bc-specs").attr("data-spec-urls") ?? "";
} else if ($.find("iframe.interactive").length) {
specialSectionType = "interactive_example";
dataQuery = $.find("iframe.interactive").attr("src");
}

// Some old legacy documents haven't been re-rendered yet, since it
Expand Down Expand Up @@ -366,6 +375,24 @@ function _addSingleSpecialSection(
},
},
];
} else if (specialSectionType === "interactive_example") {
const iframe = $.find("iframe.interactive");
const heights = JSON.parse(
iframe.attr("data-heights")
) as InteractiveEditorHeights;

return [
{
type: specialSectionType,
value: {
title,
id,
isH3,
src: query,
heights,
},
},
];
}

throw new Error(`Unrecognized special section type '${specialSectionType}'`);
Expand Down
15 changes: 14 additions & 1 deletion client/src/document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import "./index.scss";
import "./interactive-examples.scss";
import { DocumentSurvey } from "../ui/molecules/document-survey";
import { useIncrementFrequentlyViewed } from "../plus/collections/frequently-viewed";
import { InteractiveExample } from "./ingredients/interactive-example";
// import { useUIStatus } from "../ui-context";

// Lazy sub-components
Expand Down Expand Up @@ -257,7 +258,12 @@ export function Document(props /* TODO: define a TS interface for this */) {
function RenderDocumentBody({ doc }) {
return doc.body.map((section, i) => {
if (section.type === "prose") {
return <Prose key={section.value.id} section={section.value} />;
return (
<Prose
key={section.value.id || `top_level_prose${i}`}
section={section.value}
/>
);
} else if (section.type === "browser_compatibility") {
return (
<LazyBrowserCompatibilityTable
Expand All @@ -269,6 +275,13 @@ function RenderDocumentBody({ doc }) {
return (
<SpecificationSection key={`specifications${i}`} {...section.value} />
);
} else if (section.type === "interactive_example") {
return (
<InteractiveExample
key={`interactive_example${i}`}
{...section.value}
/>
);
} else {
console.warn(section);
throw new Error(`No idea how to handle a '${section.type}' section`);
Expand Down
84 changes: 84 additions & 0 deletions client/src/document/ingredients/interactive-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useEffect, useLayoutEffect, useRef } from "react";

interface InteractiveEditorHeight {
minFrameWidth: number;
height: number;
}
export type InteractiveEditorHeights = InteractiveEditorHeight[];
interface InteractiveEditor {
name: string;
heights: InteractiveEditorHeights;
}
export interface InteractiveExamplesHeightData {
editors: InteractiveEditor[];
examples: Record<string, string>;
}

/**
* Replaces iframe created by EmbedInteractiveExample.ejs and sets its height dynamically based on editor heights provided from height-data.json
*/
export function InteractiveExample({
src,
heights,
}: {
src: string;
heights: InteractiveEditorHeights;
}) {
const ref = useRef<HTMLIFrameElement>(null);

useLayoutEffect(() => {
if (ref.current) {
setHeight(ref.current, heights);

// Updating height whenever iframe is resized
const observer = new ResizeObserver((entries) =>
entries.forEach((e) =>
setHeight(e.target as typeof ref.current, heights)
)
);
observer.observe(ref.current);
return () => (ref.current ? observer.unobserve(ref.current) : undefined);
}
}, [ref.current]);

return (
<iframe
ref={ref}
className="interactive"
src={src}
title="MDN Web Docs Interactive Example"
></iframe>
);
}

function setHeight(frame: HTMLIFrameElement, heights) {
const frameWidth = getIFrameWidth(frame);
const height = calculateHeight(frameWidth, heights);
frame.style.height = height;
}

/**
* Calculates height of the iframe based on its width and data provided by height-data.json
*/
function calculateHeight(
frameWidth: number,
heights: InteractiveEditorHeights
) {
let frameHeight = 0;
for (const height of heights) {
if (frameWidth >= height.minFrameWidth) {
frameHeight = height.height;
}
}
return `${frameHeight}px`;
}

function getIFrameWidth(frame: HTMLIFrameElement) {
const styles = getComputedStyle(frame);

return (
frame.clientWidth -
parseFloat(styles.paddingLeft) -
parseFloat(styles.paddingRight)
);
}
78 changes: 2 additions & 76 deletions client/src/document/interactive-examples.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,85 +2,11 @@

.interactive {
background-color: var(--background-secondary);
border: 1px solid var(--border-primary);
border-radius: var(--elem-radius);
border: none;
color: var(--text-primary);

// Since heights are now responsive, these classes are added
// in the EmbedInteractiveExample.ejs macro.
height: 675px;
margin: 1rem 0;
padding: 0;
width: 100%;

&.is-js-height,
&.is-taller-height,
&.is-shorter-height {
border: 0 none;
}

&.is-js-height {
height: 513px;
}

&.is-shorter-height {
height: 433px;
}

&.is-taller-height {
height: 725px;
}

&.is-tabbed-shorter-height {
height: 487px;
}

&.is-tabbed-standard-height {
height: 548px;
}

&.is-tabbed-taller-height {
height: 774px;
}
}

// The layout switches at 590px in the `mdn/bob` app.
// In order to respect the height shifts without using
// JS, a complicated media query is needed. This is
// fragile, as if the margins or anything changes
// on the main layout, this will need to be adjusted.

// This spans from the time the iframe is 590px
// wide in the mobile layout to the time it switches
// to two columns. Then, from the time the iframe
// is 590px wide in the two-column layout on up.
@media screen and (min-width: 688px) and (max-width: $screen-md - 1),
screen and (min-width: 1008px) {
.interactive {
height: 375px;

&.is-js-height {
height: 444px;
}

&.is-shorter-height {
height: 364px;
}

&.is-taller-height {
height: 654px;
}

&.is-tabbed-shorter-height {
height: 351px;
}

&.is-tabbed-standard-height {
height: 421px;
}

&.is-tabbed-taller-height {
height: 631px;
}
}
// Height of the editor is set in interactive-example.tsx
}
30 changes: 19 additions & 11 deletions kumascript/macros/EmbedInteractiveExample.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ const url = (new URL($0, env.interactive_examples.base_url)).toString();
// Iframe height is acquired from height-data.json file exported by interactive-examples
const heightData = await mdn.fetchInteractiveExampleHeightData();
let height;
if (heightData[$0]) {
height = heightData[$0];
} else if ($0.includes('/js/')) {
height = 'js';
} else {
height = 'default';
}
const heightClass = 'is-' + height + '-height';
const editorName = heightData?.examples?.[$0];
const editors = heightData?.editors;
const editor = editors?.find(e => e.name === editorName);
const heights = editor?.heights;
// We get heights in form of a string, so we can place it in data-heights attribute, used by interactive-example.tsx
const heightsStr = JSON.stringify(heights || "", null, "");
const text = mdn.localStringMap({
"en-US": {
Expand Down Expand Up @@ -51,6 +48,17 @@ const text = mdn.localStringMap({
}
});
if (heights) {
// iframe is later replaced by interactive-example.tsx
%>
<h2><%=text["title"]%></h2>
<iframe class="interactive" height="200" src="<%= url %>" title="MDN Web Docs Interactive Example" data-heights="<%= heightsStr %>"></iframe>
<%
} else {
%>
<h2><%=text["title"]%></h2>
<iframe class="interactive <%= heightClass %>" height="200" src="<%= url %>" title="MDN Web Docs Interactive Example"></iframe>
<div class="notecard warning">
<p>
Error! Height of interactive example couldn't be fetched.
</p>
</div>
<% } %>
7 changes: 4 additions & 3 deletions kumascript/src/api/mdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import got from "got";
import { KumaThis } from "../environment";
import * as util from "./util";
import { INTERACTIVE_EXAMPLES_BASE_URL } from "../../../libs/env";
import { InteractiveExamplesHeightData } from "../../../client/src/document/ingredients/interactive-example";

// Module level caching for repeat calls to fetchWebExtExamples().
let webExtExamples: any = null;

let interactiveExampleHeightData: object = null;
let interactiveExampleHeightData: InteractiveExamplesHeightData | null = null;

const mdn = {
/**
Expand Down Expand Up @@ -163,15 +164,15 @@ const mdn = {
async fetchInteractiveExampleHeightData() {
if (!interactiveExampleHeightData) {
try {
interactiveExampleHeightData = await got(
interactiveExampleHeightData = await got<InteractiveExamplesHeightData>(
INTERACTIVE_EXAMPLES_BASE_URL + "/height-data.json",
{
timeout: 1000,
retry: 5,
}
).json();
} catch (error) {
interactiveExampleHeightData = {};
interactiveExampleHeightData = null;
}
}
return interactiveExampleHeightData;
Expand Down
Loading

0 comments on commit 363f73f

Please sign in to comment.