Showing preview only (8,240K chars total). Download the full file or copy to clipboard to get everything.
Repository: advplyr/audiobookshelf
Branch: master
Commit: 8b89b276544b
Files: 912
Total size: 7.7 MB
Directory structure:
gitextract_pkg28qgc/
├── .devcontainer/
│ ├── Dockerfile
│ ├── dev.js
│ ├── devcontainer.json
│ └── post-create.sh
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.yaml
│ │ ├── config.yml
│ │ └── feature.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── apply_comments.yaml
│ ├── close-issues-on-release.yml
│ ├── close_blank_issues.yaml
│ ├── codeql.yml
│ ├── component-tests.yml
│ ├── docker-build.yml
│ ├── i18n-integration.yml
│ ├── integration-test.yml
│ ├── lint-openapi.yml
│ ├── notify-abs-windows.yml
│ └── unit-tests.yml
├── .gitignore
├── .prettierrc
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── Dockerfile
├── LICENSE
├── build/
│ ├── debian/
│ │ ├── DEBIAN/
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ ├── preinst
│ │ │ └── prerm
│ │ ├── etc/
│ │ │ └── default/
│ │ │ └── .gitkeep
│ │ ├── lib/
│ │ │ └── systemd/
│ │ │ └── system/
│ │ │ └── audiobookshelf.service
│ │ └── usr/
│ │ ├── lib/
│ │ │ └── .gitkeep
│ │ └── share/
│ │ └── .gitkeep
│ └── linuxpackager
├── client/
│ ├── assets/
│ │ ├── absicons.css
│ │ ├── app.css
│ │ ├── defaultStyles.css
│ │ ├── draggable.css
│ │ ├── ebooks/
│ │ │ ├── basic.js
│ │ │ ├── htmlParser.js
│ │ │ └── mobi.js
│ │ ├── fonts.css
│ │ ├── tailwind.css
│ │ ├── transitions.css
│ │ └── trix.css
│ ├── components/
│ │ ├── app/
│ │ │ ├── Appbar.vue
│ │ │ ├── BookShelfCategorized.vue
│ │ │ ├── BookShelfRow.vue
│ │ │ ├── BookShelfToolbar.vue
│ │ │ ├── ConfigSideNav.vue
│ │ │ ├── LazyBookshelf.vue
│ │ │ ├── MediaPlayerContainer.vue
│ │ │ ├── SettingsContent.vue
│ │ │ └── SideRail.vue
│ │ ├── cards/
│ │ │ ├── AuthorCard.vue
│ │ │ ├── AuthorSearchCard.vue
│ │ │ ├── BookMatchCard.vue
│ │ │ ├── EpisodeSearchCard.vue
│ │ │ ├── GenreSearchCard.vue
│ │ │ ├── GroupCard.vue
│ │ │ ├── ItemSearchCard.vue
│ │ │ ├── ItemTaskRunningCard.vue
│ │ │ ├── ItemUploadCard.vue
│ │ │ ├── LazyBookCard.vue
│ │ │ ├── LazyCollectionCard.vue
│ │ │ ├── LazyPlaylistCard.vue
│ │ │ ├── LazySeriesCard.vue
│ │ │ ├── NarratorCard.vue
│ │ │ ├── NarratorSearchCard.vue
│ │ │ ├── NotificationCard.vue
│ │ │ ├── PodcastFeedSummaryCard.vue
│ │ │ ├── SeriesSearchCard.vue
│ │ │ └── TagSearchCard.vue
│ │ ├── content/
│ │ │ └── LibraryItemDetails.vue
│ │ ├── controls/
│ │ │ ├── FilterSelect.vue
│ │ │ ├── GlobalSearch.vue
│ │ │ ├── LibraryFilterSelect.vue
│ │ │ ├── LibrarySortSelect.vue
│ │ │ ├── PlaybackSpeedControl.vue
│ │ │ ├── SortSelect.vue
│ │ │ └── VolumeControl.vue
│ │ ├── covers/
│ │ │ ├── AuthorImage.vue
│ │ │ ├── BookCover.vue
│ │ │ ├── CollectionCover.vue
│ │ │ ├── GroupCover.vue
│ │ │ ├── PlaylistCover.vue
│ │ │ └── PreviewCover.vue
│ │ ├── modals/
│ │ │ ├── AccountModal.vue
│ │ │ ├── AddCustomMetadataProviderModal.vue
│ │ │ ├── ApiKeyCreatedModal.vue
│ │ │ ├── ApiKeyModal.vue
│ │ │ ├── AudioFileDataModal.vue
│ │ │ ├── BackupScheduleModal.vue
│ │ │ ├── BatchQuickMatchModel.vue
│ │ │ ├── BookmarksModal.vue
│ │ │ ├── ChaptersModal.vue
│ │ │ ├── Dialog.vue
│ │ │ ├── EditSeriesInputInnerModal.vue
│ │ │ ├── ListeningSessionModal.vue
│ │ │ ├── Modal.vue
│ │ │ ├── PlayerSettingsModal.vue
│ │ │ ├── RawCoverPreviewModal.vue
│ │ │ ├── ShareModal.vue
│ │ │ ├── SleepTimerModal.vue
│ │ │ ├── UploadImageModal.vue
│ │ │ ├── authors/
│ │ │ │ └── EditModal.vue
│ │ │ ├── bookmarks/
│ │ │ │ └── BookmarkItem.vue
│ │ │ ├── changelog/
│ │ │ │ └── ViewModal.vue
│ │ │ ├── collections/
│ │ │ │ ├── AddCreateModal.vue
│ │ │ │ ├── CollectionItem.vue
│ │ │ │ └── EditModal.vue
│ │ │ ├── emails/
│ │ │ │ ├── EReaderDeviceModal.vue
│ │ │ │ └── UserEReaderDeviceModal.vue
│ │ │ ├── item/
│ │ │ │ ├── EditModal.vue
│ │ │ │ └── tabs/
│ │ │ │ ├── Chapters.vue
│ │ │ │ ├── Cover.vue
│ │ │ │ ├── Details.vue
│ │ │ │ ├── Episodes.vue
│ │ │ │ ├── Files.vue
│ │ │ │ ├── Match.vue
│ │ │ │ ├── Schedule.vue
│ │ │ │ └── Tools.vue
│ │ │ ├── libraries/
│ │ │ │ ├── EditLibrary.vue
│ │ │ │ ├── EditModal.vue
│ │ │ │ ├── LazyFolderChooser.vue
│ │ │ │ ├── LibraryScannerSettings.vue
│ │ │ │ ├── LibrarySettings.vue
│ │ │ │ ├── LibraryTools.vue
│ │ │ │ └── ScheduleScan.vue
│ │ │ ├── notification/
│ │ │ │ └── NotificationEditModal.vue
│ │ │ ├── player/
│ │ │ │ ├── QueueItemRow.vue
│ │ │ │ └── QueueItemsModal.vue
│ │ │ ├── playlists/
│ │ │ │ ├── AddCreateModal.vue
│ │ │ │ ├── EditModal.vue
│ │ │ │ └── UserPlaylistItem.vue
│ │ │ ├── podcast/
│ │ │ │ ├── EditEpisode.vue
│ │ │ │ ├── EpisodeFeed.vue
│ │ │ │ ├── NewModal.vue
│ │ │ │ ├── OpmlFeedsModal.vue
│ │ │ │ ├── RemoveEpisode.vue
│ │ │ │ ├── ViewEpisode.vue
│ │ │ │ └── tabs/
│ │ │ │ ├── EpisodeDetails.vue
│ │ │ │ └── EpisodeMatch.vue
│ │ │ └── rssfeed/
│ │ │ ├── OpenCloseModal.vue
│ │ │ └── ViewFeedModal.vue
│ │ ├── player/
│ │ │ ├── PlayerPlaybackControls.vue
│ │ │ ├── PlayerTrackBar.vue
│ │ │ └── PlayerUi.vue
│ │ ├── prompt/
│ │ │ ├── Confirm.vue
│ │ │ └── Dialog.vue
│ │ ├── readers/
│ │ │ ├── ComicReader.vue
│ │ │ ├── EpubReader.vue
│ │ │ ├── MobiReader.vue
│ │ │ ├── PdfReader.vue
│ │ │ └── Reader.vue
│ │ ├── stats/
│ │ │ ├── DailyListeningChart.vue
│ │ │ ├── Heatmap.vue
│ │ │ ├── PreviewIcons.vue
│ │ │ ├── YearInReview.vue
│ │ │ ├── YearInReviewBanner.vue
│ │ │ ├── YearInReviewServer.vue
│ │ │ └── YearInReviewShort.vue
│ │ ├── tables/
│ │ │ ├── ApiKeysTable.vue
│ │ │ ├── AudioTracksTableRow.vue
│ │ │ ├── BackupsTable.vue
│ │ │ ├── ChaptersTable.vue
│ │ │ ├── CollectionBooksTable.vue
│ │ │ ├── CustomMetadataProviderTable.vue
│ │ │ ├── EbookFilesTable.vue
│ │ │ ├── EbookFilesTableRow.vue
│ │ │ ├── LibraryFilesTable.vue
│ │ │ ├── LibraryFilesTableRow.vue
│ │ │ ├── PlaylistItemsTable.vue
│ │ │ ├── TracksTable.vue
│ │ │ ├── UploadedFilesTable.vue
│ │ │ ├── UsersTable.vue
│ │ │ ├── collection/
│ │ │ │ └── BookTableRow.vue
│ │ │ ├── library/
│ │ │ │ ├── LibrariesTable.vue
│ │ │ │ └── LibraryItem.vue
│ │ │ ├── playlist/
│ │ │ │ └── ItemTableRow.vue
│ │ │ └── podcast/
│ │ │ ├── DownloadQueueTable.vue
│ │ │ ├── LazyEpisodeRow.vue
│ │ │ └── LazyEpisodesTable.vue
│ │ ├── ui/
│ │ │ ├── Btn.vue
│ │ │ ├── Checkbox.vue
│ │ │ ├── ContextMenuDropdown.vue
│ │ │ ├── Dropdown.vue
│ │ │ ├── EditableText.vue
│ │ │ ├── FileInput.vue
│ │ │ ├── IconBtn.vue
│ │ │ ├── InputDropdown.vue
│ │ │ ├── LibrariesDropdown.vue
│ │ │ ├── LibraryIcon.vue
│ │ │ ├── LoadingIndicator.vue
│ │ │ ├── MediaIconPicker.vue
│ │ │ ├── MultiSelect.vue
│ │ │ ├── MultiSelectDropdown.vue
│ │ │ ├── MultiSelectQueryInput.vue
│ │ │ ├── QueryInput.vue
│ │ │ ├── RangeInput.vue
│ │ │ ├── ReadIconBtn.vue
│ │ │ ├── RichTextEditor.vue
│ │ │ ├── SelectInput.vue
│ │ │ ├── TextInput.vue
│ │ │ ├── TextInputWithLabel.vue
│ │ │ ├── TextareaInput.vue
│ │ │ ├── TextareaWithLabel.vue
│ │ │ ├── TimePicker.vue
│ │ │ ├── ToggleBtns.vue
│ │ │ ├── ToggleSwitch.vue
│ │ │ ├── Tooltip.vue
│ │ │ └── VueTrix.vue
│ │ └── widgets/
│ │ ├── AbridgedIndicator.vue
│ │ ├── Alert.vue
│ │ ├── AlreadyInLibraryIndicator.vue
│ │ ├── BookDetailsEdit.vue
│ │ ├── CoverSizeWidget.vue
│ │ ├── CronExpressionBuilder.vue
│ │ ├── EncoderOptionsCard.vue
│ │ ├── ExplicitIndicator.vue
│ │ ├── ItemSlider.vue
│ │ ├── LoadingSpinner.vue
│ │ ├── MoreMenu.vue
│ │ ├── NotificationWidget.vue
│ │ ├── OnlineIndicator.vue
│ │ ├── PodcastDetailsEdit.vue
│ │ ├── PodcastTypeIndicator.vue
│ │ ├── RssFeedMetadataBuilder.vue
│ │ └── SeriesInputWidget.vue
│ ├── cypress/
│ │ ├── support/
│ │ │ ├── commands.js
│ │ │ ├── component-index.html
│ │ │ └── component.js
│ │ └── tests/
│ │ ├── components/
│ │ │ └── cards/
│ │ │ ├── AuthorCard.cy.js
│ │ │ ├── ItemSlider.cy.js
│ │ │ ├── LazyBookCard.cy.js
│ │ │ ├── LazySeriesCard.cy.js
│ │ │ └── NarratorCard.cy.js
│ │ └── utils/
│ │ └── ElapsedPrettyExtended.cy.js
│ ├── cypress.config.js
│ ├── layouts/
│ │ ├── blank.vue
│ │ ├── default.vue
│ │ └── error.vue
│ ├── middleware/
│ │ └── authenticated.js
│ ├── mixins/
│ │ ├── bookshelfCardsHelpers.js
│ │ ├── menuKeyboardNavigation.js
│ │ └── uploadHelpers.js
│ ├── nuxt.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── account.vue
│ │ ├── audiobook/
│ │ │ └── _id/
│ │ │ ├── chapters.vue
│ │ │ ├── edit.vue
│ │ │ └── manage.vue
│ │ ├── author/
│ │ │ └── _id.vue
│ │ ├── batch/
│ │ │ └── index.vue
│ │ ├── collection/
│ │ │ └── _id.vue
│ │ ├── config/
│ │ │ ├── api-keys/
│ │ │ │ └── index.vue
│ │ │ ├── authentication.vue
│ │ │ ├── backups.vue
│ │ │ ├── email.vue
│ │ │ ├── index.vue
│ │ │ ├── item-metadata-utils/
│ │ │ │ ├── custom-metadata-providers.vue
│ │ │ │ ├── genres.vue
│ │ │ │ ├── index.vue
│ │ │ │ └── tags.vue
│ │ │ ├── libraries.vue
│ │ │ ├── log.vue
│ │ │ ├── notifications.vue
│ │ │ ├── rss-feeds.vue
│ │ │ ├── sessions.vue
│ │ │ ├── stats.vue
│ │ │ └── users/
│ │ │ ├── _id/
│ │ │ │ ├── index.vue
│ │ │ │ └── sessions.vue
│ │ │ └── index.vue
│ │ ├── config.vue
│ │ ├── index.vue
│ │ ├── item/
│ │ │ └── _id/
│ │ │ └── index.vue
│ │ ├── library/
│ │ │ └── _library/
│ │ │ ├── bookshelf/
│ │ │ │ └── _id.vue
│ │ │ ├── index.vue
│ │ │ ├── narrators.vue
│ │ │ ├── podcast/
│ │ │ │ ├── download-queue.vue
│ │ │ │ ├── latest.vue
│ │ │ │ └── search.vue
│ │ │ ├── search.vue
│ │ │ ├── series/
│ │ │ │ └── _id.vue
│ │ │ └── stats.vue
│ │ ├── login.vue
│ │ ├── oops.vue
│ │ ├── playlist/
│ │ │ └── _id.vue
│ │ ├── share/
│ │ │ └── _slug.vue
│ │ └── upload/
│ │ └── index.vue
│ ├── players/
│ │ ├── AudioTrack.js
│ │ ├── CastPlayer.js
│ │ ├── LocalAudioPlayer.js
│ │ ├── PlayerHandler.js
│ │ └── castUtils.js
│ ├── plugins/
│ │ ├── axios.js
│ │ ├── chromecast.js
│ │ ├── constants.js
│ │ ├── i18n.js
│ │ ├── init.client.js
│ │ ├── toast.js
│ │ ├── utils.js
│ │ └── version.js
│ ├── postcss.config.js
│ ├── static/
│ │ ├── fonts/
│ │ │ ├── Source_Sans_Pro/
│ │ │ │ └── OFL.txt
│ │ │ └── Ubuntu_Mono/
│ │ │ └── UFL.txt
│ │ ├── libarchive/
│ │ │ ├── wasm-gen/
│ │ │ │ ├── libarchive.js
│ │ │ │ └── libarchive.wasm
│ │ │ └── worker-bundle.js
│ │ ├── libs/
│ │ │ └── marked/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ └── robots.txt
│ ├── store/
│ │ ├── globals.js
│ │ ├── index.js
│ │ ├── libraries.js
│ │ ├── scanners.js
│ │ ├── tasks.js
│ │ ├── user.js
│ │ └── users.js
│ └── strings/
│ ├── ar.json
│ ├── be.json
│ ├── bg.json
│ ├── bn.json
│ ├── ca.json
│ ├── cs.json
│ ├── da.json
│ ├── de.json
│ ├── el.json
│ ├── en-us.json
│ ├── es.json
│ ├── et.json
│ ├── eu.json
│ ├── fa.json
│ ├── fi.json
│ ├── fr.json
│ ├── gu.json
│ ├── he.json
│ ├── hi.json
│ ├── hr.json
│ ├── hu.json
│ ├── is.json
│ ├── it.json
│ ├── ja.json
│ ├── ko.json
│ ├── lt.json
│ ├── nl.json
│ ├── no.json
│ ├── pl.json
│ ├── pt-br.json
│ ├── ro.json
│ ├── ru.json
│ ├── sk.json
│ ├── sl.json
│ ├── sv.json
│ ├── tr.json
│ ├── uk.json
│ ├── vi-vn.json
│ ├── zh-cn.json
│ └── zh-tw.json
├── custom-metadata-provider-specification.yaml
├── docker-compose.yml
├── docker-template.xml
├── docs/
│ ├── README.md
│ ├── controllers/
│ │ ├── AuthorController.yaml
│ │ ├── EmailController.yaml
│ │ ├── LibraryController.yaml
│ │ ├── NotificationController.yaml
│ │ ├── PodcastController.yaml
│ │ └── SeriesController.yaml
│ ├── objects/
│ │ ├── Folder.yaml
│ │ ├── Library.yaml
│ │ ├── LibraryItem.yaml
│ │ ├── Notification.yaml
│ │ ├── entities/
│ │ │ ├── Author.yaml
│ │ │ ├── PodcastEpisode.yaml
│ │ │ └── Series.yaml
│ │ ├── files/
│ │ │ ├── AudioFile.yaml
│ │ │ ├── AudioTrack.yaml
│ │ │ └── EBookFile.yaml
│ │ ├── mediaTypes/
│ │ │ ├── Book.yaml
│ │ │ ├── Podcast.yaml
│ │ │ └── media.yaml
│ │ ├── metadata/
│ │ │ ├── AudioMetaTags.yaml
│ │ │ ├── BookMetadata.yaml
│ │ │ ├── FileMetadata.yaml
│ │ │ └── PodcastMetadata.yaml
│ │ └── settings/
│ │ └── EmailSettings.yaml
│ ├── openapi.json
│ ├── root.yaml
│ └── schemas.yaml
├── index.js
├── package.json
├── prod.js
├── readme.md
├── server/
│ ├── Auth.js
│ ├── Database.js
│ ├── Logger.js
│ ├── Server.js
│ ├── SocketAuthority.js
│ ├── Watcher.js
│ ├── auth/
│ │ ├── LocalAuthStrategy.js
│ │ ├── OidcAuthStrategy.js
│ │ └── TokenManager.js
│ ├── controllers/
│ │ ├── ApiKeyController.js
│ │ ├── AuthorController.js
│ │ ├── BackupController.js
│ │ ├── CacheController.js
│ │ ├── CollectionController.js
│ │ ├── CustomMetadataProviderController.js
│ │ ├── EmailController.js
│ │ ├── FileSystemController.js
│ │ ├── LibraryController.js
│ │ ├── LibraryItemController.js
│ │ ├── MeController.js
│ │ ├── MiscController.js
│ │ ├── NotificationController.js
│ │ ├── PlaylistController.js
│ │ ├── PodcastController.js
│ │ ├── RSSFeedController.js
│ │ ├── SearchController.js
│ │ ├── SeriesController.js
│ │ ├── SessionController.js
│ │ ├── ShareController.js
│ │ ├── StatsController.js
│ │ ├── ToolsController.js
│ │ └── UserController.js
│ ├── finders/
│ │ ├── AuthorFinder.js
│ │ ├── BookFinder.js
│ │ └── PodcastFinder.js
│ ├── libs/
│ │ ├── archiver/
│ │ │ ├── LICENSE
│ │ │ ├── archiverUtils/
│ │ │ │ ├── balancedMatch/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── braceExpansion/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── file.js
│ │ │ │ ├── fsRealpath/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── old.js
│ │ │ │ ├── glob/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── common.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── sync.js
│ │ │ │ ├── index.js
│ │ │ │ ├── inflight/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lazystream/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── readable-stream/
│ │ │ │ │ ├── lib/
│ │ │ │ │ │ ├── _stream_duplex.js
│ │ │ │ │ │ ├── _stream_passthrough.js
│ │ │ │ │ │ ├── _stream_readable.js
│ │ │ │ │ │ ├── _stream_transform.js
│ │ │ │ │ │ ├── _stream_writable.js
│ │ │ │ │ │ └── internal/
│ │ │ │ │ │ └── streams/
│ │ │ │ │ │ ├── BufferList.js
│ │ │ │ │ │ ├── destroy.js
│ │ │ │ │ │ ├── stream-browser.js
│ │ │ │ │ │ └── stream.js
│ │ │ │ │ ├── passthrough.js
│ │ │ │ │ └── readable.js
│ │ │ │ ├── lodash.difference/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lodash.flatten/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lodash.isplainobject/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lodash.union/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── minimatch/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── readableStream/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── _stream_duplex.js
│ │ │ │ │ ├── _stream_passthrough.js
│ │ │ │ │ ├── _stream_readable.js
│ │ │ │ │ ├── _stream_transform.js
│ │ │ │ │ ├── _stream_writable.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── internal/
│ │ │ │ │ │ ├── streams/
│ │ │ │ │ │ │ ├── add-abort-signal.js
│ │ │ │ │ │ │ ├── buffer_list.js
│ │ │ │ │ │ │ ├── compose.js
│ │ │ │ │ │ │ ├── destroy.js
│ │ │ │ │ │ │ ├── duplex.js
│ │ │ │ │ │ │ ├── duplexify.js
│ │ │ │ │ │ │ ├── end-of-stream.js
│ │ │ │ │ │ │ ├── from.js
│ │ │ │ │ │ │ ├── lazy_transform.js
│ │ │ │ │ │ │ ├── legacy.js
│ │ │ │ │ │ │ ├── operators.js
│ │ │ │ │ │ │ ├── passthrough.js
│ │ │ │ │ │ │ ├── pipeline.js
│ │ │ │ │ │ │ ├── readable.js
│ │ │ │ │ │ │ ├── state.js
│ │ │ │ │ │ │ ├── transform.js
│ │ │ │ │ │ │ ├── utils.js
│ │ │ │ │ │ │ └── writable.js
│ │ │ │ │ │ └── validators.js
│ │ │ │ │ ├── ours/
│ │ │ │ │ │ ├── browser.js
│ │ │ │ │ │ ├── errors.js
│ │ │ │ │ │ ├── primordials.js
│ │ │ │ │ │ └── util.js
│ │ │ │ │ ├── stream/
│ │ │ │ │ │ └── promises.js
│ │ │ │ │ └── stream.js
│ │ │ │ ├── safeBuffer/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── stringDecoder/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ └── wrappy/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── buffer-crc32/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── compress-commons/
│ │ │ │ ├── LICENSE
│ │ │ │ ├── archivers/
│ │ │ │ │ ├── archive-entry.js
│ │ │ │ │ ├── archive-output-stream.js
│ │ │ │ │ └── zip/
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── general-purpose-bit.js
│ │ │ │ │ ├── unix-stat.js
│ │ │ │ │ ├── util.js
│ │ │ │ │ ├── zip-archive-entry.js
│ │ │ │ │ └── zip-archive-output-stream.js
│ │ │ │ ├── index.js
│ │ │ │ └── util/
│ │ │ │ └── index.js
│ │ │ ├── crc32/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── crc32-stream/
│ │ │ │ ├── LICENSE
│ │ │ │ ├── crc32-stream.js
│ │ │ │ ├── deflate-crc32-stream.js
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── lib/
│ │ │ │ ├── core.js
│ │ │ │ ├── error.js
│ │ │ │ └── plugins/
│ │ │ │ ├── json.js
│ │ │ │ └── zip.js
│ │ │ ├── normalize-path/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── readdir-glob/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ └── zip-stream/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── async/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── bcryptjs/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── busboy/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── types/
│ │ │ │ ├── multipart.js
│ │ │ │ └── urlencoded.js
│ │ │ └── utils.js
│ │ ├── commandLineArgs/
│ │ │ └── index.js
│ │ ├── dateAndTime/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── expressFileupload/
│ │ │ ├── LICENSE
│ │ │ ├── fileFactory.js
│ │ │ ├── index.js
│ │ │ ├── isEligibleRequest.js
│ │ │ ├── memHandler.js
│ │ │ ├── processMultipart.js
│ │ │ ├── processNested.js
│ │ │ ├── tempFileHandler.js
│ │ │ ├── uploadtimer.js
│ │ │ └── utilities.js
│ │ ├── fastSort/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── fluentFfmpeg/
│ │ │ ├── LICENSE
│ │ │ ├── capabilities.js
│ │ │ ├── ffprobe.js
│ │ │ ├── index.d.ts
│ │ │ ├── index.js
│ │ │ ├── options/
│ │ │ │ ├── audio.js
│ │ │ │ ├── custom.js
│ │ │ │ ├── inputs.js
│ │ │ │ ├── misc.js
│ │ │ │ ├── output.js
│ │ │ │ ├── video.js
│ │ │ │ └── videosize.js
│ │ │ ├── presets/
│ │ │ │ ├── divx.js
│ │ │ │ ├── flashvideo.js
│ │ │ │ └── podcast.js
│ │ │ ├── processor.js
│ │ │ ├── recipes.js
│ │ │ └── utils.js
│ │ ├── fsExtra/
│ │ │ ├── LICENSE
│ │ │ ├── copy/
│ │ │ │ ├── copy-sync.js
│ │ │ │ ├── copy.js
│ │ │ │ └── index.js
│ │ │ ├── empty/
│ │ │ │ └── index.js
│ │ │ ├── ensure/
│ │ │ │ ├── file.js
│ │ │ │ ├── index.js
│ │ │ │ ├── link.js
│ │ │ │ ├── symlink-paths.js
│ │ │ │ ├── symlink-type.js
│ │ │ │ └── symlink.js
│ │ │ ├── fs/
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── mkdirs/
│ │ │ │ ├── index.js
│ │ │ │ ├── make-dir.js
│ │ │ │ └── utils.js
│ │ │ ├── move/
│ │ │ │ ├── index.js
│ │ │ │ ├── move-sync.js
│ │ │ │ └── move.js
│ │ │ ├── path-exists/
│ │ │ │ └── index.js
│ │ │ ├── remove/
│ │ │ │ ├── index.js
│ │ │ │ └── rimraf.js
│ │ │ └── util/
│ │ │ ├── stat.js
│ │ │ └── utimes.js
│ │ ├── fusejs/
│ │ │ └── index.js
│ │ ├── imageType/
│ │ │ ├── LICENSE
│ │ │ ├── fileType.js
│ │ │ └── index.js
│ │ ├── isexe/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── mode.js
│ │ │ └── windows.js
│ │ ├── jsonwebtoken/
│ │ │ ├── LICENSE
│ │ │ ├── decode.js
│ │ │ ├── index.js
│ │ │ ├── lib/
│ │ │ │ ├── JsonWebTokenError.js
│ │ │ │ ├── NotBeforeError.js
│ │ │ │ ├── TokenExpiredError.js
│ │ │ │ └── timespan.js
│ │ │ ├── sign.js
│ │ │ └── verify.js
│ │ ├── jwa/
│ │ │ ├── LICENSE
│ │ │ ├── buffer-equal-constant-time/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── ecdsa-sig-formatter/
│ │ │ │ ├── LICENSE
│ │ │ │ ├── index.js
│ │ │ │ └── param-bytes-for-alg.js
│ │ │ └── index.js
│ │ ├── jws/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ └── lib/
│ │ │ ├── data-stream.js
│ │ │ ├── sign-stream.js
│ │ │ ├── tostring.js
│ │ │ └── verify-stream.js
│ │ ├── libarchive/
│ │ │ ├── LICENSE
│ │ │ ├── archive.js
│ │ │ ├── libarchiveWorker.js
│ │ │ ├── wasm-libarchive.js
│ │ │ └── wasm-module.js
│ │ ├── lodash.once/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── memorystore/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── nodeCron/
│ │ │ ├── LICENSE
│ │ │ ├── background-scheduled-task/
│ │ │ │ ├── daemon.js
│ │ │ │ └── index.js
│ │ │ ├── convert-expression/
│ │ │ │ ├── asterisk-to-range-conversion.js
│ │ │ │ ├── index.js
│ │ │ │ ├── month-names-conversion.js
│ │ │ │ ├── range-conversion.js
│ │ │ │ ├── step-values-conversion.js
│ │ │ │ └── week-day-names-conversion.js
│ │ │ ├── index.js
│ │ │ ├── pattern-validation.js
│ │ │ ├── scheduled-task.js
│ │ │ ├── scheduler.js
│ │ │ ├── storage.js
│ │ │ ├── task.js
│ │ │ └── time-matcher.js
│ │ ├── nodeFfprobe/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── nodeStreamZip/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── passportLocal/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ └── strategy.js
│ │ ├── readChunk/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── pify.js
│ │ │ └── withOpenFile.js
│ │ ├── recursiveReaddirAsync/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── requestIp/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ └── isJs.js
│ │ ├── rss/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── sanitizeHtml/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── streamsearch/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── uaParser/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── umzug/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── storage/
│ │ │ │ ├── contract.js
│ │ │ │ ├── index.js
│ │ │ │ ├── json.js
│ │ │ │ ├── memory.js
│ │ │ │ ├── mongodb.js
│ │ │ │ └── sequelize.js
│ │ │ ├── templates.js
│ │ │ ├── types.js
│ │ │ └── umzug.js
│ │ ├── universalify/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── watcher/
│ │ │ ├── LICENSE
│ │ │ ├── aborter/
│ │ │ │ ├── controller.js
│ │ │ │ └── signal.js
│ │ │ ├── are-shallow-equal.js
│ │ │ ├── atomically/
│ │ │ │ ├── consts.js
│ │ │ │ ├── index.js
│ │ │ │ └── utils/
│ │ │ │ ├── attemptify.js
│ │ │ │ ├── fs.js
│ │ │ │ ├── fs_handlers.js
│ │ │ │ ├── lang.js
│ │ │ │ ├── retryify.js
│ │ │ │ ├── retryify_queue.js
│ │ │ │ ├── scheduler.js
│ │ │ │ └── temp.js
│ │ │ ├── constants.js
│ │ │ ├── debounce.js
│ │ │ ├── enums.js
│ │ │ ├── is-primitive.js
│ │ │ ├── promise-concurrency-limiter.js
│ │ │ ├── ripstat/
│ │ │ │ ├── consts.js
│ │ │ │ ├── index.js
│ │ │ │ └── stats.js
│ │ │ ├── string-indexes.js
│ │ │ ├── tiny-readdir.js
│ │ │ ├── types.js
│ │ │ ├── utils.js
│ │ │ ├── watcher.js
│ │ │ ├── watcher_handler.js
│ │ │ ├── watcher_locker.js
│ │ │ ├── watcher_locks_resolver.js
│ │ │ ├── watcher_poller.js
│ │ │ └── watcher_stats.js
│ │ ├── which/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ └── xml/
│ │ ├── LICENSE
│ │ ├── escapeForXML.js
│ │ └── index.js
│ ├── managers/
│ │ ├── AbMergeManager.js
│ │ ├── ApiCacheManager.js
│ │ ├── AudioMetadataManager.js
│ │ ├── BackupManager.js
│ │ ├── BinaryManager.js
│ │ ├── CacheManager.js
│ │ ├── CoverManager.js
│ │ ├── CoverSearchManager.js
│ │ ├── CronManager.js
│ │ ├── EmailManager.js
│ │ ├── LogManager.js
│ │ ├── MigrationManager.js
│ │ ├── NotificationManager.js
│ │ ├── PlaybackSessionManager.js
│ │ ├── PodcastManager.js
│ │ ├── RssFeedManager.js
│ │ ├── ShareManager.js
│ │ └── TaskManager.js
│ ├── migrations/
│ │ ├── changelog.md
│ │ ├── readme.md
│ │ ├── v2.15.0-series-column-unique.js
│ │ ├── v2.15.1-reindex-nocase.js
│ │ ├── v2.15.2-index-creation.js
│ │ ├── v2.17.0-uuid-replacement.js
│ │ ├── v2.17.3-fk-constraints.js
│ │ ├── v2.17.4-use-subfolder-for-oidc-redirect-uris.js
│ │ ├── v2.17.5-remove-host-from-feed-urls.js
│ │ ├── v2.17.6-share-add-isdownloadable.js
│ │ ├── v2.17.7-add-indices.js
│ │ ├── v2.19.1-copy-title-to-library-items.js
│ │ ├── v2.19.4-improve-podcast-queries.js
│ │ ├── v2.20.0-improve-author-sort-queries.js
│ │ ├── v2.26.0-create-auth-tables.js
│ │ └── v2.33.0-add-discover-query-indexes.js
│ ├── models/
│ │ ├── ApiKey.js
│ │ ├── Author.js
│ │ ├── Book.js
│ │ ├── BookAuthor.js
│ │ ├── BookSeries.js
│ │ ├── Collection.js
│ │ ├── CollectionBook.js
│ │ ├── CustomMetadataProvider.js
│ │ ├── Device.js
│ │ ├── Feed.js
│ │ ├── FeedEpisode.js
│ │ ├── Library.js
│ │ ├── LibraryFolder.js
│ │ ├── LibraryItem.js
│ │ ├── MediaItemShare.js
│ │ ├── MediaProgress.js
│ │ ├── PlaybackSession.js
│ │ ├── Playlist.js
│ │ ├── PlaylistMediaItem.js
│ │ ├── Podcast.js
│ │ ├── PodcastEpisode.js
│ │ ├── Series.js
│ │ ├── Session.js
│ │ ├── Setting.js
│ │ └── User.js
│ ├── objects/
│ │ ├── Backup.js
│ │ ├── DailyLog.js
│ │ ├── DeviceInfo.js
│ │ ├── Notification.js
│ │ ├── PlaybackSession.js
│ │ ├── PodcastEpisodeDownload.js
│ │ ├── Stream.js
│ │ ├── Task.js
│ │ ├── TrackProgressMonitor.js
│ │ ├── files/
│ │ │ ├── AudioFile.js
│ │ │ ├── AudioTrack.js
│ │ │ ├── EBookFile.js
│ │ │ └── LibraryFile.js
│ │ ├── metadata/
│ │ │ ├── AudioMetaTags.js
│ │ │ └── FileMetadata.js
│ │ └── settings/
│ │ ├── EmailSettings.js
│ │ ├── NotificationSettings.js
│ │ └── ServerSettings.js
│ ├── providers/
│ │ ├── Audible.js
│ │ ├── AudiobookCovers.js
│ │ ├── Audnexus.js
│ │ ├── CustomProviderAdapter.js
│ │ ├── FantLab.js
│ │ ├── GoogleBooks.js
│ │ ├── MusicBrainz.js
│ │ ├── OpenLibrary.js
│ │ └── iTunes.js
│ ├── routers/
│ │ ├── ApiRouter.js
│ │ ├── HlsRouter.js
│ │ └── PublicRouter.js
│ ├── scanner/
│ │ ├── AbsMetadataFileScanner.js
│ │ ├── AudioFileScanner.js
│ │ ├── BookScanner.js
│ │ ├── LibraryItemScanData.js
│ │ ├── LibraryItemScanner.js
│ │ ├── LibraryScan.js
│ │ ├── LibraryScanner.js
│ │ ├── MediaProbeData.js
│ │ ├── NfoFileScanner.js
│ │ ├── OpfFileScanner.js
│ │ ├── PodcastScanner.js
│ │ ├── ScanLogger.js
│ │ └── Scanner.js
│ └── utils/
│ ├── areEquivalent.js
│ ├── comicBookExtractors.js
│ ├── constants.js
│ ├── ffmpegHelpers.js
│ ├── fileUtils.js
│ ├── generators/
│ │ ├── abmetadataGenerator.js
│ │ ├── hlsPlaylistGenerator.js
│ │ └── opmlGenerator.js
│ ├── globals.js
│ ├── htmlEntities.js
│ ├── htmlSanitizer.js
│ ├── index.js
│ ├── libraryHelpers.js
│ ├── longTimeout.js
│ ├── migrations/
│ │ ├── absMetadataMigration.js
│ │ ├── dbMigration.js
│ │ └── oldDbFiles.js
│ ├── notifications.js
│ ├── parsers/
│ │ ├── parseComicInfoMetadata.js
│ │ ├── parseComicMetadata.js
│ │ ├── parseEbookMetadata.js
│ │ ├── parseEpubMetadata.js
│ │ ├── parseFullName.js
│ │ ├── parseNameString.js
│ │ ├── parseNfoMetadata.js
│ │ ├── parseOPML.js
│ │ ├── parseOpfMetadata.js
│ │ ├── parseOverdriveMediaMarkers.js
│ │ └── parseSeriesString.js
│ ├── podcastUtils.js
│ ├── prober.js
│ ├── profiler.js
│ ├── queries/
│ │ ├── adminStats.js
│ │ ├── authorFilters.js
│ │ ├── libraryFilters.js
│ │ ├── libraryItemFilters.js
│ │ ├── libraryItemsBookFilters.js
│ │ ├── libraryItemsPodcastFilters.js
│ │ ├── seriesFilters.js
│ │ └── userStats.js
│ ├── rateLimiterFactory.js
│ ├── scandir.js
│ ├── stringifySequelizeQuery.js
│ └── zipHelpers.js
└── test/
└── server/
├── Logger.test.js
├── controllers/
│ ├── LibraryItemController.test.js
│ └── MeController.test.js
├── finders/
│ └── BookFinder.test.js
├── managers/
│ ├── ApiCacheManager.test.js
│ ├── BinaryManager.test.js
│ ├── MigrationManager.test.js
│ └── migrations/
│ ├── v1.0.0-migration.js
│ ├── v1.1.0-migration.js
│ ├── v1.10.0-migration.js
│ └── v1.2.0-migration.js
├── migrations/
│ ├── v0.0.1-migration_example.js
│ ├── v0.0.1-migration_example.test.js
│ ├── v2.15.0-series-column-unique.test.js
│ ├── v2.17.3-fk-constraints.test.js
│ ├── v2.17.4-use-subfolder-for-oidc-redirect-uris.test.js
│ ├── v2.17.5-remove-host-from-feed-urls.test.js
│ ├── v2.17.6-share-add-isdownloadable.test.js
│ ├── v2.19.1-copy-title-to-library-items.test.js
│ ├── v2.19.4-improve-podcast-queries.test.js
│ └── v2.20.0-improve-author-sort-queries.test.js
├── objects/
│ └── TrackProgressMonitor.test.js
├── providers/
│ └── Audible.test.js
└── utils/
├── ffmpegHelpers.test.js
├── fileUtils.test.js
├── parsers/
│ ├── parseNameString.test.js
│ ├── parseNfoMetadata.test.js
│ └── parseOpfMetadata.test.js
├── scandir.test.js
└── stringifySequeslizeQuery.test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/Dockerfile
================================================
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT=20
FROM mcr.microsoft.com/devcontainers/javascript-node:0-${VARIANT} as base
# Setup the node environment
ENV NODE_ENV=development
# Install additional OS packages.
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
curl tzdata ffmpeg && \
rm -rf /var/lib/apt/lists/*
================================================
FILE: .devcontainer/dev.js
================================================
// Using port 3333 is important when running the client web app separately
const Path = require('path')
module.exports.config = {
Port: 3333,
ConfigPath: Path.resolve('config'),
MetadataPath: Path.resolve('metadata'),
FFmpegPath: '/usr/bin/ffmpeg',
FFProbePath: '/usr/bin/ffprobe',
SkipBinariesCheck: false
}
================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Audiobookshelf",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"args": {
"VARIANT": "20"
}
},
"mounts": [
"source=abs-server-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume",
"source=abs-client-node_modules,target=${containerWorkspaceFolder}/client/node_modules,type=volume"
],
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
3000,
3333
],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "sh .devcontainer/post-create.sh",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint",
"octref.vetur"
]
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
================================================
FILE: .devcontainer/post-create.sh
================================================
#!/bin/sh
# Mark the working directory as safe for use with git
git config --global --add safe.directory $PWD
# If there is no dev.js file, create it
if [ ! -f dev.js ]; then
cp .devcontainer/dev.js .
fi
# Update permissions for node_modules folders
# https://code.visualstudio.com/remote/advancedcontainers/improve-performance#_use-a-targeted-named-volume
if [ -d node_modules ]; then
sudo chown $(id -u):$(id -g) node_modules
fi
if [ -d client/node_modules ]; then
sudo chown $(id -u):$(id -g) client/node_modules
fi
# Install packages for the server
if [ -f package.json ]; then
npm ci
fi
# Install packages and build the client
if [ -f client/package.json ]; then
(cd client; npm ci; npm run generate)
fi
================================================
FILE: .dockerignore
================================================
.env
node_modules
npm-debug.log
.git
.gitignore
/config
/audiobooks
/audiobooks2
/media/
/metadata
dev.js
test/
/client/.nuxt/
/client/dist/
/dist/
/deploy/
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .gitattributes
================================================
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Declare files that will always have CRLF line endings on checkout.
.devcontainer/post-create.sh text eol=lf
================================================
FILE: .github/ISSUE_TEMPLATE/bug.yaml
================================================
name: 🐞 Bug Report
description: File a bug/issue and help us improve Audiobookshelf
title: '[Bug]: '
labels: ['bug', 'triage']
body:
- type: markdown
attributes:
value: 'Thank you for filing a bug report! 🐛'
- type: markdown
attributes:
value: 'Please first search for your issue and check the [docs](https://audiobookshelf.org/docs).'
- type: markdown
attributes:
value: 'Report issues with the mobile app [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose).'
- type: markdown
attributes:
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: 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. '
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'
validations:
required: true
- type: dropdown
id: install
attributes:
label: How are you running audiobookshelf?
options:
- Docker
- Debian/PPA
- Windows Tray App
- Built from source
- Other (list in "Additional Notes" box)
validations:
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.'
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/HQgCbd6E75
about: Ask questions, get help troubleshooting, and join the Abs community here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature.yml
================================================
name: 🚀 Feature Request
description: Request a feature/enhancement
title: '[Enhancement]: '
labels: ['enhancement']
body:
- type: markdown
attributes:
value: '#### *Mobile app features should be [requested here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)*.'
- type: markdown
attributes:
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
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.
================================================
FILE: .github/pull_request_template.md
================================================
<!--
For Work In Progress Pull Requests, please use the Draft PR feature,
see https://github.blog/2019-02-14-introducing-draft-pull-requests/ for further details.
If you do not follow this template, the PR may be closed without review.
Please ensure all checks pass.
If you are a new contributor, the workflows will need to be manually approved before they run.
-->
## Brief summary
<!-- Please provide a brief summary of what your PR attempts to achieve. -->
## Which issue is fixed?
<!-- Which issue number does this PR fix? Ex: "Fixes #1234" -->
## In-depth Description
<!--
Describe your solution in more depth.
How does it work? Why is this the best solution?
Does it solve a problem that affects multiple users or is this an edge case for your setup?
-->
## How have you tested this?
<!-- Please describe in detail with reproducible steps how you tested your changes. -->
## Screenshots
<!-- If your PR includes any changes to the web client, please include screenshots or a short video from before and after your changes. -->
================================================
FILE: .github/workflows/apply_comments.yaml
================================================
name: Add issue comments by label
on:
issues:
types:
- labeled
jobs:
help-wanted:
if: github.event.label.name == 'help wanted'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Help wanted comment
run: gh issue comment "$NUMBER" --body "$BODY"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
BODY: >
This issue is not able to be completed due to limited bandwidth or access to the required test hardware.
This issue is available for anyone to work on.
config-issue:
if: github.event.label.name == 'config-issue'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Config issue comment
run: gh issue close "$NUMBER" --reason "not planned" --comment "$BODY"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
BODY: >
After reviewing this issue, this appears to be a problem with your setup and not Audiobookshelf. This issue is being closed to keep the issue tracker focused on Audiobookshelf itself. Please reach out on the Audiobookshelf Discord for community support.
Some common search terms to help you find the solution to your problem:
- Reverse proxy
- Enabling websockets
- SSL (https vs http)
- Configuring a static IP
- `localhost` versus IP address
- hairpin NAT
- VPN
- firewall ports
- public versus private network
- bridge versus host mode
- Docker networking
- DNS (such as EAI_AGAIN errors)
After you have followed these steps, please post the solution or steps you followed to fix the problem to help others in the future, or show that it is a problem with Audiobookshelf so we can reopen the issue.
================================================
FILE: .github/workflows/close-issues-on-release.yml
================================================
name: Close fixed issues on release.
on:
release:
types: [published]
permissions:
contents: read
issues: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Close issues marked as fixed upon a release.
uses: gcampbell-msft/fixed-pending-release@7fa1b75a0c04bcd4b375110522878e5f6100cff5
with:
label: 'awaiting release'
removeLabel: true
applyToAll: true
message: Fixed in [${releaseTag}](${releaseUrl}).
================================================
FILE: .github/workflows/close_blank_issues.yaml
================================================
name: Close Issues not using a template
on:
issues:
types:
- opened
permissions:
issues: write
jobs:
close_issue:
runs-on: ubuntu-latest
steps:
- name: Check issue headings
uses: actions/github-script@v7
with:
script: |
const issueBody = context.payload.issue.body || "";
// Match Markdown headings (e.g., # Heading, ## Heading)
const headingRegex = /^(#{1,6})\s.+/gm;
const headings = [...issueBody.matchAll(headingRegex)];
if (headings.length < 3) {
// Post a comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: "Thank you for opening an issue! To help us review your request efficiently, please use one of the provided issue templates. If you're seeking information or have a general question, consider opening a Discussion or joining the conversation on our Discord. Thanks!"
});
// Close the issue
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
state: "closed"
});
}
================================================
FILE: .github/workflows/codeql.yml
================================================
name: 'CodeQL'
on:
push:
branches: ['master']
# Only build when files in these directories have been changed
paths:
- client/**
- server/**
- test/**
- index.js
- package.json
pull_request:
# The branches below must be a subset of the branches above
branches: ['master']
# Only build when files in these directories have been changed
paths:
- client/**
- server/**
- test/**
- index.js
- package.json
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@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
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@v3
# ℹ️ 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@v3
with:
category: '/language:${{matrix.language}}'
================================================
FILE: .github/workflows/component-tests.yml
================================================
name: Run Component Tests
on:
workflow_dispatch:
inputs:
ref:
description: 'Branch/Tag/SHA to test'
required: true
pull_request:
paths:
- 'client/**'
- '.github/workflows/component-tests.yml'
push:
paths:
- 'client/**'
- '.github/workflows/component-tests.yml'
jobs:
run-component-tests:
name: Run Component 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
cache: 'npm'
- name: Install dependencies
run: |
cd client
npm ci
- name: Run tests
run: |
cd client
npm test
================================================
FILE: .github/workflows/docker-build.yml
================================================
---
name: Build and Push Docker Image
on:
# Allows you to run workflow manually from Actions tab
workflow_dispatch:
inputs:
tags:
description: 'Docker Tag'
required: true
default: 'latest'
push:
branches: [main, master]
tags:
- 'v*.*.*'
# Only build when files in these directories have been changed
paths:
- client/**
- server/**
- index.js
- package.json
jobs:
build:
if: ${{ !contains(github.event.head_commit.message, 'skip ci') && github.repository == 'advplyr/audiobookshelf' }}
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: advplyr/audiobookshelf,ghcr.io/${{ github.repository_owner }}/audiobookshelf
tags: |
type=edge,branch=master
type=semver,pattern={{version}}
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to Dockerhub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_PASSWORD }}
- name: Build image
uses: docker/build-push-action@v6
with:
tags: ${{ github.event.inputs.tags || steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
context: .
platforms: linux/amd64,linux/arm64
push: true
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
================================================
FILE: .github/workflows/i18n-integration.yml
================================================
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
cache: 'npm'
# 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.3.0
with:
directory: 'client/strings/' # Adjust the directory path as needed
================================================
FILE: .github/workflows/integration-test.yml
================================================
name: Integration Test
on:
pull_request:
push:
branches-ignore:
- 'dependabot/**' # Don't run dependabot branches, as they are already covered by pull requests
# Only build when files in these directories have been changed
paths:
- client/**
- server/**
- test/**
- index.js
- package.json
jobs:
build:
name: build and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: install pkg (using yao-pkg fork for targeting node20)
run: npm install -g @yao-pkg/pkg
- name: get client dependencies
working-directory: client
run: npm ci
- name: build client
working-directory: client
run: npm run generate
- name: get server dependencies
run: npm ci --only=production
- name: build binary
run: pkg -t node20-linux-x64 -o audiobookshelf .
- name: run audiobookshelf
run: |
./audiobookshelf &
sleep 5
- name: test if server is available
run: curl -sf http://127.0.0.1:3333 | grep Audiobookshelf
================================================
FILE: .github/workflows/lint-openapi.yml
================================================
name: API linting
# Run on pull requests or pushes when there is a change to any OpenAPI files in docs/
on:
pull_request:
push:
paths:
- 'docs/**'
# This action only needs read permissions
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# Check out the repository
- name: Checkout
uses: actions/checkout@v4
# Set up node to run the javascript
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
# Install Redocly CLI
- name: Install Redocly CLI
run: npm install -g @redocly/cli@latest
# Perform linting for exploded spec
- name: Run linting for exploded spec
run: redocly lint docs/root.yaml --format=github-actions
# Perform linting for bundled spec
- name: Run linting for bundled spec
run: redocly lint docs/openapi.json --format=github-actions
================================================
FILE: .github/workflows/notify-abs-windows.yml
================================================
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
================================================
FILE: .github/workflows/unit-tests.yml
================================================
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
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
================================================
FILE: .gitignore
================================================
.env
/dev.js
**/node_modules/
/config/
/audiobooks/
/audiobooks2/
/podcasts/
/media/
/metadata/
/plugins/
/client/.nuxt/
/client/dist/
/dist/
/deploy/
/coverage/
/.nyc_output/
/ffmpeg*
/ffprobe*
/unicode*
/libnusqlite3*
sw.*
.DS_STORE
.idea/*
tailwind.compiled.css
tailwind.config.js
================================================
FILE: .prettierrc
================================================
{
"semi": false,
"singleQuote": true,
"printWidth": 400,
"proseWrap": "never",
"trailingComma": "none",
"overrides": [
{
"files": ["*.html"],
"options": {
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
}
]
}
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": [
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
"octref.vetur"
]
}
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug server",
"runtimeExecutable": "npm",
"args": [
"run",
"dev"
],
"skipFiles": [
"<node_internals>/**"
]
},
{
"type": "node",
"request": "launch",
"name": "Debug client (nuxt)",
"runtimeExecutable": "npm",
"args": [
"run",
"dev"
],
"cwd": "${workspaceFolder}/client",
"skipFiles": [
"${workspaceFolder}/<node_internals>/**"
]
}
],
"compounds": [
{
"name": "Debug server and client (nuxt)",
"configurations": [
"Debug server",
"Debug client (nuxt)"
]
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"vetur.format.defaultFormatterOptions": {
"prettier": {
"semi": false,
"singleQuote": true,
"printWidth": 400,
"proseWrap": "never",
"trailingComma": "none"
},
"prettyhtml": {
"printWidth": 400,
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
},
"editor.formatOnSave": true,
"editor.detectIndentation": true,
"editor.tabSize": 2,
"javascript.format.semicolons": "remove",
"[javascript][json][jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "octref.vetur"
}
}
================================================
FILE: .vscode/tasks.json
================================================
{
"version": "2.0.0",
"tasks": [
{
"path": "client",
"type": "npm",
"script": "generate",
"detail": "nuxt generate",
"label": "Build client",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"dependsOn": [
"Build client"
],
"type": "npm",
"script": "dev",
"detail": "nodemon --watch server index.js",
"label": "Run server",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"path": "client",
"type": "npm",
"script": "dev",
"detail": "nuxt",
"label": "Run Live-reload client",
"group": {
"kind": "test",
"isDefault": false
}
}
]
}
================================================
FILE: Dockerfile
================================================
ARG NUSQLITE3_DIR="/usr/local/lib/nusqlite3"
ARG NUSQLITE3_PATH="${NUSQLITE3_DIR}/libnusqlite3.so"
### STAGE 0: Build client ###
FROM node:20-alpine AS build-client
WORKDIR /client
COPY /client /client
RUN npm ci && npm cache clean --force
RUN npm run generate
### STAGE 1: Build server ###
FROM node:20-alpine AS build-server
ARG NUSQLITE3_DIR
ARG TARGETPLATFORM
ENV NODE_ENV=production
RUN apk add --no-cache --update \
curl \
make \
python3 \
g++ \
unzip
WORKDIR /server
COPY index.js package* /server
COPY /server /server/server
RUN case "$TARGETPLATFORM" in \
"linux/amd64") \
curl -L -o /tmp/library.zip "https://github.com/mikiher/nunicode-sqlite/releases/download/v1.2/libnusqlite3-linux-musl-x64.zip" ;; \
"linux/arm64") \
curl -L -o /tmp/library.zip "https://github.com/mikiher/nunicode-sqlite/releases/download/v1.2/libnusqlite3-linux-musl-arm64.zip" ;; \
*) echo "Unsupported platform: $TARGETPLATFORM" && exit 1 ;; \
esac && \
unzip /tmp/library.zip -d $NUSQLITE3_DIR && \
rm /tmp/library.zip
RUN npm ci --only=production
### STAGE 2: Create minimal runtime image ###
FROM node:20-alpine
ARG NUSQLITE3_DIR
ARG NUSQLITE3_PATH
# Install only runtime dependencies
RUN apk add --no-cache --update \
tzdata \
ffmpeg \
tini
WORKDIR /app
# Copy compiled frontend and server from build stages
COPY --from=build-client /client/dist /app/client/dist
COPY --from=build-server /server /app
COPY --from=build-server ${NUSQLITE3_PATH} ${NUSQLITE3_PATH}
EXPOSE 80
ENV PORT=80
ENV NODE_ENV=production
ENV CONFIG_PATH="/config"
ENV METADATA_PATH="/metadata"
ENV SOURCE="docker"
ENV NUSQLITE3_DIR=${NUSQLITE3_DIR}
ENV NUSQLITE3_PATH=${NUSQLITE3_PATH}
ENTRYPOINT ["tini", "--"]
CMD ["node", "index.js"]
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Audiobookshelf Self-hosted audiobook server
Copyright (C) 2021 advplyr
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Audiobookshelf Copyright (C) 2021 advplyr
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: build/debian/DEBIAN/control
================================================
Package: audiobookshelf
Version: 1.6.41
Section: base
Priority: optional
Architecture: amd64
Depends:
Maintainer: advplyr
Description: Self-hosted audiobook server for managing and playing audiobooks
================================================
FILE: build/debian/DEBIAN/postinst
================================================
#!/bin/bash
set -e
set -o pipefail
ABS_LOG_DIR="/var/log/audiobookshelf"
declare -r init_type='auto'
declare -ri no_rebuild='0'
start_service () {
: "${1:?'Service name was not defined'}"
declare -r service_name="$1"
if hash systemctl 2> /dev/null; then
if [[ "$init_type" == 'auto' || "$init_type" == 'systemd' ]]; then
{
systemctl enable "$service_name.service" && \
systemctl start "$service_name.service"
} || echo "$service_name could not be registered or started"
fi
elif hash service 2> /dev/null; then
if [[ "$init_type" == 'auto' || "$init_type" == 'upstart' || "$init_type" == 'sysv' ]]; then
service "$service_name" start || echo "$service_name could not be registered or started"
fi
elif hash start 2> /dev/null; then
if [[ "$init_type" == 'auto' || "$init_type" == 'upstart' ]]; then
start "$service_name" || echo "$service_name could not be registered or started"
fi
elif hash update-rc.d 2> /dev/null; then
if [[ "$init_type" == 'auto' || "$init_type" == 'sysv' ]]; then
{
update-rc.d "$service_name" defaults && \
"/etc/init.d/$service_name" start
} || echo "$service_name could not be registered or started"
fi
else
echo 'Your system does not appear to use systemd, Upstart, or System V, so the service could not be started'
fi
}
# Create log directory if not there and set ownership
if [ ! -d "$ABS_LOG_DIR" ]; then
mkdir -p "$ABS_LOG_DIR"
chown -R 'audiobookshelf:audiobookshelf' "$ABS_LOG_DIR"
fi
start_service 'audiobookshelf'
================================================
FILE: build/debian/DEBIAN/preinst
================================================
#!/bin/bash
set -e
set -o pipefail
DEFAULT_DATA_DIR="/usr/share/audiobookshelf"
CONFIG_PATH="/etc/default/audiobookshelf"
DEFAULT_PORT=13378
DEFAULT_HOST="0.0.0.0"
add_user() {
: "${1:?'User was not defined'}"
declare -r user="$1"
declare -r uid="$2"
if [ -z "$uid" ]; then
declare -r uid_flags=""
else
declare -r uid_flags="--uid $uid"
fi
declare -r group="${3:-$user}"
declare -r descr="${4:-No description}"
declare -r shell="${5:-/bin/false}"
if ! getent passwd "$user" 2>&1 >/dev/null; then
echo "Creating system user: $user in $group with $descr and shell $shell"
useradd $uid_flags --gid $group --no-create-home --system --shell $shell -c "$descr" $user
fi
}
add_group() {
: "${1:?'Group was not defined'}"
declare -r group="$1"
declare -r gid="$2"
if [ -z "$gid" ]; then
declare -r gid_flags=""
else
declare -r gid_flags="--gid $gid"
fi
if ! getent group "$group" 2>&1 >/dev/null; then
echo "Creating system group: $group"
groupadd $gid_flags --system $group
fi
}
setup_config() {
if [ -f "$CONFIG_PATH" ]; then
echo "Existing config found."
cat $CONFIG_PATH
else
if [ ! -d "$DEFAULT_DATA_DIR" ]; then
# Create directory and set permissions
echo "Creating default data dir at $DEFAULT_DATA_DIR"
mkdir "$DEFAULT_DATA_DIR"
chown -R 'audiobookshelf:audiobookshelf' "$DEFAULT_DATA_DIR"
fi
echo "Creating default config."
config_text="METADATA_PATH=$DEFAULT_DATA_DIR/metadata
CONFIG_PATH=$DEFAULT_DATA_DIR/config
PORT=$DEFAULT_PORT
HOST=$DEFAULT_HOST"
echo "$config_text"
echo "$config_text" > /etc/default/audiobookshelf;
echo "Config created"
fi
}
add_group 'audiobookshelf' ''
add_user 'audiobookshelf' '' 'audiobookshelf' 'audiobookshelf user-daemon' '/bin/false'
setup_config
================================================
FILE: build/debian/DEBIAN/prerm
================================================
#!/bin/bash
set -e
set -o pipefail
declare -r init_type='auto'
declare -r service_name='audiobookshelf'
if [[ "$init_type" == 'auto' || "$init_type" == 'systemd' || "$init_type" == 'upstart' || "$init_type" == 'sysv' ]]; then
if hash systemctl 2> /dev/null; then
systemctl disable "$service_name.service" && \
systemctl stop "$service_name.service" || \
echo "$service_name wasn't even running!"
elif hash service 2> /dev/null; then
service "$service_name" stop || echo "$service_name wasn't even running!"
elif hash stop 2> /dev/null; then
stop "$service_name" || echo "$service_name wasn't even running!"
elif hash update-rc.d 2> /dev/null; then
{
update-rc.d "$service_name" remove && \
"/etc/init.d/$service_name" stop
} || "$service_name wasn't even running!"
else
echo "Your system does not appear to use upstart, systemd or sysv, so $service_name could not be stopped"
echo 'Unless these systems were removed since install, no processes have been left running'
fi
fi
================================================
FILE: build/debian/etc/default/.gitkeep
================================================
================================================
FILE: build/debian/lib/systemd/system/audiobookshelf.service
================================================
[Unit]
Description=Self-hosted audiobook server for managing and playing audiobooks
Requires=network.target
[Service]
Type=simple
EnvironmentFile=/etc/default/audiobookshelf
WorkingDirectory=/usr/share/audiobookshelf
ExecStart=/usr/share/audiobookshelf/audiobookshelf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
User=audiobookshelf
Group=audiobookshelf
[Install]
WantedBy=multi-user.target
================================================
FILE: build/debian/usr/lib/.gitkeep
================================================
================================================
FILE: build/debian/usr/share/.gitkeep
================================================
================================================
FILE: build/linuxpackager
================================================
#!/bin/bash
set -e
set -o pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
cd "$SCRIPT_DIR/.."
# Get package version without double quotes
VERSION="$( eval echo $( jq '.version' package.json) )"
DESCRIPTION="$( eval echo $( jq '.description' package.json) )"
OUTPUT_FILE="audiobookshelf_${VERSION}_amd64.deb"
echo ">>> Building Client"
echo "--------------------"
cd client
rm -rf node_modules
npm ci --unsafe-perm=true --allow-root
npm run generate
cd ..
echo ">>> Building Server"
echo "--------------------"
rm -rf node_modules
npm ci --unsafe-perm=true --allow-root
echo ">>> Packaging"
echo "--------------------"
# Create debian control file
mkdir -p dist
rm -rf dist/debian
cp -R build/debian dist/debian
chmod -R 775 dist/debian
controlfile="Package: audiobookshelf
Version: $VERSION
Section: base
Priority: optional
Architecture: amd64
Depends:
Maintainer: advplyr
Description: $DESCRIPTION"
echo "$controlfile" > dist/debian/DEBIAN/control;
# Package debian
pkg -t node20-linux-x64 -o dist/debian/usr/share/audiobookshelf/audiobookshelf .
fakeroot dpkg-deb -Zxz --build dist/debian
mv dist/debian.deb "dist/$OUTPUT_FILE"
echo "Finished! Filename: $OUTPUT_FILE"
================================================
FILE: client/assets/absicons.css
================================================
@font-face {
font-family: 'absicons';
src: url('~static/fonts/absicons/absicons.eot?2jfq33');
src: url('~static/fonts/absicons/absicons.eot?2jfq33#iefix') format('embedded-opentype'),
url('~static/fonts/absicons/absicons.ttf?2jfq33') format('truetype'),
url('~static/fonts/absicons/absicons.woff?2jfq33') format('woff'),
url('~static/fonts/absicons/absicons.svg?2jfq33#absicons') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
.abs-icons {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'absicons' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-books-1:before {
content: "\e905";
}
.icon-microphone-1:before {
content: "\e902";
}
.icon-radio:before {
content: "\e903";
}
.icon-podcast:before {
content: "\e904";
}
.icon-audiobookshelf:before {
content: "\e900";
}
.icon-database:before {
content: "\e906";
}
.icon-microphone-2:before {
content: "\e901";
}
.icon-headphones:before {
content: "\e910";
}
.icon-music:before {
content: "\e911";
}
.icon-video:before {
content: "\e914";
}
.icon-microphone-3:before {
content: "\e91e";
}
.icon-book-1:before {
content: "\e91f";
}
.icon-books-2:before {
content: "\e920";
}
.icon-file-picture:before {
content: "\e927";
}
.icon-database-1:before {
content: "\e964";
}
.icon-rocket:before {
content: "\e9a5";
}
.icon-power:before {
content: "\e9b5";
}
.icon-star:before {
content: "\e9d9";
}
.icon-heart:before {
content: "\e9da";
}
.icon-rss:before {
content: "\ea9b";
}
================================================
FILE: client/assets/app.css
================================================
@import './fonts.css';
@import './transitions.css';
@import './draggable.css';
@import './defaultStyles.css';
@import './absicons.css';
:root {
--bookshelf-texture-img: url(~static/textures/wood_default.jpg);
--bookshelf-divider-bg: 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%);
}
.page {
width: 100%;
height: calc(100% - 64px);
max-height: calc(100% - 64px);
}
.page.streaming {
height: calc(100% - 64px - 165px);
max-height: calc(100% - 64px - 165px);
}
#bookshelf {
height: calc(100% - 40px);
background-image: linear-gradient(to right bottom, #2e2e2e, #303030, #313131, #333333, #353535, #343434, #323232, #313131, #2c2c2c, #282828, #232323, #1f1f1f);
/* For Firefox */
scrollbar-width: thin;
scrollbar-color: #855620 rgba(0, 0, 0, 0);
}
.bookshelf-row {
width: calc(100vw - (100vw - 100%));
}
@media (max-width: 768px) {
#bookshelf {
height: calc(100% - 80px);
}
.bookshelf-row {
width: 100vw;
}
}
#page-wrapper {
background-image: linear-gradient(to right bottom, #2e2e2e, #303030, #313131, #333333, #353535, #343434, #323232, #313131, #2c2c2c, #282828, #232323, #1f1f1f);
}
/* width */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar:horizontal {
height: 8px;
}
/* Track */
::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0);
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #855620;
border-radius: 4px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #704922;
}
.no-scroll::-webkit-scrollbar {
display: none;
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 {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
.tracksTable {
border-collapse: collapse;
width: 100%;
border: 1px solid #474747;
}
.tracksTable tr:nth-child(even) {
background-color: #2e2e2e;
}
.tracksTable tr {
background-color: #373838;
}
.tracksTable tr:hover:not(:has(th)) {
background-color: #474747;
}
.tracksTable td {
padding: 4px 8px;
}
.tracksTable th {
padding: 4px 8px;
font-size: 0.75rem;
}
.arrow-down {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
}
.arrow-down-small {
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
}
.triangle-right {
width: 0;
height: 0;
border-left: 8px solid transparent;
border-bottom: 8px solid transparent;
border-top: 8px solid rgb(34, 127, 35);
border-right: 8px solid rgb(34, 127, 35);
}
.icon-text {
font-size: 1.1rem;
}
.box-shadow-md {
box-shadow: 2px 8px 6px #111111aa;
}
.box-shadow-sm-up {
box-shadow: 0px -5px 8px #11111122;
}
.box-shadow-md-up {
box-shadow: 0px -8px 8px #11111144;
}
.box-shadow-lg-up {
box-shadow: 0px -12px 8px #111111ee;
}
.box-shadow-xl {
box-shadow: 2px 14px 8px #111111aa;
}
.box-shadow-book {
box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166;
}
.box-shadow-progressbar {
box-shadow: 0px -1px 4px rgb(62, 50, 2, 0.5);
}
.shadow-height {
height: calc(100% - 4px);
}
.box-shadow-book3d {
box-shadow: 4px 1px 8px #11111166, 1px -4px 8px #11111166;
}
.box-shadow-side {
box-shadow: 5px 0px 5px #11111166;
}
/*
Bookshelf Label
*/
.categoryPlacard {
letter-spacing: 1px;
}
.shinyBlack {
background-color: #2d3436;
background-image: linear-gradient(315deg, #19191a 0%, rgb(15, 15, 15) 74%);
border-color: rgba(255, 244, 182, 0.6);
border-style: solid;
color: #fce3a6;
}
.cover-bg {
width: calc(100% + 40px);
height: calc(100% + 40px);
top: -20px;
left: -20px;
background-size: 100% 100%;
background-position: center;
opacity: 1;
filter: blur(20px);
}
/* Padding for toastification toasts in the top right to not cover appbar/toolbar */
.app-bar-and-toolbar .Vue-Toastification__container.top-right {
padding-top: 104px;
}
.app-bar .Vue-Toastification__container.top-right {
padding-top: 64px;
}
.no-bars .Vue-Toastification__container.top-right {
padding-top: 8px;
}
.abs-btn::before {
content: '';
position: absolute;
border-radius: 6px;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0);
transition: all 0.1s ease-in-out;
}
.abs-btn:hover:not(:disabled)::before {
background-color: rgba(255, 255, 255, 0.1);
}
.abs-btn:disabled::before {
background-color: rgba(0, 0, 0, 0.2);
}
================================================
FILE: client/assets/defaultStyles.css
================================================
/*
This is for setting regular html styles for places where embedding HTML will be
like podcast episode descriptions. Otherwise TailwindCSS will have stripped all default markup.
*/
.default-style p {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
.default-style a {
text-decoration: none;
color: #5985ff;
}
.default-style ul {
display: block;
list-style: circle;
list-style-type: disc;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
padding-inline-start: 40px;
}
.default-style ol {
display: block;
list-style: decimal;
list-style-type: decimal;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
padding-inline-start: 40px;
}
.default-style li {
display: list-item;
text-align: -webkit-match-parent;
}
.default-style li::marker {
unicode-bidi: isolate;
font-variant-numeric: tabular-nums;
text-transform: none;
text-indent: 0px !important;
text-align: start !important;
text-align-last: start !important;
}
.default-style.less-spacing p {
margin-block-start: 0;
}
.default-style.less-spacing ul {
margin-block-start: 0;
}
.default-style.less-spacing ol {
margin-block-start: 0;
}
================================================
FILE: client/assets/draggable.css
================================================
.flip-list-move {
transition: transform 0.5s;
}
.no-move {
transition: transform 0s;
}
.ghost {
opacity: 0.5;
background-color: rgba(255, 255, 255, 0.25);
}
.list-group {
min-height: 30px;
}
.drag-handle {
cursor: n-resize;
}
.list-group-item:not(.exclude) {
cursor: n-resize;
}
.list-group-item.exclude {
cursor: not-allowed;
}
.list-group-item:not(.ghost):not(.exclude):hover {
background-color: rgba(0, 0, 0, 0.1);
}
.list-group-item:nth-child(even):not(.ghost):not(.exclude) {
background-color: rgba(0, 0, 0, 0.25);
}
.list-group-item:nth-child(even):not(.ghost):not(.exclude):hover {
background-color: rgba(0, 0, 0, 0.1);
}
.list-group-item.exclude:not(.ghost) {
background-color: rgba(255, 0, 0, 0.25);
}
.list-group-item.exclude:not(.ghost):hover {
background-color: rgba(223, 0, 0, 0.25);
}
================================================
FILE: client/assets/ebooks/basic.js
================================================
/*
Calibres stylesheet
*/
export default `
@charset "UTF-8";
/*
Calibre styles
*/
.arabic {
display: block;
list-style-type: decimal;
margin-bottom: 1em;
margin-right: 0;
margin-top: 1em;
text-align: justify
}
.attribution {
display: block;
font-size: 1em;
line-height: 1.2;
text-align: left;
margin: 0.3em 0
}
.big {
font-size: 1.375em;
line-height: 1.2
}
.big1 {
font-size: 1em
}
.block {
display: block;
text-align: justify;
margin: 1em 1em 2em
}
.block1 {
display: block;
text-align: justify;
margin: 1em 4em
}
.block2 {
display: block;
text-align: justify;
margin: 1em 1em 1em 2em
}
.bullet {
display: block;
list-style-type: disc;
margin-bottom: 1em;
margin-right: 0;
margin-top: 1em;
text-align: disc
}
.calibre {
background-color: #000007;
display: block;
font-family: Charis, "Times New Roman", Verdana, Arial;
font-size: 1.125em;
line-height: 1.2;
padding-left: 0;
padding-right: 0;
text-align: center;
margin: 0 5pt
}
.calibre1 {
display: block
}
.calibre2 {
height: auto;
width: auto
}
.calibre3:not(strong) {
display: block;
font-family: Charis, "Times New Roman", Verdana, Arial;
font-size: 1.125em;
line-height: 1.2;
padding-left: 0;
padding-right: 0;
margin: 0 5pt
}
.calibre4 {
font-weight: bold
}
.calibre5 {
font-style: italic
}
.calibre6 {
background-color: #FFF;
display: block;
font-family: Charis, "Times New Roman", Verdana, Arial;
font-size: 1.125em;
line-height: 1.2;
padding-left: 0;
padding-right: 0;
text-align: center;
margin: 0 5pt
}
.calibre7 {
display: list-item
}
.calibre8 {
font-size: 1em;
line-height: 1.2;
vertical-align: super
}
.calibre9 {
border-collapse: separate;
border-spacing: 2px;
display: table;
margin-bottom: 0;
margin-top: 0;
text-indent: 0
}
.calibre10 {
display: table-row;
vertical-align: middle
}
.calibre11 {
display: table-cell;
text-align: right;
vertical-align: inherit;
padding: 1px
}
.calibre12 {
display: table-cell;
text-align: left;
vertical-align: inherit;
padding: 1px
}
.calibre13 {
height: 1em;
width: auto
}
.calibre14 {
font-size: 0.88889em;
line-height: 1.2;
vertical-align: super
}
.calibre15 {
font-size: 1em;
line-height: 1.2;
vertical-align: sub
}
.calibre16 {
display: block;
list-style-type: decimal;
margin-bottom: 1em;
margin-right: 0;
margin-top: 1em
}
.calibre17 {
display: block;
font-size: 1.125em;
font-weight: bold;
line-height: 1.2;
margin: 0.83em 0
}
.center {
display: block;
text-align: center;
margin: 1em 0
}
.center1 {
display: block;
font-size: 1em;
font-weight: bold;
line-height: 1.2;
text-align: center;
margin: -2em 0 3em
}
.center2 {
display: block;
font-size: 1em;
font-weight: bold;
line-height: 1.2;
text-align: center;
margin: 2em 0 1em
}
.center3 {
display: block;
text-align: center;
margin: -1em 0 1em
}
.center4 {
display: block;
text-align: center;
text-indent: 3%;
margin: 1em 0
}
.chapter {
display: block;
font-size: 1.125em;
font-weight: bold;
line-height: 2em;
text-align: center;
margin: 2em 0 1em
}
.chapter1 {
display: block;
font-size: 0.88889em;
line-height: 1.2;
margin-left: 0.5em;
margin-right: 0.5em;
margin-top: 2em
}
.chapter2 {
display: block;
font-size: 1.125em;
font-weight: bold;
line-height: 2em;
text-align: center;
margin: 2em 0 3em
}
.copyright {
display: block;
font-size: 0.88889em;
line-height: 1.2;
margin-top: 4em;
text-align: center
}
.dedication {
display: block;
font-size: 0.88889em;
line-height: 1.2;
margin-top: 4em
}
.dropcaps {
float: left;
font-size: 3.4375rem;
line-height: 50px;
margin-right: 0.09em;
margin-top: -0.05em;
padding-top: 1px
}
.dropcaps1 {
float: left;
font-size: 3.4375rem;
line-height: 50px;
margin-right: 0.09em;
margin-top: 0.15em;
padding-top: 1px
}
.extract {
display: block;
text-align: justify;
margin: 2em 0 0.3em
}
.extract1 {
display: block;
text-align: justify;
text-indent: 3%;
margin: 2em 0 0.3em
}
.extract2 {
display: block;
text-align: justify;
margin: 1em 0 0.3em
}
.footnote {
border-bottom-style: solid;
border-bottom-width: 0;
border-left-style: solid;
border-left-width: 0;
border-right-style: solid;
border-right-width: 0;
border-top-style: solid;
border-top-width: 1px;
display: block;
font-size: 1em;
line-height: 1.2;
margin-top: 2 em
}
.footnote1 {
display: block;
text-align: justify;
margin: 0.3em 0 0.3em 2
}
.footnote2 {
border-bottom-style: solid;
border-bottom-width: 0;
border-left-style: solid;
border-left-width: 0;
border-right-style: solid;
border-right-width: 0;
border-top-style: solid;
border-top-width: 1px;
display: block;
font-size: 0.88889em;
line-height: 1.2;
margin-top: 2 em
}
.hanging {
display: block;
font-size: 0.88889em;
line-height: 1.2;
text-align: left;
text-indent: -1em;
margin: 0.5em 0 0.3em 1em
}
.hanging1 {
display: block;
font-size: 0.88889em;
line-height: 1.2;
text-align: left;
text-indent: -1em;
margin: 0.5em 0 0.3em 1.5em
}
.hanging2 {
display: block;
font-size: 1em;
line-height: 1.2;
text-indent: -1em;
margin: 0.5em 0 0.3em 1em
}
.hanging3 {
display: block;
font-size: 1em;
line-height: 1.2;
text-align: left;
text-indent: 1em;
margin: 0.1em 0 0.3em 1em
}
.hanging4 {
display: block;
font-size: 1em;
line-height: 1.2;
text-align: left;
text-indent: 0.1em;
margin: 0.1em 0 0.3em 1em
}
a.hlink {
text-decoration: none
}
.indent {
display: block;
text-align: justify;
text-indent: 1em;
margin: 0.3em 0
}
.line {
border-top: currentColor solid 1px;
border-bottom: currentColor solid 1px
}
.loweralpha {
display: block;
list-style-type: lower-alpha;
margin-bottom: 1em;
margin-right: 0;
margin-top: 1em;
text-align: justify
}
.none {
display: block;
list-style-type: none;
margin-bottom: 1em;
margin-right: 0;
margin-top: 1em;
text-align: justify
}
.none1 {
display: block;
list-style-type: none;
margin-bottom: 0;
margin-right: 0;
margin-top: 0;
text-align: justify
}
.nonindent {
display: block;
text-align: justify;
margin: 0.3em 0
}
.nonindent1 {
display: block;
font-size: 1.125em;
line-height: 1.2;
text-indent: -1em;
margin: 0.5em 0 0.3em 0.1em
}
.nonindent2 {
display: block;
font-size: 1.125em;
line-height: 1.2;
text-indent: -1em;
margin: 0.5em 0 0.3em -0.5em
}
.nonindent3 {
display: block;
text-align: justify;
text-indent: 3%;
margin: 0.3em 0
}
.part {
display: block;
font-size: 1em;
font-weight: bold;
line-height: 2em;
text-align: center;
margin: 4em 0 1em
}
.preface {
display: block;
font-size: 0.88889em;
line-height: 1.2;
margin-left: 2em;
margin-right: 2em;
text-align: justify
}
.pubhlink {
color: green;
text-decoration: none
}
.right {
display: block;
text-align: right;
margin: 0.3em 0
}
.section {
display: block;
font-size: 1.125em;
font-weight: bold;
line-height: 1.2;
text-align: center;
margin: 2em 0 0.5em
}
.section1 {
display: block;
font-size: 1.125em;
font-weight: bold;
line-height: 1.2;
text-align: left;
margin: 2em 0 0.3em
}
.section2 {
display: block;
font-size: 1em;
font-weight: bold;
line-height: 1.2;
text-align: left;
margin: 2em 0 0.3em 1em
}
.small {
font-size: 0.66667em
}
.small1 {
font-size: 0.75em
}
.subchapter {
display: block;
font-size: 1.125em;
font-weight: bold;
line-height: 1.2;
margin: 1em 0
}
.textbox {
background-color: #E4E4E4;
display: block;
line-height: 1.5em;
margin-bottom: 2em;
margin-top: 2em;
text-align: justify;
border-top: currentColor double 2px;
border-bottom: currentColor double 2px
}
.textbox1 {
display: block;
text-align: justify;
margin: 0.3em 0.5em 0.3em 0.8em
}
.textbox2 {
display: block;
text-align: justify;
text-indent: 1em;
margin: 0.3em 0.5em
}
.textbox3 {
display: block;
text-align: justify;
text-indent: 3%;
margin: 0.3em 0.5em 0.3em 0.8em
}
.titlepage {
display: block;
margin-left: -0.4em;
margin-top: 1.2em
}
.toc {
display: block;
font-size: 1em;
line-height: 1.2;
text-align: center
}
.toc1 {
display: block;
font-size: 1em;
font-weight: bold;
line-height: 1.2;
text-align: center;
margin: 0.67em 0 3em
}
.underline {
text-decoration: underline
}
`
================================================
FILE: client/assets/ebooks/htmlParser.js
================================================
/*
This is borrowed from koodo-reader https://github.com/troyeguo/koodo-reader/tree/master/src
*/
export const isTitle = (
line,
isContainDI = false,
isContainChapter = false,
isContainCHAPTER = false
) => {
return (
line.length < 30 &&
line.indexOf("[") === -1 &&
line.indexOf("(") === -1 &&
(line.startsWith("CHAPTER") ||
line.startsWith("Chapter") ||
line.startsWith("序章") ||
line.startsWith("前言") ||
line.startsWith("声明") ||
line.startsWith("聲明") ||
line.startsWith("写在前面的话") ||
line.startsWith("后记") ||
line.startsWith("楔子") ||
line.startsWith("后序") ||
line.startsWith("寫在前面的話") ||
line.startsWith("後記") ||
line.startsWith("後序") ||
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
line
) ||
(line.startsWith("第") && startWithDI(line)) ||
(line.startsWith("卷") && startWithJUAN(line)) ||
startWithRomanNum(line) ||
(!isContainDI &&
!isContainChapter &&
!isContainCHAPTER &&
line.indexOf("第") > -1 &&
(line[line.indexOf("第") - 1] === " " ||
line[line.indexOf("第") - 1] === " " ||
line[line.indexOf("第") - 1] === "、" ||
line[line.indexOf("第") - 1] === ":" ||
line[line.indexOf("第") - 1] === ":") &&
startWithDI(line.substr(line.indexOf("第")))) ||
(!isContainDI &&
!isContainChapter &&
!isContainCHAPTER &&
line.indexOf(" ") &&
startWithNumAndSpace(line)) ||
(!isContainDI &&
!isContainChapter &&
!isContainCHAPTER &&
line.indexOf(" ") &&
startWithNumAndSpace(line)) ||
(!isContainDI &&
!isContainChapter &&
!isContainCHAPTER &&
line.indexOf("、") &&
startWithNumAndPause(line)) ||
(!isContainDI &&
!isContainChapter &&
!isContainCHAPTER &&
line.indexOf(":") &&
startWithNumAndColon(line)) ||
(!isContainDI &&
!isContainChapter &&
!isContainCHAPTER &&
line.indexOf(":") &&
startWithNumAndColon(line)))
);
};
const startWithDI = (line) => {
let keywords = [
"章",
"节",
"回",
"節",
"卷",
"部",
"輯",
"辑",
"話",
"集",
"话",
"篇",
];
let flag = false;
for (let i = 0; i < keywords.length; i++) {
if (
(line.indexOf(keywords[i]) > -1 &&
(line[line.indexOf(keywords[i]) + 1] === " " ||
line[line.indexOf(keywords[i]) + 1] === " " ||
line[line.indexOf(keywords[i]) + 1] === "、" ||
line[line.indexOf(keywords[i]) + 1] === ":" ||
line[line.indexOf(keywords[i]) + 1] === ":")) ||
!line[line.indexOf(keywords[i]) + 1]
) {
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(1, line.indexOf(keywords[i])).trim()
) ||
/^\d+$/.test(line.substring(1, line.indexOf(keywords[i])).trim())
) {
flag = true;
}
if (flag) break;
}
}
return flag;
};
const startWithJUAN = (line) => {
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(1, line.indexOf(" "))
) ||
/^\d+$/.test(line.substring(1, line.indexOf(" ")))
)
return true;
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(1, line.indexOf(" "))
) ||
/^\d+$/.test(line.substring(1, line.indexOf(" ")))
)
return true;
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(1)
) ||
/^\d+$/.test(line.substring(1))
)
return true;
return false;
};
const startWithRomanNum = (line) => {
if (
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
line.substring(0, line.indexOf(" "))
)
)
return true;
if (
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
line.substring(0, line.indexOf("."))
)
)
return true;
if (
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
line.trim()
)
)
return true;
return false;
};
const startWithNumAndSpace = (line) => {
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(0, line.indexOf(" "))
)
)
return true;
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(0, line.indexOf(" "))
)
)
return true;
if (/^\d+$/.test(line.substring(0, line.indexOf(" ")))) return true;
if (/^\d+$/.test(line.substring(0, line.indexOf(" ")))) return true;
return false;
};
const startWithNumAndColon = (line) => {
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(0, line.indexOf(":"))
)
)
return true;
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(0, line.indexOf(":"))
)
)
return true;
if (/^\d+$/.test(line.substring(0, line.indexOf(":")))) return true;
if (/^\d+$/.test(line.substring(0, line.indexOf(":")))) return true;
return false;
};
const startWithNumAndPause = (line) => {
if (
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
line.substring(0, line.indexOf("、"))
)
)
return true;
if (/^\d+$/.test(line.substring(0, line.indexOf("、")))) return true;
return false;
};
class HtmlParser {
bookDoc;
contentList;
contentTitleList;
constructor(bookDoc) {
this.bookDoc = bookDoc;
this.contentList = [];
this.contentTitleList = [];
this.getContent(bookDoc);
}
getContent(bookDoc) {
this.contentList = Array.from(
bookDoc.querySelectorAll("h1,h2,h3,h4,h5,b,font")
).filter((item, index) => {
return isTitle(item.innerText.trim());
});
for (let i = 0; i < this.contentList.length; i++) {
let random = Math.floor(Math.random() * 900000) + 100000;
this.contentTitleList.push({
label: this.contentList[i].innerText,
id: "title" + random,
href: "#title" + random,
subitems: [],
});
}
for (let i = 0; i < this.contentList.length; i++) {
this.contentList[i].id = this.contentTitleList[i].id;
}
}
getAnchoredDoc() {
return this.bookDoc;
}
getContentList() {
return this.contentTitleList.filter((item, index) => {
if (index > 0) {
return item.label !== this.contentTitleList[index - 1].label;
} else {
return true;
}
});
}
}
export default HtmlParser;
================================================
FILE: client/assets/ebooks/mobi.js
================================================
/*
This is borrowed from koodo-reader https://github.com/troyeguo/koodo-reader/tree/master/src
*/
function ab2str(buf) {
if (buf instanceof ArrayBuffer) {
buf = new Uint8Array(buf);
}
return new TextDecoder("utf-8").decode(buf);
}
var domParser = new DOMParser();
class Buffer {
capacity;
fragment_list;
imageArray;
cur_fragment;
constructor(capacity) {
this.capacity = capacity;
this.fragment_list = [];
this.imageArray = [];
this.cur_fragment = new Fragment(capacity);
this.fragment_list.push(this.cur_fragment);
}
write(byte) {
var result = this.cur_fragment.write(byte);
if (!result) {
this.cur_fragment = new Fragment(this.capacity);
this.fragment_list.push(this.cur_fragment);
this.cur_fragment.write(byte);
}
}
get(idx) {
var fi = 0;
while (fi < this.fragment_list.length) {
var frag = this.fragment_list[fi];
if (idx < frag.size) {
return frag.get(idx);
}
idx -= frag.size;
fi += 1;
}
return null;
}
size() {
var s = 0;
for (var i = 0; i < this.fragment_list.length; i++) {
s += this.fragment_list[i].size;
}
return s;
}
shrink() {
var total_buffer = new Uint8Array(this.size());
var offset = 0;
for (var i = 0; i < this.fragment_list.length; i++) {
var frag = this.fragment_list[i];
if (frag.full()) {
total_buffer.set(frag.buffer, offset);
} else {
total_buffer.set(frag.buffer.slice(0, frag.size), offset);
}
offset += frag.size;
}
return total_buffer;
}
}
var copagesne_uint8array = function (buffers) {
var total_size = 0;
for (let i = 0; i < buffers.length; i++) {
var buffer = buffers[i];
total_size += buffer.length;
}
var total_buffer = new Uint8Array(total_size);
var offset = 0;
for (let i = 0; i < buffers.length; i++) {
buffer = buffers[i];
total_buffer.set(buffer, offset);
offset += buffer.length;
}
return total_buffer;
};
class Fragment {
buffer;
capacity;
size;
constructor(capacity) {
this.buffer = new Uint8Array(capacity);
this.capacity = capacity;
this.size = 0;
}
write(byte) {
if (this.size >= this.capacity) {
return false;
}
this.buffer[this.size] = byte;
this.size += 1;
return true;
}
full() {
return this.size === this.capacity;
}
get(idx) {
return this.buffer[idx];
}
}
var uncompression_lz77 = function (data) {
var length = data.length;
var offset = 0; // Current offset into data
var buffer = new Buffer(data.length);
while (offset < length) {
var char = data[offset];
offset += 1;
if (char === 0) {
buffer.write(char);
} else if (char <= 8) {
for (var i = offset; i < offset + char; i++) {
buffer.write(data[i]);
}
offset += char;
} else if (char <= 0x7f) {
buffer.write(char);
} else if (char <= 0xbf) {
var next = data[offset];
offset += 1;
var distance = (((char << 8) | next) >> 3) & 0x7ff;
var lz_length = (next & 0x7) + 3;
var buffer_size = buffer.size();
for (let i = 0; i < lz_length; i++) {
buffer.write(buffer.get(buffer_size - distance));
buffer_size += 1;
}
} else {
buffer.write(32);
buffer.write(char ^ 0x80);
}
}
return buffer;
};
class MobiFile {
view;
buffer;
offset;
header;
palm_header;
mobi_header;
reclist;
constructor(data) {
this.view = new DataView(data);
this.buffer = this.view.buffer;
this.offset = 0;
this.header = null;
}
parse() { }
getUint8() {
var v = this.view.getUint8(this.offset);
this.offset += 1;
return v;
}
getUint16() {
var v = this.view.getUint16(this.offset);
this.offset += 2;
return v;
}
getUint32() {
var v = this.view.getUint32(this.offset);
this.offset += 4;
return v;
}
getStr(size) {
var v = ab2str(this.buffer.slice(this.offset, this.offset + size));
this.offset += size;
return v;
}
skip(size) {
this.offset += size;
}
setoffset(_of) {
this.offset = _of;
}
get_record_extrasize(data, flags) {
var pos = data.length - 1;
var extra = 0;
for (var i = 15; i > 0; i--) {
if (flags & (1 << i)) {
var res = this.buffer_get_varlen(data, pos);
var size = res[0];
var l = res[1];
pos = res[2];
pos -= size - l;
extra += size;
}
}
if (flags & 1) {
var a = data[pos];
extra += (a & 0x3) + 1;
}
return extra;
}
// data should be uint8array
buffer_get_varlen(data, pos) {
var l = 0;
var size = 0;
var byte_count = 0;
var mask = 0x7f;
var stop_flag = 0x80;
var shift = 0;
for (var i = 0; ; i++) {
var byte = data[pos];
size |= (byte & mask) << shift;
shift += 7;
l += 1;
byte_count += 1;
pos -= 1;
var to_stop = byte & stop_flag;
if (byte_count >= 4 || to_stop > 0) {
break;
}
}
return [size, l, pos];
}
// 读出文本内容
read_text() {
var text_end = this.palm_header.record_count;
var buffers = [];
for (var i = 1; i <= text_end; i++) {
buffers.push(this.read_text_record(i));
}
var all = copagesne_uint8array(buffers);
return ab2str(all);
}
read_text_record(i) {
var flags = this.mobi_header.extra_flags;
var begin = this.reclist[i].offset;
var end = this.reclist[i + 1].offset;
var data = new Uint8Array(this.buffer.slice(begin, end));
var ex = this.get_record_extrasize(data, flags);
data = new Uint8Array(this.buffer.slice(begin, end - ex));
if (this.palm_header.compression === 2) {
var buffer = uncompression_lz77(data);
return buffer.shrink();
} else {
return data;
}
}
// 从buffer中读出image
read_image(idx) {
var first_image_idx = this.mobi_header.first_image_idx;
var begin = this.reclist[first_image_idx + idx].offset;
var end = this.reclist[first_image_idx + idx + 1].offset;
var data = new Uint8Array(this.buffer.slice(begin, end));
return new Blob([data.buffer]);
}
load() {
this.header = this.load_pdbheader();
this.reclist = this.load_reclist();
this.load_record0();
}
load_pdbheader() {
var header = {};
header.name = this.getStr(32);
header.attr = this.getUint16();
header.version = this.getUint16();
header.ctime = this.getUint32();
header.mtime = this.getUint32();
header.btime = this.getUint32();
header.mod_num = this.getUint32();
header.appinfo_offset = this.getUint32();
header.sortinfo_offset = this.getUint32();
header.type = this.getStr(4);
header.creator = this.getStr(4);
header.uid = this.getUint32();
header.next_rec = this.getUint32();
header.record_num = this.getUint16();
return header;
}
load_reclist() {
var reclist = [];
for (var i = 0; i < this.header.record_num; i++) {
var record = {};
record.offset = this.getUint32();
// TODO(zz) change
record.attr = this.getUint32();
reclist.push(record);
}
return reclist;
}
load_record0() {
this.palm_header = this.load_record0_header();
this.mobi_header = this.load_mobi_header();
}
load_record0_header() {
var p_header = {};
var first_record = this.reclist[0];
this.setoffset(first_record.offset);
p_header.compression = this.getUint16();
this.skip(2);
p_header.text_length = this.getUint32();
p_header.record_count = this.getUint16();
p_header.record_size = this.getUint16();
p_header.encryption_type = this.getUint16();
this.skip(2);
return p_header;
}
load_mobi_header() {
var mobi_header = {};
var start_offset = this.offset;
mobi_header.identifier = this.getUint32();
mobi_header.header_length = this.getUint32();
mobi_header.mobi_type = this.getUint32();
mobi_header.text_encoding = this.getUint32();
mobi_header.uid = this.getUint32();
mobi_header.generator_version = this.getUint32();
this.skip(40);
mobi_header.first_nonbook_index = this.getUint32();
mobi_header.full_name_offset = this.getUint32();
mobi_header.full_name_length = this.getUint32();
mobi_header.language = this.getUint32();
mobi_header.input_language = this.getUint32();
mobi_header.output_language = this.getUint32();
mobi_header.min_version = this.getUint32();
mobi_header.first_image_idx = this.getUint32();
mobi_header.huff_rec_index = this.getUint32();
mobi_header.huff_rec_count = this.getUint32();
mobi_header.datp_rec_index = this.getUint32();
mobi_header.datp_rec_count = this.getUint32();
mobi_header.exth_flags = this.getUint32();
this.skip(36);
mobi_header.drm_offset = this.getUint32();
mobi_header.drm_count = this.getUint32();
mobi_header.drm_size = this.getUint32();
mobi_header.drm_flags = this.getUint32();
this.skip(8);
// TODO (zz) fdst_index
this.skip(4);
this.skip(46);
mobi_header.extra_flags = this.getUint16();
this.setoffset(start_offset + mobi_header.header_length);
return mobi_header;
}
load_exth_header() {
// TODO
return {};
}
extractContent(s) {
var span = document.createElement("span");
span.innerHTML = s;
return span.textContent || span.innerText;
}
render(isElectron = false) {
return new Promise((resolve, reject) => {
this.load();
var content = this.read_text();
var bookDoc = domParser.parseFromString(content, "text/html")
.documentElement;
let lines = Array.from(
bookDoc.querySelectorAll("p,b,font,h3,h2,h1")
);
let parseContent = [];
for (let i = 0, len = lines.length; i < len - 1; i++) {
lines[i].innerText &&
lines[i].innerText !== parseContent[parseContent.length - 1] &&
parseContent.push(lines[i].innerText);
let imgDoms = lines[i].getElementsByTagName("img");
if (imgDoms.length > 0) {
for (let i = 0; i < imgDoms.length; i++) {
parseContent.push("#image");
}
}
}
const handleImage = async () => {
var imgDoms = bookDoc.getElementsByTagName("img");
parseContent.push("~image");
for (let i = 0; i < imgDoms.length; i++) {
const src = await this.render_image(imgDoms, i);
parseContent.push(
src + " " + imgDoms[i].width + " " + imgDoms[i].height
);
}
if (imgDoms.length > 200 || !isElectron) {
resolve(bookDoc);
} else {
resolve(parseContent.join("\n \n"));
}
};
handleImage();
});
}
render_image = (imgDoms, i) => {
return new Promise((resolve, reject) => {
var imgDom = imgDoms[i];
var idx = +imgDom.getAttribute("recindex");
var blob = this.read_image(idx - 1);
var imgReader = new FileReader();
imgReader.onload = (e) => {
imgDom.src = e.target?.result;
resolve(e.target?.result);
};
imgReader.onerror = function (err) {
reject(err);
};
imgReader.readAsDataURL(blob);
});
};
}
export default MobiFile;
================================================
FILE: client/assets/fonts.css
================================================
@font-face {
font-family: 'Material Symbols Rounded';
font-style: normal;
font-weight: 400;
src: url(~static/fonts/MaterialSymbolsRounded.woff2) format('woff2');
}
.material-symbols {
font-family: 'Material Symbols Rounded';
font-weight: normal;
font-style: normal;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
vertical-align: top;
}
.material-symbols.fill {
font-variation-settings:
'FILL' 1
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Light.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('ttf');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('truetype');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('truetype');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('truetype');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('truetype');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('truetype');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(~static/fonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+0370-03FF;
}
/* latin-ext */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
================================================
FILE: client/assets/tailwind.css
================================================
@import 'tailwindcss';
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
[role='button'],
button {
cursor: pointer;
}
}
@theme {
--spacing-0\.5e: 0.125em;
--spacing-1e: 0.25em;
--spacing-1\.5e: 0.375em;
--spacing-2e: 0.5em;
--spacing-2\.5e: 0.625em;
--spacing-3e: 0.75em;
--spacing-3\.5e: 0.875em;
--spacing-4e: 1em;
--spacing-5e: 1.25em;
--spacing-6e: 1.5em;
--spacing-7e: 1.75em;
--spacing-8e: 2em;
--spacing-9e: 2.25em;
--spacing-10e: 2.5em;
--spacing-11e: 2.75em;
--spacing-12e: 3em;
--spacing-14e: 3.5em;
--spacing-16e: 4em;
--spacing-20e: 5em;
--spacing-24e: 6em;
--spacing-28e: 7em;
--spacing-32e: 8em;
--spacing-36e: 9em;
--spacing-40e: 10em;
--spacing-44e: 11em;
--spacing-48e: 12em;
--spacing-52e: 13em;
--spacing-56e: 14em;
--spacing-60e: 15em;
--spacing-64e: 16em;
--spacing-72e: 18em;
--spacing-80e: 20em;
--spacing-96e: 24em;
--color-bg: #373838;
--color-primary: #232323;
--color-accent: #1ad691;
--color-error: #ff5252;
--color-info: #2196f3;
--color-success: #4caf50;
--color-warning: #fb8c00;
--color-darkgreen: rgb(34, 127, 35);
--color-black-50: #bbbbbb;
--color-black-100: #666666;
--color-black-200: #555555;
--color-black-300: #444444;
--color-black-400: #333333;
--color-black-500: #222222;
--color-black-600: #111111;
--color-black-700: #101010;
--font-sans: 'Source Sans Pro';
--font-mono: 'Ubuntu Mono';
--text-xxs: 0.625rem;
--text-1\.5xl: 1.375rem;
--text-2\.5xl: 1.6875rem;
--text-4\.5xl: 2.625rem;
}
================================================
FILE: client/assets/transitions.css
================================================
.slide-enter-active {
-moz-transition-duration: 0.1s;
-webkit-transition-duration: 0.1s;
-o-transition-duration: 0.1s;
transition-duration: 0.1s;
-moz-transition-timing-function: ease-in;
-webkit-transition-timing-function: ease-in;
-o-transition-timing-function: ease-in;
transition-timing-function: ease-in;
}
.slide-leave-active {
-moz-transition-duration: 0.2s;
-webkit-transition-duration: 0.2s;
-o-transition-duration: 0.2s;
transition-duration: 0.2s;
-moz-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
-webkit-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
-o-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
.slide-enter-to,
.slide-leave {
max-height: 600px;
overflow: hidden;
}
.slide-enter,
.slide-leave-to {
overflow: hidden;
max-height: 0;
}
.menu-enter,
.menu-leave-active {
transform: translateY(-15px);
}
.menu-enter-active {
transition: all 0.2s;
}
.menu-leave-active {
transition: all 0.1s;
}
.menu-enter,
.menu-leave-active {
opacity: 0;
}
.menux-enter,
.menux-leave-active {
transform: translateX(15px);
}
.menux-enter-active {
transition: all 0.2s;
}
.menux-leave-active {
transition: all 0.1s;
}
.menux-enter,
.menux-leave-active {
opacity: 0;
}
.list-complete-item {
transition: all 0.8s ease;
}
.list-complete-enter-from,
.list-complete-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
================================================
FILE: client/assets/trix.css
================================================
@charset "UTF-8";
/*
Trix 1.3.1
Copyright © 2020 Basecamp, LLC
http://trix-editor.org/*/
trix-editor {
border: 1px solid rgb(75, 85, 99);
border-radius: 3px;
background: rgb(35, 35, 35);
margin: 0;
padding: 0.4em 0.6em;
min-height: 5em;
outline: none;
}
trix-toolbar * {
box-sizing: border-box;
}
trix-toolbar .trix-button-row {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
overflow-x: auto;
}
trix-toolbar .trix-button-group {
display: flex;
margin-bottom: 10px;
border: 1px solid rgb(75, 85, 99);
border-top-color: rgb(75, 85, 99);
border-bottom-color: rgb(75, 85, 99);
border-radius: 3px;
}
trix-toolbar .trix-button-group:not(:first-child) {
margin-left: 1.5vw;
}
@media (max-device-width: 768px) {
trix-toolbar .trix-button-group:not(:first-child) {
margin-left: 0;
}
}
trix-toolbar .trix-button-group-spacer {
flex-grow: 1;
}
@media (max-device-width: 768px) {
trix-toolbar .trix-button-group-spacer {
display: none;
}
}
trix-toolbar .trix-button {
position: relative;
float: left;
color: rgba(0, 0, 0, 0.6);
font-size: 0.75em;
font-weight: 600;
white-space: nowrap;
padding: 0 0.5em;
margin: 0;
outline: none;
border: none;
border-radius: 0;
background: transparent;
}
trix-toolbar .trix-button:not(:first-child) {
border-left: 1px solid rgb(75, 85, 99);
}
trix-toolbar .trix-button.trix-active {
background: #bbb;
color: black;
}
trix-toolbar .trix-button:not(:disabled) {
cursor: pointer;
background: rgb(35, 35, 35);
}
trix-toolbar .trix-button:disabled {
color: rgba(0, 0, 0, 0.25);
}
@media (max-device-width: 768px) {
trix-toolbar .trix-button {
letter-spacing: -0.01em;
padding: 0 0.3em;
}
}
trix-toolbar .trix-button--icon {
font-size: inherit;
width: 2.6em;
height: 1.6em;
max-width: calc(0.8em + 4vw);
text-indent: -9999px;
}
@media (max-device-width: 768px) {
trix-toolbar .trix-button--icon {
height: 2em;
max-width: calc(0.8em + 3.5vw);
}
}
trix-toolbar .trix-button--icon::before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.6;
content: "";
background-position: center;
background-repeat: no-repeat;
background-size: contain;
filter: invert(100%);
}
@media (max-device-width: 768px) {
trix-toolbar .trix-button--icon::before {
right: 6%;
left: 6%;
}
}
trix-toolbar .trix-button--icon.trix-active::before {
opacity: 1;
}
trix-toolbar .trix-button--icon:disabled::before {
opacity: 0.125;
}
trix-toolbar .trix-button--icon-attach::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M16.5%206v11.5a4%204%200%201%201-8%200V5a2.5%202.5%200%200%201%205%200v10.5a1%201%200%201%201-2%200V6H10v9.5a2.5%202.5%200%200%200%205%200V5a4%204%200%201%200-8%200v12.5a5.5%205.5%200%200%200%2011%200V6h-1.5z%22%2F%3E%3C%2Fsvg%3E);
top: 8%;
bottom: 4%;
}
trix-toolbar .trix-button--icon-bold::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M15.6%2011.8c1-.7%201.6-1.8%201.6-2.8a4%204%200%200%200-4-4H7v14h7c2.1%200%203.7-1.7%203.7-3.8%200-1.5-.8-2.8-2.1-3.4zM10%207.5h3a1.5%201.5%200%201%201%200%203h-3v-3zm3.5%209H10v-3h3.5a1.5%201.5%200%201%201%200%203z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-italic::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M10%205v3h2.2l-3.4%208H6v3h8v-3h-2.2l3.4-8H18V5h-8z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-link::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M9.88%2013.7a4.3%204.3%200%200%201%200-6.07l3.37-3.37a4.26%204.26%200%200%201%206.07%200%204.3%204.3%200%200%201%200%206.06l-1.96%201.72a.91.91%200%201%201-1.3-1.3l1.97-1.71a2.46%202.46%200%200%200-3.48-3.48l-3.38%203.37a2.46%202.46%200%200%200%200%203.48.91.91%200%201%201-1.3%201.3z%22%2F%3E%3Cpath%20d%3D%22M4.25%2019.46a4.3%204.3%200%200%201%200-6.07l1.93-1.9a.91.91%200%201%201%201.3%201.3l-1.93%201.9a2.46%202.46%200%200%200%203.48%203.48l3.37-3.38c.96-.96.96-2.52%200-3.48a.91.91%200%201%201%201.3-1.3%204.3%204.3%200%200%201%200%206.07l-3.38%203.38a4.26%204.26%200%200%201-6.07%200z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-strike::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M12.73%2014l.28.14c.26.15.45.3.57.44.12.14.18.3.18.5%200%20.3-.15.56-.44.75-.3.2-.76.3-1.39.3A13.52%2013.52%200%200%201%207%2014.95v3.37a10.64%2010.64%200%200%200%204.84.88c1.26%200%202.35-.19%203.28-.56.93-.37%201.64-.9%202.14-1.57s.74-1.45.74-2.32c0-.26-.02-.51-.06-.75h-5.21zm-5.5-4c-.08-.34-.12-.7-.12-1.1%200-1.29.52-2.3%201.58-3.02%201.05-.72%202.5-1.08%204.34-1.08%201.62%200%203.28.34%204.97%201l-1.3%202.93c-1.47-.6-2.73-.9-3.8-.9-.55%200-.96.08-1.2.26-.26.17-.38.38-.38.64%200%20.27.16.52.48.74.17.12.53.3%201.05.53H7.23zM3%2013h18v-2H3v2z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-quote::before {
background-image: url(data:image/svg+xml,%3Csvg%20version%3D%221%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M6%2017h3l2-4V7H5v6h3zm8%200h3l2-4V7h-6v6h3z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-heading-1::before {
background-image: url(data:image/svg+xml,%3Csvg%20version%3D%221%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M12%209v3H9v7H6v-7H3V9h9zM8%204h14v3h-6v12h-3V7H8V4z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-code::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M18.2%2012L15%2015.2l1.4%201.4L21%2012l-4.6-4.6L15%208.8l3.2%203.2zM5.8%2012L9%208.8%207.6%207.4%203%2012l4.6%204.6L9%2015.2%205.8%2012z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-bullet-list::before {
background-image: url(data:image/svg+xml,%3Csvg%20version%3D%221%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%204a2%202%200%201%200%200%204%202%202%200%200%200%200-4zm0%206a2%202%200%201%200%200%204%202%202%200%200%200%200-4zm0%206a2%202%200%201%200%200%204%202%202%200%200%200%200-4zm4%203h14v-2H8v2zm0-6h14v-2H8v2zm0-8v2h14V5H8z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-number-list::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M2%2017h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1%203h1.8L2%2013.1v.9h3v-1H3.2L5%2010.9V10H2v1zm5-6v2h14V5H7zm0%2014h14v-2H7v2zm0-6h14v-2H7v2z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-undo::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M12.5%208c-2.6%200-5%201-6.9%202.6L2%207v9h9l-3.6-3.6A8%208%200%200%201%2020%2016l2.4-.8a10.5%2010.5%200%200%200-10-7.2z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-redo::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M18.4%2010.6a10.5%2010.5%200%200%200-16.9%204.6L4%2016a8%208%200%200%201%2012.7-3.6L13%2016h9V7l-3.6%203.6z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-decrease-nesting-level::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M3%2019h19v-2H3v2zm7-6h12v-2H10v2zm-8.3-.3l2.8%202.9L6%2014.2%204%2012l2-2-1.4-1.5L1%2012l.7.7zM3%205v2h19V5H3z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-button--icon-increase-nesting-level::before {
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M3%2019h19v-2H3v2zm7-6h12v-2H10v2zm-6.9-1L1%2014.2l1.4%201.4L6%2012l-.7-.7-2.8-2.8L1%209.9%203.1%2012zM3%205v2h19V5H3z%22%2F%3E%3C%2Fsvg%3E);
}
trix-toolbar .trix-dialogs {
position: relative;
}
trix-toolbar .trix-dialog {
position: absolute;
top: 0;
left: 0;
right: 0;
font-size: 0.75em;
padding: 15px 10px;
background: rgb(48, 48, 48);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border: 1px solid rgb(112, 112, 112);
border-radius: 5px;
z-index: 5;
}
trix-toolbar .trix-input--dialog {
font-size: inherit;
font-weight: normal;
padding: 0.5em 0.8em;
margin: 0 10px 0 0;
border-radius: 3px;
border: 1px solid #bbb;
background-color: rgb(95, 95, 95);
box-shadow: none;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
}
trix-toolbar .trix-input--dialog.validate:invalid {
box-shadow: #F00 0px 0px 1.5px 1px;
}
trix-toolbar .trix-button--dialog {
font-size: inherit;
padding: 0.5em;
border-bottom: none;
color: #eee;
}
trix-toolbar .trix-dialog--link {
max-width: 600px;
}
trix-toolbar .trix-dialog__link-fields {
display: flex;
align-items: baseline;
}
trix-toolbar .trix-dialog__link-fields .trix-input {
flex: 1;
}
trix-toolbar .trix-dialog__link-fields .trix-button-group {
flex: 0 0 content;
margin: 0;
}
trix-editor [data-trix-mutable]:not(.attachment__caption-editor) {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
trix-editor [data-trix-mutable]::-moz-selection,
trix-editor [data-trix-cursor-target]::-moz-selection,
trix-editor [data-trix-mutable] ::-moz-selection {
background: none;
}
trix-editor [data-trix-mutable]::selection,
trix-editor [data-trix-cursor-target]::selection,
trix-editor [data-trix-mutable] ::selection {
background: none;
}
trix-editor [data-trix-mutable].attachment__caption-editor:focus::-moz-selection {
background: highlight;
}
trix-editor [data-trix-mutable].attachment__caption-editor:focus::selection {
background: highlight;
}
trix-editor [data-trix-mutable].attachment.attachment--file {
box-shadow: 0 0 0 2px highlight;
border-color: transparent;
}
trix-editor [data-trix-mutable].attachment img {
box-shadow: 0 0 0 2px highlight;
}
trix-editor .attachment {
position: relative;
}
trix-editor .attachment:hover {
cursor: default;
}
trix-editor .attachment--preview .attachment__caption:hover {
cursor: text;
}
trix-editor .attachment__progress {
position: absolute;
z-index: 1;
height: 20px;
top: calc(50% - 10px);
left: 5%;
width: 90%;
opacity: 0.9;
transition: opacity 200ms ease-in;
}
trix-editor .attachment__progress[value="100"] {
opacity: 0;
}
trix-editor .attachment__caption-editor {
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
font-size: inherit;
font-family: inherit;
line-height: inherit;
color: inherit;
text-align: center;
vertical-align: top;
border: none;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
}
trix-editor .attachment__toolbar {
position: absolute;
z-index: 1;
top: -0.9em;
left: 0;
width: 100%;
text-align: center;
}
trix-editor .trix-button-group {
display: inline-flex;
}
trix-editor .trix-button {
position: relative;
float: left;
color: #666;
white-space: nowrap;
font-size: 80%;
padding: 0 0.8em;
margin: 0;
outline: none;
border: none;
border-radius: 0;
background: transparent;
}
trix-editor .trix-button:not(:first-child) {
border-left: 1px solid #ccc;
}
trix-editor .trix-button.trix-active {
background: #cbeefa;
}
trix-editor .trix-button:not(:disabled) {
cursor: pointer;
}
trix-editor .trix-button--remove {
text-indent: -9999px;
display: inline-block;
padding: 0;
outline: none;
width: 1.8em;
height: 1.8em;
line-height: 1.8em;
border-radius: 50%;
background-color: #fff;
border: 2px solid highlight;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25);
}
trix-editor .trix-button--remove::before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.7;
content: "";
background-image: url(data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.4L17.6%205%2012%2010.6%206.4%205%205%206.4l5.6%205.6L5%2017.6%206.4%2019l5.6-5.6%205.6%205.6%201.4-1.4-5.6-5.6z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E);
background-position: center;
background-repeat: no-repeat;
background-size: 90%;
}
trix-editor .trix-button--remove:hover {
border-color: #333;
}
trix-editor .trix-button--remove:hover::before {
opacity: 1;
}
trix-editor .attachment__metadata-container {
position: relative;
}
trix-editor .attachment__metadata {
position: absolute;
left: 50%;
top: 2em;
transform: translate(-50%, 0);
max-width: 90%;
padding: 0.1em 0.6em;
font-size: 0.8em;
color: #fff;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 3px;
}
trix-editor .attachment__metadata .attachment__name {
display: inline-block;
max-width: 100%;
vertical-align: bottom;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
trix-editor .attachment__metadata .attachment__size {
margin-left: 0.2em;
white-space: nowrap;
}
.trix-content {
line-height: inherit;
}
.trix-content * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.trix-content p {
box-sizing: border-box;
margin-top: 0;
margin-bottom: 0.5em;
padding: 0;
}
.trix-content h1 {
font-size: 1.2em;
line-height: 1.2;
}
.trix-content blockquote {
border: 0 solid #ccc;
border-left-width: 0.3em;
margin-left: 0.3em;
padding-left: 0.6em;
}
.trix-content [dir=rtl] blockquote,
.trix-content blockquote[dir=rtl] {
border-width: 0;
border-right-width: 0.3em;
margin-right: 0.3em;
padding-right: 0.6em;
}
.trix-content li {
margin-left: 1em;
}
.trix-content [dir=rtl] li {
margin-right: 1em;
}
.trix-content pre {
display: inline-block;
width: 100%;
vertical-align: top;
font-family: monospace;
font-size: 0.9em;
padding: 0.5em;
white-space: pre;
background-color: #eee;
overflow-x: auto;
}
.trix-content img {
max-width: 100%;
height: auto;
}
.trix-content .attachment {
display: inline-block;
position: relative;
max-width: 100%;
}
.trix-content .attachment a {
color: inherit;
text-decoration: none;
}
.trix-content .attachment a:hover,
.trix-content .attachment a:visited:hover {
color: inherit;
}
.trix-content .attachment__caption {
text-align: center;
}
.trix-content .attachment__caption .attachment__name+.attachment__size::before {
content: ' · ';
}
.trix-content .attachment--preview {
width: 100%;
text-align: center;
}
.trix-content .attachment--preview .attachment__caption {
color: #666;
font-size: 0.9em;
line-height: 1.2;
}
.trix-content .attachment--file {
color: #333;
line-height: 1;
margin: 0 2px 2px 2px;
padding: 0.4em 1em;
border: 1px solid #bbb;
border-radius: 5px;
}
.trix-content .attachment-gallery {
display: flex;
flex-wrap: wrap;
position: relative;
}
.trix-content .attachment-gallery .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%;
}
.trix-content .attachment-gallery.attachment-gallery--2 .attachment,
.trix-content .attachment-gallery.attachment-gallery--4 .attachment {
flex-basis: 50%;
max-width: 50%;
}
================================================
FILE: client/components/app/Appbar.vue
================================================
<template>
<div class="w-full h-16 bg-primary relative">
<div id="appbar" role="toolbar" aria-label="Appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-60">
<div class="flex h-full items-center">
<nuxt-link to="/">
<img src="~static/icon.svg" :alt="$strings.ButtonHome" class="w-8 min-w-8 h-8 mr-2 sm:w-10 sm:min-w-10 sm:h-10 sm:mr-4" />
</nuxt-link>
<nuxt-link to="/">
<h1 class="text-xl mr-6 hidden lg:block hover:underline">audiobookshelf</h1>
</nuxt-link>
<ui-libraries-dropdown class="mr-2" />
<controls-global-search v-if="currentLibrary" class="mr-1 sm:mr-0" />
<div class="grow" />
<ui-tooltip v-if="isChromecastInitialized && !isHttps" direction="bottom" text="Casting requires a secure connection" class="flex items-center">
<span class="material-symbols text-2xl text-warning/50"> cast </span>
</ui-tooltip>
<div v-if="isChromecastInitialized" class="w-6 min-w-6 h-6 ml-2 mr-1 sm:mx-2 cursor-pointer">
<google-cast-launcher></google-cast-launcher>
</div>
<widgets-notification-widget class="hidden md:block" />
<nuxt-link v-if="currentLibrary" to="/config/stats" class="hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderYourStats" direction="bottom" class="flex items-center">
<span class="material-symbols text-2xl" aria-label="User Stats" role="button"></span>
</ui-tooltip>
</nuxt-link>
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.ButtonUpload" direction="bottom" class="flex items-center">
<span class="material-symbols text-2xl" aria-label="Upload Media" role="button"></span>
</ui-tooltip>
</nuxt-link>
<nuxt-link v-if="userIsAdminOrUp" to="/config" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderSettings" direction="bottom" class="flex items-center">
<span class="material-symbols text-2xl" aria-label="System Settings" role="button"></span>
</ui-tooltip>
</nuxt-link>
<nuxt-link to="/account" class="relative w-9 h-9 md:w-32 bg-fg border border-gray-500 rounded-sm shadow-xs ml-1.5 sm:ml-3 md:ml-5 md:pl-3 md:pr-10 py-2 text-left sm:text-sm cursor-pointer hover:bg-bg/40" aria-haspopup="listbox" aria-expanded="true">
<span class="items-center hidden md:flex">
<span class="block truncate">{{ username }}</span>
</span>
<span class="h-full md:ml-3 md:absolute inset-y-0 md:right-0 flex items-center justify-center md:pr-2 pointer-events-none">
<span class="material-symbols text-xl text-gray-100"></span>
</span>
</nuxt-link>
</div>
<div v-show="numMediaItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
<h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}</h1>
<div class="grow" />
<ui-btn v-if="!isPodcastLibrary && selectedMediaItemsArePlayable" color="bg-success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems">
<span class="material-symbols fill text-2xl -ml-2 pr-1 text-white">play_arrow</span>
{{ $strings.ButtonPlay }}
</ui-btn>
<ui-tooltip v-if="isBookLibrary" :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
<ui-read-icon-btn :disabled="processingBatch" :is-read="selectedIsFinished" @click="toggleBatchRead" class="mx-1.5" />
</ui-tooltip>
<ui-tooltip v-if="userCanUpdate && isBookLibrary" :text="$strings.LabelAddToCollection" direction="bottom">
<ui-icon-btn :disabled="processingBatch" icon="collections_bookmark" @click="batchAddToCollectionClick" class="mx-1.5" />
</ui-tooltip>
<template v-if="userCanUpdate">
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
<ui-icon-btn :disabled="processingBatch" icon="edit" bg-color="bg-warning" class="mx-1.5" @click="batchEditClick" />
</ui-tooltip>
</template>
<ui-tooltip v-if="userCanDelete" :text="$strings.ButtonRemove" direction="bottom">
<ui-icon-btn :disabled="processingBatch" icon="delete" bg-color="bg-error" class="mx-1.5" @click="batchDeleteClick" />
</ui-tooltip>
<ui-context-menu-dropdown v-if="contextMenuItems.length && !processingBatch" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" />
<ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom" class="flex items-center">
<span class="material-symbols text-3xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatch ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
</ui-tooltip>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
totalEntities: 0
}
},
computed: {
currentLibrary() {
return this.$store.getters['libraries/getCurrentLibrary']
},
libraryName() {
return this.currentLibrary ? this.currentLibrary.name : 'unknown'
},
libraryMediaType() {
return this.currentLibrary ? this.currentLibrary.mediaType : null
},
isPodcastLibrary() {
return this.libraryMediaType === 'podcast'
},
isBookLibrary() {
return this.libraryMediaType === 'book'
},
isHome() {
return this.$route.name === 'library-library'
},
user() {
return this.$store.state.user.user
},
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
},
username() {
return this.user ? this.user.username : 'err'
},
numMediaItemsSelected() {
return this.selectedMediaItems.length
},
selectedMediaItems() {
return this.$store.state.globals.selectedMediaItems
},
selectedMediaItemsArePlayable() {
return !this.selectedMediaItems.some((i) => !i.hasTracks)
},
userMediaProgress() {
return this.$store.state.user.user.mediaProgress || []
},
userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate']
},
userCanDelete() {
return this.$store.getters['user/getUserCanDelete']
},
userCanUpload() {
return this.$store.getters['user/getUserCanUpload']
},
selectedIsFinished() {
// Find an item that is not finished, if none then all items finished
return !this.selectedMediaItems.find((item) => {
const itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === item.id)
return !itemProgress || !itemProgress.isFinished
})
},
processingBatch() {
return this.$store.state.processingBatch
},
isChromecastEnabled() {
return this.$store.getters['getServerSetting']('chromecastEnabled')
},
isChromecastInitialized() {
return this.$store.state.globals.isChromecastInitialized
},
isHttps() {
return location.protocol === 'https:' || process.env.NODE_ENV === 'development'
},
contextMenuItems() {
if (!this.userIsAdminOrUp) return []
const options = [
{
text: this.$strings.ButtonQuickMatch,
action: 'quick-match'
}
]
if (!this.isPodcastLibrary && this.selectedMediaItemsArePlayable) {
options.push({
text: this.$strings.ButtonQuickEmbedMetadata,
action: 'quick-embed'
})
}
options.push({
text: this.$strings.ButtonReScan,
action: 'rescan'
})
// The limit of 50 is introduced because of the URL length. Each id has 36 chars, so 36 * 40 = 1440
// + 40 , separators = 1480 chars + base path 280 chars = 1760 chars. This keeps the URL under 2000 chars even with longer domains
if (this.selectedMediaItems.length <= 40) {
options.push({
text: this.$strings.LabelDownload,
action: 'download'
})
}
return options
}
},
methods: {
requestBatchQuickEmbed() {
const payload = {
message: this.$strings.MessageConfirmQuickEmbed,
allowHtml: true,
callback: (confirmed) => {
if (confirmed) {
this.$axios
.$post(`/api/tools/batch/embed-metadata`, {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then(() => {
console.log('Audio metadata embed started')
this.cancelSelectionMode()
})
.catch((error) => {
console.error('Audio metadata embed failed', error)
const errorMsg = error.response.data || 'Failed to embed metadata'
this.$toast.error(errorMsg)
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
contextMenuAction({ action }) {
if (action === 'quick-embed') {
this.requestBatchQuickEmbed()
} else if (action === 'quick-match') {
this.batchAutoMatchClick()
} else if (action === 'rescan') {
this.batchRescan()
} else if (action === 'download') {
this.batchDownload()
}
},
async batchRescan() {
const payload = {
message: this.$getString('MessageConfirmReScanLibraryItems', [this.selectedMediaItems.length]),
callback: (confirmed) => {
if (confirmed) {
this.$axios
.$post(`/api/items/batch/scan`, {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then(() => {
console.log('Batch Re-Scan started')
this.cancelSelectionMode()
})
.catch((error) => {
console.error('Batch Re-Scan failed', error)
const errorMsg = error.response.data || 'Failed to batch re-scan'
this.$toast.error(errorMsg)
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
async batchDownload() {
const libraryItemIds = this.selectedMediaItems.map((i) => i.id)
console.log('Downloading library items', libraryItemIds)
this.$downloadFile(`/api/libraries/${this.$store.state.libraries.currentLibraryId}/download?token=${this.$store.getters['user/getToken']}&ids=${libraryItemIds.join(',')}`)
},
async playSelectedItems() {
this.$store.commit('setProcessingBatch', true)
const libraryItemIds = this.selectedMediaItems.map((i) => i.id)
const libraryItems = await this.$axios
.$post(`/api/items/batch/get`, { libraryItemIds })
.then((res) => res.libraryItems)
.catch((error) => {
const errorMsg = error.response.data || 'Failed to get items'
console.error(errorMsg, error)
this.$toast.error(errorMsg)
return []
})
if (!libraryItems.length) {
this.$store.commit('setProcessingBatch', false)
return
}
const queueItems = []
libraryItems.forEach((item) => {
let subtitle = ''
if (item.mediaType === 'book') subtitle = item.media.metadata.authors.map((au) => au.name).join(', ')
queueItems.push({
libraryItemId: item.id,
libraryId: item.libraryId,
episodeId: null,
title: item.media.metadata.title,
subtitle,
caption: '',
duration: item.media.duration || null,
coverPath: item.media.coverPath || null
})
})
this.$eventBus.$emit('play-item', {
libraryItemId: queueItems[0].libraryItemId,
queueItems
})
this.$store.commit('setProcessingBatch', false)
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
},
cancelSelectionMode() {
if (this.processingBatch) return
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
},
toggleBatchRead() {
this.$store.commit('setProcessingBatch', true)
const newIsFinished = !this.selectedIsFinished
const updateProgressPayloads = this.selectedMediaItems.map((item) => {
return {
libraryItemId: item.id,
isFinished: newIsFinished
}
})
console.log('Progress payloads', updateProgressPayloads)
this.$axios
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
.then(() => {
this.$toast.success(this.$strings.ToastBatchUpdateSuccess)
this.$store.commit('setProcessingBatch', false)
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
})
.catch((error) => {
this.$toast.error(this.$strings.ToastBatchUpdateFailed)
console.error('Failed to batch update read/not read', error)
this.$store.commit('setProcessingBatch', false)
})
},
batchDeleteClick() {
const payload = {
message: this.$getString('MessageConfirmDeleteLibraryItems', [this.numMediaItemsSelected]),
checkboxLabel: this.$strings.LabelDeleteFromFileSystemCheckbox,
yesButtonText: this.$strings.ButtonDelete,
yesButtonColor: 'error',
checkboxDefaultValue: !Number(localStorage.getItem('softDeleteDefault') || 0),
callback: (confirmed, hardDelete) => {
if (confirmed) {
localStorage.setItem('softDeleteDefault', hardDelete ? 0 : 1)
this.$store.commit('setProcessingBatch', true)
this.$axios
.$post(`/api/items/batch/delete?hard=${hardDelete ? 1 : 0}`, {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then(() => {
this.$toast.success(this.$strings.ToastBatchDeleteSuccess)
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
})
.catch((error) => {
console.error('Batch delete failed', error)
this.$toast.error(this.$strings.ToastBatchDeleteFailed)
})
.finally(() => {
this.$store.commit('setProcessingBatch', false)
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
batchEditClick() {
this.$router.push('/batch')
},
batchAddToCollectionClick() {
this.$store.commit('globals/setShowBatchCollectionsModal', true)
},
setBookshelfTotalEntities(totalEntities) {
this.totalEntities = totalEntities
},
batchAutoMatchClick() {
this.$store.commit('globals/setShowBatchQuickMatchModal', true)
}
},
mounted() {
this.$eventBus.$on('bookshelf-total-entities', this.setBookshelfTotalEntities)
},
beforeDestroy() {
this.$eventBus.$off('bookshelf-total-entities', this.setBookshelfTotalEntities)
}
}
</script>
<style>
#appbar {
box-shadow: 0px 5px 5px #11111155;
}
</style>
================================================
FILE: client/components/app/BookShelfCategorized.vue
================================================
<template>
<div id="bookshelf" ref="wrapper" class="w-full max-w-full h-full overflow-y-scroll relative" :style="{ fontSize: sizeMultiplier + 'rem' }">
<!-- Cover size widget -->
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
<div v-if="loaded && !shelves.length && !search" class="w-full flex flex-col items-center justify-center py-12">
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
<div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="bg-primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
<ui-btn color="bg-success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
</div>
</div>
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center">
<p class="text-center text-xl py-4">{{ $strings.MessageBookshelfNoResultsForQuery }}</p>
</div>
<!-- Alternate plain view -->
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24e">
<template v-for="(shelf, index) in supportedShelves">
<widgets-item-slider :shelf-id="shelf.id" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" :type="shelf.type" class="bookshelf-row pl-8e my-6e" @selectEntity="(payload) => selectEntity(payload, index)">
<h2 class="font-semibold text-gray-100">{{ $strings[shelf.labelStringKey] }}</h2>
</widgets-item-slider>
</template>
</div>
<!-- Regular bookshelf view -->
<div v-else class="w-full">
<template v-for="(shelf, index) in supportedShelves">
<app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" @selectEntity="(payload) => selectEntity(payload, index)" />
</template>
</div>
</div>
</template>
<script>
export default {
props: {
search: Boolean,
results: {
type: Object,
default: () => {}
}
},
data() {
return {
loaded: false,
keywordFilterTimeout: null,
scannerParseSubtitle: false,
wrapperClientWidth: 0,
shelves: [],
lastItemIndexSelected: -1,
tempIsScanning: false
}
},
computed: {
supportedShelves() {
return this.shelves.filter((shelf) => ['book', 'podcast', 'episode', 'series', 'authors', 'narrators'].includes(shelf.type))
},
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
currentLibraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
},
libraryName() {
return this.$store.getters['libraries/getCurrentLibraryName']
},
isAlternativeBookshelfView() {
return this.$store.getters['getHomeBookshelfView'] === this.$constants.BookshelfView.DETAIL
},
bookCoverWidth() {
var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
if (this.isCoverSquareAspectRatio) return coverSize * 1.6
return coverSize
},
coverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
isCoverSquareAspectRatio() {
return this.coverAspectRatio == 1
},
sizeMultiplier() {
return this.$store.getters['user/getSizeMultiplier']
},
selectedMediaItems() {
return this.$store.state.globals.selectedMediaItems || []
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
isScanningLibrary() {
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
}
},
methods: {
selectEntity({ entity, shiftKey }, shelfIndex) {
const shelf = this.shelves[shelfIndex]
const entityShelfIndex = shelf.entities.findIndex((ent) => ent.id === entity.id)
const indexOf = shelf.shelfStartIndex + entityShelfIndex
const lastLastItemIndexSelected = this.lastItemIndexSelected
if (!this.selectedMediaItems.some((i) => i.id === entity.id)) {
this.lastItemIndexSelected = indexOf
} else {
this.lastItemIndexSelected = -1
}
if (shiftKey && lastLastItemIndexSelected >= 0) {
let loopStart = indexOf
let loopEnd = lastLastItemIndexSelected
if (indexOf > lastLastItemIndexSelected) {
loopStart = lastLastItemIndexSelected
loopEnd = indexOf
}
const flattenedEntitiesArray = []
this.shelves.map((s) => flattenedEntitiesArray.push(...s.entities))
let isSelecting = false
// If any items in this range is not selected then select all otherwise unselect all
for (let i = loopStart; i <= loopEnd; i++) {
const thisEntity = flattenedEntitiesArray[i]
if (thisEntity) {
if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) {
isSelecting = true
break
}
}
}
if (isSelecting) this.lastItemIndexSelected = indexOf
for (let i = loopStart; i <= loopEnd; i++) {
const thisEntity = flattenedEntitiesArray[i]
if (thisEntity) {
const mediaItem = {
id: thisEntity.id,
mediaType: thisEntity.mediaType,
hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.audioFile || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length)
}
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting })
} else {
console.error('Invalid entity index', i)
}
}
} else {
const mediaItem = {
id: entity.id,
mediaType: entity.mediaType,
hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
}
this.$store.commit('globals/toggleMediaItemSelected', mediaItem)
}
this.$nextTick(() => {
this.$eventBus.$emit('item-selected', entity)
})
},
async init() {
this.wrapperClientWidth = this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0
if (this.search) {
this.setShelvesFromSearch()
} else {
await this.fetchCategories()
}
this.loaded = true
},
async fetchCategories() {
// Sets the limit for the number of items to be displayed based on the viewport width.
const viewportWidth = window.innerWidth
let limit
if (viewportWidth >= 3240) {
limit = 15
} else if (viewportWidth >= 2880 && viewportWidth < 3240) {
limit = 12
}
const limitQuery = limit ? `&limit=${limit}` : ''
const categories = await this.$axios
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete,share${limitQuery}`)
.then((data) => {
return data
})
.catch((error) => {
console.error('Failed to fetch categories', error)
return []
})
let totalEntityCount = 0
for (const shelf of categories) {
shelf.shelfStartIndex = totalEntityCount
totalEntityCount += shelf.entities.length
}
this.shelves = categories
},
async setShelvesFromSearch() {
const shelves = []
if (this.results.books?.length) {
shelves.push({
id: 'books',
label: 'Books',
labelStringKey: 'LabelBooks',
type: 'book',
entities: this.results.books.map((res) => res.libraryItem)
})
}
if (this.results.podcasts?.length) {
shelves.push({
id: 'podcasts',
label: 'Podcasts',
labelStringKey: 'LabelPodcasts',
type: 'podcast',
entities: this.results.podcasts.map((res) => res.libraryItem)
})
}
if (this.results.episodes?.length) {
shelves.push({
id: 'episodes',
label: 'Episodes',
labelStringKey: 'LabelEpisodes',
type: 'episode',
entities: this.results.episodes.map((res) => res.libraryItem)
})
}
if (this.results.series?.length) {
shelves.push({
id: 'series',
label: 'Series',
labelStringKey: 'LabelSeries',
type: 'series',
entities: this.results.series.map((seriesObj) => {
return {
...seriesObj.series,
books: seriesObj.books,
type: 'series'
}
})
})
}
if (this.results.tags?.length) {
shelves.push({
id: 'tags',
label: 'Tags',
labelStringKey: 'LabelTags',
type: 'tags',
entities: this.results.tags.map((tagObj) => {
return {
name: tagObj.name,
books: tagObj.books || [],
type: 'tags'
}
})
})
}
if (this.results.authors?.length) {
shelves.push({
id: 'authors',
label: 'Authors',
labelStringKey: 'LabelAuthors',
type: 'authors',
entities: this.results.authors.map((a) => {
return {
...a,
type: 'author'
}
})
})
}
if (this.results.narrators?.length) {
shelves.push({
id: 'narrators',
label: 'Narrators',
labelStringKey: 'LabelNarrators',
type: 'narrators',
entities: this.results.narrators.map((n) => {
return {
...n,
type: 'narrator'
}
})
})
}
this.shelves = shelves
},
scan() {
this.tempIsScanning = true
this.$store
.dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId })
.catch((error) => {
console.error('Failed to start scan', error)
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
})
.finally(() => {
this.tempIsScanning = false
})
},
userUpdated(user) {
if (user.id !== this.$store.state.user.user.id) return
if (user.seriesHideFromContinueListening && user.seriesHideFromContinueListening.length) {
this.removeAllSeriesFromContinueSeries(user.seriesHideFromContinueListening)
}
if (user.mediaProgress.length) {
const mediaProgressToHide = user.mediaProgress.filter((mp) => mp.hideFromContinueListening)
this.removeItemsFromContinueListeningReading(mediaProgressToHide, 'continue-listening')
this.removeItemsFromContinueListeningReading(mediaProgressToHide, 'continue-reading')
}
},
libraryItemAdded(libraryItem) {
console.log('libraryItem added', libraryItem)
// TODO: Check if libraryItem would be on this shelf
if (!this.search) {
this.fetchCategories()
}
},
libraryItemUpdated(libraryItem) {
console.log('libraryItem updated', libraryItem)
this.shelves.forEach((shelf) => {
if (shelf.type == 'book' || shelf.type == 'podcast') {
shelf.entities = shelf.entities.map((ent) => {
if (ent.id === libraryItem.id) {
return libraryItem
}
return ent
})
} else if (shelf.type === 'series') {
shelf.entities.forEach((ent) => {
ent.books = ent.books.map((book) => {
if (book.id === libraryItem.id) return libraryItem
return book
})
})
}
})
},
removeBookFromShelf(libraryItem) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'book' || shelf.type == 'podcast') {
shelf.entities = shelf.entities.filter((ent) => {
return ent.id !== libraryItem.id
})
} else if (shelf.type === 'series') {
shelf.entities.forEach((ent) => {
ent.books = ent.books.filter((book) => {
return book.id !== libraryItem.id
})
})
}
})
},
libraryItemRemoved(libraryItem) {
this.removeBookFromShelf(libraryItem)
},
libraryItemsAdded(libraryItems) {
console.log('libraryItems added', libraryItems)
// First items added to library
const isThisLibrary = libraryItems.some((li) => li.libraryId === this.currentLibraryId)
if (!this.shelves.length && !this.search && isThisLibrary) {
this.fetchCategories()
return
}
const recentlyAddedShelf = this.shelves.find((shelf) => shelf.id === 'recently-added')
if (!recentlyAddedShelf) return
// Add new library item to the recently added shelf
for (const libraryItem of libraryItems) {
if (libraryItem.libraryId === this.currentLibraryId && !recentlyAddedShelf.entities.some((ent) => ent.id === libraryItem.id)) {
// Add to front of array
recentlyAddedShelf.entities.unshift(libraryItem)
}
}
},
libraryItemsUpdated(items) {
items.forEach((li) => {
this.libraryItemUpdated(li)
})
},
episodeAdded(episodeWithLibraryItem) {
const isThisLibrary = episodeWithLibraryItem.libraryItem?.libraryId === this.currentLibraryId
if (!this.search && isThisLibrary) {
this.fetchCategories()
}
},
removeAllSeriesFromContinueSeries(seriesIds) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'book' && shelf.id == 'continue-series') {
// Filter out series books from continue series shelf
shelf.entities = shelf.entities.filter((ent) => {
if (ent.media.metadata.series && seriesIds.includes(ent.media.metadata.series.id)) return false
return true
})
}
})
},
removeItemsFromContinueListeningReading(mediaProgressItems, categoryId) {
const continueListeningShelf = this.shelves.find((s) => s.id === categoryId)
if (continueListeningShelf) {
if (continueListeningShelf.type === 'book') {
continueListeningShelf.entities = continueListeningShelf.entities.filter((ent) => {
if (mediaProgressItems.some((mp) => mp.libraryItemId === ent.id)) return false
return true
})
} else if (continueListeningShelf.type === 'episode') {
continueListeningShelf.entities = continueListeningShelf.entities.filter((ent) => {
if (!ent.recentEpisode) return true // Should always have this here
if (mediaProgressItems.some((mp) => mp.libraryItemId === ent.id && mp.episodeId === ent.recentEpisode.id)) return false
return true
})
}
}
},
authorUpdated(author) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'authors') {
shelf.entities = shelf.entities.map((ent) => {
if (ent.id === author.id) {
return {
...ent,
...author
}
}
return ent
})
}
})
},
authorRemoved(author) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'authors') {
shelf.entities = shelf.entities.filter((ent) => ent.id != author.id)
}
})
},
shareOpen(mediaItemShare) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'book') {
shelf.entities = shelf.entities.map((ent) => {
if (ent.media.id === mediaItemShare.mediaItemId) {
return {
...ent,
mediaItemShare
}
}
return ent
})
}
})
},
shareClosed(mediaItemShare) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'book') {
shelf.entities = shelf.entities.map((ent) => {
if (ent.media.id === mediaItemShare.mediaItemId) {
return {
...ent,
mediaItemShare: null
}
}
return ent
})
}
})
},
initListeners() {
if (this.$root.socket) {
this.$root.socket.on('user_updated', this.userUpdated)
this.$root.socket.on('author_updated', this.authorUpdated)
this.$root.socket.on('author_removed', this.authorRemoved)
this.$root.socket.on('item_updated', this.libraryItemUpdated)
this.$root.socket.on('item_added', this.libraryItemAdded)
this.$root.socket.on('item_removed', this.libraryItemRemoved)
this.$root.socket.on('items_updated', this.libraryItemsUpdated)
this.$root.socket.on('items_added', this.libraryItemsAdded)
this.$root.socket.on('episode_added', this.episodeAdded)
this.$root.socket.on('share_open', this.shareOpen)
this.$root.socket.on('share_closed', this.shareClosed)
} else {
console.error('Error socket not initialized')
}
},
removeListeners() {
if (this.$root.socket) {
this.$root.socket.off('user_updated', this.userUpdated)
this.$root.socket.off('author_updated', this.authorUpdated)
this.$root.socket.off('author_removed', this.authorRemoved)
this.$root.socket.off('item_updated', this.libraryItemUpdated)
this.$root.socket.off('item_added', this.libraryItemAdded)
this.$root.socket.off('item_removed', this.libraryItemRemoved)
this.$root.socket.off('items_updated', this.libraryItemsUpdated)
this.$root.socket.off('items_added', this.libraryItemsAdded)
this.$root.socket.off('episode_added', this.episodeAdded)
this.$root.socket.off('share_open', this.shareOpen)
this.$root.socket.off('share_closed', this.shareClosed)
} else {
console.error('Error socket not initialized')
}
}
},
mounted() {
this.initListeners()
this.init()
},
beforeDestroy() {
this.removeListeners()
}
}
</script>
================================================
FILE: client/components/app/BookShelfRow.vue
================================================
<template>
<div class="relative">
<div ref="shelf" class="w-full max-w-full bookshelf-row categorizedBookshelfRow relative overflow-x-scroll no-scroll overflow-y-hidden z-10" :style="{ paddingLeft: paddingLeft + 'em' }" @scroll="scrolled">
<div class="w-full h-full pt-6e">
<div v-if="shelf.type === 'book' || shelf.type === 'podcast'" class="flex items-center">
<template v-for="(entity, index) in shelf.entities">
<cards-lazy-book-card :key="entity.id" :ref="`shelf-book-${entity.id}`" :index="index" :book-mount="entity" :continue-listening-shelf="continueListeningShelf" class="relative mx-2e" @hook:updated="updatedBookCard" @select="selectItem" @edit="editItem" />
</template>
</div>
<div v-if="shelf.type === 'episode'" class="flex items-center">
<template v-for="(entity, index) in shelf.entities">
<cards-lazy-book-card :key="entity.recentEpisode.id" :ref="`shelf-episode-${entity.recentEpisode.id}`" :index="index" :book-mount="entity" :continue-listening-shelf="continueListeningShelf" class="relative mx-2e" @hook:updated="updatedBookCard" @select="selectItem" @editPodcast="editItem" @edit="editEpisode" />
</template>
</div>
<div v-if="shelf.type === 'series'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-lazy-series-card :key="entity.name" :series-mount="entity" class="relative mx-2e" @hook:updated="updatedBookCard" />
</template>
</div>
<div v-if="shelf.type === 'tags'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-group-card :key="entity.name" :group="entity" class="relative mx-2e" @hook:updated="updatedBookCard" />
</template>
</div>
<div v-if="shelf.type === 'authors'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-author-card :key="entity.id" :authorMount="entity" @hook:updated="updatedBookCard" class="mx-2e" @edit="editAuthor" />
</template>
</div>
<div v-if="shelf.type === 'narrators'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-narrator-card :key="entity.name" :narrator="entity" @hook:updated="updatedBookCard" class="mx-2e" />
</template>
</div>
</div>
</div>
<div class="relative">
<div class="relative text-center categoryPlacard transform z-30 top-0 left-4e md:left-8e w-44e rounded-md">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-xs border" :style="{ padding: `0em 0.5em` }">
<h2 :style="{ fontSize: 0.9 + 'em' }">{{ $strings[shelf.labelStringKey] }}</h2>
</div>
</div>
<div class="bookshelfDividerCategorized h-6e w-full absolute top-0 left-0 right-0 z-20"></div>
</div>
<button v-show="canScrollLeft && !isScrolling" :aria-label="$strings.ButtonScrollLeft" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollLeft">
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_left</span>
</button>
<button v-show="canScrollRight && !isScrolling" :aria-label="$strings.ButtonScrollRight" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollRight">
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_right</span>
</button>
</div>
</template>
<script>
export default {
props: {
index: Number,
shelf: {
type: Object,
default: () => {}
},
continueListeningShelf: Boolean
},
data() {
return {
canScrollRight: false,
canScrollLeft: false,
isScrolling: false,
scrollTimer: null,
updateTimer: null
}
},
computed: {
sizeMultiplier() {
return this.$store.getters['user/getSizeMultiplier']
},
paddingLeft() {
if (window.innerWidth < 768) return 1
return 2.5
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
isSelectionMode() {
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
}
},
methods: {
clearSelectedEntities() {
this.updateSelectionMode(false)
},
editAuthor(author) {
this.$store.commit('globals/showEditAuthorModal', author)
},
editItem(libraryItem, tab = 'details') {
var itemIds = this.shelf.entities.map((e) => e.id)
this.$store.commit('setBookshelfBookIds', itemIds)
this.$store.commit('showEditModalOnTab', { libraryItem, tab: tab || 'details' })
},
editEpisode({ libraryItem, episode }) {
this.$store.commit('setEpisodeTableEpisodeIds', [episode.id])
this.$store.commit('setSelectedLibraryItem', libraryItem)
this.$store.commit('globals/setSelectedEpisode', episode)
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
},
updateSelectionMode(val) {
const selectedMediaItems = this.$store.state.globals.selectedMediaItems
if (this.shelf.type === 'book' || this.shelf.type === 'podcast') {
this.shelf.entities.forEach((ent) => {
var component = this.$refs[`shelf-book-${ent.id}`]
if (!component || !component.length) return
component = component[0]
component.setSelectionMode(val)
component.selected = selectedMediaItems.some((i) => i.id === ent.id)
})
} else if (this.shelf.type === 'episode') {
this.shelf.entities.forEach((ent) => {
var component = this.$refs[`shelf-episode-${ent.recentEpisode.id}`]
if (!component || !component.length) return
component = component[0]
component.setSelectionMode(val)
component.selected = selectedMediaItems.some((i) => i.id === ent.id)
})
}
},
selectItem(payload) {
this.$emit('selectEntity', payload)
},
itemSelectedEvt() {
this.updateSelectionMode(this.isSelectionMode)
},
scrolled() {
clearTimeout(this.scrollTimer)
this.scrollTimer = setTimeout(() => {
this.isScrolling = false
this.$nextTick(this.checkCanScroll)
}, 50)
},
scrollLeft() {
if (!this.$refs.shelf) {
return
}
this.isScrolling = true
this.$refs.shelf.scrollLeft = 0
},
scrollRight() {
if (!this.$refs.shelf) {
return
}
this.isScrolling = true
this.$refs.shelf.scrollLeft = 999
},
updatedBookCard() {
clearTimeout(this.updateTimer)
this.updateTimer = setTimeout(() => {
this.$nextTick(this.checkCanScroll)
}, 100)
},
checkCanScroll() {
if (!this.$refs.shelf) {
return
}
var clientWidth = this.$refs.shelf.clientWidth
var scrollWidth = this.$refs.shelf.scrollWidth
var scrollLeft = this.$refs.shelf.scrollLeft
if (scrollWidth > clientWidth) {
this.canScrollRight = scrollLeft === 0
this.canScrollLeft = scrollLeft > 0
} else {
this.canScrollRight = false
this.canScrollLeft = false
}
}
},
mounted() {
this.$eventBus.$on('bookshelf_clear_selection', this.clearSelectedEntities)
this.$eventBus.$on('item-selected', this.itemSelectedEvt)
},
beforeDestroy() {
this.$eventBus.$off('bookshelf_clear_selection', this.clearSelectedEntities)
this.$eventBus.$off('item-selected', this.itemSelectedEvt)
}
}
</script>
<style>
.categorizedBookshelfRow {
scroll-behavior: smooth;
background-image: var(--bookshelf-texture-img);
background-repeat: repeat-x;
}
.bookshelfDividerCategorized {
background: rgb(149, 119, 90);
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%);
box-shadow: 2px 14px 8px #111111aa;
}
.book-shelf-arrow-right {
height: calc(100% - 1.5em);
background: rgb(48, 48, 48);
background: linear-gradient(90deg, rgba(48, 48, 48, 0) 0%, rgba(25, 25, 25, 0.25) 8%, rgba(17, 17, 17, 0.4) 28%, rgba(17, 17, 17, 0.6) 71%, rgba(10, 10, 10, 0.6) 86%, rgba(0, 0, 0, 0.7) 100%);
}
.book-shelf-arrow-left {
height: calc(100% - 1.5em);
background: rgb(48, 48, 48);
background: linear-gradient(-90deg, rgba(48, 48, 48, 0) 0%, rgba(25, 25, 25, 0.25) 8%, rgba(17, 17, 17, 0.4) 28%, rgba(17, 17, 17, 0.6) 71%, rgba(10, 10, 10, 0.6) 86%, rgba(0, 0, 0, 0.7) 100%);
}
</style>
================================================
FILE: client/components/app/BookShelfToolbar.vue
================================================
<template>
<div class="w-full h-20 md:h-10 relative">
<div class="flex md:hidden h-10 items-center">
<nuxt-link :to="`/library/${currentLibraryId}`" class="grow h-full flex justify-center items-center" :class="isHomePage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isHomePage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonHome }}</p>
<span v-else class="material-symbols text-lg">home</span>
</nuxt-link>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="grow h-full flex justify-center items-center" :class="isLibraryPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isLibraryPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonLibrary }}</p>
<span v-else class="material-symbols text-lg">import_contacts</span>
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="grow h-full flex justify-center items-center" :class="isPodcastLatestPage ? 'bg-primary/80' : 'bg-primary/40'">
<p class="text-sm">{{ $strings.ButtonLatest }}</p>
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="grow h-full flex justify-center items-center" :class="isSeriesPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isSeriesPage" class="text-sm">{{ $strings.ButtonSeries }}</p>
<span v-else class="material-symbols text-lg">view_column</span>
</nuxt-link>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p>
<span v-else class="material-symbols text-lg"></span>
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="grow h-full flex justify-center items-center" :class="isCollectionsPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isCollectionsPage" class="text-sm">{{ $strings.ButtonCollections }}</p>
<span v-else class="material-symbols text-lg"></span>
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
<span v-else class="material-symbols text-lg">groups</span>
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary/80' : 'bg-primary/40'">
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="grow h-full flex justify-center items-center" :class="isPodcastDownloadQueuePage ? 'bg-primary/80' : 'bg-primary/40'">
<p class="text-sm">{{ $strings.ButtonDownloadQueue }}</p>
</nuxt-link>
</div>
<div id="toolbar" role="toolbar" aria-label="Library Toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-40 flex items-center justify-end md:justify-start px-2 md:px-8">
<!-- Series books page -->
<template v-if="selectedSeries">
<p class="pl-2 text-base md:text-lg">
{{ seriesName }}
</p>
<div class="w-6 h-6 rounded-full bg-black/30 flex items-center justify-center ml-3">
<span class="font-mono">{{ $formatNumber(numShowing) }}</span>
</div>
<div class="grow" />
<!-- RSS feed -->
<ui-tooltip v-if="seriesRssFeed" :text="$strings.LabelOpenRSSFeed" direction="top">
<ui-icon-btn icon="rss_feed" class="mx-0.5" :size="7" icon-font-size="1.2rem" bg-color="bg-success" outlined @click="showOpenSeriesRSSFeed" />
</ui-tooltip>
<ui-context-menu-dropdown v-if="!isBatchSelecting && seriesContextMenuItems.length" :items="seriesContextMenuItems" class="mx-px" @action="seriesContextMenuAction" />
</template>
<!-- library & collections page -->
<template v-else-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome && !isAuthorsPage">
<p class="hidden md:block">{{ $formatNumber(numShowing) }} {{ entityName }}</p>
<div class="grow hidden sm:inline-block" />
<!-- library filter select -->
<controls-library-filter-select v-if="isLibraryPage && !isBatchSelecting" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" />
<!-- library sort select -->
<controls-library-sort-select v-if="isLibraryPage && !isBatchSelecting" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateOrder" />
<!-- series filter select -->
<controls-library-filter-select v-if="isSeriesPage && !isBatchSelecting" v-model="settings.seriesFilterBy" is-series class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesFilter" />
<!-- series sort select -->
<controls-sort-select v-if="isSeriesPage && !isBatchSelecting" v-model="settings.seriesSortBy" :descending.sync="settings.seriesSortDesc" :items="seriesSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesSort" />
<!-- issues page remove all button -->
<ui-btn v-if="isIssuesFilter && userCanDelete && !isBatchSelecting" :loading="processingIssues" color="bg-error" small class="ml-4" @click="removeAllIssues">{{ $strings.ButtonRemoveAll }} {{ $formatNumber(numShowing) }} {{ entityName }}</ui-btn>
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template>
<!-- search page -->
<template v-else-if="page === 'search'">
<div class="grow" />
<p>{{ $strings.MessageSearchResultsFor }} "{{ searchQuery }}"</p>
<div class="grow" />
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template>
<!-- authors page -->
<template v-else-if="isAuthorsPage">
<p class="hidden md:block">{{ $formatNumber(numShowing) }} {{ entityName }}</p>
<div class="grow hidden sm:inline-block" />
<ui-btn v-if="userCanUpdate && !isBatchSelecting" :loading="processingAuthors" color="bg-primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn>
<!-- author sort select -->
<controls-sort-select v-model="settings.authorSortBy" :descending.sync="settings.authorSortDesc" :items="authorSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateAuthorSort" />
</template>
<!-- home page -->
<template v-else-if="isHome">
<div class="grow" />
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template>
</div>
</div>
</template>
<script>
export default {
props: {
page: String,
isHome: Boolean,
selectedSeries: {
type: Object,
default: () => null
},
searchQuery: String
},
data() {
return {
settings: {},
hasInit: false,
totalEntities: 0,
processingSeries: false,
processingIssues: false,
processingAuthors: false
}
},
computed: {
seriesContextMenuItems() {
if (!this.selectedSeries) return []
const items = [
{
text: this.isSeriesFinished ? this.$strings.MessageMarkAsNotFinished : this.$strings.MessageMarkAsFinished,
action: 'mark-series-finished'
}
]
if (this.userIsAdminOrUp || this.selectedSeries.rssFeed) {
items.push({
text: this.$strings.LabelOpenRSSFeed,
action: 'open-rss-feed'
})
}
if (this.isSeriesRemovedFromContinueListening) {
items.push({
text: this.$strings.LabelReAddSeriesToContinueListening,
action: 're-add-to-continue-listening'
})
}
this.addSubtitlesMenuItem(items)
this.addCollapseSubSeriesMenuItem(items)
return items
},
seriesSortItems() {
return [
{
text: this.$strings.LabelName,
value: 'name'
},
{
text: this.$strings.LabelNumberOfBooks,
value: 'numBooks'
},
{
text: this.$strings.LabelAddedAt,
value: 'addedAt'
},
{
text: this.$strings.LabelLastBookAdded,
value: 'lastBookAdded'
},
{
text: this.$strings.LabelLastBookUpdated,
value: 'lastBookUpdated'
},
{
text: this.$strings.LabelTotalDuration,
value: 'totalDuration'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
}
]
},
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']
},
userCanDelete() {
return this.$store.getters['user/getUserCanDelete']
},
userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate']
},
userCanDownload() {
return this.$store.getters['user/getUserCanDownload']
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
libraryProvider() {
return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
},
currentLibraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
},
isBookLibrary() {
return this.currentLibraryMediaType === 'book'
},
isPodcastLibrary() {
return this.currentLibraryMediaType === 'podcast'
},
isLibraryPage() {
return this.page === ''
},
isSeriesPage() {
return this.page === 'series'
},
isCollectionsPage() {
return this.page === 'collections'
},
isPlaylistsPage() {
return this.page === 'playlists'
},
isHomePage() {
return this.$route.name === 'library-library'
},
isPodcastSearchPage() {
return this.$route.name === 'library-library-podcast-search'
},
isPodcastLatestPage() {
return this.$route.name === 'library-library-podcast-latest'
},
isPodcastDownloadQueuePage() {
return this.$route.name === 'library-library-podcast-download-queue'
},
isAuthorsPage() {
return this.page === 'authors'
},
numShowing() {
return this.totalEntities
},
entityName() {
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
if (!this.page) return this.$strings.LabelBooks
if (this.isSeriesPage) return this.$strings.LabelSeries
if (this.isCollectionsPage) return this.$strings.LabelCollections
if (this.isPlaylistsPage) return this.$strings.LabelPlaylists
if (this.isAuthorsPage) return this.$strings.LabelAuthors
return ''
},
seriesId() {
return this.selectedSeries ? this.selectedSeries.id : null
},
seriesName() {
return this.selectedSeries ? this.selectedSeries.name : null
},
seriesProgress() {
return this.selectedSeries ? this.selectedSeries.progress : null
},
seriesRssFeed() {
return this.selectedSeries ? this.selectedSeries.rssFeed : null
},
seriesLibraryItemIds() {
if (!this.seriesProgress) return []
return this.seriesProgress.libraryItemIds || []
},
isBatchSelecting() {
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
},
isSeriesFinished() {
return this.seriesProgress && !!this.seriesProgress.isFinished
},
isSeriesRemovedFromContinueListening() {
if (!this.seriesId) return false
return this.$store.getters['user/getIsSeriesRemovedFromContinueListening'](this.seriesId)
},
filterBy() {
return this.$store.getters['user/getUserSetting']('filterBy')
},
isIssuesFilter() {
return this.filterBy === 'issues' && this.$route.query.filter === 'issues'
},
contextMenuItems() {
const items = []
if (this.isPodcastLibrary && this.isLibraryPage && this.userCanDownload) {
items.push({
text: this.$strings.LabelExportOPML,
action: 'export-opml'
})
}
this.addSubtitlesMenuItem(items)
this.addCollapseSeriesMenuItem(items)
return items
},
showPlaylists() {
return this.$store.state.libraries.numUserPlaylists > 0
}
},
methods: {
addSubtitlesMenuItem(items) {
if (this.isBookLibrary && (!this.page || this.page === 'search')) {
if (this.settings.showSubtitles) {
items.push({
text: this.$strings.LabelHideSubtitles,
action: 'hide-subtitles'
})
} else {
items.push({
text: this.$strings.LabelShowSubtitles,
action: 'show-subtitles'
})
}
}
},
addCollapseSeriesMenuItem(items) {
if (this.isLibraryPage && this.isBookLibrary && !this.isBatchSelecting) {
if (this.settings.collapseSeries) {
items.push({
text: this.$strings.LabelExpandSeries,
action: 'expand-series'
})
} else {
items.push({
text: this.$strings.LabelCollapseSeries,
action: 'collapse-series'
})
}
}
},
addCollapseSubSeriesMenuItem(items) {
if (this.selectedSeries && this.isBookLibrary && !this.isBatchSelecting) {
gitextract_pkg28qgc/
├── .devcontainer/
│ ├── Dockerfile
│ ├── dev.js
│ ├── devcontainer.json
│ └── post-create.sh
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.yaml
│ │ ├── config.yml
│ │ └── feature.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── apply_comments.yaml
│ ├── close-issues-on-release.yml
│ ├── close_blank_issues.yaml
│ ├── codeql.yml
│ ├── component-tests.yml
│ ├── docker-build.yml
│ ├── i18n-integration.yml
│ ├── integration-test.yml
│ ├── lint-openapi.yml
│ ├── notify-abs-windows.yml
│ └── unit-tests.yml
├── .gitignore
├── .prettierrc
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── Dockerfile
├── LICENSE
├── build/
│ ├── debian/
│ │ ├── DEBIAN/
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ ├── preinst
│ │ │ └── prerm
│ │ ├── etc/
│ │ │ └── default/
│ │ │ └── .gitkeep
│ │ ├── lib/
│ │ │ └── systemd/
│ │ │ └── system/
│ │ │ └── audiobookshelf.service
│ │ └── usr/
│ │ ├── lib/
│ │ │ └── .gitkeep
│ │ └── share/
│ │ └── .gitkeep
│ └── linuxpackager
├── client/
│ ├── assets/
│ │ ├── absicons.css
│ │ ├── app.css
│ │ ├── defaultStyles.css
│ │ ├── draggable.css
│ │ ├── ebooks/
│ │ │ ├── basic.js
│ │ │ ├── htmlParser.js
│ │ │ └── mobi.js
│ │ ├── fonts.css
│ │ ├── tailwind.css
│ │ ├── transitions.css
│ │ └── trix.css
│ ├── components/
│ │ ├── app/
│ │ │ ├── Appbar.vue
│ │ │ ├── BookShelfCategorized.vue
│ │ │ ├── BookShelfRow.vue
│ │ │ ├── BookShelfToolbar.vue
│ │ │ ├── ConfigSideNav.vue
│ │ │ ├── LazyBookshelf.vue
│ │ │ ├── MediaPlayerContainer.vue
│ │ │ ├── SettingsContent.vue
│ │ │ └── SideRail.vue
│ │ ├── cards/
│ │ │ ├── AuthorCard.vue
│ │ │ ├── AuthorSearchCard.vue
│ │ │ ├── BookMatchCard.vue
│ │ │ ├── EpisodeSearchCard.vue
│ │ │ ├── GenreSearchCard.vue
│ │ │ ├── GroupCard.vue
│ │ │ ├── ItemSearchCard.vue
│ │ │ ├── ItemTaskRunningCard.vue
│ │ │ ├── ItemUploadCard.vue
│ │ │ ├── LazyBookCard.vue
│ │ │ ├── LazyCollectionCard.vue
│ │ │ ├── LazyPlaylistCard.vue
│ │ │ ├── LazySeriesCard.vue
│ │ │ ├── NarratorCard.vue
│ │ │ ├── NarratorSearchCard.vue
│ │ │ ├── NotificationCard.vue
│ │ │ ├── PodcastFeedSummaryCard.vue
│ │ │ ├── SeriesSearchCard.vue
│ │ │ └── TagSearchCard.vue
│ │ ├── content/
│ │ │ └── LibraryItemDetails.vue
│ │ ├── controls/
│ │ │ ├── FilterSelect.vue
│ │ │ ├── GlobalSearch.vue
│ │ │ ├── LibraryFilterSelect.vue
│ │ │ ├── LibrarySortSelect.vue
│ │ │ ├── PlaybackSpeedControl.vue
│ │ │ ├── SortSelect.vue
│ │ │ └── VolumeControl.vue
│ │ ├── covers/
│ │ │ ├── AuthorImage.vue
│ │ │ ├── BookCover.vue
│ │ │ ├── CollectionCover.vue
│ │ │ ├── GroupCover.vue
│ │ │ ├── PlaylistCover.vue
│ │ │ └── PreviewCover.vue
│ │ ├── modals/
│ │ │ ├── AccountModal.vue
│ │ │ ├── AddCustomMetadataProviderModal.vue
│ │ │ ├── ApiKeyCreatedModal.vue
│ │ │ ├── ApiKeyModal.vue
│ │ │ ├── AudioFileDataModal.vue
│ │ │ ├── BackupScheduleModal.vue
│ │ │ ├── BatchQuickMatchModel.vue
│ │ │ ├── BookmarksModal.vue
│ │ │ ├── ChaptersModal.vue
│ │ │ ├── Dialog.vue
│ │ │ ├── EditSeriesInputInnerModal.vue
│ │ │ ├── ListeningSessionModal.vue
│ │ │ ├── Modal.vue
│ │ │ ├── PlayerSettingsModal.vue
│ │ │ ├── RawCoverPreviewModal.vue
│ │ │ ├── ShareModal.vue
│ │ │ ├── SleepTimerModal.vue
│ │ │ ├── UploadImageModal.vue
│ │ │ ├── authors/
│ │ │ │ └── EditModal.vue
│ │ │ ├── bookmarks/
│ │ │ │ └── BookmarkItem.vue
│ │ │ ├── changelog/
│ │ │ │ └── ViewModal.vue
│ │ │ ├── collections/
│ │ │ │ ├── AddCreateModal.vue
│ │ │ │ ├── CollectionItem.vue
│ │ │ │ └── EditModal.vue
│ │ │ ├── emails/
│ │ │ │ ├── EReaderDeviceModal.vue
│ │ │ │ └── UserEReaderDeviceModal.vue
│ │ │ ├── item/
│ │ │ │ ├── EditModal.vue
│ │ │ │ └── tabs/
│ │ │ │ ├── Chapters.vue
│ │ │ │ ├── Cover.vue
│ │ │ │ ├── Details.vue
│ │ │ │ ├── Episodes.vue
│ │ │ │ ├── Files.vue
│ │ │ │ ├── Match.vue
│ │ │ │ ├── Schedule.vue
│ │ │ │ └── Tools.vue
│ │ │ ├── libraries/
│ │ │ │ ├── EditLibrary.vue
│ │ │ │ ├── EditModal.vue
│ │ │ │ ├── LazyFolderChooser.vue
│ │ │ │ ├── LibraryScannerSettings.vue
│ │ │ │ ├── LibrarySettings.vue
│ │ │ │ ├── LibraryTools.vue
│ │ │ │ └── ScheduleScan.vue
│ │ │ ├── notification/
│ │ │ │ └── NotificationEditModal.vue
│ │ │ ├── player/
│ │ │ │ ├── QueueItemRow.vue
│ │ │ │ └── QueueItemsModal.vue
│ │ │ ├── playlists/
│ │ │ │ ├── AddCreateModal.vue
│ │ │ │ ├── EditModal.vue
│ │ │ │ └── UserPlaylistItem.vue
│ │ │ ├── podcast/
│ │ │ │ ├── EditEpisode.vue
│ │ │ │ ├── EpisodeFeed.vue
│ │ │ │ ├── NewModal.vue
│ │ │ │ ├── OpmlFeedsModal.vue
│ │ │ │ ├── RemoveEpisode.vue
│ │ │ │ ├── ViewEpisode.vue
│ │ │ │ └── tabs/
│ │ │ │ ├── EpisodeDetails.vue
│ │ │ │ └── EpisodeMatch.vue
│ │ │ └── rssfeed/
│ │ │ ├── OpenCloseModal.vue
│ │ │ └── ViewFeedModal.vue
│ │ ├── player/
│ │ │ ├── PlayerPlaybackControls.vue
│ │ │ ├── PlayerTrackBar.vue
│ │ │ └── PlayerUi.vue
│ │ ├── prompt/
│ │ │ ├── Confirm.vue
│ │ │ └── Dialog.vue
│ │ ├── readers/
│ │ │ ├── ComicReader.vue
│ │ │ ├── EpubReader.vue
│ │ │ ├── MobiReader.vue
│ │ │ ├── PdfReader.vue
│ │ │ └── Reader.vue
│ │ ├── stats/
│ │ │ ├── DailyListeningChart.vue
│ │ │ ├── Heatmap.vue
│ │ │ ├── PreviewIcons.vue
│ │ │ ├── YearInReview.vue
│ │ │ ├── YearInReviewBanner.vue
│ │ │ ├── YearInReviewServer.vue
│ │ │ └── YearInReviewShort.vue
│ │ ├── tables/
│ │ │ ├── ApiKeysTable.vue
│ │ │ ├── AudioTracksTableRow.vue
│ │ │ ├── BackupsTable.vue
│ │ │ ├── ChaptersTable.vue
│ │ │ ├── CollectionBooksTable.vue
│ │ │ ├── CustomMetadataProviderTable.vue
│ │ │ ├── EbookFilesTable.vue
│ │ │ ├── EbookFilesTableRow.vue
│ │ │ ├── LibraryFilesTable.vue
│ │ │ ├── LibraryFilesTableRow.vue
│ │ │ ├── PlaylistItemsTable.vue
│ │ │ ├── TracksTable.vue
│ │ │ ├── UploadedFilesTable.vue
│ │ │ ├── UsersTable.vue
│ │ │ ├── collection/
│ │ │ │ └── BookTableRow.vue
│ │ │ ├── library/
│ │ │ │ ├── LibrariesTable.vue
│ │ │ │ └── LibraryItem.vue
│ │ │ ├── playlist/
│ │ │ │ └── ItemTableRow.vue
│ │ │ └── podcast/
│ │ │ ├── DownloadQueueTable.vue
│ │ │ ├── LazyEpisodeRow.vue
│ │ │ └── LazyEpisodesTable.vue
│ │ ├── ui/
│ │ │ ├── Btn.vue
│ │ │ ├── Checkbox.vue
│ │ │ ├── ContextMenuDropdown.vue
│ │ │ ├── Dropdown.vue
│ │ │ ├── EditableText.vue
│ │ │ ├── FileInput.vue
│ │ │ ├── IconBtn.vue
│ │ │ ├── InputDropdown.vue
│ │ │ ├── LibrariesDropdown.vue
│ │ │ ├── LibraryIcon.vue
│ │ │ ├── LoadingIndicator.vue
│ │ │ ├── MediaIconPicker.vue
│ │ │ ├── MultiSelect.vue
│ │ │ ├── MultiSelectDropdown.vue
│ │ │ ├── MultiSelectQueryInput.vue
│ │ │ ├── QueryInput.vue
│ │ │ ├── RangeInput.vue
│ │ │ ├── ReadIconBtn.vue
│ │ │ ├── RichTextEditor.vue
│ │ │ ├── SelectInput.vue
│ │ │ ├── TextInput.vue
│ │ │ ├── TextInputWithLabel.vue
│ │ │ ├── TextareaInput.vue
│ │ │ ├── TextareaWithLabel.vue
│ │ │ ├── TimePicker.vue
│ │ │ ├── ToggleBtns.vue
│ │ │ ├── ToggleSwitch.vue
│ │ │ ├── Tooltip.vue
│ │ │ └── VueTrix.vue
│ │ └── widgets/
│ │ ├── AbridgedIndicator.vue
│ │ ├── Alert.vue
│ │ ├── AlreadyInLibraryIndicator.vue
│ │ ├── BookDetailsEdit.vue
│ │ ├── CoverSizeWidget.vue
│ │ ├── CronExpressionBuilder.vue
│ │ ├── EncoderOptionsCard.vue
│ │ ├── ExplicitIndicator.vue
│ │ ├── ItemSlider.vue
│ │ ├── LoadingSpinner.vue
│ │ ├── MoreMenu.vue
│ │ ├── NotificationWidget.vue
│ │ ├── OnlineIndicator.vue
│ │ ├── PodcastDetailsEdit.vue
│ │ ├── PodcastTypeIndicator.vue
│ │ ├── RssFeedMetadataBuilder.vue
│ │ └── SeriesInputWidget.vue
│ ├── cypress/
│ │ ├── support/
│ │ │ ├── commands.js
│ │ │ ├── component-index.html
│ │ │ └── component.js
│ │ └── tests/
│ │ ├── components/
│ │ │ └── cards/
│ │ │ ├── AuthorCard.cy.js
│ │ │ ├── ItemSlider.cy.js
│ │ │ ├── LazyBookCard.cy.js
│ │ │ ├── LazySeriesCard.cy.js
│ │ │ └── NarratorCard.cy.js
│ │ └── utils/
│ │ └── ElapsedPrettyExtended.cy.js
│ ├── cypress.config.js
│ ├── layouts/
│ │ ├── blank.vue
│ │ ├── default.vue
│ │ └── error.vue
│ ├── middleware/
│ │ └── authenticated.js
│ ├── mixins/
│ │ ├── bookshelfCardsHelpers.js
│ │ ├── menuKeyboardNavigation.js
│ │ └── uploadHelpers.js
│ ├── nuxt.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── account.vue
│ │ ├── audiobook/
│ │ │ └── _id/
│ │ │ ├── chapters.vue
│ │ │ ├── edit.vue
│ │ │ └── manage.vue
│ │ ├── author/
│ │ │ └── _id.vue
│ │ ├── batch/
│ │ │ └── index.vue
│ │ ├── collection/
│ │ │ └── _id.vue
│ │ ├── config/
│ │ │ ├── api-keys/
│ │ │ │ └── index.vue
│ │ │ ├── authentication.vue
│ │ │ ├── backups.vue
│ │ │ ├── email.vue
│ │ │ ├── index.vue
│ │ │ ├── item-metadata-utils/
│ │ │ │ ├── custom-metadata-providers.vue
│ │ │ │ ├── genres.vue
│ │ │ │ ├── index.vue
│ │ │ │ └── tags.vue
│ │ │ ├── libraries.vue
│ │ │ ├── log.vue
│ │ │ ├── notifications.vue
│ │ │ ├── rss-feeds.vue
│ │ │ ├── sessions.vue
│ │ │ ├── stats.vue
│ │ │ └── users/
│ │ │ ├── _id/
│ │ │ │ ├── index.vue
│ │ │ │ └── sessions.vue
│ │ │ └── index.vue
│ │ ├── config.vue
│ │ ├── index.vue
│ │ ├── item/
│ │ │ └── _id/
│ │ │ └── index.vue
│ │ ├── library/
│ │ │ └── _library/
│ │ │ ├── bookshelf/
│ │ │ │ └── _id.vue
│ │ │ ├── index.vue
│ │ │ ├── narrators.vue
│ │ │ ├── podcast/
│ │ │ │ ├── download-queue.vue
│ │ │ │ ├── latest.vue
│ │ │ │ └── search.vue
│ │ │ ├── search.vue
│ │ │ ├── series/
│ │ │ │ └── _id.vue
│ │ │ └── stats.vue
│ │ ├── login.vue
│ │ ├── oops.vue
│ │ ├── playlist/
│ │ │ └── _id.vue
│ │ ├── share/
│ │ │ └── _slug.vue
│ │ └── upload/
│ │ └── index.vue
│ ├── players/
│ │ ├── AudioTrack.js
│ │ ├── CastPlayer.js
│ │ ├── LocalAudioPlayer.js
│ │ ├── PlayerHandler.js
│ │ └── castUtils.js
│ ├── plugins/
│ │ ├── axios.js
│ │ ├── chromecast.js
│ │ ├── constants.js
│ │ ├── i18n.js
│ │ ├── init.client.js
│ │ ├── toast.js
│ │ ├── utils.js
│ │ └── version.js
│ ├── postcss.config.js
│ ├── static/
│ │ ├── fonts/
│ │ │ ├── Source_Sans_Pro/
│ │ │ │ └── OFL.txt
│ │ │ └── Ubuntu_Mono/
│ │ │ └── UFL.txt
│ │ ├── libarchive/
│ │ │ ├── wasm-gen/
│ │ │ │ ├── libarchive.js
│ │ │ │ └── libarchive.wasm
│ │ │ └── worker-bundle.js
│ │ ├── libs/
│ │ │ └── marked/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ └── robots.txt
│ ├── store/
│ │ ├── globals.js
│ │ ├── index.js
│ │ ├── libraries.js
│ │ ├── scanners.js
│ │ ├── tasks.js
│ │ ├── user.js
│ │ └── users.js
│ └── strings/
│ ├── ar.json
│ ├── be.json
│ ├── bg.json
│ ├── bn.json
│ ├── ca.json
│ ├── cs.json
│ ├── da.json
│ ├── de.json
│ ├── el.json
│ ├── en-us.json
│ ├── es.json
│ ├── et.json
│ ├── eu.json
│ ├── fa.json
│ ├── fi.json
│ ├── fr.json
│ ├── gu.json
│ ├── he.json
│ ├── hi.json
│ ├── hr.json
│ ├── hu.json
│ ├── is.json
│ ├── it.json
│ ├── ja.json
│ ├── ko.json
│ ├── lt.json
│ ├── nl.json
│ ├── no.json
│ ├── pl.json
│ ├── pt-br.json
│ ├── ro.json
│ ├── ru.json
│ ├── sk.json
│ ├── sl.json
│ ├── sv.json
│ ├── tr.json
│ ├── uk.json
│ ├── vi-vn.json
│ ├── zh-cn.json
│ └── zh-tw.json
├── custom-metadata-provider-specification.yaml
├── docker-compose.yml
├── docker-template.xml
├── docs/
│ ├── README.md
│ ├── controllers/
│ │ ├── AuthorController.yaml
│ │ ├── EmailController.yaml
│ │ ├── LibraryController.yaml
│ │ ├── NotificationController.yaml
│ │ ├── PodcastController.yaml
│ │ └── SeriesController.yaml
│ ├── objects/
│ │ ├── Folder.yaml
│ │ ├── Library.yaml
│ │ ├── LibraryItem.yaml
│ │ ├── Notification.yaml
│ │ ├── entities/
│ │ │ ├── Author.yaml
│ │ │ ├── PodcastEpisode.yaml
│ │ │ └── Series.yaml
│ │ ├── files/
│ │ │ ├── AudioFile.yaml
│ │ │ ├── AudioTrack.yaml
│ │ │ └── EBookFile.yaml
│ │ ├── mediaTypes/
│ │ │ ├── Book.yaml
│ │ │ ├── Podcast.yaml
│ │ │ └── media.yaml
│ │ ├── metadata/
│ │ │ ├── AudioMetaTags.yaml
│ │ │ ├── BookMetadata.yaml
│ │ │ ├── FileMetadata.yaml
│ │ │ └── PodcastMetadata.yaml
│ │ └── settings/
│ │ └── EmailSettings.yaml
│ ├── openapi.json
│ ├── root.yaml
│ └── schemas.yaml
├── index.js
├── package.json
├── prod.js
├── readme.md
├── server/
│ ├── Auth.js
│ ├── Database.js
│ ├── Logger.js
│ ├── Server.js
│ ├── SocketAuthority.js
│ ├── Watcher.js
│ ├── auth/
│ │ ├── LocalAuthStrategy.js
│ │ ├── OidcAuthStrategy.js
│ │ └── TokenManager.js
│ ├── controllers/
│ │ ├── ApiKeyController.js
│ │ ├── AuthorController.js
│ │ ├── BackupController.js
│ │ ├── CacheController.js
│ │ ├── CollectionController.js
│ │ ├── CustomMetadataProviderController.js
│ │ ├── EmailController.js
│ │ ├── FileSystemController.js
│ │ ├── LibraryController.js
│ │ ├── LibraryItemController.js
│ │ ├── MeController.js
│ │ ├── MiscController.js
│ │ ├── NotificationController.js
│ │ ├── PlaylistController.js
│ │ ├── PodcastController.js
│ │ ├── RSSFeedController.js
│ │ ├── SearchController.js
│ │ ├── SeriesController.js
│ │ ├── SessionController.js
│ │ ├── ShareController.js
│ │ ├── StatsController.js
│ │ ├── ToolsController.js
│ │ └── UserController.js
│ ├── finders/
│ │ ├── AuthorFinder.js
│ │ ├── BookFinder.js
│ │ └── PodcastFinder.js
│ ├── libs/
│ │ ├── archiver/
│ │ │ ├── LICENSE
│ │ │ ├── archiverUtils/
│ │ │ │ ├── balancedMatch/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── braceExpansion/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── file.js
│ │ │ │ ├── fsRealpath/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── old.js
│ │ │ │ ├── glob/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── common.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── sync.js
│ │ │ │ ├── index.js
│ │ │ │ ├── inflight/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lazystream/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── readable-stream/
│ │ │ │ │ ├── lib/
│ │ │ │ │ │ ├── _stream_duplex.js
│ │ │ │ │ │ ├── _stream_passthrough.js
│ │ │ │ │ │ ├── _stream_readable.js
│ │ │ │ │ │ ├── _stream_transform.js
│ │ │ │ │ │ ├── _stream_writable.js
│ │ │ │ │ │ └── internal/
│ │ │ │ │ │ └── streams/
│ │ │ │ │ │ ├── BufferList.js
│ │ │ │ │ │ ├── destroy.js
│ │ │ │ │ │ ├── stream-browser.js
│ │ │ │ │ │ └── stream.js
│ │ │ │ │ ├── passthrough.js
│ │ │ │ │ └── readable.js
│ │ │ │ ├── lodash.difference/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lodash.flatten/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lodash.isplainobject/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── lodash.union/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── minimatch/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── readableStream/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── _stream_duplex.js
│ │ │ │ │ ├── _stream_passthrough.js
│ │ │ │ │ ├── _stream_readable.js
│ │ │ │ │ ├── _stream_transform.js
│ │ │ │ │ ├── _stream_writable.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── internal/
│ │ │ │ │ │ ├── streams/
│ │ │ │ │ │ │ ├── add-abort-signal.js
│ │ │ │ │ │ │ ├── buffer_list.js
│ │ │ │ │ │ │ ├── compose.js
│ │ │ │ │ │ │ ├── destroy.js
│ │ │ │ │ │ │ ├── duplex.js
│ │ │ │ │ │ │ ├── duplexify.js
│ │ │ │ │ │ │ ├── end-of-stream.js
│ │ │ │ │ │ │ ├── from.js
│ │ │ │ │ │ │ ├── lazy_transform.js
│ │ │ │ │ │ │ ├── legacy.js
│ │ │ │ │ │ │ ├── operators.js
│ │ │ │ │ │ │ ├── passthrough.js
│ │ │ │ │ │ │ ├── pipeline.js
│ │ │ │ │ │ │ ├── readable.js
│ │ │ │ │ │ │ ├── state.js
│ │ │ │ │ │ │ ├── transform.js
│ │ │ │ │ │ │ ├── utils.js
│ │ │ │ │ │ │ └── writable.js
│ │ │ │ │ │ └── validators.js
│ │ │ │ │ ├── ours/
│ │ │ │ │ │ ├── browser.js
│ │ │ │ │ │ ├── errors.js
│ │ │ │ │ │ ├── primordials.js
│ │ │ │ │ │ └── util.js
│ │ │ │ │ ├── stream/
│ │ │ │ │ │ └── promises.js
│ │ │ │ │ └── stream.js
│ │ │ │ ├── safeBuffer/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ ├── stringDecoder/
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ └── index.js
│ │ │ │ └── wrappy/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── buffer-crc32/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── compress-commons/
│ │ │ │ ├── LICENSE
│ │ │ │ ├── archivers/
│ │ │ │ │ ├── archive-entry.js
│ │ │ │ │ ├── archive-output-stream.js
│ │ │ │ │ └── zip/
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── general-purpose-bit.js
│ │ │ │ │ ├── unix-stat.js
│ │ │ │ │ ├── util.js
│ │ │ │ │ ├── zip-archive-entry.js
│ │ │ │ │ └── zip-archive-output-stream.js
│ │ │ │ ├── index.js
│ │ │ │ └── util/
│ │ │ │ └── index.js
│ │ │ ├── crc32/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── crc32-stream/
│ │ │ │ ├── LICENSE
│ │ │ │ ├── crc32-stream.js
│ │ │ │ ├── deflate-crc32-stream.js
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── lib/
│ │ │ │ ├── core.js
│ │ │ │ ├── error.js
│ │ │ │ └── plugins/
│ │ │ │ ├── json.js
│ │ │ │ └── zip.js
│ │ │ ├── normalize-path/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── readdir-glob/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ └── zip-stream/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── async/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── bcryptjs/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── busboy/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── types/
│ │ │ │ ├── multipart.js
│ │ │ │ └── urlencoded.js
│ │ │ └── utils.js
│ │ ├── commandLineArgs/
│ │ │ └── index.js
│ │ ├── dateAndTime/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── expressFileupload/
│ │ │ ├── LICENSE
│ │ │ ├── fileFactory.js
│ │ │ ├── index.js
│ │ │ ├── isEligibleRequest.js
│ │ │ ├── memHandler.js
│ │ │ ├── processMultipart.js
│ │ │ ├── processNested.js
│ │ │ ├── tempFileHandler.js
│ │ │ ├── uploadtimer.js
│ │ │ └── utilities.js
│ │ ├── fastSort/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── fluentFfmpeg/
│ │ │ ├── LICENSE
│ │ │ ├── capabilities.js
│ │ │ ├── ffprobe.js
│ │ │ ├── index.d.ts
│ │ │ ├── index.js
│ │ │ ├── options/
│ │ │ │ ├── audio.js
│ │ │ │ ├── custom.js
│ │ │ │ ├── inputs.js
│ │ │ │ ├── misc.js
│ │ │ │ ├── output.js
│ │ │ │ ├── video.js
│ │ │ │ └── videosize.js
│ │ │ ├── presets/
│ │ │ │ ├── divx.js
│ │ │ │ ├── flashvideo.js
│ │ │ │ └── podcast.js
│ │ │ ├── processor.js
│ │ │ ├── recipes.js
│ │ │ └── utils.js
│ │ ├── fsExtra/
│ │ │ ├── LICENSE
│ │ │ ├── copy/
│ │ │ │ ├── copy-sync.js
│ │ │ │ ├── copy.js
│ │ │ │ └── index.js
│ │ │ ├── empty/
│ │ │ │ └── index.js
│ │ │ ├── ensure/
│ │ │ │ ├── file.js
│ │ │ │ ├── index.js
│ │ │ │ ├── link.js
│ │ │ │ ├── symlink-paths.js
│ │ │ │ ├── symlink-type.js
│ │ │ │ └── symlink.js
│ │ │ ├── fs/
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── mkdirs/
│ │ │ │ ├── index.js
│ │ │ │ ├── make-dir.js
│ │ │ │ └── utils.js
│ │ │ ├── move/
│ │ │ │ ├── index.js
│ │ │ │ ├── move-sync.js
│ │ │ │ └── move.js
│ │ │ ├── path-exists/
│ │ │ │ └── index.js
│ │ │ ├── remove/
│ │ │ │ ├── index.js
│ │ │ │ └── rimraf.js
│ │ │ └── util/
│ │ │ ├── stat.js
│ │ │ └── utimes.js
│ │ ├── fusejs/
│ │ │ └── index.js
│ │ ├── imageType/
│ │ │ ├── LICENSE
│ │ │ ├── fileType.js
│ │ │ └── index.js
│ │ ├── isexe/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── mode.js
│ │ │ └── windows.js
│ │ ├── jsonwebtoken/
│ │ │ ├── LICENSE
│ │ │ ├── decode.js
│ │ │ ├── index.js
│ │ │ ├── lib/
│ │ │ │ ├── JsonWebTokenError.js
│ │ │ │ ├── NotBeforeError.js
│ │ │ │ ├── TokenExpiredError.js
│ │ │ │ └── timespan.js
│ │ │ ├── sign.js
│ │ │ └── verify.js
│ │ ├── jwa/
│ │ │ ├── LICENSE
│ │ │ ├── buffer-equal-constant-time/
│ │ │ │ ├── LICENSE
│ │ │ │ └── index.js
│ │ │ ├── ecdsa-sig-formatter/
│ │ │ │ ├── LICENSE
│ │ │ │ ├── index.js
│ │ │ │ └── param-bytes-for-alg.js
│ │ │ └── index.js
│ │ ├── jws/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ └── lib/
│ │ │ ├── data-stream.js
│ │ │ ├── sign-stream.js
│ │ │ ├── tostring.js
│ │ │ └── verify-stream.js
│ │ ├── libarchive/
│ │ │ ├── LICENSE
│ │ │ ├── archive.js
│ │ │ ├── libarchiveWorker.js
│ │ │ ├── wasm-libarchive.js
│ │ │ └── wasm-module.js
│ │ ├── lodash.once/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── memorystore/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── nodeCron/
│ │ │ ├── LICENSE
│ │ │ ├── background-scheduled-task/
│ │ │ │ ├── daemon.js
│ │ │ │ └── index.js
│ │ │ ├── convert-expression/
│ │ │ │ ├── asterisk-to-range-conversion.js
│ │ │ │ ├── index.js
│ │ │ │ ├── month-names-conversion.js
│ │ │ │ ├── range-conversion.js
│ │ │ │ ├── step-values-conversion.js
│ │ │ │ └── week-day-names-conversion.js
│ │ │ ├── index.js
│ │ │ ├── pattern-validation.js
│ │ │ ├── scheduled-task.js
│ │ │ ├── scheduler.js
│ │ │ ├── storage.js
│ │ │ ├── task.js
│ │ │ └── time-matcher.js
│ │ ├── nodeFfprobe/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── nodeStreamZip/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── passportLocal/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ └── strategy.js
│ │ ├── readChunk/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── pify.js
│ │ │ └── withOpenFile.js
│ │ ├── recursiveReaddirAsync/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── requestIp/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ └── isJs.js
│ │ ├── rss/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── sanitizeHtml/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── streamsearch/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── uaParser/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── umzug/
│ │ │ ├── LICENSE
│ │ │ ├── index.js
│ │ │ ├── storage/
│ │ │ │ ├── contract.js
│ │ │ │ ├── index.js
│ │ │ │ ├── json.js
│ │ │ │ ├── memory.js
│ │ │ │ ├── mongodb.js
│ │ │ │ └── sequelize.js
│ │ │ ├── templates.js
│ │ │ ├── types.js
│ │ │ └── umzug.js
│ │ ├── universalify/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ ├── watcher/
│ │ │ ├── LICENSE
│ │ │ ├── aborter/
│ │ │ │ ├── controller.js
│ │ │ │ └── signal.js
│ │ │ ├── are-shallow-equal.js
│ │ │ ├── atomically/
│ │ │ │ ├── consts.js
│ │ │ │ ├── index.js
│ │ │ │ └── utils/
│ │ │ │ ├── attemptify.js
│ │ │ │ ├── fs.js
│ │ │ │ ├── fs_handlers.js
│ │ │ │ ├── lang.js
│ │ │ │ ├── retryify.js
│ │ │ │ ├── retryify_queue.js
│ │ │ │ ├── scheduler.js
│ │ │ │ └── temp.js
│ │ │ ├── constants.js
│ │ │ ├── debounce.js
│ │ │ ├── enums.js
│ │ │ ├── is-primitive.js
│ │ │ ├── promise-concurrency-limiter.js
│ │ │ ├── ripstat/
│ │ │ │ ├── consts.js
│ │ │ │ ├── index.js
│ │ │ │ └── stats.js
│ │ │ ├── string-indexes.js
│ │ │ ├── tiny-readdir.js
│ │ │ ├── types.js
│ │ │ ├── utils.js
│ │ │ ├── watcher.js
│ │ │ ├── watcher_handler.js
│ │ │ ├── watcher_locker.js
│ │ │ ├── watcher_locks_resolver.js
│ │ │ ├── watcher_poller.js
│ │ │ └── watcher_stats.js
│ │ ├── which/
│ │ │ ├── LICENSE
│ │ │ └── index.js
│ │ └── xml/
│ │ ├── LICENSE
│ │ ├── escapeForXML.js
│ │ └── index.js
│ ├── managers/
│ │ ├── AbMergeManager.js
│ │ ├── ApiCacheManager.js
│ │ ├── AudioMetadataManager.js
│ │ ├── BackupManager.js
│ │ ├── BinaryManager.js
│ │ ├── CacheManager.js
│ │ ├── CoverManager.js
│ │ ├── CoverSearchManager.js
│ │ ├── CronManager.js
│ │ ├── EmailManager.js
│ │ ├── LogManager.js
│ │ ├── MigrationManager.js
│ │ ├── NotificationManager.js
│ │ ├── PlaybackSessionManager.js
│ │ ├── PodcastManager.js
│ │ ├── RssFeedManager.js
│ │ ├── ShareManager.js
│ │ └── TaskManager.js
│ ├── migrations/
│ │ ├── changelog.md
│ │ ├── readme.md
│ │ ├── v2.15.0-series-column-unique.js
│ │ ├── v2.15.1-reindex-nocase.js
│ │ ├── v2.15.2-index-creation.js
│ │ ├── v2.17.0-uuid-replacement.js
│ │ ├── v2.17.3-fk-constraints.js
│ │ ├── v2.17.4-use-subfolder-for-oidc-redirect-uris.js
│ │ ├── v2.17.5-remove-host-from-feed-urls.js
│ │ ├── v2.17.6-share-add-isdownloadable.js
│ │ ├── v2.17.7-add-indices.js
│ │ ├── v2.19.1-copy-title-to-library-items.js
│ │ ├── v2.19.4-improve-podcast-queries.js
│ │ ├── v2.20.0-improve-author-sort-queries.js
│ │ ├── v2.26.0-create-auth-tables.js
│ │ └── v2.33.0-add-discover-query-indexes.js
│ ├── models/
│ │ ├── ApiKey.js
│ │ ├── Author.js
│ │ ├── Book.js
│ │ ├── BookAuthor.js
│ │ ├── BookSeries.js
│ │ ├── Collection.js
│ │ ├── CollectionBook.js
│ │ ├── CustomMetadataProvider.js
│ │ ├── Device.js
│ │ ├── Feed.js
│ │ ├── FeedEpisode.js
│ │ ├── Library.js
│ │ ├── LibraryFolder.js
│ │ ├── LibraryItem.js
│ │ ├── MediaItemShare.js
│ │ ├── MediaProgress.js
│ │ ├── PlaybackSession.js
│ │ ├── Playlist.js
│ │ ├── PlaylistMediaItem.js
│ │ ├── Podcast.js
│ │ ├── PodcastEpisode.js
│ │ ├── Series.js
│ │ ├── Session.js
│ │ ├── Setting.js
│ │ └── User.js
│ ├── objects/
│ │ ├── Backup.js
│ │ ├── DailyLog.js
│ │ ├── DeviceInfo.js
│ │ ├── Notification.js
│ │ ├── PlaybackSession.js
│ │ ├── PodcastEpisodeDownload.js
│ │ ├── Stream.js
│ │ ├── Task.js
│ │ ├── TrackProgressMonitor.js
│ │ ├── files/
│ │ │ ├── AudioFile.js
│ │ │ ├── AudioTrack.js
│ │ │ ├── EBookFile.js
│ │ │ └── LibraryFile.js
│ │ ├── metadata/
│ │ │ ├── AudioMetaTags.js
│ │ │ └── FileMetadata.js
│ │ └── settings/
│ │ ├── EmailSettings.js
│ │ ├── NotificationSettings.js
│ │ └── ServerSettings.js
│ ├── providers/
│ │ ├── Audible.js
│ │ ├── AudiobookCovers.js
│ │ ├── Audnexus.js
│ │ ├── CustomProviderAdapter.js
│ │ ├── FantLab.js
│ │ ├── GoogleBooks.js
│ │ ├── MusicBrainz.js
│ │ ├── OpenLibrary.js
│ │ └── iTunes.js
│ ├── routers/
│ │ ├── ApiRouter.js
│ │ ├── HlsRouter.js
│ │ └── PublicRouter.js
│ ├── scanner/
│ │ ├── AbsMetadataFileScanner.js
│ │ ├── AudioFileScanner.js
│ │ ├── BookScanner.js
│ │ ├── LibraryItemScanData.js
│ │ ├── LibraryItemScanner.js
│ │ ├── LibraryScan.js
│ │ ├── LibraryScanner.js
│ │ ├── MediaProbeData.js
│ │ ├── NfoFileScanner.js
│ │ ├── OpfFileScanner.js
│ │ ├── PodcastScanner.js
│ │ ├── ScanLogger.js
│ │ └── Scanner.js
│ └── utils/
│ ├── areEquivalent.js
│ ├── comicBookExtractors.js
│ ├── constants.js
│ ├── ffmpegHelpers.js
│ ├── fileUtils.js
│ ├── generators/
│ │ ├── abmetadataGenerator.js
│ │ ├── hlsPlaylistGenerator.js
│ │ └── opmlGenerator.js
│ ├── globals.js
│ ├── htmlEntities.js
│ ├── htmlSanitizer.js
│ ├── index.js
│ ├── libraryHelpers.js
│ ├── longTimeout.js
│ ├── migrations/
│ │ ├── absMetadataMigration.js
│ │ ├── dbMigration.js
│ │ └── oldDbFiles.js
│ ├── notifications.js
│ ├── parsers/
│ │ ├── parseComicInfoMetadata.js
│ │ ├── parseComicMetadata.js
│ │ ├── parseEbookMetadata.js
│ │ ├── parseEpubMetadata.js
│ │ ├── parseFullName.js
│ │ ├── parseNameString.js
│ │ ├── parseNfoMetadata.js
│ │ ├── parseOPML.js
│ │ ├── parseOpfMetadata.js
│ │ ├── parseOverdriveMediaMarkers.js
│ │ └── parseSeriesString.js
│ ├── podcastUtils.js
│ ├── prober.js
│ ├── profiler.js
│ ├── queries/
│ │ ├── adminStats.js
│ │ ├── authorFilters.js
│ │ ├── libraryFilters.js
│ │ ├── libraryItemFilters.js
│ │ ├── libraryItemsBookFilters.js
│ │ ├── libraryItemsPodcastFilters.js
│ │ ├── seriesFilters.js
│ │ └── userStats.js
│ ├── rateLimiterFactory.js
│ ├── scandir.js
│ ├── stringifySequelizeQuery.js
│ └── zipHelpers.js
└── test/
└── server/
├── Logger.test.js
├── controllers/
│ ├── LibraryItemController.test.js
│ └── MeController.test.js
├── finders/
│ └── BookFinder.test.js
├── managers/
│ ├── ApiCacheManager.test.js
│ ├── BinaryManager.test.js
│ ├── MigrationManager.test.js
│ └── migrations/
│ ├── v1.0.0-migration.js
│ ├── v1.1.0-migration.js
│ ├── v1.10.0-migration.js
│ └── v1.2.0-migration.js
├── migrations/
│ ├── v0.0.1-migration_example.js
│ ├── v0.0.1-migration_example.test.js
│ ├── v2.15.0-series-column-unique.test.js
│ ├── v2.17.3-fk-constraints.test.js
│ ├── v2.17.4-use-subfolder-for-oidc-redirect-uris.test.js
│ ├── v2.17.5-remove-host-from-feed-urls.test.js
│ ├── v2.17.6-share-add-isdownloadable.test.js
│ ├── v2.19.1-copy-title-to-library-items.test.js
│ ├── v2.19.4-improve-podcast-queries.test.js
│ └── v2.20.0-improve-author-sort-queries.test.js
├── objects/
│ └── TrackProgressMonitor.test.js
├── providers/
│ └── Audible.test.js
└── utils/
├── ffmpegHelpers.test.js
├── fileUtils.test.js
├── parsers/
│ ├── parseNameString.test.js
│ ├── parseNfoMetadata.test.js
│ └── parseOpfMetadata.test.js
├── scandir.test.js
└── stringifySequeslizeQuery.test.js
Showing preview only (300K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3797 symbols across 366 files)
FILE: client/assets/ebooks/htmlParser.js
class HtmlParser (line 204) | class HtmlParser {
method constructor (line 208) | constructor(bookDoc) {
method getContent (line 214) | getContent(bookDoc) {
method getAnchoredDoc (line 234) | getAnchoredDoc() {
method getContentList (line 237) | getContentList() {
FILE: client/assets/ebooks/mobi.js
function ab2str (line 5) | function ab2str(buf) {
class Buffer (line 14) | class Buffer {
method constructor (line 19) | constructor(capacity) {
method write (line 26) | write(byte) {
method get (line 34) | get(idx) {
method size (line 46) | size() {
method shrink (line 53) | shrink() {
class Fragment (line 85) | class Fragment {
method constructor (line 89) | constructor(capacity) {
method write (line 95) | write(byte) {
method full (line 103) | full() {
method get (line 106) | get(idx) {
class MobiFile (line 148) | class MobiFile {
method constructor (line 156) | constructor(data) {
method parse (line 163) | parse() { }
method getUint8 (line 165) | getUint8() {
method getUint16 (line 171) | getUint16() {
method getUint32 (line 177) | getUint32() {
method getStr (line 183) | getStr(size) {
method skip (line 189) | skip(size) {
method setoffset (line 193) | setoffset(_of) {
method get_record_extrasize (line 197) | get_record_extrasize(data, flags) {
method buffer_get_varlen (line 218) | buffer_get_varlen(data, pos) {
method read_text (line 241) | read_text() {
method read_text_record (line 251) | read_text_record(i) {
method read_image (line 268) | read_image(idx) {
method load (line 276) | load() {
method load_pdbheader (line 282) | load_pdbheader() {
method load_reclist (line 301) | load_reclist() {
method load_record0 (line 312) | load_record0() {
method load_record0_header (line 317) | load_record0_header() {
method load_mobi_header (line 333) | load_mobi_header() {
method load_exth_header (line 384) | load_exth_header() {
method extractContent (line 388) | extractContent(s) {
method render (line 393) | render(isElectron = false) {
FILE: client/cypress/tests/components/cards/ItemSlider.cy.js
function createMountOptions (line 5) | function createMountOptions(shelftype) {
FILE: client/cypress/tests/components/cards/LazyBookCard.cy.js
function createMountOptions (line 7) | function createMountOptions() {
FILE: client/cypress/tests/utils/ElapsedPrettyExtended.cy.js
function DHMStoSeconds (line 8) | function DHMStoSeconds(days, hours, minutes, seconds) {
FILE: client/mixins/bookshelfCardsHelpers.js
method data (line 9) | data() {
method getComponentClass (line 18) | getComponentClass() {
method getComponentName (line 25) | getComponentName() {
method setCardSize (line 32) | async setCardSize() {
method mountEntityCard (line 77) | mountEntityCard(index) {
FILE: client/mixins/menuKeyboardNavigation.js
method data (line 24) | data() {
method menuNavigationHandler (line 30) | menuNavigationHandler(event) {
method recalcScroll (line 60) | recalcScroll() {
method isMenuItemSelected (line 79) | isMenuItemSelected(item) {
FILE: client/mixins/uploadHelpers.js
method data (line 4) | data() {
method checkFileType (line 13) | checkFileType(filename) {
method filterItemFiles (line 26) | filterItemFiles(files, mediaType) {
method itemFromTreeItems (line 44) | itemFromTreeItems(items, mediaType) {
method traverseForItem (line 58) | traverseForItem(folder, mediaType, depth = 1) {
method fileTreeToItems (line 72) | fileTreeToItems(filetree, mediaType) {
method getFilesDropped (line 97) | getFilesDropped(dataTransferItems) {
method cleanBook (line 151) | cleanBook(book, index) {
method cleanPodcast (line 181) | cleanPodcast(item, index) {
method cleanItem (line 199) | cleanItem(item, mediaType, index) {
method getItemsFromDataTransferItems (line 203) | async getItemsFromDataTransferItems(dataTransferItems, mediaType) {
method getItemsFromFilelist (line 227) | getItemsFromFilelist(filelist, mediaType) {
FILE: client/players/AudioTrack.js
class AudioTrack (line 1) | class AudioTrack {
method constructor (line 2) | constructor(track, sessionId, routerBasePath) {
method fullContentUrl (line 23) | get fullContentUrl() {
method relativeContentUrl (line 33) | get relativeContentUrl() {
FILE: client/players/CastPlayer.js
class CastPlayer (line 4) | class CastPlayer extends EventEmitter {
method constructor (line 5) | constructor(ctx) {
method currentTrack (line 33) | get currentTrack() {
method initialize (line 37) | initialize() {
method evtMediaInfoChanged (line 44) | evtMediaInfoChanged() {
method destroy (line 67) | destroy() {
method set (line 73) | async set(libraryItem, tracks, isHlsTranscode, startTime, playWhenRead...
method resetStream (line 94) | resetStream(startTime) {
method playPause (line 98) | playPause() {
method play (line 102) | play() {
method pause (line 106) | pause() {
method getCurrentTime (line 110) | getCurrentTime() {
method getDuration (line 115) | getDuration() {
method setPlaybackRate (line 121) | setPlaybackRate(playbackRate) {
method seek (line 125) | async seek(time, playWhenReady) {
method setVolume (line 141) | setVolume(volume) {
FILE: client/players/LocalAudioPlayer.js
class LocalAudioPlayer (line 4) | class LocalAudioPlayer extends EventEmitter {
method constructor (line 5) | constructor(ctx) {
method currentTrack (line 27) | get currentTrack() {
method initialize (line 31) | initialize() {
method evtPlay (line 59) | evtPlay() {
method evtPause (line 62) | evtPause() {
method evtProgress (line 65) | evtProgress() {
method evtEnded (line 69) | evtEnded() {
method evtError (line 81) | evtError(error) {
method evtLoadedMetadata (line 85) | evtLoadedMetadata(data) {
method evtTimeupdate (line 97) | evtTimeupdate() {
method destroy (line 103) | destroy() {
method set (line 110) | set(libraryItem, tracks, isHlsTranscode, startTime, playWhenReady = fa...
method setHlsStream (line 128) | setHlsStream() {
method setDirectPlay (line 195) | setDirectPlay() {
method loadCurrentTrack (line 203) | loadCurrentTrack() {
method destroyHlsInstance (line 212) | destroyHlsInstance() {
method resetStream (line 221) | async resetStream(startTime) {
method playPause (line 227) | playPause() {
method play (line 233) | play() {
method pause (line 238) | pause() {
method getCurrentTime (line 243) | getCurrentTime() {
method getDuration (line 248) | getDuration() {
method setPlaybackRate (line 254) | setPlaybackRate(playbackRate) {
method seek (line 260) | seek(time, playWhenReady) {
method setVolume (line 291) | setVolume(volume) {
method isValidDuration (line 297) | isValidDuration(duration) {
method getBufferedRanges (line 304) | getBufferedRanges() {
method getLastBufferedTime (line 330) | getLastBufferedTime() {
FILE: client/players/PlayerHandler.js
class PlayerHandler (line 5) | class PlayerHandler {
method constructor (line 6) | constructor(ctx) {
method isCasting (line 28) | get isCasting() {
method libraryItemId (line 31) | get libraryItemId() {
method isPlayingCastedItem (line 34) | get isPlayingCastedItem() {
method isPlayingLocalItem (line 37) | get isPlayingLocalItem() {
method playerPlaying (line 40) | get playerPlaying() {
method episode (line 43) | get episode() {
method jumpForwardAmount (line 47) | get jumpForwardAmount() {
method jumpBackwardAmount (line 50) | get jumpBackwardAmount() {
method setSessionId (line 54) | setSessionId(sessionId) {
method load (line 59) | load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOve...
method switchPlayer (line 72) | switchPlayer(playWhenReady) {
method setPlayerListeners (line 113) | setPlayerListeners() {
method playerError (line 121) | playerError() {
method playerFinished (line 129) | playerFinished() {
method playerStateChange (line 141) | playerStateChange(state) {
method playerTimeupdate (line 165) | playerTimeupdate(time) {
method playerBufferTimeUpdate (line 169) | playerBufferTimeUpdate(buffertime) {
method getDeviceId (line 173) | getDeviceId() {
method prepare (line 182) | async prepare(forceTranscode = false) {
method prepareOpenSession (line 203) | prepareOpenSession(session, playbackRate) {
method prepareSession (line 217) | prepareSession(session) {
method closePlayer (line 240) | closePlayer() {
method resetPlayer (line 246) | resetPlayer() {
method resetStream (line 258) | resetStream(startTime, streamId) {
method startPlayInterval (line 270) | startPlayInterval() {
method sendCloseSession (line 289) | sendCloseSession() {
method sendProgressSync (line 308) | sendProgressSync(currentTime) {
method stopPlayInterval (line 336) | stopPlayInterval() {
method playPause (line 341) | playPause() {
method play (line 345) | play() {
method pause (line 349) | pause() {
method getCurrentTime (line 353) | getCurrentTime() {
method getDuration (line 357) | getDuration() {
method jumpBackward (line 361) | jumpBackward() {
method jumpForward (line 368) | jumpForward() {
method setVolume (line 375) | setVolume(volume) {
method setPlaybackRate (line 380) | setPlaybackRate(playbackRate) {
method seek (line 386) | seek(time, shouldSync = true) {
FILE: client/players/castUtils.js
function getMediaInfoFromTrack (line 1) | function getMediaInfoFromTrack(libraryItem, castImage, track) {
function buildCastMediaInfo (line 30) | function buildCastMediaInfo(libraryItem, coverUrl, tracks) {
function buildCastQueueRequest (line 35) | function buildCastQueueRequest(libraryItem, coverUrl, tracks, startTime) {
function castLoadMedia (line 68) | function castLoadMedia(castSession, request) {
function buildCastLoadRequest (line 80) | function buildCastLoadRequest(libraryItem, coverUrl, tracks, startTime, ...
FILE: client/plugins/i18n.js
function loadTranslationStrings (line 134) | function loadTranslationStrings(code) {
function loadi18n (line 147) | async function loadi18n(code) {
function initialize (line 192) | async function initialize() {
FILE: client/plugins/init.client.js
function xmlToJson (line 159) | function xmlToJson(xml) {
FILE: client/plugins/utils.js
function supplant (line 229) | function supplant(str, subs) {
FILE: client/plugins/version.js
function parseSemver (line 4) | function parseSemver(ver) {
function getReleases (line 28) | function getReleases() {
function checkForUpdate (line 53) | async function checkForUpdate() {
FILE: client/static/libarchive/wasm-gen/libarchive.js
function locateFile (line 8) | function locateFile(path) { if (Module["locateFile"]) { return Module["l...
function dynamicAlloc (line 8) | function dynamicAlloc(size) { var ret = HEAP32[DYNAMICTOP_PTR >> 2]; var...
function getNativeTypeSize (line 8) | function getNativeTypeSize(type) { switch (type) { case "i1": case "i8":...
function assert (line 8) | function assert(condition, text) { if (!condition) { abort("Assertion fa...
function getCFunc (line 8) | function getCFunc(ident) { var func = Module["_" + ident]; assert(func, ...
function ccall (line 8) | function ccall(ident, returnType, argTypes, args, opts) { var toC = { "s...
function cwrap (line 8) | function cwrap(ident, returnType, argTypes, opts) { argTypes = argTypes ...
function setValue (line 8) | function setValue(ptr, value, type, noSafe) { type = type || "i8"; if (t...
function allocate (line 8) | function allocate(slab, types, allocator, ptr) { var zeroinit, size; if ...
function getMemory (line 8) | function getMemory(size) { if (!runtimeInitialized) return dynamicAlloc(...
function UTF8ArrayToString (line 8) | function UTF8ArrayToString(u8Array, idx, maxBytesToRead) { var endIdx = ...
function UTF8ToString (line 8) | function UTF8ToString(ptr, maxBytesToRead) { return ptr ? UTF8ArrayToStr...
function stringToUTF8Array (line 8) | function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) { i...
function stringToUTF8 (line 8) | function stringToUTF8(str, outPtr, maxBytesToWrite) { return stringToUTF...
function lengthBytesUTF8 (line 8) | function lengthBytesUTF8(str) { var len = 0; for (var i = 0; i < str.len...
function writeArrayToMemory (line 8) | function writeArrayToMemory(array, buffer) { HEAP8.set(array, buffer) }
function writeAsciiToMemory (line 8) | function writeAsciiToMemory(str, buffer, dontAddNull) { for (var i = 0; ...
function demangle (line 8) | function demangle(func) { return func }
function demangleAll (line 8) | function demangleAll(text) { var regex = /__Z[\w\d_]+/g; return text.rep...
function jsStackTrace (line 8) | function jsStackTrace() { var err = new Error; if (!err.stack) { try { t...
function stackTrace (line 8) | function stackTrace() { var js = jsStackTrace(); if (Module["extraStackT...
function alignUp (line 8) | function alignUp(x, multiple) { if (x % multiple > 0) { x += multiple - ...
function updateGlobalBufferViews (line 8) | function updateGlobalBufferViews() { Module["HEAP8"] = HEAP8 = new Int8A...
function callRuntimeCallbacks (line 8) | function callRuntimeCallbacks(callbacks) { while (callbacks.length > 0) ...
function preRun (line 8) | function preRun() { if (Module["preRun"]) { if (typeof Module["preRun"] ...
function ensureInitRuntime (line 8) | function ensureInitRuntime() { if (runtimeInitialized) return; runtimeIn...
function preMain (line 8) | function preMain() { FS.ignorePermissions = false; callRuntimeCallbacks(...
function exitRuntime (line 8) | function exitRuntime() { runtimeExited = true }
function postRun (line 8) | function postRun() { if (Module["postRun"]) { if (typeof Module["postRun...
function addOnPreRun (line 8) | function addOnPreRun(cb) { __ATPRERUN__.unshift(cb) }
function addOnPostRun (line 8) | function addOnPostRun(cb) { __ATPOSTRUN__.unshift(cb) }
function getUniqueRunDependency (line 8) | function getUniqueRunDependency(id) { return id }
function addRunDependency (line 8) | function addRunDependency(id) { runDependencies++; if (Module["monitorRu...
function removeRunDependency (line 8) | function removeRunDependency(id) { runDependencies--; if (Module["monito...
function isDataURI (line 8) | function isDataURI(filename) { return String.prototype.startsWith ? file...
function getBinary (line 8) | function getBinary() { try { if (Module["wasmBinary"]) { return new Uint...
function getBinaryPromise (line 8) | function getBinaryPromise() { if (!Module["wasmBinary"] && (ENVIRONMENT_...
function createWasm (line 8) | function createWasm(env) { var info = { "env": env, "global": { "NaN": N...
function ___buildEnvironment (line 8) | function ___buildEnvironment(environ) { var MAX_ENV_VALUES = 64; var TOT...
function ___setErrNo (line 8) | function ___setErrNo(value) { if (Module["___errno_location"]) HEAP32[Mo...
function trim (line 8) | function trim(arr) { var start = 0; for (; start < arr.length; start++) ...
function isRealDir (line 8) | function isRealDir(p) { return p !== "." && p !== ".." }
function toAbsolute (line 8) | function toAbsolute(root) { return function (p) { return PATH.join2(root...
function done (line 8) | function done(err) { if (err) { if (!done.errored) { done.errored = true...
function ensureParent (line 8) | function ensureParent(path) { var parts = path.split("/"); var parent = ...
function base (line 8) | function base(path) { var parts = path.split("/"); return parts[parts.le...
function doCallback (line 8) | function doCallback(err) { FS.syncFSRequests--; return callback(err) }
function done (line 8) | function done(err) { if (err) { if (!done.errored) { done.errored = true...
function LazyUint8Array (line 8) | function LazyUint8Array() { this.lengthKnown = false; this.chunks = [] }
function processData (line 8) | function processData(byteArray) { function finish(byteArray) { if (preFi...
function finish (line 8) | function finish() { if (fail == 0) onload(); else onerror() }
function finish (line 8) | function finish() { if (fail == 0) onload(); else onerror() }
function ___syscall140 (line 8) | function ___syscall140(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall146 (line 8) | function ___syscall146(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall168 (line 8) | function ___syscall168(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall195 (line 8) | function ___syscall195(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall196 (line 8) | function ___syscall196(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall197 (line 8) | function ___syscall197(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall221 (line 8) | function ___syscall221(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall3 (line 8) | function ___syscall3(which, varargs) { SYSCALLS.varargs = varargs; try {...
function ___syscall4 (line 8) | function ___syscall4(which, varargs) { SYSCALLS.varargs = varargs; try {...
function ___syscall41 (line 8) | function ___syscall41(which, varargs) { SYSCALLS.varargs = varargs; try ...
function ___syscall42 (line 8) | function ___syscall42(which, varargs) { SYSCALLS.varargs = varargs; try ...
function ___syscall5 (line 8) | function ___syscall5(which, varargs) { SYSCALLS.varargs = varargs; try {...
function ___syscall6 (line 8) | function ___syscall6(which, varargs) { SYSCALLS.varargs = varargs; try {...
function _abort (line 8) | function _abort() { Module["abort"]() }
function _emscripten_get_heap_size (line 8) | function _emscripten_get_heap_size() { return HEAP8.length }
function abortOnCannotGrowMemory (line 8) | function abortOnCannotGrowMemory(requestedSize) { abort("OOM") }
function emscripten_realloc_buffer (line 8) | function emscripten_realloc_buffer(size) { var PAGE_MULTIPLE = 65536; si...
function _emscripten_resize_heap (line 8) | function _emscripten_resize_heap(requestedSize) { var oldSize = _emscrip...
function _exit (line 8) | function _exit(status) { exit(status) }
function _tzset (line 8) | function _tzset() { if (_tzset.called) return; _tzset.called = true; HEA...
function _localtime_r (line 8) | function _localtime_r(time, tmPtr) { _tzset(); var date = new Date(HEAP3...
function _localtime (line 8) | function _localtime(time) { return _localtime_r(time, ___tm_current) }
function _emscripten_memcpy_big (line 8) | function _emscripten_memcpy_big(dest, src, num) { HEAPU8.set(HEAPU8.suba...
function _mktime (line 8) | function _mktime(tmPtr) { _tzset(); var date = new Date(HEAP32[tmPtr + 2...
function _posix_spawn_file_actions_addclose (line 8) | function _posix_spawn_file_actions_addclose() { err("missing function: p...
function _posix_spawn_file_actions_adddup2 (line 8) | function _posix_spawn_file_actions_adddup2() { err("missing function: po...
function _posix_spawn_file_actions_destroy (line 8) | function _posix_spawn_file_actions_destroy() { err("missing function: po...
function _posix_spawn_file_actions_init (line 8) | function _posix_spawn_file_actions_init() { err("missing function: posix...
function _fork (line 8) | function _fork() { ___setErrNo(11); return -1 }
function _posix_spawnp (line 8) | function _posix_spawnp() { return _fork.apply(null, arguments) }
function _timegm (line 8) | function _timegm(tmPtr) { _tzset(); var time = Date.UTC(HEAP32[tmPtr + 2...
function _wait (line 8) | function _wait(stat_loc) { ___setErrNo(10); return -1 }
function _waitpid (line 8) | function _waitpid() { return _wait.apply(null, arguments) }
function intArrayFromString (line 8) | function intArrayFromString(stringy, dontAddNull, length) { var len = le...
function ExitStatus (line 8) | function ExitStatus(status) { this.name = "ExitStatus"; this.message = "...
function run (line 8) | function run(args) { args = args || Module["arguments"]; if (runDependen...
function exit (line 8) | function exit(status, implicit) { if (implicit && Module["noExitRuntime"...
function abort (line 8) | function abort(what) { if (Module["onAbort"]) { Module["onAbort"](what) ...
FILE: client/static/libarchive/worker-bundle.js
class r (line 1) | class r { constructor(e) { this._wasmModule = e, this._runCode = e.runCo...
method constructor (line 1) | constructor(e) { this._wasmModule = e, this._runCode = e.runCode, this...
method open (line 1) | open(e) { null !== this._file && (console.warn("Closing previous file"...
method close (line 1) | close() { this._runCode.closeArchive(this._archive), this._wasmModule....
method hasEncryptedData (line 1) | hasEncryptedData() { this._archive = this._runCode.openArchive(this._f...
method setPassphrase (line 1) | setPassphrase(e) { this._passphrase = e }
method entries (line 1) | *entries(r = !1, t = null) { let n; for (this._archive = this._runCode...
method _loadFile (line 1) | _loadFile(e, r, t) { try { const n = new Uint8Array(e); this._fileLeng...
method _promiseHandles (line 1) | _promiseHandles() { let e = null, r = null; return { promise: new Prom...
function m (line 1) | function m(e) { var r = M[q >> 2], t = r + e + 15 & -16; if (t <= Fe()) ...
function h (line 1) | function h(e) { switch (e) { case "i1": case "i8": return 1; case "i16":...
function y (line 1) | function y(e, r) { e || Ke("Assertion failed: " + r) }
function E (line 1) | function E(e) { var r = n["_" + e]; return y(r, "Cannot call unknown fun...
function _ (line 1) | function _(e, r, t, n, o) { var i = { string: function (e) { var r = 0; ...
function b (line 1) | function b(e, r, t, n) { switch ("*" === (t = t || "i8").charAt(t.length...
function S (line 1) | function S(e, r, t, n) { var o, i; "number" == typeof e ? (o = !0, i = e...
function F (line 1) | function F(e) { return $ ? Le(e) : m(e) }
function N (line 1) | function N(e, r, t) { for (var n = r + t, o = r; e[o] && !(o >= n);)++o;...
function I (line 1) | function I(e, r) { return e ? N(R, e, r) : "" }
function z (line 1) | function z(e, r, t, n) { if (!(n > 0)) return 0; for (var o = t, i = t +...
function C (line 1) | function C(e, r, t) { return z(e, R, r, t) }
function L (line 1) | function L(e) { for (var r = 0, t = 0; t < e.length; ++t) { var n = e.ch...
function U (line 1) | function U(e, r, t) { for (var n = 0; n < e.length; ++n)P[r++ >> 0] = e....
function j (line 1) | function j() { var e = function () { var e = new Error; if (!e.stack) { ...
function H (line 1) | function H(e, r) { return e % r > 0 && (e += r - e % r), e }
function W (line 1) | function W() { n.HEAP8 = P = new Int8Array(A), n.HEAP16 = T = new Int16A...
function Z (line 1) | function Z(e) { for (; e.length > 0;) { var r = e.shift(); if ("function...
function oe (line 1) | function oe(e) { te++, n.monitorRunDependencies && n.monitorRunDependenc...
function ie (line 1) | function ie(e) { if (te--, n.monitorRunDependencies && n.monitorRunDepen...
function se (line 1) | function se(e) { return String.prototype.startsWith ? e.startsWith(ae) :...
function fe (line 1) | function fe() { try { if (n.wasmBinary) return new Uint8Array(n.wasmBina...
function le (line 1) | function le(e) { var r = { env: e, global: { NaN: NaN, Infinity: 1 / 0 }...
function me (line 1) | function me(e) { return n.___errno_location && (M[n.___errno_location() ...
function t (line 1) | function t(e) { for (var r = 0; r < e.length && "" === e[r]; r++); for (...
function n (line 1) | function n(e) { return "." !== e && ".." !== e }
function o (line 1) | function o(e) { return function (r) { return pe.join2(e, r) } }
method constructor (line 1) | constructor() { this.preRun = [], this.postRun = [], this.totalDepende...
method print (line 1) | print(...e) { console.log(e) }
method printErr (line 1) | printErr(...e) { console.error(e) }
method initFunctions (line 1) | initFunctions() { this.runCode = { getVersion: this.cwrap("get_version...
method monitorRunDependencies (line 1) | monitorRunDependencies() { }
method locateFile (line 1) | locateFile(e) { return `wasm-gen/${e}` }
function c (line 1) | function c(e) { return e ? c.errored ? void 0 : (c.errored = !0, t(e)) :...
function n (line 1) | function n(e) { for (var n = e.split("/"), o = r, i = 0; i < n.length - ...
function o (line 1) | function o(e) { var r = e.split("/"); return r[r.length - 1] }
method constructor (line 1) | constructor() { this.preRun = [], this.postRun = [], this.totalDepende...
method print (line 1) | print(...e) { console.log(e) }
method printErr (line 1) | printErr(...e) { console.error(e) }
method initFunctions (line 1) | initFunctions() { this.runCode = { getVersion: this.cwrap("get_version...
method monitorRunDependencies (line 1) | monitorRunDependencies() { }
method locateFile (line 1) | locateFile(e) { return `wasm-gen/${e}` }
function o (line 1) | function o(e) { return _e.syncFSRequests--, r(e) }
method constructor (line 1) | constructor() { this.preRun = [], this.postRun = [], this.totalDepende...
method print (line 1) | print(...e) { console.log(e) }
method printErr (line 1) | printErr(...e) { console.error(e) }
method initFunctions (line 1) | initFunctions() { this.runCode = { getVersion: this.cwrap("get_version...
method monitorRunDependencies (line 1) | monitorRunDependencies() { }
method locateFile (line 1) | locateFile(e) { return `wasm-gen/${e}` }
function i (line 1) | function i(e) { if (e) return i.errored ? void 0 : (i.errored = !0, o(e)...
function i (line 1) | function i() { this.lengthKnown = !1, this.chunks = [] }
function d (line 1) | function d(t) { function d(t) { f && f(), u || _e.createDataFile(e, r, t...
function c (line 1) | function c() { 0 == s ? r() : t() }
function f (line 1) | function f() { 0 == u ? r() : t() }
function Fe (line 1) | function Fe() { return P.length }
function Ae (line 1) | function Ae(e) { var r = Fe(); if (e > 2147418112) return !1; for (var t...
function Re (line 1) | function Re() { if (!Re.called) { Re.called = !0, M[ze() >> 2] = 60 * (n...
function Be (line 1) | function Be(e, r, t) { var n = t > 0 ? t : L(e) + 1, o = new Array(n), i...
function We (line 1) | function We(e) { this.name = "ExitStatus", this.message = "Program termi...
function qe (line 1) | function qe(e) { function r() { n.calledRun || (n.calledRun = !0, g || (...
function Ke (line 1) | function Ke(e) { throw n.onAbort && n.onAbort(e), void 0 !== e ? (d(e), ...
class o (line 1) | class o { constructor() { this.preRun = [], this.postRun = [], this.tota...
method constructor (line 1) | constructor() { this.preRun = [], this.postRun = [], this.totalDepende...
method print (line 1) | print(...e) { console.log(e) }
method printErr (line 1) | printErr(...e) { console.error(e) }
method initFunctions (line 1) | initFunctions() { this.runCode = { getVersion: this.cwrap("get_version...
method monitorRunDependencies (line 1) | monitorRunDependencies() { }
method locateFile (line 1) | locateFile(e) { return `wasm-gen/${e}` }
FILE: client/static/libs/marked/index.js
function i (line 6) | function i(e,t){for(var u=0;u<t.length;u++){var n=t[u];n.enumerable=n.en...
function s (line 6) | function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var u=0,n=new Ar...
function b (line 6) | function b(e,t){var u,n="undefined"!=typeof Symbol&&e[Symbol.iterator]||...
function e (line 6) | function e(){return{baseUrl:null,breaks:!1,extensions:null,gfm:!0,header...
function u (line 6) | function u(e){return t[e]}
function D (line 6) | function D(e,t){if(t){if(n.test(e))return e.replace(l,u)}else if(a.test(...
function x (line 6) | function x(e){return e.replace(c,function(e,t){return"colon"===(t=t.toLo...
function p (line 6) | function p(u,e){u="string"==typeof u?u:u.source,e=e||"";var n={replace:f...
function g (line 6) | function g(e,t,u){if(e){try{n=decodeURIComponent(x(u)).replace(f,"").toL...
function d (line 6) | function d(e){for(var t,u,n=1;n<arguments.length;n++)for(u in t=argument...
function C (line 6) | function C(e,t){var u=e.replace(/\|/g,function(e,t,u){for(var n=!1,r=t;0...
function k (line 6) | function k(e,t,u){var n=e.length;if(0===n)return"";for(var r=0;r<n;){var...
function E (line 6) | function E(e){e&&e.sanitize&&!e.silent&&console.warn("marked(): sanitize...
function m (line 6) | function m(e,t){if(t<1)return"";for(var u="";1<t;)1&t&&(u+=e),t>>=1,e+=e...
function B (line 6) | function B(e,t,u,n){var r=t.href,t=t.title?D(t.title):null,i=e[1].replac...
function e (line 6) | function e(e){this.options=e||r.defaults}
function j (line 6) | function j(e){return e.replace(/---/g,"—").replace(/--/g,"–").replace(/(...
function _ (line 6) | function _(e){for(var t,u="",n=e.length,r=0;r<n;r++)t=e.charCodeAt(r),u+...
function u (line 6) | function u(e){this.tokens=[],this.tokens.links=Object.create(null),this....
function e (line 6) | function e(e){this.options=e||r.defaults}
function e (line 6) | function e(){}
function e (line 6) | function e(){this.seen={}}
function u (line 6) | function u(e){this.options=e||r.defaults,this.options.renderer=this.opti...
function I (line 6) | function I(e,u,n){if(null==e)throw new Error("marked(): input parameter ...
FILE: client/store/globals.js
method updateWindowSize (line 118) | updateWindowSize(state, { width, height }) {
method setShowCollectionsModal (line 123) | setShowCollectionsModal(state, val) {
method setShowBatchCollectionsModal (line 127) | setShowBatchCollectionsModal(state, val) {
method setShowEditCollectionModal (line 131) | setShowEditCollectionModal(state, val) {
method setShowPlaylistsModal (line 134) | setShowPlaylistsModal(state, val) {
method setShowEditPlaylistModal (line 137) | setShowEditPlaylistModal(state, val) {
method setShowEditPodcastEpisodeModal (line 140) | setShowEditPodcastEpisodeModal(state, val) {
method setShowViewPodcastEpisodeModal (line 143) | setShowViewPodcastEpisodeModal(state, val) {
method setShowRSSFeedOpenCloseModal (line 146) | setShowRSSFeedOpenCloseModal(state, val) {
method setRSSFeedOpenCloseModal (line 149) | setRSSFeedOpenCloseModal(state, entity) {
method setShowShareModal (line 153) | setShowShareModal(state, val) {
method setShareModal (line 156) | setShareModal(state, mediaItemShare) {
method setShowConfirmPrompt (line 160) | setShowConfirmPrompt(state, val) {
method setConfirmPrompt (line 163) | setConfirmPrompt(state, options) {
method setShowRawCoverPreviewModal (line 167) | setShowRawCoverPreviewModal(state, val) {
method setRawCoverPreviewModal (line 170) | setRawCoverPreviewModal(state, rawCoverUrl) {
method setEditCollection (line 174) | setEditCollection(state, collection) {
method setEditPlaylist (line 178) | setEditPlaylist(state, playlist) {
method setSelectedEpisode (line 182) | setSelectedEpisode(state, episode) {
method setSelectedPlaylistItems (line 185) | setSelectedPlaylistItems(state, items) {
method showEditAuthorModal (line 188) | showEditAuthorModal(state, author) {
method setShowEditAuthorModal (line 192) | setShowEditAuthorModal(state, val) {
method setSelectedAuthor (line 195) | setSelectedAuthor(state, author) {
method setChromecastInitialized (line 198) | setChromecastInitialized(state, val) {
method setCasting (line 201) | setCasting(state, val) {
method setShowBatchQuickMatchModal (line 204) | setShowBatchQuickMatchModal(state, val) {
method resetSelectedMediaItems (line 207) | resetSelectedMediaItems(state) {
method toggleMediaItemSelected (line 210) | toggleMediaItemSelected(state, item) {
method setMediaItemSelected (line 217) | setMediaItemSelected(state, { item, selected }) {
FILE: client/store/index.js
method updateServerSettings (line 68) | updateServerSettings({ commit }, payload) {
method checkForUpdate (line 88) | checkForUpdate({ commit }) {
method setRouterBasePath (line 131) | setRouterBasePath(state, rbp) {
method setSource (line 134) | setSource(state, source) {
method setPlayerIsFullscreen (line 137) | setPlayerIsFullscreen(state, val) {
method setLastBookshelfScrollData (line 140) | setLastBookshelfScrollData(state, { scrollTop, path, name }) {
method setBookshelfBookIds (line 143) | setBookshelfBookIds(state, val) {
method setEpisodeTableEpisodeIds (line 146) | setEpisodeTableEpisodeIds(state, val) {
method setPreviousPath (line 149) | setPreviousPath(state, val) {
method setVersionData (line 152) | setVersionData(state, versionData) {
method setServerSettings (line 155) | setServerSettings(state, settings) {
method setPlaybackSessionId (line 159) | setPlaybackSessionId(state, playbackSessionId) {
method setMediaPlaying (line 162) | setMediaPlaying(state, payload) {
method updateStreamLibraryItem (line 174) | updateStreamLibraryItem(state, libraryItem) {
method setIsPlaying (line 178) | setIsPlaying(state, isPlaying) {
method setPlayerQueueItems (line 181) | setPlayerQueueItems(state, items) {
method removeItemFromQueue (line 184) | removeItemFromQueue(state, item) {
method addItemToQueue (line 190) | addItemToQueue(state, item) {
method setPlayerQueueAutoPlay (line 199) | setPlayerQueueAutoPlay(state, autoPlay) {
method showEditModal (line 203) | showEditModal(state, libraryItem) {
method showEditModalOnTab (line 208) | showEditModalOnTab(state, { libraryItem, tab }) {
method setEditModalTab (line 213) | setEditModalTab(state, tab) {
method setShowEditModal (line 216) | setShowEditModal(state, val) {
method setEditPodcastModalTab (line 219) | setEditPodcastModalTab(state, tab) {
method showEReader (line 222) | showEReader(state, { libraryItem, keepProgress, fileId }) {
method setShowEReader (line 229) | setShowEReader(state, val) {
method setDeveloperMode (line 232) | setDeveloperMode(state, val) {
method setSelectedLibraryItem (line 235) | setSelectedLibraryItem(state, val) {
method setProcessingBatch (line 238) | setProcessingBatch(state, val) {
method setOpenModal (line 241) | setOpenModal(state, val) {
method setInnerModalOpen (line 244) | setInnerModalOpen(state, val) {
FILE: client/store/libraries.js
method requestLibraryScan (line 75) | requestLibraryScan({ state, commit }, { libraryId, force }) {
method loadFolders (line 78) | loadFolders({ state, commit }) {
method fetch (line 101) | fetch({ state, dispatch, commit, rootState, rootGetters }, libraryId) {
method load (line 142) | load({ state, commit, rootState }) {
method setFolders (line 170) | setFolders(state, folders) {
method setFoldersLastUpdate (line 173) | setFoldersLastUpdate(state) {
method setLastLoad (line 176) | setLastLoad(state, date) {
method setLibraryIssues (line 179) | setLibraryIssues(state, val) {
method setCurrentLibrary (line 182) | setCurrentLibrary(state, { id }) {
method set (line 185) | set(state, libraries) {
method addUpdate (line 191) | addUpdate(state, library) {
method remove (line 203) | remove(state, library) {
method addListener (line 210) | addListener(state, listener) {
method removeListener (line 215) | removeListener(state, listenerId) {
method setLibraryFilterData (line 218) | setLibraryFilterData(state, filterData) {
method setNumUserPlaylists (line 221) | setNumUserPlaylists(state, numUserPlaylists) {
method removeSeriesFromFilterData (line 224) | removeSeriesFromFilterData(state, seriesId) {
method updateFilterDataWithItem (line 228) | updateFilterDataWithItem(state, libraryItem) {
method setCollections (line 324) | setCollections(state, collections) {
method addUpdateCollection (line 327) | addUpdateCollection(state, collection) {
method removeCollection (line 335) | removeCollection(state, collection) {
method setUserPlaylists (line 338) | setUserPlaylists(state, playlists) {
method addUpdateUserPlaylist (line 342) | addUpdateUserPlaylist(state, playlist) {
method removeUserPlaylist (line 351) | removeUserPlaylist(state, playlist) {
method setEReaderDevices (line 355) | setEReaderDevices(state, ereaderDevices) {
FILE: client/store/scanners.js
method fetchProviders (line 20) | async fetchProviders({ commit, state }) {
method refreshProviders (line 35) | async refreshProviders({ commit, state }) {
method setAllProviders (line 53) | setAllProviders(state, providers) {
FILE: client/store/tasks.js
method updateAudioFilesEncoding (line 33) | updateAudioFilesEncoding(state, payload) {
method updateAudioFilesFinished (line 39) | updateAudioFilesFinished(state, payload) {
method updateTaskProgress (line 45) | updateTaskProgress(state, payload) {
method setTasks (line 48) | setTasks(state, tasks) {
method addUpdateTask (line 51) | addUpdateTask(state, task) {
method removeTask (line 65) | removeTask(state, task) {
method setQueuedEmbedLIds (line 68) | setQueuedEmbedLIds(state, libraryItemIds) {
method addQueuedEmbedLId (line 71) | addQueuedEmbedLId(state, libraryItemId) {
method removeQueuedEmbedLId (line 76) | removeQueuedEmbedLId(state, libraryItemId) {
FILE: client/store/user.js
method checkUpdateLibrarySortFilter (line 86) | checkUpdateLibrarySortFilter({ state, dispatch, commit }, mediaType) {
method updateUserSettings (line 115) | updateUserSettings({ state, commit }, payload) {
method loadUserSettings (line 131) | loadUserSettings({ state, commit }) {
method refreshToken (line 150) | refreshToken({ state, commit }) {
method setUser (line 173) | setUser(state, user) {
method setAccessToken (line 176) | setAccessToken(state, token) {
method updateMediaProgress (line 185) | updateMediaProgress(state, { id, data }) {
method setSettings (line 198) | setSettings(state, settings) {
FILE: client/store/users.js
method setUsersOnline (line 17) | setUsersOnline(state, usersOnline) {
method updateUserOnline (line 20) | updateUserOnline(state, user) {
method removeUserOnline (line 28) | removeUserOnline(state, user) {
FILE: index.js
constant PORT (line 41) | const PORT = options.port || process.env.PORT || 3333
constant HOST (line 42) | const HOST = options.host || process.env.HOST
constant CONFIG_PATH (line 43) | const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resol...
constant METADATA_PATH (line 44) | const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path...
constant SOURCE (line 45) | const SOURCE = options.source || process.env.SOURCE || 'debian'
constant ROUTER_BASE_PATH (line 47) | const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH ?? '/audiobookshelf'
FILE: prod.js
constant PORT (line 22) | const PORT = options.port || process.env.PORT || 3333
constant HOST (line 23) | const HOST = options.host || process.env.HOST
constant CONFIG_PATH (line 24) | const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resol...
constant METADATA_PATH (line 25) | const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path...
constant SOURCE (line 26) | const SOURCE = options.source || process.env.SOURCE || 'debian'
constant ROUTER_BASE_PATH (line 28) | const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH ?? '/audiobookshelf'
FILE: server/Auth.js
class Auth (line 18) | class Auth {
method constructor (line 19) | constructor() {
method authNotNeeded (line 36) | authNotNeeded(req) {
method ifAuthNeeded (line 45) | ifAuthNeeded(middleware) {
method isAuthenticated (line 61) | isAuthenticated(req, res, next) {
method generateAccessToken (line 73) | generateAccessToken(user) {
method invalidateJwtSessionsForUser (line 86) | async invalidateJwtSessionsForUser(user, req, res) {
method getUserLoginResponsePayload (line 96) | async getUserLoginResponsePayload(user) {
method initPassportJs (line 111) | async initPassportJs() {
method unuseAuthStrategy (line 169) | unuseAuthStrategy(name) {
method useAuthStrategy (line 184) | useAuthStrategy(name) {
method isAuthMethodAPIBased (line 200) | isAuthMethodAPIBased(authMethod) {
method paramsToCookies (line 218) | paramsToCookies(req, res, authMethod = 'local') {
method handleLoginSuccessBasedOnCookie (line 258) | async handleLoginSuccessBasedOnCookie(req, res) {
method handleLoginSuccess (line 294) | async handleLoginSuccess(req, res, returnTokens = false) {
method initAuthRoutes (line 318) | async initAuthRoutes(router) {
FILE: server/Database.js
class Database (line 13) | class Database {
method constructor (line 14) | constructor() {
method models (line 36) | get models() {
method userModel (line 41) | get userModel() {
method sessionModel (line 46) | get sessionModel() {
method apiKeyModel (line 51) | get apiKeyModel() {
method libraryModel (line 56) | get libraryModel() {
method libraryFolderModel (line 61) | get libraryFolderModel() {
method authorModel (line 66) | get authorModel() {
method seriesModel (line 71) | get seriesModel() {
method bookModel (line 76) | get bookModel() {
method bookSeriesModel (line 81) | get bookSeriesModel() {
method bookAuthorModel (line 86) | get bookAuthorModel() {
method podcastModel (line 91) | get podcastModel() {
method podcastEpisodeModel (line 96) | get podcastEpisodeModel() {
method libraryItemModel (line 101) | get libraryItemModel() {
method podcastEpisodeModel (line 106) | get podcastEpisodeModel() {
method mediaProgressModel (line 111) | get mediaProgressModel() {
method collectionModel (line 116) | get collectionModel() {
method collectionBookModel (line 121) | get collectionBookModel() {
method playlistModel (line 126) | get playlistModel() {
method playlistMediaItemModel (line 131) | get playlistMediaItemModel() {
method feedModel (line 136) | get feedModel() {
method feedEpisodeModel (line 141) | get feedEpisodeModel() {
method playbackSessionModel (line 146) | get playbackSessionModel() {
method customMetadataProviderModel (line 151) | get customMetadataProviderModel() {
method mediaItemShareModel (line 156) | get mediaItemShareModel() {
method deviceModel (line 161) | get deviceModel() {
method checkHasDb (line 169) | async checkHasDb() {
method init (line 181) | async init(force = false) {
method connect (line 216) | async connect() {
method loadExtension (line 284) | async loadExtension(extension) {
method disconnect (line 309) | async disconnect() {
method reconnect (line 317) | async reconnect() {
method buildModels (line 322) | buildModels(force = false) {
method compareVersions (line 358) | compareVersions(v1, v2) {
method loadData (line 370) | async loadData() {
method createRootUser (line 426) | async createRootUser(username, pash, auth) {
method updateServerSettings (line 433) | updateServerSettings() {
method updateSetting (line 439) | updateSetting(settings) {
method getPlaybackSessions (line 444) | getPlaybackSessions(where = null) {
method getPlaybackSession (line 449) | getPlaybackSession(sessionId) {
method createPlaybackSession (line 454) | createPlaybackSession(oldSession) {
method updatePlaybackSession (line 459) | updatePlaybackSession(oldSession) {
method removePlaybackSession (line 464) | removePlaybackSession(sessionId) {
method replaceTagInFilterData (line 469) | replaceTagInFilterData(oldTag, newTag) {
method removeTagFromFilterData (line 478) | removeTagFromFilterData(tag) {
method addTagsToFilterData (line 484) | addTagsToFilterData(libraryId, tags) {
method replaceGenreInFilterData (line 493) | replaceGenreInFilterData(oldGenre, newGenre) {
method removeGenreFromFilterData (line 502) | removeGenreFromFilterData(genre) {
method addGenresToFilterData (line 508) | addGenresToFilterData(libraryId, genres) {
method replaceNarratorInFilterData (line 517) | replaceNarratorInFilterData(oldNarrator, newNarrator) {
method removeNarratorFromFilterData (line 526) | removeNarratorFromFilterData(narrator) {
method addNarratorsToFilterData (line 532) | addNarratorsToFilterData(libraryId, narrators) {
method removeSeriesFromFilterData (line 541) | removeSeriesFromFilterData(libraryId, seriesId) {
method addSeriesToFilterData (line 546) | addSeriesToFilterData(libraryId, seriesName, seriesId) {
method removeAuthorFromFilterData (line 556) | removeAuthorFromFilterData(libraryId, authorId) {
method addAuthorToFilterData (line 561) | addAuthorToFilterData(libraryId, authorName, authorId) {
method addPublisherToFilterData (line 571) | addPublisherToFilterData(libraryId, publisher) {
method addPublishedDecadeToFilterData (line 576) | addPublishedDecadeToFilterData(libraryId, decade) {
method addLanguageToFilterData (line 581) | addLanguageToFilterData(libraryId, language) {
method checkAuthorExists (line 594) | async checkAuthorExists(libraryId, authorId) {
method checkSeriesExists (line 609) | async checkSeriesExists(libraryId, seriesId) {
method getAuthorIdByName (line 623) | async getAuthorIdByName(libraryId, authorName) {
method getSeriesIdByName (line 637) | async getSeriesIdByName(libraryId, seriesName) {
method resetLibraryIssuesFilterData (line 648) | async resetLibraryIssuesFilterData(libraryId) {
method cleanDatabase (line 675) | async cleanDatabase() {
method deactivateExpiredApiKeys (line 814) | async deactivateExpiredApiKeys() {
method cleanupExpiredSessions (line 828) | async cleanupExpiredSessions() {
method createTextSearchQuery (line 839) | async createTextSearchQuery(query) {
method addTriggers (line 849) | async addTriggers() {
method addTriggerIfNotExists (line 857) | async addTriggerIfNotExists(sourceTable, sourceColumn, sourceIdColumn,...
method addAuthorNamesTriggersIfNotExist (line 879) | async addAuthorNamesTriggersIfNotExist() {
method convertToSnakeCase (line 947) | convertToSnakeCase(str) {
method constructor (line 952) | constructor(sequelize, supportsUnaccent, query) {
method normalize (line 965) | normalize(value) {
method init (line 973) | async init() {
method matchExpression (line 990) | matchExpression(column) {
FILE: server/Logger.js
class Logger (line 5) | class Logger {
method constructor (line 6) | constructor() {
method timestamp (line 19) | get timestamp() {
method levelString (line 23) | get levelString() {
method source (line 30) | get source() {
method getLogLevelString (line 35) | getLogLevelString(level) {
method addSocketListener (line 44) | addSocketListener(socket, level) {
method removeSocketListener (line 61) | removeSocketListener(socketId) {
method #logToFileAndListeners (line 72) | async #logToFileAndListeners(level, levelName, args, src) {
method setLogLevel (line 95) | setLogLevel(level) {
method #log (line 110) | #log(levelName, source, ...args) {
method trace (line 118) | trace(...args) {
method debug (line 122) | debug(...args) {
method info (line 126) | info(...args) {
method warn (line 130) | warn(...args) {
method error (line 134) | error(...args) {
method fatal (line 138) | fatal(...args) {
method note (line 142) | note(...args) {
FILE: server/Server.js
class Server (line 48) | class Server {
method constructor (line 49) | constructor(SOURCE, PORT, HOST, CONFIG_PATH, METADATA_PATH, ROUTER_BAS...
method authMiddleware (line 129) | authMiddleware(req, res, next) {
method cancelLibraryScan (line 134) | cancelLibraryScan(libraryId) {
method init (line 142) | async init() {
method initProcessEventListeners (line 190) | initProcessEventListeners() {
method start (line 219) | async start() {
method initializeServer (line 431) | async initializeServer(req, res) {
method cleanUserData (line 445) | async cleanUserData() {
method stop (line 504) | async stop() {
FILE: server/SocketAuthority.js
class SocketAuthority (line 15) | class SocketAuthority {
method constructor (line 16) | constructor() {
method getUsersOnline (line 29) | getUsersOnline() {
method getClientsForUser (line 46) | getClientsForUser(userId) {
method emitter (line 56) | emitter(evt, data, filter = null) {
method clientEmitter (line 67) | clientEmitter(userId, evt, data) {
method adminEmitter (line 80) | adminEmitter(evt, data) {
method libraryItemEmitter (line 95) | libraryItemEmitter(evt, libraryItem) {
method libraryItemsEmitter (line 110) | libraryItemsEmitter(evt, libraryItems) {
method close (line 129) | async close() {
method initialize (line 145) | initialize(Server) {
method authenticateSocket (line 249) | async authenticateSocket(socket, token) {
method cancelScan (line 308) | cancelScan(id) {
method handleCoverSearch (line 318) | async handleCoverSearch(socket, payload) {
method handleCancelCoverSearch (line 382) | handleCancelCoverSearch(socket, requestId) {
method cancelSocketCoverSearches (line 401) | cancelSocketCoverSearches(socketId) {
FILE: server/Watcher.js
class FolderWatcher (line 17) | class FolderWatcher extends EventEmitter {
method constructor (line 18) | constructor() {
method pendingFilePaths (line 45) | get pendingFilePaths() {
method buildLibraryWatcher (line 53) | buildLibraryWatcher(library) {
method initWatcher (line 108) | initWatcher(libraries) {
method addLibrary (line 120) | addLibrary(library) {
method updateLibrary (line 129) | updateLibrary(library) {
method removeLibrary (line 166) | removeLibrary(library) {
method close (line 178) | close() {
method onFileAdded (line 188) | onFileAdded(libraryId, path) {
method onFileRemoved (line 209) | onFileRemoved(libraryId, path) {
method onFileRename (line 223) | onFileRename(libraryId, pathFrom, pathTo) {
method waitForFileToAdd (line 239) | async waitForFileToAdd(path, lastMTimeMs = 0, loop = 0) {
method addFileUpdate (line 268) | addFileUpdate(libraryId, path, type) {
method handlePendingFileUpdatesTimeout (line 326) | handlePendingFileUpdatesTimeout() {
method checkShouldIgnorePath (line 356) | checkShouldIgnorePath(path) {
method checkShouldIgnoreFilePath (line 369) | checkShouldIgnoreFilePath(path) {
method cleanDirPath (line 379) | cleanDirPath(path) {
method addIgnoreDir (line 389) | addIgnoreDir(path) {
method removeIgnoreDir (line 407) | removeIgnoreDir(path) {
FILE: server/auth/LocalAuthStrategy.js
class LocalAuthStrategy (line 12) | class LocalAuthStrategy {
method constructor (line 13) | constructor() {
method getStrategy (line 22) | getStrategy() {
method init (line 32) | init() {
method unuse (line 39) | unuse() {
method verifyCredentials (line 51) | async verifyCredentials(req, username, password, done) {
method logFailedLoginAttempt (line 105) | logFailedLoginAttempt(req, username, message) {
method hashPassword (line 115) | hashPassword(password) {
method comparePassword (line 133) | comparePassword(password, user) {
method changePassword (line 145) | async changePassword(user, password, newPassword) {
FILE: server/auth/OidcAuthStrategy.js
class OidcAuthStrategy (line 11) | class OidcAuthStrategy {
method constructor (line 12) | constructor() {
method getStrategy (line 24) | getStrategy() {
method getClient (line 44) | getClient() {
method getScope (line 75) | getScope() {
method init (line 89) | init() {
method unuse (line 100) | unuse() {
method verifyCallback (line 112) | async verifyCallback(tokenset, userinfo, done) {
method validateGroupClaim (line 169) | validateGroupClaim(userinfo) {
method setUserGroup (line 187) | async setUserGroup(user, userinfo) {
method updateUserPermissions (line 225) | async updateUserPermissions(user, userinfo) {
method generatePkce (line 247) | generatePkce(req, isMobileFlow) {
method isValidRedirectUri (line 275) | isValidRedirectUri(uri) {
method getAuthorizationUrl (line 286) | getAuthorizationUrl(req) {
method getEndSessionUrl (line 390) | getEndSessionUrl(req, idToken, authMethod) {
method getIssuerConfig (line 435) | async getIssuerConfig(issuerUrl) {
method handleMobileRedirect (line 479) | handleMobileRedirect(req, res) {
method isValidWebCallbackUrl (line 514) | isValidWebCallbackUrl(callbackUrl, req) {
FILE: server/auth/TokenManager.js
class TokenManager (line 9) | class TokenManager {
method constructor (line 13) | constructor() {
method TokenSecret (line 27) | get TokenSecret() {
method initTokenSecret (line 35) | async initTokenSecret() {
method setRefreshTokenCookie (line 58) | setRefreshTokenCookie(req, res, refreshToken) {
method validateAccessToken (line 76) | static validateAccessToken(token) {
method generateAccessToken (line 92) | static generateAccessToken(user) {
method generateAccessToken (line 104) | generateAccessToken(user) {
method generateTempAccessToken (line 114) | generateTempAccessToken(user) {
method generateRefreshToken (line 137) | generateRefreshToken(user) {
method createTokensAndSession (line 161) | async createTokensAndSession(user, req) {
method rotateTokensForSession (line 188) | async rotateTokensForSession(session, user, req, res) {
method jwtAuthCheck (line 216) | async jwtAuthCheck(jwt_payload, done) {
method handleRefreshToken (line 285) | async handleRefreshToken(refreshToken, req, res) {
method invalidateJwtSessionsForUser (line 371) | async invalidateJwtSessionsForUser(user, req, res) {
method invalidateRefreshToken (line 408) | async invalidateRefreshToken(refreshToken) {
FILE: server/controllers/ApiKeyController.js
class ApiKeyController (line 13) | class ApiKeyController {
method constructor (line 14) | constructor() {}
method getAll (line 22) | async getAll(req, res) {
method create (line 50) | async create(req, res) {
method update (line 112) | async update(req, res) {
method delete (line 179) | async delete(req, res) {
method middleware (line 197) | middleware(req, res, next) {
FILE: server/controllers/AuthorController.js
class AuthorController (line 31) | class AuthorController {
method constructor (line 32) | constructor() {}
method findOne (line 40) | async findOne(req, res) {
method update (line 96) | async update(req, res) {
method delete (line 233) | async delete(req, res) {
method uploadImage (line 267) | async uploadImage(req, res) {
method deleteImage (line 313) | async deleteImage(req, res) {
method match (line 337) | async match(req, res) {
method getImage (line 391) | async getImage(req, res) {
method middleware (line 427) | async middleware(req, res, next) {
FILE: server/controllers/BackupController.js
class BackupController (line 15) | class BackupController {
method constructor (line 16) | constructor() {}
method getAll (line 26) | getAll(req, res) {
method create (line 42) | create(req, res) {
method delete (line 54) | async delete(req, res) {
method upload (line 70) | upload(req, res) {
method updatePath (line 87) | async updatePath(req, res) {
method download (line 134) | download(req, res) {
method apply (line 154) | apply(req, res) {
method middleware (line 164) | middleware(req, res, next) {
FILE: server/controllers/CacheController.js
class CacheController (line 11) | class CacheController {
method constructor (line 12) | constructor() {}
method purgeCache (line 20) | async purgeCache(req, res) {
method purgeItemsCache (line 34) | async purgeItemsCache(req, res) {
FILE: server/controllers/CollectionController.js
class CollectionController (line 22) | class CollectionController {
method constructor (line 23) | constructor() {}
method create (line 32) | async create(req, res) {
method findAll (line 110) | async findAll(req, res) {
method findOne (line 123) | async findOne(req, res) {
method update (line 142) | async update(req, res) {
method delete (line 209) | async delete(req, res) {
method addBook (line 229) | async addBook(req, res) {
method removeBook (line 266) | async removeBook(req, res) {
method addBatch (line 313) | async addBatch(req, res) {
method removeBatch (line 375) | async removeBatch(req, res) {
method middleware (line 428) | async middleware(req, res, next) {
FILE: server/controllers/CustomMetadataProviderController.js
class CustomMetadataProviderController (line 15) | class CustomMetadataProviderController {
method constructor (line 16) | constructor() {}
method getAll (line 24) | async getAll(req, res) {
method create (line 38) | async create(req, res) {
method delete (line 72) | async delete(req, res) {
method middleware (line 108) | async middleware(req, res, next) {
FILE: server/controllers/EmailController.js
class EmailController (line 13) | class EmailController {
method constructor (line 14) | constructor() {}
method getSettings (line 22) | getSettings(req, res) {
method updateSettings (line 34) | async updateSettings(req, res) {
method sendTest (line 52) | async sendTest(req, res) {
method updateEReaderDevices (line 62) | async updateEReaderDevices(req, res) {
method sendEBookToDevice (line 96) | async sendEBookToDevice(req, res) {
method adminMiddleware (line 133) | adminMiddleware(req, res, next) {
FILE: server/controllers/FileSystemController.js
class FileSystemController (line 16) | class FileSystemController {
method constructor (line 17) | constructor() {}
method getPaths (line 24) | async getPaths(req, res) {
method checkPathExists (line 85) | async checkPathExists(req, res) {
FILE: server/controllers/LibraryController.js
class LibraryController (line 40) | class LibraryController {
method constructor (line 41) | constructor() {}
method create (line 50) | async create(req, res) {
method findAll (line 188) | async findAll(req, res) {
method findOne (line 220) | async findOne(req, res) {
method getEpisodeDownloadQueue (line 242) | async getEpisodeDownloadQueue(req, res) {
method update (line 255) | async update(req, res) {
method delete (line 525) | async delete(req, res) {
method getLibraryItems (line 604) | async getLibraryItems(req, res) {
method removeLibraryItemsWithIssues (line 650) | async removeLibraryItemsWithIssues(req, res) {
method getAllSeriesForLibrary (line 742) | async getAllSeriesForLibrary(req, res) {
method getSeriesForLibrary (line 778) | async getSeriesForLibrary(req, res) {
method getCollectionsForLibrary (line 814) | async getCollectionsForLibrary(req, res) {
method getUserPlaylistsForLibrary (line 853) | async getUserPlaylistsForLibrary(req, res) {
method getLibraryFilterData (line 878) | async getLibraryFilterData(req, res) {
method getUserPersonalizedShelves (line 890) | async getUserPersonalizedShelves(req, res) {
method reorder (line 913) | async reorder(req, res) {
method search (line 959) | async search(req, res) {
method stats (line 978) | async stats(req, res) {
method getAuthors (line 1021) | async getAuthors(req, res) {
method getNarrators (line 1110) | async getNarrators(req, res) {
method updateNarrator (line 1157) | async updateNarrator(req, res) {
method removeNarrator (line 1205) | async removeNarrator(req, res) {
method matchAll (line 1245) | async matchAll(req, res) {
method scan (line 1262) | async scan(req, res) {
method getRecentEpisodes (line 1283) | async getRecentEpisodes(req, res) {
method getOPMLFile (line 1306) | async getOPMLFile(req, res) {
method removeAllMetadataFiles (line 1333) | async removeAllMetadataFiles(req, res) {
method getPodcastTitles (line 1386) | async getPodcastTitles(req, res) {
method downloadMultiple (line 1422) | async downloadMultiple(req, res) {
method middleware (line 1467) | async middleware(req, res, next) {
FILE: server/controllers/LibraryItemController.js
class LibraryItemController (line 39) | class LibraryItemController {
method constructor (line 40) | constructor() {}
method findOne (line 51) | async findOne(req, res) {
method delete (line 95) | async delete(req, res) {
method handleDownloadError (line 133) | static handleDownloadError(error, res) {
method download (line 150) | async download(req, res) {
method updateMedia (line 189) | async updateMedia(req, res) {
method uploadCover (line 271) | async uploadCover(req, res, updateAndReturnJson = true) {
method updateCover (line 317) | async updateCover(req, res) {
method removeCover (line 349) | async removeCover(req, res) {
method getCover (line 373) | async getCover(req, res) {
method startPlaybackSession (line 415) | startPlaybackSession(req, res) {
method startEpisodePlaybackSession (line 432) | startEpisodePlaybackSession(req, res) {
method updateTracks (line 453) | async updateTracks(req, res) {
method match (line 498) | async match(req, res) {
method batchDelete (line 530) | async batchDelete(req, res) {
method batchUpdate (line 595) | async batchUpdate(req, res) {
method batchGet (line 690) | async batchGet(req, res) {
method batchQuickMatch (line 709) | async batchQuickMatch(req, res) {
method batchScan (line 766) | async batchScan(req, res) {
method scan (line 806) | async scan(req, res) {
method getMetadataObject (line 830) | getMetadataObject(req, res) {
method updateMediaChapters (line 850) | async updateMediaChapters(req, res) {
method getFFprobeData (line 913) | async getFFprobeData(req, res) {
method getLibraryFile (line 935) | async getLibraryFile(req, res) {
method deleteLibraryFile (line 958) | async deleteLibraryFile(req, res) {
method downloadLibraryFile (line 1019) | async downloadLibraryFile(req, res) {
method getEBookFile (line 1067) | async getEBookFile(req, res) {
method updateEbookFileStatus (line 1111) | async updateEbookFileStatus(req, res) {
method middleware (line 1166) | async middleware(req, res, next) {
FILE: server/controllers/MeController.js
class MeController (line 16) | class MeController {
method constructor (line 17) | constructor() {}
method getCurrentUser (line 25) | getCurrentUser(req, res) {
method getListeningSessions (line 37) | async getListeningSessions(req, res) {
method getItemListeningSessions (line 65) | async getItemListeningSessions(req, res) {
method getListeningStats (line 108) | async getListeningStats(req, res) {
method getMediaProgress (line 119) | async getMediaProgress(req, res) {
method removeMediaProgress (line 133) | async removeMediaProgress(req, res) {
method createUpdateMediaProgress (line 155) | async createUpdateMediaProgress(req, res) {
method batchUpdateMediaProgress (line 177) | async batchUpdateMediaProgress(req, res) {
method createBookmark (line 207) | async createBookmark(req, res) {
method updateBookmark (line 240) | async updateBookmark(req, res) {
method removeBookmark (line 278) | async removeBookmark(req, res) {
method updatePassword (line 316) | async updatePassword(req, res) {
method getAllLibraryItemsInProgress (line 345) | async getAllLibraryItemsInProgress(req, res) {
method removeSeriesFromContinueListening (line 392) | async removeSeriesFromContinueListening(req, res) {
method readdSeriesFromContinueListening (line 411) | async readdSeriesFromContinueListening(req, res) {
method removeItemFromContinueListening (line 430) | async removeItemFromContinueListening(req, res) {
method updateUserEReaderDevices (line 455) | async updateUserEReaderDevices(req, res) {
method getStatsForYear (line 507) | async getStatsForYear(req, res) {
FILE: server/controllers/MiscController.js
class MiscController (line 25) | class MiscController {
method constructor (line 26) | constructor() {}
method handleUpload (line 35) | async handleUpload(req, res) {
method getTasks (line 109) | getTasks(req, res) {
method updateServerSettings (line 132) | async updateServerSettings(req, res) {
method updateSortingPrefixes (line 169) | async updateSortingPrefixes(req, res) {
method authorize (line 271) | async authorize(req, res) {
method getAllTags (line 283) | async getAllTags(req, res) {
method renameTag (line 327) | async renameTag(req, res) {
method deleteTag (line 382) | async deleteTag(req, res) {
method getAllGenres (line 422) | async getAllGenres(req, res) {
method renameGenre (line 465) | async renameGenre(req, res) {
method deleteGenre (line 520) | async deleteGenre(req, res) {
method updateWatchedPath (line 565) | updateWatchedPath(req, res) {
method validateCronExpression (line 602) | validateCronExpression(req, res) {
method getAuthSettings (line 623) | getAuthSettings(req, res) {
method updateAuthSettings (line 638) | async updateAuthSettings(req, res) {
method getAdminStatsForYear (line 743) | async getAdminStatsForYear(req, res) {
method getLoggerData (line 764) | async getLoggerData(req, res) {
FILE: server/controllers/NotificationController.js
class NotificationController (line 13) | class NotificationController {
method constructor (line 14) | constructor() {}
method get (line 25) | get(req, res) {
method update (line 38) | async update(req, res) {
method getData (line 55) | getData(req, res) {
method fireTestEvent (line 67) | async fireTestEvent(req, res) {
method createNotification (line 78) | async createNotification(req, res) {
method deleteNotification (line 93) | async deleteNotification(req, res) {
method updateNotification (line 106) | async updateNotification(req, res) {
method sendNotificationTest (line 122) | async sendNotificationTest(req, res) {
method middleware (line 137) | middleware(req, res, next) {
FILE: server/controllers/PlaylistController.js
class PlaylistController (line 19) | class PlaylistController {
method constructor (line 20) | constructor() {}
method create (line 29) | async create(req, res) {
method findAllForUser (line 134) | async findAllForUser(req, res) {
method findOne (line 147) | async findOne(req, res) {
method update (line 161) | async update(req, res) {
method delete (line 260) | async delete(req, res) {
method addItem (line 278) | async addItem(req, res) {
method removeItem (line 342) | async removeItem(req, res) {
method addBatch (line 389) | async addBatch(req, res) {
method removeBatch (line 458) | async removeBatch(req, res) {
method createFromCollection (line 506) | async createFromCollection(req, res) {
method middleware (line 566) | async middleware(req, res, next) {
FILE: server/controllers/PodcastController.js
class PodcastController (line 29) | class PodcastController {
method create (line 39) | async create(req, res) {
method getPodcastFeed (line 184) | async getPodcastFeed(req, res) {
method getFeedsFromOPMLText (line 210) | async getFeedsFromOPMLText(req, res) {
method bulkCreatePodcastsFromOpmlFeedUrls (line 233) | async bulkCreatePodcastsFromOpmlFeedUrls(req, res) {
method checkNewEpisodes (line 268) | async checkNewEpisodes(req, res) {
method clearEpisodeDownloadQueue (line 295) | clearEpisodeDownloadQueue(req, res) {
method getEpisodeDownloads (line 312) | getEpisodeDownloads(req, res) {
method findEpisode (line 326) | async findEpisode(req, res) {
method downloadEpisodes (line 351) | async downloadEpisodes(req, res) {
method quickMatchEpisodes (line 374) | async quickMatchEpisodes(req, res) {
method updateEpisode (line 397) | async updateEpisode(req, res) {
method getEpisode (line 452) | async getEpisode(req, res) {
method removeEpisode (line 471) | async removeEpisode(req, res) {
method middleware (line 533) | async middleware(req, res, next) {
FILE: server/controllers/RSSFeedController.js
class RSSFeedController (line 14) | class RSSFeedController {
method constructor (line 15) | constructor() {}
method getAll (line 25) | async getAll(req, res) {
method openRSSFeedForItem (line 41) | async openRSSFeedForItem(req, res) {
method openRSSFeedForCollection (line 90) | async openRSSFeedForCollection(req, res) {
method openRSSFeedForSeries (line 133) | async openRSSFeedForSeries(req, res) {
method closeRSSFeed (line 176) | async closeRSSFeed(req, res) {
method middleware (line 194) | middleware(req, res, next) {
FILE: server/controllers/SearchController.js
class SearchController (line 38) | class SearchController {
method constructor (line 39) | constructor() {}
method fetchLibraryItem (line 47) | static async fetchLibraryItem(id) {
method mapCustomProviders (line 60) | static mapCustomProviders(providers) {
method formatProvider (line 72) | static formatProvider(providerValue) {
method findBooks (line 85) | async findBooks(req, res) {
method findCovers (line 113) | async findCovers(req, res) {
method findPodcasts (line 141) | async findPodcasts(req, res) {
method findAuthor (line 165) | async findAuthor(req, res) {
method findChapters (line 186) | async findChapters(req, res) {
method getAllProviders (line 220) | async getAllProviders(req, res) {
FILE: server/controllers/SeriesController.js
class SeriesController (line 22) | class SeriesController {
method constructor (line 23) | constructor() {}
method findOne (line 35) | async findOne(req, res) {
method update (line 70) | async update(req, res) {
method middleware (line 95) | async middleware(req, res, next) {
FILE: server/controllers/SessionController.js
class SessionController (line 18) | class SessionController {
method constructor (line 19) | constructor() {}
method getAllWithUserData (line 29) | async getAllWithUserData(req, res) {
method getOpenSessions (line 122) | async getOpenSessions(req, res) {
method getOpenSession (line 152) | async getOpenSession(req, res) {
method sync (line 166) | sync(req, res) {
method close (line 178) | close(req, res) {
method delete (line 192) | async delete(req, res) {
method batchDelete (line 213) | async batchDelete(req, res) {
method syncLocal (line 254) | syncLocal(req, res) {
method syncLocalSessions (line 266) | syncLocalSessions(req, res) {
method getTrack (line 279) | async getTrack(req, res) {
method openSessionMiddleware (line 338) | openSessionMiddleware(req, res, next) {
method middleware (line 357) | async middleware(req, res, next) {
FILE: server/controllers/ShareController.js
class ShareController (line 22) | class ShareController {
method constructor (line 23) | constructor() {}
method getMediaItemShareBySlug (line 35) | async getMediaItemShareBySlug(req, res) {
method getMediaItemShareCoverImage (line 136) | async getMediaItemShareCoverImage(req, res) {
method getMediaItemShareAudioTrack (line 176) | async getMediaItemShareAudioTrack(req, res) {
method downloadMediaItemShare (line 222) | async downloadMediaItemShare(req, res) {
method updateMediaItemShareProgress (line 281) | async updateMediaItemShareProgress(req, res) {
method createMediaItemShare (line 315) | async createMediaItemShare(req, res) {
method deleteMediaItemShare (line 380) | async deleteMediaItemShare(req, res) {
FILE: server/controllers/StatsController.js
class StatsController (line 13) | class StatsController {
method constructor (line 14) | constructor() {}
method getServerStats (line 23) | async getServerStats(req, res) {
method getAdminStatsForYear (line 50) | async getAdminStatsForYear(req, res) {
method middleware (line 66) | async middleware(req, res, next) {
FILE: server/controllers/ToolsController.js
class ToolsController (line 17) | class ToolsController {
method constructor (line 18) | constructor() {}
method encodeM4b (line 29) | async encodeM4b(req, res) {
method cancelM4bEncode (line 66) | async cancelM4bEncode(req, res) {
method embedAudioFileMetadata (line 84) | async embedAudioFileMetadata(req, res) {
method batchEmbedMetadata (line 112) | async batchEmbedMetadata(req, res) {
method middleware (line 159) | async middleware(req, res, next) {
FILE: server/controllers/UserController.js
class UserController (line 21) | class UserController {
method constructor (line 22) | constructor() {}
method findAll (line 29) | async findAll(req, res) {
method findOne (line 59) | async findOne(req, res) {
method create (line 116) | async create(req, res) {
method update (line 205) | async update(req, res) {
method delete (line 365) | async delete(req, res) {
method unlinkFromOpenID (line 415) | async unlinkFromOpenID(req, res) {
method getListeningSessions (line 435) | async getListeningSessions(req, res) {
method getListeningStats (line 472) | async getListeningStats(req, res) {
method getOnlineUsers (line 485) | async getOnlineUsers(req, res) {
method middleware (line 502) | async middleware(req, res, next) {
FILE: server/finders/AuthorFinder.js
class AuthorFinder (line 8) | class AuthorFinder {
method constructor (line 9) | constructor() {
method findAuthorByASIN (line 13) | findAuthorByASIN(asin, region) {
method findAuthorByName (line 25) | async findAuthorByName(name, region, options = {}) {
method saveAuthorImage (line 43) | async saveAuthorImage(authorId, url) {
FILE: server/finders/BookFinder.js
class BookFinder (line 13) | class BookFinder {
method constructor (line 16) | constructor() {
method findByISBN (line 31) | async findByISBN(isbn) {
method filterSearchResults (line 39) | filterSearchResults(books, title, author, maxTitleDistance, maxAuthorD...
method getOpenLibResults (line 109) | async getOpenLibResults(title, author, maxTitleDistance, maxAuthorDist...
method getGoogleBooksResults (line 133) | async getGoogleBooksResults(title, author) {
method getFantLabResults (line 150) | async getFantLabResults(title, author) {
method getAudiobookCoversResults (line 166) | async getAudiobookCoversResults(search) {
method getiTunesAudiobooksResults (line 177) | async getiTunesAudiobooksResults(title) {
method getAudibleResults (line 189) | async getAudibleResults(title, author, asin, provider) {
method getCustomProviderResults (line 205) | async getCustomProviderResults(title, author, isbn, providerSlug) {
method constructor (line 217) | constructor(cleanAuthor) {
method add (line 225) | add(title) {
method size (line 261) | get size() {
method getCandidates (line 265) | getCandidates() {
method delete (line 284) | delete(title) {
method #removeAuthorFromTitle (line 288) | #removeAuthorFromTitle(title) {
method constructor (line 301) | constructor(cleanAuthor, audnexus) {
method validateAuthor (line 308) | validateAuthor(name, region = '', maxLevenshtein = 2) {
method add (line 322) | add(author) {
method size (line 328) | get size() {
method agressivelyCleanAuthor (line 332) | get agressivelyCleanAuthor() {
method getCandidates (line 340) | async getCandidates() {
method delete (line 357) | delete(author) {
method search (line 374) | async search(libraryItem, provider, title, author, isbn, asin, options...
method calculateMatchConfidence (line 462) | calculateMatchConfidence(book, libraryItemDurationMinutes, actualTitle...
method runSearch (line 578) | async runSearch(title, author, provider, asin, maxTitleDistance, maxAu...
method findCovers (line 608) | async findCovers(provider, title, author, options = {}) {
method findChapters (line 642) | findChapters(asin, region) {
function hasSubtitle (line 648) | function hasSubtitle(title) {
function stripSubtitle (line 651) | function stripSubtitle(title) {
function replaceAccentedChars (line 660) | function replaceAccentedChars(str) {
function cleanTitleForCompares (line 669) | function cleanTitleForCompares(title, keepSubtitle = false) {
function cleanAuthorForCompares (line 686) | function cleanAuthorForCompares(author) {
function stripRedundantSpaces (line 700) | function stripRedundantSpaces(str) {
FILE: server/finders/PodcastFinder.js
class PodcastFinder (line 4) | class PodcastFinder {
method constructor (line 5) | constructor() {
method search (line 15) | async search(term, options = {}) {
method findCovers (line 27) | async findCovers(term) {
FILE: server/libs/archiver/archiverUtils/balancedMatch/index.js
function balanced (line 8) | function balanced(a, b, str) {
function maybeMatch (line 29) | function maybeMatch(reg, str) {
function range (line 41) | function range(a, b, str) {
FILE: server/libs/archiver/archiverUtils/braceExpansion/index.js
function numeric (line 12) | function numeric(str) {
function escapeBraces (line 21) | function escapeBraces(str) {
function unescapeBraces (line 32) | function unescapeBraces(str) {
function parseCommaParts (line 46) | function parseCommaParts(str) {
function expandTop (line 74) | function expandTop(str) {
function embrace (line 94) | function embrace(str) {
function isPadded (line 101) | function isPadded(el) {
function lte (line 109) | function lte(i, y) {
function gte (line 117) | function gte(i, y) {
function expand (line 125) | function expand(str, isTop) {
FILE: server/libs/archiver/archiverUtils/fsRealpath/index.js
function newError (line 14) | function newError(er) {
function realpath (line 22) | function realpath(p, cache, cb) {
function realpathSync (line 40) | function realpathSync(p, cache) {
FILE: server/libs/archiver/archiverUtils/fsRealpath/old.js
function rethrow (line 31) | function rethrow() {
function maybeCallback (line 66) | function maybeCallback(cb) {
function start (line 108) | function start() {
function start (line 210) | function start() {
function LOOP (line 232) | function LOOP() {
function gotStat (line 260) | function gotStat(err, stat) {
function gotTarget (line 289) | function gotTarget(err, target, base) {
function gotResolvedLink (line 297) | function gotResolvedLink(resolvedLink) {
FILE: server/libs/archiver/archiverUtils/glob/common.js
function ownProp (line 9) | function ownProp(obj, field) {
function alphasort (line 19) | function alphasort(a, b) {
function setupIgnores (line 23) | function setupIgnores(self, options) {
function ignoreMap (line 35) | function ignoreMap(pattern) {
function setopts (line 48) | function setopts(self, pattern, options) {
function finish (line 122) | function finish(self) {
function mark (line 179) | function mark(self, p) {
function makeAbs (line 203) | function makeAbs(self, f) {
function isIgnored (line 224) | function isIgnored(self, path) {
function childrenIgnored (line 233) | function childrenIgnored(self, path) {
FILE: server/libs/archiver/archiverUtils/glob/index.js
function glob (line 61) | function glob(pattern, options, cb) {
function extend (line 80) | function extend(origin, add) {
function Glob (line 116) | function Glob(pattern, options, cb) {
function next (line 210) | function next() {
function lstatcb_ (line 507) | function lstatcb_(er, lstat) {
function readdirCb (line 549) | function readdirCb(self, abs, cb) {
function lstatcb_ (line 752) | function lstatcb_(er, lstat) {
FILE: server/libs/archiver/archiverUtils/glob/sync.js
function globSync (line 15) | function globSync(pattern, options) {
function GlobSync (line 23) | function GlobSync(pattern, options) {
FILE: server/libs/archiver/archiverUtils/inflight/index.js
function inflight (line 7) | function inflight(key, cb) {
function makeres (line 17) | function makeres(key) {
function slice (line 48) | function slice(args) {
FILE: server/libs/archiver/archiverUtils/lazystream/index.js
function beforeFirstCall (line 19) | function beforeFirstCall(instance, method, callback) {
function Readable (line 27) | function Readable(fn, options) {
function Writable (line 43) | function Writable(fn, options) {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/_stream_duplex.js
function Duplex (line 66) | function Duplex(options) {
function onend (line 93) | function onend() {
function onEndNT (line 103) | function onEndNT(self) {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/_stream_passthrough.js
function PassThrough (line 39) | function PassThrough(options) {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/_stream_readable.js
function _uint8ArrayToBuffer (line 60) | function _uint8ArrayToBuffer(chunk) {
function _isUint8Array (line 63) | function _isUint8Array(obj) {
function prependListener (line 93) | function prependListener(emitter, event, fn) {
function ReadableState (line 105) | function ReadableState(options, stream) {
function Readable (line 182) | function Readable(options) {
function readableAddChunk (line 257) | function readableAddChunk(stream, chunk, encoding, addToFront, skipChunk...
function addChunk (line 293) | function addChunk(stream, state, chunk, addToFront) {
function chunkInvalid (line 307) | function chunkInvalid(state, chunk) {
function needMoreData (line 322) | function needMoreData(state) {
function computeNewHighWaterMark (line 340) | function computeNewHighWaterMark(n) {
function howMuchToRead (line 359) | function howMuchToRead(n, state) {
function onEofChunk (line 478) | function onEofChunk(stream, state) {
function emitReadable (line 496) | function emitReadable(stream) {
function emitReadable_ (line 506) | function emitReadable_(stream) {
function maybeReadMore (line 518) | function maybeReadMore(stream, state) {
function maybeReadMore_ (line 525) | function maybeReadMore_(stream, state) {
function onunpipe (line 569) | function onunpipe(readable, unpipeInfo) {
function onend (line 579) | function onend() {
function cleanup (line 592) | function cleanup() {
function ondata (line 620) | function ondata(chunk) {
function onerror (line 640) | function onerror(er) {
function onclose (line 651) | function onclose() {
function onfinish (line 656) | function onfinish() {
function unpipe (line 663) | function unpipe() {
function pipeOnDrain (line 680) | function pipeOnDrain(src) {
function nReadingNextTick (line 767) | function nReadingNextTick(self) {
function resume (line 784) | function resume(stream, state) {
function resume_ (line 791) | function resume_(stream, state) {
function flow (line 814) | function flow(stream) {
function fromList (line 900) | function fromList(n, state) {
function fromListPartial (line 920) | function fromListPartial(n, list, hasStrings) {
function copyFromBufferString (line 940) | function copyFromBufferString(n, list) {
function copyFromBuffer (line 969) | function copyFromBuffer(n, list) {
function endReadable (line 996) | function endReadable(stream) {
function endReadableNT (line 1009) | function endReadableNT(state, stream) {
function indexOf (line 1018) | function indexOf(xs, x) {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/_stream_transform.js
function afterTransform (line 78) | function afterTransform(er, data) {
function Transform (line 103) | function Transform(options) {
function prefinish (line 135) | function prefinish() {
function done (line 202) | function done(stream, er, data) {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/_stream_writable.js
function WriteReq (line 37) | function WriteReq(chunk, encoding, cb) {
function CorkedRequest (line 46) | function CorkedRequest(state) {
function _uint8ArrayToBuffer (line 87) | function _uint8ArrayToBuffer(chunk) {
function _isUint8Array (line 90) | function _isUint8Array(obj) {
function nop (line 100) | function nop() { }
function WritableState (line 102) | function WritableState(options, stream) {
function Writable (line 252) | function Writable(options) {
function writeAfterEnd (line 289) | function writeAfterEnd(stream, cb) {
function validChunk (line 299) | function validChunk(stream, state, chunk, cb) {
function decodeChunk (line 366) | function decodeChunk(state, chunk, encoding) {
function writeOrBuffer (line 386) | function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) {
function doWrite (line 425) | function doWrite(stream, state, writev, len, chunk, encoding, cb) {
function onwriteError (line 434) | function onwriteError(stream, state, sync, er, cb) {
function onwriteStateUpdate (line 458) | function onwriteStateUpdate(state) {
function onwrite (line 465) | function onwrite(stream, er) {
function afterWrite (line 490) | function afterWrite(stream, state, finished, cb) {
function onwriteDrain (line 500) | function onwriteDrain(stream, state) {
function clearBuffer (line 508) | function clearBuffer(stream, state) {
function needFinish (line 599) | function needFinish(state) {
function callFinal (line 602) | function callFinal(stream, state) {
function prefinish (line 613) | function prefinish(stream, state) {
function finishMaybe (line 626) | function finishMaybe(stream, state) {
function endWritable (line 638) | function endWritable(stream, state, cb) {
function onCorkedFinish (line 648) | function onCorkedFinish(corkReq, state, err) {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/internal/streams/BufferList.js
function _classCallCheck (line 3) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function copyBuffer (line 8) | function copyBuffer(src, target, offset) {
function BufferList (line 13) | function BufferList() {
FILE: server/libs/archiver/archiverUtils/lazystream/readable-stream/lib/internal/streams/destroy.js
function destroy (line 10) | function destroy(err, cb) {
function undestroy (line 51) | function undestroy() {
function emitErrorNT (line 68) | function emitErrorNT(self, err) {
FILE: server/libs/archiver/archiverUtils/lodash.difference/index.js
function apply (line 52) | function apply(func, thisArg, args) {
function arrayIncludes (line 71) | function arrayIncludes(array, value) {
function arrayIncludesWith (line 85) | function arrayIncludesWith(array, value, comparator) {
function arrayMap (line 106) | function arrayMap(array, iteratee) {
function arrayPush (line 125) | function arrayPush(array, values) {
function baseFindIndex (line 147) | function baseFindIndex(array, predicate, fromIndex, fromRight) {
function baseIndexOf (line 168) | function baseIndexOf(array, value, fromIndex) {
function baseIsNaN (line 190) | function baseIsNaN(value) {
function baseUnary (line 201) | function baseUnary(func) {
function cacheHas (line 215) | function cacheHas(cache, key) {
function getValue (line 227) | function getValue(object, key) {
function isHostObject (line 238) | function isHostObject(value) {
function Hash (line 303) | function Hash(entries) {
function hashClear (line 321) | function hashClear() {
function hashDelete (line 335) | function hashDelete(key) {
function hashGet (line 348) | function hashGet(key) {
function hashHas (line 366) | function hashHas(key) {
function hashSet (line 381) | function hashSet(key, value) {
function ListCache (line 401) | function ListCache(entries) {
function listCacheClear (line 419) | function listCacheClear() {
function listCacheDelete (line 432) | function listCacheDelete(key) {
function listCacheGet (line 457) | function listCacheGet(key) {
function listCacheHas (line 473) | function listCacheHas(key) {
function listCacheSet (line 487) | function listCacheSet(key, value) {
function MapCache (line 513) | function MapCache(entries) {
function mapCacheClear (line 531) | function mapCacheClear() {
function mapCacheDelete (line 548) | function mapCacheDelete(key) {
function mapCacheGet (line 561) | function mapCacheGet(key) {
function mapCacheHas (line 574) | function mapCacheHas(key) {
function mapCacheSet (line 588) | function mapCacheSet(key, value) {
function SetCache (line 608) | function SetCache(values) {
function setCacheAdd (line 628) | function setCacheAdd(value) {
function setCacheHas (line 642) | function setCacheHas(value) {
function assocIndexOf (line 658) | function assocIndexOf(array, key) {
function baseDifference (line 679) | function baseDifference(array, values, iteratee, comparator) {
function baseFlatten (line 735) | function baseFlatten(array, depth, predicate, isStrict, result) {
function baseIsNative (line 766) | function baseIsNative(value) {
function baseRest (line 782) | function baseRest(func, start) {
function getMapData (line 811) | function getMapData(map, key) {
function getNative (line 826) | function getNative(object, key) {
function isFlattenable (line 838) | function isFlattenable(value) {
function isKeyable (line 850) | function isKeyable(value) {
function isMasked (line 864) | function isMasked(func) {
function toSource (line 875) | function toSource(func) {
function eq (line 946) | function eq(value, other) {
function isArguments (line 968) | function isArguments(value) {
function isArrayLike (line 1024) | function isArrayLike(value) {
function isArrayLikeObject (line 1053) | function isArrayLikeObject(value) {
function isFunction (line 1074) | function isFunction(value) {
function isLength (line 1107) | function isLength(value) {
function isObject (line 1137) | function isObject(value) {
function isObjectLike (line 1166) | function isObjectLike(value) {
FILE: server/libs/archiver/archiverUtils/lodash.flatten/index.js
function arrayPush (line 35) | function arrayPush(array, values) {
function baseFlatten (line 75) | function baseFlatten(array, depth, predicate, isStrict, result) {
function isFlattenable (line 105) | function isFlattenable(value) {
function flatten (line 124) | function flatten(array) {
function isArguments (line 147) | function isArguments(value) {
function isArrayLike (line 203) | function isArrayLike(value) {
function isArrayLikeObject (line 232) | function isArrayLikeObject(value) {
function isFunction (line 253) | function isFunction(value) {
function isLength (line 286) | function isLength(value) {
function isObject (line 316) | function isObject(value) {
function isObjectLike (line 345) | function isObjectLike(value) {
FILE: server/libs/archiver/archiverUtils/lodash.isplainobject/index.js
function isHostObject (line 20) | function isHostObject(value) {
function overArg (line 40) | function overArg(func, transform) {
function isObjectLike (line 93) | function isObjectLike(value) {
function isPlainObject (line 125) | function isPlainObject(value) {
FILE: server/libs/archiver/archiverUtils/lodash.union/index.js
function apply (line 53) | function apply(func, thisArg, args) {
function arrayIncludes (line 72) | function arrayIncludes(array, value) {
function arrayIncludesWith (line 86) | function arrayIncludesWith(array, value, comparator) {
function arrayPush (line 106) | function arrayPush(array, values) {
function baseFindIndex (line 128) | function baseFindIndex(array, predicate, fromIndex, fromRight) {
function baseIndexOf (line 149) | function baseIndexOf(array, value, fromIndex) {
function baseIsNaN (line 171) | function baseIsNaN(value) {
function cacheHas (line 183) | function cacheHas(cache, key) {
function getValue (line 195) | function getValue(object, key) {
function isHostObject (line 206) | function isHostObject(value) {
function setToArray (line 225) | function setToArray(set) {
function Hash (line 289) | function Hash(entries) {
function hashClear (line 307) | function hashClear() {
function hashDelete (line 321) | function hashDelete(key) {
function hashGet (line 334) | function hashGet(key) {
function hashHas (line 352) | function hashHas(key) {
function hashSet (line 367) | function hashSet(key, value) {
function ListCache (line 387) | function ListCache(entries) {
function listCacheClear (line 405) | function listCacheClear() {
function listCacheDelete (line 418) | function listCacheDelete(key) {
function listCacheGet (line 443) | function listCacheGet(key) {
function listCacheHas (line 459) | function listCacheHas(key) {
function listCacheSet (line 473) | function listCacheSet(key, value) {
function MapCache (line 499) | function MapCache(entries) {
function mapCacheClear (line 517) | function mapCacheClear() {
function mapCacheDelete (line 534) | function mapCacheDelete(key) {
function mapCacheGet (line 547) | function mapCacheGet(key) {
function mapCacheHas (line 560) | function mapCacheHas(key) {
function mapCacheSet (line 574) | function mapCacheSet(key, value) {
function SetCache (line 594) | function SetCache(values) {
function setCacheAdd (line 614) | function setCacheAdd(value) {
function setCacheHas (line 628) | function setCacheHas(value) {
function assocIndexOf (line 644) | function assocIndexOf(array, key) {
function baseFlatten (line 665) | function baseFlatten(array, depth, predicate, isStrict, result) {
function baseIsNative (line 696) | function baseIsNative(value) {
function baseRest (line 712) | function baseRest(func, start) {
function baseUniq (line 742) | function baseUniq(array, iteratee, comparator) {
function getMapData (line 813) | function getMapData(map, key) {
function getNative (line 828) | function getNative(object, key) {
function isFlattenable (line 840) | function isFlattenable(value) {
function isKeyable (line 852) | function isKeyable(value) {
function isMasked (line 866) | function isMasked(func) {
function toSource (line 877) | function toSource(func) {
function eq (line 941) | function eq(value, other) {
function isArguments (line 963) | function isArguments(value) {
function isArrayLike (line 1019) | function isArrayLike(value) {
function isArrayLikeObject (line 1048) | function isArrayLikeObject(value) {
function isFunction (line 1069) | function isFunction(value) {
function isLength (line 1102) | function isLength(value) {
function isObject (line 1132) | function isObject(value) {
function isObjectLike (line 1161) | function isObjectLike(value) {
function noop (line 1177) | function noop() {
FILE: server/libs/archiver/archiverUtils/minimatch/index.js
constant GLOBSTAR (line 20) | const GLOBSTAR = Symbol('globstar **')
method constructor (line 82) | constructor(pattern, options) {
constant MAX_PATTERN_LENGTH (line 125) | const MAX_PATTERN_LENGTH = 1024 * 64
constant SUBPARSE (line 147) | const SUBPARSE = Symbol('subparse')
class Minimatch (line 165) | class Minimatch {
method constructor (line 166) | constructor(pattern, options) {
method debug (line 189) | debug() { }
method make (line 191) | make() {
method parseNegate (line 237) | parseNegate() {
method matchOne (line 258) | matchOne(file, pattern, partial) {
method braceExpand (line 412) | braceExpand() {
method parse (line 416) | parse(pattern, isSub) {
method makeRe (line 776) | makeRe() {
method match (line 850) | match(f, partial = this.partial) {
method defaults (line 904) | static defaults(def) {
FILE: server/libs/archiver/archiverUtils/readableStream/index.js
method get (line 27) | get() {
method get (line 60) | get() {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/add-abort-signal.js
function isNodeStream (line 17) | function isNodeStream(obj) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/buffer_list.js
method constructor (line 8) | constructor() {
method push (line 14) | push(v) {
method unshift (line 25) | unshift(v) {
method shift (line 35) | shift() {
method clear (line 44) | clear() {
method join (line 49) | join(s) {
method concat (line 59) | concat(n) {
method consume (line 74) | consume(n, hasStrings) {
method first (line 92) | first() {
method [SymbolIterator] (line 96) | *[SymbolIterator]() {
method _getString (line 102) | _getString(n) {
method _getBuffer (line 135) | _getBuffer(n) {
method [Symbol.for('nodejs.util.inspect.custom')] (line 169) | [Symbol.for('nodejs.util.inspect.custom')](_, options) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/compose.js
function onfinished (line 57) | function onfinished(err) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/destroy.js
function checkError (line 16) | function checkError(err, w, r) {
function destroy (line 32) | function destroy(err, cb) {
function _destroy (line 68) | function _destroy(self, err, cb) {
function emitErrorCloseNT (line 107) | function emitErrorCloseNT(self, err) {
function emitCloseNT (line 112) | function emitCloseNT(self) {
function emitErrorNT (line 129) | function emitErrorNT(self, err) {
function undestroy (line 148) | function undestroy() {
function errorOrDestroy (line 179) | function errorOrDestroy(stream, err, sync) {
function construct (line 213) | function construct(stream, cb) {
function constructNT (line 239) | function constructNT(stream) {
function emitConstructNT (line 277) | function emitConstructNT(stream) {
function isRequest (line 281) | function isRequest(stream) {
function emitCloseLegacy (line 285) | function emitCloseLegacy(stream) {
function emitErrorCloseLegacy (line 289) | function emitErrorCloseLegacy(stream, err) {
function destroyer (line 294) | function destroyer(stream, err) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/duplex.js
function Duplex (line 51) | function Duplex(options) {
method get (line 87) | get() {
method set (line 95) | set(value) {
function lazyWebStreams (line 107) | function lazyWebStreams() {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/duplexify.js
class Duplexify (line 47) | class Duplexify extends Duplex {
method constructor (line 48) | constructor(options) {
method final (line 132) | final(cb) {
method read (line 216) | read() { }
function fromAsyncGen (line 237) | function fromAsyncGen(fn) {
function _duplexify (line 292) | function _duplexify(pair) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/end-of-stream.js
function isRequest (line 29) | function isRequest(stream) {
function eos (line 35) | function eos(stream, options, callback) {
function finished (line 245) | function finished(stream, opts) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/from.js
function from (line 7) | function from(Readable, iterable, opts) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/lazy_transform.js
function LazyTransform (line 14) | function LazyTransform(options) {
function makeGetter (line 21) | function makeGetter(name) {
function makeSetter (line 34) | function makeSetter(name) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/legacy.js
function Stream (line 7) | function Stream(opts) {
function ondata (line 17) | function ondata(chunk) {
function ondrain (line 25) | function ondrain() {
function onend (line 41) | function onend() {
function onclose (line 47) | function onclose() {
function onerror (line 53) | function onerror(er) {
function cleanup (line 64) | function cleanup() {
function prependListener (line 84) | function prependListener(emitter, event, fn) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/operators.js
function map (line 32) | function map(fn, options) {
function asIndexedPairs (line 195) | function asIndexedPairs(options = undefined) {
function some (line 227) | async function some(fn, options = undefined) {
function every (line 236) | async function every(fn, options = undefined) {
function find (line 250) | async function find(fn, options) {
function forEach (line 258) | async function forEach(fn, options) {
function filter (line 271) | function filter(fn, options) {
class ReduceAwareErrMissingArgs (line 288) | class ReduceAwareErrMissingArgs extends ERR_MISSING_ARGS {
method constructor (line 289) | constructor() {
function reduce (line 295) | async function reduce(reducer, initialValue, options) {
function toArray (line 377) | async function toArray(options) {
function flatMap (line 409) | function flatMap(fn, options) {
function toIntegerOrInfinity (line 418) | function toIntegerOrInfinity(number) {
function drop (line 434) | function drop(number, options = undefined) {
function take (line 477) | function take(number, options = undefined) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/passthrough.js
function PassThrough (line 35) | function PassThrough(options) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/pipeline.js
function destroyer (line 31) | function destroyer(stream, reading, writing) {
function popCallback (line 56) | function popCallback(streams) {
function makeAsyncIterable (line 64) | function makeAsyncIterable(val) {
function pump (line 83) | async function pump(iterable, writable, finish, { end }) {
function pipeline (line 148) | function pipeline(...streams) {
function pipelineImpl (line 152) | function pipelineImpl(streams, callback, opts) {
function pipe (line 357) | function pipe(src, dst, finish, { end }) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/readable.js
function ReadableState (line 84) | function ReadableState(options, stream, isDuplex) {
function Readable (line 165) | function Readable(options) {
function readableAddChunk (line 210) | function readableAddChunk(stream, chunk, encoding, addToFront) {
function addChunk (line 274) | function addChunk(stream, state, chunk, addToFront) {
constant MAX_HWM (line 321) | const MAX_HWM = 0x40000000
function computeNewHighWaterMark (line 323) | function computeNewHighWaterMark(n) {
function howMuchToRead (line 342) | function howMuchToRead(n, state) {
function onEofChunk (line 478) | function onEofChunk(stream, state) {
function emitReadable (line 510) | function emitReadable(stream) {
function emitReadable_ (line 522) | function emitReadable_(stream) {
function maybeReadMore (line 545) | function maybeReadMore(stream, state) {
function maybeReadMore_ (line 552) | function maybeReadMore_(stream, state) {
function onunpipe (line 618) | function onunpipe(readable, unpipeInfo) {
function onend (line 629) | function onend() {
function cleanup (line 637) | function cleanup() {
function pause (line 661) | function pause() {
function ondata (line 691) | function ondata(chunk) {
function onerror (line 702) | function onerror(er) {
function onclose (line 721) | function onclose() {
function onfinish (line 728) | function onfinish() {
function unpipe (line 736) | function unpipe() {
function pipeOnDrain (line 755) | function pipeOnDrain(src, dest) {
function updateReadableListening (line 870) | function updateReadableListening(self) {
function nReadingNextTick (line 885) | function nReadingNextTick(self) {
function resume (line 907) | function resume(stream, state) {
function resume_ (line 914) | function resume_(stream, state) {
function flow (line 940) | function flow(stream) {
function streamToAsyncIterator (line 1005) | function streamToAsyncIterator(stream, options) {
function next (line 1020) | function next(resolve) {
method get (line 1077) | get() {
method set (line 1086) | set(val) {
method get (line 1135) | get() {
method get (line 1142) | get() {
method get (line 1149) | get() {
method get (line 1156) | get() {
method get (line 1161) | get() {
method get (line 1168) | get() {
method set (line 1172) | set(value) {
method get (line 1186) | get() {
method get (line 1194) | get() {
method get (line 1200) | get() {
method set (line 1204) | set(value) {
function fromList (line 1215) | function fromList(n, state) {
function endReadable (line 1233) | function endReadable(stream) {
function endReadableNT (line 1243) | function endReadableNT(state, stream) {
function endWritableNT (line 1269) | function endWritableNT(stream) {
function lazyWebStreams (line 1283) | function lazyWebStreams() {
method destroy (line 1309) | destroy(err, callback) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/state.js
function highWaterMarkFrom (line 7) | function highWaterMarkFrom(options, isDuplex, duplexKey) {
function getDefaultHighWaterMark (line 11) | function getDefaultHighWaterMark(objectMode) {
function getHighWaterMark (line 15) | function getHighWaterMark(state, options, duplexKey, isDuplex) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/transform.js
function Transform (line 76) | function Transform(options) {
function final (line 96) | function final(cb) {
function prefinish (line 128) | function prefinish() {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/utils.js
function isReadableNodeStream (line 10) | function isReadableNodeStream(obj, strict = false) {
function isWritableNodeStream (line 28) | function isWritableNodeStream(obj) {
function isDuplexNodeStream (line 44) | function isDuplexNodeStream(obj) {
function isNodeStream (line 54) | function isNodeStream(obj) {
function isIterable (line 64) | function isIterable(obj, isAsync) {
function isDestroyed (line 71) | function isDestroyed(stream) {
function isWritableEnded (line 79) | function isWritableEnded(stream) {
function isWritableFinished (line 88) | function isWritableFinished(stream, strict) {
function isReadableEnded (line 97) | function isReadableEnded(stream) {
function isReadableFinished (line 106) | function isReadableFinished(stream, strict) {
function isReadable (line 114) | function isReadable(stream) {
function isWritable (line 121) | function isWritable(stream) {
function isFinished (line 127) | function isFinished(stream, opts) {
function isWritableErrored (line 147) | function isWritableErrored(stream) {
function isReadableErrored (line 166) | function isReadableErrored(stream) {
function isClosed (line 185) | function isClosed(stream) {
function isOutgoingMessage (line 214) | function isOutgoingMessage(stream) {
function isServerResponse (line 223) | function isServerResponse(stream) {
function isServerRequest (line 227) | function isServerRequest(stream) {
function willEmitClose (line 238) | function willEmitClose(stream) {
function isDisturbed (line 248) | function isDisturbed(stream) {
function isErrored (line 259) | function isErrored(stream) {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/streams/writable.js
function nop (line 67) | function nop() {}
function WritableState (line 71) | function WritableState(options, stream, isDuplex) {
function resetBuffer (line 165) | function resetBuffer(state) {
method get (line 177) | get() {
function Writable (line 182) | function Writable(options) {
function _write (line 229) | function _write(stream, chunk, encoding, cb) {
function writeOrBuffer (line 304) | function writeOrBuffer(stream, state, chunk, encoding, callback) {
function doWrite (line 341) | function doWrite(stream, state, writev, len, chunk, encoding, cb) {
function onwriteError (line 352) | function onwriteError(stream, state, er, cb) {
function onwrite (line 364) | function onwrite(stream, er) {
function afterWriteTick (line 424) | function afterWriteTick({ stream, state, count, cb }) {
function afterWrite (line 429) | function afterWrite(stream, state, count, cb) {
function errorBuffer (line 449) | function errorBuffer(state) {
function clearBuffer (line 482) | function clearBuffer(stream, state) {
function needFinish (line 606) | function needFinish(state) {
function callFinal (line 621) | function callFinal(stream, state) {
function prefinish (line 664) | function prefinish(stream, state) {
function finishMaybe (line 676) | function finishMaybe(stream, state, sync) {
function finish (line 702) | function finish(stream, state) {
method get (line 731) | get() {
method get (line 736) | get() {
method set (line 740) | set(value) {
method get (line 748) | get() {
method set (line 757) | set(val) {
method get (line 765) | get() {
method get (line 770) | get() {
method get (line 775) | get() {
method get (line 780) | get() {
method get (line 785) | get() {
method get (line 792) | get() {
method get (line 797) | get() {
method get (line 802) | get() {
method get (line 809) | get() {
function lazyWebStreams (line 849) | function lazyWebStreams() {
FILE: server/libs/archiver/archiverUtils/readableStream/internal/validators.js
function isInt32 (line 29) | function isInt32(value) {
function isUint32 (line 33) | function isUint32(value) {
function parseFileMode (line 52) | function parseFileMode(value, name, def) {
function validateString (line 112) | function validateString(value, name) {
function validateNumber (line 116) | function validateNumber(value, name) {
function validateBoolean (line 131) | function validateBoolean(value, name) {
function validateSignalName (line 169) | function validateSignalName(signal, name = 'signal') {
function validateEncoding (line 187) | function validateEncoding(data, encoding) {
function validatePort (line 197) | function validatePort(port, name = 'Port', allowZero = true) {
FILE: server/libs/archiver/archiverUtils/readableStream/ours/browser.js
method get (line 30) | get() {
FILE: server/libs/archiver/archiverUtils/readableStream/ours/errors.js
function assert (line 30) | function assert(value, message) {
function addNumericalSeparator (line 36) | function addNumericalSeparator(val) {
function getMessage (line 48) | function getMessage(key, msg, args) {
function E (line 70) | function E(code, message, Base) {
function hideStackFrames (line 96) | function hideStackFrames(fn) {
function aggregateTwoErrors (line 106) | function aggregateTwoErrors(innerError, outerError) {
class AbortError (line 122) | class AbortError extends Error {
method constructor (line 123) | constructor(message = 'The operation was aborted', options = undefined) {
FILE: server/libs/archiver/archiverUtils/readableStream/ours/primordials.js
method ArrayIsArray (line 11) | ArrayIsArray(self) {
method ArrayPrototypeIncludes (line 15) | ArrayPrototypeIncludes(self, el) {
method ArrayPrototypeIndexOf (line 19) | ArrayPrototypeIndexOf(self, el) {
method ArrayPrototypeJoin (line 23) | ArrayPrototypeJoin(self, sep) {
method ArrayPrototypeMap (line 27) | ArrayPrototypeMap(self, fn) {
method ArrayPrototypePop (line 31) | ArrayPrototypePop(self, el) {
method ArrayPrototypePush (line 35) | ArrayPrototypePush(self, el) {
method ArrayPrototypeSlice (line 39) | ArrayPrototypeSlice(self, start, end) {
method FunctionPrototypeCall (line 45) | FunctionPrototypeCall(fn, thisArgs, ...args) {
method FunctionPrototypeSymbolHasInstance (line 49) | FunctionPrototypeSymbolHasInstance(self, instance) {
method ObjectDefineProperties (line 61) | ObjectDefineProperties(self, props) {
method ObjectDefineProperty (line 65) | ObjectDefineProperty(self, name, prop) {
method ObjectGetOwnPropertyDescriptor (line 69) | ObjectGetOwnPropertyDescriptor(self, name) {
method ObjectKeys (line 73) | ObjectKeys(obj) {
method ObjectSetPrototypeOf (line 77) | ObjectSetPrototypeOf(target, proto) {
method PromisePrototypeCatch (line 83) | PromisePrototypeCatch(self, fn) {
method PromisePrototypeThen (line 87) | PromisePrototypeThen(self, thenFn, catchFn) {
method PromiseReject (line 91) | PromiseReject(err) {
method RegExpPrototypeTest (line 97) | RegExpPrototypeTest(self, value) {
method StringPrototypeSlice (line 104) | StringPrototypeSlice(self, start, end) {
method StringPrototypeToLowerCase (line 108) | StringPrototypeToLowerCase(self) {
method StringPrototypeToUpperCase (line 112) | StringPrototypeToUpperCase(self) {
method StringPrototypeTrim (line 116) | StringPrototypeTrim(self) {
method TypedArrayPrototypeSet (line 125) | TypedArrayPrototypeSet(self, buf, len) {
FILE: server/libs/archiver/archiverUtils/readableStream/ours/util.js
class AggregateError (line 21) | class AggregateError extends Error {
method constructor (line 22) | constructor(errors) {
method once (line 42) | once(callback) {
method promisify (line 69) | promisify(fn) {
method debuglog (line 81) | debuglog() {
method format (line 85) | format(format, ...args) {
method inspect (line 103) | inspect(value) {
method isAsyncFunction (line 139) | isAsyncFunction(fn) {
method isArrayBufferView (line 143) | isArrayBufferView(arr) {
FILE: server/libs/archiver/archiverUtils/readableStream/stream.js
function fn (line 59) | function fn(...args) {
function fn (line 84) | function fn(...args) {
method get (line 122) | get() {
method get (line 129) | get() {
method get (line 136) | get() {
FILE: server/libs/archiver/archiverUtils/readableStream/stream/promises.js
function pipeline (line 11) | function pipeline(...streams) {
FILE: server/libs/archiver/archiverUtils/safeBuffer/index.js
function copyProps (line 7) | function copyProps(src, dst) {
function SafeBuffer (line 20) | function SafeBuffer(arg, encodingOrOffset, length) {
FILE: server/libs/archiver/archiverUtils/stringDecoder/index.js
function _normalizeEncoding (line 38) | function _normalizeEncoding(enc) {
function normalizeEncoding (line 68) | function normalizeEncoding(enc) {
function StringDecoder (line 78) | function StringDecoder(encoding) {
function utf8CheckByte (line 139) | function utf8CheckByte(byte) {
function utf8CheckIncomplete (line 147) | function utf8CheckIncomplete(self, buf, i) {
function utf8CheckExtraBytes (line 180) | function utf8CheckExtraBytes(self, buf, p) {
function utf8FillLast (line 200) | function utf8FillLast(buf) {
function utf8Text (line 215) | function utf8Text(buf, i) {
function utf8End (line 226) | function utf8End(buf) {
function utf16Text (line 236) | function utf16Text(buf, i) {
function utf16End (line 259) | function utf16End(buf) {
function base64Text (line 268) | function base64Text(buf, i) {
function base64End (line 282) | function base64End(buf) {
function simpleWrite (line 289) | function simpleWrite(buf) {
function simpleEnd (line 293) | function simpleEnd(buf) {
FILE: server/libs/archiver/archiverUtils/wrappy/index.js
function wrappy (line 7) | function wrappy(fn, cb) {
FILE: server/libs/archiver/buffer-crc32/index.js
function newEmptyBuffer (line 67) | function newEmptyBuffer(length) {
function ensureBuffer (line 73) | function ensureBuffer(input) {
function bufferizeInt (line 94) | function bufferizeInt(num) {
function _crc32 (line 100) | function _crc32(buf, previous) {
function crc32 (line 112) | function crc32() {
FILE: server/libs/archiver/compress-commons/archivers/zip/zip-archive-output-stream.js
function handleStuff (line 167) | function handleStuff() {
FILE: server/libs/archiver/crc32-stream/crc32-stream.js
class CRC32Stream (line 14) | class CRC32Stream extends Transform {
method constructor (line 15) | constructor(options) {
method _transform (line 23) | _transform(chunk, encoding, callback) {
method digest (line 32) | digest(encoding) {
method hex (line 38) | hex() {
method size (line 42) | size() {
FILE: server/libs/archiver/crc32-stream/deflate-crc32-stream.js
class DeflateCRC32Stream (line 15) | class DeflateCRC32Stream extends DeflateRaw {
method constructor (line 16) | constructor(options) {
method push (line 26) | push(chunk, encoding) {
method _transform (line 34) | _transform(chunk, encoding, callback) {
method digest (line 43) | digest(encoding) {
method hex (line 49) | hex() {
method size (line 53) | size(compressed = false) {
FILE: server/libs/archiver/crc32/index.js
function signed_crc_table (line 28) | function signed_crc_table() {
function slice_by_16_tables (line 48) | function slice_by_16_tables(T) {
function crc32_bstr (line 64) | function crc32_bstr(bstr, seed) {
function crc32_buf (line 70) | function crc32_buf(B, seed) {
function crc32_str (line 85) | function crc32_str(str, seed) {
FILE: server/libs/archiver/lib/core.js
function onGlobEnd (line 637) | function onGlobEnd() {
function onGlobError (line 642) | function onGlobError(err) {
function onGlobMatch (line 646) | function onGlobMatch(match) {
function onGlobEnd (line 733) | function onGlobEnd() {
function onGlobError (line 738) | function onGlobError(err) {
function onGlobMatch (line 742) | function onGlobMatch(match) {
FILE: server/libs/archiver/lib/error.js
constant ERROR_CODES (line 11) | const ERROR_CODES = {
function ArchiverError (line 30) | function ArchiverError(code, data) {
FILE: server/libs/archiver/lib/plugins/json.js
function onend (line 74) | function onend(err, sourceBuffer) {
FILE: server/libs/archiver/readdir-glob/index.js
function readdir (line 13) | function readdir(dir, strict) {
function stat (line 42) | function stat(file, followSymlinks) {
function readOptions (line 106) | function readOptions(options) {
class ReaddirGlob (line 125) | class ReaddirGlob extends EventEmitter {
method constructor (line 126) | constructor(cwd, options, cb) {
method _shouldSkipDirectory (line 179) | _shouldSkipDirectory(relative) {
method _fileMatches (line 184) | _fileMatches(relative, isDirectory) {
method _next (line 191) | _next() {
method abort (line 227) | abort() {
method pause (line 231) | pause() {
method resume (line 235) | resume() {
function readdirGlob (line 245) | function readdirGlob(pattern, options, cb) {
FILE: server/libs/async/index.js
function t (line 6) | function t(e,...t){return(...n)=>e(...t,...n)}
function n (line 6) | function n(e){return function(...t){var n=t.pop();return e.call(this,t,n)}}
function a (line 6) | function a(e){setTimeout(e,0)}
function i (line 6) | function i(e){return(t,...n)=>e(()=>t(...n))}
function r (line 6) | function r(e){return u(e)?function(...t){const n=t.pop(),a=e.apply(this,...
function s (line 6) | function s(e,t){return e.then(e=>{l(t,null,e)},e=>{l(t,e&&e.message?e:ne...
function l (line 6) | function l(e,t,n){try{e(t,n)}catch(e){_e(t=>{throw t},e)}}
function u (line 6) | function u(e){return"AsyncFunction"===e[Symbol.toStringTag]}
function d (line 6) | function d(e){return"AsyncGenerator"===e[Symbol.toStringTag]}
function p (line 6) | function p(e){return"function"==typeof e[Symbol.asyncIterator]}
function c (line 6) | function c(e){if("function"!=typeof e)throw new Error("expected a functi...
function o (line 6) | function o(e,t=e.length){if(!t)throw new Error("arity is undefined");ret...
function h (line 6) | function h(e){return function(t,...n){const a=o(function(a){var i=this;r...
function f (line 6) | function f(e,t,n,a){t=t||[];var i=[],r=0,s=c(n);return e(t,(e,t,n)=>{var...
function y (line 6) | function y(e){return e&&"number"==typeof e.length&&0<=e.length&&0==e.len...
function m (line 6) | function m(e){function t(...t){if(null!==e){var n=e;e=null,n.apply(this,...
function g (line 6) | function g(e){return e[Symbol.iterator]&&e[Symbol.iterator]()}
function k (line 6) | function k(e){var t=-1,n=e.length;return function(){return++t<n?{value:e...
function v (line 6) | function v(e){var t=-1;return function(){var n=e.next();return n.done?nu...
function S (line 6) | function S(e){var t=e?Object.keys(e):[],n=-1,a=t.length;return function ...
function x (line 6) | function x(e){if(y(e))return k(e);var t=g(e);return t?v(t):S(e)}
function L (line 6) | function L(e){return function(...t){if(null===e)throw new Error("Callbac...
function E (line 6) | function E(e,t,n,a){function i(){p>=t||d||l||(d=!0,e.next().then(({value...
function O (line 6) | function O(e,t,n){function a(e,t){!1===e&&(l=!0);!0===l||(e?n(e):(++r===...
function _ (line 6) | function _(e,t,n){return Ie(e,1/0,t,n)}
function b (line 6) | function b(){function e(e,...a){return e?n(e):void t(1<a.length?a:a[0])}...
function A (line 6) | function A(e,t,n){function a(e,t){g.push(()=>l(e,t))}function i(){if(!h)...
function I (line 6) | function I(e){let t="",n=0,a=e.indexOf("*/");for(;n<e.length;)if("/"===e...
function M (line 6) | function M(e){const t=I(e.toString());let n=t.match(Pe);if(n||(n=t.match...
function j (line 6) | function j(e,t){var n={};return Object.keys(e).forEach(t=>{function a(e,...
function w (line 6) | function w(e,t){e.length=1,e.head=e.tail=t}
function B (line 6) | function B(e,t,n){function a(e,t){f[e].push(t)}function i(e,t){const n=(...
function T (line 6) | function T(e,t){return B(e,1,t)}
function F (line 6) | function F(e,t,n){return B(e,t,n)}
function C (line 6) | function C(...e){var t=e.map(c);return function(...e){var n=this,a=e[e.l...
function P (line 6) | function P(...e){return C(...e.reverse())}
function R (line 6) | function R(...e){return function(...t){var n=t.pop();return n(null,...e)}}
function z (line 6) | function z(e,t){return(n,a,i,r)=>{var s,l=!1;const u=c(i);n(a,(n,a,i)=>{...
function N (line 6) | function N(e){return(t,...n)=>c(t)(...n,(t,...n)=>{"object"==typeof cons...
function V (line 6) | function V(e,t,n){const a=c(t);return Xe(e,(...e)=>{const t=e.pop();a(.....
function Y (line 6) | function Y(e){return(t,n,a)=>e(t,a)}
function q (line 6) | function q(e){return u(e)?e:function(...t){var n=t.pop(),a=!0;t.push((.....
function D (line 6) | function D(e,t,n,a){var r=Array(t.length);e(t,(e,t,a)=>{n(e,(e,n)=>{r[t]...
function Q (line 6) | function Q(e,t,n,a){var i=[];e(t,(e,t,a)=>{n(e,(n,r)=>n?a(n):void(r&&i.p...
function U (line 6) | function U(e,t,n,a){var i=y(t)?D:Q;return i(e,t,c(n),a)}
function G (line 6) | function G(e,t,n){return ut(e,1/0,t,n)}
function W (line 6) | function W(e,t,n){return ut(e,1,t,n)}
function H (line 6) | function H(e,t,n){return pt(e,1/0,t,n)}
function J (line 6) | function J(e,t,n){return pt(e,1,t,n)}
function K (line 6) | function K(e,t=e=>e){var a=Object.create(null),r=Object.create(null),s=c...
function X (line 6) | function X(e,t){return ot(Me,e,t)}
function Z (line 6) | function Z(e,t,n){return ot(Ae(t),e,n)}
function $ (line 6) | function $(e,t){var n=c(e);return B((e,t)=>{n(e[0],t)},t,1)}
function ee (line 6) | function ee(e){return(e<<1)+1}
function te (line 6) | function te(e){return(e+1>>1)-1}
function ne (line 6) | function ne(e,t){return e.priority===t.priority?e.pushCount<t.pushCount:...
function ae (line 6) | function ae(e,t){function n(e,t){return Array.isArray(e)?e.map(e=>({data...
function ie (line 6) | function ie(e,t,n,a){var i=[...e].reverse();return qe(i,t,n,a)}
function re (line 6) | function re(e){var t=c(e);return n(function(e,n){return e.push((e,...t)=...
function se (line 6) | function se(e){var t;return Array.isArray(e)?t=e.map(re):(t={},Object.ke...
function le (line 6) | function le(e,t,n,a){const i=c(n);return U(e,t,(e,t)=>{i(e,(e,n)=>{t(e,!...
function ue (line 6) | function ue(e){return function(){return e}}
function de (line 6) | function de(e,t,n){function a(){r((e,...t)=>{!1===e||(e&&s++<i.times&&("...
function pe (line 6) | function pe(e,n){if("object"==typeof n)e.times=+n.times||kt,e.intervalFu...
function ce (line 6) | function ce(e,t){t||(t=e,e=null);let a=e&&e.arity||t.length;u(t)&&(a+=1)...
function oe (line 6) | function oe(e,t){return ot(Be,e,t)}
function he (line 6) | function he(e,t,a){var i=c(e);return n((n,r)=>{var s,l=!1;n.push((...e)=...
function fe (line 6) | function fe(e){for(var t=Array(e);e--;)t[e]=e;return t}
function ye (line 6) | function ye(e,t,n,a){var i=c(n);return De(fe(e),t,i,a)}
function me (line 6) | function me(e,t,n){return ye(e,1/0,t,n)}
function ge (line 6) | function ge(e,t,n){return ye(e,1,t,n)}
function ke (line 6) | function ke(e,t,n,a){3>=arguments.length&&"function"==typeof t&&(a=n,n=t...
function ve (line 6) | function ve(e){return(...t)=>(e.unmemoized||e)(...t)}
function Se (line 6) | function Se(e,t,n){const a=c(e);return _t(e=>a((t,n)=>e(t,!n)),t,n)}
function i (line 6) | function i(e,t){if(!u)if(c-=1,e)l=!0,a(e);else if(!1===e)l=!0,u=!0;else{...
function r (line 6) | function r(){for(o=!0;c<e&&!l;){var t=s();if(null===t)return l=!0,void(0...
class Ve (line 6) | class Ve{constructor(){this.head=this.tail=null,this.length=0}removeLink...
method constructor (line 6) | constructor(){this.head=this.tail=null,this.length=0}
method removeLink (line 6) | removeLink(e){return e.prev?e.prev.next=e.next:this.head=e.next,e.next...
method empty (line 6) | empty(){for(;this.head;)this.shift();return this}
method insertAfter (line 6) | insertAfter(e,t){t.prev=e,t.next=e.next,e.next?e.next.prev=t:this.tail...
method insertBefore (line 6) | insertBefore(e,t){t.prev=e.prev,t.next=e,e.prev?e.prev.next=t:this.hea...
method unshift (line 6) | unshift(e){this.head?this.insertBefore(this.head,e):w(this,e)}
method push (line 6) | push(e){this.tail?this.insertAfter(this.tail,e):w(this,e)}
method shift (line 6) | shift(){return this.head&&this.removeLink(this.head)}
method pop (line 6) | pop(){return this.tail&&this.removeLink(this.tail)}
method toArray (line 6) | toArray(){return[...this]}
method remove (line 6) | remove(e){for(var t=this.head;t;){var{next:n}=t;e(t)&&this.removeLink(...
method [Symbol.iterator] (line 6) | *[Symbol.iterator](){for(var e=this.head;e;)yield e.data,e=e.next}
function a (line 6) | function a(e,...t){return e?n(e):void(!1===e||(r=t,l(...t,i)))}
function i (line 6) | function i(e,t){return e?n(e):!1===e?void 0:t?void s(a):n(null,...r)}
function n (line 6) | function n(e){return e?a(e):void(!1===e||i(n))}
class ht (line 6) | class ht{constructor(){this.heap=[],this.pushCount=Number.MIN_SAFE_INTEG...
method constructor (line 6) | constructor(){this.heap=[],this.pushCount=Number.MIN_SAFE_INTEGER}
method length (line 6) | get length(){return this.heap.length}
method empty (line 6) | empty(){return this.heap=[],this}
method percUp (line 6) | percUp(e){for(let n;0<e&&ne(this.heap[e],this.heap[n=te(e)]);){let a=t...
method percDown (line 6) | percDown(e){for(let n,a;(n=ee(e))<this.heap.length&&(n+1<this.heap.len...
method push (line 6) | push(e){e.pushCount=++this.pushCount,this.heap.push(e),this.percUp(thi...
method unshift (line 6) | unshift(e){return this.heap.push(e)}
method shift (line 6) | shift(){let[e]=this.heap;return this.heap[0]=this.heap[this.heap.lengt...
method toArray (line 6) | toArray(){return[...this]}
method remove (line 6) | remove(e){let t=0;for(let n=0;n<this.heap.length;n++)e(this.heap[n])||...
method [Symbol.iterator] (line 6) | *[Symbol.iterator](){for(let e=0;e<this.heap.length;e++)yield this.heap[...
function a (line 6) | function a(e,t){var n=e.criteria,a=t.criteria;return n<a?-1:n>a?1:0}
function a (line 6) | function a(e,...t){if(e)return n(e);l=t;!1===e||s(i)}
function i (line 6) | function i(e,t){return e?n(e):!1===e?void 0:t?void r(a):n(null,...l)}
function n (line 6) | function n(t){var n=c(e[i++]);n(...t,L(a))}
function a (line 6) | function a(a,...r){return!1===a?void 0:a||i===e.length?t(a,...r):void n(r)}
FILE: server/libs/bcryptjs/index.js
function g (line 11) | function g(){this.batch_=null}
function p (line 11) | function p(b){return b instanceof h?b:new h(function(a,h){a(b)})}
function b (line 13) | function b(b){return function(A){h||(h=!0,b.call(a,A))}}
function b (line 15) | function b(a,b){return"function"==typeof a?function(b){try{t(a(b))}catch...
function b (line 16) | function b(){switch(g.state_){case 1:a(g.result_);break;case 2:h(g.resul...
function m (line 17) | function m(b){return function(h){r[b]=h;q--;0==q&&a(r)}}
function a (line 18) | function a(n){if("undefined"!==typeof module&&module&&module.exports)try...
function g (line 19) | function g(n,k){for(var a=n.length^k.length,c=0;c<n.length;++c)a|=n.char...
function p (line 19) | function p(n,k){var a=0,c=[];if(0>=k||k>n.length)throw Error("Illegal le...
function q (line 19) | function q(a,k){var n=0,c=a.length,e=0,f=[];if(0>=
function h (line 20) | function h(a,k,l,c){var e=
function v (line 23) | function v(a,k){for(var n=0,c=0;4>n;++n)c=c<<8|a[k]&255,k=(k+1)%a.length...
function b (line 23) | function b(a,k,l){for(var c=0,e=[0,0],f=k.length,d=l.length,n,b=0;b<f;b+...
function A (line 24) | function A(a,k,b,c){for(var e=0,f=[0,0],d=b.length,n=c.length,l,g=0;g<d;...
function w (line 24) | function w(a,k,l,c,e){function f(){e&&e(m/l);if(m<l)for(var g=Date.now()...
function t (line 26) | function t(a,k,b,c){function e(a){var c=[];c.push("$2");"a"<=f&&c.push(f...
function c (line 29) | function c(c){r(function(){try{c(null,m.genSaltSync(a))}catch(f){c(f)}})}
function e (line 30) | function e(f){"string"===typeof a&&"number"===typeof b?m.genSalt(b,funct...
function e (line 31) | function e(f){"string"!==typeof a||"string"!==typeof b?r(f.bind(this,Err...
FILE: server/libs/busboy/index.js
function getInstance (line 10) | function getInstance(cfg) {
constant TYPES (line 46) | const TYPES = [
FILE: server/libs/busboy/types/multipart.js
constant BUF_CRLF (line 15) | const BUF_CRLF = Buffer.from('\r\n');
constant BUF_CR (line 16) | const BUF_CR = Buffer.from('\r');
constant BUF_DASH (line 17) | const BUF_DASH = Buffer.from('-');
function noop (line 19) | function noop() { }
constant MAX_HEADER_PAIRS (line 21) | const MAX_HEADER_PAIRS = 2000;
constant MAX_HEADER_SIZE (line 22) | const MAX_HEADER_SIZE = 16 * 1024;
constant HPARSER_NAME (line 24) | const HPARSER_NAME = 0;
constant HPARSER_PRE_OWS (line 25) | const HPARSER_PRE_OWS = 1;
constant HPARSER_VALUE (line 26) | const HPARSER_VALUE = 2;
class HeaderParser (line 27) | class HeaderParser {
method constructor (line 28) | constructor(cb) {
method reset (line 39) | reset() {
method push (line 49) | push(chunk, pos, end) {
class FileStream (line 174) | class FileStream extends Readable {
method constructor (line 175) | constructor(opts, owner) {
method _read (line 194) | _read(n) {
function callAndUnsetCb (line 208) | function callAndUnsetCb(self, err) {
function nullDecoder (line 217) | function nullDecoder(val, hint) {
class Multipart (line 221) | class Multipart extends Writable {
method constructor (line 222) | constructor(cfg) {
method detect (line 565) | static detect(conType) {
method _write (line 569) | _write(chunk, enc, cb) {
method _destroy (line 576) | _destroy(err, cb) {
method _final (line 589) | _final(cb) {
function finalcb (line 600) | function finalcb(self, cb, err) {
function checkEndState (line 607) | function checkEndState(self) {
constant TOKEN (line 619) | const TOKEN = [
constant FIELD_VCHAR (line 638) | const FIELD_VCHAR = [
FILE: server/libs/busboy/types/urlencoded.js
class URLEncoded (line 7) | class URLEncoded extends Writable {
method constructor (line 8) | constructor(cfg) {
method detect (line 51) | static detect(conType) {
method _write (line 56) | _write(chunk, enc, cb) {
method _final (line 220) | _final(cb) {
function readPctEnc (line 242) | function readPctEnc(self, chunk, pos, len) {
function skipKeyBytes (line 290) | function skipKeyBytes(self, chunk, pos, len) {
function skipValBytes (line 310) | function skipValBytes(self, chunk, pos, len) {
constant HEX_VALUES (line 330) | const HEX_VALUES = [
FILE: server/libs/busboy/utils.js
function parseContentType (line 3) | function parseContentType(str) {
function parseContentTypeParams (line 48) | function parseContentTypeParams(str, i, params) {
function parseDisposition (line 163) | function parseDisposition(str, defDecoder) {
function parseDispositionParams (line 184) | function parseDispositionParams(str, i, params, defDecoder) {
function getDecoder (line 384) | function getDecoder(charset) {
function convertToUTF8 (line 473) | function convertToUTF8(data, charset, hint) {
function basename (line 479) | function basename(path) {
constant TOKEN (line 493) | const TOKEN = [
constant QDTEXT (line 512) | const QDTEXT = [
constant CHARSET (line 531) | const CHARSET = [
constant EXTENDED_VALUE (line 550) | const EXTENDED_VALUE = [
constant HEX_VALUES (line 570) | const HEX_VALUES = [
FILE: server/libs/commandLineArgs/index.js
function isObject (line 40) | function isObject(input) {
function isArrayLike (line 44) | function isArrayLike(input) {
function arrayify (line 53) | function arrayify(input) {
function isObject$1 (line 98) | function isObject$1(input) {
function isArrayLike$1 (line 102) | function isArrayLike$1(input) {
function arrayify$1 (line 111) | function arrayify$1(input) {
function findReplace (line 153) | function findReplace(array, testFn) {
class ArgvArray (line 208) | class ArgvArray extends Array {
method load (line 213) | load(argv) {
method clear (line 229) | clear() {
method expandOptionEqualsNotation (line 236) | expandOptionEqualsNotation() {
method expandGetoptNotation (line 255) | expandGetoptNotation() {
method hasCombinedShortOptions (line 265) | hasCombinedShortOptions() {
method from (line 269) | static from(argv) {
function expandCombinedShortArg (line 282) | function expandCombinedShortArg(arg) {
function isOptionEqualsNotation (line 294) | function isOptionEqualsNotation(arg) {
function isOption (line 304) | function isOption(arg) {
function isLongOption (line 314) | function isLongOption(arg) {
function getOptionName (line 324) | function getOptionName(arg) {
function isValue (line 336) | function isValue(arg) {
function isExecArg (line 340) | function isExecArg(arg) {
function isNumber (line 375) | function isNumber(n) {
function isPlainObject (line 405) | function isPlainObject(input) {
function isArrayLike$2 (line 421) | function isArrayLike$2(input) {
function isObject$2 (line 431) | function isObject$2(input) {
function isDefined (line 441) | function isDefined(input) {
function isString (line 451) | function isString(input) {
function isBoolean (line 461) | function isBoolean(input) {
function isFunction (line 471) | function isFunction(input) {
function isClass (line 481) | function isClass(input) {
function isPrimitive (line 495) | function isPrimitive(input) {
function isPromise (line 515) | function isPromise(input) {
function isIterable (line 564) | function isIterable(input) {
class OptionDefinition (line 600) | class OptionDefinition {
method constructor (line 601) | constructor(definition) {
method isBoolean (line 838) | isBoolean() {
method isMultiple (line 842) | isMultiple() {
method create (line 846) | static create(def) {
class Definitions (line 859) | class Definitions extends Array {
method validate (line 865) | validate(caseInsensitive) {
method get (line 959) | get(arg, caseInsensitive) {
method getDefault (line 983) | getDefault() {
method isGrouped (line 987) | isGrouped() {
method whereGrouped (line 991) | whereGrouped() {
method whereNotGrouped (line 995) | whereNotGrouped() {
method whereDefaultValueSet (line 999) | whereDefaultValueSet() {
method from (line 1003) | static from(definitions, caseInsensitive) {
function halt (line 1011) | function halt(name, message) {
function containsValidGroup (line 1017) | function containsValidGroup(def) {
function hasDuplicates (line 1021) | function hasDuplicates(array) {
class ArgvParser (line 1040) | class ArgvParser {
method constructor (line 1048) | constructor(definitions, options) {
method [Symbol.iterator] (line 1070) | *[Symbol.iterator]() {
class Option (line 1171) | class Option {
method constructor (line 1172) | constructor(definition) {
method get (line 1178) | get() {
method set (line 1182) | set(val) {
method _set (line 1186) | _set(val, state) {
method resetToDefault (line 1217) | resetToDefault() {
method create (line 1234) | static create(definition) {
class FlagOption (line 1244) | class FlagOption extends Option {
method set (line 1245) | set(val) {
method create (line 1249) | static create(def) {
class Output (line 1257) | class Output extends Map {
method constructor (line 1258) | constructor(definitions) {
method toObject (line 1272) | toObject(options) {
class GroupedOutput (line 1287) | class GroupedOutput extends Output {
method toObject (line 1288) | toObject(options) {
function commandLineArgs (line 1350) | function commandLineArgs(optionDefinitions, options) {
FILE: server/libs/expressFileupload/index.js
constant DEFAULT_OPTIONS (line 9) | const DEFAULT_OPTIONS = {
FILE: server/libs/expressFileupload/isEligibleRequest.js
constant ACCEPTABLE_CONTENT_TYPE (line 1) | const ACCEPTABLE_CONTENT_TYPE = /^(multipart\/.+);(.*)$/i;
constant UNACCEPTABLE_METHODS (line 2) | const UNACCEPTABLE_METHODS = ['GET', 'HEAD'];
FILE: server/libs/expressFileupload/uploadtimer.js
class UploadTimer (line 1) | class UploadTimer {
method constructor (line 7) | constructor(timeout = 0, callback = () => {}) {
method clear (line 13) | clear() {
method set (line 17) | set() {
FILE: server/libs/expressFileupload/utilities.js
constant SAFE_FILE_NAME_REGEX (line 8) | const SAFE_FILE_NAME_REGEX = /[^\w-]/g;
constant MAX_EXTENSION_LENGTH (line 9) | const MAX_EXTENSION_LENGTH = 3;
constant TEMP_COUNTER_MAX (line 12) | const TEMP_COUNTER_MAX = 65536;
constant TEMP_PREFIX (line 13) | const TEMP_PREFIX = 'tmp';
constant OBJECT_PROTOTYPE_KEYS (line 83) | const OBJECT_PROTOTYPE_KEYS = Object.getOwnPropertyNames(Object.prototype);
constant ARRAY_PROTOTYPE_KEYS (line 84) | const ARRAY_PROTOTYPE_KEYS = Object.getOwnPropertyNames(Array.prototype);
FILE: server/libs/fastSort/index.js
function getSortStrategy (line 53) | function getSortStrategy(sortBy, comparer, order) {
function createNewSortInstance (line 87) | function createNewSortInstance(opts) {
FILE: server/libs/fluentFfmpeg/ffprobe.js
function legacyTag (line 7) | function legacyTag(key) { return key.match(/^TAG:/); }
function legacyDisposition (line 8) | function legacyDisposition(key) { return key.match(/^DISPOSITION:/); }
function parseFfprobeOutput (line 10) | function parseFfprobeOutput(out) {
function handleCallback (line 103) | function handleCallback(err, data) {
function handleExit (line 179) | function handleExit(err) {
FILE: server/libs/fluentFfmpeg/index.d.ts
type FfmpegCommandLogger (line 7) | interface FfmpegCommandLogger {
type FfmpegCommandOptions (line 14) | interface FfmpegCommandOptions {
type FilterSpecification (line 26) | interface FilterSpecification {
type PresetFunction (line 33) | type PresetFunction = (command: FfmpegCommand) => void;
type Filter (line 35) | interface Filter {
type Filters (line 42) | interface Filters {
type FiltersCallback (line 45) | type FiltersCallback = (err: Error, filters: Filters) => void;
type Codec (line 47) | interface Codec {
type Codecs (line 59) | interface Codecs {
type CodecsCallback (line 62) | type CodecsCallback = (err: Error, codecs: Codecs) => void;
type Encoder (line 64) | interface Encoder {
type Encoders (line 73) | interface Encoders {
type EncodersCallback (line 76) | type EncodersCallback = (err: Error, encoders: Encoders) => void;
type Format (line 78) | interface Format {
type Formats (line 83) | interface Formats {
type FormatsCallback (line 86) | type FormatsCallback = (err: Error, formats: Formats) => void;
type FfprobeData (line 88) | interface FfprobeData {
type FfprobeStream (line 94) | interface FfprobeStream {
type FfprobeStreamDisposition (line 144) | interface FfprobeStreamDisposition {
type FfprobeFormat (line 160) | interface FfprobeFormat {
type ScreenshotsConfig (line 175) | interface ScreenshotsConfig {
type AudioVideoFilter (line 185) | interface AudioVideoFilter {
class FfmpegCommand (line 203) | class FfmpegCommand extends events.EventEmitter {
FILE: server/libs/fluentFfmpeg/index.js
function FfmpegCommand (line 36) | function FfmpegCommand(input, options) {
FILE: server/libs/fluentFfmpeg/options/videosize.js
function getScalePadFilters (line 19) | function getScalePadFilters(width, height, aspect, color) {
function createSizeFilters (line 68) | function createSizeFilters(output, key, value) {
FILE: server/libs/fluentFfmpeg/processor.js
function runFfprobe (line 19) | function runFfprobe(command) {
function handleExit (line 160) | function handleExit(err) {
function emitEnd (line 415) | function emitEnd(err, stdout, stderr) {
FILE: server/libs/fluentFfmpeg/recipes.js
function getMetadata (line 156) | function getMetadata(cb) {
FILE: server/libs/fluentFfmpeg/utils.js
function parseProgressLine (line 19) | function parseProgressLine(line) {
function emit (line 385) | function emit(line) {
FILE: server/libs/fsExtra/copy/copy-sync.js
function copySync (line 9) | function copySync (src, dest, opts) {
function handleFilterAndCopy (line 32) | function handleFilterAndCopy (destStat, src, dest, opts) {
function startCopy (line 39) | function startCopy (destStat, src, dest, opts) {
function getStats (line 44) | function getStats (destStat, src, dest, opts) {
function onFile (line 58) | function onFile (srcStat, destStat, src, dest, opts) {
function mayCopyFile (line 63) | function mayCopyFile (srcStat, src, dest, opts) {
function copyFile (line 72) | function copyFile (srcStat, src, dest, opts) {
function handleTimestamps (line 78) | function handleTimestamps (srcMode, src, dest) {
function fileIsNotWritable (line 86) | function fileIsNotWritable (srcMode) {
function makeFileWritable (line 90) | function makeFileWritable (dest, srcMode) {
function setDestMode (line 94) | function setDestMode (dest, srcMode) {
function setDestTimestamps (line 98) | function setDestTimestamps (src, dest) {
function onDir (line 106) | function onDir (srcStat, destStat, src, dest, opts) {
function mkDirAndCopy (line 111) | function mkDirAndCopy (srcMode, src, dest, opts) {
function copyDir (line 117) | function copyDir (src, dest, opts) {
function copyDirItem (line 121) | function copyDirItem (item, src, dest, opts) {
function onLink (line 128) | function onLink (destStat, src, dest, opts) {
function copyLink (line 164) | function copyLink (resolvedSrc, dest) {
FILE: server/libs/fsExtra/copy/copy.js
function copy (line 10) | function copy (src, dest, opts, cb) {
function checkParentDir (line 44) | function checkParentDir (destStat, src, dest, opts, cb) {
function handleFilter (line 56) | function handleFilter (onInclude, destStat, src, dest, opts, cb) {
function startCopy (line 63) | function startCopy (destStat, src, dest, opts, cb) {
function getStats (line 68) | function getStats (destStat, src, dest, opts, cb) {
function onFile (line 84) | function onFile (srcStat, destStat, src, dest, opts, cb) {
function mayCopyFile (line 89) | function mayCopyFile (srcStat, src, dest, opts, cb) {
function copyFile (line 100) | function copyFile (srcStat, src, dest, opts, cb) {
function handleTimestampsAndMode (line 108) | function handleTimestampsAndMode (srcMode, src, dest, cb) {
function fileIsNotWritable (line 121) | function fileIsNotWritable (srcMode) {
function makeFileWritable (line 125) | function makeFileWritable (dest, srcMode, cb) {
function setDestTimestampsAndMode (line 129) | function setDestTimestampsAndMode (srcMode, src, dest, cb) {
function setDestMode (line 136) | function setDestMode (dest, srcMode, cb) {
function setDestTimestamps (line 140) | function setDestTimestamps (src, dest, cb) {
function onDir (line 150) | function onDir (srcStat, destStat, src, dest, opts, cb) {
function mkDirAndCopy (line 155) | function mkDirAndCopy (srcMode, src, dest, opts, cb) {
function copyDir (line 165) | function copyDir (src, dest, opts, cb) {
function copyDirItems (line 172) | function copyDirItems (items, src, dest, opts, cb) {
function copyDirItem (line 178) | function copyDirItem (items, item, src, dest, opts, cb) {
function onLink (line 191) | function onLink (destStat, src, dest, opts, cb) {
function copyLink (line 228) | function copyLink (resolvedSrc, dest, cb) {
FILE: server/libs/fsExtra/empty/index.js
function emptyDirSync (line 20) | function emptyDirSync(dir) {
FILE: server/libs/fsExtra/ensure/file.js
function createFile (line 8) | function createFile(file, callback) {
function createFileSync (line 43) | function createFileSync(file) {
FILE: server/libs/fsExtra/ensure/link.js
function createLink (line 10) | function createLink(srcpath, dstpath, callback) {
function createLinkSync (line 39) | function createLinkSync(srcpath, dstpath) {
FILE: server/libs/fsExtra/ensure/symlink-paths.js
function symlinkPaths (line 29) | function symlinkPaths (srcpath, dstpath, callback) {
function symlinkPathsSync (line 67) | function symlinkPathsSync (srcpath, dstpath) {
FILE: server/libs/fsExtra/ensure/symlink-type.js
function symlinkType (line 5) | function symlinkType (srcpath, type, callback) {
function symlinkTypeSync (line 16) | function symlinkTypeSync (srcpath, type) {
FILE: server/libs/fsExtra/ensure/symlink.js
function createSymlink (line 22) | function createSymlink(srcpath, dstpath, type, callback) {
function _createSymlink (line 39) | function _createSymlink(srcpath, dstpath, type, callback) {
function createSymlinkSync (line 58) | function createSymlinkSync(srcpath, dstpath, type) {
FILE: server/libs/fsExtra/move/move-sync.js
function moveSync (line 10) | function moveSync (src, dest, opts) {
function isParentRoot (line 20) | function isParentRoot (dest) {
function doRename (line 26) | function doRename (src, dest, overwrite, isChangingCase) {
function rename (line 36) | function rename (src, dest, overwrite) {
function moveAcrossDevice (line 45) | function moveAcrossDevice (src, dest, overwrite) {
FILE: server/libs/fsExtra/move/move.js
function move (line 11) | function move (src, dest, opts, cb) {
function isParentRoot (line 35) | function isParentRoot (dest) {
function doRename (line 41) | function doRename (src, dest, overwrite, isChangingCase, cb) {
function rename (line 56) | function rename (src, dest, overwrite, cb) {
function moveAcrossDevice (line 64) | function moveAcrossDevice (src, dest, overwrite, cb) {
FILE: server/libs/fsExtra/path-exists/index.js
function pathExists (line 5) | function pathExists(path) {
FILE: server/libs/fsExtra/remove/index.js
function remove (line 7) | function remove(path, callback) {
function removeSync (line 13) | function removeSync(path) {
FILE: server/libs/fsExtra/remove/rimraf.js
function defaults (line 9) | function defaults (options) {
function rimraf (line 27) | function rimraf (p, options, cb) {
function rimraf_ (line 72) | function rimraf_ (p, options, cb) {
function fixWinEPERM (line 112) | function fixWinEPERM (p, options, er, cb) {
function fixWinEPERMSync (line 134) | function fixWinEPERMSync (p, options, er) {
function rmdir (line 167) | function rmdir (p, options, originalEr, cb) {
function rmkids (line 186) | function rmkids (p, options, cb) {
function rimrafSync (line 216) | function rimrafSync (p, options) {
function rmdirSync (line 259) | function rmdirSync (p, options, originalEr) {
function rmkidsSync (line 276) | function rmkidsSync (p, options) {
FILE: server/libs/fsExtra/util/stat.js
function getStats (line 7) | function getStats (src, dest, opts) {
function getStatsSync (line 20) | function getStatsSync (src, dest, opts) {
function checkPaths (line 35) | function checkPaths (src, dest, funcName, opts, cb) {
function checkPathsSync (line 66) | function checkPathsSync (src, dest, funcName, opts) {
function checkParentPaths (line 98) | function checkParentPaths (src, srcStat, dest, funcName, cb) {
function checkParentPathsSync (line 114) | function checkParentPathsSync (src, srcStat, dest, funcName) {
function areIdentical (line 131) | function areIdentical (srcStat, destStat) {
function isSrcSubdir (line 137) | function isSrcSubdir (src, dest) {
function errMsg (line 143) | function errMsg (src, dest, funcName) {
FILE: server/libs/fsExtra/util/utimes.js
function utimesMillis (line 5) | function utimesMillis (path, atime, mtime, callback) {
function utimesMillisSync (line 17) | function utimesMillisSync (path, atime, mtime) {
FILE: server/libs/fusejs/index.js
function e (line 13) | function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
function t (line 13) | function t(t){for(var n=1;n<arguments.length;n++){var r=null!=arguments[...
function n (line 13) | function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbo...
function r (line 13) | function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a ...
function u (line 13) | function u(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function i (line 13) | function i(e,t,n){return t&&u(e.prototype,t),n&&u(e,n),Object.defineProp...
function o (line 13) | function o(e,t,n){return(t=s(t))in e?Object.defineProperty(e,t,{value:n,...
function a (line 13) | function a(e){return function(e){if(Array.isArray(e))return c(e)}(e)||fu...
function c (line 13) | function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Ar...
function s (line 13) | function s(e){var t=function(e,t){if("object"!=typeof e||null===e)return...
function h (line 13) | function h(e){return Array.isArray?Array.isArray(e):"[object Array]"===p...
function f (line 13) | function f(e){return null==e?"":function(e){if("string"==typeof e)return...
function d (line 13) | function d(e){return"string"==typeof e}
function v (line 13) | function v(e){return"number"==typeof e}
function g (line 13) | function g(e){return!0===e||!1===e||function(e){return function(e){retur...
function A (line 13) | function A(e){return null!=e}
function y (line 13) | function y(e){return!e.trim().length}
function p (line 13) | function p(e){return null==e?void 0===e?"[object Undefined]":"[object Nu...
function e (line 13) | function e(t){var n=this;r(this,e),this._keys=[],this._keyMap={};var u=0...
function B (line 13) | function B(e){var t=null,n=null,r=null,u=1,i=null;if(d(e)||h(e))r=e,t=D(...
function D (line 13) | function D(e){return h(e)?e:e.split(".")}
function b (line 13) | function b(e){return h(e)?e.join("."):e}
function e (line 13) | function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function L (line 13) | function L(e,t){var n=arguments.length>2&&void 0!==arguments[2]?argument...
function S (line 13) | function S(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[...
function O (line 13) | function O(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?argume...
function j (line 13) | function j(e){for(var t={},n=0,r=e.length;n<r;n+=1){var u=e.charAt(n);t[...
function e (line 13) | function e(t){var n=this,u=arguments.length>1&&void 0!==arguments[1]?arg...
function W (line 13) | function W(e,t){for(var n=0,r=P.length;n<r;n+=1){var u=P[n];if(u.conditi...
function z (line 13) | function z(e,t){var n=e.matches;t.matches=[],A(n)&&n.forEach((function(e...
function T (line 13) | function T(e,t){t.score=e.score}
function e (line 13) | function e(n){var u=arguments.length>1&&void 0!==arguments[1]?arguments[...
FILE: server/libs/imageType/fileType.js
function readUInt64LE (line 7) | function readUInt64LE(buf, offset = 0) {
FILE: server/libs/isexe/index.js
function isexe (line 16) | function isexe(path, options, cb) {
function sync (line 50) | function sync(path, options) {
FILE: server/libs/isexe/mode.js
function isexe (line 6) | function isexe(path, options, cb) {
function sync (line 12) | function sync(path, options) {
function checkStat (line 16) | function checkStat(stat, options) {
function checkMode (line 20) | function checkMode(stat, options) {
FILE: server/libs/isexe/windows.js
function checkPathExt (line 6) | function checkPathExt(path, options) {
function checkStat (line 27) | function checkStat(stat, path, options) {
function isexe (line 34) | function isexe(path, options, cb) {
function sync (line 40) | function sync(path, options) {
FILE: server/libs/jsonwebtoken/sign.js
function isPlainObject (line 11) | function isPlainObject(value) {
function isInteger (line 15) | function isInteger(val) {
function isNumber (line 18) | function isNumber(val) {
function isString (line 21) | function isString(val) {
function validate (line 46) | function validate(schema, allowUnknown, object, parameterName) {
function validateOptions (line 65) | function validateOptions(options) {
function validatePayload (line 69) | function validatePayload(payload) {
function failure (line 107) | function failure(err) {
FILE: server/libs/jwa/buffer-equal-constant-time/index.js
function bufferEq (line 8) | function bufferEq(a, b) {
FILE: server/libs/jwa/ecdsa-sig-formatter/index.js
function base64Url (line 15) | function base64Url(base64) {
function signatureAsBuffer (line 22) | function signatureAsBuffer(signature) {
function derToJose (line 32) | function derToJose(signature, alg) {
function countPadding (line 117) | function countPadding(buf, start, stop) {
function joseToDer (line 131) | function joseToDer(signature, alg) {
FILE: server/libs/jwa/ecdsa-sig-formatter/param-bytes-for-alg.js
function getParamSize (line 3) | function getParamSize(keySize) {
function getParamBytesForAlg (line 14) | function getParamBytesForAlg(alg) {
FILE: server/libs/jwa/index.js
function checkIsPublicKey (line 23) | function checkIsPublicKey(key) {
function checkIsPrivateKey (line 53) | function checkIsPrivateKey(key) {
function checkIsSecretKey (line 69) | function checkIsSecretKey(key) {
function fromBase64 (line 95) | function fromBase64(base64) {
function toBase64 (line 102) | function toBase64(base64url) {
function typeError (line 117) | function typeError(template) {
function bufferOrString (line 123) | function bufferOrString(obj) {
function normalizeInput (line 127) | function normalizeInput(thing) {
function createHmacSigner (line 133) | function createHmacSigner(bits) {
function createHmacVerifier (line 143) | function createHmacVerifier(bits) {
function createKeySigner (line 150) | function createKeySigner(bits) {
function createKeyVerifier (line 162) | function createKeyVerifier(bits) {
function createPSSKeySigner (line 173) | function createPSSKeySigner(bits) {
function createPSSKeyVerifier (line 187) | function createPSSKeyVerifier(bits) {
function createECDSASigner (line 202) | function createECDSASigner(bits) {
function createECDSAVerifer (line 211) | function createECDSAVerifer(bits) {
function createNoneSigner (line 220) | function createNoneSigner() {
function createNoneVerifier (line 226) | function createNoneVerifier() {
FILE: server/libs/jws/lib/data-stream.js
function DataStream (line 6) | function DataStream(data) {
FILE: server/libs/jws/lib/sign-stream.js
function base64url (line 9) | function base64url(string, encoding) {
function jwsSecuredInput (line 18) | function jwsSecuredInput(header, payload, encoding) {
function jwsSign (line 25) | function jwsSign(opts) {
function SignStream (line 36) | function SignStream(opts) {
FILE: server/libs/jws/lib/verify-stream.js
function isObject (line 10) | function isObject(thing) {
function safeJsonParse (line 14) | function safeJsonParse(thing) {
function headerFromJWS (line 21) | function headerFromJWS(jwsSig) {
function securedInputFromJWS (line 26) | function securedInputFromJWS(jwsSig) {
function signatureFromJWS (line 30) | function signatureFromJWS(jwsSig) {
function payloadFromJWS (line 34) | function payloadFromJWS(jwsSig, encoding) {
function isValidJws (line 40) | function isValidJws(string) {
function jwsVerify (line 44) | function jwsVerify(jwsSig, algorithm, secretOrKey) {
function jwsDecode (line 57) | function jwsDecode(jwsSig, opts) {
function VerifyStream (line 80) | function VerifyStream(opts) {
FILE: server/libs/libarchive/archive.js
class CompressedFile (line 11) | class CompressedFile {
method constructor (line 13) | constructor(name, size, path, archiveRef) {
method name (line 23) | get name() {
method size (line 29) | get size() {
method extract (line 37) | extract() {
class Archive (line 43) | class Archive {
method open (line 50) | static open(fileBuffer) {
method constructor (line 60) | constructor(file, options) {
method open (line 74) | async open() {
method close (line 90) | close() {
method hasEncryptedData (line 99) | hasEncryptedData() {
method usePassword (line 112) | usePassword(archivePassword) {
method getFilesObject (line 126) | getFilesObject() {
method getFilesArray (line 145) | getFilesArray() {
method extractSingleFile (line 151) | extractSingleFile(target) {
method extractFiles (line 171) | extractFiles(extractCallback) {
method _cloneContent (line 196) | _cloneContent(obj) {
method _objectToArray (line 205) | _objectToArray(obj, path = '') {
method _getProp (line 220) | _getProp(obj, path) {
method _postMessage (line 232) | _postMessage(msg, callback) {
method _msgHandler (line 239) | _msgHandler(callback, resolve, reject, msg) {
method _workerMsg (line 253) | _workerMsg(msg) {
FILE: server/libs/libarchive/wasm-libarchive.js
function locateFile (line 11) | function locateFile(path) { if (Module["locateFile"]) { return Module["l...
function dynamicAlloc (line 11) | function dynamicAlloc(size) { var ret = HEAP32[DYNAMICTOP_PTR >> 2]; var...
function getNativeTypeSize (line 11) | function getNativeTypeSize(type) { switch (type) { case "i1": case "i8":...
function assert (line 11) | function assert(condition, text) { if (!condition) { abort("Assertion fa...
function getCFunc (line 11) | function getCFunc(ident) { var func = Module["_" + ident]; assert(func, ...
function ccall (line 11) | function ccall(ident, returnType, argTypes, args, opts) { var toC = { "s...
function cwrap (line 11) | function cwrap(ident, returnType, argTypes, opts) { argTypes = argTypes ...
function setValue (line 11) | function setValue(ptr, value, type, noSafe) { type = type || "i8"; if (t...
function allocate (line 11) | function allocate(slab, types, allocator, ptr) { var zeroinit, size; if ...
function getMemory (line 11) | function getMemory(size) { if (!runtimeInitialized) return dynamicAlloc(...
function UTF8ArrayToString (line 11) | function UTF8ArrayToString(u8Array, idx, maxBytesToRead) { var endIdx = ...
function UTF8ToString (line 11) | function UTF8ToString(ptr, maxBytesToRead) { return ptr ? UTF8ArrayToStr...
function stringToUTF8Array (line 11) | function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) { i...
function stringToUTF8 (line 11) | function stringToUTF8(str, outPtr, maxBytesToWrite) { return stringToUTF...
function lengthBytesUTF8 (line 11) | function lengthBytesUTF8(str) { var len = 0; for (var i = 0; i < str.len...
function writeArrayToMemory (line 11) | function writeArrayToMemory(array, buffer) { HEAP8.set(array, buffer) }
function writeAsciiToMemory (line 11) | function writeAsciiToMemory(str, buffer, dontAddNull) { for (var i = 0; ...
function demangle (line 11) | function demangle(func) { return func }
function demangleAll (line 11) | function demangleAll(text) { var regex = /__Z[\w\d_]+/g; return text.rep...
function jsStackTrace (line 11) | function jsStackTrace() { var err = new Error; if (!err.stack) { try { t...
function stackTrace (line 11) | function stackTrace() { var js = jsStackTrace(); if (Module["extraStackT...
function alignUp (line 11) | function alignUp(x, multiple) { if (x % multiple > 0) { x += multiple - ...
function updateGlobalBufferViews (line 11) | function updateGlobalBufferViews() { Module["HEAP8"] = HEAP8 = new Int8A...
function callRuntimeCallbacks (line 11) | function callRuntimeCallbacks(callbacks) { while (callbacks.length > 0) ...
function preRun (line 11) | function preRun() { if (Module["preRun"]) { if (typeof Module["preRun"] ...
function ensureInitRuntime (line 11) | function ensureInitRuntime() { if (runtimeInitialized) return; runtimeIn...
function preMain (line 11) | function preMain() { FS.ignorePermissions = false; callRuntimeCallbacks(...
function exitRuntime (line 11) | function exitRuntime() { runtimeExited = true }
function postRun (line 11) | function postRun() { if (Module["postRun"]) { if (typeof Module["postRun...
function addOnPreRun (line 11) | function addOnPreRun(cb) { __ATPRERUN__.unshift(cb) }
function addOnPostRun (line 11) | function addOnPostRun(cb) { __ATPOSTRUN__.unshift(cb) }
function getUniqueRunDependency (line 11) | function getUniqueRunDependency(id) { return id }
function addRunDependency (line 11) | function addRunDependency(id) { runDependencies++; if (Module["monitorRu...
function removeRunDependency (line 11) | function removeRunDependency(id) { runDependencies--; if (Module["monito...
function isDataURI (line 11) | function isDataURI(filename) { return String.prototype.startsWith ? file...
function getBinary (line 11) | function getBinary() { try { if (Module["wasmBinary"]) { return new Uint...
function getBinaryPromise (line 11) | function getBinaryPromise() { if (!Module["wasmBinary"] && (ENVIRONMENT_...
function createWasm (line 11) | function createWasm(env) { var info = { "env": env, "global": { "NaN": N...
function ___buildEnvironment (line 11) | function ___buildEnvironment(environ) { var MAX_ENV_VALUES = 64; var TOT...
function ___setErrNo (line 11) | function ___setErrNo(value) { if (Module["___errno_location"]) HEAP32[Mo...
function trim (line 11) | function trim(arr) { var start = 0; for (; start < arr.length; start++) ...
function isRealDir (line 11) | function isRealDir(p) { return p !== "." && p !== ".." }
function toAbsolute (line 11) | function toAbsolute(root) { return function (p) { return PATH.join2(root...
function done (line 11) | function done(err) { if (err) { if (!done.errored) { done.errored = true...
function ensureParent (line 11) | function ensureParent(path) { var parts = path.split("/"); var parent = ...
function base (line 11) | function base(path) { var parts = path.split("/"); return parts[parts.le...
function doCallback (line 11) | function doCallback(err) { FS.syncFSRequests--; return callback(err) }
function done (line 11) | function done(err) { if (err) { if (!done.errored) { done.errored = true...
function LazyUint8Array (line 11) | function LazyUint8Array() { this.lengthKnown = false; this.chunks = [] }
function processData (line 11) | function processData(byteArray) { function finish(byteArray) { if (preFi...
function finish (line 11) | function finish() { if (fail == 0) onload(); else onerror() }
function finish (line 11) | function finish() { if (fail == 0) onload(); else onerror() }
function ___syscall140 (line 11) | function ___syscall140(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall146 (line 11) | function ___syscall146(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall168 (line 11) | function ___syscall168(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall195 (line 11) | function ___syscall195(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall196 (line 11) | function ___syscall196(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall197 (line 11) | function ___syscall197(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall221 (line 11) | function ___syscall221(which, varargs) { SYSCALLS.varargs = varargs; try...
function ___syscall3 (line 11) | function ___syscall3(which, varargs) { SYSCALLS.varargs = varargs; try {...
function ___syscall4 (line 11) | function ___syscall4(which, varargs) { SYSCALLS.varargs = varargs; try {...
function ___syscall41 (line 11) | function ___syscall41(which, varargs) { SYSCALLS.varargs = varargs; try ...
function ___syscall42 (line 11) | function ___syscall42(which, varargs) { SYSCALLS.varargs = varargs; try ...
function ___syscall5 (line 11) | function ___syscall5(which, varargs) { SYSCALLS.varargs = varargs; try {...
function ___syscall6 (line 11) | function ___syscall6(which, varargs) { SYSCALLS.varargs = varargs; try {...
function _abort (line 11) | function _abort() { Module["abort"]() }
function _emscripten_get_heap_size (line 11) | function _emscripten_get_heap_size() { return HEAP8.length }
function abortOnCannotGrowMemory (line 11) | function abortOnCannotGrowMemory(requestedSize) { abort("OOM") }
function emscripten_realloc_buffer (line 11) | function emscripten_realloc_buffer(size) { var PAGE_MULTIPLE = 65536; si...
function _emscripten_resize_heap (line 11) | function _emscripten_resize_heap(requestedSize) { var oldSize = _emscrip...
function _exit (line 11) | function _exit(status) { exit(status) }
function _tzset (line 11) | function _tzset() { if (_tzset.called) return; _tzset.called = true; HEA...
function _localtime_r (line 11) | function _localtime_r(time, tmPtr) { _tzset(); var date = new Date(HEAP3...
function _localtime (line 11) | function _localtime(time) { return _localtime_r(time, ___tm_current) }
function _emscripten_memcpy_big (line 11) | function _emscripten_memcpy_big(dest, src, num) { HEAPU8.set(HEAPU8.suba...
function _mktime (line 11) | function _mktime(tmPtr) { _tzset(); var date = new Date(HEAP32[tmPtr + 2...
function _posix_spawn_file_actions_addclose (line 11) | function _posix_spawn_file_actions_addclose() { err("missing function: p...
function _posix_spawn_file_actions_adddup2 (line 11) | function _posix_spawn_file_actions_adddup2() { err("missing function: po...
function _posix_spawn_file_actions_destroy (line 11) | function _posix_spawn_file_actions_destroy() { err("missing function: po...
function _posix_spawn_file_actions_init (line 11) | function _posix_spawn_file_actions_init() { err("missing function: posix...
function _fork (line 11) | function _fork() { ___setErrNo(11); return -1 }
function _posix_spawnp (line 11) | function _posix_spawnp() { return _fork.apply(null, arguments) }
function _timegm (line 11) | function _timegm(tmPtr) { _tzset(); var time = Date.UTC(HEAP32[tmPtr + 2...
function _wait (line 11) | function _wait(stat_loc) { ___setErrNo(10); return -1 }
function _waitpid (line 11) | function _waitpid() { return _wait.apply(null, arguments) }
function intArrayFromString (line 11) | function intArrayFromString(stringy, dontAddNull, length) { var len = le...
function ExitStatus (line 11) | function ExitStatus(status) { this.name = "ExitStatus"; this.message = "...
function run (line 11) | function run(args) { args = args || Module["arguments"]; if (runDependen...
function exit (line 11) | function exit(status, implicit) { if (implicit && Module["noExitRuntime"...
function abort (line 11) | function abort(what) { if (Module["onAbort"]) { Module["onAbort"](what) ...
FILE: server/libs/libarchive/wasm-module.js
constant TYPE_MAP (line 8) | const TYPE_MAP = {
class ArchiveReader (line 18) | class ArchiveReader {
method constructor (line 23) | constructor(wasmModule) {
method open (line 34) | open(file) {
method close (line 48) | close() {
method hasEncryptedData (line 60) | hasEncryptedData() {
method setPassphrase (line 77) | setPassphrase(passphrase) {
method entries (line 86) | *entries(skipExtraction = false, except = null) {
method _loadFile (line 119) | _loadFile(fileBuffer, resolve, reject) {
method _promiseHandles (line 131) | _promiseHandles() {
class WasmModule (line 142) | class WasmModule {
method constructor (line 143) | constructor() {
method print (line 149) | print(...text) {
method printErr (line 153) | printErr(...text) {
method initFunctions (line 157) | initFunctions() {
method monitorRunDependencies (line 222) | monitorRunDependencies() { }
method locateFile (line 224) | locateFile(path /* ,prefix */) {
FILE: server/libs/lodash.once/index.js
function before (line 67) | function before(n, func) {
function once (line 102) | function once(func) {
function isObject (line 131) | function isObject(value) {
function isObjectLike (line 160) | function isObjectLike(value) {
function isSymbol (line 181) | function isSymbol(value) {
function toFinite (line 209) | function toFinite(value) {
function toInteger (line 247) | function toInteger(value) {
function toNumber (line 277) | function toNumber(value) {
FILE: server/libs/memorystore/index.js
method constructor (line 23) | constructor(checkPeriod, ttl, max) {
method get (line 45) | get(sid, fn) {
method set (line 68) | set(sid, sess, fn) {
method destroy (line 87) | destroy(sid, fn) {
method touch (line 108) | touch(sid, sess, fn) {
FILE: server/libs/nodeCron/background-scheduled-task/daemon.js
function register (line 5) | function register(message){
FILE: server/libs/nodeCron/background-scheduled-task/index.js
class BackgroundScheduledTask (line 8) | class BackgroundScheduledTask extends EventEmitter {
method constructor (line 9) | constructor(cronExpression, taskPath, options) {
method start (line 27) | start() {
method stop (line 50) | stop() {
method pid (line 56) | pid() {
method isRunning (line 62) | isRunning() {
FILE: server/libs/nodeCron/convert-expression/asterisk-to-range-conversion.js
function convertAsterisk (line 3) | function convertAsterisk(expression, replecement){
function convertAsterisksToRanges (line 10) | function convertAsterisksToRanges(expressions){
FILE: server/libs/nodeCron/convert-expression/index.js
function appendSeccondExpression (line 14) | function appendSeccondExpression(expressions) {
function removeSpaces (line 21) | function removeSpaces(str) {
function normalizeIntegers (line 26) | function normalizeIntegers(expressions) {
function interprete (line 54) | function interprete(expression) {
FILE: server/libs/nodeCron/convert-expression/month-names-conversion.js
function convertMonthName (line 8) | function convertMonthName(expression, items){
function interprete (line 15) | function interprete(monthExpression){
FILE: server/libs/nodeCron/convert-expression/range-conversion.js
function replaceWithRange (line 3) | function replaceWithRange(expression, text, init, end) {
function convertRange (line 21) | function convertRange(expression){
function convertAllRanges (line 31) | function convertAllRanges(expressions){
FILE: server/libs/nodeCron/convert-expression/step-values-conversion.js
function convertSteps (line 4) | function convertSteps(expressions){
FILE: server/libs/nodeCron/convert-expression/week-day-names-conversion.js
function convertWeekDayName (line 7) | function convertWeekDayName(expression, items){
function convertWeekDays (line 14) | function convertWeekDays(expression){
FILE: server/libs/nodeCron/index.js
function schedule (line 24) | function schedule(expression, func, options) {
function createTask (line 32) | function createTask(expression, func, options) {
function validate (line 45) | function validate(expression) {
function getTasks (line 60) | function getTasks() {
FILE: server/libs/nodeCron/pattern-validation.js
function isValidExpression (line 13) | function isValidExpression(expression, min, max) {
function isInvalidSecond (line 34) | function isInvalidSecond(expression) {
function isInvalidMinute (line 42) | function isInvalidMinute(expression) {
function isInvalidHour (line 50) | function isInvalidHour(expression) {
function isInvalidDayOfMonth (line 58) | function isInvalidDayOfMonth(expression) {
function isInvalidMonth (line 66) | function isInvalidMonth(expression) {
function isInvalidWeekDay (line 74) | function isInvalidWeekDay(expression) {
function validateFields (line 84) | function validateFields(patterns, executablePatterns) {
function validate (line 112) | function validate(pattern) {
FILE: server/libs/nodeCron/scheduled-task.js
class ScheduledTask (line 8) | class ScheduledTask extends EventEmitter {
method constructor (line 9) | constructor(cronExpression, func, options) {
method now (line 37) | now(now = 'manual') {
method start (line 42) | start() {
method stop (line 46) | stop() {
FILE: server/libs/nodeCron/scheduler.js
class Scheduler (line 6) | class Scheduler extends EventEmitter {
method constructor (line 7) | constructor(pattern, timezone, autorecover) {
method start (line 13) | start() {
method stop (line 41) | stop() {
FILE: server/libs/nodeCron/task.js
class Task (line 5) | class Task extends EventEmitter{
method constructor (line 6) | constructor(execution){
method execute (line 14) | execute(now) {
FILE: server/libs/nodeCron/time-matcher.js
function matchPattern (line 4) | function matchPattern(pattern, value){
class TimeMatcher (line 12) | class TimeMatcher{
method constructor (line 13) | constructor(pattern, timezone){
method match (line 20) | match(date){
method apply (line 33) | apply(date){
FILE: server/libs/nodeFfprobe/index.js
function doProbe (line 9) | function doProbe(file) {
FILE: server/libs/nodeStreamZip/index.js
function open (line 148) | function open() {
function readFile (line 163) | function readFile() {
function readUntilFoundCallback (line 178) | function readUntilFoundCallback(err, bytesRead) {
function readCentralDirectory (line 209) | function readCentralDirectory() {
function readCentralDirectoryComplete (line 224) | function readCentralDirectoryComplete() {
function readZip64CentralDirectoryLocator (line 259) | function readZip64CentralDirectoryLocator() {
function readZip64CentralDirectoryLocatorComplete (line 279) | function readZip64CentralDirectoryLocatorComplete() {
function readZip64CentralDirectoryComplete (line 299) | function readZip64CentralDirectoryComplete() {
function readEntries (line 312) | function readEntries() {
function readEntriesCallback (line 322) | function readEntriesCallback(err, bytesRead) {
function checkEntriesExist (line 366) | function checkEntriesExist() {
method get (line 373) | get() {
function dataOffset (line 484) | function dataOffset(entry) {
function canVerifyCrc (line 488) | function canVerifyCrc(entry) {
function extract (line 493) | function extract(entry, outPath, callback) {
function createDirectories (line 531) | function createDirectories(baseDir, dirs, callback) {
function extractFiles (line 545) | function extractFiles(baseDir, baseRelPath, files, callback, extractedCo...
method constructor (line 672) | constructor(config) {
method entriesCount (line 689) | get entriesCount() {
method comment (line 693) | get comment() {
method entry (line 697) | async entry(name) {
method entries (line 702) | async entries() {
method stream (line 707) | async stream(entry) {
method entryData (line 720) | async entryData(entry) {
method extract (line 735) | async extract(entry, outPath) {
method close (line 748) | async close() {
class CentralDirectoryHeader (line 762) | class CentralDirectoryHeader {
method read (line 763) | read(data) {
class CentralDirectoryLoc64Header (line 780) | class CentralDirectoryLoc64Header {
method read (line 781) | read(data) {
class CentralDirectoryZip64Header (line 790) | class CentralDirectoryZip64Header {
method read (line 791) | read(data) {
class ZipEntry (line 806) | class ZipEntry {
method readHeader (line 807) | readHeader(data, offset) {
method readDataHeader (line 847) | readDataHeader(data) {
method read (line 881) | read(data, offset, textDecoder) {
method validateName (line 896) | validateName() {
method readExtra (line 902) | readExtra(data, offset) {
method parseZip64Extra (line 917) | parseZip64Extra(data, offset, length) {
method encrypted (line 939) | get encrypted() {
method isFile (line 943) | get isFile() {
class FsRead (line 948) | class FsRead {
method constructor (line 949) | constructor(fd, buffer, offset, length, position, callback) {
method read (line 960) | read(sync) {
method readCallback (line 990) | readCallback(sync, err, bytesRead) {
class FileWindowBuffer (line 1003) | class FileWindowBuffer {
method constructor (line 1004) | constructor(fd) {
method checkOp (line 1011) | checkOp() {
method read (line 1017) | read(pos, length, callback) {
method expandLeft (line 1026) | expandLeft(length, callback) {
method expandRight (line 1036) | expandRight(length, callback) {
method moveRight (line 1050) | moveRight(length, callback, shift) {
class EntryDataReaderStream (line 1069) | class EntryDataReaderStream extends stream.Readable {
method constructor (line 1070) | constructor(fd, offset, length) {
method _read (line 1079) | _read(n) {
method readCallback (line 1088) | readCallback(err, bytesRead, buffer) {
class EntryVerifyStream (line 1104) | class EntryVerifyStream extends stream.Transform {
method constructor (line 1105) | constructor(baseStm, crc, size) {
method _transform (line 1113) | _transform(data, encoding, callback) {
class CrcVerify (line 1124) | class CrcVerify {
method constructor (line 1125) | constructor(crc, size) {
method data (line 1134) | data(data) {
method getCrcTable (line 1157) | static getCrcTable() {
function parseZipTime (line 1182) | function parseZipTime(timebytes, datebytes) {
function toBits (line 1198) | function toBits(dec, size) {
function readUInt64LE (line 1206) | function readUInt64LE(buffer, offset) {
FILE: server/libs/passportLocal/strategy.js
function lookup (line 8) | function lookup(obj, field) {
function Strategy (line 53) | function Strategy(options, verify) {
function verified (line 98) | function verified(err, user, info) {
FILE: server/libs/recursiveReaddirAsync/index.js
function adopt (line 32) | function adopt(value) { return value instanceof P ? value : new P(functi...
function fulfilled (line 34) | function fulfilled(value) { try { step(generator.next(value)); } catch (...
function rejected (line 35) | function rejected(value) { try { step(generator["throw"](value)); } catc...
function step (line 36) | function step(result) { result.done ? resolve(result.value) : adopt(resu...
function stat (line 69) | function stat(file) {
function readFile (line 92) | function readFile(file, encoding = 'base64') {
function checkItem (line 114) | function checkItem(path, settings) {
function addOptionalKeys (line 133) | function addOptionalKeys(obj, file, settings, deep) {
function read (line 151) | function read(rpath, data, settings, deep, resolve, reject) {
function myReaddir (line 188) | function myReaddir(path, settings, deep) {
function normalizePath (line 219) | function normalizePath(path) {
function exists (line 228) | function exists(fullname, settings) {
function onlyInclude (line 244) | function onlyInclude(settings, content) {
function listDir (line 265) | function listDir(path, settings, progress, deep = 0) {
function statDir (line 291) | function statDir(collection, settings, progress, deep) {
function statDirItem (line 322) | function statDirItem(collection, i, settings, progress, deep) {
function list (line 358) | function list(path, options, progress) {
FILE: server/libs/requestIp/index.js
function _typeof (line 5) | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbo...
function getClientIpFromXForwardedFor (line 16) | function getClientIpFromXForwardedFor(value) {
function getClientIp (line 57) | function getClientIp(req) {
function mw (line 149) | function mw(options) {
FILE: server/libs/requestIp/isJs.js
function a (line 5) | function a(n) { return function () { return !n.apply(null, e.call(argume...
function u (line 5) | function u(n) { return function () { var t = c(arguments); var e = t.len...
function o (line 5) | function o(n) { return function () { var t = c(arguments); var e = t.len...
function f (line 5) | function f(n, t) { var e = t + ""; var r = +(e.match(/\d+/) || NaN); var...
function c (line 5) | function c(t) { var r = e.call(t); var a = r.length; if (a === 1 && n.ar...
function d (line 5) | function d(t, e) { n[t] = function (n) { return e[t].test(n) } }
function j (line 5) | function j() { var t = n; for (var e in t) { if (r.call(t, e) && n["func...
FILE: server/libs/rss/index.js
function ifTruePush (line 12) | function ifTruePush(bool, array, data) {
function ifTruePushArray (line 18) | function ifTruePushArray(bool, array, dataArray) {
function getSize (line 28) | function getSize(filename) {
function generateXML (line 35) | function generateXML(data) {
function RSS (line 142) | function RSS(options, items) {
FILE: server/libs/sanitizeHtml/index.js
function escapeStringRegexp (line 13) | function escapeStringRegexp(string) {
function isObject (line 26) | function isObject(o) {
function isPlainObject (line 30) | function isPlainObject(o) {
function each (line 60) | function each(obj, cb) {
function has (line 69) | function has(obj, key) {
function isEmptyObject (line 73) | function isEmptyObject(obj) {
constant VALID_HTML_ATTRIBUTE_NAME (line 95) | const VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
function sanitizeHtml (line 101) | function sanitizeHtml(html, options, _recursing) {
FILE: server/libs/streamsearch/index.js
function memcmp (line 12) | function memcmp(buf1, pos1, buf2, pos2, num) {
class SBMH (line 20) | class SBMH {
method constructor (line 21) | constructor(needle, cb) {
method reset (line 97) | reset() {
method push (line 103) | push(chunk, pos) {
method destroy (line 114) | destroy() {
function feed (line 122) | function feed(self, data) {
function matchNeedle (line 260) | function matchNeedle(self, data, pos, len) {
FILE: server/libs/umzug/storage/contract.js
function isUmzugStorage (line 4) | function isUmzugStorage(arg) {
FILE: server/libs/umzug/storage/json.js
method readAsync (line 31) | async readAsync(filepath) {
method writeAsync (line 35) | async writeAsync(filepath, content) {
class JSONStorage (line 40) | class JSONStorage {
method constructor (line 41) | constructor(options) {
method logMigration (line 45) | async logMigration({ name: migrationName }) {
method unlogMigration (line 50) | async unlogMigration({ name: migrationName }) {
method executed (line 55) | async executed() {
FILE: server/libs/umzug/storage/memory.js
method logMigration (line 7) | async logMigration({ name }) {
method unlogMigration (line 10) | async unlogMigration({ name }) {
FILE: server/libs/umzug/storage/mongodb.js
function isMongoDBCollectionOptions (line 4) | function isMongoDBCollectionOptions(arg) {
class MongoDBStorage (line 7) | class MongoDBStorage {
method constructor (line 8) | constructor(options) {
method logMigration (line 19) | async logMigration({ name: migrationName }) {
method unlogMigration (line 22) | async unlogMigration({ name: migrationName }) {
method executed (line 25) | async executed() {
FILE: server/libs/umzug/storage/sequelize.js
constant DIALECTS_WITH_CHARSET_AND_COLLATE (line 4) | const DIALECTS_WITH_CHARSET_AND_COLLATE = new Set(['mysql', 'mariadb']);
class SequelizeStorage (line 5) | class SequelizeStorage {
method constructor (line 13) | constructor(options) {
method getModel (line 27) | getModel() {
method syncModel (line 51) | async syncModel() {
method logMigration (line 54) | async logMigration({ name: migrationName }) {
method unlogMigration (line 60) | async unlogMigration({ name: migrationName }) {
method executed (line 68) | async executed() {
method _model (line 80) | _model() {
FILE: server/libs/umzug/umzug.js
class MigrationError (line 53) | class MigrationError extends Error {
method constructor (line 55) | constructor(migration, original) {
method info (line 63) | get info() {
method errorString (line 66) | static errorString(cause) {
class Umzug (line 71) | class Umzug {
method constructor (line 73) | constructor(options) {
method logging (line 79) | logging(message) {
method executed (line 84) | async executed() {
method _executed (line 92) | async _executed(context) {
method pending (line 98) | async pending() {
method _pending (line 105) | async _pending(context) {
method runCommand (line 110) | async runCommand(command, cb) {
method up (line 118) | async up(options = {}) {
method down (line 163) | async down(options = {}) {
method create (line 206) | async create(options) {
method defaultCreationTemplate (line 266) | static defaultCreationTemplate(filepath) {
method findNameIndex (line 286) | findNameIndex(migrations, name) {
method findMigrations (line 293) | findMigrations(migrations, names) {
method getContext (line 303) | async getContext() {
method getMigrationsResolver (line 309) | getMigrationsResolver(inputMigrations) {
class MissingResolverError (line 381) | class MissingResolverError extends Error {
method constructor (line 382) | constructor(filepath) {
FILE: server/libs/watcher/aborter/controller.js
function AbortController (line 6) | function AbortController() {
FILE: server/libs/watcher/aborter/signal.js
function AbortSignal (line 6) | function AbortSignal() {
FILE: server/libs/watcher/are-shallow-equal.js
function areShallowEqual (line 6) | function areShallowEqual(x, y) {
FILE: server/libs/watcher/atomically/consts.js
constant DEFAULT_ENCODING (line 5) | const DEFAULT_ENCODING = 'utf8';
constant DEFAULT_FILE_MODE (line 7) | const DEFAULT_FILE_MODE = 0o666;
constant DEFAULT_FOLDER_MODE (line 9) | const DEFAULT_FOLDER_MODE = 0o777;
constant DEFAULT_READ_OPTIONS (line 11) | const DEFAULT_READ_OPTIONS = {};
constant DEFAULT_WRITE_OPTIONS (line 13) | const DEFAULT_WRITE_OPTIONS = {};
constant DEFAULT_TIMEOUT_ASYNC (line 15) | const DEFAULT_TIMEOUT_ASYNC = 5000;
constant DEFAULT_TIMEOUT_SYNC (line 17) | const DEFAULT_TIMEOUT_SYNC = 100;
constant IS_POSIX (line 19) | const IS_POSIX = !!process.getuid;
constant IS_USER_ROOT (line 21) | const IS_USER_ROOT = process.getuid ? !process.getuid() : false;
constant LIMIT_BASENAME_LENGTH (line 23) | const LIMIT_BASENAME_LENGTH = 128;
constant LIMIT_FILES_DESCRIPTORS (line 25) | const LIMIT_FILES_DESCRIPTORS = 10000;
FILE: server/libs/watcher/atomically/index.js
function readFile (line 11) | function readFile(filePath, options = consts_1.DEFAULT_READ_OPTIONS) {
function readFileSync (line 20) | function readFileSync(filePath, options = consts_1.DEFAULT_READ_OPTIONS) {
FILE: server/libs/watcher/constants.js
constant DEBOUNCE (line 10) | const DEBOUNCE = 300;
constant DEPTH (line 12) | const DEPTH = 20;
constant PLATFORM (line 14) | const PLATFORM = os_1.default.platform();
constant IS_LINUX (line 16) | const IS_LINUX = (PLATFORM === 'linux');
constant IS_MAC (line 18) | const IS_MAC = (PLATFORM === 'darwin');
constant IS_WINDOWS (line 20) | const IS_WINDOWS = (PLATFORM === 'win32');
constant HAS_NATIVE_RECURSION (line 22) | const HAS_NATIVE_RECURSION = IS_MAC || IS_WINDOWS;
constant POLLING_INTERVAL (line 24) | const POLLING_INTERVAL = 3000;
constant POLLING_TIMEOUT (line 26) | const POLLING_TIMEOUT = 20000;
constant RENAME_TIMEOUT (line 28) | const RENAME_TIMEOUT = 1250;
FILE: server/libs/watcher/debounce.js
function debounce (line 15) | function debounce(func, wait, immediate) {
FILE: server/libs/watcher/promise-concurrency-limiter.js
class Limiter (line 4) | class Limiter {
method constructor (line 6) | constructor(options) {
method add (line 12) | add(fn) {
method flush (line 20) | flush() {
method run (line 28) | run(fn) {
FILE: server/libs/watcher/ripstat/consts.js
constant IS_WINDOWS (line 5) | const IS_WINDOWS = (process.platform === 'win32');
constant RETRY_TIMEOUT (line 9) | const RETRY_TIMEOUT = 5000;
FILE: server/libs/watcher/ripstat/stats.js
class Stats (line 9) | class Stats {
method constructor (line 11) | constructor(stats) {
method _isMode (line 28) | _isMode(mode) {
method isDirectory (line 32) | isDirectory() {
method isFile (line 35) | isFile() {
method isBlockDevice (line 38) | isBlockDevice() {
method isCharacterDevice (line 41) | isCharacterDevice() {
method isSymbolicLink (line 44) | isSymbolicLink() {
method isFIFO (line 47) | isFIFO() {
method isSocket (line 50) | isSocket() {
FILE: server/libs/watcher/string-indexes.js
function indexes (line 3) | function indexes(str, substr) {
FILE: server/libs/watcher/utils.js
method castError (line 30) | castError(exception) {
method isError (line 43) | isError(x) {
FILE: server/libs/watcher/watcher.js
class Watcher (line 17) | class Watcher extends events_1.EventEmitter {
method constructor (line 19) | constructor(target, options, handler) {
method isClosed (line 39) | isClosed() {
method isIgnored (line 42) | isIgnored(targetPath, ignore) {
method isReady (line 45) | isReady() {
method close (line 48) | close() {
method error (line 58) | error(exception) {
method event (line 64) | event(event, targetPath, targetPathNext) {
method ready (line 70) | ready() {
method pollerExists (line 76) | pollerExists(targetPath, options) {
method subwatcherExists (line 86) | subwatcherExists(targetPath, options) {
method watchersClose (line 96) | watchersClose(folderPath, filePath, recursive = true) {
method watchersLock (line 120) | watchersLock(callback) {
method watchersRestore (line 128) | watchersRestore() {
method watcherAdd (line 136) | async watcherAdd(config, baseWatcherHandler) {
method watcherClose (line 144) | watcherClose(config) {
method watcherExists (line 162) | watcherExists(folderPath, options, handler, filePath) {
method watchDirectories (line 180) | async watchDirectories(foldersPaths, options, handler, filePath, baseW...
method watchDirectory (line 214) | async watchDirectory(folderPath, options, handler, filePath, baseWatch...
method watchFileOnce (line 240) | async watchFileOnce(filePath, options, callback) {
method watchFile (line 266) | async watchFile(filePath, options, handler) {
method watchPollingOnce (line 275) | async watchPollingOnce(targetPath, options, callback) {
method watchPolling (line 293) | async watchPolling(targetPath, options, callback) {
method watchUnknownChild (line 314) | async watchUnknownChild(targetPath, options, handler) {
method watchUnknownTarget (line 320) | async watchUnknownTarget(targetPath, options, handler) {
method watchPaths (line 326) | async watchPaths(targetPaths, options, handler) {
method watchPath (line 342) | async watchPath(targetPath, options, handler) {
method watch (line 368) | async watch(target, options, handler = utils_1.default.lang.noop) {
FILE: server/libs/watcher/watcher_handler.js
class WatcherHandler (line 11) | class WatcherHandler {
method constructor (line 13) | constructor(watcher, config, base) {
method _isSubRoot (line 24) | _isSubRoot(targetPath) {
method _makeHandlerBatched (line 32) | _makeHandlerBatched(delay = constants_1.DEBOUNCE) {
method eventsDeduplicate (line 59) | eventsDeduplicate(events) {
method eventsPopulate (line 74) | async eventsPopulate(targetPaths, events = [], isInitial = false) {
method eventsPopulateAddDir (line 90) | async eventsPopulateAddDir(targetPaths, targetPath, events = [], isIni...
method eventsPopulateUnlinkDir (line 104) | async eventsPopulateUnlinkDir(targetPaths, targetPath, events = [], is...
method onTargetAdd (line 117) | onTargetAdd(targetPath) {
method onTargetAddDir (line 127) | onTargetAddDir(targetPath) {
method onTargetChange (line 140) | onTargetChange(targetPath) {
method onTargetUnlink (line 145) | onTargetUnlink(targetPath) {
method onTargetUnlinkDir (line 156) | onTargetUnlinkDir(targetPath) {
method onTargetEvent (line 168) | onTargetEvent(event) {
method onTargetEvents (line 186) | onTargetEvents(events) {
method onWatcherEvent (line 191) | onWatcherEvent(event, targetPath, isInitial = false) {
method onWatcherChange (line 194) | onWatcherChange(event = "change" /* CHANGE */, targetName) {
method onWatcherError (line 204) | onWatcherError(error) {
method init (line 213) | async init() {
method initWatcherEvents (line 217) | async initWatcherEvents() {
method initInitialEvents (line 223) | async initInitialEvents() {
FILE: server/libs/watcher/watcher_locker.js
class WatcherLocker (line 11) | class WatcherLocker {
method constructor (line 13) | constructor(watcher) {
method getLockAdd (line 18) | getLockAdd(config, timeout = constants_1.RENAME_TIMEOUT) {
method getLockUnlink (line 54) | getLockUnlink(config, timeout = constants_1.RENAME_TIMEOUT) {
method getLockTargetAdd (line 78) | getLockTargetAdd(targetPath, timeout) {
method getLockTargetAddDir (line 87) | getLockTargetAddDir(targetPath, timeout) {
method getLockTargetUnlink (line 96) | getLockTargetUnlink(targetPath, timeout) {
method getLockTargetUnlinkDir (line 105) | getLockTargetUnlinkDir(targetPath, timeout) {
method reset (line 114) | reset() {
FILE: server/libs/watcher/watcher_poller.js
class WatcherPoller (line 10) | class WatcherPoller {
method constructor (line 11) | constructor() {
method getIno (line 17) | getIno(targetPath, event, type) {
method getStats (line 28) | getStats(targetPath) {
method poll (line 31) | async poll(targetPath, timeout) {
method reset (line 40) | reset() {
method update (line 44) | async update(targetPath, timeout) {
method updateIno (line 98) | updateIno(targetPath, event, stats) {
method updateStats (line 102) | updateStats(targetPath, stats) {
FILE: server/libs/watcher/watcher_stats.js
class WatcherStats (line 6) | class WatcherStats {
method constructor (line 8) | constructor(stats) {
method isFile (line 20) | isFile() {
method isDirectory (line 23) | isDirectory() {
method isSymbolicLink (line 26) | isSymbolicLink() {
FILE: server/libs/which/index.js
constant COLON (line 11) | const COLON = isWindows ? ';' : ':'
FILE: server/libs/xml/escapeForXML.js
function escapeForXML (line 9) | function escapeForXML(string) {
FILE: server/libs/xml/index.js
function xml (line 10) | function xml(input, options) {
function element (line 103) | function element(/*input, …*/) {
function create_indent (line 132) | function create_indent(character, count) {
function resolve (line 136) | function resolve(data, indent, indent_count) {
function format (line 228) | function format(append, elem, end) {
function attribute (line 281) | function attribute(key, value) {
FILE: server/managers/AbMergeManager.js
class AbMergeManager (line 19) | class AbMergeManager {
method constructor (line 20) | constructor() {
method getPendingTaskByLibraryItemId (line 32) | getPendingTaskByLibraryItemId(libraryItemId) {
method cancelEncode (line 42) | cancelEncode(task) {
method startAudiobookMerge (line 57) | async startAudiobookMerge(userId, libraryItem, options = {}) {
method runAudiobookMerge (line 110) | async runAudiobookMerge(libraryItem, task, encodingOptions) {
method removeTask (line 260) | async removeTask(task, removeTempFilepath = false) {
FILE: server/managers/ApiCacheManager.js
class ApiCacheManager (line 5) | class ApiCacheManager {
method constructor (line 12) | constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions...
method init (line 17) | init(database = Database) {
method getModelName (line 22) | getModelName(model) {
method clearByUrlPattern (line 29) | clearByUrlPattern(urlPattern) {
method clearUserProgressSlices (line 44) | clearUserProgressSlices(modelName, hook) {
method clear (line 52) | clear(model, hook) {
method reset (line 66) | reset() {
method middleware (line 73) | get middleware() {
FILE: server/managers/AudioMetadataManager.js
class AudioMetadataMangaer (line 16) | class AudioMetadataMangaer {
method constructor (line 17) | constructor() {
method getQueuedTaskData (line 29) | getQueuedTaskData() {
method getIsLibraryItemQueuedOrProcessing (line 33) | getIsLibraryItemQueuedOrProcessing(libraryItemId) {
method getMetadataObjectForApi (line 42) | getMetadataObjectForApi(libraryItem) {
method handleBatchEmbed (line 52) | handleBatchEmbed(userId, libraryItems, options = {}) {
method updateMetadataForItem (line 64) | async updateMetadataForItem(userId, libraryItem, options = {}) {
method runMetadataEmbed (line 133) | async runMetadataEmbed(task) {
method handleTaskFinished (line 270) | handleTaskFinished(task) {
FILE: server/managers/BackupManager.js
class BackupManager (line 20) | class BackupManager {
method constructor (line 21) | constructor() {
method backupPath (line 30) | get backupPath() {
method backupPathEnvSet (line 34) | get backupPathEnvSet() {
method backupSchedule (line 38) | get backupSchedule() {
method backupsToKeep (line 42) | get backupsToKeep() {
method maxBackupSize (line 46) | get maxBackupSize() {
method init (line 50) | async init() {
method reload (line 68) | async reload() {
method scheduleCron (line 75) | scheduleCron() {
method updateCronSchedule (line 88) | updateCronSchedule() {
method uploadBackup (line 103) | async uploadBackup(req, res) {
method requestCreateBackup (line 161) | async requestCreateBackup(res) {
method requestApplyBackup (line 178) | async requestApplyBackup(apiCacheManager, backup, res) {
method loadBackups (line 247) | async loadBackups() {
method runBackup (line 296) | async runBackup() {
method removeBackup (line 356) | async removeBackup(backup) {
method backupSqliteDb (line 371) | backupSqliteDb(backup) {
method zipBackup (line 394) | zipBackup(sqliteBackupPath, backup) {
FILE: server/managers/BinaryManager.js
class ZippedAssetDownloader (line 13) | class ZippedAssetDownloader {
method constructor (line 14) | constructor() {
method getReleaseUrl (line 18) | ge
Condensed preview — 912 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (8,538K chars).
[
{
"path": ".devcontainer/Dockerfile",
"chars": 504,
"preview": "# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, "
},
{
"path": ".devcontainer/dev.js",
"chars": 321,
"preview": "// Using port 3333 is important when running the client web app separately\nconst Path = require('path')\nmodule.exports.c"
},
{
"path": ".devcontainer/devcontainer.json",
"chars": 1442,
"preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.co"
},
{
"path": ".devcontainer/post-create.sh",
"chars": 729,
"preview": "#!/bin/sh\n\n# Mark the working directory as safe for use with git\ngit config --global --add safe.directory $PWD\n\n# If the"
},
{
"path": ".dockerignore",
"chars": 156,
"preview": ".env\nnode_modules\nnpm-debug.log\n.git\n.gitignore\n/config\n/audiobooks\n/audiobooks2\n/media/\n/metadata\ndev.js\ntest/\n/client/"
},
{
"path": ".editorconfig",
"chars": 129,
"preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_whitespa"
},
{
"path": ".gitattributes",
"chars": 196,
"preview": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text=auto\n\n# Declare files that will always h"
},
{
"path": ".github/ISSUE_TEMPLATE/bug.yaml",
"chars": 2974,
"preview": "name: 🐞 Bug Report\ndescription: File a bug/issue and help us improve Audiobookshelf\ntitle: '[Bug]: '\nlabels: ['bug', 'tr"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 185,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Discord\n url: https://discord.gg/HQgCbd6E75\n about: Ask quest"
},
{
"path": ".github/ISSUE_TEMPLATE/feature.yml",
"chars": 2057,
"preview": "name: 🚀 Feature Request\ndescription: Request a feature/enhancement\ntitle: '[Enhancement]: '\nlabels: ['enhancement']\nbody"
},
{
"path": ".github/pull_request_template.md",
"chars": 1045,
"preview": "<!--\nFor Work In Progress Pull Requests, please use the Draft PR feature,\nsee https://github.blog/2019-02-14-introducing"
},
{
"path": ".github/workflows/apply_comments.yaml",
"chars": 2136,
"preview": "name: Add issue comments by label\non:\n issues:\n types:\n - labeled\njobs:\n help-wanted:\n if: github.event.lab"
},
{
"path": ".github/workflows/close-issues-on-release.yml",
"chars": 494,
"preview": "name: Close fixed issues on release.\non:\n release:\n types: [published]\n\npermissions:\n contents: read\n issues: writ"
},
{
"path": ".github/workflows/close_blank_issues.yaml",
"chars": 1401,
"preview": "name: Close Issues not using a template\n\non:\n issues:\n types:\n - opened\n\npermissions:\n issues: write\n\njobs:\n "
},
{
"path": ".github/workflows/codeql.yml",
"chars": 2890,
"preview": "name: 'CodeQL'\n\non:\n push:\n branches: ['master']\n # Only build when files in these directories have been changed\n"
},
{
"path": ".github/workflows/component-tests.yml",
"chars": 1030,
"preview": "name: Run Component Tests\n\non:\n workflow_dispatch:\n inputs:\n ref:\n description: 'Branch/Tag/SHA to test'"
},
{
"path": ".github/workflows/docker-build.yml",
"chars": 2302,
"preview": "---\nname: Build and Push Docker Image\n\non:\n # Allows you to run workflow manually from Actions tab\n workflow_dispatch:"
},
{
"path": ".github/workflows/i18n-integration.yml",
"chars": 867,
"preview": "name: Verify all i18n files are alphabetized\n\non:\n pull_request:\n paths:\n - client/strings/** # Should only che"
},
{
"path": ".github/workflows/integration-test.yml",
"chars": 1244,
"preview": "name: Integration Test\n\non:\n pull_request:\n push:\n branches-ignore:\n - 'dependabot/**' # Don't run dependabot "
},
{
"path": ".github/workflows/lint-openapi.yml",
"chars": 964,
"preview": "name: API linting\n\n# Run on pull requests or pushes when there is a change to any OpenAPI files in docs/\non:\n pull_requ"
},
{
"path": ".github/workflows/notify-abs-windows.yml",
"chars": 419,
"preview": "name: Dispatch an abs-windows event\n\non:\n release:\n types: [published]\n workflow_dispatch:\n\njobs:\n abs-windows-dis"
},
{
"path": ".github/workflows/unit-tests.yml",
"chars": 793,
"preview": "name: Run Unit Tests\n\non:\n workflow_dispatch:\n inputs:\n ref:\n description: 'Branch/Tag/SHA to test'\n "
},
{
"path": ".gitignore",
"chars": 285,
"preview": ".env\n/dev.js\n**/node_modules/\n/config/\n/audiobooks/\n/audiobooks2/\n/podcasts/\n/media/\n/metadata/\n/plugins/\n/client/.nuxt/"
},
{
"path": ".prettierrc",
"chars": 297,
"preview": "{\n \"semi\": false,\n \"singleQuote\": true,\n \"printWidth\": 400,\n \"proseWrap\": \"never\",\n \"trailingComma\": \"none\",\n \"ove"
},
{
"path": ".vscode/extensions.json",
"chars": 112,
"preview": "{\n \"recommendations\": [\n \"EditorConfig.EditorConfig\",\n \"esbenp.prettier-vscode\",\n \"octref.vetur\"\n ]\n}"
},
{
"path": ".vscode/launch.json",
"chars": 961,
"preview": "{\n // Use IntelliSense to learn about possible attributes.\n // Hover to view descriptions of existing attributes.\n //"
},
{
"path": ".vscode/settings.json",
"chars": 639,
"preview": "{\n \"vetur.format.defaultFormatterOptions\": {\n \"prettier\": {\n \"semi\": false,\n \"singleQuote\": true,\n \"p"
},
{
"path": ".vscode/tasks.json",
"chars": 645,
"preview": "{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"path\": \"client\",\n\t\t\t\"type\": \"npm\",\n\t\t\t\"script\": \"generate\",\n\t\t\t\"detail\": \"nux"
},
{
"path": "Dockerfile",
"chars": 1746,
"preview": "ARG NUSQLITE3_DIR=\"/usr/local/lib/nusqlite3\"\nARG NUSQLITE3_PATH=\"${NUSQLITE3_DIR}/libnusqlite3.so\"\n\n### STAGE 0: Build c"
},
{
"path": "LICENSE",
"chars": 35104,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "build/debian/DEBIAN/control",
"chars": 200,
"preview": "Package: audiobookshelf\nVersion: 1.6.41\nSection: base\nPriority: optional\nArchitecture: amd64\nDepends:\nMaintainer: advply"
},
{
"path": "build/debian/DEBIAN/postinst",
"chars": 1580,
"preview": "#!/bin/bash\nset -e\nset -o pipefail\n\nABS_LOG_DIR=\"/var/log/audiobookshelf\"\n\ndeclare -r init_type='auto'\ndeclare -ri no_re"
},
{
"path": "build/debian/DEBIAN/preinst",
"chars": 1839,
"preview": "#!/bin/bash\nset -e\nset -o pipefail\n\nDEFAULT_DATA_DIR=\"/usr/share/audiobookshelf\"\nCONFIG_PATH=\"/etc/default/audiobookshel"
},
{
"path": "build/debian/DEBIAN/prerm",
"chars": 1035,
"preview": "#!/bin/bash\nset -e\nset -o pipefail\n\ndeclare -r init_type='auto'\ndeclare -r service_name='audiobookshelf'\n\nif [[ \"$init_t"
},
{
"path": "build/debian/etc/default/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "build/debian/lib/systemd/system/audiobookshelf.service",
"chars": 397,
"preview": "[Unit]\nDescription=Self-hosted audiobook server for managing and playing audiobooks\nRequires=network.target\n\n[Service]\nT"
},
{
"path": "build/debian/usr/lib/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "build/debian/usr/share/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "build/linuxpackager",
"chars": 1223,
"preview": "#!/bin/bash\nset -e\nset -o pipefail\n\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\"\n\ncd \"$SC"
},
{
"path": "client/assets/absicons.css",
"chars": 1801,
"preview": "@font-face {\n font-family: 'absicons';\n src: url('~static/fonts/absicons/absicons.eot?2jfq33');\n src: url('~static/fo"
},
{
"path": "client/assets/app.css",
"chars": 4757,
"preview": "@import './fonts.css';\n@import './transitions.css';\n@import './draggable.css';\n@import './defaultStyles.css';\n@import '."
},
{
"path": "client/assets/defaultStyles.css",
"chars": 1333,
"preview": "/*\n\n This is for setting regular html styles for places where embedding HTML will be\n like podcast episode description"
},
{
"path": "client/assets/draggable.css",
"chars": 835,
"preview": ".flip-list-move {\n transition: transform 0.5s;\n}\n\n.no-move {\n transition: transform 0s;\n}\n\n.ghost {\n opacity: 0.5;\n "
},
{
"path": "client/assets/ebooks/basic.js",
"chars": 9450,
"preview": "/*\nCalibres stylesheet\n*/\n\nexport default `\n@charset \"UTF-8\";\n\n/*\n Calibre styles\n*/\n.arabic {\n display: block;\n "
},
{
"path": "client/assets/ebooks/htmlParser.js",
"chars": 6924,
"preview": "/*\nThis is borrowed from koodo-reader https://github.com/troyeguo/koodo-reader/tree/master/src\n*/\n\nexport const isTitle "
},
{
"path": "client/assets/ebooks/mobi.js",
"chars": 11307,
"preview": "/*\nThis is borrowed from koodo-reader https://github.com/troyeguo/koodo-reader/tree/master/src\n*/\n\nfunction ab2str(buf) "
},
{
"path": "client/assets/fonts.css",
"chars": 8727,
"preview": "@font-face {\n font-family: 'Material Symbols Rounded';\n font-style: normal;\n font-weight: 400;\n src: url(~static/fon"
},
{
"path": "client/assets/tailwind.css",
"chars": 1995,
"preview": "@import 'tailwindcss';\n\n/*\n The default border color has changed to `currentColor` in Tailwind CSS v4,\n so we've added"
},
{
"path": "client/assets/transitions.css",
"chars": 1530,
"preview": ".slide-enter-active {\n -moz-transition-duration: 0.1s;\n -webkit-transition-duration: 0.1s;\n -o-transition-duration: 0"
},
{
"path": "client/assets/trix.css",
"chars": 16042,
"preview": "@charset \"UTF-8\";\n\n/*\nTrix 1.3.1\nCopyright © 2020 Basecamp, LLC\nhttp://trix-editor.org/*/\ntrix-editor {\n border: 1px so"
},
{
"path": "client/components/app/Appbar.vue",
"chars": 15710,
"preview": "<template>\n <div class=\"w-full h-16 bg-primary relative\">\n <div id=\"appbar\" role=\"toolbar\" aria-label=\"Appbar\" class"
},
{
"path": "client/components/app/BookShelfCategorized.vue",
"chars": 18578,
"preview": "<template>\n <div id=\"bookshelf\" ref=\"wrapper\" class=\"w-full max-w-full h-full overflow-y-scroll relative\" :style=\"{ fon"
},
{
"path": "client/components/app/BookShelfRow.vue",
"chars": 8739,
"preview": "<template>\n <div class=\"relative\">\n <div ref=\"shelf\" class=\"w-full max-w-full bookshelf-row categorizedBookshelfRow "
},
{
"path": "client/components/app/BookShelfToolbar.vue",
"chars": 23978,
"preview": "<template>\n <div class=\"w-full h-20 md:h-10 relative\">\n <div class=\"flex md:hidden h-10 items-center\">\n <nuxt-l"
},
{
"path": "client/components/app/ConfigSideNav.vue",
"chars": 5666,
"preview": "<template>\n <div role=\"toolbar\" aria-orientation=\"vertical\" aria-label=\"Config Sidebar\">\n <div role=\"navigation\" ari"
},
{
"path": "client/components/app/LazyBookshelf.vue",
"chars": 36150,
"preview": "<template>\n <div id=\"bookshelf\" ref=\"bookshelf\" class=\"w-full overflow-y-auto\" :style=\"{ fontSize: sizeMultiplier + 're"
},
{
"path": "client/components/app/MediaPlayerContainer.vue",
"chars": 20615,
"preview": "<template>\n <div v-if=\"streamLibraryItem\" id=\"mediaPlayerContainer\" class=\"w-full fixed bottom-0 left-0 right-0 h-48 lg"
},
{
"path": "client/components/app/SettingsContent.vue",
"chars": 870,
"preview": "<template>\n <div class=\"bg-bg rounded-md shadow-lg border border-white/5 p-2 sm:p-4 mb-8\">\n <div class=\"flex items-c"
},
{
"path": "client/components/app/SideRail.vue",
"chars": 11526,
"preview": "<template>\n <div role=\"toolbar\" aria-orientation=\"vertical\" aria-label=\"Library Sidebar\" class=\"w-20 bg-bg h-full fixed"
},
{
"path": "client/components/cards/AuthorCard.vue",
"chars": 5761,
"preview": "<template>\n <div class=\"pb-3e\" :style=\"{ minWidth: cardWidth + 'px', maxWidth: cardWidth + 'px' }\">\n <nuxt-link :to="
},
{
"path": "client/components/cards/AuthorSearchCard.vue",
"chars": 901,
"preview": "<template>\n <div class=\"flex h-full px-1 overflow-hidden\">\n <div class=\"overflow-hidden bg-primary rounded-sm\" style"
},
{
"path": "client/components/cards/BookMatchCard.vue",
"chars": 4553,
"preview": "<template>\n <div v-if=\"book\" class=\"w-full border-b border-gray-700 pb-2\">\n <div class=\"flex py-1 hover:bg-gray-300/"
},
{
"path": "client/components/cards/EpisodeSearchCard.vue",
"chars": 1331,
"preview": "<template>\n <div class=\"flex items-center h-full px-1 overflow-hidden\">\n <covers-book-cover :library-item=\"libraryIt"
},
{
"path": "client/components/cards/GenreSearchCard.vue",
"chars": 762,
"preview": "<template>\n <div class=\"flex h-full px-1 overflow-hidden\">\n <div class=\"w-10 h-10 flex items-center justify-center\">"
},
{
"path": "client/components/cards/GroupCard.vue",
"chars": 2795,
"preview": "<template>\n <div class=\"relative\">\n <div class=\"rounded-xs h-full relative\" :style=\"{ width: cardWidth + 'px', heigh"
},
{
"path": "client/components/cards/ItemSearchCard.vue",
"chars": 1698,
"preview": "<template>\n <div class=\"flex items-center h-full px-1 overflow-hidden\">\n <covers-book-cover :library-item=\"libraryIt"
},
{
"path": "client/components/cards/ItemTaskRunningCard.vue",
"chars": 4401,
"preview": "<template>\n <div class=\"flex items-center px-1 overflow-hidden\">\n <div class=\"w-8 flex items-center justify-center\">"
},
{
"path": "client/components/cards/ItemUploadCard.vue",
"chars": 8170,
"preview": "<template>\n <div class=\"relative w-full py-4 px-6 border border-white/10 shadow-lg rounded-md my-6\">\n <div class=\"ab"
},
{
"path": "client/components/cards/LazyBookCard.vue",
"chars": 44284,
"preview": "<template>\n <div ref=\"card\" :id=\"`book-card-${index}`\" tabindex=\"0\" :style=\"{ minWidth: coverWidth + 'px', maxWidth: co"
},
{
"path": "client/components/cards/LazyCollectionCard.vue",
"chars": 4448,
"preview": "<template>\n <div ref=\"card\" :id=\"`collection-card-${index}`\" role=\"button\" :style=\"{ width: cardWidth + 'px' }\" class=\""
},
{
"path": "client/components/cards/LazyPlaylistCard.vue",
"chars": 4070,
"preview": "<template>\n <div ref=\"card\" :id=\"`playlist-card-${index}`\" role=\"button\" :style=\"{ width: cardWidth + 'px', fontSize: s"
},
{
"path": "client/components/cards/LazySeriesCard.vue",
"chars": 7754,
"preview": "<template>\n <div cy-id=\"card\" ref=\"card\" :id=\"`series-card-${index}`\" tabindex=\"0\" :style=\"{ width: cardWidth + 'px' }\""
},
{
"path": "client/components/cards/NarratorCard.vue",
"chars": 1812,
"preview": "<template>\n <div>\n <nuxt-link :to=\"`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`\">\n "
},
{
"path": "client/components/cards/NarratorSearchCard.vue",
"chars": 785,
"preview": "<template>\n <div class=\"flex h-full px-1 overflow-hidden\">\n <div class=\"w-10 h-10 flex items-center justify-center\">"
},
{
"path": "client/components/cards/NotificationCard.vue",
"chars": 6102,
"preview": "<template>\n <div class=\"w-full border border-white/10 rounded-xl p-4 my-2\" :class=\"notification.enabled ? 'bg-primary/2"
},
{
"path": "client/components/cards/PodcastFeedSummaryCard.vue",
"chars": 1797,
"preview": "<template>\n <div ref=\"wrapper\" class=\"w-full p-2 border border-white/10 rounded-sm\">\n <div class=\"flex\">\n <div "
},
{
"path": "client/components/cards/SeriesSearchCard.vue",
"chars": 917,
"preview": "<template>\n <div class=\"flex h-full px-1 overflow-hidden\">\n <covers-group-cover :name=\"name\" :book-items=\"bookItems\""
},
{
"path": "client/components/cards/TagSearchCard.vue",
"chars": 761,
"preview": "<template>\n <div class=\"flex h-full px-1 overflow-hidden\">\n <div class=\"w-10 h-10 flex items-center justify-center\">"
},
{
"path": "client/components/content/LibraryItemDetails.vue",
"chars": 5920,
"preview": "<template>\n <div>\n <div v-if=\"narrators?.length\" class=\"flex py-0.5 mt-4\">\n <div class=\"w-34 min-w-34 sm:w-34 s"
},
{
"path": "client/components/controls/FilterSelect.vue",
"chars": 3598,
"preview": "<template>\n <div ref=\"wrapper\" class=\"relative\" v-click-outside=\"clickOutside\">\n <div class=\"relative h-9\">\n <b"
},
{
"path": "client/components/controls/GlobalSearch.vue",
"chars": 9628,
"preview": "<template>\n <div class=\"\">\n <div class=\"w-full relative sm:w-80\">\n <form role=\"search\" @submit.prevent=\"submitS"
},
{
"path": "client/components/controls/LibraryFilterSelect.vue",
"chars": 17103,
"preview": "<template>\n <div ref=\"wrapper\" class=\"relative\" v-click-outside=\"clickOutside\">\n <div class=\"relative h-7\">\n <b"
},
{
"path": "client/components/controls/LibrarySortSelect.vue",
"chars": 6370,
"preview": "<template>\n <div ref=\"wrapper\" class=\"relative\" v-click-outside=\"clickOutside\">\n <button type=\"button\" class=\"relati"
},
{
"path": "client/components/controls/PlaybackSpeedControl.vue",
"chars": 4387,
"preview": "<template>\n <div ref=\"wrapper\" class=\"relative ml-4 sm:ml-8\" v-click-outside=\"clickOutside\">\n <div class=\"flex items"
},
{
"path": "client/components/controls/SortSelect.vue",
"chars": 2830,
"preview": "<template>\n <div ref=\"wrapper\" class=\"relative\" v-click-outside=\"clickOutside\">\n <button type=\"button\" class=\"relati"
},
{
"path": "client/components/controls/VolumeControl.vue",
"chars": 4577,
"preview": "<template>\n <div class=\"relative\" v-click-outside=\"clickOutside\" @mouseover=\"mouseover\" @mouseleave=\"mouseleave\">\n <"
},
{
"path": "client/components/covers/AuthorImage.vue",
"chars": 3359,
"preview": "<template>\n <div ref=\"wrapper\" :class=\"`rounded-${rounded}`\" class=\"w-full h-full bg-primary overflow-hidden\">\n <svg"
},
{
"path": "client/components/covers/BookCover.vue",
"chars": 5931,
"preview": "<template>\n <div class=\"relative rounded-xs overflow-hidden\" :style=\"{ height: height + 'px', width: width + 'px', maxW"
},
{
"path": "client/components/covers/CollectionCover.vue",
"chars": 2155,
"preview": "<template>\n <div class=\"relative rounded-xs overflow-hidden\" :style=\"{ width: width + 'px', height: height + 'px' }\">\n "
},
{
"path": "client/components/covers/GroupCover.vue",
"chars": 6413,
"preview": "<template>\n <div ref=\"wrapper\" :style=\"{ height: height + 'px', width: width + 'px' }\" class=\"relative\">\n <div v-if="
},
{
"path": "client/components/covers/PlaylistCover.vue",
"chars": 1559,
"preview": "<template>\n <div class=\"relative rounded-xs overflow-hidden\" :style=\"{ width: width + 'px', height: height + 'px' }\">\n "
},
{
"path": "client/components/covers/PreviewCover.vue",
"chars": 3844,
"preview": "<template>\n <div class=\"relative rounded-xs\" :style=\"{ height: width * bookCoverAspectRatio + 'px', width: width + 'px'"
},
{
"path": "client/components/modals/AccountModal.vue",
"chars": 14862,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"account\" :width=\"800\" :height=\"'unset'\" :processing=\"process"
},
{
"path": "client/components/modals/AddCustomMetadataProviderModal.vue",
"chars": 3276,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"custom-metadata-provider\" :width=\"600\" :height=\"'unset'\" :pr"
},
{
"path": "client/components/modals/ApiKeyCreatedModal.vue",
"chars": 1597,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"api-key-created\" :width=\"800\" :height=\"'unset'\" persistent>\n"
},
{
"path": "client/components/modals/ApiKeyModal.vue",
"chars": 6119,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"api-key\" :width=\"800\" :height=\"'unset'\" :processing=\"process"
},
{
"path": "client/components/modals/AudioFileDataModal.vue",
"chars": 5838,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"audiofile-data-modal\" :width=\"700\" :height=\"'unset'\">\n <div v-if=\"aud"
},
{
"path": "client/components/modals/BackupScheduleModal.vue",
"chars": 2553,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"backup-scheduler\" :width=\"700\" :height=\"'unset'\" :processing=\"processing"
},
{
"path": "client/components/modals/BatchQuickMatchModel.vue",
"chars": 4681,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"batchQuickMatch\" :processing=\"processing\" :width=\"500\" :height=\"'unset'\""
},
{
"path": "client/components/modals/BookmarksModal.vue",
"chars": 4190,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"bookmarks\" :width=\"500\" :height=\"'unset'\">\n <template #outer>\n <"
},
{
"path": "client/components/modals/ChaptersModal.vue",
"chars": 2780,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"chapters\" :width=\"600\" :height=\"'unset'\">\n <div id=\"chapter-modal-wra"
},
{
"path": "client/components/modals/Dialog.vue",
"chars": 1654,
"preview": "<template>\n <modals-modal v-model=\"show\" :width=\"300\" height=\"100%\">\n <template #outer>\n <div v-if=\"title\" clas"
},
{
"path": "client/components/modals/EditSeriesInputInnerModal.vue",
"chars": 4528,
"preview": "<template>\n <div ref=\"wrapper\" role=\"dialog\" aria-modal=\"true\" class=\"hidden absolute top-0 left-0 w-full h-full bg-bla"
},
{
"path": "client/components/modals/ListeningSessionModal.vue",
"chars": 9013,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"listening-session-modal\" :processing=\"processing\" :width=\"700\" :height=\""
},
{
"path": "client/components/modals/Modal.vue",
"chars": 4742,
"preview": "<template>\n <div ref=\"wrapper\" role=\"dialog\" aria-modal=\"true\" class=\"modal modal-bg w-full h-full fixed top-0 left-0 b"
},
{
"path": "client/components/modals/PlayerSettingsModal.vue",
"chars": 3627,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"player-settings\" :width=\"500\" :height=\"'unset'\">\n <div ref=\"container"
},
{
"path": "client/components/modals/RawCoverPreviewModal.vue",
"chars": 704,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"cover\" :width=\"'90%'\" :height=\"'90%'\" :contentMarginTop=\"0\">\n <div cl"
},
{
"path": "client/components/modals/ShareModal.vue",
"chars": 7908,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"share\" :width=\"600\" :height=\"'unset'\" :processing=\"processin"
},
{
"path": "client/components/modals/SleepTimerModal.vue",
"chars": 5132,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"sleep-timer\" :width=\"350\" :height=\"'unset'\">\n <template #outer>\n "
},
{
"path": "client/components/modals/UploadImageModal.vue",
"chars": 3436,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"upload-image\" :width=\"500\" :height=\"'unset'\">\n <div ref=\"container\" c"
},
{
"path": "client/components/modals/authors/EditModal.vue",
"chars": 8513,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"edit-author\" :width=\"800\" :height=\"'unset'\" :processing=\"processing\">\n "
},
{
"path": "client/components/modals/bookmarks/BookmarkItem.vue",
"chars": 3256,
"preview": "<template>\n <div class=\"flex items-center px-4 py-4 justify-start relative hover:bg-primary/10\" :class=\"wrapperClass\" @"
},
{
"path": "client/components/modals/changelog/ViewModal.vue",
"chars": 2241,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"changelog\" :width=\"800\" :height=\"'unset'\">\n <template #outer>\n <"
},
{
"path": "client/components/modals/collections/AddCreateModal.vue",
"chars": 8833,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"collections\" :processing=\"processing\" :width=\"500\" :height=\"'unset'\">\n "
},
{
"path": "client/components/modals/collections/CollectionItem.vue",
"chars": 1897,
"preview": "<template>\n <div class=\"flex items-center px-4 py-2 justify-start relative hover:bg-black-400\" @mouseover=\"mouseover\" @"
},
{
"path": "client/components/modals/collections/EditModal.vue",
"chars": 5302,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"edit-collection\" :width=\"700\" :height=\"'unset'\" :processing=\"processing\""
},
{
"path": "client/components/modals/emails/EReaderDeviceModal.vue",
"chars": 6639,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"ereader-device-edit\" :width=\"800\" :height=\"'unset'\" :process"
},
{
"path": "client/components/modals/emails/UserEReaderDeviceModal.vue",
"chars": 5411,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"ereader-device-edit\" :width=\"800\" :height=\"'unset'\" :process"
},
{
"path": "client/components/modals/item/EditModal.vue",
"chars": 10419,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"edit-book\" :width=\"800\" :height=\"height\" :processing=\"processing\" :conte"
},
{
"path": "client/components/modals/item/tabs/Chapters.vue",
"chars": 1183,
"preview": "<template>\n <div class=\"w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6\">\n <div class=\"w-full mb-4\">\n "
},
{
"path": "client/components/modals/item/tabs/Cover.vue",
"chars": 17288,
"preview": "<template>\n <div class=\"w-full h-full overflow-hidden overflow-y-auto px-2 sm:px-4 py-6 relative\">\n <div class=\"flex"
},
{
"path": "client/components/modals/item/tabs/Details.vue",
"chars": 7153,
"preview": "<template>\n <div class=\"w-full h-full relative\">\n <div id=\"formWrapper\" class=\"w-full overflow-y-auto\">\n <widge"
},
{
"path": "client/components/modals/item/tabs/Episodes.vue",
"chars": 5364,
"preview": "<template>\n <div class=\"w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6\">\n <div class=\"w-full mb-4\">\n "
},
{
"path": "client/components/modals/item/tabs/Files.vue",
"chars": 999,
"preview": "<template>\n <div class=\"w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6\">\n <tables-library-files-table exp"
},
{
"path": "client/components/modals/item/tabs/Match.vue",
"chars": 31641,
"preview": "<template>\n <div id=\"match-wrapper\" class=\"w-full h-full overflow-hidden px-2 md:px-4 py-4 md:py-6 relative\">\n <form"
},
{
"path": "client/components/modals/item/tabs/Schedule.vue",
"chars": 6932,
"preview": "<template>\n <div class=\"w-full h-full relative\">\n <div id=\"scheduleWrapper\" class=\"w-full overflow-y-auto px-2 py-4 "
},
{
"path": "client/components/modals/item/tabs/Tools.vue",
"chars": 4133,
"preview": "<template>\n <div class=\"w-full h-full overflow-hidden overflow-y-auto px-4 py-6\">\n <p class=\"text-xl font-semibold m"
},
{
"path": "client/components/modals/libraries/EditLibrary.vue",
"chars": 5635,
"preview": "<template>\n <div class=\"w-full h-full md:px-4 py-2 mb-4\">\n <div v-if=\"!showDirectoryPicker\" class=\"w-full h-full md:"
},
{
"path": "client/components/modals/libraries/EditModal.vue",
"chars": 8552,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"edit-library\" :width=\"800\" :height=\"'unset'\" :processing=\"processing\">\n "
},
{
"path": "client/components/modals/libraries/LazyFolderChooser.vue",
"chars": 6734,
"preview": "<template>\n <div class=\"w-full h-full bg-bg absolute top-0 left-0 px-4 py-4 z-10\">\n <div class=\"flex items-center py"
},
{
"path": "client/components/modals/libraries/LibraryScannerSettings.vue",
"chars": 5397,
"preview": "<template>\n <div class=\"w-full h-full px-1 md:px-4 py-1 mb-4\">\n <div class=\"flex items-center justify-between mb-2\">"
},
{
"path": "client/components/modals/libraries/LibrarySettings.vue",
"chars": 8959,
"preview": "<template>\n <div class=\"w-full h-full px-1 md:px-4 py-1 mb-4\">\n <div class=\"flex flex-wrap\">\n <div class=\"flex "
},
{
"path": "client/components/modals/libraries/LibraryTools.vue",
"chars": 2476,
"preview": "<template>\n <div class=\"w-full h-full px-1 md:px-2 py-1 mb-4\">\n <div class=\"w-full border border-black-200 p-4 my-8\""
},
{
"path": "client/components/modals/libraries/ScheduleScan.vue",
"chars": 1712,
"preview": "<template>\n <div class=\"w-full h-full px-1 md:px-4 py-1 mb-4\">\n <div class=\"flex items-center justify-between mb-4\">"
},
{
"path": "client/components/modals/notification/NotificationEditModal.vue",
"chars": 6005,
"preview": "<template>\n <modals-modal ref=\"modal\" v-model=\"show\" name=\"notification-edit\" :width=\"800\" :height=\"'unset'\" :processin"
},
{
"path": "client/components/modals/player/QueueItemRow.vue",
"chars": 2985,
"preview": "<template>\n <div v-if=\"item\" class=\"w-full flex items-center px-4 py-2\" :class=\"wrapperClass\" @mouseover=\"mouseover\" @m"
},
{
"path": "client/components/modals/player/QueueItemsModal.vue",
"chars": 1873,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"queue-items\" :width=\"800\" :height=\"'unset'\">\n <template #outer>\n "
},
{
"path": "client/components/modals/playlists/AddCreateModal.vue",
"chars": 7590,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"playlists\" :processing=\"processing\" :width=\"500\" :height=\"'unset'\">\n "
},
{
"path": "client/components/modals/playlists/EditModal.vue",
"chars": 4154,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"edit-playlist\" :width=\"700\" :height=\"'unset'\" :processing=\"processing\">\n"
},
{
"path": "client/components/modals/playlists/UserPlaylistItem.vue",
"chars": 1769,
"preview": "<template>\n <div class=\"flex items-center px-4 py-2 justify-start relative hover:bg-black-400\" @mouseover=\"mouseover\" @"
},
{
"path": "client/components/modals/podcast/EditEpisode.vue",
"chars": 7540,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"podcast-episode-edit-modal\" :width=\"800\" :height=\"'unset'\" :processing=\""
},
{
"path": "client/components/modals/podcast/EpisodeFeed.vue",
"chars": 12824,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"podcast-episodes-modal\" :width=\"1200\" :height=\"'unset'\" :processing=\"pro"
},
{
"path": "client/components/modals/podcast/NewModal.vue",
"chars": 8918,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"new-podcast-modal\" :width=\"1000\" :height=\"'unset'\" :processing=\"processi"
},
{
"path": "client/components/modals/podcast/OpmlFeedsModal.vue",
"chars": 3959,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"opml-feeds-modal\" :width=\"1000\" :height=\"'unset'\" :processing=\"processin"
},
{
"path": "client/components/modals/podcast/RemoveEpisode.vue",
"chars": 3111,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"podcast-episode-remove-modal\" :width=\"500\" :height=\"'unset'\" :processing"
},
{
"path": "client/components/modals/podcast/ViewEpisode.vue",
"chars": 4778,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"podcast-episode-view-modal\" :width=\"800\" :height=\"'unset'\" :processing=\""
},
{
"path": "client/components/modals/podcast/tabs/EpisodeDetails.vue",
"chars": 5883,
"preview": "<template>\n <div>\n <div class=\"flex flex-wrap\">\n <div class=\"w-1/5 p-1\">\n <ui-text-input-with-label v-mo"
},
{
"path": "client/components/modals/podcast/tabs/EpisodeMatch.vue",
"chars": 5261,
"preview": "<template>\n <div style=\"min-height: 200px\">\n <template v-if=\"!podcastFeedUrl\">\n <div class=\"py-8\">\n <wid"
},
{
"path": "client/components/modals/rssfeed/OpenCloseModal.vue",
"chars": 6063,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"rss-feed-modal\" :width=\"600\" :height=\"'unset'\" :processing=\"processing\">"
},
{
"path": "client/components/modals/rssfeed/ViewFeedModal.vue",
"chars": 3111,
"preview": "<template>\n <modals-modal v-model=\"show\" name=\"rss-feed-view-modal\" :processing=\"processing\" :width=\"700\" :height=\"'uns"
},
{
"path": "client/components/player/PlayerPlaybackControls.vue",
"chars": 3832,
"preview": "<template>\n <div class=\"flex justify-center pt-4 pb-2 lg:pt-0 lg:pb-2\">\n <div class=\"flex items-center justify-cente"
},
{
"path": "client/components/player/PlayerTrackBar.vue",
"chars": 8114,
"preview": "<template>\n <div class=\"relative\">\n <!-- Track -->\n <div ref=\"track\" class=\"w-full h-2 bg-gray-700 relative curso"
},
{
"path": "client/components/player/PlayerUi.vue",
"chars": 14970,
"preview": "<template>\n <div class=\"w-full -mt-6\">\n <div class=\"w-full relative mb-1\">\n <div class=\"absolute -top-10 lg:top"
},
{
"path": "client/components/prompt/Confirm.vue",
"chars": 4775,
"preview": "<template>\n <div ref=\"wrapper\" class=\"modal modal-bg w-full h-full fixed top-0 left-0 bg-primary/75 flex items-center j"
},
{
"path": "client/components/prompt/Dialog.vue",
"chars": 1918,
"preview": "<template>\n <div ref=\"wrapper\" class=\"modal modal-bg w-full h-full fixed top-0 left-0 bg-primary/75 flex items-center j"
},
{
"path": "client/components/readers/ComicReader.vue",
"chars": 13511,
"preview": "<template>\n <div class=\"w-full h-full\">\n <div v-show=\"showPageMenu\" v-click-outside=\"clickOutside\" class=\"pagemenu a"
},
{
"path": "client/components/readers/EpubReader.vue",
"chars": 15822,
"preview": "<template>\n <div id=\"epub-reader\" class=\"h-full w-full\">\n <div class=\"h-full flex items-center justify-center\">\n "
},
{
"path": "client/components/readers/MobiReader.vue",
"chars": 4280,
"preview": "<template>\n <div class=\"w-full h-full\">\n <div class=\"h-full max-h-full w-full\">\n <div class=\"ebook-viewer absol"
},
{
"path": "client/components/readers/PdfReader.vue",
"chars": 6662,
"preview": "<template>\n <div class=\"w-full h-full pt-20 relative\">\n <div v-show=\"canGoPrev\" class=\"absolute top-0 left-0 h-full "
},
{
"path": "client/components/readers/Reader.vue",
"chars": 15416,
"preview": "<template>\n <div v-if=\"show\" id=\"reader\" :data-theme=\"ereaderTheme\" class=\"group absolute top-0 left-0 w-full z-60 data"
},
{
"path": "client/components/stats/DailyListeningChart.vue",
"chars": 7759,
"preview": "<template>\n <div class=\"w-96 my-6 mx-auto\">\n <h1 class=\"text-2xl mb-4\">{{ $strings.HeaderStatsMinutesListeningChart "
},
{
"path": "client/components/stats/Heatmap.vue",
"chars": 10147,
"preview": "<template>\n <div id=\"heatmap\" class=\"w-full\">\n <div class=\"mx-auto\" :style=\"{ height: innerHeight + 160 + 'px', widt"
},
{
"path": "client/components/stats/PreviewIcons.vue",
"chars": 3144,
"preview": "<template>\n <div class=\"flex flex-wrap justify-center mt-6\">\n <div class=\"flex p-2\">\n <span class=\"material-sym"
},
{
"path": "client/components/stats/YearInReview.vue",
"chars": 11103,
"preview": "<template>\n <div>\n <div v-if=\"processing\" role=\"img\" :aria-label=\"$strings.MessageLoading\" class=\"max-w-[800px] h-80"
},
{
"path": "client/components/stats/YearInReviewBanner.vue",
"chars": 8482,
"preview": "<template>\n <div class=\"bg-bg rounded-md shadow-lg border border-white/5 p-1 sm:p-4 mb-4\">\n <!-- hack to get icon fo"
},
{
"path": "client/components/stats/YearInReviewServer.vue",
"chars": 9868,
"preview": "<template>\n <div>\n <div v-if=\"processing\" role=\"img\" :aria-label=\"$strings.MessageLoading\" class=\"max-w-[800px] h-80"
},
{
"path": "client/components/stats/YearInReviewShort.vue",
"chars": 6639,
"preview": "<template>\n <div>\n <div v-if=\"processing\" class=\"max-w-[600px] h-32 sm:h-[200px] flex items-center justify-center\">\n"
},
{
"path": "client/components/tables/ApiKeysTable.vue",
"chars": 5223,
"preview": "<template>\n <div>\n <div class=\"text-center\">\n <table v-if=\"apiKeys.length > 0\" id=\"api-keys\">\n <tr>\n "
},
{
"path": "client/components/tables/AudioTracksTableRow.vue",
"chars": 3172,
"preview": "<template>\n <tr>\n <td class=\"text-center\">\n <p>{{ track.index }}</p>\n </td>\n <td class=\"font-sans\">{{ sho"
},
{
"path": "client/components/tables/BackupsTable.vue",
"chars": 8183,
"preview": "<template>\n <div class=\"text-center mt-4 relative\">\n <div class=\"flex py-4\">\n <ui-file-input ref=\"fileInput\" cl"
},
{
"path": "client/components/tables/ChaptersTable.vue",
"chars": 3948,
"preview": "<template>\n <div class=\"w-full my-2\">\n <div class=\"w-full bg-primary px-6 py-2 flex items-center cursor-pointer\" @cl"
},
{
"path": "client/components/tables/CollectionBooksTable.vue",
"chars": 3053,
"preview": "<template>\n <div class=\"w-full bg-primary/40\">\n <div class=\"w-full h-14 flex items-center px-4 md:px-6 py-2 bg-prima"
},
{
"path": "client/components/tables/CustomMetadataProviderTable.vue",
"chars": 3227,
"preview": "<template>\n <div class=\"min-h-40\">\n <table v-if=\"providers.length\" id=\"providers\">\n <tr>\n <th>{{ $string"
},
{
"path": "client/components/tables/EbookFilesTable.vue",
"chars": 3318,
"preview": "<template>\n <div class=\"w-full my-2\">\n <div class=\"w-full bg-primary px-4 md:px-6 py-2 flex items-center cursor-poin"
},
{
"path": "client/components/tables/EbookFilesTableRow.vue",
"chars": 4120,
"preview": "<template>\n <tr>\n <td class=\"px-4\">\n {{ showFullPath ? file.metadata.path : file.metadata.relPath }} <ui-toolti"
},
{
"path": "client/components/tables/LibraryFilesTable.vue",
"chars": 3567,
"preview": "<template>\n <div class=\"w-full my-2\">\n <div class=\"w-full bg-primary px-4 md:px-6 py-2 flex items-center cursor-poin"
},
{
"path": "client/components/tables/LibraryFilesTableRow.vue",
"chars": 2966,
"preview": "<template>\n <tr>\n <td class=\"px-4\">\n {{ showFullPath ? file.metadata.path : file.metadata.relPath }}\n </td>\n"
},
{
"path": "client/components/tables/PlaylistItemsTable.vue",
"chars": 3622,
"preview": "<template>\n <div class=\"w-full bg-primary/40\">\n <div class=\"w-full h-14 flex items-center px-4 md:px-6 py-2 bg-prima"
},
{
"path": "client/components/tables/TracksTable.vue",
"chars": 3462,
"preview": "<template>\n <div class=\"w-full my-2\">\n <div class=\"w-full bg-primary px-4 md:px-6 py-2 flex items-center cursor-poin"
},
{
"path": "client/components/tables/UploadedFilesTable.vue",
"chars": 1651,
"preview": "<template>\n <div class=\"w-full my-4\">\n <div class=\"w-full bg-primary px-6 py-1 flex items-center cursor-pointer\" @cl"
},
{
"path": "client/components/tables/UsersTable.vue",
"chars": 7149,
"preview": "<template>\n <div>\n <div class=\"text-center\">\n <table id=\"accounts\">\n <tr>\n <th>{{ $strings.Labe"
},
{
"path": "client/components/tables/collection/BookTableRow.vue",
"chars": 7669,
"preview": "<template>\n <div class=\"w-full px-1 md:px-2 py-2 overflow-hidden relative\" @mouseover=\"mouseover\" @mouseleave=\"mouselea"
},
{
"path": "client/components/tables/library/LibrariesTable.vue",
"chars": 3221,
"preview": "<template>\n <div>\n <draggable v-if=\"libraryCopies.length\" :list=\"libraryCopies\" v-bind=\"dragOptions\" class=\"list-gro"
},
{
"path": "client/components/tables/library/LibraryItem.vue",
"chars": 5687,
"preview": "<template>\n <div class=\"w-full pl-2 pr-4 md:px-4 h-12 border border-white/10 flex items-center relative -mt-px\" :class="
},
{
"path": "client/components/tables/playlist/ItemTableRow.vue",
"chars": 8180,
"preview": "<template>\n <div class=\"w-full px-1 md:px-2 py-2 overflow-hidden relative\" @mouseover=\"mouseover\" @mouseleave=\"mouselea"
},
{
"path": "client/components/tables/podcast/DownloadQueueTable.vue",
"chars": 2318,
"preview": "<template>\n <div class=\"w-full my-2\">\n <div class=\"w-full bg-primary px-4 md:px-6 py-2 flex items-center\">\n <p "
},
{
"path": "client/components/tables/podcast/LazyEpisodeRow.vue",
"chars": 9622,
"preview": "<template>\n <div :id=\"`lazy-episode-${index}`\" class=\"w-full h-full cursor-pointer\" @mouseover=\"mouseover\" @mouseleave="
},
{
"path": "client/components/tables/podcast/LazyEpisodesTable.vue",
"chars": 22478,
"preview": "<template>\n <div id=\"lazy-episodes-table\" class=\"w-full py-6\">\n <div class=\"flex flex-wrap flex-col md:flex-row md:i"
},
{
"path": "client/components/ui/Btn.vue",
"chars": 2311,
"preview": "<template>\n <nuxt-link v-if=\"to\" :to=\"to\" class=\"abs-btn rounded-md shadow-md relative border border-gray-600 text-cent"
},
{
"path": "client/components/ui/Checkbox.vue",
"chars": 2683,
"preview": "<template>\n <label tabindex=\"0\" ref=\"labelRef\" class=\"flex justify-start items-center\" :class=\"!disabled ? 'cursor-poin"
},
{
"path": "client/components/ui/ContextMenuDropdown.vue",
"chars": 4615,
"preview": "<template>\n <div class=\"relative h-9 w-9\" v-click-outside=\"clickOutsideObj\">\n <slot :disabled=\"disabled\" :showMenu=\""
},
{
"path": "client/components/ui/Dropdown.vue",
"chars": 4065,
"preview": "<template>\n <div class=\"relative w-full\" v-click-outside=\"clickOutsideObj\">\n <p v-if=\"label\" class=\"text-sm font-sem"
},
{
"path": "client/components/ui/EditableText.vue",
"chars": 1151,
"preview": "<template>\n <input ref=\"input\" v-model=\"inputValue\" :type=\"type\" :readonly=\"readonly\" :disabled=\"disabled\" :placeholder"
},
{
"path": "client/components/ui/FileInput.vue",
"chars": 984,
"preview": "<template>\n <div>\n <input ref=\"fileInput\" type=\"file\" :accept=\"accept\" class=\"hidden\" @change=\"inputChanged\" />\n "
},
{
"path": "client/components/ui/IconBtn.vue",
"chars": 2188,
"preview": "<template>\n <button :aria-label=\"ariaLabel\" class=\"icon-btn rounded-md flex items-center justify-center relative\" @mous"
},
{
"path": "client/components/ui/InputDropdown.vue",
"chars": 4545,
"preview": "<template>\n <div class=\"w-full\">\n <label v-if=\"label\" class=\"px-1 text-sm font-semibold\" :class=\"disabled ? 'text-gr"
},
{
"path": "client/components/ui/LibrariesDropdown.vue",
"chars": 4039,
"preview": "<template>\n <div v-if=\"currentLibrary\" class=\"relative h-8 max-w-52 md:min-w-32\" v-click-outside=\"clickOutsideObj\">\n "
},
{
"path": "client/components/ui/LibraryIcon.vue",
"chars": 866,
"preview": "<template>\n <div :class=\"`${classList}`\" class=\"flex items-center justify-center\">\n <span class=\"abs-icons\" :class=\""
},
{
"path": "client/components/ui/LoadingIndicator.vue",
"chars": 1803,
"preview": "<template>\n <div :class=\"hasSlotContent ? 'w-auto' : 'w-40'\">\n <div class=\"bg-bg border border-gray-500 py-2 px-5 ro"
},
{
"path": "client/components/ui/MediaIconPicker.vue",
"chars": 2116,
"preview": "<template>\n <div class=\"relative w-full h-9\" v-click-outside=\"clickOutsideObj\">\n <p class=\"text-sm font-semibold\">{{"
}
]
// ... and 712 more files (download for full content)
About this extraction
This page contains the full source code of the advplyr/audiobookshelf GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 912 files (7.7 MB), approximately 2.1M tokens, and a symbol index with 3797 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.