mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Gallery: add delete functionality for gallery items
This commit is contained in:
@@ -2439,7 +2439,7 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
|||||||
const image = messageElement.find('.mes_img');
|
const image = messageElement.find('.mes_img');
|
||||||
const text = messageElement.find('.mes_text');
|
const text = messageElement.find('.mes_text');
|
||||||
const isInline = !!mes.extra?.inline_image;
|
const isInline = !!mes.extra?.inline_image;
|
||||||
image.off('load').on('load', function () {
|
const doAdjustScroll = () => {
|
||||||
if (!adjustScroll) {
|
if (!adjustScroll) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2447,6 +2447,16 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
|||||||
const newChatHeight = $('#chat').prop('scrollHeight');
|
const newChatHeight = $('#chat').prop('scrollHeight');
|
||||||
const diff = newChatHeight - chatHeight;
|
const diff = newChatHeight - chatHeight;
|
||||||
$('#chat').scrollTop(scrollPosition + diff);
|
$('#chat').scrollTop(scrollPosition + diff);
|
||||||
|
};
|
||||||
|
image.off('load').on('load', function () {
|
||||||
|
image.removeAttr('alt');
|
||||||
|
image.removeClass('error');
|
||||||
|
doAdjustScroll();
|
||||||
|
});
|
||||||
|
image.off('error').on('error', function () {
|
||||||
|
image.attr('alt', '');
|
||||||
|
image.addClass('error');
|
||||||
|
doAdjustScroll();
|
||||||
});
|
});
|
||||||
image.attr('src', mes.extra?.image);
|
image.attr('src', mes.extra?.image);
|
||||||
image.attr('title', mes.extra?.title || mes.title || '');
|
image.attr('title', mes.extra?.title || mes.title || '');
|
||||||
|
@@ -15,10 +15,12 @@ import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/S
|
|||||||
import { DragAndDropHandler } from '../../dragdrop.js';
|
import { DragAndDropHandler } from '../../dragdrop.js';
|
||||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
import { t, translate } from '../../i18n.js';
|
import { t, translate } from '../../i18n.js';
|
||||||
|
import { Popup } from '../../popup.js';
|
||||||
|
|
||||||
const extensionName = 'gallery';
|
const extensionName = 'gallery';
|
||||||
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
||||||
let firstTime = true;
|
let firstTime = true;
|
||||||
|
let deleteModeActive = false;
|
||||||
|
|
||||||
// Exposed defaults for future tweaking
|
// Exposed defaults for future tweaking
|
||||||
let thumbnailHeight = 150;
|
let thumbnailHeight = 150;
|
||||||
@@ -146,6 +148,29 @@ async function getGalleryFolders() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a gallery item based on the provided URL.
|
||||||
|
* @param {string} url - The URL of the image to be deleted.
|
||||||
|
*/
|
||||||
|
async function deleteGalleryItem(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/images/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({ path: url }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error. Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(t`Image deleted successfully.`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete the image:', error);
|
||||||
|
toastr.error(t`Failed to delete the image. Check the console for details.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the sort order for the gallery.
|
* Sets the sort order for the gallery.
|
||||||
* @param {string} order Sort order
|
* @param {string} order Sort order
|
||||||
@@ -260,7 +285,7 @@ async function initGallery(items, url) {
|
|||||||
*
|
*
|
||||||
* @returns {Promise<void>} - Promise representing the completion of the gallery display process.
|
* @returns {Promise<void>} - Promise representing the completion of the gallery display process.
|
||||||
*/
|
*/
|
||||||
async function showCharGallery() {
|
async function showCharGallery(deleteModeState = false) {
|
||||||
// Load necessary files if it's the first time calling the function
|
// Load necessary files if it's the first time calling the function
|
||||||
if (firstTime) {
|
if (firstTime) {
|
||||||
await loadFileToDocument(
|
await loadFileToDocument(
|
||||||
@@ -276,6 +301,7 @@ async function showCharGallery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
deleteModeActive = deleteModeState;
|
||||||
let url = selected_group || this_chid;
|
let url = selected_group || this_chid;
|
||||||
if (!selected_group && this_chid !== undefined) {
|
if (!selected_group && this_chid !== undefined) {
|
||||||
url = getGalleryFolder(characters[this_chid]);
|
url = getGalleryFolder(characters[this_chid]);
|
||||||
@@ -429,6 +455,18 @@ async function makeMovable(url) {
|
|||||||
galleryFolderAccept.title = t`Change gallery folder`;
|
galleryFolderAccept.title = t`Change gallery folder`;
|
||||||
galleryFolderAccept.addEventListener('click', onChangeFolder);
|
galleryFolderAccept.addEventListener('click', onChangeFolder);
|
||||||
|
|
||||||
|
const galleryDeleteMode = document.createElement('div');
|
||||||
|
galleryDeleteMode.classList.add('right_menu_button', 'fa-solid', 'fa-trash', 'fa-fw');
|
||||||
|
galleryDeleteMode.classList.toggle('warning', deleteModeActive);
|
||||||
|
galleryDeleteMode.title = t`Delete mode`;
|
||||||
|
galleryDeleteMode.addEventListener('click', () => {
|
||||||
|
deleteModeActive = !deleteModeActive;
|
||||||
|
galleryDeleteMode.classList.toggle('warning', deleteModeActive);
|
||||||
|
if (deleteModeActive) {
|
||||||
|
toastr.info(t`Delete mode is ON. Click on images you want to delete.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const galleryFolderRestore = document.createElement('div');
|
const galleryFolderRestore = document.createElement('div');
|
||||||
galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw');
|
galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw');
|
||||||
galleryFolderRestore.title = t`Restore gallery folder`;
|
galleryFolderRestore.title = t`Restore gallery folder`;
|
||||||
@@ -436,6 +474,7 @@ async function makeMovable(url) {
|
|||||||
|
|
||||||
topBarElement.appendChild(galleryFolderInput);
|
topBarElement.appendChild(galleryFolderInput);
|
||||||
topBarElement.appendChild(galleryFolderAccept);
|
topBarElement.appendChild(galleryFolderAccept);
|
||||||
|
topBarElement.appendChild(galleryDeleteMode);
|
||||||
topBarElement.appendChild(galleryFolderRestore);
|
topBarElement.appendChild(galleryFolderRestore);
|
||||||
newElement.append(topBarElement);
|
newElement.append(topBarElement);
|
||||||
|
|
||||||
@@ -635,9 +674,19 @@ function sanitizeHTMLId(id) {
|
|||||||
function viewWithDragbox(items) {
|
function viewWithDragbox(items) {
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
|
const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
|
||||||
// ID should just be the last part of the URL, removing the extension
|
if (deleteModeActive) {
|
||||||
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
|
Popup.show.confirm(t`Are you sure you want to delete this image?`, url)
|
||||||
makeDragImg(id, url);
|
.then(async (confirmed) => {
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteGalleryItem(url).then(() => showCharGallery(deleteModeActive));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// ID should just be the last part of the URL, removing the extension
|
||||||
|
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
|
||||||
|
makeDragImg(id, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,3 +43,8 @@
|
|||||||
#dragGallery {
|
#dragGallery {
|
||||||
min-height: 25dvh;
|
min-height: 25dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#gallery .right_menu_button.warning {
|
||||||
|
opacity: 1;
|
||||||
|
filter: unset;
|
||||||
|
}
|
||||||
|
@@ -5059,6 +5059,12 @@ a:hover {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mes_img.error {
|
||||||
|
visibility: hidden;
|
||||||
|
min-height: 100px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
.mes_img_swipes,
|
.mes_img_swipes,
|
||||||
.mes_img_controls {
|
.mes_img_controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -5099,6 +5105,8 @@ a:hover {
|
|||||||
filter: brightness(150%);
|
filter: brightness(150%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mes_img_container:has(.mes_img.error) .mes_img_swipes,
|
||||||
|
.mes_img_container:has(.mes_img.error) .mes_img_controls,
|
||||||
.mes_img_container:hover .mes_img_swipes,
|
.mes_img_container:hover .mes_img_swipes,
|
||||||
.mes_img_container:focus-within .mes_img_swipes,
|
.mes_img_container:focus-within .mes_img_swipes,
|
||||||
.mes_img_container:hover .mes_img_controls,
|
.mes_img_container:hover .mes_img_controls,
|
||||||
|
@@ -5,7 +5,7 @@ import { Buffer } from 'node:buffer';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import sanitize from 'sanitize-filename';
|
import sanitize from 'sanitize-filename';
|
||||||
|
|
||||||
import { clientRelativePath, removeFileExtension, getImages } from '../util.js';
|
import { clientRelativePath, removeFileExtension, getImages, isPathUnderParent } from '../util.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the directory for the provided file path exists.
|
* Ensure the directory for the provided file path exists.
|
||||||
@@ -126,3 +126,27 @@ router.post('/folders', (request, response) => {
|
|||||||
return response.status(500).send({ error: 'Unable to retrieve folders' });
|
return response.status(500).send({ error: 'Unable to retrieve folders' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/delete', async (request, response) => {
|
||||||
|
try {
|
||||||
|
if (!request.body.path) {
|
||||||
|
return response.status(400).send('No path specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathToDelete = path.join(request.user.directories.root, request.body.path);
|
||||||
|
if (!isPathUnderParent(request.user.directories.userImages, pathToDelete)) {
|
||||||
|
return response.status(400).send('Invalid path');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(pathToDelete)) {
|
||||||
|
return response.status(404).send('File not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unlinkSync(pathToDelete);
|
||||||
|
console.info(`Deleted image: ${request.body.path} from ${request.user.profile.handle}`);
|
||||||
|
return response.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user