Merge branch 'staging' into smol-tag-improvements

This commit is contained in:
Wolfsblvt
2024-06-02 21:07:46 +02:00
93 changed files with 4666 additions and 1911 deletions

View File

@ -464,33 +464,50 @@ export function encodeStyleTags(text) {
*/
export function decodeStyleTags(text) {
const styleDecodeRegex = /<custom-style>(.+?)<\/custom-style>/gms;
const mediaAllowed = isExternalMediaAllowed();
function sanitizeRule(rule) {
if (Array.isArray(rule.selectors)) {
for (let i = 0; i < rule.selectors.length; i++) {
const selector = rule.selectors[i];
if (selector) {
const selectors = (selector.split(' ') ?? []).map((v) => {
if (v.startsWith('.')) {
return '.custom-' + v.substring(1);
}
return v;
}).join(' ');
rule.selectors[i] = '.mes_text ' + selectors;
}
}
}
if (!mediaAllowed && Array.isArray(rule.declarations) && rule.declarations.length > 0) {
rule.declarations = rule.declarations.filter(declaration => !declaration.value.includes('://'));
}
}
function sanitizeRuleSet(ruleSet) {
if (Array.isArray(ruleSet.selectors) || Array.isArray(ruleSet.declarations)) {
sanitizeRule(ruleSet);
}
if (Array.isArray(ruleSet.rules)) {
ruleSet.rules = ruleSet.rules.filter(rule => rule.type !== 'import');
for (const mediaRule of ruleSet.rules) {
sanitizeRuleSet(mediaRule);
}
}
}
return text.replaceAll(styleDecodeRegex, (_, style) => {
try {
let styleCleaned = unescape(style).replaceAll(/<br\/>/g, '');
const ast = css.parse(styleCleaned);
const rules = ast?.stylesheet?.rules;
if (rules) {
for (const rule of rules) {
if (rule.type === 'rule') {
if (rule.selectors) {
for (let i = 0; i < rule.selectors.length; i++) {
let selector = rule.selectors[i];
if (selector) {
let selectors = (selector.split(' ') ?? []).map((v) => {
if (v.startsWith('.')) {
return '.custom-' + v.substring(1);
}
return v;
}).join(' ');
rule.selectors[i] = '.mes_text ' + selectors;
}
}
}
}
}
const sheet = ast?.stylesheet;
if (sheet) {
sanitizeRuleSet(ast.stylesheet);
}
return `<style>${css.stringify(ast)}</style>`;
} catch (error) {
@ -752,7 +769,7 @@ async function moveAttachment(attachment, source, callback) {
* @param {boolean} [confirm=true] If true, show a confirmation dialog
* @returns {Promise<void>} A promise that resolves when the attachment is deleted.
*/
async function deleteAttachment(attachment, source, callback, confirm = true) {
export async function deleteAttachment(attachment, source, callback, confirm = true) {
if (confirm) {
const result = await callGenericPopup('Are you sure you want to delete this attachment?', POPUP_TYPE.CONFIRM);
@ -839,6 +856,12 @@ async function openAttachmentManager() {
[ATTACHMENT_SOURCE.CHAT]: '.chatAttachmentsList',
};
const selected = template
.find(sources[source])
.find('.attachmentListItemCheckbox:checked')
.map((_, el) => $(el).closest('.attachmentListItem').attr('data-attachment-url'))
.get();
template.find(sources[source]).empty();
// Sort attachments by sortField and sortOrder, and apply filter
@ -848,6 +871,8 @@ async function openAttachmentManager() {
const isDisabled = isAttachmentDisabled(attachment);
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
attachmentTemplate.toggleClass('disabled', isDisabled);
attachmentTemplate.attr('data-attachment-url', attachment.url);
attachmentTemplate.attr('data-attachment-source', source);
attachmentTemplate.find('.attachmentFileIcon').attr('title', attachment.url);
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
@ -860,6 +885,10 @@ async function openAttachmentManager() {
attachmentTemplate.find('.enableAttachmentButton').toggle(isDisabled).on('click', () => enableAttachment(attachment, renderAttachments));
attachmentTemplate.find('.disableAttachmentButton').toggle(!isDisabled).on('click', () => disableAttachment(attachment, renderAttachments));
template.find(sources[source]).append(attachmentTemplate);
if (selected.includes(attachment.url)) {
attachmentTemplate.find('.attachmentListItemCheckbox').prop('checked', true);
}
}
}
@ -1003,6 +1032,57 @@ async function openAttachmentManager() {
localStorage.setItem('DataBank_sortOrder', sortOrder);
renderAttachments();
});
template.find('.bulkActionDelete').on('click', async () => {
const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
if (selectedAttachments.length === 0) {
toastr.info('No attachments selected.', 'Data Bank');
return;
}
const confirm = await callGenericPopup('Are you sure you want to delete the selected attachments?', POPUP_TYPE.CONFIRM);
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
const attachments = getDataBankAttachments();
selectedAttachments.forEach(async (checkbox) => {
const listItem = checkbox.closest('.attachmentListItem');
if (!(listItem instanceof HTMLElement)) {
return;
}
const url = listItem.dataset.attachmentUrl;
const source = listItem.dataset.attachmentSource;
const attachment = attachments.find(a => a.url === url);
if (!attachment) {
return;
}
await deleteAttachment(attachment, source, () => {}, false);
});
document.querySelectorAll('.attachmentListItemCheckbox, .attachmentsBulkEditCheckbox').forEach(checkbox => {
if (checkbox instanceof HTMLInputElement) {
checkbox.checked = false;
}
});
await renderAttachments();
});
template.find('.bulkActionSelectAll').on('click', () => {
$('.attachmentListItemCheckbox:visible').each((_, checkbox) => {
if (checkbox instanceof HTMLInputElement) {
checkbox.checked = true;
}
});
});
template.find('.bulkActionSelectNone').on('click', () => {
$('.attachmentListItemCheckbox:visible').each((_, checkbox) => {
if (checkbox instanceof HTMLInputElement) {
checkbox.checked = false;
}
});
});
const cleanupFn = await renderButtons();
await verifyAttachments();
@ -1074,7 +1154,7 @@ async function runScraper(scraperId, target, callback) {
* Uploads a file attachment to the server.
* @param {File} file File to upload
* @param {string} target Target for the attachment
* @returns
* @returns {Promise<string>} Path to the uploaded file
*/
export async function uploadFileAttachmentToServer(file, target) {
const isValid = await validateFile(file);
@ -1131,6 +1211,8 @@ export async function uploadFileAttachmentToServer(file, target) {
saveSettingsDebounced();
break;
}
return fileUrl;
}
function ensureAttachmentsExist() {
@ -1158,36 +1240,42 @@ function ensureAttachmentsExist() {
}
/**
* Gets all currently available attachments. Ignores disabled attachments.
* Gets all currently available attachments. Ignores disabled attachments by default.
* @param {boolean} [includeDisabled=false] If true, include disabled attachments
* @returns {FileAttachment[]} List of attachments
*/
export function getDataBankAttachments() {
export function getDataBankAttachments(includeDisabled = false) {
ensureAttachmentsExist();
const globalAttachments = extension_settings.attachments ?? [];
const chatAttachments = chat_metadata.attachments ?? [];
const characterAttachments = extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
return [...globalAttachments, ...chatAttachments, ...characterAttachments].filter(x => !isAttachmentDisabled(x));
return [...globalAttachments, ...chatAttachments, ...characterAttachments].filter(x => includeDisabled || !isAttachmentDisabled(x));
}
/**
* Gets all attachments for a specific source. Includes disabled attachments.
* Gets all attachments for a specific source. Includes disabled attachments by default.
* @param {string} source Attachment source
* @param {boolean} [includeDisabled=true] If true, include disabled attachments
* @returns {FileAttachment[]} List of attachments
*/
export function getDataBankAttachmentsForSource(source) {
export function getDataBankAttachmentsForSource(source, includeDisabled = true) {
ensureAttachmentsExist();
switch (source) {
case ATTACHMENT_SOURCE.GLOBAL:
return extension_settings.attachments ?? [];
case ATTACHMENT_SOURCE.CHAT:
return chat_metadata.attachments ?? [];
case ATTACHMENT_SOURCE.CHARACTER:
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
function getBySource() {
switch (source) {
case ATTACHMENT_SOURCE.GLOBAL:
return extension_settings.attachments ?? [];
case ATTACHMENT_SOURCE.CHAT:
return chat_metadata.attachments ?? [];
case ATTACHMENT_SOURCE.CHARACTER:
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
}
return [];
}
return [];
return getBySource().filter(x => includeDisabled || !isAttachmentDisabled(x));
}
/**
@ -1349,6 +1437,7 @@ jQuery(function () {
});
$(document).on('click', 'body.documentstyle .mes .mes_text', function () {
if (window.getSelection().toString()) return;
if ($('.edit_textarea').length) return;
$(this).closest('.mes').find('.mes_edit').trigger('click');
});