From 05ce9c6eda81e6ca1f1a8b3b10253d1f0dc6eec1 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 29 May 2023 17:38:38 -0500 Subject: [PATCH] Add:Email smtp config & send ebooks to devices #1474 --- client/components/app/Appbar.vue | 2 +- client/components/app/BookShelfToolbar.vue | 4 +- client/components/app/ConfigSideNav.vue | 5 + client/components/cards/LazyBookCard.vue | 48 +++- .../modals/emails/EReaderDeviceModal.vue | 171 ++++++++++++ .../components/tables/AudioTracksTableRow.vue | 2 +- .../tables/LibraryFilesTableRow.vue | 2 +- .../components/tables/library/LibraryItem.vue | 2 +- .../tables/podcast/EpisodesTable.vue | 2 +- client/components/ui/ContextMenuDropdown.vue | 40 ++- client/components/ui/Menu.vue | 61 ----- client/components/widgets/MoreMenu.vue | 41 ++- client/layouts/default.vue | 8 + client/pages/collection/_id.vue | 2 +- client/pages/config.vue | 14 +- client/pages/config/email.vue | 244 ++++++++++++++++++ client/pages/item/_id/index.vue | 45 +++- client/pages/login.vue | 5 +- client/store/libraries.js | 6 +- client/strings/de.json | 14 + client/strings/en-us.json | 14 + client/strings/es.json | 14 + client/strings/fr.json | 14 + client/strings/gu.json | 14 + client/strings/hi.json | 14 + client/strings/hr.json | 14 + client/strings/it.json | 14 + client/strings/nl.json | 14 + client/strings/pl.json | 14 + client/strings/ru.json | 14 + client/strings/zh-cn.json | 14 + package-lock.json | 16 +- package.json | 3 +- server/Auth.js | 1 + server/Db.js | 11 + server/Server.js | 2 + server/controllers/EmailController.js | 86 ++++++ server/managers/EmailManager.js | 73 ++++++ server/objects/settings/EmailSettings.js | 101 ++++++++ server/routers/ApiRouter.js | 11 + 40 files changed, 1077 insertions(+), 99 deletions(-) create mode 100644 client/components/modals/emails/EReaderDeviceModal.vue delete mode 100644 client/components/ui/Menu.vue create mode 100644 client/pages/config/email.vue create mode 100644 server/controllers/EmailController.js create mode 100644 server/managers/EmailManager.js create mode 100644 server/objects/settings/EmailSettings.js diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index e220f1ff..3ae1f3ed 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -211,7 +211,7 @@ export default { } this.$store.commit('globals/setConfirmPrompt', payload) }, - contextMenuAction(action) { + contextMenuAction({ action }) { if (action === 'quick-embed') { this.requestBatchQuickEmbed() } else if (action === 'quick-match') { diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 30739b54..47ac73a4 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -296,7 +296,7 @@ export default { } }, methods: { - contextMenuAction(action) { + contextMenuAction({ action }) { if (action === 'export-opml') { this.exportOPML() } @@ -304,7 +304,7 @@ export default { exportOPML() { this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true) }, - seriesContextMenuAction(action) { + seriesContextMenuAction({ action }) { if (action === 'open-rss-feed') { this.showOpenSeriesRSSFeed() } else if (action === 're-add-to-continue-listening') { diff --git a/client/components/app/ConfigSideNav.vue b/client/components/app/ConfigSideNav.vue index af4645f4..0868bafb 100644 --- a/client/components/app/ConfigSideNav.vue +++ b/client/components/app/ConfigSideNav.vue @@ -90,6 +90,11 @@ export default { title: this.$strings.HeaderNotifications, path: '/config/notifications' }, + { + id: 'config-email', + title: this.$strings.HeaderEmail, + path: '/config/email' + }, { id: 'config-item-metadata-utils', title: this.$strings.HeaderItemMetadataUtils, diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index c4f7f9a9..0fffe3dc 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -448,7 +448,6 @@ export default { } ] if (this.continueListeningShelf) { - items.push({ func: 'removeFromContinueListening', text: this.$strings.ButtonRemoveFromContinueListening @@ -490,6 +489,18 @@ export default { text: this.$strings.LabelAddToPlaylist }) } + if (this.ebookFormat && this.store.state.libraries.ereaderDevices?.length) { + items.push({ + text: this.$strings.LabelSendEbookToDevice, + subitems: this.store.state.libraries.ereaderDevices.map((d) => { + return { + text: d.name, + func: 'sendToDevice', + data: d.name + } + }) + }) + } } if (this.userCanUpdate) { items.push({ @@ -720,6 +731,37 @@ export default { // More menu func this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'match' }) }, + sendToDevice(deviceName) { + // More menu func + const payload = { + // message: `Are you sure you want to send ${this.ebookFormat} ebook "${this.title}" to device "${deviceName}"?`, + message: this.$getString('MessageConfirmSendEbookToDevice', [this.ebookFormat, this.title, deviceName]), + callback: (confirmed) => { + if (confirmed) { + const payload = { + libraryItemId: this.libraryItemId, + deviceName + } + this.processing = true + const axios = this.$axios || this.$nuxt.$axios + axios + .$post(`/api/emails/send-ebook-to-device`, payload) + .then(() => { + this.$toast.success(this.$getString('ToastSendEbookToDeviceSuccess', [deviceName])) + }) + .catch((error) => { + console.error('Failed to send e-book to device', error) + this.$toast.error(this.$strings.ToastSendEbookToDeviceFailed) + }) + .finally(() => { + this.processing = false + }) + } + }, + type: 'yesNo' + } + this.store.commit('globals/setConfirmPrompt', payload) + }, removeSeriesFromContinueListening() { const axios = this.$axios || this.$nuxt.$axios this.processing = true @@ -833,8 +875,8 @@ export default { items: this.moreMenuItems }, created() { - this.$on('action', (func) => { - if (_this[func]) _this[func]() + this.$on('action', (action) => { + if (action.func && _this[action.func]) _this[action.func](action.data) }) this.$on('close', () => { _this.isMoreMenuOpen = false diff --git a/client/components/modals/emails/EReaderDeviceModal.vue b/client/components/modals/emails/EReaderDeviceModal.vue new file mode 100644 index 00000000..4b6e87cf --- /dev/null +++ b/client/components/modals/emails/EReaderDeviceModal.vue @@ -0,0 +1,171 @@ + + + diff --git a/client/components/tables/AudioTracksTableRow.vue b/client/components/tables/AudioTracksTableRow.vue index c2c3fc95..73eeb6bd 100644 --- a/client/components/tables/AudioTracksTableRow.vue +++ b/client/components/tables/AudioTracksTableRow.vue @@ -77,7 +77,7 @@ export default { } }, methods: { - contextMenuAction(action) { + contextMenuAction({ action }) { if (action === 'delete') { this.deleteLibraryFile() } else if (action === 'download') { diff --git a/client/components/tables/LibraryFilesTableRow.vue b/client/components/tables/LibraryFilesTableRow.vue index 529fca00..898462b0 100644 --- a/client/components/tables/LibraryFilesTableRow.vue +++ b/client/components/tables/LibraryFilesTableRow.vue @@ -72,7 +72,7 @@ export default { } }, methods: { - contextMenuAction(action) { + contextMenuAction({ action }) { if (action === 'delete') { this.deleteLibraryFile() } else if (action === 'download') { diff --git a/client/components/tables/library/LibraryItem.vue b/client/components/tables/library/LibraryItem.vue index 7a99e0e6..6cec8867 100644 --- a/client/components/tables/library/LibraryItem.vue +++ b/client/components/tables/library/LibraryItem.vue @@ -94,7 +94,7 @@ export default { } }, methods: { - contextMenuAction(action) { + contextMenuAction({ action }) { this.showMobileMenu = false if (action === 'edit') { this.editClick() diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue index feec71eb..027aef07 100644 --- a/client/components/tables/podcast/EpisodesTable.vue +++ b/client/components/tables/podcast/EpisodesTable.vue @@ -185,7 +185,7 @@ export default { this.searchText = this.search.toLowerCase().trim() }, 500) }, - contextMenuAction(action) { + contextMenuAction({ action }) { if (action === 'quick-match-episodes') { if (this.quickMatchingEpisodes) return diff --git a/client/components/ui/ContextMenuDropdown.vue b/client/components/ui/ContextMenuDropdown.vue index 6f486d5d..1c8a2a69 100644 --- a/client/components/ui/ContextMenuDropdown.vue +++ b/client/components/ui/ContextMenuDropdown.vue @@ -7,9 +7,19 @@ -
+
@@ -42,11 +52,31 @@ export default { events: ['mousedown'], isActive: true }, - showMenu: false + showMenu: false, + mouseoverItemIndex: null, + isOverSubItemMenu: false } }, computed: {}, methods: { + mouseoverSubItemMenu(index) { + this.isOverSubItemMenu = true + }, + mouseleaveSubItemMenu(index) { + setTimeout(() => { + if (this.isOverSubItemMenu && this.mouseoverItemIndex === index) this.mouseoverItemIndex = null + }, 1) + }, + mouseoverItem(index) { + this.isOverSubItemMenu = false + this.mouseoverItemIndex = index + }, + mouseleaveItem(index) { + setTimeout(() => { + if (this.isOverSubItemMenu) return + if (this.mouseoverItemIndex === index) this.mouseoverItemIndex = null + }, 1) + }, clickShowMenu() { if (this.disabled) return this.showMenu = !this.showMenu @@ -54,10 +84,10 @@ export default { clickedOutside() { this.showMenu = false }, - clickAction(action) { + clickAction(action, data) { if (this.disabled) return this.showMenu = false - this.$emit('action', action) + this.$emit('action', { action, data }) } }, mounted() {} diff --git a/client/components/ui/Menu.vue b/client/components/ui/Menu.vue deleted file mode 100644 index 4c06ac0b..00000000 --- a/client/components/ui/Menu.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - \ No newline at end of file diff --git a/client/components/widgets/MoreMenu.vue b/client/components/widgets/MoreMenu.vue index 1caa476b..9f7538e0 100644 --- a/client/components/widgets/MoreMenu.vue +++ b/client/components/widgets/MoreMenu.vue @@ -1,7 +1,17 @@