diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 9c231e1391..31f00754f5 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -2,8 +2,7 @@ trigger:
batch: true
branches:
include:
- - master
- - release-*
+ - '*'
tags:
include:
- '*'
@@ -13,90 +12,94 @@ pr:
- '*'
jobs:
- - job: build
- displayName: 'Build'
+- job: Build
+ displayName: 'Build'
- pool:
- vmImage: 'ubuntu-latest'
+ strategy:
+ matrix:
+ Development:
+ BuildConfiguration: development
+ Production:
+ BuildConfiguration: production
+ Standalone:
+ BuildConfiguration: standalone
- strategy:
- matrix:
- Development:
- BuildConfiguration: development
- Production:
- BuildConfiguration: production
- Standalone:
- BuildConfiguration: standalone
- maxParallel: 3
+ pool:
+ vmImage: 'ubuntu-latest'
- steps:
- - task: NodeTool@0
- displayName: 'Install Node'
- inputs:
- versionSpec: '12.x'
+ steps:
+ - task: NodeTool@0
+ displayName: 'Install Node'
+ inputs:
+ versionSpec: '12.x'
- - task: Cache@2
- displayName: 'Check Cache'
- inputs:
- key: 'yarn | yarn.lock'
- path: 'node_modules'
- cacheHitVar: CACHE_RESTORED
+ - task: Cache@2
+ displayName: 'Check Cache'
+ inputs:
+ key: 'yarn | yarn.lock'
+ path: 'node_modules'
+ cacheHitVar: CACHE_RESTORED
- - script: 'yarn install --frozen-lockfile'
- displayName: 'Install Dependencies'
- condition: ne(variables.CACHE_RESTORED, 'true')
+ - script: 'yarn install --frozen-lockfile'
+ displayName: 'Install Dependencies'
+ condition: ne(variables.CACHE_RESTORED, 'true')
- - script: 'yarn build:development'
- displayName: 'Build Development'
- condition: eq(variables['BuildConfiguration'], 'development')
+ - script: 'yarn build:development'
+ displayName: 'Build Development'
+ condition: eq(variables['BuildConfiguration'], 'development')
- - script: 'yarn build:production'
- displayName: 'Build Bundle'
- condition: eq(variables['BuildConfiguration'], 'production')
+ - script: 'yarn build:production'
+ displayName: 'Build Bundle'
+ condition: eq(variables['BuildConfiguration'], 'production')
- - script: 'yarn build:standalone'
- displayName: 'Build Standalone'
- condition: eq(variables['BuildConfiguration'], 'standalone')
+ - script: 'yarn build:standalone'
+ displayName: 'Build Standalone'
+ condition: eq(variables['BuildConfiguration'], 'standalone')
- - script: 'test -d dist'
- displayName: 'Check Build'
+ - script: 'test -d dist'
+ displayName: 'Check Build'
- - script: 'mv dist jellyfin-web'
- displayName: 'Rename Directory'
- condition: succeeded()
+ - script: 'mv dist jellyfin-web'
+ displayName: 'Rename Directory'
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Release'
- condition: succeeded()
- inputs:
- targetPath: '$(Build.SourcesDirectory)/jellyfin-web'
- artifactName: 'jellyfin-web-$(BuildConfiguration)'
+ - task: ArchiveFiles@2
+ displayName: 'Archive Directory'
+ inputs:
+ rootFolderOrFile: 'jellyfin-web'
+ includeRootFolder: true
+ archiveFile: 'jellyfin-web-$(BuildConfiguration)'
- - job: lint
- displayName: 'Lint'
+ - task: PublishPipelineArtifact@1
+ displayName: 'Publish Release'
+ inputs:
+ targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
+ artifactName: 'jellyfin-web-$(BuildConfiguration)'
- pool:
- vmImage: 'ubuntu-latest'
+- job: Lint
+ displayName: 'Lint'
- steps:
- - task: NodeTool@0
- displayName: 'Install Node'
- inputs:
- versionSpec: '12.x'
+ pool:
+ vmImage: 'ubuntu-latest'
- - task: Cache@2
- displayName: 'Check Cache'
- inputs:
- key: 'yarn | yarn.lock'
- path: 'node_modules'
- cacheHitVar: CACHE_RESTORED
+ steps:
+ - task: NodeTool@0
+ displayName: 'Install Node'
+ inputs:
+ versionSpec: '12.x'
- - script: 'yarn install --frozen-lockfile'
- displayName: 'Install Dependencies'
- condition: ne(variables.CACHE_RESTORED, 'true')
+ - task: Cache@2
+ displayName: 'Check Cache'
+ inputs:
+ key: 'yarn | yarn.lock'
+ path: 'node_modules'
+ cacheHitVar: CACHE_RESTORED
- - script: 'yarn run lint --quiet'
- displayName: 'Run ESLint'
+ - script: 'yarn install --frozen-lockfile'
+ displayName: 'Install Dependencies'
+ condition: ne(variables.CACHE_RESTORED, 'true')
- - script: 'yarn run stylelint'
- displayName: 'Run Stylelint'
+ - script: 'yarn run lint --quiet'
+ displayName: 'Run ESLint'
+
+ - script: 'yarn run stylelint'
+ displayName: 'Run Stylelint'
diff --git a/.eslintrc.yml b/.eslintrc.yml
index f501f33546..0b92c0c9b0 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -22,6 +22,7 @@ extends:
- plugin:import/errors
- plugin:import/warnings
- plugin:eslint-comments/recommended
+ - plugin:compat/recommended
globals:
# Browser globals
@@ -74,7 +75,7 @@ rules:
no-multiple-empty-lines: ["error", { "max": 1 }]
no-trailing-spaces: ["error"]
one-var: ["error", "never"]
- semi: ["warn"]
+ semi: ["error"]
space-before-blocks: ["error"]
# TODO: Fix warnings and remove these rules
no-redeclare: ["warn"]
@@ -85,3 +86,86 @@ rules:
promise/no-return-wrap: ["warn"]
# TODO: Remove after ES6 migration is complete
import/no-unresolved: ["warn"]
+
+settings:
+ polyfills:
+ # Native Promises Only
+ - Promise
+ # whatwg-fetch
+ - fetch
+ # document-register-element
+ - document.registerElement
+ # resize-observer-polyfill
+ - ResizeObserver
+ # fast-text-encoding
+ - TextEncoder
+ # intersection-observer
+ - IntersectionObserver
+ # Core-js
+ - Object.assign
+ - Object.is
+ - Object.setPrototypeOf
+ - Object.toString
+ - Object.freeze
+ - Object.seal
+ - Object.preventExtensions
+ - Object.isFrozen
+ - Object.isSealed
+ - Object.isExtensible
+ - Object.getOwnPropertyDescriptor
+ - Object.getPrototypeOf
+ - Object.keys
+ - Object.getOwnPropertyNames
+ - Function.name
+ - Function.hasInstance
+ - Array.from
+ - Array.arrayOf
+ - Array.copyWithin
+ - Array.fill
+ - Array.find
+ - Array.findIndex
+ - Array.iterator
+ - String.fromCodePoint
+ - String.raw
+ - String.iterator
+ - String.codePointAt
+ - String.endsWith
+ - String.includes
+ - String.repeat
+ - String.startsWith
+ - String.trim
+ - String.anchor
+ - String.big
+ - String.blink
+ - String.bold
+ - String.fixed
+ - String.fontcolor
+ - String.fontsize
+ - String.italics
+ - String.link
+ - String.small
+ - String.strike
+ - String.sub
+ - String.sup
+ - RegExp
+ - Number
+ - Math
+ - Date
+ - async
+ - Symbol
+ - Map
+ - Set
+ - WeakMap
+ - WeakSet
+ - ArrayBuffer
+ - DataView
+ - Int8Array
+ - Uint8Array
+ - Uint8ClampedArray
+ - Int16Array
+ - Uint16Array
+ - Int32Array
+ - Uint32Array
+ - Float32Array
+ - Float64Array
+ - Reflect
diff --git a/babel.config.json b/babel.config.json
new file mode 100644
index 0000000000..1320b9a327
--- /dev/null
+++ b/babel.config.json
@@ -0,0 +1,3 @@
+{
+ "presets": ["@babel/preset-env"]
+}
diff --git a/gulpfile.js b/gulpfile.js
index 0eb5593541..973c400263 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -62,7 +62,7 @@ function serve() {
port: 8080
});
- let events = ['add', 'change'];
+ const events = ['add', 'change'];
watch(options.javascript.query).on('all', function (event, path) {
if (events.includes(event)) {
@@ -105,7 +105,7 @@ function clean() {
return del(['dist/']);
}
-let pipelineJavascript = lazypipe()
+const pipelineJavascript = lazypipe()
.pipe(function () {
return mode.development(sourcemaps.init({ loadMaps: true }));
})
@@ -140,7 +140,7 @@ function apploader(standalone) {
.pipe(pipelineJavascript())
.pipe(dest('dist/'))
.pipe(browserSync.stream());
- };
+ }
task.displayName = 'apploader';
diff --git a/package.json b/package.json
index bac0d7e251..bc8089947f 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"cssnano": "^4.1.10",
"del": "^5.1.0",
"eslint": "^6.8.0",
+ "eslint-plugin-compat": "^3.5.1",
"eslint-plugin-eslint-comments": "^3.1.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-promise": "^4.2.1",
@@ -54,21 +55,25 @@
},
"dependencies": {
"alameda": "^1.4.0",
+ "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"core-js": "^3.6.4",
"date-fns": "^2.11.1",
"document-register-element": "^1.14.3",
+ "fast-text-encoding": "^1.0.1",
"flv.js": "^1.5.0",
"hls.js": "^0.13.1",
"howler": "^2.1.3",
+ "intersection-observer": "^0.7.0",
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.4.1",
"jstree": "^3.3.7",
- "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf",
+ "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova",
"material-design-icons-iconfont": "^5.0.1",
"native-promise-only": "^0.8.0-a",
"page": "^1.11.5",
"query-string": "^6.11.1",
"resize-observer-polyfill": "^1.5.1",
+ "screenfull": "^5.0.2",
"shaka-player": "^2.5.10",
"sortablejs": "^1.10.2",
"swiper": "^5.3.1",
@@ -89,9 +94,13 @@
"src/components/filesystem.js",
"src/components/input/keyboardnavigation.js",
"src/components/sanatizefilename.js",
- "src/scripts/settings/webSettings.js",
"src/components/scrollManager.js",
- "src/scripts/dfnshelper.js"
+ "src/scripts/settings/appSettings.js",
+ "src/scripts/settings/userSettings.js",
+ "src/scripts/settings/webSettings.js",
+ "src/scripts/dfnshelper.js",
+ "src/scripts/imagehelper.js",
+ "src/scripts/inputManager.js"
],
"plugins": [
"@babel/plugin-transform-modules-amd"
@@ -115,7 +124,7 @@
"Firefox ESR"
],
"scripts": {
- "serve": "gulp serve",
+ "serve": "gulp serve --development",
"prepare": "gulp --production",
"build:development": "gulp --development",
"build:production": "gulp --production",
diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css
index e4b5bcf8d6..83bc9d9826 100644
--- a/src/assets/css/librarybrowser.css
+++ b/src/assets/css/librarybrowser.css
@@ -21,7 +21,11 @@
}
.libraryPage {
- padding-top: 7em;
+ padding-top: 7em !important;
+}
+
+.layout-mobile .libraryPage {
+ padding-top: 4em !important;
}
.itemDetailPage {
@@ -128,10 +132,6 @@
margin-top: 0;
}
-.layout-mobile .pageTitleWithDefaultLogo {
- background-image: url(../img/icon-transparent.png);
-}
-
.headerLeft,
.skinHeader {
display: -webkit-box;
@@ -246,6 +246,7 @@
}
@media all and (min-width: 40em) {
+ .dashboardDocument .adminDrawerLogo,
.dashboardDocument .mainDrawerButton {
display: none !important;
}
@@ -313,7 +314,7 @@
}
.dashboardDocument .mainDrawer-scrollContainer {
- margin-top: 4.6em !important;
+ margin-top: 4.65em !important;
}
}
@@ -606,12 +607,11 @@
}
.detailLogo {
- width: 67.25vw;
- height: 14.5vh;
+ width: 30vw;
+ height: 25vh;
position: absolute;
- top: 15vh;
- right: 0;
- -webkit-background-size: contain;
+ top: 10vh;
+ right: 20vw;
background-size: contain;
}
@@ -619,26 +619,8 @@
display: none;
}
-@media all and (max-width: 87.5em) {
- .detailLogo {
- right: 5%;
- }
-}
-
-@media all and (max-width: 75em) {
- .detailLogo {
- right: 2%;
- }
-}
-
@media all and (max-width: 68.75em) {
.detailLogo {
- width: 14.91em;
- height: 3.5em;
- right: 5%;
- bottom: 5%;
- top: auto;
- background-position: center right;
display: none;
}
}
@@ -1119,50 +1101,3 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.itemsViewSettingsContainer > .button-flat {
margin: 0;
}
-
-.layout-mobile #myPreferencesMenuPage {
- padding-top: 3.75em;
-}
-
-.itemDetailsGroup {
- margin-bottom: 1.5em;
-}
-
-.trackSelections {
- max-width: 44em;
-}
-
-.detailsGroupItem,
-.trackSelections .selectContainer {
- display: flex;
- max-width: 44em;
- margin: 0 0 0.5em !important;
-}
-
-.trackSelections .selectContainer {
- margin: 0 0 0.3em !important;
-}
-
-.detailsGroupItem .label,
-.trackSelections .selectContainer .selectLabel {
- cursor: default;
- flex-grow: 0;
- flex-shrink: 0;
- flex-basis: 6.25em;
- margin: 0 0.6em 0 0;
-}
-
-.trackSelections .selectContainer .selectLabel {
- margin: 0 0.2em 0 0;
-}
-
-.trackSelections .selectContainer .detailTrackSelect {
- font-size: inherit;
- padding: 0;
- overflow: hidden;
-}
-
-.trackSelections .selectContainer .selectArrowContainer .selectArrow {
- margin-top: 0;
- font-size: 1.4em;
-}
diff --git a/src/bundle.js b/src/bundle.js
index 316f42c94a..eb358797e6 100644
--- a/src/bundle.js
+++ b/src/bundle.js
@@ -13,7 +13,7 @@ _define("document-register-element", function() {
// fetch
var fetch = require("whatwg-fetch");
_define("fetch", function() {
- return fetch
+ return fetch;
});
// query-string
@@ -85,15 +85,15 @@ _define("webcomponents", function() {
});
// libass-wasm
-var libass_wasm = require("libass-wasm");
+var libassWasm = require("libass-wasm");
_define("JavascriptSubtitlesOctopus", function() {
- return libass_wasm;
+ return libassWasm;
});
// material-icons
-var material_icons = require("material-design-icons-iconfont/dist/material-design-icons.css");
+var materialIcons = require("material-design-icons-iconfont/dist/material-design-icons.css");
_define("material-icons", function() {
- return material_icons;
+ return materialIcons;
});
// noto font
@@ -113,13 +113,36 @@ _define("polyfill", function () {
return polyfill;
});
-// Date-FNS
-var date_fns = require("date-fns");
-_define("date-fns", function () {
- return date_fns;
+// domtokenlist-shim
+var classlist = require("classlist.js");
+_define("classlist-polyfill", function () {
+ return classlist;
});
-var date_fns_locale = require("date-fns/locale");
-_define("date-fns/locale", function () {
- return date_fns_locale;
+// Date-FNS
+var dateFns = require("date-fns");
+_define("date-fns", function () {
+ return dateFns;
+});
+
+var dateFnsLocale = require("date-fns/locale");
+_define("date-fns/locale", function () {
+ return dateFnsLocale;
+});
+
+var fast_text_encoding = require("fast-text-encoding");
+_define("fast-text-encoding", function () {
+ return fast_text_encoding;
+});
+
+// intersection-observer
+var intersection_observer = require("intersection-observer");
+_define("intersection-observer", function () {
+ return intersection_observer;
+});
+
+// screenfull
+var screenfull = require("screenfull");
+_define("screenfull", function () {
+ return screenfull;
});
diff --git a/src/components/activitylog.js b/src/components/activitylog.js
index 934a610ad0..62eda74d5f 100644
--- a/src/components/activitylog.js
+++ b/src/components/activitylog.js
@@ -16,7 +16,7 @@ define(["events", "globalize", "dom", "date-fns", "dfnshelper", "userSettings",
html += 'dvr"
+ }) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr";
} else {
html += '' + icon + '';
}
diff --git a/src/components/appRouter.js b/src/components/appRouter.js
index 62bfb3cf40..a602d6dce8 100644
--- a/src/components/appRouter.js
+++ b/src/components/appRouter.js
@@ -268,6 +268,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
}
function getMaxBandwidth() {
+ /* eslint-disable compat/compat */
if (navigator.connection) {
var max = navigator.connection.downlinkMax;
if (max && max > 0 && max < Number.POSITIVE_INFINITY) {
@@ -279,6 +280,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
return max;
}
}
+ /* eslint-enable compat/compat */
return null;
}
@@ -577,8 +579,9 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
function showDirect(path) {
return new Promise(function(resolve, reject) {
- resolveOnNextShow = resolve, page.show(baseUrl()+path)
- })
+ resolveOnNextShow = resolve;
+ page.show(baseUrl() + path);
+ });
}
function show(path, options) {
diff --git a/src/components/apphost.js b/src/components/apphost.js
index 868416b369..56e9c93521 100644
--- a/src/components/apphost.js
+++ b/src/components/apphost.js
@@ -279,8 +279,8 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings"], f
features.push("screensaver");
webSettings.enableMultiServer().then(enabled => {
- if (enabled) features.push("multiserver")
- })
+ if (enabled) features.push("multiserver");
+ });
if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
features.push("subtitleappearancesettings");
@@ -351,8 +351,6 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings"], f
var deviceName;
var appName = "Jellyfin Web";
var appVersion = "10.5.0";
- var visibilityChange;
- var visibilityState;
var appHost = {
getWindowState: function () {
@@ -383,7 +381,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings"], f
return window.NativeShell.AppHost.getDefaultLayout();
}
- return getDefaultLayout()
+ return getDefaultLayout();
},
getDeviceProfile: getDeviceProfile,
init: function () {
@@ -426,40 +424,26 @@ define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings"], f
}
};
- var doc = self.document;
var isHidden = false;
+ var hidden;
+ var visibilityChange;
- if (doc) {
- if (void 0 !== doc.visibilityState) {
- visibilityChange = "visibilitychange";
- visibilityState = "hidden";
+ if (typeof document.hidden !== "undefined") { /* eslint-disable-line compat/compat */
+ hidden = "hidden";
+ visibilityChange = "visibilitychange";
+ } else if (typeof document.webkitHidden !== "undefined") {
+ hidden = "webkitHidden";
+ visibilityChange = "webkitvisibilitychange";
+ }
+
+ document.addEventListener(visibilityChange, function () {
+ /* eslint-disable-next-line compat/compat */
+ if (document[hidden]) {
+ onAppHidden();
} else {
- if (void 0 !== doc.mozHidden) {
- visibilityChange = "mozvisibilitychange";
- visibilityState = "mozVisibilityState";
- } else {
- if (void 0 !== doc.msHidden) {
- visibilityChange = "msvisibilitychange";
- visibilityState = "msVisibilityState";
- } else {
- if (void 0 !== doc.webkitHidden) {
- visibilityChange = "webkitvisibilitychange";
- visibilityState = "webkitVisibilityState";
- }
- }
- }
+ onAppVisible();
}
- }
-
- if (doc) {
- doc.addEventListener(visibilityChange, function () {
- if (document[visibilityState]) {
- onAppHidden();
- } else {
- onAppVisible();
- }
- });
- }
+ }, false);
if (self.addEventListener) {
self.addEventListener("focus", onAppVisible);
diff --git a/src/components/backdropscreensaver/plugin.js b/src/components/backdropscreensaver/plugin.js
index c0bd31fb86..55de27a138 100644
--- a/src/components/backdropscreensaver/plugin.js
+++ b/src/components/backdropscreensaver/plugin.js
@@ -52,5 +52,5 @@ define(["connectionManager"], function (connectionManager) {
currentSlideshow = null;
}
};
- }
+ };
});
diff --git a/src/components/chromecast/chromecasthelpers.js b/src/components/chromecast/chromecasthelpers.js
index 2fef0c68b3..9967a4d96c 100644
--- a/src/components/chromecast/chromecasthelpers.js
+++ b/src/components/chromecast/chromecasthelpers.js
@@ -188,9 +188,9 @@ define(['events'], function (events) {
return apiClient.getEndpointInfo().then(function (endpoint) {
if (endpoint.IsInNetwork) {
return apiClient.getPublicSystemInfo().then(function (info) {
- var localAddress = info.LocalAddress
+ var localAddress = info.LocalAddress;
if (!localAddress) {
- console.debug("No valid local address returned, defaulting to external one")
+ console.debug("No valid local address returned, defaulting to external one");
localAddress = serverAddress;
}
addToCache(serverAddress, localAddress);
diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js
index 1d5c23eb1f..fc976068e7 100644
--- a/src/components/directorybrowser/directorybrowser.js
+++ b/src/components/directorybrowser/directorybrowser.js
@@ -7,11 +7,11 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
systemInfo = info;
return info;
}
- )
+ );
}
function onDialogClosed() {
- loading.hide()
+ loading.hide();
}
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
@@ -24,7 +24,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
var promises = [];
if ("Network" === path) {
- promises.push(ApiClient.getNetworkDevices())
+ promises.push(ApiClient.getNetworkDevices());
} else {
if (path) {
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
@@ -101,7 +101,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
html += Globalize.translate("MessageDirectoryPickerLinuxInstruction");
html += "
";
}
- html += ""
+ html += "";
}
html += '