<!DOCTYPE html>
<html lang="zh_cn">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
  <title>
    <%= title %>
  </title>
</head>
<style>
  :root {
    --bg-page: #eef1f6;
    --bg-card: #ffffff;
    --border: #e2e6ed;
    --text: #1a1d24;
    --text-muted: #5c6370;
    --primary: #3b6cff;
    --primary-hover: #2d5ae6;
    --danger: #e85d5d;
    --danger-hover: #d04545;
    --radius: 10px;
    --radius-sm: 6px;
    --shadow: 0 4px 24px rgba(26, 29, 36, 0.08);
  }

  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: "Microsoft YaHei UI", -apple-system, BlinkMacSystemFont,
      "Segoe UI", sans-serif;
  }

  body {
    padding: 16px;
    min-height: 100vh;
    background: var(--bg-page);
    color: var(--text);
    line-height: 1.65;
  }

  .container {
    max-width: 880px;
    margin: 0 auto;
    background: var(--bg-card);
    padding: 24px 28px;
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    border: 1px solid var(--border);
  }

  .tutorial {
    background: linear-gradient(135deg, #f6f8fc 0%, #eef2f9 100%);
    padding: 16px 18px;
    border-radius: var(--radius-sm);
    margin-bottom: 22px;
    border: 1px solid var(--border);
  }

  .tutorial h2 {
    font-size: 1.1rem;
    font-weight: 600;
    margin-bottom: 10px;
    color: var(--text);
  }

  .tutorial p {
    margin: 6px 0;
    font-size: 0.92rem;
    color: var(--text-muted);
  }

  .input-group {
    margin: 14px 0 0;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }

  .input-group label {
    min-width: 48px;
    font-weight: 500;
    font-size: 0.9rem;
  }

  .input-wrapper {
    display: flex;
    gap: 10px;
    flex: 1;
    min-width: 200px;
    flex-wrap: wrap;
    align-items: center;
  }

  input[type="text"] {
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 14px;
    flex: 1;
    min-width: 160px;
    outline: none;
    transition: border-color 0.2s, box-shadow 0.2s;
    background: #fafbfc;
  }

  input[type="text"]:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(59, 108, 255, 0.15);
    background: #fff;
  }

  .btn {
    padding: 9px 16px;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 0.875rem;
    font-weight: 500;
    white-space: nowrap;
    transition: background 0.2s, transform 0.12s, box-shadow 0.2s;
  }

  .btn:active {
    transform: scale(0.98);
  }

  .btn-primary {
    background: var(--primary);
    color: #fff;
    box-shadow: 0 2px 8px rgba(59, 108, 255, 0.35);
  }

  .btn-primary:hover {
    background: var(--primary-hover);
  }

  .btn-secondary {
    background: #fff;
    color: var(--text);
    border: 1px solid var(--border);
  }

  .btn-secondary:hover {
    background: #f4f6fa;
    border-color: #cfd5df;
  }

  .btn-ghost {
    background: transparent;
    color: var(--primary);
    border: 1px solid transparent;
  }

  .btn-ghost:hover {
    background: rgba(59, 108, 255, 0.08);
  }

  .btn-danger {
    background: #fff;
    color: var(--danger);
    border: 1px solid rgba(232, 93, 93, 0.35);
  }

  .btn-danger:hover {
    background: #fff5f5;
    border-color: var(--danger);
  }

  .value-section {
    margin-top: 22px;
    padding-top: 20px;
    border-top: 1px solid var(--border);
  }

  .value-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 12px;
    margin-bottom: 12px;
  }

  .value-toolbar__label {
    font-weight: 600;
    font-size: 0.95rem;
    margin-right: 4px;
  }

  .value-toolbar__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
  }

  #textarea {
    width: 100%;
    min-height: 320px;
    padding: 16px;
    background: #fafbfc;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
    line-height: 1.55;
    font-size: 0.875rem;
    max-height: 70vh;
    overflow: auto;
  }

  #textarea[contenteditable="true"] {
    background: #fff;
    /* 允许移动端长按出现复制/粘贴等系统菜单 */
    -webkit-user-select: text;
    user-select: text;
    -webkit-touch-callout: default;
  }

  #textarea[contenteditable="false"] {
    background: #f0f2f5;
    cursor: not-allowed;
    color: var(--text-muted);
  }

  #markdownPreview {
    margin-top: 0;
    width: 100%;
    height: 70vh;
    min-height: 300px;
    background: #fff;
    padding: 18px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: auto;
    line-height: 1.6;
  }

  #markdownPreview h1,
  #markdownPreview h2,
  #markdownPreview h3 {
    margin-top: 0.75em;
    margin-bottom: 0.4em;
  }

  .app-toast {
    position: fixed;
    bottom: max(24px, env(safe-area-inset-bottom, 0px));
    left: 50%;
    transform: translateX(-50%) translateY(12px);
    max-width: calc(100vw - 32px);
    padding: 12px 18px;
    background: rgba(26, 29, 36, 0.92);
    color: #fff;
    font-size: 0.875rem;
    line-height: 1.45;
    text-align: center;
    border-radius: 10px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s, transform 0.25s;
    z-index: 9999;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  }

  .app-toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }

  .app-toast.app-toast--warn {
    background: rgba(90, 70, 20, 0.94);
  }

  .app-toast.app-toast--err {
    background: rgba(120, 36, 36, 0.94);
  }

  .draft-conflict {
    display: none;
    margin-bottom: 12px;
    padding: 12px 14px;
    border-radius: 10px;
    border: 1px solid rgba(201, 154, 37, 0.35);
    background: rgba(255, 248, 225, 0.92);
    color: #5f4712;
    line-height: 1.5;
  }

  .draft-conflict.is-visible {
    display: block;
  }

  .draft-conflict__title {
    font-weight: 600;
    margin-bottom: 4px;
  }

  .draft-conflict__meta {
    font-size: 0.8125rem;
    color: #7b6320;
    margin-top: 4px;
  }

  .draft-conflict__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
  }

  /* 光标指示器样式 */
  .cursor-indicator {
    position: absolute;
    width: 2px;
    height: 20px;
    background-color: var(--primary);
    pointer-events: none;
    z-index: 1000;
    animation: blink 1s infinite;
  }

  @keyframes blink {

    0%,
    50% {
      opacity: 1;
    }

    51%,
    100% {
      opacity: 0;
    }
  }

  .cursor-label {
    position: absolute;
    background: var(--primary);
    color: white;
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 12px;
    white-space: nowrap;
    pointer-events: none;
    z-index: 1001;
    transform: translateY(-100%);
  }

  @media screen and (max-width: 600px) {
    body {
      padding: 10px;
    }

    .container {
      padding: 18px 16px;
    }

    .input-group {
      flex-direction: column;
      align-items: stretch;
      gap: 8px;
    }

    .input-group label {
      margin-bottom: 0;
    }

    .input-wrapper {
      width: 100%;
    }

    .input-wrapper .btn {
      flex: 1;
      min-width: calc(50% - 5px);
    }

    .value-toolbar__actions {
      width: 100%;
    }

    .value-toolbar__actions .btn {
      flex: 1;
      min-width: calc(50% - 4px);
    }

    #textarea {
      font-size: 13px;
      padding: 12px;
    }
  }
</style>

<body>
  <div class="container">
    <div class="tutorial">
      <h2>使用说明</h2>
      <p>1、点击初始化可随机生成 key，或在输入框内输入字母后点击初始化</p>
      <p>2、连接成功后即可在下方编辑区输入内容</p>
      <p>3、在另一页面输入相同 key 即可同步显示内容</p>
    </div>
    <div class="input-group">
      <label for="randomText">Key</label>
      <div class="input-wrapper">
        <input type="text" id="randomText" placeholder="输入字母或点击初始化" onkeyup="onInput(event)" />
        <button type="button" class="btn btn-primary" id="initBtn" onclick="init()">初始化</button>
        <button type="button" class="btn btn-secondary" id="share" onclick="share()">分享链接</button>
      </div>
    </div>

    <section class="value-section">
      <div class="value-toolbar">
        <span class="value-toolbar__label">Value</span>
        <div class="value-toolbar__actions">
          <button type="button" class="btn btn-primary" id="copyAllBtn" onclick="copyAllContent()" title="复制纯文本">
            一键复制
          </button>
          <button type="button" class="btn btn-secondary" style="display: none" id="formatJsBtn" onclick="formatJs()">
            JS 格式化
          </button>
          <button type="button" class="btn btn-secondary" id="restoreDraftBtn" style="display: none" onclick="restoreLocalDraft()">
            恢复草稿
          </button>
          <button type="button" class="btn btn-danger" onclick="clearContent()">清空</button>
          <button type="button" class="btn btn-ghost" onclick="toggleMarkdownPreview()">Markdown 预览</button>
        </div>
      </div>

      <div class="draft-conflict" id="draftConflictBar" aria-live="polite">
        <div class="draft-conflict__title" id="draftConflictTitle">检测到草稿冲突</div>
        <div id="draftConflictMessage">服务端内容已更新，你可以先查看服务端最新版，或保留并恢复本地草稿后继续编辑。</div>
        <div class="draft-conflict__meta" id="draftConflictMeta"></div>
        <div class="draft-conflict__actions">
          <button type="button" class="btn btn-secondary" onclick="viewServerLatest()">查看服务端最新版</button>
          <button type="button" class="btn btn-secondary" onclick="keepLocalDraft()">保留本地草稿</button>
          <button type="button" class="btn btn-primary" onclick="restoreLocalDraft(true)">恢复草稿</button>
        </div>
      </div>

      <div id="markdownPreview" style="display: none"></div>

      <div contenteditable="false"
        style="width: 100%; height: 70vh; min-height: 300px; position: relative;" id="textarea"
        oninput="sendUpdate()" onkeydown="keypress(event)" onkeyup="updateCursorPosition()"
        onmouseup="updateCursorPosition()" onfocus="updateCursorPosition()"></div>
    </section>
  </div>
  <div class="app-toast" id="appToast" role="status" aria-live="polite"></div>
</body>

</html>
<!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/vs2015.min.css">-->
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js"></script>-->
<!--<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/languages/javascript.min.js"></script>-->
<script>
  let initKey = `<%= k %>`;
  var socket_url = "wss://" + location.hostname + "/copysocket/";
  var interval = null;
  var textareaDom = document.getElementById("textarea");
  var codeShow = document.getElementById("codeShow");

  // 简化的变量
  let clientId = Math.random().toString(36).substr(2, 9);
  let isApplyingRemoteChange = false;
  let lastCursorPosition = 0;
  let lastContent = "";
  let cursorUpdateTimeout = null;
  let contentUpdateTimeout = null;
  let isComposing = false;
  let currentRevision = 0;
  let lastAppliedRevision = 0;
  let pendingBaseRevision = null;
  let latestAuthoritativeContent = "";
  let draftConflictState = null;
  const LOCAL_DRAFT_PREFIX = "copy_local_draft:";

  function randomString(len) {
    len = len || 32;
    var $chars = "ABCDEFGHJKMNPQRSTWXYZ";
    var maxPos = $chars.length;
    var pwd = "";
    for (let i = 0; i < len; i++) {
      pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
    }
    return pwd;
  }

  function onInput(e) {
    if (e.keyCode === 13) {
      init();
    }
  }

  if (localStorage.text || initKey) {
    document.getElementById("randomText").value = initKey || localStorage.text;
    init(true);
  }

  function share() {
    var url =
      location.origin +
      location.pathname +
      "?k=" +
      document.getElementById("randomText").value;
    var ok = function () {
      showToast("分享链接已复制到剪贴板");
    };
    var fail = function () {
      showToast("无法写入剪贴板，请从浏览器地址栏复制当前页链接", 3200, "warn");
    };
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(url).then(ok).catch(function () {
        try {
          fallbackCopyText(url);
          ok();
        } catch (err) {
          fail();
        }
      });
    } else {
      try {
        fallbackCopyText(url);
        ok();
      } catch (err) {
        fail();
      }
    }
  }

  function getDraftStorageKey(name) {
    return LOCAL_DRAFT_PREFIX + (name || "");
  }

  function getCurrentSessionName() {
    return (document.getElementById("randomText").value || "").trim();
  }

  function readLocalDraft(name) {
    if (!name) return null;
    try {
      const raw = localStorage.getItem(getDraftStorageKey(name));
      if (!raw) return null;
      const parsed = JSON.parse(raw);
      if (!parsed || typeof parsed.content !== "string") return null;
      return parsed;
    } catch (err) {
      console.log("读取本地草稿失败:", err);
      return null;
    }
  }

  function removeLocalDraft(name) {
    if (!name) return;
    try {
      localStorage.removeItem(getDraftStorageKey(name));
    } catch (err) {
      console.log("删除本地草稿失败:", err);
    }
    updateRestoreDraftButton();
  }

  function updateRestoreDraftButton() {
    const btn = document.getElementById("restoreDraftBtn");
    if (!btn) return;
    const draft = readLocalDraft(getCurrentSessionName());
    btn.style.display = draft ? "inline-flex" : "none";
    btn.title = draft && draft.savedAt ? "本地草稿时间：" + draft.savedAt : "恢复本地草稿";
  }

  function hideDraftConflict() {
    const bar = document.getElementById("draftConflictBar");
    if (bar) {
      bar.classList.remove("is-visible");
    }
    draftConflictState = null;
  }

  function showDraftConflict(options) {
    const bar = document.getElementById("draftConflictBar");
    if (!bar) return;

    draftConflictState = {
      serverContent: options && typeof options.serverContent === "string"
        ? options.serverContent
        : latestAuthoritativeContent,
      draft: options && options.draft ? options.draft : readLocalDraft(getCurrentSessionName()),
    };

    const title = document.getElementById("draftConflictTitle");
    const message = document.getElementById("draftConflictMessage");
    const meta = document.getElementById("draftConflictMeta");
    const draft = draftConflictState.draft;
    const source = options && options.source === "stale_revision" ? "stale_revision" : "remote_update";

    if (title) {
      title.textContent = source === "stale_revision"
        ? "保存失败：服务端内容已领先本地草稿"
        : "检测到服务端更新与本地草稿冲突";
    }
    if (message) {
      message.textContent = source === "stale_revision"
        ? "你的本次修改没有直接覆盖服务端内容。本地草稿已保留，你可以先查看服务端最新版，再决定是否恢复草稿继续编辑。"
        : "服务端已经有更新内容。你可以先查看服务端最新版，保留本地草稿，或直接恢复草稿后继续编辑。";
    }
    if (meta) {
      const baseMeta = draft && draft.savedAt
        ? "本地草稿保存时间：" + draft.savedAt
        : "本地草稿已暂存到当前浏览器。";
      meta.textContent = source === "stale_revision"
        ? baseMeta + " 你也可以稍后通过上方“恢复草稿”按钮再取回内容。"
        : baseMeta;
    }

    bar.classList.add("is-visible");
    updateRestoreDraftButton();
  }

  function saveRejectedDraft(content, reason) {
    const name = getCurrentSessionName();
    if (!name || typeof content !== "string") return;
    try {
      localStorage.setItem(
        getDraftStorageKey(name),
        JSON.stringify({
          content,
          reason: reason || "stale_revision",
          savedAt: new Date().toLocaleString(),
          revision: currentRevision,
        })
      );
    } catch (err) {
      console.log("保存本地草稿失败:", err);
    }
    updateRestoreDraftButton();
  }

  function restoreLocalDraft(fromConflictAction) {
    const name = getCurrentSessionName();
    const draft = readLocalDraft(name);
    if (!draft) {
      showToast("没有可恢复的本地草稿", 2200, "warn");
      hideDraftConflict();
      updateRestoreDraftButton();
      return;
    }
    textareaDom.innerHTML = draft.content || "";
    lastContent = getCurrentContent();
    showToast(fromConflictAction ? "已切换到本地草稿，你的修改将继续同步到服务端" : "已恢复本地草稿，请确认后自动同步", 2800, "warn");
    hideDraftConflict();
    updateRestoreDraftButton();
    queueContentSync(0);
  }

  function keepLocalDraft() {
    hideDraftConflict();
    updateRestoreDraftButton();
    showToast("已保留本地草稿；你可以先查看服务端最新版，稍后再点“恢复草稿”", 3200, "warn");
  }

  function viewServerLatest() {
    textareaDom.innerHTML = latestAuthoritativeContent || "";
    lastContent = getCurrentContent();
    hideDraftConflict();
    updateRestoreDraftButton();
    showToast("已切换为服务端最新版；如需继续使用本地内容，可再点“恢复草稿”", 3200, "warn");
  }

  function promptRestoreLocalDraft(authoritativeContent) {
    const draft = readLocalDraft(getCurrentSessionName());
    if (!draft || typeof draft.content !== "string") {
      hideDraftConflict();
      updateRestoreDraftButton();
      return;
    }
    if (draft.content === (authoritativeContent || "")) {
      removeLocalDraft(getCurrentSessionName());
      hideDraftConflict();
      return;
    }
    showDraftConflict({
      draft,
      serverContent: authoritativeContent || "",
      source: "remote_update",
    });
    showToast("检测到草稿冲突，可选择“查看服务端最新版 / 保留本地草稿 / 恢复草稿”", 4200, "warn");
    updateRestoreDraftButton();
  }

  /** 通用轻提示：kind 为 'ok' | 'warn' | 'err' */
  function showToast(msg, durationMs, kind) {
    var el = document.getElementById("appToast");
    if (!el) return;
    el.textContent = msg;
    el.classList.remove("app-toast--warn", "app-toast--err");
    if (kind === "warn") el.classList.add("app-toast--warn");
    if (kind === "err") el.classList.add("app-toast--err");
    el.classList.add("show");
    clearTimeout(window._appToastTimer);
    var ms = durationMs == null ? 2200 : durationMs;
    window._appToastTimer = setTimeout(function () {
      el.classList.remove("show");
    }, ms);
  }

  function fallbackCopyText(text) {
    var ta = document.createElement("textarea");
    ta.value = text;
    ta.setAttribute("readonly", "");
    ta.style.position = "fixed";
    ta.style.left = "-9999px";
    document.body.appendChild(ta);
    ta.select();
    try {
      document.execCommand("copy");
    } catch (err) {}
    document.body.removeChild(ta);
  }

  /** 复制编辑区纯文本（一键复制） */
  function copyAllContent() {
    var text = (textareaDom.innerText || "").replace(/\u00a0/g, " ");
    if (!text.trim()) {
      showToast("暂无内容可复制", 2200, "warn");
      return;
    }
    var done = function () {
      showToast("已复制到剪贴板");
    };
    var failCopy = function () {
      showToast("复制失败，请长按选中内容后用系统菜单复制", 3200, "err");
    };
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).then(done).catch(function () {
        try {
          fallbackCopyText(text);
          done();
        } catch (err) {
          failCopy();
        }
      });
    } else {
      try {
        fallbackCopyText(text);
        done();
      } catch (err) {
        failCopy();
      }
    }
  }

  function init(init) {
    if (!document.getElementById("randomText").value) {
      document.getElementById("randomText").value = randomString(6);
    }
    localStorage.text = document.getElementById("randomText").value;
    if (!init && initKey) {
      location.href = location.origin + location.pathname;
    }
    currentRevision = 0;
    lastAppliedRevision = 0;
    pendingBaseRevision = null;
    latestAuthoritativeContent = "";
    hideDraftConflict();
    lastContent = getCurrentContent();
    updateRestoreDraftButton();
    reConnect();
  }

  // 简化的光标位置获取
  function getCursorPosition() {
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      return getTextOffset(range.startContainer, range.startOffset);
    }
    return 0;
  }

  // 简化的光标位置设置
  function setCursorPosition(position) {
    try {
      const selection = window.getSelection();
      const range = document.createRange();
      const textNode = getTextNodeAtPosition(textareaDom, position);
      if (textNode) {
        range.setStart(textNode.node, textNode.offset);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    } catch (e) {
      console.log("设置光标位置失败:", e);
    }
  }

  // 获取文本偏移量
  function getTextOffset(node, offset) {
    let totalOffset = 0;
    const walker = document.createTreeWalker(
      textareaDom,
      NodeFilter.SHOW_TEXT,
      null,
      false
    );

    let currentNode;
    while (currentNode = walker.nextNode()) {
      if (currentNode === node) {
        return totalOffset + offset;
      }
      totalOffset += currentNode.textContent.length;
    }
    return totalOffset;
  }

  // 获取指定位置的文本节点
  function getTextNodeAtPosition(element, position) {
    let currentOffset = 0;
    const walker = document.createTreeWalker(
      element,
      NodeFilter.SHOW_TEXT,
      null,
      false
    );

    let node;
    while (node = walker.nextNode()) {
      const nodeLength = node.textContent.length;
      if (currentOffset + nodeLength >= position) {
        return {
          node: node,
          offset: position - currentOffset
        };
      }
      currentOffset += nodeLength;
    }
    return null;
  }

  // 防抖的光标位置更新
  function updateCursorPosition() {
    if (isApplyingRemoteChange) return;

    clearTimeout(cursorUpdateTimeout);
    cursorUpdateTimeout = setTimeout(() => {
      lastCursorPosition = getCursorPosition();
      // 发送光标位置更新
      if (window.ws && window.ws.readyState === WebSocket.OPEN) {
        window.ws.send(JSON.stringify({
          type: "cursor_update",
          name: document.getElementById("randomText").value,
          clientId: clientId,
          position: lastCursorPosition
        }));
      }
    }, 100);
  }

  // 简化的远程光标显示
  function showRemoteCursor(clientId, position, username) {
    // 移除所有旧的光标指示器
    const existingIndicators = document.querySelectorAll('.cursor-indicator, .cursor-label');
    existingIndicators.forEach(el => el.remove());

    try {
      const textNode = getTextNodeAtPosition(textareaDom, position);
      if (!textNode) return;

      const range = document.createRange();
      range.setStart(textNode.node, textNode.offset);
      const rect = range.getBoundingClientRect();
      const textareaRect = textareaDom.getBoundingClientRect();

      // 检查位置是否在可视区域内
      if (rect.left < textareaRect.left || rect.left > textareaRect.right) return;

      const indicator = document.createElement('div');
      indicator.className = 'cursor-indicator';
      indicator.setAttribute('data-client-id', clientId);
      indicator.style.left = (rect.left - textareaRect.left) + 'px';
      indicator.style.top = (rect.top - textareaRect.top) + 'px';

      const label = document.createElement('div');
      label.className = 'cursor-label';
      label.textContent = username || `用户${clientId.substr(0, 4)}`;
      label.style.left = (rect.left - textareaRect.left) + 'px';
      label.style.top = (rect.top - textareaRect.top - 20) + 'px';

      textareaDom.appendChild(indicator);
      textareaDom.appendChild(label);

      // 3秒后自动移除
      setTimeout(() => {
        if (indicator.parentNode) {
          indicator.remove();
          label.remove();
        }
      }, 3000);
    } catch (e) {
      console.log("显示远程光标失败:", e);
    }
  }

  // 仅对图片长按：自定义保存；普通文本不阻止，以便移动端出现复制/粘贴菜单
  textareaDom.addEventListener("contextmenu", function (e) {
    var img = null;
    for (var n = e.target; n && n !== textareaDom; n = n.parentNode) {
      if (n.tagName === "IMG") {
        img = n;
        break;
      }
    }
    if (!img) return;
    e.preventDefault();
    e.stopPropagation();
    var imgUrl = img.src;
    var a = document.createElement("a");
    a.setAttribute("target", "_blank");
    a.href = imgUrl;
    a.download = imgUrl;
    a.click();
  });

  function getCurrentContent() {
    return textareaDom.innerHTML || "";
  }

  function flushPendingUpdate(force) {
    if (isApplyingRemoteChange) return;
    if (!window.ws || window.ws.readyState !== WebSocket.OPEN) return;

    clearTimeout(contentUpdateTimeout);
    contentUpdateTimeout = null;

    const currentContent = getCurrentContent();
    if (!force && currentContent === lastContent) {
      return;
    }

    pendingBaseRevision = currentRevision;
    window.ws.send(
      JSON.stringify({
        type: "copy_update",
        name: document.getElementById("randomText").value,
        content: currentContent,
        clientId: clientId,
        baseRevision: pendingBaseRevision
      })
    );
    lastContent = currentContent;
  }

  function queueContentSync(delay) {
    if (isApplyingRemoteChange) return;

    clearTimeout(contentUpdateTimeout);
    contentUpdateTimeout = setTimeout(() => {
      flushPendingUpdate(false);
    }, delay);
  }

  textareaDom.addEventListener("compositionstart", function () {
    isComposing = true;
  });

  textareaDom.addEventListener("compositionend", function () {
    isComposing = false;
    queueContentSync(0);
    updateCursorPosition();
  });

  textareaDom.addEventListener("blur", function () {
    flushPendingUpdate(false);
  });

  document.addEventListener("visibilitychange", function () {
    if (document.visibilityState === "hidden") {
      flushPendingUpdate(false);
    }
  });

  document.getElementById("randomText").addEventListener("input", function () {
    hideDraftConflict();
    updateRestoreDraftButton();
  });

  document.getElementById("randomText").addEventListener("blur", function () {
    updateRestoreDraftButton();
  });

  window.addEventListener("pagehide", function () {
    flushPendingUpdate(false);
  });

  function reConnect() {
    if (window.WebSocket) {
      var ws = new WebSocket(socket_url);
      window.ws = ws;

      ws.onopen = function (e) {
        console.log("连接服务器成功");
        document.getElementById("initBtn").innerText = "连接成功";
        textareaDom.setAttribute("contenteditable", true);
        clearInterval(interval);
        ws.send(
          JSON.stringify({
            type: "copy_init",
            name: document.getElementById("randomText").value,
            clientId: clientId
          })
        );
      };

      ws.onclose = function (e) {
        console.log("服务器关闭");
        document.getElementById("initBtn").innerText = "连接断开";
        clearInterval(interval);
        interval = setInterval(() => {
          reConnect();
        }, 2000);
      };

      ws.onerror = function () {
        document.getElementById("initBtn").innerText = "连接出错";
        console.log("连接出错");
      };

      ws.onmessage = function (e) {
        let data;
        try {
          data = JSON.parse(e.data);
        } catch (e) {
          data = e.data;
        }
        console.log(data);

        if (data.type === "ping") {
          ws.send(JSON.stringify({ type: "pong" }));
          return;
        }

        if (data.type === "cursor_update" && data.clientId !== clientId) {
          showRemoteCursor(data.clientId, data.position, data.username);
          return;
        }

        if (!data || data.type !== "copy") {
          return;
        }

        const incomingRevision = Number(data.revision);
        const normalizedRevision = Number.isFinite(incomingRevision)
          ? incomingRevision
          : 0;
        const incomingContent = data.content || "";
        const isOwnMessage = data.sourceClientId && data.sourceClientId === clientId;
        const isAckForPendingRevision =
          isOwnMessage && pendingBaseRevision !== null && normalizedRevision === pendingBaseRevision + 1;

        if (!data.authoritative && normalizedRevision < currentRevision) {
          return;
        }

        if (normalizedRevision < lastAppliedRevision) {
          return;
        }

        currentRevision = Math.max(currentRevision, normalizedRevision);

        if (data.authoritative) {
          latestAuthoritativeContent = incomingContent;
        }

        if (data.rejected && data.reason === "stale_revision") {
          const localRejectedContent = getCurrentContent();
          if (localRejectedContent && localRejectedContent !== incomingContent) {
            saveRejectedDraft(localRejectedContent, data.reason);
          }
          showDraftConflict({
            draft: readLocalDraft(getCurrentSessionName()),
            serverContent: incomingContent,
            source: "stale_revision",
          });
          showToast("保存失败：服务端已有更新，请选择“查看服务端最新版 / 保留本地草稿 / 恢复草稿”", 4600, "warn");
          pendingBaseRevision = null;
          lastAppliedRevision = Math.max(lastAppliedRevision, normalizedRevision);
          latestAuthoritativeContent = incomingContent;
          return;
        }

        if (isAckForPendingRevision) {
          pendingBaseRevision = null;
          lastAppliedRevision = Math.max(lastAppliedRevision, normalizedRevision);
          lastContent = incomingContent;
          hideDraftConflict();
          removeLocalDraft(getCurrentSessionName());
          return;
        }

        if (isOwnMessage && !data.rejected) {
          pendingBaseRevision = null;
          lastAppliedRevision = Math.max(lastAppliedRevision, normalizedRevision);
          lastContent = incomingContent;
          hideDraftConflict();
          removeLocalDraft(getCurrentSessionName());
          return;
        }

        if (textareaDom.innerHTML === incomingContent && !data.rejected) {
          lastAppliedRevision = Math.max(lastAppliedRevision, normalizedRevision);
          lastContent = incomingContent;
          pendingBaseRevision = null;
          if (data.authoritative) {
            promptRestoreLocalDraft(incomingContent);
          } else {
            hideDraftConflict();
          }
          return;
        }

        isApplyingRemoteChange = true;
        textareaDom.innerHTML = incomingContent;
        lastContent = incomingContent;
        lastAppliedRevision = Math.max(lastAppliedRevision, normalizedRevision);
        pendingBaseRevision = null;

        if (data.authoritative) {
          promptRestoreLocalDraft(incomingContent);
        } else {
          hideDraftConflict();
        }

        // 恢复光标位置
        setTimeout(() => {
          try {
            setCursorPosition(lastCursorPosition);
          } catch (e) {
            console.log("恢复光标位置失败:", e);
          }
          isApplyingRemoteChange = false;
        }, 10);
      };

      // 简化的内容更新发送
      window.sendUpdate = function (forceImmediate) {
        if (isApplyingRemoteChange) return;

        if (forceImmediate === true) {
          flushPendingUpdate(true);
          return;
        }

        if (isComposing) {
          queueContentSync(0);
          return;
        }

        queueContentSync(120);
      };
    }
  }

  function highlightCode() {
    codeShow.innerHTML = hljs.highlight(textareaDom.innerHTML, {
      language: "javascript",
    }).value;
  }

  function clearContent() {
    textareaDom.innerHTML = "";
    lastContent = "";
    sendUpdate(true);
  }

  function formatJs() {
    textareaDom.innerHTML = js_beautify(textareaDom.innerHTML);
    window.sendUpdate(true);
  }

  function keypress(e) {
    if (e.keyCode === 9) {
      e.preventDefault();
      document.execCommand('insertText', false, '    ');
    }
  }

  let isMarkdownPreviewMode = false;

  function toggleMarkdownPreview() {
    const previewDiv = document.getElementById("markdownPreview");
    const textareaDiv = document.getElementById("textarea");

    isMarkdownPreviewMode = !isMarkdownPreviewMode;

    if (isMarkdownPreviewMode) {
      previewDiv.style.display = "block";
      textareaDiv.style.display = "none";
      let content = textareaDiv.innerHTML
        .replace(/<br\s*\/?>/gi, "\n")
        .replace(/<div>/gi, "\n")
        .replace(/<\/div>/gi, "\n")
        .replace(/\n{3,}/g, "\n\n")
        .trim();

      previewDiv.innerHTML = marked.parse(content);
    } else {
      previewDiv.style.display = "none";
      textareaDiv.style.display = "block";
    }
  }
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.0/beautify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>