來做一個html生成器
Re: 來做一個html生成器
之前當點擊span.ejwoomaddbutton{+}時,將彈出一個對話框div.ejwoomwindow。
現在不行了,查找原因並修正。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ejwoom HTML Editor</title>
<style>
body {font-family:system-ui,sans-serif;margin:0;padding:0;background:#f5f5f5;}
.ejwoomtab {display:flex;background:#333;color:white;}
.ejwoomtab span {padding:12px 20px;cursor:pointer;}
.ejwoomtab span.active {background:#007bff;}
.ejwoomedit, .ejwoomresult {
padding:20px;background:white;min-height:100vh;display:none;
}
.ejwoomedit.active, .ejwoomresult.active {display:block;}
.ejwoomcontrol {
margin-bottom:15px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;
}
.ejwoomcontrol span {
padding:6px 12px;background:#eee;border-radius:4px;cursor:pointer;
}
.ejwoomcontrol span:hover {background:#ddd;}
.ejwoomtitle {
width:100%;padding:10px;font-size:18px;border:1px solid #ccc;
border-radius:4px;box-sizing:border-box;margin-bottom:15px;
}
.ejwoombuttons {
display:flex;gap:8px;flex-wrap:wrap;margin-bottom:15px;
}
.ejwoombuttons span {
padding:3px 7px;background:#007bff;color:white;border:none;
border-radius:4px;cursor:pointer;font-size:14px;
}
.ejwoombuttons span:hover {background:#0056b3;}
.ejwoomcontent {
width:100%;height:70vh;padding:12px;font-size:16px;
font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;
border:1px solid #ccc;border-radius:4px;box-sizing:border-box;
resize:vertical;
}
.ejwoomattach {
width:36px;height:36px;background:#28a745;color:white;border:none;
border-radius:8px;font-size:20px;cursor:pointer;
display:flex;align-items:center;justify-content:center;margin-top:10px;
}
.ejwoomattached {
display:flex;align-items:center;gap:8px;
background:#f8f9fa;border:1px solid #dee2e6;border-radius:6px;
padding:8px;margin:8px 0;font-size:14px;cursor:pointer;
}
.wmattachtype {
background:#e3f2fd;line-height:24px;border-radius:4px;
font-weight:bold;color:#1976d2;padding:0 7px;text-align:center;
}
.wmattachname {
flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
white-space:nowrap;background:#f5f5f5;padding:0 8px;
border-radius:4px;color:#333;margin-right:8px;
}
.wmattachsize {
background:#e8f5e8;color:#2e7d32;font-weight:500;
padding:2px 6px;border-radius:4px;margin-right:8px;font-size:12px;
}
.wmattachdelete {
background:#ffebee;color:#d32f2f;width:24px;height:24px;
border-radius:50%;line-height:22px!important;font-size:18px;
text-align:center;cursor:pointer;
}
.ejwoomdownload {margin-bottom:20px;}
.ejwoomdownload a {
padding:10px 16px;background:#28a745;color:white;
text-decoration:none;border-radius:6px;display:inline-block;
}
.ejwoomdownloadsize {
margin-left:15px;color:#666;font-size:14px;
}
.ejwoompreview {
border:1px solid #ddd;padding:20px;background:white;
border-radius:6px;min-height:70vh;overflow:auto;
}
/* Custom Button Styles */
.ejwoomaddbutton {
padding:3px 7px;background:#6c757d;color:white;border-radius:4px;
cursor:pointer;font-size:14px;font-weight:bold;
}
.ejwoomaddbutton:hover {background:#5a6268;}
.ejwoomcustombutton {
padding:3px 7px;background:#17a2b8;color:white;border-radius:4px;
cursor:pointer;font-size:14px;
}
.ejwoomcustombutton:hover {background:#138496;}
/* Modal Window */
.ejwoomwindow {
position:fixed;inset:0;background:rgba(0,0,0,0.7);
display:none;align-items:center;justify-content:center;z-index:999;
}
.ejwoomwindow.active {display:flex;}
.ejwoomwindow-content {
background:white;padding:28px 32px;border-radius:12px;
width:90%;max-width:540px;max-height:90vh;overflow-y:auto;
box-shadow:0 20px 40px rgba(0,0,0,0.22);
display:flex;flex-direction:column;gap:18px;
font-family:system-ui,sans-serif;
}
.ejwoom-btn-selector {
display:flex;flex-wrap:wrap;gap:10px;
border-bottom:1px solid #eee;padding-bottom:15px;
}
.ejwoomselbutton {
padding:6px 14px;background:#e9ecef;border-radius:6px;
cursor:pointer;user-select:none;transition:all 0.2s;font-weight:500;
}
.ejwoomselbutton:hover {background:#dee2e6;}
.ejwoomselbutton.selected {
background:#007bff;color:white;
}
.ejwoomselbutton.selected:hover {background:#0056b3;}
.ejwoom-input-row {
display:flex;align-items:center;gap:10px;
}
.ejwoom-input-row label {flex-basis:100px;flex-shrink:0;}
.ejwoom-input-row input[type="text"] {
flex-grow:1;padding:8px;border:1px solid #ccc;border-radius:4px;
}
.ejwoom-modal-controls {
display:flex;gap:10px;justify-content:flex-end;
padding-top:10px;border-top:1px solid #eee;
}
.ejwoom-modal-controls button {
border:none;padding:8px 16px;border-radius:4px;
cursor:pointer;color:white;font-weight:500;
}
.wmconfirm {background:#007bff;}
.wmconfirm:hover {background:#0056b3;}
.wmdelete {background:#dc3545;}
.wmdelete:hover {background:#c82333;}
.wmcancel {background:#6c757d;}
.wmcancel:hover {background:#5a6268;}
.wmdelete:disabled {opacity:0.5;cursor:not-allowed;background:#ccc;}
</style>
</head>
<body>
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
</div>
<div class="ejwoomedit active">
<div class="ejwoomcontrol">
<span class="ejwoomundo">Undo</span>
<span class="ejwoomredo">Redo</span>
</div>
<input type="text" class="ejwoomtitle" placeholder="Enter title...">
<div class="ejwoombuttons">
<span>b</span><span>u</span><span>s</span><span>i</span>
<span>h1</span><span>h2</span><span>h3</span><span>h4</span><span>h5</span><span>h6</span>
<span>ul</span><span>ol</span><span>li</span><span>dl</span><span>dt</span><span>dd</span>
<span class="ejwoomaddbutton">+</span>
</div>
<textarea class="ejwoomcontent"><div style="white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all;">
<h1>Hello World</h1>
</div></textarea>
<div id="attachments"></div>
<button class="ejwoomattach" title="Attach file">+</button>
</div>
<div class="ejwoomresult"></div>
<input type="file" id="attachfile" style="display:none;" multiple>
<script>
document.addEventListener('DOMContentLoaded', function () {
const editTab = document.querySelector('.ejwoomedittab');
const resultTab = document.querySelector('.ejwoomresulttab');
const editDiv = document.querySelector('.ejwoomedit');
const resultDiv = document.querySelector('.ejwoomresult');
const titleInput = document.querySelector('.ejwoomtitle');
const textarea = document.querySelector('.ejwoomcontent');
const attachBtn = document.querySelector('.ejwoomattach');
const attachInput = document.getElementById('attachfile');
const attachmentsContainer = document.getElementById('attachments');
const buttonsContainer = document.querySelector('.ejwoombuttons');
const addButton = document.querySelector('.ejwoomaddbutton');
const attachedFiles = new Map();
const imageExts = new Set(['png','jpg','jpeg','gif','svg','webp','bmp','ico']);
// === Helpers ===
function getExt(name) {
const i = name.lastIndexOf('.');
return i === -1 ? '' : name.slice(i + 1).toLowerCase();
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// === Attachment System ===
function createAttachmentRow(file) {
const row = document.createElement('div');
row.className = 'ejwoomattached';
const ext = getExt(file.name);
const typeSpan = document.createElement('span');
typeSpan.className = 'wmattachtype';
typeSpan.textContent = (imageExts.has(ext) ? 'img' : 'file') + '/' + ext;
const nameSpan = document.createElement('span');
nameSpan.className = 'wmattachname';
nameSpan.textContent = file.name;
nameSpan.title = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'wmattachsize';
sizeSpan.textContent = formatBytes(file.size);
const deleteSpan = document.createElement('span');
deleteSpan.className = 'wmattachdelete';
deleteSpan.textContent = '×';
deleteSpan.onclick = (e) => {
e.stopPropagation();
row.remove();
attachedFiles.delete(file.name);
};
const insertRef = () => {
const start = textarea.selectionStart;
const ref = imageExts.has(ext) ? `[img:${file.name}]` : `[file:${file.name}]`;
textarea.value = textarea.value.slice(0, start) + ref + textarea.value.slice(start);
textarea.focus();
textarea.setSelectionRange(start + ref.length, start + ref.length);
};
row.onclick = (e) => {
if (e.target === deleteSpan) return;
insertRef();
};
row.append(typeSpan, nameSpan, sizeSpan, deleteSpan);
return row;
}
attachBtn.onclick = () => attachInput.click();
attachInput.onchange = e => {
for (let file of e.target.files) {
if (attachedFiles.has(file.name)) {
alert(`File "${file.name}" is already attached.`);
continue;
}
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(file.name, file);
}
attachInput.value = '';
};
// === Button Wrapping ===
function wrapSelection(before, after = '') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) + before + selected + after + textarea.value.substring(end);
textarea.focus();
textarea.setSelectionRange(start + before.length, start + before.length + selected.length);
}
// Default buttons
document.querySelectorAll('.ejwoombuttons span:not(.ejwoomaddbutton):not(.ejwoomcustombutton)').forEach(span => {
span.onclick = () => wrapSelection(`<${span.textContent}>`, `</${span.textContent}>`);
});
// === Custom Button Editor (Fixed!) ===
addButton.onclick = function () {
const modal = document.createElement('div');
modal.className = 'ejwoomwindow active';
modal.innerHTML = `
<div class="ejwoomwindow-content">
<div class="ejwoom-btn-selector">
<span class="ejwoomselbutton selected" data-value="new">new</span>
</div>
<div class="ejwoom-input-row"><label>Name</label><input type="text" placeholder="e.g. blockquote"></div>
<div class="ejwoom-input-row"><label>Before HTML</label><input type="text" placeholder="<div class="tip">"></div>
<div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="</div>"></div>
<div class="ejwoom-modal-controls">
<button class="wmconfirm">Confirm</button>
<button class="wmdelete" disabled>Delete</button>
<button class="wmcancel">Cancel</button>
</div>
</div>`;
document.body.appendChild(modal);
const selector = modal.querySelector('.ejwoom-btn-selector');
const nameInp = modal.querySelectorAll('input')[0];
const beforeInp = modal.querySelectorAll('input')[1];
const afterInp = modal.querySelectorAll('input')[2];
const confirmBtn = modal.querySelector('.wmconfirm');
const deleteBtn = modal.querySelector('.wmdelete');
const cancelBtn = modal.querySelector('.wmcancel');
// Load existing custom buttons into selector
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(btn => {
const sel = document.createElement('span');
sel.className = 'ejwoomselbutton';
sel.textContent = btn.textContent;
sel.dataset.value = btn.textContent;
sel._target = btn;
selector.appendChild(sel);
});
function updateForm() {
const selected = selector.querySelector('.selected');
if (!selected || selected.dataset.value === 'new') {
nameInp.value = beforeInp.value = afterInp.value = '';
deleteBtn.disabled = true;
} else {
const target = selected._target;
nameInp.value = target.textContent;
beforeInp.value = target.dataset.before ? atob(target.dataset.before) : '';
afterInp.value = target.dataset.after ? atob(target.dataset.after) : '';
deleteBtn.disabled = false;
}
}
selector.onclick = e => {
const btn = e.target.closest('.ejwoomselbutton');
if (!btn) return;
selector.querySelectorAll('.ejwoomselbutton').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
updateForm();
};
deleteBtn.onclick = () => {
const selected = selector.querySelector('.selected');
if (selected && selected.dataset.value !== 'new') {
selected._target.remove();
selected.remove();
selector.querySelector('[data-value="new"]').classList.add('selected');
updateForm();
}
};
confirmBtn.onclick = () => {
const name = nameInp.value.trim();
if (!name) return alert('Button name is required');
const selected = selector.querySelector('.selected');
let targetSpan;
if (selected.dataset.value === 'new') {
targetSpan = document.createElement('span');
targetSpan.className = 'ejwoomcustombutton';
buttonsContainer.insertBefore(targetSpan, addButton);
const newSel = document.createElement('span');
newSel.className = 'ejwoomselbutton';
newSel.textContent = name;
newSel.dataset.value = name;
newSel._target = targetSpan;
selector.appendChild(newSel);
} else {
targetSpan = selected._target;
}
targetSpan.textContent = name;
if (beforeInp.value) targetSpan.dataset.before = btoa(beforeInp.value);
else delete targetSpan.dataset.before;
if (afterInp.value) targetSpan.dataset.after = btoa(afterInp.value);
else delete targetSpan.dataset.after;
// Apply click behavior
targetSpan.onclick = () => {
const before = targetSpan.dataset.before ? atob(targetSpan.dataset.before) : '';
const after = targetSpan.dataset.after ? atob(targetSpan.dataset.after) : '';
wrapSelection(before, after);
};
modal.remove();
};
cancelBtn.onclick = () => modal.remove();
updateForm();
};
// === Preview & Download (Fixed filename + [img:] [file:]) ===
async function buildResult() {
resultDiv.innerHTML = `
<div class="ejwoomdownload">
<a id="downloadlink" download="">Download HTML</a>
<span class="ejwoomdownloadsize"></span>
</div>
<div class="ejwoompreview"></div>
`;
const downloadLink = resultDiv.querySelector('#downloadlink');
const sizeSpan = resultDiv.querySelector('.ejwoomdownloadsize');
const previewDiv = resultDiv.querySelector('.ejwoompreview');
let title = (titleInput.value || 'Untitled').trim();
let content = textarea.value.trim();
let styleContent = '';
content = content.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
styleContent += css + '\n';
return '';
});
let processedContent = content;
const replacements = new Map();
for (const [name, file] of attachedFiles) {
const base64 = await fileToBase64(file);
const ext = getExt(name);
const isImage = imageExts.has(ext);
let html = '';
if (isImage) {
let dims = '';
if (ext === 'svg') {
try {
const svg = atob(base64.split(',')[1]);
const w = svg.match(/width=["'](\d+)["']/i);
const h = svg.match(/height=["'](\d+)["']/i);
if (w) dims += ` width="${w[1]}"`;
if (h) dims += ` height="${h[1]}"`;
} catch (e) {}
}
html = `<img src="${base64}" alt="${name}" style="max-width:100%;height:auto;"${dims}>`;
} else {
html = `<a href="${base64}" download="${name}">Download ${name}</a>`;
}
replacements.set(`[img:${name}]`, html);
replacements.set(`[file:${name}]`, html);
}
for (const [key, val] of replacements) {
processedContent = processedContent.replace(
new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), val
);
}
previewDiv.innerHTML = processedContent;
const fullHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
${styleContent ? '<style>' + styleContent.trim() + '</style>' : ''}
</head>
<body>
${processedContent}
</body>
</html>`;
const blob = new Blob([fullHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = title + '.html';
downloadLink.textContent = 'Download ' + title + '.html';
sizeSpan.textContent = formatBytes(blob.size);
}
// === Tab Switching ===
resultTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
resultTab.classList.add('active');
editDiv.classList.remove('active');
resultDiv.classList.add('active');
buildResult();
};
editTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
editTab.classList.add('active');
editDiv.classList.add('active');
resultDiv.classList.remove('active');
};
});
</script>
</body>
</html>https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
之前當點擊span.wmattachdelete時,這個div.ejwoomattached將被刪除。
現在要求textarea.ejwoomcontent中的所有「[wmattachname]」也要一起被刪除。
所有代碼都要使用英文。
代码: 全选
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ejwoom HTML Editor</title>
<style>
body {font-family:system-ui,sans-serif;margin:0;padding:0;background:#f5f5f5;}
.ejwoomtab {display:flex;background:#333;color:white;}
.ejwoomtab span {padding:12px 20px;cursor:pointer;}
.ejwoomtab span.active {background:#007bff;}
.ejwoomedit, .ejwoomresult {
padding:20px;background:white;min-height:100vh;display:none;
}
.ejwoomedit.active, .ejwoomresult.active {display:block;}
.ejwoomcontrol {
margin-bottom:15px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;
}
.ejwoomcontrol span {
padding:6px 12px;background:#eee;border-radius:4px;cursor:pointer;
}
.ejwoomcontrol span:hover {background:#ddd;}
.ejwoomtitle {
width:100%;padding:10px;font-size:18px;border:1px solid #ccc;
border-radius:4px;box-sizing:border-box;margin-bottom:15px;
}
.ejwoombuttons {
display:flex;gap:8px;flex-wrap:wrap;margin-bottom:15px;
}
.ejwoombuttons span {
padding:3px 7px;background:#007bff;color:white;border:none;
border-radius:4px;cursor:pointer;font-size:14px;
}
.ejwoombuttons span:hover {background:#0056b3;}
.ejwoomcontent {
width:100%;height:70vh;padding:12px;font-size:16px;
font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;
border:1px solid #ccc;border-radius:4px;box-sizing:border-box;
resize:vertical;
}
.ejwoomattach {
width:36px;height:36px;background:#28a745;color:white;border:none;
border-radius:8px;font-size:20px;cursor:pointer;
display:flex;align-items:center;justify-content:center;margin-top:10px;
}
.ejwoomattached {
display:flex;align-items:center;gap:8px;
background:#f8f9fa;border:1px solid #dee2e6;border-radius:6px;
padding:8px;margin:8px 0;font-size:14px;cursor:pointer;
}
.wmattachtype {
background:#e3f2fd;line-height:24px;border-radius:4px;
font-weight:bold;color:#1976d2;padding:0 7px;text-align:center;
}
.wmattachname {
flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
white-space:nowrap;background:#f5f5f5;padding:0 8px;
border-radius:4px;color:#333;margin-right:8px;
}
.wmattachsize {
background:#e8f5e8;color:#2e7d32;font-weight:500;
padding:2px 6px;border-radius:4px;margin-right:8px;font-size:12px;
}
.wmattachdelete {
background:#ffebee;color:#d32f2f;width:24px;height:24px;
border-radius:50%;line-height:22px!important;font-size:18px;
text-align:center;cursor:pointer;
}
.ejwoomdownload {margin-bottom:20px;}
.ejwoomdownload a {
padding:10px 16px;background:#28a745;color:white;
text-decoration:none;border-radius:6px;display:inline-block;
}
.ejwoomdownloadsize {
margin-left:15px;color:#666;font-size:14px;
}
.ejwoompreview {
border:1px solid #ddd;padding:20px;background:white;
border-radius:6px;min-height:70vh;overflow:auto;
}
.ejwoomaddbutton {
padding:3px 7px;background:#6c757d;color:white;border-radius:4px;
cursor:pointer;font-size:14px;font-weight:bold;
}
.ejwoomaddbutton:hover {background:#5a6268;}
.ejwoomcustombutton {
padding:3px 7px;background:#17a2b8;color:white;border-radius:4px;
cursor:pointer;font-size:14px;
}
.ejwoomcustombutton:hover {background:#138496;}
.ejwoomwindow {
position:fixed;inset:0;background:rgba(0,0,0,0.7);
display:none;align-items:center;justify-content:center;z-index:999;
}
.ejwoomwindow.active {display:flex;}
.ejwoomwindow-content {
background:white;padding:28px 32px;border-radius:12px;
width:90%;max-width:540px;max-height:90vh;overflow-y:auto;
box-shadow:0 20px 40px rgba(0,0,0,0.22);
display:flex;flex-direction:column;gap:18px;
font-family:system-ui,sans-serif;
}
.ejwoom-btn-selector {
display:flex;flex-wrap:wrap;gap:10px;
border-bottom:1px solid #eee;padding-bottom:15px;
}
.ejwoomselbutton {
padding:6px 14px;background:#e9ecef;border-radius:6px;
cursor:pointer;user-select:none;transition:all 0.2s;font-weight:500;
}
.ejwoomselbutton:hover {background:#dee2e6;}
.ejwoomselbutton.selected {
background:#007bff;color:white;
}
.ejwoomselbutton.selected:hover {background:#0056b3;}
.ejwoom-input-row {
display:flex;align-items:center;gap:10px;
}
.ejwoom-input-row label {flex-basis:100px;flex-shrink:0;}
.ejwoom-input-row input[type="text"] {
flex-grow:1;padding:8px;border:1px solid #ccc;border-radius:4px;
}
.ejwoom-modal-controls {
display:flex;gap:10px;justify-content:flex-end;
padding-top:10px;border-top:1px solid #eee;
}
.ejwoom-modal-controls button {
border:none;padding:8px 16px;border-radius:4px;
cursor:pointer;color:white;font-weight:500;
}
.wmconfirm {background:#007bff;}
.wmconfirm:hover {background:#0056b3;}
.wmdelete {background:#dc3545;}
.wmdelete:hover {background:#c82333;}
.wmcancel {background:#6c757d;}
.wmcancel:hover {background:#5a6268;}
.wmdelete:disabled {opacity:0.5;cursor:not-allowed;background:#ccc;}
</style>
</head>
<body>
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
</div>
<div class="ejwoomedit active">
<div class="ejwoomcontrol">
<span class="ejwoomundo">Undo</span>
<span class="ejwoomredo">Redo</span>
</div>
<input type="text" class="ejwoomtitle" placeholder="Enter title...">
<div class="ejwoombuttons">
<span>b</span><span>u</span><span>s</span><span>i</span>
<span>h1</span><span>h2</span><span>h3</span><span>h4</span><span>h5</span><span>h6</span>
<span>ul</span><span>ol</span><span>li</span><span>dl</span><span>dt</span><span>dd</span>
<span class="ejwoomaddbutton">+</span>
</div>
<textarea class="ejwoomcontent"><div style="white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all;">
<h1>Hello World</h1>
</div></textarea>
<div id="attachments"></div>
<button class="ejwoomattach" title="Attach file">+</button>
</div>
<div class="ejwoomresult"></div>
<input type="file" id="attachfile" style="display:none;" multiple>
<script>
document.addEventListener('DOMContentLoaded', function () {
const editTab = document.querySelector('.ejwoomedittab');
const resultTab = document.querySelector('.ejwoomresulttab');
const editDiv = document.querySelector('.ejwoomedit');
const resultDiv = document.querySelector('.ejwoomresult');
const titleInput = document.querySelector('.ejwoomtitle');
const textarea = document.querySelector('.ejwoomcontent');
const attachBtn = document.querySelector('.ejwoomattach');
const attachInput = document.getElementById('attachfile');
const attachmentsContainer = document.getElementById('attachments');
const buttonsContainer = document.querySelector('.ejwoombuttons');
const addButton = document.querySelector('.ejwoomaddbutton');
const attachedFiles = new Map();
const imageExts = new Set(['png','jpg','jpeg','gif','svg','webp','bmp','ico']);
// === Helpers ===
function getExt(name) {
const i = name.lastIndexOf('.');
return i === -1 ? '' : name.slice(i + 1).toLowerCase();
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// === Remove references from textarea when deleting attachment ===
function removeReferencesFromTextarea(filename) {
const esc = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regexImg = new RegExp(`\\[img:${esc}\\]`, 'g');
const regexFile = new RegExp(`\\[file:${esc}\\]`, 'g');
textarea.value = textarea.value
.replace(regexImg, '')
.replace(regexFile, '');
}
// === Attachment System ===
function createAttachmentRow(file) {
const row = document.createElement('div');
row.className = 'ejwoomattached';
const ext = getExt(file.name);
const typeSpan = document.createElement('span');
typeSpan.className = 'wmattachtype';
typeSpan.textContent = (imageExts.has(ext) ? 'img' : 'file') + '/' + ext;
const nameSpan = document.createElement('span');
nameSpan.className = 'wmattachname';
nameSpan.textContent = file.name;
nameSpan.title = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'wmattachsize';
sizeSpan.textContent = formatBytes(file.size);
const deleteSpan = document.createElement('span');
deleteSpan.className = 'wmattachdelete';
deleteSpan.textContent = '×';
deleteSpan.onclick = (e) => {
e.stopPropagation();
// Remove all [img:filename] and [file:filename] from textarea
removeReferencesFromTextarea(file.name);
// Remove UI row
row.remove();
// Remove from Map
attachedFiles.delete(file.name);
};
const insertRef = () => {
const start = textarea.selectionStart;
const ref = imageExts.has(ext) ? `[img:${file.name}]` : `[file:${file.name}]`;
textarea.value = textarea.value.slice(0, start) + ref + textarea.value.slice(start);
textarea.focus();
textarea.setSelectionRange(start + ref.length, start + ref.length);
};
row.onclick = (e) => {
if (e.target === deleteSpan) return;
insertRef();
};
row.append(typeSpan, nameSpan, sizeSpan, deleteSpan);
return row;
}
attachBtn.onclick = () => attachInput.click();
attachInput.onchange = e => {
for (let file of e.target.files) {
if (attachedFiles.has(file.name)) {
alert(`File "${file.name}" is already attached.`);
continue;
}
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(file.name, file);
}
attachInput.value = '';
};
// === Button Wrapping ===
function wrapSelection(before, after = '') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) + before + selected + after + textarea.value.substring(end);
textarea.focus();
textarea.setSelectionRange(start + before.length, start + before.length + selected.length);
}
// Default buttons
document.querySelectorAll('.ejwoombuttons span:not(.ejwoomaddbutton):not(.ejwoomcustombutton)').forEach(span => {
span.onclick = () => wrapSelection(`<${span.textContent}>`, `</${span.textContent}>`);
});
// === Custom Button Editor (unchanged) ===
addButton.onclick = function () {
const modal = document.createElement('div');
modal.className = 'ejwo Aomwindow active';
modal.innerHTML = `
<div class="ejwoomwindow-content">
<div class="ejwoom-btn-selector">
<span class="ejwoomselbutton selected" data-value="new">new</span>
</div>
<div class="ejwoom-input-row"><label>Name</label><input type="text" placeholder="e.g. blockquote"></div>
<div class="ejwoom-input-row"><label>Before HTML</label><input type="text" placeholder="<div class="tip">"></div>
<div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="</div>"></div>
<div class="ejwoom-modal-controls">
<button class="wmconfirm">Confirm</button>
<button class="wmdelete" disabled>Delete</button>
<button class="wmcancel">Cancel</button>
</div>
</div>`;
document.body.appendChild(modal);
const selector = modal.querySelector('.ejwoom-btn-selector');
const nameInp = modal.querySelectorAll('input')[0];
const beforeInp = modal.querySelectorAll('input')[1];
const afterInp = modal.querySelectorAll('input')[2];
const confirmBtn = modal.querySelector('.wmconfirm');
const deleteBtn = modal.querySelector('.wmdelete');
const cancelBtn = modal.querySelector('.wmcancel');
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(btn => {
const sel = document.createElement('span');
sel.className = 'ejwoomselbutton';
sel.textContent = btn.textContent;
sel.dataset.value = btn.textContent;
sel._target = btn;
selector.appendChild(sel);
});
function updateForm() {
const selected = selector.querySelector('.selected');
if (!selected || selected.dataset.value === 'new') {
nameInp.value = beforeInp.value = afterInp.value = '';
deleteBtn.disabled = true;
} else {
const target = selected._target;
nameInp.value = target.textContent;
beforeInp.value = target.dataset.before ? atob(target.dataset.before) : '';
afterInp.value = target.dataset.after ? atob(target.dataset.after) : '';
deleteBtn.disabled = false;
}
}
selector.onclick = e => {
const btn = e.target.closest('.ejwoomselbutton');
if (!btn) return;
selector.querySelectorAll('.ejwoomselbutton').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
updateForm();
};
deleteBtn.onclick = () => {
const selected = selector.querySelector('.selected');
if (selected && selected.dataset.value !== 'new') {
selected._target.remove();
selected.remove();
selector.querySelector('[data-value="new"]').classList.add('selected');
updateForm();
}
};
confirmBtn.onclick = () => {
const name = nameInp.value.trim();
if (!name) return alert('Button name is required');
const selected = selector.querySelector('.selected');
let targetSpan;
if (selected.dataset.value === 'new') {
targetSpan = document.createElement('span');
targetSpan.className = 'ejwoomcustombutton';
buttonsContainer.insertBefore(targetSpan, addButton);
const newSel = document.createElement('span');
newSel.className = 'ejwoomselbutton';
newSel.textContent = name;
newSel.dataset.value = name;
newSel._target = targetSpan;
selector.appendChild(newSel);
} else {
targetSpan = selected._target;
}
targetSpan.textContent = name;
if (beforeInp.value) targetSpan.dataset.before = btoa(beforeInp.value);
else delete targetSpan.dataset.before;
if (afterInp.value) targetSpan.dataset.after = btoa(afterInp.value);
else delete targetSpan.dataset.after;
targetSpan.onclick = () => {
const before = targetSpan.dataset.before ? atob(targetSpan.dataset.before) : '';
const after = targetSpan.dataset.after ? atob(targetSpan.dataset.after) : '';
wrapSelection(before, after);
};
modal.remove();
};
cancelBtn.onclick = () => modal.remove();
updateForm();
};
// === Preview & Download ===
async function buildResult() {
resultDiv.innerHTML = `
<div class="ejwoomdownload">
<a id="downloadlink" download="">Download HTML</a>
<span class="ejwoomdownloadsize"></span>
</div>
<div class="ejwoompreview"></div>
`;
const downloadLink = resultDiv.querySelector('#downloadlink');
const sizeSpan = resultDiv.querySelector('.ejwoomdownloadsize');
const previewDiv = resultDiv.querySelector('.ejwoompreview');
let title = (titleInput.value || 'Untitled').trim();
let content = textarea.value.trim();
let styleContent = '';
content = content.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
styleContent += css + '\n';
return '';
});
let processedContent = content;
const replacements = new Map();
for (const [name, file] of attachedFiles) {
const base64 = await fileToBase64(file);
const ext = getExt(name);
const isImage = imageExts.has(ext);
let html = '';
if (isImage) {
let dims = '';
if (ext === 'svg') {
try {
const svg = atob(base64.split(',')[1]);
const w = svg.match(/width=["'](\d+)["']/i);
const h = svg.match(/height=["'](\d+)["']/i);
if (w) dims += ` width="${w[1]}"`;
if (h) dims += ` height="${h[1]}"`;
} catch (e) {}
}
html = `<img src="${base64}" alt="${name}" style="max-width:100%;height:auto;"${dims}>`;
} else {
html = `<a href="${base64}" download="${name}">Download ${name}</a>`;
}
replacements.set(`[img:${name}]`, html);
replacements.set(`[file:${name}]`, html);
}
for (const [key, val] of replacements) {
processedContent = processedContent.replace(
new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), val
);
}
previewDiv.innerHTML = processedContent;
const fullHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
${styleContent ? '<style>' + styleContent.trim() + '</style>' : ''}
</head>
<body>
${processedContent}
</body>
</html>`;
const blob = new Blob([fullHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = title + '.html';
downloadLink.textContent = 'Download ' + title + '.html';
sizeSpan.textContent = formatBytes(blob.size);
}
// === Tab Switching ===
resultTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
resultTab.classList.add('active');
editDiv.classList.remove('active');
resultDiv.classList.add('active');
buildResult();
};
editTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
editTab.classList.add('active');
editDiv.classList.add('active');
resultDiv.classList.remove('active');
};
});
</script>
</body>
</html>https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ejwoom HTML Editor</title>
<style>
body {font-family:system-ui,sans-serif;margin:0;padding:0;background:#f5f5f5;}
.ejwoomtab {display:flex;background:#333;color:white;}
.ejwoomtab span {padding:12px 20px;cursor:pointer;}
.ejwoomtab span.active {background:#007bff;}
.ejwoomedit, .ejwoomresult {
padding:20px;background:white;min-height:100vh;display:none;
}
.ejwoomedit.active, .ejwoomresult.active {display:block;}
.ejwoomcontrol {
margin-bottom:15px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;
}
.ejwoomcontrol span {
padding:6px 12px;background:#eee;border-radius:4px;cursor:pointer;
}
.ejwoomcontrol span:hover {background:#ddd;}
.ejwoomtitle {
width:100%;padding:10px;font-size:18px;border:1px solid #ccc;
border-radius:4px;box-sizing:border-box;margin-bottom:15px;
}
.ejwoombuttons {
display:flex;gap:8px;flex-wrap:wrap;margin-bottom:15px;
}
.ejwoombuttons span {
padding:3px 7px;background:#007bff;color:white;border:none;
border-radius:4px;cursor:pointer;font-size:14px;
}
.ejwoombuttons span:hover {background:#0056b3;}
.ejwoomcontent {
width:100%;height:70vh;padding:12px;font-size:16px;
font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;
border:1px solid #ccc;border-radius:4px;box-sizing:border-box;
resize:vertical;
}
.ejwoomattach {
width:36px;height:36px;background:#28a745;color:white;border:none;
border-radius:8px;font-size:20px;cursor:pointer;
display:flex;align-items:center;justify-content:center;margin-top:10px;
}
.ejwoomattached {
display:flex;align-items:center;gap:8px;
background:#f8f9fa;border:1px solid #dee2e6;border-radius:6px;
padding:8px;margin:8px 0;font-size:14px;cursor:pointer;
}
.wmattachtype {
background:#e3f2fd;line-height:24px;border-radius:4px;
font-weight:bold;color:#1976d2;padding:0 7px;text-align:center;
}
.wmattachname {
flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
white-space:nowrap;background:#f5f5f5;padding:0 8px;
border-radius:4px;color:#333;margin-right:8px;
}
.wmattachsize {
background:#e8f5e8;color:#2e7d32;font-weight:500;
padding:2px 6px;border-radius:4px;margin-right:8px;font-size:12px;
}
.wmattachdelete {
background:#ffebee;color:#d32f2f;width:24px;height:24px;
border-radius:50%;line-height:22px!important;font-size:18px;
text-align:center;cursor:pointer;
}
.ejwoomdownload {margin-bottom:20px;}
.ejwoomdownload a {
padding:10px 16px;background:#28a745;color:white;
text-decoration:none;border-radius:6px;display:inline-block;
}
.ejwoomdownloadsize {
margin-left:15px;color:#666;font-size:14px;
}
.ejwoompreview {
border:1px solid #ddd;padding:20px;background:white;
border-radius:6px;min-height:70vh;overflow:auto;
}
.ejwoomaddbutton {
padding:3px 7px;background:#6c757d;color:white;border-radius:4px;
cursor:pointer;font-size:14px;font-weight:bold;
}
.ejwoomaddbutton:hover {background:#5a6268;}
.ejwoomcustombutton {
padding:3px 7px;background:#17a2b8;color:white;border-radius:4px;
cursor:pointer;font-size:14px;
}
.ejwoomcustombutton:hover {background:#138496;}
/* Modal */
.ejwoomwindow {
position:fixed;inset:0;background:rgba(0,0,0,0.7);
display:none;align-items:center;justify-content:center;z-index:999;
}
.ejwoomwindow.active {display:flex;}
.ejwoomwindow-content {
background:white;padding:28px 32px;border-radius:12px;
width:90%;max-width:540px;max-height:90vh;overflow-y:auto;
box-shadow:0 20px 40px rgba(0,0,0,0.22);
display:flex;flex-direction:column;gap:18px;
font-family:system-ui,sans-serif;
}
.ejwoom-btn-selector {
display:flex;flex-wrap:wrap;gap:10px;
border-bottom:1px solid #eee;padding-bottom:15px;
}
.ejwoomselbutton {
padding:6px 14px;background:#e9ecef;border-radius:6px;
cursor:pointer;user-select:none;transition:all 0.2s;font-weight:500;
}
.ejwoomselbutton:hover {background:#dee2e6;}
.ejwoomselbutton.selected {
background:#007bff;color:white;
}
.ejwoomselbutton.selected:hover {background:#0056b3;}
.ejwoom-input-row {
display:flex;align-items:center;gap:10px;
}
.ejwoom-input-row label {flex-basis:100px;flex-shrink:0;}
.ejwoom-input-row input[type="text"] {
flex-grow:1;padding:8px;border:1px solid #ccc;border-radius:4px;
}
.ejwoom-modal-controls {
display:flex;gap:10px;justify-content:flex-end;
padding-top:10px;border-top:1px solid #eee;
}
.ejwoom-modal-controls button {
border:none;padding:8px 16px;border-radius:4px;
cursor:pointer;color:white;font-weight:500;
}
.wmconfirm {background:#007bff;}
.wmconfirm:hover {background:#0056b3;}
.wmdelete {background:#dc3545;}
.wmdelete:hover {background:#c82333;}
.wmcancel {background:#6c757d;}
.wmcancel:hover {background:#5a6268;}
.wmdelete:disabled {opacity:0.5;cursor:not-allowed;background:#ccc;}
</style>
</head>
<body>
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
</div>
<div class="ejwoomedit active">
<div class="ejwoomcontrol">
<span class="ejwoomundo">Undo</span>
<span class="ejwoomredo">Redo</span>
</div>
<input type="text" class="ejwoomtitle" placeholder="Enter title...">
<div class="ejwoombuttons">
<span>b</span><span>u</span><span>s</span><span>i</span>
<span>h1</span><span>h2</span><span>h3</span><span>h4</span><span>h5</span><span>h6</span>
<span>ul</span><span>ol</span><span>li</span><span>dl</span><span>dt</span><span>dd</span>
<span class="ejwoomaddbutton">+</span>
</div>
<textarea class="ejwoomcontent"><div style="white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all;">
<h1>Hello World</h1>
</div></textarea>
<div id="attachments"></div>
<button class="ejwoomattach" title="Attach file">+</button>
</div>
<div class="ejwoomresult"></div>
<input type="file" id="attachfile" style="display:none;" multiple>
<script>
document.addEventListener('DOMContentLoaded', function () {
const editTab = document.querySelector('.ejwoomedittab');
const resultTab = document.querySelector('.ejwoomresulttab');
const editDiv = document.querySelector('.ejwoomedit');
const resultDiv = document.querySelector('.ejwoomresult');
const titleInput = document.querySelector('.ejwoomtitle');
const textarea = document.querySelector('.ejwoomcontent');
const attachBtn = document.querySelector('.ejwoomattach');
const attachInput = document.getElementById('attachfile');
const attachmentsContainer = document.getElementById('attachments');
const buttonsContainer = document.querySelector('.ejwoombuttons');
const addButton = document.querySelector('.ejwoomaddbutton');
const attachedFiles = new Map();
const imageExts = new Set(['png','jpg','jpeg','gif','svg','webp','bmp','ico']);
// === Helpers ===
function getExt(name) {
const i = name.lastIndexOf('.');
return i === -1 ? '' : name.slice(i + 1).toLowerCase();
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// === Remove references when deleting attachment ===
function removeReferencesFromTextarea(filename) {
const esc = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regexImg = new RegExp(`\\[img:${esc}\\]`, 'g');
const regexFile = new RegExp(`\\[file:${esc}\\]`, 'g');
textarea.value = textarea.value.replace(regexImg, '').replace(regexFile, '');
}
// === Attachment System ===
function createAttachmentRow(file) {
const row = document.createElement('div');
row.className = 'ejwoomattached';
const ext = getExt(file.name);
const typeSpan = document.createElement('span');
typeSpan.className = 'wmattachtype';
typeSpan.textContent = (imageExts.has(ext) ? 'img' : 'file') + '/' + ext;
const nameSpan = document.createElement('span');
nameSpan.className = 'wmattachname';
nameSpan.textContent = file.name;
nameSpan.title = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'wmattachsize';
sizeSpan.textContent = formatBytes(file.size);
const deleteSpan = document.createElement('span');
deleteSpan.className = 'wmattachdelete';
deleteSpan.textContent = '×';
deleteSpan.onclick = (e) => {
e.stopPropagation();
removeReferencesFromTextarea(file.name);
row.remove();
attachedFiles.delete(file.name);
};
const insertRef = () => {
const start = textarea.selectionStart;
const ref = imageExts.has(ext) ? `[img:${file.name}]` : `[file:${file.name}]`;
textarea.value = textarea.value.slice(0, start) + ref + textarea.value.slice(start);
textarea.focus();
textarea.setSelectionRange(start + ref.length, start + ref.length);
};
row.onclick = (e) => {
if (e.target === deleteSpan) return;
insertRef();
};
row.append(typeSpan, nameSpan, sizeSpan, deleteSpan);
return row;
}
attachBtn.onclick = () => attachInput.click();
attachInput.onchange = e => {
for (let file of e.target.files) {
if (attachedFiles.has(file.name)) {
alert(`File "${file.name}" is already attached.`);
continue;
}
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(file.name, file);
}
attachInput.value = '';
};
// === Button Wrapping ===
function wrapSelection(before, after = '') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) + before + selected + after + textarea.value.substring(end);
textarea.focus();
textarea.setSelectionRange(start + before.length, start + before.length + selected.length);
}
document.querySelectorAll('.ejwoombuttons span:not(.ejwoomaddbutton):not(.ejwoomcustombutton)').forEach(span => {
span.onclick = () => wrapSelection(`<${span.textContent}>`, `</${span.textContent}>`);
});
// === Custom Button Editor (FIXED: class name typo) ===
addButton.onclick = function () {
const modal = document.createElement('div');
modal.className = 'ejwoomwindow active'; // ← 這裡修正了!原來是 ejwo Aomwindow
modal.innerHTML = `
<div class="ejwoomwindow-content">
<div class="ejwoom-btn-selector">
<span class="ejwoomselbutton selected" data-value="new">new</span>
</div>
<div class="ejwoom-input-row"><label>Name</label><input type="text" placeholder="e.g. blockquote"></div>
<div class="ejwoom-input-row"><label>Before HTML</label><input type="text" placeholder="<div class="tip">"></div>
<div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="</div>"></div>
<div class="ejwoom-modal-controls">
<button class="wmconfirm">Confirm</button>
<button class="wmdelete" disabled>Delete</button>
<button class="wmcancel">Cancel</button>
</div>
</div>`;
document.body.appendChild(modal);
const selector = modal.querySelector('.ejwoom-btn-selector');
const nameInp = modal.querySelectorAll('input')[0];
const beforeInp = modal.querySelectorAll('input')[1];
const afterInp = modal.querySelectorAll('input')[2];
const confirmBtn = modal.querySelector('.wmconfirm');
const deleteBtn = modal.querySelector('.wmdelete');
const cancelBtn = modal.querySelector('.wmcancel');
// Load existing custom buttons
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(btn => {
const sel = document.createElement('span');
sel.className = 'ejwoomselbutton';
sel.textContent = btn.textContent;
sel.dataset.value = btn.textContent;
sel._target = btn;
selector.appendChild(sel);
});
function updateForm() {
const selected = selector.querySelector('.selected');
if (!selected || selected.dataset.value === 'new') {
nameInp.value = beforeInp.value = afterInp.value = '';
deleteBtn.disabled = true;
} else {
const target = selected._target;
nameInp.value = target.textContent;
beforeInp.value = target.dataset.before ? atob(target.dataset.before) : '';
afterInp.value = target.dataset.after ? atob(target.dataset.after) : '';
deleteBtn.disabled = false;
}
}
selector.onclick = e => {
const btn = e.target.closest('.ejwoomselbutton');
if (!btn) return;
selector.querySelectorAll('.ejwoomselbutton').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
updateForm();
};
deleteBtn.onclick = () => {
const selected = selector.querySelector('.selected');
if (selected && selected.dataset.value !== 'new') {
selected._target.remove();
selected.remove();
selector.querySelector('[data-value="new"]').classList.add('selected');
updateForm();
}
};
confirmBtn.onclick = () => {
const name = nameInp.value.trim();
if (!name) return alert('Button name is required');
const selected = selector.querySelector('.selected');
let targetSpan;
if (selected.dataset.value === 'new') {
targetSpan = document.createElement('span');
targetSpan.className = 'ejwoomcustombutton';
buttonsContainer.insertBefore(targetSpan, addButton);
const newSel = document.createElement('span');
newSel.className = 'ejwoomselbutton';
newSel.textContent = name;
newSel.dataset.value = name;
newSel._target = targetSpan;
selector.appendChild(newSel);
} else {
targetSpan = selected._target;
}
targetSpan.textContent = name;
if (beforeInp.value) targetSpan.dataset.before = btoa(beforeInp.value);
else delete targetSpan.dataset.before;
if (afterInp.value) targetSpan.dataset.after = btoa(afterInp.value);
else delete targetSpan.dataset.after;
targetSpan.onclick = () => {
const before = targetSpan.dataset.before ? atob(targetSpan.dataset.before) : '';
const after = targetSpan.dataset.after ? atob(targetSpan.dataset.after) : '';
wrapSelection(before, after);
};
modal.remove();
};
cancelBtn.onclick = () => modal.remove();
updateForm();
};
// === Preview & Download ===
async function buildResult() {
// ... (unchanged, omitted for brevity)
resultDiv.innerHTML = `
<div class="ejwoomdownload">
<a id="downloadlink" download="">Download HTML</a>
<span class="ejwoomdownloadsize"></span>
</div>
<div class="ejwoompreview"></div>
`;
const downloadLink = resultDiv.querySelector('#downloadlink');
const sizeSpan = resultDiv.querySelector('.ejwoomdownloadsize');
const previewDiv = resultDiv.querySelector('.ejwoompreview');
let title = (titleInput.value || 'Untitled').trim();
let content = textarea.value.trim();
let styleContent = '';
content = content.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
styleContent += css + '\n';
return '';
});
let processedContent = content;
const replacements = new Map();
for (const [name, file] of attachedFiles) {
const base64 = await fileToBase64(file);
const ext = getExt(name);
const isImage = imageExts.has(ext);
let html = isImage
? `<img src="${base64}" alt="${name}" style="max-width:100%;height:auto;">`
: `<a href="${base64}" download="${name}">Download ${name}</a>`;
replacements.set(`[img:${name}]`, html);
replacements.set(`[file:${name}]`, html);
}
for (const [key, val] of replacements) {
processedContent = processedContent.replace(
new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), val
);
}
previewDiv.innerHTML = processedContent;
const fullHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
${styleContent ? '<style>' + styleContent.trim() + '</style>' : ''}
</head>
<body>${processedContent}</body>
</html>`;
const blob = new Blob([fullHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = title + '.html';
downloadLink.textContent = 'Download ' + title + '.html';
sizeSpan.textContent = formatBytes(blob.size);
}
// === Tab Switching ===
resultTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
resultTab.classList.add('active');
editDiv.classList.remove('active');
resultDiv.classList.add('active');
buildResult();
};
editTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
editTab.classList.add('active');
editDiv.classList.add('active');
resultDiv.classList.remove('active');
};
});
</script>
</body>
</html>https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ejwoom HTML Editor</title>
<style>
body {font-family:system-ui,sans-serif;margin:0;padding:0;background:#f5f5f5;}
.ejwoomtab {display:flex;background:#333;color:white;}
.ejwoomtab span {padding:12px 20px;cursor:pointer;}
.ejwoomtab span.active {background:#007bff;}
.ejwoomedit, .ejwoomresult {
padding:20px;background:white;min-height:100vh;display:none;
}
.ejwoomedit.active, .ejwoomresult.active {display:block;}
.ejwoomcontrol {
margin-bottom:15px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;
}
.ejwoomcontrol span {
padding:6px 12px;background:#eee;border-radius:4px;cursor:pointer;
}
.ejwoomcontrol span:hover {background:#ddd;}
.ejwoomtitle {
width:100%;padding:10px;font-size:18px;border:1px solid #ccc;
border-radius:4px;box-sizing:border-box;margin-bottom:15px;
}
.ejwoombuttons {
display:flex;gap:8px;flex-wrap:wrap;margin-bottom:15px;
}
.ejwoombuttons span {
padding:3px 7px;background:#007bff;color:white;border:none;
border-radius:4px;cursor:pointer;font-size:14px;
}
.ejwoombuttons span:hover {background:#0056b3;}
.ejwoomcontent {
width:100%;height:70vh;padding:12px;font-size:16px;
font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;
border:1px solid #ccc;border-radius:4px;box-sizing:border-box;
resize:vertical;
}
.ejwoomattach {
width:36px;height:36px;background:#28a745;color:white;border:none;
border-radius:8px;font-size:20px;cursor:pointer;
display:flex;align-items:center;justify-content:center;margin-top:10px;
}
.ejwoomattached {
display:flex;align-items:center;gap:8px;
background:#f8f9fa;border:1px solid #dee2e6;border-radius:6px;
padding:8px;margin:8px 0;font-size:14px;cursor:pointer;
}
.wmattachtype {
background:#e3f2fd;line-height:24px;border-radius:4px;
font-weight:bold;color:#1976d2;padding:0 7px;text-align:center;
}
.wmattachname {
flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
white-space:nowrap;background:#f5f5f5;padding:0 8px;
border-radius:4px;color:#333;margin-right:8px;
}
.wmattachsize {
background:#e8f5e8;color:#2e7d32;font-weight:500;
padding:2px 6px;border-radius:4px;margin-right:8px;font-size:12px;
}
.wmattachdelete {
background:#ffebee;color:#d32f2f;width:24px;height:24px;
border-radius:50%;line-height:22px!important;font-size:18px;
text-align:center;cursor:pointer;
}
.ejwoomdownload {margin-bottom:20px;}
.ejwoomdownload a {
padding:10px 16px;background:#28a745;color:white;
text-decoration:none;border-radius:6px;display:inline-block;
}
.ejwoomdownloadsize {
margin-left:15px;color:#666;font-size:14px;
}
.ejwoompreview {
border:1px solid #ddd;padding:20px;background:white;
border-radius:6px;min-height:70vh;overflow:auto;
}
/* Custom Button Styles */
.ejwoomaddbutton {
padding:3px 7px;background:#6c757d;color:white;border-radius:4px;
cursor:pointer;font-size:14px;font-weight:bold;
}
.ejwoomaddbutton:hover {background:#5a6268;}
.ejwoomcustombutton {
padding:3px 7px;background:#17a2b8;color:white;border-radius:4px;
cursor:pointer;font-size:14px;
}
.ejwoomcustombutton:hover {background:#138496;}
/* Modal Window */
.ejwoomwindow {
position:fixed;inset:0;background:rgba(0,0,0,0.7);
display:none;align-items:center;justify-content:center;z-index:999;
}
.ejwoomwindow.active {display:flex;}
.ejwoomwindow-content {
background:white;padding:28px 32px;border-radius:12px;
width:90%;max-width:540px;max-height:90vh;overflow-y:auto;
box-shadow:0 20px 40px rgba(0,0,0,0.22);
display:flex;flex-direction:column;gap:18px;
font-family:system-ui,sans-serif;
}
.ejwoom-btn-selector {
display:flex;flex-wrap:wrap;gap:10px;
border-bottom:1px solid #eee;padding-bottom:15px;
}
.ejwoomselbutton {
padding:6px 14px;background:#e9ecef;border-radius:6px;
cursor:pointer;user-select:none;transition:all 0.2s;font-weight:500;
}
.ejwoomselbutton:hover {background:#dee2e6;}
.ejwoomselbutton.selected {
background:#007bff;color:white;
}
.ejwoomselbutton.selected:hover {background:#0056b3;}
.ejwoom-input-row {
display:flex;align-items:center;gap:10px;
}
.ejwoom-input-row label {flex-basis:100px;flex-shrink:0;}
.ejwoom-input-row input[type="text"] {
flex-grow:1;padding:8px;border:1px solid #ccc;border-radius:4px;
}
.ejwoom-modal-controls {
display:flex;gap:10px;justify-content:flex-end;
padding-top:10px;border-top:1px solid #eee;
}
.ejwoom-modal-controls button {
border:none;padding:8px 16px;border-radius:4px;
cursor:pointer;color:white;font-weight:500;
}
.wmconfirm {background:#007bff;}
.wmconfirm:hover {background:#0056b3;}
.wmdelete {background:#dc3545;}
.wmdelete:hover {background:#c82333;}
.wmcancel {background:#6c757d;}
.wmcancel:hover {background:#5a6268;}
.wmdelete:disabled {opacity:0.5;cursor:not-allowed;background:#ccc;}
</style>
</head>
<body>
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
</div>
<div class="ejwoomedit active">
<div class="ejwoomcontrol">
<span class="ejwoomundo">Undo</span>
<span class="ejwoomredo">Redo</span>
</div>
<input type="text" class="ejwoomtitle" placeholder="Enter title...">
<div class="ejwoombuttons">
<span>b</span><span>u</span><span>s</span><span>i</span>
<span>h1</span><span>h2</span><span>h3</span><span>h4</span><span>h5</span><span>h6</span>
<span>ul</span><span>ol</span><span>li</span><span>dl</span><span>dt</span><span>dd</span>
<span class="ejwoomaddbutton">+</span>
</div>
<textarea class="ejwoomcontent"><div style="white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all;">
<h1>Hello World</h1>
</div></textarea>
<div id="attachments"></div>
<button class="ejwoomattach" title="Attach file">+</button>
</div>
<div class="ejwoomresult"></div>
<input type="file" id="attachfile" style="display:none;" multiple>
<script>
document.addEventListener('DOMContentLoaded', function () {
const editTab = document.querySelector('.ejwoomedittab');
const resultTab = document.querySelector('.ejwoomresulttab');
const editDiv = document.querySelector('.ejwoomedit');
const resultDiv = document.querySelector('.ejwoomresult');
const titleInput = document.querySelector('.ejwoomtitle');
const textarea = document.querySelector('.ejwoomcontent');
const attachBtn = document.querySelector('.ejwoomattach');
const attachInput = document.getElementById('attachfile');
const attachmentsContainer = document.getElementById('attachments');
const buttonsContainer = document.querySelector('.ejwoombuttons');
const addButton = document.querySelector('.ejwoomaddbutton');
const attachedFiles = new Map();
const imageExts = new Set(['png','jpg','jpeg','gif','svg','webp','bmp','ico']);
// === Helpers ===
function getExt(name) {
const i = name.lastIndexOf('.');
return i === -1 ? '' : name.slice(i + 1).toLowerCase();
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// === Attachment System ===
function createAttachmentRow(file) {
const row = document.createElement('div');
row.className = 'ejwoomattached';
const ext = getExt(file.name);
const typeSpan = document.createElement('span');
typeSpan.className = 'wmattachtype';
typeSpan.textContent = (imageExts.has(ext) ? 'img' : 'file') + '/' + ext;
const nameSpan = document.createElement('span');
nameSpan.className = 'wmattachname';
nameSpan.textContent = file.name;
nameSpan.title = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'wmattachsize';
sizeSpan.textContent = formatBytes(file.size);
const deleteSpan = document.createElement('span');
deleteSpan.className = 'wmattachdelete';
deleteSpan.textContent = '×';
deleteSpan.onclick = (e) => {
e.stopPropagation();
// Remove all references of this file from the editor content
const fileName = file.name;
const esc = fileName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`\\[img:${esc}\\]|\\[file:${esc}\\]`, 'g');
textarea.value = textarea.value.replace(regex, '');
row.remove();
attachedFiles.delete(file.name);
};
const insertRef = () => {
const start = textarea.selectionStart;
const ref = imageExts.has(ext) ? `[img:${file.name}]` : `[file:${file.name}]`;
textarea.value = textarea.value.slice(0, start) + ref + textarea.value.slice(start);
textarea.focus();
textarea.setSelectionRange(start + ref.length, start + ref.length);
};
row.onclick = (e) => {
if (e.target === deleteSpan) return;
insertRef();
};
row.append(typeSpan, nameSpan, sizeSpan, deleteSpan);
return row;
}
attachBtn.onclick = () => attachInput.click();
attachInput.onchange = e => {
for (let file of e.target.files) {
if (attachedFiles.has(file.name)) {
alert(`File "${file.name}" is already attached.`);
continue;
}
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(file.name, file);
}
attachInput.value = '';
};
// === Button Wrapping ===
function wrapSelection(before, after = '') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) + before + selected + after + textarea.value.substring(end);
textarea.focus();
textarea.setSelectionRange(start + before.length, start + before.length + selected.length);
}
// Default buttons
document.querySelectorAll('.ejwoombuttons span:not(.ejwoomaddbutton):not(.ejwoomcustombutton)').forEach(span => {
span.onclick = () => wrapSelection(`<${span.textContent}>`, `</${span.textContent}>`);
});
// === Custom Button Editor (Fixed!) ===
addButton.onclick = function () {
const modal = document.createElement('div');
modal.className = 'ejwoomwindow active';
modal.innerHTML = `
<div class="ejwoomwindow-content">
<div class="ejwoom-btn-selector">
<span class="ejwoomselbutton selected" data-value="new">new</span>
</div>
<div class="ejwoom-input-row"><label>Name</label><input type="text" placeholder="e.g. blockquote"></div>
<div class="ejwoom-input-row"><label>Before HTML</label><input type="text" placeholder="<div class="tip">"></div>
<div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="</div>"></div>
<div class="ejwoom-modal-controls">
<button class="wmconfirm">Confirm</button>
<button class="wmdelete" disabled>Delete</button>
<button class="wmcancel">Cancel</button>
</div>
</div>`;
document.body.appendChild(modal);
const selector = modal.querySelector('.ejwoom-btn-selector');
const nameInp = modal.querySelectorAll('input')[0];
const beforeInp = modal.querySelectorAll('input')[1];
const afterInp = modal.querySelectorAll('input')[2];
const confirmBtn = modal.querySelector('.wmconfirm');
const deleteBtn = modal.querySelector('.wmdelete');
const cancelBtn = modal.querySelector('.wmcancel');
// Load existing custom buttons into selector
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(btn => {
const sel = document.createElement('span');
sel.className = 'ejwoomselbutton';
sel.textContent = btn.textContent;
sel.dataset.value = btn.textContent;
sel._target = btn;
selector.appendChild(sel);
});
function updateForm() {
const selected = selector.querySelector('.selected');
if (!selected || selected.dataset.value === 'new') {
nameInp.value = beforeInp.value = afterInp.value = '';
deleteBtn.disabled = true;
} else {
const target = selected._target;
nameInp.value = target.textContent;
beforeInp.value = target.dataset.before ? atob(target.dataset.before) : '';
afterInp.value = target.dataset.after ? atob(target.dataset.after) : '';
deleteBtn.disabled = false;
}
}
selector.onclick = e => {
const btn = e.target.closest('.ejwoomselbutton');
if (!btn) return;
selector.querySelectorAll('.ejwoomselbutton').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
updateForm();
};
deleteBtn.onclick = () => {
const selected = selector.querySelector('.selected');
if (selected && selected.dataset.value !== 'new') {
selected._target.remove();
selected.remove();
selector.querySelector('[data-value="new"]').classList.add('selected');
updateForm();
}
};
confirmBtn.onclick = () => {
const name = nameInp.value.trim();
if (!name) return alert('Button name is required');
const selected = selector.querySelector('.selected');
let targetSpan;
if (selected.dataset.value === 'new') {
targetSpan = document.createElement('span');
targetSpan.className = 'ejwoomcustombutton';
buttonsContainer.insertBefore(targetSpan, addButton);
const newSel = document.createElement('span');
newSel.className = 'ejwoomselbutton';
newSel.textContent = name;
newSel.dataset.value = name;
newSel._target = targetSpan;
selector.appendChild(newSel);
} else {
targetSpan = selected._target;
}
targetSpan.textContent = name;
if (beforeInp.value) targetSpan.dataset.before = btoa(beforeInp.value);
else delete targetSpan.dataset.before;
if (afterInp.value) targetSpan.dataset.after = btoa(afterInp.value);
else delete targetSpan.dataset.after;
// Apply click behavior
targetSpan.onclick = () => {
const beforeRaw = targetSpan.dataset.before ? atob(targetSpan.dataset.before) : '';
const afterRaw = targetSpan.dataset.after ? atob(targetSpan.dataset.after) : '';
if (beforeRaw === '' && afterRaw === '') {
// If both before and after are empty, use <name>selected text</name>
const name = targetSpan.textContent.trim();
wrapSelection(`<${name}>`, `</${name}>`);
} else {
// Otherwise use custom before/after (even if one is empty)
wrapSelection(beforeRaw, afterRaw);
}
};
modal.remove();
};
cancelBtn.onclick = () => modal.remove();
updateForm();
};
// === Preview & Download (Fixed filename + [img:] [file:]) ===
async function buildResult() {
// Collect all custom buttons to inject as <template> in the final HTML
const customButtons = Array.from(buttonsContainer.querySelectorAll('.ejwoomcustombutton'));
const templateHTML = customButtons.length > 0
? `<template id="ejwoom-custom-buttons">\n${customButtons.map(btn => ' ' + btn.outerHTML.trim()).join('\n')}\n</template>\n`
: '';
resultDiv.innerHTML = `
<div class="ejwoomdownload">
<a id="downloadlink" download="">Download HTML</a>
<span class="ejwoomdownloadsize"></span>
</div>
<div class="ejwoompreview"></div>
`;
const downloadLink = resultDiv.querySelector('#downloadlink');
const sizeSpan = resultDiv.querySelector('.ejwoomdownloadsize');
const previewDiv = resultDiv.querySelector('.ejwoompreview');
let title = (titleInput.value || 'Untitled').trim();
let content = textarea.value.trim();
let styleContent = '';
content = content.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
styleContent += css + '\n';
return '';
});
let processedContent = content;
const replacements = new Map();
for (const [name, file] of attachedFiles) {
const base64 = await fileToBase64(file);
const ext = getExt(name);
const isImage = imageExts.has(ext);
let html = '';
if (isImage) {
let dims = '';
if (ext === 'svg') {
try {
const svg = atob(base64.split(',')[1]);
const w = svg.match(/width=["'](\d+)["']/i);
const h = svg.match(/height=["'](\d+)["']/i);
if (w) dims += ` width="${w[1]}"`;
if (h) dims += ` height="${h[1]}"`;
} catch (e) {}
}
html = `<img src="${base64}" alt="${name}" style="max-width:100%;height:auto;"${dims}>`;
} else {
html = `<a href="${base64}" download="${name}">Download ${name}</a>`;
}
replacements.set(`[img:${name}]`, html);
replacements.set(`[file:${name}]`, html);
}
for (const [key, val] of replacements) {
processedContent = processedContent.replace(
new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), val
);
}
previewDiv.innerHTML = processedContent;
const fullHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
${styleContent ? '<style>' + styleContent.trim() + '</style>' : ''}
</head>
<body>
${templateHTML}${processedContent}
</body>
</html>`;
const blob = new Blob([fullHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = title + '.html';
downloadLink.textContent = 'Download ' + title + '.html';
sizeSpan.textContent = formatBytes(blob.size);
}
// === Tab Switching ===
resultTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
resultTab.classList.add('active');
editDiv.classList.remove('active');
resultDiv.classList.add('active');
buildResult();
};
editTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
editTab.classList.add('active');
editDiv.classList.add('active');
resultDiv.classList.remove('active');
};
});
</script>
</body>
</html>https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
增加並修改html、css和js:
一,在ejwoomtab內增加一個span.ejwoomreadfiletab{read file},當點擊它時,div.ejwoomedit隱藏,div.ejwoomresult如果有將被銷毀,生成div.ejwoomreadfile。當點擊其它兩個span時,div.ejwoomreadfile(如果有)將被銷毀。
二,div.ejwoomreadfile內有div.ejwoomreadarea+div.ejwoomreadoptions。div.ejwoomreadoptions起初隱藏。
三,div.ejwoomreadarea起初是一個四周都是緣色虛線的框,內部文字垂直以及水平居中,文字內容是「select the html file that saved before」,幫我把這句英文修正得更道地些。
四,當div.ejwoomreadarea被點擊時,它將選取一個html文件,之後把文件名顯示在div.ejwoomreadfile內,它的虛線變為橙色。同時它的上方出現一個div.ejwoomreadreset{reset the file select},同樣幫我潤色下英文。同時div.ejwoomreadoptions將顯示。
五,如果點擊div.ejwoomreadreset,則div.ejwoomreadarea恢復初始狀態,div.ejwoomreadreset消失,div.ejwoomreadoptions隱藏。
六,div.ejwoomreadoptions內有三個button:.ejwoomreadall{read all},.ejwoomreadhtml{read html only},.ejwoomreadbutton{read button only}。
6.1,如果.ejwoomreadall被點擊,把文件內容的body內的template(如有)去掉之後,把它的outerHTML寫入textarea.ejwoomcontent中。並把template的元素都添加到ejwoomaddbutton的前面。
6.2,如果.ejwoomreadhtml被點擊,則只把文件內容的body內的template(如有)去掉之後,把它的outerHTML寫入textarea.ejwoomcontent中。
6.3,如果.ejwoomreadbutton被點擊,只把文件內容的template內的元素添加到ejwoomaddbutton的前面。
6.4,當.ejwoomreadall或.ejwoomreadhtml被點擊時,textarea.ejwoomcontent的內容被替換之後,先將attachments清空。如果內容中存在插入的圖片或文件下載,則要把它轉成ejwoomattached,原來的textarea裡的內容也轉成「[wmattachname]」。
6.5,以上三個button的其中一個被點擊,都將顯示div.ejwoomedit,並銷毀div.ejwoomreadfile。
只需要給出所要修改的代碼,所有代碼都要使用英文。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
增加並修改html、css和js:
一,在ejwoomtab內增加一個span.ejwoomreadfiletab{read file},當點擊它時,div.ejwoomedit隱藏,div.ejwoomresult(如果有)將被銷毀,生成div.ejwoomreadfile。當點擊其它兩個span時,div.ejwoomreadfile(如果有)將被銷毀。
二,div.ejwoomreadfile內有div.ejwoomreadarea+div.ejwoomreadoptions。div.ejwoomreadoptions起初隱藏。
三,div.ejwoomreadarea起初是一個四周都是緣色虛線的框,內部文字垂直以及水平居中,文字內容是「Open the HTML file that you saved before.」。
四,當div.ejwoomreadarea被點擊時,它將選取一個html文件,之後把文件名顯示在div.ejwoomreadfile內,它的虛線變為橙色。同時它的上方出現一個div.ejwoomreadreset{Reset file selection.}。同時div.ejwoomreadoptions將顯示。
五,如果點擊div.ejwoomreadreset,則div.ejwoomreadarea恢復初始狀態,div.ejwoomreadreset消失,div.ejwoomreadoptions隱藏。
六,div.ejwoomreadoptions內有三個button:.ejwoomreadall{read all},.ejwoomreadhtml{read html only},.ejwoomreadbutton{read button only}。
6.1,如果.ejwoomreadall被點擊,把文件內容的body內的template(如有)去掉之後,把它的outerHTML寫入textarea.ejwoomcontent中。並把template的元素都添加到ejwoomaddbutton的前面。
6.2,如果.ejwoomreadhtml被點擊,則只把文件內容的body內的template(如有)去掉之後,把它的outerHTML寫入textarea.ejwoomcontent中。
6.3,如果.ejwoomreadbutton被點擊,只把文件內容的template內的元素添加到ejwoomaddbutton的前面。
6.4,當.ejwoomreadall或.ejwoomreadhtml被點擊時,textarea.ejwoomcontent的內容被替換之後,先將attachments清空。如果內容中存在插入的圖片或文件下載,則要把它轉成ejwoomattached,原來的textarea裡的內容也轉成「[wmattachname]」。
6.5,以上三個button的其中一個被點擊,都將顯示div.ejwoomedit,並銷毀div.ejwoomreadfile。
只需要給出所要修改的代碼,所有代碼都要使用英文。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
Below are **only the parts you need to add / modify** in your existing `ejwoom7.html` file.
All code is in English and can be copied directly.
```html
<!-- 1. Add new tab -->
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
<span class="ejwoomreadfiletab">Read File</span>
</div>
```
```html
<!-- 2. Add the new container (place it after .ejwoomresult) -->
<div class="ejwoomresult"></div>
<!-- New Read File panel -->
<div class="ejwoomreadfile">
<div class="ejwoomreadreset" style="display:none; color:#d32f2f; cursor:pointer; margin-bottom:10px; font-weight:500;">
Reset file selection.
</div>
<div class="ejwoomreadarea">
Open the HTML file that you saved before.
</div>
<div class="ejwoomreadoptions" style="display:none; margin-top:20px; display:flex; gap:12px; justify-content:center;">
<button class="ejwoomreadall">Read All</button>
<button class="ejwoomreadhtml">Read HTML Only</button>
<button class="ejwoomreadbutton">Read Buttons Only</button>
</div>
</div>
<!-- Hidden file input for reading -->
<input type="file" id="readfileinput" accept=".html,.htm" style="display:none;">
```
```css
/* 3. CSS for the new panel */
.ejwoomreadfile {
padding:40px 20px;
background:white;
min-height:100vh;
display:none;
}
.ejwoomreadfile.active {display:block;}
.ejwoomreadarea {
border:4px dashed #999;
border-radius:12px;
height:300px;
display:flex;
align-items:center;
justify-content:center;
text-align:center;
font-size:18px;
color:#555;
cursor:pointer;
transition:border-color 0.3s;
}
.ejwoomreadarea.selected {
border-color:#ff9800;
}
.ejwoomreadarea:hover {border-color:#666;}
```
```javascript
// ==== 7. JavaScript additions (add inside the existing <script> block, after the existing variables) ====
const readFileTab = document.querySelector('.ejwoomreadfiletab');
const readFileDiv = document.createElement('div');
readFileDiv.className = 'ejwoomreadfile'; // will be replaced by the HTML we added above
const readArea = document.querySelector('.ejwoomreadarea');
const readReset = document.querySelector('.ejwoomreadreset');
const readOptions = document.querySelector('.ejwoomreadoptions');
const readFileInput = document.getElementById('readfileinput');
let currentReadFile = null; // File object that user selected
// Tab click – Read File
readFileTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
readFileTab.classList.add('active');
editDiv.classList.remove('active');
resultDiv.classList.remove('active');
resultDiv.innerHTML = ''; // destroy preview
document.querySelector('.ejwoomreadfile').classList.add('active');
};
// Click on the dashed area → open file picker
readArea.onclick = () => readFileInput.click();
readFileInput.onchange = e => {
const file = e.target.files[0];
if (!file) return;
currentReadFile = file;
readArea.textContent = file.name;
readArea.classList.add('selected');
readReset.style.display = 'block';
readOptions.style.display = 'flex';
};
// Reset button
readReset.onclick = () => {
currentReadFile = null;
readArea.textContent = 'Open the HTML file that you saved before.';
readArea.classList.remove('selected');
readReset.style.display = 'none';
readOptions.style.display = 'none';
readFileInput.value = '';
};
// Helper: extract content without <template id="ejwoom-custom-buttons">
function getBodyContent(doc) {
const template = doc.querySelector('template#ejwoom-custom-buttons');
if (template) template.remove();
return doc.body.innerHTML.trim();
}
// Helper: extract custom buttons from template
function getCustomButtons(doc) {
const template = doc.querySelector('template#ejwoom-custom-buttons');
if (!template) return [];
return Array.from(template.content.querySelectorAll('.ejwoomcustombutton'));
}
// Process the three read buttons
document.querySelector('.ejwoomreadall').onclick =
document.querySelector('.ejwoomreadhtml').onclick =
document.querySelector('.ejwoomreadbutton').onclick = function () {
if (!currentReadFile) return;
const reader = new FileReader();
reader.onload = function (ev) {
const parser = new DOMParser();
const doc = parser.parseFromString(ev.target.result, 'text/html');
const bodyHTML = getBodyContent(doc);
const customBtns = getCustomButtons(doc);
// Common: clear current attachments
attachmentsContainer.innerHTML = '';
attachedFiles.clear();
if (this.classList.contains('ejwoomreadall') || this.classList.contains('ejwoomreadhtml')) {
textarea.value = bodyHTML;
// Re-create attachments from embedded base64 data (img src or a href starting with data:)
const tempDiv = document.createElement('div');
tempDiv.innerHTML = bodyHTML;
tempDiv.querySelectorAll('img[src^="data:"], a[href^="data:"][download]').forEach(el => {
const src = el.src || el.href;
const downloadName = el.alt || el.getAttribute('download') || 'unnamed';
const blob = dataURLtoBlob(src);
const file = new File([blob], downloadName, {type: blob.type});
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(downloadName, file);
// Replace the element with the placeholder used by the editor
const placeholder = el.tagName === 'IMG' ? `[img:${downloadName}]` : `[file:${downloadName}]`;
textarea.value = textarea.value.replace(el.outerHTML, placeholder);
});
}
// Add custom buttons (for All and Buttons only)
if (this.classList.contains('ejwoomreadall') || this.classList.contains('ejwoomreadbutton')) {
customBtns.forEach(btn => {
const newBtn = btn.cloneNode(true);
buttonsContainer.insertBefore(newBtn, addButton);
// Re-attach click handler (same logic as when creating custom buttons)
newBtn.onclick = () => {
const beforeRaw = newBtn.dataset.before ? atob(newBtn.dataset.before) : '';
const afterRaw = newBtn.dataset.after ? atob(newBtn.dataset.after) : '';
if (beforeRaw === '' && afterRaw === '') {
const name = newBtn.textContent.trim();
wrapSelection(`<${name}>`, `</${name}>`);
} else {
wrapSelection(beforeRaw, afterRaw);
}
};
});
}
// Switch back to Edit tab
editTab.click();
};
reader.readAsText(currentReadFile);
};
// Utility: convert dataURL → Blob (used when re-creating attachments)
function dataURLtoBlob(dataurl) {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) u8arr[n] = bstr.charCodeAt(n);
return new Blob([u8arr], {type: mime});
}
```
Just add/replace the sections above in your original file and the new **“Read File”** functionality (with all the requested behaviours) will work perfectly. Enjoy!https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
增加並修改html、css和js:
一,在ejwoomtab內增加一個span.ejwoomreadfiletab{read file},當點擊它時,div.ejwoomedit隱藏,div.ejwoomresult(如果有)將被銷毀,生成div.ejwoomreadfile。
當點擊span.ejwoomedittab和span.ejwoomresulttab時,div.ejwoomreadfile(如果有)將被銷毀。
二,div.ejwoomreadfile內有div.ejwoomreadarea+div.ejwoomreadoptions。div.ejwoomreadoptions起初隱藏。
三,div.ejwoomreadarea起初是一個四周都是緣色虛線的框,內部文字垂直以及水平居中,文字內容是「Open the HTML file that you saved before.」。
四,當div.ejwoomreadarea被點擊時,它將選取一個html文件,之後把文件名顯示在div.ejwoomreadfile內,它的虛線變為橙色。同時它的上方出現一個div.ejwoomreadreset{Reset file selection.}。同時div.ejwoomreadoptions將顯示。
五,如果點擊div.ejwoomreadreset,則div.ejwoomreadarea恢復初始狀態,div.ejwoomreadreset消失,div.ejwoomreadoptions隱藏。
六,div.ejwoomreadoptions內有三個button:a.ejwoomreadall{read all},a.ejwoomreadhtml{read html only},a.ejwoomreadbutton{read button only}。
6.1,這三個button的其中一個被點擊,都將顯示div.ejwoomedit,並銷毀div.ejwoomreadfile。
6.2,這三個button以<a>為標籤,其樣式跟「.ejwoomdownload a」一致,但顏色不同。
6.3,如果.ejwoomreadall被點擊,把文件內容的body內的template(如有)去掉之後,把文件內容的body的outerHTML寫入textarea.ejwoomcontent中。並把template的元素(如有)都添加到ejwoomaddbutton的前面。
6.4,如果.ejwoomreadhtml被點擊,則只把文件內容的body內的template(如有)去掉之後,把文件內容的body的outerHTML寫入textarea.ejwoomcontent中。
6.5,如果.ejwoomreadbutton被點擊,只把文件內容的template內的元素添加到ejwoomaddbutton的前面。
6.6,當.ejwoomreadall或.ejwoomreadhtml被點擊時,textarea.ejwoomcontent的內容替換成這個HTML文件的body的內容之後,先將attachments清空。如果這個HTML文件的body的內容中存在插入的圖片或文件下載,則要把它轉成ejwoomattached,原來的textarea裡的內容也轉成「[type:wmattachname]」。
例如:這個HTML文件的body的內容中存在「<a href="data:text/plain;base64,TQo=" download="tmp2.txt">Download tmp2.txt</a>」,則在div#attachments中加入「<div class="ejwoomattached"><span class="wmattachtype">file/txt</span><span class="wmattachname" title="tmp2.txt">tmp2.txt</span><span class="wmattachsize">2 B</span><span class="wmattachdelete">×</span></div>」,之後把原位置的內容替換為「[file:tmp2.txt]」。
只需要給出所要修改的代碼,所有代碼都要使用英文。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 來做一個html生成器
代码: 全选
Below are **only the parts you need to add or replace** in your existing `ejwoom7.html` file.
All code is in English and ready to copy-paste.
```html
<!-- 1. Add new tab in .ejwoomtab -->
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
<span class="ejwoomreadfiletab">Read file</span>
</div>
```
```css
/* Add these CSS rules (put anywhere in <style>) */
.ejwoomreadfile {
padding:20px;
background:white;
min-height:100vh;
display:none;
}
.ejwoomreadfile.active {display:block;}
.ejwoomreadarea {
border:4px dashed #999;
border-radius:12px;
height:60vh;
display:flex;
align-items:center;
justify-content:center;
text-align:center;
color:#555;
font-size:20px;
cursor:pointer;
transition:border-color .3s;
}
.ejwoomreadarea.selected {
border-color:#ff9800;
}
.ejwoomreadreset {
margin:15px 0;
color:#d32f2f;
cursor:pointer;
font-weight:500;
text-align:center;
}
.ejwoomreadoptions {
margin-top:20px;
text-align:center;
display:none;
}
.ejwoomreadoptions.active {display:block;}
.ejwoomreadoptions a {
padding:10px 16px;
background:#28a745;
color:white;
text-decoration:none;
border-radius:6px;
display:inline-block;
margin:0 8px;
}
.ejwoomreadoptions a.readhtml {background:#007bff;}
.ejwoomreadoptions a.readbutton {background:#6c757d;}
```
```html
<!-- Add hidden file input for reading (place anywhere after <body>) -->
<input type="file" id="readfileinput" accept=".html,.htm" style="display:none;">
```
```javascript
/* Add these variables near the top of the existing script (after other const declarations) */
const readFileTab = document.querySelector('.ejwoomreadfiletab');
const readFileInput = document.getElementById('readfileinput');
let readFileDiv = null; // will hold the created div.ejwoomreadfile
let currentReadFile = null; // File object that user selected
```
```javascript
/* ==== READ FILE TAB FUNCTIONALITY ==== */
function createReadFileDiv() {
readFileDiv = document.createElement('div');
readFileDiv.className = 'ejwoomreadfile active';
readFileDiv.innerHTML = `
<div class="ejwoomreadarea">
Open the HTML file that you saved before.
</div>
<div class="ejwoomreadreset" style="display:none;">Reset file selection.</div>
<div class="ejwoomreadoptions">
<a class="ejwoomreadall">Read all</a>
<a class="ejwoomreadhtml readhtml">Read HTML only</a>
<a class="ejwoomreadbutton readbutton">Read buttons only</a>
</div>
`;
document.body.appendChild(readFileDiv);
const area = readFileDiv.querySelector('.ejwoomreadarea');
const reset = readFileDiv.querySelector('.ejwoomreadreset');
const options = readFileDiv.querySelector('.ejwoomreadoptions');
area.onclick = () => readFileInput.click();
readFileInput.onchange = e => {
const file = e.target.files[0];
if (!file) return;
currentReadFile = file;
area.textContent = file.name;
area.classList.add('selected');
reset.style.display = 'block';
options.classList.add('active');
};
reset.onclick = () => {
currentReadFile = null;
area.textContent = 'Open the HTML file that you saved before.';
area.classList.remove('selected');
reset.style.display = 'none';
options.classList.remove('active');
readFileInput.value = '';
};
// Button actions
readFileDiv.querySelector('.ejwoomreadall').onclick = () => loadFileContent('all');
readFileDiv.querySelector('.ejwoomreadhtml').onclick = () => loadFileContent('html');
readFileDiv.querySelector('.ejwoomreadbutton').onclick = () => loadFileContent('buttons');
}
async function loadFileContent(mode) {
if (!currentReadFile) return;
const text = await currentReadFile.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
// Clear current attachments
attachmentsContainer.innerHTML = '';
attachedFiles.clear();
// Extract custom buttons from <template id="ejwoom-custom-buttons">
const template = doc.querySelector('template#ejwoom-custom-buttons');
const customButtonsHTML = template ? template.innerHTML.trim() : '';
// Remove the template from body (we don't want it in the content)
if (template) template.remove();
const bodyHTML = doc.body.innerHTML.trim();
// === Re-create attachments from data-URIs that appear in bodyHTML ===
const dataUriRegex = /<a[^>]+href=["']data:([^"']+)["'][^>]*download=["']([^"']+)["']|<img[^>]+src=["']data:([^"']+)["']/gi;
let match;
while ((match = dataUriRegex.exec(bodyHTML)) !== null) {
const dataUri = match[1] || match[3];
const filename = match[2] || match[0].split('download=')[1]?.split('"')[1] || 'unknown';
if (!dataUri || !filename) continue;
const blob = dataURLtoBlob(dataUri);
const file = new File([blob], filename, {type: blob.type});
// Add visual attachment row
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(filename, file);
}
// Replace data-URIs with [img:...] or [file:...] in the content we will put into textarea
let cleanedContent = bodyHTML
cleanedContent = cleanedContent.replace(/<img[^>]+src=["']data:[^"']+["'][^>]*>/gi, (m) => {
const name = m.match(/alt=["']([^"']+)["']/)?.[1] ||
m.match(/download=["']([^"']+)["']/)?.[1] || 'image';
return `[img:${name}]`;
});
cleanedContent = cleanedContent.replace(/<a[^>]+href=["']data:[^"']+["'][^>]*download=["']([^"']+)["'][^>]*>.*?</a>/gi, '[file:$1]');
if (mode === 'all' || mode === 'html') {
textarea.value = cleanedContent;
}
if (mode === 'all' || mode === 'buttons') {
// Remove old custom buttons
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(b => b.remove());
// Insert saved custom buttons
if (customButtonsHTML) {
const temp = document.createElement('div');
temp.innerHTML = customButtonsHTML;
Array.from(temp.children).forEach(btn => {
btn.className = 'ejwoomcustombutton';
// restore click behaviour (same logic as when creating new ones)
btn.onclick = () => {
const beforeRaw = btn.dataset.before ? atob(btn.dataset.before) : '';
const afterRaw = btn.dataset.after ? atob(btn.dataset.after) : '';
if (beforeRaw === '' && afterRaw === '') {
const name = btn.textContent.trim();
wrapSelection(`<${name}>`, `</${name}>`);
} else {
wrapSelection(beforeRaw, afterRaw);
}
};
buttonsContainer.insertBefore(btn, addButton);
});
}
}
// Switch back to Edit tab
editTab.click();
}
// Helper: convert dataURL → Blob
function dataURLtoBlob(dataurl) {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) u8arr[n] = bstr.charCodeAt(n);
return new Blob([u8arr], {type: mime});
}
/* Tab switching – add Read file tab handling */
readFileTab.onclick = () => {
// hide edit & destroy result if exists
editDiv.classList.remove('active');
resultDiv.classList.remove('active');
resultDiv.innerHTML = '';
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
readFileTab.classList.add('active');
if (!readFileDiv) createReadFileDiv();
else readFileDiv.classList.add('active');
};
// When going to Edit or Preview → destroy read file div if exists
const destroyReadFile = () => {
if (readFileDiv) {
readFileDiv.remove();
readFileDiv = null;
}
};
editTab.onclick = () => {
destroyReadFile();
// … existing code for edit tab …
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
editTab.classList.add('active');
editDiv.classList.add('active');
resultDiv.classList.remove('active');
};
resultTab.onclick = () => {
destroyReadFile();
// … existing code for result tab …
};
```
Just replace/add the blocks above in your original file and the new “Read file” tab with all requested features (file selection, reset, three coloured buttons, importing content/buttons/attachments) will work perfectly. Enjoy!代码: 全选
<div class="ejwoomtab">
<span class="ejwoomedittab active">Edit</span>
<span class="ejwoomresulttab">Preview & Download</span>
<span class="ejwoomreadfiletab">Read File</span>
</div>
<input type="file" id="attachfile" style="display:none;" multiple>
<input type="file" id="readhtmlfile" style="display:none;" accept=".html,.htm">
/* Read File Tab */
.ejwoomreadfile {
padding: 20px;
background: white;
min-height: 100vh;
}
.ejwoomreadarea {
border: 3px dashed #28a745;
border-radius: 8px;
padding: 20px;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 1.2em;
color: #555;
cursor: pointer;
transition: all 0.3s;
}
.ejwoomreadarea.selected {
border-color: #fd7e14; /* Orange */
color: #333;
font-weight: bold;
}
.ejwoomreadreset {
margin: -10px 0 15px 0;
color: #007bff;
cursor: pointer;
text-decoration: underline;
font-size: 14px;
}
.ejwoomreadoptions {
display: none; /* Hidden by default */
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.ejwoomreadoptions a {
padding: 10px 16px;
background: #007bff; /* Blue */
color: white;
text-decoration: none;
border-radius: 6px;
display: inline-block;
cursor: pointer;
}
.ejwoomreadoptions a:hover {
background: #0056b3;
}
<script>
document.addEventListener('DOMContentLoaded', function () {
const editTab = document.querySelector('.ejwoomedittab');
const resultTab = document.querySelector('.ejwoomresulttab');
const readfileTab = document.querySelector('.ejwoomreadfiletab'); // New
const editDiv = document.querySelector('.ejwoomedit');
const resultDiv = document.querySelector('.ejwoomresult');
// .ejwoomreadfile is created dynamically
const titleInput = document.querySelector('.ejwoomtitle');
const textarea = document.querySelector('.ejwoomcontent');
const attachBtn = document.querySelector('.ejwoomattach');
const attachInput = document.getElementById('attachfile');
const readHtmlInput = document.getElementById('readhtmlfile'); // New
const attachmentsContainer = document.getElementById('attachments');
const buttonsContainer = document.querySelector('.ejwoombuttons');
const addButton = document.querySelector('.ejwoomaddbutton');
const attachedFiles = new Map();
const imageExts = new Set(['png','jpg','jpeg','gif','svg','webp','bmp','ico']);
let loadedHtmlContent = null; // New: To store read file content
// === Helpers ===
function getExt(name) {
const i = name.lastIndexOf('.');
return i === -1 ? '' : name.slice(i + 1).toLowerCase();
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// New: Convert Data URL back to File object
function dataURLtoFile(dataurl, filename) {
try {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
} catch (e) {
console.error("Error converting data URL to file:", e);
return null;
}
}
// === Attachment System ===
function createAttachmentRow(file) {
const row = document.createElement('div');
row.className = 'ejwoomattached';
const ext = getExt(file.name);
const typeSpan = document.createElement('span');
typeSpan.className = 'wmattachtype';
typeSpan.textContent = (imageExts.has(ext) ? 'img' : 'file') + '/' + ext;
const nameSpan = document.createElement('span');
nameSpan.className = 'wmattachname';
nameSpan.textContent = file.name;
nameSpan.title = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'wmattachsize';
sizeSpan.textContent = formatBytes(file.size);
const deleteSpan = document.createElement('span');
deleteSpan.className = 'wmattachdelete';
deleteSpan.textContent = '×';
deleteSpan.onclick = (e) => {
e.stopPropagation();
// Remove all references of this file from the editor content
const fileName = file.name;
const esc = fileName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`\\[img:${esc}\\]|\\[file:${esc}\\]`, 'g');
textarea.value = textarea.value.replace(regex, '');
row.remove();
attachedFiles.delete(file.name);
};
const insertRef = () => {
const start = textarea.selectionStart;
const ref = imageExts.has(ext) ? `[img:${file.name}]` : `[file:${file.name}]`;
textarea.value = textarea.value.slice(0, start) + ref + textarea.value.slice(start);
textarea.focus();
textarea.setSelectionRange(start + ref.length, start + ref.length);
};
row.onclick = (e) => {
if (e.target === deleteSpan) return;
insertRef();
};
row.append(typeSpan, nameSpan, sizeSpan, deleteSpan);
return row;
}
attachBtn.onclick = () => attachInput.click();
attachInput.onchange = e => {
for (let file of e.target.files) {
if (attachedFiles.has(file.name)) {
alert(`File "${file.name}" is already attached.`);
continue;
}
attachmentsContainer.appendChild(createAttachmentRow(file));
attachedFiles.set(file.name, file);
}
attachInput.value = '';
};
// === Button Wrapping ===
function wrapSelection(before, after = '') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) + before + selected + after + textarea.value.substring(end);
textarea.focus();
textarea.setSelectionRange(start + before.length, start + before.length + selected.length);
}
// Default buttons
document.querySelectorAll('.ejwoombuttons span:not(.ejwoomaddbutton):not(.ejwoomcustombutton)').forEach(span => {
span.onclick = () => wrapSelection(`<${span.textContent}>`, `</${span.textContent}>`);
});
// New: Refactored function to apply click logic to custom buttons
function applyCustomButtonLogic(targetSpan) {
targetSpan.onclick = () => {
const beforeRaw = targetSpan.dataset.before ? atob(targetSpan.dataset.before) : '';
const afterRaw = targetSpan.dataset.after ? atob(targetSpan.dataset.after) : '';
if (beforeRaw === '' && afterRaw === '') {
// If both before and after are empty, use <name>selected text</name>
const name = targetSpan.textContent.trim();
wrapSelection(`<${name}>`, `</${name}>`);
} else {
// Otherwise use custom before/after (even if one is empty)
wrapSelection(beforeRaw, afterRaw);
}
};
}
// === Custom Button Editor (Fixed!) ===
addButton.onclick = function () {
const modal = document.createElement('div');
modal.className = 'ejwoomwindow active';
modal.innerHTML = `
<div class="ejwoomwindow-content">
<div class="ejwoom-btn-selector">
<span class="ejwoomselbutton selected" data-value="new">new</span>
</div>
<div class="ejwoom-input-row"><label>Name</label><input type="text" placeholder="e.g. blockquote"></div>
<div class="ejwoom-input-row"><label>Before HTML</label><input type="text" placeholder="<div class="tip">"></div>
<div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="</div>"></div>
<div class="ejwoom-modal-controls">
<button class="wmconfirm">Confirm</button>
<button class="wmdelete" disabled>Delete</button>
<button class="wmcancel">Cancel</button>
</div>
</div>`;
document.body.appendChild(modal);
const selector = modal.querySelector('.ejwoom-btn-selector');
const nameInp = modal.querySelectorAll('input')[0];
const beforeInp = modal.querySelectorAll('input')[1];
const afterInp = modal.querySelectorAll('input')[2];
const confirmBtn = modal.querySelector('.wmconfirm');
const deleteBtn = modal.querySelector('.wmdelete');
const cancelBtn = modal.querySelector('.wmcancel');
// Load existing custom buttons into selector
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(btn => {
const sel = document.createElement('span');
sel.className = 'ejwoomselbutton';
sel.textContent = btn.textContent;
sel.dataset.value = btn.textContent;
sel._target = btn;
selector.appendChild(sel);
});
function updateForm() {
const selected = selector.querySelector('.selected');
if (!selected || selected.dataset.value === 'new') {
nameInp.value = beforeInp.value = afterInp.value = '';
deleteBtn.disabled = true;
} else {
const target = selected._target;
nameInp.value = target.textContent;
beforeInp.value = target.dataset.before ? atob(target.dataset.before) : '';
afterInp.value = target.dataset.after ? atob(target.dataset.after) : '';
deleteBtn.disabled = false;
}
}
selector.onclick = e => {
const btn = e.target.closest('.ejwoomselbutton');
if (!btn) return;
selector.querySelectorAll('.ejwoomselbutton').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
updateForm();
};
deleteBtn.onclick = () => {
const selected = selector.querySelector('.selected');
if (selected && selected.dataset.value !== 'new') {
selected._target.remove();
selected.remove();
selector.querySelector('[data-value="new"]').classList.add('selected');
updateForm();
}
};
confirmBtn.onclick = () => {
const name = nameInp.value.trim();
if (!name) return alert('Button name is required');
const selected = selector.querySelector('.selected');
let targetSpan;
if (selected.dataset.value === 'new') {
targetSpan = document.createElement('span');
targetSpan.className = 'ejwoomcustombutton';
buttonsContainer.insertBefore(targetSpan, addButton);
const newSel = document.createElement('span');
newSel.className = 'ejwoomselbutton';
newSel.textContent = name;
newSel.dataset.value = name;
newSel._target = targetSpan;
selector.appendChild(newSel);
} else {
targetSpan = selected._target;
}
targetSpan.textContent = name;
if (beforeInp.value) targetSpan.dataset.before = btoa(beforeInp.value);
else delete targetSpan.dataset.before;
if (afterInp.value) targetSpan.dataset.after = btoa(afterInp.value);
else delete targetSpan.dataset.after;
// Apply click behavior
applyCustomButtonLogic(targetSpan);
modal.remove();
};
cancelBtn.onclick = () => modal.remove();
updateForm();
};
// === Preview & Download (Fixed filename + [img:] [file:]) ===
async function buildResult() {
// Collect all custom buttons to inject as <template> in the final HTML
const customButtons = Array.from(buttonsContainer.querySelectorAll('.ejwoomcustombutton'));
const templateHTML = customButtons.length > 0
? `<template id="ejwoom-custom-buttons">\n${customButtons.map(btn => ' ' + btn.outerHTML.trim()).join('\n')}\n</template>\n`
: '';
resultDiv.innerHTML = `
<div class="ejwoomdownload">
<a id="downloadlink" download="">Download HTML</a>
<span class="ejwoomdownloadsize"></span>
</div>
<div class="ejwoompreview"></div>
`;
const downloadLink = resultDiv.querySelector('#downloadlink');
const sizeSpan = resultDiv.querySelector('.ejwoomdownloadsize');
const previewDiv = resultDiv.querySelector('.ejwoompreview');
let title = (titleInput.value || 'Untitled').trim();
let content = textarea.value.trim();
let styleContent = '';
content = content.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
styleContent += css + '\n';
return '';
});
let processedContent = content;
const replacements = new Map();
for (const [name, file] of attachedFiles) {
const base64 = await fileToBase64(file);
const ext = getExt(name);
const isImage = imageExts.has(ext);
let html = '';
if (isImage) {
let dims = '';
if (ext === 'svg') {
try {
const svg = atob(base64.split(',')[1]);
const w = svg.match(/width=["'](\d+)["']/i);
const h = svg.match(/height=["'](\d+)["']/i);
if (w) dims += ` width="${w[1]}"`;
if (h) dims += ` height="${h[1]}"`;
} catch (e) {}
}
html = `<img src="${base64}" alt="${name}" style="max-width:100%;height:auto;"${dims}>`;
} else {
html = `<a href="${base64}" download="${name}">Download ${name}</a>`;
}
replacements.set(`[img:${name}]`, html);
replacements.set(`[file:${name}]`, html);
}
for (const [key, val] of replacements) {
processedContent = processedContent.replace(
new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), val
);
}
previewDiv.innerHTML = processedContent;
const fullHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width:device-width, initial-scale=1.0">
<title>${title}</title>
${styleContent ? '<style>' + styleContent.trim() + '</style>' : ''}
</head>
<body>
${templateHTML}${processedContent}
</body>
</html>`;
const blob = new Blob([fullHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = title + '.html';
downloadLink.textContent = 'Download ' + title + '.html';
sizeSpan.textContent = formatBytes(blob.size);
}
// === NEW: Read File Functions ===
// 6.6: Parse HTML, extract attachments, and replace with placeholders
function rebuildAttachmentsFromHTML(htmlString) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = htmlString;
// Clear existing attachments
attachmentsContainer.innerHTML = '';
attachedFiles.clear();
// Find all data-based images and links
tempDiv.querySelectorAll('img[src^="data:"], a[href^="data:"][download]').forEach(el => {
const dataUrl = el.tagName === 'IMG' ? el.getAttribute('src') : el.getAttribute('href');
const filename = el.tagName === 'IMG' ? el.getAttribute('alt') : el.getAttribute('download');
if (!filename) {
console.warn('Found data URL element without alt/download name. Skipping.', el);
return;
}
if (attachedFiles.has(filename)) {
// Already processed a file with this name, just replace
const placeholder = el.tagName === 'IMG' ? `[img:${filename}]` : `[file:${filename}]`;
el.parentNode.replaceChild(document.createTextNode(placeholder), el);
return;
}
const file = dataURLtoFile(dataUrl, filename);
if (file) {
// Add to attachment systems
attachedFiles.set(file.name, file);
attachmentsContainer.appendChild(createAttachmentRow(file));
// Replace element with placeholder
const placeholder = el.tagName === 'IMG' ? `[img:${filename}]` : `[file:${filename}]`;
el.parentNode.replaceChild(document.createTextNode(placeholder), el);
}
});
// Return the HTML string with placeholders
return tempDiv.innerHTML;
}
// 6.5: Load buttons from a parsed HTML document
function parseAndLoadButtons(htmlDoc) {
// Clear existing custom buttons
buttonsContainer.querySelectorAll('.ejwoomcustombutton').forEach(btn => btn.remove());
const template = htmlDoc.getElementById('ejwoom-custom-buttons');
if (template) {
// Clone nodes to import them into the current document
Array.from(template.content.children).forEach(node => {
if (node.tagName === 'SPAN' && node.classList.contains('ejwoomcustombutton')) {
const newButton = node.cloneNode(true);
buttonsContainer.insertBefore(newButton, addButton);
applyCustomButtonLogic(newButton); // Re-apply click logic
}
});
}
}
// 6.4: Get body HTML and remove the template
function parseAndLoadHTML(htmlDoc) {
const body = htmlDoc.body;
if (!body) return '';
const template = body.querySelector('#ejwoom-custom-buttons');
if (template) {
template.remove();
}
// Also try to find title
const titleTag = htmlDoc.head.querySelector('title');
if (titleTag && titleTag.textContent) {
titleInput.value = titleTag.textContent;
}
return body.innerHTML; // Use innerHTML to skip the <body> tag itself
}
// Switch to Edit Tab helper
function switchToEditTab() {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
editTab.classList.add('active');
editDiv.classList.add('active');
resultDiv.classList.remove('active');
const readDiv = document.querySelector('.ejwoomreadfile');
if (readDiv) readDiv.remove();
}
// === Tab Switching ===
resultTab.onclick = () => {
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
resultTab.classList.add('active');
editDiv.classList.remove('active');
resultDiv.classList.add('active');
const readDiv = document.querySelector('.ejwoomreadfile'); // New
if (readDiv) readDiv.remove(); // New
buildResult();
};
editTab.onclick = () => {
switchToEditTab(); // Use helper
};
// New: Read File Tab click
readfileTab.onclick = () => {
// Set active tab
document.querySelectorAll('.ejwoomtab span').forEach(s => s.classList.remove('active'));
readfileTab.classList.add('active');
// Hide other main divs
editDiv.classList.remove('active');
resultDiv.classList.remove('active');
// Clean up old read div if it exists
let readDiv = document.querySelector('.ejwoomreadfile');
if (readDiv) readDiv.remove();
// Create new read file UI
readDiv = document.createElement('div');
readDiv.className = 'ejwoomreadfile';
readDiv.innerHTML = `
<div class="ejwoomreadarea">Open the HTML file that you saved before.</div>
<div class="ejwoomreadoptions">
<a class="ejwoomreadall">Read All</a>
<a class="ejwoomreadhtml">Read HTML Only</a>
<a class="ejwoomreadbutton">Read Buttons Only</a>
</div>
`;
document.body.appendChild(readDiv);
const readArea = readDiv.querySelector('.ejwoomreadarea');
const readOptions = readDiv.querySelector('.ejwoomreadoptions');
// Reset function
function resetReadFileUI() {
readArea.innerHTML = 'Open the HTML file that you saved before.';
readArea.classList.remove('selected');
readOptions.style.display = 'none';
const resetBtn = readDiv.querySelector('.ejwoomreadreset');
if (resetBtn) resetBtn.remove();
readHtmlInput.value = ''; // Clear file input
loadedHtmlContent = null;
}
// Read area click
readArea.onclick = () => readHtmlInput.click();
// Read file input change
readHtmlInput.onchange = e => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
loadedHtmlContent = ev.target.result;
// Update UI
readArea.textContent = file.name;
readArea.classList.add('selected');
readOptions.style.display = 'flex';
// Add reset button if it doesn't exist
if (!readDiv.querySelector('.ejwoomreadreset')) {
const resetBtn = document.createElement('div');
resetBtn.className = 'ejwoomreadreset';
resetBtn.textContent = 'Reset file selection.';
resetBtn.onclick = resetReadFileUI;
readDiv.insertBefore(resetBtn, readArea);
}
};
reader.onerror = () => {
alert('Error reading file.');
resetReadFileUI();
};
reader.readAsText(file);
};
// Option button clicks
readDiv.querySelector('.ejwoomreadall').onclick = () => {
if (!loadedHtmlContent) return alert('No file loaded.');
const parser = new DOMParser();
const doc = parser.parseFromString(loadedHtmlContent, 'text/html');
parseAndLoadButtons(doc);
let bodyHtml = parseAndLoadHTML(doc);
textarea.value = rebuildAttachmentsFromHTML(bodyHtml);
switchToEditTab();
};
readDiv.querySelector('.ejwoomreadhtml').onclick = () => {
if (!loadedHtmlContent) return alert('No file loaded.');
const parser = new DOMParser();
const doc = parser.parseFromString(loadedHtmlContent, 'text/html');
let bodyHtml = parseAndLoadHTML(doc);
textarea.value = rebuildAttachmentsFromHTML(bodyHtml);
switchToEditTab();
};
readDiv.querySelector('.ejwoomreadbutton').onclick = () => {
if (!loadedHtmlContent) return alert('No file loaded.');
const parser = new DOMParser();
const doc = parser.parseFromString(loadedHtmlContent, 'text/html');
parseAndLoadButtons(doc);
switchToEditTab();
};
};
});
</script>
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 4 访客
