// ===== Save & Export Module =====
// Wait for editor to be ready
document.addEventListener('DOMContentLoaded', () => {
// Get editor reference from global API
const { editor, getValue } = window.editorAPI || {};
if (!editor) {
console.warn('Editor not found, save functionality may be limited');
return;
}
// DOM elements
const saveBtn = document.getElementById("saveBtn");
const saveMenu = document.getElementById("saveMenu");
const doSave = document.getElementById("doSave");
const doArtifact = document.getElementById("doArtifact");
// ===== Save Menu Management =====
saveBtn.addEventListener("click", (e) => {
const open = saveMenu.classList.toggle("open");
saveBtn.setAttribute("aria-expanded", String(open));
e.stopPropagation();
});
document.addEventListener("click", (e) => {
if (!saveMenu.contains(e.target) && !saveBtn.contains(e.target)) {
saveMenu.classList.remove("open");
saveBtn.setAttribute("aria-expanded", "false");
}
});
// ===== Save Functions =====
// Get current file info
function getFileInfo() {
const content = getValue ? getValue() : editor.getValue();
const mode = editor.session.getMode().$id;
// Determine file extension based on mode
let extension = '.txt';
let mimeType = 'text/plain';
switch (mode) {
case 'ace/mode/html':
extension = '.html';
mimeType = 'text/html';
break;
case 'ace/mode/javascript':
extension = '.js';
mimeType = 'text/javascript';
break;
case 'ace/mode/css':
extension = '.css';
mimeType = 'text/css';
break;
case 'ace/mode/php':
extension = '.php';
mimeType = 'text/plain';
break;
case 'ace/mode/python':
extension = '.py';
mimeType = 'text/plain';
break;
case 'ace/mode/json':
extension = '.json';
mimeType = 'application/json';
break;
}
// Try to extract filename from content (for HTML files)
let filename = 'untitled' + extension;
if (mode === 'ace/mode/html') {
const titleMatch = content.match(/<title[^>]*>([^<]+)<\/title>/i);
if (titleMatch) {
// Clean title for filename
const cleanTitle = titleMatch[1]
.trim()
.replace(/[^\w\s-]/g, '') // Remove special chars
.replace(/\s+/g, '-') // Replace spaces with dashes
.toLowerCase();
if (cleanTitle && cleanTitle.length > 0) {
filename = cleanTitle + extension;
}
}
}
return {
content,
filename,
extension,
mimeType,
mode
};
}
// Download file
function downloadFile(content, filename, mimeType = 'text/plain') {
try {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Clean up the URL object
setTimeout(() => URL.revokeObjectURL(url), 100);
return true;
} catch (error) {
console.error('Download failed:', error);
return false;
}
}
// Copy to clipboard
async function copyToClipboard(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
return true;
} else {
// Fallback for older browsers or non-HTTPS
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const result = document.execCommand('copy');
document.body.removeChild(textArea);
return result;
}
} catch (error) {
console.error('Copy to clipboard failed:', error);
return false;
}
}
// Show temporary notification
function showNotification(message, type = 'success') {
// Create notification element
const notification = document.createElement('div');
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
padding: 12px 20px;
background: ${type === 'success' ? '#1b4332' : '#722f37'};
color: ${type === 'success' ? '#90ee90' : '#ff9999'};
border: 1px solid ${type === 'success' ? '#2d6a4f' : '#c9184a'};
border-radius: 8px;
font-size: 14px;
font-family: system-ui, sans-serif;
z-index: 1000;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateX(0)';
}, 10);
// Animate out and remove
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
setTimeout(() => document.body.removeChild(notification), 300);
}, 3000);
}
// ===== Save Actions =====
// Regular save (download file)
function handleSave() {
const fileInfo = getFileInfo();
const success = downloadFile(fileInfo.content, fileInfo.filename, fileInfo.mimeType);
if (success) {
showNotification(`Downloaded: ${fileInfo.filename}`);
} else {
showNotification('Save failed - please try again', 'error');
}
saveMenu.classList.remove("open");
saveBtn.setAttribute("aria-expanded", "false");
}
// Save as artifact (copy to clipboard with metadata)
async function handleArtifact() {
const fileInfo = getFileInfo();
// Create artifact-style content
const artifactContent = `# ${fileInfo.filename}
\`\`\`${fileInfo.mode.replace('ace/mode/', '')}
${fileInfo.content}
\`\`\`
<!--
File Info:
- Mode: ${fileInfo.mode}
- Extension: ${fileInfo.extension}
- Generated: ${new Date().toISOString()}
-->`;
const success = await copyToClipboard(artifactContent);
if (success) {
showNotification('Code copied to clipboard as artifact!');
} else {
showNotification('Copy failed - please try manually', 'error');
}
saveMenu.classList.remove("open");
saveBtn.setAttribute("aria-expanded", "false");
}
// ===== Event Listeners =====
doSave.addEventListener("click", handleSave);
doArtifact.addEventListener("click", handleArtifact);
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + S for save
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
handleSave();
}
// Ctrl/Cmd + Shift + S for artifact
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'S') {
e.preventDefault();
handleArtifact();
}
});
// ===== Export Save API =====
window.saveAPI = {
handleSave,
handleArtifact,
downloadFile,
copyToClipboard,
getFileInfo,
showNotification
};
console.log("Save module loaded successfully");
});