681 lines
22 KiB
JavaScript
681 lines
22 KiB
JavaScript
class QLEditor {
|
|
constructor(username, project, selectedFile) {
|
|
this.username = username;
|
|
this.project = project;
|
|
this.selectedFile = selectedFile;
|
|
this.editor = null;
|
|
this.autoSaveTimer = null;
|
|
this.isDirty = false;
|
|
this.consoleHistory = [];
|
|
this.consoleHistoryIndex = -1;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.bindEvents();
|
|
this.setupAutoSave();
|
|
this.updateStatus();
|
|
this.initConsole();
|
|
console.log(`QL Editor initialized for ${this.username}/${this.project}/${this.selectedFile}`);
|
|
}
|
|
|
|
bindEvents() {
|
|
// Zmiana pliku
|
|
document.querySelectorAll('.file-item').forEach(item => {
|
|
item.addEventListener('click', (e) => {
|
|
const filename = e.currentTarget.dataset.filename;
|
|
if (filename !== this.selectedFile) {
|
|
this.switchFile(filename);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Zapisz przycisk
|
|
document.getElementById('saveBtn')?.addEventListener('click', () => this.saveFile());
|
|
|
|
// Nowy plik
|
|
document.getElementById('newFileBtn')?.addEventListener('click', () => this.showNewFileModal());
|
|
|
|
// Uruchom kod
|
|
document.getElementById('runBtn')?.addEventListener('click', () => this.runCode());
|
|
|
|
// Formatuj kod
|
|
document.getElementById('formatBtn')?.addEventListener('click', () => this.formatCode());
|
|
|
|
// Śledź zmiany w edytorze
|
|
const textarea = document.getElementById('codeEditor');
|
|
if (textarea) {
|
|
textarea.addEventListener('input', () => {
|
|
this.isDirty = true;
|
|
this.updateStatus();
|
|
});
|
|
|
|
// Skróty klawiaturowe w edytorze
|
|
textarea.addEventListener('keydown', (e) => {
|
|
// Ctrl+S - zapisz
|
|
if (e.ctrlKey && e.key === 's') {
|
|
e.preventDefault();
|
|
this.saveFile();
|
|
}
|
|
// Ctrl+Enter - uruchom
|
|
if (e.ctrlKey && e.key === 'Enter') {
|
|
e.preventDefault();
|
|
this.runCode();
|
|
}
|
|
// Ctrl+F - formatuj
|
|
if (e.ctrlKey && e.key === 'f') {
|
|
e.preventDefault();
|
|
this.formatCode();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Globalne skróty klawiaturowe
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.ctrlKey && e.key === 's') {
|
|
e.preventDefault();
|
|
this.saveFile();
|
|
}
|
|
});
|
|
|
|
// Ostrzeżenie przed opuszczeniem strony
|
|
window.addEventListener('beforeunload', (e) => {
|
|
if (this.isDirty) {
|
|
e.preventDefault();
|
|
e.returnValue = 'Masz niezapisane zmiany. Czy na pewno chcesz opuścić stronę?';
|
|
return e.returnValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
initConsole() {
|
|
const consoleInput = document.getElementById('consoleInput');
|
|
const consoleSend = document.getElementById('consoleSend');
|
|
const clearConsoleBtn = document.getElementById('clearConsoleBtn');
|
|
|
|
if (!consoleInput) return;
|
|
|
|
// Enter w konsoli
|
|
consoleInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
this.executeConsoleCommand();
|
|
}
|
|
|
|
// Strzałki dla historii
|
|
if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
this.navigateConsoleHistory(-1);
|
|
}
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
this.navigateConsoleHistory(1);
|
|
}
|
|
});
|
|
|
|
// Przycisk wysyłania
|
|
if (consoleSend) {
|
|
consoleSend.addEventListener('click', () => this.executeConsoleCommand());
|
|
}
|
|
|
|
// Przycisk czyszczenia konsoli
|
|
if (clearConsoleBtn) {
|
|
clearConsoleBtn.addEventListener('click', () => this.clearConsole());
|
|
}
|
|
|
|
// Auto-focus na konsoli
|
|
setTimeout(() => {
|
|
consoleInput?.focus();
|
|
}, 100);
|
|
}
|
|
|
|
switchFile(filename) {
|
|
if (this.isDirty) {
|
|
if (!confirm('Masz niezapisane zmiany. Czy na pewno chcesz zmienić plik?')) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
window.location.href = `/editor/${this.username}/${this.project}?file=${filename}`;
|
|
}
|
|
|
|
async saveFile() {
|
|
const content = document.getElementById('codeEditor').value;
|
|
const filename = this.selectedFile;
|
|
|
|
if (!content.trim()) {
|
|
this.showNotification('Plik jest pusty!', 'warning');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showNotification('Zapisywanie...', 'info');
|
|
|
|
const response = await fetch(`/editor/${this.username}/${this.project}/save`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
filename: filename,
|
|
content: content
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showNotification('✓ Plik zapisany pomyślnie', 'success');
|
|
this.isDirty = false;
|
|
this.updateStatus();
|
|
this.addConsoleMessage('✓ Plik zapisany', 'success');
|
|
|
|
// Zaktualizuj czas modyfikacji w liście plików
|
|
const fileItem = document.querySelector(`[data-filename="${filename}"] .file-modified`);
|
|
if (fileItem) {
|
|
const now = new Date();
|
|
fileItem.textContent = now.toLocaleTimeString();
|
|
}
|
|
} else {
|
|
throw new Error(data.error || 'Błąd zapisu');
|
|
}
|
|
} catch (error) {
|
|
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
|
|
this.addConsoleMessage(`✗ Błąd zapisu: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async createFile(filename) {
|
|
if (!filename) {
|
|
this.showNotification('Podaj nazwę pliku', 'warning');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/editor/${this.username}/${this.project}/create`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
filename: filename
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showNotification('✓ Plik utworzony pomyślnie', 'success');
|
|
this.addConsoleMessage(`✓ Utworzono plik: ${filename}`, 'success');
|
|
|
|
// Przełącz na nowy plik
|
|
setTimeout(() => {
|
|
window.location.href = `/editor/${this.username}/${this.project}?file=${filename}`;
|
|
}, 500);
|
|
} else {
|
|
throw new Error(data.error || 'Błąd tworzenia pliku');
|
|
}
|
|
} catch (error) {
|
|
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async deleteFile() {
|
|
if (!this.selectedFile || this.selectedFile === 'README.md') {
|
|
this.showNotification('Nie można usunąć tego pliku', 'warning');
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`Czy na pewno chcesz usunąć plik "${this.selectedFile}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/editor/${this.username}/${this.project}/delete`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
filename: this.selectedFile
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showNotification('✓ Plik usunięty pomyślnie', 'success');
|
|
this.addConsoleMessage(`✓ Usunięto plik: ${this.selectedFile}`, 'success');
|
|
|
|
// Wróć do głównego pliku
|
|
setTimeout(() => {
|
|
window.location.href = `/editor/${this.username}/${this.project}`;
|
|
}, 500);
|
|
} else {
|
|
throw new Error(data.error || 'Błąd usuwania pliku');
|
|
}
|
|
} catch (error) {
|
|
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
showNewFileModal() {
|
|
const filename = prompt('Podaj nazwę nowego pliku (np. program.ql):', 'nowy.ql');
|
|
if (filename) {
|
|
this.createFile(filename);
|
|
}
|
|
}
|
|
|
|
setupAutoSave() {
|
|
// Auto-zapis co 30 sekund jeśli są zmiany
|
|
this.autoSaveTimer = setInterval(() => {
|
|
if (this.isDirty) {
|
|
this.autoSave();
|
|
}
|
|
}, 30000);
|
|
}
|
|
|
|
async autoSave() {
|
|
const content = document.getElementById('codeEditor').value;
|
|
const filename = this.selectedFile;
|
|
|
|
try {
|
|
const response = await fetch(`/editor/${this.username}/${this.project}/save`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
filename: filename,
|
|
content: content
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
this.addConsoleMessage('✓ Auto-zapisano plik', 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('Auto-save error:', error);
|
|
}
|
|
}
|
|
|
|
updateStatus() {
|
|
const status = document.getElementById('editorStatus');
|
|
if (status) {
|
|
const editor = document.getElementById('codeEditor');
|
|
if (!editor) return;
|
|
|
|
const lines = editor.value.split('\n').length;
|
|
const chars = editor.value.length;
|
|
const dirtyIndicator = this.isDirty ? ' ●' : '';
|
|
|
|
status.textContent = `${this.selectedFile} | Linie: ${lines} | Znaki: ${chars}${dirtyIndicator}`;
|
|
}
|
|
}
|
|
async runCode() {
|
|
const code = document.getElementById('codeEditor').value;
|
|
|
|
if (!code.trim()) {
|
|
return;
|
|
}
|
|
|
|
// WYCZYŚĆ KONSOLĘ PRZED NOWYM WYKONANIEM
|
|
this.clearConsoleForRun();
|
|
|
|
try {
|
|
const response = await fetch(`/editor/${this.username}/${this.project}/execute`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({ code: code })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'success') {
|
|
// WYŚWIETL TYLKO OUTPUT Z PRINT()
|
|
if (data.output && data.output.length > 0) {
|
|
data.output.forEach(line => {
|
|
if (line && line.trim()) {
|
|
this.addConsoleMessage(line, 'response');
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// Tylko błędy
|
|
if (data.output && data.output.length > 0) {
|
|
this.addConsoleMessage(data.output[0], 'error');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
this.addConsoleMessage(`Błąd: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Dodaj tę nową funkcję do klasy:
|
|
clearConsoleForRun() {
|
|
const consoleOutput = document.getElementById('consoleOutput');
|
|
if (!consoleOutput) return;
|
|
|
|
// WYCZYŚĆ KONSOLĘ, ale NIE dodawaj komunikatów "QL Console"
|
|
consoleOutput.innerHTML = '';
|
|
|
|
// Możesz dodać tylko nazwę polecenia jak w terminalu:
|
|
// this.addConsoleMessage(`$ ql ${this.selectedFile}`, 'command');
|
|
}
|
|
|
|
formatCode() {
|
|
const editor = document.getElementById('codeEditor');
|
|
if (!editor) return;
|
|
|
|
let code = editor.value;
|
|
|
|
// Proste formatowanie kodu QL
|
|
// 1. Zamień taby na 4 spacje
|
|
code = code.replace(/\t/g, ' ');
|
|
|
|
// 2. Usuń nadmiarowe puste linie
|
|
code = code.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
|
|
// 3. Usuń białe znaki na końcu linii
|
|
code = code.replace(/\s+$/gm, '');
|
|
|
|
// 4. Dodaj wcięcia dla bloków
|
|
let indent = 0;
|
|
const lines = code.split('\n');
|
|
const formattedLines = [];
|
|
|
|
for (let line of lines) {
|
|
line = line.trim();
|
|
|
|
// Zmniejsz wcięcie przed zamknięciem bloku
|
|
if (line.includes('}')) {
|
|
indent = Math.max(0, indent - 1);
|
|
}
|
|
|
|
// Dodaj wcięcie
|
|
const indentedLine = ' '.repeat(indent) + line;
|
|
formattedLines.push(indentedLine);
|
|
|
|
// Zwiększ wcięcie po otwarciu bloku
|
|
if (line.includes('{')) {
|
|
indent++;
|
|
}
|
|
}
|
|
|
|
editor.value = formattedLines.join('\n');
|
|
this.isDirty = true;
|
|
this.updateStatus();
|
|
this.addConsoleMessage('✓ Kod sformatowany', 'success');
|
|
this.showNotification('Kod sformatowany!', 'info');
|
|
}
|
|
|
|
executeConsoleCommand() {
|
|
const consoleInput = document.getElementById('consoleInput');
|
|
if (!consoleInput) return;
|
|
|
|
const command = consoleInput.value.trim();
|
|
if (!command) return;
|
|
|
|
// Dodaj do historii
|
|
this.consoleHistory.push(command);
|
|
this.consoleHistoryIndex = -1;
|
|
|
|
// Wyświetl komendę
|
|
this.addConsoleLine(`> ${command}`, 'command');
|
|
|
|
// Wykonaj komendę
|
|
const result = this.processConsoleCommand(command);
|
|
if (result) {
|
|
this.addConsoleLine(result);
|
|
}
|
|
|
|
// Wyczyść input
|
|
consoleInput.value = '';
|
|
consoleInput.focus();
|
|
}
|
|
|
|
processConsoleCommand(command) {
|
|
const parts = command.trim().split(' ');
|
|
const cmd = parts[0].toLowerCase();
|
|
const args = parts.slice(1);
|
|
|
|
const commands = {
|
|
'help': () => {
|
|
return `Dostępne komendy:
|
|
help - wyświetla tę pomoc
|
|
ls - lista plików w projekcie
|
|
clear - czyści konsolę
|
|
run - uruchamia aktualny plik
|
|
save - zapisuje aktualny plik
|
|
format - formatuje kod
|
|
status - pokazuje status projektu
|
|
time - pokazuje aktualny czas
|
|
echo [text] - wyświetla tekst`;
|
|
},
|
|
|
|
'ls': () => {
|
|
const fileList = document.getElementById('fileList');
|
|
if (!fileList) return 'Brak listy plików';
|
|
|
|
const files = Array.from(fileList.querySelectorAll('.list-group-item'))
|
|
.map(item => item.dataset.filename)
|
|
.filter(Boolean);
|
|
|
|
if (files.length === 0) return 'Brak plików w projekcie';
|
|
return files.map(f => `📄 ${f}`).join('\n');
|
|
},
|
|
|
|
'clear': () => {
|
|
this.clearConsole();
|
|
return 'Konsola wyczyszczona';
|
|
},
|
|
|
|
'run': () => {
|
|
this.runCode();
|
|
return 'Uruchamianie kodu...';
|
|
},
|
|
|
|
'save': () => {
|
|
this.saveFile();
|
|
return 'Zapisywanie pliku...';
|
|
},
|
|
|
|
'format': () => {
|
|
this.formatCode();
|
|
return 'Formatowanie kodu...';
|
|
},
|
|
|
|
'status': () => {
|
|
const editor = document.getElementById('codeEditor');
|
|
if (!editor) return 'Brak edytora';
|
|
|
|
const lines = editor.value.split('\n').length;
|
|
const chars = editor.value.length;
|
|
return `Projekt: ${this.project}
|
|
Plik: ${this.selectedFile}
|
|
Linie: ${lines}
|
|
Znaki: ${chars}
|
|
Zmiany: ${this.isDirty ? 'TAK' : 'NIE'}`;
|
|
},
|
|
|
|
'time': () => new Date().toLocaleString(),
|
|
|
|
'echo': () => args.join(' ')
|
|
};
|
|
|
|
if (commands[cmd]) {
|
|
try {
|
|
const result = commands[cmd]();
|
|
return typeof result === 'string' ? result : '';
|
|
} catch (error) {
|
|
return `Błąd: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
return `Nieznana komenda: ${cmd}\nWpisz 'help' aby zobaczyć dostępne komendy`;
|
|
}
|
|
|
|
navigateConsoleHistory(direction) {
|
|
const consoleInput = document.getElementById('consoleInput');
|
|
if (!consoleInput || this.consoleHistory.length === 0) return;
|
|
|
|
if (direction === -1) { // Strzałka w górę
|
|
if (this.consoleHistoryIndex < this.consoleHistory.length - 1) {
|
|
if (this.consoleHistoryIndex === -1) {
|
|
this.currentCommand = consoleInput.value;
|
|
}
|
|
this.consoleHistoryIndex++;
|
|
}
|
|
} else { // Strzałka w dół
|
|
if (this.consoleHistoryIndex > 0) {
|
|
this.consoleHistoryIndex--;
|
|
} else if (this.consoleHistoryIndex === 0) {
|
|
this.consoleHistoryIndex = -1;
|
|
consoleInput.value = this.currentCommand || '';
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.consoleHistoryIndex >= 0) {
|
|
consoleInput.value = this.consoleHistory[this.consoleHistory.length - 1 - this.consoleHistoryIndex];
|
|
}
|
|
|
|
// Przesuń kursor na koniec
|
|
consoleInput.selectionStart = consoleInput.selectionEnd = consoleInput.value.length;
|
|
}
|
|
|
|
addConsoleLine(text, type = 'response') {
|
|
const consoleOutput = document.getElementById('consoleOutput');
|
|
if (!consoleOutput) return;
|
|
|
|
const line = document.createElement('div');
|
|
line.className = `console-${type}`;
|
|
line.textContent = text;
|
|
consoleOutput.appendChild(line);
|
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
|
}
|
|
|
|
addConsoleMessage(message, type = 'info') {
|
|
this.addConsoleLine(message, type);
|
|
}
|
|
|
|
clearConsole() {
|
|
const consoleOutput = document.getElementById('consoleOutput');
|
|
if (!consoleOutput) return;
|
|
|
|
consoleOutput.innerHTML = '';
|
|
this.addConsoleMessage('=== QL Console ===', 'success');
|
|
this.addConsoleMessage('Wpisz \'help\' aby uzyskać pomoc', 'info');
|
|
}
|
|
|
|
showNotification(message, type = 'info') {
|
|
// Utwórz powiadomienie Bootstrap
|
|
const alert = document.createElement('div');
|
|
alert.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
|
alert.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 9999;
|
|
min-width: 300px;
|
|
max-width: 400px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
`;
|
|
|
|
// Ikona w zależności od typu
|
|
let icon = 'bi-info-circle';
|
|
if (type === 'success') icon = 'bi-check-circle';
|
|
if (type === 'warning') icon = 'bi-exclamation-triangle';
|
|
if (type === 'danger' || type === 'error') icon = 'bi-x-circle';
|
|
|
|
alert.innerHTML = `
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi ${icon} me-2"></i>
|
|
<span>${message}</span>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
document.body.appendChild(alert);
|
|
|
|
// Auto-ukrywanie
|
|
setTimeout(() => {
|
|
if (alert.parentNode) {
|
|
// Użyj Bootstrapowego zamykania
|
|
const bsAlert = new bootstrap.Alert(alert);
|
|
bsAlert.close();
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
// Czyszczenie interwału przy zamknięciu
|
|
destroy() {
|
|
if (this.autoSaveTimer) {
|
|
clearInterval(this.autoSaveTimer);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inicjalizacja edytora po załadowaniu strony
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const username = document.body.dataset.username;
|
|
const project = document.body.dataset.project;
|
|
const selectedFile = document.body.dataset.selectedFile;
|
|
|
|
if (username && project && selectedFile) {
|
|
window.qleditor = new QLEditor(username, project, selectedFile);
|
|
}
|
|
|
|
// Podświetlanie składni dla QL (podstawowe)
|
|
const editor = document.getElementById('codeEditor');
|
|
if (editor) {
|
|
// Zapisz oryginalny tekst dla podświetlania
|
|
let originalText = editor.value;
|
|
|
|
editor.addEventListener('input', function() {
|
|
// Możesz dodać podświetlanie składni tutaj
|
|
// Przykład prostego podświetlania:
|
|
const keywords = ['func', 'use', 'if', 'else', 'while', 'for', 'return', 'print', 'true', 'false', 'null'];
|
|
let text = this.value;
|
|
|
|
// To tylko przykład - w prawdziwym edytorze użyj biblioteki jak CodeMirror lub Monaco
|
|
keywords.forEach(keyword => {
|
|
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
|
|
text = text.replace(regex, `<span class="text-primary fw-bold">${keyword}</span>`);
|
|
});
|
|
|
|
// Zapisz oryginalny tekst
|
|
originalText = this.value;
|
|
});
|
|
}
|
|
|
|
// Czyszczenie przy opuszczeniu strony
|
|
window.addEventListener('unload', () => {
|
|
if (window.qleditor) {
|
|
window.qleditor.destroy();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Globalne funkcje dostępne z konsoli (opcjonalnie)
|
|
window.Console = {
|
|
run: function() {
|
|
if (window.qleditor) {
|
|
window.qleditor.runCode();
|
|
}
|
|
},
|
|
save: function() {
|
|
if (window.qleditor) {
|
|
window.qleditor.saveFile();
|
|
}
|
|
},
|
|
format: function() {
|
|
if (window.qleditor) {
|
|
window.qleditor.formatCode();
|
|
}
|
|
},
|
|
clear: function() {
|
|
if (window.qleditor) {
|
|
window.qleditor.clearConsole();
|
|
}
|
|
}
|
|
}; |