mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-13 22:14:24 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4b58bf435 | |||
| aea4df1ab9 | |||
| a7534af258 | |||
| c98763fc48 | |||
| f6b922fe8d | |||
| d27185e8cb | |||
| fe55002f84 | |||
| 656936378e | |||
| c023f029d7 | |||
| 28d98b4dbc | |||
| e510174f12 | |||
| 08c9e8d47d | |||
| 1908ec3df5 | |||
| df3878d4ca | |||
| 1097de6f1f | |||
| e408070b19 | |||
| af67c2e86f | |||
| 6a52d2a968 | |||
| 093be36192 | |||
| 3cdf16e86f | |||
| 6edfcfb271 | |||
| 42eb4e3f04 |
@@ -353,6 +353,14 @@ export default {
|
||||
if (!this.userProgressLastUpdated) return '\u00A0'
|
||||
return this.$getString('LabelLastProgressDate', [this.$formatDatetime(this.userProgressLastUpdated, this.dateFormat, this.timeFormat)])
|
||||
}
|
||||
if (this.orderBy === 'progress.createdAt') {
|
||||
if (!this.userProgressStartedDate) return '\u00A0'
|
||||
return this.$getString('LabelStartedDate', [this.$formatDatetime(this.userProgressStartedDate, this.dateFormat, this.timeFormat)])
|
||||
}
|
||||
if (this.orderBy === 'progress.finishedAt') {
|
||||
if (!this.userProgressFinishedDate) return '\u00A0'
|
||||
return this.$getString('LabelFinishedDate', [this.$formatDatetime(this.userProgressFinishedDate, this.dateFormat, this.timeFormat)])
|
||||
}
|
||||
return null
|
||||
},
|
||||
episodeProgress() {
|
||||
@@ -389,6 +397,14 @@ export default {
|
||||
if (!this.userProgress) return null
|
||||
return this.userProgress.lastUpdate
|
||||
},
|
||||
userProgressStartedDate() {
|
||||
if (!this.userProgress) return null
|
||||
return this.userProgress.startedAt
|
||||
},
|
||||
userProgressFinishedDate() {
|
||||
if (!this.userProgress) return null
|
||||
return this.userProgress.finishedAt
|
||||
},
|
||||
itemIsFinished() {
|
||||
if (this.booksInSeries) return this.seriesIsFinished
|
||||
return this.userProgress ? !!this.userProgress.isFinished : false
|
||||
|
||||
@@ -134,6 +134,14 @@ export default {
|
||||
text: this.$strings.LabelLibrarySortByProgress,
|
||||
value: 'progress'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelLibrarySortByProgressStarted,
|
||||
value: 'progress.createdAt'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelLibrarySortByProgressFinished,
|
||||
value: 'progress.finishedAt'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelRandomly,
|
||||
value: 'random'
|
||||
@@ -200,4 +208,4 @@ export default {
|
||||
.librarySortMenu {
|
||||
max-height: calc(100vh - 125px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -131,35 +131,26 @@
|
||||
</div>
|
||||
|
||||
<div class="grow py-2">
|
||||
<ui-dropdown :label="$strings.LabelSettingsDateFormat" v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-52" @input="(val) => updateSettingsKey('dateFormat', val)" />
|
||||
<ui-dropdown :label="$strings.LabelSettingsDateFormat" v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-72" @input="(val) => updateSettingsKey('dateFormat', val)" />
|
||||
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ dateExample }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grow py-2">
|
||||
<ui-dropdown :label="$strings.LabelSettingsTimeFormat" v-model="newServerSettings.timeFormat" :items="timeFormats" small class="max-w-52" @input="(val) => updateSettingsKey('timeFormat', val)" />
|
||||
<ui-dropdown :label="$strings.LabelSettingsTimeFormat" v-model="newServerSettings.timeFormat" :items="timeFormats" small class="max-w-72" @input="(val) => updateSettingsKey('timeFormat', val)" />
|
||||
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ timeExample }}</p>
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
<ui-dropdown :label="$strings.LabelLanguageDefaultServer" ref="langDropdown" v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-52" @input="updateServerLanguage" />
|
||||
<ui-dropdown :label="$strings.LabelLanguageDefaultServer" ref="langDropdown" v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-72" @input="updateServerLanguage" />
|
||||
</div>
|
||||
|
||||
<!-- old experimental features -->
|
||||
<!-- <div class="pt-4">
|
||||
<h2 class="font-semibold">{{ $strings.HeaderSettingsExperimental }}</h2>
|
||||
<div class="pt-4">
|
||||
<h2 class="font-semibold">{{ $strings.HeaderSettingsSecurity }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-2">
|
||||
<ui-toggle-switch labeledBy="settings-experimental-features" v-model="showExperimentalFeatures" />
|
||||
<ui-tooltip :text="$strings.LabelSettingsExperimentalFeaturesHelp">
|
||||
<p class="pl-4">
|
||||
<span id="settings-experimental-features">{{ $strings.LabelSettingsExperimentalFeatures }}</span>
|
||||
<a :aria-label="$strings.LabelSettingsExperimentalFeaturesHelp" href="https://github.com/advplyr/audiobookshelf/discussions/75" target="_blank">
|
||||
<span class="material-symbols icon-text">info</span>
|
||||
</a>
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</div> -->
|
||||
<div class="py-2">
|
||||
<ui-multi-select v-model="newServerSettings.allowedOrigins" :items="newServerSettings.allowedOrigins" :label="$strings.LabelCorsAllowed" class="max-w-72" @input="updateCorsOrigins" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</app-settings-content>
|
||||
@@ -323,6 +314,27 @@ export default {
|
||||
updateServerLanguage(val) {
|
||||
this.updateSettingsKey('language', val)
|
||||
},
|
||||
updateCorsOrigins(val) {
|
||||
const validOrigins = []
|
||||
const invalidOrigins = []
|
||||
|
||||
val.forEach((origin) => {
|
||||
const trimmedOrigin = origin.trim().toLowerCase()
|
||||
try {
|
||||
new URL(trimmedOrigin)
|
||||
validOrigins.push(trimmedOrigin)
|
||||
} catch {
|
||||
invalidOrigins.push(trimmedOrigin)
|
||||
}
|
||||
})
|
||||
|
||||
if (invalidOrigins.length > 0) {
|
||||
this.$toast.error(this.$strings.ToastInvalidUrls)
|
||||
}
|
||||
|
||||
this.newServerSettings.allowedOrigins = validOrigins
|
||||
this.updateSettingsKey('allowedOrigins', validOrigins)
|
||||
},
|
||||
updateSettingsKey(key, val) {
|
||||
if (key === 'scannerDisableWatcher') {
|
||||
this.newServerSettings.scannerDisableWatcher = val
|
||||
@@ -352,6 +364,7 @@ export default {
|
||||
initServerSettings() {
|
||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||
this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
|
||||
this.newServerSettings.allowedOrigins = [...(this.newServerSettings.allowedOrigins || [])]
|
||||
this.scannerEnableWatcher = !this.newServerSettings.scannerDisableWatcher
|
||||
|
||||
this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"HeaderSettingsExperimental": "Experimental Features",
|
||||
"HeaderSettingsGeneral": "General",
|
||||
"HeaderSettingsScanner": "Scanner",
|
||||
"HeaderSettingsSecurity": "Security",
|
||||
"HeaderSettingsWebClient": "Web Client",
|
||||
"HeaderSleepTimer": "Sleep Timer",
|
||||
"HeaderStatsLargestItems": "Largest Items",
|
||||
@@ -293,6 +294,7 @@
|
||||
"LabelContinueListening": "Continue Listening",
|
||||
"LabelContinueReading": "Continue Reading",
|
||||
"LabelContinueSeries": "Continue Series",
|
||||
"LabelCorsAllowed": "Allowed CORS Origins",
|
||||
"LabelCover": "Cover",
|
||||
"LabelCoverImageURL": "Cover Image URL",
|
||||
"LabelCoverProvider": "Cover Provider",
|
||||
@@ -374,6 +376,7 @@
|
||||
"LabelFilterByUser": "Filter by User",
|
||||
"LabelFindEpisodes": "Find Episodes",
|
||||
"LabelFinished": "Finished",
|
||||
"LabelFinishedDate": "Finished {0}",
|
||||
"LabelFolder": "Folder",
|
||||
"LabelFolders": "Folders",
|
||||
"LabelFontBold": "Bold",
|
||||
@@ -432,6 +435,8 @@
|
||||
"LabelLibraryItem": "Library Item",
|
||||
"LabelLibraryName": "Library Name",
|
||||
"LabelLibrarySortByProgress": "Progress Updated",
|
||||
"LabelLibrarySortByProgressFinished": "Finished Date",
|
||||
"LabelLibrarySortByProgressStarted": "Started Date",
|
||||
"LabelLimit": "Limit",
|
||||
"LabelLineSpacing": "Line spacing",
|
||||
"LabelListenAgain": "Listen Again",
|
||||
@@ -629,6 +634,7 @@
|
||||
"LabelStartTime": "Start Time",
|
||||
"LabelStarted": "Started",
|
||||
"LabelStartedAt": "Started At",
|
||||
"LabelStartedDate": "Started {0}",
|
||||
"LabelStatsAudioTracks": "Audio Tracks",
|
||||
"LabelStatsAuthors": "Authors",
|
||||
"LabelStatsBestDay": "Best Day",
|
||||
@@ -1034,6 +1040,7 @@
|
||||
"ToastInvalidImageUrl": "Invalid image URL",
|
||||
"ToastInvalidMaxEpisodesToDownload": "Invalid max episodes to download",
|
||||
"ToastInvalidUrl": "Invalid URL",
|
||||
"ToastInvalidUrls": "One or more URLs are invalid",
|
||||
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||
"ToastItemDeletedFailed": "Failed to delete item",
|
||||
"ToastItemDeletedSuccess": "Deleted item",
|
||||
|
||||
+2
-2
@@ -240,8 +240,8 @@ class Server {
|
||||
* Running in development allows cors to allow testing the mobile apps in the browser
|
||||
* or env variable ALLOW_CORS = '1'
|
||||
*/
|
||||
if (global.AllowCors || Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/)) {
|
||||
const allowedOrigins = ['capacitor://localhost', 'http://localhost']
|
||||
if (global.AllowCors || Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/) || global.ServerSettings.allowedOrigins?.length) {
|
||||
const allowedOrigins = ['capacitor://localhost', 'http://localhost', ...(global.ServerSettings.allowedOrigins ? global.ServerSettings.allowedOrigins : [])]
|
||||
if (global.AllowCors || Logger.isDev || allowedOrigins.some((o) => o === req.get('origin'))) {
|
||||
res.header('Access-Control-Allow-Origin', req.get('origin'))
|
||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
||||
|
||||
@@ -53,6 +53,7 @@ class ServerSettings {
|
||||
this.dateFormat = 'MM/dd/yyyy'
|
||||
this.timeFormat = 'HH:mm'
|
||||
this.language = 'en-us'
|
||||
this.allowedOrigins = []
|
||||
|
||||
this.logLevel = Logger.logLevel
|
||||
|
||||
@@ -120,6 +121,7 @@ class ServerSettings {
|
||||
this.dateFormat = settings.dateFormat || 'MM/dd/yyyy'
|
||||
this.timeFormat = settings.timeFormat || 'HH:mm'
|
||||
this.language = settings.language || 'en-us'
|
||||
this.allowedOrigins = settings.allowedOrigins || []
|
||||
this.logLevel = settings.logLevel || Logger.logLevel
|
||||
this.version = settings.version || null
|
||||
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
||||
@@ -231,6 +233,7 @@ class ServerSettings {
|
||||
dateFormat: this.dateFormat,
|
||||
timeFormat: this.timeFormat,
|
||||
language: this.language,
|
||||
allowedOrigins: this.allowedOrigins,
|
||||
logLevel: this.logLevel,
|
||||
version: this.version,
|
||||
buildNumber: this.buildNumber,
|
||||
|
||||
@@ -289,7 +289,11 @@ module.exports = {
|
||||
const nullDir = sortDesc ? 'DESC NULLS FIRST' : 'ASC NULLS LAST'
|
||||
return [[Sequelize.literal(`CAST(\`series.bookSeries.sequence\` AS FLOAT) ${nullDir}`)]]
|
||||
} else if (sortBy === 'progress') {
|
||||
return [[Sequelize.literal('mediaProgresses.updatedAt'), dir]]
|
||||
return [[Sequelize.literal(`mediaProgresses.updatedAt ${dir} NULLS LAST`)]]
|
||||
} else if (sortBy === 'progress.createdAt') {
|
||||
return [[Sequelize.literal(`mediaProgresses.createdAt ${dir} NULLS LAST`)]]
|
||||
} else if (sortBy === 'progress.finishedAt') {
|
||||
return [[Sequelize.literal(`mediaProgresses.finishedAt ${dir} NULLS LAST`)]]
|
||||
} else if (sortBy === 'random') {
|
||||
return [Database.sequelize.random()]
|
||||
}
|
||||
@@ -519,7 +523,7 @@ module.exports = {
|
||||
}
|
||||
bookIncludes.push({
|
||||
model: Database.mediaProgressModel,
|
||||
attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'],
|
||||
attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt', 'createdAt', 'finishedAt'],
|
||||
where: mediaProgressWhere,
|
||||
required: false
|
||||
})
|
||||
@@ -530,10 +534,10 @@ module.exports = {
|
||||
}
|
||||
|
||||
// When sorting by progress but not filtering by progress, include media progresses
|
||||
if (filterGroup !== 'progress' && sortBy === 'progress') {
|
||||
if (filterGroup !== 'progress' && ['progress.createdAt', 'progress.finishedAt', 'progress'].includes(sortBy)) {
|
||||
bookIncludes.push({
|
||||
model: Database.mediaProgressModel,
|
||||
attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'],
|
||||
attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt', 'createdAt', 'finishedAt'],
|
||||
where: {
|
||||
userId: user.id
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user