diff --git a/jslib b/jslib index 154c087b97..ce40a803d8 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 154c087b97f1bb8f5f8de097c317ee3f78986194 +Subproject commit ce40a803d85f90b5078ecd4d7c374714c43940c8 diff --git a/src/app/tools/import.component.html b/src/app/tools/import.component.html index e58cfbc5c8..fce6908e92 100644 --- a/src/app/tools/import.component.html +++ b/src/app/tools/import.component.html @@ -1,3 +1,29 @@ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+ +
diff --git a/src/app/tools/import.component.ts b/src/app/tools/import.component.ts index e9e4ca98bd..ae0b01183a 100644 --- a/src/app/tools/import.component.ts +++ b/src/app/tools/import.component.ts @@ -2,8 +2,164 @@ import { Component, } from '@angular/core'; +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { I18nService } from 'jslib/abstractions/i18n.service'; + +import { BitwardenCsvImporter } from 'jslib/importers/bitwardenCsvImporter'; +import { Importer } from 'jslib/importers/importer'; +import { KeePassXCsvImporter } from 'jslib/importers/keepassxCsvImporter'; +import { LastPassCsvImporter } from 'jslib/importers/lastpassCsvImporter'; + @Component({ selector: 'app-import', templateUrl: 'import.component.html', }) -export class ImportComponent { } +export class ImportComponent { + featuredImportOptions: any[]; + importOptions: any[]; + format: string = null; + fileContents: string; + + constructor(private i18nService: I18nService, protected analytics: Angulartics2, + protected toasterService: ToasterService) { + const bw = new BitwardenCsvImporter(); + const lp = new LastPassCsvImporter(); + + this.featuredImportOptions = [ + { id: null, name: '-- ' + i18nService.t('select') + ' --' }, + { id: 'bitwardencsv', name: 'Bitwarden (csv)' }, + { id: 'lastpasscsv', name: 'LastPass (csv)' }, + { id: 'chromecsv', name: 'Chrome (csv)' }, + { id: 'firefoxcsv', name: 'Firefox (csv)' }, + { id: 'keepass2xml', name: 'KeePass 2 (xml)' }, + { id: '1password1pif', name: '1Password (1pif)' }, + { id: 'dashlanecsv', name: 'Dashlane (csv)' }, + ]; + + this.importOptions = [ + { id: 'keepassxcsv', name: 'KeePassX (csv)' }, + { id: '1password6wincsv', name: '1Password 6 Windows (csv)' }, + { id: 'roboformcsv', name: 'RoboForm (csv)' }, + { id: 'keepercsv', name: 'Keeper (csv)' }, + { id: 'enpasscsv', name: 'Enpass (csv)' }, + { id: 'safeincloudxml', name: 'SafeInCloud (xml)' }, + { id: 'pwsafexml', name: 'Password Safe (xml)' }, + { id: 'stickypasswordxml', name: 'Sticky Password (xml)' }, + { id: 'msecurecsv', name: 'mSecure (csv)' }, + { id: 'truekeycsv', name: 'True Key (csv)' }, + { id: 'passwordbossjson', name: 'Password Boss (json)' }, + { id: 'zohovaultcsv', name: 'Zoho Vault (csv)' }, + { id: 'splashidcsv', name: 'SplashID (csv)' }, + { id: 'passworddragonxml', name: 'Password Dragon (xml)' }, + { id: 'padlockcsv', name: 'Padlock (csv)' }, + { id: 'clipperzhtml', name: 'Clipperz (html)' }, + { id: 'avirajson', name: 'Avira (json)' }, + { id: 'saferpasscsv', name: 'SaferPass (csv)' }, + { id: 'upmcsv', name: 'Universal Password Manager (csv)' }, + { id: 'ascendocsv', name: 'Ascendo DataVault (csv)' }, + { id: 'meldiumcsv', name: 'Meldium (csv)' }, + { id: 'passkeepcsv', name: 'PassKeep (csv)' }, + { id: 'operacsv', name: 'Opera (csv)' }, + { id: 'vivaldicsv', name: 'Vivaldi (csv)' }, + { id: 'gnomejson', name: 'GNOME Passwords and Keys/Seahorse (json)' }, + ].sort((a, b) => { + if (a.name == null && b.name != null) { + return -1; + } + if (a.name != null && b.name == null) { + return 1; + } + if (a.name == null && b.name == null) { + return 0; + } + + return this.i18nService.collator ? this.i18nService.collator.compare(a.name, b.name) : + a.name.localeCompare(b.name); + }); + } + + async submit() { + const importer = this.getImporter(); + if (importer === null) { + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('selectFormat')); + return; + } + + const fileEl = document.getElementById('file') as HTMLInputElement; + const files = fileEl.files; + if ((files == null || files.length === 0) && (this.fileContents == null || this.fileContents === '')) { + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('selectFile')); + return; + } + + let fileContents = this.fileContents; + if (files != null && files.length > 0) { + try { + const content = await this.getFileContents(files[0]); + if (content != null) { + fileContents = content; + } + } catch { } + } + + if (fileContents == null || fileContents === '') { + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('selectFile')); + return; + } + + const importResult = await importer.parse(fileContents); + console.log(importResult); + if (importResult.success) { + + } else { + + } + } + + private getFileContents(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsText(file, 'utf-8'); + reader.onload = (evt) => { + if (this.format === 'lastpasscsv' && file.type === 'text/html') { + const parser = new DOMParser(); + const doc = parser.parseFromString(evt.target.result, 'text/html'); + const pre = doc.querySelector('pre'); + if (pre != null) { + resolve(pre.textContent); + return; + } + reject(); + return; + } + + resolve(evt.target.result); + }; + reader.onerror = () => { + reject(); + }; + }); + } + + private getImporter(): Importer { + if (this.format == null || this.format === '') { + return null; + } + + switch (this.format) { + case 'bitwardencsv': + return new BitwardenCsvImporter(); + case 'lastpasscsv': + return new LastPassCsvImporter(); + case 'keepassxcsv': + return new KeePassXCsvImporter(); + default: + return null; + } + } +} diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index b5bb74ee93..afb1872e65 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -862,5 +862,17 @@ }, "tools": { "message": "Tools" + }, + "import": { + "message": "Import" + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" } } diff --git a/src/scss/styles.scss b/src/scss/styles.scss index 6ef13f04ad..65547486be 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -351,6 +351,12 @@ app-password-generator-history { } } +app-import { + textarea { + height: 150px; + } +} + app-avatar { img { @extend .rounded;