|
import app from '../app.js';
|
|
|
|
const api = app.getAPI();
|
|
const logger = app.getLogger();
|
|
|
|
class CodeEditorView extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.editor = null;
|
|
this.file = null;
|
|
this.previousView = null;
|
|
this.isRendered = false;
|
|
}
|
|
|
|
connectedCallback() {
|
|
logger.debug('CodeEditorView connected');
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
logger.debug('CodeEditorView disconnected');
|
|
this.destroyEditor();
|
|
}
|
|
|
|
async setFile(file, previousView = 'files') {
|
|
if (this.isRendered) {
|
|
logger.warn('Editor already rendered, skipping');
|
|
return;
|
|
}
|
|
|
|
this.file = file;
|
|
this.previousView = previousView;
|
|
this.isRendered = true;
|
|
|
|
try {
|
|
logger.debug('Loading file', { fileName: file.name });
|
|
const blob = await api.downloadFile(file.id);
|
|
const content = await blob.text();
|
|
|
|
this.createUI(content);
|
|
this.createEditor(content);
|
|
} catch (error) {
|
|
logger.error('Failed to load file', error);
|
|
document.dispatchEvent(new CustomEvent('show-toast', {
|
|
detail: { message: 'Failed to load file: ' + error.message, type: 'error' }
|
|
}));
|
|
window.history.back();
|
|
}
|
|
}
|
|
|
|
createUI(content) {
|
|
this.innerHTML = `
|
|
<div class="code-editor-overlay">
|
|
<div class="code-editor-container">
|
|
<div class="code-editor-header">
|
|
<div class="header-left">
|
|
<button class="button" id="back-btn">Back</button>
|
|
<h2 class="editor-filename">${this.escapeHtml(this.file.name)}</h2>
|
|
</div>
|
|
<div class="header-right">
|
|
<button class="button button-primary" id="save-btn">Save & Close</button>
|
|
</div>
|
|
</div>
|
|
<div class="code-editor-body">
|
|
<textarea id="editor-textarea"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const backBtn = this.querySelector('#back-btn');
|
|
const saveBtn = this.querySelector('#save-btn');
|
|
|
|
backBtn.addEventListener('click', () => this.close());
|
|
saveBtn.addEventListener('click', () => this.save());
|
|
|
|
document.addEventListener('keydown', this.handleKeydown.bind(this));
|
|
}
|
|
|
|
createEditor(content) {
|
|
const textarea = this.querySelector('#editor-textarea');
|
|
if (!textarea) {
|
|
logger.error('Textarea not found');
|
|
return;
|
|
}
|
|
|
|
textarea.value = content;
|
|
|
|
const mode = this.getMode(this.file.name);
|
|
logger.debug('Creating CodeMirror editor', { mode, fileSize: content.length });
|
|
|
|
this.editor = CodeMirror.fromTextArea(textarea, {
|
|
mode: mode,
|
|
lineNumbers: true,
|
|
lineWrapping: true,
|
|
indentUnit: 4,
|
|
indentWithTabs: false,
|
|
theme: 'default',
|
|
readOnly: false,
|
|
autofocus: true,
|
|
extraKeys: {
|
|
'Ctrl-S': () => { this.save(); return false; },
|
|
'Cmd-S': () => { this.save(); return false; },
|
|
'Esc': () => { this.close(); return false; }
|
|
}
|
|
});
|
|
|
|
this.editor.setSize('100%', '100%');
|
|
|
|
setTimeout(() => {
|
|
if (this.editor) {
|
|
this.editor.refresh();
|
|
this.editor.focus();
|
|
logger.debug('Editor ready and focused');
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
getMode(filename) {
|
|
const ext = filename.split('.').pop().toLowerCase();
|
|
const modes = {
|
|
'js': 'javascript',
|
|
'json': { name: 'javascript', json: true },
|
|
'py': 'python',
|
|
'md': 'markdown',
|
|
'html': 'htmlmixed',
|
|
'xml': 'xml',
|
|
'css': 'css',
|
|
'txt': 'text/plain',
|
|
'log': 'text/plain',
|
|
'sh': 'shell',
|
|
'yaml': 'yaml',
|
|
'yml': 'yaml'
|
|
};
|
|
return modes[ext] || 'text/plain';
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
handleKeydown(e) {
|
|
if (e.key === 'Escape' && !this.editor.getOption('readOnly')) {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
async save() {
|
|
if (!this.editor) {
|
|
logger.warn('No editor instance');
|
|
return;
|
|
}
|
|
|
|
const saveBtn = this.querySelector('#save-btn');
|
|
if (!saveBtn) return;
|
|
|
|
try {
|
|
saveBtn.disabled = true;
|
|
saveBtn.textContent = 'Saving...';
|
|
|
|
const content = this.editor.getValue();
|
|
logger.debug('Saving file', { fileName: this.file.name, size: content.length });
|
|
|
|
await api.updateFile(this.file.id, content);
|
|
|
|
logger.info('File saved successfully', { fileName: this.file.name });
|
|
|
|
document.dispatchEvent(new CustomEvent('show-toast', {
|
|
detail: { message: 'File saved successfully!', type: 'success' }
|
|
}));
|
|
|
|
setTimeout(() => {
|
|
this.close();
|
|
}, 500);
|
|
|
|
} catch (error) {
|
|
logger.error('Failed to save file', error);
|
|
document.dispatchEvent(new CustomEvent('show-toast', {
|
|
detail: { message: 'Failed to save: ' + error.message, type: 'error' }
|
|
}));
|
|
|
|
if (saveBtn) {
|
|
saveBtn.disabled = false;
|
|
saveBtn.textContent = 'Save & Close';
|
|
}
|
|
}
|
|
}
|
|
|
|
close() {
|
|
logger.debug('Closing editor');
|
|
window.history.back();
|
|
}
|
|
|
|
hide() {
|
|
logger.debug('Hiding editor');
|
|
this.destroyEditor();
|
|
this.remove();
|
|
}
|
|
|
|
destroyEditor() {
|
|
if (this.editor) {
|
|
logger.debug('Destroying CodeMirror instance');
|
|
try {
|
|
this.editor.toTextArea();
|
|
} catch (e) {
|
|
logger.warn('Error destroying editor', e);
|
|
}
|
|
this.editor = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define('code-editor-view', CodeEditorView);
|
|
export { CodeEditorView };
|