From d2512d324a3850562738b84575037b84fd6fcdd1 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Thu, 2 Feb 2023 00:38:35 +0100 Subject: [PATCH 01/26] Integration Test This patch adds a minimal integration test building Audiobookshelf as a binary, running it and checking if the server is available on each push and pull request. We can easily extend this with a Selenium or Playwright test later, but it should already alert us about problems in the build pipeline without the need for any developer to take a look at the new patches. --- .github/workflows/integration-test.yml | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/integration-test.yml diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 000000000..f88db2ed1 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,44 @@ +name: Integration Test + +on: + pull_request: + push: + branches-ignore: + - 'dependabot/**' # Don't run dependabot branches, as they are already covered by pull requests + +jobs: + build: + name: build and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: setup nade + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: install pkg + run: npm install -g pkg + + - name: get client dependencies + working-directory: client + run: npm ci + + - name: build client + working-directory: client + run: npm run generate + + - name: get server dependencies + run: npm ci --only=production + + - name: build binary + run: pkg -t node18-linux-x64 -o audiobookshelf . + + - name: run audiobookshelf + run: | + ./audiobookshelf & + sleep 5 + + - name: test if server is available + run: curl -sf http://127.0.0.1:3333 | grep Audiobookshelf From 19e39f63210d5243ec91369d1e0da80f7dd5a934 Mon Sep 17 00:00:00 2001 From: gefan Date: Tue, 7 Feb 2023 00:18:48 +0800 Subject: [PATCH 02/26] kill zombie processes to reduce memory usage --- server/libs/nodeFfprobe/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/libs/nodeFfprobe/index.js b/server/libs/nodeFfprobe/index.js index bdab89ad0..a2f284cb9 100644 --- a/server/libs/nodeFfprobe/index.js +++ b/server/libs/nodeFfprobe/index.js @@ -18,7 +18,10 @@ module.exports = (function () { proc.stdout.on('data', function (data) { probeData.push(data) }) proc.stderr.on('data', function (data) { errData.push(data) }) - proc.on('exit', code => { exitCode = code }) + proc.on('exit', code => { + exitCode = code + proc.kill() + }) proc.on('error', err => reject(err)) proc.on('close', () => { try { From 61c32d99e7f13f7a0d30d5789289a5155d3cdb80 Mon Sep 17 00:00:00 2001 From: gefan Date: Tue, 7 Feb 2023 00:18:57 +0800 Subject: [PATCH 03/26] scan media files in batches --- server/scanner/MediaFileScanner.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server/scanner/MediaFileScanner.js b/server/scanner/MediaFileScanner.js index b572e9351..e9a54b662 100644 --- a/server/scanner/MediaFileScanner.js +++ b/server/scanner/MediaFileScanner.js @@ -115,11 +115,16 @@ class MediaFileScanner { const scanStart = Date.now() const mediaMetadata = libraryItem.media.metadata || null - const proms = [] - for (let i = 0; i < mediaLibraryFiles.length; i++) { - proms.push(this.scan(mediaType, mediaLibraryFiles[i], mediaMetadata)) + const batchSize = 32 + const results = [] + for (let batch = 0; batch < mediaLibraryFiles.length; batch += batchSize) { + const proms = [] + for (let i = batch; i < Math.min(batch + batchSize, mediaLibraryFiles.length); i++) { + proms.push(this.scan(mediaType, mediaLibraryFiles[i], mediaMetadata)) + } + results.push(...await Promise.all(proms).then((scanResults) => scanResults.filter(sr => sr))) } - const results = await Promise.all(proms).then((scanResults) => scanResults.filter(sr => sr)) + return { audioFiles: results.filter(r => r.audioFile).map(r => r.audioFile), videoFiles: results.filter(r => r.videoFile).map(r => r.videoFile), From cf927f61a0b1714794cb38b026476a056ae59e25 Mon Sep 17 00:00:00 2001 From: Dmitry Naboychenko Date: Tue, 7 Feb 2023 00:25:18 +0300 Subject: [PATCH 04/26] Add FantLab.ru Book Finder --- client/store/scanners.js | 4 + server/finders/BookFinder.js | 20 ++++- server/providers/FantLab.js | 170 +++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 server/providers/FantLab.js diff --git a/client/store/scanners.js b/client/store/scanners.js index 8443baf9f..0a339a035 100644 --- a/client/store/scanners.js +++ b/client/store/scanners.js @@ -52,6 +52,10 @@ export const state = () => ({ { text: 'Audible.es', value: 'audible.es' + }, + { + text: 'FantLab.ru', + value: 'fantlab' } ], podcastProviders: [ diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 9c9937825..a7d5b7309 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -3,6 +3,7 @@ const GoogleBooks = require('../providers/GoogleBooks') const Audible = require('../providers/Audible') const iTunes = require('../providers/iTunes') const Audnexus = require('../providers/Audnexus') +const FantLab = require('../providers/FantLab') const Logger = require('../Logger') const { levenshteinDistance } = require('../utils/index') @@ -13,6 +14,7 @@ class BookFinder { this.audible = new Audible() this.iTunesApi = new iTunes() this.audnexus = new Audnexus() + this.fantLab = new FantLab() this.verbose = false } @@ -146,6 +148,17 @@ class BookFinder { return books } + async getFantLabResults(title, author) { + var books = await this.fantLab.search(title, author) + if (this.verbose) Logger.debug(`FantLab Book Search Results: ${books.length || 0}`) + if (books.errorCode) { + Logger.error(`FantLab Search Error ${books.errorCode}`) + return [] + } + + return books + } + async getiTunesAudiobooksResults(title, author) { return this.iTunesApi.searchAudiobooks(title) } @@ -172,7 +185,10 @@ class BookFinder { books = await this.getiTunesAudiobooksResults(title, author) } else if (provider === 'openlibrary') { books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance) - } else { + } else if (provider === 'fantlab') { + books = await this.getFantLabResults(title, author) + } + else { books = await this.getGoogleBooksResults(title, author) } @@ -186,7 +202,7 @@ class BookFinder { return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options) } - if (["google", "audible", "itunes"].includes(provider)) return books + if (["google", "audible", "itunes", 'fantlab'].includes(provider)) return books return books.sort((a, b) => { return a.totalDistance - b.totalDistance diff --git a/server/providers/FantLab.js b/server/providers/FantLab.js new file mode 100644 index 000000000..50a74c876 --- /dev/null +++ b/server/providers/FantLab.js @@ -0,0 +1,170 @@ +const axios = require('axios') +const Logger = require('../Logger') + +class FantLab { + // 7 - other + // 11 - essay + // 12 - article + // 22 - disser + // 23 - monography + // 24 - study + // 25 - encyclopedy + // 26 - magazine + // 46 - sketch + // 47 - reportage + // 49 - excerpt + // 51 - interview + // 52 - review + // 55 - libretto + // 56 - anthology series + // 57 - newspaper + // types can get here https://api.fantlab.ru/config.json + _filterWorkType = [7, 11, 12, 22, 23, 24, 25, 26, 46, 47, 49, 51, 52, 55, 56, 57]; + _baseUrl = 'https://api.fantlab.ru' + + constructor() { } + + async search(title, author) { + var searchString = encodeURIComponent(title) + if (author) { + searchString += encodeURIComponent(' ' + author) + } + var url = `${this._baseUrl}/search-works?q=${searchString}&page=1&onlymatches=1` + Logger.debug(`[FantLab] Search url: ${url}`) + var items = await axios.get(url).then((res) => { + return res.data || [] + }).catch(error => { + Logger.error('[FantLab] search error', error) + return [] + }) + + return Promise.all(items.map(async item => await this.getWork(item))) + } + + async getWork(item) { + var { work_id, work_type_id } = item + + if (this._filterWorkType.includes(work_type_id)) { + return { title: null } + } + + var url = `${this._baseUrl}/work/${work_id}/extended` + var bookData = await axios.get(url).then((resp) => { + return resp.data || null + }).catch((error) => { + Logger.error(`[FantLab] work info reques error`, error) + return null + }) + + return await this.cleanBookData(bookData) + } + + async cleanBookData(bookData) { + var { authors, work_name_alts, work_id, work_name, work_year, work_description, image, classificatory, editions_blocks } = bookData; + + var subtitle = Array.isArray(work_name_alts) ? work_name_alts[0] : null + var auth = authors.map(function (author) { + return author.name + }); + + var genres = classificatory ? this.tryGetGenres(classificatory) : [] + + var imageAndIsbn = await this.tryGetCoverFromEditions(editions_blocks) + + if (imageAndIsbn) { + var { imageUrl, isbn } = imageAndIsbn + + if (imageUrl) { + image = imageUrl + } + } + + var cover = 'https://fantlab.ru' + image + + return { + id: work_id, + title: work_name, + subtitle: subtitle || null, + author: auth ? auth.join(', ') : null, + publisher: null, + publishedYear: work_year, + description: work_description, + cover: image ? cover : null, + genres: genres, + isbn: isbn + } + } + + tryGetGenres(classificatory) { + var { genre_group } = classificatory; + if (!genre_group) { + return [] + } + var genresGroup = genre_group.find(group => group.genre_group_id = 1) // genres and subgenres + + // genre_group_id=2 - General Characteristics + // genre_group_id=3 - Arena + // genre_group_id=4 - Duration of action + // genre_group_id=6 - Story moves + // genre_group_id=7 - Story linearity + // genre_group_id=5 - Recommended age of the reader + + if (!genresGroup) return [] + + var { genre } = genresGroup; + var rootGenre = genre[0]; + + var { label } = rootGenre + + return [label].concat(this.tryGetSubGenres(rootGenre)) + } + + tryGetSubGenres(rootGenre) { + var { genre } = rootGenre + return genre ? genre.map(genreObj => genreObj.label) : [] + } + + async tryGetCoverFromEditions(editions) { + + if (!editions) { + return null + } + + var bookEditions = editions['30'] // try get audiobooks first + if (!bookEditions) { + bookEditions = editions['10'] // paper editions in ru lang + } + + if (!bookEditions) { + return null + } + + var { list } = bookEditions + + var lastEdition = list[list.length - 1] + + var editionId = lastEdition['edition_id'] + var isbn = lastEdition['isbn'] // get only from paper edition + + return { + imageUrl: await this.getCoverFromEdition(editionId), + isbn: isbn + } + } + + async getCoverFromEdition(editionId) { + var url = `${this._baseUrl}/edition/${editionId}` + + var editionInfo = await axios.get(url).then((resp) => { + return resp.data || null + }).catch(error => { + Logger.error('[FantLab] search cover from edition error', error) + return null + }) + + return editionInfo ? editionInfo['image'] : null + } + +} + +module.exports = FantLab \ No newline at end of file From ba07761de3093445f6dd0d9a247e1c85b2e5451f Mon Sep 17 00:00:00 2001 From: gefan Date: Tue, 7 Feb 2023 12:33:33 +0800 Subject: [PATCH 05/26] Revert "kill zombie processes to reduce memory usage" This reverts commit 19e39f63210d5243ec91369d1e0da80f7dd5a934. --- server/libs/nodeFfprobe/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/libs/nodeFfprobe/index.js b/server/libs/nodeFfprobe/index.js index a2f284cb9..bdab89ad0 100644 --- a/server/libs/nodeFfprobe/index.js +++ b/server/libs/nodeFfprobe/index.js @@ -18,10 +18,7 @@ module.exports = (function () { proc.stdout.on('data', function (data) { probeData.push(data) }) proc.stderr.on('data', function (data) { errData.push(data) }) - proc.on('exit', code => { - exitCode = code - proc.kill() - }) + proc.on('exit', code => { exitCode = code }) proc.on('error', err => reject(err)) proc.on('close', () => { try { From b9307143bd208b4c279ff81edfc10f26c15d9221 Mon Sep 17 00:00:00 2001 From: Dmitry Naboychenko Date: Wed, 8 Feb 2023 22:32:27 +0300 Subject: [PATCH 06/26] FantLab match provider fixes after code review --- server/providers/FantLab.js | 60 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/server/providers/FantLab.js b/server/providers/FantLab.js index 50a74c876..0cfd9f21b 100644 --- a/server/providers/FantLab.js +++ b/server/providers/FantLab.js @@ -19,57 +19,59 @@ class FantLab { // 56 - anthology series // 57 - newspaper // types can get here https://api.fantlab.ru/config.json - _filterWorkType = [7, 11, 12, 22, 23, 24, 25, 26, 46, 47, 49, 51, 52, 55, 56, 57]; + _filterWorkType = [7, 11, 12, 22, 23, 24, 25, 26, 46, 47, 49, 51, 52, 55, 56, 57] _baseUrl = 'https://api.fantlab.ru' constructor() { } async search(title, author) { - var searchString = encodeURIComponent(title) + let searchString = encodeURIComponent(title) if (author) { searchString += encodeURIComponent(' ' + author) } - var url = `${this._baseUrl}/search-works?q=${searchString}&page=1&onlymatches=1` + const url = `${this._baseUrl}/search-works?q=${searchString}&page=1&onlymatches=1` Logger.debug(`[FantLab] Search url: ${url}`) - var items = await axios.get(url).then((res) => { + const items = await axios.get(url).then((res) => { return res.data || [] }).catch(error => { Logger.error('[FantLab] search error', error) return [] }) - return Promise.all(items.map(async item => await this.getWork(item))) + return Promise.all(items.map(async item => await this.getWork(item))).then(resArray => { + return resArray.filter(res => res != null) + }) } async getWork(item) { - var { work_id, work_type_id } = item + const { work_id, work_type_id } = item if (this._filterWorkType.includes(work_type_id)) { return { title: null } } - var url = `${this._baseUrl}/work/${work_id}/extended` - var bookData = await axios.get(url).then((resp) => { + const url = `${this._baseUrl}/work/${work_id}/extended` + const bookData = await axios.get(url).then((resp) => { return resp.data || null }).catch((error) => { Logger.error(`[FantLab] work info reques error`, error) return null }) - return await this.cleanBookData(bookData) + return this.cleanBookData(bookData) } async cleanBookData(bookData) { - var { authors, work_name_alts, work_id, work_name, work_year, work_description, image, classificatory, editions_blocks } = bookData; + let { authors, work_name_alts, work_id, work_name, work_year, work_description, image, classificatory, editions_blocks } = bookData - var subtitle = Array.isArray(work_name_alts) ? work_name_alts[0] : null - var auth = authors.map(function (author) { + const subtitle = Array.isArray(work_name_alts) ? work_name_alts[0] : null + const auth = authors.map(function (author) { return author.name - }); + }) - var genres = classificatory ? this.tryGetGenres(classificatory) : [] + const genres = classificatory ? this.tryGetGenres(classificatory) : [] - var imageAndIsbn = await this.tryGetCoverFromEditions(editions_blocks) + const imageAndIsbn = await this.tryGetCoverFromEditions(editions_blocks) if (imageAndIsbn) { var { imageUrl, isbn } = imageAndIsbn @@ -79,7 +81,7 @@ class FantLab { } } - var cover = 'https://fantlab.ru' + image + const cover = 'https://fantlab.ru' + image return { id: work_id, @@ -96,11 +98,11 @@ class FantLab { } tryGetGenres(classificatory) { - var { genre_group } = classificatory; + const { genre_group } = classificatory if (!genre_group) { return [] } - var genresGroup = genre_group.find(group => group.genre_group_id = 1) // genres and subgenres + const genresGroup = genre_group.find(group => group.genre_group_id == 1) // genres and subgenres // genre_group_id=2 - General Characteristics // genre_group_id=3 - Arena @@ -111,16 +113,16 @@ class FantLab { if (!genresGroup) return [] - var { genre } = genresGroup; - var rootGenre = genre[0]; + const { genre } = genresGroup + const rootGenre = genre[0] - var { label } = rootGenre + const { label } = rootGenre return [label].concat(this.tryGetSubGenres(rootGenre)) } tryGetSubGenres(rootGenre) { - var { genre } = rootGenre + const { genre } = rootGenre return genre ? genre.map(genreObj => genreObj.label) : [] } @@ -130,7 +132,7 @@ class FantLab { return null } - var bookEditions = editions['30'] // try get audiobooks first + let bookEditions = editions['30'] // try get audiobooks first if (!bookEditions) { bookEditions = editions['10'] // paper editions in ru lang } @@ -139,12 +141,12 @@ class FantLab { return null } - var { list } = bookEditions + const { list } = bookEditions - var lastEdition = list[list.length - 1] + const lastEdition = list[list.length - 1] - var editionId = lastEdition['edition_id'] - var isbn = lastEdition['isbn'] // get only from paper edition + const editionId = lastEdition['edition_id'] + const isbn = lastEdition['isbn'] // get only from paper edition return { imageUrl: await this.getCoverFromEdition(editionId), @@ -153,9 +155,9 @@ class FantLab { } async getCoverFromEdition(editionId) { - var url = `${this._baseUrl}/edition/${editionId}` + const url = `${this._baseUrl}/edition/${editionId}` - var editionInfo = await axios.get(url).then((resp) => { + const editionInfo = await axios.get(url).then((resp) => { return resp.data || null }).catch(error => { Logger.error('[FantLab] search cover from edition error', error) From 371cd3b2e5025d8c6dcec826d635e5085b124bf5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 9 Feb 2023 23:09:44 +0300 Subject: [PATCH 07/26] Update server/providers/FantLab.js Co-authored-by: advplyr <67830747+advplyr@users.noreply.github.com> --- server/providers/FantLab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/providers/FantLab.js b/server/providers/FantLab.js index 0cfd9f21b..eb30c599b 100644 --- a/server/providers/FantLab.js +++ b/server/providers/FantLab.js @@ -47,7 +47,7 @@ class FantLab { const { work_id, work_type_id } = item if (this._filterWorkType.includes(work_type_id)) { - return { title: null } + return null } const url = `${this._baseUrl}/work/${work_id}/extended` From 2ad03bcb9ae6124251eb16c1bd8149280b7f47c8 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 10 Feb 2023 15:33:42 -0600 Subject: [PATCH 08/26] Fix:Bad backup files and backing up playlists, feeds #1485 --- server/managers/BackupManager.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index 94b83e374..bb6a47a08 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -295,14 +295,16 @@ class BackupManager { // pipe archive data to the file archive.pipe(output) - archive.directory(this.db.LibraryItemsPath, 'config/libraryItems/data') - archive.directory(this.db.UsersPath, 'config/users/data') - archive.directory(this.db.SessionsPath, 'config/sessions/data') - archive.directory(this.db.LibrariesPath, 'config/libraries/data') - archive.directory(this.db.SettingsPath, 'config/settings/data') - archive.directory(this.db.CollectionsPath, 'config/collections/data') - archive.directory(this.db.AuthorsPath, 'config/authors/data') - archive.directory(this.db.SeriesPath, 'config/series/data') + archive.directory(Path.join(this.db.LibraryItemsPath, 'data'), 'config/libraryItems/data') + archive.directory(Path.join(this.db.UsersPath, 'data'), 'config/users/data') + archive.directory(Path.join(this.db.SessionsPath, 'data'), 'config/sessions/data') + archive.directory(Path.join(this.db.LibrariesPath, 'data'), 'config/libraries/data') + archive.directory(Path.join(this.db.SettingsPath, 'data'), 'config/settings/data') + archive.directory(Path.join(this.db.CollectionsPath, 'data'), 'config/collections/data') + archive.directory(Path.join(this.db.AuthorsPath, 'data'), 'config/authors/data') + archive.directory(Path.join(this.db.SeriesPath, 'data'), 'config/series/data') + archive.directory(Path.join(this.db.PlaylistsPath, 'data'), 'config/playlists/data') + archive.directory(Path.join(this.db.FeedsPath, 'data'), 'config/feeds/data') if (this.serverSettings.backupMetadataCovers) { Logger.debug(`[BackupManager] Backing up Metadata Items "${this.ItemsMetadataPath}"`) From 33846e46faede2774d248bb79a40e93f9eca595f Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 10 Feb 2023 17:07:25 -0600 Subject: [PATCH 09/26] Fix:Handle podcast RSS feeds with iso-8859-1 encoding #1489 --- package-lock.json | 2 +- server/utils/podcastUtils.js | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df088c027..2f76ab438 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2314,4 +2314,4 @@ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" } } -} \ No newline at end of file +} diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js index 9cb9049f9..ef7d54d12 100644 --- a/server/utils/podcastUtils.js +++ b/server/utils/podcastUtils.js @@ -191,7 +191,17 @@ module.exports.parsePodcastRssFeedXml = async (xml, excludeEpisodeMetadata = fal module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => { Logger.debug(`[podcastUtils] getPodcastFeed for "${feedUrl}"`) - return axios.get(feedUrl, { timeout: 6000 }).then(async (data) => { + return axios.get(feedUrl, { timeout: 6000, responseType: 'arraybuffer' }).then(async (data) => { + + // Adding support for ios-8859-1 encoded RSS feeds. + // See: https://github.com/advplyr/audiobookshelf/issues/1489 + const contentType = data.headers?.['content-type'] || '' // e.g. text/xml; charset=iso-8859-1 + if (contentType.toLowerCase().includes('iso-8859-1')) { + data.data = data.data.toString('latin1') + } else { + data.data = data.data.toString() + } + if (!data || !data.data) { Logger.error(`[podcastUtils] getPodcastFeed: Invalid podcast feed request response (${feedUrl})`) return false From e195eec1c5cf7b9456ea9bbab155c06477c9fc06 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 10 Feb 2023 17:22:23 -0600 Subject: [PATCH 10/26] Fix:OPF parser supporting attributes on tags #1478 --- server/utils/parsers/parseOpfMetadata.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/server/utils/parsers/parseOpfMetadata.js b/server/utils/parsers/parseOpfMetadata.js index c48503eff..f51a7e939 100644 --- a/server/utils/parsers/parseOpfMetadata.js +++ b/server/utils/parsers/parseOpfMetadata.js @@ -22,7 +22,27 @@ function fetchCreators(creators, role) { function fetchTagString(metadata, tag) { if (!metadata[tag] || !metadata[tag].length) return null - const value = metadata[tag][0] + let value = metadata[tag][0] + + /* + EXAMPLES: + + "dc:title": [ + { + "_": "The Quest for Character", + "$": { + "opf:file-as": "Quest for Character What the Story of Socrates and Alcibiades" + } + } + ] + + OR + + "dc:title": [ + "The Quest for Character" + ] + */ + if (typeof value === 'object') value = value._ if (typeof value !== 'string') return null return value } From 8f8d6f81abf3f08176cfd75744f60cf5d42fe574 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 10 Feb 2023 17:25:19 -0600 Subject: [PATCH 11/26] Fix:Upload API endpoint crashing on invalid request with no files #1473 --- server/controllers/MiscController.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/controllers/MiscController.js b/server/controllers/MiscController.js index 69e5be22f..c7ef950f2 100644 --- a/server/controllers/MiscController.js +++ b/server/controllers/MiscController.js @@ -19,6 +19,10 @@ class MiscController { Logger.warn('User attempted to upload without permission', req.user) return res.sendStatus(403) } + if (!req.files) { + Logger.error('Invalid request, no files') + return res.sendStatus(400) + } var files = Object.values(req.files) var title = req.body.title var author = req.body.author From f35c96e1186916e1aa78e4f1fc5620662d0a7b18 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 11 Feb 2023 14:25:25 -0600 Subject: [PATCH 12/26] FantLab minor refactor --- server/providers/FantLab.js | 70 +++++++++++++------------------------ 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/server/providers/FantLab.js b/server/providers/FantLab.js index eb30c599b..41328ca12 100644 --- a/server/providers/FantLab.js +++ b/server/providers/FantLab.js @@ -39,7 +39,7 @@ class FantLab { }) return Promise.all(items.map(async item => await this.getWork(item))).then(resArray => { - return resArray.filter(res => res != null) + return resArray.filter(res => res) }) } @@ -54,7 +54,7 @@ class FantLab { const bookData = await axios.get(url).then((resp) => { return resp.data || null }).catch((error) => { - Logger.error(`[FantLab] work info reques error`, error) + Logger.error(`[FantLab] work info request for url "${url}" error`, error) return null }) @@ -65,44 +65,30 @@ class FantLab { let { authors, work_name_alts, work_id, work_name, work_year, work_description, image, classificatory, editions_blocks } = bookData const subtitle = Array.isArray(work_name_alts) ? work_name_alts[0] : null - const auth = authors.map(function (author) { - return author.name - }) - - const genres = classificatory ? this.tryGetGenres(classificatory) : [] + const authorNames = authors.map(au => (au.name || '').trim()).filter(au => au) const imageAndIsbn = await this.tryGetCoverFromEditions(editions_blocks) - if (imageAndIsbn) { - var { imageUrl, isbn } = imageAndIsbn - - if (imageUrl) { - image = imageUrl - } - } - - const cover = 'https://fantlab.ru' + image + const imageToUse = imageAndIsbn?.imageUrl || image return { id: work_id, title: work_name, subtitle: subtitle || null, - author: auth ? auth.join(', ') : null, + author: authorNames.length ? authorNames.join(', ') : null, publisher: null, publishedYear: work_year, description: work_description, - cover: image ? cover : null, - genres: genres, - isbn: isbn + cover: imageToUse ? `https://fantlab.ru${imageToUse}` : null, + genres: this.tryGetGenres(classificatory), + isbn: imageAndIsbn?.isbn || null } } tryGetGenres(classificatory) { - const { genre_group } = classificatory - if (!genre_group) { - return [] - } - const genresGroup = genre_group.find(group => group.genre_group_id == 1) // genres and subgenres + if (!classificatory || !classificatory.genre_group) return [] + + const genresGroup = classificatory.genre_group.find(group => group.genre_group_id == 1) // genres and subgenres // genre_group_id=2 - General Characteristics // genre_group_id=3 - Arena @@ -111,10 +97,9 @@ class FantLab { // genre_group_id=7 - Story linearity // genre_group_id=5 - Recommended age of the reader - if (!genresGroup) return [] + if (!genresGroup || !genresGroup.genre || !genresGroup.genre.length) return [] - const { genre } = genresGroup - const rootGenre = genre[0] + const rootGenre = genresGroup.genre[0] const { label } = rootGenre @@ -122,51 +107,46 @@ class FantLab { } tryGetSubGenres(rootGenre) { - const { genre } = rootGenre - return genre ? genre.map(genreObj => genreObj.label) : [] + if (!rootGenre.genre || !rootGenre.genre.length) return [] + return rootGenre.genre.map(g => g.label).filter(g => g) } async tryGetCoverFromEditions(editions) { - if (!editions) { return null } - let bookEditions = editions['30'] // try get audiobooks first - if (!bookEditions) { - bookEditions = editions['10'] // paper editions in ru lang - } - - if (!bookEditions) { + // 30 = audio, 10 = paper + // Prefer audio if available + const bookEditions = editions['30'] || editions['10'] + if (!bookEditions || !bookEditions.list || !bookEditions.list.length) { return null } - const { list } = bookEditions - - const lastEdition = list[list.length - 1] + const lastEdition = bookEditions.list.pop() const editionId = lastEdition['edition_id'] - const isbn = lastEdition['isbn'] // get only from paper edition + const isbn = lastEdition['isbn'] || null // get only from paper edition return { imageUrl: await this.getCoverFromEdition(editionId), - isbn: isbn + isbn } } async getCoverFromEdition(editionId) { + if (!editionId) return null const url = `${this._baseUrl}/edition/${editionId}` const editionInfo = await axios.get(url).then((resp) => { return resp.data || null }).catch(error => { - Logger.error('[FantLab] search cover from edition error', error) + Logger.error(`[FantLab] search cover from edition with url "${url}" error`, error) return null }) - return editionInfo ? editionInfo['image'] : null + return editionInfo?.image || null } - } module.exports = FantLab \ No newline at end of file From a8f62023023278d948c30fc173b49624afee79eb Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 11 Feb 2023 15:02:56 -0600 Subject: [PATCH 13/26] Remove Gentium Book font, reduce appbar icon and title font size --- client/assets/fonts.css | 8 ------- client/components/app/Appbar.vue | 4 ++-- .../components/app/BookShelfCategorized.vue | 4 ++-- client/components/app/BookShelfRow.vue | 2 +- client/components/app/BookShelfToolbar.vue | 4 ++-- client/components/app/LazyBookshelf.vue | 2 +- client/components/app/SideRail.vue | 20 +++++++++--------- client/components/cards/GroupCard.vue | 2 +- client/components/cards/LazyAlbumCard.vue | 2 +- client/components/cards/LazyBookCard.vue | 6 +++--- .../components/cards/LazyCollectionCard.vue | 2 +- client/components/cards/LazyPlaylistCard.vue | 2 +- client/components/cards/LazySeriesCard.vue | 4 ++-- client/components/covers/BookCover.vue | 8 +++---- client/components/covers/CollectionCover.vue | 2 +- client/components/covers/GroupCover.vue | 2 +- client/components/covers/PreviewCover.vue | 2 +- client/components/modals/AccountModal.vue | 2 +- .../components/modals/BackupScheduleModal.vue | 2 +- .../modals/BatchQuickMatchModel.vue | 2 +- client/components/modals/BookmarksModal.vue | 2 +- .../modals/ListeningSessionModal.vue | 2 +- client/components/modals/SleepTimerModal.vue | 2 +- .../components/modals/authors/EditModal.vue | 2 +- .../components/modals/changelog/ViewModal.vue | 2 +- .../modals/collections/AddCreateModal.vue | 2 +- .../modals/collections/EditModal.vue | 2 +- client/components/modals/item/EditModal.vue | 4 ++-- .../components/modals/item/tabs/Episodes.vue | 4 ++-- .../components/modals/libraries/EditModal.vue | 4 ++-- .../notification/NotificationEditModal.vue | 2 +- .../modals/player/QueueItemsModal.vue | 2 +- .../modals/playlists/AddCreateModal.vue | 2 +- .../components/modals/playlists/EditModal.vue | 2 +- .../components/modals/podcast/EditEpisode.vue | 4 ++-- .../components/modals/podcast/EpisodeFeed.vue | 2 +- client/components/modals/podcast/NewModal.vue | 2 +- .../modals/podcast/OpmlFeedsModal.vue | 2 +- .../modals/podcast/RemoveEpisode.vue | 2 +- .../components/modals/podcast/ViewEpisode.vue | 2 +- .../modals/rssfeed/OpenCloseModal.vue | 2 +- client/components/readers/Reader.vue | 2 +- .../components/stats/DailyListeningChart.vue | 4 ++-- client/components/stats/PreviewIcons.vue | 10 ++++----- client/components/tables/ChaptersTable.vue | 4 ++-- .../components/tables/LibraryFilesTable.vue | 4 ++-- client/components/tables/TracksTable.vue | 2 +- .../components/tables/UploadedFilesTable.vue | 6 +++--- .../components/tables/library/LibraryItem.vue | 2 +- client/pages/audiobook/_id/chapters.vue | 2 +- client/pages/audiobook/_id/edit.vue | 14 ++++++------ client/pages/config/index.vue | 6 +++--- client/pages/config/library-stats.vue | 12 +++++------ client/pages/config/stats.vue | 12 +++++------ client/pages/config/users/_id/index.vue | 2 +- client/static/fonts/GentiumBookBasic.woff2 | Bin 22856 -> 0 bytes client/tailwind.config.js | 3 +-- 57 files changed, 104 insertions(+), 113 deletions(-) delete mode 100644 client/static/fonts/GentiumBookBasic.woff2 diff --git a/client/assets/fonts.css b/client/assets/fonts.css index 30f381dbe..f9acc6bfb 100644 --- a/client/assets/fonts.css +++ b/client/assets/fonts.css @@ -48,14 +48,6 @@ font-size: 1.5rem; } -@font-face { - font-family: 'Gentium Book Basic'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url(~static/fonts/GentiumBookBasic.woff2) format('woff2'); -} - /* cyrillic-ext */ @font-face { font-family: 'Source Sans Pro'; diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index 20ff627fa..5f22febe6 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -3,11 +3,11 @@
- + -

audiobookshelf logo_dev

+

audiobookshelf logo_dev

diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index 0f60feac6..fe1d5df55 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -4,14 +4,14 @@
-

{{ libraryName }} Library is empty!

+

{{ libraryName }} Library is empty!

Configure Scanner Scan Library
-

No results for query

+

No results for query

diff --git a/client/components/app/BookShelfRow.vue b/client/components/app/BookShelfRow.vue index 931e6b505..bd97d56a3 100644 --- a/client/components/app/BookShelfRow.vue +++ b/client/components/app/BookShelfRow.vue @@ -44,7 +44,7 @@
-
+

{{ $strings[shelf.labelStringKey] }}

diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 8a8f5f889..124c332d7 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -42,7 +42,7 @@
-

{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}

+

{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}

{{ $strings.ButtonConfigureScanner }} {{ $strings.ButtonScanLibrary }} diff --git a/client/components/app/SideRail.vue b/client/components/app/SideRail.vue index 174ec3722..bf58e3419 100644 --- a/client/components/app/SideRail.vue +++ b/client/components/app/SideRail.vue @@ -8,7 +8,7 @@ -

{{ $strings.ButtonHome }}

+

{{ $strings.ButtonHome }}

@@ -16,7 +16,7 @@ format_list_bulleted -

{{ $strings.ButtonLatest }}

+

{{ $strings.ButtonLatest }}

@@ -26,7 +26,7 @@ -

{{ $strings.ButtonLibrary }}

+

{{ $strings.ButtonLibrary }}

@@ -36,7 +36,7 @@ -

{{ $strings.ButtonSeries }}

+

{{ $strings.ButtonSeries }}

@@ -44,7 +44,7 @@ collections_bookmark -

{{ $strings.ButtonCollections }}

+

{{ $strings.ButtonCollections }}

@@ -57,7 +57,7 @@ /> -

{{ $strings.ButtonAuthors }}

+

{{ $strings.ButtonAuthors }}

@@ -65,7 +65,7 @@ -

{{ $strings.ButtonSearch }}

+

{{ $strings.ButtonSearch }}

@@ -73,7 +73,7 @@ album -

Albums

+

Albums

@@ -81,7 +81,7 @@ queue_music -

{{ $strings.ButtonPlaylists }}

+

{{ $strings.ButtonPlaylists }}

@@ -89,7 +89,7 @@ warning -

{{ $strings.ButtonIssues }}

+

{{ $strings.ButtonIssues }}

diff --git a/client/components/cards/GroupCard.vue b/client/components/cards/GroupCard.vue index 954e3f695..14e11cfae 100644 --- a/client/components/cards/GroupCard.vue +++ b/client/components/cards/GroupCard.vue @@ -6,7 +6,7 @@
-

{{ groupName }}

+

{{ groupName }}

{{ bookItems.length }}
diff --git a/client/components/cards/LazyAlbumCard.vue b/client/components/cards/LazyAlbumCard.vue index 6fdebc58d..ecbd22803 100644 --- a/client/components/cards/LazyAlbumCard.vue +++ b/client/components/cards/LazyAlbumCard.vue @@ -5,7 +5,7 @@
-
+

{{ title }}

diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index 4ecec4f11..38bbb76ae 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -23,7 +23,7 @@
-

{{ title }}

+

{{ title }}

@@ -32,13 +32,13 @@
-

+

{{ titleCleaned }}

-

{{ authorCleaned }}

+

{{ authorCleaned }}

diff --git a/client/components/cards/LazyCollectionCard.vue b/client/components/cards/LazyCollectionCard.vue index 79189ed1c..006e29e26 100644 --- a/client/components/cards/LazyCollectionCard.vue +++ b/client/components/cards/LazyCollectionCard.vue @@ -12,7 +12,7 @@ rss_feed -
+

{{ title }}

diff --git a/client/components/cards/LazyPlaylistCard.vue b/client/components/cards/LazyPlaylistCard.vue index bfbb1a8ce..b65d5d44a 100644 --- a/client/components/cards/LazyPlaylistCard.vue +++ b/client/components/cards/LazyPlaylistCard.vue @@ -9,7 +9,7 @@ edit
-
+

{{ title }}

diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue index 2a40f6ece..e87892a1f 100644 --- a/client/components/cards/LazySeriesCard.vue +++ b/client/components/cards/LazySeriesCard.vue @@ -10,12 +10,12 @@
-

{{ displayTitle }}

+

{{ displayTitle }}

rss_feed -
+

{{ displayTitle }}

diff --git a/client/components/covers/BookCover.vue b/client/components/covers/BookCover.vue index 93a76f147..be39ae3ce 100644 --- a/client/components/covers/BookCover.vue +++ b/client/components/covers/BookCover.vue @@ -7,7 +7,7 @@
-

{{ title }}

+

{{ title }}

@@ -17,17 +17,17 @@
-

Invalid Cover

+

Invalid Cover

-

{{ titleCleaned }}

+

{{ titleCleaned }}

-

{{ authorCleaned }}

+

{{ authorCleaned }}

diff --git a/client/components/covers/CollectionCover.vue b/client/components/covers/CollectionCover.vue index 5ca536450..48d1caeb9 100644 --- a/client/components/covers/CollectionCover.vue +++ b/client/components/covers/CollectionCover.vue @@ -19,7 +19,7 @@
-

Empty Collection

+

Empty Collection

diff --git a/client/components/covers/GroupCover.vue b/client/components/covers/GroupCover.vue index a33fce0ee..2c7872e97 100644 --- a/client/components/covers/GroupCover.vue +++ b/client/components/covers/GroupCover.vue @@ -138,7 +138,7 @@ export default { var innerP = document.createElement('p') innerP.textContent = this.name - innerP.className = 'text-sm font-book text-white' + innerP.className = 'text-sm text-white' imgdiv.appendChild(innerP) return imgdiv diff --git a/client/components/covers/PreviewCover.vue b/client/components/covers/PreviewCover.vue index fac0271ae..f25d655de 100644 --- a/client/components/covers/PreviewCover.vue +++ b/client/components/covers/PreviewCover.vue @@ -14,7 +14,7 @@
-

Invalid Cover

+

Invalid Cover

diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue index b44e5e899..b1cdabf0a 100644 --- a/client/components/modals/AccountModal.vue +++ b/client/components/modals/AccountModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/BackupScheduleModal.vue b/client/components/modals/BackupScheduleModal.vue index 399de9e4b..71e91959d 100644 --- a/client/components/modals/BackupScheduleModal.vue +++ b/client/components/modals/BackupScheduleModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/BatchQuickMatchModel.vue b/client/components/modals/BatchQuickMatchModel.vue index 0bbace4b5..06900bd9e 100644 --- a/client/components/modals/BatchQuickMatchModel.vue +++ b/client/components/modals/BatchQuickMatchModel.vue @@ -2,7 +2,7 @@ diff --git a/client/components/modals/BookmarksModal.vue b/client/components/modals/BookmarksModal.vue index 3e6bca7c3..de76c4c1f 100644 --- a/client/components/modals/BookmarksModal.vue +++ b/client/components/modals/BookmarksModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/ListeningSessionModal.vue b/client/components/modals/ListeningSessionModal.vue index e05d647b9..fb435107d 100644 --- a/client/components/modals/ListeningSessionModal.vue +++ b/client/components/modals/ListeningSessionModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/SleepTimerModal.vue b/client/components/modals/SleepTimerModal.vue index 6230cf202..84e1d7bb6 100644 --- a/client/components/modals/SleepTimerModal.vue +++ b/client/components/modals/SleepTimerModal.vue @@ -2,7 +2,7 @@ diff --git a/client/components/modals/authors/EditModal.vue b/client/components/modals/authors/EditModal.vue index c95026d14..f2430390d 100644 --- a/client/components/modals/authors/EditModal.vue +++ b/client/components/modals/authors/EditModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/changelog/ViewModal.vue b/client/components/modals/changelog/ViewModal.vue index 214cad289..98b28b215 100644 --- a/client/components/modals/changelog/ViewModal.vue +++ b/client/components/modals/changelog/ViewModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/collections/AddCreateModal.vue b/client/components/modals/collections/AddCreateModal.vue index e124f9e3a..47e7f45e9 100644 --- a/client/components/modals/collections/AddCreateModal.vue +++ b/client/components/modals/collections/AddCreateModal.vue @@ -2,7 +2,7 @@ diff --git a/client/components/modals/collections/EditModal.vue b/client/components/modals/collections/EditModal.vue index ab719fbcb..551a63ac9 100644 --- a/client/components/modals/collections/EditModal.vue +++ b/client/components/modals/collections/EditModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/item/EditModal.vue b/client/components/modals/item/EditModal.vue index 55b16610d..67296370f 100644 --- a/client/components/modals/item/EditModal.vue +++ b/client/components/modals/item/EditModal.vue @@ -2,12 +2,12 @@
diff --git a/client/components/modals/item/tabs/Episodes.vue b/client/components/modals/item/tabs/Episodes.vue index 159470b37..f64eea4ed 100644 --- a/client/components/modals/item/tabs/Episodes.vue +++ b/client/components/modals/item/tabs/Episodes.vue @@ -19,7 +19,7 @@
{{ $strings.MessageNoEpisodes }}
- + @@ -33,7 +33,7 @@ -
Sort # {{ $strings.LabelEpisode }} {{ $strings.EpisodeTitle }}

{{ episode.episode }}

+ {{ episode.title }} diff --git a/client/components/modals/libraries/EditModal.vue b/client/components/modals/libraries/EditModal.vue index f83ebf74c..2d1ceac5d 100644 --- a/client/components/modals/libraries/EditModal.vue +++ b/client/components/modals/libraries/EditModal.vue @@ -2,12 +2,12 @@
diff --git a/client/components/modals/notification/NotificationEditModal.vue b/client/components/modals/notification/NotificationEditModal.vue index d45c00c64..55264f03d 100644 --- a/client/components/modals/notification/NotificationEditModal.vue +++ b/client/components/modals/notification/NotificationEditModal.vue @@ -2,7 +2,7 @@ diff --git a/client/components/modals/player/QueueItemsModal.vue b/client/components/modals/player/QueueItemsModal.vue index 0c8de3a76..d7893abd8 100644 --- a/client/components/modals/player/QueueItemsModal.vue +++ b/client/components/modals/player/QueueItemsModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/playlists/AddCreateModal.vue b/client/components/modals/playlists/AddCreateModal.vue index e583a6eea..9bfb65431 100644 --- a/client/components/modals/playlists/AddCreateModal.vue +++ b/client/components/modals/playlists/AddCreateModal.vue @@ -2,7 +2,7 @@ diff --git a/client/components/modals/playlists/EditModal.vue b/client/components/modals/playlists/EditModal.vue index 05f560b47..9130c5b85 100644 --- a/client/components/modals/playlists/EditModal.vue +++ b/client/components/modals/playlists/EditModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/podcast/EditEpisode.vue b/client/components/modals/podcast/EditEpisode.vue index d207e4ec5..236a0d875 100644 --- a/client/components/modals/podcast/EditEpisode.vue +++ b/client/components/modals/podcast/EditEpisode.vue @@ -2,12 +2,12 @@
diff --git a/client/components/modals/podcast/EpisodeFeed.vue b/client/components/modals/podcast/EpisodeFeed.vue index 642a6e0ea..be701f3a9 100644 --- a/client/components/modals/podcast/EpisodeFeed.vue +++ b/client/components/modals/podcast/EpisodeFeed.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/podcast/NewModal.vue b/client/components/modals/podcast/NewModal.vue index 71e2410f3..59b8dfd9d 100644 --- a/client/components/modals/podcast/NewModal.vue +++ b/client/components/modals/podcast/NewModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/podcast/OpmlFeedsModal.vue b/client/components/modals/podcast/OpmlFeedsModal.vue index ede76ec57..7d7327d26 100644 --- a/client/components/modals/podcast/OpmlFeedsModal.vue +++ b/client/components/modals/podcast/OpmlFeedsModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/podcast/RemoveEpisode.vue b/client/components/modals/podcast/RemoveEpisode.vue index 31b1bf896..0083228d2 100644 --- a/client/components/modals/podcast/RemoveEpisode.vue +++ b/client/components/modals/podcast/RemoveEpisode.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/podcast/ViewEpisode.vue b/client/components/modals/podcast/ViewEpisode.vue index 936b18cba..79f22a031 100644 --- a/client/components/modals/podcast/ViewEpisode.vue +++ b/client/components/modals/podcast/ViewEpisode.vue @@ -2,7 +2,7 @@
diff --git a/client/components/modals/rssfeed/OpenCloseModal.vue b/client/components/modals/rssfeed/OpenCloseModal.vue index 1f7c79c35..576bb4d7f 100644 --- a/client/components/modals/rssfeed/OpenCloseModal.vue +++ b/client/components/modals/rssfeed/OpenCloseModal.vue @@ -2,7 +2,7 @@
diff --git a/client/components/readers/Reader.vue b/client/components/readers/Reader.vue index 85ae0b4fe..5481f2504 100644 --- a/client/components/readers/Reader.vue +++ b/client/components/readers/Reader.vue @@ -4,7 +4,7 @@ close
-
+

{{ abTitle }}

by {{ abAuthor }}

diff --git a/client/components/stats/DailyListeningChart.vue b/client/components/stats/DailyListeningChart.vue index fda6ed894..d96813243 100644 --- a/client/components/stats/DailyListeningChart.vue +++ b/client/components/stats/DailyListeningChart.vue @@ -1,6 +1,6 @@