Recipients.vue 23 KB

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