mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-06-20 20:05:44 +02:00
Add:iOS offline ebooks
This commit is contained in:
parent
8b39ba9c92
commit
147f89f870
13 changed files with 157 additions and 26 deletions
|
@ -244,12 +244,13 @@ export default {
|
|||
async extract() {
|
||||
this.loading = true
|
||||
|
||||
var buff = await this.$axios.$get(this.url, {
|
||||
const buff = await this.$axios.$get(this.url, {
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.userToken}`
|
||||
}
|
||||
})
|
||||
|
||||
const archive = await Archive.open(buff)
|
||||
const originalFilesObject = await archive.getFilesObject()
|
||||
// to support images in subfolders we need to flatten the object
|
||||
|
|
|
@ -284,7 +284,7 @@ export default {
|
|||
|
||||
/** @type {EpubReader} */
|
||||
const reader = this
|
||||
|
||||
console.log('initEpub', reader.url)
|
||||
/** @type {ePub.Book} */
|
||||
reader.book = new ePub(reader.url, {
|
||||
width: window.innerWidth,
|
||||
|
|
|
@ -3,11 +3,19 @@
|
|||
<!-- toolbar -->
|
||||
<div class="h-32 pt-10 w-full px-2 fixed top-0 left-0 z-30 transition-transform bg-bg text-white" :class="showingToolbar ? 'translate-y-0' : '-translate-y-32'" @touchstart.stop @mousedown.stop @touchend.stop @mouseup.stop>
|
||||
<div class="flex items-center mb-2">
|
||||
<button type="button" class="inline-flex mx-2" @click.stop="show = false"><span class="material-icons-outlined text-3xl text-white">chevron_left</span></button>
|
||||
<button type="button" class="inline-flex mx-2" @click.stop="show = false">
|
||||
<span class="material-icons-outlined text-3xl text-white">chevron_left</span>
|
||||
</button>
|
||||
<div class="flex-grow" />
|
||||
<button v-if="isComic || isEpub" type="button" class="inline-flex mx-2" @click.stop="clickTOCBtn"><span class="material-icons-outlined text-2xl text-white">format_list_bulleted</span></button>
|
||||
<button v-if="isEpub" type="button" class="inline-flex mx-2" @click.stop="clickSettingsBtn"><span class="material-icons text-2xl text-white">settings</span></button>
|
||||
<button v-if="comicHasMetadata" type="button" class="inline-flex mx-2" @click.stop="clickMetadataBtn"><span class="material-icons text-2xl text-white">more</span></button>
|
||||
<button v-if="isComic || isEpub" type="button" class="inline-flex mx-2" @click.stop="clickTOCBtn">
|
||||
<span class="material-icons-outlined text-2xl text-white">format_list_bulleted</span>
|
||||
</button>
|
||||
<button v-if="isEpub" type="button" class="inline-flex mx-2" @click.stop="clickSettingsBtn">
|
||||
<span class="material-icons text-2xl text-white">settings</span>
|
||||
</button>
|
||||
<button v-if="comicHasMetadata" type="button" class="inline-flex mx-2" @click.stop="clickMetadataBtn">
|
||||
<span class="material-icons text-2xl text-white">more</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-center truncate">{{ title }}</p>
|
||||
|
@ -110,6 +118,8 @@ export default {
|
|||
this.comicHasMetadata = false
|
||||
this.registerListeners()
|
||||
this.hideToolbar()
|
||||
|
||||
console.log('showReader for ebookFile', JSON.stringify(this.ebookFile))
|
||||
} else {
|
||||
this.unregisterListeners()
|
||||
this.$showHideStatusBar(true)
|
||||
|
@ -196,7 +206,7 @@ export default {
|
|||
return this.ebookFormat == 'cbz' || this.ebookFormat == 'cbr'
|
||||
},
|
||||
isLocal() {
|
||||
return !!this.ebookFile?.isLocal
|
||||
return !!this.ebookFile?.isLocal || !!this.ebookFile?.localFileId
|
||||
},
|
||||
localContentUrl() {
|
||||
return this.ebookFile?.contentUrl
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
4D66B954282EE87C008272D4 /* AbsDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B953282EE87C008272D4 /* AbsDownloader.swift */; };
|
||||
4D66B956282EE951008272D4 /* AbsFileSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B955282EE951008272D4 /* AbsFileSystem.m */; };
|
||||
4D66B958282EEA14008272D4 /* AbsFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B957282EEA14008272D4 /* AbsFileSystem.swift */; };
|
||||
4D91EEC62A40F28D004807ED /* EBookFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D91EEC52A40F28D004807ED /* EBookFile.swift */; };
|
||||
4DF74912287105C600AC7814 /* DeviceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF74911287105C600AC7814 /* DeviceSettings.swift */; };
|
||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
|
||||
|
@ -97,6 +98,7 @@
|
|||
4D66B955282EE951008272D4 /* AbsFileSystem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsFileSystem.m; sourceTree = "<group>"; };
|
||||
4D66B957282EEA14008272D4 /* AbsFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsFileSystem.swift; sourceTree = "<group>"; };
|
||||
4D8D412C26E187E400BA5F0D /* App-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "App-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
4D91EEC52A40F28D004807ED /* EBookFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EBookFile.swift; sourceTree = "<group>"; };
|
||||
4DF74911287105C600AC7814 /* DeviceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSettings.swift; sourceTree = "<group>"; };
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
504EC3041FED79650016851F /* Audiobookshelf.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Audiobookshelf.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -299,6 +301,7 @@
|
|||
E9D5505928AC1C4500C746DD /* Folder.swift */,
|
||||
E9D5505B28AC1C6200C746DD /* LibraryFile.swift */,
|
||||
E9D5505D28AC1C8500C746DD /* MediaProgress.swift */,
|
||||
4D91EEC52A40F28D004807ED /* EBookFile.swift */,
|
||||
);
|
||||
path = server;
|
||||
sourceTree = "<group>";
|
||||
|
@ -545,6 +548,7 @@
|
|||
E9D5506028AC1CA900C746DD /* PlaybackMetadata.swift in Sources */,
|
||||
E9D5504828AC1A7A00C746DD /* MediaType.swift in Sources */,
|
||||
E9D5504E28AC1B0700C746DD /* AudioFile.swift in Sources */,
|
||||
4D91EEC62A40F28D004807ED /* EBookFile.swift in Sources */,
|
||||
E9DFCBFB28C28F4A00B36356 /* AudioPlayerSleepTimer.swift in Sources */,
|
||||
E9D5505428AC1B7900C746DD /* AudioTrack.swift in Sources */,
|
||||
E9D5505C28AC1C6200C746DD /* LibraryFile.swift in Sources */,
|
||||
|
|
|
@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
// Override point for customization after application launch.
|
||||
|
||||
let configuration = Realm.Configuration(
|
||||
schemaVersion: 9,
|
||||
schemaVersion: 11,
|
||||
migrationBlock: { [weak self] migration, oldSchemaVersion in
|
||||
if (oldSchemaVersion < 1) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)")
|
||||
|
|
|
@ -265,7 +265,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
|||
// Handle the different media type downloads
|
||||
switch item.mediaType {
|
||||
case "book":
|
||||
guard item.media?.tracks.count ?? 0 > 0 else { throw LibraryItemDownloadError.noTracks }
|
||||
guard item.media?.tracks.count ?? 0 > 0 || item.media?.ebookFile != nil else { throw LibraryItemDownloadError.noTracks }
|
||||
item.media?.tracks.forEach { t in tracks.append(AudioTrack.detachCopy(of: t)!) }
|
||||
case "podcast":
|
||||
guard let episode = episode else { throw LibraryItemDownloadError.podcastEpisodeNotFound }
|
||||
|
@ -285,6 +285,12 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
|||
tasks.append(task)
|
||||
}
|
||||
|
||||
if (item.media?.ebookFile != nil) {
|
||||
let task = try startLibraryItemEbookDownload(downloadItemId: downloadItem.id!, item: item, ebookFile: item.media!.ebookFile!)
|
||||
downloadItem.downloadItemParts.append(task.part)
|
||||
tasks.append(task)
|
||||
}
|
||||
|
||||
// Also download the cover
|
||||
if item.media?.coverPath != nil && !(item.media?.coverPath!.isEmpty ?? true) {
|
||||
if let task = try? startLibraryItemCoverDownload(downloadItemId: downloadItem.id!, item: item) {
|
||||
|
@ -318,7 +324,22 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
|||
let localUrl = "\(itemDirectory)/\(filename)"
|
||||
|
||||
let task = session.downloadTask(with: serverUrl)
|
||||
let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: track.title ?? "Unknown", serverPath: Store.serverConfig!.address, audioTrack: track, episode: episode, size: track.metadata?.size ?? 0)
|
||||
let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: track.title ?? "Unknown", serverPath: Store.serverConfig!.address, audioTrack: track, episode: episode, ebookFile: nil, size: track.metadata?.size ?? 0)
|
||||
|
||||
// Store the id on the task so the download item can be pulled from the database later
|
||||
task.taskDescription = part.id
|
||||
|
||||
return DownloadItemPartTask(part: part, task: task)
|
||||
}
|
||||
|
||||
private func startLibraryItemEbookDownload(downloadItemId: String, item: LibraryItem, ebookFile: EBookFile) throws -> DownloadItemPartTask {
|
||||
let filename = ebookFile.metadata?.filename ?? "ebook.\(ebookFile.ebookFormat)"
|
||||
let serverPath = "/api/items/\(item.id)/file/\(ebookFile.ino)/download"
|
||||
let itemDirectory = try createLibraryItemFileDirectory(item: item)
|
||||
let localUrl = "\(itemDirectory)/\(filename)"
|
||||
|
||||
let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: filename, serverPath: serverPath, audioTrack: nil, episode: nil, ebookFile: ebookFile, size: ebookFile.metadata?.size ?? 0)
|
||||
let task = session.downloadTask(with: part.downloadURL!)
|
||||
|
||||
// Store the id on the task so the download item can be pulled from the database later
|
||||
task.taskDescription = part.id
|
||||
|
@ -337,7 +358,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
|||
$0.metadata?.path == item.media?.coverPath
|
||||
})
|
||||
|
||||
let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: "cover", serverPath: serverPath, audioTrack: nil, episode: nil, size: coverLibraryFile?.metadata?.size ?? 0)
|
||||
let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: "cover", serverPath: serverPath, audioTrack: nil, episode: nil, ebookFile: nil, size: coverLibraryFile?.metadata?.size ?? 0)
|
||||
let task = session.downloadTask(with: part.downloadURL!)
|
||||
|
||||
// Store the id on the task so the download item can be pulled from the database later
|
||||
|
|
|
@ -17,6 +17,7 @@ class DownloadItemPart: Object, Codable {
|
|||
@Persisted var serverPath: String?
|
||||
@Persisted var audioTrack: AudioTrack?
|
||||
@Persisted var episode: PodcastEpisode?
|
||||
@Persisted var ebookFile: EBookFile?
|
||||
@Persisted var completed: Bool = false
|
||||
@Persisted var moved: Bool = false
|
||||
@Persisted var failed: Bool = false
|
||||
|
@ -63,7 +64,7 @@ class DownloadItemPart: Object, Codable {
|
|||
}
|
||||
|
||||
extension DownloadItemPart {
|
||||
convenience init(downloadItemId: String, filename: String, destination: String, itemTitle: String, serverPath: String, audioTrack: AudioTrack?, episode: PodcastEpisode?, size: Double) {
|
||||
convenience init(downloadItemId: String, filename: String, destination: String, itemTitle: String, serverPath: String, audioTrack: AudioTrack?, episode: PodcastEpisode?, ebookFile: EBookFile?, size: Double) {
|
||||
self.init()
|
||||
|
||||
self.id = destination.toBase64()
|
||||
|
@ -74,6 +75,7 @@ extension DownloadItemPart {
|
|||
self.serverPath = serverPath
|
||||
self.audioTrack = AudioTrack.detachCopy(of: audioTrack)
|
||||
self.episode = PodcastEpisode.detachCopy(of: episode)
|
||||
self.ebookFile = EBookFile.detachCopy(of: ebookFile)
|
||||
|
||||
let config = Store.serverConfig!
|
||||
var downloadUrl = "\(config.address)\(serverPath)?token=\(config.token)"
|
||||
|
@ -101,6 +103,6 @@ extension DownloadItemPart {
|
|||
}
|
||||
|
||||
func mimeType() -> String? {
|
||||
audioTrack?.mimeType ?? episode?.audioTrack?.mimeType
|
||||
audioTrack?.mimeType ?? episode?.audioTrack?.mimeType ?? ebookFile?.mimeType()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,12 @@ extension LocalLibraryItem {
|
|||
for i in fromMedia.tracks.indices {
|
||||
_ = fromMedia.tracks[i].setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: i)
|
||||
}
|
||||
if fromMedia.ebookFile != nil {
|
||||
let ebookLocalFile = files.first(where: { $0.filename == fromMedia.ebookFile?.metadata?.filename ?? "" })
|
||||
if ebookLocalFile != nil {
|
||||
_ = fromMedia.ebookFile?.setLocalInfo(localFile: ebookLocalFile!)
|
||||
}
|
||||
}
|
||||
} else if ( self.isPodcast ) {
|
||||
let episodes = List<PodcastEpisode>()
|
||||
for episode in fromMedia.episodes {
|
||||
|
|
78
ios/App/Shared/models/server/EBookFile.swift
Normal file
78
ios/App/Shared/models/server/EBookFile.swift
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// EBookFile.swift
|
||||
// Audiobookshelf
|
||||
//
|
||||
// Created by Advplyr on 6/19/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RealmSwift
|
||||
|
||||
class EBookFile: EmbeddedObject, Codable {
|
||||
@Persisted var ino: String = ""
|
||||
@Persisted var metadata: FileMetadata?
|
||||
@Persisted var ebookFormat: String
|
||||
@Persisted var contentUrl: String?
|
||||
@Persisted var localFileId: String?
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case ino, metadata, ebookFormat, contentUrl, localFileId
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
super.init()
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
ino = try values.decode(String.self, forKey: .ino)
|
||||
metadata = try values.decode(FileMetadata.self, forKey: .metadata)
|
||||
ebookFormat = try values.decode(String.self, forKey: .ebookFormat)
|
||||
contentUrl = try? values.decode(String.self, forKey: .contentUrl)
|
||||
localFileId = try? values.decodeIfPresent(String.self, forKey: .localFileId)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(ino, forKey: .ino)
|
||||
try container.encode(metadata, forKey: .metadata)
|
||||
try container.encode(ebookFormat, forKey: .ebookFormat)
|
||||
try container.encode(contentUrl, forKey: .contentUrl)
|
||||
try container.encode(localFileId, forKey: .localFileId)
|
||||
}
|
||||
}
|
||||
|
||||
extension EBookFile {
|
||||
func setLocalInfo(localFile: LocalFile) -> Bool {
|
||||
self.localFileId = localFile.id
|
||||
self.contentUrl = localFile.contentUrl
|
||||
return false
|
||||
}
|
||||
|
||||
func getLocalFile() -> LocalFile? {
|
||||
guard let localFileId = self.localFileId else { return nil }
|
||||
return Database.shared.getLocalFile(localFileId: localFileId)
|
||||
}
|
||||
|
||||
func mimeType() -> String? {
|
||||
var mimeType = ""
|
||||
switch ebookFormat {
|
||||
case "epub":
|
||||
mimeType = "application/epub+zip"
|
||||
case "pdf":
|
||||
mimeType = "application/pdf"
|
||||
case "mobi":
|
||||
mimeType = "application/x-mobipocket-ebook"
|
||||
case "azw3":
|
||||
mimeType = "application/vnd.amazon.mobi8-ebook"
|
||||
case "cbr":
|
||||
mimeType = "application/vnd.comicbook-rar"
|
||||
case "cbz":
|
||||
mimeType = "application/vnd.comicbook+zip"
|
||||
default:
|
||||
mimeType = "application/epub+zip"
|
||||
}
|
||||
return mimeType
|
||||
}
|
||||
}
|
|
@ -16,12 +16,14 @@ class MediaProgress: EmbeddedObject, Codable {
|
|||
@Persisted var progress: Double = 0
|
||||
@Persisted var currentTime: Double = 0
|
||||
@Persisted var isFinished: Bool = false
|
||||
@Persisted var ebookLocation: String?
|
||||
@Persisted var ebookProgress: Double?
|
||||
@Persisted var lastUpdate: Double = 0
|
||||
@Persisted var startedAt: Double = 0
|
||||
@Persisted var finishedAt: Double?
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case id, libraryItemId, episodeId, duration, progress, currentTime, isFinished, lastUpdate, startedAt, finishedAt
|
||||
case id, libraryItemId, episodeId, duration, progress, currentTime, isFinished, ebookLocation, ebookProgress, lastUpdate, startedAt, finishedAt
|
||||
}
|
||||
|
||||
override init() {
|
||||
|
@ -37,6 +39,8 @@ class MediaProgress: EmbeddedObject, Codable {
|
|||
progress = try values.doubleOrStringDecoder(key: .progress)
|
||||
currentTime = try values.doubleOrStringDecoder(key: .currentTime)
|
||||
isFinished = try values.decode(Bool.self, forKey: .isFinished)
|
||||
ebookLocation = try values.decodeIfPresent(String.self, forKey: .ebookLocation)
|
||||
ebookProgress = try values.doubleOrStringDecoder(key: .ebookProgress)
|
||||
lastUpdate = try values.doubleOrStringDecoder(key: .lastUpdate)
|
||||
startedAt = try values.doubleOrStringDecoder(key: .startedAt)
|
||||
finishedAt = try? values.doubleOrStringDecoder(key: .finishedAt)
|
||||
|
@ -51,6 +55,8 @@ class MediaProgress: EmbeddedObject, Codable {
|
|||
try container.encode(progress, forKey: .progress)
|
||||
try container.encode(currentTime, forKey: .currentTime)
|
||||
try container.encode(isFinished, forKey: .isFinished)
|
||||
try container.encode(ebookLocation, forKey: .ebookLocation)
|
||||
try container.encode(ebookProgress, forKey: .ebookProgress)
|
||||
try container.encode(lastUpdate, forKey: .lastUpdate)
|
||||
try container.encode(startedAt, forKey: .startedAt)
|
||||
try container.encode(finishedAt, forKey: .finishedAt)
|
||||
|
|
|
@ -14,6 +14,7 @@ class MediaType: EmbeddedObject, Codable {
|
|||
@Persisted var coverPath: String?
|
||||
@Persisted var tags = List<String>()
|
||||
@Persisted var audioFiles = List<AudioFile>()
|
||||
@Persisted var ebookFile: EBookFile?
|
||||
@Persisted var chapters = List<Chapter>()
|
||||
@Persisted var tracks = List<AudioTrack>()
|
||||
@Persisted var size: Int?
|
||||
|
@ -22,7 +23,7 @@ class MediaType: EmbeddedObject, Codable {
|
|||
@Persisted var autoDownloadEpisodes: Bool?
|
||||
|
||||
private enum CodingKeys : String, CodingKey {
|
||||
case libraryItemId, metadata, coverPath, tags, audioFiles, chapters, tracks, size, duration, episodes, autoDownloadEpisodes
|
||||
case libraryItemId, metadata, coverPath, tags, audioFiles, ebookFile, chapters, tracks, size, duration, episodes, autoDownloadEpisodes
|
||||
}
|
||||
|
||||
override init() {
|
||||
|
@ -41,6 +42,7 @@ class MediaType: EmbeddedObject, Codable {
|
|||
if let fileList = try? values.decode([AudioFile].self, forKey: .audioFiles) {
|
||||
audioFiles.append(objectsIn: fileList)
|
||||
}
|
||||
ebookFile = try? values.decode(EBookFile.self, forKey: .ebookFile)
|
||||
if let chapterList = try? values.decode([Chapter].self, forKey: .chapters) {
|
||||
chapters.append(objectsIn: chapterList)
|
||||
}
|
||||
|
@ -62,6 +64,7 @@ class MediaType: EmbeddedObject, Codable {
|
|||
try container.encode(coverPath, forKey: .coverPath)
|
||||
try container.encode(Array(tags), forKey: .tags)
|
||||
try container.encode(Array(audioFiles), forKey: .audioFiles)
|
||||
try container.encode(ebookFile, forKey: .ebookFile)
|
||||
try container.encode(Array(chapters), forKey: .chapters)
|
||||
try container.encode(Array(tracks), forKey: .tracks)
|
||||
try container.encode(size, forKey: .size)
|
||||
|
|
|
@ -63,13 +63,13 @@
|
|||
</div>
|
||||
|
||||
<!-- metadata -->
|
||||
<div id="metadata" class="grid gap-2 my-2" style="">
|
||||
<div id="metadata" class="grid gap-2 my-2" style>
|
||||
<div v-if="podcastAuthor || (bookAuthors && bookAuthors.length)" class="text-white text-opacity-60 uppercase text-sm">Author</div>
|
||||
<div v-if="podcastAuthor" class="text-sm">{{ podcastAuthor }}</div>
|
||||
<div v-else-if="bookAuthors && bookAuthors.length" class="text-sm">
|
||||
<template v-for="(author, index) in bookAuthors">
|
||||
<nuxt-link :key="author.id" :to="`/bookshelf/library?filter=authors.${$encode(author.id)}`" class="underline">{{ author.name }}</nuxt-link
|
||||
><span :key="`${author.id}-comma`" v-if="index < bookAuthors.length - 1">, </span>
|
||||
<nuxt-link :key="author.id" :to="`/bookshelf/library?filter=authors.${$encode(author.id)}`" class="underline">{{ author.name }}</nuxt-link>
|
||||
<span :key="`${author.id}-comma`" v-if="index < bookAuthors.length - 1">,</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
@ -79,8 +79,8 @@
|
|||
<div v-if="series && series.length" class="text-white text-opacity-60 uppercase text-sm">Series</div>
|
||||
<div v-if="series && series.length" class="truncate text-sm">
|
||||
<template v-for="(series, index) in seriesList">
|
||||
<nuxt-link :key="series.id" :to="`/bookshelf/series/${series.id}`" class="underline">{{ series.text }}</nuxt-link
|
||||
><span :key="`${series.id}-comma`" v-if="index < seriesList.length - 1">, </span>
|
||||
<nuxt-link :key="series.id" :to="`/bookshelf/series/${series.id}`" class="underline">{{ series.text }}</nuxt-link>
|
||||
<span :key="`${series.id}-comma`" v-if="index < seriesList.length - 1">, </span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
@ -90,16 +90,16 @@
|
|||
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">{{ narrators.length === 1 ? 'Narrator' : 'Narrators' }}</div>
|
||||
<div v-if="narrators && narrators.length" class="truncate text-sm">
|
||||
<template v-for="(narrator, index) in narrators">
|
||||
<nuxt-link :key="narrator" :to="`/bookshelf/library?filter=narrators.${$encode(narrator)}`" class="underline">{{ narrator }}</nuxt-link
|
||||
><span :key="index" v-if="index < narrators.length - 1">, </span>
|
||||
<nuxt-link :key="narrator" :to="`/bookshelf/library?filter=narrators.${$encode(narrator)}`" class="underline">{{ narrator }}</nuxt-link>
|
||||
<span :key="index" v-if="index < narrators.length - 1">,</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="genres.length" class="text-white text-opacity-60 uppercase text-sm">{{ genres.length === 1 ? 'Genre' : 'Genres' }}</div>
|
||||
<div v-if="genres.length" class="truncate text-sm">
|
||||
<template v-for="(genre, index) in genres">
|
||||
<nuxt-link :key="genre" :to="`/bookshelf/library?filter=genres.${$encode(genre)}`" class="underline">{{ genre }}</nuxt-link
|
||||
><span :key="index" v-if="index < genres.length - 1">, </span>
|
||||
<nuxt-link :key="genre" :to="`/bookshelf/library?filter=genres.${$encode(genre)}`" class="underline">{{ genre }}</nuxt-link>
|
||||
<span :key="index" v-if="index < genres.length - 1">,</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
@ -359,7 +359,7 @@ export default {
|
|||
},
|
||||
showDownload() {
|
||||
if (this.isPodcast || this.hasLocal) return false
|
||||
return this.user && this.userCanDownload && (this.showPlay || (this.showRead && !this.isIos))
|
||||
return this.user && this.userCanDownload && (this.showPlay || this.showRead)
|
||||
},
|
||||
libraryFiles() {
|
||||
return this.libraryItem.libraryFiles || []
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default function ({ $axios, store }) {
|
||||
$axios.onRequest(config => {
|
||||
console.log('[Axios] Making request to ' + config.url)
|
||||
if (config.url.startsWith('http:') || config.url.startsWith('https:')) {
|
||||
if (config.url.startsWith('http:') || config.url.startsWith('https:') || config.url.startsWith('capacitor:')) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue