Add branch selection on extension installer

Closes #3865
This commit is contained in:
Cohee
2025-04-28 22:53:22 +03:00
parent 0ca4cc08bb
commit 310b0f30cd
5 changed files with 72 additions and 32 deletions

View File

@ -1120,7 +1120,7 @@ export function initRossMods() {
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', { const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }], customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
onClose: (popup) => { onClose: (popup) => {
regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false; regenerateWithCtrlEnter = Boolean(popup.inputResults.get('regenerateWithCtrlEnter') ?? false);
}, },
}); });
if (!result) { if (!result) {

View File

@ -1061,7 +1061,7 @@ async function getExtensionVersion(extensionName, abortSignal) {
* @param {boolean} global Is the extension global? * @param {boolean} global Is the extension global?
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function installExtension(url, global) { export async function installExtension(url, global, branch = '') {
console.debug('Extension installation started', url); console.debug('Extension installation started', url);
toastr.info(t`Please wait...`, t`Installing extension`); toastr.info(t`Please wait...`, t`Installing extension`);
@ -1072,6 +1072,7 @@ export async function installExtension(url, global) {
body: JSON.stringify({ body: JSON.stringify({
url, url,
global, global,
branch,
}), }),
}); });
@ -1406,9 +1407,17 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
await popup.complete(POPUP_RESULT.AFFIRMATIVE); await popup.complete(POPUP_RESULT.AFFIRMATIVE);
}, },
}; };
/** @type {import('./popup.js').CustomPopupInput} */
const branchNameInput = {
id: 'extension_branch_name',
label: t`Branch name (optional)`,
type: 'text',
tooltip: 'e.g. main, master, dev',
};
const customButtons = isCurrentUserAdmin ? [installForAllButton] : []; const customButtons = isCurrentUserAdmin ? [installForAllButton] : [];
const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons }); const customInputs = [branchNameInput];
const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons, customInputs });
const input = await popup.show(); const input = await popup.show();
if (!input) { if (!input) {
@ -1417,7 +1426,8 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
} }
const url = String(input).trim(); const url = String(input).trim();
await installExtension(url, global); const branchName = String(popup.inputResults.get('extension_branch_name') ?? '').trim();
await installExtension(url, global, branchName);
} }
export async function initExtensions() { export async function initExtensions() {

View File

@ -291,7 +291,7 @@ async function installAsset(url, assetType, filename) {
try { try {
if (category === 'extension') { if (category === 'extension') {
console.debug(DEBUG_PREFIX, 'Installing extension ', url); console.debug(DEBUG_PREFIX, 'Installing extension ', url);
await installExtension(url); await installExtension(url, false);
console.debug(DEBUG_PREFIX, 'Extension installed.'); console.debug(DEBUG_PREFIX, 'Extension installed.');
return; return;
} }
@ -309,7 +309,7 @@ async function installAsset(url, assetType, filename) {
console.debug(DEBUG_PREFIX, 'Importing character ', filename); console.debug(DEBUG_PREFIX, 'Importing character ', filename);
const blob = await result.blob(); const blob = await result.blob();
const file = new File([blob], filename, { type: blob.type }); const file = new File([blob], filename, { type: blob.type });
await processDroppedFiles([file], true); await processDroppedFiles([file]);
console.debug(DEBUG_PREFIX, 'Character downloaded.'); console.debug(DEBUG_PREFIX, 'Character downloaded.');
} }
} }

View File

@ -71,7 +71,8 @@ export const POPUP_RESULT = {
* @property {string} id - The id for the html element * @property {string} id - The id for the html element
* @property {string} label - The label text for the input * @property {string} label - The label text for the input
* @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label * @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
* @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set) * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set)
* @property {string?} [type='checkbox'] - The type of the input (default is checkbox)
*/ */
/** /**
@ -157,7 +158,7 @@ export class Popup {
/** @type {POPUP_RESULT|number} */ result; /** @type {POPUP_RESULT|number} */ result;
/** @type {any} */ value; /** @type {any} */ value;
/** @type {Map<string,boolean>?} */ inputResults; /** @type {Map<string,string|boolean>?} */ inputResults;
/** @type {any} */ cropData; /** @type {any} */ cropData;
/** @type {HTMLElement} */ lastFocus; /** @type {HTMLElement} */ lastFocus;
@ -260,13 +261,14 @@ export class Popup {
return; return;
} }
if (!input.type || input.type === 'checkbox') {
const label = document.createElement('label'); const label = document.createElement('label');
label.classList.add('checkbox_label', 'justifyCenter'); label.classList.add('checkbox_label', 'justifyCenter');
label.setAttribute('for', input.id); label.setAttribute('for', input.id);
const inputElement = document.createElement('input'); const inputElement = document.createElement('input');
inputElement.type = 'checkbox'; inputElement.type = 'checkbox';
inputElement.id = input.id; inputElement.id = input.id;
inputElement.checked = input.defaultState ?? false; inputElement.checked = Boolean(input.defaultState ?? false);
label.appendChild(inputElement); label.appendChild(inputElement);
const labelText = document.createElement('span'); const labelText = document.createElement('span');
labelText.innerText = input.label; labelText.innerText = input.label;
@ -282,6 +284,29 @@ export class Popup {
} }
this.inputControls.appendChild(label); this.inputControls.appendChild(label);
}
if (input.type === 'text') {
const label = document.createElement('label');
label.classList.add('text_label', 'justifyCenter');
label.setAttribute('for', input.id);
const inputElement = document.createElement('input');
inputElement.classList.add('text_pole');
inputElement.type = 'text';
inputElement.id = input.id;
inputElement.value = String(input.defaultState ?? '');
inputElement.placeholder = input.tooltip ?? '';
const labelText = document.createElement('span');
labelText.innerText = input.label;
labelText.dataset.i18n = input.label;
label.appendChild(labelText);
label.appendChild(inputElement);
this.inputControls.appendChild(label);
}
}); });
// Set the default button class // Set the default button class
@ -529,7 +554,8 @@ export class Popup {
this.inputResults = new Map(this.customInputs.map(input => { this.inputResults = new Map(this.customInputs.map(input => {
/** @type {HTMLInputElement} */ /** @type {HTMLInputElement} */
const inputControl = this.dlg.querySelector(`#${input.id}`); const inputControl = this.dlg.querySelector(`#${input.id}`);
return [inputControl.id, inputControl.checked]; const value = input.type === 'text' ? inputControl.value : inputControl.checked;
return [inputControl.id, value];
})); }));
} }
@ -619,7 +645,7 @@ export class Popup {
/** @readonly @type {Popup[]} Remember all popups */ /** @readonly @type {Popup[]} Remember all popups */
popups: [], popups: [],
/** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, boolean>?}?} Last popup result */ /** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, string|boolean>?}?} Last popup result */
lastResult: null, lastResult: null,
/** @returns {boolean} Checks if any modal popup dialog is open */ /** @returns {boolean} Checks if any modal popup dialog is open */

View File

@ -76,7 +76,7 @@ router.post('/install', async (request, response) => {
fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions); fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions);
} }
const { url, global } = request.body; const { url, global, branch } = request.body;
if (global && !request.user.profile.admin) { if (global && !request.user.profile.admin) {
console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`); console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`);
@ -90,8 +90,12 @@ router.post('/install', async (request, response) => {
return response.status(409).send(`Directory already exists at ${extensionPath}`); return response.status(409).send(`Directory already exists at ${extensionPath}`);
} }
await git.clone(url, extensionPath, { '--depth': 1 }); const cloneOptions = { '--depth': 1 };
console.info(`Extension has been cloned at ${extensionPath}`); if (branch) {
cloneOptions['--branch'] = branch;
}
await git.clone(url, extensionPath, cloneOptions);
console.info(`Extension has been cloned to ${extensionPath} from ${url} at ${branch || '(default)'} branch`);
const { version, author, display_name } = await getManifest(extensionPath); const { version, author, display_name } = await getManifest(extensionPath);