mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-02 02:47:52 +01:00
Add clickable buttons in Welcome chat message.
Add bool `uses_system_ui` on system messages to override sanitizer for buttons when set Modify uponSanitizeAttribute DOMPurify hook to allow unmangled class names on attributes in some cases Add event listener for .drawer-opener to open a navbar drawer
This commit is contained in:
parent
23639ce1fe
commit
02b0000117
192
public/script.js
192
public/script.js
@ -293,10 +293,17 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) {
|
||||
}
|
||||
});
|
||||
|
||||
DOMPurify.addHook('uponSanitizeAttribute', (_, data, config) => {
|
||||
DOMPurify.addHook('uponSanitizeAttribute', (node, data, config) => {
|
||||
if (!config['MESSAGE_SANITIZE']) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Retain the classes on UI elements of messages that interact with the main UI */
|
||||
const permittedNodeTypes = ['BUTTON', 'DIV'];
|
||||
if (config['MESSAGE_ALLOW_SYSTEM_UI'] && node.classList.contains('menu_button') && permittedNodeTypes.includes(node.nodeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.attrName) {
|
||||
case 'class': {
|
||||
if (data.attrValue) {
|
||||
@ -650,7 +657,8 @@ async function getSystemMessages() {
|
||||
force_avatar: system_avatar,
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
mes: await renderTemplateAsync('welcome', { displayVersion }),
|
||||
uses_system_ui: true,
|
||||
mes: await renderTemplateAsync('welcome', { displayVersion } ),
|
||||
},
|
||||
group: {
|
||||
name: systemUserName,
|
||||
@ -1916,9 +1924,10 @@ export async function sendTextareaMessage() {
|
||||
* @param {boolean} isSystem If the message was sent by the system
|
||||
* @param {boolean} isUser If the message was sent by the user
|
||||
* @param {number} messageId Message index in chat array
|
||||
* @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
||||
export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}) {
|
||||
if (!mes) {
|
||||
return '';
|
||||
}
|
||||
@ -2029,7 +2038,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'] };
|
||||
const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'], ...sanitizerOverrides };
|
||||
mes = encodeStyleTags(mes);
|
||||
mes = DOMPurify.sanitize(mes, config);
|
||||
mes = decodeStyleTags(mes);
|
||||
@ -2234,6 +2243,18 @@ export function addCopyToCodeBlocks(messageElement) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a single message to the chat.
|
||||
* @param {object} mes Message object
|
||||
* @param {object} [options] Options
|
||||
* @param {string} [options.type='normal'] Message type
|
||||
* @param {number} [options.insertAfter=null] Message ID to insert the new message after
|
||||
* @param {boolean} [options.scroll=true] Whether to scroll to the new message
|
||||
* @param {number} [options.insertBefore=null] Message ID to insert the new message before
|
||||
* @param {number} [options.forceId=null] Force the message ID
|
||||
* @param {boolean} [options.showSwipes=true] Whether to show swipe buttons
|
||||
* @returns {void}
|
||||
*/
|
||||
export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true } = {}) {
|
||||
let messageText = mes['mes'];
|
||||
const momentDate = timestampToMoment(mes.send_date);
|
||||
@ -2262,7 +2283,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
||||
} else if (this_chid === undefined) {
|
||||
avatarImg = system_avatar;
|
||||
} else {
|
||||
if (characters[this_chid].avatar != 'none') {
|
||||
if (characters[this_chid].avatar !== 'none') {
|
||||
avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
} else {
|
||||
avatarImg = default_avatar;
|
||||
@ -2277,12 +2298,16 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
||||
avatarImg = mes['force_avatar'];
|
||||
}
|
||||
|
||||
// if mes.uses_system_ui is true, set an override on the sanitizer options
|
||||
const sanitizerOverrides = mes.uses_system_ui ? { MESSAGE_ALLOW_SYSTEM_UI: true } : {};
|
||||
|
||||
messageText = messageFormatting(
|
||||
messageText,
|
||||
mes.name,
|
||||
isSystem,
|
||||
mes.is_user,
|
||||
chat.indexOf(mes),
|
||||
sanitizerOverrides,
|
||||
);
|
||||
const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1);
|
||||
let bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||||
@ -2330,7 +2355,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
||||
}
|
||||
|
||||
//shows or hides the Prompt display button
|
||||
let mesIdToFind = type == 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
|
||||
let mesIdToFind = type === 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
|
||||
|
||||
//if we have itemized messages, and the array isn't null..
|
||||
if (params.isUser === false && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) {
|
||||
@ -2651,7 +2676,7 @@ export function sendSystemMessage(type, text, extra = {}) {
|
||||
newMessage.mes = text;
|
||||
}
|
||||
|
||||
if (type == system_message_types.SLASH_COMMANDS) {
|
||||
if (type === system_message_types.SLASH_COMMANDS) {
|
||||
newMessage.mes = getSlashCommandsHelp();
|
||||
}
|
||||
|
||||
@ -2665,7 +2690,7 @@ export function sendSystemMessage(type, text, extra = {}) {
|
||||
chat.push(newMessage);
|
||||
addOneMessage(newMessage);
|
||||
is_send_press = false;
|
||||
if (type == system_message_types.SLASH_COMMANDS) {
|
||||
if (type === system_message_types.SLASH_COMMANDS) {
|
||||
const browser = new SlashCommandBrowser();
|
||||
const spinner = document.querySelector('#chat .last_mes .custom-slashHelp');
|
||||
const parent = spinner.parentElement;
|
||||
@ -7854,7 +7879,7 @@ function openAlternateGreetings() {
|
||||
if (menu_type !== 'create') {
|
||||
await createOrEditCharacter();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
for (let index = 0; index < getArray().length; index++) {
|
||||
@ -9070,6 +9095,90 @@ function doTogglePanels() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to open a navbar drawer when a drawer open button is clicked.
|
||||
* Handles click events on .drawer-opener elements.
|
||||
* Opens the drawer associated with the clicked button according to the data-target attribute.
|
||||
* @returns {void}
|
||||
*/
|
||||
function doDrawerOpenClick() {
|
||||
const targetDrawerID = $(this).attr('data-target');
|
||||
const drawer = $(`#${targetDrawerID}`);
|
||||
const drawerToggle = drawer.find('.drawer-toggle');
|
||||
const drawerWasOpenAlready = drawerToggle.parent().find('.drawer-content').hasClass('openDrawer');
|
||||
if (drawerWasOpenAlready || drawer.hasClass('resizing') ) { return; }
|
||||
doNavbarIconClick.call(drawerToggle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to open or close a navbar drawer when a navbar icon is clicked.
|
||||
* Handles click events on .drawer-toggle elements.
|
||||
* @returns {void}
|
||||
*/
|
||||
function doNavbarIconClick() {
|
||||
var icon = $(this).find('.drawer-icon');
|
||||
var drawer = $(this).parent().find('.drawer-content');
|
||||
if (drawer.hasClass('resizing')) { return; }
|
||||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||||
|
||||
if (!drawerWasOpenAlready) { //to open the drawer
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
$('.openIcon').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
icon.toggleClass('openIcon closedIcon');
|
||||
drawer.toggleClass('openDrawer closedDrawer');
|
||||
|
||||
//console.log(targetDrawerID);
|
||||
if (targetDrawerID === 'right-nav-panel') {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||||
duration: 200,
|
||||
easing: 'swing',
|
||||
start: function () {
|
||||
jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll
|
||||
},
|
||||
complete: async function () {
|
||||
favsToHotswap();
|
||||
await delay(50);
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('#rm_print_characters_block').trigger('scroll');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {
|
||||
await resetScrollHeight($(this));
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
} else if (drawerWasOpenAlready) { //to close manually
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
||||
if (pinnedDrawerClicked) {
|
||||
$(drawer).addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).removeClass('resizing');
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
drawer.toggleClass('closedDrawer openDrawer');
|
||||
}
|
||||
}
|
||||
|
||||
function addDebugFunctions() {
|
||||
const doBackfill = async () => {
|
||||
for (const message of chat) {
|
||||
@ -10659,69 +10768,8 @@ jQuery(async function () {
|
||||
stopScriptExecution();
|
||||
});
|
||||
|
||||
$('.drawer-toggle').on('click', function () {
|
||||
var icon = $(this).find('.drawer-icon');
|
||||
var drawer = $(this).parent().find('.drawer-content');
|
||||
if (drawer.hasClass('resizing')) { return; }
|
||||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||||
|
||||
if (!drawerWasOpenAlready) { //to open the drawer
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
$('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
icon.toggleClass('openIcon closedIcon');
|
||||
drawer.toggleClass('openDrawer closedDrawer');
|
||||
|
||||
//console.log(targetDrawerID);
|
||||
if (targetDrawerID === 'right-nav-panel') {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||||
duration: 200,
|
||||
easing: 'swing',
|
||||
start: function () {
|
||||
jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll
|
||||
},
|
||||
complete: async function () {
|
||||
favsToHotswap();
|
||||
await delay(50);
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('#rm_print_characters_block').trigger('scroll');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {
|
||||
await resetScrollHeight($(this));
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
} else if (drawerWasOpenAlready) { //to close manually
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
||||
if (pinnedDrawerClicked) {
|
||||
$(drawer).addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).removeClass('resizing');
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
drawer.toggleClass('closedDrawer openDrawer');
|
||||
}
|
||||
});
|
||||
$(document).on('click', '.drawer-opener', doDrawerOpenClick);
|
||||
$('.drawer-toggle').on('click', doNavbarIconClick);
|
||||
|
||||
$('html').on('touchstart mousedown', function (e) {
|
||||
var clickTarget = $(e.target);
|
||||
|
@ -1,65 +1,95 @@
|
||||
<h3>
|
||||
<span id="version_display_welcome">{{displayVersion}}</span>
|
||||
<span id="version_display_welcome">{{displayVersion}}</span>
|
||||
</h3>
|
||||
<a href="https://docs.sillytavern.app/usage/update/" target="_blank" data-i18n="Want to update?">
|
||||
Want to update?
|
||||
Want to update?
|
||||
</a>
|
||||
<hr>
|
||||
<h3 data-i18n="How to start chatting?">How to start chatting?</h3>
|
||||
<ol>
|
||||
<li>
|
||||
<span data-i18n="Click _space">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Click _space">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character."> and pick a character.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Click _space">Click </span>
|
||||
<button class="menu_button menu_button_icon drawer-opener inline-flex" data-target="sys-settings-button">
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
<span data-i18n="[title]API Connections">API Connections</span>
|
||||
</button>
|
||||
<span data-i18n="and select a">and select a</span>
|
||||
<a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank">
|
||||
<span class="fa-solid fa-circle-question"></span>
|
||||
<span data-i18n="Chat API">Chat API</span></a>.
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Click _space">Click </span>
|
||||
<button class="menu_button menu_button_icon drawer-opener inline-flex" data-target="rightNavHolder">
|
||||
<i class="fa-solid fa-address-card"></i>
|
||||
<span data-i18n="[title]Character Management">Character Management</span>
|
||||
</button>
|
||||
<span data-i18n="and pick a character."> and pick a character.</span>
|
||||
</li>
|
||||
</ol>
|
||||
<div>
|
||||
<span data-i18n="You can browse a list of bundled characters in the">
|
||||
You can browse a list of bundled characters in the
|
||||
</span>
|
||||
<i data-i18n="Download Extensions & Assets">
|
||||
Download Extensions & Assets
|
||||
</i>
|
||||
<span data-i18n="menu within">
|
||||
menu within
|
||||
</span>
|
||||
<code><i class="fa-solid fa-cubes"></i></code>
|
||||
<span>.</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<span data-i18n="You can add more">You can add more</span>
|
||||
<button class="open_characters_library menu_button menu_button_icon inline-flex">
|
||||
<i class="fa-solid fa-image-portrait"></i>
|
||||
<span data-i18n="Sample characters">Sample characters</span>
|
||||
</button>
|
||||
<span data-i18n="or">or</span>
|
||||
<button class="external_import_button menu_button menu_button_icon inline-flex">
|
||||
<i class="fa-solid fa-cloud-arrow-down"></i>
|
||||
<span data-i18n="Import Characters">Import characters</span>
|
||||
</button>
|
||||
<span data-i18n="from other websites">from other websites.</span>
|
||||
|
||||
<span data-i18n="Go to the">Go to the</span>
|
||||
|
||||
<i data-i18n="Download Extensions & Assets">Download Extensions & Assets</i>
|
||||
<span data-i18n="menu within">menu within</span>
|
||||
|
||||
<button class="menu_button menu_button_icon drawer-opener inline-flex" data-target="extensions-settings-button">
|
||||
<i class="fa-solid fa-cubes"></i>
|
||||
<span data-i18n="[title]Extensions">Extensions</span>
|
||||
</button>
|
||||
|
||||
<span data-i18n="to install additional features.">to install additional features.</span>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<h3 data-i18n="Confused or lost?">Confused or lost?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="note-link-span"><a class="fa-solid fa-circle-question" target="_blank" href="https://docs.sillytavern.app/"></a></span> - <span data-i18n="click these icons!">click these icons!</span>
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Enter">Enter </span><code>/?</code><span data-i18n="in the chat bar"> in the chat bar</span>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.sillytavern.app/" data-i18n="SillyTavern Documentation Site">
|
||||
SillyTavern Documentation Site
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="note-link-span"><a class="fa-solid fa-circle-question" target="_blank"
|
||||
href="https://docs.sillytavern.app/"></a></span> - <span
|
||||
data-i18n="click these icons!">click these icons!</span>
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Enter">Enter </span><code>/?</code><span data-i18n="in the chat bar"> in the chat bar</span>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.sillytavern.app/" data-i18n="SillyTavern Documentation Site">
|
||||
SillyTavern Documentation Site
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<h3 data-i18n="Still have questions?">Still have questions?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" href="https://discord.gg/sillytavern" data-i18n="Join the SillyTavern Discord">
|
||||
Join the SillyTavern Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern/issues" data-i18n="Post a GitHub issue">
|
||||
Post a GitHub issue
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern#questions-or-suggestions" data-i18n="Contact the developers">
|
||||
Contact the developers
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://discord.gg/sillytavern" data-i18n="Join the SillyTavern Discord">
|
||||
Join the SillyTavern Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern/issues" data-i18n="Post a GitHub issue">
|
||||
Post a GitHub issue
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern#questions-or-suggestions"
|
||||
data-i18n="Contact the developers">
|
||||
Contact the developers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -1208,8 +1208,9 @@ textarea.autoSetHeight {
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
font-family: var(--mainFontFamily);
|
||||
select,
|
||||
button {
|
||||
font-family: var(--mainFontFamily), sans-serif;
|
||||
font-size: var(--mainFontSize);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user