-
arrow_back_ios
+
+
+
+ arrow_forward_ios
+
+
+
![]()
-
-
- arrow_forward_ios
-
@@ -44,11 +57,10 @@ import { Archive } from 'libarchive.js/main.js'
Archive.init({
workerUrl: '/libarchive/worker-bundle.js'
})
-// Archive.init()
export default {
props: {
- src: String
+ url: String
},
data() {
return {
@@ -59,19 +71,24 @@ export default {
page: 0,
numPages: 0,
showPageMenu: false,
+ showInfoMenu: false,
loadTimeout: null,
- loadedFirstPage: false
+ loadedFirstPage: false,
+ comicMetadata: null
}
},
watch: {
- src: {
+ url: {
immediate: true,
- handler(newVal) {
+ handler() {
this.extract()
}
}
},
computed: {
+ comicMetadataKeys() {
+ return this.comicMetadata ? Object.keys(this.comicMetadata) : []
+ },
canGoNext() {
return this.page < this.numPages - 1
},
@@ -82,12 +99,13 @@ export default {
methods: {
clickOutside() {
if (this.showPageMenu) this.showPageMenu = false
+ if (this.showInfoMenu) this.showInfoMenu = false
},
- goNextPage() {
+ next() {
if (!this.canGoNext) return
this.setPage(this.page + 1)
},
- goPrevPage() {
+ prev() {
if (!this.canGoPrev) return
this.setPage(this.page - 1)
},
@@ -126,9 +144,9 @@ export default {
},
async extract() {
this.loading = true
- console.log('Extracting', this.src)
+ console.log('Extracting', this.url)
- var buff = await this.$axios.$get(this.src, {
+ var buff = await this.$axios.$get(this.url, {
responseType: 'blob'
})
const archive = await Archive.open(buff)
@@ -136,6 +154,9 @@ export default {
var filenames = Object.keys(this.filesObject)
this.parseFilenames(filenames)
+ var xmlFile = filenames.find((f) => (Path.extname(f) || '').toLowerCase() === '.xml')
+ if (xmlFile) await this.extractXmlFile(xmlFile)
+
this.numPages = this.pages.length
if (this.pages.length) {
@@ -147,6 +168,23 @@ export default {
this.loading = false
}
},
+ async extractXmlFile(filename) {
+ console.log('extracting xml filename', filename)
+ try {
+ var file = await this.filesObject[filename].extract()
+ var reader = new FileReader()
+ reader.onload = (e) => {
+ this.comicMetadata = this.$xmlToJson(e.target.result)
+ console.log('Metadata', this.comicMetadata)
+ }
+ reader.onerror = (e) => {
+ console.error(e)
+ }
+ reader.readAsText(file)
+ } catch (error) {
+ console.error(error)
+ }
+ },
parseImageFilename(filename) {
var basename = Path.basename(filename, Path.extname(filename))
var numbersinpath = basename.match(/\d{1,4}/g)
@@ -177,30 +215,10 @@ export default {
orderedImages = orderedImages.concat(noNumImages.map((i) => i.filename))
this.pages = orderedImages
- },
- keyUp(e) {
- if ((e.keyCode || e.which) == 37) {
- this.goPrevPage()
- } else if ((e.keyCode || e.which) == 39) {
- this.goNextPage()
- } else if ((e.keyCode || e.which) == 27) {
- this.unregisterListeners()
- this.$emit('close')
- }
- },
- registerListeners() {
- document.addEventListener('keyup', this.keyUp)
- },
- unregisterListeners() {
- document.removeEventListener('keyup', this.keyUp)
}
},
- mounted() {
- this.registerListeners()
- },
- beforeDestroy() {
- this.unregisterListeners()
- }
+ mounted() {},
+ beforeDestroy() {}
}
@@ -213,7 +231,8 @@ export default {
margin: auto;
}
.comicwrapper {
- width: calc(100vw - 300px);
+ width: 100vw;
height: calc(100vh - 40px);
+ margin-top: 20px;
}
\ No newline at end of file
diff --git a/client/components/readers/EpubReader.vue b/client/components/readers/EpubReader.vue
new file mode 100644
index 00000000..9c19c3ec
--- /dev/null
+++ b/client/components/readers/EpubReader.vue
@@ -0,0 +1,129 @@
+
+
+
+
+ chevron_left
+
+
+
+ chevron_right
+
+
+
+
+
+
diff --git a/client/components/readers/MobiReader.vue b/client/components/readers/MobiReader.vue
new file mode 100644
index 00000000..e19a11f1
--- /dev/null
+++ b/client/components/readers/MobiReader.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/components/app/PdfReader.vue b/client/components/readers/PdfReader.vue
similarity index 55%
rename from client/components/app/PdfReader.vue
rename to client/components/readers/PdfReader.vue
index 718b1240..09a03610 100644
--- a/client/components/app/PdfReader.vue
+++ b/client/components/readers/PdfReader.vue
@@ -1,23 +1,31 @@
-
+
+
+
+
+ arrow_forward_ios
+
+
+
+
+
{{ page }} / {{ numPages }}
+
+
-
- arrow_back_ios
-
{{ Math.floor(loadedRatio * 100) }}%
-
-
-
-
-
+
@@ -29,7 +37,7 @@ export default {
pdf
},
props: {
- src: String
+ url: String
},
data() {
return {
@@ -57,11 +65,11 @@ export default {
numPagesLoaded(e) {
this.numPages = e
},
- goPrevPage() {
+ prev() {
if (this.page <= 1) return
this.page--
},
- goNextPage() {
+ next() {
if (this.page >= this.numPages) return
this.page++
},
diff --git a/client/components/readers/Reader.vue b/client/components/readers/Reader.vue
new file mode 100644
index 00000000..bcb3a7c0
--- /dev/null
+++ b/client/components/readers/Reader.vue
@@ -0,0 +1,166 @@
+
+
+
+ close
+
+
+
+
{{ abTitle }}
+
by {{ abAuthor }}
+
+
+
+
+
{{ ebookType }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index 02bbe48d..7d960aac 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -7,7 +7,7 @@
-
+
diff --git a/client/package.json b/client/package.json
index b9307743..7f936969 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
- "version": "1.4.11",
+ "version": "1.4.12",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {
diff --git a/client/pages/audiobook/_id/index.vue b/client/pages/audiobook/_id/index.vue
index 4c7f33ca..051dc90e 100644
--- a/client/pages/audiobook/_id/index.vue
+++ b/client/pages/audiobook/_id/index.vue
@@ -24,11 +24,6 @@
{{ seriesText }}
-
Narrated By
@@ -81,10 +76,6 @@
warning_amber
Book has no audio tracks but has valid ebook files. The e-reader is experimental and can be turned on in config.
-
-
warning_amber
-
Book has valid ebook files, but the experimental e-reader currently only supports epub files.
-
Your Progress: {{ Math.round(progressPercent * 100) }}%
@@ -152,8 +143,6 @@
-
-
@@ -321,14 +310,10 @@ export default {
ebooks() {
return this.audiobook.ebooks
},
- showEpubAlert() {
- return this.ebooks.length && !this.numEbooks && !this.tracks.length
- },
showExperimentalReadAlert() {
return !this.tracks.length && this.ebooks.length && !this.showExperimentalFeatures
},
numEbooks() {
- // Number of currently supported for reading ebooks, not all ebooks
return this.audiobook.numEbooks
},
userToken() {
diff --git a/client/plugins/axios.js b/client/plugins/axios.js
index f6f7c548..88468b2a 100644
--- a/client/plugins/axios.js
+++ b/client/plugins/axios.js
@@ -1,5 +1,9 @@
export default function ({ $axios, store }) {
$axios.onRequest(config => {
+ if (!config.url) {
+ console.error('Axios request invalid config', config)
+ return
+ }
if (config.url.startsWith('http:') || config.url.startsWith('https:')) {
return
}
diff --git a/client/plugins/init.client.js b/client/plugins/init.client.js
index bc3df5cb..924b13b0 100644
--- a/client/plugins/init.client.js
+++ b/client/plugins/init.client.js
@@ -127,6 +127,19 @@ Vue.prototype.$sanitizeFilename = (input, replacement = '') => {
return sanitized
}
+
+function xmlToJson(xml) {
+ const json = {};
+ for (const res of xml.matchAll(/(?:<(\w*)(?:\s[^>]*)*>)((?:(?!<\1).)*)(?:<\/\1>)|<(\w*)(?:\s*)*\/>/gm)) {
+ const key = res[1] || res[3];
+ const value = res[2] && xmlToJson(res[2]);
+ json[key] = ((value && Object.keys(value).length) ? value : res[2]) || null;
+
+ }
+ return json;
+}
+Vue.prototype.$xmlToJson = xmlToJson
+
const encode = (text) => encodeURIComponent(Buffer.from(text).toString('base64'))
Vue.prototype.$encode = encode
const decode = (text) => Buffer.from(decodeURIComponent(text), 'base64').toString()
diff --git a/package.json b/package.json
index 6ebb9b8b..8861e419 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
- "version": "1.4.11",
+ "version": "1.4.12",
"description": "Self-hosted audiobook server for managing and playing audiobooks",
"main": "index.js",
"scripts": {
diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js
index f1948a64..0f41a9ec 100644
--- a/server/objects/Audiobook.js
+++ b/server/objects/Audiobook.js
@@ -127,22 +127,6 @@ class Audiobook {
return this.otherFiles.filter(file => file.filetype === 'ebook')
}
- get hasEpub() {
- return this.ebooks.find(file => file.ext === '.epub')
- }
-
- get hasMobi() {
- return this.ebooks.find(file => file.ext === '.mobi' || file.ext === '.azw3')
- }
-
- get hasPdf() {
- return this.ebooks.find(file => file.ext === '.pdf')
- }
-
- get hasComic() {
- return this.ebooks.find(file => file.ext === '.cbr' || file.ext === '.cbz')
- }
-
get hasMissingIno() {
return !this.ino || this._audioFiles.find(abf => !abf.ino) || this._otherFiles.find(f => !f.ino) || this._tracks.find(t => !t.ino)
}
@@ -214,7 +198,7 @@ class Audiobook {
hasInvalidParts: this.invalidParts ? this.invalidParts.length : 0,
// numEbooks: this.ebooks.length,
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
- numEbooks: (this.hasEpub || this.hasMobi || this.hasPdf || this.hasComic) ? 1 : 0,
+ numEbooks: this.ebooks.length,
numTracks: this.tracks.length,
chapters: this.chapters || [],
isMissing: !!this.isMissing,
@@ -241,7 +225,7 @@ class Audiobook {
audioFiles: this._audioFiles.map(audioFile => audioFile.toJSON()),
otherFiles: this._otherFiles.map(otherFile => otherFile.toJSON()),
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
- numEbooks: (this.hasEpub || this.hasMobi || this.hasPdf || this.hasComic) ? 1 : 0,
+ numEbooks: this.ebooks.length,
numTracks: this.tracks.length,
tags: this.tags,
book: this.bookToJSON(),
diff --git a/server/utils/index.js b/server/utils/index.js
index 781fdcde..317b4492 100644
--- a/server/utils/index.js
+++ b/server/utils/index.js
@@ -29,26 +29,6 @@ const levenshteinDistance = (str1, str2, caseSensitive = false) => {
}
module.exports.levenshteinDistance = levenshteinDistance
-const cleanString = (str) => {
- if (!str) return ''
-
- // Now supporting all utf-8 characters, can remove this method in future
-
- // replace accented characters: https://stackoverflow.com/a/49901740/7431543
- // str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
-
- // const availableChars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
- // const cleanChar = (char) => availableChars.indexOf(char) < 0 ? '?' : char
-
- // var cleaned = ''
- // for (let i = 0; i < str.length; i++) {
- // cleaned += cleanChar(str[i])
- // }
-
- return cleaned.trim()
-}
-module.exports.cleanString = cleanString
-
module.exports.isObject = (val) => {
return val !== null && typeof val === 'object'
}