mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-19 03:30:51 +01:00
Add:Email smtp config & send ebooks to devices #1474
This commit is contained in:
parent
15aaf2863c
commit
05ce9c6eda
@ -211,7 +211,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
},
|
},
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'quick-embed') {
|
if (action === 'quick-embed') {
|
||||||
this.requestBatchQuickEmbed()
|
this.requestBatchQuickEmbed()
|
||||||
} else if (action === 'quick-match') {
|
} else if (action === 'quick-match') {
|
||||||
|
@ -296,7 +296,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'export-opml') {
|
if (action === 'export-opml') {
|
||||||
this.exportOPML()
|
this.exportOPML()
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ export default {
|
|||||||
exportOPML() {
|
exportOPML() {
|
||||||
this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true)
|
this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true)
|
||||||
},
|
},
|
||||||
seriesContextMenuAction(action) {
|
seriesContextMenuAction({ action }) {
|
||||||
if (action === 'open-rss-feed') {
|
if (action === 'open-rss-feed') {
|
||||||
this.showOpenSeriesRSSFeed()
|
this.showOpenSeriesRSSFeed()
|
||||||
} else if (action === 're-add-to-continue-listening') {
|
} else if (action === 're-add-to-continue-listening') {
|
||||||
|
@ -90,6 +90,11 @@ export default {
|
|||||||
title: this.$strings.HeaderNotifications,
|
title: this.$strings.HeaderNotifications,
|
||||||
path: '/config/notifications'
|
path: '/config/notifications'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'config-email',
|
||||||
|
title: this.$strings.HeaderEmail,
|
||||||
|
path: '/config/email'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'config-item-metadata-utils',
|
id: 'config-item-metadata-utils',
|
||||||
title: this.$strings.HeaderItemMetadataUtils,
|
title: this.$strings.HeaderItemMetadataUtils,
|
||||||
|
@ -448,7 +448,6 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (this.continueListeningShelf) {
|
if (this.continueListeningShelf) {
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
func: 'removeFromContinueListening',
|
func: 'removeFromContinueListening',
|
||||||
text: this.$strings.ButtonRemoveFromContinueListening
|
text: this.$strings.ButtonRemoveFromContinueListening
|
||||||
@ -490,6 +489,18 @@ export default {
|
|||||||
text: this.$strings.LabelAddToPlaylist
|
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) {
|
if (this.userCanUpdate) {
|
||||||
items.push({
|
items.push({
|
||||||
@ -720,6 +731,37 @@ export default {
|
|||||||
// More menu func
|
// More menu func
|
||||||
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'match' })
|
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() {
|
removeSeriesFromContinueListening() {
|
||||||
const axios = this.$axios || this.$nuxt.$axios
|
const axios = this.$axios || this.$nuxt.$axios
|
||||||
this.processing = true
|
this.processing = true
|
||||||
@ -833,8 +875,8 @@ export default {
|
|||||||
items: this.moreMenuItems
|
items: this.moreMenuItems
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$on('action', (func) => {
|
this.$on('action', (action) => {
|
||||||
if (_this[func]) _this[func]()
|
if (action.func && _this[action.func]) _this[action.func](action.data)
|
||||||
})
|
})
|
||||||
this.$on('close', () => {
|
this.$on('close', () => {
|
||||||
_this.isMoreMenuOpen = false
|
_this.isMoreMenuOpen = false
|
||||||
|
171
client/components/modals/emails/EReaderDeviceModal.vue
Normal file
171
client/components/modals/emails/EReaderDeviceModal.vue
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<template>
|
||||||
|
<modals-modal ref="modal" v-model="show" name="ereader-device-edit" :width="800" :height="'unset'" :processing="processing">
|
||||||
|
<template #outer>
|
||||||
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
|
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<form @submit.prevent="submitForm">
|
||||||
|
<div class="w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300">
|
||||||
|
<div class="w-full px-3 py-5 md:p-12">
|
||||||
|
<div class="flex items-center -mx-1 mb-2">
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="ereaderNameInput" v-model="newDevice.name" :disabled="processing" :label="$strings.LabelName" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="ereaderEmailInput" v-model="newDevice.email" :disabled="processing" :label="$strings.LabelEmail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center pt-4">
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<ui-btn color="success" type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</modals-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Boolean,
|
||||||
|
existingDevices: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
ereaderDevice: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
processing: false,
|
||||||
|
newDevice: {
|
||||||
|
name: '',
|
||||||
|
email: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return this.ereaderDevice ? 'Create Device' : 'Update Device'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitForm() {
|
||||||
|
this.$refs.ereaderNameInput.blur()
|
||||||
|
this.$refs.ereaderEmailInput.blur()
|
||||||
|
|
||||||
|
if (!this.newDevice.name?.trim() || !this.newDevice.email?.trim()) {
|
||||||
|
this.$toast.error('Name and email required')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newDevice.name = this.newDevice.name.trim()
|
||||||
|
this.newDevice.email = this.newDevice.email.trim()
|
||||||
|
|
||||||
|
if (!this.ereaderDevice) {
|
||||||
|
if (this.existingDevices.some((d) => d.name === this.newDevice.name)) {
|
||||||
|
this.$toast.error('EReader device with that name already exists')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitCreate()
|
||||||
|
} else {
|
||||||
|
if (this.ereaderDevice.name !== this.newDevice.name && this.existingDevices.some((d) => d.name === this.newDevice.name)) {
|
||||||
|
this.$toast.error('EReader device with that name already exists')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitUpdate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitUpdate() {
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
const existingDevicesWithoutThisOne = this.existingDevices.filter((d) => d.name !== this.ereaderDevice.name)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
ereaderDevices: [
|
||||||
|
...existingDevicesWithoutThisOne,
|
||||||
|
{
|
||||||
|
...this.newDevice
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/emails/ereader-devices`, payload)
|
||||||
|
.then((data) => {
|
||||||
|
this.$emit('update', data.ereaderDevices)
|
||||||
|
this.$toast.success('Device updated')
|
||||||
|
this.show = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update device', error)
|
||||||
|
this.$toast.error('Failed to update device')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitCreate() {
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
ereaderDevices: [
|
||||||
|
...this.existingDevices,
|
||||||
|
{
|
||||||
|
...this.newDevice
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$post('/api/emails/ereader-devices', payload)
|
||||||
|
.then((data) => {
|
||||||
|
this.$emit('update', data.ereaderDevices || [])
|
||||||
|
this.$toast.success('Device added')
|
||||||
|
this.show = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to add device', error)
|
||||||
|
this.$toast.error('Failed to add device')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (this.ereaderDevice) {
|
||||||
|
this.newDevice.name = this.ereaderDevice.name
|
||||||
|
this.newDevice.email = this.ereaderDevice.email
|
||||||
|
} else {
|
||||||
|
this.newDevice.name = ''
|
||||||
|
this.newDevice.email = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
@ -77,7 +77,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'delete') {
|
if (action === 'delete') {
|
||||||
this.deleteLibraryFile()
|
this.deleteLibraryFile()
|
||||||
} else if (action === 'download') {
|
} else if (action === 'download') {
|
||||||
|
@ -72,7 +72,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'delete') {
|
if (action === 'delete') {
|
||||||
this.deleteLibraryFile()
|
this.deleteLibraryFile()
|
||||||
} else if (action === 'download') {
|
} else if (action === 'download') {
|
||||||
|
@ -94,7 +94,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
this.showMobileMenu = false
|
this.showMobileMenu = false
|
||||||
if (action === 'edit') {
|
if (action === 'edit') {
|
||||||
this.editClick()
|
this.editClick()
|
||||||
|
@ -185,7 +185,7 @@ export default {
|
|||||||
this.searchText = this.search.toLowerCase().trim()
|
this.searchText = this.search.toLowerCase().trim()
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'quick-match-episodes') {
|
if (action === 'quick-match-episodes') {
|
||||||
if (this.quickMatchingEpisodes) return
|
if (this.quickMatchingEpisodes) return
|
||||||
|
|
||||||
|
@ -7,9 +7,19 @@
|
|||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<transition name="menu">
|
<transition name="menu">
|
||||||
<div v-show="showMenu" class="absolute right-0 mt-1 z-10 bg-bg border border-black-200 shadow-lg max-h-56 rounded-md py-1 overflow-auto focus:outline-none sm:text-sm" :style="{ width: menuWidth }">
|
<div v-show="showMenu" class="absolute right-0 mt-1 z-10 bg-bg border border-black-200 shadow-lg rounded-md py-1 focus:outline-none sm:text-sm" :style="{ width: menuWidth }">
|
||||||
<template v-for="(item, index) in items">
|
<template v-for="(item, index) in items">
|
||||||
<div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.action)">
|
<template v-if="item.subitems">
|
||||||
|
<div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-default" @mouseover="mouseoverItem(index)" @mouseleave="mouseleaveItem(index)" @click.stop>
|
||||||
|
<p>{{ item.text }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="mouseoverItemIndex === index" :key="`subitems-${index}`" @mouseover="mouseoverSubItemMenu(index)" @mouseleave="mouseleaveSubItemMenu(index)" class="absolute w-36 bg-bg rounded-md border border-black-200 shadow-lg z-50 -ml-px" :style="{ left: menuWidth, top: index * 29 + 'px' }">
|
||||||
|
<div v-for="(subitem, subitemindex) in item.subitems" :key="`subitem-${subitemindex}`" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(subitem.action, subitem.data)">
|
||||||
|
<p>{{ subitem.text }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.action)">
|
||||||
<p>{{ item.text }}</p>
|
<p>{{ item.text }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -42,11 +52,31 @@ export default {
|
|||||||
events: ['mousedown'],
|
events: ['mousedown'],
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
showMenu: false
|
showMenu: false,
|
||||||
|
mouseoverItemIndex: null,
|
||||||
|
isOverSubItemMenu: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {
|
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() {
|
clickShowMenu() {
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
this.showMenu = !this.showMenu
|
this.showMenu = !this.showMenu
|
||||||
@ -54,10 +84,10 @@ export default {
|
|||||||
clickedOutside() {
|
clickedOutside() {
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
},
|
},
|
||||||
clickAction(action) {
|
clickAction(action, data) {
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
this.$emit('action', action)
|
this.$emit('action', { action, data })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="relative" v-click-outside="clickOutside">
|
|
||||||
<button type="button" class="relative w-full bg-fg border border-gray-500 rounded shadow-sm pl-3 pr-10 py-2 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" @click.stop.prevent="showMenu = !showMenu">
|
|
||||||
<span class="flex items-center">
|
|
||||||
<span class="block truncate">{{ label }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
|
||||||
<span class="material-icons text-2xl text-gray-100" aria-label="User Account" role="button">person</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<transition name="menu">
|
|
||||||
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full bg-primary border border-black-200 shadow-lg max-h-56 rounded-b-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" tabindex="-1" role="listbox" aria-activedescendant="listbox-option-3">
|
|
||||||
<template v-for="item in items">
|
|
||||||
<nuxt-link :key="item.value" v-if="item.to" :to="item.to">
|
|
||||||
<li :key="item.value" class="text-gray-100 select-none relative py-2 cursor-pointer hover:bg-black-400" id="listbox-option-0" role="option" @click="clickedOption(item.value)">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="font-normal ml-3 block truncate font-sans">{{ item.text }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</nuxt-link>
|
|
||||||
<li v-else :key="item.value" class="text-gray-100 select-none relative py-2 cursor-pointer hover:bg-black-400" id="listbox-option-0" role="option" @click="clickedOption(item.value)">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="font-normal ml-3 block truncate font-sans">{{ item.text }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
default: 'Menu'
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showMenu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clickOutside() {
|
|
||||||
this.showMenu = false
|
|
||||||
},
|
|
||||||
clickedOption(itemValue) {
|
|
||||||
this.$emit('action', itemValue)
|
|
||||||
this.showMenu = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,7 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute w-36 bg-bg rounded-md border border-black-200 shadow-lg z-50" v-click-outside="clickOutsideObj" style="top: 0; left: 0">
|
<div class="absolute w-36 bg-bg rounded-md border border-black-200 shadow-lg z-50" v-click-outside="clickOutsideObj" style="top: 0; left: 0">
|
||||||
<template v-for="(item, index) in items">
|
<template v-for="(item, index) in items">
|
||||||
<div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.func)">
|
<template v-if="item.subitems">
|
||||||
|
<div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-default" @mouseover="mouseoverItem(index)" @mouseleave="mouseleaveItem(index)" @click.stop>
|
||||||
|
<p>{{ item.text }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="mouseoverItemIndex === index" :key="`subitems-${index}`" @mouseover="mouseoverSubItemMenu(index)" @mouseleave="mouseleaveSubItemMenu(index)" class="absolute w-36 bg-bg rounded-md border border-black-200 shadow-lg z-50" :style="{ left: 143 + 'px', top: index * 28 + 'px' }">
|
||||||
|
<div v-for="(subitem, subitemindex) in item.subitems" :key="`subitem-${subitemindex}`" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(subitem.func, subitem.data)">
|
||||||
|
<p>{{ subitem.text }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @mouseover="mouseoverItem(index)" @mouseleave="mouseleaveItem(index)" @click.stop="clickAction(item.func)">
|
||||||
<p>{{ item.text }}</p>
|
<p>{{ item.text }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -22,13 +32,36 @@ export default {
|
|||||||
handler: this.clickedOutside,
|
handler: this.clickedOutside,
|
||||||
events: ['mousedown'],
|
events: ['mousedown'],
|
||||||
isActive: true
|
isActive: true
|
||||||
}
|
},
|
||||||
|
mouseoverItemIndex: null,
|
||||||
|
isOverSubItemMenu: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {
|
methods: {
|
||||||
clickAction(func) {
|
mouseoverSubItemMenu(index) {
|
||||||
this.$emit('action', func)
|
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)
|
||||||
|
},
|
||||||
|
clickAction(func, data) {
|
||||||
|
this.$emit('action', {
|
||||||
|
func,
|
||||||
|
data
|
||||||
|
})
|
||||||
this.close()
|
this.close()
|
||||||
},
|
},
|
||||||
clickedOutside(e) {
|
clickedOutside(e) {
|
||||||
|
@ -380,6 +380,11 @@ export default {
|
|||||||
adminMessageEvt(message) {
|
adminMessageEvt(message) {
|
||||||
this.$toast.info(message)
|
this.$toast.info(message)
|
||||||
},
|
},
|
||||||
|
ereaderDevicesUpdated(data) {
|
||||||
|
if (!data?.ereaderDevices) return
|
||||||
|
|
||||||
|
this.$store.commit('libraries/setEReaderDevices', data.ereaderDevices)
|
||||||
|
},
|
||||||
initializeSocket() {
|
initializeSocket() {
|
||||||
this.socket = this.$nuxtSocket({
|
this.socket = this.$nuxtSocket({
|
||||||
name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
|
name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
|
||||||
@ -452,6 +457,9 @@ export default {
|
|||||||
this.socket.on('task_finished', this.taskFinished)
|
this.socket.on('task_finished', this.taskFinished)
|
||||||
this.socket.on('metadata_embed_queue_update', this.metadataEmbedQueueUpdate)
|
this.socket.on('metadata_embed_queue_update', this.metadataEmbedQueueUpdate)
|
||||||
|
|
||||||
|
// EReader Device Listeners
|
||||||
|
this.socket.on('ereader-devices-updated', this.ereaderDevicesUpdated)
|
||||||
|
|
||||||
this.socket.on('backup_applied', this.backupApplied)
|
this.socket.on('backup_applied', this.backupApplied)
|
||||||
|
|
||||||
this.socket.on('batch_quickmatch_complete', this.batchQuickMatchComplete)
|
this.socket.on('batch_quickmatch_complete', this.batchQuickMatchComplete)
|
||||||
|
@ -145,7 +145,7 @@ export default {
|
|||||||
feed: this.rssFeed
|
feed: this.rssFeed
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
contextMenuAction(action) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'delete') {
|
if (action === 'delete') {
|
||||||
this.removeClick()
|
this.removeClick()
|
||||||
} else if (action === 'create-playlist') {
|
} else if (action === 'create-playlist') {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="configContent" :class="`page-${currentPage}`">
|
<div class="configContent" :class="`page-${currentPage}`">
|
||||||
<div v-show="isMobilePortrait" class="w-full pb-4 px-2 flex border-b border-white border-opacity-10 mb-2 cursor-pointer" @click.stop.prevent="toggleShowMore">
|
<div v-show="isMobilePortrait" class="w-full pb-4 px-2 flex border-b border-white border-opacity-10 mb-2 cursor-pointer" @click.stop.prevent="toggleShowMore">
|
||||||
<span class="material-icons text-2xl cursor-pointer">arrow_forward</span>
|
<span class="material-icons text-2xl cursor-pointer">arrow_forward</span>
|
||||||
<p class="pl-3 capitalize">{{ $strings.HeaderSettings }}</p>
|
<p class="pl-3 capitalize">{{ currentPage }}</p>
|
||||||
</div>
|
</div>
|
||||||
<nuxt-child />
|
<nuxt-child />
|
||||||
</div>
|
</div>
|
||||||
@ -55,6 +55,7 @@ export default {
|
|||||||
else if (pageName === 'library-stats') return this.$strings.HeaderLibraryStats
|
else if (pageName === 'library-stats') return this.$strings.HeaderLibraryStats
|
||||||
else if (pageName === 'users') return this.$strings.HeaderUsers
|
else if (pageName === 'users') return this.$strings.HeaderUsers
|
||||||
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
|
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
|
||||||
|
else if (pageName === 'email') return this.$strings.HeaderEmail
|
||||||
}
|
}
|
||||||
return this.$strings.HeaderSettings
|
return this.$strings.HeaderSettings
|
||||||
}
|
}
|
||||||
@ -79,14 +80,6 @@ export default {
|
|||||||
width: 900px;
|
width: 900px;
|
||||||
max-width: calc(100% - 176px);
|
max-width: calc(100% - 176px);
|
||||||
}
|
}
|
||||||
.configContent.page-library-stats {
|
|
||||||
width: 1200px;
|
|
||||||
}
|
|
||||||
@media (max-width: 1550px) {
|
|
||||||
.configContent.page-library-stats {
|
|
||||||
margin-left: 176px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 1240px) {
|
@media (max-width: 1240px) {
|
||||||
.configContent {
|
.configContent {
|
||||||
margin-left: 176px;
|
margin-left: 176px;
|
||||||
@ -98,8 +91,5 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.configContent.page-library-stats {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
244
client/pages/config/email.vue
Normal file
244
client/pages/config/email.vue
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<app-settings-content :header-text="$strings.HeaderEmailSettings" :description="''">
|
||||||
|
<form @submit.prevent="submitForm">
|
||||||
|
<div class="flex items-center -mx-1 mb-2">
|
||||||
|
<div class="w-full md:w-3/4 px-1">
|
||||||
|
<ui-text-input-with-label ref="hostInput" v-model="newSettings.host" :disabled="savingSettings" :label="$strings.LabelHost" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/4 px-1">
|
||||||
|
<ui-text-input-with-label ref="portInput" v-model="newSettings.port" type="number" :disabled="savingSettings" :label="$strings.LabelPort" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-2 py-3">
|
||||||
|
<ui-toggle-switch labeledBy="email-settings-secure" v-model="newSettings.secure" :disabled="savingSettings" />
|
||||||
|
<ui-tooltip :text="$strings.LabelEmailSettingsSecureHelp">
|
||||||
|
<div class="pl-4 flex items-center">
|
||||||
|
<span id="email-settings-secure">{{ $strings.LabelEmailSettingsSecure }}</span>
|
||||||
|
<span class="material-icons text-lg pl-1">info_outlined</span>
|
||||||
|
</div>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center -mx-1 mb-2">
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="userInput" v-model="newSettings.user" :disabled="savingSettings" :label="$strings.LabelUsername" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="passInput" v-model="newSettings.pass" type="password" :disabled="savingSettings" :label="$strings.LabelPassword" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center -mx-1 mb-2">
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="fromInput" v-model="newSettings.fromAddress" :disabled="savingSettings" :label="$strings.LabelEmailSettingsFromAddress" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between pt-4">
|
||||||
|
<ui-btn :loading="sendingTest" :disabled="savingSettings || !newSettings.host" type="button" @click="sendTestClick">{{ $strings.ButtonTest }}</ui-btn>
|
||||||
|
<ui-btn :loading="savingSettings" :disabled="!hasUpdates" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div v-show="loading" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-25 flex items-center justify-center">
|
||||||
|
<ui-loading-indicator />
|
||||||
|
</div>
|
||||||
|
</app-settings-content>
|
||||||
|
|
||||||
|
<app-settings-content :header-text="$strings.HeaderEReaderDevices" showAddButton :description="''" @clicked="addNewDeviceClick">
|
||||||
|
<table v-if="existingEReaderDevices.length" class="tracksTable my-4">
|
||||||
|
<tr>
|
||||||
|
<th class="text-left">{{ $strings.LabelName }}</th>
|
||||||
|
<th class="text-left">{{ $strings.LabelEmail }}</th>
|
||||||
|
<th class="w-40"></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="device in existingEReaderDevices" :key="device.name">
|
||||||
|
<td>
|
||||||
|
<p class="text-sm md:text-base text-gray-100">{{ device.name }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="text-left">
|
||||||
|
<p class="text-sm md:text-base text-gray-100">{{ device.email }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="w-40">
|
||||||
|
<div class="flex justify-end items-center h-10">
|
||||||
|
<ui-icon-btn icon="edit" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name" class="mx-1" @click="editDeviceClick(device)" />
|
||||||
|
<ui-icon-btn icon="delete" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name" @click="deleteDeviceClick(device)" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div v-else class="text-center py-4">
|
||||||
|
<p class="text-lg text-gray-100">No Devices</p>
|
||||||
|
</div>
|
||||||
|
</app-settings-content>
|
||||||
|
|
||||||
|
<modals-emails-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="existingEReaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
savingSettings: false,
|
||||||
|
sendingTest: false,
|
||||||
|
deletingDeviceName: null,
|
||||||
|
settings: null,
|
||||||
|
newSettings: {
|
||||||
|
host: null,
|
||||||
|
port: 465,
|
||||||
|
secure: true,
|
||||||
|
user: null,
|
||||||
|
pass: null,
|
||||||
|
fromAddress: null
|
||||||
|
},
|
||||||
|
newEReaderDevice: {
|
||||||
|
name: '',
|
||||||
|
email: ''
|
||||||
|
},
|
||||||
|
selectedEReaderDevice: null,
|
||||||
|
showEReaderDeviceModal: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasUpdates() {
|
||||||
|
if (!this.settings) return true
|
||||||
|
for (const key in this.newSettings) {
|
||||||
|
if (key === 'ereaderDevices') continue
|
||||||
|
if (this.newSettings[key] !== this.settings[key]) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
existingEReaderDevices() {
|
||||||
|
return this.settings?.ereaderDevices || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
editDeviceClick(device) {
|
||||||
|
this.selectedEReaderDevice = device
|
||||||
|
this.showEReaderDeviceModal = true
|
||||||
|
},
|
||||||
|
deleteDeviceClick(device) {
|
||||||
|
const payload = {
|
||||||
|
message: `Are you sure you want to delete e-reader device "${device.name}"?`,
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.deleteDevice(device)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: 'yesNo'
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
|
},
|
||||||
|
deleteDevice(device) {
|
||||||
|
const payload = {
|
||||||
|
ereaderDevices: this.existingEReaderDevices.filter((d) => d.name !== device.name)
|
||||||
|
}
|
||||||
|
this.deletingDeviceName = device.name
|
||||||
|
this.$axios
|
||||||
|
.$patch(`/emails/ereader-devices`, payload)
|
||||||
|
.then((data) => {
|
||||||
|
this.ereaderDevicesUpdated(data.ereaderDevices)
|
||||||
|
this.$toast.success('Device deleted')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to delete device', error)
|
||||||
|
this.$toast.error('Failed to delete device')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.deletingDeviceName = null
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ereaderDevicesUpdated(ereaderDevices) {
|
||||||
|
this.settings.ereaderDevices = ereaderDevices
|
||||||
|
this.newSettings.ereaderDevices = ereaderDevices.map((d) => ({ ...d }))
|
||||||
|
},
|
||||||
|
addNewDeviceClick() {
|
||||||
|
this.selectedEReaderDevice = null
|
||||||
|
this.showEReaderDeviceModal = true
|
||||||
|
},
|
||||||
|
sendTestClick() {
|
||||||
|
this.sendingTest = true
|
||||||
|
this.$axios
|
||||||
|
.$post('/api/emails/test')
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Test Email Sent')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to send test email', error)
|
||||||
|
const errorMsg = error.response.data || 'Failed to send test email'
|
||||||
|
this.$toast.error(errorMsg)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.sendingTest = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
validateForm() {
|
||||||
|
for (const ref of [this.$refs.hostInput, this.$refs.portInput, this.$refs.userInput, this.$refs.passInput, this.$refs.fromInput]) {
|
||||||
|
if (ref?.blur) ref.blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.newSettings.port) {
|
||||||
|
this.newSettings.port = Number(this.newSettings.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
if (!this.validateForm()) return
|
||||||
|
|
||||||
|
const updatePayload = {
|
||||||
|
host: this.newSettings.host,
|
||||||
|
port: this.newSettings.port,
|
||||||
|
secure: this.newSettings.secure,
|
||||||
|
user: this.newSettings.user,
|
||||||
|
pass: this.newSettings.pass,
|
||||||
|
fromAddress: this.newSettings.fromAddress
|
||||||
|
}
|
||||||
|
this.savingSettings = true
|
||||||
|
this.$axios
|
||||||
|
.$patch('/api/emails/settings', updatePayload)
|
||||||
|
.then((data) => {
|
||||||
|
this.settings = data.settings
|
||||||
|
this.newSettings = {
|
||||||
|
...data.settings
|
||||||
|
}
|
||||||
|
this.$toast.success('Email settings updated')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update email settings', error)
|
||||||
|
this.$toast.error('Failed to update email settings')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.savingSettings = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$get(`/api/emails/settings`)
|
||||||
|
.then((data) => {
|
||||||
|
this.settings = data.settings
|
||||||
|
this.newSettings = {
|
||||||
|
...this.settings
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to get email settings', error)
|
||||||
|
this.$toast.error('Failed to load email settings')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
beforeDestroy() {}
|
||||||
|
}
|
||||||
|
</script>
|
@ -431,6 +431,19 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.ebookFile && this.$store.state.libraries.ereaderDevices?.length) {
|
||||||
|
items.push({
|
||||||
|
text: this.$strings.LabelSendEbookToDevice,
|
||||||
|
subitems: this.$store.state.libraries.ereaderDevices.map((d) => {
|
||||||
|
return {
|
||||||
|
text: d.name,
|
||||||
|
action: 'sendToDevice',
|
||||||
|
data: d.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (this.userCanDelete) {
|
if (this.userCanDelete) {
|
||||||
items.push({
|
items.push({
|
||||||
text: this.$strings.ButtonDelete,
|
text: this.$strings.ButtonDelete,
|
||||||
@ -704,7 +717,35 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
},
|
},
|
||||||
contextMenuAction(action) {
|
sendToDevice(deviceName) {
|
||||||
|
const payload = {
|
||||||
|
message: this.$getString('MessageConfirmSendEbookToDevice', [this.ebookFile.ebookFormat, this.title, deviceName]),
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
const payload = {
|
||||||
|
libraryItemId: this.libraryItemId,
|
||||||
|
deviceName
|
||||||
|
}
|
||||||
|
this.processing = true
|
||||||
|
this.$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)
|
||||||
|
},
|
||||||
|
contextMenuAction({ action, data }) {
|
||||||
if (action === 'collections') {
|
if (action === 'collections') {
|
||||||
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
||||||
this.$store.commit('globals/setShowCollectionsModal', true)
|
this.$store.commit('globals/setShowCollectionsModal', true)
|
||||||
@ -719,6 +760,8 @@ export default {
|
|||||||
this.downloadLibraryItem()
|
this.downloadLibraryItem()
|
||||||
} else if (action === 'delete') {
|
} else if (action === 'delete') {
|
||||||
this.deleteLibraryItem()
|
this.deleteLibraryItem()
|
||||||
|
} else if (action === 'sendToDevice') {
|
||||||
|
this.sendToDevice(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -107,7 +107,7 @@ export default {
|
|||||||
const payload = {
|
const payload = {
|
||||||
newRoot: { ...this.newRoot }
|
newRoot: { ...this.newRoot }
|
||||||
}
|
}
|
||||||
var success = await this.$axios
|
const success = await this.$axios
|
||||||
.$post('/init', payload)
|
.$post('/init', payload)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -124,9 +124,10 @@ export default {
|
|||||||
|
|
||||||
location.reload()
|
location.reload()
|
||||||
},
|
},
|
||||||
setUser({ user, userDefaultLibraryId, serverSettings, Source, feeds }) {
|
setUser({ user, userDefaultLibraryId, serverSettings, Source, ereaderDevices }) {
|
||||||
this.$store.commit('setServerSettings', serverSettings)
|
this.$store.commit('setServerSettings', serverSettings)
|
||||||
this.$store.commit('setSource', Source)
|
this.$store.commit('setSource', Source)
|
||||||
|
this.$store.commit('libraries/setEReaderDevices', ereaderDevices)
|
||||||
this.$setServerLanguageCode(serverSettings.language)
|
this.$setServerLanguageCode(serverSettings.language)
|
||||||
|
|
||||||
if (serverSettings.chromecastEnabled) {
|
if (serverSettings.chromecastEnabled) {
|
||||||
|
@ -11,7 +11,8 @@ export const state = () => ({
|
|||||||
filterData: null,
|
filterData: null,
|
||||||
numUserPlaylists: 0,
|
numUserPlaylists: 0,
|
||||||
collections: [],
|
collections: [],
|
||||||
userPlaylists: []
|
userPlaylists: [],
|
||||||
|
ereaderDevices: []
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
@ -339,5 +340,8 @@ export const mutations = {
|
|||||||
removeUserPlaylist(state, playlist) {
|
removeUserPlaylist(state, playlist) {
|
||||||
state.userPlaylists = state.userPlaylists.filter(p => p.id !== playlist.id)
|
state.userPlaylists = state.userPlaylists.filter(p => p.id !== playlist.id)
|
||||||
state.numUserPlaylists = state.userPlaylists.length
|
state.numUserPlaylists = state.userPlaylists.length
|
||||||
|
},
|
||||||
|
setEReaderDevices(state, ereaderDevices) {
|
||||||
|
state.ereaderDevices = ereaderDevices
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "M4B-Kodierung starten",
|
"ButtonStartM4BEncode": "M4B-Kodierung starten",
|
||||||
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
||||||
"ButtonSubmit": "Ok",
|
"ButtonSubmit": "Ok",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Hochladen",
|
"ButtonUpload": "Hochladen",
|
||||||
"ButtonUploadBackup": "Sicherung hochladen",
|
"ButtonUploadBackup": "Sicherung hochladen",
|
||||||
"ButtonUploadCover": "Titelbild hochladen",
|
"ButtonUploadCover": "Titelbild hochladen",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Aktuelle Downloads",
|
"HeaderCurrentDownloads": "Aktuelle Downloads",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download Warteschlange",
|
"HeaderDownloadQueue": "Download Warteschlange",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episoden",
|
"HeaderEpisodes": "Episoden",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Dateien",
|
"HeaderFiles": "Dateien",
|
||||||
"HeaderFindChapters": "Kapitel suchen",
|
"HeaderFindChapters": "Kapitel suchen",
|
||||||
"HeaderIgnoredFiles": "Ignorierte Dateien",
|
"HeaderIgnoredFiles": "Ignorierte Dateien",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Laufzeit",
|
"LabelDuration": "Laufzeit",
|
||||||
"LabelDurationFound": "Gefundene Laufzeit:",
|
"LabelDurationFound": "Gefundene Laufzeit:",
|
||||||
"LabelEdit": "Bearbeiten",
|
"LabelEdit": "Bearbeiten",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Eingebettetes Cover",
|
"LabelEmbeddedCover": "Eingebettetes Cover",
|
||||||
"LabelEnable": "Aktivieren",
|
"LabelEnable": "Aktivieren",
|
||||||
"LabelEnd": "Ende",
|
"LabelEnd": "Ende",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Kategorie",
|
"LabelGenre": "Kategorie",
|
||||||
"LabelGenres": "Kategorien",
|
"LabelGenres": "Kategorien",
|
||||||
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Stunde",
|
"LabelHour": "Stunde",
|
||||||
"LabelIcon": "Symbol",
|
"LabelIcon": "Symbol",
|
||||||
"LabelIncludeInTracklist": "In die Titelliste aufnehmen",
|
"LabelIncludeInTracklist": "In die Titelliste aufnehmen",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Podcast Typ",
|
"LabelPodcastType": "Podcast Typ",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Zu ignorierende(s) Vorwort(e) (Groß- und Kleinschreibung wird nicht berücksichtigt)",
|
"LabelPrefixesToIgnore": "Zu ignorierende(s) Vorwort(e) (Groß- und Kleinschreibung wird nicht berücksichtigt)",
|
||||||
"LabelPreventIndexing": "Verhindere, dass dein Feed von iTunes- und Google-Podcast-Verzeichnissen indiziert wird",
|
"LabelPreventIndexing": "Verhindere, dass dein Feed von iTunes- und Google-Podcast-Verzeichnissen indiziert wird",
|
||||||
"LabelProgress": "Fortschritt",
|
"LabelProgress": "Fortschritt",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Titel",
|
"LabelSearchTitle": "Titel",
|
||||||
"LabelSearchTitleOrASIN": "Titel oder ASIN",
|
"LabelSearchTitleOrASIN": "Titel oder ASIN",
|
||||||
"LabelSeason": "Staffel",
|
"LabelSeason": "Staffel",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Reihenfolge",
|
"LabelSequence": "Reihenfolge",
|
||||||
"LabelSeries": "Serien",
|
"LabelSeries": "Serien",
|
||||||
"LabelSeriesName": "Serienname",
|
"LabelSeriesName": "Serienname",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Sind Sie sicher, dass Sie den Tag \"{0}\" in \"{1}\" für alle Hörbücher/Podcasts umbenennen wollen?",
|
"MessageConfirmRenameTag": "Sind Sie sicher, dass Sie den Tag \"{0}\" in \"{1}\" für alle Hörbücher/Podcasts umbenennen wollen?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Hinweis: Tag existiert bereits -> Tags werden zusammengelegt.",
|
"MessageConfirmRenameTagMergeNote": "Hinweis: Tag existiert bereits -> Tags werden zusammengelegt.",
|
||||||
"MessageConfirmRenameTagWarning": "Warnung! Ein ähnlicher Tag mit einem anderen Wortlaut existiert bereits: \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warnung! Ein ähnlicher Tag mit einem anderen Wortlaut existiert bereits: \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Episode herunterladen",
|
"MessageDownloadingEpisode": "Episode herunterladen",
|
||||||
"MessageDragFilesIntoTrackOrder": "Verschieben Sie die Dateien in die richtige Reihenfolge",
|
"MessageDragFilesIntoTrackOrder": "Verschieben Sie die Dateien in die richtige Reihenfolge",
|
||||||
"MessageEmbedFinished": "Einbettung abgeschlossen!",
|
"MessageEmbedFinished": "Einbettung abgeschlossen!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
||||||
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
|
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
|
||||||
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
|
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
|
||||||
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Start M4B Encode",
|
"ButtonStartM4BEncode": "Start M4B Encode",
|
||||||
"ButtonStartMetadataEmbed": "Start Metadata Embed",
|
"ButtonStartMetadataEmbed": "Start Metadata Embed",
|
||||||
"ButtonSubmit": "Submit",
|
"ButtonSubmit": "Submit",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Upload",
|
"ButtonUpload": "Upload",
|
||||||
"ButtonUploadBackup": "Upload Backup",
|
"ButtonUploadBackup": "Upload Backup",
|
||||||
"ButtonUploadCover": "Upload Cover",
|
"ButtonUploadCover": "Upload Cover",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Current Downloads",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelDurationFound": "Duration found:",
|
"LabelDurationFound": "Duration found:",
|
||||||
"LabelEdit": "Edit",
|
"LabelEdit": "Edit",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
"LabelIncludeInTracklist": "Include in Tracklist",
|
"LabelIncludeInTracklist": "Include in Tracklist",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Podcast Type",
|
"LabelPodcastType": "Podcast Type",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
||||||
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
||||||
"LabelProgress": "Progress",
|
"LabelProgress": "Progress",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
"LabelSeriesName": "Series Name",
|
"LabelSeriesName": "Series Name",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
||||||
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Downloading episode",
|
"MessageDownloadingEpisode": "Downloading episode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||||
"MessageEmbedFinished": "Embed Finished!",
|
"MessageEmbedFinished": "Embed Finished!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
||||||
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Series update failed",
|
"ToastSeriesUpdateFailed": "Series update failed",
|
||||||
"ToastSeriesUpdateSuccess": "Series update success",
|
"ToastSeriesUpdateSuccess": "Series update success",
|
||||||
"ToastSessionDeleteFailed": "Failed to delete session",
|
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Iniciar Codificación M4B",
|
"ButtonStartM4BEncode": "Iniciar Codificación M4B",
|
||||||
"ButtonStartMetadataEmbed": "Iniciar la Inserción de Metadata",
|
"ButtonStartMetadataEmbed": "Iniciar la Inserción de Metadata",
|
||||||
"ButtonSubmit": "Enviar",
|
"ButtonSubmit": "Enviar",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Subir",
|
"ButtonUpload": "Subir",
|
||||||
"ButtonUploadBackup": "Subir Respaldo",
|
"ButtonUploadBackup": "Subir Respaldo",
|
||||||
"ButtonUploadCover": "Subir Portada",
|
"ButtonUploadCover": "Subir Portada",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Descargando Actualmente",
|
"HeaderCurrentDownloads": "Descargando Actualmente",
|
||||||
"HeaderDetails": "Detalles",
|
"HeaderDetails": "Detalles",
|
||||||
"HeaderDownloadQueue": "Lista de Descarga",
|
"HeaderDownloadQueue": "Lista de Descarga",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodios",
|
"HeaderEpisodes": "Episodios",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Elemento",
|
"HeaderFiles": "Elemento",
|
||||||
"HeaderFindChapters": "Buscar Capitulo",
|
"HeaderFindChapters": "Buscar Capitulo",
|
||||||
"HeaderIgnoredFiles": "Ignorar Elemento",
|
"HeaderIgnoredFiles": "Ignorar Elemento",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Duración",
|
"LabelDuration": "Duración",
|
||||||
"LabelDurationFound": "Duración Comprobada:",
|
"LabelDurationFound": "Duración Comprobada:",
|
||||||
"LabelEdit": "Editar",
|
"LabelEdit": "Editar",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Portada Integrada",
|
"LabelEmbeddedCover": "Portada Integrada",
|
||||||
"LabelEnable": "Habilitar",
|
"LabelEnable": "Habilitar",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genero",
|
"LabelGenre": "Genero",
|
||||||
"LabelGenres": "Géneros",
|
"LabelGenres": "Géneros",
|
||||||
"LabelHardDeleteFile": "Eliminar Definitivamente",
|
"LabelHardDeleteFile": "Eliminar Definitivamente",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hora",
|
"LabelHour": "Hora",
|
||||||
"LabelIcon": "Icono",
|
"LabelIcon": "Icono",
|
||||||
"LabelIncludeInTracklist": "Incluir en Tracklist",
|
"LabelIncludeInTracklist": "Incluir en Tracklist",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Tipo Podcast",
|
"LabelPodcastType": "Tipo Podcast",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
|
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
|
||||||
"LabelPreventIndexing": "Evite que su fuente sea indexado por iTunes y Google podcast directories",
|
"LabelPreventIndexing": "Evite que su fuente sea indexado por iTunes y Google podcast directories",
|
||||||
"LabelProgress": "Progreso",
|
"LabelProgress": "Progreso",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Buscar Titulo",
|
"LabelSearchTitle": "Buscar Titulo",
|
||||||
"LabelSearchTitleOrASIN": "Buscar Titulo o ASIN",
|
"LabelSearchTitleOrASIN": "Buscar Titulo o ASIN",
|
||||||
"LabelSeason": "Temporada",
|
"LabelSeason": "Temporada",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Secuencia",
|
"LabelSequence": "Secuencia",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
"LabelSeriesName": "Nombre de la Serie",
|
"LabelSeriesName": "Nombre de la Serie",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Esta seguro que desea renombrar la etiqueta \"{0}\" a \"{1}\" de todos los elementos?",
|
"MessageConfirmRenameTag": "Esta seguro que desea renombrar la etiqueta \"{0}\" a \"{1}\" de todos los elementos?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Nota: Esta etiqueta ya existe por lo que se fusionarán.",
|
"MessageConfirmRenameTagMergeNote": "Nota: Esta etiqueta ya existe por lo que se fusionarán.",
|
||||||
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Descargando Capitulo",
|
"MessageDownloadingEpisode": "Descargando Capitulo",
|
||||||
"MessageDragFilesIntoTrackOrder": "Arrastras los archivos en el orden correcto de la pista.",
|
"MessageDragFilesIntoTrackOrder": "Arrastras los archivos en el orden correcto de la pista.",
|
||||||
"MessageEmbedFinished": "Incorporación Terminada!",
|
"MessageEmbedFinished": "Incorporación Terminada!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Elemento eliminado de la colección.",
|
"ToastRemoveItemFromCollectionSuccess": "Elemento eliminado de la colección.",
|
||||||
"ToastRSSFeedCloseFailed": "Error al cerrar fuente RSS",
|
"ToastRSSFeedCloseFailed": "Error al cerrar fuente RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Fuente RSS cerrada",
|
"ToastRSSFeedCloseSuccess": "Fuente RSS cerrada",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Error al actualizar la serie",
|
"ToastSeriesUpdateFailed": "Error al actualizar la serie",
|
||||||
"ToastSeriesUpdateSuccess": "Series actualizada",
|
"ToastSeriesUpdateSuccess": "Series actualizada",
|
||||||
"ToastSessionDeleteFailed": "Error al eliminar sesión",
|
"ToastSessionDeleteFailed": "Error al eliminar sesión",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Démarrer l’encodage M4B",
|
"ButtonStartM4BEncode": "Démarrer l’encodage M4B",
|
||||||
"ButtonStartMetadataEmbed": "Démarrer les Métadonnées intégrées",
|
"ButtonStartMetadataEmbed": "Démarrer les Métadonnées intégrées",
|
||||||
"ButtonSubmit": "Soumettre",
|
"ButtonSubmit": "Soumettre",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Téléverser",
|
"ButtonUpload": "Téléverser",
|
||||||
"ButtonUploadBackup": "Téléverser une sauvegarde",
|
"ButtonUploadBackup": "Téléverser une sauvegarde",
|
||||||
"ButtonUploadCover": "Téléverser une couverture",
|
"ButtonUploadCover": "Téléverser une couverture",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "File d’attente de téléchargement",
|
"HeaderCurrentDownloads": "File d’attente de téléchargement",
|
||||||
"HeaderDetails": "Détails",
|
"HeaderDetails": "Détails",
|
||||||
"HeaderDownloadQueue": "Queue de téléchargement",
|
"HeaderDownloadQueue": "Queue de téléchargement",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Épisodes",
|
"HeaderEpisodes": "Épisodes",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Fichiers",
|
"HeaderFiles": "Fichiers",
|
||||||
"HeaderFindChapters": "Trouver les chapitres",
|
"HeaderFindChapters": "Trouver les chapitres",
|
||||||
"HeaderIgnoredFiles": "Fichiers Ignorés",
|
"HeaderIgnoredFiles": "Fichiers Ignorés",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Durée",
|
"LabelDuration": "Durée",
|
||||||
"LabelDurationFound": "Durée trouvée :",
|
"LabelDurationFound": "Durée trouvée :",
|
||||||
"LabelEdit": "Modifier",
|
"LabelEdit": "Modifier",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Couverture du livre intégrée",
|
"LabelEmbeddedCover": "Couverture du livre intégrée",
|
||||||
"LabelEnable": "Activer",
|
"LabelEnable": "Activer",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Suppression du fichier",
|
"LabelHardDeleteFile": "Suppression du fichier",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Heure",
|
"LabelHour": "Heure",
|
||||||
"LabelIcon": "Icone",
|
"LabelIcon": "Icone",
|
||||||
"LabelIncludeInTracklist": "Inclure dans la liste des pistes",
|
"LabelIncludeInTracklist": "Inclure dans la liste des pistes",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Type de Podcast",
|
"LabelPodcastType": "Type de Podcast",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)",
|
"LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)",
|
||||||
"LabelPreventIndexing": "Empêcher l’indexation de votre flux par les bases de donénes iTunes et Google podcast",
|
"LabelPreventIndexing": "Empêcher l’indexation de votre flux par les bases de donénes iTunes et Google podcast",
|
||||||
"LabelProgress": "Progression",
|
"LabelProgress": "Progression",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Titre de recherche",
|
"LabelSearchTitle": "Titre de recherche",
|
||||||
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
|
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
|
||||||
"LabelSeason": "Saison",
|
"LabelSeason": "Saison",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Séquence",
|
"LabelSequence": "Séquence",
|
||||||
"LabelSeries": "Séries",
|
"LabelSeries": "Séries",
|
||||||
"LabelSeriesName": "Nom de la série",
|
"LabelSeriesName": "Nom de la série",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer l’étiquette « {0} » vers « {1} » pour tous les articles ?",
|
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer l’étiquette « {0} » vers « {1} » pour tous les articles ?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.",
|
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.",
|
||||||
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà « {0} ».",
|
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà « {0} ».",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l’ordre correct",
|
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l’ordre correct",
|
||||||
"MessageEmbedFinished": "Intégration Terminée !",
|
"MessageEmbedFinished": "Intégration Terminée !",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Article supprimé de la collection",
|
"ToastRemoveItemFromCollectionSuccess": "Article supprimé de la collection",
|
||||||
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Échec de la mise à jour de la série",
|
"ToastSeriesUpdateFailed": "Échec de la mise à jour de la série",
|
||||||
"ToastSeriesUpdateSuccess": "Mise à jour de la série réussie",
|
"ToastSeriesUpdateSuccess": "Mise à jour de la série réussie",
|
||||||
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "M4B એન્કોડ શરૂ કરો",
|
"ButtonStartM4BEncode": "M4B એન્કોડ શરૂ કરો",
|
||||||
"ButtonStartMetadataEmbed": "મેટાડેટા એમ્બેડ શરૂ કરો",
|
"ButtonStartMetadataEmbed": "મેટાડેટા એમ્બેડ શરૂ કરો",
|
||||||
"ButtonSubmit": "સબમિટ કરો",
|
"ButtonSubmit": "સબમિટ કરો",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "અપલોડ કરો",
|
"ButtonUpload": "અપલોડ કરો",
|
||||||
"ButtonUploadBackup": "બેકઅપ અપલોડ કરો",
|
"ButtonUploadBackup": "બેકઅપ અપલોડ કરો",
|
||||||
"ButtonUploadCover": "કવર અપલોડ કરો",
|
"ButtonUploadCover": "કવર અપલોડ કરો",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Current Downloads",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelDurationFound": "Duration found:",
|
"LabelDurationFound": "Duration found:",
|
||||||
"LabelEdit": "Edit",
|
"LabelEdit": "Edit",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
"LabelIncludeInTracklist": "Include in Tracklist",
|
"LabelIncludeInTracklist": "Include in Tracklist",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Podcast Type",
|
"LabelPodcastType": "Podcast Type",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
||||||
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
||||||
"LabelProgress": "Progress",
|
"LabelProgress": "Progress",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
"LabelSeriesName": "Series Name",
|
"LabelSeriesName": "Series Name",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
||||||
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Downloading episode",
|
"MessageDownloadingEpisode": "Downloading episode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||||
"MessageEmbedFinished": "Embed Finished!",
|
"MessageEmbedFinished": "Embed Finished!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
||||||
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Series update failed",
|
"ToastSeriesUpdateFailed": "Series update failed",
|
||||||
"ToastSeriesUpdateSuccess": "Series update success",
|
"ToastSeriesUpdateSuccess": "Series update success",
|
||||||
"ToastSessionDeleteFailed": "Failed to delete session",
|
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "M4B एन्कोडिंग शुरू करें",
|
"ButtonStartM4BEncode": "M4B एन्कोडिंग शुरू करें",
|
||||||
"ButtonStartMetadataEmbed": "मेटाडेटा एम्बेडिंग शुरू करें",
|
"ButtonStartMetadataEmbed": "मेटाडेटा एम्बेडिंग शुरू करें",
|
||||||
"ButtonSubmit": "जमा करें",
|
"ButtonSubmit": "जमा करें",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "अपलोड करें",
|
"ButtonUpload": "अपलोड करें",
|
||||||
"ButtonUploadBackup": "बैकअप अपलोड करें",
|
"ButtonUploadBackup": "बैकअप अपलोड करें",
|
||||||
"ButtonUploadCover": "कवर अपलोड करें",
|
"ButtonUploadCover": "कवर अपलोड करें",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Current Downloads",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelDurationFound": "Duration found:",
|
"LabelDurationFound": "Duration found:",
|
||||||
"LabelEdit": "Edit",
|
"LabelEdit": "Edit",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
"LabelIncludeInTracklist": "Include in Tracklist",
|
"LabelIncludeInTracklist": "Include in Tracklist",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Podcast Type",
|
"LabelPodcastType": "Podcast Type",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
||||||
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
||||||
"LabelProgress": "Progress",
|
"LabelProgress": "Progress",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
"LabelSeriesName": "Series Name",
|
"LabelSeriesName": "Series Name",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
||||||
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Downloading episode",
|
"MessageDownloadingEpisode": "Downloading episode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||||
"MessageEmbedFinished": "Embed Finished!",
|
"MessageEmbedFinished": "Embed Finished!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
||||||
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Series update failed",
|
"ToastSeriesUpdateFailed": "Series update failed",
|
||||||
"ToastSeriesUpdateSuccess": "Series update success",
|
"ToastSeriesUpdateSuccess": "Series update success",
|
||||||
"ToastSessionDeleteFailed": "Failed to delete session",
|
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Pokreni M4B kodiranje",
|
"ButtonStartM4BEncode": "Pokreni M4B kodiranje",
|
||||||
"ButtonStartMetadataEmbed": "Pokreni ugradnju metapodataka",
|
"ButtonStartMetadataEmbed": "Pokreni ugradnju metapodataka",
|
||||||
"ButtonSubmit": "Submit",
|
"ButtonSubmit": "Submit",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Upload",
|
"ButtonUpload": "Upload",
|
||||||
"ButtonUploadBackup": "Upload backup",
|
"ButtonUploadBackup": "Upload backup",
|
||||||
"ButtonUploadCover": "Upload Cover",
|
"ButtonUploadCover": "Upload Cover",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Current Downloads",
|
||||||
"HeaderDetails": "Detalji",
|
"HeaderDetails": "Detalji",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Epizode",
|
"HeaderEpisodes": "Epizode",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Datoteke",
|
"HeaderFiles": "Datoteke",
|
||||||
"HeaderFindChapters": "Pronađi poglavlja",
|
"HeaderFindChapters": "Pronađi poglavlja",
|
||||||
"HeaderIgnoredFiles": "Zanemarene datoteke",
|
"HeaderIgnoredFiles": "Zanemarene datoteke",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Trajanje",
|
"LabelDuration": "Trajanje",
|
||||||
"LabelDurationFound": "Pronađeno trajanje:",
|
"LabelDurationFound": "Pronađeno trajanje:",
|
||||||
"LabelEdit": "Uredi",
|
"LabelEdit": "Uredi",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Uključi",
|
"LabelEnable": "Uključi",
|
||||||
"LabelEnd": "Kraj",
|
"LabelEnd": "Kraj",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Žanrovi",
|
"LabelGenres": "Žanrovi",
|
||||||
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
|
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Sat",
|
"LabelHour": "Sat",
|
||||||
"LabelIcon": "Ikona",
|
"LabelIcon": "Ikona",
|
||||||
"LabelIncludeInTracklist": "Dodaj u Tracklist",
|
"LabelIncludeInTracklist": "Dodaj u Tracklist",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Podcast Type",
|
"LabelPodcastType": "Podcast Type",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Prefiksi za ignorirati (mala i velika slova nisu bitna)",
|
"LabelPrefixesToIgnore": "Prefiksi za ignorirati (mala i velika slova nisu bitna)",
|
||||||
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
||||||
"LabelProgress": "Napredak",
|
"LabelProgress": "Napredak",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Traži naslov",
|
"LabelSearchTitle": "Traži naslov",
|
||||||
"LabelSearchTitleOrASIN": "Traži naslov ili ASIN",
|
"LabelSearchTitleOrASIN": "Traži naslov ili ASIN",
|
||||||
"LabelSeason": "Sezona",
|
"LabelSeason": "Sezona",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Sekvenca",
|
"LabelSequence": "Sekvenca",
|
||||||
"LabelSeries": "Serije",
|
"LabelSeries": "Serije",
|
||||||
"LabelSeriesName": "Ime serije",
|
"LabelSeriesName": "Ime serije",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
||||||
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Preuzimam epizodu",
|
"MessageDownloadingEpisode": "Preuzimam epizodu",
|
||||||
"MessageDragFilesIntoTrackOrder": "Povuci datoteke u pravilan redoslijed tracka.",
|
"MessageDragFilesIntoTrackOrder": "Povuci datoteke u pravilan redoslijed tracka.",
|
||||||
"MessageEmbedFinished": "Embed završen!",
|
"MessageEmbedFinished": "Embed završen!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Stavka uklonjena iz kolekcije",
|
"ToastRemoveItemFromCollectionSuccess": "Stavka uklonjena iz kolekcije",
|
||||||
"ToastRSSFeedCloseFailed": "Neuspješno zatvaranje RSS Feeda",
|
"ToastRSSFeedCloseFailed": "Neuspješno zatvaranje RSS Feeda",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS Feed zatvoren",
|
"ToastRSSFeedCloseSuccess": "RSS Feed zatvoren",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Series update failed",
|
"ToastSeriesUpdateFailed": "Series update failed",
|
||||||
"ToastSeriesUpdateSuccess": "Series update success",
|
"ToastSeriesUpdateSuccess": "Series update success",
|
||||||
"ToastSessionDeleteFailed": "Neuspješno brisanje serije",
|
"ToastSessionDeleteFailed": "Neuspješno brisanje serije",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Inizia L'Encoda del M4B",
|
"ButtonStartM4BEncode": "Inizia L'Encoda del M4B",
|
||||||
"ButtonStartMetadataEmbed": "Inizia Incorporo Metadata",
|
"ButtonStartMetadataEmbed": "Inizia Incorporo Metadata",
|
||||||
"ButtonSubmit": "Invia",
|
"ButtonSubmit": "Invia",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Carica",
|
"ButtonUpload": "Carica",
|
||||||
"ButtonUploadBackup": "Carica Backup",
|
"ButtonUploadBackup": "Carica Backup",
|
||||||
"ButtonUploadCover": "Carica Cover",
|
"ButtonUploadCover": "Carica Cover",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Current Downloads",
|
||||||
"HeaderDetails": "Dettagli",
|
"HeaderDetails": "Dettagli",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodi",
|
"HeaderEpisodes": "Episodi",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "File",
|
"HeaderFiles": "File",
|
||||||
"HeaderFindChapters": "Trova Capitoli",
|
"HeaderFindChapters": "Trova Capitoli",
|
||||||
"HeaderIgnoredFiles": "File Ignorati",
|
"HeaderIgnoredFiles": "File Ignorati",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Durata",
|
"LabelDuration": "Durata",
|
||||||
"LabelDurationFound": "Durata Trovata:",
|
"LabelDurationFound": "Durata Trovata:",
|
||||||
"LabelEdit": "Modifica",
|
"LabelEdit": "Modifica",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Abilita",
|
"LabelEnable": "Abilita",
|
||||||
"LabelEnd": "Fine",
|
"LabelEnd": "Fine",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genere",
|
"LabelGenre": "Genere",
|
||||||
"LabelGenres": "Generi",
|
"LabelGenres": "Generi",
|
||||||
"LabelHardDeleteFile": "Elimina Definitivamente",
|
"LabelHardDeleteFile": "Elimina Definitivamente",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Ora",
|
"LabelHour": "Ora",
|
||||||
"LabelIcon": "Icona",
|
"LabelIcon": "Icona",
|
||||||
"LabelIncludeInTracklist": "Includi nella Tracklist",
|
"LabelIncludeInTracklist": "Includi nella Tracklist",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Timo di Podcast",
|
"LabelPodcastType": "Timo di Podcast",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Suffissi da ignorare (specificando maiuscole e minuscole)",
|
"LabelPrefixesToIgnore": "Suffissi da ignorare (specificando maiuscole e minuscole)",
|
||||||
"LabelPreventIndexing": "Impedisci che il tuo feed venga indicizzato da iTunes e dalle directory dei podcast di Google",
|
"LabelPreventIndexing": "Impedisci che il tuo feed venga indicizzato da iTunes e dalle directory dei podcast di Google",
|
||||||
"LabelProgress": "Cominciati",
|
"LabelProgress": "Cominciati",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Cerca Titolo",
|
"LabelSearchTitle": "Cerca Titolo",
|
||||||
"LabelSearchTitleOrASIN": "Cerca titolo o ASIN",
|
"LabelSearchTitleOrASIN": "Cerca titolo o ASIN",
|
||||||
"LabelSeason": "Stagione",
|
"LabelSeason": "Stagione",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Sequenza",
|
"LabelSequence": "Sequenza",
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
"LabelSeriesName": "Nome Serie",
|
"LabelSeriesName": "Nome Serie",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Sei sicuro di voler rinominare il tag \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
"MessageConfirmRenameTag": "Sei sicuro di voler rinominare il tag \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Nota: Questo tag esiste già e verrà unito nel vecchio.",
|
"MessageConfirmRenameTagMergeNote": "Nota: Questo tag esiste già e verrà unito nel vecchio.",
|
||||||
"MessageConfirmRenameTagWarning": "Avvertimento! Esiste già un tag simile con un nome simile \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Avvertimento! Esiste già un tag simile con un nome simile \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Download episodio in corso",
|
"MessageDownloadingEpisode": "Download episodio in corso",
|
||||||
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
||||||
"MessageEmbedFinished": "Incorporamento finito!",
|
"MessageEmbedFinished": "Incorporamento finito!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Oggetto rimosso dalla Raccolta",
|
"ToastRemoveItemFromCollectionSuccess": "Oggetto rimosso dalla Raccolta",
|
||||||
"ToastRSSFeedCloseFailed": "Errore chiusura RSS feed",
|
"ToastRSSFeedCloseFailed": "Errore chiusura RSS feed",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed chiuso",
|
"ToastRSSFeedCloseSuccess": "RSS feed chiuso",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Aggiornaento Serie Fallito",
|
"ToastSeriesUpdateFailed": "Aggiornaento Serie Fallito",
|
||||||
"ToastSeriesUpdateSuccess": "Serie Aggornate",
|
"ToastSeriesUpdateSuccess": "Serie Aggornate",
|
||||||
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
|
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Start M4B-encoding",
|
"ButtonStartM4BEncode": "Start M4B-encoding",
|
||||||
"ButtonStartMetadataEmbed": "Start insluiten metadata",
|
"ButtonStartMetadataEmbed": "Start insluiten metadata",
|
||||||
"ButtonSubmit": "Indienen",
|
"ButtonSubmit": "Indienen",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Upload",
|
"ButtonUpload": "Upload",
|
||||||
"ButtonUploadBackup": "Upload back-up",
|
"ButtonUploadBackup": "Upload back-up",
|
||||||
"ButtonUploadCover": "Upload cover",
|
"ButtonUploadCover": "Upload cover",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Huidige downloads",
|
"HeaderCurrentDownloads": "Huidige downloads",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download-wachtrij",
|
"HeaderDownloadQueue": "Download-wachtrij",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Afleveringen",
|
"HeaderEpisodes": "Afleveringen",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Bestanden",
|
"HeaderFiles": "Bestanden",
|
||||||
"HeaderFindChapters": "Zoek hoofdstukken",
|
"HeaderFindChapters": "Zoek hoofdstukken",
|
||||||
"HeaderIgnoredFiles": "Genegeerde bestanden",
|
"HeaderIgnoredFiles": "Genegeerde bestanden",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Duur",
|
"LabelDuration": "Duur",
|
||||||
"LabelDurationFound": "Gevonden duur:",
|
"LabelDurationFound": "Gevonden duur:",
|
||||||
"LabelEdit": "Wijzig",
|
"LabelEdit": "Wijzig",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Ingesloten cover",
|
"LabelEmbeddedCover": "Ingesloten cover",
|
||||||
"LabelEnable": "Inschakelen",
|
"LabelEnable": "Inschakelen",
|
||||||
"LabelEnd": "Einde",
|
"LabelEnd": "Einde",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Hard-delete bestand",
|
"LabelHardDeleteFile": "Hard-delete bestand",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Uur",
|
"LabelHour": "Uur",
|
||||||
"LabelIcon": "Icoon",
|
"LabelIcon": "Icoon",
|
||||||
"LabelIncludeInTracklist": "Includeer in tracklijst",
|
"LabelIncludeInTracklist": "Includeer in tracklijst",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Podcasttype",
|
"LabelPodcastType": "Podcasttype",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Te negeren voorzetsels (ongeacht hoofdlettergebruik)",
|
"LabelPrefixesToIgnore": "Te negeren voorzetsels (ongeacht hoofdlettergebruik)",
|
||||||
"LabelPreventIndexing": "Voorkom indexering van je feed door iTunes- en Google podcastmappen",
|
"LabelPreventIndexing": "Voorkom indexering van je feed door iTunes- en Google podcastmappen",
|
||||||
"LabelProgress": "Voortgang",
|
"LabelProgress": "Voortgang",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Zoek titel",
|
"LabelSearchTitle": "Zoek titel",
|
||||||
"LabelSearchTitleOrASIN": "Zoek titel of ASIN",
|
"LabelSearchTitleOrASIN": "Zoek titel of ASIN",
|
||||||
"LabelSeason": "Seizoen",
|
"LabelSeason": "Seizoen",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Sequentie",
|
"LabelSequence": "Sequentie",
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
"LabelSeriesName": "Naam serie",
|
"LabelSeriesName": "Naam serie",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Weet je zeker dat je tag \"{0}\" wil hernoemen naar\"{1}\" voor alle onderdelen?",
|
"MessageConfirmRenameTag": "Weet je zeker dat je tag \"{0}\" wil hernoemen naar\"{1}\" voor alle onderdelen?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Opmerking: Deze tag bestaat al, dus zullen ze worden samengevoegd.",
|
"MessageConfirmRenameTagMergeNote": "Opmerking: Deze tag bestaat al, dus zullen ze worden samengevoegd.",
|
||||||
"MessageConfirmRenameTagWarning": "Waarschuwing! Een gelijknamige tag met ander hoofdlettergebruik bestaat al: \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Waarschuwing! Een gelijknamige tag met ander hoofdlettergebruik bestaat al: \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Aflevering aan het dowloaden",
|
"MessageDownloadingEpisode": "Aflevering aan het dowloaden",
|
||||||
"MessageDragFilesIntoTrackOrder": "Sleep bestanden in de juiste trackvolgorde",
|
"MessageDragFilesIntoTrackOrder": "Sleep bestanden in de juiste trackvolgorde",
|
||||||
"MessageEmbedFinished": "Insluiting voltooid!",
|
"MessageEmbedFinished": "Insluiting voltooid!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Onderdeel verwijderd uit collectie",
|
"ToastRemoveItemFromCollectionSuccess": "Onderdeel verwijderd uit collectie",
|
||||||
"ToastRSSFeedCloseFailed": "Sluiten RSS-feed mislukt",
|
"ToastRSSFeedCloseFailed": "Sluiten RSS-feed mislukt",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-feed gesloten",
|
"ToastRSSFeedCloseSuccess": "RSS-feed gesloten",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Bijwerken serie mislukt",
|
"ToastSeriesUpdateFailed": "Bijwerken serie mislukt",
|
||||||
"ToastSeriesUpdateSuccess": "Bijwerken serie gelukt",
|
"ToastSeriesUpdateSuccess": "Bijwerken serie gelukt",
|
||||||
"ToastSessionDeleteFailed": "Verwijderen sessie mislukt",
|
"ToastSessionDeleteFailed": "Verwijderen sessie mislukt",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Eksportuj jako plik M4B",
|
"ButtonStartM4BEncode": "Eksportuj jako plik M4B",
|
||||||
"ButtonStartMetadataEmbed": "Osadź metadane",
|
"ButtonStartMetadataEmbed": "Osadź metadane",
|
||||||
"ButtonSubmit": "Zaloguj",
|
"ButtonSubmit": "Zaloguj",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Wgraj",
|
"ButtonUpload": "Wgraj",
|
||||||
"ButtonUploadBackup": "Wgraj kopię zapasową",
|
"ButtonUploadBackup": "Wgraj kopię zapasową",
|
||||||
"ButtonUploadCover": "Wgraj okładkę",
|
"ButtonUploadCover": "Wgraj okładkę",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Current Downloads",
|
||||||
"HeaderDetails": "Szczegóły",
|
"HeaderDetails": "Szczegóły",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Rozdziały",
|
"HeaderEpisodes": "Rozdziały",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Pliki",
|
"HeaderFiles": "Pliki",
|
||||||
"HeaderFindChapters": "Wyszukaj rozdziały",
|
"HeaderFindChapters": "Wyszukaj rozdziały",
|
||||||
"HeaderIgnoredFiles": "Zignoruj pliki",
|
"HeaderIgnoredFiles": "Zignoruj pliki",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Czas trwania",
|
"LabelDuration": "Czas trwania",
|
||||||
"LabelDurationFound": "Znaleziona długość:",
|
"LabelDurationFound": "Znaleziona długość:",
|
||||||
"LabelEdit": "Edytuj",
|
"LabelEdit": "Edytuj",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Włącz",
|
"LabelEnable": "Włącz",
|
||||||
"LabelEnd": "Zakończ",
|
"LabelEnd": "Zakończ",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Gatunek",
|
"LabelGenre": "Gatunek",
|
||||||
"LabelGenres": "Gatunki",
|
"LabelGenres": "Gatunki",
|
||||||
"LabelHardDeleteFile": "Usuń trwale plik",
|
"LabelHardDeleteFile": "Usuń trwale plik",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Godzina",
|
"LabelHour": "Godzina",
|
||||||
"LabelIcon": "Ikona",
|
"LabelIcon": "Ikona",
|
||||||
"LabelIncludeInTracklist": "Dołącz do listy odtwarzania",
|
"LabelIncludeInTracklist": "Dołącz do listy odtwarzania",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasty",
|
"LabelPodcasts": "Podcasty",
|
||||||
"LabelPodcastType": "Podcast Type",
|
"LabelPodcastType": "Podcast Type",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Ignorowane prefiksy (wielkość liter nie ma znaczenia)",
|
"LabelPrefixesToIgnore": "Ignorowane prefiksy (wielkość liter nie ma znaczenia)",
|
||||||
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
||||||
"LabelProgress": "Postęp",
|
"LabelProgress": "Postęp",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Wyszukaj tytuł",
|
"LabelSearchTitle": "Wyszukaj tytuł",
|
||||||
"LabelSearchTitleOrASIN": "Szukaj tytuł lub ASIN",
|
"LabelSearchTitleOrASIN": "Szukaj tytuł lub ASIN",
|
||||||
"LabelSeason": "Sezon",
|
"LabelSeason": "Sezon",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Kolejność",
|
"LabelSequence": "Kolejność",
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
"LabelSeriesName": "Nazwy serii",
|
"LabelSeriesName": "Nazwy serii",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
|
||||||
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Pobieranie odcinka",
|
"MessageDownloadingEpisode": "Pobieranie odcinka",
|
||||||
"MessageDragFilesIntoTrackOrder": "przeciągnij pliki aby ustawić właściwą kolejność utworów",
|
"MessageDragFilesIntoTrackOrder": "przeciągnij pliki aby ustawić właściwą kolejność utworów",
|
||||||
"MessageEmbedFinished": "Osadzanie zakończone!",
|
"MessageEmbedFinished": "Osadzanie zakończone!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Pozycja usunięta z kolekcji",
|
"ToastRemoveItemFromCollectionSuccess": "Pozycja usunięta z kolekcji",
|
||||||
"ToastRSSFeedCloseFailed": "Zamknięcie kanału RSS nie powiodło się",
|
"ToastRSSFeedCloseFailed": "Zamknięcie kanału RSS nie powiodło się",
|
||||||
"ToastRSSFeedCloseSuccess": "Zamknięcie kanału RSS powiodło się",
|
"ToastRSSFeedCloseSuccess": "Zamknięcie kanału RSS powiodło się",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Series update failed",
|
"ToastSeriesUpdateFailed": "Series update failed",
|
||||||
"ToastSeriesUpdateSuccess": "Series update success",
|
"ToastSeriesUpdateSuccess": "Series update success",
|
||||||
"ToastSessionDeleteFailed": "Nie udało się usunąć sesji",
|
"ToastSessionDeleteFailed": "Nie udało się usunąć sesji",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "Начать кодирование M4B",
|
"ButtonStartM4BEncode": "Начать кодирование M4B",
|
||||||
"ButtonStartMetadataEmbed": "Начать встраивание метаданных",
|
"ButtonStartMetadataEmbed": "Начать встраивание метаданных",
|
||||||
"ButtonSubmit": "Применить",
|
"ButtonSubmit": "Применить",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Загрузить",
|
"ButtonUpload": "Загрузить",
|
||||||
"ButtonUploadBackup": "Загрузить бэкап",
|
"ButtonUploadBackup": "Загрузить бэкап",
|
||||||
"ButtonUploadCover": "Загрузить обложку",
|
"ButtonUploadCover": "Загрузить обложку",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "Текущие закачки",
|
"HeaderCurrentDownloads": "Текущие закачки",
|
||||||
"HeaderDetails": "Подробности",
|
"HeaderDetails": "Подробности",
|
||||||
"HeaderDownloadQueue": "Очередь скачивания",
|
"HeaderDownloadQueue": "Очередь скачивания",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Эпизоды",
|
"HeaderEpisodes": "Эпизоды",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "Файлы",
|
"HeaderFiles": "Файлы",
|
||||||
"HeaderFindChapters": "Найти главы",
|
"HeaderFindChapters": "Найти главы",
|
||||||
"HeaderIgnoredFiles": "Игнорируемые Файлы",
|
"HeaderIgnoredFiles": "Игнорируемые Файлы",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "Длина",
|
"LabelDuration": "Длина",
|
||||||
"LabelDurationFound": "Найденная длина:",
|
"LabelDurationFound": "Найденная длина:",
|
||||||
"LabelEdit": "Редактировать",
|
"LabelEdit": "Редактировать",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Включить",
|
"LabelEnable": "Включить",
|
||||||
"LabelEnd": "Конец",
|
"LabelEnd": "Конец",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "Жанр",
|
"LabelGenre": "Жанр",
|
||||||
"LabelGenres": "Жанры",
|
"LabelGenres": "Жанры",
|
||||||
"LabelHardDeleteFile": "Жесткое удаление файла",
|
"LabelHardDeleteFile": "Жесткое удаление файла",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Часы",
|
"LabelHour": "Часы",
|
||||||
"LabelIcon": "Иконка",
|
"LabelIcon": "Иконка",
|
||||||
"LabelIncludeInTracklist": "Включать в список воспроизведения",
|
"LabelIncludeInTracklist": "Включать в список воспроизведения",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "Подкаст",
|
"LabelPodcast": "Подкаст",
|
||||||
"LabelPodcasts": "Подкасты",
|
"LabelPodcasts": "Подкасты",
|
||||||
"LabelPodcastType": "Тип подкаста",
|
"LabelPodcastType": "Тип подкаста",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Игнорируемые префиксы (без учета регистра)",
|
"LabelPrefixesToIgnore": "Игнорируемые префиксы (без учета регистра)",
|
||||||
"LabelPreventIndexing": "Запретить индексацию фида каталогами подкастов iTunes и Google",
|
"LabelPreventIndexing": "Запретить индексацию фида каталогами подкастов iTunes и Google",
|
||||||
"LabelProgress": "Прогресс",
|
"LabelProgress": "Прогресс",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "Поиск по названию",
|
"LabelSearchTitle": "Поиск по названию",
|
||||||
"LabelSearchTitleOrASIN": "Поиск по названию или ASIN",
|
"LabelSearchTitleOrASIN": "Поиск по названию или ASIN",
|
||||||
"LabelSeason": "Сезон",
|
"LabelSeason": "Сезон",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "Последовательность",
|
"LabelSequence": "Последовательность",
|
||||||
"LabelSeries": "Серия",
|
"LabelSeries": "Серия",
|
||||||
"LabelSeriesName": "Имя серии",
|
"LabelSeriesName": "Имя серии",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
||||||
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Эпизод скачивается",
|
"MessageDownloadingEpisode": "Эпизод скачивается",
|
||||||
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
||||||
"MessageEmbedFinished": "Встраивание завершено!",
|
"MessageEmbedFinished": "Встраивание завершено!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
||||||
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
||||||
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
||||||
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "开始 M4B 编码",
|
"ButtonStartM4BEncode": "开始 M4B 编码",
|
||||||
"ButtonStartMetadataEmbed": "开始嵌入元数据",
|
"ButtonStartMetadataEmbed": "开始嵌入元数据",
|
||||||
"ButtonSubmit": "提交",
|
"ButtonSubmit": "提交",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "上传",
|
"ButtonUpload": "上传",
|
||||||
"ButtonUploadBackup": "上传备份",
|
"ButtonUploadBackup": "上传备份",
|
||||||
"ButtonUploadCover": "上传封面",
|
"ButtonUploadCover": "上传封面",
|
||||||
@ -97,7 +98,10 @@
|
|||||||
"HeaderCurrentDownloads": "当前下载",
|
"HeaderCurrentDownloads": "当前下载",
|
||||||
"HeaderDetails": "详情",
|
"HeaderDetails": "详情",
|
||||||
"HeaderDownloadQueue": "下载队列",
|
"HeaderDownloadQueue": "下载队列",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "剧集",
|
"HeaderEpisodes": "剧集",
|
||||||
|
"HeaderEReaderDevices": "E-Reader Devices",
|
||||||
"HeaderFiles": "文件",
|
"HeaderFiles": "文件",
|
||||||
"HeaderFindChapters": "查找章节",
|
"HeaderFindChapters": "查找章节",
|
||||||
"HeaderIgnoredFiles": "忽略的文件",
|
"HeaderIgnoredFiles": "忽略的文件",
|
||||||
@ -219,6 +223,10 @@
|
|||||||
"LabelDuration": "持续时间",
|
"LabelDuration": "持续时间",
|
||||||
"LabelDurationFound": "找到持续时间:",
|
"LabelDurationFound": "找到持续时间:",
|
||||||
"LabelEdit": "编辑",
|
"LabelEdit": "编辑",
|
||||||
|
"LabelEmail": "Email",
|
||||||
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "嵌入封面",
|
"LabelEmbeddedCover": "嵌入封面",
|
||||||
"LabelEnable": "启用",
|
"LabelEnable": "启用",
|
||||||
"LabelEnd": "结束",
|
"LabelEnd": "结束",
|
||||||
@ -241,6 +249,7 @@
|
|||||||
"LabelGenre": "流派",
|
"LabelGenre": "流派",
|
||||||
"LabelGenres": "流派",
|
"LabelGenres": "流派",
|
||||||
"LabelHardDeleteFile": "完全删除文件",
|
"LabelHardDeleteFile": "完全删除文件",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "小时",
|
"LabelHour": "小时",
|
||||||
"LabelIcon": "图标",
|
"LabelIcon": "图标",
|
||||||
"LabelIncludeInTracklist": "包含在音轨列表中",
|
"LabelIncludeInTracklist": "包含在音轨列表中",
|
||||||
@ -326,6 +335,7 @@
|
|||||||
"LabelPodcast": "播客",
|
"LabelPodcast": "播客",
|
||||||
"LabelPodcasts": "播客",
|
"LabelPodcasts": "播客",
|
||||||
"LabelPodcastType": "播客类型",
|
"LabelPodcastType": "播客类型",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "忽略的前缀 (不区分大小写)",
|
"LabelPrefixesToIgnore": "忽略的前缀 (不区分大小写)",
|
||||||
"LabelPreventIndexing": "防止 iTunes 和 Google 播客目录对你的源进行索引",
|
"LabelPreventIndexing": "防止 iTunes 和 Google 播客目录对你的源进行索引",
|
||||||
"LabelProgress": "进度",
|
"LabelProgress": "进度",
|
||||||
@ -350,6 +360,7 @@
|
|||||||
"LabelSearchTitle": "搜索标题",
|
"LabelSearchTitle": "搜索标题",
|
||||||
"LabelSearchTitleOrASIN": "搜索标题或 ASIN",
|
"LabelSearchTitleOrASIN": "搜索标题或 ASIN",
|
||||||
"LabelSeason": "季",
|
"LabelSeason": "季",
|
||||||
|
"LabelSendEbookToDevice": "Send E-Book to...",
|
||||||
"LabelSequence": "序列",
|
"LabelSequence": "序列",
|
||||||
"LabelSeries": "系列",
|
"LabelSeries": "系列",
|
||||||
"LabelSeriesName": "系列名称",
|
"LabelSeriesName": "系列名称",
|
||||||
@ -494,6 +505,7 @@
|
|||||||
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
||||||
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
||||||
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "正在下载剧集",
|
"MessageDownloadingEpisode": "正在下载剧集",
|
||||||
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
||||||
"MessageEmbedFinished": "嵌入完成!",
|
"MessageEmbedFinished": "嵌入完成!",
|
||||||
@ -648,6 +660,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
||||||
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Failed to send e-book to device",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-book sent to device \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "更新系列失败",
|
"ToastSeriesUpdateFailed": "更新系列失败",
|
||||||
"ToastSeriesUpdateSuccess": "系列已更新",
|
"ToastSeriesUpdateSuccess": "系列已更新",
|
||||||
"ToastSessionDeleteFailed": "删除会话失败",
|
"ToastSessionDeleteFailed": "删除会话失败",
|
||||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"node-tone": "^1.0.1",
|
"node-tone": "^1.0.1",
|
||||||
|
"nodemailer": "^6.9.2",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
},
|
},
|
||||||
@ -835,6 +836,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-tone/-/node-tone-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-tone/-/node-tone-1.0.1.tgz",
|
||||||
"integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w=="
|
"integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemailer": {
|
||||||
|
"version": "6.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.2.tgz",
|
||||||
|
"integrity": "sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "2.0.20",
|
"version": "2.0.20",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
|
||||||
@ -1946,6 +1955,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-tone/-/node-tone-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-tone/-/node-tone-1.0.1.tgz",
|
||||||
"integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w=="
|
"integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w=="
|
||||||
},
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"version": "6.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.2.tgz",
|
||||||
|
"integrity": "sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg=="
|
||||||
|
},
|
||||||
"nodemon": {
|
"nodemon": {
|
||||||
"version": "2.0.20",
|
"version": "2.0.20",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
|
||||||
@ -2314,4 +2328,4 @@
|
|||||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,11 @@
|
|||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"node-tone": "^1.0.1",
|
"node-tone": "^1.0.1",
|
||||||
|
"nodemailer": "^6.9.2",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^2.0.20"
|
"nodemon": "^2.0.20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ class Auth {
|
|||||||
user: user.toJSONForBrowser(),
|
user: user.toJSONForBrowser(),
|
||||||
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
|
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
|
||||||
serverSettings: this.db.serverSettings.toJSONForBrowser(),
|
serverSettings: this.db.serverSettings.toJSONForBrowser(),
|
||||||
|
ereaderDevices: this.db.emailSettings.getEReaderDevices(user),
|
||||||
Source: global.Source
|
Source: global.Source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
server/Db.js
11
server/Db.js
@ -12,6 +12,7 @@ const Author = require('./objects/entities/Author')
|
|||||||
const Series = require('./objects/entities/Series')
|
const Series = require('./objects/entities/Series')
|
||||||
const ServerSettings = require('./objects/settings/ServerSettings')
|
const ServerSettings = require('./objects/settings/ServerSettings')
|
||||||
const NotificationSettings = require('./objects/settings/NotificationSettings')
|
const NotificationSettings = require('./objects/settings/NotificationSettings')
|
||||||
|
const EmailSettings = require('./objects/settings/EmailSettings')
|
||||||
const PlaybackSession = require('./objects/PlaybackSession')
|
const PlaybackSession = require('./objects/PlaybackSession')
|
||||||
|
|
||||||
class Db {
|
class Db {
|
||||||
@ -49,6 +50,7 @@ class Db {
|
|||||||
|
|
||||||
this.serverSettings = null
|
this.serverSettings = null
|
||||||
this.notificationSettings = null
|
this.notificationSettings = null
|
||||||
|
this.emailSettings = null
|
||||||
|
|
||||||
// Stores previous version only if upgraded
|
// Stores previous version only if upgraded
|
||||||
this.previousVersion = null
|
this.previousVersion = null
|
||||||
@ -156,6 +158,10 @@ class Db {
|
|||||||
this.notificationSettings = new NotificationSettings()
|
this.notificationSettings = new NotificationSettings()
|
||||||
await this.insertEntity('settings', this.notificationSettings)
|
await this.insertEntity('settings', this.notificationSettings)
|
||||||
}
|
}
|
||||||
|
if (!this.emailSettings) {
|
||||||
|
this.emailSettings = new EmailSettings()
|
||||||
|
await this.insertEntity('settings', this.emailSettings)
|
||||||
|
}
|
||||||
global.ServerSettings = this.serverSettings.toJSON()
|
global.ServerSettings = this.serverSettings.toJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +208,11 @@ class Db {
|
|||||||
if (notificationSettings) {
|
if (notificationSettings) {
|
||||||
this.notificationSettings = new NotificationSettings(notificationSettings)
|
this.notificationSettings = new NotificationSettings(notificationSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emailSettings = this.settings.find(s => s.id === 'email-settings')
|
||||||
|
if (emailSettings) {
|
||||||
|
this.emailSettings = new EmailSettings(emailSettings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const p5 = this.collectionsDb.select(() => true).then((results) => {
|
const p5 = this.collectionsDb.select(() => true).then((results) => {
|
||||||
|
@ -25,6 +25,7 @@ const HlsRouter = require('./routers/HlsRouter')
|
|||||||
const StaticRouter = require('./routers/StaticRouter')
|
const StaticRouter = require('./routers/StaticRouter')
|
||||||
|
|
||||||
const NotificationManager = require('./managers/NotificationManager')
|
const NotificationManager = require('./managers/NotificationManager')
|
||||||
|
const EmailManager = require('./managers/EmailManager')
|
||||||
const CoverManager = require('./managers/CoverManager')
|
const CoverManager = require('./managers/CoverManager')
|
||||||
const AbMergeManager = require('./managers/AbMergeManager')
|
const AbMergeManager = require('./managers/AbMergeManager')
|
||||||
const CacheManager = require('./managers/CacheManager')
|
const CacheManager = require('./managers/CacheManager')
|
||||||
@ -66,6 +67,7 @@ class Server {
|
|||||||
// Managers
|
// Managers
|
||||||
this.taskManager = new TaskManager()
|
this.taskManager = new TaskManager()
|
||||||
this.notificationManager = new NotificationManager(this.db)
|
this.notificationManager = new NotificationManager(this.db)
|
||||||
|
this.emailManager = new EmailManager(this.db)
|
||||||
this.backupManager = new BackupManager(this.db)
|
this.backupManager = new BackupManager(this.db)
|
||||||
this.logManager = new LogManager(this.db)
|
this.logManager = new LogManager(this.db)
|
||||||
this.cacheManager = new CacheManager()
|
this.cacheManager = new CacheManager()
|
||||||
|
86
server/controllers/EmailController.js
Normal file
86
server/controllers/EmailController.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
const Logger = require('../Logger')
|
||||||
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
|
|
||||||
|
class EmailController {
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
getSettings(req, res) {
|
||||||
|
res.json({
|
||||||
|
settings: this.db.emailSettings
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSettings(req, res) {
|
||||||
|
const updated = this.db.emailSettings.update(req.body)
|
||||||
|
if (updated) {
|
||||||
|
await this.db.updateEntity('settings', this.db.emailSettings)
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
settings: this.db.emailSettings
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendTest(req, res) {
|
||||||
|
this.emailManager.sendTest(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateEReaderDevices(req, res) {
|
||||||
|
if (!req.body.ereaderDevices || !Array.isArray(req.body.ereaderDevices)) {
|
||||||
|
return res.status(400).send('Invalid payload. ereaderDevices array required')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ereaderDevices = req.body.ereaderDevices
|
||||||
|
for (const device of ereaderDevices) {
|
||||||
|
if (!device.name || !device.email) {
|
||||||
|
return res.status(400).send('Invalid payload. ereaderDevices array items must have name and email')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = this.db.emailSettings.update({
|
||||||
|
ereaderDevices
|
||||||
|
})
|
||||||
|
if (updated) {
|
||||||
|
await this.db.updateEntity('settings', this.db.emailSettings)
|
||||||
|
SocketAuthority.adminEmitter('ereader-devices-updated', {
|
||||||
|
ereaderDevices: this.db.emailSettings.ereaderDevices
|
||||||
|
})
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
ereaderDevices: this.db.emailSettings.ereaderDevices
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendEBookToDevice(req, res) {
|
||||||
|
Logger.debug(`[EmailController] Send ebook to device request for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`)
|
||||||
|
|
||||||
|
const libraryItem = this.db.getLibraryItem(req.body.libraryItemId)
|
||||||
|
if (!libraryItem) {
|
||||||
|
return res.status(404).send('Library item not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ebookFile = libraryItem.media.ebookFile
|
||||||
|
if (!ebookFile) {
|
||||||
|
return res.status(404).send('EBook file not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = this.db.emailSettings.getEReaderDevice(req.body.deviceName)
|
||||||
|
if (!device) {
|
||||||
|
return res.status(404).send('E-reader device not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emailManager.sendEBookToDevice(ebookFile, device, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
middleware(req, res, next) {
|
||||||
|
if (!req.user.isAdminOrUp) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = new EmailController()
|
73
server/managers/EmailManager.js
Normal file
73
server/managers/EmailManager.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const nodemailer = require('nodemailer')
|
||||||
|
const Logger = require("../Logger")
|
||||||
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
|
|
||||||
|
class EmailManager {
|
||||||
|
constructor(db) {
|
||||||
|
this.db = db
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransporter() {
|
||||||
|
return nodemailer.createTransport(this.db.emailSettings.getTransportObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendTest(res) {
|
||||||
|
Logger.info(`[EmailManager] Sending test email`)
|
||||||
|
const transporter = this.getTransporter()
|
||||||
|
|
||||||
|
const success = await transporter.verify().catch((error) => {
|
||||||
|
Logger.error(`[EmailManager] Failed to verify SMTP connection config`, error)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return res.status(400).send('Failed to verify SMTP connection configuration')
|
||||||
|
}
|
||||||
|
|
||||||
|
transporter.sendMail({
|
||||||
|
from: this.db.emailSettings.fromAddress,
|
||||||
|
to: this.db.emailSettings.fromAddress,
|
||||||
|
subject: 'Test email from Audiobookshelf',
|
||||||
|
text: 'Success!'
|
||||||
|
}).then((result) => {
|
||||||
|
Logger.info(`[EmailManager] Test email sent successfully`, result)
|
||||||
|
res.sendStatus(200)
|
||||||
|
}).catch((error) => {
|
||||||
|
Logger.error(`[EmailManager] Failed to send test email`, error)
|
||||||
|
res.status(400).send(error.message || 'Failed to send test email')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendEBookToDevice(ebookFile, device, res) {
|
||||||
|
Logger.info(`[EmailManager] Sending ebook "${ebookFile.metadata.filename}" to device "${device.name}"/"${device.email}"`)
|
||||||
|
const transporter = this.getTransporter()
|
||||||
|
|
||||||
|
const success = await transporter.verify().catch((error) => {
|
||||||
|
Logger.error(`[EmailManager] Failed to verify SMTP connection config`, error)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return res.status(400).send('Failed to verify SMTP connection configuration')
|
||||||
|
}
|
||||||
|
|
||||||
|
transporter.sendMail({
|
||||||
|
from: this.db.emailSettings.fromAddress,
|
||||||
|
to: device.email,
|
||||||
|
html: '<div dir="auto"></div>',
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: ebookFile.metadata.filename,
|
||||||
|
path: ebookFile.metadata.path,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).then((result) => {
|
||||||
|
Logger.info(`[EmailManager] Ebook sent to device successfully`, result)
|
||||||
|
res.sendStatus(200)
|
||||||
|
}).catch((error) => {
|
||||||
|
Logger.error(`[EmailManager] Failed to send ebook to device`, error)
|
||||||
|
res.status(400).send(error.message || 'Failed to send ebook to device')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = EmailManager
|
101
server/objects/settings/EmailSettings.js
Normal file
101
server/objects/settings/EmailSettings.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
const Logger = require('../../Logger')
|
||||||
|
const { areEquivalent, copyValue, isNullOrNaN } = require('../../utils')
|
||||||
|
|
||||||
|
// REF: https://nodemailer.com/smtp/
|
||||||
|
class EmailSettings {
|
||||||
|
constructor(settings = null) {
|
||||||
|
this.id = 'email-settings'
|
||||||
|
this.host = null
|
||||||
|
this.port = 465
|
||||||
|
this.secure = true
|
||||||
|
this.user = null
|
||||||
|
this.pass = null
|
||||||
|
this.fromAddress = null
|
||||||
|
|
||||||
|
// Array of { name:String, email:String }
|
||||||
|
this.ereaderDevices = []
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
this.construct(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
construct(settings) {
|
||||||
|
this.host = settings.host
|
||||||
|
this.port = settings.port
|
||||||
|
this.secure = !!settings.secure
|
||||||
|
this.user = settings.user
|
||||||
|
this.pass = settings.pass
|
||||||
|
this.fromAddress = settings.fromAddress
|
||||||
|
this.ereaderDevices = settings.ereaderDevices?.map(d => ({ ...d })) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
secure: this.secure,
|
||||||
|
user: this.user,
|
||||||
|
pass: this.pass,
|
||||||
|
fromAddress: this.fromAddress,
|
||||||
|
ereaderDevices: this.ereaderDevices.map(d => ({ ...d }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(payload) {
|
||||||
|
if (!payload) return false
|
||||||
|
|
||||||
|
if (payload.port !== undefined) {
|
||||||
|
if (isNullOrNaN(payload.port)) payload.port = 465
|
||||||
|
else payload.port = Number(payload.port)
|
||||||
|
}
|
||||||
|
if (payload.secure !== undefined) payload.secure = !!payload.secure
|
||||||
|
|
||||||
|
if (payload.ereaderDevices !== undefined && !Array.isArray(payload.ereaderDevices)) payload.ereaderDevices = undefined
|
||||||
|
|
||||||
|
let hasUpdates = false
|
||||||
|
|
||||||
|
const json = this.toJSON()
|
||||||
|
for (const key in json) {
|
||||||
|
if (key === 'id') continue
|
||||||
|
|
||||||
|
if (payload[key] !== undefined && !areEquivalent(payload[key], json[key])) {
|
||||||
|
this[key] = copyValue(payload[key])
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransportObject() {
|
||||||
|
const payload = {
|
||||||
|
host: this.host,
|
||||||
|
secure: this.secure
|
||||||
|
}
|
||||||
|
if (this.port) payload.port = this.port
|
||||||
|
if (this.user && this.pass !== undefined) {
|
||||||
|
payload.auth = {
|
||||||
|
user: this.user,
|
||||||
|
pass: this.pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
getEReaderDevices(user) {
|
||||||
|
// Only accessible to admin or up
|
||||||
|
if (!user.isAdminOrUp) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ereaderDevices.map(d => ({ ...d }))
|
||||||
|
}
|
||||||
|
|
||||||
|
getEReaderDevice(deviceName) {
|
||||||
|
return this.ereaderDevices.find(d => d.name === deviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = EmailSettings
|
@ -20,6 +20,7 @@ const AuthorController = require('../controllers/AuthorController')
|
|||||||
const SessionController = require('../controllers/SessionController')
|
const SessionController = require('../controllers/SessionController')
|
||||||
const PodcastController = require('../controllers/PodcastController')
|
const PodcastController = require('../controllers/PodcastController')
|
||||||
const NotificationController = require('../controllers/NotificationController')
|
const NotificationController = require('../controllers/NotificationController')
|
||||||
|
const EmailController = require('../controllers/EmailController')
|
||||||
const SearchController = require('../controllers/SearchController')
|
const SearchController = require('../controllers/SearchController')
|
||||||
const CacheController = require('../controllers/CacheController')
|
const CacheController = require('../controllers/CacheController')
|
||||||
const ToolsController = require('../controllers/ToolsController')
|
const ToolsController = require('../controllers/ToolsController')
|
||||||
@ -50,6 +51,7 @@ class ApiRouter {
|
|||||||
this.rssFeedManager = Server.rssFeedManager
|
this.rssFeedManager = Server.rssFeedManager
|
||||||
this.cronManager = Server.cronManager
|
this.cronManager = Server.cronManager
|
||||||
this.notificationManager = Server.notificationManager
|
this.notificationManager = Server.notificationManager
|
||||||
|
this.emailManager = Server.emailManager
|
||||||
this.taskManager = Server.taskManager
|
this.taskManager = Server.taskManager
|
||||||
|
|
||||||
this.bookFinder = new BookFinder()
|
this.bookFinder = new BookFinder()
|
||||||
@ -259,6 +261,15 @@ class ApiRouter {
|
|||||||
this.router.patch('/notifications/:id', NotificationController.middleware.bind(this), NotificationController.updateNotification.bind(this))
|
this.router.patch('/notifications/:id', NotificationController.middleware.bind(this), NotificationController.updateNotification.bind(this))
|
||||||
this.router.get('/notifications/:id/test', NotificationController.middleware.bind(this), NotificationController.sendNotificationTest.bind(this))
|
this.router.get('/notifications/:id/test', NotificationController.middleware.bind(this), NotificationController.sendNotificationTest.bind(this))
|
||||||
|
|
||||||
|
//
|
||||||
|
// Email Routes (Admin and up)
|
||||||
|
//
|
||||||
|
this.router.get('/emails/settings', EmailController.middleware.bind(this), EmailController.getSettings.bind(this))
|
||||||
|
this.router.patch('/emails/settings', EmailController.middleware.bind(this), EmailController.updateSettings.bind(this))
|
||||||
|
this.router.post('/emails/test', EmailController.middleware.bind(this), EmailController.sendTest.bind(this))
|
||||||
|
this.router.post('/emails/ereader-devices', EmailController.middleware.bind(this), EmailController.updateEReaderDevices.bind(this))
|
||||||
|
this.router.post('/emails/send-ebook-to-device', EmailController.middleware.bind(this), EmailController.sendEBookToDevice.bind(this))
|
||||||
|
|
||||||
//
|
//
|
||||||
// Search Routes
|
// Search Routes
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user