<?php
$documentUrl = isset($documentUrl) ? (string) $documentUrl : (isset($Params['url']) ? (string) $Params['url'] : '');
$documentPath = parse_url($documentUrl, PHP_URL_PATH);
$documentName = basename($documentPath ? $documentPath : $documentUrl);
$initialHtmlJson = json_encode(
    (string) $html,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_INVALID_UTF8_SUBSTITUTE
);
$hasOriginalHtml = isset($orgHtml) && is_string($orgHtml);
$originalHtmlJson = $hasOriginalHtml ? json_encode(
    $orgHtml,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_INVALID_UTF8_SUBSTITUTE
) : 'null';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改公告源码 - <?php echo htmlspecialchars($documentName, ENT_QUOTES, 'UTF-8'); ?></title>
    <style>
        :root {
            --ink: #202433;
            --muted: #707789;
            --line: #e5e7ed;
            --surface: #fff;
            --canvas: #eef0f4;
            --accent: #6847f5;
            --danger: #dd3f53;
            --shadow: 0 12px 36px rgba(27, 31, 46, .1);
        }
        * { box-sizing: border-box; }
        html, body { height: 100%; }
        #editForm { height: 100%; }
        body {
            margin: 0;
            overflow: hidden;
            color: var(--ink);
            background: var(--canvas);
            font: 14px/1.5 Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
        }
        button, textarea { font: inherit; }
        button { color: inherit; }
        .app {
            height: 100%;
            display: grid;
            grid-template-rows: 52px minmax(0, 1fr);
        }
        .topbar {
            z-index: 20;
            display: grid;
            grid-template-columns: minmax(180px, auto) minmax(120px, 1fr) auto auto;
            align-items: center;
            gap: 12px;
            padding: 0 12px;
            border-bottom: 1px solid var(--line);
            background: rgba(255, 255, 255, .96);
            backdrop-filter: blur(16px);
        }
        .brand, .top-actions, .history-actions, .mode-switch {
            display: flex;
            align-items: center;
        }
        .brand { min-width: 0; gap: 11px; }
        .brand-mark {
            width: 32px;
            height: 32px;
            flex: 0 0 auto;
            display: grid;
            place-items: center;
            border-radius: 11px;
            color: #fff;
            background: linear-gradient(145deg, #8064ff, #5532dc);
            box-shadow: 0 8px 18px rgba(104, 71, 245, .28);
            font-size: 17px;
            font-weight: 800;
        }
        .brand-copy { min-width: 0; }
        .brand-name {
            overflow: hidden;
            font-weight: 750;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .brand-sub {
            overflow: hidden;
            margin-top: 2px;
            color: var(--muted);
            font-size: 11px;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .selection-status {
            min-width: 0;
            overflow: hidden;
            padding-left: 12px;
            border-left: 1px solid var(--line);
            color: var(--muted);
            font-size: 12px;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .mode-switch {
            gap: 4px;
            padding: 4px;
            border: 1px solid var(--line);
            border-radius: 11px;
            background: #f5f6f8;
        }
        .mode-button {
            border: 0;
            padding: 7px 14px;
            border-radius: 8px;
            color: var(--muted);
            background: transparent;
            cursor: pointer;
            font-size: 13px;
            font-weight: 650;
        }
        .mode-button.active {
            color: var(--ink);
            background: #fff;
            box-shadow: 0 2px 8px rgba(30, 34, 49, .09);
        }
        .top-actions { justify-content: flex-end; gap: 8px; }
        .history-actions {
            gap: 3px;
            margin-right: 3px;
            padding-right: 9px;
            border-right: 1px solid var(--line);
        }
        .icon-button, .toolbar-button, .primary-action {
            min-height: 32px;
            border: 1px solid var(--line);
            border-radius: 10px;
            background: #fff;
            cursor: pointer;
            transition: border-color .16s ease, background .16s ease, transform .16s ease;
        }
        .icon-button { width: 32px; padding: 0; color: #596071; font-size: 17px; }
        .toolbar-button { padding: 0 13px; color: #51586a; font-size: 13px; font-weight: 650; }
        .primary-action {
            padding: 0 16px;
            border-color: var(--accent);
            color: #fff;
            background: var(--accent);
            font-weight: 700;
        }
        button:hover:not(:disabled) { transform: translateY(-1px); border-color: #c9c4ec; }
        button:disabled { opacity: .38; cursor: not-allowed; }
        .workspace {
            min-height: 0;
            height: 100%;
            overflow: hidden;
        }
        .canvas-area {
            min-width: 0;
            min-height: 0;
            height: 100%;
            display: grid;
            grid-template-rows: minmax(0, 1fr);
            background:
                radial-gradient(circle at 1px 1px, rgba(99, 106, 125, .13) 1px, transparent 0) 0 0 / 18px 18px,
                var(--canvas);
        }
        .canvas-area.source-mode {
            grid-template-rows: minmax(0, 1fr);
            background: #fff;
        }
        .stage { min-height: 0; overflow: auto; padding: 10px; }
        .viewport {
            width: 100%;
            max-width: none;
            min-height: 100%;
            margin: 0 auto;
            overflow: hidden;
            border: 1px solid #d8dbe3;
            border-radius: 6px;
            background: #fff;
            box-shadow: var(--shadow);
        }
        #pageFrame {
            display: block;
            width: 100%;
            min-height: calc(100vh - 72px);
            border: 0;
            background: #fff;
        }
        .source-wrap {
            min-height: 0;
            height: 100%;
            display: none;
            grid-template-rows: 36px minmax(0, 1fr);
            margin: 0;
            overflow: hidden;
            border: 0;
            border-radius: 0;
            background: #fff;
            box-shadow: none;
        }
        .source-wrap.visible { display: grid; }
        .source-wrap:fullscreen {
            width: 100%;
            height: 100%;
            display: grid;
            grid-template-rows: 36px minmax(0, 1fr);
            background: #fff;
        }
        .source-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 14px;
            border-bottom: 1px solid #e1e4ea;
            color: #687083;
            background: #f7f8fa;
            font-size: 12px;
        }
        .source-actions { display: flex; align-items: center; gap: 6px; }
        .source-header button {
            padding: 5px 10px;
            border: 1px solid #d8dce5;
            border-radius: 7px;
            color: #4f5668;
            background: #fff;
            cursor: pointer;
            font-size: 12px;
        }
        #sourceEditor {
            width: 100%;
            height: 100%;
            min-height: 0;
            resize: none;
            border: 0;
            outline: 0;
            padding: 14px 16px 60px;
            color: #202433;
            background: #fff;
            caret-color: #6847f5;
            font: 13px/1.72 Consolas, "SFMono-Regular", Monaco, monospace;
            tab-size: 2;
            white-space: pre;
        }
        .context-toolbar {
            position: fixed;
            z-index: 80;
            display: flex;
            align-items: center;
            gap: 4px;
            max-width: calc(100vw - 20px);
            padding: 6px;
            border: 1px solid rgba(255, 255, 255, .12);
            border-radius: 12px;
            color: #fff;
            background: #242837;
            box-shadow: 0 12px 34px rgba(22, 25, 36, .28);
            white-space: nowrap;
        }
        .context-toolbar[hidden] { display: none; }
        .context-toolbar::after {
            content: "";
            position: absolute;
            left: 22px;
            bottom: -6px;
            width: 11px;
            height: 11px;
            background: #242837;
            transform: rotate(45deg);
        }
        .context-toolbar.below::after { top: -6px; bottom: auto; }
        .context-button {
            min-height: 34px;
            padding: 0 11px;
            border: 0;
            border-radius: 8px;
            color: #e9ebf2;
            background: transparent;
            cursor: pointer;
            font-size: 12px;
            font-weight: 650;
        }
        .context-button:hover:not(:disabled) { color: #fff; background: #373c4d; transform: none; }
        .context-button.edit { color: #c9bdff; }
        .context-button.danger { color: #ffb7c0; }
        .context-divider { width: 1px; height: 22px; margin: 0 2px; background: #414658; }
        .toast-stack {
            position: fixed;
            z-index: 100;
            right: 18px;
            bottom: 18px;
            display: grid;
            gap: 8px;
            pointer-events: none;
        }
        .toast {
            min-width: 220px;
            padding: 12px 14px;
            border: 1px solid rgba(255, 255, 255, .14);
            border-radius: 11px;
            color: #fff;
            background: #252938;
            box-shadow: 0 12px 32px rgba(24, 27, 38, .22);
            animation: toast-in .2s ease both;
            font-size: 13px;
        }
        .toast.error { background: #a72f40; }
        @keyframes toast-in {
            from { opacity: 0; transform: translateY(8px); }
        }
        @media (max-width: 980px) {
            .topbar { grid-template-columns: minmax(150px, auto) minmax(90px, 1fr) auto auto; padding-inline: 10px; }
            .brand-sub { display: none; }
            .selection-status { padding-left: 8px; }
        }
        @media (max-width: 720px) {
            body { overflow: auto; }
            .app { min-width: 650px; }
        }
    </style>
</head>
<body>
<form id="editForm" action="" method="post">
    <input type="hidden" name="url" value="<?php echo htmlspecialchars($documentUrl, ENT_QUOTES, 'UTF-8'); ?>">
    <textarea id="contentField" name="content" hidden></textarea>

    <div class="app">
        <header class="topbar">
            <div class="brand">
                <div class="brand-mark">&lt;/&gt;</div>
                <div class="brand-copy">
                    <div class="brand-name">公告源码编辑器</div>
                    <div class="brand-sub" title="<?php echo htmlspecialchars($documentUrl, ENT_QUOTES, 'UTF-8'); ?>">
                        <?php echo htmlspecialchars($documentName, ENT_QUOTES, 'UTF-8'); ?>
                    </div>
                </div>
            </div>

            <div class="selection-status" id="breadcrumb">请点选内容，双击文字可直接编辑</div>

            <div class="mode-switch" aria-label="编辑模式">
                <button class="mode-button active" id="visualModeButton" type="button">可视化</button>
                <button class="mode-button" id="sourceModeButton" type="button">HTML 源码</button>
            </div>

            <div class="top-actions">
                <div class="history-actions">
                    <button class="icon-button" id="undoButton" type="button" title="撤销 (Ctrl+Z)" aria-label="撤销">↶</button>
                    <button class="icon-button" id="redoButton" type="button" title="重做 (Ctrl+Y)" aria-label="重做">↷</button>
                </div>
                <?php if($hasOriginalHtml){ ?>
                <button class="toolbar-button" id="loadOriginalButton" type="button" title="载入首次修改前保存的 _ORG.html">载入最原始</button>
                <?php } ?>
                <button class="toolbar-button" id="resetButton" type="button" title="放弃本次编辑，恢复打开页面时读取的当前文件">
                    <span class="reset-label">重载当前版本</span>
                </button>
                <button class="primary-action" id="saveButton" type="submit">保存公告</button>
            </div>
        </header>

        <main class="workspace">
            <section class="canvas-area">
                <div class="stage" id="visualStage">
                    <div class="viewport">
                        <iframe id="pageFrame" title="公告页面画布" sandbox="allow-same-origin"></iframe>
                    </div>
                </div>
                <div class="source-wrap" id="sourceWrap">
                    <div class="source-header">
                        <span><?php echo htmlspecialchars($documentName, ENT_QUOTES, 'UTF-8'); ?></span>
                        <div class="source-actions">
                            <button id="fullscreenSourceButton" type="button">全屏源码</button>
                            <button id="applySourceButton" type="button">应用到画布</button>
                        </div>
                    </div>
                    <textarea id="sourceEditor" spellcheck="false" aria-label="HTML 源码编辑器"></textarea>
                </div>
            </section>
        </main>
    </div>
</form>

<div class="context-toolbar" id="contextToolbar" role="toolbar" aria-label="所选内容操作" hidden>
    <button class="context-button edit" id="editTextButton" type="button">修改文字</button>
    <span class="context-divider" aria-hidden="true"></span>
    <button class="context-button" id="duplicateBeforeButton" type="button" title="复制当前所选元素的直接父级元素">复制父级到前面</button>
    <button class="context-button" id="duplicateAfterButton" type="button" title="复制当前所选元素的直接父级元素">复制父级到后面</button>
    <span class="context-divider" aria-hidden="true"></span>
    <button class="context-button danger" id="deleteElementButton" type="button" title="只删除当前选中的元素">删除当前元素</button>
    <button class="context-button danger" id="deleteParentButton" type="button" title="删除当前所选元素的直接父级元素">删除父级元素</button>
</div>
<div class="toast-stack" id="toastStack" aria-live="polite"></div>

<script>
(() => {
    'use strict';

    const INITIAL_HTML = <?php echo $initialHtmlJson === false ? '""' : $initialHtmlJson; ?>;
    const ORIGINAL_HTML = <?php echo $originalHtmlJson === false ? 'null' : $originalHtmlJson; ?>;
    const EDITOR_STYLE_ID = 'gxb-html-editor-style';
    const MAX_HISTORY = 50;

    const form = document.getElementById('editForm');
    const contentField = document.getElementById('contentField');
    const frame = document.getElementById('pageFrame');
    const visualStage = document.getElementById('visualStage');
    const sourceWrap = document.getElementById('sourceWrap');
    const sourceEditor = document.getElementById('sourceEditor');
    const visualModeButton = document.getElementById('visualModeButton');
    const sourceModeButton = document.getElementById('sourceModeButton');
    const canvasArea = document.querySelector('.canvas-area');
    const breadcrumb = document.getElementById('breadcrumb');
    const contextToolbar = document.getElementById('contextToolbar');
    const toastStack = document.getElementById('toastStack');
    const undoButton = document.getElementById('undoButton');
    const redoButton = document.getElementById('redoButton');
    const saveButton = document.getElementById('saveButton');
    const loadOriginalButton = document.getElementById('loadOriginalButton');
    const fullscreenSourceButton = document.getElementById('fullscreenSourceButton');
    const editTextButton = document.getElementById('editTextButton');
    const duplicateBeforeButton = document.getElementById('duplicateBeforeButton');
    const duplicateAfterButton = document.getElementById('duplicateAfterButton');
    const deleteElementButton = document.getElementById('deleteElementButton');
    const deleteParentButton = document.getElementById('deleteParentButton');

    let mode = 'visual';
    let selectedElement = null;
    let history = [];
    let historyIndex = -1;
    let historyTimer = null;
    let sourceDirty = false;
    let pageDirty = false;
    let isSubmitting = false;

    function showToast(message, type = 'success') {
        const toast = document.createElement('div');
        toast.className = `toast ${type === 'error' ? 'error' : ''}`;
        toast.textContent = message;
        toastStack.appendChild(toast);
        setTimeout(() => {
            toast.style.opacity = '0';
            toast.style.transform = 'translateY(6px)';
            setTimeout(() => toast.remove(), 220);
        }, 2600);
    }

    function markDirty() {
        pageDirty = true;
        document.title = document.title.replace(/^\* /, '');
        document.title = `* ${document.title}`;
    }

    function editorCss() {
        return `
            [data-gxb-editor-selected] {
                outline: 2px solid #6847f5 !important;
                outline-offset: 3px !important;
            }
            [data-gxb-editor-hover] {
                outline: 1px dashed rgba(104, 71, 245, .75) !important;
                outline-offset: 2px !important;
            }
            [contenteditable="true"] {
                cursor: text !important;
                outline: 2px solid #18a77b !important;
            }
            body { min-height: 100vh; }
        `;
    }

    function renderDocument(html, onReady) {
        selectedElement = null;
        updateSelectionUi();
        frame.onload = () => {
            setupFrame();
            if (typeof onReady === 'function') onReady();
        };
        frame.srcdoc = String(html || '');
    }

    function setupFrame() {
        const doc = frame.contentDocument;
        if (!doc) return;

        doc.getElementById(EDITOR_STYLE_ID)?.remove();
        const style = doc.createElement('style');
        style.id = EDITOR_STYLE_ID;
        style.textContent = editorCss();
        doc.head.appendChild(style);

        doc.addEventListener('click', event => {
            const target = event.target;
            if (!(target instanceof frame.contentWindow.Element)) return;
            if (target.closest('a')) event.preventDefault();
            selectElement(target === doc.documentElement ? doc.body : target);
            event.stopPropagation();
        }, true);

        doc.addEventListener('mouseover', event => {
            const target = event.target;
            if (!(target instanceof frame.contentWindow.Element) || target === doc.body || target === selectedElement) return;
            target.setAttribute('data-gxb-editor-hover', '');
        }, true);

        doc.addEventListener('mouseout', event => {
            const target = event.target;
            if (target instanceof frame.contentWindow.Element) target.removeAttribute('data-gxb-editor-hover');
        }, true);

        doc.addEventListener('dblclick', event => {
            const target = event.target;
            if (!(target instanceof frame.contentWindow.HTMLElement) || !isTextEditable(target)) return;
            selectElement(target);
            beginTextEdit();
            event.preventDefault();
        }, true);

        doc.addEventListener('input', event => {
            if (event.target !== selectedElement) return;
            breadcrumb.textContent = `已选择：${getContentSummary(selectedElement, 55)}`;
            updateContextToolbarPosition();
            markDirty();
            scheduleHistory();
        }, true);

        doc.addEventListener('focusout', event => {
            const target = event.target;
            if (!(target instanceof frame.contentWindow.HTMLElement) || !target.hasAttribute('contenteditable')) return;
            target.removeAttribute('contenteditable');
            pushHistory();
            updateSelectionUi();
        }, true);

        doc.addEventListener('keydown', event => {
            if ((event.key === 'Delete' || event.key === 'Backspace') && selectedElement && !selectedElement.hasAttribute('contenteditable')) {
                event.preventDefault();
                deleteParentSelected();
            }
        });

        frame.contentWindow.addEventListener('scroll', updateContextToolbarPosition, { passive: true });
    }

    function documentType(doc) {
        const doctype = doc && doc.doctype;
        if (!doctype) return '<!DOCTYPE html>';
        let result = `<!DOCTYPE ${doctype.name}`;
        if (doctype.publicId) result += ` PUBLIC "${doctype.publicId}"`;
        if (doctype.systemId) result += `${doctype.publicId ? '' : ' SYSTEM'} "${doctype.systemId}"`;
        return `${result}>`;
    }

    function cleanHtmlFromFrame() {
        const doc = frame.contentDocument;
        if (!doc) return sourceEditor.value || INITIAL_HTML;
        const clone = doc.documentElement.cloneNode(true);
        clone.querySelector(`#${EDITOR_STYLE_ID}`)?.remove();
        clone.querySelectorAll('[data-gxb-editor-selected], [data-gxb-editor-hover], [contenteditable], [draggable]').forEach(element => {
            element.removeAttribute('data-gxb-editor-selected');
            element.removeAttribute('data-gxb-editor-hover');
            element.removeAttribute('contenteditable');
            element.removeAttribute('draggable');
        });
        return `${documentType(doc)}\n${clone.outerHTML}`;
    }

    function currentHtml() {
        return mode === 'source' ? sourceEditor.value : cleanHtmlFromFrame();
    }

    function getContentSummary(element, maxLength = 80) {
        if (!element) return '没有可显示的内容';
        if (element.tagName === 'IMG') {
            return element.getAttribute('alt') ? `图片：${element.getAttribute('alt')}` : '一张图片';
        }
        const text = (element.textContent || '').replace(/\s+/g, ' ').trim();
        if (!text) return '没有文字的区域';
        return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
    }

    function isTextEditable(element) {
        if (!element || ['IMG', 'HR', 'BR', 'INPUT', 'TEXTAREA', 'SELECT', 'VIDEO', 'AUDIO', 'IFRAME', 'SCRIPT', 'STYLE'].includes(element.tagName)) {
            return false;
        }

        // 允许 <td>文字<br>文字</td> 以及 span/b/font 等行内格式，
        // 但不允许直接编辑整行、整表或包含多个内容区块的外层容器。
        return !element.querySelector(
            'table, thead, tbody, tfoot, tr, td, th, div, section, article, header, footer, ' +
            'ul, ol, li, p, h1, h2, h3, h4, h5, h6, form, fieldset, img, video, audio, iframe'
        );
    }

    function selectElement(element) {
        if (!element || element === frame.contentDocument.documentElement) return;
        if (selectedElement) selectedElement.removeAttribute('data-gxb-editor-selected');
        selectedElement = element;
        selectedElement.removeAttribute('data-gxb-editor-hover');
        selectedElement.setAttribute('data-gxb-editor-selected', '');
        updateSelectionUi();
    }

    function updateSelectionUi() {
        const hasSelection = Boolean(selectedElement && selectedElement.isConnected);
        const doc = frame.contentDocument;
        const isPage = hasSelection && doc && selectedElement === doc.body;
        const parent = hasSelection ? selectedElement.parentElement : null;
        const canUseParent = Boolean(doc && parent && parent !== doc.body && parent !== doc.documentElement);
        contextToolbar.hidden = !hasSelection || isPage || mode !== 'visual';
        editTextButton.disabled = !hasSelection || !isTextEditable(selectedElement);
        duplicateBeforeButton.disabled = !canUseParent;
        duplicateAfterButton.disabled = !canUseParent;
        deleteElementButton.disabled = !hasSelection || isPage;
        deleteParentButton.disabled = !canUseParent;
        breadcrumb.textContent = hasSelection
            ? `已选择：${getContentSummary(selectedElement, 55)}`
            : '请在公告中点选需要修改的内容';
        if (!contextToolbar.hidden) requestAnimationFrame(updateContextToolbarPosition);
    }

    function updateContextToolbarPosition() {
        if (!selectedElement || !selectedElement.isConnected || mode !== 'visual') {
            contextToolbar.hidden = true;
            return;
        }
        const doc = frame.contentDocument;
        if (!doc || selectedElement === doc.body) {
            contextToolbar.hidden = true;
            return;
        }

        const selectedRect = selectedElement.getBoundingClientRect();
        const frameRect = frame.getBoundingClientRect();
        const isVisible = selectedRect.bottom > 0 &&
            selectedRect.top < frameRect.height &&
            frameRect.bottom > 64 &&
            frameRect.top < window.innerHeight;
        contextToolbar.hidden = false;
        contextToolbar.style.visibility = isVisible ? 'visible' : 'hidden';

        const toolbarRect = contextToolbar.getBoundingClientRect();
        const selectedTop = frameRect.top + selectedRect.top;
        const selectedBottom = frameRect.top + selectedRect.bottom;
        let top = selectedTop - toolbarRect.height - 10;
        let placeBelow = false;
        if (top < 70) {
            top = selectedBottom + 10;
            placeBelow = true;
        }
        top = Math.min(top, window.innerHeight - toolbarRect.height - 10);
        const desiredLeft = frameRect.left + selectedRect.left;
        const left = Math.max(10, Math.min(desiredLeft, window.innerWidth - toolbarRect.width - 10));

        contextToolbar.classList.toggle('below', placeBelow);
        contextToolbar.style.top = `${Math.max(70, top)}px`;
        contextToolbar.style.left = `${left}px`;
    }

    function beginTextEdit() {
        if (!selectedElement || !isTextEditable(selectedElement)) {
            showToast('该区域包含多层结构，请点选更内层的文字或单元格', 'error');
            return;
        }
        selectedElement.setAttribute('contenteditable', 'true');
        selectedElement.focus();
        const range = frame.contentDocument.createRange();
        range.selectNodeContents(selectedElement);
        range.collapse(false);
        const selection = frame.contentWindow.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
        contextToolbar.hidden = true;
    }

    function deleteParentSelected() {
        if (!selectedElement) return;
        const doc = frame.contentDocument;
        const parent = selectedElement.parentElement;
        if (!parent || parent === doc.body || parent === doc.documentElement) {
            showToast('当前内容已经在页面最外层，不能再向上删除', 'error');
            return;
        }
        const summary = getContentSummary(parent, 120);
        if (!confirm(`确定删除当前所选元素的直接父级元素吗？\n\n父级元素内容：\n“${summary}”\n\n如果删错了，可以点击顶部的“撤销”。`)) return;
        const next = parent.nextElementSibling || parent.previousElementSibling || parent.parentElement;
        parent.remove();
        selectedElement = null;
        if (next && next !== doc.documentElement) selectElement(next);
        else updateSelectionUi();
        markDirty();
        pushHistory();
        showToast('父级元素已删除');
    }

    function deleteSelectedElement() {
        if (!selectedElement) return;
        const doc = frame.contentDocument;
        if (!doc || selectedElement === doc.body || selectedElement === doc.documentElement) {
            showToast('不能删除整个公告页面', 'error');
            return;
        }

        const summary = getContentSummary(selectedElement, 120);
        if (!confirm(`确定删除当前选中的元素吗？\n\n当前元素内容：\n“${summary}”\n\n如果删错了，可以点击顶部的“撤销”。`)) return;

        const next = selectedElement.nextElementSibling ||
            selectedElement.previousElementSibling ||
            selectedElement.parentElement;
        selectedElement.remove();
        selectedElement = null;
        if (next && next !== doc.documentElement) selectElement(next);
        else updateSelectionUi();
        markDirty();
        pushHistory();
        showToast('当前元素已删除');
    }

    function duplicateParentSelected(position) {
        if (!selectedElement) return;
        const doc = frame.contentDocument;
        const parent = selectedElement.parentElement;
        if (!parent || parent === doc.body || parent === doc.documentElement) {
            showToast('当前内容已经在页面最外层，没有可复制的上一级区域', 'error');
            return;
        }

        const directionText = position === 'before' ? '前面' : '后面';
        const summary = getContentSummary(parent, 120);
        if (!confirm(`确定把当前元素的直接父级复制到${directionText}吗？\n\n父级元素内容：\n“${summary}”\n\n复制后可以点击顶部的“撤销”恢复。`)) return;

        const childPath = [];
        let current = selectedElement;
        while (current && current !== parent) {
            childPath.unshift(Array.from(current.parentElement.children).indexOf(current));
            current = current.parentElement;
        }

        const clone = parent.cloneNode(true);
        clone.querySelectorAll('[id]').forEach(element => element.removeAttribute('id'));
        clone.removeAttribute('id');
        clone.querySelectorAll('[data-gxb-editor-selected], [data-gxb-editor-hover], [contenteditable]').forEach(element => {
            element.removeAttribute('data-gxb-editor-selected');
            element.removeAttribute('data-gxb-editor-hover');
            element.removeAttribute('contenteditable');
        });
        clone.removeAttribute('data-gxb-editor-selected');
        if (position === 'before') {
            parent.parentElement.insertBefore(clone, parent);
        } else {
            parent.parentElement.insertBefore(clone, parent.nextSibling);
        }

        let clonedSelection = clone;
        childPath.forEach(index => {
            if (clonedSelection && clonedSelection.children[index]) clonedSelection = clonedSelection.children[index];
        });
        selectElement(clonedSelection || clone);
        clone.scrollIntoView({ behavior: 'smooth', block: 'center' });
        markDirty();
        pushHistory();
        showToast(position === 'before' ? '父级元素已复制到前面' : '父级元素已复制到后面');
    }

    function pushHistory(htmlOverride) {
        clearTimeout(historyTimer);
        const value = typeof htmlOverride === 'string' ? htmlOverride : currentHtml();
        if (history[historyIndex] === value) return;
        history = history.slice(0, historyIndex + 1);
        history.push(value);
        if (history.length > MAX_HISTORY) history.shift();
        historyIndex = history.length - 1;
        updateHistoryButtons();
    }

    function scheduleHistory() {
        clearTimeout(historyTimer);
        historyTimer = setTimeout(() => pushHistory(), 450);
    }

    function updateHistoryButtons() {
        undoButton.disabled = historyIndex <= 0;
        redoButton.disabled = historyIndex >= history.length - 1;
    }

    function restoreHistory(index) {
        if (index < 0 || index >= history.length) return;
        historyIndex = index;
        const value = history[historyIndex];
        if (mode === 'source') {
            sourceEditor.value = value;
            sourceDirty = false;
        } else {
            renderDocument(value);
        }
        markDirty();
        updateHistoryButtons();
    }

    function switchMode(nextMode) {
        if (nextMode === mode) return;
        clearTimeout(historyTimer);
        if (nextMode === 'source') {
            contextToolbar.hidden = true;
            breadcrumb.textContent = '正在编辑 HTML 源码';
            sourceEditor.value = cleanHtmlFromFrame();
            sourceDirty = false;
            visualStage.style.display = 'none';
            canvasArea.classList.add('source-mode');
            sourceWrap.classList.add('visible');
            visualModeButton.classList.remove('active');
            sourceModeButton.classList.add('active');
            mode = 'source';
            sourceEditor.focus();
            return;
        }

        const value = sourceEditor.value;
        if (sourceDirty) pushHistory(value);
        sourceWrap.classList.remove('visible');
        canvasArea.classList.remove('source-mode');
        visualStage.style.display = '';
        sourceModeButton.classList.remove('active');
        visualModeButton.classList.add('active');
        mode = 'visual';
        sourceDirty = false;
        renderDocument(value);
    }

    function saveDocument() {
        clearTimeout(historyTimer);
        const value = currentHtml();
        if (!value.trim()) {
            showToast('HTML 源码不能为空', 'error');
            return;
        }
        contentField.value = value;
        isSubmitting = true;
        saveButton.disabled = true;
        saveButton.textContent = '正在保存…';
        form.submit();
    }

    visualModeButton.addEventListener('click', () => switchMode('visual'));
    sourceModeButton.addEventListener('click', () => switchMode('source'));
    fullscreenSourceButton.addEventListener('click', async () => {
        try {
            if (document.fullscreenElement === sourceWrap) {
                await document.exitFullscreen();
            } else {
                await sourceWrap.requestFullscreen();
            }
        } catch (error) {
            showToast('当前浏览器不支持源码全屏', 'error');
        }
    });
    document.addEventListener('fullscreenchange', () => {
        fullscreenSourceButton.textContent = document.fullscreenElement === sourceWrap ? '退出全屏' : '全屏源码';
    });
    document.getElementById('applySourceButton').addEventListener('click', async () => {
        if (document.fullscreenElement === sourceWrap) await document.exitFullscreen();
        switchMode('visual');
        showToast('源码已应用到画布');
    });
    sourceEditor.addEventListener('input', () => {
        sourceDirty = true;
        markDirty();
        scheduleHistory();
    });
    sourceEditor.addEventListener('keydown', event => {
        if (event.key !== 'Tab') return;
        event.preventDefault();
        const start = sourceEditor.selectionStart;
        const end = sourceEditor.selectionEnd;
        sourceEditor.setRangeText('  ', start, end, 'end');
        sourceDirty = true;
        markDirty();
    });

    undoButton.addEventListener('click', () => restoreHistory(historyIndex - 1));
    redoButton.addEventListener('click', () => restoreHistory(historyIndex + 1));
    editTextButton.addEventListener('click', beginTextEdit);
    duplicateBeforeButton.addEventListener('click', () => duplicateParentSelected('before'));
    duplicateAfterButton.addEventListener('click', () => duplicateParentSelected('after'));
    deleteElementButton.addEventListener('click', deleteSelectedElement);
    deleteParentButton.addEventListener('click', deleteParentSelected);
    visualStage.addEventListener('scroll', updateContextToolbarPosition, { passive: true });
    window.addEventListener('resize', updateContextToolbarPosition, { passive: true });
    form.addEventListener('submit', event => {
        event.preventDefault();
        saveDocument();
    });
    document.getElementById('resetButton').addEventListener('click', () => {
        if (pageDirty && !confirm('确定放弃本次修改并重新载入原始源码吗？')) return;
        history = [];
        historyIndex = -1;
        sourceDirty = false;
        pageDirty = false;
        document.title = document.title.replace(/^\* /, '');
        if (mode === 'source') {
            sourceEditor.value = INITIAL_HTML;
        } else {
            renderDocument(INITIAL_HTML);
        }
        pushHistory(INITIAL_HTML);
        showToast('已重新载入当前版本');
    });
    if (loadOriginalButton) {
        loadOriginalButton.addEventListener('click', () => {
            if (!confirm('确定载入首次修改前保存的 _ORG.html 吗？\\n\\n载入后不会立即覆盖当前文件，请确认内容后再点击“保存公告”。')) return;
            const value = ORIGINAL_HTML || '';
            if (mode === 'source') {
                sourceEditor.value = value;
                sourceDirty = false;
            } else {
                renderDocument(value);
            }
            markDirty();
            pushHistory(value);
            showToast('已载入最原始文件，请确认后保存');
        });
    }
    document.addEventListener('keydown', event => {
        const key = event.key.toLowerCase();
        if ((event.ctrlKey || event.metaKey) && key === 's') {
            event.preventDefault();
            saveDocument();
        } else if ((event.ctrlKey || event.metaKey) && key === 'z' && !event.shiftKey) {
            event.preventDefault();
            restoreHistory(historyIndex - 1);
        } else if ((event.ctrlKey || event.metaKey) && (key === 'y' || (key === 'z' && event.shiftKey))) {
            event.preventDefault();
            restoreHistory(historyIndex + 1);
        }
    });
    window.addEventListener('beforeunload', event => {
        if (!pageDirty || isSubmitting) return;
        event.preventDefault();
        event.returnValue = '';
    });

    renderDocument(INITIAL_HTML, () => {
        pushHistory(INITIAL_HTML);
        updateHistoryButtons();
    });
})();
</script>
</body>
</html>
