Fix code review findings for Story 3.3: Preset Import & Export
Security & Quality Improvements:
- Fix XSS vulnerabilities in PresetImportDialog, PresetExportDialog, and templates
- Add resource leak protection in downloadExportFile() with try/finally
- Fix encapsulation violation by using public API instead of _presetsCache
- Add rollback mechanism for partial failures in replace mode
- Add preset name validation (length, characters, empty check)
- Add duplicate name detection within import files
- Add file size validation (5MB limit) and type validation
- Fix event listener leaks with proper cleanup in _onRender/_onClose
- Add constructor parameter validation for all dialogs
Acceptance Criteria Compliance:
- Fix AC-2: Export filename now uses world name (via parent.name)
- Fix AC-6: Error message matches spec exactly ('Import failed: invalid JSON format')
- Fix AC-8: Merge/Replace messages match spec format
Code Quality:
- Add shared HTML escaping utilities (src/utils/html.js)
- Consolidate duplicate localization strings (removed 28 duplicates from SCRYING_POOL)
- Use SCENE_PRESET_VERSION constant instead of hardcoded 1
- Handle null options in importPresets()
- Graceful handling of skipValidation with invalid data
Test Results: 679 passed, 3 failed (pre-existing in DirectorsBoard)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -1,29 +1,29 @@
|
||||
<div class="sp-dialog-content">
|
||||
<p class="sp-export-description">
|
||||
{{localize "SCRYING_POOL.ExportPresetsDescription"}}
|
||||
{{localize "video-view-manager.presetExport.description"}}
|
||||
</p>
|
||||
|
||||
<div class="sp-export-info">
|
||||
<span class="sp-info-label">{{localize "SCRYING_POOL.Scene"}}:</span>
|
||||
<span class="sp-info-label">{{localize "video-view-manager.presetExport.scene"}}:</span>
|
||||
<span class="sp-info-value">{{sceneName}}</span>
|
||||
</div>
|
||||
|
||||
<div class="sp-export-info">
|
||||
<span class="sp-info-label">{{localize "SCRYING_POOL.PresetCount"}}:</span>
|
||||
<span class="sp-info-label">{{localize "video-view-manager.presetExport.presetCount"}}:</span>
|
||||
<span class="sp-info-value">{{presetCount}}</span>
|
||||
</div>
|
||||
|
||||
<div class="sp-export-info">
|
||||
<span class="sp-info-label">{{localize "SCRYING_POOL.Filename"}}:</span>
|
||||
<span class="sp-info-label">{{localize "video-view-manager.presetExport.filename"}}:</span>
|
||||
<span class="sp-info-value sp-filename">{{filename}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sp-dialog-buttons">
|
||||
<button type="button" class="sp-btn sp-btn-primary sp-export-btn">
|
||||
<i class="fas fa-download"></i> {{localize "SCRYING_POOL.Export"}}
|
||||
<i class="fas fa-download"></i> {{localize "video-view-manager.presetExport.export"}}
|
||||
</button>
|
||||
<button type="button" class="sp-btn sp-btn-secondary" data-action="close">
|
||||
{{localize "SCRYING_POOL.Cancel"}}
|
||||
{{localize "video-view-manager.presetExport.cancel"}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+12
-12
@@ -1,23 +1,23 @@
|
||||
<div class="sp-dialog-content">
|
||||
<p class="sp-import-description">
|
||||
{{localize "SCRYING_POOL.ImportPresetsDescription"}}
|
||||
{{localize "video-view-manager.presetImport.description"}}
|
||||
</p>
|
||||
|
||||
{{#if hasExistingPresets}}
|
||||
<div class="sp-warning-box">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{{localize "SCRYING_POOL.ExistingPresetsWarning" existingPresetCount=existingPresetCount}}</span>
|
||||
<span>{{localize "video-view-manager.presetImport.existingPresetsWarning" existingPresetCount=existingPresetCount}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{!-- File Selection --}}
|
||||
<div class="sp-form-group">
|
||||
<label class="sp-form-label">{{localize "SCRYING_POOL.SelectFile"}}</label>
|
||||
<label class="sp-form-label">{{localize "video-view-manager.presetImport.selectFile"}}</label>
|
||||
<div class="sp-file-upload">
|
||||
<input type="file" class="sp-file-input" accept=".json" />
|
||||
<label class="sp-file-label">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span class="sp-file-text">{{localize "SCRYING_POOL.ChooseFile"}}</span>
|
||||
<span class="sp-file-text">{{localize "video-view-manager.presetImport.chooseFile"}}</span>
|
||||
</label>
|
||||
</div>
|
||||
{{#if selectedFileName}}
|
||||
@@ -30,17 +30,17 @@
|
||||
|
||||
{{!-- Mode Selection --}}
|
||||
<div class="sp-form-group">
|
||||
<label class="sp-form-label">{{localize "SCRYING_POOL.ImportMode"}}</label>
|
||||
<label class="sp-form-label">{{localize "video-view-manager.presetImport.importMode"}}</label>
|
||||
<div class="sp-radio-group">
|
||||
<label class="sp-radio-label">
|
||||
<input type="radio" name="import-mode" class="sp-mode-merge" value="merge" {{checked (eq mode "merge")}} />
|
||||
<span class="sp-radio-text">{{mergeLabel}}</span>
|
||||
<span class="sp-radio-hint">{{localize "SCRYING_POOL.ImportModeMergeHint"}}</span>
|
||||
<span class="sp-radio-hint">{{localize "video-view-manager.presetImport.importModeMergeHint"}}</span>
|
||||
</label>
|
||||
<label class="sp-radio-label">
|
||||
<input type="radio" name="import-mode" class="sp-mode-replace" value="replace" {{checked (eq mode "replace")}} />
|
||||
<span class="sp-radio-text">{{replaceLabel}}</span>
|
||||
<span class="sp-radio-hint">{{localize "SCRYING_POOL.ImportModeReplaceHint"}}</span>
|
||||
<span class="sp-radio-hint">{{localize "video-view-manager.presetImport.importModeReplaceHint"}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
{{!-- Preview Section --}}
|
||||
{{#if previewItems.length}}
|
||||
<div class="sp-preview-section">
|
||||
<h3 class="sp-preview-title">{{localize "SCRYING_POOL.PreviewTitle"}}</h3>
|
||||
<h3 class="sp-preview-title">{{localize "video-view-manager.presetImport.previewTitle"}}</h3>
|
||||
<ul class="sp-preview-list">
|
||||
{{#each previewItems as |item|}}
|
||||
<li class="sp-preview-item {{unless item.valid 'sp-preview-item--invalid'}}">
|
||||
@@ -68,7 +68,7 @@
|
||||
<div class="sp-confirmation-section">
|
||||
<div class="sp-confirmation-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{{localize "SCRYING_POOL.ReplaceConfirmation" existingPresetCount=existingPresetCount}}</span>
|
||||
<span>{{localize "video-view-manager.presetImport.replaceConfirmation" existingPresetCount=existingPresetCount}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -77,14 +77,14 @@
|
||||
<div class="sp-dialog-buttons">
|
||||
{{#unless requiresConfirmation}}
|
||||
<button type="button" class="sp-btn sp-btn-primary sp-import-btn" {{disabled (not previewItems.length) }}>
|
||||
<i class="fas fa-file-import"></i> {{localize "SCRYING_POOL.Import"}}
|
||||
<i class="fas fa-file-import"></i> {{localize "video-view-manager.presetImport.import"}}
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" class="sp-btn sp-btn-danger sp-confirm-btn">
|
||||
<i class="fas fa-check"></i> {{localize "SCRYING_POOL.ConfirmReplace"}}
|
||||
<i class="fas fa-check"></i> {{localize "video-view-manager.presetImport.confirmReplace"}}
|
||||
</button>
|
||||
{{/unless}}
|
||||
<button type="button" class="sp-btn sp-btn-secondary sp-cancel-btn">
|
||||
{{localize "SCRYING_POOL.Cancel"}}
|
||||
{{localize "video-view-manager.presetImport.cancel"}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user