From 7cb7584a7e6e7935e087cd995c8594cb4fd05e8c Mon Sep 17 00:00:00 2001 From: Alexis Tyler Date: Thu, 23 Aug 2018 18:03:01 +0930 Subject: [PATCH] Convert titles and headers to VueRouter (#4663) * Add routes * Update templates for the added routes * Sync themes * Webpack * More /home routes * /home/snatchSelection * /home/testRename * /home/postprocess * /home/status * More /addShows routes * /addShows/trendingShows * /addShows/popularShows * /addShows/popularAnime * More / routes * /news * /changes * /IRC * Move /errorlogs routes * Restart & Shutdown * Format router.js * Add missing router, change errorlogs header * Update errorlogs.mako * Simplify route nesting * Add (extremely) simple router test * Put router after store in schedule.mako * Add router to 404.mako 500.mako They don't have paths so nothing to add to the router. --- .../slim/src/components/display-show.vue | 13 +- themes-default/slim/src/components/home.vue | 8 - .../src/components/manual-post-process.vue | 8 - .../slim/src/components/snatch-selection.vue | 17 + themes-default/slim/src/router.js | 298 +++++++++++++++++- themes-default/slim/test/specs/router.spec.js | 6 + themes-default/slim/views/404.mako | 1 + themes-default/slim/views/500.mako | 1 + themes-default/slim/views/IRC.mako | 1 + .../slim/views/addShows_addExistingShow.mako | 6 +- .../slim/views/addShows_newShow.mako | 6 +- .../slim/views/addShows_recommended.mako | 7 +- .../slim/views/addShows_trendingShows.mako | 5 +- themes-default/slim/views/config_anime.mako | 13 +- .../slim/views/config_backuprestore.mako | 7 +- themes-default/slim/views/config_general.mako | 13 +- .../slim/views/config_notifications.mako | 13 +- .../slim/views/config_postProcessing.mako | 7 +- .../slim/views/config_providers.mako | 11 +- themes-default/slim/views/config_search.mako | 7 +- .../slim/views/config_subtitles.mako | 7 +- themes-default/slim/views/editShow.mako | 14 +- themes-default/slim/views/errorlogs.mako | 30 +- themes-default/slim/views/genericMessage.mako | 1 + themes-default/slim/views/history.mako | 7 +- themes-default/slim/views/home.mako | 2 +- .../slim/views/home_postprocess.mako | 3 +- themes-default/slim/views/manage.mako | 13 +- .../slim/views/manage_backlogOverview.mako | 6 +- .../slim/views/manage_episodeStatuses.mako | 6 +- .../slim/views/manage_failedDownloads.mako | 7 +- .../slim/views/manage_manageSearches.mako | 6 +- .../slim/views/manage_massEdit.mako | 4 +- .../slim/views/manage_subtitleMissed.mako | 13 +- .../slim/views/manage_subtitleMissedPP.mako | 11 +- themes-default/slim/views/markdown.mako | 1 + themes-default/slim/views/restart.mako | 3 +- themes-default/slim/views/schedule.mako | 2 +- themes-default/slim/views/testRename.mako | 13 +- themes-default/slim/views/viewlogs.mako | 11 +- themes/dark/assets/js/vendors.js | 10 +- themes/dark/templates/404.mako | 1 + themes/dark/templates/500.mako | 1 + themes/dark/templates/IRC.mako | 1 + .../templates/addShows_addExistingShow.mako | 6 +- themes/dark/templates/addShows_newShow.mako | 6 +- .../dark/templates/addShows_recommended.mako | 7 +- .../templates/addShows_trendingShows.mako | 5 +- themes/dark/templates/config_anime.mako | 13 +- .../dark/templates/config_backuprestore.mako | 7 +- themes/dark/templates/config_general.mako | 13 +- .../dark/templates/config_notifications.mako | 13 +- .../dark/templates/config_postProcessing.mako | 7 +- themes/dark/templates/config_providers.mako | 11 +- themes/dark/templates/config_search.mako | 7 +- themes/dark/templates/config_subtitles.mako | 7 +- themes/dark/templates/editShow.mako | 14 +- themes/dark/templates/errorlogs.mako | 30 +- themes/dark/templates/genericMessage.mako | 1 + themes/dark/templates/history.mako | 7 +- themes/dark/templates/home.mako | 2 +- themes/dark/templates/home_postprocess.mako | 3 +- themes/dark/templates/manage.mako | 13 +- .../templates/manage_backlogOverview.mako | 6 +- .../templates/manage_episodeStatuses.mako | 6 +- .../templates/manage_failedDownloads.mako | 7 +- .../dark/templates/manage_manageSearches.mako | 6 +- themes/dark/templates/manage_massEdit.mako | 4 +- .../dark/templates/manage_subtitleMissed.mako | 13 +- .../templates/manage_subtitleMissedPP.mako | 11 +- themes/dark/templates/markdown.mako | 1 + themes/dark/templates/restart.mako | 3 +- themes/dark/templates/schedule.mako | 2 +- themes/dark/templates/testRename.mako | 13 +- themes/dark/templates/viewlogs.mako | 11 +- themes/light/assets/js/vendors.js | 10 +- themes/light/templates/404.mako | 1 + themes/light/templates/500.mako | 1 + themes/light/templates/IRC.mako | 1 + .../templates/addShows_addExistingShow.mako | 6 +- themes/light/templates/addShows_newShow.mako | 6 +- .../light/templates/addShows_recommended.mako | 7 +- .../templates/addShows_trendingShows.mako | 5 +- themes/light/templates/config_anime.mako | 13 +- .../light/templates/config_backuprestore.mako | 7 +- themes/light/templates/config_general.mako | 13 +- .../light/templates/config_notifications.mako | 13 +- .../templates/config_postProcessing.mako | 7 +- themes/light/templates/config_providers.mako | 11 +- themes/light/templates/config_search.mako | 7 +- themes/light/templates/config_subtitles.mako | 7 +- themes/light/templates/editShow.mako | 14 +- themes/light/templates/errorlogs.mako | 30 +- themes/light/templates/genericMessage.mako | 1 + themes/light/templates/history.mako | 7 +- themes/light/templates/home.mako | 2 +- themes/light/templates/home_postprocess.mako | 3 +- themes/light/templates/manage.mako | 13 +- .../templates/manage_backlogOverview.mako | 6 +- .../templates/manage_episodeStatuses.mako | 6 +- .../templates/manage_failedDownloads.mako | 7 +- .../templates/manage_manageSearches.mako | 6 +- themes/light/templates/manage_massEdit.mako | 4 +- .../templates/manage_subtitleMissed.mako | 13 +- .../templates/manage_subtitleMissedPP.mako | 11 +- themes/light/templates/markdown.mako | 1 + themes/light/templates/restart.mako | 3 +- themes/light/templates/schedule.mako | 2 +- themes/light/templates/testRename.mako | 13 +- themes/light/templates/viewlogs.mako | 11 +- 110 files changed, 599 insertions(+), 545 deletions(-) create mode 100644 themes-default/slim/test/specs/router.spec.js diff --git a/themes-default/slim/src/components/display-show.vue b/themes-default/slim/src/components/display-show.vue index 32b42273a7..339a9f6abc 100644 --- a/themes-default/slim/src/components/display-show.vue +++ b/themes-default/slim/src/components/display-show.vue @@ -11,8 +11,17 @@ export default { AppLink, PlotInfo }, - data() { - return {}; + metaInfo() { + if (!this.show || !this.show.title) { + return { + title: 'Medusa' + }; + } + const { title } = this.show; + return { + title, + titleTemplate: '%s | Medusa' + }; }, computed: { ...mapState({ diff --git a/themes-default/slim/src/components/home.vue b/themes-default/slim/src/components/home.vue index f2e141650f..78d93699d5 100644 --- a/themes-default/slim/src/components/home.vue +++ b/themes-default/slim/src/components/home.vue @@ -9,14 +9,6 @@ export default { components: { AppLink }, - metaInfo: { - title: 'Home' - }, - data() { - return { - header: 'Show List' - }; - }, computed: { ...mapState([ 'config' diff --git a/themes-default/slim/src/components/manual-post-process.vue b/themes-default/slim/src/components/manual-post-process.vue index c67b671d63..e2c8897804 100644 --- a/themes-default/slim/src/components/manual-post-process.vue +++ b/themes-default/slim/src/components/manual-post-process.vue @@ -2,14 +2,6 @@ export default { name: 'manual-post-process', template: '#manual-post-process-template', - metaInfo: { - title: 'Post Processing' - }, - data() { - return { - header: 'Post Processing' - }; - }, mounted() { $('#episodeDir').fileBrowser({ title: 'Select Unprocessed Episode Folder', diff --git a/themes-default/slim/src/components/snatch-selection.vue b/themes-default/slim/src/components/snatch-selection.vue index fa4e6e19af..f015bc9494 100644 --- a/themes-default/slim/src/components/snatch-selection.vue +++ b/themes-default/slim/src/components/snatch-selection.vue @@ -8,6 +8,18 @@ export default { components: { AppLink }, + metaInfo() { + if (!this.show || !this.show.title) { + return { + title: 'Medusa' + }; + } + const { title } = this.show; + return { + title, + titleTemplate: '%s | Medusa' + }; + }, computed: { ...mapState({ shows: state => state.shows.shows @@ -43,6 +55,11 @@ export default { return show; } }, + created() { + const { indexer, id, $store } = this; + // Needed for the title + $store.dispatch('getShow', { indexer, id }); + }, mounted() { window.addEventListener('load', () => { // Adjust the summary background position and size diff --git a/themes-default/slim/src/router.js b/themes-default/slim/src/router.js index ea6dc41be9..e949a6cb26 100644 --- a/themes-default/slim/src/router.js +++ b/themes-default/slim/src/router.js @@ -7,17 +7,58 @@ import { NotFound } from './components'; -const routes = [{ - path: '/login', - name: 'login', +const homeRoutes = [{ + path: '/home', + name: 'home', meta: { - title: 'Login' - }, - component: Login + title: 'Home', + header: 'Show List' + } +}, { + path: '/home/editShow', + name: 'editShow' }, { path: '/home/displayShow', name: 'displayShow' }, { + path: '/home/snatchSelection', + name: 'snatchSelection' +}, { + path: '/home/testRename', + name: 'testRename', + meta: { + title: 'Preview Rename', + header: 'Preview Rename' + } +}, { + path: '/home/postprocess', + name: 'postprocess', + meta: { + title: 'Manual Post-Processing', + header: 'Manual Post-Processing' + } +}, { + path: '/home/status', + name: 'status', + meta: { + title: 'Status' + } +}, { + path: '/home/restart', + name: 'restart', + meta: { + title: 'Restarting...', + header: 'Performing Restart' + } +}, { + path: '/home/shutdown', + name: 'shutdown', + meta: { + header: 'Shutting down' + } +}]; + +const configRoutes = [{ path: '/config', name: 'config', meta: { @@ -26,6 +67,64 @@ const routes = [{ }, component: Config }, { + path: '/config/anime', + name: 'configAnime', + meta: { + title: 'Config - Anime', + header: 'Anime' + } +}, { + path: '/config/backuprestore', + name: 'configBackupRestore', + meta: { + title: 'Config - Backup/Restore', + header: 'Backup/Restore' + } +}, { + path: '/config/general', + name: 'configGeneral', + meta: { + title: 'Config - General', + header: 'General Configuration' + } +}, { + path: '/config/notifications', + name: 'configNotifications', + meta: { + title: 'Config - Notifications', + header: 'Notifications' + } +}, { + path: '/config/postProcessing', + name: 'configPostProcessing', + meta: { + title: 'Config - Post Processing', + header: 'Post Processing' + } +}, { + path: '/config/providers', + name: 'configSearchProviders', + meta: { + title: 'Config - Providers', + header: 'Search Providers' + } +}, { + path: '/config/search', + name: 'configSearchSettings', + meta: { + title: 'Config - Episode Search', + header: 'Search Settings' + } +}, { + path: '/config/subtitles', + name: 'configSubtitles', + meta: { + title: 'Config - Subtitles', + header: 'Subtitles' + } +}]; + +const addShowRoutes = [{ path: '/addShows', name: 'addShows', meta: { @@ -34,6 +133,48 @@ const routes = [{ }, component: AddShows }, { + path: '/addShows/addExistingShows', + name: 'addExistingShows', + meta: { + title: 'Add Existing Shows', + header: 'Add Existing Shows' + } +}, { + path: '/addShows/newShow', + name: 'addNewShow', + meta: { + title: 'Add New Show', + header: 'Add New Show' + } +}, { + path: '/addShows/trendingShows', + name: 'addTrendingShows' +}, { + path: 'popularShows', + name: 'addPopularShows', + meta: { + title: 'Popular Shows', + header: 'Popular Shows' + } +}, { + path: '/addShows/popularAnime', + name: 'addPopularAnime', + meta: { + title: 'Popular Anime Shows', + header: 'Popular Anime Shows' + } +}]; + +const loginRoute = { + path: '/login', + name: 'login', + meta: { + title: 'Login' + }, + component: Login +}; + +const addRecommendedRoute = { path: '/addRecommended', name: 'addRecommended', meta: { @@ -41,15 +182,126 @@ const routes = [{ header: 'Add Recommended Shows' }, component: AddRecommended -}, { +}; + +const scheduleRoute = { path: '/schedule', name: 'schedule', meta: { title: 'Schedule', header: 'Schedule' - }, // eslint-disable-line comma-dangle - // component: scheduleComponent + } +}; + +const historyRoute = { + path: '/history', + name: 'history', + meta: { + title: 'History', + header: 'History' + } +}; + +const manageRoutes = [{ + path: '/manage', + name: 'manage', + meta: { + title: 'Mass Update', + header: 'Mass Update' + } +}, { + path: '/manage/backlogOverview', + name: 'manageBacklogOverview', + meta: { + title: 'Backlog Overview', + header: 'Backlog Overview' + } +}, { + path: '/manage/episodeStatuses', + name: 'manageEpisodeOverview', + meta: { + title: 'Episode Overview', + header: 'Episode Overview' + } +}, { + path: '/manage/failedDownloads', + name: 'manageFailedDownloads', + meta: { + title: 'Failed Downloads', + header: 'Failed Downlaods' + } +}, { + path: '/manage/manageSearches', + name: 'manageManageSearches', + meta: { + title: 'Manage Searches', + header: 'Manage Searches' + } +}, { + path: '/manage/massEdit', + name: 'manageMassEdit', + meta: { + title: 'Mass Edit' + } }, { + path: '/manage/subtitleMissed', + name: 'manageSubtitleMissed', + meta: { + title: 'Missing Subtitles', + header: 'Missing Subtitles' + } +}, { + path: '/manage/subtitleMissedPP', + name: 'manageSubtitleMissedPP', + meta: { + title: 'Missing Subtitles in Post-Process folder', + header: 'Missing Subtitles in Post-Process folder' + } +}]; + +const errorLogsRoutes = [{ + path: '/errorlogs', + name: 'errorlogs', + meta: { + title: 'Logs & Errors' + } +}, { + path: '/errorlogs/viewlog', + name: 'viewlog', + meta: { + title: 'Logs', + header: 'Log File' + } +}]; + +const newsRoute = { + path: '/news', + name: 'news', + meta: { + title: 'News', + header: 'News' + } +}; + +const changesRoute = { + path: '/changes', + name: 'changes', + meta: { + title: 'Changelog', + header: 'Changelog' + } +}; + +const ircRoute = { + path: '/IRC', + name: 'IRC', + meta: { + title: 'IRC', + header: 'IRC' + } +}; + +const notFoundRoute = { path: '/not-found', name: 'not-found', meta: { @@ -57,11 +309,31 @@ const routes = [{ header: '404 - page not found' }, component: NotFound +}; + // @NOTE: Redirect can only be added once all routes are vue -// }, { -// path: '*', -// redirect: '/not-found' -}]; +/* +const notFoundRedirect = { + path: '*', + redirect: '/not-found' +}; +*/ + +const routes = [ + ...homeRoutes, + ...configRoutes, + ...addShowRoutes, + loginRoute, + addRecommendedRoute, + scheduleRoute, + historyRoute, + ...manageRoutes, + ...errorLogsRoutes, + newsRoute, + changesRoute, + ircRoute, + notFoundRoute +]; const router = new VueRouter({ base: document.body.getAttribute('web-root') + '/', diff --git a/themes-default/slim/test/specs/router.spec.js b/themes-default/slim/test/specs/router.spec.js new file mode 100644 index 0000000000..184c12984c --- /dev/null +++ b/themes-default/slim/test/specs/router.spec.js @@ -0,0 +1,6 @@ +import test from 'ava'; +import router from '../../src/router'; + +test('router compiles', t => { + t.truthy(router); +}); diff --git a/themes-default/slim/views/404.mako b/themes-default/slim/views/404.mako index a8d1d57980..d764922a57 100644 --- a/themes-default/slim/views/404.mako +++ b/themes-default/slim/views/404.mako @@ -4,6 +4,7 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', metaInfo: { title: '404' diff --git a/themes-default/slim/views/500.mako b/themes-default/slim/views/500.mako index 0e7c1e1f5c..56d7bde940 100644 --- a/themes-default/slim/views/500.mako +++ b/themes-default/slim/views/500.mako @@ -4,6 +4,7 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', metaInfo: { title: '500' diff --git a/themes-default/slim/views/IRC.mako b/themes-default/slim/views/IRC.mako index 9867962af2..2d782cbb21 100644 --- a/themes-default/slim/views/IRC.mako +++ b/themes-default/slim/views/IRC.mako @@ -4,6 +4,7 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', data() { return {}; diff --git a/themes-default/slim/views/addShows_addExistingShow.mako b/themes-default/slim/views/addShows_addExistingShow.mako index 788c839670..65e1c48ef0 100644 --- a/themes-default/slim/views/addShows_addExistingShow.mako +++ b/themes-default/slim/views/addShows_addExistingShow.mako @@ -14,10 +14,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Existing Show' - }, data() { <% indexers = { str(i): { 'name': v['name'], 'showUrl': v['show_url'] } for i, v in iteritems(indexerConfig) } %> return { @@ -219,7 +217,7 @@ window.app = new Vue({ <%block name="content"> -

Existing Show

+

{{ $route.meta.header }}

  • Add Existing Show
diff --git a/themes-default/slim/views/addShows_newShow.mako b/themes-default/slim/views/addShows_newShow.mako index 8ffef02446..e5e1b0b8da 100644 --- a/themes-default/slim/views/addShows_newShow.mako +++ b/themes-default/slim/views/addShows_newShow.mako @@ -32,10 +32,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'New Show' - }, data() { return { // @TODO: Fix Python conversions @@ -412,7 +410,7 @@ window.app = new Vue({ <%block name="content"> -

New Show

+

{{ $route.meta.header }}

  • Add New Show
diff --git a/themes-default/slim/views/addShows_recommended.mako b/themes-default/slim/views/addShows_recommended.mako index 5f7b1d1cf5..9ec0589069 100644 --- a/themes-default/slim/views/addShows_recommended.mako +++ b/themes-default/slim/views/addShows_recommended.mako @@ -7,6 +7,7 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', data() { return { @@ -19,11 +20,7 @@ window.app = new Vue({ <%block name="content">
- % if not header is UNDEFINED: -

${header}

- % else: -

${title}

- % endif +

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/addShows_trendingShows.mako b/themes-default/slim/views/addShows_trendingShows.mako index 14fb5dcedc..f5450013d2 100644 --- a/themes-default/slim/views/addShows_trendingShows.mako +++ b/themes-default/slim/views/addShows_trendingShows.mako @@ -10,6 +10,7 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', data() { return { @@ -22,11 +23,7 @@ window.app = new Vue({ <%block name="content">
- % if not header is UNDEFINED:

${header}

- % else: -

${title}

- % endif
diff --git a/themes-default/slim/views/config_anime.mako b/themes-default/slim/views/config_anime.mako index fe2615a09b..6c8b55e35e 100644 --- a/themes-default/slim/views/config_anime.mako +++ b/themes-default/slim/views/config_anime.mako @@ -7,21 +7,14 @@ window.app = {}; window.app = new Vue({ store, - el: '#vue-wrap', - metaInfo: { - title: 'Config - Anime' - }, - data() { - return { - header: 'Anime' - }; - } + router, + el: '#vue-wrap' }); <%block name="content">
-

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/config_backuprestore.mako b/themes-default/slim/views/config_backuprestore.mako index 7e25cbc353..e8c7805c3a 100644 --- a/themes-default/slim/views/config_backuprestore.mako +++ b/themes-default/slim/views/config_backuprestore.mako @@ -4,13 +4,10 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Config - Backup/Restore' - }, data() { return { - header: 'Backup/Restore', backup: { disabled: false, status: '', @@ -62,7 +59,7 @@ window.app = new Vue({ <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/config_general.mako b/themes-default/slim/views/config_general.mako index 1214ce3b7b..a9aa3652e2 100644 --- a/themes-default/slim/views/config_general.mako +++ b/themes-default/slim/views/config_general.mako @@ -16,20 +16,13 @@ window.app = {}; window.app = new Vue({ store, - el: '#vue-wrap', - metaInfo: { - title: 'Config - General' - }, - data() { - return { - header: 'General Configuration' - }; - } + router, + el: '#vue-wrap' }); <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

<% indexer = 0 %> % if app.INDEXER_DEFAULT: <% indexer = app.INDEXER_DEFAULT %> diff --git a/themes-default/slim/views/config_notifications.mako b/themes-default/slim/views/config_notifications.mako index 6b7e51d6b7..0d4b6bb472 100644 --- a/themes-default/slim/views/config_notifications.mako +++ b/themes-default/slim/views/config_notifications.mako @@ -12,20 +12,13 @@ window.app = {}; window.app = new Vue({ store, - el: '#vue-wrap', - metaInfo: { - title: 'Config - Notifications' - }, - data() { - return { - header: 'Notifications' - }; - } + router, + el: '#vue-wrap' }); <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/config_postProcessing.mako b/themes-default/slim/views/config_postProcessing.mako index 7ac461984b..761a906238 100644 --- a/themes-default/slim/views/config_postProcessing.mako +++ b/themes-default/slim/views/config_postProcessing.mako @@ -4,14 +4,11 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Config - Post Processing' - }, data() { return { configLoaded: false, - header: 'Post Processing', presets: [ { pattern: '%SN - %Sx%0E - %EN', example: 'Show Name - 2x03 - Ep Name' }, { pattern: '%S.N.S%0SE%0E.%E.N', example: 'Show.Name.S02E03.Ep.Name' }, @@ -188,7 +185,7 @@ window.app = new Vue({ <%block name="content">
-

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/config_providers.mako b/themes-default/slim/views/config_providers.mako index dcd6c28f39..7163f0fcb2 100644 --- a/themes-default/slim/views/config_providers.mako +++ b/themes-default/slim/views/config_providers.mako @@ -10,15 +10,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Config - Providers' - }, - data() { - return { - header: 'Search Providers' - }; - }, beforeMount() { $('#config-components').tabs(); }, @@ -854,7 +847,7 @@ window.app = new Vue({ <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/config_search.mako b/themes-default/slim/views/config_search.mako index 82d11a9e21..ecc567c9c7 100644 --- a/themes-default/slim/views/config_search.mako +++ b/themes-default/slim/views/config_search.mako @@ -13,13 +13,10 @@ def js_bool(value): window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Config - Episode Search' - }, data() { return { - header: 'Search Settings', clients: { torrent: { blackhole: { @@ -285,7 +282,7 @@ window.app = new Vue({ <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/config_subtitles.mako b/themes-default/slim/views/config_subtitles.mako index d33ef39d0e..18ab5e70e4 100644 --- a/themes-default/slim/views/config_subtitles.mako +++ b/themes-default/slim/views/config_subtitles.mako @@ -10,10 +10,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Config - Subtitles' - }, data() { <% subtitle_code_filter = [{'id': code, 'name': subtitles.name_from_code(code)} @@ -22,7 +20,6 @@ window.app = new Vue({ for code in subtitles.wanted_languages()] %> return { - header: 'Subtitles', services: ${json.dumps(subtitles.sorted_service_list())}, subtitleCodeFilter: ${json.dumps(subtitle_code_filter)}, wantedLanguages: ${json.dumps(wanted_languages)}, @@ -66,7 +63,7 @@ window.app = new Vue({ <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/editShow.mako b/themes-default/slim/views/editShow.mako index 6e800b390d..e230baa616 100644 --- a/themes-default/slim/views/editShow.mako +++ b/themes-default/slim/views/editShow.mako @@ -4,9 +4,19 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Edit Show' + metaInfo() { + const { title } = this.series; + if (!title) { + return { + title: 'Medusa' + }; + } + return { + title, + titleTemplate: 'Editing %s | Medusa' + }; }, data() { return { diff --git a/themes-default/slim/views/errorlogs.mako b/themes-default/slim/views/errorlogs.mako index 1334ad959d..649c63a903 100644 --- a/themes-default/slim/views/errorlogs.mako +++ b/themes-default/slim/views/errorlogs.mako @@ -1,22 +1,28 @@ <%inherit file="/layouts/main.mako"/> <%! - from medusa import logger - from medusa import classes + from medusa import classes, logger %> <%block name="scripts"> @@ -35,15 +41,13 @@ pre { from mako.filters import html_escape if logLevel == logger.WARNING: errors = classes.WarningViewer.errors - title = 'WARNING logs' else: errors = classes.ErrorViewer.errors - title = 'ERROR logs' %>
-

{{header}}

+

{{ header }}

diff --git a/themes-default/slim/views/genericMessage.mako b/themes-default/slim/views/genericMessage.mako index e132949c92..3b2d6e40cb 100644 --- a/themes-default/slim/views/genericMessage.mako +++ b/themes-default/slim/views/genericMessage.mako @@ -4,6 +4,7 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', data() { return {}; diff --git a/themes-default/slim/views/history.mako b/themes-default/slim/views/history.mako index 7833a83254..0a285434f2 100644 --- a/themes-default/slim/views/history.mako +++ b/themes-default/slim/views/history.mako @@ -18,13 +18,10 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'History' - }, data() { return { - header: 'History', limit: '${limit}' }; }, @@ -103,7 +100,7 @@ window.app = new Vue({
-

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/home.mako b/themes-default/slim/views/home.mako index 82d25dc295..0d5535e8b6 100644 --- a/themes-default/slim/views/home.mako +++ b/themes-default/slim/views/home.mako @@ -51,7 +51,7 @@
-

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/home_postprocess.mako b/themes-default/slim/views/home_postprocess.mako index 7f39c7f967..72caa571f4 100644 --- a/themes-default/slim/views/home_postprocess.mako +++ b/themes-default/slim/views/home_postprocess.mako @@ -8,7 +8,7 @@
-

{{header}}

+

{{ $route.meta.header }}

@@ -112,6 +112,7 @@ window.app = {}; window.app = new Vue({ el: '#vue-wrap', store, + router, data() { return { // This loads manual-post-process.vue diff --git a/themes-default/slim/views/manage.mako b/themes-default/slim/views/manage.mako index 8df3d779ee..c10d56598b 100644 --- a/themes-default/slim/views/manage.mako +++ b/themes-default/slim/views/manage.mako @@ -11,15 +11,8 @@ window.app = {}; window.app = new Vue({ store, - el: '#vue-wrap', - metaInfo: { - title: 'Mass Update' - }, - data() { - return { - header: 'Mass Update' - }; - } + router, + el: '#vue-wrap' }); @@ -32,7 +25,7 @@ window.app = new Vue({
-

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/manage_backlogOverview.mako b/themes-default/slim/views/manage_backlogOverview.mako index a08c652041..2959f7ee53 100644 --- a/themes-default/slim/views/manage_backlogOverview.mako +++ b/themes-default/slim/views/manage_backlogOverview.mako @@ -10,10 +10,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Backlog Overview' - }, data() { return { }; @@ -162,7 +160,7 @@ window.app = new Vue({
-

Backlog Overview

+

{{ $route.meta.header }}

diff --git a/themes-default/slim/views/manage_episodeStatuses.mako b/themes-default/slim/views/manage_episodeStatuses.mako index 25648fd561..682cb2f881 100644 --- a/themes-default/slim/views/manage_episodeStatuses.mako +++ b/themes-default/slim/views/manage_episodeStatuses.mako @@ -8,10 +8,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Episode Overview' - }, data() { return { }; @@ -81,7 +79,7 @@ window.app = new Vue({ <%block name="content">
-

Episode Overview

+

{{ $route.meta.header }}

% if not whichStatus or (whichStatus and not ep_counts): % if whichStatus:

None of your episodes have status ${statusStrings[int(whichStatus)]}

diff --git a/themes-default/slim/views/manage_failedDownloads.mako b/themes-default/slim/views/manage_failedDownloads.mako index d626631052..d6b448bf3a 100644 --- a/themes-default/slim/views/manage_failedDownloads.mako +++ b/themes-default/slim/views/manage_failedDownloads.mako @@ -15,13 +15,10 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Failed Downloads' - }, data() { return { - header: 'Failed Downloads', limit: '${limit}' }; } @@ -29,7 +26,7 @@ window.app = new Vue({ <%block name="content"> -

{{header}}

+

{{ $route.meta.header }}

Limit: -

{{header}}

+

{{ $route.meta.header }}

Preview of the proposed name changes

% if int(show.air_by_date) == 1 and app.NAMING_CUSTOM_ABD: diff --git a/themes-default/slim/views/viewlogs.mako b/themes-default/slim/views/viewlogs.mako index b8f1839076..eabdfafcf9 100644 --- a/themes-default/slim/views/viewlogs.mako +++ b/themes-default/slim/views/viewlogs.mako @@ -10,15 +10,8 @@ window.app = {}; window.app = new Vue({ store, + router, el: '#vue-wrap', - metaInfo: { - title: 'Logs' - }, - data() { - return { - header: 'Log File' - }; - }, mounted() { function getParams() { return $.param({ @@ -76,7 +69,7 @@ pre {
-

{{header}}

+

{{ $route.meta.header }}

diff --git a/themes/dark/assets/js/vendors.js b/themes/dark/assets/js/vendors.js index 3df1f2c847..2928290894 100644 --- a/themes/dark/assets/js/vendors.js +++ b/themes/dark/assets/js/vendors.js @@ -591,7 +591,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("/* WEBPACK VAR INJECTION */(function($) {\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _vuex = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n\nvar _api = __webpack_require__(/*! ../api */ \"./src/api.js\");\n\nvar _appLink = _interopRequireDefault(__webpack_require__(/*! ./app-link.vue */ \"./src/components/app-link.vue\"));\n\nvar _plotInfo = _interopRequireDefault(__webpack_require__(/*! ./plot-info.vue */ \"./src/components/plot-info.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar _default = {\n name: 'displayShow',\n template: '#display-show-template',\n components: {\n AppLink: _appLink.default,\n PlotInfo: _plotInfo.default\n },\n data: function data() {\n return {};\n },\n computed: _objectSpread({}, (0, _vuex.mapState)({\n shows: function shows(state) {\n return state.shows.shows;\n }\n }), (0, _vuex.mapGetters)(['getShowById']), {\n indexer: function indexer() {\n return this.$route.query.indexername;\n },\n id: function id() {\n return this.$route.query.seriesid;\n },\n show: function show() {\n var indexer = this.indexer,\n id = this.id,\n getShowById = this.getShowById,\n shows = this.shows,\n $store = this.$store;\n var defaults = $store.state.defaults;\n\n if (shows.length === 0 || !indexer || !id) {\n return defaults.show;\n }\n\n var show = getShowById({\n indexer: indexer,\n id: id\n });\n\n if (!show) {\n return defaults.show;\n }\n\n return show;\n }\n }),\n mounted: function mounted() {\n var moveSummaryBackground = this.moveSummaryBackground,\n movecheckboxControlsBackground = this.movecheckboxControlsBackground,\n setQuality = this.setQuality,\n setEpisodeSceneNumbering = this.setEpisodeSceneNumbering,\n setAbsoluteSceneNumbering = this.setAbsoluteSceneNumbering,\n setInputValidInvalid = this.setInputValidInvalid,\n setSeasonSceneException = this.setSeasonSceneException,\n showHideRows = this.showHideRows;\n $(window).on('resize', function () {\n moveSummaryBackground();\n movecheckboxControlsBackground();\n });\n window.addEventListener('load', function () {\n // Adjust the summary background position and size\n window.dispatchEvent(new Event('resize'));\n $.ajaxEpSearch({\n colorRow: true\n });\n startAjaxEpisodeSubtitles(); // eslint-disable-line no-undef\n\n $.ajaxEpSubtitlesSearch();\n $.ajaxEpRedownloadSubtitle();\n });\n $(document.body).on('click', '.imdbPlot', function (event) {\n var $target = $(event.currentTarget);\n $target.prev('span').toggle();\n\n if ($target.html() === '..show less') {\n $target.html('..show more');\n } else {\n $target.html('..show less');\n }\n\n moveSummaryBackground();\n movecheckboxControlsBackground();\n });\n $(document.body).on('change', '#seasonJump', function (event) {\n var id = $('#seasonJump option:selected').val();\n\n if (id && id !== 'jump') {\n var season = $('#seasonJump option:selected').data('season');\n $('html,body').animate({\n scrollTop: $('[name=\"' + id.substring(1) + '\"]').offset().top - 100\n }, 'slow');\n $('#collapseSeason-' + season).collapse('show');\n location.hash = id;\n }\n\n $(event.currentTarget).val('jump');\n });\n $(document.body).on('click', '#changeStatus', function () {\n var epArr = [];\n var status = $('#statusSelect').val();\n var quality = $('#qualitySelect').val();\n var showSlug = $('#series-slug').val();\n $('.epCheck').each(function (index, element) {\n if (element.checked === true) {\n epArr.push($(element).attr('id'));\n }\n });\n\n if (epArr.length === 0) {\n return false;\n }\n\n if (quality) {\n setQuality(quality, showSlug, epArr);\n }\n\n if (status) {\n window.location.href = $('base').attr('href') + 'home/setStatus?' + 'indexername=' + $('#indexer-name').attr('value') + '&seriesid=' + $('#series-id').attr('value') + '&eps=' + epArr.join('|') + '&status=' + status;\n }\n });\n $(document.body).on('click', '.seasonCheck', function (event) {\n var seasCheck = event.currentTarget;\n var seasNo = $(seasCheck).attr('id');\n $('#collapseSeason-' + seasNo).collapse('show');\n var seasonIdentifier = 's' + seasNo;\n $('.epCheck:visible').each(function (index, element) {\n var epParts = $(element).attr('id').split('e');\n\n if (epParts[0] === seasonIdentifier) {\n element.checked = seasCheck.checked;\n }\n });\n });\n var lastCheck = null;\n $(document.body).on('click', '.epCheck', function (event) {\n var target = event.currentTarget;\n\n if (!lastCheck || !event.shiftKey) {\n lastCheck = target;\n return;\n }\n\n var check = target;\n var found = 0;\n $('.epCheck').each(function (index, element) {\n if (found === 1) {\n element.checked = lastCheck.checked;\n }\n\n if (found === 2) {\n return false;\n }\n\n if (element === check || element === lastCheck) {\n found++;\n }\n });\n }); // Selects all visible episode checkboxes.\n\n $(document.body).on('click', '.seriesCheck', function () {\n $('.epCheck:visible').each(function (index, element) {\n element.checked = true;\n });\n $('.seasonCheck:visible').each(function (index, element) {\n element.checked = true;\n });\n }); // Clears all visible episode checkboxes and the season selectors\n\n $(document.body).on('click', '.clearAll', function () {\n $('.epCheck:visible').each(function (index, element) {\n element.checked = false;\n });\n $('.seasonCheck:visible').each(function (index, element) {\n element.checked = false;\n });\n }); // Show/hide different types of rows when the checkboxes are changed\n\n $(document.body).on('change', '#checkboxControls input', function (event) {\n var whichClass = $(event.currentTarget).attr('id');\n showHideRows(whichClass);\n }); // Initially show/hide all the rows according to the checkboxes\n\n $('#checkboxControls input').each(function (index, element) {\n var status = $(element).prop('checked');\n $('tr.' + $(element).attr('id')).each(function (index, tableRow) {\n if (status) {\n $(tableRow).show();\n } else {\n $(tableRow).hide();\n }\n });\n });\n $(document.body).on('change', '.sceneSeasonXEpisode', function (event) {\n var target = event.currentTarget; // Strip non-numeric characters\n\n var value = $(target).val();\n $(target).val(value.replace(/[^0-9xX]*/g, ''));\n var forSeason = $(target).attr('data-for-season');\n var forEpisode = $(target).attr('data-for-episode'); // If empty reset the field\n\n if (value === '') {\n setEpisodeSceneNumbering(forSeason, forEpisode, null, null);\n return;\n }\n\n var m = $(target).val().match(/^(\\d+)x(\\d+)$/i);\n var onlyEpisode = $(target).val().match(/^(\\d+)$/i);\n var sceneSeason = null;\n var sceneEpisode = null;\n var isValid = false;\n\n if (m) {\n sceneSeason = m[1];\n sceneEpisode = m[2];\n isValid = setInputValidInvalid(true, $(target));\n } else if (onlyEpisode) {\n // For example when '5' is filled in instead of '1x5', asume it's the first season\n sceneSeason = forSeason;\n sceneEpisode = onlyEpisode[1];\n isValid = setInputValidInvalid(true, $(target));\n } else {\n isValid = setInputValidInvalid(false, $(target));\n }\n\n if (isValid) {\n setEpisodeSceneNumbering(forSeason, forEpisode, sceneSeason, sceneEpisode);\n }\n });\n $(document.body).on('change', '.sceneAbsolute', function (event) {\n var target = event.currentTarget; // Strip non-numeric characters\n\n $(target).val($(target).val().replace(/[^0-9xX]*/g, ''));\n var forAbsolute = $(target).attr('data-for-absolute');\n var m = $(target).val().match(/^(\\d{1,3})$/i);\n var sceneAbsolute = null;\n\n if (m) {\n sceneAbsolute = m[1];\n }\n\n setAbsoluteSceneNumbering(forAbsolute, sceneAbsolute);\n });\n $('#showTable, #animeTable').tablesorter({\n widgets: ['saveSort', 'stickyHeaders', 'columnSelector'],\n widgetOptions: {\n columnSelector_saveColumns: true,\n // eslint-disable-line camelcase\n columnSelector_layout: '',\n // eslint-disable-line camelcase\n columnSelector_mediaquery: false,\n // eslint-disable-line camelcase\n columnSelector_cssChecked: 'checked' // eslint-disable-line camelcase\n\n }\n });\n $('#popover').popover({\n placement: 'bottom',\n html: true,\n // Required if content has HTML\n content: '
'\n }).on('shown.bs.popover', function () {\n // Bootstrap popover event triggered when the popover opens\n $.tablesorter.columnSelector.attachTo($('#showTable, #animeTable'), '#popover-target');\n }); // Moved and rewritten this from displayShow. This changes the button when clicked for collapsing/expanding the\n // Season to Show Episodes or Hide Episodes.\n\n $('.collapse.toggle').on('hide.bs.collapse', function () {\n var reg = /collapseSeason-(\\d+)/g;\n var result = reg.exec(this.id);\n $('#showseason-' + result[1]).text('Show Episodes');\n $('#season-' + result[1] + '-cols').addClass('shadow');\n });\n $('.collapse.toggle').on('show.bs.collapse', function () {\n var reg = /collapseSeason-(\\d+)/g;\n var result = reg.exec(this.id);\n $('#showseason-' + result[1]).text('Hide Episodes');\n $('#season-' + result[1] + '-cols').removeClass('shadow');\n }); // Generate IMDB stars\n\n $('.imdbstars').each(function (index, element) {\n $(element).html($('').width($(element).text() * 12));\n });\n attachImdbTooltip(); // eslint-disable-line no-undef\n // @TODO: OMG: This is just a basic json, in future it should be based on the CRUD route.\n // Get the season exceptions and the xem season mappings.\n\n $.getJSON('home/getSeasonSceneExceptions', {\n indexername: $('#indexer-name').val(),\n seriesid: $('#series-id').val() // eslint-disable-line camelcase\n\n }, function (data) {\n setSeasonSceneException(data);\n });\n $(document.body).on('click', '.display-specials a', function (event) {\n _api.api.patch('config/main', {\n layout: {\n show: {\n specials: $(event.currentTarget).text() !== 'Hide'\n }\n }\n }).then(function (response) {\n log.info(response.data);\n window.location.reload();\n }).catch(function (error) {\n log.error(error.data);\n });\n });\n },\n methods: {\n // Adjust the summary background position and size on page load and resize\n moveSummaryBackground: function moveSummaryBackground() {\n var height = $('#summary').height() + 10;\n var top = $('#summary').offset().top + 5;\n $('#summaryBackground').height(height);\n $('#summaryBackground').offset({\n top: top,\n left: 0\n });\n $('#summaryBackground').show();\n },\n movecheckboxControlsBackground: function movecheckboxControlsBackground() {\n var height = $('#checkboxControls').height() + 10;\n var top = $('#checkboxControls').offset().top - 3;\n $('#checkboxControlsBackground').height(height);\n $('#checkboxControlsBackground').offset({\n top: top,\n left: 0\n });\n $('#checkboxControlsBackground').show();\n },\n setQuality: function setQuality(quality, showSlug, episodes) {\n var patchData = {};\n episodes.forEach(function (episode) {\n patchData[episode] = {\n quality: parseInt(quality, 10)\n };\n });\n\n _api.api.patch('series/' + showSlug + '/episodes', patchData).then(function (response) {\n log.info(response.data);\n window.location.reload();\n }).catch(function (error) {\n log.error(error.data);\n });\n },\n setEpisodeSceneNumbering: function setEpisodeSceneNumbering(forSeason, forEpisode, sceneSeason, sceneEpisode) {\n var indexerName = $('#indexer-name').val();\n var seriesId = $('#series-id').val();\n\n if (sceneSeason === '') {\n sceneSeason = null;\n }\n\n if (sceneEpisode === '') {\n sceneEpisode = null;\n }\n\n $.getJSON('home/setSceneNumbering', {\n indexername: indexerName,\n seriesid: seriesId,\n forSeason: forSeason,\n forEpisode: forEpisode,\n sceneSeason: sceneSeason,\n sceneEpisode: sceneEpisode\n }, function (data) {\n // Set the values we get back\n if (data.sceneSeason === null || data.sceneEpisode === null) {\n $('#sceneSeasonXEpisode_' + seriesId + '_' + forSeason + '_' + forEpisode).val('');\n } else {\n $('#sceneSeasonXEpisode_' + seriesId + '_' + forSeason + '_' + forEpisode).val(data.sceneSeason + 'x' + data.sceneEpisode);\n }\n\n if (!data.success) {\n if (data.errorMessage) {\n alert(data.errorMessage); // eslint-disable-line no-alert\n } else {\n alert('Update failed.'); // eslint-disable-line no-alert\n }\n }\n });\n },\n setAbsoluteSceneNumbering: function setAbsoluteSceneNumbering(forAbsolute, sceneAbsolute) {\n var indexerName = $('#indexer-name').val();\n var seriesId = $('#series-id').val();\n\n if (sceneAbsolute === '') {\n sceneAbsolute = null;\n }\n\n $.getJSON('home/setSceneNumbering', {\n indexername: indexerName,\n seriesid: seriesId,\n forAbsolute: forAbsolute,\n sceneAbsolute: sceneAbsolute\n }, function (data) {\n // Set the values we get back\n if (data.sceneAbsolute === null) {\n $('#sceneAbsolute_' + seriesId + '_' + forAbsolute).val('');\n } else {\n $('#sceneAbsolute_' + seriesId + '_' + forAbsolute).val(data.sceneAbsolute);\n }\n\n if (!data.success) {\n if (data.errorMessage) {\n alert(data.errorMessage); // eslint-disable-line no-alert\n } else {\n alert('Update failed.'); // eslint-disable-line no-alert\n }\n }\n });\n },\n setInputValidInvalid: function setInputValidInvalid(valid, el) {\n if (valid) {\n $(el).css({\n 'background-color': '#90EE90',\n // Green\n 'color': '#FFF',\n // eslint-disable-line quote-props\n 'font-weight': 'bold'\n });\n return true;\n }\n\n $(el).css({\n 'background-color': '#FF0000',\n // Red\n 'color': '#FFF!important',\n // eslint-disable-line quote-props\n 'font-weight': 'bold'\n });\n return false;\n },\n // Set the season exception based on using the get_xem_numbering_for_show() for animes if available in data.xemNumbering,\n // or else try to map using just the data.season_exceptions.\n setSeasonSceneException: function setSeasonSceneException(data) {\n $.each(data.seasonExceptions, function (season, nameExceptions) {\n var foundInXem = false; // Check if it is a season name exception, we don't handle the show name exceptions here\n\n if (season >= 0) {\n // Loop through the xem mapping, and check if there is a xem_season, that needs to show the season name exception\n $.each(data.xemNumbering, function (indexerSeason, xemSeason) {\n if (xemSeason === parseInt(season, 10)) {\n foundInXem = true;\n $('', {\n id: 'xem-exception-season-' + xemSeason,\n alt: '[xem]',\n height: '16',\n width: '16',\n src: 'images/xem.png',\n title: nameExceptions.join(', ')\n }).appendTo('[data-season=' + indexerSeason + ']');\n }\n }); // This is not a xem season exception, let's set the exceptions as a medusa exception\n\n if (!foundInXem) {\n $('', {\n id: 'xem-exception-season-' + season,\n alt: '[medusa]',\n height: '16',\n width: '16',\n src: 'images/ico/favicon-16.png',\n title: nameExceptions.join(', ')\n }).appendTo('[data-season=' + season + ']');\n }\n }\n });\n },\n showHideRows: function showHideRows(whichClass) {\n var status = $('#checkboxControls > input, #' + whichClass).prop('checked');\n $('tr.' + whichClass).each(function (index, element) {\n if (status) {\n $(element).show();\n } else {\n $(element).hide();\n }\n }); // Hide season headers with no episodes under them\n\n $('tr.seasonheader').each(function (index, element) {\n var numRows = 0;\n var seasonNo = $(element).attr('id');\n $('tr.' + seasonNo + ' :visible').each(function () {\n numRows++;\n });\n\n if (numRows === 0) {\n $(element).hide();\n $('#' + seasonNo + '-cols').hide();\n } else {\n $(element).show();\n $('#' + seasonNo + '-cols').show();\n }\n });\n }\n }\n};\nexports.default = _default;\n/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\")))\n\n//# sourceURL=webpack:///./src/components/display-show.vue?./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options"); +eval("/* WEBPACK VAR INJECTION */(function($) {\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _vuex = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n\nvar _api = __webpack_require__(/*! ../api */ \"./src/api.js\");\n\nvar _appLink = _interopRequireDefault(__webpack_require__(/*! ./app-link.vue */ \"./src/components/app-link.vue\"));\n\nvar _plotInfo = _interopRequireDefault(__webpack_require__(/*! ./plot-info.vue */ \"./src/components/plot-info.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar _default = {\n name: 'displayShow',\n template: '#display-show-template',\n components: {\n AppLink: _appLink.default,\n PlotInfo: _plotInfo.default\n },\n metaInfo: function metaInfo() {\n if (!this.show || !this.show.title) {\n return {\n title: 'Medusa'\n };\n }\n\n var title = this.show.title;\n return {\n title: title,\n titleTemplate: '%s | Medusa'\n };\n },\n computed: _objectSpread({}, (0, _vuex.mapState)({\n shows: function shows(state) {\n return state.shows.shows;\n }\n }), (0, _vuex.mapGetters)(['getShowById']), {\n indexer: function indexer() {\n return this.$route.query.indexername;\n },\n id: function id() {\n return this.$route.query.seriesid;\n },\n show: function show() {\n var indexer = this.indexer,\n id = this.id,\n getShowById = this.getShowById,\n shows = this.shows,\n $store = this.$store;\n var defaults = $store.state.defaults;\n\n if (shows.length === 0 || !indexer || !id) {\n return defaults.show;\n }\n\n var show = getShowById({\n indexer: indexer,\n id: id\n });\n\n if (!show) {\n return defaults.show;\n }\n\n return show;\n }\n }),\n mounted: function mounted() {\n var moveSummaryBackground = this.moveSummaryBackground,\n movecheckboxControlsBackground = this.movecheckboxControlsBackground,\n setQuality = this.setQuality,\n setEpisodeSceneNumbering = this.setEpisodeSceneNumbering,\n setAbsoluteSceneNumbering = this.setAbsoluteSceneNumbering,\n setInputValidInvalid = this.setInputValidInvalid,\n setSeasonSceneException = this.setSeasonSceneException,\n showHideRows = this.showHideRows;\n $(window).on('resize', function () {\n moveSummaryBackground();\n movecheckboxControlsBackground();\n });\n window.addEventListener('load', function () {\n // Adjust the summary background position and size\n window.dispatchEvent(new Event('resize'));\n $.ajaxEpSearch({\n colorRow: true\n });\n startAjaxEpisodeSubtitles(); // eslint-disable-line no-undef\n\n $.ajaxEpSubtitlesSearch();\n $.ajaxEpRedownloadSubtitle();\n });\n $(document.body).on('click', '.imdbPlot', function (event) {\n var $target = $(event.currentTarget);\n $target.prev('span').toggle();\n\n if ($target.html() === '..show less') {\n $target.html('..show more');\n } else {\n $target.html('..show less');\n }\n\n moveSummaryBackground();\n movecheckboxControlsBackground();\n });\n $(document.body).on('change', '#seasonJump', function (event) {\n var id = $('#seasonJump option:selected').val();\n\n if (id && id !== 'jump') {\n var season = $('#seasonJump option:selected').data('season');\n $('html,body').animate({\n scrollTop: $('[name=\"' + id.substring(1) + '\"]').offset().top - 100\n }, 'slow');\n $('#collapseSeason-' + season).collapse('show');\n location.hash = id;\n }\n\n $(event.currentTarget).val('jump');\n });\n $(document.body).on('click', '#changeStatus', function () {\n var epArr = [];\n var status = $('#statusSelect').val();\n var quality = $('#qualitySelect').val();\n var showSlug = $('#series-slug').val();\n $('.epCheck').each(function (index, element) {\n if (element.checked === true) {\n epArr.push($(element).attr('id'));\n }\n });\n\n if (epArr.length === 0) {\n return false;\n }\n\n if (quality) {\n setQuality(quality, showSlug, epArr);\n }\n\n if (status) {\n window.location.href = $('base').attr('href') + 'home/setStatus?' + 'indexername=' + $('#indexer-name').attr('value') + '&seriesid=' + $('#series-id').attr('value') + '&eps=' + epArr.join('|') + '&status=' + status;\n }\n });\n $(document.body).on('click', '.seasonCheck', function (event) {\n var seasCheck = event.currentTarget;\n var seasNo = $(seasCheck).attr('id');\n $('#collapseSeason-' + seasNo).collapse('show');\n var seasonIdentifier = 's' + seasNo;\n $('.epCheck:visible').each(function (index, element) {\n var epParts = $(element).attr('id').split('e');\n\n if (epParts[0] === seasonIdentifier) {\n element.checked = seasCheck.checked;\n }\n });\n });\n var lastCheck = null;\n $(document.body).on('click', '.epCheck', function (event) {\n var target = event.currentTarget;\n\n if (!lastCheck || !event.shiftKey) {\n lastCheck = target;\n return;\n }\n\n var check = target;\n var found = 0;\n $('.epCheck').each(function (index, element) {\n if (found === 1) {\n element.checked = lastCheck.checked;\n }\n\n if (found === 2) {\n return false;\n }\n\n if (element === check || element === lastCheck) {\n found++;\n }\n });\n }); // Selects all visible episode checkboxes.\n\n $(document.body).on('click', '.seriesCheck', function () {\n $('.epCheck:visible').each(function (index, element) {\n element.checked = true;\n });\n $('.seasonCheck:visible').each(function (index, element) {\n element.checked = true;\n });\n }); // Clears all visible episode checkboxes and the season selectors\n\n $(document.body).on('click', '.clearAll', function () {\n $('.epCheck:visible').each(function (index, element) {\n element.checked = false;\n });\n $('.seasonCheck:visible').each(function (index, element) {\n element.checked = false;\n });\n }); // Show/hide different types of rows when the checkboxes are changed\n\n $(document.body).on('change', '#checkboxControls input', function (event) {\n var whichClass = $(event.currentTarget).attr('id');\n showHideRows(whichClass);\n }); // Initially show/hide all the rows according to the checkboxes\n\n $('#checkboxControls input').each(function (index, element) {\n var status = $(element).prop('checked');\n $('tr.' + $(element).attr('id')).each(function (index, tableRow) {\n if (status) {\n $(tableRow).show();\n } else {\n $(tableRow).hide();\n }\n });\n });\n $(document.body).on('change', '.sceneSeasonXEpisode', function (event) {\n var target = event.currentTarget; // Strip non-numeric characters\n\n var value = $(target).val();\n $(target).val(value.replace(/[^0-9xX]*/g, ''));\n var forSeason = $(target).attr('data-for-season');\n var forEpisode = $(target).attr('data-for-episode'); // If empty reset the field\n\n if (value === '') {\n setEpisodeSceneNumbering(forSeason, forEpisode, null, null);\n return;\n }\n\n var m = $(target).val().match(/^(\\d+)x(\\d+)$/i);\n var onlyEpisode = $(target).val().match(/^(\\d+)$/i);\n var sceneSeason = null;\n var sceneEpisode = null;\n var isValid = false;\n\n if (m) {\n sceneSeason = m[1];\n sceneEpisode = m[2];\n isValid = setInputValidInvalid(true, $(target));\n } else if (onlyEpisode) {\n // For example when '5' is filled in instead of '1x5', asume it's the first season\n sceneSeason = forSeason;\n sceneEpisode = onlyEpisode[1];\n isValid = setInputValidInvalid(true, $(target));\n } else {\n isValid = setInputValidInvalid(false, $(target));\n }\n\n if (isValid) {\n setEpisodeSceneNumbering(forSeason, forEpisode, sceneSeason, sceneEpisode);\n }\n });\n $(document.body).on('change', '.sceneAbsolute', function (event) {\n var target = event.currentTarget; // Strip non-numeric characters\n\n $(target).val($(target).val().replace(/[^0-9xX]*/g, ''));\n var forAbsolute = $(target).attr('data-for-absolute');\n var m = $(target).val().match(/^(\\d{1,3})$/i);\n var sceneAbsolute = null;\n\n if (m) {\n sceneAbsolute = m[1];\n }\n\n setAbsoluteSceneNumbering(forAbsolute, sceneAbsolute);\n });\n $('#showTable, #animeTable').tablesorter({\n widgets: ['saveSort', 'stickyHeaders', 'columnSelector'],\n widgetOptions: {\n columnSelector_saveColumns: true,\n // eslint-disable-line camelcase\n columnSelector_layout: '',\n // eslint-disable-line camelcase\n columnSelector_mediaquery: false,\n // eslint-disable-line camelcase\n columnSelector_cssChecked: 'checked' // eslint-disable-line camelcase\n\n }\n });\n $('#popover').popover({\n placement: 'bottom',\n html: true,\n // Required if content has HTML\n content: '
'\n }).on('shown.bs.popover', function () {\n // Bootstrap popover event triggered when the popover opens\n $.tablesorter.columnSelector.attachTo($('#showTable, #animeTable'), '#popover-target');\n }); // Moved and rewritten this from displayShow. This changes the button when clicked for collapsing/expanding the\n // Season to Show Episodes or Hide Episodes.\n\n $('.collapse.toggle').on('hide.bs.collapse', function () {\n var reg = /collapseSeason-(\\d+)/g;\n var result = reg.exec(this.id);\n $('#showseason-' + result[1]).text('Show Episodes');\n $('#season-' + result[1] + '-cols').addClass('shadow');\n });\n $('.collapse.toggle').on('show.bs.collapse', function () {\n var reg = /collapseSeason-(\\d+)/g;\n var result = reg.exec(this.id);\n $('#showseason-' + result[1]).text('Hide Episodes');\n $('#season-' + result[1] + '-cols').removeClass('shadow');\n }); // Generate IMDB stars\n\n $('.imdbstars').each(function (index, element) {\n $(element).html($('').width($(element).text() * 12));\n });\n attachImdbTooltip(); // eslint-disable-line no-undef\n // @TODO: OMG: This is just a basic json, in future it should be based on the CRUD route.\n // Get the season exceptions and the xem season mappings.\n\n $.getJSON('home/getSeasonSceneExceptions', {\n indexername: $('#indexer-name').val(),\n seriesid: $('#series-id').val() // eslint-disable-line camelcase\n\n }, function (data) {\n setSeasonSceneException(data);\n });\n $(document.body).on('click', '.display-specials a', function (event) {\n _api.api.patch('config/main', {\n layout: {\n show: {\n specials: $(event.currentTarget).text() !== 'Hide'\n }\n }\n }).then(function (response) {\n log.info(response.data);\n window.location.reload();\n }).catch(function (error) {\n log.error(error.data);\n });\n });\n },\n methods: {\n // Adjust the summary background position and size on page load and resize\n moveSummaryBackground: function moveSummaryBackground() {\n var height = $('#summary').height() + 10;\n var top = $('#summary').offset().top + 5;\n $('#summaryBackground').height(height);\n $('#summaryBackground').offset({\n top: top,\n left: 0\n });\n $('#summaryBackground').show();\n },\n movecheckboxControlsBackground: function movecheckboxControlsBackground() {\n var height = $('#checkboxControls').height() + 10;\n var top = $('#checkboxControls').offset().top - 3;\n $('#checkboxControlsBackground').height(height);\n $('#checkboxControlsBackground').offset({\n top: top,\n left: 0\n });\n $('#checkboxControlsBackground').show();\n },\n setQuality: function setQuality(quality, showSlug, episodes) {\n var patchData = {};\n episodes.forEach(function (episode) {\n patchData[episode] = {\n quality: parseInt(quality, 10)\n };\n });\n\n _api.api.patch('series/' + showSlug + '/episodes', patchData).then(function (response) {\n log.info(response.data);\n window.location.reload();\n }).catch(function (error) {\n log.error(error.data);\n });\n },\n setEpisodeSceneNumbering: function setEpisodeSceneNumbering(forSeason, forEpisode, sceneSeason, sceneEpisode) {\n var indexerName = $('#indexer-name').val();\n var seriesId = $('#series-id').val();\n\n if (sceneSeason === '') {\n sceneSeason = null;\n }\n\n if (sceneEpisode === '') {\n sceneEpisode = null;\n }\n\n $.getJSON('home/setSceneNumbering', {\n indexername: indexerName,\n seriesid: seriesId,\n forSeason: forSeason,\n forEpisode: forEpisode,\n sceneSeason: sceneSeason,\n sceneEpisode: sceneEpisode\n }, function (data) {\n // Set the values we get back\n if (data.sceneSeason === null || data.sceneEpisode === null) {\n $('#sceneSeasonXEpisode_' + seriesId + '_' + forSeason + '_' + forEpisode).val('');\n } else {\n $('#sceneSeasonXEpisode_' + seriesId + '_' + forSeason + '_' + forEpisode).val(data.sceneSeason + 'x' + data.sceneEpisode);\n }\n\n if (!data.success) {\n if (data.errorMessage) {\n alert(data.errorMessage); // eslint-disable-line no-alert\n } else {\n alert('Update failed.'); // eslint-disable-line no-alert\n }\n }\n });\n },\n setAbsoluteSceneNumbering: function setAbsoluteSceneNumbering(forAbsolute, sceneAbsolute) {\n var indexerName = $('#indexer-name').val();\n var seriesId = $('#series-id').val();\n\n if (sceneAbsolute === '') {\n sceneAbsolute = null;\n }\n\n $.getJSON('home/setSceneNumbering', {\n indexername: indexerName,\n seriesid: seriesId,\n forAbsolute: forAbsolute,\n sceneAbsolute: sceneAbsolute\n }, function (data) {\n // Set the values we get back\n if (data.sceneAbsolute === null) {\n $('#sceneAbsolute_' + seriesId + '_' + forAbsolute).val('');\n } else {\n $('#sceneAbsolute_' + seriesId + '_' + forAbsolute).val(data.sceneAbsolute);\n }\n\n if (!data.success) {\n if (data.errorMessage) {\n alert(data.errorMessage); // eslint-disable-line no-alert\n } else {\n alert('Update failed.'); // eslint-disable-line no-alert\n }\n }\n });\n },\n setInputValidInvalid: function setInputValidInvalid(valid, el) {\n if (valid) {\n $(el).css({\n 'background-color': '#90EE90',\n // Green\n 'color': '#FFF',\n // eslint-disable-line quote-props\n 'font-weight': 'bold'\n });\n return true;\n }\n\n $(el).css({\n 'background-color': '#FF0000',\n // Red\n 'color': '#FFF!important',\n // eslint-disable-line quote-props\n 'font-weight': 'bold'\n });\n return false;\n },\n // Set the season exception based on using the get_xem_numbering_for_show() for animes if available in data.xemNumbering,\n // or else try to map using just the data.season_exceptions.\n setSeasonSceneException: function setSeasonSceneException(data) {\n $.each(data.seasonExceptions, function (season, nameExceptions) {\n var foundInXem = false; // Check if it is a season name exception, we don't handle the show name exceptions here\n\n if (season >= 0) {\n // Loop through the xem mapping, and check if there is a xem_season, that needs to show the season name exception\n $.each(data.xemNumbering, function (indexerSeason, xemSeason) {\n if (xemSeason === parseInt(season, 10)) {\n foundInXem = true;\n $('', {\n id: 'xem-exception-season-' + xemSeason,\n alt: '[xem]',\n height: '16',\n width: '16',\n src: 'images/xem.png',\n title: nameExceptions.join(', ')\n }).appendTo('[data-season=' + indexerSeason + ']');\n }\n }); // This is not a xem season exception, let's set the exceptions as a medusa exception\n\n if (!foundInXem) {\n $('', {\n id: 'xem-exception-season-' + season,\n alt: '[medusa]',\n height: '16',\n width: '16',\n src: 'images/ico/favicon-16.png',\n title: nameExceptions.join(', ')\n }).appendTo('[data-season=' + season + ']');\n }\n }\n });\n },\n showHideRows: function showHideRows(whichClass) {\n var status = $('#checkboxControls > input, #' + whichClass).prop('checked');\n $('tr.' + whichClass).each(function (index, element) {\n if (status) {\n $(element).show();\n } else {\n $(element).hide();\n }\n }); // Hide season headers with no episodes under them\n\n $('tr.seasonheader').each(function (index, element) {\n var numRows = 0;\n var seasonNo = $(element).attr('id');\n $('tr.' + seasonNo + ' :visible').each(function () {\n numRows++;\n });\n\n if (numRows === 0) {\n $(element).hide();\n $('#' + seasonNo + '-cols').hide();\n } else {\n $(element).show();\n $('#' + seasonNo + '-cols').show();\n }\n });\n }\n }\n};\nexports.default = _default;\n/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\")))\n\n//# sourceURL=webpack:///./src/components/display-show.vue?./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -615,7 +615,7 @@ eval("/* WEBPACK VAR INJECTION */(function($) {\n\nObject.defineProperty(exports /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("/* WEBPACK VAR INJECTION */(function($) {\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _vuex = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n\nvar _debounce = _interopRequireDefault(__webpack_require__(/*! lodash/debounce */ \"./node_modules/lodash/debounce.js\"));\n\nvar _appLink = _interopRequireDefault(__webpack_require__(/*! ./app-link.vue */ \"./src/components/app-link.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar _default = {\n name: 'home',\n template: '#home-template',\n components: {\n AppLink: _appLink.default\n },\n metaInfo: {\n title: 'Home'\n },\n data: function data() {\n return {\n header: 'Show List'\n };\n },\n computed: _objectSpread({}, (0, _vuex.mapState)(['config']), {\n layout: {\n get: function get() {\n var config = this.config;\n return config.layout.home;\n },\n set: function set(layout) {\n var $store = this.$store;\n var page = 'home';\n $store.dispatch('setLayout', {\n page: page,\n layout: layout\n });\n }\n }\n }),\n methods: {\n initializePosterSizeSlider: function initializePosterSizeSlider() {\n var resizePosters = function resizePosters(newSize) {\n var fontSize;\n var logoWidth;\n var borderRadius;\n var borderWidth;\n\n if (newSize < 125) {\n // Small\n borderRadius = 3;\n borderWidth = 4;\n } else if (newSize < 175) {\n // Medium\n fontSize = 9;\n logoWidth = 40;\n borderRadius = 4;\n borderWidth = 5;\n } else {\n // Large\n fontSize = 11;\n logoWidth = 50;\n borderRadius = 6;\n borderWidth = 6;\n } // If there's a poster popup, remove it before resizing\n\n\n $('#posterPopup').remove();\n\n if (fontSize === undefined) {\n $('.show-details').hide();\n } else {\n $('.show-details').show();\n $('.show-dlstats, .show-quality').css('fontSize', fontSize);\n $('.show-network-image').css('width', logoWidth);\n }\n\n $('.show-container').css({\n width: newSize,\n borderWidth: borderWidth,\n borderRadius: borderRadius\n });\n };\n\n var posterSize;\n\n if (typeof Storage !== 'undefined') {\n posterSize = parseInt(localStorage.getItem('posterSize'), 10);\n }\n\n if (typeof posterSize !== 'number' || isNaN(posterSize)) {\n posterSize = 188;\n }\n\n resizePosters(posterSize);\n $('#posterSizeSlider').slider({\n min: 75,\n max: 250,\n value: posterSize,\n change: function change(e, ui) {\n if (typeof Storage !== 'undefined') {\n localStorage.setItem('posterSize', ui.value);\n }\n\n resizePosters(ui.value);\n $('.show-grid').isotope('layout');\n }\n });\n }\n },\n mounted: function mounted() {\n var _this = this;\n\n // Resets the tables sorting, needed as we only use a single call for both tables in tablesorter\n $(document.body).on('click', '.resetsorting', function () {\n $('table').trigger('filterReset');\n }); // Handle filtering in the poster layout\n\n $(document.body).on('input', '#filterShowName', (0, _debounce.default)(function () {\n $('.show-grid').isotope({\n filter: function filter() {\n var name = $(this).attr('data-name').toLowerCase();\n return name.indexOf($('#filterShowName').val().toLowerCase()) > -1;\n }\n });\n }, 500));\n var imgLazyLoad = new LazyLoad({\n // Example of options object -> see options section\n threshold: 500\n }); // This needs to be refined to work a little faster.\n\n $('.progressbar').each(function () {\n var percentage = $(this).data('progress-percentage');\n var classToAdd = percentage === 100 ? 100 : percentage > 80 ? 80 : percentage > 60 ? 60 : percentage > 40 ? 40 : 20; // eslint-disable-line no-nested-ternary\n\n $(this).progressbar({\n value: percentage\n });\n\n if ($(this).data('progress-text')) {\n $(this).append('
' + $(this).data('progress-text') + '
');\n }\n\n $(this).find('.ui-progressbar-value').addClass('progress-' + classToAdd);\n });\n $('img#network').on('error', function () {\n $(this).parent().text($(this).attr('alt'));\n $(this).remove();\n });\n $('#showListTableSeries:has(tbody tr), #showListTableAnime:has(tbody tr)').tablesorter({\n debug: false,\n sortList: [[7, 1], [2, 0]],\n textExtraction: function () {\n return {\n 0: function _(node) {\n return $(node).find('time').attr('datetime');\n },\n // eslint-disable-line brace-style\n 1: function _(node) {\n return $(node).find('time').attr('datetime');\n },\n // eslint-disable-line brace-style\n 3: function _(node) {\n return $(node).find('span').prop('title').toLowerCase();\n },\n // eslint-disable-line brace-style\n 4: function _(node) {\n return $(node).find('a[data-indexer-name]').attr('data-indexer-name');\n },\n // eslint-disable-line brace-style\n 5: function _(node) {\n return $(node).find('span').text().toLowerCase();\n },\n // eslint-disable-line brace-style\n 6: function _(node) {\n return $(node).find('span:first').text();\n },\n // eslint-disable-line brace-style\n 7: function _(node) {\n return $(node).data('show-size');\n },\n // eslint-disable-line brace-style\n 8: function _(node) {\n return $(node).find('img').attr('alt');\n },\n // eslint-disable-line brace-style\n 10: function _(node) {\n return $(node).find('img').attr('alt');\n } // eslint-disable-line brace-style\n\n };\n }(),\n widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter', 'columnSelector'],\n headers: {\n 0: {\n sorter: 'realISODate'\n },\n 1: {\n sorter: 'realISODate'\n },\n 2: {\n sorter: 'showNames'\n },\n 4: {\n sorter: 'text'\n },\n 5: {\n sorter: 'quality'\n },\n 6: {\n sorter: 'eps'\n },\n 7: {\n sorter: 'digit'\n },\n 8: {\n filter: 'parsed'\n },\n 10: {\n filter: 'parsed'\n }\n },\n widgetOptions: {\n filter_columnFilters: true,\n // eslint-disable-line camelcase\n filter_hideFilters: true,\n // eslint-disable-line camelcase\n filter_saveFilters: true,\n // eslint-disable-line camelcase\n filter_functions: {\n // eslint-disable-line camelcase\n 5: function _(e, n, f) {\n // eslint-disable-line complexity\n var test = false;\n var pct = Math.floor(n % 1 * 1000);\n\n if (f === '') {\n test = true;\n } else {\n var result = f.match(/(<|<=|>=|>)\\s+(\\d+)/i);\n\n if (result) {\n if (result[1] === '<') {\n if (pct < parseInt(result[2], 10)) {\n test = true;\n }\n } else if (result[1] === '<=') {\n if (pct <= parseInt(result[2], 10)) {\n test = true;\n }\n } else if (result[1] === '>=') {\n if (pct >= parseInt(result[2], 10)) {\n test = true;\n }\n } else if (result[1] === '>') {\n if (pct > parseInt(result[2], 10)) {\n test = true;\n }\n }\n }\n\n result = f.match(/(\\d+)\\s(-|to)\\s+(\\d+)/i);\n\n if (result) {\n if (result[2] === '-' || result[2] === 'to') {\n if (pct >= parseInt(result[1], 10) && pct <= parseInt(result[3], 10)) {\n test = true;\n }\n }\n }\n\n result = f.match(/(=)?\\s?(\\d+)\\s?(=)?/i);\n\n if (result) {\n if (result[1] === '=' || result[3] === '=') {\n if (parseInt(result[2], 10) === pct) {\n test = true;\n }\n }\n }\n\n if (!isNaN(parseFloat(f)) && isFinite(f)) {\n if (parseInt(f, 10) === pct) {\n test = true;\n }\n }\n }\n\n return test;\n }\n },\n columnSelector_mediaquery: false // eslint-disable-line camelcase\n\n },\n sortStable: true,\n sortAppend: [[2, 0]]\n }).bind('sortEnd', function () {\n imgLazyLoad.handleScroll();\n }).bind('filterEnd', function () {\n imgLazyLoad.handleScroll();\n });\n $('.show-grid').imagesLoaded(function () {\n _this.initializePosterSizeSlider();\n\n $('.loading-spinner').hide();\n $('.show-grid').show().isotope({\n itemSelector: '.show-container',\n sortBy: MEDUSA.config.posterSortby,\n sortAscending: MEDUSA.config.posterSortdir,\n layoutMode: 'masonry',\n masonry: {\n isFitWidth: true\n },\n getSortData: {\n name: function name(itemElem) {\n var name = $(itemElem).attr('data-name') || '';\n return (MEDUSA.config.sortArticle ? name : name.replace(/^((?:The|A|An)\\s)/i, '')).toLowerCase();\n },\n network: '[data-network]',\n date: function date(itemElem) {\n var date = $(itemElem).attr('data-date');\n return date.length && parseInt(date, 10) || Number.POSITIVE_INFINITY;\n },\n progress: function progress(itemElem) {\n var progress = $(itemElem).attr('data-progress');\n return progress.length && parseInt(progress, 10) || Number.NEGATIVE_INFINITY;\n },\n indexer: function indexer(itemElem) {\n var indexer = $(itemElem).attr('data-indexer');\n\n if (indexer === undefined) {\n return Number.NEGATIVE_INFINITY;\n }\n\n return indexer.length && parseInt(indexer, 10) || Number.NEGATIVE_INFINITY;\n }\n }\n }).on('layoutComplete arrangeComplete removeComplete', function () {\n imgLazyLoad.update();\n imgLazyLoad.handleScroll();\n }); // When posters are small enough to not display the .show-details\n // table, display a larger poster when hovering.\n\n var posterHoverTimer = null;\n $('.show-container').on('mouseenter', function () {\n var poster = $(this);\n\n if (poster.find('.show-details').css('display') !== 'none') {\n return;\n }\n\n posterHoverTimer = setTimeout(function () {\n posterHoverTimer = null;\n $('#posterPopup').remove();\n var popup = poster.clone().attr({\n id: 'posterPopup'\n });\n var origLeft = poster.offset().left;\n var origTop = poster.offset().top;\n popup.css({\n position: 'absolute',\n margin: 0,\n top: origTop,\n left: origLeft\n });\n popup.find('.show-details').show();\n popup.on('mouseleave', function () {\n $(this).remove();\n });\n popup.css({\n zIndex: '9999'\n });\n popup.appendTo('body');\n var height = 438;\n var width = 250;\n var newTop = origTop + poster.height() / 2 - height / 2;\n var newLeft = origLeft + poster.width() / 2 - width / 2; // Make sure the popup isn't outside the viewport\n\n var margin = 5;\n var scrollTop = $(window).scrollTop();\n var scrollLeft = $(window).scrollLeft();\n var scrollBottom = scrollTop + $(window).innerHeight();\n var scrollRight = scrollLeft + $(window).innerWidth();\n\n if (newTop < scrollTop + margin) {\n newTop = scrollTop + margin;\n }\n\n if (newLeft < scrollLeft + margin) {\n newLeft = scrollLeft + margin;\n }\n\n if (newTop + height + margin > scrollBottom) {\n newTop = scrollBottom - height - margin;\n }\n\n if (newLeft + width + margin > scrollRight) {\n newLeft = scrollRight - width - margin;\n }\n\n popup.animate({\n top: newTop,\n left: newLeft,\n width: 250,\n height: 438\n });\n }, 300);\n }).on('mouseleave', function () {\n if (posterHoverTimer !== null) {\n clearTimeout(posterHoverTimer);\n }\n });\n imgLazyLoad.update();\n imgLazyLoad.handleScroll();\n });\n $(document.body).on('change', '#postersort', function () {\n $('.show-grid').isotope({\n sortBy: $(this).val()\n });\n $.get($(this).find('option[value=' + $(this).val() + ']').attr('data-sort'));\n });\n $(document.body).on('change', '#postersortdirection', function () {\n $('.show-grid').isotope({\n sortAscending: $(this).val() === '1'\n });\n $.get($(this).find('option[value=' + $(this).val() + ']').attr('data-sort'));\n });\n $('#popover').popover({\n placement: 'bottom',\n html: true,\n // Required if content has HTML\n content: '
'\n }).on('shown.bs.popover', function () {\n // Bootstrap popover event triggered when the popover opens\n // call this function to copy the column selection code into the popover\n $.tablesorter.columnSelector.attachTo($('#showListTableSeries'), '#popover-target');\n\n if (MEDUSA.config.animeSplitHome) {\n $.tablesorter.columnSelector.attachTo($('#showListTableAnime'), '#popover-target');\n }\n });\n $(document.body).on('change', '#showRootDir', function () {\n api.patch('config/main', {\n selectedRootIndex: parseInt($(this).val(), 10)\n }).then(function (response) {\n log.info(response);\n window.location.reload();\n }).catch(function (error) {\n log.info(error);\n });\n });\n var rootDir = MEDUSA.config.rootDirs;\n var rootDirIndex = MEDUSA.config.selectedRootIndex;\n\n if (rootDir) {\n var backendDirs = rootDir.slice(1);\n\n if (backendDirs.length >= 2) {\n $('#showRoot').show();\n var item = ['All Folders'];\n var rootDirOptions = item.concat(backendDirs);\n $.each(rootDirOptions, function (i, item) {\n $('#showRootDir').append($('