From 9e92bfaae78b3bd1f1dfdc9ba02ced18729bef95 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 14 Jun 2020 23:37:48 +0300 Subject: [PATCH 01/58] Fix subtitle line spacing --- .../subtitleappearancehelper.js | 71 ++++++------------- src/plugins/htmlVideoPlayer/plugin.js | 2 +- src/plugins/htmlVideoPlayer/style.css | 6 ++ 3 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index bec8156ac..503c81472 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -1,55 +1,30 @@ define([], function () { 'use strict'; - function getTextStyles(settings, isCue) { + function getTextStyles(settings) { var list = []; - if (isCue) { - switch (settings.textSize || '') { - - case 'smaller': - list.push({ name: 'font-size', value: '.5em' }); - break; - case 'small': - list.push({ name: 'font-size', value: '.7em' }); - break; - case 'large': - list.push({ name: 'font-size', value: '1.3em' }); - break; - case 'larger': - list.push({ name: 'font-size', value: '1.72em' }); - break; - case 'extralarge': - list.push({ name: 'font-size', value: '2em' }); - break; - default: - case 'medium': - break; - } - } else { - switch (settings.textSize || '') { - - case 'smaller': - list.push({ name: 'font-size', value: '.8em' }); - break; - case 'small': - list.push({ name: 'font-size', value: 'inherit' }); - break; - case 'larger': - list.push({ name: 'font-size', value: '2em' }); - break; - case 'extralarge': - list.push({ name: 'font-size', value: '2.2em' }); - break; - case 'large': - list.push({ name: 'font-size', value: '1.72em' }); - break; - default: - case 'medium': - list.push({ name: 'font-size', value: '1.36em' }); - break; - } + switch (settings.textSize || '') { + case 'smaller': + list.push({ name: 'font-size', value: '.8em' }); + break; + case 'small': + list.push({ name: 'font-size', value: 'inherit' }); + break; + case 'larger': + list.push({ name: 'font-size', value: '2em' }); + break; + case 'extralarge': + list.push({ name: 'font-size', value: '2.2em' }); + break; + case 'large': + list.push({ name: 'font-size', value: '1.72em' }); + break; + default: + case 'medium': + list.push({ name: 'font-size', value: '1.36em' }); + break; } switch (settings.dropShadow || '') { @@ -122,10 +97,10 @@ define([], function () { return []; } - function getStyles(settings, isCue) { + function getStyles(settings) { return { - text: getTextStyles(settings, isCue), + text: getTextStyles(settings), window: getWindowStyles(settings) }; } diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index cc312bb95..930261253 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1178,7 +1178,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa document.getElementsByTagName('head')[0].appendChild(styleElem); } - styleElem.innerHTML = getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings(), true), '.htmlvideoplayer'); + styleElem.innerHTML = getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings()), '.htmlvideoplayer'); }); } diff --git a/src/plugins/htmlVideoPlayer/style.css b/src/plugins/htmlVideoPlayer/style.css index b83a7816f..0f63e72d7 100644 --- a/src/plugins/htmlVideoPlayer/style.css +++ b/src/plugins/htmlVideoPlayer/style.css @@ -33,6 +33,12 @@ video::-webkit-media-controls { text-shadow: 0.14em 0.14em 0.14em rgba(0, 0, 0, 1); -webkit-font-smoothing: antialiased; font-family: inherit; + line-height: normal; /* Restore value. See -webkit-media-text-track-container 'line-height' */ +} + +.htmlvideoplayer::-webkit-media-text-track-container { + font-size: 170% !important; /* Override element inline style */ + line-height: 50%; /* Child element cannot set line height smaller than its parent has. This allow smaller values for children */ } .htmlvideoplayer-moveupsubtitles::-webkit-media-text-track-display { From 597b4258d9eba3a663082632149511d4500264ce Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 7 Jul 2020 01:06:47 +0300 Subject: [PATCH 02/58] Add subtitle position --- .../subtitleappearancehelper.js | 39 ++++++++-- .../subtitlesettings/subtitlesettings.js | 71 ++++++++++++++++++- .../subtitlesettings.template.html | 53 ++++++++++++++ src/elements/emby-slider/emby-slider.css | 15 ++++ src/elements/emby-slider/emby-slider.js | 10 +++ src/plugins/htmlVideoPlayer/plugin.js | 39 +++++----- src/plugins/htmlVideoPlayer/style.css | 9 ++- src/scripts/settings/userSettings.js | 6 +- src/strings/en-us.json | 5 +- src/strings/ru.json | 5 +- 10 files changed, 219 insertions(+), 33 deletions(-) diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index 503c81472..e611228d9 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -1,7 +1,7 @@ define([], function () { 'use strict'; - function getTextStyles(settings) { + function getTextStyles(settings, preview) { var list = []; @@ -89,19 +89,44 @@ define([], function () { break; } + if (!preview) { + const pos = parseInt(settings.verticalPosition); + const lineHeight = 1.35; // FIXME: It is better to read this value from element + const line = Math.abs(pos * lineHeight); + if (pos < 0) { + list.push({ name: 'min-height', value: `${line}em` }); + list.push({ name: 'margin-top', value: '' }); + } else { + list.push({ name: 'min-height', value: '' }); + list.push({ name: 'margin-top', value: `${line}em` }); + } + } + return list; } - function getWindowStyles(settings) { + function getWindowStyles(settings, preview) { + const list = []; - return []; + if (!preview) { + const pos = parseInt(settings.verticalPosition); + if (pos < 0) { + list.push({ name: 'top', value: '' }); + list.push({ name: 'bottom', value: '0' }); + } else { + list.push({ name: 'top', value: '0' }); + list.push({ name: 'bottom', value: '' }); + } + } + + return list; } - function getStyles(settings) { + function getStyles(settings, preview) { return { - text: getTextStyles(settings), - window: getWindowStyles(settings) + text: getTextStyles(settings, preview), + window: getWindowStyles(settings, preview) }; } @@ -117,7 +142,7 @@ define([], function () { function applyStyles(elements, appearanceSettings) { - var styles = getStyles(appearanceSettings); + var styles = getStyles(appearanceSettings, !!elements.preview); if (elements.text) { applyStyleList(styles.text, elements.text); diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js index d728360d0..819631184 100644 --- a/src/components/subtitlesettings/subtitlesettings.js +++ b/src/components/subtitlesettings/subtitlesettings.js @@ -1,4 +1,4 @@ -define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loading', 'connectionManager', 'subtitleAppearanceHelper', 'dom', 'events', 'listViewStyle', 'emby-select', 'emby-input', 'emby-checkbox', 'flexStyles'], function (require, globalize, appSettings, appHost, focusManager, loading, connectionManager, subtitleAppearanceHelper, dom, events) { +define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loading', 'connectionManager', 'subtitleAppearanceHelper', 'dom', 'events', 'layoutManager', 'listViewStyle', 'emby-select', 'emby-input', 'emby-checkbox', 'emby-slider', 'flexStyles'], function (require, globalize, appSettings, appHost, focusManager, loading, connectionManager, subtitleAppearanceHelper, dom, events, layoutManager) { 'use strict'; function populateLanguages(select, languages) { @@ -21,6 +21,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi appearanceSettings.font = context.querySelector('#selectFont').value; appearanceSettings.textBackground = context.querySelector('#inputTextBackground').value; appearanceSettings.textColor = context.querySelector('#inputTextColor').value; + appearanceSettings.verticalPosition = context.querySelector('#sliderVerticalPosition').value; return appearanceSettings; } @@ -47,6 +48,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi context.querySelector('#inputTextBackground').value = appearanceSettings.textBackground || 'transparent'; context.querySelector('#inputTextColor').value = appearanceSettings.textColor || '#ffffff'; context.querySelector('#selectFont').value = appearanceSettings.font || ''; + context.querySelector('#sliderVerticalPosition').value = appearanceSettings.verticalPosition; context.querySelector('#selectSubtitleBurnIn').value = appSettings.get('subtitleburnin') || ''; @@ -134,10 +136,45 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi var elements = { window: view.querySelector('.subtitleappearance-preview-window'), - text: view.querySelector('.subtitleappearance-preview-text') + text: view.querySelector('.subtitleappearance-preview-text'), + preview: true }; subtitleAppearanceHelper.applyStyles(elements, appearanceSettings); + + subtitleAppearanceHelper.applyStyles({ + window: view.querySelector('.subtitleappearance-fullpreview-window'), + text: view.querySelector('.subtitleappearance-fullpreview-text') + }, appearanceSettings); + } + + const subtitlePreviewDelay = 1000; + let subtitlePreviewTimer; + + function showSubtitlePreview(persistent) { + clearTimeout(subtitlePreviewTimer); + + this._fullPreview.classList.remove('subtitleappearance-fullpreview-hide'); + + if (persistent) { + this._refFullPreview++; + } + + if (this._refFullPreview === 0) { + subtitlePreviewTimer = setTimeout(hideSubtitlePreview.bind(this), subtitlePreviewDelay); + } + } + + function hideSubtitlePreview(persistent) { + clearTimeout(subtitlePreviewTimer); + + if (persistent) { + this._refFullPreview--; + } + + if (this._refFullPreview === 0) { + this._fullPreview.classList.add('subtitleappearance-fullpreview-hide'); + } } function embed(options, self) { @@ -162,6 +199,36 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi if (appHost.supports('subtitleappearancesettings')) { options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide'); + + self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview'); + self._refFullPreview = 0; + + const sliderVerticalPosition = options.element.querySelector('#sliderVerticalPosition'); + sliderVerticalPosition.addEventListener('input', onAppearanceFieldChange); + sliderVerticalPosition.addEventListener('input', () => showSubtitlePreview.call(self)); + + const eventPrefix = window.PointerEvent ? 'pointer' : 'mouse'; + sliderVerticalPosition.addEventListener(`${eventPrefix}enter`, () => showSubtitlePreview.call(self, true)); + sliderVerticalPosition.addEventListener(`${eventPrefix}leave`, () => hideSubtitlePreview.call(self, true)); + + if (layoutManager.tv) { + sliderVerticalPosition.addEventListener('focus', () => showSubtitlePreview.call(self, true)); + sliderVerticalPosition.addEventListener('blur', () => hideSubtitlePreview.call(self, true)); + + // Give CustomElements time to attach + setTimeout(() => { + sliderVerticalPosition.classList.add('focusable'); + sliderVerticalPosition.enableKeyboardDragging(); + }, 0); + } + + options.element.querySelector('.chkPreview').addEventListener('change', (e) => { + if (e.target.checked) { + showSubtitlePreview.call(self, true); + } else { + hideSubtitlePreview.call(self, true); + } + }); } self.loadData(); diff --git a/src/components/subtitlesettings/subtitlesettings.template.html b/src/components/subtitlesettings/subtitlesettings.template.html index 716296a25..2f49fa4c3 100644 --- a/src/components/subtitlesettings/subtitlesettings.template.html +++ b/src/components/subtitlesettings/subtitlesettings.template.html @@ -38,6 +38,45 @@ ${HeaderSubtitleAppearance} + + +
+
+
+ ${HeaderSubtitleAppearance} +
+ ${TheseSettingsAffectSubtitlesOnThisDevice} +
+
+
+
@@ -89,6 +128,20 @@
+ +
+
+ +
+
${SubtitleVerticalPositionHelp}
+
+ +
+ +
'; - } - } - - itemHtml += ''; - - return itemHtml; - }).join(''); - - html += '
'; - } - - var elem = context.querySelector('.subtitleList'); - - if (subs.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - elem.innerHTML = html; - } - - function fillLanguages(context, apiClient, languages) { - var selectLanguage = context.querySelector('#selectLanguage'); - - selectLanguage.innerHTML = languages.map(function (l) { - return ''; - }); - - var lastLanguage = userSettings.get('subtitleeditor-language'); - if (lastLanguage) { - selectLanguage.value = lastLanguage; - } else { - apiClient.getCurrentUser().then(function (user) { - var lang = user.Configuration.SubtitleLanguagePreference; - - if (lang) { - selectLanguage.value = lang; - } - }); - } - } - - function renderSearchResults(context, results) { - var lastProvider = ''; - var html = ''; - - if (!results.length) { - context.querySelector('.noSearchResults').classList.remove('hide'); - context.querySelector('.subtitleResults').innerHTML = ''; - loading.hide(); - return; - } - - context.querySelector('.noSearchResults').classList.add('hide'); - - for (var i = 0, length = results.length; i < length; i++) { - var result = results[i]; - - var provider = result.ProviderName; - - if (provider !== lastProvider) { - if (i > 0) { - html += ''; - } - html += '

' + provider + '

'; - html += '
'; - lastProvider = provider; - } - - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; if (layoutManager.tv) { className += ' listItem-focusscale listItem-button'; } - html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + className += ' listItem-noborder'; - html += ''; + itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - var bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + itemHtml += ''; - html += '
'; + itemHtml += '
'; - html += '
' + (result.Name) + '
'; - html += '
'; + itemHtml += '
'; + itemHtml += s.DisplayTitle || ''; + itemHtml += '
'; - if (result.Format) { - html += '' + globalize.translate('FormatValue', result.Format) + ''; + if (s.Path) { + itemHtml += '
' + (s.Path) + '
'; } - if (result.DownloadCount != null) { - html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; - } - html += '
'; - - if (result.Comment) { - html += '
' + (result.Comment) + '
'; - } - - if (result.IsHashMatch) { - html += '
' + globalize.translate('PerfectMatch') + '
'; - } - - html += '
'; + itemHtml += ''; + itemHtml += '
'; if (!layoutManager.tv) { - html += ''; + if (s.Path) { + itemHtml += ''; + } } - html += ''; + itemHtml += ''; + + return itemHtml; + }).join(''); + + html += '
'; + } + + let elem = context.querySelector('.subtitleList'); + + if (subs.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + elem.innerHTML = html; +} + +function fillLanguages(context, apiClient, languages) { + let selectLanguage = context.querySelector('#selectLanguage'); + + selectLanguage.innerHTML = languages.map(function (l) { + return ''; + }); + + let lastLanguage = userSettings.get('subtitleeditor-language'); + if (lastLanguage) { + selectLanguage.value = lastLanguage; + } else { + apiClient.getCurrentUser().then(function (user) { + let lang = user.Configuration.SubtitleLanguagePreference; + + if (lang) { + selectLanguage.value = lang; + } + }); + } +} + +function renderSearchResults(context, results) { + let lastProvider = ''; + let html = ''; + + if (!results.length) { + context.querySelector('.noSearchResults').classList.remove('hide'); + context.querySelector('.subtitleResults').innerHTML = ''; + loading.hide(); + return; + } + + context.querySelector('.noSearchResults').classList.add('hide'); + + for (let i = 0, length = results.length; i < length; i++) { + let result = results[i]; + + let provider = result.ProviderName; + + if (provider !== lastProvider) { + if (i > 0) { + html += ''; + } + html += '

' + provider + '

'; + html += '
'; + lastProvider = provider; } - if (results.length) { - html += '
'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; + if (layoutManager.tv) { + className += ' listItem-focusscale listItem-button'; } - var elem = context.querySelector('.subtitleResults'); - elem.innerHTML = html; + html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + + html += ''; + + let bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + + html += '
'; + + html += '
' + (result.Name) + '
'; + html += '
'; + + if (result.Format) { + html += '' + globalize.translate('FormatValue', result.Format) + ''; + } + + if (result.DownloadCount != null) { + html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; + } + html += '
'; + + if (result.Comment) { + html += '
' + (result.Comment) + '
'; + } + + if (result.IsHashMatch) { + html += '
' + globalize.translate('PerfectMatch') + '
'; + } + + html += '
'; + + if (!layoutManager.tv) { + html += ''; + } + + html += ''; + } + + if (results.length) { + html += ''; + } + + let elem = context.querySelector('.subtitleResults'); + elem.innerHTML = html; + + loading.hide(); +} + +function searchForSubtitles(context, language) { + userSettings.set('subtitleeditor-language', language); + + loading.show(); + + let apiClient = connectionManager.getApiClient(currentItem.ServerId); + let url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + + apiClient.getJSON(url).then(function (results) { + renderSearchResults(context, results); + }); +} + +function reload(context, apiClient, itemId) { + context.querySelector('.noSearchResults').classList.add('hide'); + + function onGetItem(item) { + currentItem = item; + + fillSubtitleList(context, item); + let file = item.Path || ''; + let index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); + if (index > -1) { + file = file.substring(index + 1); + } + + if (file) { + context.querySelector('.pathValue').innerHTML = file; + context.querySelector('.originalFile').classList.remove('hide'); + } else { + context.querySelector('.pathValue').innerHTML = ''; + context.querySelector('.originalFile').classList.add('hide'); + } loading.hide(); } - function searchForSubtitles(context, language) { - userSettings.set('subtitleeditor-language', language); + if (typeof itemId === 'string') { + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + } else { + onGetItem(itemId); + } +} - loading.show(); +function onSearchSubmit(e) { + let form = this; - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - var url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + let lang = form.querySelector('#selectLanguage', form).value; - apiClient.getJSON(url).then(function (results) { - renderSearchResults(context, results); - }); + searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); + + e.preventDefault(); + return false; +} + +function onSubtitleListClick(e) { + let btnDelete = dom.parentWithClass(e.target, 'btnDelete'); + if (btnDelete) { + let index = btnDelete.getAttribute('data-index'); + let context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); + deleteLocalSubtitle(context, index); + } +} + +function onSubtitleResultsClick(e) { + let subtitleId; + let context; + + let btnOptions = dom.parentWithClass(e.target, 'btnOptions'); + if (btnOptions) { + subtitleId = btnOptions.getAttribute('data-subid'); + context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); + showDownloadOptions(btnOptions, context, subtitleId); } - function reload(context, apiClient, itemId) { - context.querySelector('.noSearchResults').classList.add('hide'); + let btnDownload = dom.parentWithClass(e.target, 'btnDownload'); + if (btnDownload) { + subtitleId = btnDownload.getAttribute('data-subid'); + context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); + downloadRemoteSubtitles(context, subtitleId); + } +} - function onGetItem(item) { - currentItem = item; +function showDownloadOptions(button, context, subtitleId) { + let items = []; - fillSubtitleList(context, item); - var file = item.Path || ''; - var index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); - if (index > -1) { - file = file.substring(index + 1); + items.push({ + name: globalize.translate('Download'), + id: 'download' + }); + + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: items, + positionTo: button + + }).then(function (id) { + switch (id) { + case 'download': + downloadRemoteSubtitles(context, subtitleId); + break; + default: + break; } + }); + }); +} - if (file) { - context.querySelector('.pathValue').innerHTML = file; - context.querySelector('.originalFile').classList.remove('hide'); - } else { - context.querySelector('.pathValue').innerHTML = ''; - context.querySelector('.originalFile').classList.add('hide'); - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + let fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - loading.hide(); - } +function showEditorInternal(itemId, serverId, template) { + hasChanges = false; - if (typeof itemId === 'string') { - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + let apiClient = connectionManager.getApiClient(serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + let dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - onGetItem(itemId); - } - } - - function onSearchSubmit(e) { - var form = this; - - var lang = form.querySelector('#selectLanguage', form).value; - - searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); - - e.preventDefault(); - return false; - } - - function onSubtitleListClick(e) { - var btnDelete = dom.parentWithClass(e.target, 'btnDelete'); - if (btnDelete) { - var index = btnDelete.getAttribute('data-index'); - var context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); - deleteLocalSubtitle(context, index); - } - } - - function onSubtitleResultsClick(e) { - var subtitleId; - var context; - - var btnOptions = dom.parentWithClass(e.target, 'btnOptions'); - if (btnOptions) { - subtitleId = btnOptions.getAttribute('data-subid'); - context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); - showDownloadOptions(btnOptions, context, subtitleId); + dialogOptions.size = 'small'; } - var btnDownload = dom.parentWithClass(e.target, 'btnDownload'); - if (btnDownload) { - subtitleId = btnDownload.getAttribute('data-subid'); - context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); - downloadRemoteSubtitles(context, subtitleId); + let dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + dlg.classList.add('subtitleEditorDialog'); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); + + dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); + + let btnSubmit = dlg.querySelector('.btnSubmit'); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); + } else { + btnSubmit.classList.add('hide'); } - } - function showDownloadOptions(button, context, subtitleId) { - var items = []; + let editorContent = dlg.querySelector('.formDialogContent'); - items.push({ - name: globalize.translate('Download'), - id: 'download' + dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); + dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); + + apiClient.getCultures().then(function (languages) { + fillLanguages(editorContent, apiClient, languages); }); - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: items, - positionTo: button - - }).then(function (id) { - switch (id) { - case 'download': - downloadRemoteSubtitles(context, subtitleId); - break; - default: - break; - } - }); + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); }); - } - - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } - - function showEditorInternal(itemId, serverId, template) { - hasChanges = false; - - var apiClient = connectionManager.getApiClient(serverId); - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('subtitleEditorDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); - - dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); - - var btnSubmit = dlg.querySelector('.btnSubmit'); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); - } else { - btnSubmit.classList.add('hide'); - } - - var editorContent = dlg.querySelector('.formDialogContent'); - - dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); - dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); - - apiClient.getCultures().then(function (languages) { - fillLanguages(editorContent, apiClient, languages); - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - return new Promise(function (resolve, reject) { - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (hasChanges) { - resolve(); - } else { - reject(); - } - }); - - dialogHelper.open(dlg); - - reload(editorContent, apiClient, item); - }); - }); - } - - function showEditor(itemId, serverId) { - loading.show(); return new Promise(function (resolve, reject) { - require(['text!./subtitleeditor.template.html'], function (template) { - showEditorInternal(itemId, serverId, template).then(resolve, reject); - }); - }); - } + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } - return { - show: showEditor - }; -}); + if (hasChanges) { + resolve(); + } else { + reject(); + } + }); + + dialogHelper.open(dlg); + + reload(editorContent, apiClient, item); + }); + }); +} + +function showEditor(itemId, serverId) { + loading.show(); + + return new Promise(function (resolve, reject) { + import('text!./subtitleeditor.template.html').then(({default: template}) => { + showEditorInternal(itemId, serverId, template).then(resolve, reject); + }); + }); +} + +export default { + show: showEditor +}; From cdb07e94436052d3d3fcc7b7f61f9160a935a93d Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 09:03:30 +0100 Subject: [PATCH 29/58] Migration of subtitlesync to ES6 module --- package.json | 1 + src/components/subtitlesync/subtitlesync.js | 219 ++++++++++---------- 2 files changed, 111 insertions(+), 109 deletions(-) diff --git a/package.json b/package.json index a7dcc4311..6409981f9 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "src/components/settingshelper.js", "src/components/shortcuts.js", "src/components/subtitleeditor/subtitleeditor.js", + "src/components/subtitlesync/subtitlesync.js", "src/components/subtitlesettings/subtitleappearancehelper.js", "src/components/subtitlesettings/subtitlesettings.js", "src/components/syncPlay/groupSelectionMenu.js", diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index 203d88535..efb2087a1 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -1,147 +1,148 @@ -define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, layoutManager, template, css) { - 'use strict'; +import playbackManager from 'playbackManager'; +import layoutManager from 'layoutManager'; +import template from 'text!./subtitlesync.template.html'; +import 'css!./subtitlesync'; - playbackManager = playbackManager.default || playbackManager; +let player; +let subtitleSyncSlider; +let subtitleSyncTextField; +let subtitleSyncCloseButton; +let subtitleSyncContainer; - var player; - var subtitleSyncSlider; - var subtitleSyncTextField; - var subtitleSyncCloseButton; - var subtitleSyncContainer; +function init(instance) { + const parent = document.createElement('div'); + document.body.appendChild(parent); + parent.innerHTML = template; - function init(instance) { - var parent = document.createElement('div'); - document.body.appendChild(parent); - parent.innerHTML = template; + subtitleSyncSlider = parent.querySelector('.subtitleSyncSlider'); + subtitleSyncTextField = parent.querySelector('.subtitleSyncTextField'); + subtitleSyncCloseButton = parent.querySelector('.subtitleSync-closeButton'); + subtitleSyncContainer = parent.querySelector('.subtitleSyncContainer'); - subtitleSyncSlider = parent.querySelector('.subtitleSyncSlider'); - subtitleSyncTextField = parent.querySelector('.subtitleSyncTextField'); - subtitleSyncCloseButton = parent.querySelector('.subtitleSync-closeButton'); - subtitleSyncContainer = parent.querySelector('.subtitleSyncContainer'); + if (layoutManager.tv) { + subtitleSyncSlider.classList.add('focusable'); + // HACK: Delay to give time for registered element attach (Firefox) + setTimeout(function () { + subtitleSyncSlider.enableKeyboardDragging(); + }, 0); + } - if (layoutManager.tv) { - subtitleSyncSlider.classList.add('focusable'); - // HACK: Delay to give time for registered element attach (Firefox) - setTimeout(function () { - subtitleSyncSlider.enableKeyboardDragging(); - }, 0); - } + subtitleSyncContainer.classList.add('hide'); - subtitleSyncContainer.classList.add('hide'); + subtitleSyncTextField.updateOffset = function (offset) { + this.textContent = offset + 's'; + }; - subtitleSyncTextField.updateOffset = function(offset) { - this.textContent = offset + 's'; - }; + subtitleSyncTextField.addEventListener('click', function () { + // keep focus to prevent fade with osd + this.hasFocus = true; + }); - subtitleSyncTextField.addEventListener('click', function () { + subtitleSyncTextField.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + // if input key is enter search for float pattern + let inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); + if (inputOffset) { + inputOffset = inputOffset[0]; + + // replace current text by considered offset + this.textContent = inputOffset + 's'; + + inputOffset = parseFloat(inputOffset); + // set new offset + playbackManager.setSubtitleOffset(inputOffset, player); + // synchronize with slider value + subtitleSyncSlider.updateOffset( + getPercentageFromOffset(inputOffset)); + } else { + this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; + } + this.hasFocus = false; + event.preventDefault(); + } else { // keep focus to prevent fade with osd this.hasFocus = true; - }); - - subtitleSyncTextField.addEventListener('keydown', function(event) { - if (event.key === 'Enter') { - // if input key is enter search for float pattern - var inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); - if (inputOffset) { - inputOffset = inputOffset[0]; - - // replace current text by considered offset - this.textContent = inputOffset + 's'; - - inputOffset = parseFloat(inputOffset); - // set new offset - playbackManager.setSubtitleOffset(inputOffset, player); - // synchronize with slider value - subtitleSyncSlider.updateOffset( - getPercentageFromOffset(inputOffset)); - } else { - this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; - } - this.hasFocus = false; + if (event.key.match(/[+-\d.s]/) === null) { event.preventDefault(); - } else { - // keep focus to prevent fade with osd - this.hasFocus = true; - if (event.key.match(/[+-\d.s]/) === null) { - event.preventDefault(); - } } + } - // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable - event.stopPropagation(); - }); + // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable + event.stopPropagation(); + }); - subtitleSyncTextField.blur = function() { - // prevent textfield to blur while element has focus - if (!this.hasFocus && this.prototype) { - this.prototype.blur(); - } - }; + subtitleSyncTextField.blur = function () { + // prevent textfield to blur while element has focus + if (!this.hasFocus && this.prototype) { + this.prototype.blur(); + } + }; - subtitleSyncSlider.updateOffset = function(percent) { - // default value is 0s = 50% - this.value = percent === undefined ? 50 : percent; - }; + subtitleSyncSlider.updateOffset = function (percent) { + // default value is 0s = 50% + this.value = percent === undefined ? 50 : percent; + }; - subtitleSyncSlider.addEventListener('change', function () { - // set new offset - playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); - // synchronize with textField value - subtitleSyncTextField.updateOffset( - getOffsetFromPercentage(this.value)); - }); + subtitleSyncSlider.addEventListener('change', function () { + // set new offset + playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); + // synchronize with textField value + subtitleSyncTextField.updateOffset( + getOffsetFromPercentage(this.value)); + }); - subtitleSyncSlider.getBubbleHtml = function (value) { - var newOffset = getOffsetFromPercentage(value); - return '

' + + subtitleSyncSlider.getBubbleHtml = function (value) { + const newOffset = getOffsetFromPercentage(value); + return '

' + (newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's' + '

'; - }; + }; - subtitleSyncCloseButton.addEventListener('click', function() { - playbackManager.disableShowingSubtitleOffset(player); - SubtitleSync.prototype.toggle('forceToHide'); - }); + subtitleSyncCloseButton.addEventListener('click', function () { + playbackManager.disableShowingSubtitleOffset(player); + SubtitleSync.prototype.toggle('forceToHide'); + }); - instance.element = parent; - } + instance.element = parent; +} - function getOffsetFromPercentage(value) { - // convert percent to fraction - var offset = (value - 50) / 50; - // multiply by offset min/max range value (-x to +x) : - offset *= 30; - return offset.toFixed(1); - } +function getOffsetFromPercentage(value) { + // convert percent to fraction + let offset = (value - 50) / 50; + // multiply by offset min/max range value (-x to +x) : + offset *= 30; + return offset.toFixed(1); +} - function getPercentageFromOffset(value) { - // divide by offset min/max range value (-x to +x) : - var percentValue = value / 30; - // convert fraction to percent - percentValue *= 50; - percentValue += 50; - return Math.min(100, Math.max(0, percentValue.toFixed())); - } +function getPercentageFromOffset(value) { + // divide by offset min/max range value (-x to +x) : + let percentValue = value / 30; + // convert fraction to percent + percentValue *= 50; + percentValue += 50; + return Math.min(100, Math.max(0, percentValue.toFixed())); +} - function SubtitleSync(currentPlayer) { +class SubtitleSync { + constructor(currentPlayer) { player = currentPlayer; init(this); } - SubtitleSync.prototype.destroy = function() { + destroy() { SubtitleSync.prototype.toggle('forceToHide'); if (player) { playbackManager.disableShowingSubtitleOffset(player); playbackManager.setSubtitleOffset(0, player); } - var elem = this.element; + const elem = this.element; if (elem) { elem.parentNode.removeChild(elem); this.element = null; } - }; + } - SubtitleSync.prototype.toggle = function(action) { + toggle(action) { if (player && playbackManager.supportSubtitleOffset(player)) { /* eslint-disable no-fallthrough */ switch (action) { @@ -170,7 +171,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', } /* eslint-enable no-fallthrough */ } - }; + } +} - return SubtitleSync; -}); +export default SubtitleSync; From 3680f478457f10acf2ee94d8c23c60b395b9a86e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 09:46:23 +0100 Subject: [PATCH 30/58] Remove duplicates from package.json --- package.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/package.json b/package.json index ad4c8e110..2ac1702d5 100644 --- a/package.json +++ b/package.json @@ -191,12 +191,6 @@ "src/controllers/music/musicplaylists.js", "src/controllers/music/musicrecommended.js", "src/controllers/music/songs.js", - "src/controllers/dashboard/plugins/repositories.js", - "src/controllers/movies/moviecollections.js", - "src/controllers/movies/moviegenres.js", - "src/controllers/movies/movies.js", - "src/controllers/movies/moviesrecommended.js", - "src/controllers/movies/movietrailers.js", "src/controllers/dashboard/mediaLibrary.js", "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", @@ -218,7 +212,6 @@ "src/controllers/edititemmetadata.js", "src/controllers/favorites.js", "src/controllers/hometab.js", - "src/controllers/dashboard/plugins/repositories.js", "src/controllers/movies/moviecollections.js", "src/controllers/movies/moviegenres.js", "src/controllers/movies/movies.js", From d61fa6bdf654dbe68ae1b14d11341e09a0cab65e Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 12:20:42 +0000 Subject: [PATCH 31/58] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index c61bfe9e1..7c27d407c 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -800,5 +800,6 @@ "LabelForgotPasswordUsernameHelp": "הכנס/י את שם המשתמש שלך, אם את/ה זוכר/ת אותו.", "LabelFont": "גופן:", "LabelFolder": "תיקייה:", - "LabelFileOrUrl": "קובץ או כתובת אינטרנט:" + "LabelFileOrUrl": "קובץ או כתובת אינטרנט:", + "Season": "עונה" } From 3204eefc5c8f9cf0dbe2bace960f8f6ef50a57cd Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 12:26:20 +0000 Subject: [PATCH 32/58] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 7c27d407c..838c1c2ac 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -157,7 +157,7 @@ "LabelBirthYear": "שנת לידה:", "LabelBlastMessageInterval": "תדירות הודעות דחיפה", "LabelBlastMessageIntervalHelp": "מגדיר את משך הזמן בשניות בין הודעות דחיפה של השרת.", - "LabelCachePath": "נתיב cache:", + "LabelCachePath": "נתיב מטמון:", "LabelChannels": "ערוצים:", "LabelCollection": "אוספים:", "LabelCommunityRating": "דירוג הקהילה:", @@ -204,7 +204,7 @@ "LabelMaxScreenshotsPerItem": "מספר תמונות מסך מקסימאלי לפריט:", "LabelMessageTitle": "כותרת הודעה:", "LabelMetadataDownloadLanguage": "שפת הורדה מועדפת:", - "LabelMetadataPath": "נתיב Metadata:", + "LabelMetadataPath": "נתיב מטא-דאטה:", "LabelMinBackdropDownloadWidth": "רוחב תמונת רקע מינימאלי להורדה:", "LabelMinResumeDuration": "משך המשכה מינימאלי:", "LabelMinResumeDurationHelp": "קובץ קצר מזה לא יהיה ניתן להמשך ניגון מנקודת העצירה", @@ -302,7 +302,7 @@ "OptionAlbumArtist": "אמן אלבום", "OptionAllUsers": "כל המשתמשים", "OptionAllowLinkSharing": "אפשר שיתוף ברשתות חברתיות", - "OptionAllowMediaPlayback": "הרשה נגינת מדיה", + "OptionAllowMediaPlayback": "אפשר ניגון מדיה", "OptionAllowUserToManageServer": "אפשר למשתמש זה לנהל את השרת", "OptionArtist": "אמן", "OptionAscending": "סדר עולה", @@ -419,7 +419,7 @@ "SeriesSettings": "הגדרות סדרה", "SeriesYearToPresent": "{0} - היום", "ServerNameIsRestarting": "שרת Jellyfin - {0} מופעל מחדש.", - "ServerNameIsShuttingDown": "שרת Jellyfin - {0} נכבה.", + "ServerNameIsShuttingDown": "שרת Jellyfin - {0} בתהליך כיבוי.", "ServerUpdateNeeded": "שרת אמבי זה צריך להיות מעודכן. כדי להוריד את הגרסה העדכנית ביותר, בקר בכתובת {0}", "Settings": "הגדרות", "SettingsSaved": "ההגדרות נשמרו.", @@ -801,5 +801,40 @@ "LabelFont": "גופן:", "LabelFolder": "תיקייה:", "LabelFileOrUrl": "קובץ או כתובת אינטרנט:", - "Season": "עונה" + "Season": "עונה", + "OptionEnableAccessFromAllDevices": "אפשר גישה מכל המכשירים", + "Primary": "ראשי", + "Menu": "תפריט", + "LiveTV": "שידורים חיים", + "ManageLibrary": "נהל ספרייה", + "Logo": "לוגו", + "OptionDateAddedImportTime": "השתמש בתאריך הסריקה לתוך הספרייה", + "OptionDateAddedFileTime": "השתמש בתאריך יצירת הקובץ", + "OptionBlockTrailers": "קדימונים", + "OptionBlockMusic": "מוזיקה", + "OptionBlockLiveTvChannels": "ערוצי שידורים חיים", + "OptionBlockBooks": "ספרים", + "OptionAllowRemoteSharedDevices": "אפשר שליטה מרחוק על מכשירים משותפים", + "OptionAllowRemoteControlOthers": "אפשר שליטה מרחוק על משתמשים אחרים", + "SelectAdminUsername": "נא לבחור שם משתמש עבור חשבון המנהל.", + "OptionHideUserFromLoginHelp": "שימושי עבור חשבונות פרטיים או חשבונות מנהל מוסתרים. המשתמש יצטרך להזין את שם המשתמש והסיסמה ידנית על מנת להתחבר.", + "MessagePlayAccessRestricted": "התוכן הזה לא ניתן לניגון כרגע. למידע נוסף, נא ליצור קשר עם מנהל המערכת שלך.", + "MessageContactAdminToResetPassword": "נא ליצור קשר עם מנהל המערכת שלך על מנת לאפס את הסיסמה שלך.", + "HeaderAdmin": "מנהל", + "TabDisplay": "תצוגה", + "HeaderDisplay": "תצוגה", + "Suggestions": "המלצות", + "MessageSyncPlayNoGroupsAvailable": "אין קבוצות זמינות. התחל לנגן משהו קודם.", + "OptionHomeVideos": "תמונות", + "Home": "בית", + "LabelServerName": "שם השרת:", + "TabPlugins": "תוספים", + "MessageNoPluginsInstalled": "אין לך תוספים מותקנים.", + "MessageNoAvailablePlugins": "אין תוספים זמינים.", + "TabLogs": "יומני רישום", + "LabelLogs": "יומני רישום:", + "TabNetworking": "תקשורת", + "TabDVR": "ממיר-מקליט", + "HeaderDVR": "ממיר-מקליט", + "LabelScheduledTaskLastRan": "רץ לאחרונה {0}, במשך {1}." } From cc02cb01cf6df9fedcf381c0b03c2c3f6f275334 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 12:52:34 +0000 Subject: [PATCH 33/58] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 47 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 838c1c2ac..58eb6ee1b 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -314,7 +314,7 @@ "OptionContinuing": "ממשיך", "OptionCriticRating": "ציון מבקרים", "OptionCustomUsers": "מותאם אישית", - "OptionDaily": "יומי", + "OptionDaily": "כל יום", "OptionDateAdded": "תאריך הוספה", "OptionDatePlayed": "תאריך ניגון", "OptionDescending": "סדר יורד", @@ -337,7 +337,7 @@ "OptionHasSubtitles": "כתוביות", "OptionHasThemeSong": "שיר נושא", "OptionHasThemeVideo": "סרט נושא", - "OptionHasTrailer": "טריילר", + "OptionHasTrailer": "קדימון", "OptionHideUser": "הסתר משתמש זה בחלון ההתחברות", "OptionImdbRating": "דירוג IMDb", "OptionLikes": "נבחרים", @@ -348,27 +348,27 @@ "OptionOnAppStartup": "בהפעלת התוכנה", "OptionOnInterval": "כל פרק זמן", "OptionParentalRating": "דירוג בקרת הורים", - "OptionPlayCount": "מספר השמעות", + "OptionPlayCount": "כמות ניגונים", "OptionPlayed": "נוגן", - "OptionPremiereDate": "תאריך שידור ראשון", + "OptionPremiereDate": "תאריך בכורה", "OptionProfileAudio": "צליל", "OptionProfilePhoto": "תמונה", "OptionProfileVideo": "וידאו", "OptionProfileVideoAudio": "צליל וידאו", "OptionResumable": "ניתן להמשיך", - "OptionRuntime": "משך", + "OptionRuntime": "זמן ריצה", "OptionSaturday": "שבת", "OptionSpecialEpisode": "ספיישלים", "OptionSunday": "ראשון", "OptionThursday": "חמישי", - "OptionTrackName": "שם השיר", + "OptionTrackName": "שם הרצועה", "OptionTuesday": "שלישי", "OptionTvdbRating": "דירוג TVDB", "OptionUnairedEpisode": "פרקים שלא שודרו", "OptionUnplayed": "לא נוגן", "OptionWakeFromSleep": "הער ממצב שינה", "OptionWednesday": "רביעי", - "OptionWeekly": "שבועי", + "OptionWeekly": "כל שבוע", "OriginalAirDateValue": "תאריך אוויר מקורי: {0}", "Overview": "סקירה כללית", "PackageInstallCancelled": "ההתקנה של {0} (גירסה {1}) בוטלה.", @@ -836,5 +836,36 @@ "TabNetworking": "תקשורת", "TabDVR": "ממיר-מקליט", "HeaderDVR": "ממיר-מקליט", - "LabelScheduledTaskLastRan": "רץ לאחרונה {0}, במשך {1}." + "LabelScheduledTaskLastRan": "רץ לאחרונה {0}, במשך {1}.", + "LabelTheme": "ערכת נושא:", + "LabelTextSize": "גודל טקסט:", + "LabelTextColor": "צבע טקסט:", + "LabelSyncPlayAccessNone": "מבוטל עבור משתמש זה", + "LabelSyncPlayAccessJoinGroups": "אפשר למשתמש להצטרף לקבוצות", + "LabelSyncPlayAccessCreateAndJoinGroups": "אפשר למשתמש ליצור קבוצות ולהצטרף אליהן", + "LabelSyncPlayLeaveGroup": "עזוב קבוצה", + "LabelSyncPlayNewGroupDescription": "צור קבוצה חדשה", + "LabelSyncPlayNewGroup": "קבוצה חדשה", + "MoreFromValue": "עוד מ{0}", + "Writers": "תסריטאים", + "DailyAt": "כל יום ב-{0}", + "OptionWeekends": "סופי שבוע", + "OptionWeekdays": "ימי חול", + "Unplayed": "לא נוגן", + "OptionSubstring": "מחרוזת משנה", + "OptionReleaseDate": "תאריך שחרור", + "OptionRegex": "ביטוי-רגולרי", + "OptionRandom": "אקראי", + "OptionPoster": "פוסטר", + "OptionNone": "כלום", + "OptionMax": "מקסימום", + "List": "רשימה", + "OptionList": "רשימה", + "OptionIsSD": "הבחנה רגילה (SD)", + "OptionIsHD": "הבחנה גבוהה (HD)", + "OptionExternallyDownloaded": "הורדה חיצונית", + "OptionEveryday": "כל יום", + "OptionEnableExternalContentInSuggestions": "הפעל תוכן חיצוני בהמלצות", + "OptionEnableAccessToAllLibraries": "אפשר גישה לכל הספריות", + "OptionEnableAccessToAllChannels": "אפשר גישה לכל הערוצים" } From 88992fd5e73f619834a34003c35be991ed3d1cf8 Mon Sep 17 00:00:00 2001 From: Verkhaliak Anton Date: Thu, 6 Aug 2020 14:19:23 +0000 Subject: [PATCH 34/58] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ru/ --- src/strings/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/ru.json b/src/strings/ru.json index 24d5cdd90..199d0f24a 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -1521,7 +1521,7 @@ "EnableBlurHashHelp": "Рисунки, которые всё ещё загружаются, будут отображаться с размытым заполнением", "EnableBlurHash": "Включить размытые заполнители для изображений", "ButtonSyncPlay": "SyncPlay", - "ButtonCast": "В ролях", + "ButtonCast": "Транслировать", "TabRepositories": "Репозитории", "MessageNoGenresAvailable": "Разрешить поставщикам метаданных получать жанры из интернета.", "MessageAddRepository": "Если вы хотите добавить репозиторий, нажмите кнопку рядом с заголовком и заполните необходимую информацию.", From 7a7e4857658ca35ab61cd6683da159c6ef8c7701 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 6 Aug 2020 15:20:55 +0000 Subject: [PATCH 35/58] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ar/ --- src/strings/ar.json | 118 ++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 36 deletions(-) diff --git a/src/strings/ar.json b/src/strings/ar.json index a9d3d2de4..eb7d6f424 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -99,9 +99,9 @@ "DeleteUserConfirmation": "هل انت متاكد من انك تريد حذف هذا المستخدم ؟", "DeviceAccessHelp": "هذه الميزة تنطبق حصرياً على الأجهزة التي يمكن التعرف عليها فردياً ولن تمنع المتصفح من الدخول عليها. ترشيح الوصول لأجهزة المستخدم ستمنع المستخدمين من استعمال الأجهزة الجديدة إلى أن يتم اعتمادهم من هنا.", "DrmChannelsNotImported": "القنوات المجهزة بإدارة الحقوق الرقمية DRM لن تورّد.", - "EasyPasswordHelp": "الرمز الشخصي الميسرالخاص بك يمكنك من الاتصال إلى خادم مكتبتك، عبر تطبيقات أمبي على الأجهزة أو الدخول على حسابك في الشبكة الداخلية.", + "EasyPasswordHelp": "الرمز pin الميسر الخاص بك يستخدم للوصول بدون اتصل عبر التطبيقات المدعومة أو الدخول على حسابك في الشبكة الداخلية.", "EnablePhotos": "عرض الصور", - "EnablePhotosHelp": "سيتم اكتشاف الصور وعرضها مع ملفات الوسائط الأخرى", + "EnablePhotosHelp": "سيتم اكتشاف الصور وعرضها مع ملفات الوسائط الأخرى.", "ErrorAddingListingsToSchedulesDirect": "كان هناك خطأ في إضافة الاصطفاف لخدمة \"Schedules Direct\" الخاصة بك. خدمة \"Schedules Direct\" لا تسمح إلا بعدد محدود من الاصطفافات لكل حساب. قد تحتاج إلى تسجيل الدخول إلى موقع \"Schedules Direct\" لإزالة الاصطفافات الأخرى من حسابك قبل المتابعة.", "ErrorAddingMediaPathToVirtualFolder": "كان هناك خطأ في إضافة مسار الوسائط. الرجاء التأكد من صحة المسار وأن خادم أمبي لديه صلاحية الوصول إلى الموقع.", "ErrorAddingTunerDevice": "كان هناك خطأ في إضافة جهاز المولف. الرجاء التأكد من صلاحية الوصول إليه ثم عاود المحاولة.", @@ -111,7 +111,7 @@ "ErrorPleaseSelectLineup": "الرجاء اختيار اصطفاف ثم المحاولة مرة أخرى. إن لم تتوفر أية اصطفافات، فالرجاء التأكد من اسم المستخدم وكلمة المرور الخاصة بك، وتأكد من صحة رمزك البريدي.", "ErrorSavingTvProvider": "كان هناك خطأ في حفظ مزود التلفزة. الرجاء التأكد من صلاحية الوصول إليه ثم عاود المحاولة.", "ExitFullscreen": "الخروج من الشاشة الكاملة", - "ExtractChapterImagesHelp": "استخلاص صور الأبواب سيسمح لتطبيقات أمبي أن تظهر لك قوائم تصويرية لتبويبات الأفلام. هذه العملية قد تكون بطيئة، وتستغل قدرة المعالج بشكل ملحوظ، وقد تحتاج إلى حيازة بضعة غيغابايتات من مساحة التخزين بشكل مؤقت. هذه المهمة تعمل خلال عملية استكشاف المقاطع المرئية، كما يمكن أن تحدد لتكون مهمة ليلية مجدولة. يمكنك جدولة العملية من قسم جدولة المهام. لا ينصح بتشغيل هذه المهمة خلال ساعات الذروة من دخول المستخدمين.", + "ExtractChapterImagesHelp": "استخلاص صور الفصول سيسمح للتطبيقات أن تظهر لك قوائم تصويرية لتبويبات الأفلام. هذه العملية قد تكون بطيئة، وتستغل موارد الجهاز بشكل ملحوظ، وقد تحتاج إلى حيازة بضعة غيغابايتات من مساحة التخزين بشكل مؤقت. هذه المهمة تعمل خلال عملية استكشاف المقاطع المرئية، كما يمكن أن تحدد لتكون مهمة ليلية مجدولة. يمكنك جدولة العملية من قسم جدولة المهام. لا ينصح بتشغيل هذه المهمة خلال ساعات الذروة من دخول المستخدمين.", "FFmpegSavePathNotFound": "لم نستطع تحديد موقع ffmpeg باستخدام المسار الذي أدخلته. سوف نحتاج تطبيق FFprobe أيضاً ويجب أن يتواجد في نفس المكان. إن هذه الأجزاء تكون بالعادة محزومة معاً في نفس ملف الإنزال. الرجاء التأكد من المسار المدخل والمحاولة مرة أخرى.", "FastForward": "التقديم السريع", "FileNotFound": "الملف غير موجود.", @@ -129,7 +129,7 @@ "GuideProviderSelectListings": "إختر المبوبات", "H264CrfHelp": "معامل المعدل الثابت CRF هو الجودة الافتراضية لإعدادات مشفر x264. بإمكانك إعطاء قيمة تتراوح بين 0 و 51، وكلما قلت القيمة فسينتج عن ذلك جودة أفضل (على حساب حجم تخزين أعلى). القيم المعقول تتراوح بين 18 و 28. الافتراضي لـ x264 هي 23، لذا فبإمكانك استخدام هذه القيمة كنقطة بداية.", "EncoderPresetHelp": "اختر قيمة أعلى لتحسين السرة والأداء وقيمة أقل لتحسين الجودة.", - "HardwareAccelerationWarning": "تمكين التسريع بعتاد الحاسوب قد يتسبب في عدم استقرار بعض أنواع الأنظمة. تأكد من أن نظام التشغيل الخاص بك محدث إلى آخر نسخة وأن سواقات الفيديو محدثة أيضاً. إذا واجهت أية صعوبات في تسغيل الفيديو بعد تمكين هذه الخاصية، فعليك إرجاع الإعداد إلى وضعية آلي.", + "HardwareAccelerationWarning": "تمكين التسريع بعتاد الحاسوب قد يتسبب في عدم استقرار بعض أنواع الأنظمة. تأكد من أن نظام التشغيل الخاص بك محدث إلى آخر نسخة وأن سواقات الفيديو محدثة أيضاً. إذا واجهت أية صعوبات في تسغيل الفيديو بعد تمكين هذه الخاصية، فعليك إرجاع الإعداد إلى وضعية بلا None.", "HeaderAccessSchedule": "جدول الدخولات", "HeaderAccessScheduleHelp": "إنشئ جدول دخولات لكي تتمكن من تحديد ساعات للدخول.", "HeaderActiveDevices": "الأجهزة المفعّلة", @@ -145,7 +145,7 @@ "HeaderAllowMediaDeletionFrom": "السماح بحذف الوسائط من قبل", "HeaderApiKey": "مفتاح API", "HeaderApiKeys": "مفاتيح API", - "HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح api لكي تتصل بخادم أمبي. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بحساب أمبي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.", + "HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح api لكي تتصل بالخادم. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بمستخدم عادي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.", "HeaderApp": "التطبيق", "HeaderAudioSettings": "إعدادات الصوت", "HeaderBooks": "الكتب", @@ -232,7 +232,7 @@ "HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات", "HeaderProfile": "الحساب", "HeaderProfileInformation": "معلومات العريضة", - "HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل خادم أمبي في الجهاز", + "HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل الخادم في للعملاء.", "HeaderRecentlyPlayed": "تم تشغيله مؤخراً", "HeaderRecordingPostProcessing": "تطبيق ما-بعد-المعالجة للتسجيل", "HeaderRemoteControl": "التحكم عن بعد", @@ -254,7 +254,7 @@ "HeaderSelectServerCachePath": "إختر مسار كاشة الخادم", "HeaderSelectServerCachePathHelp": "تصفح أو أدخل المسار الذي ترغب أن يُستخدم كاشة لملفات الخادم. يجب أن يكون هذا المجلد قابل للكتابة فيه.", "HeaderSelectTranscodingPath": "إختر المسار المؤقت للتشفير البيني", - "HeaderSelectTranscodingPathHelp": "تصفح أو أدخل المسار الذي ترغب أن يُستخدم للملفات المؤقتة للتشفير البيني. يجب أن يكون هذا المجلد قابل للكتابة فيه.", + "HeaderSelectTranscodingPathHelp": "تصفح أو أدخل المسار الذي ترغب أن يُستخدم لملفات التشفير البيني. يجب أن يكون هذا المجلد قابل للكتابة فيه.", "HeaderSendMessage": "أرسل رسالة", "HeaderSeries": "المسلسلات", "HeaderServerSettings": "إعدادات الخادم", @@ -291,17 +291,17 @@ "HeaderXmlSettings": "إعدادات xml", "HeaderYears": "السنوات", "HeadersFolders": "مجلدات", - "ImportFavoriteChannelsHelp": "عند التفعيل، فقط القنوات التي علّمت في المفضلة على هذا المولف ستورد إلى النظام.", - "ImportMissingEpisodesHelp": "عند التمكين، المعلومات الناقصة للحلقات ستورّد إلى قاعدة بيانات أمبي وستعرض داخل المواسم والمسلسلات. قد تتسبب هذه بأوقات أطول بكثير عند تمشيط المكنبات.", + "ImportFavoriteChannelsHelp": "فقط القنوات التي علّمت في المفضلة على جهاز المولف ستورد.", + "ImportMissingEpisodesHelp": "المعلومات الناقصة للحلقات ستورّد إلى قاعدة بياناتك وستعرض داخل المواسم والمسلسلات. قد تتسبب هذه بأوقات أطول بكثير عند تمشيط المكتبات.", "LabelAbortedByServerShutdown": "(تم إهماله بسبب عملية إغلاق الخادم)", "LabelAccessDay": "يوم الأسبوع:", - "LabelAccessEnd": "تاريخ النهاية", - "LabelAccessStart": "تاريخ البداية", + "LabelAccessEnd": "وقت النهاية:", + "LabelAccessStart": "وقت البداية:", "LabelAirDays": "أيام البث:", "LabelAirTime": "وقت البث:", - "LabelAlbum": "الألبوم", + "LabelAlbum": "الألبوم:", "LabelAlbumArtHelp": "PN المستخدمة في رسومات الألبوم، داخل سمة dlna:profileID في upnp:albumArtURI. بعض الأجهزة تحتاج قيمة محددة، مهما كان حجم الصورة.", - "LabelAlbumArtMaxHeight": "الارتفاع الأقصى لرسومات الألبوم", + "LabelAlbumArtMaxHeight": "الارتفاع الأقصى لرسومات الألبوم:", "LabelAlbumArtMaxHeightHelp": "الدقة القصوى لرسومات الألبوم المظهّرة عبر سمة upnp:albumArtURI.", "LabelAlbumArtMaxWidth": "العرض الأقصى لرسوم الألبوم:", "LabelAlbumArtMaxWidthHelp": "الدقة القصوى لرسومات الألبوم المظهّرة عبر سمة upnp:albumArtURI.", @@ -310,42 +310,42 @@ "LabelAll": "الجميع", "LabelAllowHWTranscoding": "السماح بالتشفير البيني بعتاد الحاسب", "LabelAppName": "اسم التطبيق", - "LabelAppNameExample": "مثال: Sickbeard، NzbDrone", + "LabelAppNameExample": "مثال: Sickbeard، Sonarr", "LabelArtists": "الفنانون:", - "LabelArtistsHelp": "فصل الاستعمالات المتعددة ;", + "LabelArtistsHelp": "افصل بين الفنانين ب ; فاصلة منقوطة.", "LabelAudioLanguagePreference": "اللغة المفضلة للصوت:", "LabelBindToLocalNetworkAddress": "إربطه إلى عنوان شبكة محلي:", - "LabelBindToLocalNetworkAddressHelp": "هذا خياري. امتطي عنوان الآي بي المحلي لربطه بخادم http. إذا ترك فارغاً، فإن الخادم سيربطه بجميع العناوين المتاحة. تغيير هذه القيمة يتطلب إعادة تشغيل خادم أمبي.", - "LabelBlastMessageInterval": "فترات بث رسالة قيد التشغيل (بالثواني)", - "LabelBlastMessageIntervalHelp": "يحدد الفترة بالثواني بين يث رسائل قيد التشغيل", - "LabelCache": "ذاكرة الكاشة", - "LabelCachePath": "مسار ذاكرة الكاشة:", - "LabelCachePathHelp": "حدد موقع مخصص لملفات كاشة الخادم، مثل الصور وغيرها. أترك هذه الخانة فارغة لاستعمال القيمة التلقائية.", + "LabelBindToLocalNetworkAddressHelp": "تجاوز عنوان الآي بي المحلي لربطه بخادم http. إذا ترك فارغاً، فإن الخادم سيربطه بجميع العناوين المتاحة. تغيير هذه القيمة يتطلب إعادة تشغيل خادم جيلليفن.", + "LabelBlastMessageInterval": "فترات بث رسالة قيد التشغيل", + "LabelBlastMessageIntervalHelp": "يحدد الفترة بالثواني بين بث رسائل قيد التشغيل.", + "LabelCache": "مَخبأ (كاش):", + "LabelCachePath": "مسار ذاكرة الكاش:", + "LabelCachePathHelp": "حدد موقع مخصص لملفات الخادم المؤقتة، مثل الصور وغيرها. أترك هذه الخانة فارغة لاستعمال القيمة الافتراضية.", "LabelCancelled": "تم الإلغاء", - "LabelCollection": "المجموعة", + "LabelCollection": "المجموعة:", "LabelCommunityRating": "تقييم المجتمع:", - "LabelContentType": "نوع المحتوى", + "LabelContentType": "نوع المحتوى:", "LabelCountry": "البلد:", "LabelCurrentPassword": "كلمة السر الحالية:", - "LabelCustomCertificatePath": "مسار شهادة ssl مخصص:", + "LabelCustomCertificatePath": "مسار شهادة SSL المخصص:", "LabelCustomCertificatePathHelp": "مسار ملف PKCS # 12 يحتوي على شهادة ومفتاح خاص لتمكين دعم TLS على مجال مخصص.", - "LabelCustomCss": "تنيسق CSS مخصوص:", - "LabelCustomCssHelp": "طبق تنسيق css مخصوصة لواجهة الويب.", + "LabelCustomCss": "تنيسق CSS مخصص:", + "LabelCustomCssHelp": "طبق تنسيقك css المخصص لواجهة الويب.", "LabelCustomDeviceDisplayName": "اسم العرض:", - "LabelCustomDeviceDisplayNameHelp": "أذكر اسم عرض مخصوص أو أتركه فارغاً لاستخدام", + "LabelCustomDeviceDisplayNameHelp": "أذكر اسم عرض مخصوص أو أتركه فارغاً لاستخدام الاسم المبلغ من الجهاز.", "LabelDateAddedBehavior": "كيف يتصرف المحتوى الجديد نحو \"تاريخ الإضافة\" الخاص به:", - "LabelDateAddedBehaviorHelp": "إذا استعرضت قيمة واصفات البيانا فإنها سوف تستخدم قبل أن تستخدم أي من هذه الخيارات.", + "LabelDateAddedBehaviorHelp": "إذا اخذت واصفات البيانات قيمة، فإنها سوف تستخدم قبل أن تستخدم أي من هذه الخيارات.", "LabelDay": "اليوم:", "LabelDeathDate": "تاريخ الوفاة:", - "LabelDefaultUser": "المستخدم الافتراضي", + "LabelDefaultUser": "المستخدم الافتراضي:", "LabelDefaultUserHelp": "لتحديد مكتبة المستخدم التي تظهر على الأجهزة المتصلة. بإمكان الامتطاء على هذه القيمة لكل جهاز عن طريق عرائض الأجهزة.", "LabelDeviceDescription": "وصف الجهاز", - "LabelDidlMode": "طور didl:", + "LabelDidlMode": "طور DIDL:", "LabelDisplayMissingEpisodesWithinSeasons": "أظهر الحلقات المفقودة في مجلدات المواسم", "LabelDisplayName": "الاسم المعروض:", "LabelDisplaySpecialsWithinSeasons": "أظهر الحلقات الخاصة في المواسم التي بثت فيها", "LabelDownMixAudioScale": "تعزيز الصوت عند تقليل توزيع قنوات الصوت:", - "LabelDownMixAudioScaleHelp": "تعزيز الصوت عند تقليل توزيع قنوات الصوت. حدد القيمة بـ 1 للمحافظة على القيمة الأصلية للصوت.", + "LabelDownMixAudioScaleHelp": "تعزيز الصوت عند تقليل توزيع قنوات الصوت. حدد القيمة ب 1 للمحافظة على القيمة الأصلية للصوت.", "LabelDownloadLanguages": "إنزال اللغة:", "LabelDynamicExternalId": "معرفة {0}:", "LabelEasyPinCode": "الرمز الشخصي الميسر:", @@ -354,8 +354,8 @@ "LabelEnableAutomaticPortMap": "فعل الخاصية الآلية في التوفيق بين المنافذ", "LabelEnableAutomaticPortMapHelp": "حاول التوفيق بين المنفذ العالمي والمنفذ المحلي آلياً باستخدام آلية UPnP. هذه الخاصية قد لا تعمل مع بعض أنواع الراوترات.", "LabelEnableBlastAliveMessages": "بث رسائل قيد التشغيل", - "LabelEnableBlastAliveMessagesHelp": "فعل هذه الخاصية إذا كان الخادم لا يكتشف بكفاءة من قبل أجهزة UPnP الأخرى على شبكتك", - "LabelEnableDlnaClientDiscoveryInterval": "فترات استكشاف العملاء (بالثواني)", + "LabelEnableBlastAliveMessagesHelp": "فعل هذه الخاصية إذا كان الخادم لا يكتشف بكفاءة من قبل أجهزة UPnP الأخرى على شبكتك.", + "LabelEnableDlnaClientDiscoveryInterval": "فترات استكشاف العملاء", "LabelEnableDlnaClientDiscoveryIntervalHelp": "يحدد الفترة بالثواني بين عمليات بحث SSDP التي يقوم بها أمبي.", "LabelEnableDlnaDebugLogging": "تفعيل خاصية كشوفات أخطاء DLNA", "LabelEnableDlnaDebugLoggingHelp": "هذه ستنشئ سجلات كشفية ضخمة ولا ينبغي تفعيلها إلا عند الحاجة إليها بغرض استكشاف الأخطاء وحصرها.", @@ -930,7 +930,7 @@ "Backdrops": "خلفيات متغيرة للصفحة", "Backdrop": "خلفية متغيرة للصفحة", "Auto": "تلقائي", - "AuthProviderHelp": "حدد مقدم المصادقات ليتم استخدامه لمصادقة كلمة مرور هذا المستخدم.", + "AuthProviderHelp": "اختار مقدم المصادقة ليتم استخدامه لمصادقة كلمة مرور هذا المستخدم.", "AroundTime": "حول", "AttributeNew": "جديد", "AspectRatio": "نسبة العرض الى الارتفاع", @@ -1038,7 +1038,7 @@ "Director": "المخرج", "DirectPlaying": "بث بدون تحويل الصيغة", "DirectStreaming": "البث المباشر", - "DirectStreamHelp2": "البث المباشر للملف يستخدم طاقة معالجة قليلة جدًا دون أي خسارة في جودة الفيديو.", + "DirectStreamHelp2": "البث المباشر للملف يستخدم قوة معالجة قليلة جدًا دون أي خسارة في جودة الفيديو.", "DirectStreamHelp1": "الوسائط متوافقة مع الجهاز فيما يتعلق بالدقة ونوع الوسائط (H.264 ، AC3 ، إلخ) ، ولكنها في حاوية ملفات غير متوافقة (mkv ، avi ، wmv ، إلخ). سيتم إعادة حزم الفيديو في الوقت الحقيقي قبل بثه إلى الجهاز.", "DetectingDevices": "يتم الكشف عن الأجهزة", "Desktop": "سطح المكتب", @@ -1139,5 +1139,51 @@ "Dislike": "لم يعجبنى", "ButtonSyncPlay": "SyncPlay", "ExtraLarge": "كبير جدا", - "EnableNextVideoInfoOverlayHelp": "في نهاية الفيديو, عرض معلومات عن الفيديو القادم في قائمة التشغيل." + "EnableNextVideoInfoOverlayHelp": "في نهاية الفيديو, عرض معلومات عن الفيديو القادم في قائمة التشغيل.", + "LabelDroppedFrames": "الاطارات الساقطة:", + "LabelDropImageHere": "اسقط صورة هنا، او ضغط تصفح.", + "LabelDisplayOrder": "ترتيب المعروض:", + "LabelDisplayMode": "وضع المعروض:", + "LabelDisplayLanguageHelp": "ترجمة جيلليفين هو مشروع مستمر.", + "LabelDisplayLanguage": "لغة العرض:", + "LabelDiscNumber": "رقم القرص:", + "LabelDeinterlaceMethod": "طريقة تقليل التشابك:", + "LabelDefaultScreen": "الشاشة الافتراضية:", + "LabelDateTimeLocale": "وقت و تاريخ محلي:", + "LabelDateAdded": "تاريخ الاضافة:", + "LabelCustomRating": "تقييم مخصص:", + "LabelCriticRating": "تقييم النقاد:", + "LabelCorruptedFrames": "الإطارات التالفة:", + "LabelChannels": "القنوات:", + "LabelCertificatePasswordHelp": "اذا تطلبت شهادتك الامنية كلمة مرور، من فضلك ادخلها هنا.", + "LabelCertificatePassword": "كلمة مرور الشهادة الامنية:", + "LabelBurnSubtitles": "الترجمات المحروقة:", + "LabelBlockContentWithTags": "احجب العناصر بالعلامات:", + "LabelBitrate": "معدل البت:", + "LabelBirthYear": "عام الميلاد:", + "LabelBirthDate": "تاريخ الميلاد:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "حدث وصف البيانات تلقائيا من الانترنت:", + "LabelAuthProvider": "مقدم التصديق:", + "LabelAudioSampleRate": "سرعة معينة الصوت:", + "LabelAudioCodec": "ترميز الصوت:", + "LabelAudioChannels": "قنوات الصوت:", + "LabelAudioBitrate": "معدل بث الصوت:", + "LabelAudioBitDepth": "عمق بث الصوت:", + "LabelAudio": "الصوت", + "LabelAllowedRemoteAddressesMode": "وضع مرشح عنوان المضيف IP البعيد:", + "LabelAllowedRemoteAddresses": "مرشح عنوان المضيف IP البعيد:", + "LabelAirsBeforeSeason": "عروض بث قبل الموسم:", + "LabelAirsBeforeEpisode": "عروض بث قبل الحلقة:", + "LabelAirsAfterSeason": "عروض بث بعد الموسم:", + "Label3DFormat": "صيغة ثلاثية الابعاد:", + "Kids": "اطفال", + "Items": "عناصر", + "ItemCount": "{0} عنصر", + "InstantMix": "خلط فوري", + "HeaderSyncPlayEnabled": "تزامن اللعب ممكَّن", + "HeaderSyncPlaySelectGroup": "انضم لمجموعة", + "EnableDetailsBannerHelp": "اظهر صوره اللافته اعلى عنصر تفاصيل الصفحة.", + "EnableDetailsBanner": "لافتة التفاصيل", + "EnableDecodingColorDepth10Vp9": "تمكين ترميز ال10 بت عبر العتاد الصلب من اجل VP9", + "EnableDecodingColorDepth10Hevc": "تمكين ترميز ال10 بت عبر العتاد الصلب من اجل HEVC" } From 637c134145a7b5ec3e405dc9a2cd157edd1c66ce Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 15:47:14 +0000 Subject: [PATCH 36/58] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 58eb6ee1b..c266f587c 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -471,7 +471,7 @@ "TabTrailers": "טריילרים", "TabTranscoding": "קידוד", "TabUpcoming": "בקרוב", - "Tags": "תגים", + "Tags": "מילות מפתח", "TellUsAboutYourself": "ספר לנו על עצמך", "ThisWizardWillGuideYou": "אשף זה יעזור לך בהתליך ההתקנה.", "Thursday": "חמישי", @@ -588,7 +588,7 @@ "MessageConfirmRestart": "‫האם אתה בטוח שברצונך לאתחל את שרת ה-Jellyfin‏?", "HeaderThisUserIsCurrentlyDisabled": "משתמש זה אינו פעיל כרגע", "HeaderTaskTriggers": "טריגרים של המשימה", - "HeaderTags": "תגיות", + "HeaderTags": "מילות מפתח", "HeaderStopRecording": "עצור הקלטה", "HeaderSortOrder": "סדר מיון", "HeaderSortBy": "מיין לפי", @@ -867,5 +867,7 @@ "OptionEveryday": "כל יום", "OptionEnableExternalContentInSuggestions": "הפעל תוכן חיצוני בהמלצות", "OptionEnableAccessToAllLibraries": "אפשר גישה לכל הספריות", - "OptionEnableAccessToAllChannels": "אפשר גישה לכל הערוצים" + "OptionEnableAccessToAllChannels": "אפשר גישה לכל הערוצים", + "HeaderSyncPlaySelectGroup": "הצטרף לקבוצה", + "TabUsers": "משתמשים" } From 0cd82da8f2aa441650900d882a82c15f457759e6 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 20:59:14 +0200 Subject: [PATCH 37/58] Migrate navdrawer to ES6 --- .eslintignore | 1 - package.json | 1 + src/libraries/navdrawer/navdrawer.js | 639 ++++++++++++++------------- src/scripts/libraryMenu.js | 2 + 4 files changed, 324 insertions(+), 319 deletions(-) diff --git a/.eslintignore b/.eslintignore index 8e3aee83f..74b18ddcf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,3 @@ node_modules dist .idea .vscode -src/libraries diff --git a/package.json b/package.json index bf3ac42f9..7b895bc36 100644 --- a/package.json +++ b/package.json @@ -275,6 +275,7 @@ "src/elements/emby-tabs/emby-tabs.js", "src/elements/emby-textarea/emby-textarea.js", "src/elements/emby-toggle/emby-toggle.js", + "src/libraries/navdrawer/navdrawer.js", "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index 4733c617f..965b68aee 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -1,354 +1,357 @@ -define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, dom) { - "use strict"; +/* Cleaning this file properly is not neecessary, since it's an outdated library + * and will be replaced soon by a Vue component. + */ - browser = browser.default || browser; +import browser from 'browser'; +import dom from 'dom'; +import 'css!./navdrawer'; +import 'scrollStyles'; - return function (options) { - function getTouches(e) { - return e.changedTouches || e.targetTouches || e.touches; +export default function (options) { + function getTouches(e) { + return e.changedTouches || e.targetTouches || e.touches; + } + + function onMenuTouchStart(e) { + options.target.classList.remove('transition'); + var touches = getTouches(e); + var touch = touches[0] || {}; + menuTouchStartX = touch.clientX; + menuTouchStartY = touch.clientY; + menuTouchStartTime = new Date().getTime(); + } + + function setVelocity(deltaX) { + var time = new Date().getTime() - (menuTouchStartTime || 0); + velocity = Math.abs(deltaX) / time; + } + + function onMenuTouchMove(e) { + var isOpen = self.visible; + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var endY = touch.clientY || 0; + var deltaX = endX - (menuTouchStartX || 0); + var deltaY = endY - (menuTouchStartY || 0); + setVelocity(deltaX); + + if (isOpen && dragMode !== 1 && deltaX > 0) { + dragMode = 2; } - function onMenuTouchStart(e) { - options.target.classList.remove("transition"); - var touches = getTouches(e); - var touch = touches[0] || {}; - menuTouchStartX = touch.clientX; - menuTouchStartY = touch.clientY; - menuTouchStartTime = new Date().getTime(); + if (dragMode === 0 && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { + dragMode = 1; + scrollContainer.addEventListener('scroll', disableEvent); + self.showMask(); + } else if (dragMode === 0 && Math.abs(deltaY) >= 5) { + dragMode = 2; } - function setVelocity(deltaX) { - var time = new Date().getTime() - (menuTouchStartTime || 0); - velocity = Math.abs(deltaX) / time; + if (dragMode === 1) { + newPos = currentPos + deltaX; + self.changeMenuPos(); } + } - function onMenuTouchMove(e) { - var isOpen = self.visible; - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - var endY = touch.clientY || 0; - var deltaX = endX - (menuTouchStartX || 0); - var deltaY = endY - (menuTouchStartY || 0); - setVelocity(deltaX); + function onMenuTouchEnd(e) { + options.target.classList.add('transition'); + scrollContainer.removeEventListener('scroll', disableEvent); + dragMode = 0; + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var endY = touch.clientY || 0; + var deltaX = endX - (menuTouchStartX || 0); + var deltaY = endY - (menuTouchStartY || 0); + currentPos = deltaX; + self.checkMenuState(deltaX, deltaY); + } - if (isOpen && 1 !== dragMode && deltaX > 0) { - dragMode = 2; - } + function onEdgeTouchStart(e) { + if (isPeeking) { + onMenuTouchMove(e); + } else { + if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { + isPeeking = true; - if (0 === dragMode && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { - dragMode = 1; - scrollContainer.addEventListener("scroll", disableEvent); - self.showMask(); - } else if (0 === dragMode && Math.abs(deltaY) >= 5) { - dragMode = 2; - } - - if (1 === dragMode) { - newPos = currentPos + deltaX; - self.changeMenuPos(); - } - } - - function onMenuTouchEnd(e) { - options.target.classList.add("transition"); - scrollContainer.removeEventListener("scroll", disableEvent); - dragMode = 0; - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - var endY = touch.clientY || 0; - var deltaX = endX - (menuTouchStartX || 0); - var deltaY = endY - (menuTouchStartY || 0); - currentPos = deltaX; - self.checkMenuState(deltaX, deltaY); - } - - function onEdgeTouchStart(e) { - if (isPeeking) { - onMenuTouchMove(e); - } else { - if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { - isPeeking = true; - - if (e.type === "touchstart") { - dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - dom.addEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - } - - onMenuTouchStart(e); + if (e.type === 'touchstart') { + dom.removeEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); + dom.addEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); } + + onMenuTouchStart(e); } } + } - function onEdgeTouchMove(e) { - e.preventDefault(); - e.stopPropagation(); - onEdgeTouchStart(e); + function onEdgeTouchMove(e) { + e.preventDefault(); + e.stopPropagation(); + onEdgeTouchStart(e); + } + + function onEdgeTouchEnd(e) { + if (isPeeking) { + isPeeking = false; + dom.removeEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); + onMenuTouchEnd(e); } + } - function onEdgeTouchEnd(e) { - if (isPeeking) { - isPeeking = false; - dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - onMenuTouchEnd(e); - } - } + function disableEvent(e) { + e.preventDefault(); + e.stopPropagation(); + } - function disableEvent(e) { - e.preventDefault(); - e.stopPropagation(); - } + function onBackgroundTouchStart(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + backgroundTouchStartX = touch.clientX; + backgroundTouchStartTime = new Date().getTime(); + } - function onBackgroundTouchStart(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - backgroundTouchStartX = touch.clientX; - backgroundTouchStartTime = new Date().getTime(); - } + function onBackgroundTouchMove(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; - function onBackgroundTouchMove(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - - if (endX <= options.width && self.isVisible) { - countStart++; - var deltaX = endX - (backgroundTouchStartX || 0); - - if (countStart == 1) { - startPoint = deltaX; - } - if (deltaX < 0 && dragMode !== 2) { - dragMode = 1; - newPos = deltaX - startPoint + options.width; - self.changeMenuPos(); - var time = new Date().getTime() - (backgroundTouchStartTime || 0); - velocity = Math.abs(deltaX) / time; - } - } - - e.preventDefault(); - e.stopPropagation(); - } - - function onBackgroundTouchEnd(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; + if (endX <= options.width && self.isVisible) { + countStart++; var deltaX = endX - (backgroundTouchStartX || 0); - self.checkMenuState(deltaX); - countStart = 0; - } - function onMaskTransitionEnd() { - var classList = mask.classList; - - if (!classList.contains("backdrop")) { - classList.add("hide"); + if (countStart == 1) { + startPoint = deltaX; + } + if (deltaX < 0 && dragMode !== 2) { + dragMode = 1; + newPos = deltaX - startPoint + options.width; + self.changeMenuPos(); + var time = new Date().getTime() - (backgroundTouchStartTime || 0); + velocity = Math.abs(deltaX) / time; } } - var self; - var defaults; - var mask; - var newPos = 0; - var currentPos = 0; - var startPoint = 0; - var countStart = 0; - var velocity = 0; - options.target.classList.add("transition"); - var dragMode = 0; - var scrollContainer = options.target.querySelector(".mainDrawer-scrollContainer"); - scrollContainer.classList.add("scrollY"); + e.preventDefault(); + e.stopPropagation(); + } - var TouchMenuLA = function () { - self = this; - defaults = { - width: 260, - handleSize: 10, - disableMask: false, - maxMaskOpacity: 0.5 - }; - this.isVisible = false; - this.initialize(); + function onBackgroundTouchEnd(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var deltaX = endX - (backgroundTouchStartX || 0); + self.checkMenuState(deltaX); + countStart = 0; + } + + function onMaskTransitionEnd() { + var classList = mask.classList; + + if (!classList.contains('backdrop')) { + classList.add('hide'); + } + } + + var self; + var defaults; + var mask; + var newPos = 0; + var currentPos = 0; + var startPoint = 0; + var countStart = 0; + var velocity = 0; + options.target.classList.add('transition'); + var dragMode = 0; + var scrollContainer = options.target.querySelector('.mainDrawer-scrollContainer'); + scrollContainer.classList.add('scrollY'); + + var TouchMenuLA = function () { + self = this; + defaults = { + width: 260, + handleSize: 10, + disableMask: false, + maxMaskOpacity: 0.5 }; + this.isVisible = false; + this.initialize(); + }; - TouchMenuLA.prototype.initElements = function () { - options.target.classList.add("touch-menu-la"); - options.target.style.width = options.width + "px"; - options.target.style.left = -options.width + "px"; + TouchMenuLA.prototype.initElements = function () { + options.target.classList.add('touch-menu-la'); + options.target.style.width = options.width + 'px'; + options.target.style.left = -options.width + 'px'; - if (!options.disableMask) { - mask = document.createElement("div"); - mask.className = "tmla-mask hide"; - document.body.appendChild(mask); - dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, { - passive: true - }); - } - }; - - var menuTouchStartX; - var menuTouchStartY; - var menuTouchStartTime; - var edgeContainer = document.querySelector(".mainDrawerHandle"); - var isPeeking = false; - - TouchMenuLA.prototype.animateToPosition = function (pos) { - requestAnimationFrame(function () { - options.target.style.transform = pos ? "translateX(" + pos + "px)" : "none"; + if (!options.disableMask) { + mask = document.createElement('div'); + mask.className = 'tmla-mask hide'; + document.body.appendChild(mask); + dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, { + passive: true }); - }; + } + }; - TouchMenuLA.prototype.changeMenuPos = function () { - if (newPos <= options.width) { - this.animateToPosition(newPos); - } - }; + var menuTouchStartX; + var menuTouchStartY; + var menuTouchStartTime; + var edgeContainer = document.querySelector('.mainDrawerHandle'); + var isPeeking = false; - TouchMenuLA.prototype.clickMaskClose = function () { - mask.addEventListener("click", function () { + TouchMenuLA.prototype.animateToPosition = function (pos) { + requestAnimationFrame(function () { + options.target.style.transform = pos ? 'translateX(' + pos + 'px)' : 'none'; + }); + }; + + TouchMenuLA.prototype.changeMenuPos = function () { + if (newPos <= options.width) { + this.animateToPosition(newPos); + } + }; + + TouchMenuLA.prototype.clickMaskClose = function () { + mask.addEventListener('click', function () { + self.close(); + }); + }; + + TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) { + if (velocity >= 0.4) { + if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) { + self.open(); + } else { self.close(); - }); - }; - - TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) { - if (velocity >= 0.4) { - if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) { - self.open(); - } else { + } + } else { + if (newPos >= 100) { + self.open(); + } else { + if (newPos) { self.close(); } - } else { - if (newPos >= 100) { - self.open(); - } else { - if (newPos) { - self.close(); - } - } } - }; - - TouchMenuLA.prototype.open = function () { - this.animateToPosition(options.width); - currentPos = options.width; - this.isVisible = true; - options.target.classList.add("drawer-open"); - self.showMask(); - self.invoke(options.onChange); - }; - - TouchMenuLA.prototype.close = function () { - this.animateToPosition(0); - currentPos = 0; - self.isVisible = false; - options.target.classList.remove("drawer-open"); - self.hideMask(); - self.invoke(options.onChange); - }; - - TouchMenuLA.prototype.toggle = function () { - if (self.isVisible) { - self.close(); - } else { - self.open(); - } - }; - - var backgroundTouchStartX; - var backgroundTouchStartTime; - - TouchMenuLA.prototype.showMask = function () { - mask.classList.remove("hide"); - mask.classList.add("backdrop"); - }; - - TouchMenuLA.prototype.hideMask = function () { - mask.classList.add("hide"); - mask.classList.remove("backdrop"); - }; - - TouchMenuLA.prototype.invoke = function (fn) { - if (fn) { - fn.apply(self); - } - }; - - var _edgeSwipeEnabled; - - TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) { - if (!options.disableEdgeSwipe) { - if (browser.touch) { - if (enabled) { - if (!_edgeSwipeEnabled) { - _edgeSwipeEnabled = true; - dom.addEventListener(edgeContainer, "touchstart", onEdgeTouchStart, { - passive: true - }); - dom.addEventListener(edgeContainer, "touchend", onEdgeTouchEnd, { - passive: true - }); - dom.addEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, { - passive: true - }); - } - } else { - if (_edgeSwipeEnabled) { - _edgeSwipeEnabled = false; - dom.removeEventListener(edgeContainer, "touchstart", onEdgeTouchStart, { - passive: true - }); - dom.removeEventListener(edgeContainer, "touchend", onEdgeTouchEnd, { - passive: true - }); - dom.removeEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, { - passive: true - }); - } - } - } - } - }; - - TouchMenuLA.prototype.initialize = function () { - options = Object.assign(defaults, options || {}); - - if (browser.edge) { - options.disableEdgeSwipe = true; - } - - self.initElements(); - - if (browser.touch) { - dom.addEventListener(options.target, "touchstart", onMenuTouchStart, { - passive: true - }); - dom.addEventListener(options.target, "touchmove", onMenuTouchMove, { - passive: true - }); - dom.addEventListener(options.target, "touchend", onMenuTouchEnd, { - passive: true - }); - dom.addEventListener(options.target, "touchcancel", onMenuTouchEnd, { - passive: true - }); - dom.addEventListener(mask, "touchstart", onBackgroundTouchStart, { - passive: true - }); - dom.addEventListener(mask, "touchmove", onBackgroundTouchMove, {}); - dom.addEventListener(mask, "touchend", onBackgroundTouchEnd, { - passive: true - }); - dom.addEventListener(mask, "touchcancel", onBackgroundTouchEnd, { - passive: true - }); - } - - self.clickMaskClose(); - }; - - return new TouchMenuLA(); + } }; -}); + + TouchMenuLA.prototype.open = function () { + this.animateToPosition(options.width); + currentPos = options.width; + this.isVisible = true; + options.target.classList.add('drawer-open'); + self.showMask(); + self.invoke(options.onChange); + }; + + TouchMenuLA.prototype.close = function () { + this.animateToPosition(0); + currentPos = 0; + self.isVisible = false; + options.target.classList.remove('drawer-open'); + self.hideMask(); + self.invoke(options.onChange); + }; + + TouchMenuLA.prototype.toggle = function () { + if (self.isVisible) { + self.close(); + } else { + self.open(); + } + }; + + var backgroundTouchStartX; + var backgroundTouchStartTime; + + TouchMenuLA.prototype.showMask = function () { + mask.classList.remove('hide'); + mask.classList.add('backdrop'); + }; + + TouchMenuLA.prototype.hideMask = function () { + mask.classList.add('hide'); + mask.classList.remove('backdrop'); + }; + + TouchMenuLA.prototype.invoke = function (fn) { + if (fn) { + fn.apply(self); + } + }; + + var _edgeSwipeEnabled; + + TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) { + if (!options.disableEdgeSwipe) { + if (browser.touch) { + if (enabled) { + if (!_edgeSwipeEnabled) { + _edgeSwipeEnabled = true; + dom.addEventListener(edgeContainer, 'touchstart', onEdgeTouchStart, { + passive: true + }); + dom.addEventListener(edgeContainer, 'touchend', onEdgeTouchEnd, { + passive: true + }); + dom.addEventListener(edgeContainer, 'touchcancel', onEdgeTouchEnd, { + passive: true + }); + } + } else { + if (_edgeSwipeEnabled) { + _edgeSwipeEnabled = false; + dom.removeEventListener(edgeContainer, 'touchstart', onEdgeTouchStart, { + passive: true + }); + dom.removeEventListener(edgeContainer, 'touchend', onEdgeTouchEnd, { + passive: true + }); + dom.removeEventListener(edgeContainer, 'touchcancel', onEdgeTouchEnd, { + passive: true + }); + } + } + } + } + }; + + TouchMenuLA.prototype.initialize = function () { + options = Object.assign(defaults, options || {}); + + if (browser.edge) { + options.disableEdgeSwipe = true; + } + + self.initElements(); + + if (browser.touch) { + dom.addEventListener(options.target, 'touchstart', onMenuTouchStart, { + passive: true + }); + dom.addEventListener(options.target, 'touchmove', onMenuTouchMove, { + passive: true + }); + dom.addEventListener(options.target, 'touchend', onMenuTouchEnd, { + passive: true + }); + dom.addEventListener(options.target, 'touchcancel', onMenuTouchEnd, { + passive: true + }); + dom.addEventListener(mask, 'touchstart', onBackgroundTouchStart, { + passive: true + }); + dom.addEventListener(mask, 'touchmove', onBackgroundTouchMove, {}); + dom.addEventListener(mask, 'touchend', onBackgroundTouchEnd, { + passive: true + }); + dom.addEventListener(mask, 'touchcancel', onBackgroundTouchEnd, { + passive: true + }); + } + + self.clickMaskClose(); + }; + + return new TouchMenuLA(); +} diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index bbe01276b..614971764 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -806,6 +806,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' navDrawerScrollContainer.addEventListener('click', onMainDrawerClick); return new Promise(function (resolve, reject) { require(['navdrawer'], function (navdrawer) { + navdrawer = navdrawer.default || navdrawer; + navDrawerInstance = new navdrawer(getNavDrawerOptions()); if (!layoutManager.tv) { From f2e1d03ae98925a1788dbb577b608649e9004ee1 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 21:15:16 +0200 Subject: [PATCH 38/58] Migrate scroller to ES6 --- package.json | 1 + src/controllers/list.js | 2 +- src/libraries/scroller.js | 1714 ++++++++++++++++++------------------- 3 files changed, 838 insertions(+), 879 deletions(-) diff --git a/package.json b/package.json index 7b895bc36..7ed4cb8b9 100644 --- a/package.json +++ b/package.json @@ -276,6 +276,7 @@ "src/elements/emby-textarea/emby-textarea.js", "src/elements/emby-toggle/emby-toggle.js", "src/libraries/navdrawer/navdrawer.js", + "src/libraries/scroller.js", "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", diff --git a/src/controllers/list.js b/src/controllers/list.js index afa0a60fa..14c676875 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -1,4 +1,4 @@ -define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'scroller', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, AlphaPicker) { +define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, playbackManager, AlphaPicker) { 'use strict'; playbackManager = playbackManager.default || playbackManager; diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index cc75dcdee..03c630651 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -1,928 +1,886 @@ -define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'scrollStyles'], function (browser, layoutManager, dom, focusManager, ResizeObserver) { - 'use strict'; +/* Cleaning this file properly is not neecessary, since it's an outdated library + * and will be replaced soon by a Vue component. + */ - browser = browser.default || browser; - focusManager = focusManager.default || focusManager; + import browser from 'browser'; +import layoutManager from 'layoutManager'; +import dom from 'dom'; +import focusManager from 'focusManager'; +import ResizeObserver from 'ResizeObserver'; +import 'scrollStyles'; - /** +/** * Return type of the value. * * @param {Mixed} value * * @return {String} */ - function type(value) { - if (value == null) { - return String(value); - } - - if (typeof value === 'object' || typeof value === 'function') { - return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; - } - - return typeof value; +function type(value) { + if (value == null) { + return String(value); } - /** - * Disables an event it was triggered on and unbinds itself. - * - * @param {Event} event - * - * @return {Void} - */ - function disableOneEvent(event) { - /*jshint validthis:true */ - event.preventDefault(); - event.stopPropagation(); - this.removeEventListener(event.type, disableOneEvent); + if (typeof value === 'object' || typeof value === 'function') { + return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; } - /** - * Make sure that number is within the limits. - * - * @param {Number} number - * @param {Number} min - * @param {Number} max - * - * @return {Number} - */ - function within(number, min, max) { - return number < min ? min : number > max ? max : number; + return typeof value; +} + +/** + * Disables an event it was triggered on and unbinds itself. + * + * @param {Event} event + * + * @return {Void} + */ +function disableOneEvent(event) { + /*jshint validthis:true */ + event.preventDefault(); + event.stopPropagation(); + this.removeEventListener(event.type, disableOneEvent); +} + +/** + * Make sure that number is within the limits. + * + * @param {Number} number + * @param {Number} min + * @param {Number} max + * + * @return {Number} + */ +function within(number, min, max) { + return number < min ? min : number > max ? max : number; +} + +// Other global values +var dragMouseEvents = ['mousemove', 'mouseup']; +var dragTouchEvents = ['touchmove', 'touchend']; +var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); +var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA']; + +var scrollerFactory = function (frame, options) { + // Extend options + var o = Object.assign({}, { + slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. + horizontal: false, // Switch to horizontal mode. + + // Scrolling + mouseWheel: true, + scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling + + // Dragging + dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. + mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor. + touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events. + dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. + intervactive: null, // Selector for special interactive elements. + + // Mixed options + speed: 0 // Animations speed in milliseconds. 0 to disable animations. + + }, options); + + var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; + + // native scroll is a must with touch input + // also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment + // in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good + if (options.allowNativeScroll === false) { + options.enableNativeScroll = false; + } else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) { + // native smooth scroll + options.enableNativeScroll = true; + } else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) { + // transform is the only way to guarantee animation + options.enableNativeScroll = false; + } else if (!layoutManager.tv || !browser.animate) { + options.enableNativeScroll = true; } - // Other global values - var dragMouseEvents = ['mousemove', 'mouseup']; - var dragTouchEvents = ['touchmove', 'touchend']; - var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); - var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA']; - var tmpArray = []; - var time; - - // Math shorthands - var abs = Math.abs; - var sqrt = Math.sqrt; - var pow = Math.pow; - var round = Math.round; - var max = Math.max; - var min = Math.min; - - var scrollerFactory = function (frame, options) { - - // Extend options - var o = Object.assign({}, { - slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. - horizontal: false, // Switch to horizontal mode. - - // Scrolling - mouseWheel: true, - scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling - - // Dragging - dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. - mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor. - touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events. - dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. - intervactive: null, // Selector for special interactive elements. - - // Mixed options - speed: 0 // Animations speed in milliseconds. 0 to disable animations. - - }, options); - - var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; - - // native scroll is a must with touch input - // also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment - // in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good - if (options.allowNativeScroll === false) { - options.enableNativeScroll = false; - } else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) { - // native smooth scroll - options.enableNativeScroll = true; - } else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) { - - // transform is the only way to guarantee animation - options.enableNativeScroll = false; - } else if (!layoutManager.tv || !browser.animate) { - - options.enableNativeScroll = true; - } - - // Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen - if (browser.web0s) { - options.enableNativeScroll = true; - } - - // Private variables - var self = this; - self.options = o; - - // Frame - var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0]; - self._pos = { - start: 0, - center: 0, - end: 0, - cur: 0, - dest: 0 - }; - - var transform = !options.enableNativeScroll; - - // Miscellaneous - var scrollSource = frame; - var dragSourceElement = o.dragSource ? o.dragSource : frame; - var dragging = { - released: 1 - }; - var scrolling = { - last: 0, - delta: 0, - resetTime: 200 - }; - - // Expose properties - self.initialized = 0; - self.slidee = slideeElement; - self.options = o; - self.dragging = dragging; - - var nativeScrollElement = frame; - - function sibling(n, elem) { - var matched = []; - - for (; n; n = n.nextSibling) { - if (n.nodeType === 1 && n !== elem) { - matched.push(n); - } - } - return matched; - } - - var requiresReflow = true; - - var frameSize = 0; - var slideeSize = 0; - function ensureSizeInfo() { - - if (requiresReflow) { - - requiresReflow = false; - - // Reset global variables - frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; - - slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); - - // Set position limits & relativess - self._pos.end = max(slideeSize - frameSize, 0); - } - } - - /** - * Loading function. - * - * Populate arrays, set sizes, bind events, ... - * - * @param {Boolean} [isInit] Whether load is called from within self.init(). - * @return {Void} - */ - function load(isInit) { - - requiresReflow = true; - - if (!isInit) { - - ensureSizeInfo(); - - // Fix possible overflowing - var pos = self._pos; - self.slideTo(within(pos.dest, pos.start, pos.end)); - } - } - - function initFrameResizeObserver() { - - var observerOptions = {}; - - self.frameResizeObserver = new ResizeObserver(onResize, observerOptions); - - self.frameResizeObserver.observe(frame); - } - - self.reload = function () { - load(); - }; - - self.getScrollEventName = function () { - return transform ? 'scrollanimate' : 'scroll'; - }; - - self.getScrollSlider = function () { - return slideeElement; - }; - - self.getScrollFrame = function () { - return frame; - }; - - function nativeScrollTo(container, pos, immediate) { - - if (container.scroll) { - if (o.horizontal) { - - container.scroll({ - left: pos, - behavior: immediate ? 'instant' : 'smooth' - }); - } else { - - container.scroll({ - top: pos, - behavior: immediate ? 'instant' : 'smooth' - }); - } - } else if (!immediate && container.scrollTo) { - if (o.horizontal) { - container.scrollTo(Math.round(pos), 0); - } else { - container.scrollTo(0, Math.round(pos)); - } - } else { - if (o.horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } - } - } - - var lastAnimate; - - /** - * Animate to a position. - * - * @param {Int} newPos New position. - * @param {Bool} immediate Reposition immediately without an animation. - * - * @return {Void} - */ - self.slideTo = function (newPos, immediate, fullItemPos) { - - ensureSizeInfo(); - var pos = self._pos; - - newPos = within(newPos, pos.start, pos.end); - - if (!transform) { - - nativeScrollTo(nativeScrollElement, newPos, immediate); - return; - } - - // Update the animation object - var from = pos.cur; - immediate = immediate || dragging.init || !o.speed; - - var now = new Date().getTime(); - - if (o.autoImmediate) { - if (!immediate && (now - (lastAnimate || 0)) <= 50) { - immediate = true; - } - } - - if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { - - return; - } - - // Start animation rendering - // NOTE the dependency was modified here to fix a scrollbutton issue - pos.dest = newPos; - renderAnimateWithTransform(from, newPos, immediate); - lastAnimate = now; - }; - - function setStyleProperty(elem, name, value, speed, resetTransition) { - - var style = elem.style; - - if (resetTransition || browser.edge) { - style.transition = 'none'; - void elem.offsetWidth; - } - - style.transition = 'transform ' + speed + 'ms ease-out'; - style[name] = value; - } - - function dispatchScrollEventIfNeeded() { - if (o.dispatchScrollEvent) { - frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), { - bubbles: true, - cancelable: false - })); - } - } - - function renderAnimateWithTransform(fromPosition, toPosition, immediate) { - - var speed = o.speed; - - if (immediate) { - speed = o.immediateSpeed || 50; - } - - if (o.horizontal) { - setStyleProperty(slideeElement, 'transform', 'translateX(' + (-round(toPosition)) + 'px)', speed); - } else { - setStyleProperty(slideeElement, 'transform', 'translateY(' + (-round(toPosition)) + 'px)', speed); - } - self._pos.cur = toPosition; - - dispatchScrollEventIfNeeded(); - } - - function getBoundingClientRect(elem) { - - // Support: BlackBerry 5, iOS 3 (original iPhone) - // If we don't have gBCR, just use 0,0 rather than error - if (elem.getBoundingClientRect) { - return elem.getBoundingClientRect(); - } else { - return { top: 0, left: 0 }; - } - } - - /** - * Returns the position object. - * - * @param {Mixed} item - * - * @return {Object} - */ - self.getPos = function (item) { - - var scrollElement = transform ? slideeElement : nativeScrollElement; - var slideeOffset = getBoundingClientRect(scrollElement); - var itemOffset = getBoundingClientRect(item); - - var slideeStartPos = o.horizontal ? slideeOffset.left : slideeOffset.top; - var slideeEndPos = o.horizontal ? slideeOffset.right : slideeOffset.bottom; - - var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; - - var size = o.horizontal ? itemOffset.width : itemOffset.height; - if (!size && size !== 0) { - size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight']; - } - - var centerOffset = o.centerOffset || 0; - - if (!transform) { - centerOffset = 0; - if (o.horizontal) { - offset += nativeScrollElement.scrollLeft; - } else { - offset += nativeScrollElement.scrollTop; - } - } - - ensureSizeInfo(); - - var currentStart = self._pos.cur; - var currentEnd = currentStart + frameSize; - - console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); - var isVisible = offset >= currentStart && (offset + size) <= currentEnd; - - return { - start: offset, - center: offset + centerOffset - (frameSize / 2) + (size / 2), - end: offset - frameSize + size, - size: size, - isVisible: isVisible - }; - }; - - self.getCenterPosition = function (item) { - - ensureSizeInfo(); - - var pos = self.getPos(item); - return within(pos.center, pos.start, pos.end); - }; - - function dragInitSlidee(event) { - var isTouch = event.type === 'touchstart'; - - // Ignore when already in progress, or interactive element in non-touch navivagion - if (dragging.init || !isTouch && isInteractive(event.target)) { - return; - } - - // SLIDEE dragging conditions - if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { - return; - } - - if (!isTouch) { - // prevents native image dragging in Firefox - event.preventDefault(); - } - - // Reset dragging object - dragging.released = 0; - - // Properties used in dragHandler - dragging.init = 0; - dragging.source = event.target; - dragging.touch = isTouch; - var pointer = isTouch ? event.touches[0] : event; - dragging.initX = pointer.pageX; - dragging.initY = pointer.pageY; - dragging.initPos = self._pos.cur; - dragging.start = +new Date(); - dragging.time = 0; - dragging.path = 0; - dragging.delta = 0; - dragging.locked = 0; - dragging.pathToLock = isTouch ? 30 : 10; - - // Bind dragging events - if (transform) { - - if (isTouch) { - dragTouchEvents.forEach(function (eventName) { - dom.addEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - } else { - dragMouseEvents.forEach(function (eventName) { - dom.addEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - } - } - } - - /** - * Handler for dragging scrollbar handle or SLIDEE. - * - * @param {Event} event - * - * @return {Void} - */ - function dragHandler(event) { - dragging.released = event.type === 'mouseup' || event.type === 'touchend'; - var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; - dragging.pathX = pointer.pageX - dragging.initX; - dragging.pathY = pointer.pageY - dragging.initY; - dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2)); - dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; - - if (!dragging.released && dragging.path < 1) { - return; - } - - // We haven't decided whether this is a drag or not... - if (!dragging.init) { - // If the drag path was very short, maybe it's not a drag? - if (dragging.path < o.dragThreshold) { - // If the pointer was released, the path will not become longer and it's - // definitely not a drag. If not released yet, decide on next iteration - return dragging.released ? dragEnd() : undefined; - } else { - // If dragging path is sufficiently long we can confidently start a drag - // if drag is in different direction than scroll, ignore it - if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) { - dragging.init = 1; - } else { - return dragEnd(); - } - } - } - - //event.preventDefault(); - - // Disable click on a source element, as it is unwelcome when dragging - if (!dragging.locked && dragging.path > dragging.pathToLock) { - dragging.locked = 1; - dragging.source.addEventListener('click', disableOneEvent); - } - - // Cancel dragging on release - if (dragging.released) { - dragEnd(); - } - - self.slideTo(round(dragging.initPos - dragging.delta)); - } - - /** - * Stops dragging and cleans up after it. - * - * @return {Void} - */ - function dragEnd() { - dragging.released = true; - - dragTouchEvents.forEach(function (eventName) { - dom.removeEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - - dragMouseEvents.forEach(function (eventName) { - dom.removeEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - - // Make sure that disableOneEvent is not active in next tick. - setTimeout(function () { - dragging.source.removeEventListener('click', disableOneEvent); - }); - - dragging.init = 0; - } - - /** - * Check whether element is interactive. - * - * @return {Boolean} - */ - function isInteractive(element) { - - while (element) { - - if (interactiveElements.indexOf(element.tagName) !== -1) { - return true; - } - - element = element.parentNode; - } - return false; - } - - /** - * Mouse wheel delta normalization. - * - * @param {Event} event - * - * @return {Int} - */ - function normalizeWheelDelta(event) { - // JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support - scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY; - // END JELLYFIN MOD - - if (transform) { - scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; - } - return scrolling.curDelta; - } - - /** - * Mouse scrolling handler. - * - * @param {Event} event - * - * @return {Void} - */ - function scrollHandler(event) { - - ensureSizeInfo(); - var pos = self._pos; - // Ignore if there is no scrolling to be done - if (!o.scrollBy || pos.start === pos.end) { - return; - } - var delta = normalizeWheelDelta(event); - - if (transform) { - // Trap scrolling only when necessary and/or requested - if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { - //stopDefault(event, 1); - } - - self.slideBy(o.scrollBy * delta); - } else { - - if (isSmoothScrollSupported) { - delta *= 12; - } - - if (o.horizontal) { - nativeScrollElement.scrollLeft += delta; - } else { - nativeScrollElement.scrollTop += delta; - } - } - } - - /** - * Destroys instance and everything it created. - * - * @return {Void} - */ - self.destroy = function () { - - if (self.frameResizeObserver) { - self.frameResizeObserver.disconnect(); - self.frameResizeObserver = null; - } - - // Reset native FRAME element scroll - dom.removeEventListener(frame, 'scroll', resetScroll, { - passive: true - }); - - dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - - dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { - passive: true - }); - - dom.removeEventListener(frame, 'click', onFrameClick, { - passive: true, - capture: true - }); - - dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { - //passive: true - }); - - // Reset initialized status and return the instance - self.initialized = 0; - return self; - }; - - var contentRect = {}; - - function onResize(entries) { - - var entry = entries[0]; - - if (entry) { - - var newRect = entry.contentRect; - - // handle element being hidden - if (newRect.width === 0 || newRect.height === 0) { - return; - } - - if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) { - - contentRect = newRect; - - load(false); - } - } - } - - function resetScroll() { - if (o.horizontal) { - this.scrollLeft = 0; - } else { - this.scrollTop = 0; - } - } - - function onFrameClick(e) { - if (e.which === 1) { - var focusableParent = focusManager.focusableParent(e.target); - if (focusableParent && focusableParent !== document.activeElement) { - focusableParent.focus(); - } - } - } - - self.getScrollPosition = function () { - - if (transform) { - return self._pos.cur; - } - - if (o.horizontal) { - return nativeScrollElement.scrollLeft; - } else { - return nativeScrollElement.scrollTop; - } - }; - - self.getScrollSize = function () { - - if (transform) { - return slideeSize; - } - - if (o.horizontal) { - return nativeScrollElement.scrollWidth; - } else { - return nativeScrollElement.scrollHeight; - } - }; - - /** - * Initialize. - * - * @return {Object} - */ - self.init = function () { - if (self.initialized) { - return; - } - - if (!transform) { - if (o.horizontal) { - if (layoutManager.desktop && !o.hideScrollbar) { - nativeScrollElement.classList.add('scrollX'); - } else { - nativeScrollElement.classList.add('scrollX'); - nativeScrollElement.classList.add('hiddenScrollX'); - - if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { - nativeScrollElement.classList.add('smoothScrollX'); - } - } - - if (o.forceHideScrollbars) { - nativeScrollElement.classList.add('hiddenScrollX-forced'); - } - } else { - if (layoutManager.desktop && !o.hideScrollbar) { - nativeScrollElement.classList.add('scrollY'); - } else { - nativeScrollElement.classList.add('scrollY'); - nativeScrollElement.classList.add('hiddenScrollY'); - - if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { - nativeScrollElement.classList.add('smoothScrollY'); - } - } - - if (o.forceHideScrollbars) { - nativeScrollElement.classList.add('hiddenScrollY-forced'); - } - } - } else { - frame.style.overflow = 'hidden'; - slideeElement.style['will-change'] = 'transform'; - slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out'; - - if (o.horizontal) { - slideeElement.classList.add('animatedScrollX'); - } else { - slideeElement.classList.add('animatedScrollY'); - } - } - - if (transform || layoutManager.tv) { - // This can prevent others from being able to listen to mouse events - dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { - //passive: true - }); - } - - initFrameResizeObserver(); - - if (transform) { - - dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { - passive: true - }); - - if (!o.horizontal) { - dom.addEventListener(frame, 'scroll', resetScroll, { - passive: true - }); - } - - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } - - } else if (o.horizontal) { - - // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively - - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } - } - - dom.addEventListener(frame, 'click', onFrameClick, { - passive: true, - capture: true - }); - - // Mark instance as initialized - self.initialized = 1; - - // Load - load(true); - - // Return instance - return self; - }; + // Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen + if (browser.web0s) { + options.enableNativeScroll = true; + } + + // Private variables + var self = this; + self.options = o; + + // Frame + var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0]; + self._pos = { + start: 0, + center: 0, + end: 0, + cur: 0, + dest: 0 }; + var transform = !options.enableNativeScroll; + + // Miscellaneous + var scrollSource = frame; + var dragSourceElement = o.dragSource ? o.dragSource : frame; + var dragging = { + released: 1 + }; + var scrolling = { + last: 0, + delta: 0, + resetTime: 200 + }; + + // Expose properties + self.initialized = 0; + self.slidee = slideeElement; + self.options = o; + self.dragging = dragging; + + var nativeScrollElement = frame; + + function sibling(n, elem) { + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + matched.push(n); + } + } + return matched; + } + + var requiresReflow = true; + + var frameSize = 0; + var slideeSize = 0; + function ensureSizeInfo() { + if (requiresReflow) { + requiresReflow = false; + + // Reset global variables + frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; + + slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); + + // Set position limits & relativess + self._pos.end = Math.max(slideeSize - frameSize, 0); + } + } + /** - * Slide SLIDEE by amount of pixels. + * Loading function. * - * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. - * @param {Bool} immediate Reposition immediately without an animation. + * Populate arrays, set sizes, bind events, ... * + * @param {Boolean} [isInit] Whether load is called from within self.init(). * @return {Void} */ - scrollerFactory.prototype.slideBy = function (delta, immediate) { - if (!delta) { + function load(isInit) { + requiresReflow = true; + + if (!isInit) { + ensureSizeInfo(); + + // Fix possible overflowing + var pos = self._pos; + self.slideTo(within(pos.dest, pos.start, pos.end)); + } + } + + function initFrameResizeObserver() { + var observerOptions = {}; + + self.frameResizeObserver = new ResizeObserver(onResize, observerOptions); + + self.frameResizeObserver.observe(frame); + } + + self.reload = function () { + load(); + }; + + self.getScrollEventName = function () { + return transform ? 'scrollanimate' : 'scroll'; + }; + + self.getScrollSlider = function () { + return slideeElement; + }; + + self.getScrollFrame = function () { + return frame; + }; + + function nativeScrollTo(container, pos, immediate) { + if (container.scroll) { + if (o.horizontal) { + container.scroll({ + left: pos, + behavior: immediate ? 'instant' : 'smooth' + }); + } else { + container.scroll({ + top: pos, + behavior: immediate ? 'instant' : 'smooth' + }); + } + } else if (!immediate && container.scrollTo) { + if (o.horizontal) { + container.scrollTo(Math.Math.round(pos), 0); + } else { + container.scrollTo(0, Math.Math.round(pos)); + } + } else { + if (o.horizontal) { + container.scrollLeft = Math.Math.round(pos); + } else { + container.scrollTop = Math.Math.round(pos); + } + } + } + + var lastAnimate; + + /** + * Animate to a position. + * + * @param {Int} newPos New position. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.slideTo = function (newPos, immediate, fullItemPos) { + ensureSizeInfo(); + var pos = self._pos; + + newPos = within(newPos, pos.start, pos.end); + + if (!transform) { + nativeScrollTo(nativeScrollElement, newPos, immediate); return; } - this.slideTo(this._pos.dest + delta, immediate); - }; - /** - * Core method for handling `toLocation` methods. - * - * @param {String} location - * @param {Mixed} item - * @param {Bool} immediate - * - * @return {Void} - */ - scrollerFactory.prototype.to = function (location, item, immediate) { - // Optional arguments logic - if (type(item) === 'boolean') { - immediate = item; - item = undefined; - } + // Update the animation object + var from = pos.cur; + immediate = immediate || dragging.init || !o.speed; - if (item === undefined) { - this.slideTo(this._pos[location], immediate); - } else { - var itemPos = this.getPos(item); + var now = new Date().getTime(); - if (itemPos) { - this.slideTo(itemPos[location], immediate, itemPos); + if (o.autoImmediate) { + if (!immediate && (now - (lastAnimate || 0)) <= 50) { + immediate = true; } } + + if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { + return; + } + + // Start animation rendering + // NOTE the dependency was modified here to fix a scrollbutton issue + pos.dest = newPos; + renderAnimateWithTransform(from, newPos, immediate); + lastAnimate = now; }; + function setStyleProperty(elem, name, value, speed, resetTransition) { + var style = elem.style; + + if (resetTransition || browser.edge) { + style.transition = 'none'; + void elem.offsetWidth; + } + + style.transition = 'transform ' + speed + 'ms ease-out'; + style[name] = value; + } + + function dispatchScrollEventIfNeeded() { + if (o.dispatchScrollEvent) { + frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), { + bubbles: true, + cancelable: false + })); + } + } + + function renderAnimateWithTransform(fromPosition, toPosition, immediate) { + var speed = o.speed; + + if (immediate) { + speed = o.immediateSpeed || 50; + } + + if (o.horizontal) { + setStyleProperty(slideeElement, 'transform', 'translateX(' + (-Math.round(toPosition)) + 'px)', speed); + } else { + setStyleProperty(slideeElement, 'transform', 'translateY(' + (-Math.round(toPosition)) + 'px)', speed); + } + self._pos.cur = toPosition; + + dispatchScrollEventIfNeeded(); + } + + function getBoundingClientRect(elem) { + // Support: BlackBerry 5, iOS 3 (original iPhone) + // If we don't have gBCR, just use 0,0 rather than error + if (elem.getBoundingClientRect) { + return elem.getBoundingClientRect(); + } else { + return { top: 0, left: 0 }; + } + } + /** - * Animate element or the whole SLIDEE to the start of the frame. + * Returns the position object. * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * @param {Mixed} item + * + * @return {Object} + */ + self.getPos = function (item) { + var scrollElement = transform ? slideeElement : nativeScrollElement; + var slideeOffset = getBoundingClientRect(scrollElement); + var itemOffset = getBoundingClientRect(item); + + var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; + + var size = o.horizontal ? itemOffset.width : itemOffset.height; + if (!size && size !== 0) { + size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight']; + } + + var centerOffset = o.centerOffset || 0; + + if (!transform) { + centerOffset = 0; + if (o.horizontal) { + offset += nativeScrollElement.scrollLeft; + } else { + offset += nativeScrollElement.scrollTop; + } + } + + ensureSizeInfo(); + + var currentStart = self._pos.cur; + var currentEnd = currentStart + frameSize; + + console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); + var isVisible = offset >= currentStart && (offset + size) <= currentEnd; + + return { + start: offset, + center: offset + centerOffset - (frameSize / 2) + (size / 2), + end: offset - frameSize + size, + size: size, + isVisible: isVisible + }; + }; + + self.getCenterPosition = function (item) { + ensureSizeInfo(); + + var pos = self.getPos(item); + return within(pos.center, pos.start, pos.end); + }; + + function dragInitSlidee(event) { + var isTouch = event.type === 'touchstart'; + + // Ignore when already in progress, or interactive element in non-touch navivagion + if (dragging.init || !isTouch && isInteractive(event.target)) { + return; + } + + // SLIDEE dragging conditions + if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { + return; + } + + if (!isTouch) { + // prevents native image dragging in Firefox + event.preventDefault(); + } + + // Reset dragging object + dragging.released = 0; + + // Properties used in dragHandler + dragging.init = 0; + dragging.source = event.target; + dragging.touch = isTouch; + var pointer = isTouch ? event.touches[0] : event; + dragging.initX = pointer.pageX; + dragging.initY = pointer.pageY; + dragging.initPos = self._pos.cur; + dragging.start = +new Date(); + dragging.time = 0; + dragging.path = 0; + dragging.delta = 0; + dragging.locked = 0; + dragging.pathToLock = isTouch ? 30 : 10; + + // Bind dragging events + if (transform) { + if (isTouch) { + dragTouchEvents.forEach(function (eventName) { + dom.addEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + } else { + dragMouseEvents.forEach(function (eventName) { + dom.addEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + } + } + } + + /** + * Handler for dragging scrollbar handle or SLIDEE. + * + * @param {Event} event * * @return {Void} */ - scrollerFactory.prototype.toStart = function (item, immediate) { - this.to('start', item, immediate); - }; + function dragHandler(event) { + dragging.released = event.type === 'mouseup' || event.type === 'touchend'; + var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; + dragging.pathX = pointer.pageX - dragging.initX; + dragging.pathY = pointer.pageY - dragging.initY; + dragging.path = Math.sqrt(Math.pow(dragging.pathX, 2) + Math.pow(dragging.pathY, 2)); + dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; + + if (!dragging.released && dragging.path < 1) { + return; + } + + // We haven't decided whether this is a drag or not... + if (!dragging.init) { + // If the drag path was very short, maybe it's not a drag? + if (dragging.path < o.dragThreshold) { + // If the pointer was released, the path will not become longer and it's + // definitely not a drag. If not released yet, decide on next iteration + return dragging.released ? dragEnd() : undefined; + } else { + // If dragging path is sufficiently long we can confidently start a drag + // if drag is in different direction than scroll, ignore it + if (o.horizontal ? Math.abs(dragging.pathX) > Math.abs(dragging.pathY) : Math.abs(dragging.pathX) < Math.abs(dragging.pathY)) { + dragging.init = 1; + } else { + return dragEnd(); + } + } + } + + //event.preventDefault(); + + // Disable click on a source element, as it is unwelcome when dragging + if (!dragging.locked && dragging.path > dragging.pathToLock) { + dragging.locked = 1; + dragging.source.addEventListener('click', disableOneEvent); + } + + // Cancel dragging on release + if (dragging.released) { + dragEnd(); + } + + self.slideTo(Math.round(dragging.initPos - dragging.delta)); + } /** - * Animate element or the whole SLIDEE to the end of the frame. - * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * Stops dragging and cleans up after it. * * @return {Void} */ - scrollerFactory.prototype.toEnd = function (item, immediate) { - this.to('end', item, immediate); - }; + function dragEnd() { + dragging.released = true; + + dragTouchEvents.forEach(function (eventName) { + dom.removeEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + + dragMouseEvents.forEach(function (eventName) { + dom.removeEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + + // Make sure that disableOneEvent is not active in next tick. + setTimeout(function () { + dragging.source.removeEventListener('click', disableOneEvent); + }); + + dragging.init = 0; + } /** - * Animate element or the whole SLIDEE to the center of the frame. + * Check whether element is interactive. * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * @return {Boolean} + */ + function isInteractive(element) { + while (element) { + if (interactiveElements.indexOf(element.tagName) !== -1) { + return true; + } + + element = element.parentNode; + } + return false; + } + + /** + * Mouse wheel delta normalization. + * + * @param {Event} event + * + * @return {Int} + */ + function normalizeWheelDelta(event) { + // JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support + scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY; + // END JELLYFIN MOD + + if (transform) { + scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; + } + return scrolling.curDelta; + } + + /** + * Mouse scrolling handler. + * + * @param {Event} event * * @return {Void} */ - scrollerFactory.prototype.toCenter = function (item, immediate) { - this.to('center', item, immediate); + function scrollHandler(event) { + ensureSizeInfo(); + var pos = self._pos; + // Ignore if there is no scrolling to be done + if (!o.scrollBy || pos.start === pos.end) { + return; + } + var delta = normalizeWheelDelta(event); + + if (transform) { + // Trap scrolling only when necessary and/or requested + if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { + //stopDefault(event, 1); + } + + self.slideBy(o.scrollBy * delta); + } else { + if (isSmoothScrollSupported) { + delta *= 12; + } + + if (o.horizontal) { + nativeScrollElement.scrollLeft += delta; + } else { + nativeScrollElement.scrollTop += delta; + } + } + } + + /** + * Destroys instance and everything it created. + * + * @return {Void} + */ + self.destroy = function () { + if (self.frameResizeObserver) { + self.frameResizeObserver.disconnect(); + self.frameResizeObserver = null; + } + + // Reset native FRAME element scroll + dom.removeEventListener(frame, 'scroll', resetScroll, { + passive: true + }); + + dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + + dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { + passive: true + }); + + dom.removeEventListener(frame, 'click', onFrameClick, { + passive: true, + capture: true + }); + + dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { + //passive: true + }); + + // Reset initialized status and return the instance + self.initialized = 0; + return self; }; - scrollerFactory.create = function (frame, options) { - var instance = new scrollerFactory(frame, options); - return Promise.resolve(instance); + var contentRect = {}; + + function onResize(entries) { + var entry = entries[0]; + + if (entry) { + var newRect = entry.contentRect; + + // handle element being hidden + if (newRect.width === 0 || newRect.height === 0) { + return; + } + + if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) { + contentRect = newRect; + + load(false); + } + } + } + + function resetScroll() { + if (o.horizontal) { + this.scrollLeft = 0; + } else { + this.scrollTop = 0; + } + } + + function onFrameClick(e) { + if (e.which === 1) { + var focusableParent = focusManager.focusableParent(e.target); + if (focusableParent && focusableParent !== document.activeElement) { + focusableParent.focus(); + } + } + } + + self.getScrollPosition = function () { + if (transform) { + return self._pos.cur; + } + + if (o.horizontal) { + return nativeScrollElement.scrollLeft; + } else { + return nativeScrollElement.scrollTop; + } }; - return scrollerFactory; -}); + self.getScrollSize = function () { + if (transform) { + return slideeSize; + } + + if (o.horizontal) { + return nativeScrollElement.scrollWidth; + } else { + return nativeScrollElement.scrollHeight; + } + }; + + /** + * Initialize. + * + * @return {Object} + */ + self.init = function () { + if (self.initialized) { + return; + } + + if (!transform) { + if (o.horizontal) { + if (layoutManager.desktop && !o.hideScrollbar) { + nativeScrollElement.classList.add('scrollX'); + } else { + nativeScrollElement.classList.add('scrollX'); + nativeScrollElement.classList.add('hiddenScrollX'); + + if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { + nativeScrollElement.classList.add('smoothScrollX'); + } + } + + if (o.forceHideScrollbars) { + nativeScrollElement.classList.add('hiddenScrollX-forced'); + } + } else { + if (layoutManager.desktop && !o.hideScrollbar) { + nativeScrollElement.classList.add('scrollY'); + } else { + nativeScrollElement.classList.add('scrollY'); + nativeScrollElement.classList.add('hiddenScrollY'); + + if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { + nativeScrollElement.classList.add('smoothScrollY'); + } + } + + if (o.forceHideScrollbars) { + nativeScrollElement.classList.add('hiddenScrollY-forced'); + } + } + } else { + frame.style.overflow = 'hidden'; + slideeElement.style['will-change'] = 'transform'; + slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out'; + + if (o.horizontal) { + slideeElement.classList.add('animatedScrollX'); + } else { + slideeElement.classList.add('animatedScrollY'); + } + } + + if (transform || layoutManager.tv) { + // This can prevent others from being able to listen to mouse events + dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { + //passive: true + }); + } + + initFrameResizeObserver(); + + if (transform) { + dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { + passive: true + }); + + if (!o.horizontal) { + dom.addEventListener(frame, 'scroll', resetScroll, { + passive: true + }); + } + + if (o.mouseWheel) { + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + } + } else if (o.horizontal) { + // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively + + if (o.mouseWheel) { + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + } + } + + dom.addEventListener(frame, 'click', onFrameClick, { + passive: true, + capture: true + }); + + // Mark instance as initialized + self.initialized = 1; + + // Load + load(true); + + // Return instance + return self; + }; +}; + +/** + * Slide SLIDEE by amount of pixels. + * + * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.slideBy = function (delta, immediate) { + if (!delta) { + return; + } + this.slideTo(this._pos.dest + delta, immediate); +}; + +/** + * Core method for handling `toLocation` methods. + * + * @param {String} location + * @param {Mixed} item + * @param {Bool} immediate + * + * @return {Void} + */ +scrollerFactory.prototype.to = function (location, item, immediate) { + // Optional arguments logic + if (type(item) === 'boolean') { + immediate = item; + item = undefined; + } + + if (item === undefined) { + this.slideTo(this._pos[location], immediate); + } else { + var itemPos = this.getPos(item); + + if (itemPos) { + this.slideTo(itemPos[location], immediate, itemPos); + } + } +}; + +/** + * Animate element or the whole SLIDEE to the start of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toStart = function (item, immediate) { + this.to('start', item, immediate); +}; + +/** + * Animate element or the whole SLIDEE to the end of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toEnd = function (item, immediate) { + this.to('end', item, immediate); +}; + +/** + * Animate element or the whole SLIDEE to the center of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toCenter = function (item, immediate) { + this.to('center', item, immediate); +}; + +scrollerFactory.create = function (frame, options) { + var instance = new scrollerFactory(frame, options); + return Promise.resolve(instance); +}; + +export default scrollerFactory; From e7d6bc288956e3643c1c204bd413cf48725ccd17 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 22:25:14 +0200 Subject: [PATCH 39/58] Don't math twice --- src/libraries/scroller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 03c630651..ada13403b 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -229,15 +229,15 @@ var scrollerFactory = function (frame, options) { } } else if (!immediate && container.scrollTo) { if (o.horizontal) { - container.scrollTo(Math.Math.round(pos), 0); + container.scrollTo(Math.round(pos), 0); } else { - container.scrollTo(0, Math.Math.round(pos)); + container.scrollTo(0, Math.round(pos)); } } else { if (o.horizontal) { - container.scrollLeft = Math.Math.round(pos); + container.scrollLeft = Math.round(pos); } else { - container.scrollTop = Math.Math.round(pos); + container.scrollTop = Math.round(pos); } } } From 161488616f5dc789f54c8965f955a08d5402001a Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 22:28:52 +0200 Subject: [PATCH 40/58] Fix suggestions --- src/scripts/scrollHelper.js | 59 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 020888a2e..1ce30768a 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -98,40 +98,43 @@ function centerOnFocus(e, scrollSlider, horizontal) { function centerOnFocusHorizontal(e) { centerOnFocus(e, this, true); } + function centerOnFocusVertical(e) { centerOnFocus(e, this, false); } -export default { - getPosition: getPosition, - centerFocus: { - on: function (element, horizontal) { - if (horizontal) { - dom.addEventListener(element, 'focus', centerOnFocusHorizontal, { - capture: true, - passive: true - }); - } else { - dom.addEventListener(element, 'focus', centerOnFocusVertical, { - capture: true, - passive: true - }); - } - }, - off: function (element, horizontal) { - if (horizontal) { - dom.removeEventListener(element, 'focus', centerOnFocusHorizontal, { - capture: true, - passive: true - }); - } else { - dom.removeEventListener(element, 'focus', centerOnFocusVertical, { - capture: true, - passive: true - }); - } +export const centerFocus = { + on: function (element, horizontal) { + if (horizontal) { + dom.addEventListener(element, 'focus', centerOnFocusHorizontal, { + capture: true, + passive: true + }); + } else { + dom.addEventListener(element, 'focus', centerOnFocusVertical, { + capture: true, + passive: true + }); } }, + off: function (element, horizontal) { + if (horizontal) { + dom.removeEventListener(element, 'focus', centerOnFocusHorizontal, { + capture: true, + passive: true + }); + } else { + dom.removeEventListener(element, 'focus', centerOnFocusVertical, { + capture: true, + passive: true + }); + } + } +} + +export default { + getPosition: getPosition, + centerFocus: centerFocus, toCenter: toCenter, toStart: toStart }; From 453079fe068250dca9d4964ad15692e474b7b8af Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 22:48:26 +0200 Subject: [PATCH 41/58] Fix require --- src/components/guide/guide.js | 3 ++- src/scripts/scrollHelper.js | 4 +--- src/scripts/serverNotifications.js | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 50eb55f83..05fa2b608 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -4,7 +4,8 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', playbackManager = playbackManager.default || playbackManager; browser = browser.default || browser; loading = loading.default || loading; - focusManager = focusManager.default || focusManager;scrollHelper = scrollHelper.default || scrollHelper; + focusManager = focusManager.default || focusManager; + scrollHelper = scrollHelper.default || scrollHelper; serverNotifications = serverNotifications.default || serverNotifications; function showViewSettings(instance) { diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 1ce30768a..b86712368 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -3,7 +3,6 @@ import dom from 'dom'; import 'scrollStyles'; function getBoundingClientRect(elem) { - // Support: BlackBerry 5, iOS 3 (original iPhone) // If we don't have gBCR, just use 0,0 rather than error if (elem.getBoundingClientRect) { @@ -14,7 +13,6 @@ function getBoundingClientRect(elem) { } export function getPosition(scrollContainer, item, horizontal) { - const slideeOffset = getBoundingClientRect(scrollContainer); const itemOffset = getBoundingClientRect(item); @@ -130,7 +128,7 @@ export const centerFocus = { }); } } -} +}; export default { getPosition: getPosition, diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index 83af40c4e..2345cf327 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -15,12 +15,12 @@ function notifyApp() { function displayMessage(cmd) { const args = cmd.Arguments; if (args.TimeoutMs) { - require(['toast'], function (toast) { + import('toast').then((toast) => { toast({ title: args.Header, text: args.Text }); }); } else { - require(['alert'], function (alert) { - alert.default({ title: args.Header, text: args.Text }); + import('alert').then((alert) => { + alert({ title: args.Header, text: args.Text }); }); } } From 4e860a57286b517867b795ef2b0da7e1b538e743 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2020 21:12:22 +0000 Subject: [PATCH 42/58] Bump css-loader from 4.2.0 to 4.2.1 Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v4.2.0...v4.2.1) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index bf3ac42f9..146ee850d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "babel-loader": "^8.0.6", "browser-sync": "^2.26.12", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^4.2.0", + "css-loader": "^4.2.1", "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^7.6.0", diff --git a/yarn.lock b/yarn.lock index 3ec7f2148..ad55e1617 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3020,10 +3020,10 @@ css-has-pseudo@^0.10.0: postcss "^7.0.6" postcss-selector-parser "^5.0.0-rc.4" -css-loader@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.0.tgz#b57efb92ac8f0cd85bf92d89df9634ef1f51b8bf" - integrity sha512-ko7a9b0iFpWtk9eSI/C8IICvZeGtYnjxYjw45rJprokXj/+kBd/siX4vAIBq9Uij8Jubc4jL1EvSnTjCEwaHSw== +css-loader@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.1.tgz#9f48fd7eae1219d629a3f085ba9a9102ca1141a7" + integrity sha512-MoqmF1if7Z0pZIEXA4ZF9PgtCXxWbfzfJM+3p+OYfhcrwcqhaCRb74DSnfzRl7e024xEiCRn5hCvfUbTf2sgFA== dependencies: camelcase "^6.0.0" cssesc "^3.0.0" From 232ccb6b9fce607adf6bb4319f0bd6abd40ad48f Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Thu, 6 Aug 2020 23:26:51 +0200 Subject: [PATCH 43/58] Update src/scripts/serverNotifications.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/serverNotifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index 2345cf327..c84377c88 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -15,7 +15,7 @@ function notifyApp() { function displayMessage(cmd) { const args = cmd.Arguments; if (args.TimeoutMs) { - import('toast').then((toast) => { + import('toast').then(({default: toast}) => { toast({ title: args.Header, text: args.Text }); }); } else { From 915a423997914e09b020dc8cd8dfc15614518886 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Thu, 6 Aug 2020 23:26:59 +0200 Subject: [PATCH 44/58] Update src/scripts/serverNotifications.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/serverNotifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index c84377c88..2566d148f 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -19,7 +19,7 @@ function displayMessage(cmd) { toast({ title: args.Header, text: args.Text }); }); } else { - import('alert').then((alert) => { + import('alert').then(({default: alert}) => { alert({ title: args.Header, text: args.Text }); }); } From 878ab482cb43545ba7b91670baec1a8ba49a1759 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Thu, 6 Aug 2020 23:27:26 +0200 Subject: [PATCH 45/58] Update src/libraries/scroller.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/libraries/scroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index ada13403b..c460ec5b2 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -2,7 +2,7 @@ * and will be replaced soon by a Vue component. */ - import browser from 'browser'; +import browser from 'browser'; import layoutManager from 'layoutManager'; import dom from 'dom'; import focusManager from 'focusManager'; From 0872f3f0010263d48a3b546031d215a7f2c3c3b4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 09:27:11 +0100 Subject: [PATCH 46/58] Apply suggestions --- src/controllers/list.js | 49 +++++++++++++---------------- src/libraries/screensavermanager.js | 4 +-- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 979bb76a2..2c3b966c4 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -184,9 +184,9 @@ import 'emby-scroller'; const values = instance.getSortValues(); const sortBy = values.sortBy; - for (let i = 0, length = options.length; i < length; i++) { - if (sortBy === options[i].value) { - btnSortText.innerHTML = globalize.translate('SortByValue', options[i].name); + for (const option of options) { + if (sortBy === option.value) { + btnSortText.innerHTML = globalize.translate('SortByValue', option.name); break; } } @@ -407,18 +407,18 @@ import 'emby-scroller'; } function hideOrShowAll(elems, hide) { - for (let i = 0, length = elems.length; i < length; i++) { + for (const elem of elems) { if (hide) { - elems[i].classList.add('hide'); + elem.classList.add('hide'); } else { - elems[i].classList.remove('hide'); + elem.classList.remove('hide'); } } } function bindAll(elems, eventName, fn) { - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener(eventName, fn); + for (const elem of elems) { + elem.addEventListener(eventName, fn); } } @@ -724,16 +724,15 @@ class ItemsView { const btnViewSettings = view.querySelectorAll('.btnViewSettings'); - for (let i = 0, length = btnViewSettings.length; i < length; i++) { - btnViewSettings[i].addEventListener('click', showViewSettingsMenu.bind(this)); + for (const btnViewSetting of btnViewSettings) { + btnViewSetting.addEventListener('click', showViewSettingsMenu.bind(this)); } const filterButtons = view.querySelectorAll('.btnFilter'); this.filterButtons = filterButtons; const hasVisibleFilters = this.getVisibleFilters().length; - for (let i = 0, length = filterButtons.length; i < length; i++) { - const btnFilter = filterButtons[i]; + for (const btnFilter of filterButtons) { btnFilter.addEventListener('click', showFilterMenu.bind(this)); if (hasVisibleFilters) { @@ -744,10 +743,9 @@ class ItemsView { } const sortButtons = view.querySelectorAll('.btnSort'); - let i; - let length; - for (this.sortButtons = sortButtons, i = 0, length = sortButtons.length; i < length; i++) { - const sortButton = sortButtons[i]; + + this.sortButtons = sortButtons; + for (const sortButton of sortButtons) { sortButton.addEventListener('click', showSortMenu.bind(this)); if (params.type !== 'nextup') { @@ -886,10 +884,9 @@ class ItemsView { } getDefaultSortBy() { - const params = this.params; - const sortNameOption = this.getNameSortOption(params); + const sortNameOption = this.getNameSortOption(this.params); - if (params.type) { + if (this.params.type) { return sortNameOption.value; } @@ -898,16 +895,15 @@ class ItemsView { getSortMenuOptions() { const sortBy = []; - const params = this.params; - if (params.type === 'Programs') { + if (this.params.type === 'Programs') { sortBy.push({ name: globalize.translate('AirDate'), value: 'StartDate,SortName' }); } - let option = this.getNameSortOption(params); + let option = this.getNameSortOption(this.params); if (option) { sortBy.push(option); @@ -925,7 +921,7 @@ class ItemsView { sortBy.push(option); } - if (params.type !== 'Programs') { + if (this.params.type !== 'Programs') { sortBy.push({ name: globalize.translate('DateAdded'), value: 'DateCreated,SortName' @@ -938,8 +934,8 @@ class ItemsView { sortBy.push(option); } - if (!params.type) { - option = this.getNameSortOption(params); + if (!this.params.type) { + option = this.getNameSortOption(this.params); sortBy.push({ name: globalize.translate('Folders'), value: 'IsFolder,' + option.value @@ -1054,8 +1050,7 @@ class ItemsView { const filterButtons = this.filterButtons; if (filterButtons.length) { - for (let i = 0, length = filterButtons.length; i < length; i++) { - const btnFilter = filterButtons[i]; + for (const btnFilter of filterButtons) { let bubble = btnFilter.querySelector('.filterButtonBubble'); if (!bubble) { diff --git a/src/libraries/screensavermanager.js b/src/libraries/screensavermanager.js index 61d128ff1..5c24ec63d 100644 --- a/src/libraries/screensavermanager.js +++ b/src/libraries/screensavermanager.js @@ -34,9 +34,7 @@ function getScreensaverPlugin(isLoggedIn) { const plugins = pluginManager.ofType('screensaver'); - for (let i = 0, length = plugins.length; i < length; i++) { - const plugin = plugins[i]; - + for (const plugin of plugins) { if (plugin.id === option) { return plugin; } From 93151b883bb6ce5a34deadbbf3c80a038019df50 Mon Sep 17 00:00:00 2001 From: LapinoLapidus Date: Fri, 7 Aug 2020 09:20:37 +0000 Subject: [PATCH 47/58] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 860783b08..36b947855 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -161,7 +161,7 @@ "DeviceAccessHelp": "Dit geldt alleen voor apparaten die uniek geïdentificeerd kunnen worden en voorkomen niet toegang via een webbrowser. Filteren van apparaat toegang voor gebruikers voorkomt dat zij nieuwe apparaten gebruiken totdat deze hier zijn goedgekeurd.", "DirectPlaying": "Direct afspelen", "DirectStreamHelp1": "De resolutie en codec (bijv. H.264, AC3, etc.) wordt ondersteund door het apparaat, maar het medium is in een niet-ondersteunde bestandscontainer (bijv. mkv, avi, wmv). De video zal tijdens het afspelen opnieuw verpakt worden naar een andere bestandscontainer.", - "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processor kracht zonder verlies van beeldkwaliteit.", + "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processorkracht zonder verlies van beeldkwaliteit.", "DirectStreaming": "Direct streamen", "Director": "Regiseur", "Directors": "Regisseurs", @@ -378,7 +378,7 @@ "HeaderPreferredMetadataLanguage": "Gewenste metadata taal", "HeaderProfile": "Profiel", "HeaderProfileInformation": "Profiel Informatie", - "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe Jellyfin Server zich zal presenteren aan het apparaat.", + "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe de server zich zal presenteren aan het apparaat.", "HeaderRecentlyPlayed": "Recent afgespeeld", "HeaderRecordingOptions": "Opname instellingen", "HeaderRecordingPostProcessing": "Opname nabewerking", @@ -396,13 +396,13 @@ "HeaderSecondsValue": "{0} Seconden", "HeaderSelectCertificatePath": "Selecteer Certificaat Pad", "HeaderSelectMetadataPath": "Selecteer Metadata Pad", - "HeaderSelectMetadataPathHelp": "Blader of voer het pad in dat u wilt gebruiken om metadata in op te slaan. De map moet beschrijfbaar zijn.", + "HeaderSelectMetadataPathHelp": "Blader of voer het pad in dat u wilt gebruiken om metadata in op te slaan. De map moet schrijfbaar zijn.", "HeaderSelectPath": "Selecteer Pad", "HeaderSelectServer": "Selecteer server", "HeaderSelectServerCachePath": "Selecteer Server Cache Pad", "HeaderSelectServerCachePathHelp": "Bladeren of voer het pad in om te gebruiken voor server cache-bestanden. De map moet beschrijfbaar zijn.", "HeaderSelectTranscodingPath": "Selecteer Tijdelijke Transcodeer Pad", - "HeaderSelectTranscodingPathHelp": "Bladeren of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet beschrijfbaar zijn.", + "HeaderSelectTranscodingPathHelp": "Blader of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet schrijfbaar zijn.", "HeaderSendMessage": "Stuur bericht", "HeaderSeries": "Series", "HeaderSeriesOptions": "Series Opties", @@ -1255,7 +1255,7 @@ "HeaderGenres": "Genres", "HeaderHttpHeaders": "HTTP Headers", "HeaderStatus": "Status", - "AuthProviderHelp": "Selecteer een Authenticatie Provider om het wachtwoord van deze gebruiker te verifiëren.", + "AuthProviderHelp": "Selecteer een authenticatie provider om het wachtwoord van deze gebruiker te verifiëren.", "HeaderFavoriteMovies": "Favoriete Films", "HeaderFavoriteShows": "Favoriete shows", "HeaderFavoriteEpisodes": "Favoriete afleveringen", From c5f6b5057452d348e473074934498e67d0984d8b Mon Sep 17 00:00:00 2001 From: Tobias Hoos Date: Fri, 7 Aug 2020 13:22:37 +0000 Subject: [PATCH 48/58] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 1c87a95a9..0aa4d2172 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1542,5 +1542,8 @@ "ViewAlbumArtist": "Zeige Albumkünstler", "PreviousTrack": "Zum Vorherigen springen", "NextTrack": "Zum Nächsten springen", - "LabelUnstable": "Instabil" + "LabelUnstable": "Instabil", + "SubtitleVerticalPositionHelp": "Zeilennummer, in der der Text angezeigt wird. Positive Zahlen geben die Zeile von oben an. Negative Zahlen geben die Zeile von unten an.", + "Preview": "Vorschau", + "LabelSubtitleVerticalPosition": "Vertikale Position:" } From 5efd05617c7b9505302c5c1ab97e7d3339a454f9 Mon Sep 17 00:00:00 2001 From: 4d1m Date: Fri, 7 Aug 2020 15:04:04 +0000 Subject: [PATCH 49/58] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 58 +++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index f8aa98626..5489e92ab 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -61,9 +61,9 @@ "HeaderTaskTriggers": "Declanșatori Sarcini", "HeaderUsers": "Utilizatori", "Help": "Ajutor", - "ImportMissingEpisodesHelp": "Dacă este activată, informația despre episoadele lipsă va fi importată in baza de date Jellyfin și va fi afișată în cadrul serialelor. Aceasta poate cauza un timp semnificativ mai îndelungat la scanarea bibliotecilor.", + "ImportMissingEpisodesHelp": "Informația despre episoadele lipsă va fi importată în baza de date și va fi afișată în cadrul serialelor. Aceasta poate cauza un timp semnificativ mai îndelungat la scanarea bibliotecilor.", "LabelArtists": "Artisti:", - "LabelArtistsHelp": "Separare multiplă utilizând ;", + "LabelArtistsHelp": "Separară înșiruirea artiștilor utilizând ;", "LabelAudioLanguagePreference": "Preferințe de limbă pentru audio:", "LabelCachePath": "Cale pentru depozit:", "LabelCachePathHelp": "Specificați o locație specială pentru fișierele de tip depozit, precum imagini etc. Lasați gol pentru a folosi setarea implicită.", @@ -82,7 +82,7 @@ "LabelMetadataPath": "Cale pentru metadata:", "LabelMetadataPathHelp": "Specificați o locație specială pentru a descărca postere și metadata.", "LabelMinBackdropDownloadWidth": "Lățimea maximă pentru fundalurile descărcate:", - "LabelMovieRecordingPath": "Calea pentru înregistrări filme (opțional):", + "LabelMovieRecordingPath": "Calea pentru înregistrări filme:", "LabelName": "Nume:", "LabelNewPassword": "Parola nouă:", "LabelNewPasswordConfirm": "Confirmă parola nouă:", @@ -96,7 +96,7 @@ "LabelSaveLocalMetadata": "Salvează posterele si metadata în dosarele ce conțin fișierele media", "LabelSaveLocalMetadataHelp": "Salvând posterele și metadata direct in dosarele media, acestea vor fi mai accesibile pentru a fi modificate.", "LabelSelectUsers": "Selectare utilizatori:", - "LabelSeriesRecordingPath": "Calea pentru înregistrări seriale (opțional):", + "LabelSeriesRecordingPath": "Calea pentru înregistrări de seriale:", "LabelStopWhenPossible": "Oprește când este posibil:", "LabelTimeLimitHours": "Limită de timp(ore):", "LabelTranscodingTempPathHelp": "Specificați o cale specială pentru fișierele transcodate trimise clienților. Lasați gol pentru a folosi pe cea implicită în directorul de lucru al serverului.", @@ -133,7 +133,7 @@ "OptionDatePlayed": "Dată Rulare", "OptionDescending": "Descrescător", "OptionDisableUser": "Dezactivați acest utilizator", - "OptionDisableUserHelp": "Dacă este dezactivat, serverul nu va permite nicio conexiune de la acest utilizator. Conexiunile existente vor fi terminate brusc.", + "OptionDisableUserHelp": "Serverul nu va permite nici o conexiune de la acest utilizator. Conexiunile existente vor fi terminate brusc.", "OptionDislikes": "Dislike-uri", "OptionDownloadArtImage": "Fundal", "OptionDownloadBackImage": "Înapoi", @@ -419,7 +419,7 @@ "HeaderExternalIds": "ID-uri Externe:", "HeaderFavoriteBooks": "Cărți Favorite", "HeaderBranding": "Marca", - "HeaderApiKeysHelp": "Aplicațiile externe trebuie să aibă o cheie API pentru a comunica cu Jellyfin Server. Cheile sunt emise prin conectarea cu un cont Jellyfin sau prin acordarea manuală a unei chei aplicației.", + "HeaderApiKeysHelp": "Aplicațiile externe trebuie să aibă o cheie API pentru a comunica cu serverul. Cheile sunt emise prin conectarea cu un cont de utilizator sau prin acordarea manuală a unei chei aplicației.", "Sync": "Sincronizare", "ErrorAddingXmlTvFile": "A apărut o eroare la accesarea fișierului XMLTV. Vă rugăm să vă asigurați că fișierul există și încercați din nou.", "HeaderApiKey": "Cheie API", @@ -459,7 +459,7 @@ "HeaderMyMediaSmall": "Fișierele mele Media ( micșorat )", "HeaderNewApiKey": "Nouă cheie API", "HeaderNewDevices": "Dispozitive noi", - "HeaderKodiMetadataHelp": "Pentru a activa sau dezactiva metadatele NFO, editați o bibliotecă, în configurarea bibliotecii Jellyfin, și localizați secțiunea de salvare a metadatelor.", + "HeaderKodiMetadataHelp": "Pentru a activa sau dezactiva metadatele NFO, editați o bibliotecă, și localizați secțiunea de salvare a metadatelor.", "HeaderNextVideoPlayingInValue": "Următorul video se redă în {0}", "HeaderOnNow": "Pornit Acum", "HeaderOtherItems": "Alte Elemente", @@ -475,7 +475,7 @@ "HeaderPlaybackError": "Eroare la redare", "HeaderPluginInstallation": "Instalare Plugin", "HeaderProfileInformation": "Informații Profil", - "HeaderProfileServerSettingsHelp": "Aceste valori controlează modul în care Jellyfin Server va fi reprezentat in dispozitiv.", + "HeaderProfileServerSettingsHelp": "Aceste valori controlează modul în care serverul va fi reprezentat in dispozitivele clientilor.", "HeaderRecordingOptions": "Opțiuni Înregistrare", "HeaderRecordingPostProcessing": "Post procesarea înregistrării", "HeaderRemoveMediaFolder": "Eliminați Dosarul Media", @@ -614,7 +614,7 @@ "HeaderSelectServer": "Selectați Serverul", "HeaderSelectServerCachePath": "Selectați ruta pentru Server Cache", "HeaderSelectTranscodingPath": "Selectați ruta temporară pentru transcodare", - "HeaderSelectTranscodingPathHelp": "Căutați sau introduceți ruta dosarului de utilizat pentru transcodarea fișierelor temporare. Dosarul trebuie permisiuni de scriere.", + "HeaderSelectTranscodingPathHelp": "Căutați sau introduceți ruta dosarului de utilizat pentru transcodarea fișierelor. Dosarul trebuie permisiuni de scriere.", "HeaderSendMessage": "Trimite Mesaj", "HeaderSeriesOptions": "Opțiuni Seriale", "HeaderSeriesStatus": "Starea Serialelor", @@ -675,7 +675,7 @@ "LabelSeasonNumber": "Numărul sezonului:", "LabelScreensaver": "Protector de ecran:", "LabelScheduledTaskLastRan": "Ultima redare{0}, cu durata {1}.", - "LabelRuntimeMinutes": "Timp de redare (minute):", + "LabelRuntimeMinutes": "Timp de redare:", "LabelRemoteClientBitrateLimitHelp": "O limită de biți per-stream opțională pentru toate dispozitivele din rețea. Acest lucru este util pentru a împiedica dispozitivele să solicite un bitrate mai mare decât poate gestiona conexiunea dvs. de internet. Acest lucru poate duce la creșterea încărcării procesorului pe serverul dvs. pentru a transcoda videoclipurile din zbor la un bitrate mai mic.", "LabelRemoteClientBitrateLimit": "Limită de biți pentru streaming pe Internet (Mbps):", "LabelReleaseDate": "Data lansării:", @@ -720,7 +720,7 @@ "LabelOriginalTitle": "Titlu original:", "LabelOriginalAspectRatio": "Raport aspect original:", "LabelOptionalNetworkPathHelp": "Dacă acest folder este partajat în rețeaua dvs., furnizarea căii de partajare a rețelei poate permite aplicațiilor Jellyfin de pe alte dispozitive să acceseze fișiere media direct.", - "LabelOptionalNetworkPath": "(Optional) Dosar partajat în rețea:", + "LabelOptionalNetworkPath": "Dosar partajat în rețea:", "LabelNumber": "Număr:", "LabelNotificationEnabled": "Activează această notificare", "LabelNewsCategories": "Categoriile știrilor:", @@ -741,7 +741,7 @@ "LabelMinResumeDurationHelp": "Cea mai scurtă lungime video în secunde, care va salva locația de redare și vă va permite să reluați.", "LabelMinResumeDuration": "Durata minimă a reluării:", "LabelMethod": "Metoda:", - "LabelMetadataSaversHelp": "Alegeți formatele de fișiere pentru a vă salva metadatele.", + "LabelMetadataSaversHelp": "Alegeți formatele de fișiere pentru salvarea metadatele.", "LabelMetadataSavers": "Salvări de metadate:", "LabelMetadataReadersHelp": "Clasificați sursele preferate de metadate locale în ordinea priorității. Primul fișier găsit va fi citit.", "LabelMetadataReaders": "Cititori de metadate:", @@ -761,7 +761,7 @@ "LabelLoginDisclaimerHelp": "Un mesaj care va fi afișat în partea de jos a paginii de conectare.", "LabelLoginDisclaimer": "Act de renunțare la autentificare:", "LabelLockItemToPreventChanges": "Blocați acest element pentru a preveni modificările viitoare", - "LabelLocalHttpServerPortNumberHelp": "Portul TCP pe care serverul HTTP Jellyfin ar trebui să îl utilizeze.", + "LabelLocalHttpServerPortNumberHelp": "Portul TCP pentru serverul HTTP.", "LabelLocalHttpServerPortNumber": "Portul local HTTP:", "LabelLineup": "Echipa:", "LabelLanNetworks": "Rețele LAN:", @@ -788,7 +788,7 @@ "LabelIconMaxWidth": "Lățimea maximă a pictogramei:", "LabelIconMaxHeightHelp": "Rezoluția maximă a pictogramelor expuse via upnp:icon.", "LabelIconMaxHeight": "Înălțimea maximă a pictogramei:", - "LabelHttpsPortHelp": "Portul TCP pe care serverul HTTPS Jellyfin ar trebui sa îl utilizeze.", + "LabelHttpsPortHelp": "Portul TCP pentru serverul HTTPS.", "LabelHttpsPort": "Portul local HTTPS:", "LabelHomeScreenSectionValue": "Secțiunea ecranului de pornire {0}:", "LabelHomeNetworkQuality": "Calitatea pe rețeaua de domiciliu:", @@ -816,7 +816,7 @@ "LabelEndDate": "Data de încheiere:", "LabelEnableSingleImageInDidlLimitHelp": "Unele dispozitive nu vor reda corect dacă mai multe imagini sunt încorporate în Didl.", "LabelEnableSingleImageInDidlLimit": "Limitați la o singură imagine încorporată", - "LabelEnableRealtimeMonitorHelp": "Modificările la fișiere vor fi procesate imediat, pe sistemele de fișiere acceptate.", + "LabelEnableRealtimeMonitorHelp": "Modificările la fișiere vor fi procesate imediat pe sistemele de fișiere acceptate.", "LabelEnableRealtimeMonitor": "Activați monitorizarea în timp real", "LabelEnableHardwareDecodingFor": "Activați decodarea hardware pentru:", "LabelEnableDlnaServerHelp": "Permite dispozitivelor UPnP din rețeaua dvs. să răsfoiască și să redea conținut.", @@ -826,7 +826,7 @@ "LabelEnableDlnaDebugLoggingHelp": "Creați fișiere de jurnal mari și trebuie utilizate numai în funcție de necesități pentru rezolvarea problemelor.", "LabelEnableDlnaDebugLogging": "Activați jurnalul de depanare DLNA", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determină durata în secunde între căutările SSDP efectuate de Jellyfin.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval de descoperire a clientului (secunde)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval de descoperire a clientului", "LabelEnableBlastAliveMessagesHelp": "Activați acest lucru dacă serverul nu este detectat în mod fiabil de alte dispozitive UPnP din rețeaua dvs.", "LabelEnableBlastAliveMessages": "Trimitere mesaje de disponibilitate", "LabelEnableAutomaticPortMapHelp": "Încercați să mapați automat portul public către portul local prin UPnP. Este posibil să nu funcționeze cu unele modele de router. Modificările nu se vor aplica decât după repornirea serverului.", @@ -874,11 +874,11 @@ "LabelBurnSubtitles": "Imprimă subtitrările:", "LabelBlockContentWithTags": "Blochează articolele cu etichetele:", "LabelBlastMessageIntervalHelp": "Determină durata în secunde între transmiterea mesajele de viață.", - "LabelBlastMessageInterval": "Interval transmitere mesaj viu (secunde)", + "LabelBlastMessageInterval": "Interval transmitere mesaj viu", "LabelBitrate": "Rată de biți:", "LabelBirthYear": "Anul nașterii:", "LabelBirthDate": "Data nașterii:", - "LabelBindToLocalNetworkAddressHelp": "Opțional. Rescrie adresa IP locală pentru a o utiliza serverul http. Dacă este lăsat gol, serverul se va lega la toate adresele disponibile. Modificarea acestei valori necesită repornirea Jellyfin Server.", + "LabelBindToLocalNetworkAddressHelp": "Rescrie adresa IP locală a serverului http. Dacă este lăsat gol, serverul se va lega la toate adresele disponibile. Modificarea acestei valori necesită repornirea Jellyfin Server.", "LabelBindToLocalNetworkAddress": "Utilizează adresa de rețea locală:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizați automat metadatele de pe internet:", "LabelAuthProvider": "Furnizor de autentificare:", @@ -917,7 +917,7 @@ "ItemCount": "{0} articole", "InstantMix": "Mix instant", "InstallingPackage": "Instalare {0} (versiune {1})", - "ImportFavoriteChannelsHelp": "Dacă este activat, vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", + "ImportFavoriteChannelsHelp": "Vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", "Images": "Imagini", "Identify": "Identifică", "HttpsRequiresCert": "Pentru a activa conexiunile securizate, va trebui să furnizați un certificat SSL de încredere, cum ar fi Let's Encrypt. Vă rugăm să furnizați un certificat sau să dezactivați conexiunile securizate.", @@ -969,14 +969,14 @@ "OptionBlockChannelContent": "Conținut canal Internet", "OptionBlockBooks": "Cărți", "OptionBanner": "Steag", - "OptionAutomaticallyGroupSeriesHelp": "Dacă este activat, seriile distribuite pe mai multe foldere din această bibliotecă vor fi comasate automat într-o singură serie.", + "OptionAutomaticallyGroupSeriesHelp": "Seriile distribuite pe mai multe foldere din această bibliotecă vor fi comasate automat într-o singură serie.", "OptionAutomaticallyGroupSeries": "Fuzionează automat seriile care sunt răspândite pe mai multe foldere", "OptionAuto": "Auto", "OptionArtist": "Artist", "OptionAllowVideoPlaybackTranscoding": "Permiteți redarea video care necesită transcodare", "OptionAllowVideoPlaybackRemuxing": "Permiteți redarea video care necesită conversie fără re-codificare", "OptionAllowSyncTranscoding": "Permiteți descărcarea și sincronizarea media care necesită transcodare", - "OptionAllowMediaPlaybackTranscodingHelp": "Restrângerea accesului la transcodare poate provoca defecțiuni de redare în aplicațiile Jellyfin din cauza formatelor media neacceptate.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restrângerea accesului la transcodare poate provoca defecțiuni de redare în aplicațiile client din cauza formatelor media neacceptate.", "OptionAllowContentDownloading": "Permiteți descărcarea și sincronizarea media", "OptionAllowAudioPlaybackTranscoding": "Permiteți redarea audio care necesită transcodare", "OptionAllUsers": "Toți utilizatorii", @@ -1113,7 +1113,7 @@ "LatestFromLibrary": "Ultimele {0}", "Large": "Mare", "LanNetworksHelp": "Lista separată de virgule a adreselor IP sau a intrărilor de tip IP/mască de rețea pentru rețelele care vor fi luate în considerare în rețeaua locală atunci când se aplică restricțiile de lățime de bandă. Dacă este setat, toate celelalte adrese IP vor fi considerate a fi în rețeaua externă și vor fi supuse restricțiilor de lățime de bandă externe. Dacă este lăsat necompletat, numai subnetul serverului este considerat a fi în rețeaua locală.", - "LabelffmpegPathHelp": "Calea către executabilul ffmpeg, sau dosarul care conține ffmpeg.", + "LabelffmpegPathHelp": "Calea către executabilul ffmpeg sau dosarul care conține ffmpeg.", "LabelffmpegPath": "Calea către FFmpeg:", "LabelZipCode": "Cod poștal:", "LabelYear": "Anul:", @@ -1300,9 +1300,9 @@ "OptionProfileAudio": "Audio", "OptionPosterCard": "Carte de afiș", "OptionPoster": "Afiș", - "OptionPlainVideoItemsHelp": "Dacă este activat, toate videoclipurile sunt reprezentate în DIDL ca „object.item.videoItem” în loc de un tip mai specific, cum ar fi „object.item.videoItem.movie”.", + "OptionPlainVideoItemsHelp": "Toate videoclipurile sunt reprezentate în DIDL ca „object.item.videoItem” în loc de un tip mai specific, cum ar fi „object.item.videoItem.movie”.", "OptionPlainVideoItems": "Afișați toate videoclipurile ca elemente video simple", - "OptionPlainStorageFoldersHelp": "Dacă este activat, toate folderele sunt reprezentate în DIDL ca „object.container.storageFolder” în loc de un tip mai specific, cum ar fi „object.container.person.musicArtist”.", + "OptionPlainStorageFoldersHelp": "Toate dosarele sunt reprezentate în DIDL ca „object.container.storageFolder” în loc de un tip mai specific, cum ar fi „object.container.person.musicArtist”.", "OptionPlainStorageFolders": "Afișați toate dosarele ca dosare simple de stocare", "OptionOnInterval": "La un interval", "OptionOnAppStartup": "La pornirea aplicației", @@ -1315,7 +1315,7 @@ "OptionList": "Listă", "OptionIsSD": "SD", "OptionIsHD": "HD", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Dacă sunt activate, aceste solicitări vor fi respectate, dar vor ignora antetul intervalului de octeți.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Aceste solicitări vor fi respectate, dar vor ignora antetul intervalului de octeți.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorați solicitările pentru transcodarea intervalului de octeți", "OptionHomeVideos": "Fotografii", "OptionHlsSegmentedSubtitles": "Subtitrare segmentată HLS", @@ -1332,7 +1332,7 @@ "OptionEnableExternalContentInSuggestions": "Activați conținut extern în sugestii", "OptionEmbedSubtitles": "Inclus în container", "OptionDownloadLogoImage": "Siglă", - "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații din Jellyfin. Activați această opțiune pentru a descărca în prealabil toate imaginile, pe măsură ce fișierele media sunt importate. Acest lucru poate provoca scanări ale bibliotecii semnificativ mai lungi.", + "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații Jellyfin. Activați această opțiune pentru a descărca în avans toate imaginile, pe măsură ce fișiere media noi sunt importate. Acest lucru poate duce la mărirea semnificativă a timpilor de scanare a bibliotecii.", "OptionDownloadImagesInAdvance": "Descărcați imaginile în avans", "OptionDownloadDiscImage": "Disc", "OptionDisplayFolderViewHelp": "Afișați dosarele alături de celelalte biblioteci media. Acest lucru poate fi util dacă doriți să aveți o vizualizare direct în dosar.", @@ -1476,7 +1476,7 @@ "LabelRequireHttps": "Trebuie HTTPS", "LabelStable": "Stabilă", "LabelChromecastVersion": "Versiunea de Chromecast", - "LabelEnableHttpsHelp": "Activează serverul să asculte pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", + "LabelEnableHttpsHelp": "Ascultă pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", "LabelEnableHttps": "Activați HTTPS", "HeaderServerAddressSettings": "Setările adresei serverului", "HeaderRemoteAccessSettings": "Setări pentru aces remote", @@ -1539,5 +1539,7 @@ "LabelRepositoryNameHelp": "Un nume personalizat pentru a distinge acest repertoriu de altele adăugate la serverul dvs.", "ClearQueue": "Golește lista de redare", "StopPlayback": "Oprește redarea", - "ViewAlbumArtist": "Vezi artistul albumului" + "ViewAlbumArtist": "Vezi artistul albumului", + "NextTrack": "Sari la următorul", + "LabelUnstable": "Instabil" } From da7cdda13217ddd47121d5ff59447ad3db0486d7 Mon Sep 17 00:00:00 2001 From: 4d1m Date: Fri, 7 Aug 2020 15:22:47 +0000 Subject: [PATCH 50/58] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index 5489e92ab..a0f090676 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -1217,7 +1217,7 @@ "ReleaseDate": "Data lansării", "RefreshQueued": "Actualizare adăugată în coadă.", "RefreshMetadata": "Actualizați metadatele", - "RefreshDialogHelp": "Metadatele sunt actualizate pe baza setărilor și a serviciilor de internet care sunt activate în tabloul de bord Jellyfin Server.", + "RefreshDialogHelp": "Metadatele sunt actualizate pe baza setărilor și a serviciilor de internet care sunt activate în tabloul de bord.", "Refresh": "Reîmprospătează", "Recordings": "Înregistrări", "RecordingScheduled": "Înregistrare programată.", @@ -1263,7 +1263,7 @@ "PerfectMatch": "Potrivire perfectă", "People": "Oameni", "PasswordSaved": "Parolă salvată.", - "PasswordResetProviderHelp": "Alegeți un furnizor de resetare a parolei pentru a fi utilizat atunci când acest utilizator solicită o resetare a parolei", + "PasswordResetProviderHelp": "Alegeți un furnizor de resetare a parolei pentru a fi utilizat atunci când acest utilizator solicită o resetare a parolei.", "HeaderResetPassword": "Resetează parola", "PasswordResetConfirmation": "Sigur doriți să resetați parola?", "PasswordResetComplete": "Parola a fost resetată.", @@ -1541,5 +1541,9 @@ "StopPlayback": "Oprește redarea", "ViewAlbumArtist": "Vezi artistul albumului", "NextTrack": "Sari la următorul", - "LabelUnstable": "Instabil" + "LabelUnstable": "Instabil", + "Preview": "Previzualizare", + "SubtitleVerticalPositionHelp": "Numărul de linie unde apare textul. Numerele pozitive indică de sus în jos. Numerele negative indică de jos în sus.", + "LabelSubtitleVerticalPosition": "Poziție verticală:", + "PreviousTrack": "Sari anterior" } From 406e07154c048c1541bf97521b17445bf5ae7c27 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 7 Aug 2020 13:18:21 -0400 Subject: [PATCH 51/58] Hide download button for books when not supported --- src/controllers/itemDetails/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index df2855d69..b2cdc6323 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1,3 +1,4 @@ +import appHost from 'apphost'; import loading from 'loading'; import appRouter from 'appRouter'; import layoutManager from 'layoutManager'; @@ -657,7 +658,7 @@ import 'emby-select'; setPeopleHeader(page, item); loading.hide(); - if (item.Type === 'Book') { + if (item.Type === 'Book' && item.CanDownload && appHost.supports('filedownload')) { hideAll(page, 'btnDownload', true); } From 496fb43cb5ac655b6a0cd4a9de4f2b5e0f760d95 Mon Sep 17 00:00:00 2001 From: Thomas Schwery Date: Fri, 7 Aug 2020 20:29:10 +0000 Subject: [PATCH 52/58] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 6d8fb68ec..20b96f3a5 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1542,5 +1542,8 @@ "ViewAlbumArtist": "Voir l'album de l'artiste", "PreviousTrack": "Revenir au précédent", "NextTrack": "Passer au prochain", - "LabelUnstable": "Instable" + "LabelUnstable": "Instable", + "Preview": "Aperçu", + "SubtitleVerticalPositionHelp": "Numéro de ligne où le texte apparaît. Un nombre positif compte les lignes de haut en bas. Un nombre négatif, de bas en haut.", + "LabelSubtitleVerticalPosition": "Position verticale :" } From dbb6cd97301cc111ae54f500e683d88759b579c4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 22:46:12 +0100 Subject: [PATCH 53/58] Lint --- src/libraries/scroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 4bfcb7d5b..c460ec5b2 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -883,4 +883,4 @@ scrollerFactory.create = function (frame, options) { return Promise.resolve(instance); }; -export default scrollerFactory; \ No newline at end of file +export default scrollerFactory; From b512187129cfb841bc0b1b8556616150ff4fef16 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Fri, 7 Aug 2020 22:09:25 +0000 Subject: [PATCH 54/58] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sl/ --- src/strings/sl-si.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index 423ee7797..bf9ec2137 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -1429,5 +1429,7 @@ "OptionEnableM2tsModeHelp": "Omogoči m2ts način pri kodiranju v mpegts.", "OptionEnableM2tsMode": "Omogoči M2ts način", "OptionDisplayFolderViewHelp": "Prikaže mape poleg ostalih knjižnic predstavnosti. Uporabno za preprost ogled map.", - "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti" + "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti", + "Yesterday": "Včeraj", + "Yes": "Da" } From 28d34b1bae8f8b77458fb98c11f134c8fed1ad52 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 23:13:54 +0100 Subject: [PATCH 55/58] Update src/controllers/list.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/controllers/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 2c3b966c4..0027c576f 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -399,7 +399,7 @@ import 'emby-scroller'; const instance = this; import('playlistEditor').then(({default: playlistEditor}) => { - new playlistEditor.showEditor({ + new playlistEditor({ items: [], serverId: instance.params.serverId }); From b3166dd2c4132d07ec2436d7abc4efa052b016ec Mon Sep 17 00:00:00 2001 From: SaddFox Date: Fri, 7 Aug 2020 22:20:55 +0000 Subject: [PATCH 56/58] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sl/ --- src/strings/sl-si.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index bf9ec2137..077ea6780 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -1431,5 +1431,21 @@ "OptionDisplayFolderViewHelp": "Prikaže mape poleg ostalih knjižnic predstavnosti. Uporabno za preprost ogled map.", "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti", "Yesterday": "Včeraj", - "Yes": "Da" + "Yes": "Da", + "RecommendationStarring": "Nastopa {0}", + "Recordings": "Posnetki", + "RemoveFromCollection": "Odstrani iz zbirke", + "ResumeAt": "Nadaljuj od {0}", + "SaveSubtitlesIntoMediaFolders": "Shrani podnapise v mape predstavnosti", + "ScanForNewAndUpdatedFiles": "Poišči nove in spremenjene datoteke", + "Screenshot": "Posnetek zaslona", + "Screenshots": "Posnetki zaslona", + "Search": "Iskanje", + "ShowAdvancedSettings": "Prikaži napredne nastavitve", + "New": "Novo", + "SubtitleOffset": "Zamik podnapisev", + "Subtitles": "Podnapisi", + "Sunday": "Nedelja", + "TabAdvanced": "Napredno", + "TabAlbums": "Albumi" } From f34a64a607c024bea5df3fd8fac00ed68768ab85 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2020 11:13:21 +0000 Subject: [PATCH 57/58] Bump whatwg-fetch from 3.3.1 to 3.4.0 Bumps [whatwg-fetch](https://github.com/github/fetch) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/github/fetch/releases) - [Commits](https://github.com/github/fetch/compare/v3.3.1...v3.4.0) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ac2ad86e5..d7cd3b7f3 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "sortablejs": "^1.10.2", "swiper": "^5.4.5", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.3.1" + "whatwg-fetch": "^3.4.0" }, "babel": { "presets": [ diff --git a/yarn.lock b/yarn.lock index ad55e1617..fd0245816 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11994,10 +11994,10 @@ webworkify@^1.5.0: resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c" integrity sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g== -whatwg-fetch@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" - integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== +whatwg-fetch@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.0.tgz#e11de14f4878f773fbebcde8871b2c0699af8b30" + integrity sha512-rsum2ulz2iuZH08mJkT0Yi6JnKhwdw4oeyMjokgxd+mmqYSd9cPpOQf01TIWgjxG/U4+QR+AwKq6lSbXVxkyoQ== which-module@^1.0.0: version "1.0.0" From 93f23c3d32d85899406e699a66b1e40e19968899 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2020 11:18:26 +0000 Subject: [PATCH 58/58] Bump hls.js from 0.14.7 to 0.14.8 Bumps [hls.js](https://github.com/video-dev/hls.js) from 0.14.7 to 0.14.8. - [Release notes](https://github.com/video-dev/hls.js/releases) - [Changelog](https://github.com/video-dev/hls.js/blob/master/docs/release-process.md) - [Commits](https://github.com/video-dev/hls.js/compare/v0.14.7...v0.14.8) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ac2ad86e5..9f3b1d949 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", - "hls.js": "^0.14.7", + "hls.js": "^0.14.8", "howler": "^2.2.0", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index ad55e1617..bfe52b7f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5465,10 +5465,10 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hls.js@^0.14.7: - version "0.14.7" - resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.7.tgz#47fbd2662b13121ab17c07aea06b1c07828240cf" - integrity sha512-9JY0D9nwMrfQPRWc8/kEJTKK0TYfDTzIs6Xq+gdCvasRxdvQKQ2T76rdueTkS0AsFV6sQlJN0wxbnI44aRvvUA== +hls.js@^0.14.8: + version "0.14.8" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.8.tgz#c2c6ca7005524c81eece316c2a4a199258bd0590" + integrity sha512-4fh8k/sl1SmYXsT4Om8AY5fKa5tUUtAxup2sffrSMh5MNk4Kt4FOZxbjqTGL5VwkroY1oJ9twSciNQNFbPA/WQ== dependencies: eventemitter3 "^4.0.3" url-toolkit "^2.1.6"