diff --git a/.devcontainer/dev.js b/.devcontainer/dev.js index 0d113a3e..28c0074b 100644 --- a/.devcontainer/dev.js +++ b/.devcontainer/dev.js @@ -5,5 +5,6 @@ module.exports.config = { ConfigPath: Path.resolve('config'), MetadataPath: Path.resolve('metadata'), FFmpegPath: '/usr/bin/ffmpeg', - FFProbePath: '/usr/bin/ffprobe' + FFProbePath: '/usr/bin/ffprobe', + SkipBinariesCheck: true } \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..0efcc5b5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 7f47d710..89aec401 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -1,40 +1,50 @@ name: 🐞 Bug Report -description: File a bug/issue -title: "[Bug]: " -labels: ["bug", "triage"] +description: File a bug/issue and help us improve Audiobookshelf +title: '[Bug]: ' +labels: ['bug', 'triage'] body: - type: markdown attributes: - value: "### Please first search for your issue and check the [docs](https://audiobookshelf.org/docs)." + value: 'Thank you for filing a bug report! 🐛' - type: markdown attributes: - value: "### Mobile app issues report [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)." + value: 'Please first search for your issue and check the [docs](https://audiobookshelf.org/docs).' - type: markdown attributes: - value: "### Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug." + value: 'Report issues with the mobile app [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose).' - type: markdown attributes: - value: "## Be as descriptive as you can. Include screenshots, error logs, browser, file types, everything you can think of that might be relevant." + value: 'Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug.' - type: textarea id: what-happened attributes: - label: Describe the issue - description: What happened & what did you expect to happen + label: What happened? + placeholder: Tell us what you see! + validations: + required: true + - type: textarea + id: what-was-expected + attributes: + label: What did you expect to happen? + placeholder: Tell us what you expected to see! Be as descriptive as you can and include screenshots if applicable. validations: required: true - type: textarea id: steps-to-reproduce attributes: label: Steps to reproduce the issue - value: "1. " + value: '1. ' validations: required: true + - type: markdown + attributes: + value: '## Install Environment' - type: input id: version attributes: label: Audiobookshelf version description: Do not put 'Latest version', please put the actual version here - placeholder: "e.g. v1.6.60" + placeholder: 'e.g. v1.6.60' validations: required: true - type: dropdown @@ -46,6 +56,43 @@ body: - Debian/PPA - Windows Tray App - Built from source - - Other + - Other (list in "Additional Notes" box) validations: - required: true + required: true + - type: dropdown + id: server-os + attributes: + label: What OS is your Audiobookshelf server hosted from? + options: + - Windows + - macOS + - Linux + - Other (list in "Additional Notes" box) + validations: + required: true + - type: dropdown + id: desktop-browsers + attributes: + label: If the issue is being seen in the UI, what browsers are you seeing the problem on? + options: + - Chrome + - Firefox + - Safari + - Edge + - Firefox for Android + - Chrome for Android + - Safari on iOS + - Other (list in "Additional Notes" box) + - type: textarea + id: logs + attributes: + label: Logs + description: Please include any relevant logs here. This field is automatically formatted into code, so you do not need to include any backticks. + placeholder: Paste logs here + render: shell + - type: textarea + id: additional-notes + attributes: + label: Additional Notes + description: Anything else you want to add? + placeholder: 'e.g. I have tried X, Y, and Z.' diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 75363c0e..76888091 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -1,17 +1,63 @@ name: 🚀 Feature Request description: Request a feature/enhancement -title: "[Enhancement]: " -labels: ["enhancement"] +title: '[Enhancement]: ' +labels: ['enhancement'] body: - type: markdown attributes: - value: "### Please first search in both issues & discussions for your enhancement." + value: '#### *Mobile app features should be [requested here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)*.' - type: markdown attributes: - value: "### Mobile app features should be requested [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)." + value: '## Web/Server Feature Request Description' + - type: markdown + attributes: + value: 'Please first search in both issues & discussions for your enhancement.' + - type: dropdown + id: enhancment-type + attributes: + label: Type of Enhancement + options: + - Server Backend + - Web Interface/Frontend + - Documentation - type: textarea id: describe attributes: - label: Describe the feature/enhancement + label: Describe the Feature/Enhancement + description: Please help us understand what you want. + placeholder: What is your vision? validations: required: true + - type: textarea + id: the-why + attributes: + label: Why would this be helpful? + description: Please help us understand why this would enhance your experience. + placeholder: Explain the "why" or "use case". + validations: + required: true + - type: textarea + id: image + attributes: + label: Future Implementation (Screenshot) + description: Please help us visualize by including a doodle or screenshot. + placeholder: How could this look? + validations: + required: true + - type: markdown + attributes: + value: '## Web/Server Current Implementation' + - type: input + id: version + attributes: + label: Audiobookshelf Server Version + description: Do not put 'Latest version', please put your current version number here + placeholder: 'e.g. v1.6.60' + validations: + required: true + - type: textarea + id: current-image + attributes: + label: Current Implementation (Screenshot) + description: What page were you looking at when you thought of this enhancement? + placeholder: If an image is not applicable, please explain why. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..a77ab3e0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,65 @@ +name: "CodeQL" + +on: + push: + branches: [ 'master' ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ 'master' ] + schedule: + - cron: '16 5 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/i18n-integration.yml b/.github/workflows/i18n-integration.yml new file mode 100644 index 00000000..12d82c3e --- /dev/null +++ b/.github/workflows/i18n-integration.yml @@ -0,0 +1,30 @@ +name: Verify all i18n files are alphabetized + +on: + pull_request: + paths: + - client/strings/** # Should only check if any strings changed + push: + paths: + - client/strings/** # Should only check if any strings changed + +jobs: + update_translations: + runs-on: ubuntu-latest + steps: + # Check out the repository + - name: Checkout repository + uses: actions/checkout@v4 + + # Set up node to run the javascript + - name: Set up node + uses: actions/setup-node@v4 + with: + node-version: "20" + + # The only argument is the `directory`, which is where the i18n files are + # stored. + - name: Run Update JSON Files action + uses: audiobookshelf/audiobookshelf-i18n-updater@v1.2.0 + with: + directory: "client/strings/" # Adjust the directory path as needed diff --git a/.github/workflows/notify-abs-windows.yml b/.github/workflows/notify-abs-windows.yml new file mode 100644 index 00000000..9ede33b8 --- /dev/null +++ b/.github/workflows/notify-abs-windows.yml @@ -0,0 +1,17 @@ +name: Dispatch an abs-windows event + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + abs-windows-dispatch: + runs-on: ubuntu-latest + steps: + - name: Send a remote repository dispatch event + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.ABS_WINDOWS_PAT }} + repository: mikiher/audiobookshelf-windows + event-type: build-windows diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..695696c6 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,37 @@ +name: Run Unit Tests + +on: + workflow_dispatch: + inputs: + ref: + description: 'Branch/Tag/SHA to test' + required: true + pull_request: + push: + +jobs: + run-unit-tests: + name: Run Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout (push/pull request) + uses: actions/checkout@v4 + if: github.event_name != 'workflow_dispatch' + + - name: Checkout (workflow_dispatch) + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + if: github.event_name == 'workflow_dispatch' + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore index 0690f38f..9ddb1a70 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ sw.* .DS_STORE .idea/* +tailwind.compiled.css \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..d3d0ff99 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 400, + "proseWrap": "never", + "trailingComma": "none", + "overrides": [ + { + "files": ["*.html"], + "options": { + "singleQuote": false, + "wrapAttributes": false, + "sortAttributes": false + } + } + ] +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d2a04598 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", + "octref.vetur" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 397b9618..75503e6a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,11 @@ "editor.formatOnSave": true, "editor.detectIndentation": true, "editor.tabSize": 2, - "javascript.format.semicolons": "remove" + "javascript.format.semicolons": "remove", + "[javascript][json][jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[vue]": { + "editor.defaultFormatter": "octref.vetur" + } } \ No newline at end of file diff --git a/build/linuxpackager b/build/linuxpackager index a43d4ed1..5f03a2e8 100755 --- a/build/linuxpackager +++ b/build/linuxpackager @@ -50,9 +50,8 @@ echo "$controlfile" > dist/debian/DEBIAN/control; # Package debian pkg -t node18-linux-x64 -o dist/debian/usr/share/audiobookshelf/audiobookshelf . -fakeroot dpkg-deb --build dist/debian +fakeroot dpkg-deb -Zxz --build dist/debian mv dist/debian.deb "dist/$OUTPUT_FILE" -chmod +x "dist/$OUTPUT_FILE" echo "Finished! Filename: $OUTPUT_FILE" diff --git a/client/assets/app.css b/client/assets/app.css index 2e0714f9..7daf74ff 100644 --- a/client/assets/app.css +++ b/client/assets/app.css @@ -30,8 +30,7 @@ } .bookshelf-row { - /* Sidebar width + scrollbar width */ - width: calc(100vw - 88px); + width: calc(100vw - (100vw - 100%)); } @media (max-width: 768px) { diff --git a/client/assets/fonts.css b/client/assets/fonts.css index f9acc6bf..9504d4c3 100644 --- a/client/assets/fonts.css +++ b/client/assets/fonts.css @@ -24,6 +24,7 @@ word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; + vertical-align: top; } .material-icons:not([class*="text-"]) { diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index eb4e9424..b9b2fbfd 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -4,14 +4,14 @@
-

{{ libraryName }} Library is empty!

+

{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}

- Configure Scanner - Scan Library + {{ $strings.ButtonConfigureScanner }} + {{ $strings.ButtonScanLibrary }}
-

No results for query

+

{{ $strings.MessageBookshelfNoResultsForQuery }}

@@ -58,7 +58,8 @@ export default { scannerParseSubtitle: false, wrapperClientWidth: 0, shelves: [], - lastItemIndexSelected: -1 + lastItemIndexSelected: -1, + tempIsScanning: false } }, computed: { @@ -97,6 +98,9 @@ export default { }, streamLibraryItem() { return this.$store.state.streamLibraryItem + }, + isScanningLibrary() { + return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId) } }, methods: { @@ -273,14 +277,15 @@ export default { this.shelves = shelves }, scan() { + this.tempIsScanning = true this.$store .dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId }) - .then(() => { - this.$toast.success('Library scan started') - }) .catch((error) => { console.error('Failed to start scan', error) - this.$toast.error('Failed to start scan') + this.$toast.error(this.$strings.ToastLibraryScanFailedToStart) + }) + .finally(() => { + this.tempIsScanning = false }) }, userUpdated(user) { diff --git a/client/components/app/BookShelfRow.vue b/client/components/app/BookShelfRow.vue index 60632db7..5b2c4258 100644 --- a/client/components/app/BookShelfRow.vue +++ b/client/components/app/BookShelfRow.vue @@ -4,7 +4,7 @@
diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index bd31768b..9064c914 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -98,6 +98,9 @@
@@ -183,6 +186,30 @@ export default { } ] }, + authorSortItems() { + return [ + { + text: this.$strings.LabelAuthorFirstLast, + value: 'name' + }, + { + text: this.$strings.LabelAuthorLastFirst, + value: 'lastFirst' + }, + { + text: this.$strings.LabelNumberOfBooks, + value: 'numBooks' + }, + { + text: this.$strings.LabelAddedAt, + value: 'addedAt' + }, + { + text: this.$strings.LabelUpdatedAt, + value: 'updatedAt' + } + ] + }, userIsAdminOrUp() { return this.$store.getters['user/getIsAdminOrUp'] }, @@ -455,6 +482,9 @@ export default { updateCollapseBookSeries() { this.saveSettings() }, + updateAuthorSort() { + this.saveSettings() + }, saveSettings() { this.$store.dispatch('user/updateUserSettings', this.settings) }, diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 5291cdbb..805c6978 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -10,7 +10,7 @@

{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}

{{ $strings.ButtonConfigureScanner }} - {{ $strings.ButtonScanLibrary }} + {{ $strings.ButtonScanLibrary }}
@@ -62,7 +62,8 @@ export default { currScrollTop: 0, resizeTimeout: null, mountWindowWidth: 0, - lastItemIndexSelected: -1 + lastItemIndexSelected: -1, + tempIsScanning: false } }, watch: { @@ -208,6 +209,9 @@ export default { }, streamLibraryItem() { return this.$store.state.streamLibraryItem + }, + isScanningLibrary() { + return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId) } }, methods: { @@ -727,14 +731,15 @@ export default { } }, scan() { + this.tempIsScanning = true this.$store .dispatch('libraries/requestLibraryScan', { libraryId: this.currentLibraryId }) - .then(() => { - this.$toast.success('Library scan started') - }) .catch((error) => { console.error('Failed to start scan', error) - this.$toast.error('Failed to start scan') + this.$toast.error(this.$strings.ToastLibraryScanFailedToStart) + }) + .finally(() => { + this.tempIsScanning = false }) } }, @@ -775,4 +780,4 @@ export default { background: var(--bookshelf-divider-bg); box-shadow: 2px 14px 8px #111111aa; } - \ No newline at end of file + diff --git a/client/components/app/MediaPlayerContainer.vue b/client/components/app/MediaPlayerContainer.vue index 120231dc..36e3b63e 100644 --- a/client/components/app/MediaPlayerContainer.vue +++ b/client/components/app/MediaPlayerContainer.vue @@ -1,25 +1,25 @@