fix(bulk-context-menu): escape special characters in data-filepath selector to support double quotes in filenames (#845)

This commit is contained in:
Will Miao
2026-03-12 08:49:10 +08:00
parent 5c29e26c4e
commit 4fcf641d57
7 changed files with 34 additions and 10 deletions

View File

@@ -117,7 +117,10 @@ export class BulkContextMenu extends BaseContextMenu {
countSkipStatus(skipState) {
let count = 0;
for (const filePath of state.selectedModels) {
const card = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
const escapedPath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(filePath)
: filePath.replace(/["\\]/g, '\\$&');
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
if (card) {
const isSkipped = card.dataset.skip_metadata_refresh === 'true';
if (isSkipped === skipState) {

View File

@@ -201,8 +201,9 @@ class RecipeCard {
this.recipe.favorite = isFavorite;
// Re-find star icon in case of re-render during fault
const filePathForXpath = this.recipe.file_path.replace(/"/g, '"');
const currentCard = card.ownerDocument.evaluate(
`.//*[@data-filepath="${this.recipe.file_path}"]`,
`.//*[@data-filepath="${filePathForXpath}"]`,
card.ownerDocument, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
).singleNodeValue || card;

View File

@@ -846,8 +846,14 @@ function setupLoraSpecificFields(filePath) {
const currentPath = resolveFilePath();
if (!currentPath) return;
const loraCard = document.querySelector(`.model-card[data-filepath="${currentPath}"]`) ||
document.querySelector(`.model-card[data-filepath="${filePath}"]`);
const escapedCurrentPath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(currentPath)
: currentPath.replace(/["\\]/g, '\\$&');
const escapedFilePath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(filePath)
: filePath.replace(/["\\]/g, '\\$&');
const loraCard = document.querySelector(`.model-card[data-filepath="${escapedCurrentPath}"]`) ||
document.querySelector(`.model-card[data-filepath="${escapedFilePath}"]`);
const currentPresets = parsePresets(loraCard?.dataset.usage_tips);
if (key === 'strength_range') {

View File

@@ -49,7 +49,10 @@ function formatPresetKey(key) {
*/
window.removePreset = async function(key) {
const filePath = document.querySelector('#modelModal .modal-content .file-path').dataset.filepath;
const loraCard = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
const escapedPath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(filePath)
: filePath.replace(/["\\]/g, '\\$&');
const loraCard = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
const currentPresets = parsePresets(loraCard.dataset.usage_tips);
delete currentPresets[key];

View File

@@ -568,7 +568,8 @@ export class BulkManager {
}
deselectItem(filepath) {
const card = document.querySelector(`.model-card[data-filepath="${filepath}"]`);
const escapedPath = this.escapeAttributeValue(filepath);
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
if (card) {
card.classList.remove('selected');
}
@@ -632,7 +633,8 @@ export class BulkManager {
for (const filepath of state.selectedModels) {
const metadata = metadataCache.get(filepath);
if (metadata) {
const card = document.querySelector(`.model-card[data-filepath="${filepath}"]`);
const escapedPath = this.escapeAttributeValue(filepath);
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
if (card) {
this.updateMetadataCacheFromCard(filepath, card);
}

View File

@@ -7,7 +7,10 @@ let pendingExcludePath = null;
export function showDeleteModal(filePath) {
pendingDeletePath = filePath;
const card = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
const escapedPath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(filePath)
: filePath.replace(/["\\]/g, '\\$&');
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
const modelName = card ? card.dataset.name : filePath.split('/').pop();
const modal = modalManager.getModal('deleteModal').element;
const modelInfo = modal.querySelector('.delete-model-info');
@@ -47,7 +50,10 @@ export function closeDeleteModal() {
export function showExcludeModal(filePath) {
pendingExcludePath = filePath;
const card = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
const escapedPath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(filePath)
: filePath.replace(/["\\]/g, '\\$&');
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
const modelName = card ? card.dataset.name : filePath.split('/').pop();
const modal = modalManager.getModal('excludeModal').element;
const modelInfo = modal.querySelector('.exclude-model-info');

View File

@@ -197,7 +197,10 @@ export function openCivitaiByMetadata(civitaiId, versionId, modelName = null) {
}
export function openCivitai(filePath) {
const loraCard = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
const escapedPath = window.CSS && typeof window.CSS.escape === 'function'
? window.CSS.escape(filePath)
: filePath.replace(/["\\]/g, '\\$&');
const loraCard = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
if (!loraCard) return;
const metaData = JSON.parse(loraCard.dataset.meta);