diff --git a/src/js/app.js b/src/js/app.js
index 460e997..bfe532d 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -12,6 +12,12 @@ document.addEventListener('DOMContentLoaded', function () {
renderSnippetList();
+ // Auto-select first snippet on page load
+ const firstSnippet = SnippetStorage.listSnippets()[0];
+ if (firstSnippet) {
+ selectSnippet(firstSnippet.id);
+ }
+
// Load saved layout
loadLayoutFromStorage();
@@ -86,4 +92,17 @@ document.addEventListener('DOMContentLoaded', function () {
alert('Coming soon in a future phase!');
});
});
+
+ // View mode toggle buttons
+ document.getElementById('view-draft').addEventListener('click', () => {
+ switchViewMode('draft');
+ });
+
+ document.getElementById('view-published').addEventListener('click', () => {
+ switchViewMode('published');
+ });
+
+ // Publish and Revert buttons
+ document.getElementById('publish-btn').addEventListener('click', publishDraft);
+ document.getElementById('revert-btn').addEventListener('click', revertDraft);
});
diff --git a/src/js/config.js b/src/js/config.js
index bbb2911..8c2c05c 100644
--- a/src/js/config.js
+++ b/src/js/config.js
@@ -1,6 +1,7 @@
// Global variables and configuration
let editor; // Global editor instance
let renderTimeout; // For debouncing
+let currentViewMode = 'draft'; // Track current view mode: 'draft' or 'published'
// Panel resizing variables
let isResizing = false;
diff --git a/src/js/snippet-manager.js b/src/js/snippet-manager.js
index f2f363b..e97afd3 100644
--- a/src/js/snippet-manager.js
+++ b/src/js/snippet-manager.js
@@ -234,10 +234,17 @@ function renderSnippetList(searchQuery = null) {
dateText = formatSnippetDate(snippet.modified);
}
+ // Determine status: green if no draft changes, yellow if has draft
+ const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
+ const statusClass = hasDraft ? 'draft' : 'published';
+
return `
- ${snippet.name}
- ${dateText}
+
+
${snippet.name}
+
${dateText}
+
+
`;
}).join('');
@@ -441,12 +448,9 @@ function selectSnippet(snippetId) {
});
document.querySelector(`[data-snippet-id="${snippetId}"]`).classList.add('selected');
- // Load draft spec into editor (prevent auto-save during update)
- if (editor) {
- window.isUpdatingEditor = true;
- editor.setValue(JSON.stringify(snippet.draftSpec, null, 2));
- window.isUpdatingEditor = false;
- }
+ // Load spec based on current view mode
+ loadSnippetIntoEditor(snippet);
+ updateViewModeUI(snippet);
// Show and populate meta fields
const metaSection = document.getElementById('snippet-meta');
@@ -484,6 +488,9 @@ window.isUpdatingEditor = false; // Global flag to prevent auto-save/debounce du
function autoSaveDraft() {
if (!window.currentSnippetId || !editor) return;
+ // Only save to draft if we're in draft mode
+ if (currentViewMode !== 'draft') return;
+
try {
const currentSpec = JSON.parse(editor.getValue());
const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
@@ -491,6 +498,15 @@ function autoSaveDraft() {
if (snippet) {
snippet.draftSpec = currentSpec;
SnippetStorage.saveSnippet(snippet);
+
+ // Refresh snippet list to update status light
+ renderSnippetList();
+ // Restore selection
+ const selectedItem = document.querySelector(`[data-snippet-id="${window.currentSnippetId}"]`);
+ if (selectedItem) selectedItem.classList.add('selected');
+
+ // Update button states
+ updateViewModeUI(snippet);
}
} catch (error) {
// Ignore JSON parse errors during editing
@@ -502,6 +518,20 @@ function debouncedAutoSave() {
// Don't auto-save if we're programmatically updating the editor
if (window.isUpdatingEditor) return;
+ // If viewing published and no draft exists, create draft automatically
+ if (currentViewMode === 'published' && window.currentSnippetId) {
+ const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
+ if (snippet) {
+ const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
+ if (!hasDraft) {
+ // No draft exists, automatically switch to draft mode
+ currentViewMode = 'draft';
+ updateViewModeUI(snippet);
+ editor.updateOptions({ readOnly: false });
+ }
+ }
+ }
+
clearTimeout(autoSaveTimeout);
autoSaveTimeout = setTimeout(autoSaveDraft, 1000); // 1 second delay
}
@@ -659,3 +689,112 @@ function renameSnippet(snippetId, newName) {
return true;
}
+// Load snippet into editor based on view mode
+function loadSnippetIntoEditor(snippet) {
+ if (!editor) return;
+
+ window.isUpdatingEditor = true;
+
+ const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
+
+ if (currentViewMode === 'draft') {
+ editor.setValue(JSON.stringify(snippet.draftSpec, null, 2));
+ editor.updateOptions({ readOnly: false });
+ } else {
+ // Published view - always read-only if draft exists
+ editor.setValue(JSON.stringify(snippet.spec, null, 2));
+ editor.updateOptions({ readOnly: hasDraft });
+ }
+
+ window.isUpdatingEditor = false;
+}
+
+// Update view mode UI (buttons and editor state)
+function updateViewModeUI(snippet) {
+ const draftBtn = document.getElementById('view-draft');
+ const publishedBtn = document.getElementById('view-published');
+ const publishBtn = document.getElementById('publish-btn');
+ const revertBtn = document.getElementById('revert-btn');
+
+ // Update toggle button states
+ if (currentViewMode === 'draft') {
+ draftBtn.classList.add('active');
+ publishedBtn.classList.remove('active');
+ } else {
+ draftBtn.classList.remove('active');
+ publishedBtn.classList.add('active');
+ }
+
+ // Show/hide and enable/disable action buttons based on mode
+ const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
+
+ if (currentViewMode === 'draft') {
+ // In draft mode: show both buttons, enable based on draft existence
+ publishBtn.classList.add('visible');
+ revertBtn.classList.add('visible');
+ publishBtn.disabled = !hasDraft;
+ revertBtn.disabled = !hasDraft;
+ } else {
+ // In published mode: hide both buttons
+ publishBtn.classList.remove('visible');
+ revertBtn.classList.remove('visible');
+ }
+}
+
+// Switch view mode
+function switchViewMode(mode) {
+ if (!window.currentSnippetId) return;
+
+ currentViewMode = mode;
+ const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
+ if (snippet) {
+ loadSnippetIntoEditor(snippet);
+ updateViewModeUI(snippet);
+ }
+}
+
+// Publish draft to spec
+function publishDraft() {
+ if (!window.currentSnippetId) return;
+
+ const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
+ if (!snippet) return;
+
+ // Copy draftSpec to spec
+ snippet.spec = JSON.parse(JSON.stringify(snippet.draftSpec));
+ SnippetStorage.saveSnippet(snippet);
+
+ // Refresh UI
+ renderSnippetList();
+ const selectedItem = document.querySelector(`[data-snippet-id="${window.currentSnippetId}"]`);
+ if (selectedItem) selectedItem.classList.add('selected');
+
+ updateViewModeUI(snippet);
+}
+
+// Revert draft to published spec
+function revertDraft() {
+ if (!window.currentSnippetId) return;
+
+ const snippet = SnippetStorage.getSnippet(window.currentSnippetId);
+ if (!snippet) return;
+
+ if (confirm('Revert all draft changes to last published version? This cannot be undone.')) {
+ // Copy spec to draftSpec
+ snippet.draftSpec = JSON.parse(JSON.stringify(snippet.spec));
+ SnippetStorage.saveSnippet(snippet);
+
+ // Reload editor if in draft view
+ if (currentViewMode === 'draft') {
+ loadSnippetIntoEditor(snippet);
+ }
+
+ // Refresh UI
+ renderSnippetList();
+ const selectedItem = document.querySelector(`[data-snippet-id="${window.currentSnippetId}"]`);
+ if (selectedItem) selectedItem.classList.add('selected');
+
+ updateViewModeUI(snippet);
+ }
+}
+
diff --git a/src/styles.css b/src/styles.css
index d7b336b..adec5f4 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -108,12 +108,125 @@ body {
}
.panel-header {
- padding: 8px 12px;
+ padding: 6px 12px;
background: #c0c0c0;
border-bottom: 1px solid #808080;
font-weight: normal;
font-size: 12px;
color: #000000;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 28px;
+ box-sizing: border-box;
+}
+
+.editor-controls {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ height: 20px;
+}
+
+.view-label {
+ font-size: 10px;
+ color: #000000;
+ margin-right: 4px;
+}
+
+.view-toggle-group {
+ display: flex;
+}
+
+.view-toggle-btn {
+ background: #c0c0c0;
+ border: 1px solid #808080;
+ color: #000000;
+ padding: 2px 8px;
+ cursor: pointer;
+ font-size: 10px;
+ font-family: 'MS Sans Serif', Tahoma, sans-serif;
+ height: 20px;
+ box-sizing: border-box;
+}
+
+.view-toggle-btn:first-child {
+ border-right: 1px solid #808080;
+}
+
+.view-toggle-btn:last-child {
+ border-left: none;
+}
+
+.view-toggle-btn:hover:not(.active) {
+ background: #d4d0c8;
+}
+
+.view-toggle-btn:active {
+ background: #316ac5;
+ color: #ffffff;
+}
+
+.view-toggle-btn.active {
+ background: #316ac5;
+ color: #ffffff;
+ border-top: 1px solid #0a246a;
+ border-left: 1px solid #0a246a;
+ border-bottom: 1px solid #4a7ac5;
+ border-right: 1px solid #4a7ac5;
+}
+
+.view-toggle-btn.active:first-child {
+ border-right: 1px solid #4a7ac5;
+}
+
+.view-toggle-btn.active:last-child {
+ border-left: 1px solid #0a246a;
+}
+
+.action-btn {
+ border: 2px outset #c0c0c0;
+ color: #000000;
+ padding: 2px 8px;
+ cursor: pointer;
+ font-size: 10px;
+ font-family: 'MS Sans Serif', Tahoma, sans-serif;
+ display: none;
+ height: 20px;
+ box-sizing: border-box;
+}
+
+.action-btn.visible {
+ display: block;
+}
+
+.action-btn:hover {
+ filter: brightness(1.1);
+}
+
+.action-btn:active {
+ border-style: inset;
+}
+
+.action-btn:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+.publish-btn {
+ background: #90ee90;
+}
+
+.publish-btn:hover {
+ background: #a0ffa0;
+}
+
+.revert-btn {
+ background: #ffb080;
+}
+
+.revert-btn:hover {
+ background: #ffc090;
}
/* Sort controls */
@@ -287,10 +400,35 @@ body {
margin-bottom: 2px;
cursor: pointer;
background: #ffffff;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.snippet-info {
+ flex: 1;
+}
+
+.snippet-status {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ margin-left: 8px;
+}
+
+.snippet-status.published {
+ background: #00ff00;
+ box-shadow: 0 0 2px #00cc00;
+}
+
+.snippet-status.draft {
+ background: #ffff00;
+ box-shadow: 0 0 2px #cccc00;
}
.snippet-item:hover {
- background: #316ac5;
+ background: #6a9ad5;
color: #ffffff;
}
@@ -299,6 +437,10 @@ body {
color: #ffffff;
}
+.snippet-item.selected:hover {
+ background: #316ac5;
+}
+
.snippet-name {
font-weight: normal;
font-size: 12px;