diff --git a/client/assets/app.css b/client/assets/app.css
index 2a000975..6decbb29 100644
--- a/client/assets/app.css
+++ b/client/assets/app.css
@@ -42,32 +42,17 @@
height: 8px;
}
-/* ::-webkit-scrollbar:horizontal { */
-/* height: 16px; */
-/* height: 24px;
-} */
/* Track */
::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0);
}
-/* ::-webkit-scrollbar-track:horizontal { */
-/* background: rgb(149, 119, 90); */
-/* background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%); */
-/* background: linear-gradient(180deg, rgb(117, 88, 60) 0%, rgb(65, 41, 17) 17%, rgb(71, 43, 15) 88%, rgb(3, 2, 1) 100%);
- box-shadow: 2px 14px 8px #111111aa;
-} */
/* Handle */
::-webkit-scrollbar-thumb {
background: #855620;
border-radius: 4px;
}
-/* ::-webkit-scrollbar-thumb:horizontal { */
-/* background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%); */
-/* box-shadow: 2px 14px 8px #111111aa;
- border-radius: 4px;
-} */
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #704922;
@@ -78,6 +63,13 @@
opacity: 0;
}
+.no-scroll {
+ -ms-overflow-style: none;
+ /* IE and Edge */
+ scrollbar-width: none;
+ /* Firefox */
+}
+
/* Chrome, Safari, Edge, Opera */
.no-spinner::-webkit-outer-spin-button,
.no-spinner::-webkit-inner-spin-button {
diff --git a/client/components/app/BookShelfRow.vue b/client/components/app/BookShelfRow.vue
index 8fa90ee8..54f455b0 100644
--- a/client/components/app/BookShelfRow.vue
+++ b/client/components/app/BookShelfRow.vue
@@ -199,10 +199,7 @@ export default {
scroll-behavior: smooth;
width: calc(100vw - 80px);
- /* background-color: rgb(214, 116, 36); */
background-image: var(--bookshelf-texture-img);
- /* background-position: center; */
- /* background-size: contain; */
background-repeat: repeat-x;
}
@media (max-width: 768px) {
@@ -213,9 +210,7 @@ export default {
.bookshelfDividerCategorized {
background: rgb(149, 119, 90);
- /* background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%); */
background: linear-gradient(180deg, rgb(122, 94, 68) 0%, rgb(92, 62, 31) 17%, rgb(82, 54, 26) 88%, rgba(71, 48, 25, 1) 100%);
- /* background: linear-gradient(180deg, rgb(114, 85, 59) 0%, rgb(73, 48, 22) 17%, rgb(71, 43, 15) 88%, rgb(61, 41, 20) 100%); */
box-shadow: 2px 14px 8px #111111aa;
}
diff --git a/client/components/modals/authors/EditModal.vue b/client/components/modals/authors/EditModal.vue
index 36d9b726..4ff04e8d 100644
--- a/client/components/modals/authors/EditModal.vue
+++ b/client/components/modals/authors/EditModal.vue
@@ -43,13 +43,13 @@
\ No newline at end of file
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index c7c170e8..450a1bb5 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -11,6 +11,7 @@
+
diff --git a/client/pages/author/_id.vue b/client/pages/author/_id.vue
new file mode 100644
index 00000000..c800669b
--- /dev/null
+++ b/client/pages/author/_id.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
{{ author.name }}
+
+
+ edit
+
+
+
+
Description
+
{{ author.description }}
+
+
+
+
+
+ {{ libraryItems.length }} Books
+
+
+
+
+
+ {{ series.name }}
+ Series
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/pages/library/_library/authors/index.vue b/client/pages/library/_library/authors/index.vue
index 34e819c1..485bff24 100644
--- a/client/pages/library/_library/authors/index.vue
+++ b/client/pages/library/_library/authors/index.vue
@@ -7,7 +7,8 @@
-
@@ -40,9 +40,7 @@ export default {
data() {
return {
loading: true,
- authors: [],
- showAuthorModal: false,
- selectedAuthor: null
+ authors: []
}
},
computed: {
@@ -51,6 +49,9 @@ export default {
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
+ },
+ selectedAuthor() {
+ return this.$store.state.globals.selectedAuthor
}
},
methods: {
@@ -68,7 +69,7 @@ export default {
},
authorUpdated(author) {
if (this.selectedAuthor && this.selectedAuthor.id === author.id) {
- this.selectedAuthor = author
+ this.$store.commit('globals/setSelectedAuthor', author)
}
this.authors = this.authors.map((au) => {
if (au.id === author.id) {
@@ -81,8 +82,7 @@ export default {
this.authors = this.authors.filter((au) => au.id !== author.id)
},
editAuthor(author) {
- this.selectedAuthor = author
- this.showAuthorModal = true
+ this.$store.commit('globals/showEditAuthorModal', author)
}
},
mounted() {
diff --git a/client/store/globals.js b/client/store/globals.js
index 2e6a1c5c..bbc383dd 100644
--- a/client/store/globals.js
+++ b/client/store/globals.js
@@ -6,8 +6,10 @@ export const state = () => ({
showUserCollectionsModal: false,
showEditCollectionModal: false,
showEditPodcastEpisode: false,
+ showEditAuthorModal: false,
selectedEpisode: null,
selectedCollection: null,
+ selectedAuthor: null,
showBookshelfTextureModal: false,
isCasting: false, // Actively casting
isChromecastInitialized: false // Script loaded
@@ -61,6 +63,16 @@ export const mutations = {
setShowBookshelfTextureModal(state, val) {
state.showBookshelfTextureModal = val
},
+ showEditAuthorModal(state, author) {
+ state.selectedAuthor = author
+ state.showEditAuthorModal = true
+ },
+ setShowEditAuthorModal(state, val) {
+ state.showEditAuthorModal = val
+ },
+ setSelectedAuthor(state, author) {
+ state.selectedAuthor = author
+ },
setChromecastInitialized(state, val) {
state.isChromecastInitialized = val
},
diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js
index 0da182ea..23b62ae7 100644
--- a/server/controllers/AuthorController.js
+++ b/server/controllers/AuthorController.js
@@ -1,11 +1,59 @@
const Logger = require('../Logger')
const { reqSupportsWebp } = require('../utils/index')
+const { createNewSortInstance } = require('fast-sort')
+const naturalSort = createNewSortInstance({
+ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
+})
class AuthorController {
constructor() { }
async findOne(req, res) {
- return res.json(req.author)
+ const include = (req.query.include || '').split(',')
+
+ const authorJson = req.author.toJSON()
+
+ // Used on author landing page to include library items and items grouped in series
+ if (include.includes('items')) {
+ authorJson.libraryItems = this.db.libraryItems.filter(li => {
+ return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id)
+ })
+
+ if (include.includes('series')) {
+ const seriesMap = {}
+ // Group items into series
+ authorJson.libraryItems.forEach((li) => {
+ if (li.media.metadata.series) {
+ li.media.metadata.series.forEach((series) => {
+
+ const itemWithSeries = li.toJSONMinified()
+ itemWithSeries.media.metadata.series = series
+
+ if (seriesMap[series.id]) {
+ seriesMap[series.id].items.push(itemWithSeries)
+ } else {
+ seriesMap[series.id] = {
+ id: series.id,
+ name: series.name,
+ items: [itemWithSeries]
+ }
+ }
+ })
+ }
+ })
+ // Sort series items
+ for (const key in seriesMap) {
+ seriesMap[key].items = naturalSort(seriesMap[key].items).asc(li => li.media.metadata.series.sequence)
+ }
+
+ authorJson.series = Object.values(seriesMap)
+ }
+
+ // Minify library items
+ authorJson.libraryItems = authorJson.libraryItems.map(li => li.toJSONMinified())
+ }
+
+ return res.json(authorJson)
}
async update(req, res) {
@@ -41,6 +89,7 @@ class AuthorController {
}).length
this.emitter('author_updated', req.author.toJSONExpanded(numBooks))
}
+
res.json({
author: req.author.toJSON(),
updated: hasUpdated