Add type checking and JSDoc comments to some utils
This commit is contained in:
parent
2615eb8532
commit
e2bac7ec5f
|
@ -52,8 +52,12 @@
|
|||
"sillytavern": "server.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/moment": "^2.13.0",
|
||||
"dompurify": "^3.0.5",
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2",
|
||||
"showdown": "^2.1.0",
|
||||
"toastr": "^2.1.4"
|
||||
}
|
||||
},
|
||||
|
@ -645,11 +649,31 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
|
||||
},
|
||||
"node_modules/@types/moment": {
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz",
|
||||
"integrity": "sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ==",
|
||||
"deprecated": "This is a stub types definition for Moment (https://github.com/moment/moment). Moment provides its own type definitions, so you don't need @types/moment installed!",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"moment": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
|
||||
|
@ -1028,6 +1052,15 @@
|
|||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
||||
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
|
@ -1265,6 +1298,12 @@
|
|||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz",
|
||||
"integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -2157,6 +2196,15 @@
|
|||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
|
@ -3069,6 +3117,22 @@
|
|||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/showdown": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz",
|
||||
"integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"commander": "^9.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"showdown": "bin/showdown.js"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://www.paypal.me/tiviesantos"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
|
|
|
@ -80,8 +80,12 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/moment": "^2.13.0",
|
||||
"dompurify": "^3.0.5",
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2",
|
||||
"showdown": "^2.1.0",
|
||||
"toastr": "^2.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"allowUmdGlobalAccess": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"typeAcquisition": {
|
||||
"include": [
|
||||
"jquery",
|
||||
"@popperjs/core",
|
||||
"toastr",
|
||||
"showdown",
|
||||
"dompurify",
|
||||
"@types/moment"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -638,10 +638,10 @@ export function getTextTokens(tokenizerType, str) {
|
|||
function reloadMarkdownProcessor(render_formulas = false) {
|
||||
if (render_formulas) {
|
||||
converter = new showdown.Converter({
|
||||
emoji: "true",
|
||||
underline: "true",
|
||||
tables: "true",
|
||||
parseImgDimensions: "true",
|
||||
emoji: true,
|
||||
underline: true,
|
||||
tables: true,
|
||||
parseImgDimensions: true,
|
||||
extensions: [
|
||||
showdownKatex(
|
||||
{
|
||||
|
@ -655,10 +655,10 @@ function reloadMarkdownProcessor(render_formulas = false) {
|
|||
}
|
||||
else {
|
||||
converter = new showdown.Converter({
|
||||
emoji: "true",
|
||||
literalMidWordUnderscores: "true",
|
||||
parseImgDimensions: "true",
|
||||
tables: "true",
|
||||
emoji: true,
|
||||
literalMidWordUnderscores: true,
|
||||
parseImgDimensions: true,
|
||||
tables: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5571,7 +5571,7 @@ async function messageEditDone(div) {
|
|||
*
|
||||
* @param {Array} data - An array containing metadata about each chat such as file_name.
|
||||
* @param {boolean} isGroupChat - A flag indicating if the chat is a group chat.
|
||||
* @returns {Object} chat_dict - A dictionary where each key is a file_name and the value is the
|
||||
* @returns {Promise<Object>} chat_dict - A dictionary where each key is a file_name and the value is the
|
||||
* corresponding chat content fetched from the server.
|
||||
*/
|
||||
export async function getChatsFromFiles(data, isGroupChat) {
|
||||
|
@ -5621,7 +5621,7 @@ export async function getChatsFromFiles(data, isGroupChat) {
|
|||
* The function sends a POST request to the server to retrieve all chats for the character. It then
|
||||
* processes the received data, sorts it by the file name, and returns the sorted data.
|
||||
*
|
||||
* @returns {Array} - An array containing metadata of all past chats of the character, sorted
|
||||
* @returns {Promise<Array>} - An array containing metadata of all past chats of the character, sorted
|
||||
* in descending order by file name. Returns `undefined` if the fetch request is unsuccessful.
|
||||
*/
|
||||
async function getPastCharacterChats() {
|
||||
|
@ -7998,7 +7998,6 @@ $(document).ready(function () {
|
|||
/* $('#set_chat_scenario').on('click', setScenarioOverride); */
|
||||
|
||||
///////////// OPTIMIZED LISTENERS FOR LEFT SIDE OPTIONS POPUP MENU //////////////////////
|
||||
|
||||
$("#options [id]").on("click", function (event, customData) {
|
||||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||||
var id = $(this).attr("id");
|
||||
|
@ -8500,7 +8499,6 @@ $(document).ready(function () {
|
|||
showSwipeButtons();
|
||||
});
|
||||
|
||||
|
||||
$(document).on("click", ".mes_edit_delete", async function (event, customData) {
|
||||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||||
const swipeExists = (!chat[this_edit_mes_id].swipes || chat[this_edit_mes_id].swipes.length <= 1 || chat.is_user || parseInt(this_edit_mes_id) !== chat.length - 1);
|
||||
|
@ -9072,7 +9070,7 @@ $(document).ready(function () {
|
|||
await importWorldInfo(file);
|
||||
break;
|
||||
default:
|
||||
toastr.warn('Unknown content type');
|
||||
toastr.warning('Unknown content type');
|
||||
console.error('Unknown content type', customContentType);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -36,8 +36,6 @@ import {
|
|||
import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
|
||||
var RPanelPin = document.getElementById("rm_button_panel_pin");
|
||||
var LPanelPin = document.getElementById("lm_button_panel_pin");
|
||||
var WIPanelPin = document.getElementById("WI_panel_pin");
|
||||
|
@ -47,20 +45,8 @@ var LeftNavPanel = document.getElementById("left-nav-panel");
|
|||
var WorldInfo = document.getElementById("WorldInfo");
|
||||
|
||||
var SelectedCharacterTab = document.getElementById("rm_button_selected_ch");
|
||||
var AdvancedCharDefsPopup = document.getElementById("character_popup");
|
||||
var ConfirmationPopup = document.getElementById("dialogue_popup");
|
||||
var AutoConnectCheckbox = document.getElementById("auto-connect-checkbox");
|
||||
var AutoLoadChatCheckbox = document.getElementById("auto-load-chat-checkbox");
|
||||
var SelectedNavTab = ("#" + LoadLocal('SelectedNavTab'));
|
||||
|
||||
var create_save_name;
|
||||
var create_save_description;
|
||||
var create_save_personality;
|
||||
var create_save_first_message;
|
||||
var create_save_scenario;
|
||||
var create_save_mes_example;
|
||||
var count_tokens;
|
||||
var perm_tokens;
|
||||
|
||||
var connection_made = false;
|
||||
var retry_delay = 500;
|
||||
|
@ -83,32 +69,6 @@ const observer = new MutationObserver(function (mutations) {
|
|||
|
||||
observer.observe(document.documentElement, observerConfig);
|
||||
|
||||
/**
|
||||
* Wait for an element before resolving a promise
|
||||
* @param {String} querySelector - Selector of element to wait for
|
||||
* @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
|
||||
*/
|
||||
function waitForElement(querySelector, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var timer = false;
|
||||
if (document.querySelectorAll(querySelector).length) return resolve();
|
||||
const observer = new MutationObserver(() => {
|
||||
if (document.querySelectorAll(querySelector).length) {
|
||||
observer.disconnect();
|
||||
if (timer !== false) clearTimeout(timer);
|
||||
return resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
if (timeout) timer = setTimeout(() => {
|
||||
observer.disconnect();
|
||||
reject();
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts generation time from milliseconds to a human-readable format.
|
||||
|
@ -225,14 +185,6 @@ export function getMessageTimeStamp() {
|
|||
// triggers:
|
||||
$("#rm_button_create").on("click", function () { //when "+New Character" is clicked
|
||||
$(SelectedCharacterTab).children("h2").html(''); // empty nav's 3rd panel tab
|
||||
|
||||
//empty temp vars to store new char data for counting
|
||||
create_save_name = "";
|
||||
create_save_description = "";
|
||||
create_save_personality = "";
|
||||
create_save_first_message = "";
|
||||
create_save_scenario = "";
|
||||
create_save_mes_example = "";
|
||||
});
|
||||
//when any input is made to the create/edit character form textareas
|
||||
$("#rm_ch_create_block").on("input", function () { countTokensDebounced(); });
|
||||
|
@ -804,7 +756,7 @@ jQuery(async function () {
|
|||
//console.log('setting pin class via local var');
|
||||
$(RightNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
if ($(RPanelPin).prop('checked' == true)) {
|
||||
if (!!$(RPanelPin).prop('checked')) {
|
||||
console.debug('setting pin class via checkbox state');
|
||||
$(RightNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
|
@ -814,7 +766,7 @@ jQuery(async function () {
|
|||
//console.log('setting pin class via local var');
|
||||
$(LeftNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
if ($(LPanelPin).prop('checked' == true)) {
|
||||
if (!!$(LPanelPin).prop('checked')) {
|
||||
console.debug('setting pin class via checkbox state');
|
||||
$(LeftNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
|
@ -826,7 +778,7 @@ jQuery(async function () {
|
|||
$(WorldInfo).addClass('pinnedOpen');
|
||||
}
|
||||
|
||||
if ($(WIPanelPin).prop('checked' == true)) {
|
||||
if (!!$(WIPanelPin).prop('checked')) {
|
||||
console.debug('setting pin class via checkbox state');
|
||||
$(WorldInfo).addClass('pinnedOpen');
|
||||
}
|
||||
|
@ -889,8 +841,6 @@ jQuery(async function () {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
||||
|
||||
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
|
||||
$('#send_textarea').on('input', function () {
|
||||
this.style.height = '40px';
|
||||
|
@ -901,7 +851,7 @@ jQuery(async function () {
|
|||
|
||||
document.addEventListener('swiped-left', function (e) {
|
||||
var SwipeButR = $('.swipe_right:last');
|
||||
var SwipeTargetMesClassParent = e.target.closest('.last_mes');
|
||||
var SwipeTargetMesClassParent = $(e.target).closest('.last_mes');
|
||||
if (SwipeTargetMesClassParent !== null) {
|
||||
if (SwipeButR.css('display') === 'flex') {
|
||||
SwipeButR.click();
|
||||
|
@ -910,7 +860,7 @@ jQuery(async function () {
|
|||
});
|
||||
document.addEventListener('swiped-right', function (e) {
|
||||
var SwipeButL = $('.swipe_left:last');
|
||||
var SwipeTargetMesClassParent = e.target.closest('.last_mes');
|
||||
var SwipeTargetMesClassParent = $(e.target).closest('.last_mes');
|
||||
if (SwipeTargetMesClassParent !== null) {
|
||||
if (SwipeButL.css('display') === 'flex') {
|
||||
SwipeButL.click();
|
||||
|
|
|
@ -246,29 +246,42 @@ function playMessageSound() {
|
|||
}
|
||||
|
||||
const audio = document.getElementById('audio_message_sound');
|
||||
audio.volume = 0.8;
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
audio.play();
|
||||
if (audio instanceof HTMLAudioElement) {
|
||||
audio.volume = 0.8;
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
audio.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces consecutive newlines with a single newline.
|
||||
* @param {string} x String to be processed.
|
||||
* @returns {string} Processed string.
|
||||
* @example
|
||||
* collapseNewlines("\n\n\n"); // "\n"
|
||||
*/
|
||||
function collapseNewlines(x) {
|
||||
return x.replaceAll(/\n+/g, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix formatting problems in markdown.
|
||||
* @param {string} text Text to be processed.
|
||||
* @returns {string} Processed text.
|
||||
* @example
|
||||
* "^example * text*\n" // "^example *text*\n"
|
||||
* "^*example * text\n"// "^*example* text\n"
|
||||
* "^example *text *\n" // "^example *text*\n"
|
||||
* "^* example * text\n" // "^*example* text\n"
|
||||
* // take note that the side you move the asterisk depends on where its pairing is
|
||||
* // i.e. both of the following strings have the same broken asterisk ' * ',
|
||||
* // but you move the first to the left and the second to the right, to match the non-broken asterisk
|
||||
* "^example * text*\n" // "^*example * text\n"
|
||||
* // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
|
||||
* "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n"
|
||||
*/
|
||||
function fixMarkdown(text) {
|
||||
// fix formatting problems in markdown
|
||||
// e.g.:
|
||||
// "^example * text*\n" -> "^example *text*\n"
|
||||
// "^*example * text\n" -> "^*example* text\n"
|
||||
// "^example *text *\n" -> "^example *text*\n"
|
||||
// "^* example * text\n" -> "^*example* text\n"
|
||||
// take note that the side you move the asterisk depends on where its pairing is
|
||||
// i.e. both of the following strings have the same broken asterisk ' * ',
|
||||
// but you move the first to the left and the second to the right, to match the non-broken asterisk "^example * text*\n" "^*example * text\n"
|
||||
// and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
|
||||
// i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n"
|
||||
|
||||
// Find pairs of formatting characters and capture the text in between them
|
||||
const format = /([\*_]{1,2})([\s\S]*?)\1/gm;
|
||||
let matches = [];
|
||||
|
|
|
@ -25,13 +25,12 @@ function createStatBlock(statName, statValue) {
|
|||
* @returns {number} - The stat value if it is a number, otherwise 0.
|
||||
*/
|
||||
function verifyStatValue(stat) {
|
||||
return isNaN(stat) ? 0 : stat;
|
||||
return isNaN(Number(stat)) ? 0 : Number(stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total stats from character statistics.
|
||||
*
|
||||
* @param {Object} charStats - Object containing character statistics.
|
||||
* @returns {Object} - Object containing total statistics.
|
||||
*/
|
||||
function calculateTotalStats() {
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
getCharacters,
|
||||
entitiesFilter,
|
||||
} from "../script.js";
|
||||
import { FILTER_TYPES } from "./filters.js";
|
||||
import { FILTER_TYPES, FilterHelper } from "./filters.js";
|
||||
|
||||
import { groupCandidatesFilter, selected_group } from "./group-chats.js";
|
||||
import { uuidv4 } from "./utils.js";
|
||||
|
@ -24,7 +24,6 @@ export {
|
|||
importTags,
|
||||
};
|
||||
|
||||
const random_id = () => uuidv4();
|
||||
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
|
||||
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
|
||||
|
||||
|
@ -49,17 +48,21 @@ const InListActionable = {
|
|||
}
|
||||
|
||||
const DEFAULT_TAGS = [
|
||||
{ id: random_id(), name: "Plain Text" },
|
||||
{ id: random_id(), name: "OpenAI" },
|
||||
{ id: random_id(), name: "W++" },
|
||||
{ id: random_id(), name: "Boostyle" },
|
||||
{ id: random_id(), name: "PList" },
|
||||
{ id: random_id(), name: "AliChat" },
|
||||
{ id: uuidv4(), name: "Plain Text" },
|
||||
{ id: uuidv4(), name: "OpenAI" },
|
||||
{ id: uuidv4(), name: "W++" },
|
||||
{ id: uuidv4(), name: "Boostyle" },
|
||||
{ id: uuidv4(), name: "PList" },
|
||||
{ id: uuidv4(), name: "AliChat" },
|
||||
];
|
||||
|
||||
let tags = [];
|
||||
let tag_map = {};
|
||||
|
||||
/**
|
||||
* Applies the favorite filter to the character list.
|
||||
* @param {FilterHelper} filterHelper Instance of FilterHelper class.
|
||||
*/
|
||||
function applyFavFilter(filterHelper) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayFavoritesOnly = !isSelected;
|
||||
|
@ -68,6 +71,10 @@ function applyFavFilter(filterHelper) {
|
|||
filterHelper.setFilterData(FILTER_TYPES.FAV, displayFavoritesOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the "is group" filter to the character list.
|
||||
* @param {FilterHelper} filterHelper Instance of FilterHelper class.
|
||||
*/
|
||||
function filterByGroups(filterHelper) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayGroupsOnly = !isSelected;
|
||||
|
@ -253,7 +260,7 @@ async function importTags(imported_char) {
|
|||
|
||||
function createNewTag(tagName) {
|
||||
const tag = {
|
||||
id: random_id(),
|
||||
id: uuidv4(),
|
||||
name: tagName,
|
||||
color: '',
|
||||
};
|
||||
|
|
|
@ -7,6 +7,14 @@ export function onlyUnique(value, index, array) {
|
|||
return array.indexOf(value) === index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string only contains digits.
|
||||
* @param {string} str The string to check.
|
||||
* @returns {boolean} True if the string only contains digits, false otherwise.
|
||||
* @example
|
||||
* isDigitsOnly('123'); // true
|
||||
* isDigitsOnly('abc'); // false
|
||||
*/
|
||||
export function isDigitsOnly(str) {
|
||||
return /^\d+$/.test(str);
|
||||
}
|
||||
|
@ -16,6 +24,13 @@ export function getSortableDelay() {
|
|||
return navigator.maxTouchPoints > 0 ? 750 : 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rearranges an array in a random order.
|
||||
* @param {any[]} array The array to shuffle.
|
||||
* @returns {any[]} The shuffled array.
|
||||
* @example
|
||||
* shuffle([1, 2, 3]); // [2, 3, 1]
|
||||
*/
|
||||
export function shuffle(array) {
|
||||
let currentIndex = array.length,
|
||||
randomIndex;
|
||||
|
@ -31,6 +46,12 @@ export function shuffle(array) {
|
|||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file to the user's devices.
|
||||
* @param {BlobPart} content File content to download.
|
||||
* @param {string} fileName File name.
|
||||
* @param {string} contentType File content type.
|
||||
*/
|
||||
export function download(content, fileName, contentType) {
|
||||
const a = document.createElement("a");
|
||||
const file = new Blob([content], { type: contentType });
|
||||
|
@ -49,12 +70,17 @@ export async function urlContentToDataUri(url, params) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the file's text.
|
||||
* @param {Blob} file The file to read.
|
||||
* @returns {Promise<string>} A promise that resolves to the file's text.
|
||||
*/
|
||||
export function getFileText(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
resolve(String(reader.result));
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
|
@ -62,6 +88,10 @@ export function getFileText(file) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the file's array buffer.
|
||||
* @param {Blob} file The file to read.
|
||||
*/
|
||||
export function getFileBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
@ -75,12 +105,17 @@ export function getFileBuffer(file) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the base64 encoded string of a file.
|
||||
* @param {Blob} file The file to read.
|
||||
* @returns {Promise<string>} A promise that resolves to the base64 encoded string.
|
||||
*/
|
||||
export function getBase64Async(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
resolve(String(reader.result));
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
|
@ -88,15 +123,26 @@ export function getBase64Async(file) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a file blob as a JSON object.
|
||||
* @param {Blob} file The file to read.
|
||||
* @returns {Promise<any>} A promise that resolves to the parsed JSON object.
|
||||
*/
|
||||
export async function parseJsonFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = event => resolve(JSON.parse(event.target.result));
|
||||
fileReader.onerror = error => reject(error);
|
||||
fileReader.readAsText(file);
|
||||
fileReader.onload = event => resolve(JSON.parse(String(event.target.result)));
|
||||
fileReader.onerror = error => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a hash code for a string.
|
||||
* @param {string} str The string to hash.
|
||||
* @param {number} [seed=0] The seed to use for the hash.
|
||||
* @returns {number} The hash code.
|
||||
*/
|
||||
export function getStringHash(str, seed = 0) {
|
||||
if (typeof str !== 'string') {
|
||||
return 0;
|
||||
|
@ -116,6 +162,12 @@ export function getStringHash(str, seed = 0) {
|
|||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
|
||||
* @param {function} func The function to debounce.
|
||||
* @param {number} [timeout=300] The timeout in milliseconds.
|
||||
* @returns {function} The debounced function.
|
||||
*/
|
||||
export function debounce(func, timeout = 300) {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
|
@ -124,6 +176,12 @@ export function debounce(func, timeout = 300) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a throttled function that only invokes func at most once per every limit milliseconds.
|
||||
* @param {function} func The function to throttle.
|
||||
* @param {number} [limit=300] The limit in milliseconds.
|
||||
* @returns {function} The throttled function.
|
||||
*/
|
||||
export function throttle(func, limit = 300) {
|
||||
let lastCall;
|
||||
return (...args) => {
|
||||
|
@ -135,6 +193,11 @@ export function throttle(func, limit = 300) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element is in the viewport.
|
||||
* @param {any[]} el The element to check.
|
||||
* @returns {boolean} True if the element is in the viewport, false otherwise.
|
||||
*/
|
||||
export function isElementInViewport(el) {
|
||||
if (typeof jQuery === "function" && el instanceof jQuery) {
|
||||
el = el[0];
|
||||
|
@ -148,6 +211,12 @@ export function isElementInViewport(el) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a name that is unique among the names that exist.
|
||||
* @param {string} name The name to check.
|
||||
* @param {{ (y: any): boolean; }} exists Function to check if name exists.
|
||||
* @returns {string} A unique name.
|
||||
*/
|
||||
export function getUniqueName(name, exists) {
|
||||
let i = 1;
|
||||
let baseName = name;
|
||||
|
@ -158,18 +227,48 @@ export function getUniqueName(name, exists) {
|
|||
return name;
|
||||
}
|
||||
|
||||
export const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
||||
export const isSubsetOf = (a, b) => (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false;
|
||||
/**
|
||||
* Returns a promise that resolves after the specified number of milliseconds.
|
||||
* @param {number} ms The number of milliseconds to wait.
|
||||
* @returns {Promise<void>} A promise that resolves after the specified number of milliseconds.
|
||||
*/
|
||||
export function delay(ms) {
|
||||
return new Promise((res) => setTimeout(res, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an array is a subset of another array.
|
||||
* @param {any[]} a Array A
|
||||
* @param {any[]} b Array B
|
||||
* @returns {boolean} True if B is a subset of A, false otherwise.
|
||||
*/
|
||||
export function isSubsetOf(a, b) {
|
||||
return (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the trailing number in a string.
|
||||
* @param {string} str The string to process.
|
||||
* @returns {string} The string with the trailing number incremented by 1.
|
||||
* @example
|
||||
* incrementString('Hello, world! 1'); // 'Hello, world! 2'
|
||||
*/
|
||||
export function incrementString(str) {
|
||||
// Find the trailing number or it will match the empty string
|
||||
const count = str.match(/\d*$/);
|
||||
|
||||
// Take the substring up until where the integer was matched
|
||||
// Concatenate it to the matched count incremented by 1
|
||||
return str.substr(0, count.index) + (++count[0]);
|
||||
return str.substring(0, count.index) + (Number(count[0]) + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a string using the specified arguments.
|
||||
* @param {string} format The format string.
|
||||
* @returns {string} The formatted string.
|
||||
* @example
|
||||
* stringFormat('Hello, {0}!', 'world'); // 'Hello, world!'
|
||||
*/
|
||||
export function stringFormat(format) {
|
||||
const args = Array.prototype.slice.call(arguments, 1);
|
||||
return format.replace(/{(\d+)}/g, function (match, number) {
|
||||
|
@ -180,7 +279,11 @@ export function stringFormat(format) {
|
|||
});
|
||||
};
|
||||
|
||||
// Save the caret position in a contenteditable element
|
||||
/**
|
||||
* Save the caret position in a contenteditable element.
|
||||
* @param {Element} element The element to save the caret position of.
|
||||
* @returns {{ start: number, end: number }} An object with the start and end offsets of the caret.
|
||||
*/
|
||||
export function saveCaretPosition(element) {
|
||||
// Get the current selection
|
||||
const selection = window.getSelection();
|
||||
|
@ -209,7 +312,11 @@ export function saveCaretPosition(element) {
|
|||
return position;
|
||||
}
|
||||
|
||||
// Restore the caret position in a contenteditable element
|
||||
/**
|
||||
* Restore the caret position in a contenteditable element.
|
||||
* @param {Element} element The element to restore the caret position of.
|
||||
* @param {{ start: any; end: any; }} position An object with the start and end offsets of the caret.
|
||||
*/
|
||||
export function restoreCaretPosition(element, position) {
|
||||
// If the position is null, do nothing
|
||||
if (!position) {
|
||||
|
@ -236,6 +343,11 @@ export async function resetScrollHeight(element) {
|
|||
$(element).css('height', $(element).prop('scrollHeight') + 3 + 'px');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of an element to its scroll height.
|
||||
* @param {JQuery<HTMLElement>} element The element to initialize the scroll height of.
|
||||
* @returns {Promise<void>} A promise that resolves when the scroll height has been initialized.
|
||||
*/
|
||||
export async function initScrollHeight(element) {
|
||||
await delay(1);
|
||||
|
||||
|
@ -252,15 +364,27 @@ export async function initScrollHeight(element) {
|
|||
//resetScrollHeight(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares elements by their CSS order property. Used for sorting.
|
||||
* @param {any} a The first element.
|
||||
* @param {any} b The second element.
|
||||
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
|
||||
*/
|
||||
export function sortByCssOrder(a, b) {
|
||||
const _a = Number($(a).css('order'));
|
||||
const _b = Number($(b).css('order'));
|
||||
return _a - _b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims a string to the end of a nearest sentence.
|
||||
* @param {string} input The string to trim.
|
||||
* @param {boolean} include_newline Whether to include a newline character in the trimmed string.
|
||||
* @returns {string} The trimmed string.
|
||||
* @example
|
||||
* end_trim_to_sentence('Hello, world! I am from'); // 'Hello, world!'
|
||||
*/
|
||||
export function end_trim_to_sentence(input, include_newline = false) {
|
||||
// inspired from https://github.com/kaihordewebui/kaihordewebui.github.io/blob/06b95e6b7720eb85177fbaf1a7f52955d7cdbc02/index.html#L4853-L4867
|
||||
|
||||
const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$', '。', '!', '?', '”', ')', '】', '】', '’', '」', '】']); // extend this as you see fit
|
||||
let last = -1;
|
||||
|
||||
|
@ -285,6 +409,15 @@ export function end_trim_to_sentence(input, include_newline = false) {
|
|||
return input.substring(0, last + 1).trimEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of occurrences of a character in a string.
|
||||
* @param {string} string The string to count occurrences in.
|
||||
* @param {string} character The character to count occurrences of.
|
||||
* @returns {number} The number of occurrences of the character in the string.
|
||||
* @example
|
||||
* countOccurrences('Hello, world!', 'l'); // 3
|
||||
* countOccurrences('Hello, world!', 'x'); // 0
|
||||
*/
|
||||
export function countOccurrences(string, character) {
|
||||
let count = 0;
|
||||
|
||||
|
@ -297,6 +430,14 @@ export function countOccurrences(string, character) {
|
|||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a number is odd.
|
||||
* @param {number} number The number to check.
|
||||
* @returns {boolean} True if the number is odd, false otherwise.
|
||||
* @example
|
||||
* isOdd(3); // true
|
||||
* isOdd(4); // false
|
||||
*/
|
||||
export function isOdd(number) {
|
||||
return number % 2 !== 0;
|
||||
}
|
||||
|
@ -337,6 +478,12 @@ export function timestampToMoment(timestamp) {
|
|||
return moment.invalid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two moment objects for sorting.
|
||||
* @param {*} a The first moment object.
|
||||
* @param {*} b The second moment object.
|
||||
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
|
||||
*/
|
||||
export function sortMoments(a, b) {
|
||||
if (a.isBefore(b)) {
|
||||
return 1;
|
||||
|
@ -347,14 +494,21 @@ export function sortMoments(a, b) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Split string to parts no more than length in size */
|
||||
export function splitRecursive(input, length, delimitiers = ['\n\n', '\n', ' ', '']) {
|
||||
const delim = delimitiers[0] ?? '';
|
||||
/** Split string to parts no more than length in size.
|
||||
* @param {string} input The string to split.
|
||||
* @param {number} length The maximum length of each part.
|
||||
* @param {string[]} delimiters The delimiters to use when splitting the string.
|
||||
* @returns {string[]} The split string.
|
||||
* @example
|
||||
* splitRecursive('Hello, world!', 3); // ['Hel', 'lo,', 'wor', 'ld!']
|
||||
*/
|
||||
export function splitRecursive(input, length, delimiters = ['\n\n', '\n', ' ', '']) {
|
||||
const delim = delimiters[0] ?? '';
|
||||
const parts = input.split(delim);
|
||||
|
||||
const flatParts = parts.flatMap(p => {
|
||||
if (p.length < length) return p;
|
||||
return splitRecursive(input, length, delimitiers.slice(1));
|
||||
return splitRecursive(input, length, delimiters.slice(1));
|
||||
});
|
||||
|
||||
// Merge short chunks
|
||||
|
@ -378,6 +532,13 @@ export function splitRecursive(input, length, delimitiers = ['\n\n', '\n', ' ',
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid data URL.
|
||||
* @param {string} str The string to check.
|
||||
* @returns {boolean} True if the string is a valid data URL, false otherwise.
|
||||
* @example
|
||||
* isDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...'); // true
|
||||
*/
|
||||
export function isDataURL(str) {
|
||||
const regex = /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)*;?)?(base64)?,([a-z0-9!$&',()*+;=\-_%.~:@\/?#]+)?$/i;
|
||||
return regex.test(str);
|
||||
|
@ -392,6 +553,13 @@ export function getCharaFilename(chid) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts words from a string.
|
||||
* @param {string} value The string to extract words from.
|
||||
* @returns {string[]} The extracted words.
|
||||
* @example
|
||||
* extractAllWords('Hello, world!'); // ['hello', 'world']
|
||||
*/
|
||||
export function extractAllWords(value) {
|
||||
const words = [];
|
||||
|
||||
|
@ -406,21 +574,45 @@ export function extractAllWords(value) {
|
|||
return words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string for use in a regular expression.
|
||||
* @param {string} string The string to escape.
|
||||
* @returns {string} The escaped string.
|
||||
* @example
|
||||
* escapeRegex('^Hello$'); // '\\^Hello\\$'
|
||||
*/
|
||||
export function escapeRegex(string) {
|
||||
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an interface for rate limiting function calls.
|
||||
*/
|
||||
export class RateLimiter {
|
||||
constructor(intervalMillis) {
|
||||
this._intervalMillis = intervalMillis;
|
||||
this._lastResolveTime = 0;
|
||||
this._pendingResolve = Promise.resolve();
|
||||
/**
|
||||
* Creates a new RateLimiter.
|
||||
* @param {number} interval The interval in milliseconds.
|
||||
* @example
|
||||
* const rateLimiter = new RateLimiter(1000);
|
||||
* rateLimiter.waitForResolve().then(() => {
|
||||
* console.log('Waited 1000ms');
|
||||
* });
|
||||
*/
|
||||
constructor(interval) {
|
||||
this.interval = interval;
|
||||
this.lastResolveTime = 0;
|
||||
this.pendingResolve = Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the remaining time in the interval.
|
||||
* @param {AbortSignal} abortSignal An optional AbortSignal to abort the wait.
|
||||
* @returns {Promise<void>} A promise that resolves when the remaining time has elapsed.
|
||||
*/
|
||||
_waitRemainingTime(abortSignal) {
|
||||
const currentTime = Date.now();
|
||||
const elapsedTime = currentTime - this._lastResolveTime;
|
||||
const remainingTime = Math.max(0, this._intervalMillis - elapsedTime);
|
||||
const elapsedTime = currentTime - this.lastResolveTime;
|
||||
const remainingTime = Math.max(0, this.interval - elapsedTime);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
|
@ -436,19 +628,29 @@ export class RateLimiter {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the next interval to elapse.
|
||||
* @param {AbortSignal} abortSignal An optional AbortSignal to abort the wait.
|
||||
* @returns {Promise<void>} A promise that resolves when the next interval has elapsed.
|
||||
*/
|
||||
async waitForResolve(abortSignal) {
|
||||
await this._pendingResolve;
|
||||
this._pendingResolve = this._waitRemainingTime(abortSignal);
|
||||
await this.pendingResolve;
|
||||
this.pendingResolve = this._waitRemainingTime(abortSignal);
|
||||
|
||||
// Update the last resolve time
|
||||
this._lastResolveTime = Date.now() + this._intervalMillis;
|
||||
console.debug(`RateLimiter.waitForResolve() ${this._lastResolveTime}`);
|
||||
this.lastResolveTime = Date.now() + this.interval;
|
||||
console.debug(`RateLimiter.waitForResolve() ${this.lastResolveTime}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from https://github.com/LostRuins/lite.koboldai.net/blob/main/index.html
|
||||
//import tavern png data. adapted from png-chunks-extract under MIT license
|
||||
//accepts png input data, and returns the extracted JSON
|
||||
/**
|
||||
* Extracts a JSON object from a PNG file.
|
||||
* Taken from https://github.com/LostRuins/lite.koboldai.net/blob/main/index.html
|
||||
* Adapted from png-chunks-extract under MIT license
|
||||
* @param {Uint8Array} data The PNG data to extract the JSON from.
|
||||
* @param {string} identifier The identifier to look for in the PNG tEXT data.
|
||||
* @returns {object} The extracted JSON object.
|
||||
*/
|
||||
export function extractDataFromPng(data, identifier = 'chara') {
|
||||
console.log("Attempting PNG import...");
|
||||
let uint8 = new Uint8Array(4);
|
||||
|
@ -599,6 +801,13 @@ export async function saveBase64AsFile(base64Data, characterName, filename = "",
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thumbnail from a data URL.
|
||||
* @param {string} dataUrl The data URL encoded data of the image.
|
||||
* @param {number} maxWidth The maximum width of the thumbnail.
|
||||
* @param {number} maxHeight The maximum height of the thumbnail.
|
||||
* @returns {Promise<string>} A promise that resolves to the thumbnail data URL.
|
||||
*/
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
@ -634,6 +843,13 @@ export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a condition to be true. Throws an error if the condition is not true within the timeout.
|
||||
* @param {{ (): boolean; }} condition The condition to wait for.
|
||||
* @param {number} [timeout=1000] The timeout in milliseconds.
|
||||
* @param {number} [interval=100] The interval in milliseconds.
|
||||
* @returns {Promise<void>} A promise that resolves when the condition is true.
|
||||
*/
|
||||
export async function waitUntilCondition(condition, timeout = 1000, interval = 100) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
|
@ -651,6 +867,12 @@ export async function waitUntilCondition(condition, timeout = 1000, interval = 1
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UUID v4 string.
|
||||
* @returns {string} A UUID v4 string.
|
||||
* @example
|
||||
* uuidv4(); // '3e2fd9e1-0a7a-4f6d-9aaf-8a7a4babe7eb'
|
||||
*/
|
||||
export function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
|
@ -659,6 +881,11 @@ export function uuidv4() {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones an object using JSON serialization.
|
||||
* @param {any} obj The object to clone.
|
||||
* @returns {any} A deep clone of the object.
|
||||
*/
|
||||
export function deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
|
|
@ -418,7 +418,7 @@ function getWorldEntry(name, data, entry) {
|
|||
|
||||
keyInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
const value = String($(this).val());
|
||||
resetScrollHeight(this);
|
||||
data.entries[uid].key = value
|
||||
.split(",")
|
||||
|
@ -454,7 +454,7 @@ function getWorldEntry(name, data, entry) {
|
|||
keySecondaryInput.data("uid", entry.uid);
|
||||
keySecondaryInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
const value = String($(this).val());
|
||||
resetScrollHeight(this);
|
||||
data.entries[uid].keysecondary = value
|
||||
.split(",")
|
||||
|
@ -1506,19 +1506,6 @@ jQuery(() => {
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
let selectScrollTop = null;
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
selectScrollTop = selectElement.scrollTop;
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = selectScrollTop;
|
||||
}
|
||||
*/
|
||||
|
||||
onWorldInfoChange('__notSlashCommand__');
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue