[PM-2043] Fix additional space and characters copied to clipboard (#5312)

* Change appSelectCopy to accept a dynamic input on what to copy

* Renamed select-copy directive to copy-text directive to be more accurate with the new behaviour

Signed-off-by: Andre Rosado <arosado@bitwarden.com>

* Moved CopyTextDirective on jslib module to be in alphabetic ordering

---------

Signed-off-by: Andre Rosado <arosado@bitwarden.com>
Co-authored-by: Andre Rosado <arosado@bitwarden.com>
This commit is contained in:
Sammy Chang 2023-07-17 12:00:50 -04:00 committed by GitHub
parent c3adf96da7
commit 906c11acb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 49 additions and 50 deletions

View File

@ -19,7 +19,11 @@
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="generated-block" *ngIf="type === 'password'">
<div class="generated-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
<div
class="generated-wrapper"
[innerHTML]="password | colorPassword"
[appCopyText]="password"
></div>
<div class="action-buttons">
<button
type="button"
@ -41,7 +45,11 @@
</div>
</div>
<div class="generated-block" *ngIf="type === 'username'">
<div class="generated-wrapper" [innerHTML]="username | colorPassword" appSelectCopy></div>
<div
class="generated-wrapper"
[innerHTML]="username | colorPassword"
[appCopyText]="username"
></div>
<div class="action-buttons" #form [appApiAction]="usernameGeneratingPromise">
<button
type="button"

View File

@ -22,7 +22,7 @@
<div class="row-main-content">
<div
class="monospaced password-wrapper"
appSelectCopy
[appCopyText]="h.password"
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date : "medium" }}</span>

View File

@ -72,7 +72,7 @@
<div
*ngIf="showPassword && !showPasswordCount"
class="monospaced password-wrapper"
appSelectCopy
[appCopyText]="cipher.login.password"
[innerHTML]="cipher.login.password | colorPassword"
></div>
<div

View File

@ -12,7 +12,11 @@
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="generated-block" *ngIf="type === 'password'">
<div class="generated-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
<div
class="generated-wrapper"
[innerHTML]="password | colorPassword"
[appCopyText]="password"
></div>
<div class="action-buttons">
<button
type="button"
@ -35,7 +39,11 @@
</div>
</div>
<div class="generated-block" *ngIf="type === 'username'">
<div class="generated-wrapper" [innerHTML]="username | colorPassword" appSelectCopy></div>
<div
class="generated-wrapper"
[innerHTML]="username | colorPassword"
[appCopyText]="username"
></div>
<div class="action-buttons" #form [appApiAction]="usernameGeneratingPromise">
<button
type="button"

View File

@ -11,7 +11,7 @@
<div class="row-main">
<div
class="password-wrapper monospaced"
appSelectCopy
[appCopyText]="h.password"
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date : "medium" }}</span>

View File

@ -52,7 +52,7 @@
<div
*ngIf="showPassword && !showPasswordCount"
class="monospaced password-wrapper"
appSelectCopy
[appCopyText]="cipher.login.password"
[innerHTML]="cipher.login.password | colorPassword"
></div>
<div

View File

@ -8,7 +8,7 @@
<div class="card-body">
<bit-color-password
[password]="type === 'password' ? password : username"
appSelectCopy
[appCopyText]="type === 'password' ? password : username"
></bit-color-password>
</div>
</div>

View File

@ -10,7 +10,7 @@
<bit-color-password
[password]="h.password"
class="tw-block tw-font-mono"
appSelectCopy
[appCopyText]="h.password"
></bit-color-password>
<small bitTypography="body2" class="tw-text-muted">
{{ h.date | date : "medium" }}

View File

@ -0,0 +1,20 @@
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@Directive({
selector: "[appCopyText]",
})
export class CopyTextDirective {
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
@Input("appCopyText") copyText: string;
@HostListener("copy") onCopy() {
if (window == null) {
return;
}
this.platformUtilsService.copyToClipboard(this.copyText, { window: window });
}
}

View File

@ -1,37 +0,0 @@
import { Directive, ElementRef, HostListener } from "@angular/core";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@Directive({
selector: "[appSelectCopy]",
})
export class SelectCopyDirective {
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
@HostListener("copy") onCopy() {
if (window == null) {
return;
}
let copyText = "";
const selection = window.getSelection();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
const text = range.toString();
// The selection should only contain one line of text. In some cases however, the
// selection contains newlines and space characters from the indentation of following
// sibling nodes. To avoid copying passwords containing trailing newlines and spaces
// that aren't part of the password, the selection has to be trimmed.
let stringEndPos = text.length;
const newLinePos = text.search(/(?:\r\n|\r|\n)/);
if (newLinePos > -1) {
const otherPart = text.substr(newLinePos).trim();
if (otherPart === "") {
stringEndPos = newLinePos;
}
}
copyText += text.substring(0, stringEndPos);
}
this.platformUtilsService.copyToClipboard(copyText, { window: window });
}
}

View File

@ -10,13 +10,13 @@ import { ApiActionDirective } from "./directives/api-action.directive";
import { AutofocusDirective } from "./directives/autofocus.directive";
import { BoxRowDirective } from "./directives/box-row.directive";
import { CopyClickDirective } from "./directives/copy-click.directive";
import { CopyTextDirective } from "./directives/copy-text.directive";
import { FallbackSrcDirective } from "./directives/fallback-src.directive";
import { IfFeatureDirective } from "./directives/if-feature.directive";
import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive";
import { InputVerbatimDirective } from "./directives/input-verbatim.directive";
import { LaunchClickDirective } from "./directives/launch-click.directive";
import { NotPremiumDirective } from "./directives/not-premium.directive";
import { SelectCopyDirective } from "./directives/select-copy.directive";
import { StopClickDirective } from "./directives/stop-click.directive";
import { StopPropDirective } from "./directives/stop-prop.directive";
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
@ -50,6 +50,7 @@ import { IconComponent } from "./vault/components/icon.component";
AutofocusDirective,
BoxRowDirective,
CalloutComponent,
CopyTextDirective,
CreditCardNumberPipe,
EllipsisPipe,
ExportScopeCalloutComponent,
@ -61,7 +62,6 @@ import { IconComponent } from "./vault/components/icon.component";
NotPremiumDirective,
SearchCiphersPipe,
SearchPipe,
SelectCopyDirective,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
@ -81,6 +81,7 @@ import { IconComponent } from "./vault/components/icon.component";
BitwardenToastModule,
BoxRowDirective,
CalloutComponent,
CopyTextDirective,
CreditCardNumberPipe,
EllipsisPipe,
ExportScopeCalloutComponent,
@ -92,7 +93,6 @@ import { IconComponent } from "./vault/components/icon.component";
NotPremiumDirective,
SearchCiphersPipe,
SearchPipe,
SelectCopyDirective,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,