diff --git a/index.js b/index.js index 288a562..40246cc 100644 --- a/index.js +++ b/index.js @@ -119,7 +119,7 @@ import { setupClassicStatsButtons } from './src/systems/features/classicStats.js import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/systems/features/htmlCleaning.js'; import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js'; import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js'; -import { DEFAULT_HTML_PROMPT } from './src/systems/generation/promptBuilder.js'; +import { DEFAULT_HTML_PROMPT, DEFAULT_TRACKER_PROMPT } from './src/systems/generation/promptBuilder.js'; // Integration modules import { @@ -323,6 +323,29 @@ async function initUI() { extensionSettings.showInventory = $(this).prop('checked'); saveSettings(); updateSectionVisibility(); + // Re-setup desktop tabs to show/hide inventory tab + if (window.innerWidth > 1000) { + removeDesktopTabs(); + setupDesktopTabs(); + } + }); + + $('#rpg-toggle-simplified-inventory').on('change', function() { + extensionSettings.useSimplifiedInventory = $(this).prop('checked'); + saveSettings(); + renderInventory(); // Re-render inventory with new mode + }); + + $('#rpg-toggle-quests').on('change', function() { + extensionSettings.showQuests = $(this).prop('checked'); + saveSettings(); + updateSectionVisibility(); + renderQuests(); // Re-render quests + // Re-setup desktop tabs to show/hide quests tab + if (window.innerWidth > 1000) { + removeDesktopTabs(); + setupDesktopTabs(); + } }); $('#rpg-toggle-thoughts-in-chat').on('change', function() { @@ -361,6 +384,19 @@ async function initUI() { toastr.success('HTML prompt restored to default'); }); + // Custom Tracker Prompt handlers + $('#rpg-custom-tracker-prompt').on('input', function() { + extensionSettings.customTrackerPrompt = $(this).val().trim(); + saveSettings(); + }); + + $('#rpg-restore-default-tracker-prompt').on('click', function() { + extensionSettings.customTrackerPrompt = ''; + $('#rpg-custom-tracker-prompt').val(''); + saveSettings(); + toastr.success('Tracker prompt restored to default'); + }); + $('#rpg-skip-guided-mode').on('change', function() { extensionSettings.skipInjectionsForGuided = String($(this).val()); saveSettings(); @@ -460,6 +496,8 @@ async function initUI() { $('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox); $('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts); $('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory); + $('#rpg-toggle-simplified-inventory').prop('checked', extensionSettings.useSimplifiedInventory); + $('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests); $('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat); $('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble); $('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt); @@ -467,7 +505,10 @@ async function initUI() { // Set default HTML prompt as actual text if no custom prompt exists $('#rpg-custom-html-prompt').val(extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT); - $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); + // Set default tracker prompt as actual text if no custom prompt exists + $('#rpg-custom-tracker-prompt').val(extensionSettings.customTrackerPrompt || DEFAULT_TRACKER_PROMPT); + + $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations); $('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow); $('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh); diff --git a/src/core/state.js b/src/core/state.js index 64cbf68..88cd275 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -19,9 +19,12 @@ export let extensionSettings = { showInfoBox: true, showCharacterThoughts: true, showInventory: true, // Show inventory section (v2 system) + useSimplifiedInventory: false, // Use simplified single-list inventory instead of categorized (On Person/Stored/Assets) + showQuests: true, // Show quests section showThoughtsInChat: true, // Show thoughts overlay in chat enableHtmlPrompt: false, // Enable immersive HTML prompt injection customHtmlPrompt: '', // Custom HTML prompt text (empty = use default) + customTrackerPrompt: '', // Custom tracker instruction prompt (empty = use default) skipInjectionsForGuided: 'none', // skip injections for instruct injections and quiet prompts (GuidedGenerations compatibility) enablePlotButtons: true, // Show plot progression buttons above chat input panelPosition: 'right', // 'left', 'right', or 'top' diff --git a/src/i18n/en.json b/src/i18n/en.json index 8e7f0fd..f597a90 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -30,6 +30,9 @@ "template.settingsModal.display.showInfoBox": "Show Info Box", "template.settingsModal.display.showPresentCharacters": "Show Present Characters", "template.settingsModal.display.showInventory": "Show Inventory", + "template.settingsModal.display.useSimplifiedInventory": "Use Simplified Inventory", + "template.settingsModal.display.useSimplifiedInventoryNote": "Single flat list instead of On Person / Stored / Assets categories", + "template.settingsModal.display.showQuests": "Show Quests", "template.settingsModal.display.showThoughtsInChat": "Show Thoughts in Chat", "template.settingsModal.display.showThoughtsInChatNote": "Display character thoughts as overlay bubbles next to their messages", "template.settingsModal.display.alwaysShowThoughtBubble": "Always Show Thought Bubble", @@ -56,6 +59,9 @@ "template.settingsModal.advanced.skipInjectionsOptions.impersonation": "Only on impersonation requests", "template.settingsModal.advanced.skipInjectionsOptions.guided": "Always for guided or quiet prompts", "template.settingsModal.advanced.skipInjectionsNote": "When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.", + "template.settingsModal.advanced.customTrackerPromptTitle": "Custom Tracker Prompt:", + "template.settingsModal.advanced.restoreDefaultTrackerPrompt": "Restore Default", + "template.settingsModal.advanced.customTrackerPromptNote": "Customize the instructions sent to the AI for generating tracker data. Use {{user}} as a placeholder for the user's name. This is the main prompt that tells the AI how to format and update the RPG trackers.", "template.settingsModal.advanced.customHtmlPromptTitle": "Custom HTML Prompt:", "template.settingsModal.advanced.restoreDefaultHtmlPrompt": "Restore Default", "template.settingsModal.advanced.customHtmlPromptNote": "Customize the HTML prompt injected when \"Enable Immersive HTML\" is enabled. The default prompt is shown above - you can edit it directly or replace it entirely. Click \"Restore Default\" to reset. This affects all generation modes (together, separate, and plot progression).", @@ -150,6 +156,11 @@ "inventory.assets.addAssetButton": "Add Asset", "inventory.assets.addAssetPlaceholder": "Enter asset name...", "inventory.assets.description": "Assets include vehicles (cars, motorcycles), property (homes, apartments), and major equipment (workshop tools, special items).", + "inventory.simplified.title": "Inventory", + "inventory.simplified.empty": "No items", + "inventory.simplified.addItemButton": "Add Item", + "inventory.simplified.addItemPlaceholder": "Enter item name...", + "inventory.simplified.removeTitle": "Remove item", "quests.section.main": "Main Quest", "quests.section.optional": "Optional Quests", "quests.main.title": "Main Quests", diff --git a/src/i18n/zh-tw.json b/src/i18n/zh-tw.json index d197716..4e451e5 100644 --- a/src/i18n/zh-tw.json +++ b/src/i18n/zh-tw.json @@ -30,6 +30,9 @@ "template.settingsModal.display.showInfoBox": "顯示資訊框", "template.settingsModal.display.showPresentCharacters": "顯示在場角色", "template.settingsModal.display.showInventory": "顯示物品欄", + "template.settingsModal.display.useSimplifiedInventory": "使用簡化物品欄", + "template.settingsModal.display.useSimplifiedInventoryNote": "使用單一列表取代分類(隨身/倉庫/資產)", + "template.settingsModal.display.showQuests": "顯示任務", "template.settingsModal.display.showThoughtsInChat": "在聊天中顯示想法", "template.settingsModal.display.showThoughtsInChatNote": "將角色想法顯示為其訊息旁的泡泡", "template.settingsModal.display.alwaysShowThoughtBubble": "始終顯示想法泡泡", @@ -56,9 +59,12 @@ "template.settingsModal.advanced.skipInjectionsOptions.impersonation": "僅在模擬請求時跳過", "template.settingsModal.advanced.skipInjectionsOptions.guided": "始終跳過引導", "template.settingsModal.advanced.skipInjectionsNote": "當設置後,擴充功能在檢測到引導生成(通過 `instruct` 或 `quiet_prompt`)時,將根據所選模式不注入追蹤提示詞、範例或 HTML 指令。當與 GuidedGenerations 或類似擴充功能一起使用時非常有用。", + "template.settingsModal.advanced.customTrackerPromptTitle": "自訂追蹤器提示詞:", + "template.settingsModal.advanced.restoreDefaultTrackerPrompt": "恢復預設", + "template.settingsModal.advanced.customTrackerPromptNote": "自訂發送給 AI 生成追蹤器數據的指令。使用 {{user}} 作為使用者名稱的佔位符。這是告訴 AI 如何格式化和更新 RPG 追蹤器的主要提示詞。", "template.settingsModal.advanced.customHtmlPromptTitle": "自訂 HTML 提示詞:", "template.settingsModal.advanced.restoreDefaultHtmlPrompt": "恢復預設", - "template.settingsModal.advanced.customHtmlPromptNote": "自訂啟用“啟用沉浸式 HTML”時注入的 HTML 提示詞。上方顯示預設提示詞 - 您可以直接編輯或完全替換它。點擊“恢復預設”以重置。這會影響所有生成模式(同時、單獨和劇情推進)。", + "template.settingsModal.advanced.customHtmlPromptNote": "自訂啟用"啟用沉浸式 HTML"時注入的 HTML 提示詞。上方顯示預設提示詞 - 您可以直接編輯或完全替換它。點擊"恢復預設"以重置。這會影響所有生成模式(同時、單獨和劇情推進)。", "template.settingsModal.advanced.clearCache": "清除擴充功能快取", "template.settingsModal.advanced.resetFabPositions": "重置按鈕位置", "template.settingsModal.advanced.resetFabPositionsNote": "將所有浮動操作按鈕(切換、刷新、調試)重置為預設的左上位置。如果按鈕在螢幕外,這會很有用。", @@ -150,6 +156,11 @@ "inventory.assets.addAssetButton": "添加資產", "inventory.assets.addAssetPlaceholder": "輸入資產名稱...", "inventory.assets.description": "資產包括車輛(汽車、摩托車)、房產(房屋、公寓)和主要設備(車間工具、特殊物品)。", + "inventory.simplified.title": "物品欄", + "inventory.simplified.empty": "沒有物品", + "inventory.simplified.addItemButton": "添加物品", + "inventory.simplified.addItemPlaceholder": "輸入物品名稱...", + "inventory.simplified.removeTitle": "移除物品", "quests.section.main": "主線任務", "quests.section.optional": "支線任務", "quests.main.title": "主線任務", diff --git a/src/systems/generation/inventoryParser.js b/src/systems/generation/inventoryParser.js index f56e28d..9287796 100644 --- a/src/systems/generation/inventoryParser.js +++ b/src/systems/generation/inventoryParser.js @@ -3,6 +3,8 @@ * Extracts v2 inventory data from AI-generated text */ +import { extensionSettings } from '../../core/state.js'; + // Type imports /** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */ @@ -101,30 +103,84 @@ export function extractLegacyInventory(text) { return null; } +/** + * Extracts simplified inventory data (single "Inventory:" line). + * Used when useSimplifiedInventory setting is enabled. + * + * Expected format: "Inventory: Sword, Shield, 3x Potions, Gold coins" + * + * @param {string} text - Text that may contain simplified inventory + * @returns {InventoryV2|null} Parsed inventory or null + */ +export function extractSimplifiedInventory(text) { + if (!text || typeof text !== 'string') { + return null; + } + + // Match simplified format: "Inventory: ..." + const match = text.match(/Inventory:\s*(.+?)(?:\n|$)/i); + if (match && match[1]) { + const inventoryText = match[1].trim(); + // Return null for empty values like "None" or "" + if (!inventoryText || inventoryText.toLowerCase() === 'none') { + return null; + } + // Return v2 format with items stored in both 'items' (for simplified display) + // and 'onPerson' (for backward compatibility) + return { + version: 2, + onPerson: inventoryText, + stored: {}, + assets: "None", + items: inventoryText, // Simplified inventory storage + simplified: true // Flag to indicate this is simplified format + }; + } + + return null; +} + /** * Main inventory extraction function that tries v2 format first, then falls back to v1. * Converts v1 format to v2 automatically if found. + * When useSimplifiedInventory is enabled, prioritizes simple "Inventory:" format. * * @param {string} statsText - Raw stats text from AI response * @returns {InventoryV2|null} Parsed inventory in v2 format or null */ export function extractInventory(statsText) { + // If simplified inventory mode is enabled, try simplified format first + if (extensionSettings.useSimplifiedInventory) { + const simplifiedData = extractSimplifiedInventory(statsText); + if (simplifiedData) { + return simplifiedData; + } + } + // Try v2 format first const v2Data = extractInventoryData(statsText); if (v2Data) { return v2Data; } - // Fallback to v1 format and convert to v2 + // Fallback to v1/simplified format and convert to v2 const v1Data = extractLegacyInventory(statsText); if (v1Data) { // Convert v1 string to v2 format (place in onPerson) - return { + const result = { version: 2, onPerson: v1Data, stored: {}, assets: "None" }; + + // If simplified mode, also store in items field + if (extensionSettings.useSimplifiedInventory) { + result.items = v1Data; + result.simplified = true; + } + + return result; } // No inventory data found diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 040a942..9e4414f 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -16,6 +16,12 @@ import { extensionSettings, committedTrackerData, FEATURE_FLAGS } from '../../co */ export const DEFAULT_HTML_PROMPT = `If appropriate, include inline HTML, CSS, and JS segments whenever they enhance visual storytelling (e.g., for in-world screens, posters, books, letters, signs, crests, labels, etc.). Style them to match the setting's theme (e.g., fantasy, sci-fi), keep the text readable, and embed all assets directly (using inline SVGs only with no external scripts, libraries, or fonts). Use these elements freely and naturally within the narrative as characters would encounter them, including animations, 3D effects, pop-ups, dropdowns, websites, and so on. Do not wrap the HTML/CSS/JS in code fences!`; +/** + * Default tracker instruction prompt text + * Use {{user}} as placeholder for the user's name (will be replaced at runtime) + */ +export const DEFAULT_TRACKER_PROMPT = `At the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that {{user}} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences (0% if the time progressed only by a few minutes, 1-5% normally, and above 5% only if a major time-skip/event occurs).`; + /** * Gets character card information for current chat (handles both single and group chats) * @returns {string} Formatted character information @@ -91,11 +97,18 @@ async function getCharacterCardsInfo() { export function buildInventorySummary(inventory) { // Handle legacy v1 string format if (typeof inventory === 'string') { - return inventory; + return `Inventory: ${inventory}`; } // Handle v2 object format if (inventory && typeof inventory === 'object' && inventory.version === 2) { + // Check for simplified inventory mode + if (inventory.simplified || extensionSettings.useSimplifiedInventory) { + const items = inventory.items || inventory.onPerson || 'None'; + return `Inventory: ${items}`; + } + + // Full categorized format let summary = ''; // Add On Person section @@ -204,9 +217,9 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Only add tracker instructions if at least one tracker is enabled if (hasAnyTrackers) { - // Universal instruction header - instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that ${userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences (0% if the time progressed only by a few minutes, 1-5% normally, and above 5% only if a major time-skip/event occurs). -`; + // Universal instruction header - use custom prompt if set, otherwise use default + const trackerPrompt = (extensionSettings.customTrackerPrompt || DEFAULT_TRACKER_PROMPT).replace(/\{\{user\}\}/g, userName); + instructions += `\n${trackerPrompt}\n`; // Add format specifications for each enabled tracker if (extensionSettings.showUserStats) { @@ -241,9 +254,13 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon instructions += `Skills: [${skillFieldsText || 'Skill1, Skill2, etc.'}]\n`; } - // Add inventory format based on feature flag - only if showInventory is enabled + // Add inventory format - only if showInventory is enabled if (extensionSettings.showInventory) { - if (FEATURE_FLAGS.useNewInventory) { + if (extensionSettings.useSimplifiedInventory) { + // Simplified single-line inventory format + instructions += 'Inventory: [Items currently carried/worn/owned, or "None"]\n'; + } else if (FEATURE_FLAGS.useNewInventory) { + // Full v2 categorized inventory format instructions += 'On Person: [Items currently carried/worn, or "None"]\n'; instructions += 'Stored - [Location Name]: [Items stored at this location]\n'; instructions += '(Add multiple "Stored - [Location]:" lines as needed for different storage locations)\n'; @@ -254,9 +271,11 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon } } - // Add quests section - instructions += 'Main Quests: [Short title of the currently active main quest (for example, "Save the world"), or "None"]\n'; - instructions += 'Optional Quests: [Short titles of the currently active optional quests (for example, "Find Zandik\'s book"), or "None"]\n'; + // Add quests section - only if showQuests is enabled + if (extensionSettings.showQuests) { + instructions += 'Main Quests: [Short title of the currently active main quest (for example, "Save the world"), or "None"]\n'; + instructions += 'Optional Quests: [Short titles of the currently active optional quests (for example, "Find Zandik\'s book"), or "None"]\n'; + } instructions += '```\n\n'; } @@ -484,8 +503,8 @@ export function generateRPGPromptText() { promptText += `Last ${userName}'s Stats:\nNone - this is the first update.\n\n`; } - // Add current quests to the previous data context - if (extensionSettings.quests) { + // Add current quests to the previous data context - only if showQuests is enabled + if (extensionSettings.showQuests && extensionSettings.quests) { if (extensionSettings.quests.main && extensionSettings.quests.main !== 'None') { promptText += `Main Quests: ${extensionSettings.quests.main}\n`; } diff --git a/src/systems/interaction/inventoryActions.js b/src/systems/interaction/inventoryActions.js index 6c50ee3..64e338a 100644 --- a/src/systems/interaction/inventoryActions.js +++ b/src/systems/interaction/inventoryActions.js @@ -34,7 +34,8 @@ let openForms = { addLocation: false, addItemOnPerson: false, addItemStored: {}, // { [locationName]: true/false } - addItemAssets: false + addItemAssets: false, + addItemSimplified: false }; /** @@ -54,7 +55,7 @@ function updateLastGeneratedDataInventory() { /** * Shows the inline form for adding a new item. - * @param {string} field - Field name ('onPerson', 'stored', 'assets') + * @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified') * @param {string} [location] - Location name (required for 'stored' field) */ export function showAddItemForm(field, location) { @@ -76,6 +77,8 @@ export function showAddItemForm(field, location) { openForms.addItemOnPerson = true; } else if (field === 'assets') { openForms.addItemAssets = true; + } else if (field === 'simplified') { + openForms.addItemSimplified = true; } } @@ -88,7 +91,7 @@ export function showAddItemForm(field, location) { /** * Hides the inline form for adding a new item. - * @param {string} field - Field name ('onPerson', 'stored', 'assets') + * @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified') * @param {string} [location] - Location name (required for 'stored' field) */ export function hideAddItemForm(field, location) { @@ -111,6 +114,8 @@ export function hideAddItemForm(field, location) { openForms.addItemOnPerson = false; } else if (field === 'assets') { openForms.addItemAssets = false; + } else if (field === 'simplified') { + openForms.addItemSimplified = false; } } @@ -123,7 +128,7 @@ export function hideAddItemForm(field, location) { /** * Adds a new item to the inventory. - * @param {string} field - Field name ('onPerson', 'stored', 'assets') + * @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified') * @param {string} [location] - Location name (required for 'stored' field) */ export function saveAddItem(field, location) { @@ -154,7 +159,10 @@ export function saveAddItem(field, location) { // Get current items, add new one, serialize back let currentString; - if (field === 'stored') { + if (field === 'simplified') { + // For simplified inventory, use items field or fall back to onPerson + currentString = inventory.items || inventory.onPerson || 'None'; + } else if (field === 'stored') { currentString = inventory.stored[location] || 'None'; } else { currentString = inventory[field] || 'None'; @@ -165,7 +173,11 @@ export function saveAddItem(field, location) { const newString = serializeItems(items); // Save back to inventory - if (field === 'stored') { + if (field === 'simplified') { + // Update both items and onPerson for simplified mode + inventory.items = newString; + inventory.onPerson = newString; + } else if (field === 'stored') { inventory.stored[location] = newString; } else { inventory[field] = newString; @@ -183,7 +195,7 @@ export function saveAddItem(field, location) { /** * Removes an item from the inventory. - * @param {string} field - Field name ('onPerson', 'stored', 'assets') + * @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified') * @param {number} itemIndex - Index of item to remove * @param {string} [location] - Location name (required for 'stored' field) */ @@ -194,7 +206,10 @@ export function removeItem(field, itemIndex, location) { // Get current items, remove the one at index, serialize back let currentString; - if (field === 'stored') { + if (field === 'simplified') { + // For simplified inventory, use items field or fall back to onPerson + currentString = inventory.items || inventory.onPerson || 'None'; + } else if (field === 'stored') { currentString = inventory.stored[location] || 'None'; } else { currentString = inventory[field] || 'None'; @@ -212,7 +227,11 @@ export function removeItem(field, itemIndex, location) { // console.log('[RPG Companion] DEBUG newString after removal:', newString); // Save back to inventory - if (field === 'stored') { + if (field === 'simplified') { + // Update both items and onPerson for simplified mode + inventory.items = newString; + inventory.onPerson = newString; + } else if (field === 'stored') { inventory.stored[location] = newString; } else { inventory[field] = newString; @@ -585,6 +604,15 @@ export function restoreFormStates() { } } + // Restore add item simplified form + if (openForms.addItemSimplified) { + const form = $('#rpg-add-item-form-simplified'); + const input = $('#rpg-new-item-simplified'); + if (form.length > 0) { + form.show(); + } + } + // Restore add item stored forms (for each location) // Clean up orphaned states for deleted locations (Bug #3 fix) if (openForms.addItemStored && typeof openForms.addItemStored === 'object') { diff --git a/src/systems/interaction/inventoryEdit.js b/src/systems/interaction/inventoryEdit.js index 8da3a98..7a8119f 100644 --- a/src/systems/interaction/inventoryEdit.js +++ b/src/systems/interaction/inventoryEdit.js @@ -14,7 +14,7 @@ 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 {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified') * @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) @@ -33,7 +33,10 @@ export function updateInventoryItem(field, index, newName, location) { // Get current items for the field let currentString; - if (field === 'stored') { + if (field === 'simplified') { + // For simplified inventory, use items field or fall back to onPerson + currentString = inventory.items || inventory.onPerson || 'None'; + } else if (field === 'stored') { if (!location) { console.error('[RPG Companion] Location required for stored items'); return; @@ -59,7 +62,11 @@ export function updateInventoryItem(field, index, newName, location) { const newItemString = serializeItems(items); // Update the inventory - if (field === 'stored') { + if (field === 'simplified') { + // Update both items and onPerson for simplified mode + inventory.items = newItemString; + inventory.onPerson = newItemString; + } else if (field === 'stored') { inventory.stored[location] = newItemString; } else { inventory[field] = newItemString; diff --git a/src/systems/rendering/inventory.js b/src/systems/rendering/inventory.js index 00b9e76..28defdb 100644 --- a/src/systems/rendering/inventory.js +++ b/src/systems/rendering/inventory.js @@ -444,6 +444,85 @@ export function updateInventoryDisplay(containerId, options = {}) { restoreFormStates(); } +/** + * Renders the simplified (single-list) inventory view + * Used when useSimplifiedInventory setting is enabled + * @param {string} itemsString - All items as a comma-separated string + * @param {string} viewMode - View mode ('list' or 'grid') + * @returns {string} HTML for simplified inventory view + */ +export function renderSimplifiedInventoryView(itemsString, viewMode = 'list') { + const items = parseItems(itemsString); + + let itemsHtml = ''; + if (items.length === 0) { + itemsHtml = `
${i18n.getTranslation('inventory.simplified.empty')}
`; + } else { + if (viewMode === 'grid') { + // Grid view: card-style items + itemsHtml = items.map((item, index) => ` +
+ + ${escapeHtml(item)} +
+ `).join(''); + } else { + // List view: full-width rows + itemsHtml = items.map((item, index) => ` +
+ ${escapeHtml(item)} + +
+ `).join(''); + } + } + + const listViewClass = viewMode === 'list' ? 'rpg-item-list-view' : 'rpg-item-grid-view'; + + return ` +
+
+
+

${i18n.getTranslation('inventory.simplified.title')}

+
+
+ + +
+ +
+
+
+ +
+ ${itemsHtml} +
+
+
+
+ `; +} + /** * Main inventory rendering function (matches pattern of other render functions) * Gets data from state/settings and updates DOM directly. @@ -458,11 +537,25 @@ export function renderInventory() { // Get inventory data from settings const inventory = extensionSettings.userStats.inventory; - // Get current render options (active tab, collapsed locations) - const options = getInventoryRenderOptions(); + let html; + + // Check if we should render simplified inventory + if (extensionSettings.useSimplifiedInventory) { + // For simplified mode, combine all items into a single string + // Use the 'items' field if available (from simplified parsing), + // otherwise fall back to onPerson + const itemsString = inventory.items || inventory.onPerson || 'None'; + // Get view mode from settings (use 'simplified' key or fall back to 'onPerson') + const viewModes = extensionSettings.inventoryViewModes || {}; + const viewMode = viewModes.simplified || viewModes.onPerson || 'list'; + html = renderSimplifiedInventoryView(itemsString, viewMode); + } else { + // Full categorized inventory + // Get current render options (active tab, collapsed locations) + const options = getInventoryRenderOptions(); + html = generateInventoryHTML(inventory, options); + } - // Generate HTML and update DOM - const html = generateInventoryHTML(inventory, options); $inventoryContainer.html(html); // Restore form states after re-rendering (fixes Bug #1) diff --git a/src/systems/rendering/quests.js b/src/systems/rendering/quests.js index d122835..4b0dc26 100644 --- a/src/systems/rendering/quests.js +++ b/src/systems/rendering/quests.js @@ -160,7 +160,7 @@ export function renderOptionalQuestsView(optionalQuests) { * Main render function for quests */ export function renderQuests() { - if (!extensionSettings.showInventory || !$questsContainer) { + if (!extensionSettings.showQuests || !$questsContainer) { return; } diff --git a/src/systems/ui/desktop.js b/src/systems/ui/desktop.js index fb35e4f..31d3b3d 100644 --- a/src/systems/ui/desktop.js +++ b/src/systems/ui/desktop.js @@ -4,6 +4,7 @@ */ import { i18n } from '../../core/i18n.js'; +import { extensionSettings } from '../../core/state.js'; /** * Sets up desktop tab navigation for organizing content. @@ -31,21 +32,25 @@ export function setupDesktopTabs() { return; } - // Create tab navigation + // Create tab navigation - conditionally show inventory and quests tabs based on settings + const inventoryTabHtml = extensionSettings.showInventory ? ` + ` : ''; + + const questsTabHtml = extensionSettings.showQuests ? ` + ` : ''; + const $tabNav = $(`
- - + ${inventoryTabHtml}${questsTabHtml}
`); diff --git a/src/systems/ui/layout.js b/src/systems/ui/layout.js index 089cd71..6d8d899 100644 --- a/src/systems/ui/layout.js +++ b/src/systems/ui/layout.js @@ -9,7 +9,8 @@ import { $userStatsContainer, $infoBoxContainer, $thoughtsContainer, - $inventoryContainer + $inventoryContainer, + $questsContainer } from '../../core/state.js'; import { i18n } from '../../core/i18n.js'; @@ -230,22 +231,30 @@ export function updateSectionVisibility() { if ($inventoryContainer) { $inventoryContainer.toggle(extensionSettings.showInventory); } + if ($questsContainer) { + $questsContainer.toggle(extensionSettings.showQuests); + } // Show/hide dividers intelligently // Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible const showDividerAfterStats = extensionSettings.showUserStats && - (extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory); + (extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests); $('#rpg-divider-stats').toggle(showDividerAfterStats); // Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible const showDividerAfterInfo = extensionSettings.showInfoBox && - (extensionSettings.showCharacterThoughts || extensionSettings.showInventory); + (extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests); $('#rpg-divider-info').toggle(showDividerAfterInfo); - // Divider after Thoughts: shown if Thoughts is visible AND Inventory is visible + // Divider after Thoughts: shown if Thoughts is visible AND Inventory or Quests is visible const showDividerAfterThoughts = extensionSettings.showCharacterThoughts && - extensionSettings.showInventory; + (extensionSettings.showInventory || extensionSettings.showQuests); $('#rpg-divider-thoughts').toggle(showDividerAfterThoughts); + + // Divider after Inventory: shown if Inventory is visible AND Quests is visible + const showDividerAfterInventory = extensionSettings.showInventory && + extensionSettings.showQuests; + $('#rpg-divider-inventory').toggle(showDividerAfterInventory); } /** diff --git a/template.html b/template.html index 76af63e..95188b2 100644 --- a/template.html +++ b/template.html @@ -53,6 +53,9 @@ + +
+
@@ -182,6 +185,19 @@ Show Inventory + + + Single flat list instead of On Person / Stored / Assets categories + + + +