feat: implement Alpine.js settings panel for user preferences and configuration management

This commit is contained in:
2025-11-24 22:14:50 +02:00
parent edcbf0ed2b
commit 7a9deb9fc9
4 changed files with 229 additions and 231 deletions

View File

@@ -184,61 +184,12 @@ document.addEventListener('DOMContentLoaded', function () {
// Settings Modal
const settingsLink = document.getElementById('settings-link');
const settingsApplyBtn = document.getElementById('settings-apply-btn');
const settingsResetBtn = document.getElementById('settings-reset-btn');
const settingsCancelBtn = document.getElementById('settings-cancel-btn');
if (settingsLink) {
settingsLink.addEventListener('click', openSettingsModal);
}
if (settingsCancelBtn) {
settingsCancelBtn.addEventListener('click', closeSettingsModal);
}
if (settingsApplyBtn) {
settingsApplyBtn.addEventListener('click', applySettings);
}
if (settingsResetBtn) {
settingsResetBtn.addEventListener('click', function() {
if (confirm('Reset all settings to defaults? This cannot be undone.')) {
resetSettings();
loadSettingsIntoUI();
Toast.show('Settings reset to defaults', 'success');
}
});
}
// Settings UI interactions
const fontSizeSlider = document.getElementById('setting-font-size');
const fontSizeValue = document.getElementById('setting-font-size-value');
if (fontSizeSlider && fontSizeValue) {
fontSizeSlider.addEventListener('input', function() {
fontSizeValue.textContent = this.value + 'px';
});
}
const renderDebounceSlider = document.getElementById('setting-render-debounce');
const renderDebounceValue = document.getElementById('setting-render-debounce-value');
if (renderDebounceSlider && renderDebounceValue) {
renderDebounceSlider.addEventListener('input', function() {
renderDebounceValue.textContent = this.value + 'ms';
});
}
const dateFormatSelect = document.getElementById('setting-date-format');
const customDateFormatItem = document.getElementById('custom-date-format-item');
if (dateFormatSelect && customDateFormatItem) {
dateFormatSelect.addEventListener('change', function() {
if (this.value === 'custom') {
customDateFormatItem.style.display = 'block';
} else {
customDateFormatItem.style.display = 'none';
}
});
}
// Settings buttons and UI interactions now handled by Alpine.js in settingsPanel() component
// Dataset Manager
const datasetsLink = document.getElementById('datasets-link');
@@ -567,160 +518,12 @@ function registerMonacoKeyboardShortcuts() {
KeyboardActions.publishDraft);
}
// Settings modal functions (special handling for loading settings into UI)
// Settings modal functions (simplified - most logic now in Alpine settingsPanel() component)
function openSettingsModal() {
loadSettingsIntoUI();
ModalManager.open('settings-modal');
// Settings will be loaded via Alpine's init() method
}
function closeSettingsModal() {
ModalManager.close('settings-modal');
}
function loadSettingsIntoUI() {
const settings = getSettings();
// Appearance settings
const uiThemeSelect = document.getElementById('setting-ui-theme');
if (uiThemeSelect) {
uiThemeSelect.value = settings.ui.theme;
}
// Editor settings
const fontSizeSlider = document.getElementById('setting-font-size');
const fontSizeValue = document.getElementById('setting-font-size-value');
if (fontSizeSlider && fontSizeValue) {
fontSizeSlider.value = settings.editor.fontSize;
fontSizeValue.textContent = settings.editor.fontSize + 'px';
}
const editorThemeSelect = document.getElementById('setting-editor-theme');
if (editorThemeSelect) {
editorThemeSelect.value = settings.editor.theme;
}
const tabSizeSelect = document.getElementById('setting-tab-size');
if (tabSizeSelect) {
tabSizeSelect.value = settings.editor.tabSize;
}
const minimapCheckbox = document.getElementById('setting-minimap');
if (minimapCheckbox) {
minimapCheckbox.checked = settings.editor.minimap;
}
const wordWrapCheckbox = document.getElementById('setting-word-wrap');
if (wordWrapCheckbox) {
wordWrapCheckbox.checked = settings.editor.wordWrap === 'on';
}
const lineNumbersCheckbox = document.getElementById('setting-line-numbers');
if (lineNumbersCheckbox) {
lineNumbersCheckbox.checked = settings.editor.lineNumbers === 'on';
}
// Performance settings
const renderDebounceSlider = document.getElementById('setting-render-debounce');
const renderDebounceValue = document.getElementById('setting-render-debounce-value');
if (renderDebounceSlider && renderDebounceValue) {
renderDebounceSlider.value = settings.performance.renderDebounce;
renderDebounceValue.textContent = settings.performance.renderDebounce + 'ms';
}
// Formatting settings
const dateFormatSelect = document.getElementById('setting-date-format');
const customDateFormatItem = document.getElementById('custom-date-format-item');
if (dateFormatSelect) {
dateFormatSelect.value = settings.formatting.dateFormat;
if (customDateFormatItem) {
customDateFormatItem.style.display = settings.formatting.dateFormat === 'custom' ? 'block' : 'none';
}
}
const customDateFormatInput = document.getElementById('setting-custom-date-format');
if (customDateFormatInput) {
customDateFormatInput.value = settings.formatting.customDateFormat;
}
}
function applySettings() {
// Collect values from UI
const newSettings = {
'ui.theme': document.getElementById('setting-ui-theme').value,
'editor.fontSize': parseInt(document.getElementById('setting-font-size').value),
'editor.theme': document.getElementById('setting-editor-theme').value,
'editor.tabSize': parseInt(document.getElementById('setting-tab-size').value),
'editor.minimap': document.getElementById('setting-minimap').checked,
'editor.wordWrap': document.getElementById('setting-word-wrap').checked ? 'on' : 'off',
'editor.lineNumbers': document.getElementById('setting-line-numbers').checked ? 'on' : 'off',
'performance.renderDebounce': parseInt(document.getElementById('setting-render-debounce').value),
'formatting.dateFormat': document.getElementById('setting-date-format').value,
'formatting.customDateFormat': document.getElementById('setting-custom-date-format').value
};
// Validate settings
let hasErrors = false;
for (const [path, value] of Object.entries(newSettings)) {
const errors = validateSetting(path, value);
if (errors.length > 0) {
Toast.show(errors.join(', '), 'error');
hasErrors = true;
break;
}
}
if (hasErrors) {
return;
}
// Save settings
if (updateSettings(newSettings)) {
// Apply theme to document
const uiTheme = newSettings['ui.theme'];
document.documentElement.setAttribute('data-theme', uiTheme);
// Sync editor theme with UI theme
const editorTheme = uiTheme === 'experimental' ? 'vs-dark' : 'vs-light';
newSettings['editor.theme'] = editorTheme;
// Apply editor settings immediately
if (editor) {
editor.updateOptions({
fontSize: newSettings['editor.fontSize'],
theme: editorTheme,
tabSize: newSettings['editor.tabSize'],
minimap: { enabled: newSettings['editor.minimap'] },
wordWrap: newSettings['editor.wordWrap'],
lineNumbers: newSettings['editor.lineNumbers']
});
}
// Update the editor theme in settings
updateSetting('editor.theme', editorTheme);
// Update debounced render function
if (typeof updateRenderDebounce === 'function') {
updateRenderDebounce(newSettings['performance.renderDebounce']);
}
// Re-render snippet list to reflect date format changes
renderSnippetList();
// Update metadata display if a snippet is selected
if (window.currentSnippetId) {
const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
if (snippet) {
document.getElementById('snippet-created').textContent = formatDate(snippet.created, true);
document.getElementById('snippet-modified').textContent = formatDate(snippet.modified, true);
}
}
Toast.show('Settings applied successfully', 'success');
closeSettingsModal();
// Track event
Analytics.track('settings-apply', 'Applied settings');
} else {
Toast.show('Failed to save settings', 'error');
}
}

View File

@@ -222,3 +222,166 @@ function validateSetting(path, value) {
return rules[path] ? rules[path]() : [];
}
// Alpine.js Component for settings panel
// Thin wrapper - Alpine handles form state and reactivity, user-settings.js handles storage
function settingsPanel() {
return {
// Form state (loaded from settings on open)
uiTheme: 'light',
fontSize: 12,
editorTheme: 'vs-light',
tabSize: 2,
minimap: false,
wordWrap: true,
lineNumbers: true,
renderDebounce: 1500,
dateFormat: 'smart',
customDateFormat: 'yyyy-MM-dd HH:mm',
// Original values for dirty checking
originalSettings: null,
// Initialize component with current settings
init() {
this.loadSettings();
},
// Load settings from storage into form
loadSettings() {
const settings = getSettings();
this.uiTheme = settings.ui.theme;
this.fontSize = settings.editor.fontSize;
this.editorTheme = settings.editor.theme;
this.tabSize = settings.editor.tabSize;
this.minimap = settings.editor.minimap;
this.wordWrap = settings.editor.wordWrap === 'on';
this.lineNumbers = settings.editor.lineNumbers === 'on';
this.renderDebounce = settings.performance.renderDebounce;
this.dateFormat = settings.formatting.dateFormat;
this.customDateFormat = settings.formatting.customDateFormat;
// Store original values for dirty checking
this.originalSettings = JSON.stringify(this.getCurrentFormState());
},
// Get current form state as object
getCurrentFormState() {
return {
uiTheme: this.uiTheme,
fontSize: this.fontSize,
editorTheme: this.editorTheme,
tabSize: this.tabSize,
minimap: this.minimap,
wordWrap: this.wordWrap,
lineNumbers: this.lineNumbers,
renderDebounce: this.renderDebounce,
dateFormat: this.dateFormat,
customDateFormat: this.customDateFormat
};
},
// Check if settings have been modified
get isDirty() {
return this.originalSettings !== JSON.stringify(this.getCurrentFormState());
},
// Show custom date format field when 'custom' is selected
get showCustomDateFormat() {
return this.dateFormat === 'custom';
},
// Apply settings and save
apply() {
const newSettings = {
'ui.theme': this.uiTheme,
'editor.fontSize': parseInt(this.fontSize),
'editor.theme': this.editorTheme,
'editor.tabSize': parseInt(this.tabSize),
'editor.minimap': this.minimap,
'editor.wordWrap': this.wordWrap ? 'on' : 'off',
'editor.lineNumbers': this.lineNumbers ? 'on' : 'off',
'performance.renderDebounce': parseInt(this.renderDebounce),
'formatting.dateFormat': this.dateFormat,
'formatting.customDateFormat': this.customDateFormat
};
// Validate settings
let hasErrors = false;
for (const [path, value] of Object.entries(newSettings)) {
const errors = validateSetting(path, value);
if (errors.length > 0) {
Toast.show(errors.join(', '), 'error');
hasErrors = true;
break;
}
}
if (hasErrors) return;
// Save settings
if (updateSettings(newSettings)) {
// Apply theme to document
document.documentElement.setAttribute('data-theme', this.uiTheme);
// Sync editor theme with UI theme
const editorTheme = this.uiTheme === 'experimental' ? 'vs-dark' : 'vs-light';
newSettings['editor.theme'] = editorTheme;
// Apply editor settings immediately
if (editor) {
editor.updateOptions({
fontSize: newSettings['editor.fontSize'],
theme: editorTheme,
tabSize: newSettings['editor.tabSize'],
minimap: { enabled: newSettings['editor.minimap'] },
wordWrap: newSettings['editor.wordWrap'],
lineNumbers: newSettings['editor.lineNumbers']
});
}
// Update the editor theme in settings
updateSetting('editor.theme', editorTheme);
// Update debounced render function
if (typeof updateRenderDebounce === 'function') {
updateRenderDebounce(newSettings['performance.renderDebounce']);
}
// Re-render snippet list to reflect date format changes
renderSnippetList();
// Update metadata display if a snippet is selected
if (window.currentSnippetId) {
const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
if (snippet) {
document.getElementById('snippet-created').textContent = formatDate(snippet.created, true);
document.getElementById('snippet-modified').textContent = formatDate(snippet.modified, true);
}
}
Toast.success('Settings applied successfully');
closeSettingsModal();
// Track event
Analytics.track('settings-apply', 'Applied settings');
} else {
Toast.error('Failed to save settings');
}
},
// Reset to defaults
reset() {
if (confirm('Reset all settings to defaults? This cannot be undone.')) {
resetSettings();
this.loadSettings();
Toast.success('Settings reset to defaults');
}
},
// Cancel changes and close modal
cancel() {
closeSettingsModal();
}
};
}