diff --git a/src/controllers/dashboard/general.html b/src/controllers/dashboard/general.html
index 67e9bfb7ae..21c81eddd7 100644
--- a/src/controllers/dashboard/general.html
+++ b/src/controllers/dashboard/general.html
@@ -65,7 +65,7 @@
diff --git a/src/controllers/session/login/index.js b/src/controllers/session/login/index.js
index 238d6f6ffd..9aecc0f545 100644
--- a/src/controllers/session/login/index.js
+++ b/src/controllers/session/login/index.js
@@ -1,3 +1,5 @@
+import DOMPurify from 'dompurify';
+import { marked } from 'marked';
import { appHost } from '../../../components/apphost';
import appSettings from '../../../scripts/settings/appSettings';
import dom from '../../../scripts/dom';
@@ -14,6 +16,7 @@ import toast from '../../../components/toast/toast';
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
import baseAlert from '../../../components/alert';
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
+import './login.scss';
/* eslint-disable indent */
@@ -281,7 +284,20 @@ import cardBuilder from '../../../components/cardbuilder/cardBuilder';
loading.hide();
});
apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) {
- view.querySelector('.disclaimer').textContent = options.LoginDisclaimer || '';
+ const disclaimer = view.querySelector('.disclaimer');
+
+ disclaimer.innerHTML = DOMPurify.sanitize(marked(options.LoginDisclaimer || ''));
+
+ for (const elem of disclaimer.querySelectorAll('a')) {
+ elem.target = '_blank';
+ elem.classList.add('button-link');
+ elem.setAttribute('is', 'emby-linkbutton');
+
+ if (layoutManager.tv) {
+ // Disable links navigation on TV
+ elem.tabIndex = -1;
+ }
+ }
});
});
view.addEventListener('viewhide', function () {
diff --git a/src/controllers/session/login/login.scss b/src/controllers/session/login/login.scss
new file mode 100644
index 0000000000..d84d0088d1
--- /dev/null
+++ b/src/controllers/session/login/login.scss
@@ -0,0 +1,26 @@
+.disclaimerContainer {
+ display: flex;
+ margin-top: 2em;
+}
+
+.disclaimer {
+ margin: 0 auto;
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ p {
+ text-align: center;
+ }
+
+ ol,
+ ul {
+ max-width: 40em;
+ margin-left: auto;
+ margin-right: auto;
+ width: fit-content;
+ }
+}
diff --git a/src/legacy/domParserTextHtml.js b/src/legacy/domParserTextHtml.js
new file mode 100644
index 0000000000..f5a03ae442
--- /dev/null
+++ b/src/legacy/domParserTextHtml.js
@@ -0,0 +1,36 @@
+/*
+ * DOMParser HTML extension
+ * 2019-11-13
+ *
+ * By Eli Grey, http://eligrey.com
+ * Public domain.
+ * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+ */
+
+/*! @source https://gist.github.com/1129031 */
+
+(function (DOMParser) {
+ 'use strict';
+
+ const DOMParser_proto = DOMParser.prototype;
+ const real_parseFromString = DOMParser_proto.parseFromString;
+
+ // Firefox/Opera/IE throw errors on unsupported types
+ try {
+ // WebKit returns null on unsupported types
+ if ((new DOMParser).parseFromString('', 'text/html')) {
+ // text/html parsing is natively supported
+ return;
+ }
+ } catch (ex) { /* noop */ }
+
+ DOMParser_proto.parseFromString = function (markup, type) {
+ if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
+ const doc = document.implementation.createHTMLDocument('');
+ doc.documentElement.innerHTML = markup;
+ return doc;
+ } else {
+ return real_parseFromString.apply(this, arguments);
+ }
+ };
+}(DOMParser));
diff --git a/src/scripts/site.js b/src/scripts/site.js
index ccfbe9d308..9a167ff5d2 100644
--- a/src/scripts/site.js
+++ b/src/scripts/site.js
@@ -29,6 +29,7 @@ import { pageClassOn, serverAddress } from './clientUtils';
import '../libraries/screensavermanager';
import './serverNotifications';
import '../components/playback/playerSelectionMenu';
+import '../legacy/domParserTextHtml';
import '../legacy/focusPreventScroll';
import '../legacy/vendorStyles';
import SyncPlay from '../components/syncPlay/core';