mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-03 19:07:00 -05:00 
			
		
		
		
	New filters using base64 strings, keyword filter
This commit is contained in:
		
							parent
							
								
									a66a84bd2d
								
							
						
					
					
						commit
						6f6a3f71b3
					
				@ -15,7 +15,14 @@
 | 
			
		||||
          <span class="material-icons">settings</span>
 | 
			
		||||
        </nuxt-link>
 | 
			
		||||
 | 
			
		||||
        <ui-menu :label="username" :items="menuItems" @action="menuAction" class="ml-5" />
 | 
			
		||||
        <nuxt-link to="/account" class="relative w-32 bg-fg border border-gray-500 rounded shadow-sm ml-5 pl-3 pr-10 py-2 text-left focus:outline-none sm:text-sm cursor-pointer hover:bg-bg hover:bg-opacity-40" aria-haspopup="listbox" aria-expanded="true">
 | 
			
		||||
          <span class="flex items-center">
 | 
			
		||||
            <span class="block truncate">{{ username }}</span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
 | 
			
		||||
            <span class="material-icons text-gray-100">person</span>
 | 
			
		||||
          </span>
 | 
			
		||||
        </nuxt-link>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div v-show="numAudiobooksSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
 | 
			
		||||
@ -35,17 +42,6 @@
 | 
			
		||||
export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      menuItems: [
 | 
			
		||||
        {
 | 
			
		||||
          value: 'account',
 | 
			
		||||
          text: 'Account',
 | 
			
		||||
          to: '/account'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          value: 'logout',
 | 
			
		||||
          text: 'Logout'
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      processingBatchDelete: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
@ -83,20 +79,6 @@ export default {
 | 
			
		||||
        this.$router.push('/')
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    logout() {
 | 
			
		||||
      this.$axios.$post('/logout').catch((error) => {
 | 
			
		||||
        console.error(error)
 | 
			
		||||
      })
 | 
			
		||||
      if (localStorage.getItem('token')) {
 | 
			
		||||
        localStorage.removeItem('token')
 | 
			
		||||
      }
 | 
			
		||||
      this.$router.push('/login')
 | 
			
		||||
    },
 | 
			
		||||
    menuAction(action) {
 | 
			
		||||
      if (action === 'logout') {
 | 
			
		||||
        this.logout()
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    cancelSelectionMode() {
 | 
			
		||||
      if (this.processingBatchDelete) return
 | 
			
		||||
      this.$store.commit('setSelectedAudiobooks', [])
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,10 @@
 | 
			
		||||
          <div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <div v-show="!groupedBooks.length" class="w-full py-16 text-center text-xl">
 | 
			
		||||
        <div class="py-4">No Audiobooks</div>
 | 
			
		||||
        <ui-btn v-if="filterBy !== 'all' || keywordFilter" @click="clearFilter">Clear Filter</ui-btn>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@ -38,10 +42,19 @@ export default {
 | 
			
		||||
      currFilterOrderKey: null,
 | 
			
		||||
      availableSizes: [60, 80, 100, 120, 140, 160, 180, 200, 220],
 | 
			
		||||
      selectedSizeIndex: 3,
 | 
			
		||||
      rowPaddingX: 40
 | 
			
		||||
      rowPaddingX: 40,
 | 
			
		||||
      keywordFilterTimeout: null
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    keywordFilter() {
 | 
			
		||||
      this.checkKeywordFilter()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    keywordFilter() {
 | 
			
		||||
      return this.$store.state.audiobooks.keywordFilter
 | 
			
		||||
    },
 | 
			
		||||
    userAudiobooks() {
 | 
			
		||||
      return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
 | 
			
		||||
    },
 | 
			
		||||
@ -65,9 +78,28 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    isSelectionMode() {
 | 
			
		||||
      return this.$store.getters['getNumAudiobooksSelected']
 | 
			
		||||
    },
 | 
			
		||||
    filterBy() {
 | 
			
		||||
      return this.$store.getters['user/getUserSetting']('filterBy')
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    clearFilter() {
 | 
			
		||||
      this.$store.commit('audiobooks/setKeywordFilter', null)
 | 
			
		||||
      if (this.filterBy !== 'all') {
 | 
			
		||||
        this.$store.dispatch('user/updateUserSettings', {
 | 
			
		||||
          filterBy: 'all'
 | 
			
		||||
        })
 | 
			
		||||
      } else {
 | 
			
		||||
        this.setGroupedBooks()
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    checkKeywordFilter() {
 | 
			
		||||
      clearTimeout(this.keywordFilterTimeout)
 | 
			
		||||
      this.keywordFilterTimeout = setTimeout(() => {
 | 
			
		||||
        this.setGroupedBooks()
 | 
			
		||||
      }, 500)
 | 
			
		||||
    },
 | 
			
		||||
    increaseSize() {
 | 
			
		||||
      this.selectedSizeIndex = Math.min(this.availableSizes.length - 1, this.selectedSizeIndex + 1)
 | 
			
		||||
      this.resize()
 | 
			
		||||
 | 
			
		||||
@ -3,9 +3,12 @@
 | 
			
		||||
    <div id="toolbar" class="absolute top-0 left-0 w-full h-full z-20 flex items-center px-8">
 | 
			
		||||
      <p class="font-book">{{ numShowing }} Audiobooks</p>
 | 
			
		||||
      <div class="flex-grow" />
 | 
			
		||||
      <controls-filter-select v-model="settings.filterBy" class="w-48 h-7.5" @change="updateFilter" />
 | 
			
		||||
      <span class="px-4 text-sm">by</span>
 | 
			
		||||
      <controls-order-select v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5" @change="updateOrder" />
 | 
			
		||||
 | 
			
		||||
      <ui-text-input v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" />
 | 
			
		||||
 | 
			
		||||
      <controls-filter-select v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
 | 
			
		||||
 | 
			
		||||
      <controls-order-select v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@ -21,6 +24,14 @@ export default {
 | 
			
		||||
  computed: {
 | 
			
		||||
    numShowing() {
 | 
			
		||||
      return this.$store.getters['audiobooks/getFiltered']().length
 | 
			
		||||
    },
 | 
			
		||||
    _keywordFilter: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.$store.state.audiobooks.keywordFilter
 | 
			
		||||
      },
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.$store.commit('audiobooks/setKeywordFilter', val)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
 | 
			
		||||
@ -42,9 +42,9 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        </li>
 | 
			
		||||
        <template v-for="item in sublistItems">
 | 
			
		||||
          <li :key="item" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" :class="`${sublist}.${item}` === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedSublistOption(item)">
 | 
			
		||||
          <li :key="item.value" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" :class="`${sublist}.${item.value}` === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedSublistOption(item.value)">
 | 
			
		||||
            <div class="flex items-center">
 | 
			
		||||
              <span class="font-normal truncate py-2 text-xs">{{ snakeToNormal(item) }}</span>
 | 
			
		||||
              <span class="font-normal truncate py-2 text-xs">{{ item.text }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </li>
 | 
			
		||||
        </template>
 | 
			
		||||
@ -81,6 +81,11 @@ export default {
 | 
			
		||||
          text: 'Series',
 | 
			
		||||
          value: 'series',
 | 
			
		||||
          sublist: true
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          text: 'Authors',
 | 
			
		||||
          value: 'authors',
 | 
			
		||||
          sublist: true
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
@ -109,14 +114,15 @@ export default {
 | 
			
		||||
      if (!this.selected) return ''
 | 
			
		||||
      var parts = this.selected.split('.')
 | 
			
		||||
      if (parts.length > 1) {
 | 
			
		||||
        return this.snakeToNormal(parts[1])
 | 
			
		||||
        return this.$decode(parts[1])
 | 
			
		||||
      }
 | 
			
		||||
      var _sel = this.items.find((i) => i.value === this.selected)
 | 
			
		||||
      if (!_sel) return ''
 | 
			
		||||
      return _sel.text
 | 
			
		||||
    },
 | 
			
		||||
    genres() {
 | 
			
		||||
      return this.$store.state.audiobooks.genres
 | 
			
		||||
      // return this.$store.state.audiobooks.genres
 | 
			
		||||
      return this.$store.getters['audiobooks/getGenresUsed']
 | 
			
		||||
    },
 | 
			
		||||
    tags() {
 | 
			
		||||
      return this.$store.state.audiobooks.tags
 | 
			
		||||
@ -124,8 +130,16 @@ export default {
 | 
			
		||||
    series() {
 | 
			
		||||
      return this.$store.state.audiobooks.series
 | 
			
		||||
    },
 | 
			
		||||
    authors() {
 | 
			
		||||
      return this.$store.getters['audiobooks/getUniqueAuthors']
 | 
			
		||||
    },
 | 
			
		||||
    sublistItems() {
 | 
			
		||||
      return this[this.sublist] || []
 | 
			
		||||
      return (this[this.sublist] || []).map((item) => {
 | 
			
		||||
        return {
 | 
			
		||||
          text: item,
 | 
			
		||||
          value: this.$encode(item)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
@ -134,15 +148,6 @@ export default {
 | 
			
		||||
      this.showMenu = false
 | 
			
		||||
      this.$nextTick(() => this.$emit('change', 'all'))
 | 
			
		||||
    },
 | 
			
		||||
    snakeToNormal(kebab) {
 | 
			
		||||
      if (!kebab) {
 | 
			
		||||
        return 'err'
 | 
			
		||||
      }
 | 
			
		||||
      return String(kebab)
 | 
			
		||||
        .split('_')
 | 
			
		||||
        .map((t) => t.slice(0, 1).toUpperCase() + t.slice(1))
 | 
			
		||||
        .join(' ')
 | 
			
		||||
    },
 | 
			
		||||
    clickOutside() {
 | 
			
		||||
      if (!this.selectedItemSublist) this.sublist = null
 | 
			
		||||
      this.showMenu = false
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
            <div class="w-full h-full rounded-full absolute top-0 left-0 opacity-0 hover:opacity-100 px-1 bg-bg bg-opacity-75 flex items-center justify-end cursor-pointer">
 | 
			
		||||
              <span class="material-icons text-white hover:text-error" style="font-size: 1.1rem" @click.stop="removeItem(item)">close</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            {{ $snakeToNormal(item) }}
 | 
			
		||||
            {{ item }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <input ref="input" v-model="textInput" style="min-width: 40px; width: 40px" class="h-full bg-primary focus:outline-none px-1" @keydown="keydownInput" @focus="inputFocus" @blur="inputBlur" />
 | 
			
		||||
        </div>
 | 
			
		||||
@ -18,7 +18,7 @@
 | 
			
		||||
        <template v-for="item in itemsToShow">
 | 
			
		||||
          <li :key="item" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" role="option" @click="clickedOption($event, item)" @mouseup.stop.prevent @mousedown.prevent>
 | 
			
		||||
            <div class="flex items-center">
 | 
			
		||||
              <span class="font-normal ml-3 block truncate">{{ $snakeToNormal(item) }}</span>
 | 
			
		||||
              <span class="font-normal ml-3 block truncate">{{ item }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <span v-if="selected.includes(item)" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
 | 
			
		||||
              <span class="material-icons text-xl">checkmark</span>
 | 
			
		||||
@ -75,8 +75,8 @@ export default {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return this.items.filter((i) => {
 | 
			
		||||
        var normie = this.$snakeToNormal(i)
 | 
			
		||||
        var iValue = String(normie).toLowerCase()
 | 
			
		||||
        // var normie = this.$snakeToNormal(i)
 | 
			
		||||
        var iValue = String(i).toLowerCase()
 | 
			
		||||
        return iValue.includes(this.currentSearch.toLowerCase())
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
@ -170,8 +170,8 @@ export default {
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    insertNewItem(item) {
 | 
			
		||||
      var kebabItem = this.$normalToSnake(item)
 | 
			
		||||
      this.selected.push(kebabItem)
 | 
			
		||||
      // var kebabItem = this.$normalToSnake(item)
 | 
			
		||||
      this.selected.push(item)
 | 
			
		||||
      this.$emit('input', this.selected)
 | 
			
		||||
      this.textInput = null
 | 
			
		||||
      this.currentSearch = null
 | 
			
		||||
@ -183,9 +183,9 @@ export default {
 | 
			
		||||
      if (!this.textInput) return
 | 
			
		||||
 | 
			
		||||
      var cleaned = this.textInput.toLowerCase().trim()
 | 
			
		||||
      var cleanedKebab = this.$normalToSnake(cleaned)
 | 
			
		||||
      // var cleanedKebab = this.$normalToSnake(cleaned)
 | 
			
		||||
      var matchesItem = this.items.find((i) => {
 | 
			
		||||
        return i === cleaned || cleanedKebab === i
 | 
			
		||||
        return i === cleaned
 | 
			
		||||
      })
 | 
			
		||||
      if (matchesItem) {
 | 
			
		||||
        this.clickedOption(null, matchesItem)
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <input v-model="inputValue" :type="type" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="py-2 px-3 rounded bg-primary text-gray-200 focus:border-gray-500 focus:outline-none" :class="transparent ? '' : 'border border-gray-600'" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
 | 
			
		||||
  <input v-model="inputValue" :type="type" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-500 focus:outline-none border border-gray-600" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@ -12,8 +12,15 @@ export default {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'text'
 | 
			
		||||
    },
 | 
			
		||||
    transparent: Boolean,
 | 
			
		||||
    disabled: Boolean
 | 
			
		||||
    disabled: Boolean,
 | 
			
		||||
    paddingY: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 2
 | 
			
		||||
    },
 | 
			
		||||
    paddingX: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 3
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {}
 | 
			
		||||
@ -26,6 +33,12 @@ export default {
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.$emit('input', val)
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    classList() {
 | 
			
		||||
      var _list = []
 | 
			
		||||
      _list.push(`px-${this.paddingX}`)
 | 
			
		||||
      _list.push(`py-${this.paddingY}`)
 | 
			
		||||
      return _list.join(' ')
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "audiobookshelf-client",
 | 
			
		||||
  "version": "1.0.2",
 | 
			
		||||
  "version": "1.0.3",
 | 
			
		||||
  "description": "Audiobook manager and player",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="w-full h-full p-8">
 | 
			
		||||
    <div class="w-full max-w-2xl mx-auto">
 | 
			
		||||
    <div class="w-full max-w-xl mx-auto">
 | 
			
		||||
      <h1 class="text-2xl">Account</h1>
 | 
			
		||||
 | 
			
		||||
      <div class="my-4">
 | 
			
		||||
@ -27,6 +27,10 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="py-4 mt-8 flex">
 | 
			
		||||
        <ui-btn color="primary flex items-center text-lg" @click="logout"><span class="material-icons mr-4 icon-text">logout</span>Logout</ui-btn>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@ -56,6 +60,15 @@ export default {
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    logout() {
 | 
			
		||||
      this.$axios.$post('/logout').catch((error) => {
 | 
			
		||||
        console.error(error)
 | 
			
		||||
      })
 | 
			
		||||
      if (localStorage.getItem('token')) {
 | 
			
		||||
        localStorage.removeItem('token')
 | 
			
		||||
      }
 | 
			
		||||
      this.$router.push('/login')
 | 
			
		||||
    },
 | 
			
		||||
    resetForm() {
 | 
			
		||||
      this.password = null
 | 
			
		||||
      this.newPassword = null
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,9 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {}
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    streamAudiobook() {
 | 
			
		||||
      return this.$store.state.streamAudiobook
 | 
			
		||||
 | 
			
		||||
@ -57,6 +57,11 @@ Vue.prototype.$normalToSnake = (normie) => {
 | 
			
		||||
    .join('_')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const encode = (text) => encodeURIComponent(Buffer.from(text).toString('base64'))
 | 
			
		||||
Vue.prototype.$encode = encode
 | 
			
		||||
const decode = (text) => Buffer.from(decodeURIComponent(text), 'base64').toString()
 | 
			
		||||
Vue.prototype.$decode = decode
 | 
			
		||||
 | 
			
		||||
const availableChars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
 | 
			
		||||
const getCharCode = (char) => availableChars.indexOf(char)
 | 
			
		||||
const getCharFromCode = (code) => availableChars[Number(code)] || -1
 | 
			
		||||
@ -109,21 +114,6 @@ Vue.prototype.$codeToString = (code) => {
 | 
			
		||||
  return finalform
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function cleanString(str, availableChars) {
 | 
			
		||||
  var _str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
 | 
			
		||||
  var cleaned = ''
 | 
			
		||||
  for (let i = 0; i < _str.length; i++) {
 | 
			
		||||
    cleaned += availableChars.indexOf(str[i]) < 0 ? '' : str[i]
 | 
			
		||||
  }
 | 
			
		||||
  return cleaned
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const cleanFilterString = (str) => {
 | 
			
		||||
  var _str = str.toLowerCase().replace(/ /g, '_')
 | 
			
		||||
  _str = cleanString(_str, "0123456789abcdefghijklmnopqrstuvwxyz")
 | 
			
		||||
  return _str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadImageBlob(uri) {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    const img = document.createElement('img')
 | 
			
		||||
@ -204,3 +194,8 @@ Vue.prototype.$sanitizeFilename = (input, replacement = '') => {
 | 
			
		||||
    .replace(windowsTrailingRe, replacement);
 | 
			
		||||
  return sanitized
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  encode,
 | 
			
		||||
  decode
 | 
			
		||||
}
 | 
			
		||||
@ -1,14 +1,17 @@
 | 
			
		||||
import { sort } from '@/assets/fastSort'
 | 
			
		||||
import { cleanFilterString } from '@/plugins/init.client'
 | 
			
		||||
import { decode } from '@/plugins/init.client'
 | 
			
		||||
 | 
			
		||||
const STANDARD_GENRES = ['adventure', 'autobiography', 'biography', 'childrens', 'comedy', 'crime', 'dystopian', 'fantasy', 'fiction', 'health', 'history', 'horror', 'mystery', 'new_adult', 'nonfiction', 'philosophy', 'politics', 'religion', 'romance', 'sci-fi', 'self-help', 'short_story', 'technology', 'thriller', 'true_crime', 'western', 'young_adult']
 | 
			
		||||
// const STANDARD_GENRES = ['adventure', 'autobiography', 'biography', 'childrens', 'comedy', 'crime', 'dystopian', 'fantasy', 'fiction', 'health', 'history', 'horror', 'mystery', 'new_adult', 'nonfiction', 'philosophy', 'politics', 'religion', 'romance', 'sci-fi', 'self-help', 'short_story', 'technology', 'thriller', 'true_crime', 'western', 'young_adult']
 | 
			
		||||
 | 
			
		||||
const STANDARD_GENRES = ['Adventure', 'Autobiography', 'Biography', 'Childrens', 'Comedy', 'Crime', 'Dystopian', 'Fantasy', 'Fiction', 'Health', 'History', 'Horror', 'Mystery', 'New Adult', 'Nonfiction', 'Philosophy', 'Politics', 'Religion', 'Romance', 'Sci-Fi', 'Self-Help', 'Short Story', 'Technology', 'Thriller', 'True Crime', 'Western', 'Young Adult']
 | 
			
		||||
 | 
			
		||||
export const state = () => ({
 | 
			
		||||
  audiobooks: [],
 | 
			
		||||
  listeners: [],
 | 
			
		||||
  genres: [...STANDARD_GENRES],
 | 
			
		||||
  tags: [],
 | 
			
		||||
  series: []
 | 
			
		||||
  series: [],
 | 
			
		||||
  keywordFilter: null
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export const getters = {
 | 
			
		||||
@ -20,12 +23,19 @@ export const getters = {
 | 
			
		||||
    var searchGroups = ['genres', 'tags', 'series', 'authors']
 | 
			
		||||
    var group = searchGroups.find(_group => filterBy.startsWith(_group + '.'))
 | 
			
		||||
    if (group) {
 | 
			
		||||
      var filter = filterBy.replace(`${group}.`, '')
 | 
			
		||||
      var filter = decode(filterBy.replace(`${group}.`, ''))
 | 
			
		||||
      if (group === 'genres') filtered = filtered.filter(ab => ab.book && ab.book.genres.includes(filter))
 | 
			
		||||
      else if (group === 'tags') filtered = filtered.filter(ab => ab.tags.includes(filter))
 | 
			
		||||
      else if (group === 'series') filtered = filtered.filter(ab => ab.book && ab.book.series === filter)
 | 
			
		||||
      else if (group === 'authors') filtered = filtered.filter(ab => ab.book && ab.book.author === filter)
 | 
			
		||||
    }
 | 
			
		||||
    if (state.keywordFilter) {
 | 
			
		||||
      const keywordFilterKeys = ['title', 'subtitle', 'author', 'series', 'narrarator']
 | 
			
		||||
      return filtered.filter(ab => {
 | 
			
		||||
        if (!ab.book) return false
 | 
			
		||||
        return !!keywordFilterKeys.find(key => (ab.book[key] && ab.book[key].includes(state.keywordFilter)))
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    return filtered
 | 
			
		||||
  },
 | 
			
		||||
  getFilteredAndSorted: (state, getters, rootState) => () => {
 | 
			
		||||
@ -40,7 +50,12 @@ export const getters = {
 | 
			
		||||
  },
 | 
			
		||||
  getUniqueAuthors: (state) => {
 | 
			
		||||
    var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author)
 | 
			
		||||
    return [...new Set(_authors)]
 | 
			
		||||
    return [...new Set(_authors)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
 | 
			
		||||
  },
 | 
			
		||||
  getGenresUsed: (state) => {
 | 
			
		||||
    var _genres = []
 | 
			
		||||
    state.audiobooks.filter(ab => !!(ab.book && ab.book.genres)).forEach(ab => _genres = _genres.concat(ab.book.genres))
 | 
			
		||||
    return [...new Set(_genres)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -64,6 +79,9 @@ export const actions = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const mutations = {
 | 
			
		||||
  setKeywordFilter(state, val) {
 | 
			
		||||
    state.keywordFilter = val
 | 
			
		||||
  },
 | 
			
		||||
  set(state, audiobooks) {
 | 
			
		||||
    // GENRES
 | 
			
		||||
    var genres = [...state.genres]
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,8 @@ module.exports = {
 | 
			
		||||
    options: {
 | 
			
		||||
      safelist: [
 | 
			
		||||
        'bg-success',
 | 
			
		||||
        'bg-red-600'
 | 
			
		||||
        'bg-red-600',
 | 
			
		||||
        'py-1.5'
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "audiobookshelf",
 | 
			
		||||
  "version": "1.0.2",
 | 
			
		||||
  "version": "1.0.3",
 | 
			
		||||
  "description": "Self-hosted audiobook server for managing and playing audiobooks.",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user