From 90cf39750765517b9dce539a15124144dce8153c Mon Sep 17 00:00:00 2001 From: Anderson-Andre-P Date: Tue, 9 Jun 2026 20:38:59 -0300 Subject: [PATCH 1/3] feat(docs): add 'Copy page' button to page header options dropdown --- .../common/client/page_header_options.dart | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sites/docs/lib/src/components/common/client/page_header_options.dart b/sites/docs/lib/src/components/common/client/page_header_options.dart index 79115c68104..58f64f3c2ee 100644 --- a/sites/docs/lib/src/components/common/client/page_header_options.dart +++ b/sites/docs/lib/src/components/common/client/page_header_options.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:http/http.dart' as http; import 'package:jaspr/dom.dart'; import 'package:jaspr/jaspr.dart'; import 'package:universal_web/js_interop.dart'; @@ -51,6 +52,23 @@ final class _PageHeaderOptionsState extends State { String get _currentBaseUrl => web.window.location.origin + web.window.location.pathname; + String? get _rawMarkdownUrl { + final sourceUrl = component.sourceUrl; + if (sourceUrl == null) return null; + return sourceUrl + .replaceFirst('github.com', 'raw.githubusercontent.com') + .replaceFirst('/blob/', '/'); + } + + Future _copyPageContent() async { + final rawUrl = _rawMarkdownUrl; + if (rawUrl == null) return; + final response = await http.get(Uri.parse(rawUrl)); + if (response.statusCode == 200) { + await web.window.navigator.clipboard.writeText(response.body).toDart; + } + } + web.ShareData get _shareData => web.ShareData( url: _currentBaseUrl, title: component.title, @@ -91,6 +109,16 @@ final class _PageHeaderOptionsState extends State { ), ], ), + if (_rawMarkdownUrl != null) + li( + [ + Button( + icon: 'content_copy', + content: 'Copy page', + onClick: () => _copyPageContent().ignore(), + ), + ], + ), if (component.sourceUrl case final sourceUrl?) li( [ From b386310ebe09add8d39f6d0247aba4523b2773ed Mon Sep 17 00:00:00 2001 From: Anderson-Andre-P Date: Tue, 9 Jun 2026 20:42:12 -0300 Subject: [PATCH 2/3] feat(docs): add success feedback to 'Copy page' button --- sites/docs/lib/_sass/components/_misc.scss | 20 +++++++++++++++++++ .../common/client/page_header_options.dart | 20 +++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/sites/docs/lib/_sass/components/_misc.scss b/sites/docs/lib/_sass/components/_misc.scss index 9eb5f6ce177..aa1d74bf290 100644 --- a/sites/docs/lib/_sass/components/_misc.scss +++ b/sites/docs/lib/_sass/components/_misc.scss @@ -1,3 +1,23 @@ +.copy-page-toast { + position: fixed; + bottom: 1.5rem; + left: 50%; + transform: translateX(-50%); + background-color: var(--site-primary-color, #0175c2); + color: #fff; + padding: 0.65rem 1.25rem; + border-radius: var(--site-radius, 0.5rem); + font-size: 0.9rem; + box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.2); + z-index: 1000; + animation: copy-page-toast-in 0.2s ease; +} + +@keyframes copy-page-toast-in { + from { opacity: 0; transform: translateX(-50%) translateY(0.5rem); } + to { opacity: 1; transform: translateX(-50%) translateY(0); } +} + #page-content p.install-help { text-align: right; margin-block-start: -2.5rem; diff --git a/sites/docs/lib/src/components/common/client/page_header_options.dart b/sites/docs/lib/src/components/common/client/page_header_options.dart index 58f64f3c2ee..6ffc54af8bd 100644 --- a/sites/docs/lib/src/components/common/client/page_header_options.dart +++ b/sites/docs/lib/src/components/common/client/page_header_options.dart @@ -30,6 +30,7 @@ final class PageHeaderOptions extends StatefulComponent { final class _PageHeaderOptionsState extends State { bool _isShareSupported = false; + bool _copied = false; @override void initState() { @@ -66,6 +67,10 @@ final class _PageHeaderOptionsState extends State { final response = await http.get(Uri.parse(rawUrl)); if (response.statusCode == 200) { await web.window.navigator.clipboard.writeText(response.body).toDart; + setState(() => _copied = true); + Future.delayed(const Duration(seconds: 2), () { + if (mounted) setState(() => _copied = false); + }); } } @@ -75,7 +80,8 @@ final class _PageHeaderOptionsState extends State { ); @override - Component build(BuildContext _) => Dropdown( + Component build(BuildContext _) => .fragment([ + Dropdown( id: 'page-header-options', toggle: const Button(icon: 'more_vert', title: 'View page options.'), content: nav( @@ -113,8 +119,8 @@ final class _PageHeaderOptionsState extends State { li( [ Button( - icon: 'content_copy', - content: 'Copy page', + icon: _copied ? 'check' : 'content_copy', + content: _copied ? 'Copied!' : 'Copy page', onClick: () => _copyPageContent().ignore(), ), ], @@ -151,5 +157,11 @@ final class _PageHeaderOptionsState extends State { ), ], ), - ); + ), + if (_copied) + div( + classes: 'copy-page-toast', + [.text('Page copied to clipboard!')], + ), +]); } From af34a45ccc43403ce2b9b13ad2edd042e84b6aca Mon Sep 17 00:00:00 2001 From: Anderson-Andre-P Date: Tue, 9 Jun 2026 20:52:09 -0300 Subject: [PATCH 3/3] feat(docs): show 'Copy page' button only on pages with substantial content --- sites/docs/lib/main.client.options.dart | 1 + sites/docs/lib/main.server.options.dart | 7 ++++++- .../components/common/client/page_header_options.dart | 4 +++- sites/docs/lib/src/components/common/page_header.dart | 3 +++ sites/docs/lib/src/layouts/doc_layout.dart | 9 +++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/sites/docs/lib/main.client.options.dart b/sites/docs/lib/main.client.options.dart index 6ebbe20949f..f7550f26749 100644 --- a/sites/docs/lib/main.client.options.dart +++ b/sites/docs/lib/main.client.options.dart @@ -116,6 +116,7 @@ ClientOptions get defaultClientOptions => ClientOptions( title: p['title'] as String, sourceUrl: p['sourceUrl'] as String?, issueUrl: p['issueUrl'] as String?, + showCopyPage: p['showCopyPage'] as bool, ), loader: _page_header_options.loadLibrary, ), diff --git a/sites/docs/lib/main.server.options.dart b/sites/docs/lib/main.server.options.dart index 6262d37d117..0787d09c20e 100644 --- a/sites/docs/lib/main.server.options.dart +++ b/sites/docs/lib/main.server.options.dart @@ -169,7 +169,12 @@ Map __feedbackFeedbackComponent( ) => {'issueUrl': c.issueUrl}; Map __page_header_optionsPageHeaderOptions( _page_header_options.PageHeaderOptions c, -) => {'title': c.title, 'sourceUrl': c.sourceUrl, 'issueUrl': c.issueUrl}; +) => { + 'title': c.title, + 'sourceUrl': c.sourceUrl, + 'issueUrl': c.issueUrl, + 'showCopyPage': c.showCopyPage, +}; Map __simple_tooltipSimpleTooltip( _simple_tooltip.SimpleTooltip c, ) => {'target': c.target.toId(), 'content': c.content.toId()}; diff --git a/sites/docs/lib/src/components/common/client/page_header_options.dart b/sites/docs/lib/src/components/common/client/page_header_options.dart index 6ffc54af8bd..8fccb8a63ed 100644 --- a/sites/docs/lib/src/components/common/client/page_header_options.dart +++ b/sites/docs/lib/src/components/common/client/page_header_options.dart @@ -17,12 +17,14 @@ final class PageHeaderOptions extends StatefulComponent { required this.title, this.sourceUrl, this.issueUrl, + this.showCopyPage = true, super.key, }); final String title; final String? sourceUrl; final String? issueUrl; + final bool showCopyPage; @override State createState() => _PageHeaderOptionsState(); @@ -115,7 +117,7 @@ final class _PageHeaderOptionsState extends State { ), ], ), - if (_rawMarkdownUrl != null) + if (component.showCopyPage && _rawMarkdownUrl != null) li( [ Button( diff --git a/sites/docs/lib/src/components/common/page_header.dart b/sites/docs/lib/src/components/common/page_header.dart index 74178855586..a13e6d7c4ee 100644 --- a/sites/docs/lib/src/components/common/page_header.dart +++ b/sites/docs/lib/src/components/common/page_header.dart @@ -19,12 +19,14 @@ final class PageHeader extends StatelessComponent { this.description, this.wrap = true, this.showBreadcrumbs = true, + this.showCopyPage = true, }); final String title; final String? description; final bool wrap; final bool showBreadcrumbs; + final bool showCopyPage; @override Component build(BuildContext context) { @@ -49,6 +51,7 @@ final class PageHeader extends StatelessComponent { title: title, sourceUrl: sourceInfo.sourceUrl, issueUrl: sourceInfo.issueUrl, + showCopyPage: showCopyPage, ), ], ); diff --git a/sites/docs/lib/src/layouts/doc_layout.dart b/sites/docs/lib/src/layouts/doc_layout.dart index 35974839360..6a48cb0acf7 100644 --- a/sites/docs/lib/src/layouts/doc_layout.dart +++ b/sites/docs/lib/src/layouts/doc_layout.dart @@ -45,6 +45,14 @@ class DocLayout extends FlutterDocsLayout { ); } + static final _htmlTagPattern = RegExp(r'<[^>]+>'); + + bool _hasSubstantialContent(Page page) { + final text = page.content.replaceAll(_htmlTagPattern, ' '); + final wordCount = text.trim().split(RegExp(r'\s+')).where((w) => w.isNotEmpty).length; + return wordCount > 150; + } + @override Component buildBody(Page page, Component child) { final pageData = page.data.page; @@ -94,6 +102,7 @@ class DocLayout extends FlutterDocsLayout { showBreadcrumbs: allowBreadcrumbs && (pageData['showBreadcrumbs'] as bool? ?? true), + showCopyPage: _hasSubstantialContent(page), ), child,