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:
ceruleandeep 2024-10-05 17:26:28 +10:00
parent 23639ce1fe
commit 02b0000117
3 changed files with 200 additions and 121 deletions

View File

@ -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);

View File

@ -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>

View File

@ -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);
}