From 417daa264f660647b828707d783a9df581821969 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 26 Jun 2023 20:07:15 +0530 Subject: [PATCH] Temporarily comment out the plugin manager code, which seems to be conflicting with the plugin manager plugin --- ui/media/js/plugins.js | 1886 ++++++++++++++++++++-------------------- 1 file changed, 943 insertions(+), 943 deletions(-) diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js index d0511ab7..972f0d7b 100644 --- a/ui/media/js/plugins.js +++ b/ui/media/js/plugins.js @@ -85,946 +85,946 @@ async function loadUIPlugins() { /* PLUGIN MANAGER */ /* plugin tab */ -document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` - -`) - -document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', ` -
-
- Loading... -
-
-`) - -const tabPlugin = document.querySelector('#tab-plugin') -if (tabPlugin) { - linkTabContents(tabPlugin) -} - -const plugin = document.querySelector('#plugin') -plugin.innerHTML = ` -
- - - - -
-

Plugin Manager

-
Changes take effect after reloading the page
-
-
-
` -const pluginsTable = document.querySelector("#plugin-manager-section .plugins-table") -const pluginNotificationTable = document.querySelector("#plugin-notification-list .notifications-table") -const pluginNoNotification = document.querySelector("#plugin-notification-list .no-notification") - -/* notification center */ -const pluginNotificationButton = document.getElementById("plugin-notification-button"); -const pluginNotificationList = document.getElementById("plugin-notification-list"); -const notificationPill = document.getElementById("notification-pill"); -const pluginManagerSection = document.getElementById("plugin-manager-section"); -let pluginNotifications; - -// Add event listener to show/hide the action center -pluginNotificationButton.addEventListener("click", function () { - // Hide the notification pill when the action center is opened - notificationPill.style.display = "none" - pluginNotifications.lastUpdated = Date.now() - - // save the notifications - setStorageData('notifications', JSON.stringify(pluginNotifications)) - - renderPluginNotifications() - - if (pluginNotificationList.style.display === "none") { - pluginNotificationList.style.display = "block" - pluginManagerSection.style.display = "none" - } else { - pluginNotificationList.style.display = "none" - pluginManagerSection.style.display = "block" - } -}) - -document.addEventListener("tabClick", (e) => { - if (e.detail.name == 'plugin') { - pluginNotificationList.style.display = "none" - pluginManagerSection.style.display = "block" - } -}) - -async function addPluginNotification(pluginNotifications, messageText, error) { - const now = Date.now() - pluginNotifications.entries.unshift({ date: now, text: messageText, error: error }); // add new entry to the beginning of the array - if (pluginNotifications.entries.length > 50) { - pluginNotifications.entries.length = 50 // limit array length to 50 entries - } - pluginNotifications.lastUpdated = now - notificationPill.style.display = "block" - // save the notifications - await setStorageData('notifications', JSON.stringify(pluginNotifications)) -} - -function timeAgo(inputDate) { - const now = new Date(); - const date = new Date(inputDate); - const diffInSeconds = Math.floor((now - date) / 1000); - const units = [ - { name: 'year', seconds: 31536000 }, - { name: 'month', seconds: 2592000 }, - { name: 'week', seconds: 604800 }, - { name: 'day', seconds: 86400 }, - { name: 'hour', seconds: 3600 }, - { name: 'minute', seconds: 60 }, - { name: 'second', seconds: 1 } - ]; - - for (const unit of units) { - const unitValue = Math.floor(diffInSeconds / unit.seconds); - if (unitValue > 0) { - return `${unitValue} ${unit.name}${unitValue > 1 ? 's' : ''} ago`; - } - } - - return 'just now'; -} - -function convertSeconds(seconds) { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const remainingSeconds = seconds % 60; - - let timeParts = []; - if (hours === 1) { - timeParts.push(`${hours} hour`); - } else if (hours > 1) { - timeParts.push(`${hours} hours`); - } - if (minutes === 1) { - timeParts.push(`${minutes} minute`); - } else if (minutes > 1) { - timeParts.push(`${minutes} minutes`); - } - if (remainingSeconds === 1) { - timeParts.push(`${remainingSeconds} second`); - } else if (remainingSeconds > 1) { - timeParts.push(`${remainingSeconds} seconds`); - } - - return timeParts.join(', and '); -} - -function renderPluginNotifications() { - pluginNotificationTable.innerHTML = '' - - if (pluginNotifications.entries?.length > 0) { - pluginNoNotification.style.display = "none" - pluginNotificationTable.style.display = "block" - } - else { - pluginNoNotification.style.display = "block" - pluginNotificationTable.style.display = "none" - } - for (let i = 0; i < pluginNotifications.entries?.length; i++) { - const date = pluginNotifications.entries[i].date - const text = pluginNotifications.entries[i].text - const error = pluginNotifications.entries[i].error - const newRow = document.createElement('div') - - newRow.innerHTML = ` - ${text} -
${timeAgo(date)}
- `; - pluginNotificationTable.appendChild(newRow) - } -} - -/* search box */ -function filterPlugins() { - let search = pluginFilter.value.toLowerCase(); - let searchTerms = search.split(' '); - let labels = pluginsTable.querySelectorAll("label.plugin-name"); - - for (let i = 0; i < labels.length; i++) { - let label = labels[i].innerText.toLowerCase(); - let match = true; - - for (let j = 0; j < searchTerms.length; j++) { - let term = searchTerms[j].trim(); - if (term && label.indexOf(term) === -1) { - match = false; - break; - } - } - - if (match) { - labels[i].closest('.plugin-container').style.display = "flex"; - } else { - labels[i].closest('.plugin-container').style.display = "none"; - } - } -} - -// Call debounce function on filterImageModifierList function with 200ms wait time. Thanks JeLuf! -const debouncedFilterPlugins = debounce(filterPlugins, 200); - -// add the searchbox -pluginsTable.insertAdjacentHTML('beforebegin', ``) -const pluginFilter = document.getElementById("plugin-filter") // search box - -// Add the debounced function to the keyup event listener -pluginFilter.addEventListener('keyup', debouncedFilterPlugins); - -// select the text on focus -pluginFilter.addEventListener('focus', function (event) { - pluginFilter.select() -}); - -// empty the searchbox on escape -pluginFilter.addEventListener('keydown', function (event) { - if (event.key === 'Escape') { - pluginFilter.value = ''; - filterPlugins(); - } -}); - -// focus on the search box upon tab selection -document.addEventListener("tabClick", (e) => { - if (e.detail.name == 'plugin') { - pluginFilter.focus() - } -}) - -// refresh link -pluginsTable.insertAdjacentHTML('afterend', `

Refresh plugins

-

(Plugin developers, add your plugins to plugins.json)

`) -const refreshPlugins = document.getElementById("refresh-plugins") -refreshPlugins.addEventListener("click", async function (event) { - event.preventDefault() - await initPlugins(true) -}) - -function showPluginToast(message, duration = 5000, error = false, addNotification = true) { - if (addNotification === true) { - addPluginNotification(pluginNotifications, message, error) - } - try { - showToast(message, duration, error) - } catch (error) { - console.error('Error while trying to show toast:', error); - } -} - -function matchPluginFileNames(fileName1, fileName2) { - const regex = /^(.+?)(?:-\d+(\.\d+)*)?\.plugin\.js$/; - const match1 = fileName1.match(regex); - const match2 = fileName2.match(regex); - - if (match1 && match2 && match1[1] === match2[1]) { - return true; // the two file names match - } else { - return false; // the two file names do not match - } -} - -function extractFilename(filepath) { - // Normalize the path separators to forward slashes and make the file names lowercase - const normalizedFilePath = filepath.replace(/\\/g, "/").toLowerCase(); - - // Strip off the path from the file name - const fileName = normalizedFilePath.substring(normalizedFilePath.lastIndexOf("/") + 1); - - return fileName -} - -function checkFileNameInArray(paths, filePath) { - // Strip off the path from the file name - const fileName = extractFilename(filePath); - - // Check if the file name exists in the array of paths - return paths.some(path => { - // Strip off the path from the file name - const baseName = extractFilename(path); - - // Check if the file names match and return the result as a boolean - return matchPluginFileNames(fileName, baseName); - }); -} - -function isGitHub(url) { - return url.startsWith("https://raw.githubusercontent.com/") === true -} - -/* fill in the plugins table */ -function getIncompatiblePlugins(pluginId) { - const enabledPlugins = plugins.filter(plugin => plugin.enabled && plugin.id !== pluginId); - const incompatiblePlugins = enabledPlugins.filter(plugin => plugin.compatIssueIds?.includes(pluginId)); - const pluginNames = incompatiblePlugins.map(plugin => plugin.name); - if (pluginNames.length === 0) { - return null; - } - const pluginNamesList = pluginNames.map(name => `
  • ${name}
  • `).join(''); - return ``; -} - -async function initPluginTable(plugins) { - pluginsTable.innerHTML = '' - plugins.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - plugins.forEach(plugin => { - const name = plugin.name - const author = plugin.author ? ', by ' + plugin.author : '' - const version = plugin.version ? ' (version: ' + plugin.version + ')' : '' - const warning = getIncompatiblePlugins(plugin.id) ? `This plugin might conflict with:${getIncompatiblePlugins(plugin.id)}` : '' - const note = plugin.description ? `${plugin.description.replaceAll('\n', '
    ')}
    ` : `No description`; - const icon = plugin.icon ? `` : ''; - const newRow = document.createElement('div') - const localPluginFound = checkFileNameInArray(localPlugins, plugin.url) - - newRow.innerHTML = ` -
    ${icon}
    -
    ${warning}${note}Source: ${extractFilename(plugin.url)}
    -
    - ${localPluginFound ? "Installed locally" : - (plugin.localInstallOnly ? 'Download and
    install manually
    ' : - (isGitHub(plugin.url) ? - '' : - '' - ) - ) - } -
    `; - newRow.classList.add('plugin-container') - //console.log(plugin.id, plugin.localInstallOnly) - pluginsTable.appendChild(newRow) - const pluginManualInstall = pluginsTable.querySelector('#plugin-' + plugin.id + '-install') - updateManualInstallButtonCaption() - - // checkbox event handler - const pluginToggle = pluginsTable.querySelector('#plugin-' + plugin.id) - if (pluginToggle !== null) { - pluginToggle.checked = plugin.enabled // set initial state of checkbox - pluginToggle.addEventListener('change', async () => { - const container = pluginToggle.closest(".plugin-container"); - const warningElement = container.querySelector(".plugin-warning"); - - // if the plugin got enabled, download the plugin's code - plugin.enabled = pluginToggle.checked - if (plugin.enabled) { - const pluginSource = await getDocument(plugin.url); - if (pluginSource !== null) { - // Store the current scroll position before navigating away - const currentPosition = window.pageYOffset; - initPluginTable(plugins) - // When returning to the page, set the scroll position to the stored value - window.scrollTo(0, currentPosition); - warningElement?.classList.remove("hide"); - plugin.code = pluginSource - loadPlugins([plugin]) - console.log(`Plugin ${plugin.name} installed`); - showPluginToast("Plugin " + plugin.name + " installed"); - } - else { - plugin.enabled = false - pluginToggle.checked = false - console.error(`Couldn't download plugin ${plugin.name}`); - showPluginToast("Failed to install " + plugin.name + " (Couldn't fetch " + extractFilename(plugin.url) + ")", 5000, true); - } - } else { - warningElement?.classList.add("hide"); - // Store the current scroll position before navigating away - const currentPosition = window.pageYOffset; - initPluginTable(plugins) - // When returning to the page, set the scroll position to the stored value - window.scrollTo(0, currentPosition); - console.log(`Plugin ${plugin.name} uninstalled`); - showPluginToast("Plugin " + plugin.name + " uninstalled"); - } - await setStorageData('plugins', JSON.stringify(plugins)) - }) - } - - // manual install event handler - if (pluginManualInstall !== null) { - pluginManualInstall.addEventListener('click', async () => { - pluginDialogOpenDialog(inputOK, inputCancel) - pluginDialogTextarea.value = plugin.code ? plugin.code : '' - pluginDialogTextarea.select() - pluginDialogTextarea.focus() - }) - } - // Dialog OK - async function inputOK() { - let pluginSource = pluginDialogTextarea.value - // remove empty lines and trim existing lines - plugin.code = pluginSource - if (pluginSource.trim() !== '') { - plugin.enabled = true - console.log(`Plugin ${plugin.name} installed`); - showPluginToast("Plugin " + plugin.name + " installed"); - } - else { - plugin.enabled = false - console.log(`No code provided for plugin ${plugin.name}, disabling the plugin`); - showPluginToast("No code provided for plugin " + plugin.name + ", disabling the plugin"); - } - updateManualInstallButtonCaption() - await setStorageData('plugins', JSON.stringify(plugins)) - } - // Dialog Cancel - async function inputCancel() { - plugin.enabled = false - console.log(`Installation of plugin ${plugin.name} cancelled`); - showPluginToast("Cancelled installation of " + plugin.name); - } - // update button caption - function updateManualInstallButtonCaption() { - if (pluginManualInstall !== null) { - pluginManualInstall.innerHTML = plugin.code === undefined || plugin.code.trim() === '' ? 'Install' : 'Edit' - } - } - }) - prettifyInputs(pluginsTable) - filterPlugins() -} - -/* version management. Thanks Madrang! */ -const parseVersion = function (versionString, options = {}) { - if (typeof versionString === "undefined") { - throw new Error("versionString is undefined."); - } - if (typeof versionString !== "string") { - throw new Error("versionString is not a string."); - } - const lexicographical = options && options.lexicographical; - const zeroExtend = options && options.zeroExtend; - let versionParts = versionString.split('.'); - function isValidPart(x) { - const re = (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/); - return re.test(x); - } - - if (!versionParts.every(isValidPart)) { - throw new Error("Version string is invalid."); - } - - if (zeroExtend) { - while (versionParts.length < 4) { - versionParts.push("0"); - } - } - if (!lexicographical) { - versionParts = versionParts.map(Number); - } - return versionParts; -}; - -const versionCompare = function (v1, v2, options = {}) { - if (typeof v1 == "undefined") { - throw new Error("vi is undefined."); - } - if (typeof v2 === "undefined") { - throw new Error("v2 is undefined."); - } - - let v1parts; - if (typeof v1 === "string") { - v1parts = parseVersion(v1, options); - } else if (Array.isArray(v1)) { - v1parts = [...v1]; - if (!v1parts.every(p => typeof p === "number" && p !== NaN)) { - throw new Error("v1 part array does not only contains numbers."); - } - } else { - throw new Error("v1 is of an unexpected type: " + typeof v1); - } - - let v2parts; - if (typeof v2 === "string") { - v2parts = parseVersion(v2, options); - } else if (Array.isArray(v2)) { - v2parts = [...v2]; - if (!v2parts.every(p => typeof p === "number" && p !== NaN)) { - throw new Error("v2 part array does not only contains numbers."); - } - } else { - throw new Error("v2 is of an unexpected type: " + typeof v2); - } - - while (v1parts.length < v2parts.length) { - v1parts.push("0"); - } - while (v2parts.length < v1parts.length) { - v2parts.push("0"); - } - - for (let i = 0; i < v1parts.length; ++i) { - if (v2parts.length == i) { - return 1; - } - if (v1parts[i] == v2parts[i]) { - continue; - } else if (v1parts[i] > v2parts[i]) { - return 1; - } else { - return -1; - } - } - return 0; -}; - -function filterPluginsByMinEDVersion(plugins, EDVersion) { - const filteredPlugins = plugins.filter(plugin => { - if (plugin.minEDVersion) { - return versionCompare(plugin.minEDVersion, EDVersion) <= 0; - } - return true; - }); - - return filteredPlugins; -} - -function extractVersionNumber(elem) { - const versionStr = elem.innerHTML; - const regex = /v(\d+\.\d+\.\d+)/; - const matches = regex.exec(versionStr); - if (matches && matches.length > 1) { - return matches[1]; - } else { - return null; - } -} -const EasyDiffusionVersion = extractVersionNumber(document.querySelector('#top-nav > #logo')) - -/* PLUGIN MANAGEMENT */ -let plugins -let localPlugins -let initPluginsInProgress = false - -async function initPlugins(refreshPlugins = false) { - let pluginsLoaded - if (initPluginsInProgress === true) { - return - } - initPluginsInProgress = true - - const res = await fetch('/get/ui_plugins') - if (!res.ok) { - console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`) - } - else { - localPlugins = await res.json() - } - - if (refreshPlugins === false) { - // load the notifications - pluginNotifications = await getStorageData('notifications') - if (typeof pluginNotifications === "string") { - try { - pluginNotifications = JSON.parse(pluginNotifications) - } catch (e) { - console.error("Failed to parse pluginNotifications", e); - pluginNotifications = {}; - pluginNotifications.entries = []; - } - } - if (pluginNotifications !== undefined) { - if (pluginNotifications.entries && pluginNotifications.entries.length > 0 && pluginNotifications.entries[0].date && pluginNotifications.lastUpdated <= pluginNotifications.entries[0].date) { - notificationPill.style.display = "block"; - } - } else { - pluginNotifications = {}; - pluginNotifications.entries = []; - } - - // try and load plugins from local cache - plugins = await getStorageData('plugins') - if (plugins !== undefined) { - plugins = JSON.parse(plugins) - - // remove duplicate entries if any (should not happen) - plugins = deduplicatePluginsById(plugins) - - // remove plugins that don't meet the min ED version requirement - plugins = filterPluginsByMinEDVersion(plugins, EasyDiffusionVersion) - - // remove from plugins the entries that don't have mandatory fields (id, name, url) - plugins = plugins.filter((plugin) => { return plugin.id !== '' && plugin.name !== '' && plugin.url !== ''; }); - - // populate the table - initPluginTable(plugins) - await loadPlugins(plugins) - pluginsLoaded = true - } - else { - plugins = [] - pluginsLoaded = false - } - } - - // update plugins asynchronously (updated versions will be available next time the UI is loaded) - if (refreshAllowed()) { - let pluginCatalog = await getDocument(PLUGIN_CATALOG) - if (pluginCatalog !== null) { - let parseError = false; - try { - pluginCatalog = JSON.parse(pluginCatalog); - console.log('Plugin catalog successfully downloaded'); - } catch (error) { - console.error('Error parsing plugin catalog:', error); - parseError = true; - } - - if (!parseError) { - await downloadPlugins(pluginCatalog, plugins, refreshPlugins) - - // update compatIssueIds - updateCompatIssueIds() - - // remove plugins that don't meet the min ED version requirement - plugins = filterPluginsByMinEDVersion(plugins, EasyDiffusionVersion) - - // remove from plugins the entries that don't have mandatory fields (id, name, url) - plugins = plugins.filter((plugin) => { return plugin.id !== '' && plugin.name !== '' && plugin.url !== ''; }); - - // remove from plugins the entries that no longer exist in the catalog - plugins = plugins.filter((plugin) => { return pluginCatalog.some((p) => p.id === plugin.id) }); - - if (pluginCatalog.length > plugins.length) { - const newPlugins = pluginCatalog.filter((plugin) => { - return !plugins.some((p) => p.id === plugin.id); - }); - - newPlugins.forEach((plugin, index) => { - setTimeout(() => { - showPluginToast(`New plugin "${plugin.name}" is available.`); - }, (index + 1) * 1000); - }); - } - - let pluginsJson; - try { - pluginsJson = JSON.stringify(plugins); // attempt to parse plugins to JSON - } catch (error) { - console.error('Error converting plugins to JSON:', error); - } - - if (pluginsJson) { // only store the data if pluginsJson is not null or undefined - await setStorageData('plugins', pluginsJson) - } - - // refresh the display of the plugins table - initPluginTable(plugins) - if (pluginsLoaded && pluginsLoaded === false) { - loadPlugins(plugins) - } - } - } - } - else { - if (refreshPlugins) { - showPluginToast('Plugins have been refreshed recently, refresh will be available in ' + convertSeconds(getTimeUntilNextRefresh()), 5000, true, false) - } - } - initPluginsInProgress = false -} - -function updateMetaTagPlugins(plugin) { - // Update the meta tag with the list of loaded plugins - let metaTag = document.querySelector('meta[name="plugins"]'); - if (metaTag === null) { - metaTag = document.createElement('meta'); - metaTag.name = 'plugins'; - document.head.appendChild(metaTag); - } - const pluginArray = [...(metaTag.content ? metaTag.content.split(',') : []), plugin.id]; - metaTag.content = pluginArray.join(','); -} - -function updateCompatIssueIds() { - // Loop through each plugin - plugins.forEach(plugin => { - // Check if the plugin has `compatIssueIds` property - if (plugin.compatIssueIds !== undefined) { - // Loop through each of the `compatIssueIds` - plugin.compatIssueIds.forEach(issueId => { - // Find the plugin with the corresponding `issueId` - const issuePlugin = plugins.find(p => p.id === issueId); - // If the corresponding plugin is found, initialize its `compatIssueIds` property with an empty array if it's undefined - if (issuePlugin) { - if (issuePlugin.compatIssueIds === undefined) { - issuePlugin.compatIssueIds = []; - } - // If the current plugin's ID is not already in the `compatIssueIds` array, add it - if (!issuePlugin.compatIssueIds.includes(plugin.id)) { - issuePlugin.compatIssueIds.push(plugin.id); - } - } - }); - } else { - // If the plugin doesn't have `compatIssueIds` property, initialize it with an empty array - plugin.compatIssueIds = []; - } - }); -} - -function deduplicatePluginsById(plugins) { - const seenIds = new Set(); - const deduplicatedPlugins = []; - - for (const plugin of plugins) { - if (!seenIds.has(plugin.id)) { - seenIds.add(plugin.id); - deduplicatedPlugins.push(plugin); - } else { - // favor dupes that have enabled == true - const index = deduplicatedPlugins.findIndex(p => p.id === plugin.id); - if (index >= 0) { - if (plugin.enabled) { - deduplicatedPlugins[index] = plugin; - } - } - } - } - - return deduplicatedPlugins; -} - -async function loadPlugins(plugins) { - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - if (plugin.enabled === true && plugin.localInstallOnly !== true) { - const localPluginFound = checkFileNameInArray(localPlugins, plugin.url); - if (!localPluginFound) { - try { - // Indirect eval to work around sloppy plugin implementations - const indirectEval = { eval }; - console.log("Loading plugin " + plugin.name); - indirectEval.eval(plugin.code); - console.log("Plugin " + plugin.name + " loaded"); - await updateMetaTagPlugins(plugin); // add plugin to the meta tag - } catch (err) { - showPluginToast("Error loading plugin " + plugin.name + " (" + err.message + ")", null, true); - console.error("Error loading plugin " + plugin.name + ": " + err.message); - } - } else { - console.log("Skipping plugin " + plugin.name + " (installed locally)"); - } - } - } -} - -async function getFileHash(url) { - const regex = /^https:\/\/raw\.githubusercontent\.com\/(?[^/]+)\/(?[^/]+)\/(?[^/]+)\/(?.+)$/; - const match = url.match(regex); - if (!match) { - console.error('Invalid GitHub repository URL.'); - return Promise.resolve(null); - } - const owner = match.groups.owner; - const repo = match.groups.repo; - const branch = match.groups.branch; - const filePath = match.groups.filePath; - const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`; - - try { - const response = await fetch(apiUrl); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}, url: ${apiUrl}`); - } - const data = await response.json(); - return data.sha; - } catch (error) { - console.error('Error fetching data from url:', apiUrl, 'Error:', error); - return null; - } -} - -// only allow two refresh per hour -function getTimeUntilNextRefresh() { - const lastRuns = JSON.parse(localStorage.getItem('lastRuns') || '[]'); - const currentTime = new Date().getTime(); - const numRunsLast60Min = lastRuns.filter(run => currentTime - run <= 60 * 60 * 1000).length; - - if (numRunsLast60Min >= 2) { - return 3600 - Math.round((currentTime - lastRuns[lastRuns.length - 1]) / 1000); - } - - return 0; -} - -function refreshAllowed() { - const timeUntilNextRefresh = getTimeUntilNextRefresh(); - - if (timeUntilNextRefresh > 0) { - console.log(`Next refresh available in ${timeUntilNextRefresh} seconds`); - return false; - } - - const lastRuns = JSON.parse(localStorage.getItem('lastRuns') || '[]'); - const currentTime = new Date().getTime(); - lastRuns.push(currentTime); - localStorage.setItem('lastRuns', JSON.stringify(lastRuns)); - return true; -} - -async function downloadPlugins(pluginCatalog, plugins, refreshPlugins) { - // download the plugins as needed - for (const plugin of pluginCatalog) { - //console.log(plugin.id, plugin.url) - const existingPlugin = plugins.find(p => p.id === plugin.id); - // get the file hash in the GitHub repo - let sha - if (isGitHub(plugin.url) && existingPlugin?.enabled === true) { - sha = await getFileHash(plugin.url) - } - if (plugin.localInstallOnly !== true && isGitHub(plugin.url) && existingPlugin?.enabled === true && (refreshPlugins || (existingPlugin.sha !== undefined && existingPlugin.sha !== null && existingPlugin.sha !== sha) || existingPlugin?.code === undefined)) { - const pluginSource = await getDocument(plugin.url); - if (pluginSource !== null && pluginSource !== existingPlugin.code) { - console.log(`Plugin ${plugin.name} updated`); - showPluginToast("Plugin " + plugin.name + " updated", 5000); - // Update the corresponding plugin - const updatedPlugin = { - ...existingPlugin, - icon: plugin.icon ? plugin.icon : "fa-puzzle-piece", - id: plugin.id, - name: plugin.name, - description: plugin.description, - url: plugin.url, - localInstallOnly: Boolean(plugin.localInstallOnly), - version: plugin.version, - code: pluginSource, - author: plugin.author, - sha: sha, - compatIssueIds: plugin.compatIssueIds - }; - // Replace the old plugin in the plugins array - const pluginIndex = plugins.indexOf(existingPlugin); - if (pluginIndex >= 0) { - plugins.splice(pluginIndex, 1, updatedPlugin); - } else { - plugins.push(updatedPlugin); - } - } - } - else if (existingPlugin !== undefined) { - // Update the corresponding plugin's metadata - const updatedPlugin = { - ...existingPlugin, - icon: plugin.icon ? plugin.icon : "fa-puzzle-piece", - id: plugin.id, - name: plugin.name, - description: plugin.description, - url: plugin.url, - localInstallOnly: Boolean(plugin.localInstallOnly), - version: plugin.version, - author: plugin.author, - compatIssueIds: plugin.compatIssueIds - }; - // Replace the old plugin in the plugins array - const pluginIndex = plugins.indexOf(existingPlugin); - plugins.splice(pluginIndex, 1, updatedPlugin); - } - else { - plugins.push(plugin); - } - } -} - -async function getDocument(url) { - try { - let response = await fetch(url === PLUGIN_CATALOG ? PLUGIN_CATALOG : url, { cache: "no-cache" }); - if (!response.ok) { - throw new Error(`Response error: ${response.status} ${response.statusText}`); - } - let document = await response.text(); - return document; - } catch (error) { - showPluginToast("Couldn't fetch " + extractFilename(url) + " (" + error + ")", null, true); - console.error(error); - return null; - } -} - -/* MODAL DIALOG */ -const pluginDialogDialog = document.createElement("div"); -pluginDialogDialog.id = "pluginDialog-input-dialog"; -pluginDialogDialog.style.display = "none"; - -pluginDialogDialog.innerHTML = ` -
    -
    -
    -

    Paste the plugin's code here

    - -
    -
    - -
    -
    - - -
    -
    -`; - -document.body.appendChild(pluginDialogDialog); - -const pluginDialogOverlay = document.querySelector(".pluginDialog-dialog-overlay"); -const pluginDialogOkButton = document.getElementById("pluginDialog-input-ok"); -const pluginDialogCancelButton = document.getElementById("pluginDialog-input-cancel"); -const pluginDialogCloseButton = document.querySelector(".pluginDialog-dialog-close-button"); -const pluginDialogTextarea = document.getElementById("pluginDialog-input-textarea"); -let callbackOK -let callbackCancel - -function pluginDialogOpenDialog(inputOK, inputCancel) { - pluginDialogDialog.style.display = "block"; - callbackOK = inputOK - callbackCancel = inputCancel -} - -function pluginDialogCloseDialog() { - pluginDialogDialog.style.display = "none"; -} - -function pluginDialogHandleOkClick() { - const userInput = pluginDialogTextarea.value; - // Do something with the user input - callbackOK() - pluginDialogCloseDialog(); -} - -function pluginDialogHandleCancelClick() { - callbackCancel() - pluginDialogCloseDialog(); -} - -function pluginDialogHandleOverlayClick(event) { - if (event.target === pluginDialogOverlay) { - pluginDialogCloseDialog(); - } -} - -function pluginDialogHandleKeyDown(event) { - if ((event.key === "Enter" && event.ctrlKey) || event.key === "Escape") { - event.preventDefault(); - if (event.key === "Enter" && event.ctrlKey) { - pluginDialogHandleOkClick(); - } else { - pluginDialogCloseDialog(); - } - } -} - -pluginDialogTextarea.addEventListener("keydown", pluginDialogHandleKeyDown); -pluginDialogOkButton.addEventListener("click", pluginDialogHandleOkClick); -pluginDialogCancelButton.addEventListener("click", pluginDialogHandleCancelClick); -pluginDialogCloseButton.addEventListener("click", pluginDialogCloseDialog); -pluginDialogOverlay.addEventListener("click", pluginDialogHandleOverlayClick); +// document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` +// +// `) + +// document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', ` +//
    +//
    +// Loading... +//
    +//
    +// `) + +// const tabPlugin = document.querySelector('#tab-plugin') +// if (tabPlugin) { +// linkTabContents(tabPlugin) +// } + +// const plugin = document.querySelector('#plugin') +// plugin.innerHTML = ` +//
    +// +// +// +// +//
    +//

    Plugin Manager

    +//
    Changes take effect after reloading the page
    +//
    +//
    +//
    ` +// const pluginsTable = document.querySelector("#plugin-manager-section .plugins-table") +// const pluginNotificationTable = document.querySelector("#plugin-notification-list .notifications-table") +// const pluginNoNotification = document.querySelector("#plugin-notification-list .no-notification") + +// /* notification center */ +// const pluginNotificationButton = document.getElementById("plugin-notification-button"); +// const pluginNotificationList = document.getElementById("plugin-notification-list"); +// const notificationPill = document.getElementById("notification-pill"); +// const pluginManagerSection = document.getElementById("plugin-manager-section"); +// let pluginNotifications; + +// // Add event listener to show/hide the action center +// pluginNotificationButton.addEventListener("click", function () { +// // Hide the notification pill when the action center is opened +// notificationPill.style.display = "none" +// pluginNotifications.lastUpdated = Date.now() + +// // save the notifications +// setStorageData('notifications', JSON.stringify(pluginNotifications)) + +// renderPluginNotifications() + +// if (pluginNotificationList.style.display === "none") { +// pluginNotificationList.style.display = "block" +// pluginManagerSection.style.display = "none" +// } else { +// pluginNotificationList.style.display = "none" +// pluginManagerSection.style.display = "block" +// } +// }) + +// document.addEventListener("tabClick", (e) => { +// if (e.detail.name == 'plugin') { +// pluginNotificationList.style.display = "none" +// pluginManagerSection.style.display = "block" +// } +// }) + +// async function addPluginNotification(pluginNotifications, messageText, error) { +// const now = Date.now() +// pluginNotifications.entries.unshift({ date: now, text: messageText, error: error }); // add new entry to the beginning of the array +// if (pluginNotifications.entries.length > 50) { +// pluginNotifications.entries.length = 50 // limit array length to 50 entries +// } +// pluginNotifications.lastUpdated = now +// notificationPill.style.display = "block" +// // save the notifications +// await setStorageData('notifications', JSON.stringify(pluginNotifications)) +// } + +// function timeAgo(inputDate) { +// const now = new Date(); +// const date = new Date(inputDate); +// const diffInSeconds = Math.floor((now - date) / 1000); +// const units = [ +// { name: 'year', seconds: 31536000 }, +// { name: 'month', seconds: 2592000 }, +// { name: 'week', seconds: 604800 }, +// { name: 'day', seconds: 86400 }, +// { name: 'hour', seconds: 3600 }, +// { name: 'minute', seconds: 60 }, +// { name: 'second', seconds: 1 } +// ]; + +// for (const unit of units) { +// const unitValue = Math.floor(diffInSeconds / unit.seconds); +// if (unitValue > 0) { +// return `${unitValue} ${unit.name}${unitValue > 1 ? 's' : ''} ago`; +// } +// } + +// return 'just now'; +// } + +// function convertSeconds(seconds) { +// const hours = Math.floor(seconds / 3600); +// const minutes = Math.floor((seconds % 3600) / 60); +// const remainingSeconds = seconds % 60; + +// let timeParts = []; +// if (hours === 1) { +// timeParts.push(`${hours} hour`); +// } else if (hours > 1) { +// timeParts.push(`${hours} hours`); +// } +// if (minutes === 1) { +// timeParts.push(`${minutes} minute`); +// } else if (minutes > 1) { +// timeParts.push(`${minutes} minutes`); +// } +// if (remainingSeconds === 1) { +// timeParts.push(`${remainingSeconds} second`); +// } else if (remainingSeconds > 1) { +// timeParts.push(`${remainingSeconds} seconds`); +// } + +// return timeParts.join(', and '); +// } + +// function renderPluginNotifications() { +// pluginNotificationTable.innerHTML = '' + +// if (pluginNotifications.entries?.length > 0) { +// pluginNoNotification.style.display = "none" +// pluginNotificationTable.style.display = "block" +// } +// else { +// pluginNoNotification.style.display = "block" +// pluginNotificationTable.style.display = "none" +// } +// for (let i = 0; i < pluginNotifications.entries?.length; i++) { +// const date = pluginNotifications.entries[i].date +// const text = pluginNotifications.entries[i].text +// const error = pluginNotifications.entries[i].error +// const newRow = document.createElement('div') + +// newRow.innerHTML = ` +// ${text} +//
    ${timeAgo(date)}
    +// `; +// pluginNotificationTable.appendChild(newRow) +// } +// } + +// /* search box */ +// function filterPlugins() { +// let search = pluginFilter.value.toLowerCase(); +// let searchTerms = search.split(' '); +// let labels = pluginsTable.querySelectorAll("label.plugin-name"); + +// for (let i = 0; i < labels.length; i++) { +// let label = labels[i].innerText.toLowerCase(); +// let match = true; + +// for (let j = 0; j < searchTerms.length; j++) { +// let term = searchTerms[j].trim(); +// if (term && label.indexOf(term) === -1) { +// match = false; +// break; +// } +// } + +// if (match) { +// labels[i].closest('.plugin-container').style.display = "flex"; +// } else { +// labels[i].closest('.plugin-container').style.display = "none"; +// } +// } +// } + +// // Call debounce function on filterImageModifierList function with 200ms wait time. Thanks JeLuf! +// const debouncedFilterPlugins = debounce(filterPlugins, 200); + +// // add the searchbox +// pluginsTable.insertAdjacentHTML('beforebegin', ``) +// const pluginFilter = document.getElementById("plugin-filter") // search box + +// // Add the debounced function to the keyup event listener +// pluginFilter.addEventListener('keyup', debouncedFilterPlugins); + +// // select the text on focus +// pluginFilter.addEventListener('focus', function (event) { +// pluginFilter.select() +// }); + +// // empty the searchbox on escape +// pluginFilter.addEventListener('keydown', function (event) { +// if (event.key === 'Escape') { +// pluginFilter.value = ''; +// filterPlugins(); +// } +// }); + +// // focus on the search box upon tab selection +// document.addEventListener("tabClick", (e) => { +// if (e.detail.name == 'plugin') { +// pluginFilter.focus() +// } +// }) + +// // refresh link +// pluginsTable.insertAdjacentHTML('afterend', `

    Refresh plugins

    +//

    (Plugin developers, add your plugins to plugins.json)

    `) +// const refreshPlugins = document.getElementById("refresh-plugins") +// refreshPlugins.addEventListener("click", async function (event) { +// event.preventDefault() +// await initPlugins(true) +// }) + +// function showPluginToast(message, duration = 5000, error = false, addNotification = true) { +// if (addNotification === true) { +// addPluginNotification(pluginNotifications, message, error) +// } +// try { +// showToast(message, duration, error) +// } catch (error) { +// console.error('Error while trying to show toast:', error); +// } +// } + +// function matchPluginFileNames(fileName1, fileName2) { +// const regex = /^(.+?)(?:-\d+(\.\d+)*)?\.plugin\.js$/; +// const match1 = fileName1.match(regex); +// const match2 = fileName2.match(regex); + +// if (match1 && match2 && match1[1] === match2[1]) { +// return true; // the two file names match +// } else { +// return false; // the two file names do not match +// } +// } + +// function extractFilename(filepath) { +// // Normalize the path separators to forward slashes and make the file names lowercase +// const normalizedFilePath = filepath.replace(/\\/g, "/").toLowerCase(); + +// // Strip off the path from the file name +// const fileName = normalizedFilePath.substring(normalizedFilePath.lastIndexOf("/") + 1); + +// return fileName +// } + +// function checkFileNameInArray(paths, filePath) { +// // Strip off the path from the file name +// const fileName = extractFilename(filePath); + +// // Check if the file name exists in the array of paths +// return paths.some(path => { +// // Strip off the path from the file name +// const baseName = extractFilename(path); + +// // Check if the file names match and return the result as a boolean +// return matchPluginFileNames(fileName, baseName); +// }); +// } + +// function isGitHub(url) { +// return url.startsWith("https://raw.githubusercontent.com/") === true +// } + +// /* fill in the plugins table */ +// function getIncompatiblePlugins(pluginId) { +// const enabledPlugins = plugins.filter(plugin => plugin.enabled && plugin.id !== pluginId); +// const incompatiblePlugins = enabledPlugins.filter(plugin => plugin.compatIssueIds?.includes(pluginId)); +// const pluginNames = incompatiblePlugins.map(plugin => plugin.name); +// if (pluginNames.length === 0) { +// return null; +// } +// const pluginNamesList = pluginNames.map(name => `
  • ${name}
  • `).join(''); +// return `
      ${pluginNamesList}
    `; +// } + +// async function initPluginTable(plugins) { +// pluginsTable.innerHTML = '' +// plugins.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) +// plugins.forEach(plugin => { +// const name = plugin.name +// const author = plugin.author ? ', by ' + plugin.author : '' +// const version = plugin.version ? ' (version: ' + plugin.version + ')' : '' +// const warning = getIncompatiblePlugins(plugin.id) ? `This plugin might conflict with:${getIncompatiblePlugins(plugin.id)}` : '' +// const note = plugin.description ? `${plugin.description.replaceAll('\n', '
    ')}
    ` : `No description`; +// const icon = plugin.icon ? `` : ''; +// const newRow = document.createElement('div') +// const localPluginFound = checkFileNameInArray(localPlugins, plugin.url) + +// newRow.innerHTML = ` +//
    ${icon}
    +//
    ${warning}${note}Source: ${extractFilename(plugin.url)}
    +//
    +// ${localPluginFound ? "Installed locally" : +// (plugin.localInstallOnly ? 'Download and
    install manually
    ' : +// (isGitHub(plugin.url) ? +// '' : +// '' +// ) +// ) +// } +//
    `; +// newRow.classList.add('plugin-container') +// //console.log(plugin.id, plugin.localInstallOnly) +// pluginsTable.appendChild(newRow) +// const pluginManualInstall = pluginsTable.querySelector('#plugin-' + plugin.id + '-install') +// updateManualInstallButtonCaption() + +// // checkbox event handler +// const pluginToggle = pluginsTable.querySelector('#plugin-' + plugin.id) +// if (pluginToggle !== null) { +// pluginToggle.checked = plugin.enabled // set initial state of checkbox +// pluginToggle.addEventListener('change', async () => { +// const container = pluginToggle.closest(".plugin-container"); +// const warningElement = container.querySelector(".plugin-warning"); + +// // if the plugin got enabled, download the plugin's code +// plugin.enabled = pluginToggle.checked +// if (plugin.enabled) { +// const pluginSource = await getDocument(plugin.url); +// if (pluginSource !== null) { +// // Store the current scroll position before navigating away +// const currentPosition = window.pageYOffset; +// initPluginTable(plugins) +// // When returning to the page, set the scroll position to the stored value +// window.scrollTo(0, currentPosition); +// warningElement?.classList.remove("hide"); +// plugin.code = pluginSource +// loadPlugins([plugin]) +// console.log(`Plugin ${plugin.name} installed`); +// showPluginToast("Plugin " + plugin.name + " installed"); +// } +// else { +// plugin.enabled = false +// pluginToggle.checked = false +// console.error(`Couldn't download plugin ${plugin.name}`); +// showPluginToast("Failed to install " + plugin.name + " (Couldn't fetch " + extractFilename(plugin.url) + ")", 5000, true); +// } +// } else { +// warningElement?.classList.add("hide"); +// // Store the current scroll position before navigating away +// const currentPosition = window.pageYOffset; +// initPluginTable(plugins) +// // When returning to the page, set the scroll position to the stored value +// window.scrollTo(0, currentPosition); +// console.log(`Plugin ${plugin.name} uninstalled`); +// showPluginToast("Plugin " + plugin.name + " uninstalled"); +// } +// await setStorageData('plugins', JSON.stringify(plugins)) +// }) +// } + +// // manual install event handler +// if (pluginManualInstall !== null) { +// pluginManualInstall.addEventListener('click', async () => { +// pluginDialogOpenDialog(inputOK, inputCancel) +// pluginDialogTextarea.value = plugin.code ? plugin.code : '' +// pluginDialogTextarea.select() +// pluginDialogTextarea.focus() +// }) +// } +// // Dialog OK +// async function inputOK() { +// let pluginSource = pluginDialogTextarea.value +// // remove empty lines and trim existing lines +// plugin.code = pluginSource +// if (pluginSource.trim() !== '') { +// plugin.enabled = true +// console.log(`Plugin ${plugin.name} installed`); +// showPluginToast("Plugin " + plugin.name + " installed"); +// } +// else { +// plugin.enabled = false +// console.log(`No code provided for plugin ${plugin.name}, disabling the plugin`); +// showPluginToast("No code provided for plugin " + plugin.name + ", disabling the plugin"); +// } +// updateManualInstallButtonCaption() +// await setStorageData('plugins', JSON.stringify(plugins)) +// } +// // Dialog Cancel +// async function inputCancel() { +// plugin.enabled = false +// console.log(`Installation of plugin ${plugin.name} cancelled`); +// showPluginToast("Cancelled installation of " + plugin.name); +// } +// // update button caption +// function updateManualInstallButtonCaption() { +// if (pluginManualInstall !== null) { +// pluginManualInstall.innerHTML = plugin.code === undefined || plugin.code.trim() === '' ? 'Install' : 'Edit' +// } +// } +// }) +// prettifyInputs(pluginsTable) +// filterPlugins() +// } + +// /* version management. Thanks Madrang! */ +// const parseVersion = function (versionString, options = {}) { +// if (typeof versionString === "undefined") { +// throw new Error("versionString is undefined."); +// } +// if (typeof versionString !== "string") { +// throw new Error("versionString is not a string."); +// } +// const lexicographical = options && options.lexicographical; +// const zeroExtend = options && options.zeroExtend; +// let versionParts = versionString.split('.'); +// function isValidPart(x) { +// const re = (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/); +// return re.test(x); +// } + +// if (!versionParts.every(isValidPart)) { +// throw new Error("Version string is invalid."); +// } + +// if (zeroExtend) { +// while (versionParts.length < 4) { +// versionParts.push("0"); +// } +// } +// if (!lexicographical) { +// versionParts = versionParts.map(Number); +// } +// return versionParts; +// }; + +// const versionCompare = function (v1, v2, options = {}) { +// if (typeof v1 == "undefined") { +// throw new Error("vi is undefined."); +// } +// if (typeof v2 === "undefined") { +// throw new Error("v2 is undefined."); +// } + +// let v1parts; +// if (typeof v1 === "string") { +// v1parts = parseVersion(v1, options); +// } else if (Array.isArray(v1)) { +// v1parts = [...v1]; +// if (!v1parts.every(p => typeof p === "number" && p !== NaN)) { +// throw new Error("v1 part array does not only contains numbers."); +// } +// } else { +// throw new Error("v1 is of an unexpected type: " + typeof v1); +// } + +// let v2parts; +// if (typeof v2 === "string") { +// v2parts = parseVersion(v2, options); +// } else if (Array.isArray(v2)) { +// v2parts = [...v2]; +// if (!v2parts.every(p => typeof p === "number" && p !== NaN)) { +// throw new Error("v2 part array does not only contains numbers."); +// } +// } else { +// throw new Error("v2 is of an unexpected type: " + typeof v2); +// } + +// while (v1parts.length < v2parts.length) { +// v1parts.push("0"); +// } +// while (v2parts.length < v1parts.length) { +// v2parts.push("0"); +// } + +// for (let i = 0; i < v1parts.length; ++i) { +// if (v2parts.length == i) { +// return 1; +// } +// if (v1parts[i] == v2parts[i]) { +// continue; +// } else if (v1parts[i] > v2parts[i]) { +// return 1; +// } else { +// return -1; +// } +// } +// return 0; +// }; + +// function filterPluginsByMinEDVersion(plugins, EDVersion) { +// const filteredPlugins = plugins.filter(plugin => { +// if (plugin.minEDVersion) { +// return versionCompare(plugin.minEDVersion, EDVersion) <= 0; +// } +// return true; +// }); + +// return filteredPlugins; +// } + +// function extractVersionNumber(elem) { +// const versionStr = elem.innerHTML; +// const regex = /v(\d+\.\d+\.\d+)/; +// const matches = regex.exec(versionStr); +// if (matches && matches.length > 1) { +// return matches[1]; +// } else { +// return null; +// } +// } +// const EasyDiffusionVersion = extractVersionNumber(document.querySelector('#top-nav > #logo')) + +// /* PLUGIN MANAGEMENT */ +// let plugins +// let localPlugins +// let initPluginsInProgress = false + +// async function initPlugins(refreshPlugins = false) { +// let pluginsLoaded +// if (initPluginsInProgress === true) { +// return +// } +// initPluginsInProgress = true + +// const res = await fetch('/get/ui_plugins') +// if (!res.ok) { +// console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`) +// } +// else { +// localPlugins = await res.json() +// } + +// if (refreshPlugins === false) { +// // load the notifications +// pluginNotifications = await getStorageData('notifications') +// if (typeof pluginNotifications === "string") { +// try { +// pluginNotifications = JSON.parse(pluginNotifications) +// } catch (e) { +// console.error("Failed to parse pluginNotifications", e); +// pluginNotifications = {}; +// pluginNotifications.entries = []; +// } +// } +// if (pluginNotifications !== undefined) { +// if (pluginNotifications.entries && pluginNotifications.entries.length > 0 && pluginNotifications.entries[0].date && pluginNotifications.lastUpdated <= pluginNotifications.entries[0].date) { +// notificationPill.style.display = "block"; +// } +// } else { +// pluginNotifications = {}; +// pluginNotifications.entries = []; +// } + +// // try and load plugins from local cache +// plugins = await getStorageData('plugins') +// if (plugins !== undefined) { +// plugins = JSON.parse(plugins) + +// // remove duplicate entries if any (should not happen) +// plugins = deduplicatePluginsById(plugins) + +// // remove plugins that don't meet the min ED version requirement +// plugins = filterPluginsByMinEDVersion(plugins, EasyDiffusionVersion) + +// // remove from plugins the entries that don't have mandatory fields (id, name, url) +// plugins = plugins.filter((plugin) => { return plugin.id !== '' && plugin.name !== '' && plugin.url !== ''; }); + +// // populate the table +// initPluginTable(plugins) +// await loadPlugins(plugins) +// pluginsLoaded = true +// } +// else { +// plugins = [] +// pluginsLoaded = false +// } +// } + +// // update plugins asynchronously (updated versions will be available next time the UI is loaded) +// if (refreshAllowed()) { +// let pluginCatalog = await getDocument(PLUGIN_CATALOG) +// if (pluginCatalog !== null) { +// let parseError = false; +// try { +// pluginCatalog = JSON.parse(pluginCatalog); +// console.log('Plugin catalog successfully downloaded'); +// } catch (error) { +// console.error('Error parsing plugin catalog:', error); +// parseError = true; +// } + +// if (!parseError) { +// await downloadPlugins(pluginCatalog, plugins, refreshPlugins) + +// // update compatIssueIds +// updateCompatIssueIds() + +// // remove plugins that don't meet the min ED version requirement +// plugins = filterPluginsByMinEDVersion(plugins, EasyDiffusionVersion) + +// // remove from plugins the entries that don't have mandatory fields (id, name, url) +// plugins = plugins.filter((plugin) => { return plugin.id !== '' && plugin.name !== '' && plugin.url !== ''; }); + +// // remove from plugins the entries that no longer exist in the catalog +// plugins = plugins.filter((plugin) => { return pluginCatalog.some((p) => p.id === plugin.id) }); + +// if (pluginCatalog.length > plugins.length) { +// const newPlugins = pluginCatalog.filter((plugin) => { +// return !plugins.some((p) => p.id === plugin.id); +// }); + +// newPlugins.forEach((plugin, index) => { +// setTimeout(() => { +// showPluginToast(`New plugin "${plugin.name}" is available.`); +// }, (index + 1) * 1000); +// }); +// } + +// let pluginsJson; +// try { +// pluginsJson = JSON.stringify(plugins); // attempt to parse plugins to JSON +// } catch (error) { +// console.error('Error converting plugins to JSON:', error); +// } + +// if (pluginsJson) { // only store the data if pluginsJson is not null or undefined +// await setStorageData('plugins', pluginsJson) +// } + +// // refresh the display of the plugins table +// initPluginTable(plugins) +// if (pluginsLoaded && pluginsLoaded === false) { +// loadPlugins(plugins) +// } +// } +// } +// } +// else { +// if (refreshPlugins) { +// showPluginToast('Plugins have been refreshed recently, refresh will be available in ' + convertSeconds(getTimeUntilNextRefresh()), 5000, true, false) +// } +// } +// initPluginsInProgress = false +// } + +// function updateMetaTagPlugins(plugin) { +// // Update the meta tag with the list of loaded plugins +// let metaTag = document.querySelector('meta[name="plugins"]'); +// if (metaTag === null) { +// metaTag = document.createElement('meta'); +// metaTag.name = 'plugins'; +// document.head.appendChild(metaTag); +// } +// const pluginArray = [...(metaTag.content ? metaTag.content.split(',') : []), plugin.id]; +// metaTag.content = pluginArray.join(','); +// } + +// function updateCompatIssueIds() { +// // Loop through each plugin +// plugins.forEach(plugin => { +// // Check if the plugin has `compatIssueIds` property +// if (plugin.compatIssueIds !== undefined) { +// // Loop through each of the `compatIssueIds` +// plugin.compatIssueIds.forEach(issueId => { +// // Find the plugin with the corresponding `issueId` +// const issuePlugin = plugins.find(p => p.id === issueId); +// // If the corresponding plugin is found, initialize its `compatIssueIds` property with an empty array if it's undefined +// if (issuePlugin) { +// if (issuePlugin.compatIssueIds === undefined) { +// issuePlugin.compatIssueIds = []; +// } +// // If the current plugin's ID is not already in the `compatIssueIds` array, add it +// if (!issuePlugin.compatIssueIds.includes(plugin.id)) { +// issuePlugin.compatIssueIds.push(plugin.id); +// } +// } +// }); +// } else { +// // If the plugin doesn't have `compatIssueIds` property, initialize it with an empty array +// plugin.compatIssueIds = []; +// } +// }); +// } + +// function deduplicatePluginsById(plugins) { +// const seenIds = new Set(); +// const deduplicatedPlugins = []; + +// for (const plugin of plugins) { +// if (!seenIds.has(plugin.id)) { +// seenIds.add(plugin.id); +// deduplicatedPlugins.push(plugin); +// } else { +// // favor dupes that have enabled == true +// const index = deduplicatedPlugins.findIndex(p => p.id === plugin.id); +// if (index >= 0) { +// if (plugin.enabled) { +// deduplicatedPlugins[index] = plugin; +// } +// } +// } +// } + +// return deduplicatedPlugins; +// } + +// async function loadPlugins(plugins) { +// for (let i = 0; i < plugins.length; i++) { +// const plugin = plugins[i]; +// if (plugin.enabled === true && plugin.localInstallOnly !== true) { +// const localPluginFound = checkFileNameInArray(localPlugins, plugin.url); +// if (!localPluginFound) { +// try { +// // Indirect eval to work around sloppy plugin implementations +// const indirectEval = { eval }; +// console.log("Loading plugin " + plugin.name); +// indirectEval.eval(plugin.code); +// console.log("Plugin " + plugin.name + " loaded"); +// await updateMetaTagPlugins(plugin); // add plugin to the meta tag +// } catch (err) { +// showPluginToast("Error loading plugin " + plugin.name + " (" + err.message + ")", null, true); +// console.error("Error loading plugin " + plugin.name + ": " + err.message); +// } +// } else { +// console.log("Skipping plugin " + plugin.name + " (installed locally)"); +// } +// } +// } +// } + +// async function getFileHash(url) { +// const regex = /^https:\/\/raw\.githubusercontent\.com\/(?[^/]+)\/(?[^/]+)\/(?[^/]+)\/(?.+)$/; +// const match = url.match(regex); +// if (!match) { +// console.error('Invalid GitHub repository URL.'); +// return Promise.resolve(null); +// } +// const owner = match.groups.owner; +// const repo = match.groups.repo; +// const branch = match.groups.branch; +// const filePath = match.groups.filePath; +// const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`; + +// try { +// const response = await fetch(apiUrl); +// if (!response.ok) { +// throw new Error(`HTTP error! status: ${response.status}, url: ${apiUrl}`); +// } +// const data = await response.json(); +// return data.sha; +// } catch (error) { +// console.error('Error fetching data from url:', apiUrl, 'Error:', error); +// return null; +// } +// } + +// // only allow two refresh per hour +// function getTimeUntilNextRefresh() { +// const lastRuns = JSON.parse(localStorage.getItem('lastRuns') || '[]'); +// const currentTime = new Date().getTime(); +// const numRunsLast60Min = lastRuns.filter(run => currentTime - run <= 60 * 60 * 1000).length; + +// if (numRunsLast60Min >= 2) { +// return 3600 - Math.round((currentTime - lastRuns[lastRuns.length - 1]) / 1000); +// } + +// return 0; +// } + +// function refreshAllowed() { +// const timeUntilNextRefresh = getTimeUntilNextRefresh(); + +// if (timeUntilNextRefresh > 0) { +// console.log(`Next refresh available in ${timeUntilNextRefresh} seconds`); +// return false; +// } + +// const lastRuns = JSON.parse(localStorage.getItem('lastRuns') || '[]'); +// const currentTime = new Date().getTime(); +// lastRuns.push(currentTime); +// localStorage.setItem('lastRuns', JSON.stringify(lastRuns)); +// return true; +// } + +// async function downloadPlugins(pluginCatalog, plugins, refreshPlugins) { +// // download the plugins as needed +// for (const plugin of pluginCatalog) { +// //console.log(plugin.id, plugin.url) +// const existingPlugin = plugins.find(p => p.id === plugin.id); +// // get the file hash in the GitHub repo +// let sha +// if (isGitHub(plugin.url) && existingPlugin?.enabled === true) { +// sha = await getFileHash(plugin.url) +// } +// if (plugin.localInstallOnly !== true && isGitHub(plugin.url) && existingPlugin?.enabled === true && (refreshPlugins || (existingPlugin.sha !== undefined && existingPlugin.sha !== null && existingPlugin.sha !== sha) || existingPlugin?.code === undefined)) { +// const pluginSource = await getDocument(plugin.url); +// if (pluginSource !== null && pluginSource !== existingPlugin.code) { +// console.log(`Plugin ${plugin.name} updated`); +// showPluginToast("Plugin " + plugin.name + " updated", 5000); +// // Update the corresponding plugin +// const updatedPlugin = { +// ...existingPlugin, +// icon: plugin.icon ? plugin.icon : "fa-puzzle-piece", +// id: plugin.id, +// name: plugin.name, +// description: plugin.description, +// url: plugin.url, +// localInstallOnly: Boolean(plugin.localInstallOnly), +// version: plugin.version, +// code: pluginSource, +// author: plugin.author, +// sha: sha, +// compatIssueIds: plugin.compatIssueIds +// }; +// // Replace the old plugin in the plugins array +// const pluginIndex = plugins.indexOf(existingPlugin); +// if (pluginIndex >= 0) { +// plugins.splice(pluginIndex, 1, updatedPlugin); +// } else { +// plugins.push(updatedPlugin); +// } +// } +// } +// else if (existingPlugin !== undefined) { +// // Update the corresponding plugin's metadata +// const updatedPlugin = { +// ...existingPlugin, +// icon: plugin.icon ? plugin.icon : "fa-puzzle-piece", +// id: plugin.id, +// name: plugin.name, +// description: plugin.description, +// url: plugin.url, +// localInstallOnly: Boolean(plugin.localInstallOnly), +// version: plugin.version, +// author: plugin.author, +// compatIssueIds: plugin.compatIssueIds +// }; +// // Replace the old plugin in the plugins array +// const pluginIndex = plugins.indexOf(existingPlugin); +// plugins.splice(pluginIndex, 1, updatedPlugin); +// } +// else { +// plugins.push(plugin); +// } +// } +// } + +// async function getDocument(url) { +// try { +// let response = await fetch(url === PLUGIN_CATALOG ? PLUGIN_CATALOG : url, { cache: "no-cache" }); +// if (!response.ok) { +// throw new Error(`Response error: ${response.status} ${response.statusText}`); +// } +// let document = await response.text(); +// return document; +// } catch (error) { +// showPluginToast("Couldn't fetch " + extractFilename(url) + " (" + error + ")", null, true); +// console.error(error); +// return null; +// } +// } + +// /* MODAL DIALOG */ +// const pluginDialogDialog = document.createElement("div"); +// pluginDialogDialog.id = "pluginDialog-input-dialog"; +// pluginDialogDialog.style.display = "none"; + +// pluginDialogDialog.innerHTML = ` +//
    +//
    +//
    +//

    Paste the plugin's code here

    +// +//
    +//
    +// +//
    +//
    +// +// +//
    +//
    +// `; + +// document.body.appendChild(pluginDialogDialog); + +// const pluginDialogOverlay = document.querySelector(".pluginDialog-dialog-overlay"); +// const pluginDialogOkButton = document.getElementById("pluginDialog-input-ok"); +// const pluginDialogCancelButton = document.getElementById("pluginDialog-input-cancel"); +// const pluginDialogCloseButton = document.querySelector(".pluginDialog-dialog-close-button"); +// const pluginDialogTextarea = document.getElementById("pluginDialog-input-textarea"); +// let callbackOK +// let callbackCancel + +// function pluginDialogOpenDialog(inputOK, inputCancel) { +// pluginDialogDialog.style.display = "block"; +// callbackOK = inputOK +// callbackCancel = inputCancel +// } + +// function pluginDialogCloseDialog() { +// pluginDialogDialog.style.display = "none"; +// } + +// function pluginDialogHandleOkClick() { +// const userInput = pluginDialogTextarea.value; +// // Do something with the user input +// callbackOK() +// pluginDialogCloseDialog(); +// } + +// function pluginDialogHandleCancelClick() { +// callbackCancel() +// pluginDialogCloseDialog(); +// } + +// function pluginDialogHandleOverlayClick(event) { +// if (event.target === pluginDialogOverlay) { +// pluginDialogCloseDialog(); +// } +// } + +// function pluginDialogHandleKeyDown(event) { +// if ((event.key === "Enter" && event.ctrlKey) || event.key === "Escape") { +// event.preventDefault(); +// if (event.key === "Enter" && event.ctrlKey) { +// pluginDialogHandleOkClick(); +// } else { +// pluginDialogCloseDialog(); +// } +// } +// } + +// pluginDialogTextarea.addEventListener("keydown", pluginDialogHandleKeyDown); +// pluginDialogOkButton.addEventListener("click", pluginDialogHandleOkClick); +// pluginDialogCancelButton.addEventListener("click", pluginDialogHandleCancelClick); +// pluginDialogCloseButton.addEventListener("click", pluginDialogCloseDialog); +// pluginDialogOverlay.addEventListener("click", pluginDialogHandleOverlayClick);