From 736baa31412b857ec5bedf9597e0af4f6488939a Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Tue, 21 Jan 2025 16:55:12 -0600 Subject: [PATCH 1/2] New layout implementation and sidenav design (#6344) - Updates the sidenav static generation logic to match `flutter/website` for improved build performance and consistency. Also removes the need for specifying `match-page-url-exactly` on directory index pages. - This changes the 11ty related `filters.ts` and `eleventyComputed.js` files. These changes have already been reviewed and been in use by `flutter/website` for a while. - Redesigns the sidenav for improved accessibility and reducing dependence on Bootstrap. - Reimplements the site layout in preparation for Bootstrap removal and improving scrolling. - Removes a bunch of now unneeded logic and styles, further supporting the later removal of Bootstrap. Fixes https://github.com/dart-lang/site-www/issues/5791 Fixes https://github.com/dart-lang/site-www/issues/5789 Contributes to https://github.com/dart-lang/site-www/issues/4164 Contributes to https://github.com/dart-lang/site-www/issues/3849 Prepares for https://github.com/dart-lang/site-www/issues/2625 --- src/_11ty/filters.ts | 63 +++--- src/_data/eleventyComputed.js | 99 +++++++++ src/_data/{side-nav.yml => sidenav.yml} | 11 - src/_data/site.yml | 2 +- src/_includes/navigation-main.html | 2 +- src/_includes/navigation-side.html | 41 ++-- src/_includes/sidenav-level-1.html | 31 +-- src/_includes/sidenav-level-2.html | 25 ++- src/_includes/sidenav-level-3.html | 25 ++- src/_includes/sidenav-level-4.html | 7 +- src/_layouts/default.html | 56 ++--- src/_layouts/error.html | 2 +- src/_layouts/homepage.html | 2 +- src/_sass/_site.scss | 74 ++++--- src/_sass/components/_card.scss | 2 +- src/_sass/components/_header.scss | 96 +------- src/_sass/components/_search.scss | 5 +- src/_sass/components/_sidebar.scss | 281 ++++++++++-------------- src/_sass/components/_toc.scss | 16 +- src/_sass/core/_base.scss | 1 + src/_sass/core/_mixins.scss | 22 -- src/_sass/core/_variables.scss | 19 +- src/content/assets/js/main.js | 35 +-- 23 files changed, 415 insertions(+), 502 deletions(-) create mode 100644 src/_data/eleventyComputed.js rename src/_data/{side-nav.yml => sidenav.yml} (96%) diff --git a/src/_11ty/filters.ts b/src/_11ty/filters.ts index d246c0d577..d003be6b0c 100644 --- a/src/_11ty/filters.ts +++ b/src/_11ty/filters.ts @@ -9,7 +9,7 @@ export function registerFilters(eleventyConfig: UserConfig): void { eleventyConfig.addFilter('toSimpleDate', toSimpleDate); eleventyConfig.addFilter('regexReplace', regexReplace); eleventyConfig.addFilter('toISOString', toISOString); - eleventyConfig.addFilter('activeNavEntryIndexArray', activeNavEntryIndexArray); + eleventyConfig.addFilter('activeNavForPage', activeNavForPage); eleventyConfig.addFilter('arrayToSentenceString', arrayToSentenceString); eleventyConfig.addFilter('underscoreBreaker', underscoreBreaker); eleventyConfig.addFilter('throwError', function (error: any) { @@ -60,38 +60,49 @@ function toISOString(input: string | Date): string { } } -function activeNavEntryIndexArray(navEntryTree: any, pageUrlPath: string = ''): number[] | null { - const activeEntryIndexes = _getActiveNavEntries(navEntryTree, pageUrlPath); - return activeEntryIndexes.length === 0 ? null : activeEntryIndexes; -} +function activeNavForPage(pageUrlPath: string, activeNav: any) { + // Split the path for this page, dropping everything before the path. + // Example: dart.dev/tools/pub/package-layout -> + // [tools, pub, package-layout] + const parts = pageUrlPath.toLowerCase().split('/').slice(1); + let currentPathPairs = activeNav; + let lastAllowedBackupActive = []; + + parts.forEach(part => { + // If the current entry has active data, + // allow its active data to be a backup if a later path isn't found. + const currentEntryActiveData = currentPathPairs['active']; + if (currentEntryActiveData) { + lastAllowedBackupActive = currentEntryActiveData; + } -function _getActiveNavEntries(navEntryTree: any, pageUrlPath = ''): number[] { - // TODO(parlough): Simplify once standardizing with the Flutter site. - for (let i = 0; i < navEntryTree.length; i++) { - const entry = navEntryTree[i]; - - if (entry.children) { - const descendantIndexes = _getActiveNavEntries( - entry.children, - pageUrlPath, - ); - if (descendantIndexes.length > 0) { - return [i + 1, ...descendantIndexes]; - } + const paths = currentPathPairs['paths']; + + // If the current entry has children paths, explore those next. + if (paths) { + currentPathPairs = paths; } - if (entry.permalink) { - const isMatch = entry['match-page-url-exactly'] - ? pageUrlPath === entry.permalink - : pageUrlPath.includes(entry.permalink); + // Get the data for the next part. + const nextPair = currentPathPairs[part]; - if (isMatch) { - return [i + 1]; - } + // If the next part of the path doesn't have data, + // use the active data for the current backup. + if (nextPair === undefined || nextPair === null) { + return lastAllowedBackupActive; } + + currentPathPairs = nextPair; + }); + + // If the last path part has active data, use that, + // otherwise fall back to the backup active data. + let activeEntries = currentPathPairs['active']; + if (activeEntries === undefined || activeEntries === null) { + activeEntries = lastAllowedBackupActive; } - return []; + return activeEntries; } function arrayToSentenceString(list: string[], joiner: string = 'and'): string { diff --git a/src/_data/eleventyComputed.js b/src/_data/eleventyComputed.js new file mode 100644 index 0000000000..c87fb1207e --- /dev/null +++ b/src/_data/eleventyComputed.js @@ -0,0 +1,99 @@ +// noinspection JSUnusedGlobalSymbols +export default { + activeNav: (data) => { + const sidenav = data['sidenav']; + + const results = {}; + _visitPermalinks(results, sidenav, []); + return results; + }, +}; + +/** + * @param results {object} + * @param navTree {object[]} + * @param path {number[]} + */ +function _visitPermalinks(results, navTree, path) { + navTree.forEach((entry, i) => { + const permalink = entry['permalink']; + const newPath = [...path, i + 1]; + const children = entry['children']; + const hasChildren = Array.isArray(children); + + if (typeof permalink === 'string' || permalink instanceof String) { + _addLink(results, permalink, newPath, hasChildren); + } + + if (hasChildren) { + _visitPermalinks(results, children, newPath); + } + }); +} + +/** + * @param results {object} + * @param permalink {string} + * @param path {number[]} + * @param hasChildren {boolean} + */ +function _addLink(results, permalink, path, hasChildren) { + // Skip external links. + if (permalink.startsWith('http')) { + return; + } + + // Throw an error if a permalink doesn't start with a `/`. + if (!permalink.startsWith('/')) { + throw new Error(`${permalink} does not begin with a '/'`); + } + + // Split the specified permalink into parts. + const parts = permalink.split('/'); + + // Add active nav data for the specified permalink. + _addPart(results, path, parts, 1, hasChildren); +} + +/** + * @param result {object} + * @param path {number[]} + * @param parts {string[]} + * @param index {number} + * @param hasChildren {boolean} + */ +function _addPart(result, path, parts, index, hasChildren = false) { + const isLast = index === parts.length - 1; + let current = result[parts[index]]; + + if (!current) { + // If the current part isn't in the result map yet, add a new map. + current = {}; + result[parts[index]] = current; + } + + // If this is the last part of the path, + // store the active nav data. + if (isLast) { + const active = current['active']; + // Override active nav data if + // it doesn't already exist for this part, + // or the current active data was from an entry with children. + if (!active) { + current['active'] = path; + if (hasChildren) { + current['has-children'] = true; + } + } else if (!hasChildren && current['has-children'] === true) { + current['active'] = path; + current['has-children'] = false; + } + } else { + if (!current['paths']) { + current['paths'] = {}; + } + + // Continue to the next part. + _addPart(current['paths'], path, parts, index + 1, hasChildren); + } +} diff --git a/src/_data/side-nav.yml b/src/_data/sidenav.yml similarity index 96% rename from src/_data/side-nav.yml rename to src/_data/sidenav.yml index 143e3050ed..7773accd3e 100644 --- a/src/_data/side-nav.yml +++ b/src/_data/sidenav.yml @@ -2,7 +2,6 @@ expanded: false children: - title: Introduction - match-page-url-exactly: true permalink: /language - title: Syntax basics children: @@ -73,7 +72,6 @@ - title: Class modifiers children: - title: Overview & usage - match-page-url-exactly: true permalink: /language/class-modifiers - title: Class modifiers for API maintainers permalink: /language/class-modifiers-for-apis @@ -91,7 +89,6 @@ expanded: false children: - title: Sound null safety - match-page-url-exactly: true permalink: /null-safety - title: Migrating to null safety permalink: /null-safety/migration-guide @@ -106,7 +103,6 @@ expanded: false children: - title: Overview - match-page-url-exactly: true permalink: /libraries - title: dart:core permalink: /libraries/dart-core @@ -139,7 +135,6 @@ permalink: /effective-dart children: - title: Overview - match-page-url-exactly: true permalink: /effective-dart - title: Style permalink: /effective-dart/style @@ -205,7 +200,6 @@ - title: Command-line & server apps children: - title: Overview - match-page-url-exactly: true permalink: /server - title: Get started permalink: /tutorials/server/get-started @@ -222,7 +216,6 @@ - title: Web apps children: - title: Overview - match-page-url-exactly: true permalink: /web - title: Get started permalink: /web/get-started @@ -248,7 +241,6 @@ children: - title: Overview permalink: /interop/js-interop - match-page-url-exactly: true - title: Usage permalink: /interop/js-interop/usage - title: JS types @@ -265,7 +257,6 @@ expanded: false children: - title: Overview - match-page-url-exactly: true permalink: /tools - title: Editors & debuggers children: @@ -278,7 +269,6 @@ - title: DartPad children: - title: Overview - match-page-url-exactly: true permalink: /tools/dartpad - title: Troubleshooting DartPad permalink: /tools/dartpad/troubleshoot @@ -372,7 +362,6 @@ - title: Videos permalink: /resources/videos - title: Tutorials - match-page-url-exactly: true permalink: /tutorials - title: Related sites diff --git a/src/_data/site.yml b/src/_data/site.yml index 9872ff5173..7a9c81e947 100644 --- a/src/_data/site.yml +++ b/src/_data/site.yml @@ -33,7 +33,7 @@ yt: watch: 'https://www.youtube.com/watch' playlist: 'https://www.youtube.com/playlist?list=' -show_banner: true +show_banner: false # Increment this global og:image URL version number (used as a query parameter) # when you update any og:image file. (Also increment the corresponding number diff --git a/src/_includes/navigation-main.html b/src/_includes/navigation-main.html index e0b0e502f5..307ac90e2a 100644 --- a/src/_includes/navigation-main.html +++ b/src/_includes/navigation-main.html @@ -1,7 +1,7 @@ {% assign page_url_path = page.url | regexReplace: '/index$|/index\.html$|\.html$|/$' | prepend: '/*' | append: '/' -%}