treesap 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ChatInput.d.ts +7 -0
- package/dist/components/ChatInput.d.ts.map +1 -0
- package/dist/components/ChatInput.js +11 -0
- package/dist/components/ChatInput.js.map +1 -0
- package/dist/components/Sidebar.d.ts +8 -0
- package/dist/components/Sidebar.d.ts.map +1 -0
- package/dist/components/Sidebar.js +7 -0
- package/dist/components/Sidebar.js.map +1 -0
- package/dist/components/SimpleLivePreview.js +1 -1
- package/dist/components/SimpleLivePreview.js.map +1 -1
- package/dist/pages/Code.d.ts.map +1 -1
- package/dist/pages/Code.js +2 -2
- package/dist/pages/Code.js.map +1 -1
- package/dist/services/websocket.d.ts.map +1 -1
- package/dist/services/websocket.js +1 -2
- package/dist/services/websocket.js.map +1 -1
- package/dist/static/components/ChatInput.js +237 -0
- package/dist/static/components/Sidebar.js +225 -0
- package/dist/static/components/SimpleLivePreview.js +73 -53
- package/dist/static/components/Terminal.js +143 -61
- package/dist/static/signals/SidebarSignal.js +123 -0
- package/dist/static/signals/TerminalSignal.js +137 -2
- package/dist/static/styles/main.css +180 -0
- package/package.json +1 -1
- package/src/components/ChatInput.tsx +56 -0
- package/src/components/Sidebar.tsx +99 -0
- package/src/components/SimpleLivePreview.tsx +4 -4
- package/src/pages/Code.tsx +18 -55
- package/src/services/websocket.ts +1 -5
- package/src/static/components/ChatInput.js +237 -0
- package/src/static/components/Sidebar.js +225 -0
- package/src/static/components/SimpleLivePreview.js +73 -53
- package/src/static/components/Terminal.js +143 -61
- package/src/static/signals/SidebarSignal.js +123 -0
- package/src/static/signals/TerminalSignal.js +137 -2
- package/src/static/styles/main.css +180 -0
- package/tailwind.config.ts +10 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// ChatInput component JavaScript for chat-style terminal input
|
|
2
|
+
class ChatInputManager {
|
|
3
|
+
constructor(chatInputId) {
|
|
4
|
+
this.chatInputId = chatInputId;
|
|
5
|
+
this.container = document.getElementById(chatInputId);
|
|
6
|
+
this.textarea = document.getElementById(`${chatInputId}-textarea`);
|
|
7
|
+
this.sendBtn = document.getElementById(`${chatInputId}-send-btn`);
|
|
8
|
+
this.executeBtn = document.getElementById(`${chatInputId}-execute-btn`);
|
|
9
|
+
|
|
10
|
+
// Get chat input data from window
|
|
11
|
+
const chatInputData = window[`chatInputData_${chatInputId.replace(/-/g, '_')}`];
|
|
12
|
+
if (!chatInputData) {
|
|
13
|
+
console.error(`No chat input data found for ${chatInputId}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.terminalId = chatInputData.terminalId;
|
|
18
|
+
|
|
19
|
+
console.log(`ChatInput ${chatInputId} initialized for terminal:`, this.terminalId);
|
|
20
|
+
|
|
21
|
+
this.init();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
init() {
|
|
25
|
+
if (!this.container || !this.textarea || !this.sendBtn || !this.executeBtn) {
|
|
26
|
+
console.error('ChatInput elements not found!', {
|
|
27
|
+
container: !!this.container,
|
|
28
|
+
textarea: !!this.textarea,
|
|
29
|
+
sendBtn: !!this.sendBtn,
|
|
30
|
+
executeBtn: !!this.executeBtn
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.setupEventListeners();
|
|
36
|
+
this.setupAutoResize();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setupEventListeners() {
|
|
40
|
+
// Handle send to input button click
|
|
41
|
+
this.sendBtn.addEventListener('click', () => {
|
|
42
|
+
this.sendToInput();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle execute button click
|
|
46
|
+
this.executeBtn.addEventListener('click', () => {
|
|
47
|
+
this.executeCommand();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Handle textarea key events
|
|
51
|
+
this.textarea.addEventListener('keydown', (e) => {
|
|
52
|
+
// Enter without shift sends to input field
|
|
53
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
this.sendToInput();
|
|
56
|
+
}
|
|
57
|
+
// Ctrl+Enter or Cmd+Enter executes
|
|
58
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
this.executeCommand();
|
|
61
|
+
}
|
|
62
|
+
// Allow Shift+Enter for new lines (default behavior)
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handle input to auto-resize textarea
|
|
66
|
+
this.textarea.addEventListener('input', () => {
|
|
67
|
+
this.adjustTextareaHeight();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Disable buttons when textarea is empty
|
|
71
|
+
this.textarea.addEventListener('input', () => {
|
|
72
|
+
this.updateButtonState();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Focus textarea when container is clicked
|
|
76
|
+
this.container.addEventListener('click', (e) => {
|
|
77
|
+
if (e.target === this.container) {
|
|
78
|
+
this.textarea.focus();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setupAutoResize() {
|
|
84
|
+
// Initial state
|
|
85
|
+
this.adjustTextareaHeight();
|
|
86
|
+
this.updateButtonState();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
adjustTextareaHeight() {
|
|
90
|
+
if (!this.textarea) return;
|
|
91
|
+
|
|
92
|
+
// Reset height to auto to get the correct scrollHeight
|
|
93
|
+
this.textarea.style.height = 'auto';
|
|
94
|
+
|
|
95
|
+
// Calculate the new height based on content
|
|
96
|
+
const scrollHeight = this.textarea.scrollHeight;
|
|
97
|
+
const maxHeight = 120; // Max height from CSS
|
|
98
|
+
const minHeight = 40; // Min height from CSS
|
|
99
|
+
|
|
100
|
+
const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
|
|
101
|
+
this.textarea.style.height = `${newHeight}px`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateButtonState() {
|
|
105
|
+
if (!this.textarea || !this.sendBtn || !this.executeBtn) return;
|
|
106
|
+
|
|
107
|
+
const hasContent = this.textarea.value.trim().length > 0;
|
|
108
|
+
this.sendBtn.disabled = !hasContent;
|
|
109
|
+
this.executeBtn.disabled = false; // Execute button is always enabled
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sendToInput() {
|
|
113
|
+
if (!this.textarea) return;
|
|
114
|
+
|
|
115
|
+
const input = this.textarea.value.trim();
|
|
116
|
+
if (!input) return;
|
|
117
|
+
|
|
118
|
+
// Get the terminal manager for the associated terminal
|
|
119
|
+
const terminalManager = this.getTerminalManager();
|
|
120
|
+
if (!terminalManager) {
|
|
121
|
+
console.error(`No terminal manager found for terminal: ${this.terminalId}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Send the input to the terminal input field (without executing)
|
|
126
|
+
console.log(`Sending text to terminal input field ${this.terminalId}:`, input);
|
|
127
|
+
console.log('ChatInput sending (programmatic):', JSON.stringify(input), 'char codes:', input.split('').map(c => c.charCodeAt(0)));
|
|
128
|
+
|
|
129
|
+
// Simplest approach: just send the text directly
|
|
130
|
+
// This should work for most cases in Claude Code
|
|
131
|
+
terminalManager.sendInput(input);
|
|
132
|
+
|
|
133
|
+
// Clear the textarea
|
|
134
|
+
this.textarea.value = '';
|
|
135
|
+
this.adjustTextareaHeight();
|
|
136
|
+
this.updateButtonState();
|
|
137
|
+
|
|
138
|
+
// Focus back to textarea for next input
|
|
139
|
+
this.textarea.focus();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
executeCommand() {
|
|
143
|
+
// Get the terminal manager for the associated terminal
|
|
144
|
+
const terminalManager = this.getTerminalManager();
|
|
145
|
+
if (!terminalManager) {
|
|
146
|
+
console.error(`No terminal manager found for terminal: ${this.terminalId}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Execute whatever is currently in the terminal input field
|
|
151
|
+
console.log(`Executing command in terminal ${this.terminalId}`);
|
|
152
|
+
console.log('ChatInput executing (programmatic):', JSON.stringify('\r'), 'char code:', '\r'.charCodeAt(0));
|
|
153
|
+
terminalManager.sendInput('\r');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getTerminalManager() {
|
|
157
|
+
// Access global terminal managers
|
|
158
|
+
if (window.terminalManagers && window.terminalManagers.has(this.terminalId)) {
|
|
159
|
+
return window.terminalManagers.get(this.terminalId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Fallback: try to find by checking all managers
|
|
163
|
+
if (window.terminalManagers) {
|
|
164
|
+
for (const [terminalId, manager] of window.terminalManagers.entries()) {
|
|
165
|
+
if (terminalId === this.terminalId) {
|
|
166
|
+
return manager;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If not found, log available terminals for debugging
|
|
172
|
+
if (window.terminalManagers) {
|
|
173
|
+
console.log('Available terminal managers:', Array.from(window.terminalManagers.keys()));
|
|
174
|
+
console.log('Looking for terminal ID:', this.terminalId);
|
|
175
|
+
} else {
|
|
176
|
+
console.log('No terminal managers found in window object');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
focus() {
|
|
183
|
+
if (this.textarea) {
|
|
184
|
+
this.textarea.focus();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
destroy() {
|
|
189
|
+
// Clean up event listeners if needed
|
|
190
|
+
// The component will be removed from DOM, so most cleanup is automatic
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Auto-initialize when script loads
|
|
195
|
+
console.log('ChatInput.js loaded, looking for chat input containers...');
|
|
196
|
+
let chatInputManagers = new Map();
|
|
197
|
+
|
|
198
|
+
function initializeChatInputs() {
|
|
199
|
+
// Look for all sapling-islands with chat input content
|
|
200
|
+
const saplingIslands = document.querySelectorAll('sapling-island');
|
|
201
|
+
|
|
202
|
+
for (const island of saplingIslands) {
|
|
203
|
+
// Look for chat input container
|
|
204
|
+
const chatInputDiv = island.querySelector('div[id*="chat-input"]');
|
|
205
|
+
if (chatInputDiv && chatInputDiv.id) {
|
|
206
|
+
const chatInputId = chatInputDiv.id;
|
|
207
|
+
console.log('Found chat input component with ID:', chatInputId);
|
|
208
|
+
|
|
209
|
+
// Check if we already have a manager for this chat input
|
|
210
|
+
if (!chatInputManagers.has(chatInputId)) {
|
|
211
|
+
const manager = new ChatInputManager(chatInputId);
|
|
212
|
+
chatInputManagers.set(chatInputId, manager);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(`Initialized ${chatInputManagers.size} chat input(s)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Try immediate initialization
|
|
221
|
+
if (document.readyState === 'loading') {
|
|
222
|
+
document.addEventListener('DOMContentLoaded', initializeChatInputs);
|
|
223
|
+
} else {
|
|
224
|
+
initializeChatInputs();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Make available globally
|
|
228
|
+
window.chatInputManagers = chatInputManagers;
|
|
229
|
+
window.ChatInputManager = ChatInputManager;
|
|
230
|
+
window.initializeChatInputs = initializeChatInputs;
|
|
231
|
+
|
|
232
|
+
// Cleanup on page unload
|
|
233
|
+
window.addEventListener('beforeunload', () => {
|
|
234
|
+
for (const manager of chatInputManagers.values()) {
|
|
235
|
+
manager.destroy();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Sidebar component JavaScript for responsive behavior
|
|
2
|
+
import { sidebarStore } from '/signals/SidebarSignal.js';
|
|
3
|
+
|
|
4
|
+
class SidebarManager {
|
|
5
|
+
constructor(id = 'sidebar') {
|
|
6
|
+
this.id = id;
|
|
7
|
+
|
|
8
|
+
// DOM elements
|
|
9
|
+
this.backdrop = document.getElementById(`${id}-backdrop`);
|
|
10
|
+
this.pane = document.getElementById(`${id}-pane`);
|
|
11
|
+
this.closeBtn = document.getElementById(`${id}-close-btn`);
|
|
12
|
+
this.refreshBtn = document.getElementById('live-preview-refresh-btn');
|
|
13
|
+
this.urlInput = document.getElementById('live-preview-url-input');
|
|
14
|
+
this.loadBtn = document.getElementById('live-preview-load-btn');
|
|
15
|
+
|
|
16
|
+
// Reference to the sidebar store
|
|
17
|
+
this.store = sidebarStore;
|
|
18
|
+
|
|
19
|
+
this.init();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
init() {
|
|
23
|
+
console.log('Initializing Sidebar:', this.id);
|
|
24
|
+
console.log('Elements found:', {
|
|
25
|
+
backdrop: !!this.backdrop,
|
|
26
|
+
pane: !!this.pane,
|
|
27
|
+
closeBtn: !!this.closeBtn,
|
|
28
|
+
refreshBtn: !!this.refreshBtn,
|
|
29
|
+
urlInput: !!this.urlInput,
|
|
30
|
+
loadBtn: !!this.loadBtn
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Set up event listeners
|
|
34
|
+
this.setupEventListeners();
|
|
35
|
+
|
|
36
|
+
// Subscribe to store state changes
|
|
37
|
+
this.subscribeToStore();
|
|
38
|
+
|
|
39
|
+
// Initial state update
|
|
40
|
+
this.updateSidebarState();
|
|
41
|
+
this.updateMobileToggle();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setupEventListeners() {
|
|
45
|
+
// Mobile close button
|
|
46
|
+
this.closeBtn?.addEventListener('click', () => this.store.close());
|
|
47
|
+
|
|
48
|
+
// Backdrop click to close
|
|
49
|
+
this.backdrop?.addEventListener('click', () => this.store.close());
|
|
50
|
+
|
|
51
|
+
// Mobile toggle button (in main layout)
|
|
52
|
+
const mobileToggle = document.getElementById('mobile-sidebar-toggle');
|
|
53
|
+
mobileToggle?.addEventListener('click', () => this.store.toggle());
|
|
54
|
+
|
|
55
|
+
// Refresh button
|
|
56
|
+
this.refreshBtn?.addEventListener('click', () => this.refreshPreview());
|
|
57
|
+
|
|
58
|
+
// URL navigation
|
|
59
|
+
this.loadBtn?.addEventListener('click', (e) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
this.loadUrl();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.urlInput?.addEventListener('keypress', (e) => {
|
|
65
|
+
if (e.key === 'Enter') {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
this.loadUrl();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Keyboard shortcuts
|
|
72
|
+
document.addEventListener('keydown', (e) => {
|
|
73
|
+
// Escape key to close sidebar on mobile
|
|
74
|
+
if (e.key === 'Escape' && this.store.isMobile.value && this.store.isOpen.value) {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
this.store.close();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Cmd/Ctrl + B to toggle sidebar
|
|
80
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
this.store.toggle();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Listen for custom events to maintain backward compatibility
|
|
87
|
+
document.addEventListener('sidebar:toggle', () => this.store.toggle());
|
|
88
|
+
document.addEventListener('sidebar:open', () => this.store.open());
|
|
89
|
+
document.addEventListener('sidebar:close', () => this.store.close());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
subscribeToStore() {
|
|
93
|
+
// Subscribe to store changes and update UI accordingly
|
|
94
|
+
this.store.isOpen.subscribe(() => this.updateSidebarState());
|
|
95
|
+
this.store.isMobile.subscribe(() => this.updateSidebarState());
|
|
96
|
+
this.store.shouldShowBackdrop.subscribe(() => this.updateSidebarState());
|
|
97
|
+
this.store.shouldShowMobileToggle.subscribe(() => this.updateMobileToggle());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
updateSidebarState() {
|
|
101
|
+
if (!this.pane || !this.backdrop) return;
|
|
102
|
+
|
|
103
|
+
const isOpen = this.store.isOpen.value;
|
|
104
|
+
const isMobile = this.store.isMobile.value;
|
|
105
|
+
const shouldShowBackdrop = this.store.shouldShowBackdrop.value;
|
|
106
|
+
|
|
107
|
+
if (isMobile) {
|
|
108
|
+
// Mobile behavior: overlay
|
|
109
|
+
if (shouldShowBackdrop) {
|
|
110
|
+
// Show backdrop
|
|
111
|
+
this.backdrop.classList.remove('opacity-0', 'pointer-events-none');
|
|
112
|
+
this.backdrop.classList.add('opacity-100');
|
|
113
|
+
|
|
114
|
+
// Prevent body scroll
|
|
115
|
+
document.body.style.overflow = 'hidden';
|
|
116
|
+
} else {
|
|
117
|
+
// Hide backdrop
|
|
118
|
+
this.backdrop.classList.remove('opacity-100');
|
|
119
|
+
this.backdrop.classList.add('opacity-0', 'pointer-events-none');
|
|
120
|
+
|
|
121
|
+
// Restore body scroll
|
|
122
|
+
document.body.style.overflow = '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isOpen) {
|
|
126
|
+
// Show sidebar
|
|
127
|
+
this.pane.classList.remove('-translate-x-full');
|
|
128
|
+
this.pane.classList.add('translate-x-0');
|
|
129
|
+
} else {
|
|
130
|
+
// Hide sidebar
|
|
131
|
+
this.pane.classList.remove('translate-x-0');
|
|
132
|
+
this.pane.classList.add('-translate-x-full');
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
// Desktop behavior: side panel
|
|
136
|
+
// Hide backdrop (not needed on desktop)
|
|
137
|
+
this.backdrop.classList.add('opacity-0', 'pointer-events-none');
|
|
138
|
+
|
|
139
|
+
// Restore body scroll
|
|
140
|
+
document.body.style.overflow = '';
|
|
141
|
+
|
|
142
|
+
if (isOpen) {
|
|
143
|
+
// Show sidebar
|
|
144
|
+
this.pane.classList.remove('-translate-x-full');
|
|
145
|
+
this.pane.classList.add('translate-x-0');
|
|
146
|
+
this.pane.style.display = '';
|
|
147
|
+
} else {
|
|
148
|
+
// Hide sidebar completely on desktop
|
|
149
|
+
this.pane.style.display = 'none';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
updateMobileToggle() {
|
|
155
|
+
const mobileToggle = document.getElementById('mobile-sidebar-toggle');
|
|
156
|
+
const shouldShow = this.store.shouldShowMobileToggle.value;
|
|
157
|
+
|
|
158
|
+
if (mobileToggle) {
|
|
159
|
+
mobileToggle.style.display = shouldShow ? 'flex' : 'none';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
refreshPreview() {
|
|
164
|
+
// Dispatch event for SimpleLivePreview to handle
|
|
165
|
+
document.dispatchEvent(new CustomEvent('preview:refresh'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
loadUrl() {
|
|
169
|
+
if (this.urlInput) {
|
|
170
|
+
const path = this.urlInput.value.trim();
|
|
171
|
+
// Dispatch event for SimpleLivePreview to handle
|
|
172
|
+
document.dispatchEvent(new CustomEvent('preview:loadUrl', {
|
|
173
|
+
detail: { path }
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Public API methods
|
|
179
|
+
getState() {
|
|
180
|
+
return this.store.getState();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
destroy() {
|
|
184
|
+
// Restore body scroll
|
|
185
|
+
document.body.style.overflow = '';
|
|
186
|
+
|
|
187
|
+
// Clean up is handled by the signal store
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Auto-initialize when script loads
|
|
192
|
+
console.log('Sidebar.js loaded, looking for sidebar containers...');
|
|
193
|
+
|
|
194
|
+
function initializeSidebar() {
|
|
195
|
+
// Look for sapling-islands containing sidebar content
|
|
196
|
+
const saplingIslands = document.querySelectorAll('sapling-island');
|
|
197
|
+
|
|
198
|
+
for (const island of saplingIslands) {
|
|
199
|
+
// Look for sidebar pane div
|
|
200
|
+
const sidebarPane = island.querySelector('div[id$="-pane"]');
|
|
201
|
+
if (sidebarPane && sidebarPane.id.includes('sidebar')) {
|
|
202
|
+
const sidebarId = sidebarPane.id.replace('-pane', '');
|
|
203
|
+
console.log('Found Sidebar component with ID:', sidebarId);
|
|
204
|
+
|
|
205
|
+
// Create and store manager
|
|
206
|
+
const manager = new SidebarManager(sidebarId);
|
|
207
|
+
window.sidebarManager = manager; // Make globally available
|
|
208
|
+
|
|
209
|
+
break; // Only one sidebar per page
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Initialize immediately since Sapling islands are ready
|
|
215
|
+
initializeSidebar();
|
|
216
|
+
|
|
217
|
+
// Make available globally
|
|
218
|
+
window.SidebarManager = SidebarManager;
|
|
219
|
+
|
|
220
|
+
// Cleanup on page unload
|
|
221
|
+
window.addEventListener('beforeunload', () => {
|
|
222
|
+
if (window.sidebarManager) {
|
|
223
|
+
window.sidebarManager.destroy();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// SimpleLivePreview component JavaScript
|
|
2
|
+
import { sidebarStore } from '/signals/SidebarSignal.js';
|
|
3
|
+
|
|
2
4
|
class SimpleLivePreviewManager {
|
|
3
5
|
constructor(id = 'simple-preview') {
|
|
4
6
|
this.id = id;
|
|
@@ -17,8 +19,8 @@ class SimpleLivePreviewManager {
|
|
|
17
19
|
// Get preview port from iframe data attribute
|
|
18
20
|
this.previewPort = this.iframe?.getAttribute('data-preview-port') || 5173;
|
|
19
21
|
|
|
20
|
-
//
|
|
21
|
-
this.
|
|
22
|
+
// Reference to the sidebar store
|
|
23
|
+
this.store = sidebarStore;
|
|
22
24
|
|
|
23
25
|
this.init();
|
|
24
26
|
}
|
|
@@ -34,12 +36,15 @@ class SimpleLivePreviewManager {
|
|
|
34
36
|
|
|
35
37
|
// Set up event listeners
|
|
36
38
|
this.setupEventListeners();
|
|
39
|
+
|
|
40
|
+
// Subscribe to sidebar store changes
|
|
41
|
+
this.subscribeToStore();
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
setupEventListeners() {
|
|
40
45
|
// Hide sidebar toggle (both sidebar and floating button)
|
|
41
|
-
this.hideSidebarBtn?.addEventListener('click', () => this.
|
|
42
|
-
this.floatingHideSidebarBtn?.addEventListener('click', () => this.
|
|
46
|
+
this.hideSidebarBtn?.addEventListener('click', () => this.store.toggle());
|
|
47
|
+
this.floatingHideSidebarBtn?.addEventListener('click', () => this.store.toggle());
|
|
43
48
|
|
|
44
49
|
// Refresh button
|
|
45
50
|
this.refreshBtn?.addEventListener('click', () => this.refreshIframe());
|
|
@@ -62,6 +67,21 @@ class SimpleLivePreviewManager {
|
|
|
62
67
|
this.loadUrl();
|
|
63
68
|
});
|
|
64
69
|
|
|
70
|
+
// Listen for events from Sidebar component
|
|
71
|
+
document.addEventListener('preview:refresh', () => this.refreshIframe());
|
|
72
|
+
document.addEventListener('preview:loadUrl', (e) => {
|
|
73
|
+
if (e.detail && e.detail.path !== undefined) {
|
|
74
|
+
this.loadUrlFromPath(e.detail.path);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Legacy: Listen for sidebar state changes (for backward compatibility)
|
|
79
|
+
document.addEventListener('sidebar:stateChanged', (e) => {
|
|
80
|
+
if (e.detail) {
|
|
81
|
+
this.handleSidebarStateChange(e.detail);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
65
85
|
// Keyboard shortcuts
|
|
66
86
|
document.addEventListener('keydown', (e) => {
|
|
67
87
|
// Cmd/Ctrl + R for refresh
|
|
@@ -69,11 +89,6 @@ class SimpleLivePreviewManager {
|
|
|
69
89
|
e.preventDefault();
|
|
70
90
|
this.refreshIframe();
|
|
71
91
|
}
|
|
72
|
-
// Cmd/Ctrl + B to toggle sidebar panel
|
|
73
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
this.toggleSidebarVisibility();
|
|
76
|
-
}
|
|
77
92
|
});
|
|
78
93
|
|
|
79
94
|
// Handle iframe load errors (for X-Frame-Options violations)
|
|
@@ -129,55 +144,41 @@ class SimpleLivePreviewManager {
|
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
146
|
|
|
132
|
-
|
|
133
|
-
|
|
147
|
+
subscribeToStore() {
|
|
148
|
+
// Subscribe to sidebar store changes
|
|
149
|
+
this.store.shouldShowFloatingButton.subscribe(() => this.updateFloatingButton());
|
|
150
|
+
this.store.isOpen.subscribe(() => this.updateFloatingButton());
|
|
134
151
|
|
|
135
|
-
|
|
136
|
-
|
|
152
|
+
// Initial update
|
|
153
|
+
this.updateFloatingButton();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
updateFloatingButton() {
|
|
157
|
+
const floatingBtn = this.floatingHideSidebarBtn;
|
|
158
|
+
const floatingIcon = this.floatingHideSidebarIcon;
|
|
159
|
+
const shouldShow = this.store.shouldShowFloatingButton.value;
|
|
137
160
|
|
|
138
|
-
if (
|
|
139
|
-
if (
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Update button icons and titles
|
|
146
|
-
if (this.hideSidebarIcon) {
|
|
147
|
-
this.hideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple-fill');
|
|
148
|
-
}
|
|
149
|
-
if (this.floatingHideSidebarIcon) {
|
|
150
|
-
this.floatingHideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple-fill');
|
|
151
|
-
}
|
|
152
|
-
if (this.hideSidebarBtn) {
|
|
153
|
-
this.hideSidebarBtn.setAttribute('title', 'Show Sidebar');
|
|
154
|
-
}
|
|
155
|
-
if (this.floatingHideSidebarBtn) {
|
|
156
|
-
this.floatingHideSidebarBtn.setAttribute('title', 'Show Sidebar');
|
|
161
|
+
if (floatingBtn) {
|
|
162
|
+
if (shouldShow) {
|
|
163
|
+
// Show floating button when sidebar is closed
|
|
164
|
+
floatingBtn.style.display = 'flex';
|
|
165
|
+
// Update icon and title
|
|
166
|
+
if (floatingIcon) {
|
|
167
|
+
floatingIcon.setAttribute('icon', 'ph:sidebar-simple-fill');
|
|
157
168
|
}
|
|
169
|
+
floatingBtn.setAttribute('title', 'Show Sidebar');
|
|
158
170
|
} else {
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
previewPane.classList.remove('w-full');
|
|
162
|
-
previewPane.classList.add('flex-1');
|
|
163
|
-
|
|
164
|
-
// Update button icons and titles
|
|
165
|
-
if (this.hideSidebarIcon) {
|
|
166
|
-
this.hideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple');
|
|
167
|
-
}
|
|
168
|
-
if (this.floatingHideSidebarIcon) {
|
|
169
|
-
this.floatingHideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple');
|
|
170
|
-
}
|
|
171
|
-
if (this.hideSidebarBtn) {
|
|
172
|
-
this.hideSidebarBtn.setAttribute('title', 'Hide Sidebar');
|
|
173
|
-
}
|
|
174
|
-
if (this.floatingHideSidebarBtn) {
|
|
175
|
-
this.floatingHideSidebarBtn.setAttribute('title', 'Hide Sidebar');
|
|
176
|
-
}
|
|
171
|
+
// Hide floating button when sidebar is open
|
|
172
|
+
floatingBtn.style.display = 'none';
|
|
177
173
|
}
|
|
178
174
|
}
|
|
179
175
|
}
|
|
180
176
|
|
|
177
|
+
toggleSidebarVisibility() {
|
|
178
|
+
// Use the store to toggle
|
|
179
|
+
this.store.toggle();
|
|
180
|
+
}
|
|
181
|
+
|
|
181
182
|
refreshIframe() {
|
|
182
183
|
if (this.iframe) {
|
|
183
184
|
console.log('Refreshing iframe...');
|
|
@@ -191,7 +192,13 @@ class SimpleLivePreviewManager {
|
|
|
191
192
|
loadUrl() {
|
|
192
193
|
if (this.urlInput && this.iframe) {
|
|
193
194
|
const path = this.urlInput.value.trim();
|
|
194
|
-
|
|
195
|
+
this.loadUrlFromPath(path);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
loadUrlFromPath(path) {
|
|
200
|
+
if (this.iframe) {
|
|
201
|
+
console.log('loadUrlFromPath called with path:', path);
|
|
195
202
|
|
|
196
203
|
// Check if it's an external URL (starts with http:// or https://)
|
|
197
204
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
@@ -201,8 +208,10 @@ class SimpleLivePreviewManager {
|
|
|
201
208
|
// Open external URLs in a new tab
|
|
202
209
|
console.log('Opening external URL in new tab:', path);
|
|
203
210
|
window.open(path, '_blank');
|
|
204
|
-
// Clear the input
|
|
205
|
-
this.urlInput
|
|
211
|
+
// Clear the input if it exists
|
|
212
|
+
if (this.urlInput) {
|
|
213
|
+
this.urlInput.value = '';
|
|
214
|
+
}
|
|
206
215
|
return;
|
|
207
216
|
}
|
|
208
217
|
}
|
|
@@ -211,9 +220,20 @@ class SimpleLivePreviewManager {
|
|
|
211
220
|
const newUrl = path ? baseUrl + '/' + path.replace(/^\//, '') : baseUrl;
|
|
212
221
|
console.log('Loading URL in iframe:', newUrl);
|
|
213
222
|
this.iframe.src = newUrl;
|
|
223
|
+
|
|
224
|
+
// Update input if it exists
|
|
225
|
+
if (this.urlInput) {
|
|
226
|
+
this.urlInput.value = path;
|
|
227
|
+
}
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
|
|
231
|
+
handleSidebarStateChange(state) {
|
|
232
|
+
// Legacy method for backward compatibility
|
|
233
|
+
console.log('Legacy sidebar state changed:', state);
|
|
234
|
+
// The floating button is now handled by updateFloatingButton()
|
|
235
|
+
}
|
|
236
|
+
|
|
217
237
|
handleIframeError() {
|
|
218
238
|
// Get the current iframe src and open it in a new tab if it's external
|
|
219
239
|
if (this.iframe && this.iframe.src) {
|