來做一個html生成器

一些和AI相关的讨论
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

修改相應的html、css以及js:

之前當點擊span.ejwoomaddbutton{+}時,將彈出一個對話框div.ejwoomwindow。

現在不行了,查找原因並修正。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

代码: 全选

<!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="&lt;div class=&quot;tip&quot;&gt;"></div>
                <div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="&lt;/div&gt;"></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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

修改相應的html、css和js:

之前當點擊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="&lt;div class=&quot;tip&quot;&gt;"></div>
                <div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="&lt;/div&gt;"></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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

每次都要修一下打開窗口

代码: 全选

<!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="&lt;div class=&quot;tip&quot;&gt;"></div>
                <div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="&lt;/div&gt;"></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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

當前代碼:

代码: 全选

<!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="&lt;div class=&quot;tip&quot;&gt;"></div>
                <div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="&lt;/div&gt;"></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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

新增read file tab:

代码: 全选

增加並修改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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

在ejwoomtab內增加一個span.ejwoomreadfiletab{read file}

代码: 全选

增加並修改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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

grok4.1

代码: 全选

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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

仍要加READ FILE,增加示例:

代码: 全选

增加並修改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/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4128
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 163 次
被圈友点赞: 193 次
联系:

Re: 來做一個html生成器

帖子 ejsoon »

grok fast

代码: 全选

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!
gemini

代码: 全选

<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="&lt;div class=&quot;tip&quot;&gt;"></div>
                <div class="ejwoom-input-row"><label>After HTML</label><input type="text" placeholder="&lt;/div&gt;"></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 访客