來做一個html生成器

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

Re: 來做一個html生成器

帖子 ejsoon »

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

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

現在不行了,查找原因並修正。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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
圈圈精英
圈圈精英
帖子: 4121
注册时间: 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/
弈趣極光:享受思維樂趣
回复

在线用户

正浏览此版面之用户: 没有注册用户 和 1 访客