From b63b4c0fa82d185e93ff302247ae40297e87fabf Mon Sep 17 00:00:00 2001 From: "Michael[tm] Smith" Date: Tue, 12 Apr 2022 16:41:13 +0900 Subject: [PATCH] Allow the browser-compat frontmatter value to be an array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We currently have some existing documents that use multiple {{Compat(...)}} macros within the same document — because without the browser-compat frontmatter key being allowed to contain an array, there’s no other way to have multiple BCD tables in the same document except by putting multiple {{Compat(...)}} macros in the document. So, allowing the browser-compat value to contain an array eliminates the need for authors to put multiple {{Compat(...)}} macros in a document. And reducing the need for authors to use the {{Compat(...)}} macro at all is also in line with our general de-macro-ization plans — in that it takes us a big step closer to completely eliminating the {{Compat}} altogether. --- build/document-extractor.js | 151 +++++++++++++----- client/src/document/index.scss | 31 ++++ .../browser-compatibility-table/index.tsx | 22 +++ .../src/document/ingredients/spec-section.tsx | 44 +++-- client/src/document/lazy-bcd-table.tsx | 21 ++- 5 files changed, 201 insertions(+), 68 deletions(-) diff --git a/build/document-extractor.js b/build/document-extractor.js index 1410957007d1..b0f4a72e971f 100644 --- a/build/document-extractor.js +++ b/build/document-extractor.js @@ -259,7 +259,9 @@ function addSections($) { // XXX That `_addSingleSpecialSection(section.clone())` might return a // and empty array and that means it failed and we should // bail. - subSections.push(..._addSingleSpecialSection(section.clone())); + for (const item of _addSingleSpecialSection(section.clone())) { + subSections.push(...item); + } section.empty(); } else { section.append(child); @@ -280,7 +282,10 @@ function addSections($) { } return [subSections, flaws]; } - const specialSections = _addSingleSpecialSection($); + const specialSections = []; + for (const item of _addSingleSpecialSection($)) { + specialSections.push(...item); + } // The _addSingleSpecialSection() function will have sucked up the

or

// and the `div.bc-data` or `div.bc-specs` to turn it into a special section. @@ -341,49 +346,99 @@ function _addSingleSpecialSection($) { if (!dataQuery && specURLsString === "") { // I wish there was a good place to log this! const [proseSections] = _addSectionProse($); - return proseSections; + return [proseSections]; } + const query = dataQuery.replace(/^bcd:/, ""); - const { browsers, data } = packageBCD(query); + const queries = []; + // If the value of a browser-compat frontmatter key is a YAML array of + // multiple BCD feature identifiers, the “query” string here will end up + // containing a comma-separated list of the BCD identifiers — so we split + // the “query” value out into a JavaScript array. + queries.push(...query.split(",").map((item) => item.trim())); + // We create and populate the “features” value with an array of objects, + // where each object holds the results of doing a BCD query for one of + // the BCD identifiers in the “query” value. + const features = []; + for (let i = 0; i < queries.length; i++) { + features[i] = {}; + const { browsers, data } = packageBCD(queries[i]); + features[i].browsers = browsers; + features[i].data = data; + } + const sectionItems = []; if (specialSectionType === "browser_compatibility") { - if (data === undefined) { - return [ - { - type: specialSectionType, - value: { - title, - id, - isH3, - data: null, - query, - browsers: null, + for (let i = 0; i < queries.length; i++) { + const query = queries[i]; + if (features[i].data === undefined) { + sectionItems.push([ + { + type: specialSectionType, + value: { + title, + id, + isH3, + data: null, + query, + browsers: null, + }, }, - }, - ]; + ]); + } + // If we’re iterating through a list of multiple BCD features, we’ll + // end up with multiple “Browser compatibility” headings (titles) in + // the rendered page — unless we suppress rendering of all but the + // first “Browser compatibility” heading (title); “showTitle” is the + // flag we pass down through called code to cause that suppression. + const showTitle = i === 0 ? true : false; + const showTableHeadings = features.length > 1 ? true : false; + // For a “Browser compatibility” section, if we’re iterating through + // a list of multiple BCD features, we end up with multiple tables + // in the rendered page. + // + // In that case, to make it more clear which feature each table is + // for, we output the feature name as a heading before each table. + // + // But if there’s only one feature — only one table — we don’t output + // that feature-name heading. So “showTableHeadings” is the flag we + // pass down through called code to control whether those + // feature-name headings are output before the tables. + sectionItems.push( + _buildSpecialBCDSection( + showTitle, + showTableHeadings, + features[i].browsers, + features[i].data, + query + ) + ); } - return _buildSpecialBCDSection(); + return sectionItems; } else if (specialSectionType === "specifications") { - if (data === undefined && specURLsString === "") { - return [ - { - type: specialSectionType, - value: { - title, - id, - isH3, - query, - specifications: [], - }, - }, - ]; + // If we’re iterating through a list of multiple BCD features, we’ll + // end up with multiple “Specifications” headings (titles) in the + // rendered page — unless we suppress rendering of all but the first + // “Specifications” heading (title); “showTitle” is the flag we pass + // down through called code to cause that suppression. + for (let i = 0; i < queries.length; i++) { + const showTitle = i === 0 ? true : false; + sectionItems.push( + _buildSpecialSpecSection(showTitle, features[i].data, query) + ); } - return _buildSpecialSpecSection(); + return sectionItems; } throw new Error(`Unrecognized special section type '${specialSectionType}'`); - function _buildSpecialBCDSection() { + function _buildSpecialBCDSection( + showTitle, + showTableHeadings, + browsers, + data, + query + ) { // First extract a map of all release data, keyed by (normalized) browser // name and the versions. // You'll have a map that looks like this: @@ -408,6 +463,10 @@ function _addSingleSpecialSection($) { browserReleaseData.set(name, releaseData); } + if (!data) { + return []; + } + for (const [key, compat] of Object.entries(data)) { let block; if (key === "__compat") { @@ -449,10 +508,12 @@ function _addSingleSpecialSection($) { } } + title = showTitle ? title : null; return [ { type: "browser_compatibility", value: { + showTableHeadings, title, id, isH3, @@ -518,14 +579,15 @@ function _addSingleSpecialSection($) { return version.split(".").map(Number); } - function _buildSpecialSpecSection() { + function _buildSpecialSpecSection(showTitle, data, query) { // Collect spec URLs from a BCD feature, a 'spec-urls' value, or both; // For a BCD feature, it can either be a string or an array of strings. let specURLs = []; if (data) { - // If 'data' is non-null, that means we have data for a BCD feature - // that we can extract spec URLs from. + // If 'data' is non-null, that means we have data, from the value of + // a browser-compat frontmatter key, for a BCD feature that we can + // extract spec URLs from. for (const [key, compat] of Object.entries(data)) { if (key === "__compat" && compat.spec_url) { if (Array.isArray(compat.spec_url)) { @@ -535,12 +597,16 @@ function _addSingleSpecialSection($) { } } } - } - - if (specURLsString !== "") { - // If specURLsString is non-empty, then it has the string contents of - // the document’s 'spec-urls' frontmatter key: one or more URLs. - specURLs.push(...specURLsString.split(",").map((url) => url.trim())); + // If there’s no browser-compat frontmatter key, then we check the + // value of the spec-urls frontmatter key. (Otherwise, we just use + // the BCD identifier(s) from the browser-compat key, and ignore any + // spec-urls key.) + } else { + if (specURLsString !== "") { + // If specURLsString is non-empty, then it has the string contents + // of the document’s 'spec-urls' frontmatter key: one or more URLs. + specURLs.push(...specURLsString.split(",").map((url) => url.trim())); + } } // Use BCD specURLs to look up more specification data @@ -573,6 +639,7 @@ function _addSingleSpecialSection($) { }) .filter(Boolean); + title = showTitle ? title : null; return [ { type: "specifications", diff --git a/client/src/document/index.scss b/client/src/document/index.scss index 9d751f2ce876..a833aae7d123 100644 --- a/client/src/document/index.scss +++ b/client/src/document/index.scss @@ -240,6 +240,37 @@ code { font-weight: var(--font-body-strong-weight); } +div.specs p { + border: 1px solid var(--border-primary); + font: var(--type-body-l); + font-size: 0.875rem; + line-height: 1.75; + margin: 0; + padding: 0.5rem 0.75rem; +} + +div.specs ~ div.specs p, +div.specs p ~ p { + border-top-style: none; +} + +.spec-header { + background: var(--background-tertiary); + border: 1px solid var(--border-primary); + border-bottom-style: none; + font: var(--type-body-l); + font-size: 0.875rem; + font-weight: var(--font-body-strong-weight); + line-height: 1.5; + padding: 0.5rem 0.75rem; + text-align: left; + vertical-align: middle; +} + +.spec-header ~ .spec-header { + display: none; +} + table { font: var(--type-body-l); border: 1px solid var(--border-primary); diff --git a/client/src/document/ingredients/browser-compatibility-table/index.tsx b/client/src/document/ingredients/browser-compatibility-table/index.tsx index dbe15d589d7e..85fb69369c39 100644 --- a/client/src/document/ingredients/browser-compatibility-table/index.tsx +++ b/client/src/document/ingredients/browser-compatibility-table/index.tsx @@ -115,11 +115,13 @@ export default function BrowserCompatibilityTable({ data, browsers: browserInfo, locale, + showTableHeadings, }: { query: string; data: bcd.Identifier; browsers: bcd.Browsers; locale: string; + showTableHeadings: boolean; }) { const location = useLocation(); @@ -147,8 +149,28 @@ export default function BrowserCompatibilityTable({ return `${url}?${sp.toString()}`; } + // “tableHeading” holds a BCD feature name. If there’s a “description” + // key in BCD for the feature, we use that description — after stripping + // out any HTML tags from it. Otherwise, if there’s no “description” key, + // we just use the BCD feature name. (These can be different; for + // example, the BCD feature name may be “for_of”, while its description + // is the string “for...of”. + let tableHeading; + if (data && data.__compat) { + tableHeading = data.__compat.description ? ( +

]+>/g, ""), + }} + /> + ) : ( +

{name}

+ ); + } + return ( + {showTableHeadings ? tableHeading : null} } {title && isH3 && } + {/* + If we were to output HTML table markup here, then in the case where + we have multiple BCD features from a browser-compat frontmatter + key, we’d end up with multiple tables in the output. So we instead + output a simpler HTML structure for each specification, and use CSS + to push each piece together to make the collective end result look + like a table; client/src/document/index.scss has the CSS styles. + */} +
Specification
{specifications.length > 0 ? ( - - - - - - - - {specifications.map((spec) => ( - - - - ))} - -
Specification
- - {spec.title}
- {spec.bcdSpecificationURL.includes("#") && ( - - # {`${spec.bcdSpecificationURL.split("#")[1]}`} - - )} -
-
+
+ {specifications.map((spec) => ( +

+ + {spec.title}
+ {spec.bcdSpecificationURL.includes("#") && ( + # {`${spec.bcdSpecificationURL.split("#")[1]}`} + )} +
+

+ ))} +
) : (

No specification found

diff --git a/client/src/document/lazy-bcd-table.tsx b/client/src/document/lazy-bcd-table.tsx index 03aa7d648ec7..71131a7c0993 100644 --- a/client/src/document/lazy-bcd-table.tsx +++ b/client/src/document/lazy-bcd-table.tsx @@ -25,12 +25,14 @@ const isServer = typeof window === "undefined"; export function LazyBrowserCompatibilityTable({ id, + showTableHeadings, title, isH3, query, dataURL, }: { id: string; + showTableHeadings: boolean; title: string; isH3: boolean; query: string; @@ -41,7 +43,10 @@ export function LazyBrowserCompatibilityTable({ {title && !isH3 && } {title && isH3 && } {dataURL ? ( - + ) : (

@@ -59,7 +64,13 @@ export function LazyBrowserCompatibilityTable({ ); } -function LazyBrowserCompatibilityTableInner({ dataURL }: { dataURL: string }) { +function LazyBrowserCompatibilityTableInner({ + dataURL, + showTableHeadings, +}: { + dataURL: string; + showTableHeadings: boolean; +}) { const locale = useLocale(); const [bcdDataURL, setBCDDataURL] = useState(""); @@ -92,7 +103,11 @@ function LazyBrowserCompatibilityTableInner({ dataURL }: { dataURL: string }) { return ( }> - + );