|
| 1 | +import 'package:jaspr/dom.dart'; |
| 2 | +import 'package:jaspr/jaspr.dart'; |
| 3 | +import 'package:jaspr_content/jaspr_content.dart'; |
| 4 | + |
| 5 | +/// "Edit this page" link shown below doc content, matching Docusaurus exactly. |
| 6 | +/// |
| 7 | +/// Replicates the behavior of Docusaurus's [EditThisPage] component + |
| 8 | +/// [EditMetaRow] wrapper, using the same editUrl base from docusaurus.config.js: |
| 9 | +/// https://github.com/VeryGoodOpenSource/very_good_workflows/tree/main/site |
| 10 | +/// |
| 11 | +/// The SVG pencil icon is the exact path from Docusaurus's Icon/Edit/index.tsx |
| 12 | +/// (40×40 viewBox, fill="currentColor"). |
| 13 | +class EditPageLink extends StatelessComponent { |
| 14 | + const EditPageLink({super.key}); |
| 15 | + |
| 16 | + static const _editUrlBase = |
| 17 | + 'https://github.com/VeryGoodOpenSource/very_good_workflows/tree/main/site'; |
| 18 | + |
| 19 | + @override |
| 20 | + Component build(BuildContext context) { |
| 21 | + // This component is server-rendered only; context.page is not available |
| 22 | + // on the client. |
| 23 | + if (kIsWeb) return Component.fragment([]); |
| 24 | + |
| 25 | + final url = context.page.url; |
| 26 | + // Only render on /docs/* pages. |
| 27 | + if (!url.startsWith('/docs/')) return Component.fragment([]); |
| 28 | + |
| 29 | + // /docs/workflows/license_check → docs/workflows/license_check.md |
| 30 | + final filePath = '${url.replaceFirst('/', '')}.md'; |
| 31 | + final editUrl = '$_editUrlBase/$filePath'; |
| 32 | + |
| 33 | + return div(classes: 'edit-page-row', [ |
| 34 | + a( |
| 35 | + href: editUrl, |
| 36 | + classes: 'edit-page-link theme-edit-this-page', |
| 37 | + attributes: { |
| 38 | + 'target': '_blank', |
| 39 | + 'rel': 'noopener noreferrer', |
| 40 | + }, |
| 41 | + [ |
| 42 | + // Exact SVG from Docusaurus packages/docusaurus-theme-classic/ |
| 43 | + // src/theme/Icon/Edit/index.tsx |
| 44 | + // viewBox="0 0 40 40", fill="currentColor" (filled pencil) |
| 45 | + svg( |
| 46 | + width: 20.px, |
| 47 | + height: 20.px, |
| 48 | + viewBox: '0 0 40 40', |
| 49 | + attributes: { |
| 50 | + 'fill': 'currentColor', |
| 51 | + 'aria-hidden': 'true', |
| 52 | + }, |
| 53 | + [ |
| 54 | + path( |
| 55 | + d: 'm34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z', |
| 56 | + [], |
| 57 | + ), |
| 58 | + ], |
| 59 | + ), |
| 60 | + Component.text('Edit this page'), |
| 61 | + ], |
| 62 | + ), |
| 63 | + ]); |
| 64 | + } |
| 65 | + |
| 66 | + @css |
| 67 | + static List<StyleRule> get styles => [ |
| 68 | + // Block container — provides the 3rem top margin gap from content, |
| 69 | + // matching Docusaurus's `docusaurus-mt-lg` class on DocItemFooter. |
| 70 | + css('.edit-page-row').styles( |
| 71 | + margin: Margin.only(top: 3.rem), |
| 72 | + ), |
| 73 | + |
| 74 | + // The link itself: inline-flex so icon and text sit side by side. |
| 75 | + // color matches Infima's --ifm-link-color = var(--ifm-color-primary). |
| 76 | + css('.edit-page-link').styles( |
| 77 | + display: Display.inlineFlex, |
| 78 | + alignItems: AlignItems.center, |
| 79 | + color: Color('#2a48df'), |
| 80 | + textDecoration: TextDecoration.none, |
| 81 | + raw: {'gap': '0.3em'}, |
| 82 | + ), |
| 83 | + css('.edit-page-link:hover').styles( |
| 84 | + textDecoration: TextDecoration(line: TextDecorationLine.underline), |
| 85 | + ), |
| 86 | + |
| 87 | + // Dark mode: --ifm-color-primary in dark = #66fbd1 |
| 88 | + css('[data-theme="dark"] .edit-page-link').styles( |
| 89 | + color: Color('#66fbd1'), |
| 90 | + ), |
| 91 | + |
| 92 | + // Prevent the SVG from growing/shrinking inside the flex row. |
| 93 | + css('.edit-page-link svg').styles( |
| 94 | + flex: Flex(shrink: 0), |
| 95 | + ), |
| 96 | + ]; |
| 97 | +} |
0 commit comments