Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1349753

Browse files
committed
feat(docs-infra): Adds copy link to anchor functionality
Enables copying a direct link to any section by clicking its anchor. Also updates the aria-label to remove the code tag.
1 parentef034c1 commit1349753

File tree

6 files changed

+144
-0
lines changed

6 files changed

+144
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("//adev/shared-docs:defaults.bzl","ng_project")
2+
3+
ng_project(
4+
name="copy-link-anchor",
5+
srcs= [
6+
"copy-link-anchor.component.ts",
7+
],
8+
visibility= [
9+
"//adev/shared-docs/components:__pkg__",
10+
"//adev/shared-docs/components/viewers:__pkg__",
11+
],
12+
deps= [
13+
"//adev:node_modules/@angular/cdk",
14+
"//adev:node_modules/@angular/common",
15+
"//adev:node_modules/@angular/core",
16+
"//adev:node_modules/@angular/material",
17+
],
18+
)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*!
2+
*@license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import{ChangeDetectionStrategy,Component,computed,inject,input,signal}from'@angular/core';
10+
import{Clipboard}from'@angular/cdk/clipboard';
11+
import{MatTooltip}from'@angular/material/tooltip';
12+
13+
exportconstCOPY_LINK_DISPLAY_TIME_MS=500;
14+
15+
@Component({
16+
selector:'docs-copy-link-anchor',
17+
template:`
18+
<a
19+
[href]="href()"
20+
class="docs-anchor"
21+
[class.docs-anchor-copied]="copied()"
22+
tabindex="-1"
23+
[ariaLabel]="normalizedLabel()"
24+
[matTooltip]="normalizedLabel()"
25+
matTooltipPosition="after"
26+
(click)="copyLink()"
27+
[innerHTML]="label()"
28+
></a>
29+
`,
30+
imports:[MatTooltip],
31+
changeDetection:ChangeDetectionStrategy.OnPush,
32+
})
33+
exportclassCopyLinkAnchor{
34+
readonlyhref=input.required<string>();
35+
readonlylabel=input.required<string>();
36+
37+
// Remove <code> tags from the label for aria-label and tooltip
38+
readonlynormalizedLabel=computed(()=>this.label().replace(/<\/?code>/g,''));
39+
40+
privatereadonlyclipboard=inject(Clipboard);
41+
42+
protectedreadonlycopied=signal(false);
43+
44+
copyLink():void{
45+
constlink=`${window.location.origin}${this.href()}`;
46+
this.clipboard.copy(link);
47+
48+
this.copied.set(true);
49+
50+
setTimeout(()=>{
51+
this.copied.set(false);
52+
},COPY_LINK_DISPLAY_TIME_MS);
53+
}
54+
}

‎adev/shared-docs/components/viewers/BUILD.bazel‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ng_project(
2525
"//adev:node_modules/@angular/router",
2626
"//adev:node_modules/rxjs",
2727
"//adev/shared-docs/components/breadcrumb",
28+
"//adev/shared-docs/components/copy-link-anchor",
2829
"//adev/shared-docs/components/copy-source-code-button",
2930
"//adev/shared-docs/components/icon",
3031
"//adev/shared-docs/components/tab-group",
@@ -63,6 +64,7 @@ ts_project(
6364
"//adev:node_modules/@angular/platform-browser",
6465
"//adev:node_modules/@angular/router",
6566
"//adev/shared-docs/components/breadcrumb",
67+
"//adev/shared-docs/components/copy-link-anchor",
6668
"//adev/shared-docs/components/copy-source-code-button",
6769
"//adev/shared-docs/components/icon",
6870
"//adev/shared-docs/components/table-of-contents",

‎adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.spec.ts‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import {IconComponent} from '../../icon/icon.component';
1717
import{Breadcrumb}from'../../breadcrumb/breadcrumb.component';
1818
import{NavigationState}from'../../../services';
1919
import{CopySourceCodeButton}from'../../copy-source-code-button/copy-source-code-button.component';
20+
import{CopyLinkAnchor}from'../../copy-link-anchor/copy-link-anchor.component';
2021
import{TableOfContents}from'../../table-of-contents/table-of-contents.component';
2122
import{provideZonelessChangeDetection}from'@angular/core';
23+
import{Clipboard}from'@angular/cdk/clipboard';
2224

2325
describe('DocViewer',()=>{
2426
letfixture:ComponentFixture<DocViewer>;
@@ -70,6 +72,12 @@ describe('DocViewer', () => {
7072
<h3>Heading h3</h3>
7173
`;
7274

75+
constexampleContentWithDocsAnchor=`
76+
<h2 id="test-section">
77+
<a href="#test-section" class="docs-anchor" tabindex="-1" aria-label="Link to Test Section">Test Section</a>
78+
</h2>
79+
`;
80+
7381
beforeEach(()=>{
7482
exampleContentSpy=jasmine.createSpyObj('ExampleViewerContentLoader',['getCodeExampleData']);
7583
navigationStateSpy=jasmine.createSpyObj(NavigationState,['activeNavigationItem']);
@@ -208,4 +216,38 @@ describe('DocViewer', () => {
208216

209217
expect(renderComponentSpy).not.toHaveBeenCalled();
210218
});
219+
220+
it('should setup copy link functionality for docs-anchor elements',async()=>{
221+
constfixture=TestBed.createComponent(DocViewer);
222+
fixture.componentRef.setInput('docContent',exampleContentWithDocsAnchor);
223+
224+
fixture.detectChanges();
225+
awaitfixture.whenStable();
226+
227+
constcopyLinkAnchor=fixture.debugElement.query(By.directive(CopyLinkAnchor));
228+
expect(copyLinkAnchor).toBeTruthy();
229+
230+
constanchor=fixture.nativeElement.querySelector('a.docs-anchor')asHTMLAnchorElement;
231+
expect(anchor).toBeTruthy();
232+
});
233+
234+
it('should copy link to clipboard and show feedback when docs-anchor is clicked',async()=>{
235+
constclipboard=TestBed.inject(Clipboard);
236+
constclipboardSpy=spyOn(clipboard,'copy').and.returnValue(true);
237+
238+
constfixture=TestBed.createComponent(DocViewer);
239+
fixture.componentRef.setInput('docContent',exampleContentWithDocsAnchor);
240+
241+
fixture.detectChanges();
242+
awaitfixture.whenStable();
243+
244+
constanchor=fixture.nativeElement.querySelector('a.docs-anchor')asHTMLAnchorElement;
245+
anchor.click();
246+
247+
fixture.detectChanges();
248+
awaitfixture.whenStable();
249+
250+
expect(clipboardSpy).toHaveBeenCalled();
251+
expect(anchor.classList.contains('docs-anchor-copied')).toBeTrue();
252+
});
211253
});

‎adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {TableOfContents} from '../../table-of-contents/table-of-contents.compone
3939
import{DomSanitizer}from'@angular/platform-browser';
4040
import{Breadcrumb}from'../../breadcrumb/breadcrumb.component';
4141
import{CopySourceCodeButton}from'../../copy-source-code-button/copy-source-code-button.component';
42+
import{CopyLinkAnchor}from'../../copy-link-anchor/copy-link-anchor.component';
4243
import{ExampleViewer}from'../example-viewer/example-viewer.component';
4344
import{TabGroup}from'../../tab-group/tab-group.component';
4445

@@ -120,6 +121,8 @@ export class DocViewer {
120121
// In case when content contains static code snippets, then create buttons
121122
// responsible for copy source code.
122123
this.loadCopySourceCodeButtons();
124+
// Setup copy link functionality for section anchor links
125+
this.loadCopyLinkAnchors(contentContainer);
123126
// In case when content contains tabs, create tabs component and move
124127
// content in a tab into tab panel.
125128
this.constructTabs(contentContainer);
@@ -276,6 +279,24 @@ export class DocViewer {
276279
}
277280
}
278281

282+
privateloadCopyLinkAnchors(element:HTMLElement):void{
283+
constdocsAnchors=Array.from(element.querySelectorAll<HTMLAnchorElement>('a.docs-anchor'));
284+
285+
for(constanchorofdocsAnchors){
286+
consthref=anchor.getAttribute('href')!;
287+
constlabel=anchor.getAttribute('aria-label')!;
288+
289+
constcopyLinkAnchorRef=this.viewContainer.createComponent(CopyLinkAnchor);
290+
copyLinkAnchorRef.setInput('href',href);
291+
copyLinkAnchorRef.setInput('label',label);
292+
293+
anchor.parentElement!.replaceChild(
294+
copyLinkAnchorRef.location.nativeElement.firstElementChild,
295+
anchor,
296+
);
297+
}
298+
}
299+
279300
privateloadBreadcrumb(element:HTMLElement):void{
280301
constbreadcrumbPlaceholder=element.querySelector('docs-breadcrumb')asHTMLElement;
281302
constactiveNavigationItem=this.navigationState.activeNavigationItem();

‎adev/shared-docs/styles/_anchor.scss‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,11 @@
1515
opacity:1;
1616
}
1717
}
18+
19+
// Copied state
20+
&.docs-anchor-copied::after {
21+
content:'\e5ca';// codepoint for "check"
22+
color:var(--bright-blue);
23+
opacity:1;
24+
}
1825
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp