diff --git a/jslib b/jslib index b0ae1bfa4c..42348e2fdc 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit b0ae1bfa4cb3bc2642e1ecb14c6c1f0eceb06cb6 +Subproject commit 42348e2fdc6206157d68d8a9f496eaa70520ab01 diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 790c635f4b..07f5ea794b 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1506,7 +1506,7 @@ "message": "Password protected" }, "copySendLink": { - "message": "Copy Send Link", + "message": "Copy Send link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -1523,7 +1523,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Send Link", + "message": "Send link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { @@ -1539,5 +1539,110 @@ "deleteSendConfirmation": { "message": "Are you sure you want to delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "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." + }, + "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" + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "2" + } + } + }, + "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." + }, + "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": "Disable 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": "Create New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "newPassword": { + "message": "New Password" + }, + "sendDisabledWarning": { + "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Created Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Edited Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendFirefoxFileWarning": { + "message": "In order to choose a file using Firefox, open the extension in the sidebar or pop out to a new window by clicking this banner." + }, + "sendSafariFileWarning": { + "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + }, + "sendFileCalloutHeader": { + "message": "Before you start" } } diff --git a/src/popup/app-routing.animations.ts b/src/popup/app-routing.animations.ts index a05df5f4fc..e2362bec02 100644 --- a/src/popup/app-routing.animations.ts +++ b/src/popup/app-routing.animations.ts @@ -190,4 +190,10 @@ export const routerTransition = trigger('routerTransition', [ transition('tabs => send-type', inSlideLeft), transition('send-type => tabs', outSlideRight), + + transition('tabs => add-send, send-type => add-send', inSlideUp), + transition('add-send => tabs, add-send => send-type', outSlideDown), + + transition('tabs => edit-send, send-type => edit-send', inSlideUp), + transition('edit-send => tabs, edit-send => send-type', outSlideDown), ]); diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index 2e87c0f6d3..96fcd02f08 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -46,6 +46,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component'; import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; +import { SendAddEditComponent } from './send/send-add-edit.component'; import { SendGroupingsComponent } from './send/send-groupings.component'; import { SendTypeComponent } from './send/send-type.component'; @@ -243,6 +244,18 @@ const routes: Routes = [ canActivate: [AuthGuardService], data: { state: 'send-type' }, }, + { + path: 'add-send', + component: SendAddEditComponent, + canActivate: [AuthGuardService], + data: { state: 'add-send' }, + }, + { + path: 'edit-send', + component: SendAddEditComponent, + canActivate: [AuthGuardService], + data: { state: 'edit-send' }, + }, { path: 'tabs', component: TabsComponent, diff --git a/src/popup/app.component.ts b/src/popup/app.component.ts index c5e9a20b17..cbf6891fb5 100644 --- a/src/popup/app.component.ts +++ b/src/popup/app.component.ts @@ -145,6 +145,7 @@ export class AppComponent implements OnInit { if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null && (window as any).previousPopupUrl.startsWith('/tabs/')) { this.stateService.remove('GroupingsComponent'); + this.stateService.remove('GroupingsComponentScope'); this.stateService.remove('CiphersComponent'); this.stateService.remove('SendGroupingsComponent'); this.stateService.remove('SendGroupingsComponentScope'); @@ -152,7 +153,6 @@ export class AppComponent implements OnInit { } if (url.startsWith('/tabs/')) { this.stateService.remove('addEditCipherInfo'); - // TODO Remove any Send add/edit state information (?) } (window as any).previousPopupUrl = url; diff --git a/src/popup/app.module.ts b/src/popup/app.module.ts index f3e931e561..7cf73315ca 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -52,6 +52,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component'; import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; +import { SendAddEditComponent } from './send/send-add-edit.component'; import { SendGroupingsComponent } from './send/send-groupings.component'; import { SendTypeComponent } from './send/send-type.component'; @@ -81,6 +82,7 @@ import { IconComponent } from 'jslib/angular/components/icon.component'; import { CurrencyPipe, + DatePipe, registerLocaleData, } from '@angular/common'; import localeBe from '@angular/common/locales/be'; @@ -213,6 +215,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); RegisterComponent, SearchCiphersPipe, SelectCopyDirective, + SendAddEditComponent, SendGroupingsComponent, SendListComponent, SendTypeComponent, @@ -232,6 +235,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); entryComponents: [], providers: [ CurrencyPipe, + DatePipe, ], bootstrap: [AppComponent], }) diff --git a/src/popup/scss/box.scss b/src/popup/scss/box.scss index 91f8db1d5a..11341c69f3 100644 --- a/src/popup/scss/box.scss +++ b/src/popup/scss/box.scss @@ -19,6 +19,32 @@ } } + .box-header-expandable { + margin: 0 10px 5px 10px; + text-transform: uppercase; + display: flex; + + @include themify($themes) { + color: themed('headingColor'); + } + + &:hover, &:focus, &.active { + @include themify($themes) { + background-color: themed('boxBackgroundHoverColor'); + } + } + + .icon { + display: flex; + align-items: center; + margin-left: 5px; + + @include themify($themes) { + color: themed('headingColor'); + } + } + } + .box-content { border-top: 1px solid #000000; border-bottom: 1px solid #000000; @@ -202,6 +228,21 @@ } } + .flex-label { + font-size: $font-size-small; + display: flex; + flex-grow: 1; + margin-bottom: 5px; + + @include themify($themes) { + color: themed('mutedColor'); + } + + > a { + flex-grow: 0; + } + } + .text, .detail { display: block; @@ -329,7 +370,7 @@ } } - input:not([type="checkbox"]), textarea { + input:not([type="checkbox"]):not([type="radio"]), textarea { border: none; width: 100%; background-color: transparent !important; @@ -546,4 +587,27 @@ background-color: $brand-primary; } } + + .radio-group { + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + + input { + flex-grow: 0; + } + + label { + margin: 0 0 0 5px; + flex-grow: 1; + font-size: $font-size-base; + display: block; + width: 100%; + + @include themify($themes) { + color: themed('textColor'); + } + } + } } diff --git a/src/popup/scss/misc.scss b/src/popup/scss/misc.scss index 95f8bd2dde..5d2d666624 100644 --- a/src/popup/scss/misc.scss +++ b/src/popup/scss/misc.scss @@ -302,6 +302,14 @@ app-vault-icon { } } } + + &.clickable { + &:hover, &:focus, &.active { + @include themify($themes) { + background-color: themed('boxBackgroundHoverColor'); + } + } + } } input[type="password"]::-ms-reveal { diff --git a/src/popup/send/send-add-edit.component.html b/src/popup/send/send-add-edit.component.html new file mode 100644 index 0000000000..8a39e599b1 --- /dev/null +++ b/src/popup/send/send-add-edit.component.html @@ -0,0 +1,246 @@ +
diff --git a/src/popup/send/send-add-edit.component.ts b/src/popup/send/send-add-edit.component.ts new file mode 100644 index 0000000000..4823f8027f --- /dev/null +++ b/src/popup/send/send-add-edit.component.ts @@ -0,0 +1,124 @@ +import { + DatePipe, + Location, +} from '@angular/common'; + +import { Component } from '@angular/core'; + +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +import { EnvironmentService } from 'jslib/abstractions/environment.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { PolicyService } from 'jslib/abstractions/policy.service'; +import { SendService } from 'jslib/abstractions/send.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { PopupUtilsService } from '../services/popup-utils.service'; + +import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component'; + +@Component({ + selector: 'app-send-add-edit', + templateUrl: 'send-add-edit.component.html', +}) +export class SendAddEditComponent extends BaseAddEditComponent { + // Options header + showOptions = false; + // File visibility + isFirefox = false; + isSafari = false; + inPopout = false; + inSidebar = false; + + constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, + userService: UserService, messagingService: MessagingService, policyService: PolicyService, + environmentService: EnvironmentService, datePipe: DatePipe, sendService: SendService, + private route: ActivatedRoute, private router: Router, private location: Location, + private popupUtilsService: PopupUtilsService) { + super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService, + messagingService, policyService); + } + + get showFileSelector(): boolean { + return !this.editMode && (!this.isFirefox && !this.isSafari) || + (this.isFirefox && (this.inSidebar || this.inPopout)) || + (this.isSafari && this.inPopout); + } + + get showFilePopoutMessage(): boolean { + return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning); + } + + get showFirefoxFileWarning(): boolean { + return this.isFirefox && !(this.inSidebar || this.inPopout); + } + + get showSafariFileWarning(): boolean { + return this.isSafari && !this.inPopout; + } + + popOutWindow() { + this.popupUtilsService.popOut(window); + } + + async ngOnInit() { + // File visilibity + this.isFirefox = this.platformUtilsService.isFirefox(); + this.isSafari = this.platformUtilsService.isSafari(); + this.inPopout = this.popupUtilsService.inPopout(window); + this.inSidebar = this.popupUtilsService.inSidebar(window); + + const queryParamsSub = this.route.queryParams.subscribe(async (params) => { + if (params.sendId) { + this.sendId = params.sendId; + } + if (params.type) { + const type = parseInt(params.type, null); + this.type = type; + } + await this.load(); + + if (queryParamsSub != null) { + queryParamsSub.unsubscribe(); + } + }); + + window.setTimeout(() => { + if (!this.editMode) { + document.getElementById('name').focus(); + } + }, 200); + } + + async submit(): Promise