Compare commits

...

10 Commits

Author SHA1 Message Date
advplyr 964ef910b6 Version bump v2.10.1 2024-05-27 16:09:32 -05:00
advplyr ba6a88a5bf Fix:Edit author modal resetting form inputs on image change #2965 2024-05-27 16:04:36 -05:00
advplyr 1576164218 Update:Get all user playlists for library API endpoint performance improvement #2852 2024-05-27 15:37:02 -05:00
advplyr 94400f7794 Merge pull request #3023 from Dalabad/patch-1
Update de.json
2024-05-27 13:39:46 -05:00
advplyr 41e1b02f3a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-05-27 13:22:29 -05:00
advplyr 1337c60cde Fix:Debian pkg crash due to using toSorted that is only available in Node20+ #3024 2024-05-27 13:22:20 -05:00
Daniel Schosser e9b4e07bd8 Update de.json
Revert Apprise string change
2024-05-27 19:14:10 +02:00
advplyr 607fdffc18 Merge pull request #3022 from JBlond/master
Update de strings.
2024-05-27 11:47:24 -05:00
Daniel Schosser 216139119b Update de.json 2024-05-27 15:02:00 +02:00
JBlond 19cbd1f8de Update de strings.
Follow-up for: ce7f891b9b and 6fad4521d4
2024-05-27 11:35:54 +02:00
10 changed files with 123 additions and 86 deletions
+14 -10
View File
@@ -9,7 +9,7 @@
<div class="flex"> <div class="flex">
<div class="w-40 p-2"> <div class="w-40 p-2">
<div class="w-full h-45 relative"> <div class="w-full h-45 relative">
<covers-author-image :author="author" /> <covers-author-image :author="authorCopy" />
<div v-if="userCanDelete && !processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100"> <div v-if="userCanDelete && !processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100">
<span class="absolute top-2 right-2 material-icons text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span> <span class="absolute top-2 right-2 material-icons text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span>
</div> </div>
@@ -30,9 +30,6 @@
<ui-text-input-with-label v-model="authorCopy.asin" :disabled="processing" label="ASIN" /> <ui-text-input-with-label v-model="authorCopy.asin" :disabled="processing" label="ASIN" />
</div> </div>
</div> </div>
<!-- <div class="p-2">
<ui-text-input-with-label v-model="authorCopy.imagePath" :disabled="processing" :label="$strings.LabelPhotoPathURL" />
</div> -->
<div class="p-2"> <div class="p-2">
<ui-textarea-with-label v-model="authorCopy.description" :disabled="processing" :label="$strings.LabelDescription" :rows="8" /> <ui-textarea-with-label v-model="authorCopy.description" :disabled="processing" :label="$strings.LabelDescription" :rows="8" />
</div> </div>
@@ -106,9 +103,9 @@ export default {
methods: { methods: {
init() { init() {
this.imageUrl = '' this.imageUrl = ''
this.authorCopy.name = this.author.name this.authorCopy = {
this.authorCopy.asin = this.author.asin ...this.author
this.authorCopy.description = this.author.description }
}, },
removeClick() { removeClick() {
const payload = { const payload = {
@@ -171,7 +168,9 @@ export default {
.$delete(`/api/authors/${this.authorId}/image`) .$delete(`/api/authors/${this.authorId}/image`)
.then((data) => { .then((data) => {
this.$toast.success(this.$strings.ToastAuthorImageRemoveSuccess) this.$toast.success(this.$strings.ToastAuthorImageRemoveSuccess)
this.$store.commit('globals/showEditAuthorModal', data.author)
this.authorCopy.updatedAt = data.author.updatedAt
this.authorCopy.imagePath = data.author.imagePath
}) })
.catch((error) => { .catch((error) => {
console.error('Failed', error) console.error('Failed', error)
@@ -196,7 +195,9 @@ export default {
.then((data) => { .then((data) => {
this.imageUrl = '' this.imageUrl = ''
this.$toast.success('Author image updated') this.$toast.success('Author image updated')
this.$store.commit('globals/showEditAuthorModal', data.author)
this.authorCopy.updatedAt = data.author.updatedAt
this.authorCopy.imagePath = data.author.imagePath
}) })
.catch((error) => { .catch((error) => {
console.error('Failed', error) console.error('Failed', error)
@@ -231,8 +232,11 @@ export default {
} else if (response.updated) { } else if (response.updated) {
if (response.author.imagePath) { if (response.author.imagePath) {
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess) this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
this.$store.commit('globals/showEditAuthorModal', response.author)
} else this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound) } else this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
this.authorCopy = {
...response.author
}
} else { } else {
this.$toast.info('No updates were made for Author') this.$toast.info('No updates were made for Author')
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "2.10.0", "version": "2.10.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "2.10.0", "version": "2.10.1",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nuxtjs/axios": "^5.13.6", "@nuxtjs/axios": "^5.13.6",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "2.10.0", "version": "2.10.1",
"buildNumber": 1, "buildNumber": 1,
"description": "Self-hosted audiobook and podcast client", "description": "Self-hosted audiobook and podcast client",
"main": "index.js", "main": "index.js",
@@ -79,9 +79,6 @@ export default {
} }
}, },
authorUpdated(author) { authorUpdated(author) {
if (this.selectedAuthor && this.selectedAuthor.id === author.id) {
this.$store.commit('globals/setSelectedAuthor', author)
}
this.authors = this.authors.map((au) => { this.authors = this.authors.map((au) => {
if (au.id === author.id) { if (au.id === author.id) {
return author return author
+6 -6
View File
@@ -191,7 +191,7 @@
"LabelAbridged": "Gekürzt", "LabelAbridged": "Gekürzt",
"LabelAbridgedChecked": "Gekürzt (angehakt)", "LabelAbridgedChecked": "Gekürzt (angehakt)",
"LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)", "LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)",
"LabelAccessibleBy": "Accessible by", "LabelAccessibleBy": "Zugänglich für",
"LabelAccountType": "Kontoart", "LabelAccountType": "Kontoart",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Gast", "LabelAccountTypeGuest": "Gast",
@@ -271,7 +271,7 @@
"LabelDownloadNEpisodes": "Download {0} Episoden", "LabelDownloadNEpisodes": "Download {0} Episoden",
"LabelDuration": "Laufzeit", "LabelDuration": "Laufzeit",
"LabelDurationComparisonExactMatch": "(genauer Treffer)", "LabelDurationComparisonExactMatch": "(genauer Treffer)",
"LabelDurationComparisonLonger": "({0} änger)", "LabelDurationComparisonLonger": "({0} länger)",
"LabelDurationComparisonShorter": "({0} kürzer)", "LabelDurationComparisonShorter": "({0} kürzer)",
"LabelDurationFound": "Gefundene Laufzeit:", "LabelDurationFound": "Gefundene Laufzeit:",
"LabelEbook": "E-Book", "LabelEbook": "E-Book",
@@ -471,8 +471,8 @@
"LabelSettingsEnableWatcher": "Überwachung aktivieren", "LabelSettingsEnableWatcher": "Überwachung aktivieren",
"LabelSettingsEnableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek aktivieren", "LabelSettingsEnableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek aktivieren",
"LabelSettingsEnableWatcherHelp": "Aktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart", "LabelSettingsEnableWatcherHelp": "Aktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart",
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs", "LabelSettingsEpubsAllowScriptedContent": "Skriptinhalte in Epubs zulassen",
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.", "LabelSettingsEpubsAllowScriptedContentHelp": "Erlaube Epub-Dateien, Skripte auszuführen. Es wird empfohlen, diese Einstellung deaktiviert zu lassen, es sei denn, du vertraust der Quelle der Epub-Dateien.",
"LabelSettingsExperimentalFeatures": "Experimentelle Funktionen", "LabelSettingsExperimentalFeatures": "Experimentelle Funktionen",
"LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen dein Feedback und deine Hilfe beim Testen. Klicke hier, um die Github-Diskussion zu öffnen.", "LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen dein Feedback und deine Hilfe beim Testen. Klicke hier, um die Github-Diskussion zu öffnen.",
"LabelSettingsFindCovers": "Suche Titelbilder", "LabelSettingsFindCovers": "Suche Titelbilder",
@@ -501,7 +501,7 @@
"LabelShowAll": "Alles anzeigen", "LabelShowAll": "Alles anzeigen",
"LabelShowSeconds": "Zeige Sekunden", "LabelShowSeconds": "Zeige Sekunden",
"LabelSize": "Größe", "LabelSize": "Größe",
"LabelSleepTimer": "Sleep-Timer", "LabelSleepTimer": "Schlummerfunktion",
"LabelSlug": "URL Teil", "LabelSlug": "URL Teil",
"LabelStart": "Start", "LabelStart": "Start",
"LabelStarted": "Gestartet", "LabelStarted": "Gestartet",
@@ -633,7 +633,7 @@
"MessageDragFilesIntoTrackOrder": "Verschiebe die Dateien in die richtige Reihenfolge", "MessageDragFilesIntoTrackOrder": "Verschiebe die Dateien in die richtige Reihenfolge",
"MessageEmbedFinished": "Einbettung abgeschlossen!", "MessageEmbedFinished": "Einbettung abgeschlossen!",
"MessageEpisodesQueuedForDownload": "{0} Episode(n) in der Warteschlange zum Herunterladen", "MessageEpisodesQueuedForDownload": "{0} Episode(n) in der Warteschlange zum Herunterladen",
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.", "MessageEreaderDevices": "Um die Zustellung von E-Books sicherzustellen, musst du eventuell die oben genannte E-Mail-Adresse als gültigen Absender für jedes unten aufgeführte Gerät hinzufügen.",
"MessageFeedURLWillBe": "Feed-URL wird {0} sein", "MessageFeedURLWillBe": "Feed-URL wird {0} sein",
"MessageFetching": "Abrufen...", "MessageFetching": "Abrufen...",
"MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.", "MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.",
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "2.10.0", "version": "2.10.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "2.10.0", "version": "2.10.1",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"axios": "^0.27.2", "axios": "^0.27.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "2.10.0", "version": "2.10.1",
"buildNumber": 1, "buildNumber": 1,
"description": "Self-hosted audiobook and podcast server", "description": "Self-hosted audiobook and podcast server",
"main": "index.js", "main": "index.js",
+1 -2
View File
@@ -512,8 +512,7 @@ class LibraryController {
* @param {*} res * @param {*} res
*/ */
async getUserPlaylistsForLibrary(req, res) { async getUserPlaylistsForLibrary(req, res) {
let playlistsForUser = await Database.playlistModel.getPlaylistsForUserAndLibrary(req.user.id, req.library.id) let playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.user.id, req.library.id)
playlistsForUser = await Promise.all(playlistsForUser.map(async (p) => p.getOldJsonExpanded()))
const payload = { const payload = {
results: [], results: [],
+86 -49
View File
@@ -43,21 +43,24 @@ class Playlist extends Model {
}, },
order: [['playlistMediaItems', 'order', 'ASC']] order: [['playlistMediaItems', 'order', 'ASC']]
}) })
return playlists.map(p => this.getOldPlaylist(p)) return playlists.map((p) => this.getOldPlaylist(p))
} }
static getOldPlaylist(playlistExpanded) { static getOldPlaylist(playlistExpanded) {
const items = playlistExpanded.playlistMediaItems.map(pmi => { const items = playlistExpanded.playlistMediaItems
const libraryItemId = pmi.mediaItem?.podcast?.libraryItem?.id || pmi.mediaItem?.libraryItem?.id || null .map((pmi) => {
if (!libraryItemId) { const mediaItem = pmi.mediaItem || pmi.dataValues?.mediaItem
Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2)) const libraryItemId = mediaItem?.podcast?.libraryItem?.id || mediaItem?.libraryItem?.id || null
return null if (!libraryItemId) {
} Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2))
return { return null
episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '', }
libraryItemId return {
} episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '',
}).filter(pmi => pmi) libraryItemId
}
})
.filter((pmi) => pmi)
return new oldPlaylist({ return new oldPlaylist({
id: playlistExpanded.id, id: playlistExpanded.id,
@@ -77,25 +80,26 @@ class Playlist extends Model {
* @returns {Promise<object>} oldPlaylist.toJSONExpanded * @returns {Promise<object>} oldPlaylist.toJSONExpanded
*/ */
async getOldJsonExpanded(include) { async getOldJsonExpanded(include) {
this.playlistMediaItems = await this.getPlaylistMediaItems({ this.playlistMediaItems =
include: [ (await this.getPlaylistMediaItems({
{ include: [
model: this.sequelize.models.book, {
include: this.sequelize.models.libraryItem model: this.sequelize.models.book,
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
} }
} ],
], order: [['order', 'ASC']]
order: [['order', 'ASC']] })) || []
}) || []
const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this) const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this)
const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId) const libraryItemIds = oldPlaylist.items.map((i) => i.libraryItemId)
let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({ let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({
id: libraryItemIds id: libraryItemIds
@@ -167,12 +171,13 @@ class Playlist extends Model {
} }
/** /**
* Get playlists for user and optionally for library * Get old playlists for user and optionally for library
*
* @param {string} userId * @param {string} userId
* @param {[string]} libraryId optional * @param {string} [libraryId]
* @returns {Promise<Playlist[]>} * @returns {Promise<oldPlaylist[]>}
*/ */
static async getPlaylistsForUserAndLibrary(userId, libraryId = null) { static async getOldPlaylistsForUserAndLibrary(userId, libraryId = null) {
if (!userId && !libraryId) return [] if (!userId && !libraryId) return []
const whereQuery = {} const whereQuery = {}
if (userId) { if (userId) {
@@ -181,7 +186,7 @@ class Playlist extends Model {
if (libraryId) { if (libraryId) {
whereQuery.libraryId = libraryId whereQuery.libraryId = libraryId
} }
const playlists = await this.findAll({ const playlistsExpanded = await this.findAll({
where: whereQuery, where: whereQuery,
include: { include: {
model: this.sequelize.models.playlistMediaItem, model: this.sequelize.models.playlistMediaItem,
@@ -204,7 +209,37 @@ class Playlist extends Model {
['playlistMediaItems', 'order', 'ASC'] ['playlistMediaItems', 'order', 'ASC']
] ]
}) })
return playlists
const oldPlaylists = []
for (const playlistExpanded of playlistsExpanded) {
const oldPlaylist = this.getOldPlaylist(playlistExpanded)
const libraryItems = []
for (const pmi of playlistExpanded.playlistMediaItems) {
let mediaItem = pmi.mediaItem || pmi.dataValues.mediaItem
if (!mediaItem) {
Logger.error(`[Playlist] Invalid playlist media item - No media item found`, JSON.stringify(mediaItem, null, 2))
continue
}
let libraryItem = mediaItem.libraryItem || mediaItem.podcast?.libraryItem
if (mediaItem.podcast) {
libraryItem.media = mediaItem.podcast
libraryItem.media.podcastEpisodes = [mediaItem]
delete mediaItem.podcast.libraryItem
} else {
libraryItem.media = mediaItem
delete mediaItem.libraryItem
}
const oldLibraryItem = this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
libraryItems.push(oldLibraryItem)
}
const oldPlaylistJson = oldPlaylist.toJSONExpanded(libraryItems)
oldPlaylists.push(oldPlaylistJson)
}
return oldPlaylists
} }
/** /**
@@ -263,9 +298,9 @@ class Playlist extends Model {
const playlists = [] const playlists = []
for (const playlistMediaItem of playlistMediaItemsExpanded) { for (const playlistMediaItem of playlistMediaItemsExpanded) {
const playlist = playlistMediaItem.playlist const playlist = playlistMediaItem.playlist
if (playlists.some(p => p.id === playlist.id)) continue if (playlists.some((p) => p.id === playlist.id)) continue
playlist.playlistMediaItems = playlist.playlistMediaItems.map(pmi => { playlist.playlistMediaItems = playlist.playlistMediaItems.map((pmi) => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) { if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book pmi.dataValues.mediaItem = pmi.dataValues.book
@@ -289,18 +324,21 @@ class Playlist extends Model {
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
description: DataTypes.TEXT
}, },
name: DataTypes.STRING, {
description: DataTypes.TEXT sequelize,
}, { modelName: 'playlist'
sequelize, }
modelName: 'playlist' )
})
const { library, user } = sequelize.models const { library, user } = sequelize.models
library.hasMany(Playlist) library.hasMany(Playlist)
@@ -311,14 +349,14 @@ class Playlist extends Model {
}) })
Playlist.belongsTo(user) Playlist.belongsTo(user)
Playlist.addHook('afterFind', findResult => { Playlist.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.playlistMediaItems?.length) { if (instance.playlistMediaItems?.length) {
instance.playlistMediaItems = instance.playlistMediaItems.map(pmi => { instance.playlistMediaItems = instance.playlistMediaItems.map((pmi) => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) { if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book pmi.dataValues.mediaItem = pmi.dataValues.book
@@ -334,7 +372,6 @@ class Playlist extends Model {
return pmi return pmi
}) })
} }
} }
}) })
} }
+1 -1
View File
@@ -153,7 +153,7 @@ function parseChapters(_chapters) {
title title
} }
}) })
.toSorted((a, b) => a.start - b.start) .sort((a, b) => a.start - b.start)
.map((chap, index) => { .map((chap, index) => {
chap.id = index chap.id = index
return chap return chap