diff --git a/package.json b/package.json index 59d0a90be8..8ccb069877 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,12 @@ "css-loader": "^5.0.0", "cssnano": "^4.1.10", "del": "^6.0.0", - "eslint": "^7.12.0", + "eslint": "^7.12.1", "eslint-plugin-compat": "^3.5.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-promise": "^4.2.1", - "file-loader": "^6.1.1", + "file-loader": "^6.2.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", "gulp-cli": "^2.3.0", @@ -39,10 +39,10 @@ "gulp-postcss": "^8.0.0", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", - "gulp-terser": "^1.4.0", + "gulp-terser": "^1.4.1", "html-webpack-plugin": "^4.5.0", "lazypipe": "^1.0.2", - "node-sass": "^4.13.1", + "node-sass": "^5.0.0", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", "style-loader": "^2.0.0", @@ -50,7 +50,7 @@ "stylelint-config-rational-order": "^0.1.2", "stylelint-no-browser-hacks": "^1.2.1", "stylelint-order": "^4.1.0", - "webpack": "^5.2.0", + "webpack": "^5.3.2", "webpack-merge": "^4.2.2", "webpack-stream": "^6.1.0", "worker-plugin": "^5.0.0" @@ -62,6 +62,7 @@ "core-js": "^3.6.5", "date-fns": "^2.16.1", "epubjs": "^0.3.85", + "pdfjs-dist": "2.5.207", "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.12.0", @@ -69,7 +70,6 @@ "howler": "^2.2.1", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.2", - "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.5.1", "jstree": "^3.3.10", "libarchive.js": "^1.3.0", @@ -81,7 +81,7 @@ "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.2", "sortablejs": "^1.12.0", - "swiper": "^6.3.4", + "swiper": "^6.3.5", "webcomponents.js": "^0.7.24", "whatwg-fetch": "^3.4.1" }, @@ -326,6 +326,7 @@ "src/libraries/scroller.js", "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", + "src/plugins/pdfPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", "src/plugins/chromecastPlayer/chromecastHelper.js", "src/plugins/photoPlayer/plugin.js", diff --git a/src/assets/css/clearbutton.css b/src/assets/css/clearbutton.scss similarity index 100% rename from src/assets/css/clearbutton.css rename to src/assets/css/clearbutton.scss diff --git a/src/assets/css/detailtable.css b/src/assets/css/detailtable.scss similarity index 100% rename from src/assets/css/detailtable.css rename to src/assets/css/detailtable.scss diff --git a/src/assets/css/flexstyles.css b/src/assets/css/flexstyles.scss similarity index 100% rename from src/assets/css/flexstyles.css rename to src/assets/css/flexstyles.scss diff --git a/src/assets/css/fonts.css b/src/assets/css/fonts.scss similarity index 66% rename from src/assets/css/fonts.css rename to src/assets/css/fonts.scss index 6e87f11d9d..32dc2e7bd6 100644 --- a/src/assets/css/fonts.css +++ b/src/assets/css/fonts.scss @@ -1,29 +1,26 @@ -html { +@mixin font($weight: null, $size: null) { font-family: "Noto Sans", sans-serif; + font-weight: $weight; + font-size: $size; +} + +html { + @include font; text-size-adjust: 100%; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } -h1, -h2, -h3 { - font-family: "Noto Sans", sans-serif; -} - h1 { - font-weight: 400; - font-size: 1.8em; + @include font(400, 1.8em); } h2 { - font-weight: 400; - font-size: 1.5em; + @include font(400, 1.5em); } h3 { - font-weight: 400; - font-size: 1.17em; + @include font(400, 1.17em); } .layout-tv { diff --git a/src/assets/css/fonts.sized.css b/src/assets/css/fonts.sized.css deleted file mode 100644 index f60a94f236..0000000000 --- a/src/assets/css/fonts.sized.css +++ /dev/null @@ -1,31 +0,0 @@ -h1 { - font-weight: 400; - font-size: 1.8em; -} - -.layout-desktop h1 { - font-size: 2em; -} - -h2 { - font-weight: 400; - font-size: 1.5em; -} - -h3 { - font-weight: 400; - font-size: 1.17em; -} - -@media all and (min-height: 720px) { - html { - font-size: 20px; - } -} - -/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */ -@media all and (min-height: 1000px) { - html { - font-size: 27px; - } -} diff --git a/src/assets/css/fonts.sized.scss b/src/assets/css/fonts.sized.scss new file mode 100644 index 0000000000..1cec58a4a6 --- /dev/null +++ b/src/assets/css/fonts.sized.scss @@ -0,0 +1,31 @@ +@mixin header-font($size: null) { + font-weight: 400; + font-size: $size; +} + +html { + @media all and (min-height: 720px) { + font-size: 20px; + } + + /* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */ + @media all and (min-height: 1000px) { + font-size: 27px; + } +} + +h1 { + @include header-font(1.8em); + + .layout-desktop & { + font-size: 2em; + } +} + +h2 { + @include header-font(1.8em); +} + +h3 { + @include header-font(1.17em); +} diff --git a/src/assets/css/ios.css b/src/assets/css/ios.scss similarity index 100% rename from src/assets/css/ios.css rename to src/assets/css/ios.scss diff --git a/src/assets/css/livetv.css b/src/assets/css/livetv.scss similarity index 63% rename from src/assets/css/livetv.css rename to src/assets/css/livetv.scss index 695adff8c5..032bcddf48 100644 --- a/src/assets/css/livetv.css +++ b/src/assets/css/livetv.scss @@ -2,8 +2,8 @@ padding-bottom: 15em; } -@media all and (min-width: 62.5em) { - #guideTab { +#guideTab { + @media all and (min-width: 62.5em) { padding-left: 0.5em; } } diff --git a/src/assets/css/site.css b/src/assets/css/site.scss similarity index 83% rename from src/assets/css/site.css rename to src/assets/css/site.scss index 38e056df89..3d4ea7da0e 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.scss @@ -1,14 +1,19 @@ -body, -html { +@mixin fullpage { margin: 0; padding: 0; height: 100%; } -.layout-mobile, -.layout-tv { - -webkit-touch-callout: none; - user-select: none; +html { + @include fullpage; + line-height: 1.35; +} + +body { + @include fullpage; + overflow-x: hidden; + background-color: transparent !important; + -webkit-font-smoothing: antialiased; } .clipForScreenReader { @@ -36,14 +41,10 @@ html { contain: strict; } -html { - line-height: 1.35; -} - -body { - overflow-x: hidden; - background-color: transparent !important; - -webkit-font-smoothing: antialiased; +.layout-mobile, +.layout-tv { + -webkit-touch-callout: none; + user-select: none; } .mainAnimatedPage { @@ -58,7 +59,7 @@ body { overflow-y: hidden !important; } -div[data-role=page] { +div[data-role="page"] { outline: 0; } @@ -71,10 +72,10 @@ div[data-role=page] { padding-left: 0.15em; font-weight: 400; white-space: normal !important; -} -.fieldDescription + .fieldDescription { - margin-top: 0.3em; + + .fieldDescription { + margin-top: 0.3em; + } } .content-primary, @@ -85,9 +86,14 @@ div[data-role=page] { padding-bottom: 5em !important; } -@media all and (min-width: 50em) { - .readOnlyContent, - form { +.readOnlyContent { + @media all and (min-width: 50em) { + max-width: 54em; + } +} + +form { + @media all and (min-width: 50em) { max-width: 54em; } } @@ -107,14 +113,14 @@ div[data-role=page] { .headroom { will-change: transform; transition: transform 200ms linear; -} -.headroom--pinned { - transform: translateY(0%); -} + &--pinned { + transform: translateY(0%); + } -.headroom--unpinned { - transform: translateY(-100%); + &--unpinned { + transform: translateY(-100%); + } } .drawerContent { diff --git a/src/bundle.js b/src/bundle.js index 3d1d600a9f..2693ede52b 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -90,17 +90,16 @@ _define('material-icons', function() { return materialIcons; }); -// noto font -const noto = require('jellyfin-noto'); -_define('jellyfin-noto', function () { - return noto; -}); - const epubjs = require('epubjs'); _define('epubjs', function () { return epubjs; }); +const pdfjs = require('pdfjs-dist/build/pdf'); +_define('pdfjs', function () { + return pdfjs; +}); + // page.js const page = require('page'); _define('page', function() { diff --git a/src/components/apphost.js b/src/components/apphost.js index 875c7907e6..df2f7c2d2c 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -172,7 +172,6 @@ function supportsCue() { function onAppVisible() { if (isHidden) { isHidden = false; - console.debug('triggering app resume event'); events.trigger(appHost, 'resume'); } } @@ -180,7 +179,6 @@ function onAppVisible() { function onAppHidden() { if (!isHidden) { isHidden = true; - console.debug('app is hidden'); } } diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 8697a9a747..8e6fb497d9 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -1,3 +1,4 @@ +import appHost from 'apphost'; import dialogHelper from 'dialogHelper'; import layoutManager from 'layoutManager'; import globalize from 'globalize'; @@ -388,6 +389,11 @@ function showEditorInternal(itemId, serverId, template) { btnSubmit.classList.add('hide'); } + // Don't allow redirection to other websites from the TV layout + if (layoutManager.tv || !appHost.supports('externallinks')) { + dlg.querySelector('.btnHelp').remove(); + } + const editorContent = dlg.querySelector('.formDialogContent'); dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); diff --git a/src/config.json b/src/config.json index 33cc38a5f7..b34eb8659a 100644 --- a/src/config.json +++ b/src/config.json @@ -23,6 +23,12 @@ } ], "servers": [], + "fonts": [ + "https://repo.jellyfin.org/releases/other/jellyfin-noto/font-faces.css", + "https://repo.jellyfin.org/releases/other/jellyfin-noto/css/KR.css", + "https://repo.jellyfin.org/releases/other/jellyfin-noto/css/JP.css", + "https://repo.jellyfin.org/releases/other/jellyfin-noto/css/SC.css" + ], "plugins": [ "plugins/playAccessValidation/plugin", "plugins/experimentalWarnings/plugin", @@ -33,6 +39,7 @@ "plugins/bookPlayer/plugin", "plugins/youtubePlayer/plugin", "plugins/backdropScreensaver/plugin", + "plugins/pdfPlayer/plugin", "plugins/logoScreensaver/plugin", "plugins/sessionPlayer/plugin", "plugins/chromecastPlayer/plugin" diff --git a/src/controllers/dashboard/devices/devices.html b/src/controllers/dashboard/devices/devices.html index 63c348c900..e2504cd3e7 100644 --- a/src/controllers/dashboard/devices/devices.html +++ b/src/controllers/dashboard/devices/devices.html @@ -5,6 +5,7 @@

${HeaderDevices}

${Help} +
diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index 1178a0f1bd..c6e7281645 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -10,10 +10,32 @@ import 'cardStyle'; /* eslint-disable indent */ + // Local cache of loaded + let deviceIds = []; + function canDelete(deviceId) { return deviceId !== ApiClient.deviceId(); } + function deleteAllDevices(page) { + const msg = globalize.translate('DeleteDevicesConfirmation'); + + require(['confirm'], async function (confirm) { + await confirm({ + text: msg, + title: globalize.translate('HeaderDeleteDevices'), + confirmText: globalize.translate('ButtonDelete'), + primary: 'delete' + }); + + loading.show(); + await Promise.all( + deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id)) + ); + loadData(page); + }); + } + function deleteDevice(page, id) { const msg = globalize.translate('DeleteDeviceConfirmation'); @@ -23,16 +45,10 @@ import 'cardStyle'; title: globalize.translate('HeaderDeleteDevice'), confirmText: globalize.translate('Delete'), primary: 'delete' - }).then(function () { + }).then(async () => { loading.show(); - ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Devices', { - Id: id - }) - }).then(function () { - loadData(page); - }); + await ApiClient.deleteDevice(id); + loadData(page); }); }); } @@ -129,6 +145,7 @@ import 'cardStyle'; loading.show(); ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { load(page, result.Items); + deviceIds = result.Items.map((device) => device.Id); loading.hide(); }); } @@ -145,6 +162,9 @@ import 'cardStyle'; view.addEventListener('viewshow', function () { loadData(this); }); - } + view.querySelector('#deviceDeleteAll').addEventListener('click', function() { + deleteAllDevices(view); + }); + } /* eslint-enable indent */ diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index c68fe15feb..4c2d29538d 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -649,7 +649,10 @@ function reloadFromItem(instance, page, params, item, user) { const itemBirthLocation = page.querySelector('#itemBirthLocation'); if (item.Type == 'Person' && item.ProductionLocations && item.ProductionLocations.length) { - const gmap = '' + item.ProductionLocations[0] + ''; + let gmap = item.ProductionLocations[0]; + if (!layoutManager.tv && appHost.supports('externallinks')) { + gmap = `${gmap}`; + } itemBirthLocation.classList.remove('hide'); itemBirthLocation.innerHTML = globalize.translate('BirthPlaceValue', gmap); } else { @@ -1066,7 +1069,7 @@ function renderDetails(page, item, apiClient, context, isStatic) { reloadUserDataButtons(page, item); // Don't allow redirection to other websites from the TV layout - if (!layoutManager.tv) { + if (!layoutManager.tv && appHost.supports('externallinks')) { renderLinks(page, item); } @@ -1724,7 +1727,7 @@ function renderCollectionItemType(page, parentItem, type, items) { html += ''; const collectionItems = page.querySelector('.collectionItems'); collectionItems.insertAdjacentHTML('beforeend', html); - imageLoader.lazyChildren(collectionItems); + imageLoader.lazyChildren(collectionItems.lastChild); } function renderMusicVideos(page, item, user) { diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index 573e5122c5..6aed25f9a8 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -248,7 +248,7 @@ import 'emby-itemscontainer'; tabContent.querySelector('.btnNewCollection').addEventListener('click', () => { import('collectionEditor').then(({default: collectionEditor}) => { const serverId = ApiClient.serverInfo().Id; - new collectionEditor.showEditor({ + new collectionEditor({ items: [], serverId: serverId }); diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index ca02ede36d..c87282376c 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -17,7 +17,7 @@ import 'emby-button'; if (!pageData) { pageData = data[key] = { query: { - SortBy: 'SortName', + SortBy: 'Random', SortOrder: 'Ascending', IncludeItemTypes: 'Movie', Recursive: true, diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index 3a17fd7997..05dccd3862 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -17,7 +17,7 @@ import 'emby-button'; if (!pageData) { pageData = data[key] = { query: { - SortBy: 'SortName', + SortBy: 'Random', SortOrder: 'Ascending', IncludeItemTypes: 'Series', Recursive: true, diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index 0c3d6850aa..c56777f378 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -25,8 +25,9 @@ export class BookPlayer { } play(options) { - this._progress = 0; - this._loaded = false; + this.progress = 0; + this.cancellationToken = false; + this.loaded = false; loading.show(); const elem = this.createMediaElement(); @@ -36,35 +37,35 @@ export class BookPlayer { stop() { this.unbindEvents(); - const elem = this._mediaElement; - const tocElement = this._tocElement; - const rendition = this._rendition; + const elem = this.mediaElement; + const tocElement = this.tocElement; + const rendition = this.rendition; if (elem) { dialogHelper.close(elem); - this._mediaElement = null; + this.mediaElement = null; } if (tocElement) { tocElement.destroy(); - this._tocElement = null; + this.tocElement = null; } if (rendition) { rendition.destroy(); } - // Hide loader in case player was not fully loaded yet + // hide loader in case player was not fully loaded yet loading.hide(); - this._cancellationToken.shouldCancel = true; + this.cancellationToken = true; } currentItem() { - return this._currentItem; + return this.item; } currentTime() { - return this._progress * 1000; + return this.progress * 1000; } duration() { @@ -96,12 +97,10 @@ export class BookPlayer { onWindowKeyUp(e) { const key = keyboardnavigation.getKeyName(e); - - // TODO: depending on the event this can be the document or the rendition itself - const rendition = this._rendition || this; + const rendition = this.rendition; const book = rendition.book; - if (this._loaded === false) return; + if (!this.loaded) return; switch (key) { case 'l': case 'ArrowRight': @@ -114,9 +113,9 @@ export class BookPlayer { book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); break; case 'Escape': - if (this._tocElement) { + if (this.tocElement) { // Close table of contents on ESC if it is open - this._tocElement.destroy(); + this.tocElement.destroy(); } else { // Otherwise stop the entire book player this.stop(); @@ -130,7 +129,7 @@ export class BookPlayer { } bindMediaElementEvents() { - const elem = this._mediaElement; + const elem = this.mediaElement; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('#btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true}); @@ -147,11 +146,11 @@ export class BookPlayer { document.addEventListener('keyup', this.onWindowKeyUp); // FIXME: I don't really get why document keyup event is not triggered when epub is in focus - this._rendition.on('keyup', this.onWindowKeyUp); + this.rendition.on('keyup', this.onWindowKeyUp); } unbindMediaElementEvents() { - const elem = this._mediaElement; + const elem = this.mediaElement; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('#btnBookplayerExit').removeEventListener('click', this.onDialogClosed); @@ -163,20 +162,20 @@ export class BookPlayer { } unbindEvents() { - if (this._mediaElement) { + if (this.mediaElement) { this.unbindMediaElementEvents(); } document.removeEventListener('keyup', this.onWindowKeyUp); - if (this._rendition) { - this._rendition.off('keyup', this.onWindowKeyUp); + if (this.rendition) { + this.rendition.off('keyup', this.onWindowKeyUp); } } openTableOfContents() { - if (this._loaded) { - this._tocElement = new TableOfContents(this); + if (this.loaded) { + this.tocElement = new TableOfContents(this); } } @@ -191,7 +190,7 @@ export class BookPlayer { } createMediaElement() { - let elem = this._mediaElement; + let elem = this.mediaElement; if (elem) { return elem; } @@ -207,8 +206,6 @@ export class BookPlayer { removeOnClose: true }); - elem.id = 'bookPlayer'; - let html = ''; if (browser.mobile) { @@ -226,13 +223,13 @@ export class BookPlayer { html += '
'; } + elem.id = 'bookPlayer'; elem.innerHTML = html; dialogHelper.open(elem); } - this._mediaElement = elem; - + this.mediaElement = elem; return elem; } @@ -253,7 +250,7 @@ export class BookPlayer { setCurrentSrc(elem, options) { const item = options.items[0]; - this._currentItem = item; + this.item = item; this.streamInfo = { started: true, ended: false, @@ -271,13 +268,8 @@ export class BookPlayer { const book = epubjs(downloadHref, {openAs: 'epub'}); const rendition = this.render('viewer', book); - this._currentSrc = downloadHref; - this._rendition = rendition; - const cancellationToken = { - shouldCancel: false - }; - - this._cancellationToken = cancellationToken; + this.currentSrc = downloadHref; + this.rendition = rendition; return rendition.display().then(() => { const epubElem = document.querySelector('.epub-container'); @@ -285,10 +277,8 @@ export class BookPlayer { this.bindEvents(); - return this._rendition.book.locations.generate(1024).then(async () => { - if (cancellationToken.shouldCancel) { - return reject(); - } + return this.rendition.book.locations.generate(1024).then(async () => { + if (this.cancellationToken) reject(); const percentageTicks = options.startPositionTicks / 10000000; if (percentageTicks !== 0.0) { @@ -296,15 +286,14 @@ export class BookPlayer { await rendition.display(resumeLocation); } - this._loaded = true; + this.loaded = true; epubElem.style.display = 'block'; rendition.on('relocated', (locations) => { - this._progress = book.locations.percentageFromCfi(locations.start.cfi); + this.progress = book.locations.percentageFromCfi(locations.start.cfi); events.trigger(this, 'timeupdate'); }); loading.hide(); - return resolve(); }); }, () => { @@ -320,7 +309,7 @@ export class BookPlayer { } canPlayItem(item) { - if (item.Path && (item.Path.endsWith('epub'))) { + if (item.Path && item.Path.endsWith('epub')) { return true; } diff --git a/src/plugins/bookPlayer/tableOfContents.js b/src/plugins/bookPlayer/tableOfContents.js index a1c5d8f220..165c1fa9ac 100644 --- a/src/plugins/bookPlayer/tableOfContents.js +++ b/src/plugins/bookPlayer/tableOfContents.js @@ -2,8 +2,8 @@ import dialogHelper from 'dialogHelper'; export default class TableOfContents { constructor(bookPlayer) { - this._bookPlayer = bookPlayer; - this._rendition = bookPlayer._rendition; + this.bookPlayer = bookPlayer; + this.rendition = bookPlayer.rendition; this.onDialogClosed = this.onDialogClosed.bind(this); @@ -11,24 +11,24 @@ export default class TableOfContents { } destroy() { - const elem = this._elem; + const elem = this.elem; if (elem) { this.unbindEvents(); dialogHelper.close(elem); } - this._bookPlayer._tocElement = null; + this.bookPlayer.tocElement = null; } bindEvents() { - const elem = this._elem; + const elem = this.elem; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true}); } unbindEvents() { - const elem = this._elem; + const elem = this.elem; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed); @@ -52,7 +52,7 @@ export default class TableOfContents { } createMediaElement() { - const rendition = this._rendition; + const rendition = this.rendition; const elem = dialogHelper.createDialog({ size: 'small', @@ -68,7 +68,8 @@ export default class TableOfContents { tocHtml += '