代码: 全选
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EJWOOM Editor</title>
<style>
/* CSS Reset and Basic Setup */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
padding: 10px;
background-color: #f4f4f4;
}
/* Tab Styles */
.ejwoomtab {
border-bottom: 2px solid #ccc;
margin-bottom: 10px;
}
.ejwoomtab span {
display: inline-block;
padding: 10px 15px;
cursor: pointer;
background-color: #eee;
border: 1px solid #ccc;
border-bottom: none;
border-radius: 5px 5px 0 0;
position: relative;
bottom: -1px;
}
.ejwoomtab span.selected {
background-color: #fff;
border-bottom: 2px solid #fff;
font-weight: bold;
}
/* Edit Area Styles */
.ejwoomedit {
display: block;
}
/* Control Bar */
.ejwoomcontrol {
background-color: #f0f0f0;
padding: 5px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 5px;
}
.ejwoomcontrol span {
display: inline-block;
padding: 5px 8px;
cursor: pointer;
background-color: #e0e0e0;
border: 1px solid #ccc;
border-radius: 3px;
margin-right: 5px;
font-size: 14px;
}
.ejwoomcontrol span:hover {
background-color: #d0d0d0;
}
/* Requirement 2: .ejwoomtitle */
.ejwoomtitle {
width: 100%;
box-sizing: border-box; /* As requested */
padding: 10px;
font-size: 1.2em;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
/* Editor Buttons */
.ejwoombuttons {
background-color: #f0f0f0;
padding: 5px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 5px;
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.ejwoombuttons button {
padding: 5px 8px;
cursor: pointer;
background-color: #e0e0e0;
border: 1px solid #ccc;
border-radius: 3px;
font-family: monospace;
font-size: 14px;
}
.ejwoombuttons button:hover {
background-color: #d0d0d0;
}
/* Requirement 2: .ejwoomcontent */
.ejwoomcontent {
width: 100%;
height: 70vh;
font-size: 16px;
font-family: 'Courier New', Courier, monospace;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 10px;
resize: vertical;
}
/* Requirement 2: .ejwoomattach */
.ejwoomattach {
width: 36px;
height: 36px;
background-color: green;
border-radius: 5px; /* "rounded corners" */
color: white;
font-size: 24px;
font-weight: bold;
line-height: 36px;
text-align: center;
cursor: pointer;
border: none;
}
/* Attachment Styles */
.ejwoomattached {
display: flex;
flex-direction: row;
width: 100%;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 5px;
overflow: hidden; /* Ensures single line */
cursor: pointer;
background-color: #f9f9f9;
}
.ejwoomattached:hover {
background-color: #f0f0f0;
}
.ejwoomattached span {
padding: 8px;
white-space: nowrap;
}
.wmattachtype {
background-color: #dbeafe; /* Light Blue */
font-weight: bold;
flex-shrink: 0;
}
.wmattachname {
background-color: #fef3c7; /* Light Yellow */
flex-grow: 1; /* Takes remaining space */
overflow: hidden;
text-overflow: ellipsis; /* ... as requested */
}
.wmattachsize {
background-color: #dcfce7; /* Light Green */
flex-shrink: 0;
font-size: 0.9em;
color: #333;
}
.wmattachdelete {
background-color: #fee2e2; /* Light Red */
flex-shrink: 0;
font-weight: bold;
cursor: pointer;
}
.wmattachdelete:hover {
background-color: #fca5a5; /* Darker Red */
}
/* Result Area */
.ejwoomresult {
display: none; /* Hidden by default */
padding: 10px;
border: 1px solid #ccc;
background-color: #fff;
border-radius: 5px;
}
.ejwoomdownload {
padding: 10px;
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 15px;
}
.ejwoomdownload a {
padding: 8px 12px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 4px;
margin-right: 10px;
}
.ejwoomdownload a:hover {
background-color: #218838;
}
.ejwoomdownloadsize {
font-size: 0.9em;
color: #555;
}
.ejwoompreview {
border: 1px dashed #ccc;
padding: 15px;
background-color: #fff;
min-height: 100px;
}
/* Fix for user's default content style */
.ejwoompreview > div[style] {
font-family: 'Courier New', Courier, monospace;
}
/* LocalStorage Modal */
#storageModal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
#storageModalContent {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 800px;
border-radius: 8px;
position: relative;
}
#storageModalClose {
color: #aaa;
position: absolute;
top: 10px;
right: 20px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
#storageModalClose:hover {
color: #000;
}
#storageList {
list-style: none;
padding: 0;
margin-top: 20px;
max-height: 60vh;
overflow-y: auto;
}
#storageList li {
display: flex;
border: 1px solid #eee;
margin-bottom: 5px;
align-items: center;
}
#storageList li > span {
padding: 10px;
border-left: 1px solid #eee;
}
.storage-delete {
background-color: #fee2e2;
padding: 10px;
cursor: pointer;
font-weight: bold;
border: none;
}
.storage-delete:hover {
background-color: #fca5a5;
}
.storage-item {
flex-grow: 1;
cursor: pointer;
padding: 10px;
}
.storage-item:hover {
background-color: #f0f0f0;
}
.storage-name {
font-weight: bold;
flex-basis: 40%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.storage-time {
flex-basis: 35%;
font-size: 0.9em;
color: #555;
}
.storage-size {
flex-basis: 20%;
font-size: 0.9em;
color: #555;
text-align: right;
}
</style>
</head>
<body>
<div class="ejwoomtab">
<span class="ejwoomedittab selected">Edit</span>
<span class="ejwoomresulttab">Result</span>
</div>
<div class="ejwoomedit">
<div class="ejwoomcontrol">
<span class="ejwoomundo">Undo</span>
<span class="ejwoomredo">Redo</span>
<span class="ejwoomreadfile">Read HTML File</span>
<span class="ejwoomstorage">Save to Storage</span>
</div>
<input class="ejwoomtitle" type="text" placeholder="Enter title here...">
<div class="ejwoombuttons">
<button data-tag="b"><b></button>
<button data-tag="h1"><h1></button>
<button data-tag="h2"><h2></button>
<button data-tag="h3"><h3></button>
<button data-tag="h4"><h4></button>
<button data-tag="h5"><h5></button>
<button data-tag="h6"><h6></button>
<button data-tag="u"><u></button>
<button data-tag="s"><s></button>
<button data-tag="i"><i></button>
<button data-snippet="<ul>\n <li></li>\n</ul>"><ul></button>
<button data-snippet="<ol>\n <li></li>\n</ol>"><ol></button>
<button data-snippet="<dl>\n <dt></dt>\n <dd></dd>\n</dl>"><dl></button>
<button data-snippet="<details>\n <summary></summary>\n \n</details>"><details></button>
<button data-snippet="<fieldset>\n <legend></legend>\n \n</fieldset>"><fieldset></button>
</div>
<textarea class="ejwoomcontent"></textarea>
<div class="ejwoom-attach-container">
<button class="ejwoomattach">+</button>
</div>
</div>
<input type="file" id="fileInput" style="display: none;" />
<input type="file" id="htmlFileInput" style="display: none;" accept=".html, .htm" />
<div id="storageModal">
<div id="storageModalContent">
<span id="storageModalClose">×</span>
<h2>Saved Items</h2>
<ul id="storageList">
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- DOM Element References ---
const editTab = document.querySelector('.ejwoomedittab');
const resultTab = document.querySelector('.ejwoomresulttab');
const editPanel = document.querySelector('.ejwoomedit');
const tabContainer = document.querySelector('.ejwoomtab');
const titleInput = document.querySelector('.ejwoomtitle');
const contentArea = document.querySelector('.ejwoomcontent');
const attachButton = document.querySelector('.ejwoomattach');
const attachContainer = document.querySelector('.ejwoom-attach-container');
const fileInput = document.getElementById('fileInput');
const htmlFileInput = document.getElementById('htmlFileInput');
const buttonContainer = document.querySelector('.ejwoombuttons');
// Control Bar
const controlBar = document.querySelector('.ejwoomcontrol');
const undoBtn = document.querySelector('.ejwoomundo');
const redoBtn = document.querySelector('.ejwoomredo');
const readFileBtn = document.querySelector('.ejwoomreadfile');
const storageBtn = document.querySelector('.ejwoomstorage');
// Storage Modal
const storageModal = document.getElementById('storageModal');
const storageModalClose = document.getElementById('storageModalClose');
const storageList = document.getElementById('storageList');
// --- State Variables ---
/**
* @type {Map<string, {name: string, type: string, size: number, base64: string, fileType: 'img' | 'file'}>}
*/
let attachedFiles = new Map();
let historyStack = [];
let redoStack = [];
let currentHistoryIndex = -1; // Points to the current state in historyStack
let isUndoingOrRedoing = false; // Flag to prevent saving state during undo/redo
// --- Requirement 4: Default Textarea Content ---
const defaultContent = `<div style="white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all;">
<h1></h1>
</div>`;
contentArea.value = defaultContent;
// --- Requirement 7: History (Undo/Redo) ---
/** Saves the current editor state to the history stack */
function saveState() {
if (isUndoingOrRedoing) return; // Don't save state while loading a state
const state = {
title: titleInput.value,
content: contentArea.value,
files: new Map(attachedFiles) // Clone the map
};
// Clear redo stack because we are starting a new timeline
redoStack = [];
// If we've undone, truncate the history
if (currentHistoryIndex < historyStack.length - 1) {
historyStack = historyStack.slice(0, currentHistoryIndex + 1);
}
historyStack.push(state);
currentHistoryIndex = historyStack.length - 1;
updateUndoRedoButtons();
}
/** Loads a specific state into the editor */
function loadState(state) {
isUndoingOrRedoing = true; // Set flag
try {
titleInput.value = state.title;
contentArea.value = state.content;
attachedFiles = new Map(state.files); // Restore files map
// Re-render attachment elements
// Clear existing elements first
document.querySelectorAll('.ejwoomattached').forEach(el => el.remove());
// Add new ones
attachedFiles.forEach(fileData => {
createAttachmentElement(fileData);
});
} finally {
isUndoingOrRedoing = false; // Unset flag
}
updateUndoRedoButtons();
}
function updateUndoRedoButtons() {
undoBtn.style.opacity = currentHistoryIndex > 0 ? '1' : '0.5';
redoBtn.style.opacity = redoStack.length > 0 ? '1' : '0.5';
}
undoBtn.addEventListener('click', () => {
if (currentHistoryIndex > 0) {
// Push current state to redo stack
redoStack.push(historyStack.pop());
currentHistoryIndex--;
loadState(historyStack[currentHistoryIndex]);
}
});
redoBtn.addEventListener('click', () => {
if (redoStack.length > 0) {
const stateToRestore = redoStack.pop();
historyStack.push(stateToRestore);
currentHistoryIndex++;
loadState(stateToRestore);
}
});
// Debounce function
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
// Save initial state
saveState();
// Add event listeners to save state on change
const debouncedSaveState = debounce(saveState, 500);
titleInput.addEventListener('input', debouncedSaveState);
contentArea.addEventListener('input', debouncedSaveState);
// --- Requirement 5: Editor Buttons ---
buttonContainer.addEventListener('click', (e) => {
if (e.target.tagName !== 'BUTTON') return;
const button = e.target;
const tag = button.dataset.tag;
const snippet = button.dataset.snippet;
const start = contentArea.selectionStart;
const end = contentArea.selectionEnd;
const selectedText = contentArea.value.substring(start, end);
let replacement = '';
if (snippet) {
replacement = snippet;
} else if (tag) {
replacement = `<${tag}>${selectedText}</${tag}>`;
}
// Insert the text
contentArea.value = contentArea.value.substring(0, start) + replacement + contentArea.value.substring(end);
// Adjust cursor position
if (selectedText) {
contentArea.focus();
contentArea.setSelectionRange(start + replacement.length, start + replacement.length);
} else if (snippet) {
// Try to place cursor in a good spot
const cursorOffset = replacement.indexOf('</') -1; // e.g., before </li>
const finalPos = start + (cursorOffset > 0 ? cursorOffset : replacement.length);
contentArea.focus();
contentArea.setSelectionRange(finalPos, finalPos);
} else {
const cursorOffset = tag.length + 2; // Place cursor inside the tags: <tag>|</tag>
contentArea.focus();
contentArea.setSelectionRange(start + cursorOffset, start + cursorOffset);
}
saveState(); // Save state immediately after button click
});
// --- Requirement 3: File Attachment ---
attachButton.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
// Check for duplicate filenames
if (attachedFiles.has(file.name)) {
alert('A file with this name has already been attached.');
fileInput.value = ''; // Reset input
return;
}
const reader = new FileReader();
reader.onload = (readEvent) => {
const base64 = readEvent.target.result;
const fileType = getFileType(file.type, file.name);
const fileData = {
name: file.name,
type: file.type,
size: file.size,
base64: base64,
fileType: fileType // 'img' or 'file'
};
attachedFiles.set(file.name, fileData);
createAttachmentElement(fileData);
saveState(); // Save state after attaching file
};
reader.readAsDataURL(file);
fileInput.value = ''; // Reset input to allow attaching the same file after deletion
});
function getFileType(mimeType, fileName) {
const imgTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/svg+xml'];
const imgExts = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
if (imgTypes.includes(mimeType)) {
return 'img';
}
const ext = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
if (imgExts.includes(ext)) {
return 'img';
}
// All other types
return 'file';
}
function createAttachmentElement(fileData) {
const div = document.createElement('div');
div.className = 'ejwoomattached';
div.dataset.filename = fileData.name;
const typeSpan = document.createElement('span');
typeSpan.className = 'wmattachtype';
typeSpan.textContent = fileData.fileType;
const nameSpan = document.createElement('span');
nameSpan.className = 'wmattachname';
nameSpan.textContent = fileData.name;
nameSpan.title = fileData.name; // Show full name on hover
const sizeSpan = document.createElement('span');
sizeSpan.className = 'wmattachsize';
sizeSpan.textContent = `${fileData.size} bytes`;
const deleteSpan = document.createElement('span');
deleteSpan.className = 'wmattachdelete';
deleteSpan.textContent = '×'; // Multiplication sign
div.append(typeSpan, nameSpan, sizeSpan, deleteSpan);
// Insert the new element *before* the attach button
attachContainer.insertBefore(div, attachButton);
// Add event listeners
// Click on delete span
deleteSpan.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent div click handler from firing
attachedFiles.delete(fileData.name);
div.remove();
saveState(); // Save state after deleting file
});
// Click on the whole div
div.addEventListener('click', () => {
const textToInsert = `[${fileData.name}]`;
const start = contentArea.selectionStart;
const end = contentArea.selectionEnd;
contentArea.value = contentArea.value.substring(0, start) + textToInsert + contentArea.value.substring(end);
contentArea.focus();
contentArea.setSelectionRange(start + textToInsert.length, start + textToInsert.length);
saveState(); // Save state
});
}
// --- Requirement 6: Tab Switching ---
editTab.addEventListener('click', () => {
showEditTab();
});
resultTab.addEventListener('click', () => {
showResultTab();
});
function showEditTab() {
editTab.classList.add('selected');
resultTab.classList.remove('selected');
editPanel.style.display = 'block';
// Destroy result panel
const resultPanel = document.querySelector('.ejwoomresult');
if (resultPanel) {
resultPanel.remove();
}
}
function showResultTab() {
resultTab.classList.add('selected');
editTab.classList.remove('selected');
editPanel.style.display = 'none';
buildResultPanel();
}
function buildResultPanel() {
// Destroy existing one if it exists (shouldn't, but safe)
document.querySelector('.ejwoomresult')?.remove();
// Create main container
const resultPanel = document.createElement('div');
resultPanel.className = 'ejwoomresult';
resultPanel.style.display = 'block';
// Create download and preview areas
const downloadDiv = document.createElement('div');
downloadDiv.className = 'ejwoomdownload';
const previewDiv = document.createElement('div');
previewDiv.className = 'ejwoompreview';
resultPanel.append(downloadDiv, previewDiv);
// Insert after the tab container
tabContainer.insertAdjacentElement('afterend', resultPanel);
// --- Process Content for Preview and Download ---
const rawContent = contentArea.value;
const processedContent = processContentForPreview(rawContent);
// Populate preview
previewDiv.innerHTML = processedContent;
// Build and populate download section
buildDownloadLink(downloadDiv, rawContent, processedContent);
}
/** Processes content, replacing [filename] tags */
function processContentForPreview(content) {
// Regex to find [filename]
return content.replace(/\[([^\]]+)\]/g, (match, filename) => {
if (attachedFiles.has(filename)) {
const fileData = attachedFiles.get(filename);
if (fileData.fileType === 'img') {
// Special handling for SVG to get dimensions
if (fileData.type === 'image/svg+xml') {
// Decode base64 to string
const svgString = atob(fileData.base64.split(',')[1]);
let width = 'auto';
let height = 'auto';
// Try to find width/height attributes
const widthMatch = svgString.match(/width="([^"]+)"/);
const heightMatch = svgString.match(/height="([^"]+)"/);
if (widthMatch) width = widthMatch[1];
if (heightMatch) height = heightMatch[1];
// If not found, try viewBox
if (width === 'auto' || height === 'auto') {
const viewBoxMatch = svgString.match(/viewBox="([\d\s\.]+)"/);
if (viewBoxMatch) {
const parts = viewBoxMatch[1].split(' ');
if (parts.length === 4) {
width = parts[2] + 'px';
height = parts[3] + 'px';
}
}
}
// Fallback: just use 100% width
if (width === 'auto') width = '100%';
return `<img src="${fileData.base64}" alt="${filename}" style="width: ${width}; height: ${height}; max-width: 100%;">`;
} else {
// For raster images (png, jpg), we can't get dimensions
// without loading them, which is slow and complex here.
// We will just set a max-width.
return `<img src="${fileData.base64}" alt="${filename}" style="max-width: 100%; height: auto;">`;
}
} else {
// 'file' type
return `<a href="${fileData.base64}" download="${filename}">${filename}</a>`;
}
}
return match; // Return original match if file not found
});
}
/** Builds the downloadable HTML file */
function buildDownloadLink(container, rawContent, processedContent) {
const title = titleInput.value || 'Untitled';
let styleTags = '';
// Regex to find and remove <style> tags from raw content
const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gmi;
const contentWithoutStyles = rawContent.replace(styleRegex, (match) => {
styleTags += match + '\n'; // Grab the style tag
return ''; // Remove it from the body
});
// Re-process content *without* styles for the body
const bodyContent = processContentForPreview(contentWithoutStyles);
// --- Modification for Requirement 7 (ReadFile) ---
// Embed editor state into the HTML for restoration
const editorState = {
title: titleInput.value,
content: contentArea.value,
files: Array.from(attachedFiles.values()) // Convert Map to Array for JSON
};
const stateScript = `<script id="ejwoom-data" type="application/json">${JSON.stringify(editorState)}</script>`;
const htmlContent = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${title}</title>
${stateScript}
${styleTags}
</head>
<body>
${bodyContent}
</body>
</html>`;
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const fileSize = blob.size;
const fileName = `${title}.html`;
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.textContent = `Download ${fileName}`;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'ejwoomdownloadsize';
sizeSpan.textContent = `(${fileSize} bytes)`;
container.innerHTML = ''; // Clear container
container.append(a, sizeSpan);
}
// --- Requirement 7: Control Bar ---
// Read File
readFileBtn.addEventListener('click', () => {
htmlFileInput.click();
});
htmlFileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (readEvent) => {
try {
const htmlText = readEvent.target.result;
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
const stateScript = doc.getElementById('ejwoom-data');
if (!stateScript) {
alert('This HTML file does not contain readable ejwoom data.');
return;
}
const state = JSON.parse(stateScript.textContent);
// Convert files array back to Map
const filesMap = new Map();
state.files.forEach(fileData => filesMap.set(fileData.name, fileData));
const newState = {
title: state.title,
content: state.content,
files: filesMap
};
// Load the state and save it to history
loadState(newState);
saveState(); // This becomes the new "current" state
} catch (err) {
console.error('Error reading file:', err);
alert('Failed to read or parse the HTML file.');
} finally {
htmlFileInput.value = ''; // Reset input
}
};
reader.readAsText(file);
});
// LocalStorage
const STORAGE_KEY = 'ejwoomStorage';
storageBtn.addEventListener('click', () => {
try {
const storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
const state = {
title: titleInput.value,
content: contentArea.value,
files: Array.from(attachedFiles.values())
};
const stateString = JSON.stringify(state);
const size = new Blob([stateString]).size;
const entry = {
id: Date.now(),
name: titleInput.value || 'Untitled',
time: new Date().toISOString(),
size: size,
state: state
};
storage.push(entry);
localStorage.setItem(STORAGE_KEY, JSON.stringify(storage));
alert('Saved to local storage.');
checkStorageButton();
} catch (err) {
console.error('Error saving to local storage:', err);
alert('Failed to save. Storage might be full.');
}
});
function checkStorageButton() {
const storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
let getBtn = document.querySelector('.ejwoomgetstorage');
if (storage.length > 0) {
if (!getBtn) {
getBtn = document.createElement('span');
getBtn.className = 'ejwoomgetstorage';
getBtn.textContent = 'Get from Storage';
getBtn.style.backgroundColor = '#cce5ff'; // Light blue
controlBar.appendChild(getBtn);
getBtn.addEventListener('click', showStorageModal);
}
} else {
getBtn?.remove();
}
}
function showStorageModal() {
populateStorageList();
storageModal.style.display = 'block';
}
function hideStorageModal() {
storageModal.style.display = 'none';
}
function populateStorageList() {
const storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
storageList.innerHTML = ''; // Clear list
if (storage.length === 0) {
storageList.innerHTML = '<li>No items saved.</li>';
return;
}
storage.reverse().forEach(entry => { // Show newest first
const li = document.createElement('li');
const deleteBtn = document.createElement('button');
deleteBtn.className = 'storage-delete';
deleteBtn.textContent = '×';
deleteBtn.dataset.id = entry.id;
deleteBtn.onclick = () => deleteStorageItem(entry.id);
const itemDiv = document.createElement('div');
itemDiv.className = 'storage-item';
itemDiv.dataset.id = entry.id;
itemDiv.onclick = () => loadStorageItem(entry.id);
const nameSpan = document.createElement('span');
nameSpan.className = 'storage-name';
nameSpan.textContent = entry.name;
const timeSpan = document.createElement('span');
timeSpan.className = 'storage-time';
// Format yyyy-mm-dd hh:ii:ss
timeSpan.textContent = entry.time.replace('T', ' ').substring(0, 19);
const sizeSpan = document.createElement('span');
sizeSpan.className = 'storage-size';
sizeSpan.textContent = `${entry.size} bytes`;
itemDiv.append(nameSpan, timeSpan, sizeSpan);
li.append(deleteBtn, itemDiv);
storageList.appendChild(li);
});
}
function deleteStorageItem(id) {
let storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
storage = storage.filter(item => item.id !== id);
localStorage.setItem(STORAGE_KEY, JSON.stringify(storage));
populateStorageList(); // Refresh list
checkStorageButton(); // Hide "Get" button if empty
}
function loadStorageItem(id) {
const storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
const entry = storage.find(item => item.id === id);
if (entry) {
// Convert files array back to Map
const filesMap = new Map();
entry.state.files.forEach(fileData => filesMap.set(fileData.name, fileData));
const newState = {
title: entry.state.title,
content: entry.state.content,
files: filesMap
};
loadState(newState);
saveState(); // Save this loaded state as the new history point
hideStorageModal();
}
}
storageModalClose.addEventListener('click', hideStorageModal);
// Initial check on load
checkStorageButton();
});
</script>
</body>
</html>