/** * * (c) Copyright Ascensio System SIA 2020 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ let start = Date.now(); let isPluginLoading = false; // flag plugins loading const isDesktop = window.AscDesktopEditor !== undefined; // desktop detecting let isOnline = true; // flag internet connection isDesktop && checkInternet(); // check internet connection (only for desktop) let interval = null; // interval for checking internet connection (if it doesn't work on launch) const OOMarketplaceUrl = 'https://onlyoffice.github.io/'; // url to oficial store (for local version store in desktop) let current = {index: 0, screenshots: [], url: ''}; // selected plugin (for plugin view) let searchTimeout = null; // timeot for search let founded = []; // last founded elemens (for not to redraw if a result is the same) let catFiltred = []; // plugins are filtred by caterogy (used for search) let updateCount = 0; // counter for plugins in updating process let allPlugins; // list of all plugins from config let installedPlugins; // list of intalled plugins const configUrl = './config.json'; // url to config.json const elements = {}; // all elements const guidMarkeplace = 'asc.{AA2EA9B6-9EC2-415F-9762-634EE8D9A95E}'; // guid marketplace const guidSettings = 'asc.{8D67F3C5-7736-4BAE-A0F2-8C7127DC4BB8}'; // guid settings plugins let editorVersion = null; // edior current version let loader; // loader let themeType = detectThemeType(); // current theme const lang = detectLanguage(); // current language const shortLang = lang.split('-')[0]; // short language let bTranslate = false; // flag translate or not let isTranslationLoading = false; // flag translation loading let isFrameLoading = true; // flag window loading let translate = {'Loading': 'Loading'}; // translations for current language (thouse will necessary if we don't get tranlation file) let timeout = null; // delay for loader let defaultBG = themeType == 'light' ? "#F5F5F5" : '#555555'; // default background color for plugin header let isResizeOnStart = true; // flag for firs resize on start const supportedScaleValues = [1, 1.25, 1.5, 1.75, 2]; // supported scale let scale = { // current scale percent : "100%", // current scale in percent value : 1, // current scale value devicePR : 1 // device pixel ratio }; calculateScale(); const languages = [ // list of languages ['cs-CZ', 'cs', 'Czech'], ['de-DE', 'de', 'German'], ['es-ES', 'es', 'Spanish'], ['fr-FR', 'fr', 'French'], ['it-IT', 'it', 'Italian'], ['ja-JA', 'ja', 'Japanese'], ['nl-NL', 'nl', 'Dutch'], ['pt-PT', 'pt', 'Portuguese'], ['ru-RU', 'ru', 'Russian'], ['zh-ZH', 'zh', 'Chinese'] ]; const messages = { versionWarning: 'This plugin will only work in a newer version of the editor.', linkManually: 'Install plugin manually', linkPR: 'Submit your own plugin', }; const isIE = (navigator.userAgent.toLowerCase().indexOf("msie") > -1 || navigator.userAgent.toLowerCase().indexOf("trident") > -1 || navigator.userAgent.toLowerCase().indexOf("edge") > -1); // it's necessary because we show loader before all (and getting translations too) switch (shortLang) { case 'ru': translate["Loading"] = "Загрузка" break; case 'fr': translate["Loading"] = "Chargement" break; case 'es': translate["Loading"] = "Carga" break; case 'de': translate["Loading"] = "Laden" break; case 'cs': translate["Loading"] = "Načítání" break; } // it's necessary for loader (because it detects theme by this object) window.Asc = { plugin : { theme : { type : themeType } } }; const pos = location.href.indexOf('store/index.html'); const ioUrl = location.href.substring(0, pos); const bAppDirectory = getUrlSearchValue('type').length !== 0; // if we have type, the we work in appdirectory // get translation file getTranslation(); // fetch all plugins from config if (!isDesktop) fetchAllPlugins(true, false); window.onload = function() { let rule = '\n.asc-plugin-loader{background-color:' + (themeType == 'light' ? '#ffffff' : '#333333') + ';padding: 10px;display: flex;justify-content: center;align-items: center;border-radius: 5px;}\n' rule += '.asc-plugin-loader{color:' + (themeType == 'light' ? '#444444' : 'rgba(255,255,255,0.8)') + '}\n'; let styleTheme = document.createElement('style'); styleTheme.type = 'text/css'; styleTheme.innerHTML = rule; document.getElementsByTagName('head')[0].appendChild(styleTheme); if (isPluginLoading || isTranslationLoading) { toogleLoader(true, "Loading"); } // init element initElemnts(); if (isIE) elements.imgScreenshot.classList.remove('image_preview'); isFrameLoading = false; if (shortLang == "en" || (!isPluginLoading && !isTranslationLoading)) { // if nothing to translate showMarketplace(); } elements.btnMyPlugins.onclick = function(event) { // click on my plugins button toogleView(event.target, elements.btnMarketplace, messages.linkManually, false, false); }; elements.btnMarketplace.onclick = function(event) { // click on marketplace button toogleView(event.target, elements.btnMyPlugins, messages.linkPR, true, false); }; elements.arrow.onclick = onClickBack; // elements.imgScreenshot.onclick = onClickScreenshot; elements.arrowPrev.onclick = function(event) { event.preventDefault(); event.stopPropagation(); if (current.index > 0) { // todo maybe show loader current.index--; let url = current.url + current.screenshots[current.index]; elements.imgScreenshot.setAttribute('src', url); elements.imgScreenshot.onload = function() { elements.arrowNext.classList.remove('hidden'); // todo maybe hide loader } if (!current.index) elements.arrowPrev.classList.add('hidden'); } }; elements.arrowNext.onclick = function(event) { event.preventDefault(); event.stopPropagation(); if (current.index < current.screenshots.length - 1) { // todo maybe show loader current.index++; let url = current.url + current.screenshots[current.index]; elements.imgScreenshot.setAttribute('src', url); elements.imgScreenshot.onload = function() { elements.arrowPrev.classList.remove('hidden'); // todo maybe hide loader } if (current.index == current.screenshots.length - 1) elements.arrowNext.classList.add('hidden'); } }; // elements.divArrow.onclick = onClickScreenshot; elements.inpSearch.addEventListener('input', function(event) { makeSearch(event.target.value.trim().toLowerCase()); }); }; window.addEventListener('message', function(message) { // getting messages from editor or plugin message = JSON.parse(message.data); let plugin; let installed; switch (message.type) { case 'InstalledPlugins': if (message.data) { // filter installed plugins (delete removed, that are in store and some system plugins) installedPlugins = message.data.filter(function(el) { return (el.guid !== guidMarkeplace && el.guid !== guidSettings && !( el.removed && el.obj.baseUrl.includes(ioUrl) )); }); sortPlugins(false, true, 'start'); } else { installedPlugins = []; } // console.log('getInstalledPlugins: ' + (Date.now() - start)); if (message.updateInstalled) showListofPlugins(false); else if ( allPlugins || (isDesktop && !isOnline) ) getAllPluginsData(true, false); break; case 'Installed': if (!message.guid) { // somethimes we can receive such message toogleLoader(false); return; } plugin = findPlugin(true, message.guid); installed = findPlugin(false, message.guid); if (!installed && plugin) { installedPlugins.push( { baseUrl: plugin.url, guid: message.guid, canRemoved: true, obj: plugin, removed: false } ); // sortPlugins(false, true, 'name'); } else if (installed) { if (installed.obj.backup) { // нужно обновить список установленных плагинов, чтобы ссылки на ресурсы были правильными sendMessage({ type: 'getInstalled', updateInstalled: true }, '*'); } else installed.removed = false; } changeAfterInstallOrRemove(true, message.guid); toogleLoader(false); break; case 'Updated': updateCount--; if (!message.guid) { // somethimes we can receive such message if (!updateCount) toogleLoader(false); return; } installed = findPlugin(false, message.guid); plugin = findPlugin(true, message.guid); installed.obj.version = plugin.version; plugin.bHasUpdate = false; if (!elements.divSelected.classList.contains('hidden')) { this.document.getElementById('btn_update').classList.add('hidden'); } elements.spanVersion.innerText = plugin.version; let pluginDiv = this.document.getElementById(message.guid); if (pluginDiv) pluginDiv.lastChild.firstChild.remove(); if (!updateCount) toogleLoader(false); break; case 'Removed': if (!message.guid) { // somethimes we can receive such message toogleLoader(false); return; } let bUpdate = false; let bHasLocal = false; let needBackup = message.backup; plugin = findPlugin(true, message.guid); installed = findPlugin(false, message.guid); if (installed) { bHasLocal = !installed.obj.baseUrl.includes(ioUrl); if (plugin && (!bHasLocal || (isDesktop && !needBackup) ) ) { installedPlugins = installedPlugins.filter(function(el){return el.guid !== message.guid}); bUpdate = true; } else { installed.removed = true; // нужно обновить список установленных плагинов, чтобы ссылки на ресурсы были правильными if (isDesktop) sendMessage({ type: 'getInstalled', updateInstalled: true }, '*'); } } if (elements.btnMyPlugins.classList.contains('btn_toolbar_active')) { if (bUpdate) { catFiltred = installedPlugins; let searchVal = elements.inpSearch.value.trim(); if (searchVal !== '') makeSearch(searchVal.toLowerCase()); else this.document.getElementById(message.guid).remove(); } else { changeAfterInstallOrRemove(false, message.guid, bHasLocal); } } else { changeAfterInstallOrRemove(false, message.guid, bHasLocal); } toogleLoader(false); break; case 'Error': createError(message.error); toogleLoader(false); break; case 'Theme': if (message.theme.type) themeType = message.theme.type; let rule = 'a{color:'+message.theme.DemTextColor+'!important;}\na:hover{color:'+message.theme.DemTextColor+'!important;}\na:active{color:'+message.theme.DemTextColor+'!important;}\na:visited{color:'+message.theme.DemTextColor+'!important;}\n'; if (themeType.includes('light')) { this.document.getElementsByTagName('body')[0].classList.add('white_bg'); rule += '.btn_install{background-color: #444 !important; color: #fff !important}\n'; rule += '.btn_install:hover{background-color: #1c1c1c !important;}\n'; rule += '.btn_install:active{background-color: #446995 !important;}\n'; rule += '.btn_remove:active{background-color: #293f59 !important; color: #fff !important}\n'; rule += '.div_offered{color: rgba(0,0,0,0.45); !important;}\n'; rule += '.btn_install[disabled]:hover,.btn_install.disabled:hover,.btn_install[disabled]:active,.btn_install[disabled].active,.btn_install.disabled:active,.btn_install.disabled.active{background-color: #444 !important; color: #fff !important; border:1px solid #444 !important;}\n'; } else { rule += '.btn_install{background-color: #e0e0e0 !important; color: #333 !important}\n'; rule += '.btn_install:hover{background-color: #fcfcfc !important;}\n'; rule += '.btn_install:active{background-color: #fcfcfc !important;}\n'; rule += '.btn_remove:active{background-color: #555 !important; color: rgb(255,255,255,0.8) !important}\n'; rule += '.div_offered{color: rgba(255,255,255,0.8); !important;}\n'; rule += '.btn_install[disabled]:hover,.btn_install.disabled:hover,.btn_install[disabled]:active,.btn_install[disabled].active,.btn_install.disabled:active,.btn_install.disabled.active{background-color: #e0e0e0 !important; color: #333 !important; border:1px solid #e0e0e0 !important;}\n'; } let styleTheme = document.createElement('style'); styleTheme.type = 'text/css'; styleTheme.innerHTML = message.style + rule; document.getElementsByTagName('head')[0].appendChild(styleTheme); break; case 'onExternalMouseUp': let evt = document.createEvent("MouseEvents"); evt.initMouseEvent("mouseup", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); document.dispatchEvent(evt); break; case 'PluginReady': // get all installed plugins editorVersion = ( message.version && message.version.includes('.') ? Number( message.version.split('.').join('') ) : 1e8 ); sendMessage({type: 'getInstalled'}, '*'); break; case 'onClickBack': onClickBack(); break; }; }, false); function fetchAllPlugins(bFirstRender, bshowMarketplace) { // function for fetching all plugins from config clearInterval(interval); interval = null; isPluginLoading = true; makeRequest(configUrl).then( function(response) { allPlugins = JSON.parse(response); if (installedPlugins || bAppDirectory) getAllPluginsData(bFirstRender, bshowMarketplace); }, function(err) { createError( new Error( getTranslated( 'Problem with loading markeplace config.' ) ) ); isPluginLoading = false; allPlugins = []; showMarketplace(); } ); }; function makeRequest(url, responseType) { // this function makes GET request and return promise // maybe use fetch to in this function // isLoading = true; return new Promise(function (resolve, reject) { try { let xhr = new XMLHttpRequest(); xhr.open('GET', url, true); if (responseType) xhr.responseType = responseType; xhr.onload = function () { if (this.readyState == 4) { if (this.status == 200 || location.href.indexOf("file:") == 0) { resolve(this.response); } if (this.status >= 400) { reject(new Error(this.response)); } } }; xhr.onerror = function (err) { reject(err); }; xhr.send(null); } catch (error) { reject(error); } }); }; function sendMessage(message) { // this function sends message to editor parent.postMessage(JSON.stringify(message), '*'); }; function detectLanguage() { // detect language or return default let lang = getUrlSearchValue("lang"); if (lang.length == 2) lang = (lang.toLowerCase() + "-" + lang.toUpperCase()); return lang || 'en-EN'; }; function detectThemeType() { // detect theme or return default let type = getUrlSearchValue("theme-type"); return type || 'light'; }; function initElemnts() { elements.btnMyPlugins = document.getElementById('btn_myPlugins'); elements.btnMarketplace = document.getElementById('btn_marketplace'); elements.linkNewPlugin = document.getElementById('link_newPlugin'); elements.divBody = document.getElementById('div_body'); elements.divMain = document.getElementById('div_main'); elements.arrow = document.getElementById('arrow'); // elements.close = document.getElementById('close'); elements.divHeader = document.getElementById('div_header'); elements.divSelected = document.getElementById('div_selected_toolbar'); elements.divSelectedMain = document.getElementById('div_selected_main'); elements.imgIcon = document.getElementById('img_icon'); elements.spanName = document.getElementById('span_name'); elements.spanOffered = document.getElementById('span_offered'); elements.btnUpdate = document.getElementById('btn_update'); elements.btnRemove = document.getElementById('btn_remove'); elements.btnInstall = document.getElementById('btn_install'); elements.spanSelectedDescr = document.getElementById('span_selected_description'); elements.imgScreenshot = document.getElementById('image_screenshot'); elements.linkPlugin = document.getElementById('link_plugin'); elements.divScreen = document.getElementById("div_selected_image"); elements.divGitLink = document.getElementById('div_github_link'); elements.spanVersion = document.getElementById('span_ver'); elements.divVersion = document.getElementById('div_version'); elements.spanMinVersion = document.getElementById('span_min_ver'); elements.divMinVersion = document.getElementById('div_min_version'); elements.spanLanguages = document.getElementById('span_langs'); elements.divLanguages = document.getElementById('div_languages'); elements.divArrow = document.getElementById('div_arrows'); elements.arrowPrev = document.getElementById('arrow_prev'); elements.arrowNext = document.getElementById('arrow_next'); elements.inpSearch = document.getElementById('inp_search'); elements.btnUpdateAll = document.getElementById('btn_updateAll'); }; function toogleLoader(show, text) { // show or hide loader if (!show) { clearTimeout(timeout); document.getElementById('loader-container').classList.add('hidden'); loader && (loader.remove ? loader.remove() : $('#loader-container')[0].removeChild(loader)); loader = undefined; } else if(!loader) { document.getElementById('loader-container').classList.remove('hidden'); loader && (loader.remove ? loader.remove() : $('#loader-container')[0].removeChild(loader)); loader = showLoader($('#loader-container')[0], ( getTranslated(text) ) + '...'); } }; function getAllPluginsData(bFirstRender, bshowMarketplace) { // get config file for each item in config.json isPluginLoading = true; let count = 0; let Unloaded = []; let url = isDesktop ? OOMarketplaceUrl : ioUrl; allPlugins.forEach(function(pluginUrl, i, arr) { count++; pluginUrl = (pluginUrl.indexOf(":/\/") == -1) ? url + 'sdkjs-plugins/content/' + pluginUrl + '/' : pluginUrl; let confUrl = pluginUrl + 'config.json'; makeRequest(confUrl).then( function(response) { count--; let config = JSON.parse(response); config.url = confUrl; config.baseUrl = pluginUrl; arr[i] = config; if (!count) { // console.log('getAllPluginsData: ' + (Date.now() - start)); removeUnloaded(Unloaded); sortPlugins(true, false, 'name'); isPluginLoading = false; if (bFirstRender) showMarketplace(); else if (bshowMarketplace) toogleView(elements.btnMarketplace, elements.btnMyPlugins, messages.linkPR, true, true); } makeRequest(pluginUrl + 'translations/langs.json').then( function(response) { let supportedLangs = [ getTranslated('English') ]; let arr = JSON.parse(response); arr.forEach(function(full) { let short = full.split('-')[0]; for (let i = 0; i < languages.length; i++) { if (languages[i][0] == short || languages[i][1] == short) { supportedLangs.push( getTranslated( languages[i][2] ) ); } } }); if (supportedLangs.length > 1) config.languages = supportedLangs; }, function(error) { config.languages = [ getTranslated('English') ]; } ) }, function(err) { count--; Unloaded.push(i); createError(new Error('Problem with loading plugin config.\nConfig: ' + confUrl)); if (!count) { removeUnloaded(Unloaded); sortPlugins(true, false, 'name'); isPluginLoading = false; if (bFirstRender) showMarketplace(); else if (bshowMarketplace) toogleView(elements.btnMarketplace, elements.btnMyPlugins, messages.linkPR, true, true); } } ); }); if (isDesktop && installedPlugins && bFirstRender) { isPluginLoading = false; getInstalledLanguages(); showMarketplace(); } }; function getInstalledLanguages() { installedPlugins.forEach(function(pl) { makeRequest(pl.obj.baseUrl + 'translations/langs.json').then( function(response) { let supportedLangs = [ getTranslated('English') ]; let arr = JSON.parse(response); arr.forEach(function(full) { let short = full.split('-')[0]; for (let i = 0; i < languages.length; i++) { if (languages[i][0] == short || languages[i][1] == short) { supportedLangs.push( getTranslated( languages[i][2] ) ); } } }); if (supportedLangs.length > 1) pl.obj.languages = supportedLangs; }, function(error) { pl.obj.languages = [ getTranslated('English') ]; } ) }); }; function showListofPlugins(bAll, sortedArr) { // show list of plugins $('.div_notification').remove(); $('.div_item').remove(); let arr = (sortedArr ? sortedArr : (bAll ? allPlugins : installedPlugins)); // получаем список backup плагинов if (!bAll && isDesktop) { var _pluginsTmp = JSON.parse(window["AscDesktopEditor"]["GetBackupPlugins"]()); if (_pluginsTmp.length) { var len = _pluginsTmp[0]["pluginsData"].length; for (var i = 0; i < len; i++) { let plugin = _pluginsTmp[0]["pluginsData"][i]; plugin.baseUrl = _pluginsTmp[0]["url"] + plugin.guid.replace('asc.', '') + '/'; installed = findPlugin(false, plugin.guid); if (!installed) { installedPlugins.push({ "baseUrl": _pluginsTmp[0]["url"], "guid": plugin.guid, "canRemoved": true, "obj": plugin, "removed": true }); } } } } if (arr.length) { arr.forEach(function(plugin) { if (plugin && plugin.guid) createPluginDiv(plugin, !bAll); }); setTimeout(function(){if (Ps) Ps.update()}); } else { // if no istalled plugins and my plugins button was clicked let notification = Array.isArray(sortedArr) ? 'Nothing was found for this query.' : bAll ? 'Problem with loading plugins.' : 'No installed plugins.'; createNotification(notification); } if (!Ps) { Ps = new PerfectScrollbar('#div_main', {}); Ps.update(); } }; function getPluginVersion(text) { let factor = 1000; let major = 1; let minor = 0; let build = 0; if (text && text.split) { let arValues = text.split('.'); let count = arValues.length; if (count > 0) major = parseInt(arValues[0]); if (count > 1) minor = parseInt(arValues[1]); if (count > 2) build = parseInt(arValues[2]); } return major * factor * factor + minor * factor + build; } function createPluginDiv(plugin, bInstalled) { // this function creates div (preview) for plugins let div = document.createElement('div'); div.id = plugin.guid; div.setAttribute('data-guid', plugin.guid); div.className = 'div_item form-control noselect'; div.style.border = (1 / scale.devicePR) +'px solid ' + (themeType == 'ligh' ? '#c0c0c0' : '#666666'); div.onmouseenter = function(event) { event.target.classList.add('div_item_hovered'); }; div.onmouseleave = function(event) { event.target.classList.remove('div_item_hovered'); }; div.onclick = onClickItem; // todo поправить let installed = ? null : bInstalled ? plugin : findPlugin(false, plugin.guid); if (bInstalled || bAppDirectory) { plugin = findPlugin(true, plugin.guid); } let bCheckUpdate = true; if (!plugin) { plugin = installed.obj; bCheckUpdate = false; } let bNotAvailable = false; const minV = (plugin.minVersion ? Number( plugin.minVersion.split('.').join('') ) : -1); if (minV > editorVersion) { bCheckUpdate = false; bNotAvailable = true; } let bHasUpdate = false; let bRemoved = (installed && installed.removed); if (bCheckUpdate && installed && plugin) { const installedV = getPluginVersion(installed.obj.version); const lastV = getPluginVersion(plugin.version); if (lastV > installedV) { bHasUpdate = true; plugin.bHasUpdate = true; if (!bRemoved) elements.btnUpdateAll.classList.remove('hidden'); } } let variation = plugin.variations[0]; let name = (bTranslate && plugin.nameLocale && plugin.nameLocale[shortLang]) ? plugin.nameLocale[shortLang] : plugin.name; let description = (bTranslate && variation.descriptionLocale && variation.descriptionLocale[shortLang]) ? variation.descriptionLocale[shortLang] : variation.description; let bg = variation.store && variation.store.background ? variation.store.background[themeType] : defaultBG; let additional = bNotAvailable ? 'disabled title="' + getTranslated(messages.versionWarning) + '"' : ''; let template = '
' + '' + '
' + '
'+ '' + name + '' + '' + description + '' + '
' + ''; div.innerHTML = template; elements.divMain.appendChild(div); if (Ps) Ps.update(); }; function onClickInstall(target, event) { event.stopImmediatePropagation(); // click install button clearTimeout(timeout); timeout = setTimeout(toogleLoader, 200, true, "Installation"); let guid = target.parentNode.parentNode.getAttribute('data-guid'); let plugin = findPlugin(true, guid); let installed = findPlugin(false, guid); let message = { type : 'install', url : (installed ? installed.obj.baseUrl : plugin.url), guid : guid, config : (installed ? installed.obj : plugin) }; sendMessage(message); }; function onClickUpdate(target) { // click update button clearTimeout(timeout); timeout = setTimeout(toogleLoader, 200, true, "Updating"); let guid = target.parentElement.parentElement.parentElement.getAttribute('data-guid'); let plugin = findPlugin(true, guid); updateCount++; let message = { type : 'update', url : plugin.url, guid : guid, config : plugin }; sendMessage(message); }; function onClickRemove(target, event) { event.stopImmediatePropagation(); // click remove button clearTimeout(timeout); timeout = setTimeout(toogleLoader, 200, true, "Removal"); let guid = target.parentNode.parentNode.getAttribute('data-guid'); let message = { type : 'remove', guid : guid, backup : needBackupPlugin(guid) }; sendMessage(message); }; function needBackupPlugin(guid) { // проверяем установленный плагин: // если плагин есть в стор ( и его версия <= ? ), то можем удалить, пользователь сможет поставить актуальную версию // если плагина нет в стор, нужно его хранить у пользователя с возможностью восстановления return isDesktop ? findPlugin(true, guid) == undefined : false; } function onClickUpdateAll() { clearTimeout(timeout); timeout = setTimeout(toogleLoader, 200, true, "Updating"); elements.btnUpdateAll.classList.add('hidden'); let arr = allPlugins.filter(function(el) { return el.bHasUpdate; }); updateCount = arr.length; arr.forEach(function(plugin){ let message = { type : 'update', url : plugin.url, guid : plugin.guid, config : plugin }; sendMessage(message); }); }; function onClickItem() { // There we will make preview for selected plugin let offered = " Ascensio System SIA"; let guid = this.getAttribute('data-guid'); let pluginDiv = document.getElementById(guid); let divPreview = document.createElement('div'); divPreview.id = 'div_preview'; divPreview.className = 'div_preview'; let installed = bAppDirectory ? null : findPlugin(false, guid); let plugin = findPlugin(true, guid); if ( !plugin || ( isDesktop && installed ) ) { elements.divGitLink.classList.add('hidden'); plugin = installed.obj; } else { elements.divGitLink.classList.remove('hidden'); } let bCorrectUrl = isDesktop || ( !plugin.baseUrl.includes('http://') && !plugin.baseUrl.includes('file:') && !plugin.baseUrl.includes('../')); if (bCorrectUrl && plugin.variations[0].store && plugin.variations[0].store.screenshots && plugin.variations[0].store.screenshots.length) { current.screenshots = plugin.variations[0].store.screenshots; current.index = 0; current.url = plugin.baseUrl; let url = current.url + current.screenshots[current.index]; elements.imgScreenshot.setAttribute('src', url); elements.imgScreenshot.onload = function() { elements.imgScreenshot.classList.remove('hidden'); if (current.screenshots.length > 1) elements.divArrow.classList.remove('hidden'); setDivHeight(); } } else { elements.imgScreenshot.classList.add('hidden'); elements.divArrow.classList.add('hidden'); } let bHasUpdate = (pluginDiv.lastChild.firstChild.tagName === 'SPAN' && !pluginDiv.lastChild.firstChild.classList.contains('hidden')); if ( (installed && installed.obj.version) || plugin.version ) { elements.spanVersion.innerText = (installed && installed.obj.version ? installed.obj.version : plugin.version); elements.divVersion.classList.remove('hidden'); } else { elements.spanVersion.innerText = ''; elements.divVersion.classList.add('hidden'); } if ( (installed && installed.obj.minVersion) || plugin.minVersion ) { elements.spanMinVersion.innerText = (installed && installed.obj.minVersion ? installed.obj.minVersion : plugin.minVersion); elements.divMinVersion.classList.remove('hidden'); } else { elements.spanMinVersion.innerText = ''; elements.divMinVersion.classList.add('hidden'); } if (plugin.languages) { elements.spanLanguages.innerText = plugin.languages.join(', ') + '.'; elements.divLanguages.classList.remove('hidden'); } else { elements.spanLanguages.innerText = ''; elements.divLanguages.classList.add('hidden'); } let pluginUrl = plugin.baseUrl.replace('https://onlyoffice.github.io/', 'https://github.com/ONLYOFFICE/onlyoffice.github.io/tree/master/'); // TODO problem with plugins icons (different margin from top) elements.divSelected.setAttribute('data-guid', guid); // we do this, because new icons for store are too big for use it in this window. let tmp = getImageUrl(guid, true, true, 'img_icon'); elements.imgIcon.setAttribute('src', tmp); elements.spanName.innerHTML = this.children[1].children[0].innerText; elements.spanOffered.innerHTML = plugin.offered || offered; elements.spanSelectedDescr.innerHTML = this.children[1].children[1].innerText; elements.linkPlugin.setAttribute('href', pluginUrl); if (bHasUpdate) { elements.btnUpdate.classList.remove('hidden'); } else { elements.btnUpdate.classList.add('hidden'); } if (installed && !installed.removed) { if (installed.canRemoved) { elements.btnRemove.classList.remove('hidden'); } else { elements.btnRemove.classList.add('hidden'); } elements.btnInstall.classList.add('hidden'); } else { elements.btnRemove.classList.add('hidden'); elements.btnInstall.classList.remove('hidden'); } if (pluginDiv.lastChild.lastChild.hasAttribute('disabled')) {// || pluginDiv.lastChild.lastChild.hasAttribute('dataDisabled')) { elements.btnInstall.setAttribute('disabled',''); elements.btnInstall.setAttribute('title', getTranslated(messages.versionWarning)); } else { elements.btnInstall.removeAttribute('disabled'); elements.btnInstall.removeAttribute('title'); } elements.divSelected.classList.remove('hidden'); elements.divSelectedMain.classList.remove('hidden'); elements.divBody.classList.add('hidden'); sendMessage( { type : "showButton" } ); elements.arrow.classList.remove('hidden'); }; function onClickBack() { // click on left arrow in preview mode elements.imgIcon.style.display = 'none'; elements.imgScreenshot.setAttribute('src','') document.getElementById('span_overview').click(); elements.divSelected.classList.add('hidden'); elements.divSelectedMain.classList.add('hidden'); elements.divBody.classList.remove('hidden'); elements.divArrow.classList.add('hidden'); current.index = 0; current.screenshots = []; current.url = ''; elements.arrow.classList.add('hidden'); if(Ps) Ps.update(); }; function onClickScreenshot() { // todo create a modal with the big screenshot and exit from this mode // $(".arrow_conteiner_small").addClass("arrow_conteiner_big"); // $(".arrow_small").removeClass("arrow_big"); // $(".arrow_conteiner_small").removeClass(".arrow_conteiner_small"); // $(".arrow_small").removeClass(".arrow_small"); }; function onSelectPreview(target, isOverview) { // change mode of preview if ( !target.classList.contains('span_selected') ) { $(".span_selected").removeClass("span_selected"); target.classList.add("span_selected"); if (isOverview) { document.getElementById('div_selected_info').classList.add('hidden'); document.getElementById('div_selected_preview').classList.remove('hidden'); setDivHeight(); } else { document.getElementById('div_selected_preview').classList.add('hidden'); document.getElementById('div_selected_info').classList.remove('hidden'); } } }; function createNotification(text) { // creates any notification for user inside elements.divMain window (you should clear this element before making notification) let div = document.createElement('div'); div.className = 'div_notification'; let span = document.createElement('span'); span.className = 'span_notification'; span.innerHTML = getTranslated(text); div.appendChild(span); elements.divMain.appendChild(div); }; function createError(err) { // creates a modal window with error message for user and error in console console.error(err); let background = document.createElement('div'); background.className = 'asc-plugin-loader'; let span = document.createElement('span'); span.className = 'error_caption'; span.innerHTML = err.message; background.appendChild(span); document.getElementById('div_error').appendChild(background); document.getElementById('div_error').classList.remove('hidden'); setTimeout(function() { // remove error after 5 seconds background.remove(); document.getElementById('div_error').classList.add('hidden'); }, 5000); }; function setDivHeight() { // set height for div with image in preview mode if (Ps) Ps.update(); // console.log(Math.round(window.devicePixelRatio * 100)); if (elements.divScreen) { let height = elements.divScreen.parentNode.clientHeight - elements.divScreen.previousElementSibling.clientHeight - 40 + 'px'; elements.divScreen.style.height = height; elements.divScreen.style.maxHeight = height; if (isIE) { elements.imgScreenshot.style.maxHeight = height; elements.imgScreenshot.style.maxWidth = elements.divScreen.clientWidth + 'px'; } } }; window.onresize = function() { setDivHeight(); if (scale.devicePR !== window.devicePixelRatio) { scale.devicePR = window.devicePixelRatio; $('.div_item').css('border', ((1 / scale.devicePR) +'px solid ' + (themeType == 'ligh' ? '#c0c0c0' : '#666666'))); if (1 < scale.devicePR && scale.devicePR <= 2 || isResizeOnStart) { let oldScale = scale.value; isResizeOnStart = false; if (scale.devicePR < 1) return; calculateScale(); if (scale.value !== oldScale) changeIcons(); } } }; function calculateScale() { let bestIndex = 0; scale.devicePR = window.devicePixelRatio; let bestDistance = Math.abs(supportedScaleValues[0] - scale.devicePR); let currentDistance = 0; for (let i = 1, len = supportedScaleValues.length; i < len; i++) { if (true) { if (Math.abs(supportedScaleValues[i] - scale.devicePR) > 0.0001) { if ( (supportedScaleValues[i] - 0.0501) > (scale.devicePR - 0.0001)) break; } } currentDistance = Math.abs(supportedScaleValues[i] - scale.devicePR); if (currentDistance < (bestDistance - 0.0001)) { bestDistance = currentDistance; bestIndex = i; } } scale.percent = supportedScaleValues[bestIndex] * 100 + '%'; scale.value = supportedScaleValues[bestIndex]; }; function changeIcons() { let arr = document.getElementsByClassName('plugin_icon'); for (let i = 0; i < arr.length; i++) { let guid = arr[i].getAttribute('data-guid'); arr[i].setAttribute( 'src', getImageUrl( guid, false, true, ('img_' + guid) ) ); } let guid = elements.imgIcon.parentNode.parentNode.getAttribute('data-guid'); elements.imgIcon.setAttribute('src', getImageUrl(guid, true, true, 'img_icon')); }; function getTranslation() { // gets translation for current language if (shortLang != "en") { isTranslationLoading = true makeRequest('./translations/langs.json').then( function(response) { let arr = JSON.parse(response); let fullName, shortName; for (let i = 0; i < arr.length; i++) { let file = arr[i]; if (file == lang) { fullName = file; break; } else if (file.split('-')[0] == shortLang) { shortName = file; } } if (fullName || shortName) { bTranslate = true; makeRequest('./translations/' + (fullName || shortName) + '.json').then( function(res) { // console.log('getTranslation: ' + (Date.now() - start)); translate = JSON.parse(res); onTranslate(); }, function(err) { createError( new Error( getTranslated( 'Cannot load translation for current language.' ) ) ); isTranslationLoading = false; showMarketplace(); } ); } else { isTranslationLoading = false; showMarketplace(); } }, function(err) { createError( new Error( getTranslated( 'Cannot load translations list file.' ) ) ); isTranslationLoading = false; showMarketplace(); } ); } else { isTranslationLoading = false; showMarketplace(); } }; function onTranslate() { isTranslationLoading = false; // translates elements on current language elements.linkNewPlugin.innerHTML = getTranslated(messages.linkPR); elements.btnMyPlugins.innerHTML = getTranslated('My plugins'); elements.btnMarketplace.innerHTML = getTranslated('Marketplace'); elements.btnInstall.innerHTML = getTranslated('Install'); elements.btnRemove.innerHTML = getTranslated('Remove'); elements.btnUpdate.innerHTML = getTranslated('Update'); elements.btnUpdateAll.innerHTML = getTranslated('Update All'); elements.inpSearch.placeholder = getTranslated('Search plugins') + '...'; document.getElementById('lbl_header').innerHTML = getTranslated('Manage plugins'); document.getElementById('span_offered_caption').innerHTML = getTranslated('Offered by') + ': '; document.getElementById('span_overview').innerHTML = getTranslated('Overview'); document.getElementById('span_info').innerHTML = getTranslated('Info & Support'); document.getElementById('span_lern').innerHTML = getTranslated('Learn how to use') + ' '; document.getElementById('span_lern_plugin').innerHTML = getTranslated('the plugin in') + ' '; document.getElementById('span_contribute').innerHTML = getTranslated('Contribute') + ' '; document.getElementById('span_contribute_end').innerHTML = getTranslated('to the plugin developmen or report an issue on') + ' '; document.getElementById('span_help').innerHTML = getTranslated('Get help') + ' '; document.getElementById('span_help_end').innerHTML = getTranslated('with the plugin functionality on our forum.'); document.getElementById('span_create').innerHTML = getTranslated('Create a new plugin using') + ' '; document.getElementById('span_ver_caption').innerHTML = getTranslated('Version') + ': '; document.getElementById('span_min_ver_caption').innerHTML = getTranslated('The minimum supported editors version') + ': '; document.getElementById('span_langs_caption').innerHTML = getTranslated('Languages') + ': '; document.getElementById('span_categories').innerHTML = getTranslated('Categories'); document.getElementById('opt_all').innerHTML = getTranslated('All'); document.getElementById('opt_rec').innerHTML = getTranslated('Recommended'); document.getElementById('opt_dev').innerHTML = getTranslated('Developer tools'); document.getElementById('opt_work').innerHTML = getTranslated('Work'); document.getElementById('opt_enter').innerHTML = getTranslated('Entertainment'); document.getElementById('opt_com').innerHTML = getTranslated('Communication'); document.getElementById('opt_spec').innerHTML = getTranslated('Special abilities'); showMarketplace(); }; function showMarketplace() { // show main window to user if (!isPluginLoading && !isTranslationLoading && !isFrameLoading && (installedPlugins || bAppDirectory)) { if(bAppDirectory) { installedPlugins = []; document.getElementsByClassName('toolbar_top')[0].classList.add('hidden'); } createSelect(); if (isOnline) showListofPlugins(isOnline); else toogleView(elements.btnMyPlugins, elements.btnMarketplace, messages.linkManually, false, false); toogleLoader(false); catFiltred = allPlugins; // elements.divBody.classList.remove('hidden'); elements.divBody.classList.remove('transparent'); // console.log('showMarketplace: ' + (Date.now() - start)); // we are removing the header for now, since the plugin has its own // elements.divHeader.classList.remove('hidden'); } }; function createSelect() { $('#select_categories').select2({ minimumResultsForSearch: Infinity }).on('change', function(event) { filterByCategory(event.currentTarget.value); }); // $('#select_sortBy').select2({ // minimumResultsForSearch: Infinity // }).on('change', function(event) { // console.log(event.currentTarget.value); // }); }; function getImageUrl(guid, bNotForStore, bSetSize, id) { // get icon url for current plugin (according to theme and scale) let iconScale = '/icon.png'; switch (scale.percent) { case '125%': iconScale = '/icon@1.25x.png' break; case '150%': iconScale = '/icon@1.5x.png' break; case '175%': iconScale = '/icon@1.75x.png' break; case '200%': iconScale = '/icon@2x.png' break; } let curIcon = './resources/img/defaults/' + (bNotForStore ? ('info/' + themeType) : 'card') + iconScale; let plugin; // We have a problem with "http" and "file" routes. // In desktop we have a local installed marketplace. It's why we use local routes only for desktop. let baseUrl; if (installedPlugins && isDesktop) { // it doesn't work when we use icons from other resource (cors problems) // it's why we use local icons only for desktop plugin = findPlugin(false, guid); if (plugin) { plugin = plugin.obj; baseUrl = plugin.baseUrl; } } if ( ( !plugin || !isDesktop ) && allPlugins) { plugin = findPlugin(true, guid); if (plugin) baseUrl = plugin.baseUrl; } // github doesn't allow to use "http" or "file" as the URL for an image if ( plugin && ( baseUrl.includes('https://') || isDesktop) ) { let variation = plugin.variations[0]; if (!bNotForStore && variation.store && variation.store.icons) { // icons are in config of store field (work only with new scheme) // it's an object with 2 fields (for dark and light theme), which contain route to icons folder curIcon = baseUrl + variation.store.icons[themeType] + iconScale; } else if (variation.icons2) { // it's old scheme. There could be an array with objects which have theme field or an array from one object without theme field let icon = variation.icons2[0]; for (let i = 1; i < variation.icons2.length; i++) { if ( themeType.includes(variation.icons2[i].style) ) { icon = variation.icons2[i]; break; } } curIcon = baseUrl + icon[scale.percent].normal; } else if (variation.icons) { // there could be old and new scheme // there will be a string array or object like icons2 above (old scheme) // there will be a object with 2 fields (for dark and light theme), which contain route to icons folder (new scheme) if (!Array.isArray(variation.icons)) { // new scheme curIcon = baseUrl + variation.icons[themeType] + iconScale; } else { // old scheme if (typeof(variation.icons[0]) == 'object' ) { // old scheme and icons like icons2 above let icon = variation.icons[0]; for (let i = 1; i < variation.icons.length; i++) { if ( themeType.includes(variation.icons[i].style) ) { icon = variation.icons[i]; break; } } curIcon = baseUrl + icon[scale.percent].normal; } else { // old scheme and icons is a string array curIcon = baseUrl + (scale.value >= 1.2 ? variation.icons[1] : variation.icons[0]); } } } } if (bSetSize) { makeRequest(curIcon, 'blob').then( function (res) { let reader = new FileReader(); reader.onloadend = function() { let imageUrl = reader.result; let img = document.createElement('img'); img.setAttribute('src', imageUrl); img.onload = function () { let icon = document.getElementById(id); icon.style.width = ( (img.width/scale.value) >> 0 ) + 'px'; icon.style.height = ( (img.height/scale.value) >> 0 ) + 'px'; icon.style.display = ''; } } reader.readAsDataURL(res); }, function(error) { createError(error); } ); } return curIcon; }; function getUrlSearchValue(key) { let res = ''; if (window.location && window.location.search) { let search = window.location.search; let pos1 = search.indexOf(key + '='); if (-1 != pos1) { pos1 += key.length + 1; let pos2 = search.indexOf("&", pos1); res = search.substring(pos1, (pos2 != -1 ? pos2 : search.length) ) } } return res; }; function toogleView(current, oldEl, text, bAll, bForce) { if ( !current.classList.contains('btn_toolbar_active') || bForce ) { elements.inpSearch.value = ''; founded = []; oldEl.classList.remove('btn_toolbar_active'); current.classList.add('btn_toolbar_active'); elements.linkNewPlugin.innerHTML = getTranslated(text); let toolbar = document.getElementById('toolbar_tools'); if (bAll && (!isOnline || isPluginLoading) ) { $('.div_notification').remove(); $('.div_item').remove(); setTimeout(function(){if (Ps) Ps.update()}); toolbar.classList.add('hidden'); createNotification('No Internet Connection.') } else { toolbar.classList.remove('hidden'); if (document.getElementById('select_categories').value == 'all') { showListofPlugins(bAll); catFiltred = bAll ? allPlugins : installedPlugins; } else { filterByCategory(document.getElementById('select_categories').value); } } elements.linkNewPlugin.href = bAll ? "https://github.com/ONLYOFFICE/onlyoffice.github.io/pulls" : "https://api.onlyoffice.com/plugin/installation"; if (isDesktop && !bAll) { elements.linkNewPlugin.href = "#"; elements.linkNewPlugin.onclick = function (e) { e.preventDefault(); installPluginManually(); } } } }; function installPluginManually() { window["AscDesktopEditor"]["OpenFilenameDialog"]("plugin", false, function (_file) { var file = _file; if (Array.isArray(file)) file = file[0]; let result = window["AscDesktopEditor"]["PluginInstall"](file); if (result) { // нужно обновить список установленных плагинов sendMessage({ type: 'getInstalled', updateInstalled: true }, '*'); } }); }; function sortPlugins(bAll, bInst, type) { switch (type) { case 'raiting': // todo break; case 'instalations': // todo break; case 'start': if (bInst) { let protected = []; let removed = []; let arr = []; installedPlugins.forEach(function(pl){ if (!pl.canRemoved) protected.push(pl); else if (pl.removed) removed.push(pl); else arr.push(pl); }); installedPlugins = protected.concat(arr, removed); } break; default: if (bAll) { allPlugins.sort(function(a, b) { return a.name.localeCompare(b.name); }); } if (bInst) { installedPlugins.sort(function(a, b) { return a.obj.name.localeCompare(b.obj.name); }); } break; } }; function makeSearch(val) { clearTimeout(searchTimeout); searchTimeout = setTimeout(function() { let plugins = catFiltred; let bUpdate = false; let arr = plugins.filter(function(el) { let plugin = el.obj || el; let name = (plugin.nameLocale && plugin.nameLocale[shortLang]) ? plugin.nameLocale[shortLang] : plugin.name; return name.toLowerCase().includes(val); }); if (founded.length == arr.length) { if (JSON.stringify(founded) != JSON.stringify(arr)) { founded = arr; bUpdate = true; } } else { founded = arr; bUpdate = true; } if (founded.length) { if (bUpdate) showListofPlugins(elements.btnMarketplace.classList.contains('btn_toolbar_active'), founded); } else { showListofPlugins(elements.btnMarketplace.classList.contains('btn_toolbar_active'), []); } }, 100); }; function filterByCategory(category) { let plugins = elements.btnMarketplace.classList.contains('btn_toolbar_active') ? allPlugins : installedPlugins; let arr; if (category != "all") { arr = plugins.filter(function(plugin) { let variation = plugin.variations ? plugin.variations[0] : plugin.obj.variations[0]; let arrCat = (variation.store && variation.store.categories) ? variation.store.categories : []; return arrCat.includes(category); }); } else { arr = plugins; } catFiltred = arr; if (elements.inpSearch.value.trim() == '') showListofPlugins(elements.btnMarketplace.classList.contains('btn_toolbar_active'), arr); else makeSearch(elements.inpSearch.value.trim().toLowerCase()); }; function removeUnloaded(unloaded) { unloaded.forEach(function(el){ allPlugins.splice(el, 1); }) }; function findPlugin(bAll, guid) { // todo поправить везде где есть поиск, чтобы поиск запускался, если только есть такая группа плагинов let res = bAll ? allPlugins.find(function(el){return el.guid === guid}) : installedPlugins.find(function(el){return el.guid === guid}); return res; }; function changeAfterInstallOrRemove(bInstall, guid, bHasLocal) { let btn = this.document.getElementById(guid).lastChild.lastChild; btn.innerHTML = getTranslated( ( bInstall ? 'Remove' : 'Install' ) ); btn.classList.add( ( bInstall ? 'btn_remove' : 'btn_install' ) ); btn.classList.remove( ( bInstall ? 'btn_install' : 'btn_remove' ) ); btn.onclick = function(e) { if (bInstall) onClickRemove(e.target, e); else onClickInstall(e.target, e); }; // We need to keep the ability to install the local version that has been removed (maybe we should change the button) if ( !bInstall && btn.hasAttribute('dataDisabled') && !bHasLocal ) { btn.setAttribute('title', getTranslated(messages.versionWarning)); btn.setAttribute('disabled', ''); } let bHasUpdate = (btn.parentNode.childElementCount > 1); if (bHasUpdate) { if (bInstall) btn.parentNode.firstChild.classList.remove('hidden'); else btn.parentNode.firstChild.classList.add('hidden'); } if (!elements.divSelected.classList.contains('hidden')) { this.document.getElementById( ( bInstall ? 'btn_install' : 'btn_remove' ) ).classList.add('hidden'); this.document.getElementById( ( bInstall ? 'btn_remove' : 'btn_install' ) ).classList.remove('hidden'); if (bInstall && bHasUpdate) this.document.getElementById('btn_update').classList.remove('hidden'); else this.document.getElementById('btn_update').classList.add('hidden'); } }; function checkInternet() { try { let xhr = new XMLHttpRequest(); let url = 'https://raw.githubusercontent.com/ONLYOFFICE/onlyoffice.github.io/master/store/translations/langs.json'; xhr.open('GET', url, true); xhr.onload = function () { if (this.readyState == 4) { if (this.status >= 200 && this.status < 300) { isOnline = true; let bshowMarketplace = ( elements.btnMarketplace && elements.btnMarketplace.classList.contains('btn_toolbar_active') ) ? true : false; fetchAllPlugins(interval === null, bshowMarketplace); } } }; xhr.onerror = function (err) { handeNoInternet(); }; xhr.send(null); } catch (error) { handeNoInternet(); } }; function handeNoInternet() { isOnline = false; allPlugins = []; if (!interval) { interval = setInterval(function() { checkInternet(); }, 5000); } }; function getTranslated(text) { return translate[text] || text; };