`
+ $('#third_party_extension_button').on('click', async () => {
+ const html = `Enter the Git URL of the extension to import
+
+ Disclaimer: Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.
+
+ Example: https://github.com/author/extension-name
`
const input = await callPopup(html, 'input');
if (!input) {
- console.debug('Custom content import cancelled');
+ console.debug('Extension import cancelled');
return;
}
const url = input.trim();
- console.debug('Custom content import started', url);
+ console.debug('Extension import started', url);
const request = await fetch('/get_extension', {
method: 'POST',
@@ -8519,11 +8517,14 @@ $(document).ready(function () {
});
if (!request.ok) {
- toastr.info(request.statusText, 'Custom content import failed');
- console.error('Custom content import failed', request.status, request.statusText);
+ toastr.info(request.statusText, 'Extension import failed');
+ console.error('Extension import failed', request.status, request.statusText);
return;
}
+ const response = await request.json();
+ toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful');
+ console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`);
});
const $dropzone = $(document.body);
diff --git a/server.js b/server.js
index 1d2b0c802..93928efdc 100644
--- a/server.js
+++ b/server.js
@@ -2711,10 +2711,27 @@ app.post('/poe_suggest', jsonParser, async function (request, response) {
});
+/**
+ * Discover the extension folders
+ * If the folder is called third-party, search for subfolders instead
+ */
app.get('/discover_extensions', jsonParser, function (_, response) {
+
+ // get all folders in the extensions folder, except third-party
const extensions = fs
.readdirSync(directories.extensions)
- .filter(f => fs.statSync(path.join(directories.extensions, f)).isDirectory());
+ .filter(f => fs.statSync(path.join(directories.extensions, f)).isDirectory())
+ .filter(f => f !== 'third-party');
+
+ // get all folders in the third-party folder
+ const thirdPartyExtensions = fs
+ .readdirSync(path.join(directories.extensions, 'third-party'))
+ .filter(f => fs.statSync(path.join(directories.extensions, 'third-party', f)).isDirectory());
+
+ // add the third-party extensions to the extensions array
+ extensions.push(...thirdPartyExtensions.map(f => `third-party/${f}`));
+ console.log(extensions);
+
return response.send(extensions);
});
@@ -4346,74 +4363,63 @@ async function getImageBuffers(zipFilePath) {
});
}
-/**
- * This function is used ensure the list of extensions is up to date.
- * @param {object} extensionList
- * @returns
- */
-function getExtensions(extensionList) {
- const extensions = [];
- // if the extension folder does not exist, git clone the extension, also check if the extension is up to date with the git repo, but don't pull if it is not up to date
- for (const extension of extensionList) {
- const extensionPath = path.join(directories.extensions, extension.name);
- if (!fs.existsSync(extensionPath)) {
- console.log(`Extension ${extension.name} does not exist. Cloning...`);
- try {
- git.clone(extension.url, extensionPath);
- } catch (error) {
- console.error(`Failed to clone extension ${extension.name}`);
- console.error(error);
- }
- } else {
- console.log(`Extension ${extension.name} exists
- Checking if extension is up to date...`);
- try {
- git.fetch(extensionPath);
- const status = git.status(extensionPath);
- if (status.behind > 0) {
- console.log(`Extension ${extension.name} is not up to date. Pulling...`);
- git.pull(extensionPath);
- }
- } catch (error) {
- console.error(`Failed to check if extension ${extension.name} is up to date`);
- console.error(error);
- }
- }
- extensions.push(extension.name);
- }
-
-}
+const simpleGit = require('simple-git');
/**
- * This function is used to git clone a single extension into the thrid-party-extensions folder
-*/
+ * This function extracts the extension information from the manifest file.
+ * @param {string} extensionPath - The path of the extension folder
+ * @returns {Object} - Returns the manifest data as an object
+ */
+async function getManifest(extensionPath) {
+ const manifestPath = path.join(extensionPath, 'manifest.json');
+
+ // Check if manifest.json exists
+ if (!fs.existsSync(manifestPath)) {
+ throw new Error(`Manifest file not found at ${manifestPath}`);
+ }
+
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
+ return manifest;
+}
+
+/**
+ * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
+ * and return extension information and path.
+ *
+ * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property.
+ * @param {Object} response - HTTP Response object used to respond to the HTTP request.
+ *
+ * @returns {void}
+ */
app.post('/get_extension', jsonParser, async (request, response) => {
if (!request.body.url) {
- return response.sendStatus(400);
+ return response.status(400).send('Bad Request: URL is required in the request body.');
}
try {
const url = request.body.url;
- let result;
+ const git = simpleGit();
- // git clone and then get the resulting folder path of the extension
- const extensionPath = git.clone(url, directories.extensions + '/third-party');
-
- // load the info from the manifest.json in the extension folder
- const manifest = JSON.parse(fs.readFileSync(extensionPath + '/manifest.json', 'utf8'));
- // pull version, author, display_name
- const version = manifest.version;
- const author = manifest.author;
- const display_name = manifest.display_name;
- console.log(`Extension ${display_name} has been cloned`);
- // return the version, author, display_name, and the path to the extension folder
+ // get the name of the repo from the url and create the extensions folder path
+ const extensionPath = path.join(directories.extensions, 'third-party', path.basename(url, '.git'));
+
+ // Check if a folder already exists at the specified location
+ if (fs.existsSync(extensionPath)) {
+ return response.status(409).send(`Directory already exists at ${extensionPath}`);
+ }
+
+ await git.clone(url, extensionPath);
+ console.log(`Extension has been cloned at ${extensionPath}`);
+
+ // Load the info from the manifest.json in the extension folder
+ const { version, author, display_name } = await getManifest(extensionPath);
+
+ // Return the version, author, display_name, and the path to the extension folder
return response.send({ version, author, display_name, extensionPath });
} catch (error) {
console.log('Importing custom content failed', error);
- return response.sendStatus(500);
+ return response.status(500).send(`Server Error: ${error.message}`);
}
});
-
-
From f67013f7ed47b75aa68303204af3a693cecea530 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Tue, 11 Jul 2023 18:28:18 -0400
Subject: [PATCH 03/16] Ignore third party extensions
---
.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 5cc297a7d..7b9a53a21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ public/worlds/
public/css/bg_load.css
public/themes/
public/OpenAI Settings/
+public/scripts/extensions/third-party/
/uploads/
*.jsonl
config.conf
@@ -22,4 +23,4 @@ secrets.json
poe_device.json
/backups/
poe-error.log
-poe-success.log
+poe-success.log
\ No newline at end of file
From 2454963129ecf1b757d1133d98c5b5ae8c987eb9 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Tue, 11 Jul 2023 19:09:06 -0400
Subject: [PATCH 04/16] Cleanup functions
---
server.js | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/server.js b/server.js
index 93928efdc..3f448b2ba 100644
--- a/server.js
+++ b/server.js
@@ -33,7 +33,10 @@ process.chdir(directory);
const express = require('express');
const compression = require('compression');
const app = express();
-const responseTime = require('response-time')
+const responseTime = require('response-time');
+const simpleGit = require('simple-git');
+const git = simpleGit();
+
app.use(compression());
app.use(responseTime());
@@ -4364,7 +4367,6 @@ async function getImageBuffers(zipFilePath) {
}
-const simpleGit = require('simple-git');
/**
* This function extracts the extension information from the manifest file.
@@ -4399,12 +4401,8 @@ app.post('/get_extension', jsonParser, async (request, response) => {
try {
const url = request.body.url;
- const git = simpleGit();
-
- // get the name of the repo from the url and create the extensions folder path
const extensionPath = path.join(directories.extensions, 'third-party', path.basename(url, '.git'));
- // Check if a folder already exists at the specified location
if (fs.existsSync(extensionPath)) {
return response.status(409).send(`Directory already exists at ${extensionPath}`);
}
@@ -4412,10 +4410,10 @@ app.post('/get_extension', jsonParser, async (request, response) => {
await git.clone(url, extensionPath);
console.log(`Extension has been cloned at ${extensionPath}`);
- // Load the info from the manifest.json in the extension folder
+
const { version, author, display_name } = await getManifest(extensionPath);
- // Return the version, author, display_name, and the path to the extension folder
+
return response.send({ version, author, display_name, extensionPath });
} catch (error) {
From de3b39f8254133a87507940a873d4ca7c954b0f9 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Tue, 11 Jul 2023 19:14:14 -0400
Subject: [PATCH 05/16] Skip over if folder is missing
---
server.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 3f448b2ba..6f7f3a9e8 100644
--- a/server.js
+++ b/server.js
@@ -2726,7 +2726,12 @@ app.get('/discover_extensions', jsonParser, function (_, response) {
.filter(f => fs.statSync(path.join(directories.extensions, f)).isDirectory())
.filter(f => f !== 'third-party');
- // get all folders in the third-party folder
+ // get all folders in the third-party folder, if it exists
+
+ if (!fs.existsSync(path.join(directories.extensions, 'third-party'))) {
+ return response.send(extensions);
+ }
+
const thirdPartyExtensions = fs
.readdirSync(path.join(directories.extensions, 'third-party'))
.filter(f => fs.statSync(path.join(directories.extensions, 'third-party', f)).isDirectory());
From b41d0a08c7848b901b468bf19d5126687633a942 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Tue, 11 Jul 2023 19:24:13 -0400
Subject: [PATCH 06/16] Replace lines that you definitely already had...
---
public/script.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/public/script.js b/public/script.js
index 8d2f20a30..ce84993a5 100644
--- a/public/script.js
+++ b/public/script.js
@@ -8523,6 +8523,8 @@ $(document).ready(function () {
}
const response = await request.json();
+ await loadExtensionSettings(settings);
+ eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful');
console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`);
});
From 27e3485127f14579c5bb5c250eeed6d5509e7ca1 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Wed, 12 Jul 2023 01:13:36 -0400
Subject: [PATCH 07/16] Still working, added update and version get functions
---
public/scripts/extensions.js | 100 +++++++++++++++++++++++++++++------
server.js | 82 +++++++++++++++++++++++++++-
2 files changed, 166 insertions(+), 16 deletions(-)
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 3e15c4540..dc8dfbe89 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -1,4 +1,4 @@
-import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced } from "../script.js";
+import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders } from "../script.js";
import { isSubsetOf, debounce } from "./utils.js";
export {
getContext,
@@ -376,28 +376,41 @@ function addExtensionScript(name, manifest) {
return Promise.resolve();
}
-function showExtensionsDetails() {
- let html = 'Modules provided by your Extensions API:
';
- html += modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
- html += 'Available extensions:
';
- Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).forEach(extension => {
+
+
+function showExtensionsDetails() {
+ let htmlDefault = 'Default Extensions:
';
+ let htmlExternal = 'External Extensions:
';
+
+ Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).forEach(extension => async () =>{
const name = extension[0];
const manifest = extension[1];
const isActive = activeExtensions.has(name);
const isDisabled = extension_settings.disabledExtensions.includes(name);
+ const isExternal = name.startsWith('third-party');
const titleClass = isActive ? "extension_enabled" : isDisabled ? "extension_disabled" : "extension_missing";
- const iconString = isActive ? "\u2705" : isDisabled ? "\u274C" : "";
+ const checkboxClass = isDisabled ? "checkbox_disabled" : "";
- let toggleElement = `Unavailable`;
- if (isActive) {
- toggleElement = `Disable`;
+ const displayName = manifest.display_name;
+ // if external, get the version from a requst to get_extension_verion
+ let displayVersion = manifest.version ? ` v${manifest.version}` : "";
+ if(isExternal) {
+ let data = await getExtensionVersion(name.replace('third-party', ''));
+ let branch = data.currentBranchName;
+ let commitHash = data.currentCommitHash;
+ displayVersion = ` v${branch}-${commitHash}`;
}
- else if (isDisabled) {
- toggleElement = `Enable`;
+
+
+ let toggleElement = ``;
+ if (isActive || isDisabled) {
+ toggleElement = ``;
}
- html += `
${iconString} ${DOMPurify.sanitize(manifest.display_name)} ${toggleElement}
`;
+
+ let updateButton = isExternal ? `` : '';
+ let extensionHtml = `
${DOMPurify.sanitize(displayName)}${displayVersion} ${toggleElement}${updateButton}
`;
if (isActive) {
if (Array.isArray(manifest.optional)) {
@@ -405,7 +418,7 @@ function showExtensionsDetails() {
modules.forEach(x => optional.delete(x));
if (optional.size > 0) {
const optionalString = DOMPurify.sanitize([...optional].join(', '));
- html += `Optional modules: ${optionalString}
`;
+ extensionHtml += `Optional modules: ${optionalString}
`;
}
}
}
@@ -413,13 +426,69 @@ function showExtensionsDetails() {
const requirements = new Set(manifest.requires);
modules.forEach(x => requirements.delete(x));
const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
- html += `Missing modules: ${requirementsString}
`
+ extensionHtml += `Missing modules: ${requirementsString}
`
+ }
+
+ // Append the HTML to the correct section
+ if (isExternal) {
+ htmlExternal += extensionHtml;
+ } else {
+ htmlDefault += extensionHtml;
}
});
+ let html = 'Modules provided by your Extensions API:
';
+ html += modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
+ html += htmlDefault + htmlExternal;
+
callPopup(`${html}
`, 'text');
}
+
+
+async function onUpdateClick() {
+ const extensionName = $(this).data('name');
+ try {
+ const response = await fetch('/update_extension', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ extensionName })
+ });
+
+ console.log('Response', response);
+ const data = await response.json();
+ console.log('Data', data);
+ if (data.isUpToDate) {
+ console.log('Extension is up to date');
+ toastr.success('Extension is already up to date');
+ } else {
+ console.log('Extension updated');
+ toastr.success(`Extension updated to ${data.shortCommitHash}`);
+ }
+ $(this).text(data.shortCommitHash);
+ console.log(data);
+ } catch (error) {
+ console.error('Error:', error);
+ }
+};
+
+async function getExtensionVersion(extensionName) {
+ try {
+ const response = await fetch('/get_extension_version', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ extensionName })
+ });
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+
+
async function loadExtensionSettings(settings) {
if (settings.extension_settings) {
Object.assign(extension_settings, settings.extension_settings);
@@ -463,4 +532,5 @@ $(document).ready(async function () {
$("#extensions_details").on('click', showExtensionsDetails);
$(document).on('click', '.toggle_disable', onDisableExtensionClick);
$(document).on('click', '.toggle_enable', onEnableExtensionClick);
+ $(document).on('click', '.btn_update', onUpdateClick);
});
diff --git a/server.js b/server.js
index 6f7f3a9e8..f857bf9d1 100644
--- a/server.js
+++ b/server.js
@@ -2731,7 +2731,7 @@ app.get('/discover_extensions', jsonParser, function (_, response) {
if (!fs.existsSync(path.join(directories.extensions, 'third-party'))) {
return response.send(extensions);
}
-
+
const thirdPartyExtensions = fs
.readdirSync(path.join(directories.extensions, 'third-party'))
.filter(f => fs.statSync(path.join(directories.extensions, 'third-party', f)).isDirectory());
@@ -4426,3 +4426,83 @@ app.post('/get_extension', jsonParser, async (request, response) => {
return response.status(500).send(`Server Error: ${error.message}`);
}
});
+
+/**
+ * HTTP POST handler function to pull the latest updates from a given git repository
+ * based on the extension name and return the latest commit hash.
+ *
+ * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property.
+ * @param {Object} response - HTTP Response object used to respond to the HTTP request.
+ *
+ * @returns {void}
+ */
+app.post('/update_extension', jsonParser, async (request, response) => {
+ if (!request.body.extensionName) {
+ return response.status(400).send('Bad Request: extensionName is required in the request body.');
+ }
+
+ try {
+ const extensionName = request.body.extensionName;
+ const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
+
+ if (!fs.existsSync(extensionPath)) {
+ return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ }
+
+ const currentBranch = await git.cwd(extensionPath).branch();
+ const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const isUpToDate = await git.cwd(extensionPath).log({
+ from: currentCommitHash,
+ to: `origin/${currentBranch.current}`,
+ });
+
+ if (isUpToDate.total === 0) {
+ await git.cwd(extensionPath).pull('origin', currentBranch.current);
+ console.log(`Extension has been updated at ${extensionPath}`);
+ } else {
+ console.log(`Extension is up to date at ${extensionPath}`);
+ }
+
+ const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const shortCommitHash = fullCommitHash.slice(0, 7);
+
+ return response.send({ shortCommitHash, extensionPath, isUpToDate: isUpToDate.total === 0 });
+
+ } catch (error) {
+ console.log('Updating custom content failed', error);
+ return response.status(500).send(`Server Error: ${error.message}`);
+ }
+});
+
+/**
+ * Function to get current git commit hash and branch name for a given extension.
+ */
+app.post('/get_extension_version', jsonParser, async (request, response) => {
+ if (!request.body.extensionName) {
+ return response.status(400).send('Bad Request: extensionName is required in the request body.');
+ }
+
+ try {
+ const extensionName = request.body.extensionName;
+ const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
+
+ if (!fs.existsSync(extensionPath)) {
+ return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ }
+
+ const currentBranch = await git.cwd(extensionPath).branch();
+ // get only the working branch
+ const currentBranchName = currentBranch.current;
+ const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ console.log(currentBranch, currentCommitHash);
+ return response.send({ currentBranchName, currentCommitHash });
+
+ } catch (error) {
+ console.log('Getting extension version failed', error);
+ return response.status(500).send(`Server Error: ${error.message}`);
+ }
+ }
+);
+
+
+
From 396aaaf6e9c8adc31d468762736bbfe3d41a5872 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Wed, 12 Jul 2023 22:26:01 -0400
Subject: [PATCH 08/16] Add update button and check for external extenisons
---
public/script.js | 13 +++++++------
public/scripts/extensions.js | 23 ++++++++++++-----------
server.js | 34 +++++++++++++++++++++-------------
3 files changed, 40 insertions(+), 30 deletions(-)
diff --git a/public/script.js b/public/script.js
index ce84993a5..7335df735 100644
--- a/public/script.js
+++ b/public/script.js
@@ -8496,10 +8496,10 @@ $(document).ready(function () {
$('#third_party_extension_button').on('click', async () => {
const html = `Enter the Git URL of the extension to import
-
- Disclaimer: Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.
-
- Example: https://github.com/author/extension-name
`
+
+ Disclaimer: Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.
+
+ Example: https://github.com/author/extension-name
`
const input = await callPopup(html, 'input');
if (!input) {
@@ -8523,12 +8523,13 @@ $(document).ready(function () {
}
const response = await request.json();
- await loadExtensionSettings(settings);
- eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful');
console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`);
+ await loadExtensionSettings(settings);
+ eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
});
+
const $dropzone = $(document.body);
$dropzone.on('dragover', (event) => {
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index dc8dfbe89..c551cc971 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -378,12 +378,13 @@ function addExtensionScript(name, manifest) {
-
-function showExtensionsDetails() {
+async function showExtensionsDetails() {
let htmlDefault = 'Default Extensions:
';
let htmlExternal = 'External Extensions:
';
- Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).forEach(extension => async () =>{
+ const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
+
+ for (const extension of extensions) {
const name = extension[0];
const manifest = extension[1];
const isActive = activeExtensions.has(name);
@@ -394,23 +395,23 @@ function showExtensionsDetails() {
const checkboxClass = isDisabled ? "checkbox_disabled" : "";
const displayName = manifest.display_name;
- // if external, get the version from a requst to get_extension_verion
let displayVersion = manifest.version ? ` v${manifest.version}` : "";
- if(isExternal) {
+ let isUpToDate = true;
+ if (isExternal) {
let data = await getExtensionVersion(name.replace('third-party', ''));
let branch = data.currentBranchName;
let commitHash = data.currentCommitHash;
- displayVersion = ` v${branch}-${commitHash}`;
+ isUpToDate = data.isUpToDate;
+ displayVersion = ` (${branch}-${commitHash.substring(0, 7)})`;
}
-
let toggleElement = ``;
if (isActive || isDisabled) {
toggleElement = ``;
}
- let updateButton = isExternal ? `` : '';
- let extensionHtml = `
${DOMPurify.sanitize(displayName)}${displayVersion} ${toggleElement}${updateButton}
`;
+ let updateButton = isExternal && !isUpToDate ? `` : '';
+ let extensionHtml = `
${updateButton} ${DOMPurify.sanitize(displayName)}${displayVersion} ${toggleElement}
`;
if (isActive) {
if (Array.isArray(manifest.optional)) {
@@ -435,8 +436,8 @@ function showExtensionsDetails() {
} else {
htmlDefault += extensionHtml;
}
- });
-
+ }
+ // Do something with htmlDefault and htmlExternal here
let html = 'Modules provided by your Extensions API:
';
html += modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
html += htmlDefault + htmlExternal;
diff --git a/server.js b/server.js
index f857bf9d1..1e501e8f9 100644
--- a/server.js
+++ b/server.js
@@ -4390,6 +4390,17 @@ async function getManifest(extensionPath) {
return manifest;
}
+async function checkIfRepoIsUpToDate(extensionPath) {
+ const currentBranch = await git.cwd(extensionPath).branch();
+ const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const log = await git.cwd(extensionPath).log({
+ from: currentCommitHash,
+ to: `origin/${currentBranch.current}`,
+ });
+
+ return log.total === 0;
+}
+
/**
* HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
* and return extension information and path.
@@ -4449,19 +4460,14 @@ app.post('/update_extension', jsonParser, async (request, response) => {
return response.status(404).send(`Directory does not exist at ${extensionPath}`);
}
- const currentBranch = await git.cwd(extensionPath).branch();
- const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
- const isUpToDate = await git.cwd(extensionPath).log({
- from: currentCommitHash,
- to: `origin/${currentBranch.current}`,
- });
+ const isUpToDate = await checkIfRepoIsUpToDate(extensionPath);
- if (isUpToDate.total === 0) {
- await git.cwd(extensionPath).pull('origin', currentBranch.current);
- console.log(`Extension has been updated at ${extensionPath}`);
- } else {
- console.log(`Extension is up to date at ${extensionPath}`);
- }
+ if (!isUpToDate) {
+ await git.cwd(extensionPath).pull('origin', currentBranch.current);
+ console.log(`Extension has been updated at ${extensionPath}`);
+ } else {
+ console.log(`Extension is up to date at ${extensionPath}`);
+ }
const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
const shortCommitHash = fullCommitHash.slice(0, 7);
@@ -4495,7 +4501,9 @@ app.post('/get_extension_version', jsonParser, async (request, response) => {
const currentBranchName = currentBranch.current;
const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
console.log(currentBranch, currentCommitHash);
- return response.send({ currentBranchName, currentCommitHash });
+ const isUpToDate = await checkIfRepoIsUpToDate(extensionPath);
+
+ return response.send({ currentBranchName, currentCommitHash, isUpToDate });
} catch (error) {
console.log('Getting extension version failed', error);
From 75080394ac1743e31296de5ea7c09cb46042cd21 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Wed, 12 Jul 2023 22:26:23 -0400
Subject: [PATCH 09/16] Style for greyed out checkboxes
---
public/style.css | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/public/style.css b/public/style.css
index 3b870f68f..05cbeff81 100644
--- a/public/style.css
+++ b/public/style.css
@@ -3793,12 +3793,20 @@ label[for="extensions_autoconnect"] {
.extensions_info .extension_missing {
color: gray;
}
+input.extension_missing[type="checkbox"] {
+ opacity: 0.5;
+}
#extensions_list .disabled {
text-decoration: line-through;
color: lightgray;
}
+.update-button {
+ margin-right: 10px;
+ /* Adjust the value as needed */
+}
+
/* possible place for WI Entry header styling */
/* .world_entry_form .inline-drawer-header {
background-color: var(--SmartThemeShadowColor);
From 4fb65a92354eac8db7e43f2934f6b284cffd95b8 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Wed, 12 Jul 2023 22:37:24 -0400
Subject: [PATCH 10/16] Code refactor to make this more readable
---
public/scripts/extensions.js | 122 +++++++++++++++++++++--------------
1 file changed, 72 insertions(+), 50 deletions(-)
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index c551cc971..7ac782e20 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -378,6 +378,72 @@ function addExtensionScript(name, manifest) {
+async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {
+ const displayName = manifest.display_name;
+ let displayVersion = manifest.version ? ` v${manifest.version}` : "";
+ let isUpToDate = true;
+ if (isExternal) {
+ let data = await getExtensionVersion(name.replace('third-party', ''));
+ let branch = data.currentBranchName;
+ let commitHash = data.currentCommitHash;
+ isUpToDate = data.isUpToDate;
+ displayVersion = ` (${branch}-${commitHash.substring(0, 7)})`;
+ }
+
+ let toggleElement = isActive || isDisabled ?
+ `` :
+ ``;
+
+ let updateButton = isExternal && !isUpToDate ? `` : '';
+ let extensionHtml = `
+
+ ${updateButton}
+
+ ${DOMPurify.sanitize(displayName)}${displayVersion}
+
+ ${toggleElement}
+
`;
+
+ if (isActive && Array.isArray(manifest.optional)) {
+ const optional = new Set(manifest.optional);
+ modules.forEach(x => optional.delete(x));
+ if (optional.size > 0) {
+ const optionalString = DOMPurify.sanitize([...optional].join(', '));
+ extensionHtml += `Optional modules: ${optionalString}
`;
+ }
+ } else if (!isDisabled) { // Neither active nor disabled
+ const requirements = new Set(manifest.requires);
+ modules.forEach(x => requirements.delete(x));
+ const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
+ extensionHtml += `Missing modules: ${requirementsString}
`;
+ }
+
+ return extensionHtml;
+}
+
+async function getExtensionData(extension) {
+ const name = extension[0];
+ const manifest = extension[1];
+ const isActive = activeExtensions.has(name);
+ const isDisabled = extension_settings.disabledExtensions.includes(name);
+ const isExternal = name.startsWith('third-party');
+
+ const checkboxClass = isDisabled ? "checkbox_disabled" : "";
+
+ const extensionHtml = await generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass);
+
+ return { isExternal, extensionHtml };
+}
+
+
+function getModuleInformation() {
+ let moduleInfo = modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
+ return `
+ Modules provided by your Extensions API:
+ ${moduleInfo}
+ `;
+}
+
async function showExtensionsDetails() {
let htmlDefault = 'Default Extensions:
';
let htmlExternal = 'External Extensions:
';
@@ -385,63 +451,19 @@ async function showExtensionsDetails() {
const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
for (const extension of extensions) {
- const name = extension[0];
- const manifest = extension[1];
- const isActive = activeExtensions.has(name);
- const isDisabled = extension_settings.disabledExtensions.includes(name);
- const isExternal = name.startsWith('third-party');
-
- const titleClass = isActive ? "extension_enabled" : isDisabled ? "extension_disabled" : "extension_missing";
- const checkboxClass = isDisabled ? "checkbox_disabled" : "";
-
- const displayName = manifest.display_name;
- let displayVersion = manifest.version ? ` v${manifest.version}` : "";
- let isUpToDate = true;
- if (isExternal) {
- let data = await getExtensionVersion(name.replace('third-party', ''));
- let branch = data.currentBranchName;
- let commitHash = data.currentCommitHash;
- isUpToDate = data.isUpToDate;
- displayVersion = ` (${branch}-${commitHash.substring(0, 7)})`;
- }
-
- let toggleElement = ``;
- if (isActive || isDisabled) {
- toggleElement = ``;
- }
-
- let updateButton = isExternal && !isUpToDate ? `` : '';
- let extensionHtml = `
${updateButton} ${DOMPurify.sanitize(displayName)}${displayVersion} ${toggleElement}
`;
-
- if (isActive) {
- if (Array.isArray(manifest.optional)) {
- const optional = new Set(manifest.optional);
- modules.forEach(x => optional.delete(x));
- if (optional.size > 0) {
- const optionalString = DOMPurify.sanitize([...optional].join(', '));
- extensionHtml += `Optional modules: ${optionalString}
`;
- }
- }
- }
- else if (!isDisabled) { // Neither active nor disabled
- const requirements = new Set(manifest.requires);
- modules.forEach(x => requirements.delete(x));
- const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
- extensionHtml += `Missing modules: ${requirementsString}
`
- }
-
- // Append the HTML to the correct section
+ const { isExternal, extensionHtml } = await getExtensionData(extension);
if (isExternal) {
htmlExternal += extensionHtml;
} else {
htmlDefault += extensionHtml;
}
}
- // Do something with htmlDefault and htmlExternal here
- let html = 'Modules provided by your Extensions API:
';
- html += modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
- html += htmlDefault + htmlExternal;
+ const html = `
+ ${getModuleInformation()}
+ ${htmlDefault}
+ ${htmlExternal}
+ `;
callPopup(`${html}
`, 'text');
}
From cc7c42232ef25aaf0bc126c617e1c286a7cdaeb2 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Wed, 12 Jul 2023 22:37:42 -0400
Subject: [PATCH 11/16] Fetch first, then check status
---
server.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/server.js b/server.js
index 1e501e8f9..9c703ed19 100644
--- a/server.js
+++ b/server.js
@@ -4391,6 +4391,7 @@ async function getManifest(extensionPath) {
}
async function checkIfRepoIsUpToDate(extensionPath) {
+ await git.cwd(extensionPath).fetch('origin');
const currentBranch = await git.cwd(extensionPath).branch();
const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
const log = await git.cwd(extensionPath).log({
@@ -4401,6 +4402,7 @@ async function checkIfRepoIsUpToDate(extensionPath) {
return log.total === 0;
}
+
/**
* HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
* and return extension information and path.
From eccae1056f33b10362d73d3dddc44299c212b185 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Wed, 12 Jul 2023 23:54:17 -0400
Subject: [PATCH 12/16] Add clickable names to go to repos
---
public/scripts/extensions.js | 17 ++++++--
public/style.css | 2 +-
server.js | 77 ++++++++++++++++++++----------------
3 files changed, 57 insertions(+), 39 deletions(-)
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 7ac782e20..35909254a 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -382,25 +382,35 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt
const displayName = manifest.display_name;
let displayVersion = manifest.version ? ` v${manifest.version}` : "";
let isUpToDate = true;
+ let updateButton = '';
+ let originHtml = '';
if (isExternal) {
let data = await getExtensionVersion(name.replace('third-party', ''));
let branch = data.currentBranchName;
let commitHash = data.currentCommitHash;
+ let origin = data.remoteUrl
isUpToDate = data.isUpToDate;
displayVersion = ` (${branch}-${commitHash.substring(0, 7)})`;
+ updateButton = isUpToDate ?
+ `` :
+ ``;
+ originHtml = ``;
}
let toggleElement = isActive || isDisabled ?
`` :
``;
+
+ // if external, wrap the name in a link to the repo
- let updateButton = isExternal && !isUpToDate ? `` : '';
let extensionHtml = `
- ${updateButton}
+ ${updateButton}
+ ${originHtml}
${DOMPurify.sanitize(displayName)}${displayVersion}
+ ${isExternal ? '
' : ''}
${toggleElement}
`;
@@ -488,8 +498,7 @@ async function onUpdateClick() {
console.log('Extension updated');
toastr.success(`Extension updated to ${data.shortCommitHash}`);
}
- $(this).text(data.shortCommitHash);
- console.log(data);
+ showExtensionsDetails();
} catch (error) {
console.error('Error:', error);
}
diff --git a/public/style.css b/public/style.css
index 05cbeff81..333a998d0 100644
--- a/public/style.css
+++ b/public/style.css
@@ -3804,7 +3804,7 @@ input.extension_missing[type="checkbox"] {
.update-button {
margin-right: 10px;
- /* Adjust the value as needed */
+ display: inline-flex;
}
/* possible place for WI Entry header styling */
diff --git a/server.js b/server.js
index 9c703ed19..a7a2255ae 100644
--- a/server.js
+++ b/server.js
@@ -4391,18 +4391,25 @@ async function getManifest(extensionPath) {
}
async function checkIfRepoIsUpToDate(extensionPath) {
- await git.cwd(extensionPath).fetch('origin');
- const currentBranch = await git.cwd(extensionPath).branch();
- const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
- const log = await git.cwd(extensionPath).log({
- from: currentCommitHash,
- to: `origin/${currentBranch.current}`,
- });
+ await git.cwd(extensionPath).fetch('origin');
+ const currentBranch = await git.cwd(extensionPath).branch();
+ const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const log = await git.cwd(extensionPath).log({
+ from: currentCommitHash,
+ to: `origin/${currentBranch.current}`,
+ });
- return log.total === 0;
+ // Fetch remote repository information
+ const remotes = await git.cwd(extensionPath).getRemotes(true);
+
+ return {
+ isUpToDate: log.total === 0,
+ remoteUrl: remotes[0].refs.fetch, // URL of the remote repository
+ };
}
+
/**
* HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
* and return extension information and path.
@@ -4450,36 +4457,37 @@ app.post('/get_extension', jsonParser, async (request, response) => {
* @returns {void}
*/
app.post('/update_extension', jsonParser, async (request, response) => {
- if (!request.body.extensionName) {
- return response.status(400).send('Bad Request: extensionName is required in the request body.');
- }
-
- try {
- const extensionName = request.body.extensionName;
- const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
-
- if (!fs.existsSync(extensionPath)) {
- return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ if (!request.body.extensionName) {
+ return response.status(400).send('Bad Request: extensionName is required in the request body.');
}
- const isUpToDate = await checkIfRepoIsUpToDate(extensionPath);
+ try {
+ const extensionName = request.body.extensionName;
+ const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
- if (!isUpToDate) {
- await git.cwd(extensionPath).pull('origin', currentBranch.current);
- console.log(`Extension has been updated at ${extensionPath}`);
- } else {
- console.log(`Extension is up to date at ${extensionPath}`);
- }
+ if (!fs.existsSync(extensionPath)) {
+ return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ }
- const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
- const shortCommitHash = fullCommitHash.slice(0, 7);
+ const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);
+ const currentBranch = await git.cwd(extensionPath).branch();
+ if (!isUpToDate) {
- return response.send({ shortCommitHash, extensionPath, isUpToDate: isUpToDate.total === 0 });
+ await git.cwd(extensionPath).pull('origin', currentBranch.current);
+ console.log(`Extension has been updated at ${extensionPath}`);
+ } else {
+ console.log(`Extension is up to date at ${extensionPath}`);
+ }
+ await git.cwd(extensionPath).fetch('origin');
+ const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const shortCommitHash = fullCommitHash.slice(0, 7);
- } catch (error) {
- console.log('Updating custom content failed', error);
- return response.status(500).send(`Server Error: ${error.message}`);
- }
+ return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl });
+
+ } catch (error) {
+ console.log('Updating custom content failed', error);
+ return response.status(500).send(`Server Error: ${error.message}`);
+ }
});
/**
@@ -4501,11 +4509,12 @@ app.post('/get_extension_version', jsonParser, async (request, response) => {
const currentBranch = await git.cwd(extensionPath).branch();
// get only the working branch
const currentBranchName = currentBranch.current;
+ await git.cwd(extensionPath).fetch('origin');
const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
console.log(currentBranch, currentCommitHash);
- const isUpToDate = await checkIfRepoIsUpToDate(extensionPath);
+ const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);
- return response.send({ currentBranchName, currentCommitHash, isUpToDate });
+ return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl });
} catch (error) {
console.log('Getting extension version failed', error);
From 07b42e0fb40c0730466aa2e3addbb18d8bfe4267 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Thu, 13 Jul 2023 00:03:23 -0400
Subject: [PATCH 13/16] Documentation update
---
public/script.js | 10 ++++++++
public/scripts/extensions.js | 45 ++++++++++++++++++++++++++++++++----
server.js | 19 +++++++++++----
3 files changed, 64 insertions(+), 10 deletions(-)
diff --git a/public/script.js b/public/script.js
index 7335df735..a3d8fa7a5 100644
--- a/public/script.js
+++ b/public/script.js
@@ -8494,6 +8494,16 @@ $(document).ready(function () {
}
});
+ /**
+ * Handles the click event for the third-party extension import button.
+ * Prompts the user to enter the Git URL of the extension to import.
+ * After obtaining the Git URL, makes a POST request to '/get_extension' to import the extension.
+ * If the extension is imported successfully, a success message is displayed.
+ * If the extension import fails, an error message is displayed and the error is logged to the console.
+ * After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted.
+ *
+ * @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
+ */
$('#third_party_extension_button').on('click', async () => {
const html = `Enter the Git URL of the extension to import
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 35909254a..7827b1d93 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -378,6 +378,17 @@ function addExtensionScript(name, manifest) {
+/**
+ * Generates HTML string for displaying an extension in the UI.
+ *
+ * @param {string} name - The name of the extension.
+ * @param {object} manifest - The manifest of the extension.
+ * @param {boolean} isActive - Whether the extension is active or not.
+ * @param {boolean} isDisabled - Whether the extension is disabled or not.
+ * @param {boolean} isExternal - Whether the extension is external or not.
+ * @param {string} checkboxClass - The class for the checkbox HTML element.
+ * @return {string} - The HTML string that represents the extension.
+ */
async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {
const displayName = manifest.display_name;
let displayVersion = manifest.version ? ` v${manifest.version}` : "";
@@ -431,6 +442,12 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt
return extensionHtml;
}
+/**
+ * Gets extension data and generates the corresponding HTML for displaying the extension.
+ *
+ * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest.
+ * @return {object} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string.
+ */
async function getExtensionData(extension) {
const name = extension[0];
const manifest = extension[1];
@@ -446,6 +463,11 @@ async function getExtensionData(extension) {
}
+/**
+ * Gets the module information to be displayed.
+ *
+ * @return {string} - The HTML string for the module information.
+ */
function getModuleInformation() {
let moduleInfo = modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
return `
@@ -454,6 +476,9 @@ function getModuleInformation() {
`;
}
+/**
+ * Generates the HTML strings for all extensions and displays them in a popup.
+ */
async function showExtensionsDetails() {
let htmlDefault = 'Default Extensions:
';
let htmlExternal = 'External Extensions:
';
@@ -478,7 +503,12 @@ async function showExtensionsDetails() {
}
-
+/**
+ * Handles the click event for the update button of an extension.
+ * This function makes a POST request to '/update_extension' with the extension's name.
+ * If the extension is already up to date, it displays a success message.
+ * If the extension is not up to date, it updates the extension and displays a success message with the new commit hash.
+ */
async function onUpdateClick() {
const extensionName = $(this).data('name');
try {
@@ -488,14 +518,10 @@ async function onUpdateClick() {
body: JSON.stringify({ extensionName })
});
- console.log('Response', response);
const data = await response.json();
- console.log('Data', data);
if (data.isUpToDate) {
- console.log('Extension is up to date');
toastr.success('Extension is already up to date');
} else {
- console.log('Extension updated');
toastr.success(`Extension updated to ${data.shortCommitHash}`);
}
showExtensionsDetails();
@@ -504,6 +530,15 @@ async function onUpdateClick() {
}
};
+
+/**
+ * Fetches the version details of a specific extension.
+ *
+ * @param {string} extensionName - The name of the extension.
+ * @return {object} - An object containing the extension's version details.
+ * This object includes the currentBranchName, currentCommitHash, isUpToDate, and remoteUrl.
+ * @throws {error} - If there is an error during the fetch operation, it logs the error to the console.
+ */
async function getExtensionVersion(extensionName) {
try {
const response = await fetch('/get_extension_version', {
diff --git a/server.js b/server.js
index a7a2255ae..5093f0381 100644
--- a/server.js
+++ b/server.js
@@ -4413,7 +4413,7 @@ async function checkIfRepoIsUpToDate(extensionPath) {
/**
* HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
* and return extension information and path.
- *
+ *
* @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property.
* @param {Object} response - HTTP Response object used to respond to the HTTP request.
*
@@ -4448,9 +4448,11 @@ app.post('/get_extension', jsonParser, async (request, response) => {
});
/**
- * HTTP POST handler function to pull the latest updates from a given git repository
- * based on the extension name and return the latest commit hash.
- *
+ * HTTP POST handler function to pull the latest updates from a git repository
+ * based on the extension name provided in the request body. It returns the latest commit hash,
+ * the path of the extension, the status of the repository (whether it's up-to-date or not),
+ * and the remote URL of the repository.
+ *
* @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property.
* @param {Object} response - HTTP Response object used to respond to the HTTP request.
*
@@ -4491,7 +4493,14 @@ app.post('/update_extension', jsonParser, async (request, response) => {
});
/**
- * Function to get current git commit hash and branch name for a given extension.
+ * HTTP POST handler function to get the current git commit hash and branch name for a given extension.
+ * It checks whether the repository is up-to-date with the remote, and returns the status along with
+ * the remote URL of the repository.
+ *
+ * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property.
+ * @param {Object} response - HTTP Response object used to respond to the HTTP request.
+ *
+ * @returns {void}
*/
app.post('/get_extension_version', jsonParser, async (request, response) => {
if (!request.body.extensionName) {
From 35336b16468ff107056467aeae0789c5b79ccc85 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Thu, 13 Jul 2023 00:59:16 -0400
Subject: [PATCH 14/16] Delete method almost complete
---
public/script.js | 3 +++
public/scripts/extensions.js | 33 ++++++++++++++++++++++++++++++++-
public/style.css | 7 +++++++
server.js | 31 +++++++++++++++++++++++++++++++
4 files changed, 73 insertions(+), 1 deletion(-)
diff --git a/public/script.js b/public/script.js
index a3d8fa7a5..b7cd56bb2 100644
--- a/public/script.js
+++ b/public/script.js
@@ -5553,6 +5553,9 @@ function callPopup(text, type, inputValue = '', { okButton, rows } = {}) {
$("#dialogue_popup_ok").text(okButton ?? "Ok");
$("#dialogue_popup_cancel").css("display", "none");
break;
+ case "delete_extension":
+ $("#dialogue_popup_ok").text(okButton ?? "Ok");
+ break;
case "new_chat":
case "confirm":
$("#dialogue_popup_ok").text(okButton ?? "Yes");
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 7827b1d93..892f521b1 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -412,16 +412,20 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt
`` :
``;
+ let deleteButton = isExternal ? `` : '';
+
// if external, wrap the name in a link to the repo
let extensionHtml = `
- ${updateButton}
+ ${updateButton}
+ ${deleteButton}
${originHtml}
${DOMPurify.sanitize(displayName)}${displayVersion}
${isExternal ? '' : ''}
+
${toggleElement}
`;
@@ -530,6 +534,32 @@ async function onUpdateClick() {
}
};
+/**
+ * Handles the click event for the delete button of an extension.
+ * This function makes a POST request to '/delete_extension' with the extension's name.
+ * If the extension is deleted, it displays a success message.
+ * Creates a popup for the user to confirm before delete.
+ */
+async function onDeleteClick() {
+ const extensionName = $(this).data('name');
+ // use callPopup to create a popup for the user to confirm before delete
+ const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension');
+ if (confirmation) {
+ try {
+ const response = await fetch('/delete_extension', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ extensionName })
+ });
+ } catch (error) {
+ console.error('Error:', error);
+ }
+ toastr.success(`Extension ${extensionName} deleted`);
+ showExtensionsDetails();
+ }
+};
+
+
/**
* Fetches the version details of a specific extension.
@@ -600,4 +630,5 @@ $(document).ready(async function () {
$(document).on('click', '.toggle_disable', onDisableExtensionClick);
$(document).on('click', '.toggle_enable', onEnableExtensionClick);
$(document).on('click', '.btn_update', onUpdateClick);
+ $(document).on('click', '.btn_delete', onDeleteClick);
});
diff --git a/public/style.css b/public/style.css
index 333a998d0..a66763278 100644
--- a/public/style.css
+++ b/public/style.css
@@ -3807,6 +3807,13 @@ input.extension_missing[type="checkbox"] {
display: inline-flex;
}
+/* Align the content of this span to the right */
+.delete-button {
+ margin-right: 10px;
+ display: inline-flex;
+
+}
+
/* possible place for WI Entry header styling */
/* .world_entry_form .inline-drawer-header {
background-color: var(--SmartThemeShadowColor);
diff --git a/server.js b/server.js
index 5093f0381..078964939 100644
--- a/server.js
+++ b/server.js
@@ -4532,5 +4532,36 @@ app.post('/get_extension_version', jsonParser, async (request, response) => {
}
);
+/**
+ * HTTP POST handler function to delete a git repository based on the extension name provided in the request body.
+ *
+ * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property.
+ * @param {Object} response - HTTP Response object used to respond to the HTTP request.
+ *
+ * @returns {void}
+ */
+app.post('/delete_extension', jsonParser, async (request, response) => {
+ if (!request.body.extensionName) {
+ return response.status(400).send('Bad Request: extensionName is required in the request body.');
+ }
+ // Sanatize the extension name to prevent directory traversal
+ const extensionName = sanitize(request.body.extensionName);
+ try {
+ const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
+
+ if (!fs.existsSync(extensionPath)) {
+ return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ }
+
+ await fs.promises.rmdir(extensionPath, { recursive: true });
+ console.log(`Extension has been deleted at ${extensionPath}`);
+
+ return response.send(`Extension has been deleted at ${extensionPath}`);
+
+ } catch (error) {
+ console.log('Deleting custom content failed', error);
+ return response.status(500).send(`Server Error: ${error.message}`);
+ }
+});
From 2b8db6350e22458675e2e7eb051428022cd0445e Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Thu, 13 Jul 2023 01:12:57 -0400
Subject: [PATCH 15/16] Fixed a stupid delete bug, reload on delete.
---
public/scripts/extensions.js | 2 ++
server.js | 10 +++++++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 892f521b1..26171b92d 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -556,6 +556,8 @@ async function onDeleteClick() {
}
toastr.success(`Extension ${extensionName} deleted`);
showExtensionsDetails();
+ // reload the page to remove the extension from the list
+ location.reload();
}
};
diff --git a/server.js b/server.js
index 078964939..7b5ed0cfc 100644
--- a/server.js
+++ b/server.js
@@ -35,7 +35,6 @@ const compression = require('compression');
const app = express();
const responseTime = require('response-time');
const simpleGit = require('simple-git');
-const git = simpleGit();
app.use(compression());
app.use(responseTime());
@@ -4391,6 +4390,7 @@ async function getManifest(extensionPath) {
}
async function checkIfRepoIsUpToDate(extensionPath) {
+ const git = simpleGit();
await git.cwd(extensionPath).fetch('origin');
const currentBranch = await git.cwd(extensionPath).branch();
const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
@@ -4420,11 +4420,17 @@ async function checkIfRepoIsUpToDate(extensionPath) {
* @returns {void}
*/
app.post('/get_extension', jsonParser, async (request, response) => {
+ const git = simpleGit();
if (!request.body.url) {
return response.status(400).send('Bad Request: URL is required in the request body.');
}
try {
+ // make sure the third-party directory exists
+ if (!fs.existsSync(directories.extensions + '/third-party')) {
+ fs.mkdirSync(directories.extensions + '/third-party');
+ }
+
const url = request.body.url;
const extensionPath = path.join(directories.extensions, 'third-party', path.basename(url, '.git'));
@@ -4459,6 +4465,7 @@ app.post('/get_extension', jsonParser, async (request, response) => {
* @returns {void}
*/
app.post('/update_extension', jsonParser, async (request, response) => {
+ const git = simpleGit();
if (!request.body.extensionName) {
return response.status(400).send('Bad Request: extensionName is required in the request body.');
}
@@ -4503,6 +4510,7 @@ app.post('/update_extension', jsonParser, async (request, response) => {
* @returns {void}
*/
app.post('/get_extension_version', jsonParser, async (request, response) => {
+ const git = simpleGit();
if (!request.body.extensionName) {
return response.status(400).send('Bad Request: extensionName is required in the request body.');
}
From ee6bfbf4cc8fbc094a73f4bf861a4ac809168f15 Mon Sep 17 00:00:00 2001
From: BlipRanger <1860540+BlipRanger@users.noreply.github.com>
Date: Thu, 13 Jul 2023 01:30:37 -0400
Subject: [PATCH 16/16] Add simple-get to packages.
---
package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/package.json b/package.json
index 0c310b2f8..9ab26c760 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3",
"sentencepiece-js": "^1.1.0",
+ "simple-git": "^3.19.1",
"uniqolor": "^1.1.0",
"webp-converter": "2.3.2",
"ws": "^8.13.0",