1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-03-02 03:41:09 +01:00

[PM-7114] Remove legacy Send code from browser extension (#12342)

* Remove Send grouping, type and state

* Delete Send list and add-edit

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
Daniel James Smith 2024-12-20 20:46:34 +01:00 committed by GitHub
parent 4b42092c93
commit 0619ef507f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 8 additions and 1604 deletions

View File

@ -37,7 +37,6 @@
./apps/browser/store/windows/AppxManifest.xml
./apps/browser/src/background/nativeMessaging.background.ts
./apps/browser/src/models/browserComponentState.ts
./apps/browser/src/models/browserSendComponentState.ts
./apps/browser/src/models/browserGroupingsComponentState.ts
./apps/browser/src/models/biometricErrors.ts
./apps/browser/src/browser/safariApp.ts

View File

@ -1150,9 +1150,6 @@
"moveToOrganization": {
"message": "Move to organization"
},
"share": {
"message": "Share"
},
"movedItemToOrg": {
"message": "$ITEMNAME$ moved to $ORGNAME$",
"placeholders": {
@ -2379,14 +2376,6 @@
"message": "Send details",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"addSend": {
"message": "Add Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTypeText": {
"message": "Text"
},
@ -2403,16 +2392,9 @@
"hideTextByDefault": {
"message": "Hide text by default"
},
"maxAccessCountReached": {
"message": "Max access count reached",
"description": "This text will be displayed after a Send has been accessed the maximum amount of times."
},
"expired": {
"message": "Expired"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"passwordProtected": {
"message": "Password protected"
},
@ -2462,24 +2444,9 @@
"message": "Edit Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTypeHeader": {
"message": "What type of Send is this?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNameDesc": {
"message": "A friendly name to describe this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendFileDesc": {
"message": "The file you want to send."
},
"deletionDate": {
"message": "Deletion date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deletionDateDescV2": {
"message": "The Send will be permanently deleted on this date.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@ -2487,10 +2454,6 @@
"expirationDate": {
"message": "Expiration date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"oneDay": {
"message": "1 day"
},
@ -2506,43 +2469,10 @@
"custom": {
"message": "Custom"
},
"maximumAccessCount": {
"message": "Maximum Access Count"
},
"maximumAccessCountDesc": {
"message": "If set, users will no longer be able to access this Send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendPasswordDescV3": {
"message": "Add an optional password for recipients to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendDisableDesc": {
"message": "Deactivate this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendShareDesc": {
"message": "Copy this Send's link to clipboard upon save.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTextDesc": {
"message": "The text you want to send."
},
"sendHideText": {
"message": "Hide this Send's text by default.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current access count"
},
"createSend": {
"message": "New Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@ -2625,18 +2555,6 @@
"sendFileCalloutHeader": {
"message": "Before you start"
},
"sendFirefoxCustomDatePopoutMessage1": {
"message": "To use a calendar style date picker",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'"
},
"sendFirefoxCustomDatePopoutMessage2": {
"message": "click here",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'"
},
"sendFirefoxCustomDatePopoutMessage3": {
"message": "to pop out your window.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'"
},
"expirationDateIsInvalid": {
"message": "The expiration date provided is not valid."
},
@ -2652,15 +2570,9 @@
"dateParsingError": {
"message": "There was an error saving your deletion and expiration dates."
},
"hideEmail": {
"message": "Hide my email address from recipients."
},
"hideYourEmail": {
"message": "Hide your email address from viewers."
},
"sendOptionsPolicyInEffect": {
"message": "One or more organization policies are affecting your Send options."
},
"passwordPrompt": {
"message": "Master password re-prompt"
},

View File

@ -1,20 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
import { BrowserComponentState } from "./browserComponentState";
export class BrowserSendComponentState extends BrowserComponentState {
sends: SendView[];
static fromJSON(json: DeepJsonify<BrowserSendComponentState>) {
if (json == null) {
return null;
}
return Object.assign(new BrowserSendComponentState(), json, {
sends: json.sends?.map((s) => SendView.fromJSON(s)),
});
}
}

View File

@ -86,9 +86,6 @@ import BrowserPopupUtils from "../platform/popup/browser-popup-utils";
import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service";
import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component";
import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component";
import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component";
import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component";
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/send-v2/add-edit/send-add-edit.component";
import { SendCreatedComponent } from "../tools/popup/send-v2/send-created/send-created.component";
import { SendV2Component } from "../tools/popup/send-v2/send-v2.component";
@ -415,21 +412,17 @@ const routes: Routes = [
data: { elevation: 1 } satisfies RouteDataProperties,
}),
{
path: "send-type",
component: SendTypeComponent,
path: "add-send",
component: SendAddEditV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
},
...extensionRefreshSwap(SendAddEditComponent, SendAddEditV2Component, {
path: "add-send",
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
}),
...extensionRefreshSwap(SendAddEditComponent, SendAddEditV2Component, {
{
path: "edit-send",
component: SendAddEditV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
}),
},
{
path: "send-created",
component: SendCreatedComponent,
@ -768,11 +761,12 @@ const routes: Routes = [
canActivate: [authGuard],
data: { elevation: 0 } satisfies RouteDataProperties,
},
...extensionRefreshSwap(SendGroupingsComponent, SendV2Component, {
{
path: "send",
component: SendV2Component,
canActivate: [authGuard],
data: { elevation: 0 } satisfies RouteDataProperties,
}),
},
],
},
{

View File

@ -26,7 +26,6 @@ import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-
import { PopupWidthService } from "../platform/popup/layout/popup-width.service";
import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service";
import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service";
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
import { routerTransition } from "./app-routing.animations";
@ -57,7 +56,6 @@ export class AppComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
private router: Router,
private stateService: StateService,
private browserSendStateService: BrowserSendStateService,
private vaultBrowserStateService: VaultBrowserStateService,
private cipherService: CipherService,
private changeDetectorRef: ChangeDetectorRef,
@ -243,8 +241,6 @@ export class AppComponent implements OnInit, OnDestroy {
await Promise.all([
this.vaultBrowserStateService.setBrowserGroupingsComponentState(null),
this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null),
this.browserSendStateService.setBrowserSendComponentState(null),
this.browserSendStateService.setBrowserSendTypeComponentState(null),
]);
}

View File

@ -56,10 +56,6 @@ import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.comp
import { PopupPageComponent } from "../platform/popup/layout/popup-page.component";
import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component";
import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component";
import { SendListComponent } from "../tools/popup/send/components/send-list.component";
import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component";
import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component";
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
import { ActionButtonsComponent } from "../vault/popup/components/action-buttons.component";
import { CipherRowComponent } from "../vault/popup/components/cipher-row.component";
import { AddEditCustomFieldsComponent } from "../vault/popup/components/vault/add-edit-custom-fields.component";
@ -161,10 +157,6 @@ import "../platform/popup/locales";
PasswordHistoryComponent,
PremiumComponent,
RegisterComponent,
SendAddEditComponent,
SendGroupingsComponent,
SendListComponent,
SendTypeComponent,
SetPasswordComponent,
VaultSettingsComponent,
ShareComponent,

View File

@ -152,7 +152,6 @@ import { ForegroundSyncService } from "../../platform/sync/foreground-sync.servi
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
import { ExtensionLockComponentService } from "../../services/extension-lock-component.service";
import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service";
import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service";
import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service";
import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service";
@ -473,11 +472,6 @@ const safeProviders: SafeProvider[] = [
useClass: UserNotificationSettingsService,
deps: [StateProvider],
}),
safeProvider({
provide: BrowserSendStateService,
useClass: BrowserSendStateService,
deps: [StateProvider],
}),
safeProvider({
provide: MessageListener,
useFactory: (subject: Subject<Message<Record<string, unknown>>>, ngZone: NgZone) =>

View File

@ -1,98 +0,0 @@
<div
role="group"
*ngFor="let s of sends"
appA11yTitle="{{ s.name }}"
class="box-content-row box-content-row-flex"
>
<button
type="button"
class="row-main"
(click)="selectSend(s)"
appStopClick
title="{{ title }} - {{ s.name }}"
>
<div class="app-vault-icon">
<div class="icon" aria-hidden="true">
<i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type === sendType.Text"></i>
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type === sendType.File"></i>
</div>
</div>
<div class="row-main-content">
<span class="text">
{{ s.name }}
<ng-container *ngIf="s.disabled">
<i
class="bwi bwi-exclamation-triangle text-muted"
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i
class="bwi bwi-key text-muted"
title="{{ 'passwordProtected' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "passwordProtected" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i
class="bwi bwi-ban text-muted"
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i class="bwi bwi-clock text-muted" title="{{ 'expired' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i
class="bwi bwi-trash text-muted"
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</span>
<span class="detail">{{ s.deletionDate | date: "medium" }}</span>
</div>
</button>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copySendLink' | i18n }}"
(click)="copySendLink(s)"
>
<i class="bwi bwi-lg bwi-files" aria-hidden="true"></i>
</button>
<button
type="button"
class="row-btn"
[ngClass]="{ disabled: disabledByPolicy }"
[attr.disabled]="disabledByPolicy ? '' : null"
appStopClick
appStopProp
appA11yTitle="{{ 'removePassword' | i18n }}"
(click)="removePassword(s)"
*ngIf="s.password"
>
<i class="bwi bwi-lg bwi-undo" aria-hidden="true"></i>
</button>
<button
type="button"
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(s)"
>
<i class="bwi bwi-lg bwi-trash" aria-hidden="true"></i>
</button>
</div>
</div>

View File

@ -1,38 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
@Component({
selector: "app-send-list",
templateUrl: "send-list.component.html",
})
export class SendListComponent {
@Input() sends: SendView[];
@Input() title: string;
@Input() disabledByPolicy = false;
@Output() onSelected = new EventEmitter<SendView>();
@Output() onCopySendLink = new EventEmitter<SendView>();
@Output() onRemovePassword = new EventEmitter<SendView>();
@Output() onDeleteSend = new EventEmitter<SendView>();
sendType = SendType;
selectSend(s: SendView) {
this.onSelected.emit(s);
}
copySendLink(s: SendView) {
this.onCopySendLink.emit(s);
}
removePassword(s: SendView) {
this.onRemovePassword.emit(s);
}
delete(s: SendView) {
this.onDeleteSend.emit(s);
}
}

View File

@ -1,411 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
<header>
<div class="left">
<button type="button" (click)="cancel()">{{ "cancel" | i18n }}</button>
</div>
<h1 class="center">
<span class="title">{{ title }}</span>
</h1>
<div class="right">
<button type="submit">
<span [hidden]="form.loading">{{ "save" | i18n }}</span>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<main tabindex="-1" *ngIf="send">
<!-- Policy Banner -->
<app-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{ "sendDisabledWarning" | i18n }}
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout>
<!-- File Warning -->
<tools-file-popout-callout *ngIf="type === sendType.File && !disableSend" />
<!-- Name -->
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
formControlName="name"
aria-describedby="nameHelp"
[readonly]="disableSend"
/>
</div>
</div>
<div id="nameHelp" class="box-footer">
{{ "sendNameDesc" | i18n }}
</div>
</div>
<!-- Type Options -->
<div class="box" *ngIf="!editMode">
<div class="box-content no-hover">
<div class="box-content-row">
<label for="sendTypeOptions">{{ "sendTypeHeader" | i18n }}</label>
<div
class="radio-group text-default"
appBoxRow
name="SendTypeOptions"
*ngFor="let o of typeOptions"
>
<input
type="radio"
formControlName="type"
id="type_{{ o.value }}"
[value]="o.value"
[readonly]="disableSend"
/>
<label for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<!-- File -->
<div class="box" *ngIf="type === sendType.File && (editMode || showFileSelector)">
<div class="box-content no-hover">
<div class="box-content-row" *ngIf="editMode">
<label for="file">{{ "file" | i18n }}</label>
<div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div>
</div>
<div class="box-content-row" *ngIf="showFileSelector">
<label for="file">{{ "file" | i18n }}</label>
<input
type="file"
id="file"
formControlName="file"
aria-describedby="fileHelp"
[readonly]="disableSend"
/>
</div>
</div>
<div id="fileHelp" class="box-footer" *ngIf="showFileSelector">
{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}
</div>
</div>
<!-- Text -->
<div class="box" *ngIf="type === sendType.Text">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="text">{{ "sendTypeText" | i18n }}</label>
<textarea
id="text"
formControlName="text"
aria-describedby="textHelp"
rows="6"
></textarea>
</div>
</div>
<div id="textHelp" class="box-footer">
{{ "sendTextDesc" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{ "sendHideText" | i18n }}</label>
<input id="hideText" type="checkbox" name="HideText" formControlName="textHidden" />
</div>
</div>
</div>
<!-- Share -->
<div class="box">
<h2 class="box-header">
{{ "share" | i18n }}
</h2>
<div class="box-content">
<!-- Copy Link on Save -->
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyOnSave">{{ "sendShareDesc" | i18n }}</label>
<input id="copyOnSave" type="checkbox" name="CopyOnSave" formControlName="copyLink" />
</div>
</div>
</div>
<!-- Options -->
<div class="box">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showOptions = !showOptions"
[attr.aria-expanded]="showOptions"
>
<i *ngIf="!showOptions" class="bwi bwi-angle-right bwi-sm icon" aria-hidden="true"></i>
<i *ngIf="showOptions" class="bwi bwi-angle-down bwi-sm icon" aria-hidden="true"></i>
{{ "options" | i18n }}
</button>
</h2>
</div>
<div [hidden]="!showOptions">
<!-- Deletion Date -->
<div class="box">
<div class="box-content">
<ng-container *ngIf="!editMode">
<div class="box-content-row" appBoxRow>
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<select
id="deletionDate"
name="DeletionDateSelect"
aria-describedby="deletionDateHelp"
formControlName="selectedDeletionDatePreset"
required
>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div
class="box-content-row"
appBoxRow
*ngIf="formGroup.controls['selectedDeletionDatePreset'].value === 0"
>
<input
id="deletionDateCustom"
type="datetime-local"
name="DeletionDate"
aria-describedby="deletionDateHelp"
formControlName="defaultDeletionDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
/>
</div>
</ng-container>
<div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<input
id="deletionDateCustom"
type="datetime-local"
name="DeletionDate"
aria-describedby="deletionDateHelp"
formControlName="defaultDeletionDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
/>
</div>
</div>
<div id="deletionDateHelp" class="box-footer">
{{ "deletionDateDesc" | i18n }}
<ng-container
*ngIf="
!inPopout &&
isFirefox &&
(editMode ||
(formGroup.controls['selectedDeletionDatePreset'].value === 0 && !editMode))
"
>
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
<a (click)="popOutWindow()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
</ng-container>
</div>
</div>
<!-- Expiration Date -->
<div class="box">
<div class="box-content">
<ng-container *ngIf="!editMode">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="editExpirationDate">{{ "expirationDate" | i18n }}</label>
<select
id="expirationDate"
name="ExpirationDateSelect"
aria-describedby="expirationDateHelp"
formControlName="selectedExpirationDatePreset"
required
>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div
class="box-content-row"
*ngIf="formGroup.controls['selectedExpirationDatePreset'].value === 0"
appBoxRow
>
<input
id="expirationDateCustom"
type="datetime-local"
name="ExpirationDate"
aria-describedby="expirationDateHelp"
formControlName="defaultExpirationDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
[readOnly]="disableSend"
/>
</div>
</ng-container>
<div class="box-content-row" *ngIf="editMode" appBoxRow>
<div class="flex-label">
<label>{{ "expirationDate" | i18n }}</label>
<button type="button" *ngIf="!disableSend" appStopClick (click)="clearExpiration()">
{{ "clear" | i18n }}
</button>
</div>
<input
id="expirationDateCustom"
type="datetime-local"
name="ExpirationDate"
aria-describedby="expirationDateHelp"
formControlName="defaultExpirationDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
[readOnly]="disableSend"
/>
</div>
</div>
<div id="expirationDateHelp" class="box-footer">
{{ "expirationDateDesc" | i18n }}
<ng-container
*ngIf="
!inPopout &&
isFirefox &&
(editMode ||
(formGroup.controls['selectedExpirationDatePreset'].value === 0 && !editMode))
"
>
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
<a (click)="popOutWindow()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
</ng-container>
</div>
</div>
<!-- Maximum Access Count -->
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maximumAccessCount">{{ "maximumAccessCount" | i18n }}</label>
<input
id="maximumAccessCount"
min="1"
type="number"
name="MaximumAccessCount"
aria-describedby="maximumAccessCountHelp"
formControlName="maxAccessCount"
/>
</div>
</div>
<div id="maximumAccessCountHelp" class="box-footer">
{{ "maximumAccessCountDesc" | i18n }}
</div>
</div>
<!-- Current Access Count -->
<div class="box" *ngIf="editMode">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="currentAccessCount">{{ "currentAccessCount" | i18n }}</label>
<input
id="currentAccessCount"
type="text"
name="CurrentAccessCount"
formControlName="accessCount"
/>
</div>
</div>
</div>
<!-- Password -->
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</label>
<label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</label>
<input
id="password"
type="{{ showPassword ? 'text' : 'password' }}"
name="Password"
aria-describedby="passwordHelp"
class="monospaced"
formControlName="password"
appInputVerbatim
/>
</div>
<div class="action-buttons" *ngIf="!disableSend">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePasswordVisible()"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button>
</div>
</div>
</div>
<div id="passwordHelp" class="box-footer">
{{ "sendPasswordDesc" | i18n }}
</div>
</div>
<!-- Notes -->
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="notes">{{ "notes" | i18n }}</label>
<textarea
id="notes"
name="Notes"
aria-describedby="notesHelp"
rows="6"
formControlName="notes"
></textarea>
</div>
</div>
<div id="notesHelp" class="box-footer">
{{ "sendNotesDesc" | i18n }}
</div>
</div>
<!-- Hide Email -->
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{ "hideEmail" | i18n }}</label>
<input id="hideEmail" type="checkbox" name="HideEmail" formControlName="hideEmail" />
</div>
</div>
</div>
<!-- Disable Send -->
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disableSend">{{ "sendDisableDesc" | i18n }}</label>
<input id="disableSend" type="checkbox" name="DisableSend" formControlName="disabled" />
</div>
</div>
</div>
</div>
<!-- Delete -->
<div class="box list" *ngIf="editMode">
<div class="box-content single-line">
<button
type="button"
class="box-content-row"
appStopClick
(click)="delete()"
[appApiAction]="deletePromise"
#deleteBtn
>
<div class="row-main text-danger">
<div class="icon text-danger" aria-hidden="true">
<i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="$any(deleteBtn).loading"></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
></i>
</div>
<span>{{ "deleteSend" | i18n }}</span>
</div>
</button>
</div>
</div>
</main>
</form>

View File

@ -1,140 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { DatePipe, Location } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
import { FilePopoutUtilsService } from "../services/file-popout-utils.service";
@Component({
selector: "app-send-add-edit",
templateUrl: "send-add-edit.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class SendAddEditComponent extends BaseAddEditComponent implements OnInit {
// Options header
showOptions = false;
// File visibility
isFirefox = false;
inPopout = false;
showFileSelector = false;
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
messagingService: MessagingService,
policyService: PolicyService,
environmentService: EnvironmentService,
datePipe: DatePipe,
sendService: SendService,
private route: ActivatedRoute,
private router: Router,
private location: Location,
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
formBuilder: FormBuilder,
private filePopoutUtilsService: FilePopoutUtilsService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
toastService: ToastService,
) {
super(
i18nService,
platformUtilsService,
environmentService,
datePipe,
sendService,
messagingService,
policyService,
logService,
stateService,
sendApiService,
dialogService,
formBuilder,
billingAccountProfileStateService,
accountService,
toastService,
);
}
popOutWindow() {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserPopupUtils.openCurrentPagePopout(window);
}
async ngOnInit() {
// File visibility
this.showFileSelector =
!this.editMode && !this.filePopoutUtilsService.showFilePopoutMessage(window);
this.inPopout = BrowserPopupUtils.inPopout(window);
this.isFirefox = this.platformUtilsService.isFirefox();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.sendId) {
this.sendId = params.sendId;
}
if (params.type) {
const type = parseInt(params.type, null);
this.type = type;
}
await super.ngOnInit();
});
window.setTimeout(() => {
if (!this.editMode) {
document.getElementById("name").focus();
}
}, 200);
}
async submit(): Promise<boolean> {
if (await super.submit()) {
this.cancel();
return true;
}
return false;
}
async delete(): Promise<boolean> {
if (await super.delete()) {
this.cancel();
return true;
}
return false;
}
cancel() {
// If true, the window was pop'd out on the add-send page. location.back will not work
const isPopup = (window as any)?.previousPopupUrl?.startsWith("/add-send") ?? false;
if (!isPopup) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["tabs/send"]);
} else {
this.location.back();
}
}
}

View File

@ -1,119 +0,0 @@
<app-header>
<div class="left" *ngIf="showLeftHeader">
<app-pop-out></app-pop-out>
</div>
<h1 class="sr-only">{{ "send" | i18n }}</h1>
<div class="search center">
<input
type="search"
placeholder="{{ 'searchSends' | i18n }}"
id="search"
[(ngModel)]="searchText"
(input)="search(200)"
autocomplete="off"
appAutofocus
/>
<i class="bwi bwi-search"></i>
</div>
<div class="right">
<button
type="button"
(click)="addSend()"
appA11yTitle="{{ 'addSend' | i18n }}"
[disabled]="disableSend"
>
<i class="bwi bwi-plus bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
</div>
</app-header>
<main tabindex="-1" [ngClass]="{ flex: disableSend, 'tab-page': disableSend }">
<app-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{ "sendDisabledWarning" | i18n }}
</app-callout>
<div class="no-items" *ngIf="(!sends || !sends.length) && !showSearching() && !disableSend">
<i class="bwi bwi-spinner bwi-spin bwi-3x" *ngIf="!loaded"></i>
<ng-container *ngIf="loaded">
<img class="no-items-image" aria-hidden="true" />
<p>{{ "noItemsInList" | i18n }}</p>
<button
type="button"
(click)="addSend()"
class="btn block primary link"
[disabled]="disableSend"
>
{{ "addSend" | i18n }}
</button>
</ng-container>
</div>
<ng-container *ngIf="sends && sends.length && !showSearching()">
<div class="box list">
<h2 class="box-header">
{{ "types" | i18n }}
</h2>
<div class="box-content single-line">
<button
type="button"
class="box-content-row"
appStopClick
(click)="selectType(sendType.Text)"
>
<div class="row-main">
<div class="icon"><i class="bwi bwi-fw bwi-lg bwi-file-text"></i></div>
<span class="text">{{ "sendTypeText" | i18n }}</span>
</div>
<span class="row-sub-label">{{ getSendCount(sends, sendType.Text) }}</span>
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon"></i></span>
</button>
<button
type="button"
class="box-content-row"
appStopClick
(click)="selectType(sendType.File)"
>
<div class="row-main">
<div class="icon"><i class="bwi bwi-fw bwi-lg bwi-file"></i></div>
<span class="text">{{ "sendTypeFile" | i18n }}</span>
</div>
<span class="row-sub-label">{{ getSendCount(sends, sendType.File) }}</span>
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon"></i></span>
</button>
</div>
</div>
<div class="box list">
<h2 class="box-header">
{{ "allSends" | i18n }}
<div class="flex-right">{{ sends.length }}</div>
</h2>
<div class="box-content">
<app-send-list
[sends]="sends"
title="{{ 'editItem' | i18n }}"
[disabledByPolicy]="disableSend"
(onSelected)="selectSend($event)"
(onCopySendLink)="copy($event)"
(onRemovePassword)="removePassword($event)"
(onDeleteSend)="delete($event)"
></app-send-list>
</div>
</div>
</ng-container>
<ng-container *ngIf="showSearching()">
<div class="no-items" *ngIf="!filteredSends || !filteredSends.length">
<p>{{ "noItemsInList" | i18n }}</p>
</div>
<div class="box list full-list" *ngIf="filteredSends && filteredSends.length > 0">
<div class="box-content">
<app-send-list
[sends]="filteredSends"
title="{{ 'editItem' | i18n }}"
[disabledByPolicy]="disableSend"
(onSelected)="selectSend($event)"
(onCopySendLink)="copy($event)"
(onRemovePassword)="removePassword($event)"
(onDeleteSend)="delete($event)"
>
</app-send-list>
</div>
</div>
</ng-container>
</main>

View File

@ -1,203 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { BrowserSendComponentState } from "../../../models/browserSendComponentState";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
import { BrowserSendStateService } from "../services/browser-send-state.service";
const ComponentId = "SendComponent";
@Component({
selector: "app-send-groupings",
templateUrl: "send-groupings.component.html",
})
export class SendGroupingsComponent extends BaseSendComponent implements OnInit, OnDestroy {
// Header
showLeftHeader = true;
// State Handling
state: BrowserSendComponentState;
private loadedTimeout: number;
constructor(
sendService: SendService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
ngZone: NgZone,
policyService: PolicyService,
searchService: SearchService,
private stateService: BrowserSendStateService,
private router: Router,
private syncService: SyncService,
private changeDetectorRef: ChangeDetectorRef,
private broadcasterService: BroadcasterService,
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
toastService: ToastService,
) {
super(
sendService,
i18nService,
platformUtilsService,
environmentService,
ngZone,
searchService,
policyService,
logService,
sendApiService,
dialogService,
toastService,
);
this.onSuccessfulLoad = async () => {
this.selectAll();
};
}
async ngOnInit() {
// Determine Header details
this.showLeftHeader = !(
BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()
);
// Clear state of Send Type Component
await this.stateService.setBrowserSendTypeComponentState(null);
// Let super class finish
await super.ngOnInit();
// Handle State Restore if necessary
const restoredScopeState = await this.restoreState();
if (this.state?.searchText != null) {
this.searchText = this.state.searchText;
}
if (!this.syncService.syncInProgress) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.load();
} else {
this.loadedTimeout = window.setTimeout(() => {
if (!this.loaded) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.load();
}
}, 5000);
}
if (!this.syncService.syncInProgress || restoredScopeState) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY);
}
// Load all sends if sync completed in background
this.broadcasterService.subscribe(ComponentId, (message: any) => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
window.setTimeout(() => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.load();
}, 500);
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
});
});
}
ngOnDestroy() {
// Remove timeout
if (this.loadedTimeout != null) {
window.clearTimeout(this.loadedTimeout);
}
// Save state
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.saveState();
// Unsubscribe
this.broadcasterService.unsubscribe(ComponentId);
}
async selectType(type: SendType) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/send-type"], { queryParams: { type: type } });
}
async selectSend(s: SendView) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } });
}
async addSend() {
if (this.disableSend) {
return;
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/add-send"]);
}
async removePassword(s: SendView): Promise<boolean> {
if (this.disableSend) {
return;
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
super.removePassword(s);
}
showSearching() {
return this.hasSearched || (!this.searchPending && this.isSearchable);
}
getSendCount(sends: SendView[], type: SendType): number {
return sends.filter((s) => s.type === type).length;
}
private async saveState() {
this.state = Object.assign(new BrowserSendComponentState(), {
scrollY: BrowserPopupUtils.getContentScrollY(window),
searchText: this.searchText,
sends: this.sends,
});
await this.stateService.setBrowserSendComponentState(this.state);
}
private async restoreState(): Promise<boolean> {
this.state = await this.stateService.getBrowserSendComponentState();
if (this.state == null) {
return false;
}
if (this.state.sends != null) {
this.sends = this.state.sends;
}
return true;
}
}

View File

@ -1,68 +0,0 @@
<header>
<div class="left">
<button type="button" (click)="back()">
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
<span>{{ "back" | i18n }}</span>
</button>
</div>
<h1 class="sr-only">{{ "send" | i18n }}</h1>
<div class="search">
<input
type="search"
placeholder="{{ 'searchType' | i18n }}"
id="search"
[(ngModel)]="searchText"
(input)="search(200)"
autocomplete="off"
appAutofocus
/>
<i class="bwi bwi-search"></i>
</div>
<div class="right">
<button
type="button"
(click)="addSend()"
appA11yTitle="{{ 'addSend' | i18n }}"
[disabled]="disableSend"
>
<i class="bwi bwi-plus bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
</div>
</header>
<main tabindex="-1" [ngClass]="{ flex: disableSend }">
<app-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{ "sendDisabledWarning" | i18n }}
</app-callout>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="bwi bwi-spinner bwi-spin bwi-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<p>{{ "noItemsInList" | i18n }}</p>
<button
type="button"
(click)="addSend()"
class="btn block primary link"
[disabled]="disableSend"
>
{{ "addSend" | i18n }}
</button>
</ng-container>
</div>
<div class="box list only-list" *ngIf="filteredSends.length">
<h2 class="box-header">
{{ groupingTitle }}
<span class="flex-right">{{ filteredSends.length }}</span>
</h2>
<div class="box-content">
<app-send-list
[sends]="filteredSends"
title="{{ 'editItem' | i18n }}"
[disabledByPolicy]="disableSend"
(onSelected)="selectSend($event)"
(onCopySendLink)="copy($event)"
(onRemovePassword)="removePassword($event)"
(onDeleteSend)="delete($event)"
>
</app-send-list>
</div>
</div>
</main>

View File

@ -1,190 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Location } from "@angular/common";
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { BrowserComponentState } from "../../../models/browserComponentState";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
import { BrowserSendStateService } from "../services/browser-send-state.service";
const ComponentId = "SendTypeComponent";
@Component({
selector: "app-send-type",
templateUrl: "send-type.component.html",
})
export class SendTypeComponent extends BaseSendComponent implements OnInit, OnDestroy {
groupingTitle: string;
// State Handling
state: BrowserComponentState;
private refreshTimeout: number;
private applySavedState = true;
constructor(
sendService: SendService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
ngZone: NgZone,
policyService: PolicyService,
searchService: SearchService,
private stateService: BrowserSendStateService,
private route: ActivatedRoute,
private location: Location,
private changeDetectorRef: ChangeDetectorRef,
private broadcasterService: BroadcasterService,
private router: Router,
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService,
toastService: ToastService,
) {
super(
sendService,
i18nService,
platformUtilsService,
environmentService,
ngZone,
searchService,
policyService,
logService,
sendApiService,
dialogService,
toastService,
);
this.onSuccessfulLoad = async () => {
this.selectType(this.type);
};
this.applySavedState =
(window as any).previousPopupUrl != null &&
!(window as any).previousPopupUrl.startsWith("/send-type");
}
async ngOnInit() {
// Let super class finish
await super.ngOnInit();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (this.applySavedState) {
this.state = await this.stateService.getBrowserSendTypeComponentState();
if (this.state?.searchText != null) {
this.searchText = this.state.searchText;
}
}
if (params.type != null) {
this.type = parseInt(params.type, null);
switch (this.type) {
case SendType.Text:
this.groupingTitle = this.i18nService.t("sendTypeText");
break;
case SendType.File:
this.groupingTitle = this.i18nService.t("sendTypeFile");
break;
default:
break;
}
await this.load((s) => s.type === this.type);
}
// Restore state and remove reference
if (this.applySavedState && this.state != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY);
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.stateService.setBrowserSendTypeComponentState(null);
});
// Refresh Send list if sync completed in background
this.broadcasterService.subscribe(ComponentId, (message: any) => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
if (message.successfully) {
this.refreshTimeout = window.setTimeout(() => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.refresh();
}, 500);
}
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
});
});
}
ngOnDestroy() {
// Remove timeout
if (this.refreshTimeout != null) {
window.clearTimeout(this.refreshTimeout);
}
// Save state
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.saveState();
// Unsubscribe
this.broadcasterService.unsubscribe(ComponentId);
}
async selectSend(s: SendView) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } });
}
async addSend() {
if (this.disableSend) {
return;
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/add-send"], { queryParams: { type: this.type } });
}
async removePassword(s: SendView): Promise<boolean> {
if (this.disableSend) {
return;
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
super.removePassword(s);
}
back() {
(window as any).routeDirection = "b";
this.location.back();
}
private async saveState() {
this.state = {
scrollY: BrowserPopupUtils.getContentScrollY(window),
searchText: this.searchText,
};
await this.stateService.setBrowserSendTypeComponentState(this.state);
}
}

View File

@ -1,59 +0,0 @@
import {
FakeAccountService,
mockAccountServiceWith,
} from "@bitwarden/common/../spec/fake-account-service";
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
import { awaitAsync } from "@bitwarden/common/../spec/utils";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { BrowserComponentState } from "../../../models/browserComponentState";
import { BrowserSendComponentState } from "../../../models/browserSendComponentState";
import { BrowserSendStateService } from "./browser-send-state.service";
describe("Browser Send State Service", () => {
let stateProvider: FakeStateProvider;
let accountService: FakeAccountService;
let stateService: BrowserSendStateService;
const mockUserId = Utils.newGuid() as UserId;
beforeEach(() => {
accountService = mockAccountServiceWith(mockUserId);
stateProvider = new FakeStateProvider(accountService);
stateService = new BrowserSendStateService(stateProvider);
});
describe("getBrowserSendComponentState", () => {
it("should return BrowserSendComponentState", async () => {
const state = new BrowserSendComponentState();
state.scrollY = 0;
state.searchText = "test";
await stateService.setBrowserSendComponentState(state);
await awaitAsync();
const actual = await stateService.getBrowserSendComponentState();
expect(actual).toStrictEqual(state);
});
});
describe("getBrowserSendTypeComponentState", () => {
it("should return BrowserComponentState", async () => {
const state = new BrowserComponentState();
state.scrollY = 0;
state.searchText = "test";
await stateService.setBrowserSendTypeComponentState(state);
await awaitAsync();
const actual = await stateService.getBrowserSendTypeComponentState();
expect(actual).toStrictEqual(state);
});
});
});

View File

@ -1,71 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Observable, firstValueFrom } from "rxjs";
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
import { BrowserComponentState } from "../../../models/browserComponentState";
import { BrowserSendComponentState } from "../../../models/browserSendComponentState";
import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions";
/** Get or set the active user's component state for the Send browser component
*/
export class BrowserSendStateService {
/** Observable that contains the current state for active user Sends including the send data and type counts
* along with the search text and scroll position
*/
browserSendComponentState$: Observable<BrowserSendComponentState>;
/** Observable that contains the current state for active user Sends that only includes the search text
* and scroll position
*/
browserSendTypeComponentState$: Observable<BrowserComponentState>;
private activeUserBrowserSendComponentState: ActiveUserState<BrowserSendComponentState>;
private activeUserBrowserSendTypeComponentState: ActiveUserState<BrowserComponentState>;
constructor(protected stateProvider: StateProvider) {
this.activeUserBrowserSendComponentState = this.stateProvider.getActive(BROWSER_SEND_COMPONENT);
this.browserSendComponentState$ = this.activeUserBrowserSendComponentState.state$;
this.activeUserBrowserSendTypeComponentState = this.stateProvider.getActive(
BROWSER_SEND_TYPE_COMPONENT,
);
this.browserSendTypeComponentState$ = this.activeUserBrowserSendTypeComponentState.state$;
}
/** Get the active user's browser send component state
* @returns { BrowserSendComponentState } contains the sends and type counts along with the scroll position and search text for the
* send component on the browser
*/
async getBrowserSendComponentState(): Promise<BrowserSendComponentState> {
return await firstValueFrom(this.browserSendComponentState$);
}
/** Set the active user's browser send component state
* @param { BrowserSendComponentState } value sets the sends along with the scroll position and search text for
* the send component on the browser
*/
async setBrowserSendComponentState(value: BrowserSendComponentState): Promise<void> {
await this.activeUserBrowserSendComponentState.update(() => value, {
shouldUpdate: (current) => !(current == null && value == null),
});
}
/** Get the active user's browser component state
* @returns { BrowserComponentState } contains the scroll position and search text for the sends menu on the browser
*/
async getBrowserSendTypeComponentState(): Promise<BrowserComponentState> {
return await firstValueFrom(this.browserSendTypeComponentState$);
}
/** Set the active user's browser component state
* @param { BrowserComponentState } value set the scroll position and search text for the send component on the browser
*/
async setBrowserSendTypeComponentState(value: BrowserComponentState): Promise<void> {
await this.activeUserBrowserSendTypeComponentState.update(() => value, {
shouldUpdate: (current) => !(current == null && value == null),
});
}
}

View File

@ -1,39 +0,0 @@
import { Jsonify } from "type-fest";
import { BrowserSendComponentState } from "../../../models/browserSendComponentState";
import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions";
describe("Key definitions", () => {
describe("BROWSER_SEND_COMPONENT", () => {
it("should deserialize BrowserSendComponentState", () => {
const keyDef = BROWSER_SEND_COMPONENT;
const expectedState = {
scrollY: 0,
searchText: "test",
};
const result = keyDef.deserializer(
JSON.parse(JSON.stringify(expectedState)) as Jsonify<BrowserSendComponentState>,
);
expect(result).toEqual(expectedState);
});
});
describe("BROWSER_SEND_TYPE_COMPONENT", () => {
it("should deserialize BrowserComponentState", () => {
const keyDef = BROWSER_SEND_TYPE_COMPONENT;
const expectedState = {
scrollY: 0,
searchText: "test",
};
const result = keyDef.deserializer(JSON.parse(JSON.stringify(expectedState)));
expect(result).toEqual(expectedState);
});
});
});

View File

@ -1,27 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { BROWSER_SEND_MEMORY, UserKeyDefinition } from "@bitwarden/common/platform/state";
import { BrowserComponentState } from "../../../models/browserComponentState";
import { BrowserSendComponentState } from "../../../models/browserSendComponentState";
export const BROWSER_SEND_COMPONENT = new UserKeyDefinition<BrowserSendComponentState>(
BROWSER_SEND_MEMORY,
"browser_send_component",
{
deserializer: (obj: Jsonify<BrowserSendComponentState>) =>
BrowserSendComponentState.fromJSON(obj),
clearOn: ["logout", "lock"],
},
);
export const BROWSER_SEND_TYPE_COMPONENT = new UserKeyDefinition<BrowserComponentState>(
BROWSER_SEND_MEMORY,
"browser_send_type_component",
{
deserializer: (obj: Jsonify<BrowserComponentState>) => BrowserComponentState.fromJSON(obj),
clearOn: ["logout", "lock"],
},
);