mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' of github.com:Cohee1207/SillyTavern into dev
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder } from "./utils.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
var RPanelPin = document.getElementById("rm_button_panel_pin");
|
||||
@@ -275,7 +276,7 @@ export async function favsToHotswap() {
|
||||
const maxCount = 6;
|
||||
let count = 0;
|
||||
|
||||
$(selector).each(function () {
|
||||
$(selector).sort(sortByCssOrder).each(function () {
|
||||
if ($(this).hasClass('is_fav') && count < maxCount) {
|
||||
const isCharacter = $(this).hasClass('character_select');
|
||||
const isGroup = $(this).hasClass('group_select');
|
||||
|
@@ -95,8 +95,9 @@ async function activateExtensions() {
|
||||
for (let entry of extensions) {
|
||||
const name = entry[0];
|
||||
const manifest = entry[1];
|
||||
const elementExists = document.getElementById(name) !== null;
|
||||
|
||||
if (activeExtensions.has(name)) {
|
||||
if (elementExists || activeExtensions.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -96,6 +96,7 @@ $(document).ready(function () {
|
||||
function addSendPictureButton() {
|
||||
const sendButton = document.createElement('div');
|
||||
sendButton.id = 'send_picture';
|
||||
sendButton.title = 'Send a picture to chat';
|
||||
sendButton.classList.add('fa-solid');
|
||||
$(sendButton).hide();
|
||||
$(sendButton).on('click', () => $('#img_file').click());
|
||||
|
@@ -29,7 +29,7 @@ async function doDiceRoll() {
|
||||
|
||||
function addDiceRollButton() {
|
||||
const buttonHtml = `
|
||||
<div id="roll_dice" class="fa-solid fa-dice" /></div>
|
||||
<div id="roll_dice" class="fa-solid fa-dice" title="Roll the dice" /></div>
|
||||
`;
|
||||
const dropdownHtml = `
|
||||
<div id="dice_dropdown">
|
||||
|
@@ -34,39 +34,40 @@ const generationMode = {
|
||||
}
|
||||
|
||||
const triggerWords = {
|
||||
[generationMode.CHARACTER]: ['yourself', 'you', 'bot', 'AI', 'character'],
|
||||
[generationMode.USER]: ['me', 'user', 'myself'],
|
||||
[generationMode.SCENARIO]: ['scenario', 'world', 'surroundings', 'scenery'],
|
||||
[generationMode.NOW]: ['now', 'last'],
|
||||
[generationMode.FACE]: ['selfie', 'face'],
|
||||
[generationMode.CHARACTER]: ['you'],
|
||||
[generationMode.USER]: ['me'],
|
||||
[generationMode.SCENARIO]: ['scene'],
|
||||
[generationMode.NOW]: ['last'],
|
||||
[generationMode.FACE]: ['face'],
|
||||
|
||||
}
|
||||
|
||||
const quietPrompts = {
|
||||
//face-specific prompt
|
||||
[generationMode.FACE]: "[In the next reponse I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: species and race, gender, age, facial features and expresisons, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait:']",
|
||||
[generationMode.FACE]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait:']",
|
||||
//prompt for only the last message
|
||||
[generationMode.NOW]: "[Pause your roleplay and provide a brief description of the last chat message. Focus on visual details, clothing, actions. Ignore the emotions and thoughts of {{char}} and {{user}} as well as any spoken dialog. Do not roleplay as {{char}} while writing this description. Do not continue the roleplay story.]",
|
||||
|
||||
[generationMode.CHARACTER]: "[In the next reponse I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait:']",
|
||||
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait:']",
|
||||
|
||||
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
|
||||
|
||||
[generationMode.USER]: "[Pause your roleplay and provide a detailed description of {{user}}'s appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}}}} when writing this description, and do not attempt to continue the story.]",
|
||||
[generationMode.SCENARIO]: "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",
|
||||
[generationMode.FREE]: "[Pause your roleplay and provide ONLY echo this string back to me verbatim: {0}. Do not write anything after the string. Do not roleplay at all in your response.]",
|
||||
[generationMode.FREE]: "[Pause your roleplay and provide ONLY an echo this string back to me verbatim: {0}. Do not write anything after the string. Do not roleplay at all in your response.]",
|
||||
}
|
||||
|
||||
const helpString = [
|
||||
`${m('what')} – requests an SD generation. Supported "what" arguments:`,
|
||||
`${m('(argument)')} – requests SD to make an image. Supported arguments:`,
|
||||
'<ul>',
|
||||
`<li>${m(j(triggerWords[generationMode.CHARACTER]))} – AI character image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.USER]))} – user character image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.SCENARIO]))} – world scenario image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.FACE]))} – character face-up selfie image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.CHARACTER]))} – AI character full body selfie</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.FACE]))} – AI character face-only selfie</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.USER]))} – user character full body selfie</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.SCENARIO]))} – visual recap of the whole chat scenario</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.NOW]))} – visual recap of the last chat message</li>`,
|
||||
'</ul>',
|
||||
`Anything else would trigger a "free mode" with AI describing whatever you prompted.`,
|
||||
`Anything else would trigger a "free mode" to make SD generate whatever you prompted.<Br>
|
||||
example: '/sd apple tree' would generate a picture of an apple tree.`,
|
||||
].join('<br>');
|
||||
|
||||
const defaultSettings = {
|
||||
@@ -236,9 +237,17 @@ function getQuietPrompt(mode, trigger) {
|
||||
function processReply(str) {
|
||||
str = str.replaceAll('"', '')
|
||||
str = str.replaceAll('“', '')
|
||||
str = str.replaceAll('\n', ' ')
|
||||
str = str.replaceAll('\n', ', ')
|
||||
str = str.replace(/[^a-zA-Z0-9,:]+/g, ' ') // Replace everything except alphanumeric characters and commas with spaces
|
||||
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
|
||||
str = str.trim();
|
||||
|
||||
str = str
|
||||
.split(',') // list split by commas
|
||||
.map(x => x.trim()) // trim each entry
|
||||
.filter(x => x) // remove empty entries
|
||||
.join(', '); // join it back with proper spacing
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -258,7 +267,7 @@ async function generatePicture(_, trigger) {
|
||||
const prompt = processReply(await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await context.generate('quiet', { resolve, reject, quiet_prompt });
|
||||
await context.generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
@@ -268,6 +277,8 @@ async function generatePicture(_, trigger) {
|
||||
context.deactivateSendButtons();
|
||||
hideSwipeButtons();
|
||||
|
||||
console.log('Processed Stable Diffusion prompt:', prompt);
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
const result = await fetch(url, {
|
||||
@@ -294,7 +305,7 @@ async function generatePicture(_, trigger) {
|
||||
sendMessage(prompt, base64Image);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.trace(err);
|
||||
throw new Error('SD prompt text generation failed.')
|
||||
}
|
||||
finally {
|
||||
@@ -325,7 +336,7 @@ async function sendMessage(prompt, image) {
|
||||
|
||||
function addSDGenButtons() {
|
||||
const buttonHtml = `
|
||||
<div id="sd_gen" class="fa-solid fa-paintbrush" /></div>
|
||||
<div id="sd_gen" class="fa-solid fa-paintbrush" title="Trigger Stable Diffusion" /></div>
|
||||
`;
|
||||
|
||||
const waitButtonHtml = `
|
||||
@@ -411,8 +422,8 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
}
|
||||
|
||||
else if (id == "sd_world") {
|
||||
console.log("doing /sd world");
|
||||
generatePicture('sd', 'world');
|
||||
console.log("doing /sd scene");
|
||||
generatePicture('sd', 'scene');
|
||||
}
|
||||
|
||||
else if (id == "sd_last") {
|
||||
@@ -422,7 +433,7 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
});
|
||||
|
||||
jQuery(async () => {
|
||||
getContext().registerSlashCommand('sd', generatePicture, ['picture', 'image'], helpString, true, true);
|
||||
getContext().registerSlashCommand('sd', generatePicture, [], helpString, true, true);
|
||||
|
||||
const settingsHtml = `
|
||||
<div class="sd_settings">
|
||||
|
@@ -7,6 +7,7 @@ class ElevenLabsTtsProvider {
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' ... ... ... '
|
||||
|
||||
get settings() {
|
||||
return this.settings
|
||||
|
@@ -48,10 +48,8 @@ async function moduleWorker() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat/character/group changed
|
||||
// Chat changed
|
||||
if (
|
||||
(context.groupId && lastGroupId !== context.groupId) ||
|
||||
context.characterId !== lastCharacterId ||
|
||||
context.chatId !== lastChatId
|
||||
) {
|
||||
currentMessageNumber = context.chat.length ? context.chat.length : 0
|
||||
@@ -75,6 +73,7 @@ async function moduleWorker() {
|
||||
// We're currently swiping or streaming. Don't generate voice
|
||||
if (
|
||||
message.mes === '...' ||
|
||||
message.mes === '' ||
|
||||
(context.streamingProcessor && !context.streamingProcessor.isFinished)
|
||||
) {
|
||||
return
|
||||
@@ -164,7 +163,7 @@ function onAudioControlClicked() {
|
||||
|
||||
function addAudioControl() {
|
||||
$('#send_but_sheld').prepend('<div id="tts_media_control"/>')
|
||||
$('#send_but_sheld').on('click', onAudioControlClicked)
|
||||
$('#tts_media_control').attr('title', 'TTS play/pause').on('click', onAudioControlClicked)
|
||||
audioControl = document.getElementById('tts_media_control')
|
||||
updateUiAudioPlayState()
|
||||
}
|
||||
@@ -181,7 +180,7 @@ function completeCurrentAudioJob() {
|
||||
*/
|
||||
async function addAudioJob(response) {
|
||||
const audioData = await response.blob()
|
||||
if (!audioData.type in ['audio/mpeg', 'audio/wav']) {
|
||||
if (!audioData.type in ['audio/mpeg', 'audio/wav', 'audio/x-wav', 'audio/wave']) {
|
||||
throw `TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${audioData.type}`
|
||||
}
|
||||
audioJobQueue.push(audioData)
|
||||
@@ -240,12 +239,26 @@ async function processTtsQueue() {
|
||||
|
||||
console.debug('New message found, running TTS')
|
||||
currentTtsJob = ttsJobQueue.shift()
|
||||
const text = extension_settings.tts.narrate_dialogues_only
|
||||
? currentTtsJob.mes.replace(/\*[^\*]*?(\*|$)/g, '') // remove asterisks content
|
||||
: currentTtsJob.mes.replaceAll('*', '') // remove just the asterisks
|
||||
let text = extension_settings.tts.narrate_dialogues_only
|
||||
? currentTtsJob.mes.replace(/\*[^\*]*?(\*|$)/g, '').trim() // remove asterisks content
|
||||
: currentTtsJob.mes.replaceAll('*', '').trim() // remove just the asterisks
|
||||
|
||||
if (extension_settings.tts.narrate_quoted_only) {
|
||||
const special_quotes = /[“”]/g; // Extend this regex to include other special quotes
|
||||
text = text.replace(special_quotes, '"');
|
||||
const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily
|
||||
const partJoiner = (ttsProvider?.separator || ' ... ');
|
||||
text = matches ? matches.join(partJoiner) : text;
|
||||
}
|
||||
console.log(`TTS: ${text}`)
|
||||
const char = currentTtsJob.name
|
||||
|
||||
try {
|
||||
if (!text) {
|
||||
console.warn('Got empty text in TTS queue job.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!voiceMap[char]) {
|
||||
throw `${char} not in voicemap. Configure character in extension settings voice map`
|
||||
}
|
||||
@@ -282,6 +295,7 @@ function loadSettings() {
|
||||
extension_settings.tts.enabled
|
||||
)
|
||||
$('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only)
|
||||
$('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only)
|
||||
}
|
||||
|
||||
const defaultSettings = {
|
||||
@@ -374,6 +388,13 @@ function onNarrateDialoguesClick() {
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
|
||||
function onNarrateQuotedClick() {
|
||||
extension_settings.tts.narrate_quoted_only = $('#tts_narrate_quoted').prop('checked');
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
|
||||
//##############//
|
||||
// TTS Provider //
|
||||
//##############//
|
||||
@@ -453,6 +474,10 @@ $(document).ready(function () {
|
||||
<input type="checkbox" id="tts_narrate_dialogues">
|
||||
Narrate dialogues only
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_quoted">
|
||||
<input type="checkbox" id="tts_narrate_quoted">
|
||||
Narrate quoted only
|
||||
</label>
|
||||
</div>
|
||||
<label>Voice Map</label>
|
||||
<textarea id="tts_voice_map" type="text" class="text_pole textarea_compact" rows="4"
|
||||
@@ -475,6 +500,7 @@ $(document).ready(function () {
|
||||
$('#tts_apply').on('click', onApplyClick)
|
||||
$('#tts_enabled').on('click', onEnableClick)
|
||||
$('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick);
|
||||
$('#tts_narrate_quoted').on('click', onNarrateQuotedClick);
|
||||
$('#tts_voices').on('click', onTtsVoicesClick)
|
||||
$('#tts_provider_settings').on('input', onTtsProviderSettingsInput)
|
||||
for (const provider in ttsProviders) {
|
||||
|
@@ -9,6 +9,7 @@ class SileroTtsProvider {
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' .. '
|
||||
|
||||
defaultSettings = {
|
||||
provider_endpoint: "http://localhost:8001/tts",
|
||||
|
@@ -21,6 +21,7 @@ class SystemTtsProvider {
|
||||
fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'
|
||||
settings
|
||||
voices = []
|
||||
separator = ' ... '
|
||||
|
||||
defaultSettings = {
|
||||
voiceMap: {},
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { saveSettingsDebounced, changeMainAPI, callPopup, setGenerationProgress, CLIENT_VERSION, getRequestHeaders } from "../script.js";
|
||||
import { SECRET_KEYS, writeSecret } from "./secrets.js";
|
||||
import { delay } from "./utils.js";
|
||||
|
||||
export {
|
||||
@@ -217,5 +218,10 @@ jQuery(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#horde_api_key").on("input", async function () {
|
||||
const key = $(this).val().trim();
|
||||
await writeSecret(SECRET_KEYS.HORDE, key);
|
||||
});
|
||||
|
||||
$("#horde_refresh").on("click", getHordeModels);
|
||||
})
|
@@ -9,6 +9,7 @@ import {
|
||||
getRequestHeaders,
|
||||
substituteParams,
|
||||
} from "../script.js";
|
||||
import { favsToHotswap } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
selected_group,
|
||||
@@ -632,10 +633,10 @@ function loadInstructMode() {
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser) {
|
||||
const includeNames = power_user.instruct.names || (selected_group && !isUser);
|
||||
const includeNames = power_user.instruct.names || !!selected_group;
|
||||
const sequence = isUser ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const textArray = includeNames ? [sequence, name, ': ', mes, separator] : [sequence, mes, separator];
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separator] : [sequence, mes, separator];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
@@ -649,10 +650,11 @@ export function formatInstructStoryString(story) {
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructModePrompt(isImpersonate) {
|
||||
export function formatInstructModePrompt(name, isImpersonate) {
|
||||
const includeNames = power_user.instruct.names || !!selected_group;
|
||||
const sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const text = separator + sequence;
|
||||
const text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -995,6 +997,7 @@ $(document).ready(() => {
|
||||
power_user.sort_order = $(this).find(":selected").data('order');
|
||||
power_user.sort_rule = $(this).find(":selected").data('rule');
|
||||
sortCharactersList();
|
||||
favsToHotswap();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { getRequestHeaders } from "../script.js";
|
||||
import { callPopup, getRequestHeaders } from "../script.js";
|
||||
|
||||
export const SECRET_KEYS = {
|
||||
HORDE: 'api_key_horde',
|
||||
@@ -14,14 +14,50 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.NOVEL]: '#api_key_novel',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
const key = $(this).data('key');
|
||||
await writeSecret(key, '');
|
||||
secret_state[key] = false;
|
||||
updateSecretDisplay();
|
||||
$(INPUT_MAP[key]).val('');
|
||||
}
|
||||
|
||||
function updateSecretDisplay() {
|
||||
for (const [secret_key, input_selector] of Object.entries(INPUT_MAP)) {
|
||||
const validSecret = !!secret_state[secret_key];
|
||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
||||
$(input_selector).attr('placeholder', placeholder).val('');
|
||||
$(input_selector).attr('placeholder', placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
async function viewSecrets() {
|
||||
const response = await fetch('/viewsecrets', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (response.status == 403) {
|
||||
callPopup('<h3>Forbidden</h3><p>To view your API keys here, set the value of allowKeysExposure to true in config.conf file and restart the SillyTavern server.</p>', 'text');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#dialogue_popup').addClass('wide_dialogue_popup');
|
||||
const data = await response.json();
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('responsiveTable');
|
||||
$(table).append('<thead><th>Key</th><th>Value</th></thead>');
|
||||
|
||||
for (const [key,value] of Object.entries(data)) {
|
||||
$(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`);
|
||||
}
|
||||
|
||||
callPopup(table.outerHTML, 'text');
|
||||
}
|
||||
|
||||
export let secret_state = {};
|
||||
|
||||
export async function writeSecret(key, value) {
|
||||
@@ -59,4 +95,9 @@ export async function readSecretState() {
|
||||
} catch {
|
||||
console.error('Could not read secrets file');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$('#viewSecrets').on('click', viewSecrets);
|
||||
$(document).on('click', '.clear-api-key', clearSecret);
|
||||
});
|
@@ -73,8 +73,8 @@ const parser = new SlashCommandParser();
|
||||
const registerSlashCommand = parser.addCommand.bind(parser);
|
||||
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
||||
|
||||
parser.addCommand('help', helpCommandCallback, ['?'], ' – displays a help information', true, true);
|
||||
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">name</span> – sets a background by file name', false, true);
|
||||
parser.addCommand('help', helpCommandCallback, ['?'], ' – displays this help message', true, true);
|
||||
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed, will set the first one alphebetically if multiple files begin with the provided argument string', false, true);
|
||||
|
||||
function helpCommandCallback() {
|
||||
sendSystemMessage(system_message_types.HELP);
|
||||
@@ -86,7 +86,7 @@ function setBackgroundCallback(_, bg) {
|
||||
}
|
||||
console.log('Set background to ' + bg);
|
||||
const bgElement = $(`.bg_example[bgfile^="${bg.trim()}"`);
|
||||
|
||||
|
||||
if (bgElement.length) {
|
||||
bgElement.get(0).click();
|
||||
}
|
||||
|
@@ -182,4 +182,10 @@ export async function initScrollHeight(element) {
|
||||
$(element).css("height", "");
|
||||
$(element).css("height", `${newHeight}px`);
|
||||
//resetScrollHeight(element);
|
||||
}
|
||||
|
||||
export function sortByCssOrder(a, b) {
|
||||
const _a = Number($(a).css('order'));
|
||||
const _b = Number($(b).css('order'));
|
||||
return _a - _b;
|
||||
}
|
Reference in New Issue
Block a user