diff --git a/app/lib/frontend/dom/dom.dart b/app/lib/frontend/dom/dom.dart index 8fecba7355..c180c94c26 100644 --- a/app/lib/frontend/dom/dom.dart +++ b/app/lib/frontend/dom/dom.dart @@ -224,6 +224,7 @@ Node a({ String? rel, String? target, String? title, + String? name, }) { return dom.element( 'a', @@ -234,6 +235,7 @@ Node a({ if (rel != null) 'rel': rel, if (target != null) 'target': target, if (title != null) 'title': title, + if (name != null) 'name': name, ...?attributes, }, children: children, diff --git a/app/lib/frontend/templates/views/pkg/admin_page.dart b/app/lib/frontend/templates/views/pkg/admin_page.dart index bc332a4501..1da15346c5 100644 --- a/app/lib/frontend/templates/views/pkg/admin_page.dart +++ b/app/lib/frontend/templates/views/pkg/admin_page.dart @@ -9,6 +9,7 @@ import '../../../../package/models.dart'; import '../../../../shared/urls.dart' as urls; import '../../../dom/dom.dart' as d; import '../../../dom/material.dart' as material; +import '../shared/toc.dart'; /// Creates the package admin page content. d.Node packageAdminPageNode({ @@ -20,6 +21,28 @@ d.Node packageAdminPageNode({ }) { final pkgHasPublisher = package.publisherId != null; return d.fragment([ + renderToc([ + TocNode('Ownership', href: '#ownership'), + TocNode( + 'Options', + href: '#options', + children: [ + TocNode('Discontinued', href: '#discontinued'), + TocNode('Unlisted', href: '#unlisted'), + ], + ), + TocNode( + 'Automated publishing', + href: '#automated-publishing', + children: [ + TocNode('GitHub Actions', href: '#github-actions'), + TocNode('Google Cloud Service account', + href: '#google-cloud-service-account'), + ], + ), + TocNode('Version retraction', href: '#version-retraction'), + ]), + d.a(name: 'ownership'), d.h2(text: 'Package ownership'), d.div(children: [ if (!pkgHasPublisher) ...[ @@ -121,7 +144,9 @@ d.Node packageAdminPageNode({ ), ], ]), - d.h2(text: 'Package Options'), + d.a(name: 'options'), + d.h2(text: 'Package options'), + d.a(name: 'discontinued'), d.h3(text: 'Discontinued'), d.markdown( 'A package can be marked as [discontinued](https://dart.dev/tools/pub/publishing#discontinue) ' @@ -161,6 +186,7 @@ d.Node packageAdminPageNode({ ), ], if (!package.isDiscontinued) ...[ + d.a(name: 'unlisted'), d.h3(text: 'Unlisted'), d.markdown( 'A package that\'s marked as *unlisted* doesn\'t normally appear in search results on pub.dev. ' @@ -175,7 +201,8 @@ d.Node packageAdminPageNode({ ), ], _automatedPublishing(package), - d.h2(text: 'Package Version Retraction'), + d.a(name: 'version-retraction'), + d.h2(text: 'Version retraction'), d.div(children: [ d.markdown( 'You can [retract](https://dart.dev/go/package-retraction) a package version up to 7 days after publication.'), @@ -184,7 +211,7 @@ d.Node packageAdminPageNode({ ' it and stop new applications from taking dependency on it without a dependency override.'), d.markdown( 'You can restore a retracted package version if the version was retracted within the last 7 days.'), - d.h3(text: 'Retract Package Version'), + d.h3(text: 'Retract package version'), if (retractableVersions.isNotEmpty) ...[ material.dropdown( id: '-admin-retract-package-version-input', @@ -208,7 +235,7 @@ d.Node packageAdminPageNode({ if (retractableVersions.isEmpty) d.markdown('This package has no retractable versions.'), ]), - d.h3(text: 'Restore Retracted Package Version'), + d.h3(text: 'Restore retracted package version'), d.div(children: [ if (retractedVersions.isNotEmpty) ...[ material.dropdown( @@ -242,7 +269,9 @@ d.Node _automatedPublishing(Package package) { final gcp = package.automatedPublishing?.gcpConfig; final isGithubEnabled = github?.isEnabled ?? false; return d.fragment([ + d.a(name: 'automated-publishing'), d.h2(text: 'Automated publishing'), + d.a(name: 'github-actions'), d.h3(text: 'Publishing from GitHub Actions'), d.div( classes: [ @@ -342,6 +371,7 @@ d.Node _automatedPublishing(Package package) { if (isGithubEnabled) _exampleGithubWorkflow(github!), ], ), + d.a(name: 'google-cloud-service-account'), d.h3(text: 'Publishing with Google Cloud Service account'), d.markdown( 'When publishing with a GCP _service account_ is enabled, the service account configured here ' diff --git a/app/lib/frontend/templates/views/shared/toc.dart b/app/lib/frontend/templates/views/shared/toc.dart new file mode 100644 index 0000000000..163a3af625 --- /dev/null +++ b/app/lib/frontend/templates/views/shared/toc.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../../../dom/dom.dart' as d; + +/// Renders Table of Contents from [nodes]. +/// +/// The hiearchy of the sections is encoded in [TocNode.children]. +d.Node renderToc(Iterable nodes) { + return d.div( + classes: ['pub-toc-container'], + child: d.div( + classes: ['pub-toc'], + children: [ + d.div(child: d.b(text: 'Sections')), + ...nodes.expand((n) => _renderNode(n, 0)), + ], + ), + ); +} + +Iterable _renderNode(TocNode node, int level) sync* { + yield d.div( + classes: ['pub-toc-node', 'pub-toc-node-$level'], + child: node.href == null + ? d.text(node.label) + : d.a(href: node.href, text: node.label), + ); + if (node.children != null) { + yield* node.children!.expand((n) => _renderNode(n, level + 1)); + } +} + +/// Describes a tree node in the table of contents hierarchy. +class TocNode { + /// The text label to be display. + final String label; + + /// The link href to use, if omitted, only a regular text is displayed. + final String? href; + + /// Children nodes of this node. + List? children; + + TocNode( + this.label, { + this.href, + this.children, + }); +} diff --git a/app/test/frontend/golden/pkg_admin_page.html b/app/test/frontend/golden/pkg_admin_page.html index e1a473f508..5a4ad754cd 100644 --- a/app/test/frontend/golden/pkg_admin_page.html +++ b/app/test/frontend/golden/pkg_admin_page.html @@ -241,6 +241,38 @@

Metadata

+
+ +
+

Package ownership

You can transfer this package to a verified publisher if you are a member of the publisher. Transferring the package removes the current uploaders, so that only the members of the publisher can upload new versions.

@@ -321,7 +353,9 @@

Uploaders

-

Package Options

+ +

Package options

+

Discontinued

A package can be marked as @@ -345,6 +379,7 @@

Discontinued

+

Unlisted

A package that's marked as @@ -366,7 +401,9 @@

Unlisted

+

Automated publishing

+

Publishing from GitHub Actions

@@ -486,6 +523,7 @@

Publishing from GitHub Actions

+

Publishing with Google Cloud Service account

When publishing with a GCP @@ -527,7 +565,8 @@

Publishing with Google Cloud Service account

-

Package Version Retraction

+ +

Version retraction

You can @@ -536,7 +575,7 @@

Package Version Retraction

This will not remove the package version, but warn developers using it and stop new applications from taking dependency on it without a dependency override.

You can restore a retracted package version if the version was retracted within the last 7 days.

-

Retract Package Version

+

Retract package version

@@ -563,7 +602,7 @@

Retract Package Version

-

Restore Retracted Package Version

+

Restore retracted package version

diff --git a/pkg/web_css/lib/src/_base.scss b/pkg/web_css/lib/src/_base.scss index 670400a1f3..bb456547c6 100644 --- a/pkg/web_css/lib/src/_base.scss +++ b/pkg/web_css/lib/src/_base.scss @@ -440,3 +440,26 @@ a.-x-ago { padding: 8px; } } + +.pub-toc { + border-left: 4px solid var(--pub-inset-bgColor); + padding: 4px 12px; + color: var(--pub-default-text-color); + + a { + color: var(--pub-default-text-color); + } + + .pub-toc-node-0 { + margin-top: 4px; + } + .pub-toc-node-1 { + margin-left: 12px; + } + .pub-toc-node-2 { + margin-left: 24px; + } + .pub-toc-node-3 { + margin-left: 36px; + } +} diff --git a/pkg/web_css/test/expression_test.dart b/pkg/web_css/test/expression_test.dart index 4c57b21322..0d50631d8e 100644 --- a/pkg/web_css/test/expression_test.dart +++ b/pkg/web_css/test/expression_test.dart @@ -48,6 +48,7 @@ void main() { expressions.removeWhere( (e) => e.startsWith('detail-tab-') && e.endsWith('-content')); expressions.removeWhere((e) => e.startsWith('package-badge-')); + expressions.removeWhere((e) => e.startsWith('pub-toc-node-')); // shared CSS file (with dartdoc) expressions.removeAll([ 'cookie-notice-container',