diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 1d4228109..ca044b711 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -11,7 +11,7 @@ body: value: "### Mobile app issues report [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)." - type: markdown attributes: - value: "### Join the [discord server](https://discord.gg/pJsjuNCKRq) for questions or if you are not sure about a bug." + value: "### Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug." - type: markdown attributes: value: "## Be as descriptive as you can. Include screenshots, error logs, browser, file types, everything you can think of that might be relevant." diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 63cb88055..2c6cc1912 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: Discord - url: https://discord.gg/pJsjuNCKRq + url: https://discord.gg/HQgCbd6E75 about: Ask questions, get help troubleshooting, and join the Abs community here. - name: Matrix url: https://matrix.to/#/#audiobookshelf:matrix.org diff --git a/.gitignore b/.gitignore index 9360600a5..0690f38f5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /deploy/ /coverage/ /.nyc_output/ +/ffmpeg* +/ffprobe* sw.* .DS_STORE diff --git a/Dockerfile b/Dockerfile index 254720004..943fc5674 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,8 @@ RUN apk update && \ ffmpeg \ make \ python3 \ - g++ + g++ \ + tini COPY --from=tone /usr/local/bin/tone /usr/local/bin/ COPY --from=build /client/dist /client/dist @@ -31,4 +32,5 @@ RUN apk del make python3 g++ EXPOSE 80 +ENTRYPOINT ["tini", "--"] CMD ["node", "index.js"] diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue index e9b6969de..0cae1421c 100644 --- a/client/components/app/StreamContainer.vue +++ b/client/components/app/StreamContainer.vue @@ -349,7 +349,7 @@ export default { } if ('mediaSession' in navigator) { - var coverImageSrc = this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png') + var coverImageSrc = this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true) const artwork = [ { src: coverImageSrc diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index c4d1345da..04b3ce599 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -8,10 +8,10 @@
-
- {{ displayTitle }} + +

{{ displayTitle }}

-
+

{{ displayLineTwo || ' ' }}

{{ displaySortLine }}

@@ -164,6 +164,7 @@ export default { imageReady: false, selected: false, isSelectionMode: false, + displayTitleTruncated: false, showCoverBg: false } }, @@ -642,6 +643,12 @@ export default { } this.libraryItem = libraryItem + + this.$nextTick(() => { + if (this.$refs.displayTitle) { + this.displayTitleTruncated = this.$refs.displayTitle.scrollWidth > this.$refs.displayTitle.clientWidth + } + }) }, clickCard(e) { if (this.processing) return diff --git a/client/components/modals/item/tabs/Tools.vue b/client/components/modals/item/tabs/Tools.vue index 5f2ca6b33..de19e04c3 100644 --- a/client/components/modals/item/tabs/Tools.vue +++ b/client/components/modals/item/tabs/Tools.vue @@ -2,8 +2,11 @@

{{ $strings.HeaderAudiobookTools }}

+ + Not supported for the Windows install yet + -
+

{{ $strings.LabelToolsMakeM4b }}

@@ -19,22 +22,8 @@
- - - -
+

{{ $strings.LabelToolsEmbedMetadata }}

@@ -122,6 +111,12 @@ export default { }, isEncodeTaskRunning() { return this.encodeTask && !this.encodeTask?.isFinished + }, + isWindowsInstall() { + return this.Source == 'windows' + }, + Source() { + return this.$store.state.Source } }, methods: { diff --git a/client/components/modals/libraries/EditLibrary.vue b/client/components/modals/libraries/EditLibrary.vue index 598f3bcd4..d29d79298 100644 --- a/client/components/modals/libraries/EditLibrary.vue +++ b/client/components/modals/libraries/EditLibrary.vue @@ -31,7 +31,7 @@ {{ $strings.ButtonBrowseForFolder }}
- +
diff --git a/client/components/modals/libraries/FolderChooser.vue b/client/components/modals/libraries/LazyFolderChooser.vue similarity index 57% rename from client/components/modals/libraries/FolderChooser.vue rename to client/components/modals/libraries/LazyFolderChooser.vue index 6383d1028..74a81a977 100644 --- a/client/components/modals/libraries/FolderChooser.vue +++ b/client/components/modals/libraries/LazyFolderChooser.vue @@ -4,35 +4,37 @@ arrow_back

{{ $strings.HeaderChooseAFolder }}

-
-

{{ selectedPath || '\\' }}

+
+

{{ selectedPath || '/' }}

-
+
-
+
folder

..

-
+
folder

{{ dir.dirname }}

- arrow_right + arrow_right
-
+
folder

{{ dir.dirname }}

+
+ +
-
+

{{ $strings.MessageLoadingFolders }}

{{ $strings.MessageNoFoldersAvailable }}

{{ $strings.NoteFolderPicker }}

-

{{ $strings.NoteFolderPickerDebian }}

@@ -51,11 +53,12 @@ export default { }, data() { return { - loadingFolders: false, - allFolders: [], + initialLoad: false, + loadingDirs: false, + isPosix: true, + rootDirs: [], directories: [], selectedPath: '', - selectedFullPath: '', subdirs: [], level: 0, currentDir: null, @@ -89,68 +92,91 @@ export default { ...d } }) - }, - isDebian() { - return this.Source == 'debian' - }, - Source() { - return this.$store.state.Source } }, methods: { - goBack() { - var splitPaths = this.selectedPath.split('\\').slice(1) - var prev = splitPaths.slice(0, -1).join('\\') + async goBack() { + let selPath = this.selectedPath.replace(/^\//, '') + var splitPaths = selPath.split('/') - var currDirs = this.allFolders - for (let i = 0; i < splitPaths.length; i++) { - var _dir = currDirs.find((dir) => dir.dirname === splitPaths[i]) - if (_dir && _dir.path.slice(1) === prev) { - this.directories = currDirs - this.selectDir(_dir) - return - } else if (_dir) { - currDirs = _dir.dirs - } + let previousPath = '' + let lookupPath = '' + + if (splitPaths.length > 2) { + lookupPath = splitPaths.slice(0, -2).join('/') } + previousPath = splitPaths.slice(0, -1).join('/') + + if (!this.isPosix) { + // For windows drives add a trailing slash. e.g. C:/ + if (!this.isPosix && lookupPath.endsWith(':')) { + lookupPath += '/' + } + if (!this.isPosix && previousPath.endsWith(':')) { + previousPath += '/' + } + } else { + // Add leading slash + if (previousPath) previousPath = '/' + previousPath + if (lookupPath) lookupPath = '/' + lookupPath + } + + this.level-- + this.subdirs = this.directories + this.selectedPath = previousPath + this.directories = await this.fetchDirs(lookupPath, this.level) }, - selectDir(dir) { + async selectDir(dir) { if (dir.isUsed) return this.selectedPath = dir.path - this.selectedFullPath = dir.fullPath this.level = dir.level - this.subdirs = dir.dirs + this.subdirs = await this.fetchDirs(dir.path, dir.level + 1) }, - selectSubDir(dir) { + async selectSubDir(dir) { if (dir.isUsed) return this.selectedPath = dir.path - this.selectedFullPath = dir.fullPath this.level = dir.level this.directories = this.subdirs - this.subdirs = dir.dirs + this.subdirs = await this.fetchDirs(dir.path, dir.level + 1) }, selectFolder() { if (!this.selectedPath) { console.error('No Selected path') return } - if (this.paths.find((p) => p.startsWith(this.selectedFullPath))) { + if (this.paths.find((p) => p.startsWith(this.selectedPath))) { this.$toast.error(`Oops, you cannot add a parent directory of a folder already added`) return } - this.$emit('select', this.selectedFullPath) + this.$emit('select', this.selectedPath) this.selectedPath = '' - this.selectedFullPath = '' + }, + fetchDirs(path, level) { + this.loadingDirs = true + return this.$axios + .$get(`/api/filesystem?path=${path}&level=${level}`) + .then((data) => { + console.log('Fetched directories', data.directories) + this.isPosix = !!data.posix + return data.directories + }) + .catch((error) => { + console.error('Failed to get filesystem paths', error) + this.$toast.error('Failed to get filesystem paths') + return [] + }) + .finally(() => { + this.loadingDirs = false + }) }, async init() { - this.loadingFolders = true - this.allFolders = await this.$store.dispatch('libraries/loadFolders') - this.loadingFolders = false + this.initialLoad = true + this.rootDirs = await this.fetchDirs('', 0) + this.initialLoad = false - this.directories = this.allFolders + this.directories = this.rootDirs this.subdirs = [] this.selectedPath = '' - this.selectedFullPath = '' } }, mounted() { diff --git a/client/components/modals/libraries/LibraryScannerSettings.vue b/client/components/modals/libraries/LibraryScannerSettings.vue index 8ec73dd03..43938f9cf 100644 --- a/client/components/modals/libraries/LibraryScannerSettings.vue +++ b/client/components/modals/libraries/LibraryScannerSettings.vue @@ -63,7 +63,7 @@ export default { }, audioMetatags: { id: 'audioMetatags', - name: 'Audio file meta tags', + name: 'Audio file meta tags OR ebook metadata', include: true }, nfoFile: { diff --git a/client/components/modals/podcast/EpisodeFeed.vue b/client/components/modals/podcast/EpisodeFeed.vue index 4a1b4753d..b5d98a25a 100644 --- a/client/components/modals/podcast/EpisodeFeed.vue +++ b/client/components/modals/podcast/EpisodeFeed.vue @@ -68,7 +68,9 @@ export default { selectAll: false, search: null, searchTimeout: null, - searchText: null + searchText: null, + downloadedEpisodeGuidMap: {}, + downloadedEpisodeUrlMap: {} } }, watch: { @@ -122,11 +124,13 @@ export default { }, methods: { getIsEpisodeDownloaded(episode) { - return this.itemEpisodes.some((downloadedEpisode) => { - if (episode.guid && downloadedEpisode.guid === episode.guid) return true - if (!downloadedEpisode.enclosure?.url) return false - return this.getCleanEpisodeUrl(downloadedEpisode.enclosure.url) === episode.cleanUrl - }) + if (episode.guid && !!this.downloadedEpisodeGuidMap[episode.guid]) { + return true + } + if (this.downloadedEpisodeUrlMap[episode.cleanUrl]) { + return true + } + return false }, /** * UPDATE: As of v2.4.5 guid is used for matching existing downloaded episodes if it is found on the RSS feed. @@ -219,6 +223,14 @@ export default { }) }, init() { + this.downloadedEpisodeGuidMap = {} + this.downloadedEpisodeUrlMap = {} + + this.itemEpisodes.forEach((episode) => { + if (episode.guid) this.downloadedEpisodeGuidMap[episode.guid] = episode.id + if (episode.enclosure?.url) this.downloadedEpisodeUrlMap[this.getCleanEpisodeUrl(episode.enclosure.url)] = episode.id + }) + this.episodesCleaned = this.episodes .filter((ep) => ep.enclosure?.url) .map((_ep) => { diff --git a/client/components/readers/ComicReader.vue b/client/components/readers/ComicReader.vue index 67aa16c6b..d55fc0d61 100644 --- a/client/components/readers/ComicReader.vue +++ b/client/components/readers/ComicReader.vue @@ -1,7 +1,7 @@