diff --git a/site/lib/jaspr_options.dart b/site/lib/jaspr_options.dart index 4e4a38b7f14..831027edc6c 100644 --- a/site/lib/jaspr_options.dart +++ b/site/lib/jaspr_options.dart @@ -7,30 +7,31 @@ import 'package:jaspr/jaspr.dart'; import 'package:docs_flutter_dev_site/src/client/global_scripts.dart' as prefix0; -import 'package:docs_flutter_dev_site/src/components/client/archive_table.dart' +import 'package:docs_flutter_dev_site/src/components/common/client/cookie_notice.dart' as prefix1; -import 'package:docs_flutter_dev_site/src/components/client/dartpad_injector.dart' +import 'package:docs_flutter_dev_site/src/components/common/client/copy_button.dart' as prefix2; -import 'package:docs_flutter_dev_site/src/components/client/download_latest_button.dart' +import 'package:docs_flutter_dev_site/src/components/common/client/download_latest_button.dart' as prefix3; -import 'package:docs_flutter_dev_site/src/components/client/learning_resource_filters.dart' +import 'package:docs_flutter_dev_site/src/components/common/client/feedback.dart' as prefix4; -import 'package:docs_flutter_dev_site/src/components/client/learning_resource_filters_sidebar.dart' +import 'package:docs_flutter_dev_site/src/components/common/client/on_this_page_button.dart' as prefix5; -import 'package:docs_flutter_dev_site/src/components/client/on_this_page_button.dart' +import 'package:docs_flutter_dev_site/src/components/common/client/os_selector.dart' as prefix6; -import 'package:docs_flutter_dev_site/src/components/header/menu_toggle.dart' +import 'package:docs_flutter_dev_site/src/components/dartpad/dartpad_injector.dart' as prefix7; -import 'package:docs_flutter_dev_site/src/components/header/site_switcher.dart' +import 'package:docs_flutter_dev_site/src/components/layout/menu_toggle.dart' as prefix8; -import 'package:docs_flutter_dev_site/src/components/header/theme_switcher.dart' +import 'package:docs_flutter_dev_site/src/components/layout/site_switcher.dart' as prefix9; -import 'package:docs_flutter_dev_site/src/components/cookie_notice.dart' +import 'package:docs_flutter_dev_site/src/components/layout/theme_switcher.dart' as prefix10; -import 'package:docs_flutter_dev_site/src/components/copy_button.dart' +import 'package:docs_flutter_dev_site/src/components/pages/archive_table.dart' as prefix11; -import 'package:docs_flutter_dev_site/src/components/feedback.dart' as prefix12; -import 'package:docs_flutter_dev_site/src/components/os_selector.dart' +import 'package:docs_flutter_dev_site/src/components/pages/learning_resource_filters.dart' + as prefix12; +import 'package:docs_flutter_dev_site/src/components/pages/learning_resource_filters_sidebar.dart' as prefix13; /// Default [JasprOptions] for use with your jaspr project. @@ -55,86 +56,87 @@ JasprOptions get defaultJasprOptions => JasprOptions( 'src/client/global_scripts', ), - prefix1.ArchiveTable: ClientTarget( - 'src/components/client/archive_table', - params: _prefix1ArchiveTable, + prefix1.CookieNotice: ClientTarget( + 'src/components/common/client/cookie_notice', ), - prefix2.DartPadInjector: ClientTarget( - 'src/components/client/dartpad_injector', - params: _prefix2DartPadInjector, + prefix2.CopyButton: ClientTarget( + 'src/components/common/client/copy_button', + params: _prefix2CopyButton, ), prefix3.DownloadLatestButton: ClientTarget( - 'src/components/client/download_latest_button', + 'src/components/common/client/download_latest_button', params: _prefix3DownloadLatestButton, ), - prefix4.LearningResourceFilters: - ClientTarget( - 'src/components/client/learning_resource_filters', - ), - - prefix5.LearningResourceFiltersSidebar: - ClientTarget( - 'src/components/client/learning_resource_filters_sidebar', - ), - - prefix6.OnThisPageButton: ClientTarget( - 'src/components/client/on_this_page_button', + prefix4.FeedbackComponent: ClientTarget( + 'src/components/common/client/feedback', + params: _prefix4FeedbackComponent, ), - prefix10.CookieNotice: ClientTarget( - 'src/components/cookie_notice', + prefix5.OnThisPageButton: ClientTarget( + 'src/components/common/client/on_this_page_button', ), - prefix11.CopyButton: ClientTarget( - 'src/components/copy_button', - params: _prefix11CopyButton, + prefix6.OsSelector: ClientTarget( + 'src/components/common/client/os_selector', ), - prefix12.FeedbackComponent: ClientTarget( - 'src/components/feedback', - params: _prefix12FeedbackComponent, + prefix7.DartPadInjector: ClientTarget( + 'src/components/dartpad/dartpad_injector', + params: _prefix7DartPadInjector, ), - prefix7.MenuToggle: ClientTarget( - 'src/components/header/menu_toggle', + prefix8.MenuToggle: ClientTarget( + 'src/components/layout/menu_toggle', ), - prefix8.SiteSwitcher: ClientTarget( - 'src/components/header/site_switcher', + prefix9.SiteSwitcher: ClientTarget( + 'src/components/layout/site_switcher', ), - prefix9.ThemeSwitcher: ClientTarget( - 'src/components/header/theme_switcher', + prefix10.ThemeSwitcher: ClientTarget( + 'src/components/layout/theme_switcher', ), - prefix13.OsSelector: ClientTarget( - 'src/components/os_selector', + prefix11.ArchiveTable: ClientTarget( + 'src/components/pages/archive_table', + params: _prefix11ArchiveTable, ), + + prefix12.LearningResourceFilters: + ClientTarget( + 'src/components/pages/learning_resource_filters', + ), + + prefix13.LearningResourceFiltersSidebar: + ClientTarget( + 'src/components/pages/learning_resource_filters_sidebar', + ), }, styles: () => [], ); -Map _prefix1ArchiveTable(prefix1.ArchiveTable c) => { - 'os': c.os, - 'channel': c.channel, -}; -Map _prefix2DartPadInjector(prefix2.DartPadInjector c) => { +Map _prefix2CopyButton(prefix2.CopyButton c) => { + 'toCopy': c.toCopy, + 'buttonText': c.buttonText, + 'classes': c.classes, 'title': c.title, - 'theme': c.theme, - 'height': c.height, - 'runAutomatically': c.runAutomatically, }; Map _prefix3DownloadLatestButton( prefix3.DownloadLatestButton c, ) => {'os': c.os, 'arch': c.arch}; -Map _prefix11CopyButton(prefix11.CopyButton c) => { - 'toCopy': c.toCopy, - 'buttonText': c.buttonText, - 'classes': c.classes, +Map _prefix4FeedbackComponent(prefix4.FeedbackComponent c) => { + 'issueUrl': c.issueUrl, +}; +Map _prefix7DartPadInjector(prefix7.DartPadInjector c) => { 'title': c.title, + 'theme': c.theme, + 'height': c.height, + 'runAutomatically': c.runAutomatically, +}; +Map _prefix11ArchiveTable(prefix11.ArchiveTable c) => { + 'os': c.os, + 'channel': c.channel, }; -Map _prefix12FeedbackComponent(prefix12.FeedbackComponent c) => - {'issueUrl': c.issueUrl}; diff --git a/site/lib/main.dart b/site/lib/main.dart index de8c8579f41..04c6037d586 100644 --- a/site/lib/main.dart +++ b/site/lib/main.dart @@ -8,15 +8,16 @@ import 'package:jaspr_content/theme.dart'; import 'package:path/path.dart' as path; import 'jaspr_options.dart'; // Generated. Do not remove or edit. -import 'src/components/card.dart'; -import 'src/components/client/archive_table.dart'; -import 'src/components/client/download_latest_button.dart'; -import 'src/components/dash_image.dart'; -import 'src/components/expansion_list.dart'; -import 'src/components/os_selector.dart'; +import 'src/components/common/card.dart'; +import 'src/components/common/client/download_latest_button.dart'; +import 'src/components/common/client/os_selector.dart'; +import 'src/components/common/dash_image.dart'; +import 'src/components/common/tabs.dart'; +import 'src/components/common/youtube_embed.dart'; +import 'src/components/pages/archive_table.dart'; import 'src/components/pages/devtools_release_notes_index.dart'; +import 'src/components/pages/expansion_list.dart'; import 'src/components/pages/learning_resource_index.dart'; -import 'src/components/tabs.dart'; import 'src/extensions/registry.dart'; import 'src/layouts/catalog_page_layout.dart'; import 'src/layouts/doc_layout.dart'; @@ -73,59 +74,14 @@ final RegExp _passThroughPattern = RegExp(r'.*\.(txt|json|pdf)$'); List get _embeddableComponents => [ const DashTabs(), const DashImage(), + const YoutubeEmbed(), CustomComponent( pattern: RegExp('OSSelector', caseSensitive: false), - builder: (name, attributes, child) { - return const OsSelector(); - }, + builder: (_, _, _) => const OsSelector(), ), CustomComponent( pattern: RegExp('Card', caseSensitive: false), - builder: (name, attributes, child) { - final link = attributes['link']; - final title = attributes['title']!; - final outlined = attributes['outlined'] == 'true'; - return Card( - header: [ - header(classes: 'card-title', [text(title)]), - ], - content: [?child], - link: link, - filled: link != null, - outlined: outlined, - ); - }, - ), - CustomComponent( - pattern: RegExp('YouTubeEmbed', caseSensitive: false), - builder: (name, attributes, child) { - final rawVideoId = attributes['id'] as String; - final videoTitle = attributes['title'] as String; - final playlistId = attributes['playlist']; - - final String videoId; - final int startTime; - if (rawVideoId.contains('?')) { - final idAndStartTime = rawVideoId.split('?'); - videoId = idAndStartTime[0]; - - final rawStartTime = idAndStartTime[1].split('start=')[1]; - startTime = int.tryParse(rawStartTime) ?? 0; - } else { - startTime = 0; - videoId = rawVideoId; - } - - // Instead of directly including a YouTube embed iframe, - // we use https://github.com/justinribeiro/lite-youtube which - // lazily loads the video, significantly reduces page load times, - // and enables configurability through element attributes. - return raw(''' - - Watch on YouTube in a new tab: "$videoTitle" - -'''); - }, + builder: (_, attrs, child) => Card.fromAttributes(attrs, child), ), CustomComponent( pattern: RegExp('LearningResourceIndex', caseSensitive: false), @@ -133,39 +89,15 @@ List get _embeddableComponents => [ ), CustomComponent( pattern: RegExp('ArchiveTable'), - builder: (_, attributes, _) { - final os = attributes['os'] as String; - final channel = attributes['channel'] as String; - return ArchiveTable(os: os.toLowerCase(), channel: channel.toLowerCase()); - }, + builder: (_, attrs, _) => ArchiveTable.fromAttributes(attrs), ), CustomComponent( pattern: RegExp('DownloadLatestButton', caseSensitive: false), - builder: (_, attributes, _) { - final os = attributes['os'] as String; - final arch = attributes['arch']; - return DownloadLatestButton( - os: os.toLowerCase(), - arch: arch?.toLowerCase(), - ); - }, + builder: (_, attrs, _) => DownloadLatestButton.fromAttributes(attrs), ), CustomComponent( pattern: RegExp('ExpansionList', caseSensitive: false), - builder: (_, attributes, _) { - final listName = - attributes['list'] ?? - (throw Exception( - 'ExpansionList component requires a "list" attribute.', - )); - final baseId = - attributes['baseid'] ?? - (throw Exception( - 'ExpansionList component requires a "baseId" attribute.', - )); - - return ExpansionList.load(listName, baseId: baseId); - }, + builder: (_, attrs, _) => ExpansionList.fromAttributes(attrs), ), CustomComponent( pattern: RegExp('DevToolsReleaseNotesIndex', caseSensitive: false), diff --git a/site/lib/src/components/breadcrumbs.dart b/site/lib/src/components/common/breadcrumbs.dart similarity index 99% rename from site/lib/src/components/breadcrumbs.dart rename to site/lib/src/components/common/breadcrumbs.dart index 2c861f1ca05..39e0a4b861e 100644 --- a/site/lib/src/components/breadcrumbs.dart +++ b/site/lib/src/components/common/breadcrumbs.dart @@ -6,7 +6,7 @@ import 'package:collection/collection.dart'; import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; -import '../util.dart'; +import '../../util.dart'; import 'material_icon.dart'; /// Breadcrumbs navigation component that diff --git a/site/lib/src/components/button.dart b/site/lib/src/components/common/button.dart similarity index 98% rename from site/lib/src/components/button.dart rename to site/lib/src/components/common/button.dart index c1b51d715b9..41eea870529 100644 --- a/site/lib/src/components/button.dart +++ b/site/lib/src/components/common/button.dart @@ -4,7 +4,7 @@ import 'package:jaspr/jaspr.dart'; -import '../util.dart'; +import '../../util.dart'; import 'material_icon.dart'; /// A generic button component with different style variants. diff --git a/site/lib/src/components/card.dart b/site/lib/src/components/common/card.dart similarity index 81% rename from site/lib/src/components/card.dart rename to site/lib/src/components/common/card.dart index 5453203a691..2ac507cd4dd 100644 --- a/site/lib/src/components/card.dart +++ b/site/lib/src/components/common/card.dart @@ -4,7 +4,7 @@ import 'package:jaspr/jaspr.dart'; -import '../util.dart'; +import '../../util.dart'; class Card extends StatelessComponent { /// Creates a card that can have a [header], [content], and [actions]. @@ -42,6 +42,31 @@ class Card extends StatelessComponent { link = null, expandable = true; + /// Creates a [Card] from a set of attributes parsed from markdown. + factory Card.fromAttributes( + Map attributes, + Component? child, + ) { + final link = attributes['link']; + final title = + attributes['title'] ?? + (throw Exception('Card component requires a "title" attribute.')); + final outlined = attributes['outlined'] == 'true'; + return Card( + header: [ + Component.element( + tag: 'header', + classes: 'card-title', + children: [text(title)], + ), + ], + content: [?child], + link: link, + filled: link != null, + outlined: outlined, + ); + } + final List header; final List content; final List? collapsedContent; diff --git a/site/lib/src/components/chip.dart b/site/lib/src/components/common/chip.dart similarity index 99% rename from site/lib/src/components/chip.dart rename to site/lib/src/components/common/chip.dart index 895ba1a45fb..a9484dab171 100644 --- a/site/lib/src/components/chip.dart +++ b/site/lib/src/components/common/chip.dart @@ -5,9 +5,9 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/web.dart' as web; -import '../util.dart'; +import '../../util.dart'; +import '../util/global_event_listener.dart'; import 'material_icon.dart'; -import 'util/global_event_listener.dart'; /// A set of Material Design-like chips for configuration. class ChipSet extends StatelessComponent { diff --git a/site/lib/src/components/cookie_notice.dart b/site/lib/src/components/common/client/cookie_notice.dart similarity index 97% rename from site/lib/src/components/cookie_notice.dart rename to site/lib/src/components/common/client/cookie_notice.dart index 400c14ca198..d04fbadc746 100644 --- a/site/lib/src/components/cookie_notice.dart +++ b/site/lib/src/components/common/client/cookie_notice.dart @@ -5,8 +5,8 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/web.dart' as web; -import '../util.dart'; -import 'button.dart'; +import '../../../util.dart'; +import '../button.dart'; /// The cookie banner to show on a user's first time visiting the site. @client diff --git a/site/lib/src/components/copy_button.dart b/site/lib/src/components/common/client/copy_button.dart similarity index 98% rename from site/lib/src/components/copy_button.dart rename to site/lib/src/components/common/client/copy_button.dart index 5aa041dcc57..8fdaf2d306f 100644 --- a/site/lib/src/components/copy_button.dart +++ b/site/lib/src/components/common/client/copy_button.dart @@ -6,7 +6,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/web.dart' as web; -import 'button.dart'; +import '../button.dart'; @client class CopyButton extends StatefulComponent { diff --git a/site/lib/src/components/client/download_latest_button.dart b/site/lib/src/components/common/client/download_latest_button.dart similarity index 78% rename from site/lib/src/components/client/download_latest_button.dart rename to site/lib/src/components/common/client/download_latest_button.dart index 895556b6da3..25ba9fdfa6c 100644 --- a/site/lib/src/components/client/download_latest_button.dart +++ b/site/lib/src/components/common/client/download_latest_button.dart @@ -5,12 +5,27 @@ import 'package:jaspr/jaspr.dart'; import 'package:meta/meta.dart'; -import '../../models/flutter_release_model.dart'; +import '../../../models/flutter_release_model.dart'; @client class DownloadLatestButton extends StatefulComponent { const DownloadLatestButton({required this.os, this.arch, super.key}); + /// Creates a [DownloadLatestButton] from a set of attributes parsed + /// from markdown. + factory DownloadLatestButton.fromAttributes(Map attributes) { + final os = + attributes['os'] ?? + (throw Exception( + 'DownloadLatestButton component requires an "os" attribute.', + )); + final arch = attributes['arch']; + return DownloadLatestButton( + os: os.toLowerCase(), + arch: arch?.toLowerCase(), + ); + } + final String os; final String? arch; diff --git a/site/lib/src/components/feedback.dart b/site/lib/src/components/common/client/feedback.dart similarity index 97% rename from site/lib/src/components/feedback.dart rename to site/lib/src/components/common/client/feedback.dart index 68dc257e9dc..713a22c406b 100644 --- a/site/lib/src/components/feedback.dart +++ b/site/lib/src/components/common/client/feedback.dart @@ -4,8 +4,8 @@ import 'package:jaspr/jaspr.dart'; -import '../analytics/analytics.dart'; -import 'button.dart'; +import '../../../analytics/analytics.dart'; +import '../button.dart'; /// Provides the user options to provide feedback on the specified page. @client diff --git a/site/lib/src/components/client/on_this_page_button.dart b/site/lib/src/components/common/client/on_this_page_button.dart similarity index 100% rename from site/lib/src/components/client/on_this_page_button.dart rename to site/lib/src/components/common/client/on_this_page_button.dart diff --git a/site/lib/src/components/os_selector.dart b/site/lib/src/components/common/client/os_selector.dart similarity index 98% rename from site/lib/src/components/os_selector.dart rename to site/lib/src/components/common/client/os_selector.dart index c3d5428a163..24f5e3671f2 100644 --- a/site/lib/src/components/os_selector.dart +++ b/site/lib/src/components/common/client/os_selector.dart @@ -5,7 +5,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/web.dart' as web; -import '../util.dart'; +import '../../../util.dart'; @client class OsSelector extends StatefulComponent { diff --git a/site/lib/src/components/dash_image.dart b/site/lib/src/components/common/dash_image.dart similarity index 97% rename from site/lib/src/components/dash_image.dart rename to site/lib/src/components/common/dash_image.dart index 7ec5cf4daa0..0e008637ed1 100644 --- a/site/lib/src/components/dash_image.dart +++ b/site/lib/src/components/common/dash_image.dart @@ -5,7 +5,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; -import '../markdown/markdown_parser.dart'; +import '../../markdown/markdown_parser.dart'; class DashImage with CustomComponentBase { const DashImage(); diff --git a/site/lib/src/components/dropdown.dart b/site/lib/src/components/common/dropdown.dart similarity index 98% rename from site/lib/src/components/dropdown.dart rename to site/lib/src/components/common/dropdown.dart index 47ab880846a..f5ade0574c7 100644 --- a/site/lib/src/components/dropdown.dart +++ b/site/lib/src/components/common/dropdown.dart @@ -6,7 +6,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/web.dart' as web; -import 'util/global_event_listener.dart'; +import '../util/global_event_listener.dart'; /// The root component of a dropdown in a client component. /// diff --git a/site/lib/src/components/fragment_target.dart b/site/lib/src/components/common/fragment_target.dart similarity index 100% rename from site/lib/src/components/fragment_target.dart rename to site/lib/src/components/common/fragment_target.dart diff --git a/site/lib/src/components/material_icon.dart b/site/lib/src/components/common/material_icon.dart similarity index 97% rename from site/lib/src/components/material_icon.dart rename to site/lib/src/components/common/material_icon.dart index 4f04322e5f2..c9a87f50bc5 100644 --- a/site/lib/src/components/material_icon.dart +++ b/site/lib/src/components/common/material_icon.dart @@ -4,7 +4,7 @@ import 'package:jaspr/jaspr.dart'; -import '../util.dart'; +import '../../util.dart'; /// A Material Symbols icon rendered as a span element. class MaterialIcon extends StatelessComponent { diff --git a/site/lib/src/components/prev_next.dart b/site/lib/src/components/common/prev_next.dart similarity index 100% rename from site/lib/src/components/prev_next.dart rename to site/lib/src/components/common/prev_next.dart diff --git a/site/lib/src/components/search.dart b/site/lib/src/components/common/search.dart similarity index 100% rename from site/lib/src/components/search.dart rename to site/lib/src/components/common/search.dart diff --git a/site/lib/src/components/tabs.dart b/site/lib/src/components/common/tabs.dart similarity index 99% rename from site/lib/src/components/tabs.dart rename to site/lib/src/components/common/tabs.dart index e1712b938b1..407ff04caa6 100644 --- a/site/lib/src/components/tabs.dart +++ b/site/lib/src/components/common/tabs.dart @@ -5,7 +5,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; -import '../util.dart'; +import '../../util.dart'; /// A tabs component where children tabs can be switched between by the user. class DashTabs implements CustomComponent { diff --git a/site/lib/src/components/tags.dart b/site/lib/src/components/common/tags.dart similarity index 100% rename from site/lib/src/components/tags.dart rename to site/lib/src/components/common/tags.dart diff --git a/site/lib/src/components/wrapped_code_block.dart b/site/lib/src/components/common/wrapped_code_block.dart similarity index 98% rename from site/lib/src/components/wrapped_code_block.dart rename to site/lib/src/components/common/wrapped_code_block.dart index 6027ed1834e..1ed927706e0 100644 --- a/site/lib/src/components/wrapped_code_block.dart +++ b/site/lib/src/components/common/wrapped_code_block.dart @@ -4,8 +4,8 @@ import 'package:jaspr/jaspr.dart'; -import '../util.dart'; -import 'copy_button.dart'; +import '../../util.dart'; +import 'client/copy_button.dart'; /// A rendered code block with support for syntax highlighting, /// line highlighting, filenames, language specifying, diff --git a/site/lib/src/components/common/youtube_embed.dart b/site/lib/src/components/common/youtube_embed.dart new file mode 100644 index 00000000000..ce95ec7b4e2 --- /dev/null +++ b/site/lib/src/components/common/youtube_embed.dart @@ -0,0 +1,68 @@ +// Copyright 2025 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_content/jaspr_content.dart'; + +class YoutubeEmbed with CustomComponentBase { + const YoutubeEmbed(); + + @override + Pattern get pattern => RegExp('YouTubeEmbed', caseSensitive: false); + + @override + Component apply( + String name, + Map attributes, + Component? child, + ) { + final rawVideoId = + attributes['id'] ?? + (throw Exception('YouTubeEmbed component requires an "id" attribute.')); + final videoTitle = + attributes['title'] ?? + (throw Exception( + 'YouTubeEmbed component requires a "title" attribute.', + )); + final playlistId = attributes['playlist']; + + final String videoId; + final int startTime; + if (rawVideoId.contains('?')) { + final idAndStartTime = rawVideoId.split('?'); + videoId = idAndStartTime[0]; + + final rawStartTime = idAndStartTime[1].split('start=')[1]; + startTime = int.tryParse(rawStartTime) ?? 0; + } else { + startTime = 0; + videoId = rawVideoId; + } + + // Instead of directly including a YouTube embed iframe, + // we use https://github.com/justinribeiro/lite-youtube which + // lazily loads the video, significantly reduces page load times, + // and enables configurability through element attributes. + return Component.element( + tag: 'lite-youtube', + attributes: { + 'videoid': videoId, + 'videotitle': videoTitle, + 'videoStartAt': '$startTime', + if (playlistId != null) 'playlistid': playlistId, + }, + children: [ + a( + classes: 'lite-youtube-fallback', + href: 'https://www.youtube.com/watch/$videoId', + target: Target.blank, + attributes: {'rel': 'noopener'}, + [ + text('Watch on YouTube in a new tab: "$videoTitle"'), + ], + ), + ], + ); + } +} diff --git a/site/lib/src/components/client/dartpad_injector.dart b/site/lib/src/components/dartpad/dartpad_injector.dart similarity index 95% rename from site/lib/src/components/client/dartpad_injector.dart rename to site/lib/src/components/dartpad/dartpad_injector.dart index 63201b01a86..f9545c4dae2 100644 --- a/site/lib/src/components/client/dartpad_injector.dart +++ b/site/lib/src/components/dartpad/dartpad_injector.dart @@ -3,10 +3,9 @@ // found in the LICENSE file. import 'package:jaspr/jaspr.dart'; -import '../dartpad/embedded_dartpad.dart'; +import 'embedded_dartpad.dart'; -import '../dartpad/extract_content.dart' - if (dart.library.io) '../dartpad/extract_content_vm.dart'; +import 'extract_content.dart' if (dart.library.io) 'extract_content_vm.dart'; /// Prepares a code block that will be replaced with an embedded /// DartPad when the site is loaded. diff --git a/site/lib/src/components/banner.dart b/site/lib/src/components/layout/banner.dart similarity index 100% rename from site/lib/src/components/banner.dart rename to site/lib/src/components/layout/banner.dart diff --git a/site/lib/src/components/footer.dart b/site/lib/src/components/layout/footer.dart similarity index 100% rename from site/lib/src/components/footer.dart rename to site/lib/src/components/layout/footer.dart diff --git a/site/lib/src/components/header.dart b/site/lib/src/components/layout/header.dart similarity index 94% rename from site/lib/src/components/header.dart rename to site/lib/src/components/layout/header.dart index 61560d03437..14a0d962f6e 100644 --- a/site/lib/src/components/header.dart +++ b/site/lib/src/components/layout/header.dart @@ -4,11 +4,11 @@ import 'package:jaspr/jaspr.dart'; -import 'button.dart'; -import 'header/menu_toggle.dart'; -import 'header/site_switcher.dart'; -import 'header/theme_switcher.dart'; -import 'material_icon.dart'; +import '../common/button.dart'; +import '../common/material_icon.dart'; +import 'menu_toggle.dart'; +import 'site_switcher.dart'; +import 'theme_switcher.dart'; /// The site-wide top navigation bar. class DashHeader extends StatelessComponent { diff --git a/site/lib/src/components/header/menu_toggle.dart b/site/lib/src/components/layout/menu_toggle.dart similarity index 98% rename from site/lib/src/components/header/menu_toggle.dart rename to site/lib/src/components/layout/menu_toggle.dart index 64b777cb8e6..c2ee0f5b4ae 100644 --- a/site/lib/src/components/header/menu_toggle.dart +++ b/site/lib/src/components/layout/menu_toggle.dart @@ -6,7 +6,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/js_interop.dart'; import 'package:universal_web/web.dart' as web; -import '../material_icon.dart'; +import '../common/material_icon.dart'; @client final class MenuToggle extends StatefulComponent { diff --git a/site/lib/src/components/sidenav.dart b/site/lib/src/components/layout/sidenav.dart similarity index 98% rename from site/lib/src/components/sidenav.dart rename to site/lib/src/components/layout/sidenav.dart index c15dc0fef15..ddd0f7909a4 100644 --- a/site/lib/src/components/sidenav.dart +++ b/site/lib/src/components/layout/sidenav.dart @@ -4,9 +4,9 @@ import 'package:jaspr/jaspr.dart'; -import '../models/sidenav_model.dart'; -import '../util.dart'; -import 'material_icon.dart'; +import '../../models/sidenav_model.dart'; +import '../../util.dart'; +import '../common/material_icon.dart'; /// The site-wide side navigation menu, /// with entries loaded from the `src/data/sidenav.yml` file. diff --git a/site/lib/src/components/header/site_switcher.dart b/site/lib/src/components/layout/site_switcher.dart similarity index 98% rename from site/lib/src/components/header/site_switcher.dart rename to site/lib/src/components/layout/site_switcher.dart index 2f52fe4f47b..49e979f606f 100644 --- a/site/lib/src/components/header/site_switcher.dart +++ b/site/lib/src/components/layout/site_switcher.dart @@ -5,8 +5,8 @@ import 'package:jaspr/jaspr.dart'; import '../../util.dart'; -import '../button.dart'; -import '../dropdown.dart'; +import '../common/button.dart'; +import '../common/dropdown.dart'; @client final class SiteSwitcher extends StatelessComponent { diff --git a/site/lib/src/components/header/theme_switcher.dart b/site/lib/src/components/layout/theme_switcher.dart similarity index 97% rename from site/lib/src/components/header/theme_switcher.dart rename to site/lib/src/components/layout/theme_switcher.dart index 7fe8bfa8a4f..9227d6d7631 100644 --- a/site/lib/src/components/header/theme_switcher.dart +++ b/site/lib/src/components/layout/theme_switcher.dart @@ -5,9 +5,9 @@ import 'package:jaspr/jaspr.dart'; import 'package:universal_web/web.dart' as web; -import '../button.dart'; -import '../dropdown.dart'; -import '../material_icon.dart'; +import '../common/button.dart'; +import '../common/dropdown.dart'; +import '../common/material_icon.dart'; @client final class ThemeSwitcher extends StatefulComponent { diff --git a/site/lib/src/components/toc.dart b/site/lib/src/components/layout/toc.dart similarity index 95% rename from site/lib/src/components/toc.dart rename to site/lib/src/components/layout/toc.dart index f6ecd22f7c6..3721ff1d66c 100644 --- a/site/lib/src/components/toc.dart +++ b/site/lib/src/components/layout/toc.dart @@ -4,9 +4,9 @@ import 'package:jaspr/jaspr.dart'; -import '../models/on_this_page_model.dart'; -import 'client/on_this_page_button.dart'; -import 'material_icon.dart'; +import '../../models/on_this_page_model.dart'; +import '../common/client/on_this_page_button.dart'; +import '../common/material_icon.dart'; final class WideTableOfContents extends StatelessComponent { const WideTableOfContents(this.data); diff --git a/site/lib/src/components/trailing_content.dart b/site/lib/src/components/layout/trailing_content.dart similarity index 98% rename from site/lib/src/components/trailing_content.dart rename to site/lib/src/components/layout/trailing_content.dart index b876d71f6cd..4051200526c 100644 --- a/site/lib/src/components/trailing_content.dart +++ b/site/lib/src/components/layout/trailing_content.dart @@ -5,7 +5,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; -import 'feedback.dart'; +import '../common/client/feedback.dart'; /// The trailing content of a content documentation page, such as /// its last updated information, report an issue links, and similar. diff --git a/site/lib/src/components/client/archive_table.dart b/site/lib/src/components/pages/archive_table.dart similarity index 89% rename from site/lib/src/components/client/archive_table.dart rename to site/lib/src/components/pages/archive_table.dart index fa82e574cf1..58d81276c10 100644 --- a/site/lib/src/components/client/archive_table.dart +++ b/site/lib/src/components/pages/archive_table.dart @@ -11,6 +11,19 @@ import '../../models/flutter_release_model.dart'; class ArchiveTable extends StatefulComponent { const ArchiveTable({required this.os, required this.channel, super.key}); + /// Creates an [ArchiveTable] from a set of attributes parsed from markdown. + factory ArchiveTable.fromAttributes(Map attributes) { + final os = + attributes['os'] ?? + (throw Exception('ArchiveTable component requires an "os" attribute.')); + final channel = + attributes['channel'] ?? + (throw Exception( + 'ArchiveTable component requires a "channel" attribute.', + )); + return ArchiveTable(os: os.toLowerCase(), channel: channel.toLowerCase()); + } + final String os; final String channel; diff --git a/site/lib/src/components/expansion_list.dart b/site/lib/src/components/pages/expansion_list.dart similarity index 88% rename from site/lib/src/components/expansion_list.dart rename to site/lib/src/components/pages/expansion_list.dart index dbf15b0e4d7..48511c3d166 100644 --- a/site/lib/src/components/expansion_list.dart +++ b/site/lib/src/components/pages/expansion_list.dart @@ -7,8 +7,8 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; import 'package:path/path.dart' as path; -import '../markdown/markdown_parser.dart'; -import '../util.dart'; +import '../../markdown/markdown_parser.dart'; +import '../../util.dart'; class ExpansionListItem { ExpansionListItem({ @@ -71,12 +71,24 @@ class ExpansionList extends StatefulComponent { final String baseId; final List items; - /// Loads an [ExpansionList] by finding all pages in the current context that - /// are located in the specified [listName] directory (relative to the current - /// page's path). + /// Creates an [ExpansionList] from a set of attributes parsed from markdown. + /// + /// The items are found by locating all pages under the path specified by the + /// 'list' attribute, which is relative to the current page's path. /// /// Pages are sorted by their `order` frontmatter value. - static Component load(String listName, {required String baseId}) { + static Component fromAttributes(Map attributes) { + final listName = + attributes['list'] ?? + (throw Exception( + 'ExpansionList component requires a "list" attribute.', + )); + final baseId = + attributes['baseid'] ?? + (throw Exception( + 'ExpansionList component requires a "baseId" attribute.', + )); + return Builder( builder: (context) { final listPath = path.join( diff --git a/site/lib/src/components/client/learning_resource_filters.dart b/site/lib/src/components/pages/learning_resource_filters.dart similarity index 99% rename from site/lib/src/components/client/learning_resource_filters.dart rename to site/lib/src/components/pages/learning_resource_filters.dart index 63ee2e82107..37abf025721 100644 --- a/site/lib/src/components/client/learning_resource_filters.dart +++ b/site/lib/src/components/pages/learning_resource_filters.dart @@ -10,7 +10,7 @@ import 'package:universal_web/web.dart' as web; import '../../analytics/analytics.dart'; import '../../models/learning_resource_model.dart'; -import '../material_icon.dart'; +import '../common/material_icon.dart'; import '../util/global_event_listener.dart'; import 'learning_resource_filters_sidebar.dart'; diff --git a/site/lib/src/components/client/learning_resource_filters_sidebar.dart b/site/lib/src/components/pages/learning_resource_filters_sidebar.dart similarity index 99% rename from site/lib/src/components/client/learning_resource_filters_sidebar.dart rename to site/lib/src/components/pages/learning_resource_filters_sidebar.dart index d61988919bd..5a23001157e 100644 --- a/site/lib/src/components/client/learning_resource_filters_sidebar.dart +++ b/site/lib/src/components/pages/learning_resource_filters_sidebar.dart @@ -7,7 +7,7 @@ import 'package:jaspr/jaspr.dart'; import '../../analytics/analytics.dart'; import '../../models/learning_resource_model.dart'; import '../../util.dart'; -import '../material_icon.dart'; +import '../common/material_icon.dart'; import 'learning_resource_filters.dart'; @client diff --git a/site/lib/src/components/pages/learning_resource_index.dart b/site/lib/src/components/pages/learning_resource_index.dart index e1ec065c494..a55a9353cf6 100644 --- a/site/lib/src/components/pages/learning_resource_index.dart +++ b/site/lib/src/components/pages/learning_resource_index.dart @@ -7,8 +7,8 @@ import 'package:jaspr_content/jaspr_content.dart'; import '../../models/learning_resource_model.dart'; import '../../util.dart'; -import '../client/learning_resource_filters.dart'; -import '../client/learning_resource_filters_sidebar.dart'; +import 'learning_resource_filters.dart'; +import 'learning_resource_filters_sidebar.dart'; final class LearningResourceIndex extends StatelessComponent { LearningResourceIndex({super.key}); diff --git a/site/lib/src/extensions/code_block_processor.dart b/site/lib/src/extensions/code_block_processor.dart index 94b09f04b95..20c7e0911c9 100644 --- a/site/lib/src/extensions/code_block_processor.dart +++ b/site/lib/src/extensions/code_block_processor.dart @@ -10,8 +10,8 @@ import 'package:jaspr_content/jaspr_content.dart'; import 'package:meta/meta.dart'; import 'package:opal/opal.dart' as opal; -import '../components/client/dartpad_injector.dart'; -import '../components/wrapped_code_block.dart'; +import '../components/common/wrapped_code_block.dart'; +import '../components/dartpad/dartpad_injector.dart'; import '../highlight/theme/dark.dart'; import '../highlight/theme/light.dart'; import '../highlight/token_renderer.dart' as highlighter; diff --git a/site/lib/src/layouts/dash_layout.dart b/site/lib/src/layouts/dash_layout.dart index 5cd2c8a4709..102f47d9e1a 100644 --- a/site/lib/src/layouts/dash_layout.dart +++ b/site/lib/src/layouts/dash_layout.dart @@ -8,10 +8,10 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; import '../client/global_scripts.dart'; -import '../components/cookie_notice.dart'; -import '../components/footer.dart'; -import '../components/header.dart'; -import '../components/sidenav.dart'; +import '../components/common/client/cookie_notice.dart'; +import '../components/layout/footer.dart'; +import '../components/layout/header.dart'; +import '../components/layout/sidenav.dart'; import '../models/sidenav_model.dart'; import '../style_hash.dart'; import '../util.dart'; diff --git a/site/lib/src/layouts/doc_layout.dart b/site/lib/src/layouts/doc_layout.dart index d7e5e7c4cc6..dbb9463a88d 100644 --- a/site/lib/src/layouts/doc_layout.dart +++ b/site/lib/src/layouts/doc_layout.dart @@ -5,11 +5,11 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; -import '../components/banner.dart'; -import '../components/breadcrumbs.dart'; -import '../components/prev_next.dart'; -import '../components/toc.dart'; -import '../components/trailing_content.dart'; +import '../components/common/breadcrumbs.dart'; +import '../components/common/prev_next.dart'; +import '../components/layout/banner.dart'; +import '../components/layout/toc.dart'; +import '../components/layout/trailing_content.dart'; import '../extensions/header_extractor.dart'; import '../models/on_this_page_model.dart'; import '../util.dart'; diff --git a/site/lib/src/layouts/toc_layout.dart b/site/lib/src/layouts/toc_layout.dart index b3ded2d97be..9461f70a672 100644 --- a/site/lib/src/layouts/toc_layout.dart +++ b/site/lib/src/layouts/toc_layout.dart @@ -5,7 +5,7 @@ import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; -import '../components/card.dart'; +import '../components/common/card.dart'; import 'doc_layout.dart'; class TocLayout extends DocLayout {