From 0d54b571517343427ef2a314a831330a196adf00 Mon Sep 17 00:00:00 2001 From: Nicholas Wallace Date: Mon, 11 Nov 2024 21:20:53 -0700 Subject: [PATCH 01/19] Add: PR template --- .github/pull_request_template.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..f41e46cce --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,26 @@ + + +## Brief summary + + + +## In-depth Description + + + +## How have you tested this? + + + +## Screenshots + + From d5fbc1d45592414a5684a89bc40940a42020a020 Mon Sep 17 00:00:00 2001 From: Nicholas Wallace Date: Sun, 17 Nov 2024 12:22:15 -0700 Subject: [PATCH 02/19] Add: statement about workflows passing --- .github/pull_request_template.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f41e46cce..0cd521a5b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,13 +1,20 @@ ## Brief summary - + + +## Which issue is fixed? + + ## In-depth Description From ee6e2d2983f1a84f5b7fae4922b72757e8a751d4 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 19 Nov 2024 16:48:05 -0600 Subject: [PATCH 03/19] Update:Persist podcast episode table sort and filter options in local storage #1321 --- client/components/tables/podcast/LazyEpisodesTable.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/components/tables/podcast/LazyEpisodesTable.vue b/client/components/tables/podcast/LazyEpisodesTable.vue index 963cd7c96..0dae11b36 100644 --- a/client/components/tables/podcast/LazyEpisodesTable.vue +++ b/client/components/tables/podcast/LazyEpisodesTable.vue @@ -25,7 +25,6 @@ -
@@ -515,6 +514,10 @@ export default { } }, filterSortChanged() { + // Save filterKey and sortKey to local storage + localStorage.setItem('podcastEpisodesFilter', this.filterKey) + localStorage.setItem('podcastEpisodesSortBy', this.sortKey + (this.sortDesc ? '-desc' : '')) + this.init() }, refresh() { @@ -537,6 +540,11 @@ export default { } }, mounted() { + this.filterKey = localStorage.getItem('podcastEpisodesFilter') || 'incomplete' + const sortBy = localStorage.getItem('podcastEpisodesSortBy') || 'publishedAt-desc' + this.sortKey = sortBy.split('-')[0] + this.sortDesc = sortBy.split('-')[1] === 'desc' + this.episodesCopy = this.episodes.map((ep) => ({ ...ep })) this.initListeners() this.init() From ff026a06bbfbd974032a58bfd32c67c53f0aebff Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 20 Nov 2024 16:48:09 -0600 Subject: [PATCH 04/19] Fix v2.17.0 migration to ensure mediaItemShares table exists --- server/migrations/v2.17.0-uuid-replacement.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/server/migrations/v2.17.0-uuid-replacement.js b/server/migrations/v2.17.0-uuid-replacement.js index 6460b7952..4316cd769 100644 --- a/server/migrations/v2.17.0-uuid-replacement.js +++ b/server/migrations/v2.17.0-uuid-replacement.js @@ -27,10 +27,14 @@ async function up({ context: { queryInterface, logger } }) { type: 'UUID' }) - logger.info('[2.17.0 migration] Changing mediaItemShares.mediaItemId column to UUID') - await queryInterface.changeColumn('mediaItemShares', 'mediaItemId', { - type: 'UUID' - }) + if (await queryInterface.tableExists('mediaItemShares')) { + logger.info('[2.17.0 migration] Changing mediaItemShares.mediaItemId column to UUID') + await queryInterface.changeColumn('mediaItemShares', 'mediaItemId', { + type: 'UUID' + }) + } else { + logger.info('[2.17.0 migration] mediaItemShares table does not exist, skipping column change') + } logger.info('[2.17.0 migration] Changing playbackSessions.mediaItemId column to UUID') await queryInterface.changeColumn('playbackSessions', 'mediaItemId', { From fc5f35b3887044e057367331dbc384d12522db70 Mon Sep 17 00:00:00 2001 From: Harrison Rose Date: Thu, 21 Nov 2024 02:06:53 +0000 Subject: [PATCH 05/19] on iOS, do not restrict file types for upload --- client/pages/upload/index.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/pages/upload/index.vue b/client/pages/upload/index.vue index 0efa1456d..8bc57de54 100644 --- a/client/pages/upload/index.vue +++ b/client/pages/upload/index.vue @@ -84,7 +84,7 @@
- + @@ -127,6 +127,10 @@ export default { }) return extensions }, + isIOS() { + const ua = window.navigator.userAgent + return /iPad|iPhone|iPod/.test(ua) && !window.MSStream + }, streamLibraryItem() { return this.$store.state.streamLibraryItem }, From 268fb2ce9a29ff5acce81d030537141fca2a7bc1 Mon Sep 17 00:00:00 2001 From: Harrison Rose Date: Thu, 21 Nov 2024 04:43:03 +0000 Subject: [PATCH 06/19] on iOS, hide UI on upload page related to folder selection (since iOS Webkit does not support folder selection) --- client/pages/upload/index.vue | 8 ++++---- client/strings/en-us.json | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/pages/upload/index.vue b/client/pages/upload/index.vue index 8bc57de54..7c1b47676 100644 --- a/client/pages/upload/index.vue +++ b/client/pages/upload/index.vue @@ -34,12 +34,12 @@
-

{{ isDragging ? $strings.LabelUploaderDropFiles : $strings.LabelUploaderDragAndDrop }}

+

{{ isDragging ? $strings.LabelUploaderDropFiles : $strings.LabelUploaderDragAndDrop + (isIOS ? '' : ' ' + $strings.LabelUploaderDragAndDropOrFolders) }}

{{ $strings.MessageOr }}

{{ $strings.ButtonChooseFiles }} - {{ $strings.ButtonChooseAFolder }} + {{ $strings.ButtonChooseAFolder }}
@@ -48,7 +48,7 @@

- {{ $strings.NoteUploaderFoldersWithMediaFiles }} {{ $strings.NoteUploaderOnlyAudioFiles }} + {{ $strings.NoteUploaderFoldersWithMediaFiles }} {{ $strings.NoteUploaderOnlyAudioFiles }}

@@ -85,7 +85,7 @@ - + diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 8eb375500..e6392c0f9 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -662,7 +662,8 @@ "LabelUpdateDetails": "Update Details", "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located", "LabelUpdatedAt": "Updated At", - "LabelUploaderDragAndDrop": "Drag & drop files or folders", + "LabelUploaderDragAndDrop": "Drag & drop files", + "LabelUploaderDragAndDropOrFolders": "or folders", "LabelUploaderDropFiles": "Drop files", "LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series", "LabelUseAdvancedOptions": "Use Advanced Options", From 784b761629af9212d34cdf36d01005c221b125f6 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 21 Nov 2024 14:19:40 -0600 Subject: [PATCH 07/19] Fix:Unable to edit series sequence #3636 --- server/models/LibraryItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index 5b96ad521..10395c49c 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -479,7 +479,7 @@ class LibraryItem extends Model { { model: this.sequelize.models.series, through: { - attributes: ['sequence'] + attributes: ['id', 'sequence'] } } ], From 1d4e6993fc09a954b150eeaed69156559cc892c8 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 21 Nov 2024 14:56:43 -0600 Subject: [PATCH 08/19] Upload page UI updates for mobile --- client/mixins/uploadHelpers.js | 32 ++++++++++++++++---------------- client/pages/upload/index.vue | 16 ++++++++-------- client/strings/en-us.json | 4 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/client/mixins/uploadHelpers.js b/client/mixins/uploadHelpers.js index 2d7a554f0..994d36c69 100644 --- a/client/mixins/uploadHelpers.js +++ b/client/mixins/uploadHelpers.js @@ -28,10 +28,8 @@ export default { var validOtherFiles = [] var ignoredFiles = [] files.forEach((file) => { - // var filetype = this.checkFileType(file.name) if (!file.filetype) ignoredFiles.push(file) else { - // file.filetype = filetype if (file.filetype === 'audio' || (file.filetype === 'ebook' && mediaType === 'book')) validItemFiles.push(file) else validOtherFiles.push(file) } @@ -165,7 +163,7 @@ export default { var firstBookPath = Path.dirname(firstBookFile.filepath) - var dirs = firstBookPath.split('/').filter(d => !!d && d !== '.') + var dirs = firstBookPath.split('/').filter((d) => !!d && d !== '.') if (dirs.length) { audiobook.title = dirs.pop() if (dirs.length > 1) { @@ -189,7 +187,7 @@ export default { var firstAudioFile = podcast.itemFiles[0] if (!firstAudioFile.filepath) return podcast // No path var firstPath = Path.dirname(firstAudioFile.filepath) - var dirs = firstPath.split('/').filter(d => !!d && d !== '.') + var dirs = firstPath.split('/').filter((d) => !!d && d !== '.') if (dirs.length) { podcast.title = dirs.length > 1 ? dirs[1] : dirs[0] } else { @@ -212,13 +210,15 @@ export default { } var ignoredFiles = itemData.ignoredFiles var index = 1 - var items = itemData.items.filter((ab) => { - if (!ab.itemFiles.length) { - if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles) - if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles) - } - return ab.itemFiles.length - }).map(ab => this.cleanItem(ab, mediaType, index++)) + var items = itemData.items + .filter((ab) => { + if (!ab.itemFiles.length) { + if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles) + if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles) + } + return ab.itemFiles.length + }) + .map((ab) => this.cleanItem(ab, mediaType, index++)) return { items, ignoredFiles @@ -259,7 +259,7 @@ export default { otherFiles.forEach((file) => { var dir = Path.dirname(file.filepath) - var findItem = Object.values(itemMap).find(b => dir.startsWith(b.path)) + var findItem = Object.values(itemMap).find((b) => dir.startsWith(b.path)) if (findItem) { findItem.otherFiles.push(file) } else { @@ -270,18 +270,18 @@ export default { var items = [] var index = 1 // If book media type and all files are audio files then treat each one as an audiobook - if (itemMap[''] && !otherFiles.length && mediaType === 'book' && !itemMap[''].itemFiles.some(f => f.filetype !== 'audio')) { + if (itemMap[''] && !otherFiles.length && mediaType === 'book' && !itemMap[''].itemFiles.some((f) => f.filetype !== 'audio')) { items = itemMap[''].itemFiles.map((audioFile) => { return this.cleanItem({ itemFiles: [audioFile], otherFiles: [], ignoredFiles: [] }, mediaType, index++) }) } else { - items = Object.values(itemMap).map(i => this.cleanItem(i, mediaType, index++)) + items = Object.values(itemMap).map((i) => this.cleanItem(i, mediaType, index++)) } return { items, ignoredFiles: ignoredFiles } - }, + } } -} \ No newline at end of file +} diff --git a/client/pages/upload/index.vue b/client/pages/upload/index.vue index 7c1b47676..441ce88ee 100644 --- a/client/pages/upload/index.vue +++ b/client/pages/upload/index.vue @@ -1,20 +1,20 @@ @@ -54,7 +71,7 @@ export default { return this.episode.description || '' }, media() { - return this.libraryItem ? this.libraryItem.media || {} : {} + return this.libraryItem?.media || {} }, mediaMetadata() { return this.media.metadata || {} @@ -65,6 +82,14 @@ export default { podcastAuthor() { return this.mediaMetadata.author }, + audioFileFilename() { + return this.episode.audioFile?.metadata?.filename || '' + }, + audioFileSize() { + const size = this.episode.audioFile?.metadata?.size || 0 + + return this.$bytesPretty(size) + }, bookCoverAspectRatio() { return this.$store.getters['libraries/getBookCoverAspectRatio'] }