Recipients.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. <template>
  2. <div>
  3. <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
  4. <div class="relative">
  5. <input
  6. v-model="search"
  7. @keyup.esc="search = ''"
  8. tabindex="0"
  9. type="text"
  10. class="w-full md:w-64 appearance-none shadow bg-white text-grey-700 focus:outline-none rounded py-3 pl-3 pr-8"
  11. placeholder="Search Recipients"
  12. />
  13. <icon
  14. v-if="search"
  15. @click.native="search = ''"
  16. name="close-circle"
  17. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
  18. />
  19. <icon
  20. v-else
  21. name="search"
  22. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current pointer-events-none mr-2 flex items-center"
  23. />
  24. </div>
  25. <div class="mt-4 md:mt-0">
  26. <button
  27. @click="addRecipientModalOpen = true"
  28. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
  29. >
  30. Add Recipient
  31. </button>
  32. </div>
  33. </div>
  34. <vue-good-table
  35. @on-search="debounceToolips"
  36. :columns="columns"
  37. :rows="rows"
  38. :search-options="{
  39. enabled: true,
  40. skipDiacritics: true,
  41. externalQuery: search,
  42. }"
  43. :sort-options="{
  44. enabled: true,
  45. initialSortBy: { field: 'created_at', type: 'desc' },
  46. }"
  47. styleClass="vgt-table"
  48. >
  49. <div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
  50. No recipients found for that search!
  51. </div>
  52. <template slot="table-column" slot-scope="props">
  53. <span v-if="props.column.label == 'Key'">
  54. Key
  55. <span
  56. class="tooltip outline-none"
  57. :data-tippy-content="
  58. `Use this to attach recipients to new aliases as they are created e.g. alias+key@${
  59. user.username
  60. }.anonaddy.com. You can attach multiple recipients by doing alias+2.3.4@${
  61. user.username
  62. }.anonaddy.com. Separating each key by a full stop.`
  63. "
  64. >
  65. <icon name="info" class="inline-block w-4 h-4 text-grey-200 fill-current" />
  66. </span>
  67. </span>
  68. <span v-else>
  69. {{ props.column.label }}
  70. </span>
  71. </template>
  72. <template slot="table-row" slot-scope="props">
  73. <span
  74. v-if="props.column.field == 'created_at'"
  75. class="tooltip outline-none text-sm"
  76. :data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
  77. >{{ props.row.created_at | timeAgo }}
  78. </span>
  79. <span v-else-if="props.column.field == 'key'">
  80. {{ props.row.key }}
  81. </span>
  82. <span v-else-if="props.column.field == 'email'">
  83. <span
  84. class="tooltip cursor-pointer outline-none"
  85. data-tippy-content="Click to copy"
  86. v-clipboard="() => rows[props.row.originalIndex].email"
  87. v-clipboard:success="clipboardSuccess"
  88. v-clipboard:error="clipboardError"
  89. >{{ props.row.email | truncate(30) }}</span
  90. >
  91. <span
  92. v-if="isDefault(props.row.id)"
  93. class="ml-3 py-1 px-2 text-sm bg-yellow-200 text-yellow-900 rounded-full tooltip"
  94. data-tippy-content="The default recipient will be used for all aliases with no other recipients assigned"
  95. >
  96. default
  97. </span>
  98. </span>
  99. <span v-else-if="props.column.field === 'aliases'">
  100. <span
  101. v-if="props.row.aliases.length"
  102. class="tooltip outline-none"
  103. :data-tippy-content="aliasesTooltip(props.row.aliases, isDefault(props.row.id))"
  104. >{{ props.row.aliases[0].email | truncate(40) }}
  105. <span
  106. v-if="isDefault(props.row.id) && aliasesUsingDefaultCount > 1"
  107. class="block text-grey-500 text-sm"
  108. >
  109. + {{ aliasesUsingDefaultCount - 1 }}</span
  110. >
  111. <span v-else-if="props.row.aliases.length > 1" class="block text-grey-500 text-sm">
  112. + {{ props.row.aliases.length - 1 }}</span
  113. >
  114. </span>
  115. <span v-else class="block text-grey-500 text-sm">{{ props.row.aliases.length }}</span>
  116. </span>
  117. <span v-else-if="props.column.field === 'should_encrypt'">
  118. <span v-if="props.row.fingerprint" class="flex">
  119. <Toggle
  120. v-model="rows[props.row.originalIndex].should_encrypt"
  121. @on="turnOnEncryption(props.row.id)"
  122. @off="turnOffEncryption(props.row.id)"
  123. />
  124. <icon
  125. name="fingerprint"
  126. class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-200 fill-current mx-2"
  127. :data-tippy-content="props.row.fingerprint"
  128. v-clipboard="() => props.row.fingerprint"
  129. v-clipboard:success="clipboardSuccess"
  130. v-clipboard:error="clipboardError"
  131. />
  132. <icon
  133. name="delete"
  134. class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-200 fill-current"
  135. @click.native="openDeleteRecipientKeyModal(props.row)"
  136. data-tippy-content="Remove public key"
  137. />
  138. </span>
  139. <button
  140. v-else
  141. @click="openRecipientKeyModal(props.row)"
  142. class="focus:outline-none text-sm"
  143. >
  144. Add public key
  145. </button>
  146. </span>
  147. <span v-else-if="props.column.field === 'email_verified_at'">
  148. <span
  149. name="check"
  150. v-if="props.row.email_verified_at"
  151. class="py-1 px-2 bg-green-200 text-green-900 rounded-full text-xs"
  152. >
  153. verified
  154. </span>
  155. <button
  156. v-else
  157. @click="resendVerification(props.row.id)"
  158. class="focus:outline-none text-sm"
  159. :class="resendVerificationLoading ? 'cursor-not-allowed' : ''"
  160. :disabled="resendVerificationLoading"
  161. >
  162. Resend email
  163. </button>
  164. </span>
  165. <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
  166. <icon
  167. v-if="!isDefault(props.row.id)"
  168. name="trash"
  169. class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  170. @click.native="openDeleteModal(props.row)"
  171. />
  172. </span>
  173. </template>
  174. </vue-good-table>
  175. <Modal :open="addRecipientModalOpen" @close="addRecipientModalOpen = false">
  176. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  177. <h2
  178. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  179. >
  180. Add new recipient
  181. </h2>
  182. <p class="mt-4 text-grey-700">
  183. Enter the individual email of the new recipient you'd like to add.
  184. </p>
  185. <p class="mt-4 text-grey-700">
  186. You will receive an email with a verification link that will expire in one hour, you can
  187. click "Resend email" to get a new one.
  188. </p>
  189. <div class="mt-6">
  190. <p v-show="errors.newRecipient" class="mb-3 text-red-500 text-sm">
  191. {{ errors.newRecipient }}
  192. </p>
  193. <input
  194. v-model="newRecipient"
  195. type="email"
  196. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  197. :class="errors.newRecipient ? 'border-red-500' : ''"
  198. placeholder="johndoe@example.com"
  199. autofocus
  200. />
  201. <button
  202. @click="validateNewRecipient"
  203. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  204. :class="addRecipientLoading ? 'cursor-not-allowed' : ''"
  205. :disabled="addRecipientLoading"
  206. >
  207. Add Recipient
  208. <loader v-if="addRecipientLoading" />
  209. </button>
  210. <button
  211. @click="addRecipientModalOpen = false"
  212. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  213. >
  214. Cancel
  215. </button>
  216. </div>
  217. </div>
  218. </Modal>
  219. <Modal :open="addRecipientKeyModalOpen" @close="closeRecipientKeyModal">
  220. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  221. <h2
  222. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  223. >
  224. Add Public GPG Key
  225. </h2>
  226. <p class="mt-4 text-grey-700">Enter your <b>PUBLIC</b> key data in the text area below.</p>
  227. <p class="mt-4 text-grey-700">Make sure to remove <b>Comment:</b> and <b>Version:</b></p>
  228. <div class="mt-6">
  229. <p v-show="errors.recipientKey" class="mb-3 text-red-500 text-sm">
  230. {{ errors.recipientKey }}
  231. </p>
  232. <textarea
  233. v-model="recipientKey"
  234. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  235. :class="errors.recipientKey ? 'border-red-500' : ''"
  236. placeholder="Begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'"
  237. rows="10"
  238. autofocus
  239. >
  240. </textarea>
  241. <button
  242. type="button"
  243. @click="validateRecipientKey"
  244. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  245. :class="addRecipientKeyLoading ? 'cursor-not-allowed' : ''"
  246. :disabled="addRecipientKeyLoading"
  247. >
  248. Add Key
  249. <loader v-if="addRecipientKeyLoading" />
  250. </button>
  251. <button
  252. @click="closeRecipientKeyModal"
  253. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  254. >
  255. Cancel
  256. </button>
  257. </div>
  258. </div>
  259. </Modal>
  260. <Modal :open="deleteRecipientKeyModalOpen" @close="closeDeleteRecipientKeyModal">
  261. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  262. <h2
  263. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  264. >
  265. Remove recipient public key
  266. </h2>
  267. <p class="mt-4 text-grey-700">
  268. Are you sure you want to remove the public key for this recipient? It will also be removed
  269. from any other recipients using the same key.
  270. </p>
  271. <div class="mt-6">
  272. <button
  273. type="button"
  274. @click="deleteRecipientKey(recipientKeyToDelete)"
  275. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  276. :class="deleteRecipientKeyLoading ? 'cursor-not-allowed' : ''"
  277. :disabled="deleteRecipientKeyLoading"
  278. >
  279. Remove public key
  280. <loader v-if="deleteRecipientLoading" />
  281. </button>
  282. <button
  283. @click="closeDeleteRecipientKeyModal"
  284. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  285. >
  286. Cancel
  287. </button>
  288. </div>
  289. </div>
  290. </Modal>
  291. <Modal :open="deleteRecipientModalOpen" @close="closeDeleteModal">
  292. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  293. <h2
  294. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  295. >
  296. Delete recipient
  297. </h2>
  298. <p class="mt-4 text-grey-700">Are you sure you want to delete this recipient?</p>
  299. <div class="mt-6">
  300. <button
  301. type="button"
  302. @click="deleteRecipient(recipientToDelete)"
  303. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  304. :class="deleteRecipientLoading ? 'cursor-not-allowed' : ''"
  305. :disabled="deleteRecipientLoading"
  306. >
  307. Delete recipient
  308. <loader v-if="deleteRecipientLoading" />
  309. </button>
  310. <button
  311. @click="closeDeleteModal"
  312. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  313. >
  314. Cancel
  315. </button>
  316. </div>
  317. </div>
  318. </Modal>
  319. </div>
  320. </template>
  321. <script>
  322. import Modal from './../components/Modal.vue'
  323. import Toggle from './../components/Toggle.vue'
  324. import tippy from 'tippy.js'
  325. export default {
  326. props: {
  327. user: {
  328. type: Object,
  329. required: true,
  330. },
  331. initialRecipients: {
  332. type: Array,
  333. required: true,
  334. },
  335. aliasesUsingDefault: {
  336. type: Array,
  337. required: true,
  338. },
  339. aliasesUsingDefaultCount: {
  340. type: Number,
  341. required: true,
  342. },
  343. domain: {
  344. type: String,
  345. required: true,
  346. },
  347. },
  348. components: {
  349. Modal,
  350. Toggle,
  351. },
  352. created() {
  353. this.defaultRecipient = _.find(this.rows, ['id', this.user.default_recipient_id])
  354. this.defaultRecipient.aliases = this.defaultRecipient.aliases.concat(this.aliasesUsingDefault)
  355. },
  356. mounted() {
  357. this.addTooltips()
  358. },
  359. data() {
  360. return {
  361. defaultRecipient: {},
  362. newRecipient: '',
  363. recipientKey: '',
  364. search: '',
  365. addRecipientLoading: false,
  366. addRecipientModalOpen: false,
  367. recipientToDelete: null,
  368. recipientKeyToDelete: null,
  369. deleteRecipientLoading: false,
  370. deleteRecipientModalOpen: false,
  371. deleteRecipientKeyLoading: false,
  372. deleteRecipientKeyModalOpen: false,
  373. addRecipientKeyLoading: false,
  374. addRecipientKeyModalOpen: false,
  375. recipientToAddKey: {},
  376. resendVerificationLoading: false,
  377. errors: {},
  378. columns: [
  379. {
  380. label: 'Created',
  381. field: 'created_at',
  382. globalSearchDisabled: true,
  383. },
  384. {
  385. label: 'Key',
  386. field: 'key',
  387. type: 'number',
  388. },
  389. {
  390. label: 'Email',
  391. field: 'email',
  392. },
  393. {
  394. label: 'Recipient Aliases',
  395. field: 'aliases',
  396. sortable: true,
  397. sortFn: this.sortRecipientAliases,
  398. globalSearchDisabled: true,
  399. },
  400. {
  401. label: 'Encryption',
  402. field: 'should_encrypt',
  403. type: 'boolean',
  404. globalSearchDisabled: true,
  405. },
  406. {
  407. label: 'Verified',
  408. field: 'email_verified_at',
  409. globalSearchDisabled: true,
  410. },
  411. {
  412. label: '',
  413. field: 'actions',
  414. sortable: false,
  415. globalSearchDisabled: true,
  416. },
  417. ],
  418. rows: this.initialRecipients,
  419. }
  420. },
  421. watch: {
  422. addRecipientKeyModalOpen: _.debounce(function() {
  423. this.addTooltips()
  424. }, 50),
  425. },
  426. methods: {
  427. addTooltips() {
  428. tippy('.tooltip', {
  429. arrow: true,
  430. arrowType: 'round',
  431. })
  432. },
  433. debounceToolips: _.debounce(function() {
  434. this.addTooltips()
  435. }, 50),
  436. aliasesTooltip(aliases, isDefault) {
  437. let ellipses =
  438. aliases.length > 5 || (isDefault && this.aliasesUsingDefaultCount > 5) ? '...' : ''
  439. return (
  440. _.reduce(_.take(aliases, 5), (list, alias) => list + `${alias.email}<br>`, '') + ellipses
  441. )
  442. },
  443. isDefault(id) {
  444. return this.user.default_recipient_id === id
  445. },
  446. validateNewRecipient(e) {
  447. this.errors = {}
  448. if (!this.newRecipient) {
  449. this.errors.newRecipient = 'Email required'
  450. } else if (!this.validEmail(this.newRecipient)) {
  451. this.errors.newRecipient = 'Valid Email required'
  452. }
  453. if (!this.errors.newRecipient) {
  454. this.addNewRecipient()
  455. }
  456. e.preventDefault()
  457. },
  458. addNewRecipient() {
  459. this.addRecipientLoading = true
  460. axios
  461. .post(
  462. '/api/v1/recipients',
  463. JSON.stringify({
  464. email: this.newRecipient,
  465. }),
  466. {
  467. headers: { 'Content-Type': 'application/json' },
  468. }
  469. )
  470. .then(({ data }) => {
  471. this.addRecipientLoading = false
  472. data.data.key = this.rows.length + 1
  473. this.rows.push(data.data)
  474. this.newRecipient = ''
  475. this.addRecipientModalOpen = false
  476. this.success('Recipient created and verification email sent')
  477. })
  478. .catch(error => {
  479. this.addRecipientLoading = false
  480. if (error.response.status === 422) {
  481. this.error(error.response.data.errors.email[0])
  482. } else {
  483. this.error()
  484. }
  485. })
  486. },
  487. resendVerification(id) {
  488. this.resendVerificationLoading = true
  489. axios
  490. .post(
  491. '/recipients/email/resend',
  492. JSON.stringify({
  493. recipient_id: id,
  494. }),
  495. {
  496. headers: { 'Content-Type': 'application/json' },
  497. }
  498. )
  499. .then(({ data }) => {
  500. this.resendVerificationLoading = false
  501. this.success('Verification email resent')
  502. })
  503. .catch(error => {
  504. this.resendVerificationLoading = false
  505. if (error.response.status === 429) {
  506. this.error('You can only resend the email once per minute')
  507. } else {
  508. this.error()
  509. }
  510. })
  511. },
  512. openDeleteModal(recipient) {
  513. this.deleteRecipientModalOpen = true
  514. this.recipientToDelete = recipient
  515. },
  516. closeDeleteModal() {
  517. this.deleteRecipientModalOpen = false
  518. this.recipientToDelete = null
  519. },
  520. deleteRecipient(recipient) {
  521. this.deleteRecipientLoading = true
  522. axios
  523. .delete(`/api/v1/recipients/${recipient.id}`)
  524. .then(response => {
  525. let recipients = _.filter(this.rows, ['fingerprint', recipient.fingerprint])
  526. _.forEach(recipients, function(recipient) {
  527. recipient.should_encrypt = false
  528. recipient.fingerprint = null
  529. })
  530. this.rows = _.reject(this.rows, row => row.id === recipient.id)
  531. this.deleteRecipientModalOpen = false
  532. this.deleteRecipientLoading = false
  533. })
  534. .catch(error => {
  535. this.error()
  536. this.deleteRecipientLoading = false
  537. this.deleteRecipientModalOpen = false
  538. })
  539. },
  540. openDeleteRecipientKeyModal(recipient) {
  541. this.deleteRecipientKeyModalOpen = true
  542. this.recipientKeyToDelete = recipient
  543. },
  544. closeDeleteRecipientKeyModal() {
  545. this.deleteRecipientKeyModalOpen = false
  546. this.recipientKeyIdToDelete = null
  547. },
  548. deleteRecipientKey(recipient) {
  549. this.deleteRecipientKeyLoading = true
  550. axios
  551. .delete(`/api/v1/recipient-keys/${recipient.id}`)
  552. .then(response => {
  553. let recipients = _.filter(this.rows, ['fingerprint', recipient.fingerprint])
  554. _.forEach(recipients, function(recipient) {
  555. recipient.should_encrypt = false
  556. recipient.fingerprint = null
  557. })
  558. this.deleteRecipientKeyModalOpen = false
  559. this.deleteRecipientKeyLoading = false
  560. })
  561. .catch(error => {
  562. if (error.response !== undefined) {
  563. this.error(error.response.data)
  564. } else {
  565. this.error()
  566. }
  567. this.deleteRecipientKeyLoading = false
  568. this.deleteRecipientKeyModalOpen = false
  569. })
  570. },
  571. validateRecipientKey(e) {
  572. this.errors = {}
  573. if (!this.recipientKey) {
  574. this.errors.recipientKey = 'Key required'
  575. } else if (!this.validKey(this.recipientKey)) {
  576. this.errors.recipientKey = 'Valid Key required'
  577. }
  578. if (!this.errors.recipientKey) {
  579. this.addRecipientKey()
  580. }
  581. e.preventDefault()
  582. },
  583. addRecipientKey() {
  584. this.addRecipientKeyLoading = true
  585. axios
  586. .patch(
  587. `/api/v1/recipient-keys/${this.recipientToAddKey.id}`,
  588. JSON.stringify({
  589. key_data: this.recipientKey,
  590. }),
  591. {
  592. headers: { 'Content-Type': 'application/json' },
  593. }
  594. )
  595. .then(({ data }) => {
  596. this.addRecipientKeyLoading = false
  597. let recipient = _.find(this.rows, ['id', this.recipientToAddKey.id])
  598. recipient.should_encrypt = data.data.should_encrypt
  599. recipient.fingerprint = data.data.fingerprint
  600. this.recipientKey = ''
  601. this.addRecipientKeyModalOpen = false
  602. this.success(
  603. `Key Successfully Added for ${
  604. this.recipientToAddKey.email
  605. }. Make sure to check the fingerprint is correct!`
  606. )
  607. })
  608. .catch(error => {
  609. this.addRecipientKeyLoading = false
  610. if (error.response !== undefined) {
  611. this.error(error.response.data)
  612. } else {
  613. this.error()
  614. }
  615. })
  616. },
  617. turnOnEncryption(id) {
  618. axios
  619. .post(
  620. `/api/v1/encrypted-recipients`,
  621. JSON.stringify({
  622. id: id,
  623. }),
  624. {
  625. headers: { 'Content-Type': 'application/json' },
  626. }
  627. )
  628. .then(response => {
  629. //
  630. })
  631. .catch(error => {
  632. this.error()
  633. })
  634. },
  635. turnOffEncryption(id) {
  636. axios
  637. .delete(`/api/v1/encrypted-recipients/${id}`)
  638. .then(response => {
  639. //
  640. })
  641. .catch(error => {
  642. this.error()
  643. })
  644. },
  645. openRecipientKeyModal(recipient) {
  646. this.addRecipientKeyModalOpen = true
  647. this.recipientToAddKey = recipient
  648. },
  649. closeRecipientKeyModal() {
  650. this.addRecipientKeyModalOpen = false
  651. this.recipientToAddKey = {}
  652. },
  653. validEmail(email) {
  654. let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  655. return re.test(email)
  656. },
  657. validKey(key) {
  658. let re = /-----BEGIN PGP PUBLIC KEY BLOCK-----([A-Za-z0-9+=\/\n]+)-----END PGP PUBLIC KEY BLOCK-----/i
  659. return re.test(key)
  660. },
  661. clipboardSuccess() {
  662. this.success('Copied to clipboard')
  663. },
  664. clipboardError() {
  665. this.error('Could not copy to clipboard')
  666. },
  667. success(text = '') {
  668. this.$notify({
  669. title: 'Success',
  670. text: text,
  671. type: 'success',
  672. })
  673. },
  674. error(text = 'An error has occurred, please try again later') {
  675. this.$notify({
  676. title: 'Error',
  677. text: text,
  678. type: 'error',
  679. })
  680. },
  681. },
  682. }
  683. </script>