refactor(docs-infra): use CDK clipboard service (#40840)
The CDK has had a service for copying strings to the clipboard. These changes switch AIO to it, rather than having to maintain a custom solution. PR Close #40840
This commit is contained in:
parent
5f0c219883
commit
f2ee9d5679
|
@ -3,10 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { Clipboard } from '@angular/cdk/clipboard';
|
||||
|
||||
import { CodeComponent } from './code.component';
|
||||
import { CodeModule } from './code.module';
|
||||
import { CopierService } from 'app/shared//copier.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
@ -32,7 +32,6 @@ describe('CodeComponent', () => {
|
|||
imports: [ NoopAnimationsModule, CodeModule ],
|
||||
declarations: [ HostComponent ],
|
||||
providers: [
|
||||
CopierService,
|
||||
{ provide: Logger, useClass: TestLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
]
|
||||
|
@ -222,23 +221,23 @@ describe('CodeComponent', () => {
|
|||
});
|
||||
|
||||
it('should call copier service when clicked', () => {
|
||||
const copierService: CopierService = TestBed.inject(CopierService);
|
||||
const spy = spyOn(copierService, 'copyText');
|
||||
const clipboard = TestBed.inject(Clipboard);
|
||||
const spy = spyOn(clipboard, 'copy');
|
||||
expect(spy.calls.count()).toBe(0, 'before click');
|
||||
getButton().click();
|
||||
expect(spy.calls.count()).toBe(1, 'after click');
|
||||
});
|
||||
|
||||
it('should copy code text when clicked', () => {
|
||||
const copierService: CopierService = TestBed.inject(CopierService);
|
||||
const spy = spyOn(copierService, 'copyText');
|
||||
const clipboard = TestBed.inject(Clipboard);
|
||||
const spy = spyOn(clipboard, 'copy');
|
||||
getButton().click();
|
||||
expect(spy.calls.argsFor(0)[0]).toBe(oneLineCode, 'after click');
|
||||
});
|
||||
|
||||
it('should preserve newlines in the copied code', () => {
|
||||
const copierService: CopierService = TestBed.inject(CopierService);
|
||||
const spy = spyOn(copierService, 'copyText');
|
||||
const clipboard = TestBed.inject(Clipboard);
|
||||
const spy = spyOn(clipboard, 'copy');
|
||||
const expectedCode = smallMultiLineCode.trim().replace(/</g, '<').replace(/>/g, '>');
|
||||
let actualCode;
|
||||
|
||||
|
@ -259,19 +258,19 @@ describe('CodeComponent', () => {
|
|||
|
||||
it('should display a message when copy succeeds', () => {
|
||||
const snackBar: MatSnackBar = TestBed.inject(MatSnackBar);
|
||||
const copierService: CopierService = TestBed.inject(CopierService);
|
||||
const clipboard = TestBed.inject(Clipboard);
|
||||
spyOn(snackBar, 'open');
|
||||
spyOn(copierService, 'copyText').and.returnValue(true);
|
||||
spyOn(clipboard, 'copy').and.returnValue(true);
|
||||
getButton().click();
|
||||
expect(snackBar.open).toHaveBeenCalledWith('Code Copied', '', { duration: 800 });
|
||||
});
|
||||
|
||||
it('should display an error when copy fails', () => {
|
||||
const snackBar: MatSnackBar = TestBed.inject(MatSnackBar);
|
||||
const copierService: CopierService = TestBed.inject(CopierService);
|
||||
const clipboard = TestBed.inject(Clipboard);
|
||||
const logger = TestBed.inject(Logger) as unknown as TestLogger;
|
||||
spyOn(snackBar, 'open');
|
||||
spyOn(copierService, 'copyText').and.returnValue(false);
|
||||
spyOn(clipboard, 'copy').and.returnValue(false);
|
||||
getButton().click();
|
||||
expect(snackBar.open).toHaveBeenCalledWith('Copy failed. Please try again!', '', { duration: 800 });
|
||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, ElementRef, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core';
|
||||
import { Clipboard } from '@angular/cdk/clipboard';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
|
@ -96,7 +96,7 @@ export class CodeComponent implements OnChanges {
|
|||
constructor(
|
||||
private snackbar: MatSnackBar,
|
||||
private pretty: PrettyPrinter,
|
||||
private copier: CopierService,
|
||||
private clipboard: Clipboard,
|
||||
private logger: Logger) {}
|
||||
|
||||
ngOnChanges() {
|
||||
|
@ -144,7 +144,7 @@ export class CodeComponent implements OnChanges {
|
|||
/** Copies the code snippet to the user's clipboard. */
|
||||
doCopy() {
|
||||
const code = this.codeText;
|
||||
const successfullyCopied = this.copier.copyText(code);
|
||||
const successfullyCopied = this.clipboard.copy(code);
|
||||
|
||||
if (successfullyCopied) {
|
||||
this.logger.log('Copied code to clipboard:', code);
|
||||
|
|
|
@ -3,13 +3,12 @@ import { CommonModule } from '@angular/common';
|
|||
import { CodeComponent } from './code.component';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatSnackBarModule ],
|
||||
declarations: [ CodeComponent ],
|
||||
entryComponents: [ CodeComponent ],
|
||||
exports: [ CodeComponent ],
|
||||
providers: [ PrettyPrinter, CopierService ]
|
||||
providers: [ PrettyPrinter ]
|
||||
})
|
||||
export class CodeModule { }
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* This class is based on the code in the following projects:
|
||||
*
|
||||
* - https://github.com/zenorocha/select
|
||||
* - https://github.com/zenorocha/clipboard.js/
|
||||
*
|
||||
* Both released under MIT license - © Zeno Rocha
|
||||
*
|
||||
* It is also influenced by the Angular CDK `PendingCopy` class:
|
||||
* https://github.com/angular/components/blob/master/src/cdk/clipboard/pending-copy.ts
|
||||
*/
|
||||
|
||||
|
||||
export class CopierService {
|
||||
/**
|
||||
* Copy the contents of a `<textarea>` element to the clipboard.
|
||||
*
|
||||
* NOTE: For this method to work, the elements must be already inserted into the DOM.
|
||||
*
|
||||
* @param textArea The area containing the text to be copied to the clipboard.
|
||||
* @return Whether the copy operation was successful.
|
||||
*/
|
||||
private copyTextArea(textArea: HTMLTextAreaElement): boolean {
|
||||
const currentFocus = document.activeElement as HTMLOrSVGElement | null;
|
||||
|
||||
try {
|
||||
textArea.select();
|
||||
textArea.setSelectionRange(0, textArea.value.length);
|
||||
|
||||
return document.execCommand('copy');
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
// Calling `.select()` on the `<textarea>` element may have also focused it.
|
||||
// Change the focus back to the previously focused element.
|
||||
currentFocus?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary, hidden `<textarea>` element and set its value to the specified text.
|
||||
*
|
||||
* @param text The text to be inserted into the textarea.
|
||||
* @return The temporary `<textarea>` element containing the specified text.
|
||||
*/
|
||||
private createTextArea(text: string): HTMLTextAreaElement {
|
||||
const docElem = document.documentElement;
|
||||
const isRTL = docElem.getAttribute('dir') === 'rtl';
|
||||
|
||||
// Create a temporary element to hold the contents to copy.
|
||||
const textArea = document.createElement('textarea');
|
||||
const style = textArea.style;
|
||||
|
||||
// Prevent zooming on iOS.
|
||||
style.fontSize = '12pt';
|
||||
|
||||
// Reset box model.
|
||||
style.border = '0';
|
||||
style.padding = '0';
|
||||
style.margin = '0';
|
||||
|
||||
// Make the element invisible and move it out of screen horizontally.
|
||||
style.opacity = '0';
|
||||
style.position = 'fixed';
|
||||
style.top = '0';
|
||||
style[isRTL ? 'right' : 'left'] = '-999em';
|
||||
|
||||
textArea.setAttribute('aria-hidden', 'true');
|
||||
textArea.setAttribute('readonly', '');
|
||||
textArea.value = text;
|
||||
|
||||
return textArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the specified text to the clipboard.
|
||||
*
|
||||
* @param text The text to be copied to the clipboard.
|
||||
* @return Whether the copy operation was successful.
|
||||
*/
|
||||
copyText(text: string): boolean {
|
||||
// Create a `<textarea>` element with the specified text.
|
||||
const textArea = this.createTextArea(text);
|
||||
|
||||
// Insert it into the DOM.
|
||||
document.body.appendChild(textArea);
|
||||
|
||||
// Copy its contents to the clipboard.
|
||||
const success = this.copyTextArea(textArea);
|
||||
|
||||
// Remove it from the DOM, so it can be garbage-collected.
|
||||
if (textArea.parentNode) {
|
||||
// We cannot use ChildNode.remove() because of IE11.
|
||||
textArea.parentNode.removeChild(textArea);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue