diff --git a/frontend/src/app/components/clipboard/clipboard.component.html b/frontend/src/app/components/clipboard/clipboard.component.html index d23ccdf8c..c3a18d90b 100644 --- a/frontend/src/app/components/clipboard/clipboard.component.html +++ b/frontend/src/app/components/clipboard/clipboard.component.html @@ -1,15 +1,17 @@ - - - + {{ copiedMessage }} diff --git a/frontend/src/app/components/clipboard/clipboard.component.scss b/frontend/src/app/components/clipboard/clipboard.component.scss index 49294e548..6ae620ae7 100644 --- a/frontend/src/app/components/clipboard/clipboard.component.scss +++ b/frontend/src/app/components/clipboard/clipboard.component.scss @@ -7,7 +7,19 @@ padding-left: 0.4rem; } -img { - position: relative; - left: -3px; -} \ No newline at end of file +.copied-message { + background: color-mix(in srgb, var(--active-bg) 95%, transparent); + color: var(--fg); + font-family: sans-serif; + font-size: .8rem; + font-weight: 400; + text-decoration: none; + text-align: left; + padding: .6em .75rem; + border-radius: 4px; + position: absolute; + white-space: nowrap; + box-shadow: 0 .5rem 1rem -.5rem #000; + z-index: 1000; + opacity: .9; +} diff --git a/frontend/src/app/components/clipboard/clipboard.component.ts b/frontend/src/app/components/clipboard/clipboard.component.ts index 6e577d8b3..31f882d12 100644 --- a/frontend/src/app/components/clipboard/clipboard.component.ts +++ b/frontend/src/app/components/clipboard/clipboard.component.ts @@ -1,6 +1,4 @@ -import { Component, ViewChild, ElementRef, AfterViewInit, Input, ChangeDetectionStrategy } from '@angular/core'; -import * as ClipboardJS from 'clipboard'; -import * as tlite from 'tlite'; +import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-clipboard', @@ -8,15 +6,14 @@ import * as tlite from 'tlite'; styleUrls: ['./clipboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ClipboardComponent implements AfterViewInit { - @ViewChild('btn') btn: ElementRef; - @ViewChild('buttonWrapper') buttonWrapper: ElementRef; +export class ClipboardComponent { @Input() button = false; @Input() class = 'btn btn-secondary ml-1'; @Input() size: 'small' | 'normal' | 'large' = 'normal'; @Input() text: string; @Input() leftPadding = true; copiedMessage: string = $localize`:@@clipboard.copied-message:Copied!`; + showMessage = false; widths = { small: '10', @@ -24,22 +21,40 @@ export class ClipboardComponent implements AfterViewInit { large: '18', }; - clipboard: any; + constructor( + private cd: ChangeDetectorRef, + ) { } - constructor() { } - - ngAfterViewInit() { - this.clipboard = new ClipboardJS(this.btn.nativeElement); - this.clipboard.on('success', () => { - tlite.show(this.buttonWrapper.nativeElement); - setTimeout(() => { - tlite.hide(this.buttonWrapper.nativeElement); - }, 1000); - }); + async copyText() { + if (this.text && !this.showMessage) { + try { + await this.copyToClipboard(this.text); + this.showMessage = true; + this.cd.markForCheck(); + setTimeout(() => { + this.showMessage = false; + this.cd.markForCheck(); + }, 1000); + } catch (error) { + console.error('Clipboard copy failed:', error); + } + } } - onDestroy() { - this.clipboard.destroy(); + async copyToClipboard(text: string) { + if (navigator.clipboard) { + await navigator.clipboard.writeText(text); + } else { + // Use the 'out of viewport hidden text area' trick on non-secure contexts + const textarea = document.createElement('textarea'); + textarea.value = this.text; + textarea.style.opacity = '0'; + textarea.setAttribute('readonly', 'true'); // Don't trigger keyboard on mobile + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + textarea.remove(); + } } }