mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-08 23:35:01 -04:00
Added support for multiple tabs in URL
This adds support for storing multiple tabs' contents and headers in the URL (with backwards compatibility for the current standard '&input=...') and moves the tab header information to the InputWorker instead of an array in the TabWaiter.
This commit is contained in:
parent
b615d1350d
commit
bbea58dabb
7 changed files with 206 additions and 167 deletions
|
@ -212,29 +212,6 @@ class App {
|
||||||
this.manager.worker.silentBake(recipeConfig);
|
this.manager.worker.silentBake(recipeConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the user's input data.
|
|
||||||
*
|
|
||||||
* @param {string} input - The string to set the input to
|
|
||||||
*/
|
|
||||||
setInput(input) {
|
|
||||||
// Get the currently active tab.
|
|
||||||
// If there isn't one, assume there are no inputs so use inputNum of 1
|
|
||||||
let inputNum = this.manager.tabs.getActiveInputTab();
|
|
||||||
if (inputNum === -1) inputNum = 1;
|
|
||||||
this.manager.input.updateInputValue(inputNum, input);
|
|
||||||
|
|
||||||
this.manager.input.inputWorker.postMessage({
|
|
||||||
action: "setInput",
|
|
||||||
data: {
|
|
||||||
inputNum: inputNum,
|
|
||||||
silent: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates the operations accordion list with the categories and operations specified in the
|
* Populates the operations accordion list with the categories and operations specified in the
|
||||||
* view constructor.
|
* view constructor.
|
||||||
|
@ -483,13 +460,37 @@ class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read in input data from URI params
|
// Read in input data from URI params
|
||||||
if (this.uriParams.input) {
|
this.manager.input.clearAllIoClick();
|
||||||
try {
|
let maxInputNum = 1;
|
||||||
const inputData = fromBase64(this.uriParams.input);
|
for (const [param, value] of Object.entries(this.uriParams)) {
|
||||||
this.setInput(inputData);
|
if (typeof param !== "string") {
|
||||||
} catch (err) {}
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inputHeaderRegex = /input\[('?[A-Za-z0-9\-_]+)'?\]/.exec(param);
|
||||||
|
if (inputHeaderRegex === null && param !== "input") {
|
||||||
|
// Invalid parameter key.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxInputNum > 1) {
|
||||||
|
this.manager.input.addInput(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputHeaderRegex !== null && inputHeaderRegex.length !== 1) {
|
||||||
|
// This input has a custom header that can be 'imported'.
|
||||||
|
const header = inputHeaderRegex[1];
|
||||||
|
if (header[0] === "'") {
|
||||||
|
this.manager.input.setTabName(maxInputNum, fromBase64(header.substring(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manager.input.updateInputValue(maxInputNum, fromBase64(value));
|
||||||
|
maxInputNum++;
|
||||||
|
}
|
||||||
|
this.manager.input.changeTab(1, true);
|
||||||
|
this.manager.input.bakeAll();
|
||||||
|
|
||||||
// Read in theme from URI params
|
// Read in theme from URI params
|
||||||
if (this.uriParams.theme) {
|
if (this.uriParams.theme) {
|
||||||
this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme));
|
this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme));
|
||||||
|
@ -730,18 +731,16 @@ class App {
|
||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
this.autoBake();
|
this.autoBake();
|
||||||
|
|
||||||
this.updateTitle(true, null, true);
|
this.updateTitle(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the page title to contain the new recipe
|
* Update the page title to contain the new recipe
|
||||||
*
|
*
|
||||||
* @param {boolean} includeInput
|
|
||||||
* @param {string} input
|
|
||||||
* @param {boolean} [changeUrl=true]
|
* @param {boolean} [changeUrl=true]
|
||||||
*/
|
*/
|
||||||
updateTitle(includeInput, input, changeUrl=true) {
|
updateTitle(changeUrl=true) {
|
||||||
// Set title
|
// Set title
|
||||||
const recipeConfig = this.getRecipeConfig();
|
const recipeConfig = this.getRecipeConfig();
|
||||||
let title = "CyberChef";
|
let title = "CyberChef";
|
||||||
|
@ -761,8 +760,10 @@ class App {
|
||||||
|
|
||||||
// Update the current history state (not creating a new one)
|
// Update the current history state (not creating a new one)
|
||||||
if (this.options.updateUrl && changeUrl) {
|
if (this.options.updateUrl && changeUrl) {
|
||||||
this.lastStateUrl = this.manager.controls.generateStateUrl(true, includeInput, input, recipeConfig);
|
this.manager.controls.generateStateUrl(true, true, recipeConfig).then((lastStateUrl) => {
|
||||||
|
this.lastStateUrl = lastStateUrl;
|
||||||
window.history.replaceState({}, title, this.lastStateUrl);
|
window.history.replaceState({}, title, this.lastStateUrl);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -586,6 +586,13 @@
|
||||||
Keep the current tab in sync between the input and output
|
Keep the current tab in sync between the input and output
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox option-item">
|
||||||
|
<label for="emptyInputPreserve">
|
||||||
|
<input type="checkbox" option="emptyInputPreserve" id="emptyInputPreserve" checked>
|
||||||
|
Include empty inputs in URL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
|
<button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
|
||||||
|
|
|
@ -54,7 +54,8 @@ function main() {
|
||||||
autoMagic: true,
|
autoMagic: true,
|
||||||
imagePreview: true,
|
imagePreview: true,
|
||||||
syncTabs: true,
|
syncTabs: true,
|
||||||
preserveCR: "entropy"
|
preserveCR: "entropy",
|
||||||
|
emptyInputPreserve: false
|
||||||
};
|
};
|
||||||
|
|
||||||
document.removeEventListener("DOMContentLoaded", main, false);
|
document.removeEventListener("DOMContentLoaded", main, false);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Utils from "../../core/Utils.mjs";
|
import Utils from "../../core/Utils.mjs";
|
||||||
|
import { toBase64 } from "../../core/lib/Base64.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,10 +101,10 @@ class ControlsWaiter {
|
||||||
const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked;
|
const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked;
|
||||||
const includeInput = document.getElementById("save-link-input-checkbox").checked;
|
const includeInput = document.getElementById("save-link-input-checkbox").checked;
|
||||||
const saveLinkEl = document.getElementById("save-link");
|
const saveLinkEl = document.getElementById("save-link");
|
||||||
const saveLink = this.generateStateUrl(includeRecipe, includeInput, null, recipeConfig);
|
this.generateStateUrl(includeRecipe, includeInput, recipeConfig).then((saveLink) => {
|
||||||
|
|
||||||
saveLinkEl.innerHTML = Utils.escapeHtml(Utils.truncate(saveLink, 120));
|
saveLinkEl.innerHTML = Utils.escapeHtml(Utils.truncate(saveLink, 120));
|
||||||
saveLinkEl.setAttribute("href", saveLink);
|
saveLinkEl.setAttribute("href", saveLink);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,12 +113,11 @@ class ControlsWaiter {
|
||||||
*
|
*
|
||||||
* @param {boolean} includeRecipe - Whether to include the recipe in the URL.
|
* @param {boolean} includeRecipe - Whether to include the recipe in the URL.
|
||||||
* @param {boolean} includeInput - Whether to include the input in the URL.
|
* @param {boolean} includeInput - Whether to include the input in the URL.
|
||||||
* @param {string} input
|
|
||||||
* @param {Object[]} [recipeConfig] - The recipe configuration object array.
|
* @param {Object[]} [recipeConfig] - The recipe configuration object array.
|
||||||
* @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included
|
* @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
generateStateUrl(includeRecipe, includeInput, input, recipeConfig, baseURL) {
|
async generateStateUrl(includeRecipe, includeInput, recipeConfig, baseURL) {
|
||||||
recipeConfig = recipeConfig || this.app.getRecipeConfig();
|
recipeConfig = recipeConfig || this.app.getRecipeConfig();
|
||||||
|
|
||||||
const link = baseURL || window.location.protocol + "//" +
|
const link = baseURL || window.location.protocol + "//" +
|
||||||
|
@ -127,21 +127,30 @@ class ControlsWaiter {
|
||||||
|
|
||||||
includeRecipe = includeRecipe && (recipeConfig.length > 0);
|
includeRecipe = includeRecipe && (recipeConfig.length > 0);
|
||||||
|
|
||||||
// If we don't get passed an input, get it from the current URI
|
const params = [includeRecipe ? ["recipe", recipeStr] : undefined];
|
||||||
if (input === null && includeInput) {
|
|
||||||
const params = this.app.getURIParams();
|
if (includeInput) {
|
||||||
if (params.input) {
|
// getTabList() only returns visible tabs so we need to use getInputNums().
|
||||||
includeInput = true;
|
const inputRange = (await this.manager.input.getInputNums()).inputNums;
|
||||||
input = params.input;
|
for (let i = 0; i < inputRange.length; i++) {
|
||||||
} else {
|
const inputNum = parseInt(inputRange[i], 10);
|
||||||
includeInput = false;
|
|
||||||
}
|
const inputValue = toBase64(await this.manager.input.getInputValue(inputNum), "A-Za-z0-9-_");
|
||||||
|
if (typeof inputValue !== "string" || inputValue.length > 2048 ||
|
||||||
|
(!this.app.options.emptyInputPreserve && inputValue.length === 0)) {
|
||||||
|
// Don't store other datatypes or strings that are too long (arbitrary size limit).
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = [
|
let inputIdentifier = inputNum.toString();
|
||||||
includeRecipe ? ["recipe", recipeStr] : undefined,
|
const tabHeader = await this.manager.input.getTabName(inputNum);
|
||||||
includeInput ? ["input", Utils.escapeHtml(input)] : undefined,
|
if (typeof tabHeader === "string" && tabHeader.length > 0) {
|
||||||
];
|
inputIdentifier = "'" + toBase64(tabHeader, "A-Za-z0-9-_") + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push([`input[${inputIdentifier}]`, inputValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hash = params
|
const hash = params
|
||||||
.filter(v => v)
|
.filter(v => v)
|
||||||
|
@ -344,17 +353,17 @@ class ControlsWaiter {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const reportBugInfo = document.getElementById("report-bug-info");
|
const reportBugInfo = document.getElementById("report-bug-info");
|
||||||
const saveLink = this.generateStateUrl(true, true, null, null, "https://gchq.github.io/CyberChef/");
|
this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/").then((saveLink) => {
|
||||||
|
|
||||||
if (reportBugInfo) {
|
if (reportBugInfo) {
|
||||||
reportBugInfo.innerHTML = `* Version: ${PKG_VERSION}
|
reportBugInfo.innerHTML = `* Version: ${PKG_VERSION}
|
||||||
* Compile time: ${COMPILE_TIME}
|
* Compile time: ${COMPILE_TIME}
|
||||||
* User-Agent:
|
* User-Agent:
|
||||||
${navigator.userAgent}
|
${navigator.userAgent}
|
||||||
* [Link to reproduce](${saveLink})
|
* [Link to reproduce](${saveLink})
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWorker.js";
|
import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWorker.js";
|
||||||
import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs";
|
import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs";
|
||||||
import Utils, { debounce } from "../../core/Utils.mjs";
|
import Utils, { debounce } from "../../core/Utils.mjs";
|
||||||
import { toBase64 } from "../../core/lib/Base64.mjs";
|
|
||||||
import { isImage } from "../../core/lib/FileType.mjs";
|
import { isImage } from "../../core/lib/FileType.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
@ -291,7 +290,7 @@ class InputWaiter {
|
||||||
this.app.handleError(r.data);
|
this.app.handleError(r.data);
|
||||||
break;
|
break;
|
||||||
case "setUrl":
|
case "setUrl":
|
||||||
this.setUrl(r.data);
|
this.setUrl();
|
||||||
break;
|
break;
|
||||||
case "inputSwitch":
|
case "inputSwitch":
|
||||||
this.manager.output.inputSwitch(r.data);
|
this.manager.output.inputSwitch(r.data);
|
||||||
|
@ -306,6 +305,9 @@ class InputWaiter {
|
||||||
case "fileLoaded":
|
case "fileLoaded":
|
||||||
this.fileLoaded(r.data.inputNum);
|
this.fileLoaded(r.data.inputNum);
|
||||||
break;
|
break;
|
||||||
|
case "getTabName":
|
||||||
|
this.callbacks[r.data.id](r.data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.error(`Unknown action ${r.action}.`);
|
log.error(`Unknown action ${r.action}.`);
|
||||||
}
|
}
|
||||||
|
@ -363,14 +365,7 @@ class InputWaiter {
|
||||||
inputData.input.count("\n") + 1 : null;
|
inputData.input.count("\n") + 1 : null;
|
||||||
this.setInputInfo(inputData.input.length, lines);
|
this.setInputInfo(inputData.input.length, lines);
|
||||||
|
|
||||||
// Set URL to current input
|
this.setUrl();
|
||||||
const inputStr = toBase64(inputData.input, "A-Za-z0-9+/");
|
|
||||||
if (inputStr.length > 0 && inputStr.length <= 68267) {
|
|
||||||
this.setUrl({
|
|
||||||
includeInput: true,
|
|
||||||
input: inputStr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!silent) window.dispatchEvent(this.manager.statechange);
|
if (!silent) window.dispatchEvent(this.manager.statechange);
|
||||||
} else {
|
} else {
|
||||||
|
@ -527,15 +522,7 @@ class InputWaiter {
|
||||||
* @param {boolean} [force=false] - If true, forces the value to be updated even if the type is different to the currently stored type
|
* @param {boolean} [force=false] - If true, forces the value to be updated even if the type is different to the currently stored type
|
||||||
*/
|
*/
|
||||||
updateInputValue(inputNum, value, force=false) {
|
updateInputValue(inputNum, value, force=false) {
|
||||||
let includeInput = false;
|
this.setUrl();
|
||||||
const recipeStr = toBase64(value, "A-Za-z0-9+/"); // B64 alphabet with no padding
|
|
||||||
if (recipeStr.length > 0 && recipeStr.length <= 68267) {
|
|
||||||
includeInput = true;
|
|
||||||
}
|
|
||||||
this.setUrl({
|
|
||||||
includeInput: includeInput,
|
|
||||||
input: recipeStr
|
|
||||||
});
|
|
||||||
|
|
||||||
// Value is either a string set by the input or an ArrayBuffer from a LoaderWorker,
|
// Value is either a string set by the input or an ArrayBuffer from a LoaderWorker,
|
||||||
// so is safe to use typeof === "string"
|
// so is safe to use typeof === "string"
|
||||||
|
@ -1084,6 +1071,7 @@ class InputWaiter {
|
||||||
this.setupInputWorker();
|
this.setupInputWorker();
|
||||||
this.manager.worker.setupChefWorker();
|
this.manager.worker.setupChefWorker();
|
||||||
this.addInput(true);
|
this.addInput(true);
|
||||||
|
this.manager.input.changeTab(1, true);
|
||||||
this.bakeAll();
|
this.bakeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1095,7 +1083,7 @@ class InputWaiter {
|
||||||
const inputNum = this.manager.tabs.getActiveInputTab();
|
const inputNum = this.manager.tabs.getActiveInputTab();
|
||||||
if (inputNum === -1) return;
|
if (inputNum === -1) return;
|
||||||
|
|
||||||
this.manager.tabs.removeTabHeaderAlias(inputNum);
|
this.manager.input.setTabName(inputNum, "");
|
||||||
this.manager.highlighter.removeHighlights();
|
this.manager.highlighter.removeHighlights();
|
||||||
getSelection().removeAllRanges();
|
getSelection().removeAllRanges();
|
||||||
|
|
||||||
|
@ -1234,7 +1222,6 @@ class InputWaiter {
|
||||||
removeChefWorker: true
|
removeChefWorker: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.manager.tabs.removeTabHeaderAlias(inputNum);
|
|
||||||
|
|
||||||
this.manager.output.removeTab(inputNum);
|
this.manager.output.removeTab(inputNum);
|
||||||
}
|
}
|
||||||
|
@ -1248,10 +1235,9 @@ class InputWaiter {
|
||||||
if (!mouseEvent.target) {
|
if (!mouseEvent.target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tabNumStr = mouseEvent.target.closest("button").parentElement.getAttribute("inputNum");
|
const tabNum = mouseEvent.target.closest("button").parentElement.getAttribute("inputNum");
|
||||||
if (tabNumStr) {
|
if (tabNum) {
|
||||||
const tabNum = parseInt(tabNumStr, 10);
|
this.removeInput(parseInt(tabNum, 10));
|
||||||
this.removeInput(tabNum);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1437,13 +1423,61 @@ class InputWaiter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the input URL to the new value
|
* Update the input URL to the new value
|
||||||
*
|
|
||||||
* @param {object} urlData - Object containing the URL data
|
|
||||||
* @param {boolean} urlData.includeInput - If true, the input is included in the title
|
|
||||||
* @param {string} urlData.input - The input data to be included
|
|
||||||
*/
|
*/
|
||||||
setUrl(urlData) {
|
setUrl() {
|
||||||
this.app.updateTitle(urlData.includeInput, urlData.input, true);
|
this.app.updateTitle(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the custom name of any tab
|
||||||
|
*
|
||||||
|
* @param {number} inputNum - The input number of the tab
|
||||||
|
* @returns {string} - The tab's custom name or null if it has not been assigned one
|
||||||
|
*/
|
||||||
|
async getTabName(inputNum) {
|
||||||
|
if (inputNum <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const tabName = (await new Promise(resolve => {
|
||||||
|
this.queryTabName(r => {
|
||||||
|
resolve(r);
|
||||||
|
}, inputNum);
|
||||||
|
})).data;
|
||||||
|
return tabName === null ? "" : tabName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the inputWorker a message requesting the custom name of a given tab
|
||||||
|
*
|
||||||
|
* @param {object} callback - The callback to be executed after the worker finishes
|
||||||
|
* @param {number} inputNum - The input number of the tab
|
||||||
|
*/
|
||||||
|
queryTabName(callback, inputNum) {
|
||||||
|
const callbackId = this.callbackID++;
|
||||||
|
this.callbacks[callbackId] = callback;
|
||||||
|
this.inputWorker.postMessage({
|
||||||
|
action: "getTabName",
|
||||||
|
data: {
|
||||||
|
id: callbackId,
|
||||||
|
inputNum: inputNum
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the provided tab with a custo name
|
||||||
|
*
|
||||||
|
* @param {number} inputNum - The input number of the tab
|
||||||
|
* @param {string} tabName - The new name for the tab
|
||||||
|
*/
|
||||||
|
setTabName(inputNum, tabName) {
|
||||||
|
this.inputWorker.postMessage({
|
||||||
|
action: "setTabName",
|
||||||
|
data: {
|
||||||
|
inputNum: inputNum,
|
||||||
|
tabName: tabName
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1461,9 +1495,6 @@ class InputWaiter {
|
||||||
let renameContents = targetElement.textContent;
|
let renameContents = targetElement.textContent;
|
||||||
const renameContentsColon = renameContents.indexOf(":");
|
const renameContentsColon = renameContents.indexOf(":");
|
||||||
|
|
||||||
// Calling 'getInputValue()' might take a long time for large datasets,
|
|
||||||
// it could be beneficial to modify the API to allow for querying whether
|
|
||||||
// there is any input rather than getting the full string just to access its length.
|
|
||||||
const inputLength = (await this.getInputValue(this.manager.tabs.getActiveInputTab())).length;
|
const inputLength = (await this.getInputValue(this.manager.tabs.getActiveInputTab())).length;
|
||||||
|
|
||||||
// Remove the data from the renaming section
|
// Remove the data from the renaming section
|
||||||
|
@ -1499,20 +1530,25 @@ class InputWaiter {
|
||||||
if (activeInputTabNum === -1) {
|
if (activeInputTabNum === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeInputTabElement = this.manager.tabs.getTabItem(activeInputTabNum, "input");
|
const activeInputTabElement = this.manager.tabs.getTabItem(activeInputTabNum, "input");
|
||||||
if (activeInputTabElement == null || activeInputTabElement.children.size < 1) {
|
if (activeInputTabElement == null || activeInputTabElement.children.size < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabContent = activeInputTabElement.children[0];
|
const tabContent = activeInputTabElement.children[0];
|
||||||
const tabHeader = tabContent.children[0].children[0].value;
|
const tabHeader = tabContent.children[0].children[0].value;
|
||||||
const inputContents = await this.getInputValue(activeInputTabNum);
|
const inputContents = await this.getInputValue(activeInputTabNum);
|
||||||
|
|
||||||
if (tabHeader.length === 0)
|
if (tabHeader.length === 0)
|
||||||
this.manager.tabs.removeTabHeaderAlias(activeInputTabNum, false);
|
this.manager.input.setTabName(activeInputTabNum, "");
|
||||||
else
|
else
|
||||||
this.manager.tabs.addTabHeaderAlias(activeInputTabNum, tabHeader);
|
this.manager.input.setTabName(activeInputTabNum, tabHeader);
|
||||||
|
|
||||||
this.manager.tabs.updateInputTabHeader(activeInputTabNum, inputContents);
|
this.manager.tabs.updateInputTabHeader(activeInputTabNum, inputContents);
|
||||||
this.manager.tabs.updateOutputTabHeader(activeInputTabNum, inputContents);
|
this.manager.tabs.updateOutputTabHeader(activeInputTabNum, inputContents);
|
||||||
|
|
||||||
|
this.manager.input.setUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ class TabWaiter {
|
||||||
constructor(app, manager) {
|
constructor(app, manager) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.tabHeaderAliases = []; // Mapping custom tab headers to indexes/numbers.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -151,6 +150,8 @@ class TabWaiter {
|
||||||
const newTab = document.createElement("li");
|
const newTab = document.createElement("li");
|
||||||
newTab.setAttribute("inputNum", inputNum.toString());
|
newTab.setAttribute("inputNum", inputNum.toString());
|
||||||
|
|
||||||
|
newTab.setAttribute("tabHeader", "");
|
||||||
|
|
||||||
if (active) newTab.classList.add(`active-${io}-tab`);
|
if (active) newTab.classList.add(`active-${io}-tab`);
|
||||||
|
|
||||||
const newTabContent = document.createElement("div");
|
const newTabContent = document.createElement("div");
|
||||||
|
@ -178,6 +179,8 @@ class TabWaiter {
|
||||||
newTab.appendChild(newTabButton);
|
newTab.appendChild(newTabButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.manager.input.setUrl();
|
||||||
|
|
||||||
return newTab;
|
return newTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,11 +356,9 @@ class TabWaiter {
|
||||||
const tab = this.getTabItem(inputNum, io);
|
const tab = this.getTabItem(inputNum, io);
|
||||||
if (tab == null) return;
|
if (tab == null) return;
|
||||||
|
|
||||||
const customHeaderData = this.getTabHeaderAlias(inputNum);
|
const customHeaderData = await this.manager.input.getTabName(inputNum);
|
||||||
|
|
||||||
// When 'customHeaderData === `Tab ${inputNum}`' is true, it's usually due to
|
const isStandardHeader = customHeaderData.length === 0 || customHeaderData === `Tab ${inputNum}`;
|
||||||
// a user having opened the rename textbox but then closing it without change.
|
|
||||||
const isStandardHeader = customHeaderData === null || customHeaderData === `Tab ${inputNum}`;
|
|
||||||
|
|
||||||
let headerData = isStandardHeader ? `Tab ${inputNum}` : customHeaderData;
|
let headerData = isStandardHeader ? `Tab ${inputNum}` : customHeaderData;
|
||||||
const dataIsFile = data instanceof ArrayBuffer;
|
const dataIsFile = data instanceof ArrayBuffer;
|
||||||
|
@ -375,10 +376,12 @@ class TabWaiter {
|
||||||
|
|
||||||
headerData += `: ${dataPreview}`;
|
headerData += `: ${dataPreview}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
tab.firstElementChild.innerText = headerData;
|
tab.firstElementChild.innerText = headerData;
|
||||||
if (!isStandardHeader && !includeData)
|
if (!isStandardHeader && !includeData) {
|
||||||
tab.firstElementChild.innerText = `'${headerData}'`;
|
tab.firstElementChild.innerText = `'${headerData}'`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the input tab header to display a preview of the tab contents
|
* Updates the input tab header to display a preview of the tab contents
|
||||||
|
@ -441,63 +444,6 @@ class TabWaiter {
|
||||||
updateOutputTabProgress(inputNum, progress, total) {
|
updateOutputTabProgress(inputNum, progress, total) {
|
||||||
this.updateTabProgress(inputNum, progress, total, "output");
|
this.updateTabProgress(inputNum, progress, total, "output");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an alias between a custom tab header and a tab number so that
|
|
||||||
* mapping between the two is possible if DOM element is removed.
|
|
||||||
*
|
|
||||||
* @param {number} tabNum - The index of the tab being aliased
|
|
||||||
* @param {string} tabHeader - The custom tab header
|
|
||||||
*/
|
|
||||||
addTabHeaderAlias(tabNum, tabHeader) {
|
|
||||||
// First, we try to overwrite an existing alias.
|
|
||||||
for (let i = 0; i < this.tabHeaderAliases.length; i++) {
|
|
||||||
if (this.tabHeaderAliases.at(i).tabNumber === tabNum) {
|
|
||||||
this.tabHeaderAliases.at(i).customHeader = tabHeader;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.tabHeaderAliases.push({tabNumber: tabNum, customHeader: tabHeader});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a previously-assigned header alias.
|
|
||||||
*
|
|
||||||
* @param {number} tabNum - The index of the tab that should be removed.
|
|
||||||
* @param {boolean} shouldThrow - A boolean representing whether the function should throw an exception or return silently if it cannot locate the tab header.
|
|
||||||
*/
|
|
||||||
removeTabHeaderAlias(tabNum, shouldThrow) {
|
|
||||||
for (let i = 0; i < this.tabHeaderAliases.length; i++) {
|
|
||||||
|
|
||||||
if (this.tabHeaderAliases.at(i).tabNumber === tabNum) {
|
|
||||||
this.tabHeaderAliases.splice(i, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (shouldThrow)
|
|
||||||
throw `Unable to locate header alias at tab index ${tabNum.toString()}.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the custom header for a given tab.
|
|
||||||
*
|
|
||||||
* @param {number} tabNum - The index of the tab whose alias should be retrieved.
|
|
||||||
* @param {boolean} shouldThrow - Whether the function should throw an exception (instead of returning null) in the event of it being unable to locate the tab.
|
|
||||||
* @returns {string} customHeader - The custom header for the requested tab.
|
|
||||||
*/
|
|
||||||
getTabHeaderAlias(tabNum, shouldThrow) {
|
|
||||||
for (let i = 0; i < this.tabHeaderAliases.length; i++) {
|
|
||||||
|
|
||||||
if (this.tabHeaderAliases.at(i).tabNumber === tabNum)
|
|
||||||
return this.tabHeaderAliases.at(i).customHeader;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (shouldThrow)
|
|
||||||
throw `Unable to locate header alias at tab index ${tabNum.toString()}.`;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TabWaiter;
|
export default TabWaiter;
|
||||||
|
|
|
@ -113,6 +113,12 @@ self.addEventListener("message", function(e) {
|
||||||
case "getInputNums":
|
case "getInputNums":
|
||||||
self.getInputNums(r.data);
|
self.getInputNums(r.data);
|
||||||
break;
|
break;
|
||||||
|
case "getTabName":
|
||||||
|
self.getTabName(r.data);
|
||||||
|
break;
|
||||||
|
case "setTabName":
|
||||||
|
self.setTabName(r.data.inputNum, r.data.tabName);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.error(`Unknown action '${r.action}'.`);
|
log.error(`Unknown action '${r.action}'.`);
|
||||||
}
|
}
|
||||||
|
@ -413,14 +419,17 @@ self.getNearbyNums = function(inputNum, direction) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the data to display in the tab header for an input, and
|
* Gets the data to display in the tab header for an input and
|
||||||
* posts it back to the inputWaiter
|
* posts it back to the inputWaiter.
|
||||||
*
|
*
|
||||||
* @param {number} inputNum - The inputNum of the tab header
|
* @param {number} inputNum - The inputNum of the tab header.
|
||||||
*/
|
*/
|
||||||
self.updateTabHeader = function(inputNum) {
|
self.updateTabHeader = function(inputNum) {
|
||||||
const input = self.getInputObj(inputNum);
|
const input = self.getInputObj(inputNum);
|
||||||
if (input === null || input === undefined) return;
|
if (input === null || input === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let inputData = input.data;
|
let inputData = input.data;
|
||||||
if (typeof inputData !== "string") {
|
if (typeof inputData !== "string") {
|
||||||
inputData = input.data.name;
|
inputData = input.data.name;
|
||||||
|
@ -1079,3 +1088,33 @@ self.inputSwitch = function(switchData) {
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the custom name of a tab, returning null if one has not been set
|
||||||
|
*
|
||||||
|
* @param {object} inputData
|
||||||
|
* @param {number} inputData.inputNum - The input number of the tab
|
||||||
|
* @param {number} inputData.id - The callback ID for the callvack to run when returning to the inputWaiter
|
||||||
|
*/
|
||||||
|
self.getTabName = function(inputData) {
|
||||||
|
const { inputNum, id } = inputData;
|
||||||
|
const inputName = self.inputs[inputNum].inputName;
|
||||||
|
const tabName = typeof inputName !== "string" ? null : inputName;
|
||||||
|
self.postMessage({
|
||||||
|
action: "getTabName",
|
||||||
|
data: {
|
||||||
|
data: tabName,
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the custom name of a tab, overwriting any previously-assigned one
|
||||||
|
*
|
||||||
|
* @param {number} inputNum - The input number of the tab
|
||||||
|
* @param {string} tabName - The custom name that should be assigned to the tab
|
||||||
|
*/
|
||||||
|
self.setTabName = function(inputNum, tabName) {
|
||||||
|
self.inputs[inputNum].inputName = tabName;
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue