來做一個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/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 10 访客
