From 428d6fb40e3ae4395f4e52b90024153e3619b556 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Mon, 20 Oct 2025 09:25:55 +1100 Subject: [PATCH] feat(inventory): add inline editing for inventory items - Created inventoryEdit.js module with updateInventoryItem() function - Made all inventory item names editable with contenteditable (mobile-friendly) - Added rpg-editable class to 6 item rendering locations: * On Person (grid and list views) * Stored (grid and list views) * Assets (grid and list views) - Added blur event listener to save changes on edit - Validates and sanitizes edited names using sanitizeItemName() - Syncs changes to lastGeneratedData and committedTrackerData (AI-visible) - Shows full item text when editing (not truncated) - Consistent UX with other editable fields in extension (stats, character traits, etc.) - Re-renders inventory after successful edit or reverts on invalid input --- src/systems/interaction/inventoryEdit.js | 104 +++++++++++++++++++++++ src/systems/rendering/inventory.js | 22 +++-- 2 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 src/systems/interaction/inventoryEdit.js diff --git a/src/systems/interaction/inventoryEdit.js b/src/systems/interaction/inventoryEdit.js new file mode 100644 index 0000000..8da3a98 --- /dev/null +++ b/src/systems/interaction/inventoryEdit.js @@ -0,0 +1,104 @@ +/** + * Inventory Item Editing Module + * Handles inline editing of inventory item names + */ + +import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js'; +import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js'; +import { buildInventorySummary } from '../generation/promptBuilder.js'; +import { renderInventory } from '../rendering/inventory.js'; +import { parseItems, serializeItems } from '../../utils/itemParser.js'; +import { sanitizeItemName } from '../../utils/security.js'; + +/** + * Updates an existing inventory item's name. + * Validates, sanitizes, and persists the change. + * + * @param {string} field - Field name ('onPerson', 'stored', 'assets') + * @param {number} index - Index of item in the array + * @param {string} newName - New name for the item + * @param {string} [location] - Location name (required for 'stored' field) + */ +export function updateInventoryItem(field, index, newName, location) { + const inventory = extensionSettings.userStats.inventory; + + // Validate and sanitize the new item name + const sanitizedName = sanitizeItemName(newName); + if (!sanitizedName) { + console.warn('[RPG Companion] Invalid item name, reverting change'); + // Re-render to revert the change in UI + renderInventory(); + return; + } + + // Get current items for the field + let currentString; + if (field === 'stored') { + if (!location) { + console.error('[RPG Companion] Location required for stored items'); + return; + } + currentString = inventory.stored[location] || 'None'; + } else { + currentString = inventory[field] || 'None'; + } + + // Parse current items + const items = parseItems(currentString); + + // Validate index + if (index < 0 || index >= items.length) { + console.error(`[RPG Companion] Invalid item index: ${index}`); + return; + } + + // Update the item at this index + items[index] = sanitizedName; + + // Serialize back to string + const newItemString = serializeItems(items); + + // Update the inventory + if (field === 'stored') { + inventory.stored[location] = newItemString; + } else { + inventory[field] = newItemString; + } + + // Update lastGeneratedData and committedTrackerData with new inventory + updateLastGeneratedDataInventory(); + + // Save changes + saveSettings(); + saveChatData(); + updateMessageSwipeData(); + + // Re-render inventory + renderInventory(); +} + +/** + * Updates lastGeneratedData.userStats AND committedTrackerData.userStats to include + * current inventory in text format. + * This ensures manual edits are immediately visible to AI in next generation. + * @private + */ +function updateLastGeneratedDataInventory() { + const stats = extensionSettings.userStats; + const inventorySummary = buildInventorySummary(stats.inventory); + + // Rebuild the userStats text format + const statsText = + `Health: ${stats.health}%\n` + + `Satiety: ${stats.satiety}%\n` + + `Energy: ${stats.energy}%\n` + + `Hygiene: ${stats.hygiene}%\n` + + `Arousal: ${stats.arousal}%\n` + + `${stats.mood}: ${stats.conditions}\n` + + `${inventorySummary}`; + + // Update BOTH lastGeneratedData AND committedTrackerData + // This makes manual edits immediately visible to AI + lastGeneratedData.userStats = statsText; + committedTrackerData.userStats = statsText; +} diff --git a/src/systems/rendering/inventory.js b/src/systems/rendering/inventory.js index 91a2b8d..9bbae2a 100644 --- a/src/systems/rendering/inventory.js +++ b/src/systems/rendering/inventory.js @@ -5,6 +5,7 @@ import { extensionSettings, $inventoryContainer } from '../../core/state.js'; import { getInventoryRenderOptions, restoreFormStates } from '../interaction/inventoryActions.js'; +import { updateInventoryItem } from '../interaction/inventoryEdit.js'; import { parseItems } from '../../utils/itemParser.js'; // Type imports @@ -51,14 +52,14 @@ export function renderOnPersonView(onPersonItems, viewMode = 'list') { - ${escapeHtml(item)} + ${escapeHtml(item)} `).join(''); } else { // List view: full-width rows itemsHtml = items.map((item, index) => `
- ${escapeHtml(item)} + ${escapeHtml(item)} @@ -173,14 +174,14 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li - ${escapeHtml(item)} + ${escapeHtml(item)}
`).join(''); } else { // List view: full-width rows itemsHtml = items.map((item, index) => `
- ${escapeHtml(item)} + ${escapeHtml(item)} @@ -269,14 +270,14 @@ export function renderAssetsView(assets, viewMode = 'list') { - ${escapeHtml(item)} + ${escapeHtml(item)}
`).join(''); } else { // List view: full-width rows itemsHtml = items.map((item, index) => `
- ${escapeHtml(item)} + ${escapeHtml(item)} @@ -455,6 +456,15 @@ export function renderInventory() { // Restore form states after re-rendering (fixes Bug #1) restoreFormStates(); + + // Event listener for editing item names (mobile-friendly contenteditable) + $inventoryContainer.find('.rpg-item-name.rpg-editable').on('blur', function() { + const field = $(this).data('field'); + const index = parseInt($(this).data('index')); + const location = $(this).data('location'); + const newName = $(this).text().trim(); + updateInventoryItem(field, index, newName, location); + }); } /**