1/// <reference path="UI.js" /> 2/* eslint eqeqeq: 0, "no-extra-parens": "off" */ 3/* global UI */ 4 5const PluginHandler = (function () { 6 "use strict"; 7 let Plugins = {}; 8 let PluginList = []; 9 let FeatureSet = []; 10 11 let PluginHandlerObject = { 12 SetFeatures: (features) => { FeatureSet = features; }, 13 HasFeature: (feature) => FeatureSet.contains(feature), 14 RunPluginsPostAMPInit: function() 15 { 16 for (const plugin of Object.values(Plugins)) { 17 if (!plugin.hasOwnProperty("AMPDataLoaded")) { continue; } 18 plugin.AMPDataLoaded(plugin); 19 } 20 }, 21 NotifyPluginSettingChanged: function(node, value) 22 { 23 for (const plugin of Object.values(Plugins)) { 24 if (!plugin.hasOwnProperty("SettingChanged")) { continue; } 25 plugin.SettingChanged(node, value); 26 } 27 }, 28 NotifyPluginFailureState: function(newState) 29 { 30 for (const plugin of Object.values(Plugins)) { 31 if (!plugin.hasOwnProperty("StartupFailure")) { continue; } 32 plugin.StartupFailure(newState); 33 } 34 }, 35 NotifyPluginMessage: function(source, message, parameters) 36 { 37 for (const plugin of Object.values(Plugins)) 38 { 39 if (!plugin.hasOwnProperty("PushedMessage")) { continue; } 40 plugin.PushedMessage(source, message, parameters); 41 } 42 }, 43 GetSearchResults: async function(query) 44 { 45 let data = []; 46 for (const plugin of Object.values(Plugins)) { 47 if (!plugin.hasOwnProperty("GetSearchResultsAsync")) { continue; } 48 49 const result = await plugin.GetSearchResultsAsync(query); 50 if (result != null) 51 { 52 data.push(result); 53 } 54 } 55 return data; 56 }, 57 LoadPluginAsync: async function (pluginName) 58 { 59 const pluginRoot = `/Plugins/${pluginName}/`; 60 const rnd = "?t=" + (0 + Date.now()); 61 const scriptFile = pluginRoot + "Plugin.js" + rnd; 62 //add pluginName to PluginList, don't continue if it's already loaded. 63 if (PluginList.contains(pluginName)) { 64 console.log(`Plugin ${pluginName} already loaded.`); 65 return; 66 } 67 PluginList.push(pluginName); 68 69 async function scriptLoaded (data) { 70 const localPluginName = pluginName; 71 console.log(`Running init for ${localPluginName}`); 72 data = "'use strict';\n" + data; 73 let pluginConstructor = new Function(data); 74 let module = null; 75 function create() { 76 let constr = new pluginConstructor(); 77 return constr; 78 } 79 try { 80 module = create(); 81 } 82 catch (e) { 83 await UI.ShowModalAsync("Plugin Error", `An error ocurred while loading plugin ${localPluginName}. Startup will continue, but this plugin will not be loaded.`, UI.Icons.Exclamation, UI.OKActionOnly, null, null, `${e.message}\n\n${e.stack}`); 84 return; 85 } 86 let loadedPlugin = module.plugin; 87 let tabs = module.tabs || module.plugin.tabs || []; 88 89 Plugins[pluginName] = loadedPlugin; 90 if (loadedPlugin.PreInit !== undefined) { 91 loadedPlugin.PreInit(); 92 } 93 94 loadedPlugin.name = pluginName; 95 96 if (tabs === undefined) { module.tabs = []; } 97 98 if (module.stylesheet !== undefined && module.stylesheet !== "") { 99 const cssPath = pluginRoot + module.stylesheet; 100 const stylesheet = $("<link/>", { type: "text/css", rel: "stylesheet", href: cssPath + rnd }); 101 $("head").append(stylesheet); 102 } 103 104 let tabPromises = []; 105 106 for (const tab of tabs) { 107 if (tab.Disabled === true) { continue; } 108 if (tab.RequiredPermission != null && !userHasPermission(tab.RequiredPermission)) { continue; } 109 if (tab.FeatureSet && !FeatureSet.contains(tab.FeatureSet)) { continue; } 110 111 const tabId = `tab_${pluginName}_${tab.ShortName}`; 112
113 tab.id = tabId; 114 let bodyTab = $("<div class='bodyTab pluginTabContents' style='display:none;'></div>"); 115 bodyTab.attr("id", tabId); 116 117 if (tab.IsWizard == null || !tab.IsWizard) { 118 tab.vm = UI.AddSideMenuItem(tab.Name, "#" + tabId, tab.Icon, tab.Click || $.noop, tab.Order || 0, tab.IsDefault || false); 119 tab.vm.disabled(true); 120 tab.vm.bodyElement = bodyTab; 121 if (tab.PopHandler != null) { 122 UI.RegisterPopstateHandler(tab.vm.shortName, tab.PopHandler); 123 } 124 } 125 126 if (tab.IsWizard != null && tab.IsWizard) { bodyTab.addClass("wizardTab"); } 127 if (tab.BodyClass) { bodyTab.addClass(tab.BodyClass); } 128 if (tab.ViewModel) { bodyTab.attr("data-viewmodel", tab.ViewModel); } 129 130 const loadUrl = (tab.ExternalTab ? tab.File : pluginRoot + tab.File + rnd); 131 132 if (tab.IsWizard != null && tab.IsWizard) { 133 $("#mainBodyArea").append(bodyTab); 134 } 135 else { 136 //MB: Tabs not loading? Loading in the wrong place? This is probably why! 137 $("#tab_support").after(bodyTab); 138 } 139 140 tabPromises.push(new Promise(resolve => bodyTab.load(loadUrl, resolve))) 141 } 142 143 await Promise.all(tabPromises); 144 145 if (module.features !== undefined) 146 { 147 Features[pluginName] = {}; 148 for (const feature of Object.keys(module.features)) { 149 Features[pluginName][feature] = module.features[feature]; 150 } 151 } 152 153 if (loadedPlugin.PostInit !== undefined) { 154 loadedPlugin.PostInit(); 155 } 156 157 for (const tab of tabs) { 158 Locale.ApplyLocale(`#${tab.id}`); 159 $(`#${tab.id} a`).on("click", UI.HandleClickedURL); 160 tab.vm?.disabled(false); 161 } 162 } 163 164 const data = await fetch(scriptFile); 165 await scriptLoaded(await data.text()); 166 }, 167 PluginIsLoaded: (pluginName) => Plugins[pluginName] !== undefined, 168 LoadPluginExternalScript: function (pluginName, assetPath, callback) { 169 const scriptFile = `/Plugins/${pluginName}/${assetPath}`; 170 171 const doc = document, elementType = 'script', 172 newEl = doc.createElement(elementType), 173 firstScript = doc.getElementsByTagName(elementType)[0]; 174 newEl.src = scriptFile; 175 if (callback) { newEl.addEventListener('load', function (e) { callback(null, e); }, false); } 176 firstScript.parentNode.insertBefore(newEl, firstScript); 177 }, 178 LoadPluginExternalScriptAsync: async function (pluginName, assetPath) { 179 return new Promise(function (accept) {
180 PluginHandlerObject.LoadPluginExternalScript(pluginName, assetPath, accept); 181 }); 182 } 183 }; 184 185 return PluginHandlerObject; 186})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.