文章详情

返回首页

CF上部署Markdown在线笔记

分享文章 作者: Ws01 创建时间: 2025-12-19 📝 字数: 149,630 字 👁️ 阅读: 6 次

原始 Markdown

/**
 * mmnote v1.0.0 - my markdown note.
 * Copyright (c) 2025, mmnote.com. (MIT Licensed)
 * https://mmnote.com
 */
// 保存笔记的路径前缀
const SAVE_PATH = '_tmp';
// 有效的笔记名称格式(只允许字母、数字、下划线和连字符)
const VALID_NOTE_PATTERN = /^[a-zA-Z0-9_-]+$/;

/**
 * 使用 notePath 生成加密密钥
 * @param {string} notePath - 笔记路径
 * @returns {Promise<CryptoKey>} 加密密钥
 */
async function generateEncryptionKey(notePath) {
  const encoder = new TextEncoder();
  const keyData = encoder.encode(notePath);
  const hashBuffer = await crypto.subtle.digest('SHA-256', keyData);
  return await crypto.subtle.importKey(
    'raw',
    hashBuffer,
    { name: 'AES-GCM' },
    false,
    ['encrypt', 'decrypt']
  );
}

/**
 * 加密文本
 * @param {string} text - 要加密的文本
 * @param {CryptoKey} key - 加密密钥
 * @returns {Promise<string>} 加密后的base64字符串
 */
async function encryptText(text, key) {
  const encoder = new TextEncoder();
  const data = encoder.encode(text);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encryptedData = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    data
  );
  const encryptedArray = new Uint8Array(encryptedData);
  const resultArray = new Uint8Array(iv.length + encryptedArray.length);
  resultArray.set(iv);
  resultArray.set(encryptedArray, iv.length);
  return btoa(String.fromCharCode(...resultArray));
}

/**
 * 解密文本
 * @param {string} encryptedText - 加密的base64字符串
 * @param {CryptoKey} key - 解密密钥
 * @returns {Promise<string>} 解密后的文本
 */
async function decryptText(encryptedText, key) {
  try {
    const encryptedArray = new Uint8Array(atob(encryptedText).split('').map(c => c.charCodeAt(0)));
    const iv = encryptedArray.slice(0, 12);
    const data = encryptedArray.slice(12);
    const decryptedData = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv },
      key,
      data
    );
    const decoder = new TextDecoder();
    return decoder.decode(decryptedData);
  } catch (error) {
    console.error('解密失败:', error);
    return ''; // 解密失败返回空字符串
  }
}

/**
 * 处理所有传入的请求
 * @param {Request} request - 传入的请求对象
 * @returns {Response} 响应对象
 */
async function handleRequest(request) {
  const url = new URL(request.url);
  const pathParts = url.pathname.split('/').filter(Boolean);
  const noteName = pathParts[0];
  const action = pathParts[1];

  // 处理分享相关的请求
  if (noteName === 'share') {
    if (request.method === 'POST' && action) {
      try {
        const shareData = await request.json();
        await NOTES_KV.put('share_' + action, JSON.stringify(shareData));
        return new Response(null, { status: 200 });
      } catch (error) {
        return new Response('保存分享数据失败', { status: 500 });
      }
    } else if (request.method === 'GET' && action) {
      return await handleShareRequest(action);
    }
  }

  // 如果笔记名无效或不存在,生成随机名称并重定向
  if (!noteName || noteName.length > 64 || !VALID_NOTE_PATTERN.test(noteName)) {
    const randomNoteName = generateRandomNoteName();
    return Response.redirect(`${url.origin}/${randomNoteName}`, 302);
  }

  const notePath = `${SAVE_PATH}/${noteName}`;

  // 处理密码相关的请求
  if (action) {
    // 使用笔记名和固定盐值生成密码存储键
    const passwordKey = await generatePasswordKey(noteName);
    
    switch (action) {
      case 'password':
        if (request.method === 'POST') {
          const { password } = await request.json();
          // 为每个密码生成唯一盐值
          const salt = crypto.getRandomValues(new Uint8Array(16));
          const saltHex = Array.from(salt).map(b => b.toString(16).padStart(2, '0')).join('');
          // 使用盐值和密码生成最终哈希
          const finalHash = await hashPasswordWithSalt(password, salt);
          // 存储盐值和哈希
          await NOTES_KV.put(passwordKey, JSON.stringify({
            hash: finalHash,
            salt: saltHex
          }));
          return new Response(null, { status: 200 });
        } else if (request.method === 'DELETE') {
          const { password } = await request.json();
          const storedData = await NOTES_KV.get(passwordKey);
          if (storedData) {
            const { hash, salt } = JSON.parse(storedData);
            const saltArray = new Uint8Array(salt.match(/.{2}/g).map(byte => parseInt(byte, 16)));
            const checkHash = await hashPasswordWithSalt(password, saltArray);
            if (checkHash === hash) {
              await NOTES_KV.delete(passwordKey);
              return new Response(null, { status: 200 });
            }
          }
          return new Response('Invalid password', { status: 401 });
        }
        break;

      case 'password-check':
        const hasPassword = await NOTES_KV.get(passwordKey);
        return new Response(null, { status: hasPassword ? 200 : 404 });

      case 'password-verify':
        const { password } = await request.json();
        const storedData = await NOTES_KV.get(passwordKey);
        if (storedData) {
          const { hash, salt } = JSON.parse(storedData);
          const saltArray = new Uint8Array(salt.match(/.{2}/g).map(byte => parseInt(byte, 16)));
          const checkHash = await hashPasswordWithSalt(password, saltArray);
          if (checkHash === hash) {
            return new Response(null, { status: 200 });
          }
        }
        return new Response('Invalid password', { status: 401 });
    }
  }

  const raw = url.searchParams.has('raw');

  // 根据请求方法分发处理
  switch (request.method) {
    case 'POST':
      return await handlePostRequest(request, notePath);
    case 'GET':
      return raw || isCommandLineRequest(request) 
        ? await handleRawRequest(notePath) 
        : await handleGetRequest(notePath, noteName);
    default:
      return new Response('Method Not Allowed', { status: 405 });
  }
}

/**
 * 使用笔记名和固定盐值生成密码存储键
 * @param {string} noteName - 笔记名称
 * @returns {Promise<string>} 密码存储键
 */
async function generatePasswordKey(noteName) {
  const encoder = new TextEncoder();
  const data = encoder.encode(noteName + '_pwd_protected');
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return '_secure_' + hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

/**
 * 使用盐值对密码进行哈希
 * @param {string} password - 原始密码
 * @param {Uint8Array} salt - 盐值
 * @returns {Promise<string>} 哈希后的密码
 */
async function hashPasswordWithSalt(password, salt) {
  const encoder = new TextEncoder();
  const passwordData = encoder.encode(password);
  
  // 将密码和盐值连接
  const dataToHash = new Uint8Array(passwordData.length + salt.length);
  dataToHash.set(passwordData);
  dataToHash.set(salt, passwordData.length);
  
  // 进行哈希
  const hashBuffer = await crypto.subtle.digest('SHA-256', dataToHash);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

/**
 * 处理POST请求 - 保存或删除笔记内容
 * @param {Request} request - POST请求对象
 * @param {string} notePath - 笔记保存路径
 * @returns {Response} 响应对象
 */
async function handlePostRequest(request, notePath) {
  const formData = await request.formData();
  const text = formData.get('text') || await request.text();

  // 如果内容为空,删除笔记
  if (text.trim().length === 0) {
    await handleDeleteRequest(notePath);
    return new Response('Note will be deleted', { status: 200 });
  } else {
    await saveNoteContent(notePath, text);
    return new Response(null, { status: 204 });
  }
}

/**
 * 处理GET请求 - 返回笔记的HTML页面
 * @param {string} notePath - 笔记路径
 * @param {string} noteName - 笔记名称
 * @returns {Response} HTML响应
 */
async function handleGetRequest(notePath, noteName) {
  const noteContent = await getNoteContent(notePath);
  const html = generateHTML(noteName, noteContent);
  return new Response(html, { headers: { 'Content-Type': 'text/html' } });
}

/**
 * 处理原始内容请求 - 返回纯文本格式的笔记内容
 * @param {string} notePath - 笔记路径
 * @returns {Response} 文本响应
 */
async function handleRawRequest(notePath) {
  const noteContent = await getNoteContent(notePath);
  return noteContent
    ? new Response(noteContent, { headers: { 'Content-Type': 'text/plain' } })
    : new Response('404 Not Found', { status: 404 });
}

/**
 * 处理删除请求
 * @param {string} notePath - 要删除的笔记路径
 * @returns {Response} 响应对象
 */
async function handleDeleteRequest(notePath) {
  await deleteNoteContent(notePath);
  return new Response(null, { status: 204 });
}

/**
 * 生成随机笔记名称
 * @returns {string} 5位随机字符串
 */
function generateRandomNoteName() {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
  return Array.from({ length: 5 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

/**
 * 检查是否为命令行请求
 * @param {Request} request - 请求对象
 * @returns {boolean} 是否为命令行请求
 */
function isCommandLineRequest(request) {
  const userAgent = request.headers.get('User-Agent') || '';
  return userAgent.startsWith('curl') || userAgent.startsWith('Wget');
}

function generateHTML(noteName, noteContent) {
  return `<!DOCTYPE html>
  <html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${noteName} - 在线笔记</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
    <!-- 添加KaTeX依赖 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
    <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
    <!-- 添加Mermaid依赖 -->
    <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
    <style>
      :root {
        --primary-color: #4e92d1;
        --secondary-color: #6c757d;
        --bg-color: #ffffff;
        --text-color: #333333;
        --border-color: #e0e0e0;
        --container-bg: #ffffff;
        --editor-bg: #f8f8f8;
        --shadow-color: rgba(0, 0, 0, 0.1);
        --hover-color: #f0f0f0;
        --link-color: #0366d6;
        --link-hover-color: #0969da;
        --link-visited-color: #6f42c1;
      }

      [data-theme="dark"] {
        --primary-color: #a2c2f5;
        --secondary-color: #9ca3af;
        --bg-color: #1a1a1a;
        --text-color: #f1f1f1;
        --border-color: #404040;
        --container-bg: #2a2a2a;
        --editor-bg: #333333;
        --shadow-color: rgba(0, 0, 0, 0.3);
        --hover-color: #3a3a3a;
        --link-color: #58a6ff;
        --link-hover-color: #79b8ff;
        --link-visited-color: #bc8cff;
      }

      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      body {
        margin: 0;
        background: var(--bg-color);
        color: var(--text-color);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
        font-size: 16px;
        line-height: 1.6;
        transition: all 0.3s ease;
      }

      .container {
        width: 100%;
        max-width: 100%;
        margin: 0 auto;
        padding: 10px;
        min-height: 100vh;
        height: 100vh;
        display: flex;
        flex-direction: column;
        background-color: var(--container-bg);
      }

      .container.toolbar-hidden {
        padding: 10px 10px 10px 10px;
      }

      .container.toolbar-hidden .editor-container {
        height: calc(100vh - 20px);
      }

      @media (min-width: 1200px) {
        .container {
          max-width: 95%;
          box-shadow: 0 0 20px var(--shadow-color);
        }
      }

      .toolbar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 12px 15px;
        background: var(--editor-bg);
        border-radius: 12px;
        margin-bottom: 10px;
        gap: 10px;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        flex-shrink: 0;
        transition: all 0.3s ease;
        cursor: grab;
      }

      /* 添加工具栏滚动条样式 */
      .toolbar::-webkit-scrollbar {
        height: 6px;
        width: 6px;
      }

      .toolbar::-webkit-scrollbar-track {
        background: transparent;
      }

      .toolbar::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 3px;
      }

      .toolbar::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      /* 拖动时的光标样式 */
      .toolbar.dragging {
        cursor: grabbing;
      }

      .toolbar.hidden {
        display: none;
      }

      .toolbar-left {
        display: flex;
        align-items: center;
        gap: 15px;
        flex-shrink: 0; /* 防止工具栏压缩 */
      }

      .toolbar-right {
        display: flex;
        align-items: center;
        gap: 15px;
        flex-shrink: 0; /* 防止工具栏压缩 */
      }

      .toolbar-divider {
        width: 1px;
        height: 20px;
        background-color: var(--border-color);
        margin: 0 2px;
      }

      .switch-label {
        display: flex;
        align-items: center;
        gap: 8px;
        cursor: pointer;
        color: var(--text-color);
        font-size: 0.9rem;
      }

      .switch-label input[type="checkbox"] {
        width: 16px;
        height: 16px;
      }

      .mode-toggle {
        background: none;
        border: none;
        font-size: 1.2rem;
        cursor: pointer;
        padding: 8px;
        border-radius: 50%;
        transition: all 0.3s ease;
        color: var(--text-color);
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .mode-toggle:hover {
        background: var(--hover-color);
      }

      .mode-toggle .sun-icon,
      .mode-toggle .moon-icon {
        display: none;
      }

      [data-theme="dark"] .moon-icon {
        display: block;
      }

      [data-theme="light"] .sun-icon {
        display: block;
      }

      .icon-container {
        width: 20px;
        height: 20px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .note-name {
        color: var(--primary-color);
        font-weight: 500;
        cursor: pointer;
        padding: 5px 10px;
        border-radius: 6px;
        transition: all 0.3s ease;
        display: flex;
        align-items: center;
        gap: 5px;
        transform-origin: left center;
      }

      .note-name:hover {
        background: var(--hover-color);
        transform: scale(1.1);
      }

      #save-status {
        color: var(--secondary-color);
        font-size: 0.9rem;
        margin-left: 10px;
      }

      .editor-container {
        display: grid;
        grid-template-columns: 1fr;
        gap: 15px;
        flex: 1;
        min-height: 0;
        margin: 0;
        position: relative;
        transition: all 0.3s ease;
      }

      /* 编辑器包装器 */
      .editor-wrapper {
        position: relative;
        width: 100%;
        height: 100%;
        display: flex;
        background: var(--editor-bg);
        border: 1px solid var(--border-color);
        border-radius: 8px;
        overflow: hidden;
        flex-direction: column;
      }

      /* 设置统一的行高和字符宽度 */
      .editor-wrapper textarea,
      .line-numbers span {
        line-height: 1.5;
        min-height: 1.5em;
        font-size: 14px;
        font-family: 'Consolas', 'Monaco', monospace;
        box-sizing: border-box;
        letter-spacing: 0;
      }

      .line-numbers {
        padding: 15px 2px 40px 2px;
        background: var(--editor-bg);
        border-right: 1px solid var(--border-color);
        color: var(--secondary-color);
        user-select: none;
        overflow: hidden;
        min-width: 28px;
        width: auto;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
      }

      .line-numbers span {
        display: block;
        padding: 0 4px;
        min-width: 24px;
        text-align: right;
        white-space: nowrap;
      }

      .line-numbers.hidden {
        display: none;
      }

      /* 编辑器主体区域 */
      .editor-main {
        display: flex;
        flex: 1;
        overflow: hidden;
        position: relative;
      }

      /* 状态栏样式统一 */
      .status-bar,
      .preview-status-bar {
        position: relative;
        height: 25px;
        background: var(--editor-bg);
        border-top: 1px solid var(--border-color);
        display: flex;
        align-items: center;
        padding: 0 10px;
        font-size: 12px;
        color: var(--secondary-color);
        justify-content: space-between;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        white-space: nowrap;
        flex-shrink: 0;
      }

      /* 状态栏滚动条样式 */
      .status-bar::-webkit-scrollbar,
      .preview-status-bar::-webkit-scrollbar {
        height: 4px;
        width: 4px;
      }

      .status-bar::-webkit-scrollbar-track,
      .preview-status-bar::-webkit-scrollbar-track {
        background: transparent;
      }

      .status-bar::-webkit-scrollbar-thumb,
      .preview-status-bar::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 2px;
      }

      .status-bar::-webkit-scrollbar-thumb:hover,
      .preview-status-bar::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      .status-left,
      .preview-status-left {
        display: flex;
        align-items: center;
        flex-wrap: nowrap;
        min-width: min-content;
        gap: 8px;
        padding-right: 8px;
      }

      .status-right,
      .preview-status-right {
        display: flex;
        align-items: center;
        margin-left: auto;
        padding-left: 15px;
        flex-wrap: nowrap;
        min-width: min-content;
        gap: 8px;
      }

      /* 确保状态栏内容不会被截断 */
      .status-bar > div,
      .preview-status-bar > div {
        flex-shrink: 0;
      }

      .status-item,
      .preview-status-item {
        display: flex;
        align-items: center;
        gap: 5px;
        font-family: 'Consolas', 'Monaco', monospace;
        white-space: nowrap;
        color: var(--secondary-color);
      }

      .status-item label,
      .preview-status-item label {
        display: flex;
        align-items: center;
        gap: 4px;
        cursor: pointer;
        font-family: inherit;
        font-size: inherit;
        color: var(--secondary-color);
      }

      .status-item input[type="checkbox"],
      .preview-status-item input[type="checkbox"] {
        width: 14px;
        height: 14px;
        cursor: pointer;
        margin: 0;
      }

      .status-item span,
      .preview-status-item span {
        color: var(--secondary-color);
      }

      .editor-container textarea {
        flex: 1;
        height: 100%;
        padding: 15px 10px;
        border: none;
        border-radius: 0;
        background: var(--editor-bg);
        color: var(--text-color);
        resize: none;
        tab-size: 4;
        -moz-tab-size: 4;
        white-space: pre-wrap;
        word-wrap: break-word;
        overflow-wrap: break-word;
        overflow-x: hidden;
        width: 100%;
        transition: all 0.3s ease;
      }

      .editor-container textarea:focus {
        outline: none;
      }

      .editor-container.preview-mode {
        grid-template-columns: 1fr 1fr;
      }

      .preview-container {
        display: none;
        height: 100%;
        padding: 0;  /* 移除padding */
        border-radius: 8px;
        border: 1px solid var(--border-color);
        background: var(--editor-bg);
        color: var(--text-color);
        overflow: hidden;  /* 改为hidden,防止整体滚动 */
        position: relative;
        -webkit-user-select: none;  /* Safari */
        -ms-user-select: none;      /* IE 10+ */
        user-select: none;          /* Standard syntax */
      }

      /* 添加预览内容容器样式 */
      .preview-content {
        height: calc(100% - 25px);  /* 减去状态栏高度 */
        padding: 10px 12px;
        overflow-y: auto;
        overflow-x: hidden;
        -webkit-user-select: text;  /* Safari */
        -ms-user-select: text;      /* IE 10+ */
        user-select: text;          /* Standard syntax */
      }

      /* 预览区状态栏样式 */
      .preview-status-bar {
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 25px;
        background: var(--editor-bg);
        border-top: 1px solid var(--border-color);
        display: flex;
        align-items: center;
        padding: 0 10px;
        font-size: 12px;
        color: var(--secondary-color);
        z-index: 1;
        justify-content: space-between;
        min-width: 100%;
        white-space: nowrap;
      }

      .preview-status-left {
        display: flex;
        align-items: center;
        gap: 8px;
        flex-shrink: 0;
      }

      .preview-status-right {
        display: flex;
        align-items: center;
        gap: 15px;
        flex-shrink: 0;
        margin-left: auto;
      }

      .preview-status-item {
        display: flex;
        align-items: center;
        gap: 5px;
        font-family: 'Consolas', 'Monaco', monospace;
        white-space: nowrap;
        flex-shrink: 0;
      }

      .preview-status-item label {
        display: flex;
        align-items: center;
        gap: 4px;
        cursor: pointer;
        font-family: inherit;
        font-size: inherit;
        color: var(--secondary-color);
      }

      .preview-status-item input[type="checkbox"] {
        width: 14px;
        height: 14px;
        cursor: pointer;
        margin: 0;
      }

      .preview-status-item span {
        color: var(--secondary-color);
      }

      .fullscreen-toggle {
        background: none;
        border: none;
        color: var(--text-color);
        cursor: pointer;
        padding: 2px 8px;
        border-radius: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.2s ease;
        font-size: 12px;
      }

      .fullscreen-toggle:hover {
        background: var(--hover-color);
      }

      /* Markdown 内容样式 */
      .preview-container > *:first-child {
        margin-top: 0;
      }

      .preview-container > *:last-child {
        margin-bottom: 0;
      }

      .preview-container h1,
      .preview-container h2,
      .preview-container h3,
      .preview-container h4,
      .preview-container h5,
      .preview-container h6 {
        margin-top: 1.8em;
        margin-bottom: 0.8em;
        line-height: 1.2;
        color: var(--text-color);
      }

      .preview-container h1:first-child,
      .preview-container h2:first-child,
      .preview-container h3:first-child,
      .preview-container h4:first-child,
      .preview-container h5:first-child,
      .preview-container h6:first-child {
        margin-top: 0;
      }

      .preview-container p {
        margin: 1.2em 0;
        line-height: 1.8;
      }

      .preview-container a {
        display: inline;
        color: var(--link-color);
        text-decoration: none;
        border-bottom: 1px solid transparent;
        transition: all 0.2s ease;
        padding: 2px 4px;
        margin: 0 -4px;
        border-radius: 4px;
        text-align: left;
      }

      /* 处理单独一行的链接 */
      .preview-container p > a:only-child {
        display: inline-block;
        text-align: left;
        width: auto;
      }

      /* 确保链接在段落中的对齐方式 */
      .preview-container p {
        text-align: left;
      }

      .preview-container a:hover {
        background: var(--hover-color);
        color: var(--link-hover-color);
        border-bottom-color: var(--link-hover-color);
      }

      .preview-container a:visited {
        color: var(--link-visited-color);
      }

      .preview-container a:visited:hover {
        background: var(--hover-color);
        border-bottom-color: var(--link-visited-color);
      }

      .preview-container a[href^="http"]::after {
        content: "↗";
        display: inline;
        margin-left: 2px;
        font-size: 0.9em;
        opacity: 0.6;
      }
      .preview-container ul,
      .preview-container ol {
        margin: 1em 0;
        padding-left: 1.5em;
      }

      .preview-container li {
        margin: 0.5em 0;
      }

      .preview-container blockquote {
        margin: 1.2em 0;
        padding: 1em 1.2em;
        border-left: 4px solid var(--border-color);
        background: var(--bg-color);
        border-radius: 0 4px 4px 0;
        display: flow-root;
        width: fit-content;
        max-width: 100%;
        box-shadow: 0 2px 4px var(--shadow-color);
        transition: all 0.2s ease;
      }

      .preview-container blockquote > *:first-child {
        margin-top: 0;
      }

      .preview-container blockquote > *:last-child {
        margin-bottom: 0;
      }

      .preview-container blockquote p {
        margin: 0.8em 0;
        line-height: 1.6;
      }

      .preview-container blockquote + blockquote {
        margin-top: -0.5em;
      }

      /* 嵌套引用的样式 */
      .preview-container blockquote blockquote {
        margin: 0.8em 0;
        border-left-color: var(--secondary-color);
        background: var(--editor-bg);
        box-shadow: none;
      }

      /* 移动端适配 */
      @media (max-width: 768px) {
        .preview-container blockquote {
          padding: 0.8em 1em;
          margin: 1em 0;
          width: 100%;
        }
      }

      /* 代码块基础样式 */
      .preview-container pre {
        background: var(--editor-bg);
        padding: 1.2em 1em;
        border-radius: 8px;
        overflow: auto;
        position: relative;
        margin: 1.5em 0;
        font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
        line-height: 1.5;
        font-size: 0.95em;
        border: 1px solid var(--border-color);
        scrollbar-width: thin;
        scrollbar-color: var(--secondary-color) transparent;
      }

      /* 行内代码样式 */
      .preview-container code {
        background: var(--editor-bg);
        padding: 0.2em 0.4em;
        margin: 0 0.2em;
        border-radius: 4px;
        font-size: 0.9em;
        font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
        border: 1px solid var(--border-color);
      }

      /* 代码块中的代码样式 */
      .preview-container pre code {
        background: none;
        padding: 0;
        margin: 0;
        font-size: 0.95em;
        white-space: pre;
        word-break: normal;
        word-wrap: normal;
        line-height: inherit;
        tab-size: 2;
        hyphens: none;
        border: none;
      }

      /* 代码块滚动条样式 */
      .preview-container pre::-webkit-scrollbar {
        width: 6px;
        height: 6px;
      }

      .preview-container pre::-webkit-scrollbar-track {
        background: transparent;
      }

      .preview-container pre::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 3px;
        border: 2px solid var(--editor-bg);
      }

      .preview-container pre::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      /* 代码块语言标签 */
      .preview-container pre::before {
        content: attr(data-language);
        position: absolute;
        top: 0.5em;
        right: 0.5em;
        font-size: 0.85em;
        color: var(--secondary-color);
        padding: 0.2em 0.5em;
        border-radius: 3px;
        background: var(--container-bg);
        opacity: 0.8;
        transition: opacity 0.2s ease;
      }

      .preview-container pre:hover::before {
        opacity: 1;
      }

      /* 代码高亮主题 - 浅色模式 */
      .hljs {
        color: #383a42;
        background: var(--editor-bg);
      }

      .hljs-comment,
      .hljs-quote {
        color: #a0a1a7;
        font-style: italic;
      }

      .hljs-doctag,
      .hljs-keyword,
      .hljs-formula {
        color: #a626a4;
      }

      .hljs-section,
      .hljs-name,
      .hljs-selector-tag,
      .hljs-deletion,
      .hljs-subst {
        color: #e45649;
      }

      .hljs-literal {
        color: #0184bb;
      }

      .hljs-string,
      .hljs-regexp,
      .hljs-addition,
      .hljs-attribute,
      .hljs-meta .hljs-string {
        color: #50a14f;
      }

      .hljs-attr,
      .hljs-variable,
      .hljs-template-variable,
      .hljs-type,
      .hljs-selector-class,
      .hljs-selector-attr,
      .hljs-selector-pseudo,
      .hljs-number {
        color: #986801;
      }

      .hljs-symbol,
      .hljs-bullet,
      .hljs-link,
      .hljs-meta,
      .hljs-selector-id,
      .hljs-title {
        color: #4078f2;
      }

      .hljs-built_in,
      .hljs-title.class_,
      .hljs-class .hljs-title {
        color: #c18401;
      }

      .hljs-emphasis {
        font-style: italic;
      }

      .hljs-strong {
        font-weight: bold;
      }

      /* 代码高亮主题 - 深色模式 */
      [data-theme="dark"] .hljs {
        color: #abb2bf;
        background: var(--editor-bg);
      }

      [data-theme="dark"] .hljs-comment,
      [data-theme="dark"] .hljs-quote {
        color: #7f848e;
        font-style: italic;
      }

      [data-theme="dark"] .hljs-doctag,
      [data-theme="dark"] .hljs-keyword,
      [data-theme="dark"] .hljs-formula {
        color: #c678dd;
      }

      [data-theme="dark"] .hljs-section,
      [data-theme="dark"] .hljs-name,
      [data-theme="dark"] .hljs-selector-tag,
      [data-theme="dark"] .hljs-deletion,
      [data-theme="dark"] .hljs-subst {
        color: #e06c75;
      }

      [data-theme="dark"] .hljs-literal {
        color: #56b6c2;
      }

      [data-theme="dark"] .hljs-string,
      [data-theme="dark"] .hljs-regexp,
      [data-theme="dark"] .hljs-addition,
      [data-theme="dark"] .hljs-attribute,
      [data-theme="dark"] .hljs-meta .hljs-string {
        color: #98c379;
      }

      [data-theme="dark"] .hljs-attr,
      [data-theme="dark"] .hljs-variable,
      [data-theme="dark"] .hljs-template-variable,
      [data-theme="dark"] .hljs-type,
      [data-theme="dark"] .hljs-selector-class,
      [data-theme="dark"] .hljs-selector-attr,
      [data-theme="dark"] .hljs-selector-pseudo,
      [data-theme="dark"] .hljs-number {
        color: #d19a66;
      }

      [data-theme="dark"] .hljs-symbol,
      [data-theme="dark"] .hljs-bullet,
      [data-theme="dark"] .hljs-link,
      [data-theme="dark"] .hljs-meta,
      [data-theme="dark"] .hljs-selector-id,
      [data-theme="dark"] .hljs-title {
        color: #61afef;
      }

      [data-theme="dark"] .hljs-built_in,
      [data-theme="dark"] .hljs-title.class_,
      [data-theme="dark"] .hljs-class .hljs-title {
        color: #e6c07b;
      }

      [data-theme="dark"] .hljs-emphasis {
        font-style: italic;
      }

      [data-theme="dark"] .hljs-strong {
        font-weight: bold;
      }

      /* 代码复制按钮 */
      .copy-button {
        position: absolute;
        top: 0.5em;
        right: 0.5em;
        padding: 0.2em 0.5em;
        font-size: 0.85em;
        color: var(--text-color);
        background: var(--container-bg);
        border: 1px solid var(--border-color);
        border-radius: 3px;
        cursor: pointer;
        opacity: 0;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        gap: 4px;
      }

      .preview-container pre:hover .copy-button {
        opacity: 0.8;
      }

      .copy-button:hover {
        opacity: 1 !important;
        background: var(--hover-color);
      }

      .copy-button.copied {
        color: #4caf50;
        border-color: #4caf50;
        opacity: 1;
      }

      /* 移动端适配 */
      @media (max-width: 768px) {
        .preview-container pre {
          padding: 1em 0.8em;
          font-size: 0.9em;
        }

        .preview-container code {
          font-size: 0.85em;
        }

        .preview-container pre::before {
          opacity: 1;
        }
      }

      /* 代码块中的链接样式 */
      .preview-container pre a,
      .preview-container code a {
        border-bottom: none;
      }

      .preview-container pre a::after,
      .preview-container code a::after {
        display: none;
      }

      /* 图片链接样式 */
      .preview-container a:has(img) {
        border-bottom: none;
      }

      .preview-container a:has(img)::after {
        display: none;
      }

      .preview-container hr {
        margin: 2em 0;
        border: none;
        border-top: 1px solid var(--border-color);
      }

      .preview-container table {
        width: 100%;
        border-collapse: collapse;
        margin: 1.5em 0;
        overflow-x: auto;
        display: block;
      }

      .preview-container th,
      .preview-container td {
        border: 1px solid var(--border-color);
        padding: 8px 12px;
        text-align: left;
      }

      .preview-container th {
        background-color: var(--hover-color);
        font-weight: 600;
      }

      .preview-container tr:nth-child(even) {
        background-color: var(--editor-bg);
      }

      .preview-container tr:hover {
        background-color: var(--hover-color);
      }

      .preview-container img {
        max-width: 100%;
        margin: 1em 0;
        border-radius: 4px;
      }

      /* 滚动条样式 */
      .editor-container textarea,
      .preview-container,
      .preview-content {
        scrollbar-width: thin;
        scrollbar-color: var(--secondary-color) transparent;
      }

      .editor-container textarea::-webkit-scrollbar,
      .preview-container::-webkit-scrollbar,
      .preview-content::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }

      .editor-container textarea::-webkit-scrollbar-track,
      .preview-container::-webkit-scrollbar-track,
      .preview-content::-webkit-scrollbar-track {
        background: transparent;
      }

      .editor-container textarea::-webkit-scrollbar-thumb,
      .preview-container::-webkit-scrollbar-thumb,
      .preview-content::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 4px;
        border: 2px solid var(--editor-bg);
      }

      .editor-container textarea::-webkit-scrollbar-thumb:hover,
      .preview-container::-webkit-scrollbar-thumb:hover,
      .preview-content::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      /* 移动端滚动条优化 */
      @media (max-width: 768px) {
        .editor-container textarea::-webkit-scrollbar,
        .preview-container::-webkit-scrollbar,
        .preview-content::-webkit-scrollbar {
          width: 6px;
          height: 6px;
        }

        .editor-container textarea::-webkit-scrollbar-thumb,
        .preview-container::-webkit-scrollbar-thumb,
        .preview-content::-webkit-scrollbar-thumb {
          border-width: 1.5px;
        }
      }

      /* 移动端响应式布局优化 */
      @media (max-width: 768px) {
        .container {
          padding: 5px;
          height: 100vh;
          max-height: -webkit-fill-available;
          display: flex;
          flex-direction: column;
          width: 100%;
        }

        .container.toolbar-hidden {
          padding: 5px;
        }

        .editor-container {
          flex: 1;
          min-height: 0;
          gap: 5px;
          margin-bottom: env(safe-area-inset-bottom, 15px);
          width: 100%;
          display: flex;
          flex-direction: column;
        }

        .editor-container.preview-mode {
          display: flex;
          flex-direction: column;
          height: calc(100vh - 60px - env(safe-area-inset-bottom, 15px)); /* 减去工具栏和底部安全区域 */
        }

        .editor-wrapper,
        .preview-container {
          flex: 1;
          min-height: 0;
          overflow: auto;
          -webkit-overflow-scrolling: touch;
          width: 100%;
        }

        .editor-container.preview-mode .editor-wrapper,
        .editor-container.preview-mode .preview-container {
          flex: 1;
          height: 0; /* 让flex:1生效 */
          min-height: 0;
          max-height: none;
          width: 100%;
        }

        /* 处理键盘弹出时的布局 */
        @supports (-webkit-touch-callout: none) {
          .editor-container.preview-mode {
            height: calc(100vh - 60px - env(safe-area-inset-bottom, 15px) - env(keyboard-inset-height, 0px));
          }
        }

        /* 确保内容可滚动 */
        .editor-container.preview-mode .editor-wrapper textarea,
        .editor-container.preview-mode .preview-container {
          height: 100%;
          overflow-y: auto;
        }
      }

      /* 处理超小屏幕设备 */
      @media (max-width: 320px) {
        .container {
          padding: 3px 3px 12px 3px;
        }

        .container.toolbar-hidden {
          padding: 8px;
        }

        .editor-wrapper textarea,
        .line-numbers span {
          font-size: 15px;
        }

        .preview-container {
          font-size: 15px;
          padding: 10px 10px 20px 10px;
        }
      }

      /* 处理横屏模式 */
      @media (max-height: 480px) and (orientation: landscape) {
        .container {
          padding: 5px;
          width: 100%;
        }
        
        .container.toolbar-hidden {
          padding: 5px;
        }
        
        .toolbar {
          padding: 6px 10px;
          margin-bottom: 8px;
          width: 100%;
        }
        
        .editor-container {
          height: calc(100vh - 80px);
          width: 100%;
        }
        
        .editor-container.preview-mode {
          grid-template-columns: 1fr 1fr; /* 横屏时恢复左右布局 */
          gap: 8px;
          width: 100%;
        }
        
        .editor-wrapper,
        .preview-container {
          width: 100%;
          height: 100%;
          box-sizing: border-box;
        }

        .editor-wrapper textarea,
        .preview-container {
          width: 100%;
          height: 100%;
          box-sizing: border-box;
        }
        
        .editor-container.preview-mode .editor-wrapper,
        .editor-container.preview-mode .preview-container {
          height: 100%;
          width: 100%;
        }

        /* 横屏模式下状态栏优化 */
        .status-bar,
        .preview-status-bar {
          height: 24px;
          font-size: 11px;
          padding: 0 6px;
          width: 100%;
        }

        .status-bar .status-left,
        .status-bar .status-right,
        .preview-status-bar .preview-status-left,
        .preview-status-bar .preview-status-right {
          gap: 8px;
        }

        .status-bar .status-right,
        .preview-status-bar .preview-status-right {
          padding-left: 8px;
        }

        .status-item,
        .preview-status-item {
          margin-right: 8px;
        }

        .status-item label,
        .preview-status-item label {
          gap: 2px;
        }

        .status-item input[type="checkbox"],
        .preview-status-item input[type="checkbox"] {
          width: 12px;
          height: 12px;
        }

        /* 调整状态栏右侧开关的间距 */
        .status-right .status-item {
          margin-right: 4px;
        }

        .status-right .switch-label,
        .preview-status-right .switch-label {
          font-size: 11px;
          padding: 2px 4px;
        }
      }
      
      /* 适配折叠屏设备 */
      @media (max-width: 350px) and (min-height: 600px) {
        .container {
          padding: 4px;
        }
        
        .toolbar {
          flex-direction: column;
          align-items: stretch;
        }
        
        .toolbar-left,
        .toolbar-right {
          justify-content: center;
        }
        
        .editor-container {
          height: calc(100vh - 140px);
        }
      }
      
      /* 适配深色模式和高对比度显示 */
      @media (prefers-contrast: high) {
        :root {
          --border-color: #666666;
          --shadow-color: rgba(0, 0, 0, 0.3);
        }
        
        .editor-container textarea,
        .preview-container {
          border-width: 2px;
        }
      }

      /* 适配强制颜色模式 */
      @media (forced-colors: active) {
        :root {
          --border-color: CanvasText;
          --text-color: CanvasText;
          --bg-color: Canvas;
          --primary-color: LinkText;
          --secondary-color: GrayText;
          --container-bg: Canvas;
          --editor-bg: Canvas;
          --hover-color: Highlight;
          --link-color: LinkText;
          --link-hover-color: LinkText;
          --link-visited-color: VisitedText;
        }

        .editor-container textarea,
        .preview-container,
        .toolbar,
        .status-bar,
        .preview-status-bar {
          border: 1px solid CanvasText;
        }

        .markdown-toolbar button,
        .toolbar-select,
        .fullscreen-toggle {
          border: 1px solid CanvasText;
          background: Canvas;
          color: CanvasText;
        }

        .markdown-toolbar button:hover,
        .toolbar-select:hover,
        .fullscreen-toggle:hover {
          background: Highlight;
          color: HighlightText;
        }
      }
      
      /* 减少动画以适应省电模式 */
      @media (prefers-reduced-motion: reduce) {
        * {
          transition: none !important;
        }
      }

      /* 工具栏样式 */
      .markdown-toolbar {
        display: flex;
        gap: 4px;
        align-items: center;
      }

      .markdown-toolbar button {
        padding: 4px 8px;
        background: var(--editor-bg);
        border: 1px solid var(--border-color);
        border-radius: 4px;
        color: var(--text-color);
        cursor: pointer;
        font-size: 14px;
        display: flex;
        align-items: center;
        justify-content: center;
        min-width: 28px;
        height: 28px;
        transition: all 0.2s ease;
      }

      .markdown-toolbar button:hover {
        background: var(--hover-color);
      }

      .toolbar-select {
        padding: 4px 8px;
        border: 1px solid var(--border-color);
        border-radius: 4px;
        background: var(--editor-bg);
        color: var(--text-color);
        font-size: 14px;
        cursor: pointer;
      }

      .toolbar-select:hover {
        background: var(--hover-color);
      }

      /* Emoji 选择器样式 */
      .emoji-picker {
        position: fixed; /* 改为fixed定位,避免滚动问题 */
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        background: var(--container-bg);
        border: 1px solid var(--border-color);
        border-radius: 8px;
        box-shadow: 0 2px 8px var(--shadow-color);
        padding: 15px;
        display: none;
        z-index: 1000;
        max-height: 80vh;
        width: 90%;
        max-width: 400px;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
      }

      .emoji-picker-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
        gap: 8px;
      }

      .emoji-picker button {
        width: 40px;
        height: 40px;
        padding: 8px;
        border: none;
        background: none;
        cursor: pointer;
        border-radius: 8px;
        transition: all 0.2s ease;
        font-size: 20px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .emoji-picker button:hover {
        background: var(--hover-color);
      }

      .emoji-picker-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        z-index: 999;
        display: none;
      }

      /* 全屏模式样式 */
      .preview-container.fullscreen {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100vw;
        height: 100vh;
        z-index: 9999;
        border-radius: 0;
        border: none;
      }

      /* 真实全屏模式的样式 */
      .preview-container:fullscreen {
        background-color: var(--container-bg);
        width: 100vw;
        height: 100vh;
        padding: 0;
        margin: 0;
        border: none;
        border-radius: 0;
      }

      .preview-container:fullscreen .preview-content {
        height: calc(100vh - 25px);
        padding: 20px;
        max-width: 1200px;
        margin: 0 auto;
      }

      .preview-container:fullscreen .preview-status-bar {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        background: var(--editor-bg);
        border-top: 1px solid var(--border-color);
      }

      /* 移动端全屏适配 */
      @media (max-width: 768px) {
        .preview-container:fullscreen .preview-content {
          padding: 15px;
          height: calc(100vh - 25px - env(safe-area-inset-bottom, 0px));
        }
        
        .preview-container:fullscreen .preview-status-bar {
          padding-bottom: env(safe-area-inset-bottom, 0px);
        }
      }

      .preview-container.fullscreen .preview-content {
        height: calc(100vh - 25px);
        padding: 20px;
      }

      /* 全屏切换按钮样式 */
      .fullscreen-toggle {
        background: none;
        border: none;
        color: var(--text-color);
        cursor: pointer;
        padding: 2px 8px;
        border-radius: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.2s ease;
        font-size: 12px;
      }

      .fullscreen-toggle:hover {
        background: var(--hover-color);
      }

      .fullscreen-toggle .fullscreen-icon {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 16px;
        height: 16px;
      }

      /* 预览区双击时禁止选中文本 */
      .preview-container.fullscreen .preview-content {
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      /* 预览区恢复正常文本选择 */
      .preview-container:not(.fullscreen) .preview-content {
        -webkit-user-select: text;
        -moz-user-select: text;
        -ms-user-select: text;
        user-select: text;
      }

      /* 全屏模式下的状态栏样式 */
      .preview-container.fullscreen .preview-status-bar {
        position: fixed;
        bottom: env(safe-area-inset-bottom, 0);
        left: 0;
        right: 0;
        background: var(--editor-bg);
        border-top: 1px solid var(--border-color);
        z-index: 10000;
      }

      /* 响应式布局 - 移动端优化 */
      @media (max-width: 768px) {
        .toolbar {
          padding: 8px;
          margin-bottom: 12px;
          white-space: nowrap;
          gap: 8px;
        }

        .toolbar-left,
        .toolbar-right {
          gap: 8px;
        }

        .markdown-toolbar button {
          min-width: 32px;
          height: 32px;
          padding: 4px;
        }

        .toolbar-select {
          padding: 4px;
          font-size: 13px;
        }

        .note-name {
          font-size: 0.9rem;
          padding: 4px 8px;
        }

        .emoji-picker {
          padding: 10px;
        }

        .emoji-picker button {
          width: 36px;
          height: 36px;
          font-size: 18px;
        }
      }

      /* 移动端状态栏适配 */
      @media (max-width: 768px) {
        .preview-status-bar {
          padding: 0 8px;
          overflow: hidden;
        }

        .preview-status-right {
          gap: 8px;
        }

        .preview-status-item {
          margin-left: 8px;
          font-size: 11px;
        }
      }

      /* 处理超小屏幕设备状态栏 */
      @media (max-width: 320px) {
        .preview-status-bar {
          padding: 0 5px;
        }

        .preview-status-item {
          margin-left: 5px;
          font-size: 10px;
        }
      }

      /* 全屏模式下的移动端优化 */
      @media (max-width: 768px) {
        .preview-container.fullscreen {
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          width: 100vw;
          height: 100vh;
          height: -webkit-fill-available;
          z-index: 9999;
          border-radius: 0;
          padding-bottom: env(safe-area-inset-bottom, 0);
        }

        .preview-container.fullscreen .preview-content {
          height: calc(100vh - 25px - env(safe-area-inset-bottom, 0));
          padding: 15px;
        }
      }

      /* 处理横屏模式 */
      @media (max-height: 480px) and (orientation: landscape) {
        .preview-container.fullscreen .preview-status-bar {
          height: 24px;
          font-size: 11px;
          padding: 0 6px;
        }

        .preview-container.fullscreen .preview-content {
          height: calc(100vh - 24px);
          padding: 10px;
        }
      }

      /* 在现有样式的末尾添加密码相关样式 */
      .password-dialog {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: var(--container-bg);
        border: 1px solid var(--border-color);
        border-radius: 12px;
        padding: 24px;
        box-shadow: 0 8px 24px var(--shadow-color);
        z-index: 1000;
        width: 90%;
        max-width: 360px;
        transition: all 0.3s ease;
      }

      .password-dialog h3 {
        margin: 0 0 20px 0;
        color: var(--text-color);
        font-size: 1.2em;
        font-weight: 500;
        display: flex;
        align-items: center;
        gap: 8px;
      }

      .password-dialog h3::before {
        content: '🔒';
        font-size: 1.1em;
      }

      .password-dialog input[type="password"] {
        width: 100%;
        padding: 10px 14px;
        margin-bottom: 12px;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        background: var(--editor-bg);
        color: var(--text-color);
        font-size: 15px;
        transition: all 0.2s ease;
      }

      .password-dialog input[type="password"]:hover {
        border-color: var(--secondary-color);
      }

      .password-dialog input[type="password"]:focus {
        outline: none;
        border-color: var(--primary-color);
        box-shadow: 0 0 0 2px var(--primary-color-alpha);
      }

      .password-dialog-message {
        margin-bottom: 20px;
        font-size: 0.95em;
        color: #e74c3c;
        min-height: 20px;
        transition: all 0.3s ease;
        opacity: 0;
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .password-dialog-message::before {
        content: '⚠️';
        font-size: 1.1em;
      }

      .password-dialog-message.success {
        color: #2ecc71;
      }

      .password-dialog-message.success::before {
        content: '✅';
      }

      .password-dialog-message.show {
        opacity: 1;
      }

      .password-dialog-buttons {
        display: flex;
        justify-content: flex-end;
        gap: 12px;
      }

      .password-dialog button {
        padding: 8px 16px;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        background: var(--editor-bg);
        color: var(--text-color);
        cursor: pointer;
        font-size: 14px;
        font-weight: 500;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .password-dialog button:hover {
        background: var(--hover-color);
        border-color: var(--secondary-color);
      }

      .password-dialog button.primary {
        background: var(--primary-color);
        color: white;
        border-color: var(--primary-color);
      }

      .password-dialog button.primary:hover {
        opacity: 0.9;
        transform: translateY(-1px);
      }

      .password-dialog button:active {
        transform: translateY(1px);
      }

      .password-dialog-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        backdrop-filter: blur(4px);
        z-index: 999;
        transition: all 0.3s ease;
      }

      .password-protected .editor-container,
      .password-protected .toolbar {
        filter: blur(8px);
        pointer-events: none;
        user-select: none;
      }

      .password-status {
        display: flex;
        align-items: center;
        gap: 8px;
        color: var(--text-color);
        font-size: 0.9rem;
        padding: 4px 8px;
        border-radius: 6px;
        cursor: pointer;
        transition: all 0.2s ease;
      }

      .password-status:hover {
        background: var(--hover-color);
      }

      .password-status-icon {
        font-size: 1.2rem;
        transition: transform 0.3s ease;
      }

      .password-status:hover .password-status-icon {
        transform: scale(1.1);
      }

      @media (max-width: 768px) {
        .password-dialog {
          width: 90%;
          padding: 20px;
          /* 保持在屏幕中间 */
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          margin: 0;
        }

        .password-dialog h3 {
          font-size: 1.1em;
        }

        .password-dialog input[type="password"] {
          font-size: 14px;
          padding: 8px 12px;
        }

        .password-dialog button {
          padding: 7px 14px;
        }
      }

      @media (max-width: 480px) {
        .password-dialog {
          width: 100%;
          max-width: none;
          border-radius: 12px 12px 0 0;
          bottom: 0;
          top: auto;
          transform: translateX(-50%);
          padding-bottom: calc(20px + env(safe-area-inset-bottom));
        }
      }

      /* 工具栏右侧按钮样式 */
      .toolbar-right {
        display: flex;
        align-items: center;
        gap: 8px;
      }

      .toolbar-button {
        display: flex;
        align-items: center;
        gap: 8px;
        color: var(--text-color);
        font-size: 0.9rem;
        padding: 4px 8px;
        border-radius: 6px;
        cursor: pointer;
        transition: all 0.2s ease;
        border: none;
        background: none;
      }

      .toolbar-button:hover {
        background: var(--hover-color);
      }

      .toolbar-button .icon {
        font-size: 1.2rem;
        transition: transform 0.3s ease;
      }

      .toolbar-button:hover .icon {
        transform: scale(1.1);
      }

      .toolbar-button .label {
        display: none;
      }

      @media (min-width: 768px) {
        .toolbar-button {
          padding: 6px 12px;
        }

        .toolbar-button .label {
          display: inline;
        }
      }

      /* 主题切换按钮特定样式 */
      .theme-toggle .sun-icon,
      .theme-toggle .moon-icon {
        display: none;
      }

      [data-theme="dark"] .moon-icon {
        display: block;
      }

      [data-theme="light"] .sun-icon {
        display: block;
      }

      /* 密码状态按钮特定样式 */
      .password-status {
        display: flex;
        align-items: center;
        gap: 8px;
        color: var(--text-color);
        font-size: 0.9rem;
        padding: 4px 8px;
        border-radius: 6px;
        cursor: pointer;
        transition: all 0.2s ease;
      }

      .password-status:hover {
        background: var(--hover-color);
      }

      .password-status .icon {
        font-size: 1.2rem;
        transition: transform 0.3s ease;
      }

      .password-status:hover .icon {
        transform: scale(1.1);
      }

      /* 添加 Toast 提示框样式 */
      .toast-container {
        position: fixed;
        top: 20px;
        right: 20px;
        z-index: 10000;
        display: flex;
        flex-direction: column;
        gap: 10px;
        pointer-events: none;
        /* 保持在右上角 */
        top: 20px;
        right: 20px;
        width: auto;
        max-width: calc(100% - 40px);
        transform: none;
      }

      .toast {
        background: var(--container-bg);
        color: var(--text-color);
        padding: 12px 24px;
        border-radius: 8px;
        box-shadow: 0 4px 12px var(--shadow-color);
        font-size: 14px;
        display: flex;
        align-items: center;
        gap: 8px;
        opacity: 0;
        transform: translateX(100%);
        transition: all 0.3s ease;
        border: 1px solid var(--border-color);
        pointer-events: all;
        max-width: 300px;
        word-break: break-word;
      }

      .toast.show {
        opacity: 1;
        transform: translateX(0);
      }

      .toast.success {
        border-left: 4px solid #4caf50;
      }

      .toast.error {
        border-left: 4px solid #f44336;
      }

      .toast.info {
        border-left: 4px solid #2196f3;
      }

      .toast.warning {
        border-left: 4px solid #ff9800;
      }

      .toast-icon {
        font-size: 18px;
        flex-shrink: 0;
      }

      .toast-message {
        flex: 1;
        margin-right: 8px;
      }

      .toast-close {
        cursor: pointer;
        opacity: 0.7;
        transition: opacity 0.2s ease;
        padding: 4px;
        margin: -4px;
        border-radius: 4px;
      }

      .toast-close:hover {
        opacity: 1;
        background: var(--hover-color);
      }

      @media (max-width: 768px) {
        .toast-container {
          /* 保持在右上角 */
          top: 20px;
          right: 20px;
          width: auto;
          max-width: calc(100% - 40px);
          transform: none;
        }

        .toast {
          width: 100%;
          transform: translateY(100%);
        }

        .toast.show {
          transform: translateY(0);
        }
      }

      /* 添加复制全部按钮样式 */
      .copy-all-button {
        background: none;
        border: none;
        color: var(--text-color);
        cursor: pointer;
        padding: 0px 4px;
        border-radius: 4px;
        font-size: 12px;
        display: flex;
        align-items: center;
        gap: 4px;
        transition: all 0.2s ease;
      }

      .copy-all-button:hover {
        background: var(--hover-color);
      }

      /* 添加复制全部的动效样式 */
      .copy-all-item {
        cursor: pointer;
        transition: all 0.2s ease;
        padding: 0px 4px;
        border-radius: 4px;
        display: flex;
        align-items: center;
        gap: 4px;
      }

      .copy-all-item:hover {
        background: var(--hover-color);
      }

      .copy-all-item .copy-icon {
        transition: transform 0.2s ease;
      }

      .copy-all-item:hover .copy-icon {
        transform: scale(1.1);
      }

      .copy-all-item.copied {
        animation: copied-animation 0.5s ease;
      }

      @keyframes copied-animation {
        0% { transform: scale(1); }
        50% { transform: scale(1.1); }
        100% { transform: scale(1); }
      }

      /* 表格样式 */
      .content table {
        width: 100%;
        border-collapse: collapse;
        margin: 1.5em 0;
        overflow-x: auto;
        display: block;
      }

      .content th,
      .content td {
        border: 1px solid var(--border-color);
        padding: 8px 12px;
        text-align: left;
      }

      .content th {
        background-color: var(--hover-color);
        font-weight: 600;
      }

      .content tr:nth-child(even) {
        background-color: var(--editor-bg);
      }

      .content tr:hover {
        background-color: var(--hover-color);
      }

      /* 移动端表格适配 */
      @media (max-width: 768px) {
        .content table {
          font-size: 14px;
        }

        .content th,
        .content td {
          padding: 6px 8px;
        }
      }

      /* 表格滚动条样式 */
      .content table::-webkit-scrollbar {
        height: 8px;
        width: 8px;
      }

      .content table::-webkit-scrollbar-track {
        background: transparent;
      }

      .content table::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 4px;
        border: 2px solid var(--editor-bg);
      }

      .content table::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      /* 表格滚动条样式 */
      .content table::-webkit-scrollbar {
        height: 8px;
        width: 8px;
      }

      .content table::-webkit-scrollbar-track {
        background: transparent;
      }

      .content table::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 4px;
        border: 2px solid var(--editor-bg);
      }

      .content table::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      /* 图片样式 */
      .content img {
        max-width: 100%;
        height: auto;
        margin: 1em 0;
        border-radius: 8px;
        display: block;
        box-shadow: 0 2px 8px var(--shadow-color);
        transition: all 0.3s ease;
        opacity: 0;
        animation: fadeIn 0.5s ease forwards;
      }

      /* 图片容器 */
      .content p:has(img) {
        text-align: center;
        margin: 2em 0;
      }

      /* 图片悬停效果 */
      .content img:hover {
        transform: scale(1.01);
        box-shadow: 0 4px 12px var(--shadow-color);
      }

      /* 图片标题样式 */
      .content img + em {
        display: block;
        text-align: center;
        color: var(--secondary-color);
        font-size: 0.9em;
        margin-top: 0.5em;
      }

      /* 移动端图片适配 */
      @media (max-width: 768px) {
        .content img {
          border-radius: 6px;
          margin: 0.8em 0;
        }

        .content p:has(img) {
          margin: 1.5em 0;
        }

        /* 禁用移动端图片缩放动画 */
        .content img:hover {
          transform: none;
          box-shadow: 0 2px 8px var(--shadow-color);
        }
      }

      /* 图片加载动画 */
      @keyframes fadeIn {
        from {
          opacity: 0;
          transform: translateY(10px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      /* 图片加载失败样式 */
      .content img:not([src]),
      .content img[src=""],
      .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="] {
        position: relative;
        min-height: 100px;
        background: var(--editor-bg);
        border: 1px dashed var(--border-color);
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .content img:not([src])::after,
      .content img[src=""]::after,
      .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="]::after {
        content: "图片加载失败";
        position: absolute;
        color: var(--secondary-color);
        font-size: 0.9em;
      }

      /* 大图查看模式 */
      .content img.enlarged {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        max-width: 90vw;
        max-height: 90vh;
        object-fit: contain;
        z-index: 1000;
        cursor: zoom-out;
        margin: 0;
        padding: 0;
        background: var(--bg-color);
        box-shadow: 0 0 20px var(--shadow-color);
      }

      /* 大图查看遮罩层 */
      .image-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.8);
        z-index: 999;
        display: none;
        opacity: 0;
        transition: opacity 0.3s ease;
      }

      .image-overlay.active {
        display: block;
        opacity: 1;
      }

      /* 代码块复制按钮样式 */
      .content pre {
        position: relative;
      }

      .copy-button {
        position: absolute;
        top: 0.5em;
        right: 0.5em;
        padding: 0.2em 0.5em;
        font-size: 0.85em;
        color: var(--text-color);
        background: var(--container-bg);
        border: 1px solid var(--border-color);
        border-radius: 3px;
        cursor: pointer;
        opacity: 0;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        gap: 4px;
        z-index: 2;
      }

      .content pre:hover .copy-button {
        opacity: 0.8;
      }

      .copy-button:hover {
        opacity: 1 !important;
        background: var(--hover-color);
        transform: translateY(-1px);
      }

      .copy-button.copied {
        color: #4caf50;
        border-color: #4caf50;
        opacity: 1;
      }

      .copy-button.error {
        color: #f44336;
        border-color: #f44336;
        opacity: 1;
      }

      /* 代码块语言标签 */
      .content pre::before {
        content: attr(data-language);
        position: absolute;
        top: 0;
        right: 0;
        padding: 0.2em 0.5em;
        font-size: 0.85em;
        background: var(--container-bg);
        border-bottom-left-radius: 4px;
        color: var(--secondary-color);
        opacity: 0.8;
        transition: opacity 0.2s ease;
      }

      .content pre:hover::before {
        opacity: 0;
      }

      @media (max-width: 768px) {
        .copy-button {
          padding: 0.15em 0.4em;
          font-size: 0.8em;
        }

        .content pre::before {
          font-size: 0.8em;
          padding: 0.15em 0.4em;
        }
      }

      .info-bar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 15px;
        background: var(--editor-bg);
        border-radius: 12px;
        margin-bottom: 20px;
        font-size: 14px;
        border: 1px solid var(--border-color);
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        gap: 15px;
        min-width: 0;
      }

      .info-bar::-webkit-scrollbar {
        height: 6px;
        width: 6px;
      }

      .info-bar::-webkit-scrollbar-track {
        background: transparent;
      }

      .info-bar::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 3px;
      }

      .info-bar::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      .info-left {
        display: flex;
        gap: 15px;
        flex-shrink: 0;
        margin-right: auto;
      }

      .info-right {
        display: flex;
        align-items: center;
        gap: 15px;
        flex-shrink: 0;
        margin-left: auto;
      }

      .info-item {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 4px 10px;
        border-radius: 6px;
        transition: all 0.2s ease;
        white-space: nowrap;
        flex-shrink: 0;
      }

      .info-item:hover {
        background: var(--hover-color);
      }

      .theme-toggle {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 4px 10px;
        border-radius: 6px;
        border: none;
        color: var(--text-color);
        cursor: pointer;
        font-size: 14px;
        transition: all 0.2s ease;
        background: transparent;
        white-space: nowrap;
        flex-shrink: 0;
      }

      .theme-toggle:hover {
        background: var(--hover-color);
      }

      .theme-toggle:active {
        transform: scale(0.95);
      }

      .theme-toggle .sun-icon,
      .theme-toggle .moon-icon {
        display: none;
        font-size: 1.2rem;
      }

      .theme-toggle .label {
        font-size: 14px;
      }

      [data-theme="dark"] .moon-icon {
        display: block;
      }

      [data-theme="light"] .sun-icon {
        display: block;
      }

      @media (max-width: 768px) {
        .theme-toggle {
          padding: 4px 8px;
        }

        .theme-toggle .label {
          font-size: 13px;
        }
      }

      @media (max-width: 768px) {
        .info-bar {
          padding: 10px;
          gap: 10px;
        }

        .info-left,
        .info-right {
          gap: 10px;
        }

        .info-item {
          padding: 4px 8px;
          font-size: 13px;
        }
      }

      /* 添加KaTeX相关样式 */
      .katex-display {
        overflow-x: auto;
        overflow-y: hidden;
        padding: 1em 0;
        margin: 1em 0;
      }

      .katex-display::-webkit-scrollbar {
        height: 6px;
      }

      .katex-display::-webkit-scrollbar-track {
        background: transparent;
      }

      .katex-display::-webkit-scrollbar-thumb {
        background-color: var(--secondary-color);
        border-radius: 3px;
      }

      .katex-display::-webkit-scrollbar-thumb:hover {
        background-color: var(--text-color);
      }

      /* Mermaid图表样式 */
      .mermaid {
        margin: 1.5em 0;
        text-align: center;
        background: var(--editor-bg);
        padding: 1em;
        border-radius: 8px;
        border: 1px solid var(--border-color);
        overflow-x: auto;
      }

      .mermaid svg {
        max-width: 100%;
        height: auto;
      }

      /* 深色模式下的Mermaid样式 */
      [data-theme="dark"] .mermaid {
        --mermaid-bg: var(--editor-bg);
        --mermaid-fg: var(--text-color);
        --mermaid-edge: var(--text-color);
        --mermaid-label: var(--text-color);
        --mermaid-cluster: var(--border-color);
      }

      [data-theme="dark"] .mermaid .node rect,
      [data-theme="dark"] .mermaid .node circle,
      [data-theme="dark"] .mermaid .node ellipse,
      [data-theme="dark"] .mermaid .node polygon,
      [data-theme="dark"] .mermaid .node path {
        fill: var(--mermaid-bg);
        stroke: var(--mermaid-fg);
      }

      [data-theme="dark"] .mermaid .edgePath .path {
        stroke: var(--mermaid-edge) !important;
      }

      [data-theme="dark"] .mermaid .edgeLabel {
        color: var(--mermaid-label);
        background-color: var(--mermaid-bg);
      }

      [data-theme="dark"] .mermaid .cluster rect {
        fill: var(--mermaid-bg) !important;
        stroke: var(--mermaid-cluster) !important;
      }

      [data-theme="dark"] .mermaid .label {
        color: var(--mermaid-label);
      }

      [data-theme="dark"] .mermaid .node .label {
        color: var(--mermaid-label);
      }

      [data-theme="dark"] .mermaid marker {
        fill: var(--mermaid-edge);
      }
    </style>

    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/emoji-toolkit@7.0.0/lib/js/joypixels.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/emoji-toolkit@7.0.0/extras/css/joypixels.min.css">
  </head>
  <body>
    <div class="container">
      <div class="toolbar">
        <div class="toolbar-left">
          <div class="note-name" onclick="copyNoteLink()" title="点击复制链接">
            📋 ${noteName}
          </div>
          <div class="toolbar-divider"></div>
          <!-- 字体和字号选择器 -->
          <select id="font-family" class="toolbar-select" onchange="applyFont()">
            <option value="default">默认字体</option>
            <option value="serif">宋体</option>
            <option value="yahei">微软雅黑</option>
            <option value="kaiti">楷体</option>
            <option value="heiti">黑体</option>
            <option value="fangsong">仿宋</option>
            <option value="songti">新宋体</option>
            <option value="monospace">等宽字体</option>
            <option value="arial">Arial</option>
            <option value="times">Times New Roman</option>
            <option value="helvetica">Helvetica</option>
          </select>
          <select id="font-size" class="toolbar-select" onchange="applyFontSize()">
            <option value="12">12px</option>
            <option value="13">13px</option>
            <option value="14">14px</option>
            <option value="15">15px</option>
            <option value="16" selected>16px</option>
            <option value="17">17px</option>
            <option value="18">18px</option>
            <option value="20">20px</option>
            <option value="22">22px</option>
            <option value="24">24px</option>
            <option value="26">26px</option>
            <option value="28">28px</option>
            <option value="32">32px</option>
          </select>
          <div class="toolbar-divider"></div>
          <!-- Markdown 工具栏 -->
          <div class="markdown-toolbar">
            <button onclick="applyMarkdown('bold')" title="粗体 Ctrl+B">B</button>
            <button onclick="applyMarkdown('italic')" title="斜体 Ctrl+I">I</button>
            <button onclick="applyMarkdown('heading')" title="标题 Ctrl+H">H</button>
            <button onclick="applyMarkdown('strikethrough')" title="删除线 Ctrl+D">S</button>
            <button onclick="applyMarkdown('list')" title="无序列表 Ctrl+U">•</button>
            <button onclick="applyMarkdown('ordered-list')" title="有序列表 Ctrl+O">1.</button>
            <button onclick="applyMarkdown('task')" title="任务列表 Ctrl+T">☐</button>
            <button onclick="applyMarkdown('quote')" title="引用 Ctrl+Q">""</button>
            <button onclick="applyMarkdown('code')" title="代码 Ctrl+K">{}</button>
            <button onclick="applyMarkdown('table')" title="表格">⚏</button>
            <button onclick="applyMarkdown('divider')" title="分割线">—</button>
            <button onclick="applyMarkdown('link')" title="链接 Ctrl+L">🔗</button>
            <button onclick="applyMarkdown('image')" title="图片 Ctrl+P">🖼</button>
            <button onclick="showEmojiPicker()" title="表情">😊</button>
            <div class="toolbar-divider"></div>
            <button onclick="applyMarkdown('latex-inline')" title="行内公式">∑</button>
            <button onclick="applyMarkdown('latex-block')" title="公式块">∫</button>
            <button onclick="applyMarkdown('mermaid')" title="流程图">📊</button>
          </div>
          <span id="save-status"></span>
        </div>
        <div class="toolbar-right">
          <div class="password-status toolbar-button" onclick="showPasswordDialog()" title="密码保护设置">
            <span class="icon" id="password-status-icon">🔓</span>
            <span class="label">密码保护</span>
          </div>
          <button onclick="toggleDarkMode()" class="toolbar-button theme-toggle" title="切换主题">
            <div class="icon">
              <span class="sun-icon">☀️</span>
              <span class="moon-icon">🌙</span>
            </div>
            <span class="label">主题</span>
          </button>
          <div class="share-button toolbar-button" onclick="shareNote()" title="分享笔记">
            <span class="icon">📤</span>
            <span class="label">分享</span>
          </div>
        </div>
      </div>

      <div class="editor-container">
        <div class="editor-wrapper">
          <div class="editor-main">
            <div class="line-numbers"></div>
            <textarea id="content" placeholder="开始输入笔记内容..." onscroll="handleEditorScroll()">${noteContent}</textarea>
          </div>
          <div class="status-bar">
            <div class="status-left">
              <div class="status-item">
                <label title="显示/隐藏行号">
                  <input type="checkbox" id="line-numbers-toggle" checked onchange="toggleLineNumbers()">
                  行号
                </label>
              </div>
              <div class="status-item">
                <label title="显示/隐藏工具栏">
                  <input type="checkbox" id="toolbar-toggle" checked onchange="toggleToolbar()">
                  工具栏
                </label>
              </div>
              <div class="status-item">
                <label class="switch-label">
                  <input type="checkbox" id="preview-toggle" onchange="togglePreview()">
                  预览
                </label>
              </div>
            </div>
            <div class="status-right">
              <div class="status-item copy-all-item" onclick="copyAllContent()" title="复制全部内容">
                <span class="copy-icon">📋</span>
                <span>复制全部</span>
              </div>
              <div class="status-item">
                <span>字数:</span>
                <span id="char-count">0</span>
              </div>
              <div class="status-item">
                <span>单词:</span>
                <span id="word-count">0</span>
              </div>
              <div class="status-item">
                <span>行:</span>
                <span id="line-count">1</span>
              </div>
              <div class="status-item">
                <span>列:</span>
                <span id="column-count">1</span>
              </div>
            </div>
          </div>
        </div>
        <div id="preview" class="preview-container">
          <div class="preview-content" onscroll="handlePreviewScroll()" ondblclick="handlePreviewDoubleTap(event)"></div>
          <div class="preview-status-bar">
            <div class="preview-status-left">
              <button onclick="togglePreviewFullscreen()" class="fullscreen-toggle" title="切换全屏">
                <span class="fullscreen-icon" id="fullscreen-icon">⛶</span>
              </button>
              <div class="preview-status-item">
                <label class="switch-label">
                  <input type="checkbox" id="sync-scroll-toggle" onchange="toggleSyncScroll()">
                  同步滚动
                </label>
              </div>
            </div>
            <div class="preview-status-right">
              <div class="preview-status-item">
                <span>字数:</span>
                <span id="preview-char-count">0</span>
              </div>
              <div class="preview-status-item">
                <span>单词:</span>
                <span id="preview-word-count">0</span>
              </div>
              <div class="preview-status-item">
                <span>段落:</span>
                <span id="preview-paragraph-count">0</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div id="emoji-picker" class="emoji-picker"></div>
    
    <!-- 添加密码对话框 -->
    <div class="password-dialog-overlay" id="password-overlay"></div>
    <div class="password-dialog" id="password-dialog">
      <h3 id="password-dialog-title">设置密码保护</h3>
      <input type="password" id="password-input" placeholder="请输入密码" autocomplete="new-password">
      <div class="password-dialog-message" id="password-message"></div>
      <div class="password-dialog-buttons">
        <button onclick="closePasswordDialog()">
          <span>取消</span>
        </button>
        <button class="primary" onclick="handlePasswordAction()" id="password-action-btn">
          <span>确定</span>
        </button>
      </div>
    </div>

    <!-- 添加 Toast 容器 -->
    <div class="toast-container" id="toast-container"></div>

    <div class="image-overlay" id="imageOverlay" onclick="closeEnlargedImage()"></div>

    <script>
      const content = document.getElementById('content');
      const preview = document.getElementById('preview');
      const previewToggle = document.getElementById('preview-toggle');
      const syncScrollToggle = document.getElementById('sync-scroll-toggle');
      const saveStatus = document.getElementById('save-status');
      let saveTimeout;
      let isEditorScrolling = false;
      let isPreviewScrolling = false;
      let isSyncScrollEnabled = true;
	  let isEmptyNote = false; // Flag to track if the note is empty and space was added

      document.addEventListener('DOMContentLoaded', () => {
        // 配置 marked
        marked.setOptions({
          gfm: true,
          breaks: true,
          tables: true,
          headerIds: true,
          mangle: false,
          sanitize: false,
          smartLists: true,
          smartypants: true,
          xhtml: false,
          langPrefix: 'language-',
          pedantic: false,
          highlight: function(code, lang) {
            if (lang && hljs.getLanguage(lang)) {
              try {
                return hljs.highlight(code, { language: lang }).value;
              } catch (err) {}
            }
            try {
              return hljs.highlightAuto(code).value;
            } catch (err) {}
            return code;
          }
        });

        // 自定义 emoji 渲染
        const renderer = new marked.Renderer();
        const originalText = renderer.text.bind(renderer);
        renderer.text = (text) => {
          return joypixels.shortnameToImage(originalText(text));
        };
        marked.setOptions({ renderer });

        // 初始化主题
        const savedTheme = localStorage.getItem('theme') || 'light';
        document.documentElement.setAttribute('data-theme', savedTheme);

        // 初始化预览状态
        const showPreview = localStorage.getItem('preview') === 'true';
        previewToggle.checked = showPreview;
        
        // 初始化同步滚动状态
        const savedSyncScroll = localStorage.getItem('sync-scroll');
        isSyncScrollEnabled = savedSyncScroll !== 'false';
        syncScrollToggle.checked = isSyncScrollEnabled;
        
        const editorContainer = document.querySelector('.editor-container');
        editorContainer.classList.toggle('preview-mode', showPreview);
        preview.style.display = showPreview ? 'block' : 'none';
        
        if (showPreview) {
          updatePreview(content.value);
        }

        content.addEventListener('input', () => {
          updatePreview(content.value);
		  
		  // If the content becomes empty, insert a space to trigger the save
		  if (content.value.trim().length === 0) {
			  if (!isEmptyNote) {
			  content.value = ' '; // Automatically add a space if content is empty
			  saveStatus.textContent = 'Note will be deleted'; // Notify the user that the note will be deleted
			  isEmptyNote = true; // Flag that space was added
			  }
		  } else {
			  // If content is no longer empty, remove the space and update status
			  if (isEmptyNote) {
			  content.value = content.value.trim(); // Remove the space if user starts typing again
			  isEmptyNote = false; // Reset the flag
			  }
			  saveStatus.textContent = 'Saving...'; // Show saving status while content is being edited
		  }

          debounceSaveContent(content.value);
        });

        window.addEventListener('resize', () => {
          if (previewToggle.checked) {
            syncScrollPositions('editor');
          }
        });

        // 初始化行号显示状态
        const showLineNumbers = localStorage.getItem('show-line-numbers') !== 'false';
        document.getElementById('line-numbers-toggle').checked = showLineNumbers;
        const lineNumbers = document.querySelector('.line-numbers');
        lineNumbers.classList.toggle('hidden', !showLineNumbers);

        // 初始化行号和文本统计
        updateLineNumbers();
        updateTextStats();

        // 初始化工具栏显示状态
        const showToolbar = localStorage.getItem('show-toolbar') !== 'false';
        document.getElementById('toolbar-toggle').checked = showToolbar;
        const toolbar = document.querySelector('.toolbar');
        const container = document.querySelector('.container');
        toolbar.classList.toggle('hidden', !showToolbar);
        container.classList.toggle('toolbar-hidden', !showToolbar);

        // 恢复全屏状态
        const savedFullscreen = localStorage.getItem('preview-fullscreen') === 'true';
        if (savedFullscreen) {
          togglePreviewFullscreen();
        }

        // 添加 Firefox 双击事件监听
        const previewContent = document.querySelector('.preview-content');
        if (previewContent) {
          previewContent.addEventListener('mousedown', (e) => {
            if (e.detail === 2) { // 检测双击
              e.preventDefault(); // 阻止默认的文本选择
            }
          });
        }

        // 添加工具栏鼠标滚动支持
        let isMouseDown = false;
        let startX;
        let scrollLeft;

        toolbar.addEventListener('mousedown', (e) => {
          isMouseDown = true;
          toolbar.classList.add('dragging');
          startX = e.pageX - toolbar.offsetLeft;
          scrollLeft = toolbar.scrollLeft;
        });

        toolbar.addEventListener('mouseleave', () => {
          isMouseDown = false;
          toolbar.classList.remove('dragging');
        });

        toolbar.addEventListener('mouseup', () => {
          isMouseDown = false;
          toolbar.classList.remove('dragging');
        });

        toolbar.addEventListener('mousemove', (e) => {
          if (!isMouseDown) return;
          e.preventDefault();
          const x = e.pageX - toolbar.offsetLeft;
          const walk = (x - startX) * 2;
          toolbar.scrollLeft = scrollLeft - walk;
        });

        // 支持鼠标滚轮横向滚动
        toolbar.addEventListener('wheel', (e) => {
          e.preventDefault();
          toolbar.scrollLeft += e.deltaY;
        });
      });

      function togglePreview() {
        const showPreview = previewToggle.checked;
        const editorContainer = document.querySelector('.editor-container');
        
        editorContainer.classList.toggle('preview-mode', showPreview);
        preview.style.display = showPreview ? 'block' : 'none';
        
        localStorage.setItem('preview', showPreview);
        
        if (showPreview) {
          updatePreview(content.value);
          setTimeout(() => syncScrollPositions('editor'), 100);
        }
      }

      function toggleSyncScroll() {
        isSyncScrollEnabled = syncScrollToggle.checked;
        localStorage.setItem('sync-scroll', isSyncScrollEnabled);
      }

      function updatePreview(text) {
        if (previewToggle.checked) {
          const previewContent = preview.querySelector('.preview-content');
          const scrollPos = previewContent.scrollTop;
          
          // 渲染Markdown内容
          previewContent.innerHTML = marked.parse(text);
          
          // 渲染LaTeX公式
          renderMathInElement(previewContent, {
            delimiters: [
              {left: '$$', right: '$$', display: true},
              {left: '$', right: '$', display: false},
              // 移除普通方括号作为数学公式的标记
              // {left: '\\[', right: '\\]', display: true},
              // 替换为更明确的数学公式标记
              {left: 'math\\[', right: '\\]', display: true},
              {left: '\\(', right: '\\)', display: false},
              {left: '\\begin{align}', right: '\\end{align}', display: true}, // 添加对 align 环境的支持
            ],
            throwOnError: false,
            output: 'html'
          });

          // 渲染Mermaid图表
          mermaid.initialize({
            startOnLoad: false,
            theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default',
            securityLevel: 'loose',
            fontFamily: 'var(--font-family)',
          });

          const mermaidDiagrams = previewContent.querySelectorAll('pre code.language-mermaid');
          mermaidDiagrams.forEach(async (diagram, index) => {
            try {
              const pre = diagram.parentElement;
              const mermaidDiv = document.createElement('div');
              mermaidDiv.className = 'mermaid';
              mermaidDiv.id = 'mermaid-' + Date.now() + '-' + index; // 添加唯一ID
              mermaidDiv.textContent = diagram.textContent;
              pre.parentNode.replaceChild(mermaidDiv, pre);
            } catch (error) {
              console.error('Mermaid渲染错误:', error);
            }
          });

          // 等待所有图表渲染完成
          if (mermaidDiagrams.length > 0) {
            mermaid.run();
          }
          
          // 更新预览区统计信息
          updatePreviewStats(text);
          
          // 为所有代码块添加复制按钮和语言标签
          const codeBlocks = previewContent.querySelectorAll('pre code');
          codeBlocks.forEach(code => {
            const pre = code.parentElement;
            // 跳过Mermaid图表
            if (code.classList.contains('language-mermaid')) return;
            
            // 获取语言类名
            const langClass = Array.from(code.classList).find(cl => cl.startsWith('language-'));
            const language = langClass ? langClass.replace('language-', '') : '代码';
            // 设置语言标签
            pre.setAttribute('data-language', language);
            
            // 添加复制按钮
            if (!pre.querySelector('.copy-button')) {
              const button = document.createElement('button');
              button.className = 'copy-button';
              button.innerHTML = '📋 复制';
              button.onclick = (e) => {
                e.preventDefault();
                copyToClipboard(code.innerText, button);
              };
              pre.appendChild(button);
            }
          });

          // 重新应用代码高亮
          hljs.highlightAll();

          previewContent.scrollTop = scrollPos;
        }
      }

      async function copyToClipboard(text, button) {
        try {
          await navigator.clipboard.writeText(text);
          const originalText = button.innerHTML;
          button.innerHTML = '✅ 已复制';
          button.classList.add('copied');
          setTimeout(() => {
            button.innerHTML = originalText;
            button.classList.remove('copied');
          }, 2000);
        } catch (err) {
          console.error('复制失败:', err);
          button.innerHTML = '❌ 复制失败';
          setTimeout(() => {
            button.innerHTML = '📋 复制';
          }, 2000);
        }
      }

      function handleEditorScroll() {
        if (!isPreviewScrolling && previewToggle.checked && isSyncScrollEnabled) {
          isEditorScrolling = true;
          syncScrollPositions('editor');
          setTimeout(() => { isEditorScrolling = false; }, 50);
        }
      }

      function handlePreviewScroll() {
        if (!isEditorScrolling && previewToggle.checked && isSyncScrollEnabled) {
          isPreviewScrolling = true;
          const previewContent = preview.querySelector('.preview-content');
          const editorHeight = content.scrollHeight - content.clientHeight;
          const previewHeight = previewContent.scrollHeight - previewContent.clientHeight;
          
          if (previewHeight > 0) {
            const scrollPercentage = previewContent.scrollTop / previewHeight;
            content.scrollTop = scrollPercentage * editorHeight;
          }
          setTimeout(() => { isPreviewScrolling = false; }, 50);
        }
      }

      function syncScrollPositions(source) {
        if (!previewToggle.checked || !isSyncScrollEnabled) return;

        const editor = content;
        const previewContent = preview.querySelector('.preview-content');
        const editorHeight = editor.scrollHeight - editor.clientHeight;
        const previewHeight = previewContent.scrollHeight - previewContent.clientHeight;

        if (source === 'editor' && editorHeight > 0) {
          const scrollPercentage = editor.scrollTop / editorHeight;
          previewContent.scrollTop = scrollPercentage * previewHeight;
        } else if (source === 'preview' && previewHeight > 0) {
          const scrollPercentage = previewContent.scrollTop / previewHeight;
          editor.scrollTop = scrollPercentage * editorHeight;
        }
      }

      function debounceSaveContent(text) {
        clearTimeout(saveTimeout);
        saveTimeout = setTimeout(() => saveContent(text), 1000);
      }

      async function saveContent(text) {
        try {
          const response = await fetch(window.location.pathname, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: 'text=' + encodeURIComponent(text),
          });

          saveStatus.textContent = response.status === 204 ? '已保存' : '笔记将被删除';
        } catch (error) {
          saveStatus.textContent = '保存失败';
        }

        setTimeout(() => {
          saveStatus.textContent = '';
        }, 2000);
      }

      // 添加 Toast 提示框功能
      function showToast(message, type = 'info', duration = 3000) {
        const container = document.getElementById('toast-container');
        const toast = document.createElement('div');
        toast.className = 'toast ' + type;
        
        // 设置图标
        const icons = {
          success: '✅',
          error: '❌',
          info: 'ℹ️',
          warning: '⚠️'
        };
        
        toast.innerHTML = 
          '<span class="toast-icon">' + (icons[type] || icons.info) + '</span>' +
          '<span class="toast-message">' + message + '</span>' +
          '<span class="toast-close" onclick="this.parentElement.remove()">✕</span>';
        
        container.appendChild(toast);
        
        // 触发重排以启动动画
        void toast.offsetWidth;
        toast.classList.add('show');
        
        // 自动关闭
        setTimeout(() => {
          toast.classList.remove('show');
          setTimeout(() => toast.remove(), 300);
        }, duration);
      }

      // 替换原有的 alert 调用
      function copyNoteLink() {
        const link = window.location.href;
        navigator.clipboard.writeText(link).then(() => {
          showToast('笔记链接已复制到剪贴板!', 'success');
        }).catch(() => {
          showToast('复制失败,请手动复制链接。', 'error');
        });
      }

      function toggleDarkMode() {
        const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
        document.documentElement.setAttribute('data-theme', theme);
        localStorage.setItem('theme', theme);
        
        // 更新Mermaid主题
        mermaid.initialize({
          startOnLoad: false,
          theme: theme === 'dark' ? 'dark' : 'default',
          securityLevel: 'loose',
          fontFamily: 'var(--font-family)',
        });
        
        // 重新渲染预览内容以更新Mermaid图表
        if (previewToggle.checked) {
          updatePreview(content.value);
        }
      }

      // Markdown 编辑功能
      function applyMarkdown(type) {
        const textarea = document.getElementById('content');
        const start = textarea.selectionStart;
        const end = textarea.selectionEnd;
        const text = textarea.value;
        let result;

        switch(type) {
          case 'bold':
            result = insertAround(text, start, end, '**');
            break;
          case 'italic':
            result = insertAround(text, start, end, '_');
            break;
          case 'heading':
            result = insertAtLineStart(text, start, '# ');
            break;
          case 'strikethrough':
            result = insertAround(text, start, end, '~~');
            break;
          case 'list':
            result = insertAtLineStart(text, start, '- ');
            break;
          case 'ordered-list':
            result = insertAtLineStart(text, start, '1. ');
            break;
          case 'task':
            result = insertAtLineStart(text, start, '- [ ] ');
            break;
          case 'quote':
            result = insertAtLineStart(text, start, '> ');
            break;
          case 'code':
            result = insertAround(text, start, end, '\`\`\`\\n', '\\n\`\`\`');
            break;
          case 'table':
            result = insertTable(text, start);
            break;
          case 'divider':
            result = insertDivider(text, start);
            break;
          case 'link':
            result = insertLink(text, start, end);
            break;
          case 'image':
            result = insertImage(text, start, end);
            break;
          case 'latex-inline':
            result = insertAround(text, start, end, '$');
            break;
          case 'latex-block':
            result = insertAround(text, start, end, '$$\\n', '\\n$$');
            break;
          case 'mermaid':
            result = insertMermaid(text, start);
            break;
        }

        if (result) {
          textarea.value = result.text;
          textarea.selectionStart = result.selectionStart;
          textarea.selectionEnd = result.selectionEnd;
          textarea.focus();
          updatePreview(textarea.value);
          debounceSaveContent(textarea.value);
        }
      }

      // 辅助函数:在选中文本周围插入标记
      function insertAround(text, start, end, mark, endMark = mark) {
        const selection = text.substring(start, end);
        const before = text.substring(0, start);
        const after = text.substring(end);
        const newText = before + mark + selection + endMark + after;
        return {
          text: newText,
          selectionStart: start + mark.length,
          selectionEnd: end + mark.length
        };
      }

      // 辅助函数:在行首插入标记
      function insertAtLineStart(text, start, mark) {
        const lines = text.split('\\n');
        let currentPos = 0;
        let targetLine = 0;
        
        // 找到光标所在行
        for (let i = 0; i < lines.length; i++) {
          if (currentPos + lines[i].length >= start) {
            targetLine = i;
            break;
          }
          currentPos += lines[i].length + 1;
        }

        // 在目标行前添加标记
        lines[targetLine] = mark + lines[targetLine];
        
        return {
          text: lines.join('\\n'),
          selectionStart: currentPos + mark.length,
          selectionEnd: currentPos + mark.length
        };
      }

      // 插入表格
      function insertTable(text, start) {
        const tableTemplate = '\\n| 标题1 | 标题2 | 标题3 |\\n|--------|--------|--------|\\n| 内容1 | 内容2 | 内容3 |\\n';
        const newText = text.substring(0, start) + tableTemplate + text.substring(start);
        return {
          text: newText,
          selectionStart: start + tableTemplate.length,
          selectionEnd: start + tableTemplate.length
        };
      }

      // 添加插入Mermaid图表的函数
      function insertMermaid(text, start) {
        const mermaidTemplate = '\\n\`\`\`mermaid\\ngraph TD\\n    A[开始] --> B[步骤1]\\n    B --> C[步骤2]\\n    C --> D[结束]\\n\`\`\`\\n';
        const newText = text.substring(0, start) + mermaidTemplate + text.substring(start);
        return {
          text: newText,
          selectionStart: start + mermaidTemplate.length,
          selectionEnd: start + mermaidTemplate.length
        };
      }

      // 插入链接
      function insertLink(text, start, end) {
        const selection = text.substring(start, end).trim();
        const link = selection || '链接文字';
        const template = '[' + link + '](https://)';
        const newText = text.substring(0, start) + template + text.substring(end);
        return {
          text: newText,
          selectionStart: start + link.length + 3,
          selectionEnd: start + template.length - 1
        };
      }

      // 插入图片
      function insertImage(text, start, end) {
        const template = '![图片描述](https://)';
        return {
          text: text.substring(0, start) + template + text.substring(end),
          selectionStart: start + 2,
          selectionEnd: start + 6
        };
      }

      // 插入分割线
      function insertDivider(text, start) {
        const divider = '\\n---\\n';
        const newText = text.substring(0, start) + divider + text.substring(start);
        return {
          text: newText,
          selectionStart: start + divider.length,
          selectionEnd: start + divider.length
        };
      }

      // 字体设置
      function applyFont() {
        const select = document.getElementById('font-family');
        const textarea = document.getElementById('content');
        const fontMap = {
          'default': '',
          'serif': 'SimSun, serif',
          'yahei': '"Microsoft YaHei", "微软雅黑", sans-serif',
          'kaiti': 'KaiTi, "楷体", serif',
          'heiti': 'SimHei, "黑体", sans-serif',
          'fangsong': 'FangSong, "仿宋", serif',
          'songti': 'NSimSun, "新宋体", serif',
          'monospace': 'Monaco, Consolas, monospace',
          'arial': 'Arial, sans-serif',
          'times': '"Times New Roman", Times, serif',
          'helvetica': 'Helvetica, Arial, sans-serif'
        };
        textarea.style.fontFamily = fontMap[select.value] || '';
      }

      // 字号设置
      function applyFontSize() {
        const select = document.getElementById('font-size');
        const textarea = document.getElementById('content');
        textarea.style.fontSize = select.value + 'px';
      }

      // Emoji 选择器
      function showEmojiPicker() {
        const picker = document.getElementById('emoji-picker');
        const overlay = document.querySelector('.emoji-picker-overlay') || createOverlay();
        
        if (picker.style.display === 'block') {
          picker.style.display = 'none';
          overlay.style.display = 'none';
          return;
        }

        // 如果是第一次显示,初始化表情列表
        if (!picker.children.length) {
          const gridContainer = document.createElement('div');
          gridContainer.className = 'emoji-picker-grid';
          
          const emojis = ['😀', '😃', '😄', '😁', '😅', '😂', '🤣', '😊', 
                         '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘',
                         '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪',
                         '🤨', '🧐', '🤓', '😎', '🤩', '🥳', '😏', '😒',
                         '❤️', '🙌', '👍', '🎉', '✨', '🔥', '💡', '⭐',
                         '💪', '🎯', '✅', '❌', '💬', '👀', '🎨', '🎮',
                         '🎵', '🎬', '📚', '💻', '🔍', '⚡', '🌈', '🍀'];
          
          emojis.forEach(emoji => {
            const button = document.createElement('button');
            button.textContent = emoji;
            button.onclick = () => {
              insertEmoji(emoji);
              picker.style.display = 'none';
              overlay.style.display = 'none';
            };
            gridContainer.appendChild(button);
          });
          
          picker.appendChild(gridContainer);
        }

        overlay.style.display = 'block';
        picker.style.display = 'block';
      }

      // 创建遮罩层
      function createOverlay() {
        const overlay = document.createElement('div');
        overlay.className = 'emoji-picker-overlay';
        document.body.appendChild(overlay);
        
        overlay.addEventListener('click', () => {
          const picker = document.getElementById('emoji-picker');
          picker.style.display = 'none';
          overlay.style.display = 'none';
        });
        
        return overlay;
      }

      // 插入表情
      function insertEmoji(emoji) {
        const textarea = document.getElementById('content');
        const start = textarea.selectionStart;
        const end = textarea.selectionEnd;
        const text = textarea.value;
        
        textarea.value = text.substring(0, start) + emoji + text.substring(end);
        textarea.selectionStart = textarea.selectionEnd = start + emoji.length;
        textarea.focus();
        
        updatePreview(textarea.value);
        debounceSaveContent(textarea.value);
      }

      // 添加快捷键支持
      document.addEventListener('keydown', function(e) {
        if (!e.ctrlKey) return;
        
        const shortcuts = {
          'b': 'bold',
          'i': 'italic',
          'h': 'heading',
          'd': 'strikethrough',
          'u': 'list',
          'o': 'ordered-list',
          't': 'task',
          'q': 'quote',
          'k': 'code',
          'l': 'link',
          'p': 'image'
        };

        if (shortcuts[e.key.toLowerCase()]) {
          e.preventDefault();
          applyMarkdown(shortcuts[e.key.toLowerCase()]);
        }
      });

      // 更新行号和调整布局
      function updateLineNumbers() {
        const textarea = document.getElementById('content');
        const lineNumbers = document.querySelector('.line-numbers');
        const fullText = textarea.value;
        const totalLines = fullText.endsWith('\\n') ? 
          fullText.split('\\n').length :
          fullText.split('\\n').length;
        
        // 清空现有行号
        lineNumbers.innerHTML = '';
        
        // 为每一行创建行号 span 元素
        for (let i = 0; i < totalLines; i++) {
          const span = document.createElement('span');
          span.textContent = i + 1;
          lineNumbers.appendChild(span);
        }

        // 确保至少有一行
        if (totalLines === 0) {
          const span = document.createElement('span');
          span.textContent = '1';
          lineNumbers.appendChild(span);
        }

        // 调整行号区域宽度
        const maxLineNumber = totalLines || 1;
        const minWidth = 28; // 最小宽度
        const digitWidth = 8; // 每个数字的估计宽度
        const newWidth = Math.max(minWidth, String(maxLineNumber).length * digitWidth + 8);
        lineNumbers.style.minWidth = newWidth + 'px';
      }

      // 监听窗口大小变化
      window.addEventListener('resize', () => {
        updateLineNumbers();
      });

      // 更新文本统计
      function updateTextStats() {
        const textarea = document.getElementById('content');
        const text = textarea.value;
        const position = textarea.selectionStart;

        // 计算字符数(不包括空格和换行)
        const charCount = text.replace(/\s/g, '').length;
        
        // 计算单词数
        const wordCount = text.trim().split(/\s+/).filter(word => word.length > 0).length;
        
        // 计算当前行和列
        const textBeforeCursor = text.substring(0, position);
        const lines = textBeforeCursor.split('\\n');
        const currentLine = lines.length;
        const currentLineContent = lines[lines.length - 1];
        const currentColumn = currentLineContent ? currentLineContent.length + 1 : 1;
        
        // 更新显示(使用更紧凑的格式)
        document.getElementById('char-count').textContent = String(charCount);
        document.getElementById('word-count').textContent = String(wordCount);
        document.getElementById('line-count').textContent = String(currentLine);
        document.getElementById('column-count').textContent = String(currentColumn);
      }

      // 同步滚动行号
      function syncLineNumbersScroll() {
        const textarea = document.getElementById('content');
        const lineNumbers = document.querySelector('.line-numbers');
        
        // 计算最大可滚动高度
        const maxScroll = textarea.scrollHeight - textarea.clientHeight;
        const currentScroll = textarea.scrollTop;
        
        // 确保不会滚动过头
        if (currentScroll <= maxScroll) {
          lineNumbers.scrollTop = currentScroll;
        }
      }

      // 监听输入事件
      content.addEventListener('input', () => {
        updatePreview(content.value);
        updateLineNumbers();
        updateTextStats();
        debounceSaveContent(content.value);
        // 输入时也同步滚动
        syncLineNumbersScroll();
      });

      // 监听光标位置变化
      content.addEventListener('keyup', updateTextStats);
      content.addEventListener('click', updateTextStats);
      content.addEventListener('scroll', syncLineNumbersScroll);

      // 切换行号显示/隐藏
      function toggleLineNumbers() {
        const lineNumbers = document.querySelector('.line-numbers');
        const isVisible = document.getElementById('line-numbers-toggle').checked;
        lineNumbers.classList.toggle('hidden', !isVisible);
        localStorage.setItem('show-line-numbers', isVisible);
      }

      // 切换工具栏显示/隐藏
      function toggleToolbar() {
        const isVisible = document.getElementById('toolbar-toggle').checked;
        const toolbar = document.querySelector('.toolbar');
        const container = document.querySelector('.container');
        
        toolbar.classList.toggle('hidden', !isVisible);
        container.classList.toggle('toolbar-hidden', !isVisible);
        
        localStorage.setItem('show-toolbar', isVisible);
        
        // 触发一次窗口大小变化事件,以更新编辑器布局
        window.dispatchEvent(new Event('resize'));
      }

      // 添加预览区统计功能
      function updatePreviewStats(text) {
        // 计算字符数(不包括空格和换行)
        const charCount = text.replace(/\\s/g, '').length;
        
        // 计算单词数
        const wordCount = text.trim().split(/\\s+/).filter(word => word.length > 0).length;
        
        // 计算段落数(通过空行分隔)
        const paragraphCount = text.split(/\\n\\s*\\n/).filter(para => para.trim().length > 0).length;
        
        // 更新显示
        document.getElementById('preview-char-count').textContent = String(charCount);
        document.getElementById('preview-word-count').textContent = String(wordCount);
        document.getElementById('preview-paragraph-count').textContent = String(paragraphCount);
      }

      // 添加全屏切换功能
      function togglePreviewFullscreen() {
        const preview = document.getElementById('preview');
        const fullscreenIcon = document.getElementById('fullscreen-icon');
        const isFullscreen = preview.classList.contains('fullscreen');
        
        if (isFullscreen) {
          // 如果已经是容器全屏,切换到真实全屏
          if (document.fullscreenElement) {
            document.exitFullscreen();
          } else {
            preview.requestFullscreen();
          }
        } else {
          // 首次点击进入容器全屏
          preview.classList.add('fullscreen');
          fullscreenIcon.textContent = '⛶';
        }
        
        // 触发resize事件以更新布局
        window.dispatchEvent(new Event('resize'));
        
        // 保存全屏状态到本地存储
        localStorage.setItem('preview-fullscreen', preview.classList.contains('fullscreen'));
      }

      // ESC键退出全屏
      function handleFullscreenEsc(e) {
        if (e.key === 'Escape') {
          const preview = document.getElementById('preview');
          if (preview.classList.contains('fullscreen')) {
            preview.classList.remove('fullscreen');
            const fullscreenIcon = document.getElementById('fullscreen-icon');
            fullscreenIcon.textContent = '⛶';
            localStorage.setItem('preview-fullscreen', false);
          }
        }
      }

      // 监听浏览器全屏变化事件
      document.addEventListener('fullscreenchange', () => {
        const preview = document.getElementById('preview');
        const fullscreenIcon = document.getElementById('fullscreen-icon');
        
        if (!document.fullscreenElement && preview.classList.contains('fullscreen')) {
          // 从真实全屏退出时,也退出容器全屏
          preview.classList.remove('fullscreen');
          fullscreenIcon.textContent = '⛶';
          localStorage.setItem('preview-fullscreen', false);
        }
      });

      // 双击进入/退出全屏(移动端支持)
      function handlePreviewDoubleTap(e) {
        const preview = document.getElementById('preview');
        // 确保不是在状态栏上双击
        if (!e.target.closest('.preview-status-bar')) {
          e.preventDefault(); // 阻止默认行为
          // 清除任何可能的文本选择
          window.getSelection().removeAllRanges();
          togglePreviewFullscreen();
        }
      }

      // 添加密码相关功能
      let isPasswordProtected = false;
      let isPasswordVerified = false;
      let currentPasswordAction = '';

      async function checkPasswordProtection() {
        try {
          const response = await fetch(window.location.pathname + '/password-check', {
            method: 'GET',
          });
          
          if (response.status === 200) {
            isPasswordProtected = true;
            document.body.classList.add('password-protected'); // 添加密码保护状态类
            showPasswordVerification();
          }
          updatePasswordStatus();
        } catch (error) {
          console.error('检查密码保护状态失败:', error);
        }
      }

      function showPasswordMessage(message, isSuccess = false) {
        const messageEl = document.getElementById('password-message');
        messageEl.textContent = message;
        messageEl.classList.toggle('success', isSuccess);
        messageEl.classList.add('show');
        
        // 1秒后自动隐藏成功消息
        if (isSuccess) {
          setTimeout(() => {
            messageEl.classList.remove('show');
          }, 1000);
        }
      }

      function clearPasswordMessage() {
        const messageEl = document.getElementById('password-message');
        messageEl.classList.remove('show', 'success');
        messageEl.textContent = '';
      }

      function showPasswordDialog() {
        const dialog = document.getElementById('password-dialog');
        const overlay = document.getElementById('password-overlay');
        const title = document.getElementById('password-dialog-title');
        const actionBtn = document.getElementById('password-action-btn');
        const passwordInput = document.getElementById('password-input');
        
        clearPasswordMessage();
        
        if (isPasswordProtected && isPasswordVerified) {
          title.textContent = '移除密码保护';
          actionBtn.textContent = '移除';
          currentPasswordAction = 'remove';
        } else if (!isPasswordProtected) {
          title.textContent = '设置密码保护';
          actionBtn.textContent = '设置';
          currentPasswordAction = 'set';
        }
        
        dialog.style.display = 'block';
        overlay.style.display = 'block';
        passwordInput.focus();

        // 添加回车键事件监听
        passwordInput.onkeydown = (e) => {
          if (e.key === 'Enter') {
            e.preventDefault();
            handlePasswordAction();
          }
        };
      }

      function showPasswordVerification() {
        const dialog = document.getElementById('password-dialog');
        const overlay = document.getElementById('password-overlay');
        const title = document.getElementById('password-dialog-title');
        const actionBtn = document.getElementById('password-action-btn');
        const passwordInput = document.getElementById('password-input');
        
        clearPasswordMessage();
        
        title.textContent = '请输入密码';
        actionBtn.textContent = '验证';
        currentPasswordAction = 'verify';
        
        dialog.style.display = 'block';
        overlay.style.display = 'block';
        passwordInput.focus();

        // 添加回车键事件监听
        passwordInput.onkeydown = (e) => {
          if (e.key === 'Enter') {
            e.preventDefault();
            handlePasswordAction();
          }
        };
      }

      function closePasswordDialog() {
        const dialog = document.getElementById('password-dialog');
        const overlay = document.getElementById('password-overlay');
        const passwordInput = document.getElementById('password-input');
        
        dialog.style.display = 'none';
        overlay.style.display = 'none';
        passwordInput.value = '';
        passwordInput.onkeydown = null; // 移除回车键事件监听
        clearPasswordMessage();
      }

      async function handlePasswordAction() {
        const password = document.getElementById('password-input').value;
        if (!password) {
          showPasswordMessage('请输入密码');
          showToast('请输入密码', 'warning');
          return;
        }

        try {
          let response;
          switch (currentPasswordAction) {
            case 'set':
              response = await fetch(window.location.pathname + '/password', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ password: await hashPassword(password) })
              });
              if (response.status === 200) {
                isPasswordProtected = true;
                isPasswordVerified = true;
                document.body.classList.add('password-protected');
                updatePasswordStatus();
                showPasswordMessage('密码保护已设置', true);
                showToast('密码保护已设置', 'success');
                setTimeout(closePasswordDialog, 1500);
              }
              break;

            case 'verify':
              response = await fetch(window.location.pathname + '/password-verify', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ password: await hashPassword(password) })
              });
              if (response.status === 200) {
                isPasswordVerified = true;
                document.body.classList.remove('password-protected');
                updatePasswordStatus();
                showPasswordMessage('密码验证成功', true);
                showToast('密码验证成功', 'success');
                setTimeout(closePasswordDialog, 1500);
              } else {
                showPasswordMessage('密码错误');
                showToast('密码错误', 'error');
                return;
              }
              break;

            case 'remove':
              response = await fetch(window.location.pathname + '/password', {
                method: 'DELETE',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ password: await hashPassword(password) })
              });
              if (response.status === 200) {
                isPasswordProtected = false;
                isPasswordVerified = false;
                document.body.classList.remove('password-protected');
                updatePasswordStatus();
                showPasswordMessage('密码保护已移除', true);
                showToast('密码保护已移除', 'success');
                setTimeout(closePasswordDialog, 1500);
              } else {
                showPasswordMessage('密码错误');
                showToast('密码错误', 'error');
                return;
              }
              break;
          }
        } catch (error) {
          console.error('密码操作失败:', error);
          showPasswordMessage('操作失败,请重试');
          showToast('操作失败,请重试', 'error');
        }
      }

      async function hashPassword(password) {
        const encoder = new TextEncoder();
        const data = encoder.encode(password);
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
      }

      function updatePasswordStatus() {
        const icon = document.getElementById('password-status-icon');
        const label = icon.nextElementSibling;
        if (isPasswordProtected) {
          icon.textContent = '🔒';
          icon.title = '已启用密码保护';
          label.textContent = '已加密';
        } else {
          icon.textContent = '🔓';
          icon.title = '未启用密码保护';
          label.textContent = '密码保护';
        }
      }

      // 在页面加载时检查密码保护状态
      document.addEventListener('DOMContentLoaded', async () => {
        await checkPasswordProtection();
        // ... existing DOMContentLoaded code ...
      });

      // 修改复制全部内容功能,添加动效
      async function copyAllContent() {
        const copyAllItem = document.querySelector('.copy-all-item');
        const textarea = document.getElementById('content');
        const text = textarea.value;
        
        try {
          await navigator.clipboard.writeText(text);
          copyAllItem.classList.add('copied');
          setTimeout(() => copyAllItem.classList.remove('copied'), 500);
          showToast('已复制全部内容到剪贴板', 'success');
        } catch (err) {
          console.error('复制失败:', err);
          showToast('复制失败,请重试', 'error');
        }
      }

      async function shareNote() {
        const preview = document.querySelector('.preview-content');
        if (!preview) {
          showToast('请先开启预览模式', 'warning');
          return;
        }
      
        try {
          // 生成分享ID
          const shareId = Array.from({length: 8}, () => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'[Math.floor(Math.random() * 62)]).join('');
      
          // 准备分享数据
          const shareData = {
            content: preview.innerHTML,
            createTime: new Date().toISOString(),
            lastEditTime: new Date().toISOString(),
            visitCount: 0
          };
      
          // 保存分享数据
          const response = await fetch('/share/' + shareId, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(shareData)
          });
      
          if (response.ok) {
            // 生成分享链接
            const shareUrl = window.location.origin + '/share/' + shareId;
      
            // 复制链接到剪贴板
            await navigator.clipboard.writeText(shareUrl);
            showToast('分享链接已复制到剪贴板', 'success');
          } else {
            showToast('分享失败,请重试', 'error');
          }
        } catch (error) {
          console.error('分享失败:', error);
          showToast('分享失败,请重试', 'error');
        }
      }

      // 图片点击放大
      document.addEventListener('DOMContentLoaded', () => {
        const content = document.querySelector('.content');
        const overlay = document.getElementById('imageOverlay');

        content.addEventListener('click', (e) => {
          if (e.target.tagName === 'IMG' && !e.target.classList.contains('enlarged')) {
            e.target.classList.add('enlarged');
            overlay.classList.add('active');
            document.body.style.overflow = 'hidden';
          }
        });
      });

      // 关闭放大的图片
      function closeEnlargedImage() {
        const enlargedImage = document.querySelector('.enlarged');
        const overlay = document.getElementById('imageOverlay');
        
        if (enlargedImage) {
          enlargedImage.classList.remove('enlarged');
          overlay.classList.remove('active');
          document.body.style.overflow = '';
        }
      }

      // ESC键关闭放大图片
      document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
          closeEnlargedImage();
        }
      });

      // 图片加载错误处理
      document.addEventListener('DOMContentLoaded', () => {
        const images = document.querySelectorAll('.content img');
        images.forEach(img => {
          img.onerror = () => {
            img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
          };
        });
      });
    </script>
  </body>
  </html>`;
}

/**
 * 从KV存储获取笔记内容
 * @param {string} notePath - 笔记路径
 * @returns {Promise<string>} 笔记内容
 */
async function getNoteContent(notePath) {
  const encryptedText = await NOTES_KV.get(notePath);
  if (!encryptedText) return '';
  
  const key = await generateEncryptionKey(notePath);
  return await decryptText(encryptedText, key);
}

/**
 * 保存笔记内容到KV存储
 * @param {string} notePath - 笔记路径
 * @param {string} text - 笔记内容
 */
async function saveNoteContent(notePath, text) {
  const key = await generateEncryptionKey(notePath);
  const encryptedText = await encryptText(text, key);
  await NOTES_KV.put(notePath, encryptedText);
}

/**
 * 从KV存储删除笔记
 * @param {string} notePath - 要删除的笔记路径
 */
async function deleteNoteContent(notePath) {
  await NOTES_KV.delete(notePath);
}

/**
 * 处理分享页面请求
 * @param {string} shareId - 分享ID
 * @returns {Response} 响应对象
 */
async function handleShareRequest(shareId) {
  // 获取分享数据
  const shareKey = 'share_' + shareId;
  const shareData = await NOTES_KV.get(shareKey);
  
  if (!shareData) {
    return new Response('分享内容不存在或已过期', { status: 404 });
  }

  try {
    const data = JSON.parse(shareData);
    // 更新访问次数
    data.visitCount++;
    await NOTES_KV.put(shareKey, JSON.stringify(data));
    
    // 生成分享页面
    const html = generateShareHTML(shareId, data);
    return new Response(html, { 
      headers: { 'Content-Type': 'text/html;charset=utf-8' }
    });
  } catch (error) {
    return new Response('加载分享内容失败', { status: 500 });
  }
}

/**
 * 生成分享页面HTML
 * @param {string} shareId - 分享ID
 * @param {Object} data - 分享数据
 * @returns {string} HTML内容
 */
function generateShareHTML(shareId, data) {
  const shareTime = new Date(data.createTime).toLocaleString('zh-CN');
  
  return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>分享的笔记</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
  <!-- 添加KaTeX依赖 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
  <!-- 添加Mermaid依赖 -->
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
  <style>
    :root {
      --primary-color: #4e92d1;
      --secondary-color: #6c757d;
      --bg-color: #ffffff;
      --text-color: #333333;
      --border-color: #e0e0e0;
      --container-bg: #ffffff;
      --editor-bg: #f8f8f8;
      --shadow-color: rgba(0, 0, 0, 0.1);
      --hover-color: #f0f0f0;
      --link-color: #0366d6;
      --link-hover-color: #0969da;
      --link-visited-color: #6f42c1;
    }

    [data-theme="dark"] {
      --primary-color: #a2c2f5;
      --secondary-color: #9ca3af;
      --bg-color: #1a1a1a;
      --text-color: #f1f1f1;
      --border-color: #404040;
      --container-bg: #2a2a2a;
      --editor-bg: #333333;
      --shadow-color: rgba(0, 0, 0, 0.3);
      --hover-color: #3a3a3a;
      --link-color: #58a6ff;
      --link-hover-color: #79b8ff;
      --link-visited-color: #bc8cff;
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      margin: 0;
      background: var(--bg-color);
      color: var(--text-color);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      font-size: 16px;
      line-height: 1.6;
      transition: all 0.3s ease;
    }

    .container {
      width: 100%;
      max-width: 100%;
      margin: 0 auto;
      padding: 20px;
      min-height: 100vh;
      background-color: var(--container-bg);
    }

    @media (min-width: 1200px) {
      .container {
        max-width: 95%;
        box-shadow: 0 0 20px var(--shadow-color);
      }
    }

    .info-bar {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 15px;
      background: var(--editor-bg);
      border-radius: 12px;
      margin-bottom: 20px;
      font-size: 14px;
      border: 1px solid var(--border-color);
      overflow-x: auto;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;
      gap: 15px;
      min-width: 0;
    }

    .info-bar::-webkit-scrollbar {
      height: 6px;
      width: 6px;
    }

    .info-bar::-webkit-scrollbar-track {
      background: transparent;
    }
    .info-bar::-webkit-scrollbar-thumb {
      background-color: var(--secondary-color);
      border-radius: 3px;
    }

    .info-bar::-webkit-scrollbar-thumb:hover {
      background-color: var(--text-color);
    }

    .info-left {
      display: flex;
      gap: 15px;
      flex-shrink: 0;
      margin-right: auto;
    }

    .info-right {
      display: flex;
      align-items: center;
      gap: 15px;
      flex-shrink: 0;
      margin-left: auto;
    }

    .info-item {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 4px 10px;
      border-radius: 6px;
      transition: all 0.2s ease;
      white-space: nowrap;
      flex-shrink: 0;
    }

    .info-item:hover {
      background: var(--hover-color);
    }

    .theme-toggle {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 4px 10px;
      border-radius: 6px;
      border: none;
      color: var(--text-color);
      cursor: pointer;
      font-size: 14px;
      transition: all 0.2s ease;
      background: transparent;
      white-space: nowrap;
      flex-shrink: 0;
    }

    .theme-toggle:hover {
      background: var(--hover-color);
    }

    .theme-toggle:active {
      transform: scale(0.95);
    }

    .theme-toggle .sun-icon,
    .theme-toggle .moon-icon {
      display: none;
      font-size: 1.2rem;
    }

    .theme-toggle .label {
      font-size: 14px;
    }

    [data-theme="dark"] .moon-icon {
      display: block;
    }

    [data-theme="light"] .sun-icon {
      display: block;
    }

    @media (max-width: 768px) {
      .theme-toggle {
        padding: 4px 8px;
      }

      .theme-toggle .label {
        font-size: 13px;
      }
    }

    @media (max-width: 768px) {
      .info-bar {
        padding: 10px;
        gap: 10px;
      }

      .info-left,
      .info-right {
        gap: 10px;
      }

      .info-item {
        padding: 4px 8px;
        font-size: 13px;
      }
    }

    .content {
      padding: 20px;
      border: 1px solid var(--border-color);
      border-radius: 12px;
      background: var(--editor-bg);
      overflow-x: auto;
    }

    /* Markdown 内容样式 */
    .content > *:first-child {
      margin-top: 0;
    }

    .content > *:last-child {
      margin-bottom: 0;
    }

    .content h1,
    .content h2,
    .content h3,
    .content h4,
    .content h5,
    .content h6 {
      margin-top: 1.8em;
      margin-bottom: 0.8em;
      line-height: 1.2;
      color: var(--text-color);
    }

    .content h1:first-child,
    .content h2:first-child,
    .content h3:first-child,
    .content h4:first-child,
    .content h5:first-child,
    .content h6:first-child {
      margin-top: 0;
    }

    .content p {
      text-align: left !important;
      margin: 1.2em 0;
      line-height: 1.8;
    }

    .content p:has(> a:only-child) {
      text-align: left !important;
    }

    .content a {
      display: inline;
      color: var(--link-color);
      text-decoration: none;
      border-bottom: 1px solid transparent;
      transition: all 0.2s ease;
      padding: 2px 4px;
      margin: 0 -4px;
      border-radius: 4px;
      text-align: left !important;
    }

    /* 处理单独一行的链接 */
    .content p > a:only-child {
      display: inline-block;
      text-align: left !important;
      width: fit-content;
      margin-left: 0;
    }

    /* 处理图片链接的特殊情况 */
    .content p:has(img) {
      text-align: center;
      margin: 2em 0;
    }

    .content a:hover {
      background: var(--hover-color);
      color: var(--link-hover-color);
      border-bottom-color: var(--link-hover-color);
    }

    .content a:visited {
      color: var(--link-visited-color);
    }

    .content a:visited:hover {
      background: var(--hover-color);
      border-bottom-color: var(--link-visited-color);
    }

    .content a[href^="http"]::after {
      content: "↗";
      display: inline;
      margin-left: 2px;
      font-size: 0.9em;
      opacity: 0.6;
    }

    .content ul,
    .content ol {
      margin: 1em 0;
      padding-left: 1.5em;
    }

    .content li {
      margin: 0.5em 0;
    }

    .content blockquote {
      margin: 1.2em 0;
      padding: 1em 1.2em;
      border-left: 4px solid var(--border-color);
      background: var(--bg-color);
      border-radius: 0 4px 4px 0;
      display: flow-root;
      width: fit-content;
      max-width: 100%;
      box-shadow: 0 2px 4px var(--shadow-color);
      transition: all 0.2s ease;
    }

    .content blockquote > *:first-child {
      margin-top: 0;
    }

    .content blockquote > *:last-child {
      margin-bottom: 0;
    }

    .content blockquote p {
      margin: 0.8em 0;
      line-height: 1.6;
    }

    .content blockquote + blockquote {
      margin-top: -0.5em;
    }

    .content blockquote blockquote {
      margin: 0.8em 0;
      border-left-color: var(--secondary-color);
      background: var(--editor-bg);
      box-shadow: none;
    }

    .content pre {
      background: var(--editor-bg);
      padding: 1.2em 1em;
      border-radius: 8px;
      overflow: auto;
      position: relative;
      margin: 1.5em 0;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
      line-height: 1.5;
      font-size: 0.95em;
      border: 1px solid var(--border-color);
      scrollbar-width: thin;
      scrollbar-color: var(--secondary-color) transparent;
    }

    .content code {
      background: var(--editor-bg);
      padding: 0.2em 0.4em;
      margin: 0 0.2em;
      border-radius: 4px;
      font-size: 0.9em;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
      border: 1px solid var(--border-color);
    }

    .content pre code {
      background: none;
      padding: 0;
      margin: 0;
      font-size: 0.95em;
      white-space: pre;
      word-break: normal;
      word-wrap: normal;
      line-height: inherit;
      tab-size: 2;
      hyphens: none;
      border: none;
    }

    .content pre::-webkit-scrollbar {
      width: 6px;
      height: 6px;
    }

    .content pre::-webkit-scrollbar-track {
      background: transparent;
    }

    .content pre::-webkit-scrollbar-thumb {
      background-color: var(--secondary-color);
      border-radius: 3px;
      border: 2px solid var(--editor-bg);
    }

    .content pre::-webkit-scrollbar-thumb:hover {
      background-color: var(--text-color);
    }

    .content pre::before {
      content: attr(data-language);
      position: absolute;
      top: 0.5em;
      right: 0.5em;
      font-size: 0.85em;
      color: var(--secondary-color);
      padding: 0.2em 0.5em;
      border-radius: 3px;
      background: var(--container-bg);
      opacity: 0.8;
      transition: opacity 0.2s ease;
    }

    .content pre:hover::before {
      opacity: 1;
    }

    .copy-button {
      position: absolute;
      top: 0.5em;
      right: 0.5em;
      padding: 0.2em 0.5em;
      font-size: 0.85em;
      color: var(--text-color);
      background: var(--container-bg);
      border: 1px solid var(--border-color);
      border-radius: 3px;
      cursor: pointer;
      opacity: 0;
      transition: all 0.2s ease;
      display: flex;
      align-items: center;
      gap: 4px;
    }

    .content pre:hover .copy-button {
      opacity: 0.8;
    }

    .copy-button:hover {
      opacity: 1 !important;
      background: var(--hover-color);
    }

    .copy-button.copied {
      color: #4caf50;
      border-color: #4caf50;
      opacity: 1;
    }

    /* Toast 提示框样式 */
    .toast-container {
      position: fixed;
      top: 20px;
      right: 20px;
      z-index: 10000;
      display: flex;
      flex-direction: column;
      gap: 10px;
      pointer-events: none;
    }

    .toast {
      background: var(--container-bg);
      color: var(--text-color);
      padding: 12px 24px;
      border-radius: 8px;
      box-shadow: 0 4px 12px var(--shadow-color);
      font-size: 14px;
      display: flex;
      align-items: center;
      gap: 8px;
      opacity: 0;
      transform: translateX(100%);
      transition: all 0.3s ease;
      border: 1px solid var(--border-color);
      pointer-events: all;
      max-width: 300px;
      word-break: break-word;
    }

    .toast.show {
      opacity: 1;
      transform: translateX(0);
    }

    .toast.success {
      border-left: 4px solid #4caf50;
    }

    .toast.error {
      border-left: 4px solid #f44336;
    }

    .toast-icon {
      font-size: 18px;
      flex-shrink: 0;
    }

    .toast-message {
      flex: 1;
      margin-right: 8px;
    }

    .toast-close {
      cursor: pointer;
      opacity: 0.7;
      transition: opacity 0.2s ease;
      padding: 4px;
      margin: -4px;
      border-radius: 4px;
    }

    .toast-close:hover {
      opacity: 1;
      background: var(--hover-color);
    }

    /* 表格样式 */
    .content table {
      width: 100%;
      border-collapse: collapse;
      margin: 1.5em 0;
      overflow-x: auto;
      display: block;
    }

    .content th,
    .content td {
      border: 1px solid var(--border-color);
      padding: 8px 12px;
      text-align: left;
    }

    .content th {
      background-color: var(--hover-color);
      font-weight: 600;
    }

    .content tr:nth-child(even) {
      background-color: var(--editor-bg);
    }

    .content tr:hover {
      background-color: var(--hover-color);
    }

    /* 移动端表格适配 */
    @media (max-width: 768px) {
      .content table {
        font-size: 14px;
      }

      .content th,
      .content td {
        padding: 6px 8px;
      }
    }

    /* 表格滚动条样式 */
    .content table::-webkit-scrollbar {
      height: 8px;
      width: 8px;
    }

    .content table::-webkit-scrollbar-track {
      background: transparent;
    }

    .content table::-webkit-scrollbar-thumb {
      background-color: var(--secondary-color);
      border-radius: 4px;
      border: 2px solid var(--editor-bg);
    }

    .content table::-webkit-scrollbar-thumb:hover {
      background-color: var(--text-color);
    }

    /* 图片样式 */
    .content img {
      max-width: 100%;
      height: auto;
      margin: 1em 0;
      border-radius: 8px;
      display: block;
      box-shadow: 0 2px 8px var(--shadow-color);
      transition: all 0.3s ease;
      opacity: 0;
      animation: fadeIn 0.5s ease forwards;
    }

    /* 图片容器 */
    .content p:has(img) {
      text-align: center;
      margin: 2em 0;
    }

    /* 图片悬停效果 */
    .content img:hover {
      transform: scale(1.01);
      box-shadow: 0 4px 12px var(--shadow-color);
    }

    /* 图片标题样式 */
    .content img + em {
      display: block;
      text-align: center;
      color: var(--secondary-color);
      font-size: 0.9em;
      margin-top: 0.5em;
    }

    /* 移动端图片适配 */
    @media (max-width: 768px) {
      .content img {
        border-radius: 6px;
        margin: 0.8em 0;
      }

      .content p:has(img) {
        margin: 1.5em 0;
      }

      /* 禁用移动端图片缩放动画 */
      .content img:hover {
        transform: none;
        box-shadow: 0 2px 8px var(--shadow-color);
      }
    }

    /* 图片加载动画 */
    @keyframes fadeIn {
      from {
        opacity: 0;
        transform: translateY(10px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }

    /* 图片加载失败样式 */
    .content img:not([src]),
    .content img[src=""],
    .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="] {
      position: relative;
      min-height: 100px;
      background: var(--editor-bg);
      border: 1px dashed var(--border-color);
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .content img:not([src])::after,
    .content img[src=""]::after,
    .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="]::after {
      content: "图片加载失败";
      position: absolute;
      color: var(--secondary-color);
      font-size: 0.9em;
    }

    /* 大图查看模式 */
    .content img.enlarged {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      max-width: 90vw;
      max-height: 90vh;
      object-fit: contain;
      z-index: 1000;
      cursor: zoom-out;
      margin: 0;
      padding: 0;
      background: var(--bg-color);
      box-shadow: 0 0 20px var(--shadow-color);
    }

    /* 大图查看遮罩层 */
    .image-overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.8);
      z-index: 999;
      display: none;
      opacity: 0;
      transition: opacity 0.3s ease;
    }

    .image-overlay.active {
      display: block;
      opacity: 1;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="info-bar">
      <div class="info-left">
        <div class="info-item">
          <span>📝 笔记</span>
          <span>${shareId}</span>
        </div>
        <div class="info-item">
          <span>🕒 分享于</span>
          <span>${shareTime}</span>
        </div>
        <div class="info-item">
          <span>👀 访问</span>
          <span>${data.visitCount}</span>
        </div>
      </div>
      <div class="info-right">
        <button onclick="toggleDarkMode()" class="theme-toggle" title="切换主题">
          <span class="sun-icon">☀️</span>
          <span class="moon-icon">🌙</span>
          <span class="label">主题</span>
        </button>
      </div>
    </div>
    <div class="content">
      ${data.content}
    </div>
  </div>

  <div class="image-overlay" id="imageOverlay" onclick="closeEnlargedImage()"></div>
  <div class="toast-container" id="toast-container"></div>

  <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
  <script>
    // 初始化主题和代码块
    document.addEventListener('DOMContentLoaded', () => {
      const savedTheme = localStorage.getItem('theme') || 'light';
      document.documentElement.setAttribute('data-theme', savedTheme);
      
      // 为所有代码块添加复制按钮和语言标签
      const codeBlocks = document.querySelectorAll('pre code');
      codeBlocks.forEach(code => {
        const pre = code.parentElement;
        
        // 获取语言类名
        const langClass = Array.from(code.classList).find(cl => cl.startsWith('language-'));
        const language = langClass ? langClass.replace('language-', '') : '代码';
        pre.setAttribute('data-language', language);
        
        // 添加复制按钮
        const button = document.createElement('button');
        button.className = 'copy-button';
        button.innerHTML = '<span>📋</span><span>复制</span>';
        button.onclick = async (e) => {
          e.preventDefault();
          e.stopPropagation();
          await copyCode(code, button);
        };
        pre.appendChild(button);
      });

      // 应用代码高亮
      hljs.highlightAll();

      // 图片点击放大
      const content = document.querySelector('.content');
      const overlay = document.getElementById('imageOverlay');

      content.addEventListener('click', (e) => {
        if (e.target.tagName === 'IMG' && !e.target.classList.contains('enlarged')) {
          e.target.classList.add('enlarged');
          overlay.classList.add('active');
          document.body.style.overflow = 'hidden';
        }
      });

      // 图片加载错误处理
      const images = document.querySelectorAll('.content img');
      images.forEach(img => {
        img.onerror = () => {
          img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
        };
      });
    });

    // 复制代码到剪贴板
    async function copyCode(codeElement, button) {
      const originalText = button.innerHTML;
      const code = codeElement.innerText;

      try {
        await navigator.clipboard.writeText(code);
        button.innerHTML = '<span>✅</span><span>已复制</span>';
        button.classList.add('copied');
        showToast('代码已复制到剪贴板', 'success');
      } catch (err) {
        console.error('复制失败:', err);
        button.innerHTML = '<span>❌</span><span>复制失败</span>';
        button.classList.add('error');
        showToast('复制失败,请重试', 'error');
      }

      setTimeout(() => {
        button.innerHTML = originalText;
        button.classList.remove('copied', 'error');
      }, 2000);
    }

    // 切换暗色模式
    function toggleDarkMode() {
      const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
      document.documentElement.setAttribute('data-theme', theme);
      localStorage.setItem('theme', theme);
      
      // 更新Mermaid主题
      mermaid.initialize({
        startOnLoad: false,
        theme: theme === 'dark' ? 'dark' : 'default',
        securityLevel: 'loose',
        fontFamily: 'var(--font-family)',
      });
      
      // 重新渲染预览内容以更新Mermaid图表
      if (previewToggle.checked) {
        updatePreview(content.value);
      }
    }

    // 关闭放大的图片
    function closeEnlargedImage() {
      const enlargedImage = document.querySelector('.enlarged');
      const overlay = document.getElementById('imageOverlay');
      
      if (enlargedImage) {
        enlargedImage.classList.remove('enlarged');
        overlay.classList.remove('active');
        document.body.style.overflow = '';
      }
    }

    // ESC键关闭放大图片
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        closeEnlargedImage();
      }
    });

    // Toast 提示框
    function showToast(message, type = 'info', duration = 3000) {
      const container = document.getElementById('toast-container');
      const toast = document.createElement('div');
      toast.className = 'toast ' + type;
      
      const icons = {
        success: '✅',
        error: '❌',
        info: 'ℹ️',
        warning: '⚠️'
      };
      
      toast.innerHTML = 
        '<span class="toast-icon">' + (icons[type] || icons.info) + '</span>' +
        '<span class="toast-message">' + message + '</span>' +
        '<span class="toast-close" onclick="this.parentElement.remove()">✕</span>';
      
      container.appendChild(toast);
      
      void toast.offsetWidth;
      toast.classList.add('show');
      
      setTimeout(() => {
        toast.classList.remove('show');
        setTimeout(() => toast.remove(), 300);
      }, duration);
    }
  </script>
</body>
</html>`;
}

// 监听所有fetch请求
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

预览

/ * mmnote v1.0.0 - my markdown note. * Copyright (c) 2025, mmnote.com. (MIT Licensed) * https://mmnote.com */ // 保存笔记的路径前缀 const SAVEPATH = 'tmp'; // 有效的笔记名称格式(只允许字母、数字、下划线和连字符) const VALIDNOTEPATTERN = /^[a-zA-Z0-9_-]+$/;

/ * 使用 notePath 生成加密密钥 * @param {string} notePath - 笔记路径 * @returns {Promise<CryptoKey>} 加密密钥 */ async function generateEncryptionKey(notePath) { const encoder = new TextEncoder(); const keyData = encoder.encode(notePath); const hashBuffer = await crypto.subtle.digest('SHA-256', keyData); return await crypto.subtle.importKey( 'raw', hashBuffer, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'] ); }

/ * 加密文本 * @param {string} text - 要加密的文本 * @param {CryptoKey} key - 加密密钥 * @returns {Promise<string>} 加密后的base64字符串 */ async function encryptText(text, key) { const encoder = new TextEncoder(); const data = encoder.encode(text); const iv = crypto.getRandomValues(new Uint8Array(12)); const encryptedData = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, data ); const encryptedArray = new Uint8Array(encryptedData); const resultArray = new Uint8Array(iv.length + encryptedArray.length); resultArray.set(iv); resultArray.set(encryptedArray, iv.length); return btoa(String.fromCharCode(...resultArray)); }

/ * 解密文本 * @param {string} encryptedText - 加密的base64字符串 * @param {CryptoKey} key - 解密密钥 * @returns {Promise<string>} 解密后的文本 */ async function decryptText(encryptedText, key) { try { const encryptedArray = new Uint8Array(atob(encryptedText).split('').map(c => c.charCodeAt(0))); const iv = encryptedArray.slice(0, 12); const data = encryptedArray.slice(12); const decryptedData = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, key, data ); const decoder = new TextDecoder(); return decoder.decode(decryptedData); } catch (error) { console.error('解密失败:', error); return ''; // 解密失败返回空字符串 } }

/ * 处理所有传入的请求 * @param {Request} request - 传入的请求对象 * @returns {Response} 响应对象 */ async function handleRequest(request) { const url = new URL(request.url); const pathParts = url.pathname.split('/').filter(Boolean); const noteName = pathParts[0]; const action = pathParts[1];

// 处理分享相关的请求 if (noteName === 'share') { if (request.method === 'POST' && action) { try { const shareData = await request.json(); await NOTESKV.put('share' + action, JSON.stringify(shareData)); return new Response(null, { status: 200 }); } catch (error) { return new Response('保存分享数据失败', { status: 500 }); } } else if (request.method === 'GET' && action) { return await handleShareRequest(action); } }

// 如果笔记名无效或不存在,生成随机名称并重定向 if (!noteName || noteName.length > 64 || !VALIDNOTEPATTERN.test(noteName)) { const randomNoteName = generateRandomNoteName(); return Response.redirect(${url.origin}/${randomNoteName}, 302); }

const notePath = ${SAVE_PATH}/${noteName};

// 处理密码相关的请求 if (action) { // 使用笔记名和固定盐值生成密码存储键 const passwordKey = await generatePasswordKey(noteName);

switch (action) { case 'password': if (request.method === 'POST') { const { password } = await request.json(); // 为每个密码生成唯一盐值 const salt = crypto.getRandomValues(new Uint8Array(16)); const saltHex = Array.from(salt).map(b => b.toString(16).padStart(2, '0')).join(''); // 使用盐值和密码生成最终哈希 const finalHash = await hashPasswordWithSalt(password, salt); // 存储盐值和哈希 await NOTES_KV.put(passwordKey, JSON.stringify({ hash: finalHash, salt: saltHex })); return new Response(null, { status: 200 }); } else if (request.method === 'DELETE') { const { password } = await request.json(); const storedData = await NOTES_KV.get(passwordKey); if (storedData) { const { hash, salt } = JSON.parse(storedData); const saltArray = new Uint8Array(salt.match(/.{2}/g).map(byte => parseInt(byte, 16))); const checkHash = await hashPasswordWithSalt(password, saltArray); if (checkHash === hash) { await NOTES_KV.delete(passwordKey); return new Response(null, { status: 200 }); } } return new Response('Invalid password', { status: 401 }); } break;

case 'password-check': const hasPassword = await NOTES_KV.get(passwordKey); return new Response(null, { status: hasPassword ? 200 : 404 });

case 'password-verify': const { password } = await request.json(); const storedData = await NOTES_KV.get(passwordKey); if (storedData) { const { hash, salt } = JSON.parse(storedData); const saltArray = new Uint8Array(salt.match(/.{2}/g).map(byte => parseInt(byte, 16))); const checkHash = await hashPasswordWithSalt(password, saltArray); if (checkHash === hash) { return new Response(null, { status: 200 }); } } return new Response('Invalid password', { status: 401 }); } }

const raw = url.searchParams.has('raw');

// 根据请求方法分发处理 switch (request.method) { case 'POST': return await handlePostRequest(request, notePath); case 'GET': return raw || isCommandLineRequest(request) ? await handleRawRequest(notePath) : await handleGetRequest(notePath, noteName); default: return new Response('Method Not Allowed', { status: 405 }); } }

/ * 使用笔记名和固定盐值生成密码存储键 * @param {string} noteName - 笔记名称 * @returns {Promise<string>} 密码存储键 */ async function generatePasswordKey(noteName) { const encoder = new TextEncoder(); const data = encoder.encode(noteName + 'pwdprotected'); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return 'secure' + hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); }

/ * 使用盐值对密码进行哈希 * @param {string} password - 原始密码 * @param {Uint8Array} salt - 盐值 * @returns {Promise<string>} 哈希后的密码 */ async function hashPasswordWithSalt(password, salt) { const encoder = new TextEncoder(); const passwordData = encoder.encode(password);

// 将密码和盐值连接 const dataToHash = new Uint8Array(passwordData.length + salt.length); dataToHash.set(passwordData); dataToHash.set(salt, passwordData.length);

// 进行哈希 const hashBuffer = await crypto.subtle.digest('SHA-256', dataToHash); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); }

/ * 处理POST请求 - 保存或删除笔记内容 * @param {Request} request - POST请求对象 * @param {string} notePath - 笔记保存路径 * @returns {Response} 响应对象 */ async function handlePostRequest(request, notePath) { const formData = await request.formData(); const text = formData.get('text') || await request.text();

// 如果内容为空,删除笔记 if (text.trim().length === 0) { await handleDeleteRequest(notePath); return new Response('Note will be deleted', { status: 200 }); } else { await saveNoteContent(notePath, text); return new Response(null, { status: 204 }); } }

/ * 处理GET请求 - 返回笔记的HTML页面 * @param {string} notePath - 笔记路径 * @param {string} noteName - 笔记名称 * @returns {Response} HTML响应 */ async function handleGetRequest(notePath, noteName) { const noteContent = await getNoteContent(notePath); const html = generateHTML(noteName, noteContent); return new Response(html, { headers: { 'Content-Type': 'text/html' } }); }

/ * 处理原始内容请求 - 返回纯文本格式的笔记内容 * @param {string} notePath - 笔记路径 * @returns {Response} 文本响应 */ async function handleRawRequest(notePath) { const noteContent = await getNoteContent(notePath); return noteContent ? new Response(noteContent, { headers: { 'Content-Type': 'text/plain' } }) : new Response('404 Not Found', { status: 404 }); }

/ * 处理删除请求 * @param {string} notePath - 要删除的笔记路径 * @returns {Response} 响应对象 */ async function handleDeleteRequest(notePath) { await deleteNoteContent(notePath); return new Response(null, { status: 204 }); }

/ * 生成随机笔记名称 * @returns {string} 5位随机字符串 */ function generateRandomNoteName() { const chars = '0123456789abcdefghijklmnopqrstuvwxyz'; return Array.from({ length: 5 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); }

/ * 检查是否为命令行请求 * @param {Request} request - 请求对象 * @returns {boolean} 是否为命令行请求 */ function isCommandLineRequest(request) { const userAgent = request.headers.get('User-Agent') || ''; return userAgent.startsWith('curl') || userAgent.startsWith('Wget'); }

function generateHTML(noteName, noteContent) { return <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${noteName} - 在线笔记</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" media="(prefers-color-scheme: dark)"> <!-- 添加KaTeX依赖 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"> <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script> <!-- 添加Mermaid依赖 --> <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script> <style> :root { --primary-color: #4e92d1; --secondary-color: #6c757d; --bg-color: #ffffff; --text-color: #333333; --border-color: #e0e0e0; --container-bg: #ffffff; --editor-bg: #f8f8f8; --shadow-color: rgba(0, 0, 0, 0.1); --hover-color: #f0f0f0; --link-color: #0366d6; --link-hover-color: #0969da; --link-visited-color: #6f42c1; }

[data-theme="dark"] { --primary-color: #a2c2f5; --secondary-color: #9ca3af; --bg-color: #1a1a1a; --text-color: #f1f1f1; --border-color: #404040; --container-bg: #2a2a2a; --editor-bg: #333333; --shadow-color: rgba(0, 0, 0, 0.3); --hover-color: #3a3a3a; --link-color: #58a6ff; --link-hover-color: #79b8ff; --link-visited-color: #bc8cff; }

* { box-sizing: border-box; margin: 0; padding: 0; }

body { margin: 0; background: var(--bg-color); color: var(--text-color); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 16px; line-height: 1.6; transition: all 0.3s ease; }

.container { width: 100%; max-width: 100%; margin: 0 auto; padding: 10px; min-height: 100vh; height: 100vh; display: flex; flex-direction: column; background-color: var(--container-bg); }

.container.toolbar-hidden { padding: 10px 10px 10px 10px; }

.container.toolbar-hidden .editor-container { height: calc(100vh - 20px); }

@media (min-width: 1200px) { .container { max-width: 95%; box-shadow: 0 0 20px var(--shadow-color); } }

.toolbar { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: var(--editor-bg); border-radius: 12px; margin-bottom: 10px; gap: 10px; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; flex-shrink: 0; transition: all 0.3s ease; cursor: grab; }

/ 添加工具栏滚动条样式 / .toolbar::-webkit-scrollbar { height: 6px; width: 6px; }

.toolbar::-webkit-scrollbar-track { background: transparent; }

.toolbar::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 3px; }

.toolbar::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ 拖动时的光标样式 / .toolbar.dragging { cursor: grabbing; }

.toolbar.hidden { display: none; }

.toolbar-left { display: flex; align-items: center; gap: 15px; flex-shrink: 0; / 防止工具栏压缩 / }

.toolbar-right { display: flex; align-items: center; gap: 15px; flex-shrink: 0; / 防止工具栏压缩 / }

.toolbar-divider { width: 1px; height: 20px; background-color: var(--border-color); margin: 0 2px; }

.switch-label { display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--text-color); font-size: 0.9rem; }

.switch-label input[type="checkbox"] { width: 16px; height: 16px; }

.mode-toggle { background: none; border: none; font-size: 1.2rem; cursor: pointer; padding: 8px; border-radius: 50%; transition: all 0.3s ease; color: var(--text-color); display: flex; align-items: center; justify-content: center; }

.mode-toggle:hover { background: var(--hover-color); }

.mode-toggle .sun-icon, .mode-toggle .moon-icon { display: none; }

[data-theme="dark"] .moon-icon { display: block; }

[data-theme="light"] .sun-icon { display: block; }

.icon-container { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; }

.note-name { color: var(--primary-color); font-weight: 500; cursor: pointer; padding: 5px 10px; border-radius: 6px; transition: all 0.3s ease; display: flex; align-items: center; gap: 5px; transform-origin: left center; }

.note-name:hover { background: var(--hover-color); transform: scale(1.1); }

#save-status { color: var(--secondary-color); font-size: 0.9rem; margin-left: 10px; }

.editor-container { display: grid; grid-template-columns: 1fr; gap: 15px; flex: 1; min-height: 0; margin: 0; position: relative; transition: all 0.3s ease; }

/ 编辑器包装器 / .editor-wrapper { position: relative; width: 100%; height: 100%; display: flex; background: var(--editor-bg); border: 1px solid var(--border-color); border-radius: 8px; overflow: hidden; flex-direction: column; }

/ 设置统一的行高和字符宽度 / .editor-wrapper textarea, .line-numbers span { line-height: 1.5; min-height: 1.5em; font-size: 14px; font-family: 'Consolas', 'Monaco', monospace; box-sizing: border-box; letter-spacing: 0; }

.line-numbers { padding: 15px 2px 40px 2px; background: var(--editor-bg); border-right: 1px solid var(--border-color); color: var(--secondary-color); user-select: none; overflow: hidden; min-width: 28px; width: auto; display: flex; flex-direction: column; align-items: flex-end; }

.line-numbers span { display: block; padding: 0 4px; min-width: 24px; text-align: right; white-space: nowrap; }

.line-numbers.hidden { display: none; }

/ 编辑器主体区域 / .editor-main { display: flex; flex: 1; overflow: hidden; position: relative; }

/ 状态栏样式统一 / .status-bar, .preview-status-bar { position: relative; height: 25px; background: var(--editor-bg); border-top: 1px solid var(--border-color); display: flex; align-items: center; padding: 0 10px; font-size: 12px; color: var(--secondary-color); justify-content: space-between; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none; white-space: nowrap; flex-shrink: 0; }

/ 状态栏滚动条样式 / .status-bar::-webkit-scrollbar, .preview-status-bar::-webkit-scrollbar { height: 4px; width: 4px; }

.status-bar::-webkit-scrollbar-track, .preview-status-bar::-webkit-scrollbar-track { background: transparent; }

.status-bar::-webkit-scrollbar-thumb, .preview-status-bar::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 2px; }

.status-bar::-webkit-scrollbar-thumb:hover, .preview-status-bar::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

.status-left, .preview-status-left { display: flex; align-items: center; flex-wrap: nowrap; min-width: min-content; gap: 8px; padding-right: 8px; }

.status-right, .preview-status-right { display: flex; align-items: center; margin-left: auto; padding-left: 15px; flex-wrap: nowrap; min-width: min-content; gap: 8px; }

/ 确保状态栏内容不会被截断 / .status-bar > div, .preview-status-bar > div { flex-shrink: 0; }

.status-item, .preview-status-item { display: flex; align-items: center; gap: 5px; font-family: 'Consolas', 'Monaco', monospace; white-space: nowrap; color: var(--secondary-color); }

.status-item label, .preview-status-item label { display: flex; align-items: center; gap: 4px; cursor: pointer; font-family: inherit; font-size: inherit; color: var(--secondary-color); }

.status-item input[type="checkbox"], .preview-status-item input[type="checkbox"] { width: 14px; height: 14px; cursor: pointer; margin: 0; }

.status-item span, .preview-status-item span { color: var(--secondary-color); }

.editor-container textarea { flex: 1; height: 100%; padding: 15px 10px; border: none; border-radius: 0; background: var(--editor-bg); color: var(--text-color); resize: none; tab-size: 4; -moz-tab-size: 4; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; overflow-x: hidden; width: 100%; transition: all 0.3s ease; }

.editor-container textarea:focus { outline: none; }

.editor-container.preview-mode { grid-template-columns: 1fr 1fr; }

.preview-container { display: none; height: 100%; padding: 0; / 移除padding / border-radius: 8px; border: 1px solid var(--border-color); background: var(--editor-bg); color: var(--text-color); overflow: hidden; / 改为hidden,防止整体滚动 / position: relative; -webkit-user-select: none; / Safari / -ms-user-select: none; / IE 10+ / user-select: none; / Standard syntax / }

/ 添加预览内容容器样式 / .preview-content { height: calc(100% - 25px); / 减去状态栏高度 / padding: 10px 12px; overflow-y: auto; overflow-x: hidden; -webkit-user-select: text; / Safari / -ms-user-select: text; / IE 10+ / user-select: text; / Standard syntax / }

/ 预览区状态栏样式 / .preview-status-bar { position: absolute; bottom: 0; left: 0; right: 0; height: 25px; background: var(--editor-bg); border-top: 1px solid var(--border-color); display: flex; align-items: center; padding: 0 10px; font-size: 12px; color: var(--secondary-color); z-index: 1; justify-content: space-between; min-width: 100%; white-space: nowrap; }

.preview-status-left { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.preview-status-right { display: flex; align-items: center; gap: 15px; flex-shrink: 0; margin-left: auto; }

.preview-status-item { display: flex; align-items: center; gap: 5px; font-family: 'Consolas', 'Monaco', monospace; white-space: nowrap; flex-shrink: 0; }

.preview-status-item label { display: flex; align-items: center; gap: 4px; cursor: pointer; font-family: inherit; font-size: inherit; color: var(--secondary-color); }

.preview-status-item input[type="checkbox"] { width: 14px; height: 14px; cursor: pointer; margin: 0; }

.preview-status-item span { color: var(--secondary-color); }

.fullscreen-toggle { background: none; border: none; color: var(--text-color); cursor: pointer; padding: 2px 8px; border-radius: 4px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; font-size: 12px; }

.fullscreen-toggle:hover { background: var(--hover-color); }

/ Markdown 内容样式 / .preview-container > *:first-child { margin-top: 0; }

.preview-container > *:last-child { margin-bottom: 0; }

.preview-container h1, .preview-container h2, .preview-container h3, .preview-container h4, .preview-container h5, .preview-container h6 { margin-top: 1.8em; margin-bottom: 0.8em; line-height: 1.2; color: var(--text-color); }

.preview-container h1:first-child, .preview-container h2:first-child, .preview-container h3:first-child, .preview-container h4:first-child, .preview-container h5:first-child, .preview-container h6:first-child { margin-top: 0; }

.preview-container p { margin: 1.2em 0; line-height: 1.8; }

.preview-container a { display: inline; color: var(--link-color); text-decoration: none; border-bottom: 1px solid transparent; transition: all 0.2s ease; padding: 2px 4px; margin: 0 -4px; border-radius: 4px; text-align: left; }

/ 处理单独一行的链接 / .preview-container p > a:only-child { display: inline-block; text-align: left; width: auto; }

/ 确保链接在段落中的对齐方式 / .preview-container p { text-align: left; }

.preview-container a:hover { background: var(--hover-color); color: var(--link-hover-color); border-bottom-color: var(--link-hover-color); }

.preview-container a:visited { color: var(--link-visited-color); }

.preview-container a:visited:hover { background: var(--hover-color); border-bottom-color: var(--link-visited-color); }

.preview-container a[href^="http"]::after { content: "↗"; display: inline; margin-left: 2px; font-size: 0.9em; opacity: 0.6; } .preview-container ul, .preview-container ol { margin: 1em 0; padding-left: 1.5em; }

.preview-container li { margin: 0.5em 0; }

.preview-container blockquote { margin: 1.2em 0; padding: 1em 1.2em; border-left: 4px solid var(--border-color); background: var(--bg-color); border-radius: 0 4px 4px 0; display: flow-root; width: fit-content; max-width: 100%; box-shadow: 0 2px 4px var(--shadow-color); transition: all 0.2s ease; }

.preview-container blockquote > *:first-child { margin-top: 0; }

.preview-container blockquote > *:last-child { margin-bottom: 0; }

.preview-container blockquote p { margin: 0.8em 0; line-height: 1.6; }

.preview-container blockquote + blockquote { margin-top: -0.5em; }

/ 嵌套引用的样式 / .preview-container blockquote blockquote { margin: 0.8em 0; border-left-color: var(--secondary-color); background: var(--editor-bg); box-shadow: none; }

/ 移动端适配 / @media (max-width: 768px) { .preview-container blockquote { padding: 0.8em 1em; margin: 1em 0; width: 100%; } }

/ 代码块基础样式 / .preview-container pre { background: var(--editor-bg); padding: 1.2em 1em; border-radius: 8px; overflow: auto; position: relative; margin: 1.5em 0; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; line-height: 1.5; font-size: 0.95em; border: 1px solid var(--border-color); scrollbar-width: thin; scrollbar-color: var(--secondary-color) transparent; }

/ 行内代码样式 / .preview-container code { background: var(--editor-bg); padding: 0.2em 0.4em; margin: 0 0.2em; border-radius: 4px; font-size: 0.9em; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; border: 1px solid var(--border-color); }

/ 代码块中的代码样式 / .preview-container pre code { background: none; padding: 0; margin: 0; font-size: 0.95em; white-space: pre; word-break: normal; word-wrap: normal; line-height: inherit; tab-size: 2; hyphens: none; border: none; }

/ 代码块滚动条样式 / .preview-container pre::-webkit-scrollbar { width: 6px; height: 6px; }

.preview-container pre::-webkit-scrollbar-track { background: transparent; }

.preview-container pre::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 3px; border: 2px solid var(--editor-bg); }

.preview-container pre::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ 代码块语言标签 / .preview-container pre::before { content: attr(data-language); position: absolute; top: 0.5em; right: 0.5em; font-size: 0.85em; color: var(--secondary-color); padding: 0.2em 0.5em; border-radius: 3px; background: var(--container-bg); opacity: 0.8; transition: opacity 0.2s ease; }

.preview-container pre:hover::before { opacity: 1; }

/ 代码高亮主题 - 浅色模式 / .hljs { color: #383a42; background: var(--editor-bg); }

.hljs-comment, .hljs-quote { color: #a0a1a7; font-style: italic; }

.hljs-doctag, .hljs-keyword, .hljs-formula { color: #a626a4; }

.hljs-section, .hljs-name, .hljs-selector-tag, .hljs-deletion, .hljs-subst { color: #e45649; }

.hljs-literal { color: #0184bb; }

.hljs-string, .hljs-regexp, .hljs-addition, .hljs-attribute, .hljs-meta .hljs-string { color: #50a14f; }

.hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-type, .hljs-selector-class, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-number { color: #986801; }

.hljs-symbol, .hljs-bullet, .hljs-link, .hljs-meta, .hljs-selector-id, .hljs-title { color: #4078f2; }

.hljs-built_in, .hljs-title.class_, .hljs-class .hljs-title { color: #c18401; }

.hljs-emphasis { font-style: italic; }

.hljs-strong { font-weight: bold; }

/ 代码高亮主题 - 深色模式 / [data-theme="dark"] .hljs { color: #abb2bf; background: var(--editor-bg); }

[data-theme="dark"] .hljs-comment, [data-theme="dark"] .hljs-quote { color: #7f848e; font-style: italic; }

[data-theme="dark"] .hljs-doctag, [data-theme="dark"] .hljs-keyword, [data-theme="dark"] .hljs-formula { color: #c678dd; }

[data-theme="dark"] .hljs-section, [data-theme="dark"] .hljs-name, [data-theme="dark"] .hljs-selector-tag, [data-theme="dark"] .hljs-deletion, [data-theme="dark"] .hljs-subst { color: #e06c75; }

[data-theme="dark"] .hljs-literal { color: #56b6c2; }

[data-theme="dark"] .hljs-string, [data-theme="dark"] .hljs-regexp, [data-theme="dark"] .hljs-addition, [data-theme="dark"] .hljs-attribute, [data-theme="dark"] .hljs-meta .hljs-string { color: #98c379; }

[data-theme="dark"] .hljs-attr, [data-theme="dark"] .hljs-variable, [data-theme="dark"] .hljs-template-variable, [data-theme="dark"] .hljs-type, [data-theme="dark"] .hljs-selector-class, [data-theme="dark"] .hljs-selector-attr, [data-theme="dark"] .hljs-selector-pseudo, [data-theme="dark"] .hljs-number { color: #d19a66; }

[data-theme="dark"] .hljs-symbol, [data-theme="dark"] .hljs-bullet, [data-theme="dark"] .hljs-link, [data-theme="dark"] .hljs-meta, [data-theme="dark"] .hljs-selector-id, [data-theme="dark"] .hljs-title { color: #61afef; }

[data-theme="dark"] .hljs-built_in, [data-theme="dark"] .hljs-title.class_, [data-theme="dark"] .hljs-class .hljs-title { color: #e6c07b; }

[data-theme="dark"] .hljs-emphasis { font-style: italic; }

[data-theme="dark"] .hljs-strong { font-weight: bold; }

/ 代码复制按钮 / .copy-button { position: absolute; top: 0.5em; right: 0.5em; padding: 0.2em 0.5em; font-size: 0.85em; color: var(--text-color); background: var(--container-bg); border: 1px solid var(--border-color); border-radius: 3px; cursor: pointer; opacity: 0; transition: all 0.2s ease; display: flex; align-items: center; gap: 4px; }

.preview-container pre:hover .copy-button { opacity: 0.8; }

.copy-button:hover { opacity: 1 !important; background: var(--hover-color); }

.copy-button.copied { color: #4caf50; border-color: #4caf50; opacity: 1; }

/ 移动端适配 / @media (max-width: 768px) { .preview-container pre { padding: 1em 0.8em; font-size: 0.9em; }

.preview-container code { font-size: 0.85em; }

.preview-container pre::before { opacity: 1; } }

/ 代码块中的链接样式 / .preview-container pre a, .preview-container code a { border-bottom: none; }

.preview-container pre a::after, .preview-container code a::after { display: none; }

/ 图片链接样式 / .preview-container a:has(img) { border-bottom: none; }

.preview-container a:has(img)::after { display: none; }

.preview-container hr { margin: 2em 0; border: none; border-top: 1px solid var(--border-color); }

.preview-container table { width: 100%; border-collapse: collapse; margin: 1.5em 0; overflow-x: auto; display: block; }

.preview-container th, .preview-container td { border: 1px solid var(--border-color); padding: 8px 12px; text-align: left; }

.preview-container th { background-color: var(--hover-color); font-weight: 600; }

.preview-container tr:nth-child(even) { background-color: var(--editor-bg); }

.preview-container tr:hover { background-color: var(--hover-color); }

.preview-container img { max-width: 100%; margin: 1em 0; border-radius: 4px; }

/ 滚动条样式 / .editor-container textarea, .preview-container, .preview-content { scrollbar-width: thin; scrollbar-color: var(--secondary-color) transparent; }

.editor-container textarea::-webkit-scrollbar, .preview-container::-webkit-scrollbar, .preview-content::-webkit-scrollbar { width: 8px; height: 8px; }

.editor-container textarea::-webkit-scrollbar-track, .preview-container::-webkit-scrollbar-track, .preview-content::-webkit-scrollbar-track { background: transparent; }

.editor-container textarea::-webkit-scrollbar-thumb, .preview-container::-webkit-scrollbar-thumb, .preview-content::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 4px; border: 2px solid var(--editor-bg); }

.editor-container textarea::-webkit-scrollbar-thumb:hover, .preview-container::-webkit-scrollbar-thumb:hover, .preview-content::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ 移动端滚动条优化 / @media (max-width: 768px) { .editor-container textarea::-webkit-scrollbar, .preview-container::-webkit-scrollbar, .preview-content::-webkit-scrollbar { width: 6px; height: 6px; }

.editor-container textarea::-webkit-scrollbar-thumb, .preview-container::-webkit-scrollbar-thumb, .preview-content::-webkit-scrollbar-thumb { border-width: 1.5px; } }

/ 移动端响应式布局优化 / @media (max-width: 768px) { .container { padding: 5px; height: 100vh; max-height: -webkit-fill-available; display: flex; flex-direction: column; width: 100%; }

.container.toolbar-hidden { padding: 5px; }

.editor-container { flex: 1; min-height: 0; gap: 5px; margin-bottom: env(safe-area-inset-bottom, 15px); width: 100%; display: flex; flex-direction: column; }

.editor-container.preview-mode { display: flex; flex-direction: column; height: calc(100vh - 60px - env(safe-area-inset-bottom, 15px)); / 减去工具栏和底部安全区域 / }

.editor-wrapper, .preview-container { flex: 1; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; width: 100%; }

.editor-container.preview-mode .editor-wrapper, .editor-container.preview-mode .preview-container { flex: 1; height: 0; / 让flex:1生效 / min-height: 0; max-height: none; width: 100%; }

/ 处理键盘弹出时的布局 / @supports (-webkit-touch-callout: none) { .editor-container.preview-mode { height: calc(100vh - 60px - env(safe-area-inset-bottom, 15px) - env(keyboard-inset-height, 0px)); } }

/ 确保内容可滚动 / .editor-container.preview-mode .editor-wrapper textarea, .editor-container.preview-mode .preview-container { height: 100%; overflow-y: auto; } }

/ 处理超小屏幕设备 / @media (max-width: 320px) { .container { padding: 3px 3px 12px 3px; }

.container.toolbar-hidden { padding: 8px; }

.editor-wrapper textarea, .line-numbers span { font-size: 15px; }

.preview-container { font-size: 15px; padding: 10px 10px 20px 10px; } }

/ 处理横屏模式 / @media (max-height: 480px) and (orientation: landscape) { .container { padding: 5px; width: 100%; }

.container.toolbar-hidden { padding: 5px; }

.toolbar { padding: 6px 10px; margin-bottom: 8px; width: 100%; }

.editor-container { height: calc(100vh - 80px); width: 100%; }

.editor-container.preview-mode { grid-template-columns: 1fr 1fr; / 横屏时恢复左右布局 / gap: 8px; width: 100%; }

.editor-wrapper, .preview-container { width: 100%; height: 100%; box-sizing: border-box; }

.editor-wrapper textarea, .preview-container { width: 100%; height: 100%; box-sizing: border-box; }

.editor-container.preview-mode .editor-wrapper, .editor-container.preview-mode .preview-container { height: 100%; width: 100%; }

/ 横屏模式下状态栏优化 / .status-bar, .preview-status-bar { height: 24px; font-size: 11px; padding: 0 6px; width: 100%; }

.status-bar .status-left, .status-bar .status-right, .preview-status-bar .preview-status-left, .preview-status-bar .preview-status-right { gap: 8px; }

.status-bar .status-right, .preview-status-bar .preview-status-right { padding-left: 8px; }

.status-item, .preview-status-item { margin-right: 8px; }

.status-item label, .preview-status-item label { gap: 2px; }

.status-item input[type="checkbox"], .preview-status-item input[type="checkbox"] { width: 12px; height: 12px; }

/ 调整状态栏右侧开关的间距 / .status-right .status-item { margin-right: 4px; }

.status-right .switch-label, .preview-status-right .switch-label { font-size: 11px; padding: 2px 4px; } }

/ 适配折叠屏设备 / @media (max-width: 350px) and (min-height: 600px) { .container { padding: 4px; }

.toolbar { flex-direction: column; align-items: stretch; }

.toolbar-left, .toolbar-right { justify-content: center; }

.editor-container { height: calc(100vh - 140px); } }

/ 适配深色模式和高对比度显示 / @media (prefers-contrast: high) { :root { --border-color: #666666; --shadow-color: rgba(0, 0, 0, 0.3); }

.editor-container textarea, .preview-container { border-width: 2px; } }

/ 适配强制颜色模式 / @media (forced-colors: active) { :root { --border-color: CanvasText; --text-color: CanvasText; --bg-color: Canvas; --primary-color: LinkText; --secondary-color: GrayText; --container-bg: Canvas; --editor-bg: Canvas; --hover-color: Highlight; --link-color: LinkText; --link-hover-color: LinkText; --link-visited-color: VisitedText; }

.editor-container textarea, .preview-container, .toolbar, .status-bar, .preview-status-bar { border: 1px solid CanvasText; }

.markdown-toolbar button, .toolbar-select, .fullscreen-toggle { border: 1px solid CanvasText; background: Canvas; color: CanvasText; }

.markdown-toolbar button:hover, .toolbar-select:hover, .fullscreen-toggle:hover { background: Highlight; color: HighlightText; } }

/ 减少动画以适应省电模式 / @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/ 工具栏样式 / .markdown-toolbar { display: flex; gap: 4px; align-items: center; }

.markdown-toolbar button { padding: 4px 8px; background: var(--editor-bg); border: 1px solid var(--border-color); border-radius: 4px; color: var(--text-color); cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; min-width: 28px; height: 28px; transition: all 0.2s ease; }

.markdown-toolbar button:hover { background: var(--hover-color); }

.toolbar-select { padding: 4px 8px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--editor-bg); color: var(--text-color); font-size: 14px; cursor: pointer; }

.toolbar-select:hover { background: var(--hover-color); }

/ Emoji 选择器样式 / .emoji-picker { position: fixed; / 改为fixed定位,避免滚动问题 / left: 50%; top: 50%; transform: translate(-50%, -50%); background: var(--container-bg); border: 1px solid var(--border-color); border-radius: 8px; box-shadow: 0 2px 8px var(--shadow-color); padding: 15px; display: none; z-index: 1000; max-height: 80vh; width: 90%; max-width: 400px; overflow-y: auto; -webkit-overflow-scrolling: touch; }

.emoji-picker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 8px; }

.emoji-picker button { width: 40px; height: 40px; padding: 8px; border: none; background: none; cursor: pointer; border-radius: 8px; transition: all 0.2s ease; font-size: 20px; display: flex; align-items: center; justify-content: center; }

.emoji-picker button:hover { background: var(--hover-color); }

.emoji-picker-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 999; display: none; }

/ 全屏模式样式 / .preview-container.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100vw; height: 100vh; z-index: 9999; border-radius: 0; border: none; }

/ 真实全屏模式的样式 / .preview-container:fullscreen { background-color: var(--container-bg); width: 100vw; height: 100vh; padding: 0; margin: 0; border: none; border-radius: 0; }

.preview-container:fullscreen .preview-content { height: calc(100vh - 25px); padding: 20px; max-width: 1200px; margin: 0 auto; }

.preview-container:fullscreen .preview-status-bar { position: fixed; bottom: 0; left: 0; right: 0; background: var(--editor-bg); border-top: 1px solid var(--border-color); }

/ 移动端全屏适配 / @media (max-width: 768px) { .preview-container:fullscreen .preview-content { padding: 15px; height: calc(100vh - 25px - env(safe-area-inset-bottom, 0px)); }

.preview-container:fullscreen .preview-status-bar { padding-bottom: env(safe-area-inset-bottom, 0px); } }

.preview-container.fullscreen .preview-content { height: calc(100vh - 25px); padding: 20px; }

/ 全屏切换按钮样式 / .fullscreen-toggle { background: none; border: none; color: var(--text-color); cursor: pointer; padding: 2px 8px; border-radius: 4px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; font-size: 12px; }

.fullscreen-toggle:hover { background: var(--hover-color); }

.fullscreen-toggle .fullscreen-icon { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; }

/ 预览区双击时禁止选中文本 / .preview-container.fullscreen .preview-content { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }

/ 预览区恢复正常文本选择 / .preview-container:not(.fullscreen) .preview-content { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; }

/ 全屏模式下的状态栏样式 / .preview-container.fullscreen .preview-status-bar { position: fixed; bottom: env(safe-area-inset-bottom, 0); left: 0; right: 0; background: var(--editor-bg); border-top: 1px solid var(--border-color); z-index: 10000; }

/ 响应式布局 - 移动端优化 / @media (max-width: 768px) { .toolbar { padding: 8px; margin-bottom: 12px; white-space: nowrap; gap: 8px; }

.toolbar-left, .toolbar-right { gap: 8px; }

.markdown-toolbar button { min-width: 32px; height: 32px; padding: 4px; }

.toolbar-select { padding: 4px; font-size: 13px; }

.note-name { font-size: 0.9rem; padding: 4px 8px; }

.emoji-picker { padding: 10px; }

.emoji-picker button { width: 36px; height: 36px; font-size: 18px; } }

/ 移动端状态栏适配 / @media (max-width: 768px) { .preview-status-bar { padding: 0 8px; overflow: hidden; }

.preview-status-right { gap: 8px; }

.preview-status-item { margin-left: 8px; font-size: 11px; } }

/ 处理超小屏幕设备状态栏 / @media (max-width: 320px) { .preview-status-bar { padding: 0 5px; }

.preview-status-item { margin-left: 5px; font-size: 10px; } }

/ 全屏模式下的移动端优化 / @media (max-width: 768px) { .preview-container.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100vw; height: 100vh; height: -webkit-fill-available; z-index: 9999; border-radius: 0; padding-bottom: env(safe-area-inset-bottom, 0); }

.preview-container.fullscreen .preview-content { height: calc(100vh - 25px - env(safe-area-inset-bottom, 0)); padding: 15px; } }

/ 处理横屏模式 / @media (max-height: 480px) and (orientation: landscape) { .preview-container.fullscreen .preview-status-bar { height: 24px; font-size: 11px; padding: 0 6px; }

.preview-container.fullscreen .preview-content { height: calc(100vh - 24px); padding: 10px; } }

/ 在现有样式的末尾添加密码相关样式 / .password-dialog { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--container-bg); border: 1px solid var(--border-color); border-radius: 12px; padding: 24px; box-shadow: 0 8px 24px var(--shadow-color); z-index: 1000; width: 90%; max-width: 360px; transition: all 0.3s ease; }

.password-dialog h3 { margin: 0 0 20px 0; color: var(--text-color); font-size: 1.2em; font-weight: 500; display: flex; align-items: center; gap: 8px; }

.password-dialog h3::before { content: '🔒'; font-size: 1.1em; }

.password-dialog input[type="password"] { width: 100%; padding: 10px 14px; margin-bottom: 12px; border: 1px solid var(--border-color); border-radius: 8px; background: var(--editor-bg); color: var(--text-color); font-size: 15px; transition: all 0.2s ease; }

.password-dialog input[type="password"]:hover { border-color: var(--secondary-color); }

.password-dialog input[type="password"]:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 2px var(--primary-color-alpha); }

.password-dialog-message { margin-bottom: 20px; font-size: 0.95em; color: #e74c3c; min-height: 20px; transition: all 0.3s ease; opacity: 0; display: flex; align-items: center; gap: 6px; }

.password-dialog-message::before { content: '⚠️'; font-size: 1.1em; }

.password-dialog-message.success { color: #2ecc71; }

.password-dialog-message.success::before { content: '✅'; }

.password-dialog-message.show { opacity: 1; }

.password-dialog-buttons { display: flex; justify-content: flex-end; gap: 12px; }

.password-dialog button { padding: 8px 16px; border: 1px solid var(--border-color); border-radius: 8px; background: var(--editor-bg); color: var(--text-color); cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; }

.password-dialog button:hover { background: var(--hover-color); border-color: var(--secondary-color); }

.password-dialog button.primary { background: var(--primary-color); color: white; border-color: var(--primary-color); }

.password-dialog button.primary:hover { opacity: 0.9; transform: translateY(-1px); }

.password-dialog button:active { transform: translateY(1px); }

.password-dialog-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); z-index: 999; transition: all 0.3s ease; }

.password-protected .editor-container, .password-protected .toolbar { filter: blur(8px); pointer-events: none; user-select: none; }

.password-status { display: flex; align-items: center; gap: 8px; color: var(--text-color); font-size: 0.9rem; padding: 4px 8px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; }

.password-status:hover { background: var(--hover-color); }

.password-status-icon { font-size: 1.2rem; transition: transform 0.3s ease; }

.password-status:hover .password-status-icon { transform: scale(1.1); }

@media (max-width: 768px) { .password-dialog { width: 90%; padding: 20px; / 保持在屏幕中间 / top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0; }

.password-dialog h3 { font-size: 1.1em; }

.password-dialog input[type="password"] { font-size: 14px; padding: 8px 12px; }

.password-dialog button { padding: 7px 14px; } }

@media (max-width: 480px) { .password-dialog { width: 100%; max-width: none; border-radius: 12px 12px 0 0; bottom: 0; top: auto; transform: translateX(-50%); padding-bottom: calc(20px + env(safe-area-inset-bottom)); } }

/ 工具栏右侧按钮样式 / .toolbar-right { display: flex; align-items: center; gap: 8px; }

.toolbar-button { display: flex; align-items: center; gap: 8px; color: var(--text-color); font-size: 0.9rem; padding: 4px 8px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; border: none; background: none; }

.toolbar-button:hover { background: var(--hover-color); }

.toolbar-button .icon { font-size: 1.2rem; transition: transform 0.3s ease; }

.toolbar-button:hover .icon { transform: scale(1.1); }

.toolbar-button .label { display: none; }

@media (min-width: 768px) { .toolbar-button { padding: 6px 12px; }

.toolbar-button .label { display: inline; } }

/ 主题切换按钮特定样式 / .theme-toggle .sun-icon, .theme-toggle .moon-icon { display: none; }

[data-theme="dark"] .moon-icon { display: block; }

[data-theme="light"] .sun-icon { display: block; }

/ 密码状态按钮特定样式 / .password-status { display: flex; align-items: center; gap: 8px; color: var(--text-color); font-size: 0.9rem; padding: 4px 8px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; }

.password-status:hover { background: var(--hover-color); }

.password-status .icon { font-size: 1.2rem; transition: transform 0.3s ease; }

.password-status:hover .icon { transform: scale(1.1); }

/ 添加 Toast 提示框样式 / .toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 10px; pointer-events: none; / 保持在右上角 / top: 20px; right: 20px; width: auto; max-width: calc(100% - 40px); transform: none; }

.toast { background: var(--container-bg); color: var(--text-color); padding: 12px 24px; border-radius: 8px; box-shadow: 0 4px 12px var(--shadow-color); font-size: 14px; display: flex; align-items: center; gap: 8px; opacity: 0; transform: translateX(100%); transition: all 0.3s ease; border: 1px solid var(--border-color); pointer-events: all; max-width: 300px; word-break: break-word; }

.toast.show { opacity: 1; transform: translateX(0); }

.toast.success { border-left: 4px solid #4caf50; }

.toast.error { border-left: 4px solid #f44336; }

.toast.info { border-left: 4px solid #2196f3; }

.toast.warning { border-left: 4px solid #ff9800; }

.toast-icon { font-size: 18px; flex-shrink: 0; }

.toast-message { flex: 1; margin-right: 8px; }

.toast-close { cursor: pointer; opacity: 0.7; transition: opacity 0.2s ease; padding: 4px; margin: -4px; border-radius: 4px; }

.toast-close:hover { opacity: 1; background: var(--hover-color); }

@media (max-width: 768px) { .toast-container { / 保持在右上角 / top: 20px; right: 20px; width: auto; max-width: calc(100% - 40px); transform: none; }

.toast { width: 100%; transform: translateY(100%); }

.toast.show { transform: translateY(0); } }

/ 添加复制全部按钮样式 / .copy-all-button { background: none; border: none; color: var(--text-color); cursor: pointer; padding: 0px 4px; border-radius: 4px; font-size: 12px; display: flex; align-items: center; gap: 4px; transition: all 0.2s ease; }

.copy-all-button:hover { background: var(--hover-color); }

/ 添加复制全部的动效样式 / .copy-all-item { cursor: pointer; transition: all 0.2s ease; padding: 0px 4px; border-radius: 4px; display: flex; align-items: center; gap: 4px; }

.copy-all-item:hover { background: var(--hover-color); }

.copy-all-item .copy-icon { transition: transform 0.2s ease; }

.copy-all-item:hover .copy-icon { transform: scale(1.1); }

.copy-all-item.copied { animation: copied-animation 0.5s ease; }

@keyframes copied-animation { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } }

/ 表格样式 / .content table { width: 100%; border-collapse: collapse; margin: 1.5em 0; overflow-x: auto; display: block; }

.content th, .content td { border: 1px solid var(--border-color); padding: 8px 12px; text-align: left; }

.content th { background-color: var(--hover-color); font-weight: 600; }

.content tr:nth-child(even) { background-color: var(--editor-bg); }

.content tr:hover { background-color: var(--hover-color); }

/ 移动端表格适配 / @media (max-width: 768px) { .content table { font-size: 14px; }

.content th, .content td { padding: 6px 8px; } }

/ 表格滚动条样式 / .content table::-webkit-scrollbar { height: 8px; width: 8px; }

.content table::-webkit-scrollbar-track { background: transparent; }

.content table::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 4px; border: 2px solid var(--editor-bg); }

.content table::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ 表格滚动条样式 / .content table::-webkit-scrollbar { height: 8px; width: 8px; }

.content table::-webkit-scrollbar-track { background: transparent; }

.content table::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 4px; border: 2px solid var(--editor-bg); }

.content table::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ 图片样式 / .content img { max-width: 100%; height: auto; margin: 1em 0; border-radius: 8px; display: block; box-shadow: 0 2px 8px var(--shadow-color); transition: all 0.3s ease; opacity: 0; animation: fadeIn 0.5s ease forwards; }

/ 图片容器 / .content p:has(img) { text-align: center; margin: 2em 0; }

/ 图片悬停效果 / .content img:hover { transform: scale(1.01); box-shadow: 0 4px 12px var(--shadow-color); }

/ 图片标题样式 / .content img + em { display: block; text-align: center; color: var(--secondary-color); font-size: 0.9em; margin-top: 0.5em; }

/ 移动端图片适配 / @media (max-width: 768px) { .content img { border-radius: 6px; margin: 0.8em 0; }

.content p:has(img) { margin: 1.5em 0; }

/ 禁用移动端图片缩放动画 / .content img:hover { transform: none; box-shadow: 0 2px 8px var(--shadow-color); } }

/ 图片加载动画 / @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/ 图片加载失败样式 / .content img:not([src]), .content img[src=""], .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="] { position: relative; min-height: 100px; background: var(--editor-bg); border: 1px dashed var(--border-color); display: flex; align-items: center; justify-content: center; }

.content img:not([src])::after, .content img[src=""]::after, .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="]::after { content: "图片加载失败"; position: absolute; color: var(--secondary-color); font-size: 0.9em; }

/ 大图查看模式 / .content img.enlarged { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 90vw; max-height: 90vh; object-fit: contain; z-index: 1000; cursor: zoom-out; margin: 0; padding: 0; background: var(--bg-color); box-shadow: 0 0 20px var(--shadow-color); }

/ 大图查看遮罩层 / .image-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); z-index: 999; display: none; opacity: 0; transition: opacity 0.3s ease; }

.image-overlay.active { display: block; opacity: 1; }

/ 代码块复制按钮样式 / .content pre { position: relative; }

.copy-button { position: absolute; top: 0.5em; right: 0.5em; padding: 0.2em 0.5em; font-size: 0.85em; color: var(--text-color); background: var(--container-bg); border: 1px solid var(--border-color); border-radius: 3px; cursor: pointer; opacity: 0; transition: all 0.2s ease; display: flex; align-items: center; gap: 4px; z-index: 2; }

.content pre:hover .copy-button { opacity: 0.8; }

.copy-button:hover { opacity: 1 !important; background: var(--hover-color); transform: translateY(-1px); }

.copy-button.copied { color: #4caf50; border-color: #4caf50; opacity: 1; }

.copy-button.error { color: #f44336; border-color: #f44336; opacity: 1; }

/ 代码块语言标签 / .content pre::before { content: attr(data-language); position: absolute; top: 0; right: 0; padding: 0.2em 0.5em; font-size: 0.85em; background: var(--container-bg); border-bottom-left-radius: 4px; color: var(--secondary-color); opacity: 0.8; transition: opacity 0.2s ease; }

.content pre:hover::before { opacity: 0; }

@media (max-width: 768px) { .copy-button { padding: 0.15em 0.4em; font-size: 0.8em; }

.content pre::before { font-size: 0.8em; padding: 0.15em 0.4em; } }

.info-bar { display: flex; justify-content: space-between; align-items: center; padding: 15px; background: var(--editor-bg); border-radius: 12px; margin-bottom: 20px; font-size: 14px; border: 1px solid var(--border-color); overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; gap: 15px; min-width: 0; }

.info-bar::-webkit-scrollbar { height: 6px; width: 6px; }

.info-bar::-webkit-scrollbar-track { background: transparent; }

.info-bar::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 3px; }

.info-bar::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

.info-left { display: flex; gap: 15px; flex-shrink: 0; margin-right: auto; }

.info-right { display: flex; align-items: center; gap: 15px; flex-shrink: 0; margin-left: auto; }

.info-item { display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 6px; transition: all 0.2s ease; white-space: nowrap; flex-shrink: 0; }

.info-item:hover { background: var(--hover-color); }

.theme-toggle { display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 6px; border: none; color: var(--text-color); cursor: pointer; font-size: 14px; transition: all 0.2s ease; background: transparent; white-space: nowrap; flex-shrink: 0; }

.theme-toggle:hover { background: var(--hover-color); }

.theme-toggle:active { transform: scale(0.95); }

.theme-toggle .sun-icon, .theme-toggle .moon-icon { display: none; font-size: 1.2rem; }

.theme-toggle .label { font-size: 14px; }

[data-theme="dark"] .moon-icon { display: block; }

[data-theme="light"] .sun-icon { display: block; }

@media (max-width: 768px) { .theme-toggle { padding: 4px 8px; }

.theme-toggle .label { font-size: 13px; } }

@media (max-width: 768px) { .info-bar { padding: 10px; gap: 10px; }

.info-left, .info-right { gap: 10px; }

.info-item { padding: 4px 8px; font-size: 13px; } }

/ 添加KaTeX相关样式 / .katex-display { overflow-x: auto; overflow-y: hidden; padding: 1em 0; margin: 1em 0; }

.katex-display::-webkit-scrollbar { height: 6px; }

.katex-display::-webkit-scrollbar-track { background: transparent; }

.katex-display::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 3px; }

.katex-display::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ Mermaid图表样式 / .mermaid { margin: 1.5em 0; text-align: center; background: var(--editor-bg); padding: 1em; border-radius: 8px; border: 1px solid var(--border-color); overflow-x: auto; }

.mermaid svg { max-width: 100%; height: auto; }

/ 深色模式下的Mermaid样式 / [data-theme="dark"] .mermaid { --mermaid-bg: var(--editor-bg); --mermaid-fg: var(--text-color); --mermaid-edge: var(--text-color); --mermaid-label: var(--text-color); --mermaid-cluster: var(--border-color); }

[data-theme="dark"] .mermaid .node rect, [data-theme="dark"] .mermaid .node circle, [data-theme="dark"] .mermaid .node ellipse, [data-theme="dark"] .mermaid .node polygon, [data-theme="dark"] .mermaid .node path { fill: var(--mermaid-bg); stroke: var(--mermaid-fg); }

[data-theme="dark"] .mermaid .edgePath .path { stroke: var(--mermaid-edge) !important; }

[data-theme="dark"] .mermaid .edgeLabel { color: var(--mermaid-label); background-color: var(--mermaid-bg); }

[data-theme="dark"] .mermaid .cluster rect { fill: var(--mermaid-bg) !important; stroke: var(--mermaid-cluster) !important; }

[data-theme="dark"] .mermaid .label { color: var(--mermaid-label); }

[data-theme="dark"] .mermaid .node .label { color: var(--mermaid-label); }

[data-theme="dark"] .mermaid marker { fill: var(--mermaid-edge); } </style>

<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/emoji-toolkit@7.0.0/lib/js/joypixels.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/emoji-toolkit@7.0.0/extras/css/joypixels.min.css"> </head> <body> <div class="container"> <div class="toolbar"> <div class="toolbar-left"> <div class="note-name" onclick="copyNoteLink()" title="点击复制链接"> 📋 ${noteName} </div> <div class="toolbar-divider"></div> <!-- 字体和字号选择器 --> <select id="font-family" class="toolbar-select" onchange="applyFont()"> <option value="default">默认字体</option> <option value="serif">宋体</option> <option value="yahei">微软雅黑</option> <option value="kaiti">楷体</option> <option value="heiti">黑体</option> <option value="fangsong">仿宋</option> <option value="songti">新宋体</option> <option value="monospace">等宽字体</option> <option value="arial">Arial</option> <option value="times">Times New Roman</option> <option value="helvetica">Helvetica</option> </select> <select id="font-size" class="toolbar-select" onchange="applyFontSize()"> <option value="12">12px</option> <option value="13">13px</option> <option value="14">14px</option> <option value="15">15px</option> <option value="16" selected>16px</option> <option value="17">17px</option> <option value="18">18px</option> <option value="20">20px</option> <option value="22">22px</option> <option value="24">24px</option> <option value="26">26px</option> <option value="28">28px</option> <option value="32">32px</option> </select> <div class="toolbar-divider"></div> <!-- Markdown 工具栏 --> <div class="markdown-toolbar"> <button onclick="applyMarkdown('bold')" title="粗体 Ctrl+B">B</button> <button onclick="applyMarkdown('italic')" title="斜体 Ctrl+I">I</button> <button onclick="applyMarkdown('heading')" title="标题 Ctrl+H">H</button> <button onclick="applyMarkdown('strikethrough')" title="删除线 Ctrl+D">S</button> <button onclick="applyMarkdown('list')" title="无序列表 Ctrl+U">•</button> <button onclick="applyMarkdown('ordered-list')" title="有序列表 Ctrl+O">1.</button> <button onclick="applyMarkdown('task')" title="任务列表 Ctrl+T">☐</button> <button onclick="applyMarkdown('quote')" title="引用 Ctrl+Q">""</button> <button onclick="applyMarkdown('code')" title="代码 Ctrl+K">{}</button> <button onclick="applyMarkdown('table')" title="表格">⚏</button> <button onclick="applyMarkdown('divider')" title="分割线">—</button> <button onclick="applyMarkdown('link')" title="链接 Ctrl+L">🔗</button> <button onclick="applyMarkdown('image')" title="图片 Ctrl+P">🖼</button> <button onclick="showEmojiPicker()" title="表情">😊</button> <div class="toolbar-divider"></div> <button onclick="applyMarkdown('latex-inline')" title="行内公式">∑</button> <button onclick="applyMarkdown('latex-block')" title="公式块">∫</button> <button onclick="applyMarkdown('mermaid')" title="流程图">📊</button> </div> <span id="save-status"></span> </div> <div class="toolbar-right"> <div class="password-status toolbar-button" onclick="showPasswordDialog()" title="密码保护设置"> <span class="icon" id="password-status-icon">🔓</span> <span class="label">密码保护</span> </div> <button onclick="toggleDarkMode()" class="toolbar-button theme-toggle" title="切换主题"> <div class="icon"> <span class="sun-icon">☀️</span> <span class="moon-icon">🌙</span> </div> <span class="label">主题</span> </button> <div class="share-button toolbar-button" onclick="shareNote()" title="分享笔记"> <span class="icon">📤</span> <span class="label">分享</span> </div> </div> </div>

<div class="editor-container"> <div class="editor-wrapper"> <div class="editor-main"> <div class="line-numbers"></div> <textarea id="content" placeholder="开始输入笔记内容..." onscroll="handleEditorScroll()">${noteContent}</textarea> </div> <div class="status-bar"> <div class="status-left"> <div class="status-item"> <label title="显示/隐藏行号"> <input type="checkbox" id="line-numbers-toggle" checked onchange="toggleLineNumbers()"> 行号 </label> </div> <div class="status-item"> <label title="显示/隐藏工具栏"> <input type="checkbox" id="toolbar-toggle" checked onchange="toggleToolbar()"> 工具栏 </label> </div> <div class="status-item"> <label class="switch-label"> <input type="checkbox" id="preview-toggle" onchange="togglePreview()"> 预览 </label> </div> </div> <div class="status-right"> <div class="status-item copy-all-item" onclick="copyAllContent()" title="复制全部内容"> <span class="copy-icon">📋</span> <span>复制全部</span> </div> <div class="status-item"> <span>字数:</span> <span id="char-count">0</span> </div> <div class="status-item"> <span>单词:</span> <span id="word-count">0</span> </div> <div class="status-item"> <span>行:</span> <span id="line-count">1</span> </div> <div class="status-item"> <span>列:</span> <span id="column-count">1</span> </div> </div> </div> </div> <div id="preview" class="preview-container"> <div class="preview-content" onscroll="handlePreviewScroll()" ondblclick="handlePreviewDoubleTap(event)"></div> <div class="preview-status-bar"> <div class="preview-status-left"> <button onclick="togglePreviewFullscreen()" class="fullscreen-toggle" title="切换全屏"> <span class="fullscreen-icon" id="fullscreen-icon">⛶</span> </button> <div class="preview-status-item"> <label class="switch-label"> <input type="checkbox" id="sync-scroll-toggle" onchange="toggleSyncScroll()"> 同步滚动 </label> </div> </div> <div class="preview-status-right"> <div class="preview-status-item"> <span>字数:</span> <span id="preview-char-count">0</span> </div> <div class="preview-status-item"> <span>单词:</span> <span id="preview-word-count">0</span> </div> <div class="preview-status-item"> <span>段落:</span> <span id="preview-paragraph-count">0</span> </div> </div> </div> </div> </div> </div>

<div id="emoji-picker" class="emoji-picker"></div>

<!-- 添加密码对话框 --> <div class="password-dialog-overlay" id="password-overlay"></div> <div class="password-dialog" id="password-dialog"> <h3 id="password-dialog-title">设置密码保护</h3> <input type="password" id="password-input" placeholder="请输入密码" autocomplete="new-password"> <div class="password-dialog-message" id="password-message"></div> <div class="password-dialog-buttons"> <button onclick="closePasswordDialog()"> <span>取消</span> </button> <button class="primary" onclick="handlePasswordAction()" id="password-action-btn"> <span>确定</span> </button> </div> </div>

<!-- 添加 Toast 容器 --> <div class="toast-container" id="toast-container"></div>

<div class="image-overlay" id="imageOverlay" onclick="closeEnlargedImage()"></div>

<script> const content = document.getElementById('content'); const preview = document.getElementById('preview'); const previewToggle = document.getElementById('preview-toggle'); const syncScrollToggle = document.getElementById('sync-scroll-toggle'); const saveStatus = document.getElementById('save-status'); let saveTimeout; let isEditorScrolling = false; let isPreviewScrolling = false; let isSyncScrollEnabled = true; let isEmptyNote = false; // Flag to track if the note is empty and space was added

document.addEventListener('DOMContentLoaded', () => { // 配置 marked marked.setOptions({ gfm: true, breaks: true, tables: true, headerIds: true, mangle: false, sanitize: false, smartLists: true, smartypants: true, xhtml: false, langPrefix: 'language-', pedantic: false, highlight: function(code, lang) { if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch (err) {} } try { return hljs.highlightAuto(code).value; } catch (err) {} return code; } });

// 自定义 emoji 渲染 const renderer = new marked.Renderer(); const originalText = renderer.text.bind(renderer); renderer.text = (text) => { return joypixels.shortnameToImage(originalText(text)); }; marked.setOptions({ renderer });

// 初始化主题 const savedTheme = localStorage.getItem('theme') || 'light'; document.documentElement.setAttribute('data-theme', savedTheme);

// 初始化预览状态 const showPreview = localStorage.getItem('preview') === 'true'; previewToggle.checked = showPreview;

// 初始化同步滚动状态 const savedSyncScroll = localStorage.getItem('sync-scroll'); isSyncScrollEnabled = savedSyncScroll !== 'false'; syncScrollToggle.checked = isSyncScrollEnabled;

const editorContainer = document.querySelector('.editor-container'); editorContainer.classList.toggle('preview-mode', showPreview); preview.style.display = showPreview ? 'block' : 'none';

if (showPreview) { updatePreview(content.value); }

content.addEventListener('input', () => { updatePreview(content.value);

// If the content becomes empty, insert a space to trigger the save if (content.value.trim().length === 0) { if (!isEmptyNote) { content.value = ' '; // Automatically add a space if content is empty saveStatus.textContent = 'Note will be deleted'; // Notify the user that the note will be deleted isEmptyNote = true; // Flag that space was added } } else { // If content is no longer empty, remove the space and update status if (isEmptyNote) { content.value = content.value.trim(); // Remove the space if user starts typing again isEmptyNote = false; // Reset the flag } saveStatus.textContent = 'Saving...'; // Show saving status while content is being edited }

debounceSaveContent(content.value); });

window.addEventListener('resize', () => { if (previewToggle.checked) { syncScrollPositions('editor'); } });

// 初始化行号显示状态 const showLineNumbers = localStorage.getItem('show-line-numbers') !== 'false'; document.getElementById('line-numbers-toggle').checked = showLineNumbers; const lineNumbers = document.querySelector('.line-numbers'); lineNumbers.classList.toggle('hidden', !showLineNumbers);

// 初始化行号和文本统计 updateLineNumbers(); updateTextStats();

// 初始化工具栏显示状态 const showToolbar = localStorage.getItem('show-toolbar') !== 'false'; document.getElementById('toolbar-toggle').checked = showToolbar; const toolbar = document.querySelector('.toolbar'); const container = document.querySelector('.container'); toolbar.classList.toggle('hidden', !showToolbar); container.classList.toggle('toolbar-hidden', !showToolbar);

// 恢复全屏状态 const savedFullscreen = localStorage.getItem('preview-fullscreen') === 'true'; if (savedFullscreen) { togglePreviewFullscreen(); }

// 添加 Firefox 双击事件监听 const previewContent = document.querySelector('.preview-content'); if (previewContent) { previewContent.addEventListener('mousedown', (e) => { if (e.detail === 2) { // 检测双击 e.preventDefault(); // 阻止默认的文本选择 } }); }

// 添加工具栏鼠标滚动支持 let isMouseDown = false; let startX; let scrollLeft;

toolbar.addEventListener('mousedown', (e) => { isMouseDown = true; toolbar.classList.add('dragging'); startX = e.pageX - toolbar.offsetLeft; scrollLeft = toolbar.scrollLeft; });

toolbar.addEventListener('mouseleave', () => { isMouseDown = false; toolbar.classList.remove('dragging'); });

toolbar.addEventListener('mouseup', () => { isMouseDown = false; toolbar.classList.remove('dragging'); });

toolbar.addEventListener('mousemove', (e) => { if (!isMouseDown) return; e.preventDefault(); const x = e.pageX - toolbar.offsetLeft; const walk = (x - startX) * 2; toolbar.scrollLeft = scrollLeft - walk; });

// 支持鼠标滚轮横向滚动 toolbar.addEventListener('wheel', (e) => { e.preventDefault(); toolbar.scrollLeft += e.deltaY; }); });

function togglePreview() { const showPreview = previewToggle.checked; const editorContainer = document.querySelector('.editor-container');

editorContainer.classList.toggle('preview-mode', showPreview); preview.style.display = showPreview ? 'block' : 'none';

localStorage.setItem('preview', showPreview);

if (showPreview) { updatePreview(content.value); setTimeout(() => syncScrollPositions('editor'), 100); } }

function toggleSyncScroll() { isSyncScrollEnabled = syncScrollToggle.checked; localStorage.setItem('sync-scroll', isSyncScrollEnabled); }

function updatePreview(text) { if (previewToggle.checked) { const previewContent = preview.querySelector('.preview-content'); const scrollPos = previewContent.scrollTop;

// 渲染Markdown内容 previewContent.innerHTML = marked.parse(text);

// 渲染LaTeX公式 renderMathInElement(previewContent, { delimiters: [ {left: '$$', right: '$$', display: true}, {left: '$', right: '$', display: false}, // 移除普通方括号作为数学公式的标记 // {left: '\\[', right: '\\]', display: true}, // 替换为更明确的数学公式标记 {left: 'math\\[', right: '\\]', display: true}, {left: '\\(', right: '\\)', display: false}, {left: '\\begin{align}', right: '\\end{align}', display: true}, // 添加对 align 环境的支持 ], throwOnError: false, output: 'html' });

// 渲染Mermaid图表 mermaid.initialize({ startOnLoad: false, theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default', securityLevel: 'loose', fontFamily: 'var(--font-family)', });

const mermaidDiagrams = previewContent.querySelectorAll('pre code.language-mermaid'); mermaidDiagrams.forEach(async (diagram, index) => { try { const pre = diagram.parentElement; const mermaidDiv = document.createElement('div'); mermaidDiv.className = 'mermaid'; mermaidDiv.id = 'mermaid-' + Date.now() + '-' + index; // 添加唯一ID mermaidDiv.textContent = diagram.textContent; pre.parentNode.replaceChild(mermaidDiv, pre); } catch (error) { console.error('Mermaid渲染错误:', error); } });

// 等待所有图表渲染完成 if (mermaidDiagrams.length > 0) { mermaid.run(); }

// 更新预览区统计信息 updatePreviewStats(text);

// 为所有代码块添加复制按钮和语言标签 const codeBlocks = previewContent.querySelectorAll('pre code'); codeBlocks.forEach(code => { const pre = code.parentElement; // 跳过Mermaid图表 if (code.classList.contains('language-mermaid')) return;

// 获取语言类名 const langClass = Array.from(code.classList).find(cl => cl.startsWith('language-')); const language = langClass ? langClass.replace('language-', '') : '代码'; // 设置语言标签 pre.setAttribute('data-language', language);

// 添加复制按钮 if (!pre.querySelector('.copy-button')) { const button = document.createElement('button'); button.className = 'copy-button'; button.innerHTML = '📋 复制'; button.onclick = (e) => { e.preventDefault(); copyToClipboard(code.innerText, button); }; pre.appendChild(button); } });

// 重新应用代码高亮 hljs.highlightAll();

previewContent.scrollTop = scrollPos; } }

async function copyToClipboard(text, button) { try { await navigator.clipboard.writeText(text); const originalText = button.innerHTML; button.innerHTML = '✅ 已复制'; button.classList.add('copied'); setTimeout(() => { button.innerHTML = originalText; button.classList.remove('copied'); }, 2000); } catch (err) { console.error('复制失败:', err); button.innerHTML = '❌ 复制失败'; setTimeout(() => { button.innerHTML = '📋 复制'; }, 2000); } }

function handleEditorScroll() { if (!isPreviewScrolling && previewToggle.checked && isSyncScrollEnabled) { isEditorScrolling = true; syncScrollPositions('editor'); setTimeout(() => { isEditorScrolling = false; }, 50); } }

function handlePreviewScroll() { if (!isEditorScrolling && previewToggle.checked && isSyncScrollEnabled) { isPreviewScrolling = true; const previewContent = preview.querySelector('.preview-content'); const editorHeight = content.scrollHeight - content.clientHeight; const previewHeight = previewContent.scrollHeight - previewContent.clientHeight;

if (previewHeight > 0) { const scrollPercentage = previewContent.scrollTop / previewHeight; content.scrollTop = scrollPercentage * editorHeight; } setTimeout(() => { isPreviewScrolling = false; }, 50); } }

function syncScrollPositions(source) { if (!previewToggle.checked || !isSyncScrollEnabled) return;

const editor = content; const previewContent = preview.querySelector('.preview-content'); const editorHeight = editor.scrollHeight - editor.clientHeight; const previewHeight = previewContent.scrollHeight - previewContent.clientHeight;

if (source === 'editor' && editorHeight > 0) { const scrollPercentage = editor.scrollTop / editorHeight; previewContent.scrollTop = scrollPercentage * previewHeight; } else if (source === 'preview' && previewHeight > 0) { const scrollPercentage = previewContent.scrollTop / previewHeight; editor.scrollTop = scrollPercentage * editorHeight; } }

function debounceSaveContent(text) { clearTimeout(saveTimeout); saveTimeout = setTimeout(() => saveContent(text), 1000); }

async function saveContent(text) { try { const response = await fetch(window.location.pathname, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'text=' + encodeURIComponent(text), });

saveStatus.textContent = response.status === 204 ? '已保存' : '笔记将被删除'; } catch (error) { saveStatus.textContent = '保存失败'; }

setTimeout(() => { saveStatus.textContent = ''; }, 2000); }

// 添加 Toast 提示框功能 function showToast(message, type = 'info', duration = 3000) { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = 'toast ' + type;

// 设置图标 const icons = { success: '✅', error: '❌', info: 'ℹ️', warning: '⚠️' };

toast.innerHTML = '<span class="toast-icon">' + (icons[type] || icons.info) + '</span>' + '<span class="toast-message">' + message + '</span>' + '<span class="toast-close" onclick="this.parentElement.remove()">✕</span>';

container.appendChild(toast);

// 触发重排以启动动画 void toast.offsetWidth; toast.classList.add('show');

// 自动关闭 setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, duration); }

// 替换原有的 alert 调用 function copyNoteLink() { const link = window.location.href; navigator.clipboard.writeText(link).then(() => { showToast('笔记链接已复制到剪贴板!', 'success'); }).catch(() => { showToast('复制失败,请手动复制链接。', 'error'); }); }

function toggleDarkMode() { const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme);

// 更新Mermaid主题 mermaid.initialize({ startOnLoad: false, theme: theme === 'dark' ? 'dark' : 'default', securityLevel: 'loose', fontFamily: 'var(--font-family)', });

// 重新渲染预览内容以更新Mermaid图表 if (previewToggle.checked) { updatePreview(content.value); } }

// Markdown 编辑功能 function applyMarkdown(type) { const textarea = document.getElementById('content'); const start = textarea.selectionStart; const end = textarea.selectionEnd; const text = textarea.value; let result;

switch(type) { case 'bold': result = insertAround(text, start, end, ''); break; case 'italic': result = insertAround(text, start, end, '_'); break; case 'heading': result = insertAtLineStart(text, start, '# '); break; case 'strikethrough': result = insertAround(text, start, end, '~~'); break; case 'list': result = insertAtLineStart(text, start, '- '); break; case 'ordered-list': result = insertAtLineStart(text, start, '1. '); break; case 'task': result = insertAtLineStart(text, start, '- [ ] '); break; case 'quote': result = insertAtLineStart(text, start, '> '); break; case 'code': result = insertAround(text, start, end, '\\\\\n', '\\n\\\'); break; case 'table': result = insertTable(text, start); break; case 'divider': result = insertDivider(text, start); break; case 'link': result = insertLink(text, start, end); break; case 'image': result = insertImage(text, start, end); break; case 'latex-inline': result = insertAround(text, start, end, '$'); break; case 'latex-block': result = insertAround(text, start, end, '$$\\n', '\\n$$'); break; case 'mermaid': result = insertMermaid(text, start); break; }

if (result) { textarea.value = result.text; textarea.selectionStart = result.selectionStart; textarea.selectionEnd = result.selectionEnd; textarea.focus(); updatePreview(textarea.value); debounceSaveContent(textarea.value); } }

// 辅助函数:在选中文本周围插入标记 function insertAround(text, start, end, mark, endMark = mark) { const selection = text.substring(start, end); const before = text.substring(0, start); const after = text.substring(end); const newText = before + mark + selection + endMark + after; return { text: newText, selectionStart: start + mark.length, selectionEnd: end + mark.length }; }

// 辅助函数:在行首插入标记 function insertAtLineStart(text, start, mark) { const lines = text.split('\\n'); let currentPos = 0; let targetLine = 0;

// 找到光标所在行 for (let i = 0; i < lines.length; i++) { if (currentPos + lines[i].length >= start) { targetLine = i; break; } currentPos += lines[i].length + 1; }

// 在目标行前添加标记 lines[targetLine] = mark + lines[targetLine];

return { text: lines.join('\\n'), selectionStart: currentPos + mark.length, selectionEnd: currentPos + mark.length }; }

// 插入表格 function insertTable(text, start) { const tableTemplate = '\\n| 标题1 | 标题2 | 标题3 |\\n|--------|--------|--------|\\n| 内容1 | 内容2 | 内容3 |\\n'; const newText = text.substring(0, start) + tableTemplate + text.substring(start); return { text: newText, selectionStart: start + tableTemplate.length, selectionEnd: start + tableTemplate.length }; }

// 添加插入Mermaid图表的函数 function insertMermaid(text, start) { const mermaidTemplate = '\\n\\\mermaid\\ngraph TD\\n A[开始] --> B[步骤1]\\n B --> C[步骤2]\\n C --> D[结束]\\n\\\\\n'; const newText = text.substring(0, start) + mermaidTemplate + text.substring(start); return { text: newText, selectionStart: start + mermaidTemplate.length, selectionEnd: start + mermaidTemplate.length }; }

// 插入链接 function insertLink(text, start, end) { const selection = text.substring(start, end).trim(); const link = selection || '链接文字'; const template = '' + link + ''; const newText = text.substring(0, start) + template + text.substring(end); return { text: newText, selectionStart: start + link.length + 3, selectionEnd: start + template.length - 1 }; }

// 插入图片 function insertImage(text, start, end) { const template = '图片描述'; return { text: text.substring(0, start) + template + text.substring(end), selectionStart: start + 2, selectionEnd: start + 6 }; }

// 插入分割线 function insertDivider(text, start) { const divider = '\\n---\\n'; const newText = text.substring(0, start) + divider + text.substring(start); return { text: newText, selectionStart: start + divider.length, selectionEnd: start + divider.length }; }

// 字体设置 function applyFont() { const select = document.getElementById('font-family'); const textarea = document.getElementById('content'); const fontMap = { 'default': '', 'serif': 'SimSun, serif', 'yahei': '"Microsoft YaHei", "微软雅黑", sans-serif', 'kaiti': 'KaiTi, "楷体", serif', 'heiti': 'SimHei, "黑体", sans-serif', 'fangsong': 'FangSong, "仿宋", serif', 'songti': 'NSimSun, "新宋体", serif', 'monospace': 'Monaco, Consolas, monospace', 'arial': 'Arial, sans-serif', 'times': '"Times New Roman", Times, serif', 'helvetica': 'Helvetica, Arial, sans-serif' }; textarea.style.fontFamily = fontMap[select.value] || ''; }

// 字号设置 function applyFontSize() { const select = document.getElementById('font-size'); const textarea = document.getElementById('content'); textarea.style.fontSize = select.value + 'px'; }

// Emoji 选择器 function showEmojiPicker() { const picker = document.getElementById('emoji-picker'); const overlay = document.querySelector('.emoji-picker-overlay') || createOverlay();

if (picker.style.display === 'block') { picker.style.display = 'none'; overlay.style.display = 'none'; return; }

// 如果是第一次显示,初始化表情列表 if (!picker.children.length) { const gridContainer = document.createElement('div'); gridContainer.className = 'emoji-picker-grid';

const emojis = ['😀', '😃', '😄', '😁', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳', '😏', '😒', '❤️', '🙌', '👍', '🎉', '✨', '🔥', '💡', '⭐', '💪', '🎯', '✅', '❌', '💬', '👀', '🎨', '🎮', '🎵', '🎬', '📚', '💻', '🔍', '⚡', '🌈', '🍀'];

emojis.forEach(emoji => { const button = document.createElement('button'); button.textContent = emoji; button.onclick = () => { insertEmoji(emoji); picker.style.display = 'none'; overlay.style.display = 'none'; }; gridContainer.appendChild(button); });

picker.appendChild(gridContainer); }

overlay.style.display = 'block'; picker.style.display = 'block'; }

// 创建遮罩层 function createOverlay() { const overlay = document.createElement('div'); overlay.className = 'emoji-picker-overlay'; document.body.appendChild(overlay);

overlay.addEventListener('click', () => { const picker = document.getElementById('emoji-picker'); picker.style.display = 'none'; overlay.style.display = 'none'; });

return overlay; }

// 插入表情 function insertEmoji(emoji) { const textarea = document.getElementById('content'); const start = textarea.selectionStart; const end = textarea.selectionEnd; const text = textarea.value;

textarea.value = text.substring(0, start) + emoji + text.substring(end); textarea.selectionStart = textarea.selectionEnd = start + emoji.length; textarea.focus();

updatePreview(textarea.value); debounceSaveContent(textarea.value); }

// 添加快捷键支持 document.addEventListener('keydown', function(e) { if (!e.ctrlKey) return;

const shortcuts = { 'b': 'bold', 'i': 'italic', 'h': 'heading', 'd': 'strikethrough', 'u': 'list', 'o': 'ordered-list', 't': 'task', 'q': 'quote', 'k': 'code', 'l': 'link', 'p': 'image' };

if (shortcuts[e.key.toLowerCase()]) { e.preventDefault(); applyMarkdown(shortcuts[e.key.toLowerCase()]); } });

// 更新行号和调整布局 function updateLineNumbers() { const textarea = document.getElementById('content'); const lineNumbers = document.querySelector('.line-numbers'); const fullText = textarea.value; const totalLines = fullText.endsWith('\\n') ? fullText.split('\\n').length : fullText.split('\\n').length;

// 清空现有行号 lineNumbers.innerHTML = '';

// 为每一行创建行号 span 元素 for (let i = 0; i < totalLines; i++) { const span = document.createElement('span'); span.textContent = i + 1; lineNumbers.appendChild(span); }

// 确保至少有一行 if (totalLines === 0) { const span = document.createElement('span'); span.textContent = '1'; lineNumbers.appendChild(span); }

// 调整行号区域宽度 const maxLineNumber = totalLines || 1; const minWidth = 28; // 最小宽度 const digitWidth = 8; // 每个数字的估计宽度 const newWidth = Math.max(minWidth, String(maxLineNumber).length * digitWidth + 8); lineNumbers.style.minWidth = newWidth + 'px'; }

// 监听窗口大小变化 window.addEventListener('resize', () => { updateLineNumbers(); });

// 更新文本统计 function updateTextStats() { const textarea = document.getElementById('content'); const text = textarea.value; const position = textarea.selectionStart;

// 计算字符数(不包括空格和换行) const charCount = text.replace(/\s/g, '').length;

// 计算单词数 const wordCount = text.trim().split(/\s+/).filter(word => word.length > 0).length;

// 计算当前行和列 const textBeforeCursor = text.substring(0, position); const lines = textBeforeCursor.split('\\n'); const currentLine = lines.length; const currentLineContent = lines[lines.length - 1]; const currentColumn = currentLineContent ? currentLineContent.length + 1 : 1;

// 更新显示(使用更紧凑的格式) document.getElementById('char-count').textContent = String(charCount); document.getElementById('word-count').textContent = String(wordCount); document.getElementById('line-count').textContent = String(currentLine); document.getElementById('column-count').textContent = String(currentColumn); }

// 同步滚动行号 function syncLineNumbersScroll() { const textarea = document.getElementById('content'); const lineNumbers = document.querySelector('.line-numbers');

// 计算最大可滚动高度 const maxScroll = textarea.scrollHeight - textarea.clientHeight; const currentScroll = textarea.scrollTop;

// 确保不会滚动过头 if (currentScroll <= maxScroll) { lineNumbers.scrollTop = currentScroll; } }

// 监听输入事件 content.addEventListener('input', () => { updatePreview(content.value); updateLineNumbers(); updateTextStats(); debounceSaveContent(content.value); // 输入时也同步滚动 syncLineNumbersScroll(); });

// 监听光标位置变化 content.addEventListener('keyup', updateTextStats); content.addEventListener('click', updateTextStats); content.addEventListener('scroll', syncLineNumbersScroll);

// 切换行号显示/隐藏 function toggleLineNumbers() { const lineNumbers = document.querySelector('.line-numbers'); const isVisible = document.getElementById('line-numbers-toggle').checked; lineNumbers.classList.toggle('hidden', !isVisible); localStorage.setItem('show-line-numbers', isVisible); }

// 切换工具栏显示/隐藏 function toggleToolbar() { const isVisible = document.getElementById('toolbar-toggle').checked; const toolbar = document.querySelector('.toolbar'); const container = document.querySelector('.container');

toolbar.classList.toggle('hidden', !isVisible); container.classList.toggle('toolbar-hidden', !isVisible);

localStorage.setItem('show-toolbar', isVisible);

// 触发一次窗口大小变化事件,以更新编辑器布局 window.dispatchEvent(new Event('resize')); }

// 添加预览区统计功能 function updatePreviewStats(text) { // 计算字符数(不包括空格和换行) const charCount = text.replace(/\\s/g, '').length;

// 计算单词数 const wordCount = text.trim().split(/\\s+/).filter(word => word.length > 0).length;

// 计算段落数(通过空行分隔) const paragraphCount = text.split(/\\n\\s*\\n/).filter(para => para.trim().length > 0).length;

// 更新显示 document.getElementById('preview-char-count').textContent = String(charCount); document.getElementById('preview-word-count').textContent = String(wordCount); document.getElementById('preview-paragraph-count').textContent = String(paragraphCount); }

// 添加全屏切换功能 function togglePreviewFullscreen() { const preview = document.getElementById('preview'); const fullscreenIcon = document.getElementById('fullscreen-icon'); const isFullscreen = preview.classList.contains('fullscreen');

if (isFullscreen) { // 如果已经是容器全屏,切换到真实全屏 if (document.fullscreenElement) { document.exitFullscreen(); } else { preview.requestFullscreen(); } } else { // 首次点击进入容器全屏 preview.classList.add('fullscreen'); fullscreenIcon.textContent = '⛶'; }

// 触发resize事件以更新布局 window.dispatchEvent(new Event('resize'));

// 保存全屏状态到本地存储 localStorage.setItem('preview-fullscreen', preview.classList.contains('fullscreen')); }

// ESC键退出全屏 function handleFullscreenEsc(e) { if (e.key === 'Escape') { const preview = document.getElementById('preview'); if (preview.classList.contains('fullscreen')) { preview.classList.remove('fullscreen'); const fullscreenIcon = document.getElementById('fullscreen-icon'); fullscreenIcon.textContent = '⛶'; localStorage.setItem('preview-fullscreen', false); } } }

// 监听浏览器全屏变化事件 document.addEventListener('fullscreenchange', () => { const preview = document.getElementById('preview'); const fullscreenIcon = document.getElementById('fullscreen-icon');

if (!document.fullscreenElement && preview.classList.contains('fullscreen')) { // 从真实全屏退出时,也退出容器全屏 preview.classList.remove('fullscreen'); fullscreenIcon.textContent = '⛶'; localStorage.setItem('preview-fullscreen', false); } });

// 双击进入/退出全屏(移动端支持) function handlePreviewDoubleTap(e) { const preview = document.getElementById('preview'); // 确保不是在状态栏上双击 if (!e.target.closest('.preview-status-bar')) { e.preventDefault(); // 阻止默认行为 // 清除任何可能的文本选择 window.getSelection().removeAllRanges(); togglePreviewFullscreen(); } }

// 添加密码相关功能 let isPasswordProtected = false; let isPasswordVerified = false; let currentPasswordAction = '';

async function checkPasswordProtection() { try { const response = await fetch(window.location.pathname + '/password-check', { method: 'GET', });

if (response.status === 200) { isPasswordProtected = true; document.body.classList.add('password-protected'); // 添加密码保护状态类 showPasswordVerification(); } updatePasswordStatus(); } catch (error) { console.error('检查密码保护状态失败:', error); } }

function showPasswordMessage(message, isSuccess = false) { const messageEl = document.getElementById('password-message'); messageEl.textContent = message; messageEl.classList.toggle('success', isSuccess); messageEl.classList.add('show');

// 1秒后自动隐藏成功消息 if (isSuccess) { setTimeout(() => { messageEl.classList.remove('show'); }, 1000); } }

function clearPasswordMessage() { const messageEl = document.getElementById('password-message'); messageEl.classList.remove('show', 'success'); messageEl.textContent = ''; }

function showPasswordDialog() { const dialog = document.getElementById('password-dialog'); const overlay = document.getElementById('password-overlay'); const title = document.getElementById('password-dialog-title'); const actionBtn = document.getElementById('password-action-btn'); const passwordInput = document.getElementById('password-input');

clearPasswordMessage();

if (isPasswordProtected && isPasswordVerified) { title.textContent = '移除密码保护'; actionBtn.textContent = '移除'; currentPasswordAction = 'remove'; } else if (!isPasswordProtected) { title.textContent = '设置密码保护'; actionBtn.textContent = '设置'; currentPasswordAction = 'set'; }

dialog.style.display = 'block'; overlay.style.display = 'block'; passwordInput.focus();

// 添加回车键事件监听 passwordInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); handlePasswordAction(); } }; }

function showPasswordVerification() { const dialog = document.getElementById('password-dialog'); const overlay = document.getElementById('password-overlay'); const title = document.getElementById('password-dialog-title'); const actionBtn = document.getElementById('password-action-btn'); const passwordInput = document.getElementById('password-input');

clearPasswordMessage();

title.textContent = '请输入密码'; actionBtn.textContent = '验证'; currentPasswordAction = 'verify';

dialog.style.display = 'block'; overlay.style.display = 'block'; passwordInput.focus();

// 添加回车键事件监听 passwordInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); handlePasswordAction(); } }; }

function closePasswordDialog() { const dialog = document.getElementById('password-dialog'); const overlay = document.getElementById('password-overlay'); const passwordInput = document.getElementById('password-input');

dialog.style.display = 'none'; overlay.style.display = 'none'; passwordInput.value = ''; passwordInput.onkeydown = null; // 移除回车键事件监听 clearPasswordMessage(); }

async function handlePasswordAction() { const password = document.getElementById('password-input').value; if (!password) { showPasswordMessage('请输入密码'); showToast('请输入密码', 'warning'); return; }

try { let response; switch (currentPasswordAction) { case 'set': response = await fetch(window.location.pathname + '/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: await hashPassword(password) }) }); if (response.status === 200) { isPasswordProtected = true; isPasswordVerified = true; document.body.classList.add('password-protected'); updatePasswordStatus(); showPasswordMessage('密码保护已设置', true); showToast('密码保护已设置', 'success'); setTimeout(closePasswordDialog, 1500); } break;

case 'verify': response = await fetch(window.location.pathname + '/password-verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: await hashPassword(password) }) }); if (response.status === 200) { isPasswordVerified = true; document.body.classList.remove('password-protected'); updatePasswordStatus(); showPasswordMessage('密码验证成功', true); showToast('密码验证成功', 'success'); setTimeout(closePasswordDialog, 1500); } else { showPasswordMessage('密码错误'); showToast('密码错误', 'error'); return; } break;

case 'remove': response = await fetch(window.location.pathname + '/password', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: await hashPassword(password) }) }); if (response.status === 200) { isPasswordProtected = false; isPasswordVerified = false; document.body.classList.remove('password-protected'); updatePasswordStatus(); showPasswordMessage('密码保护已移除', true); showToast('密码保护已移除', 'success'); setTimeout(closePasswordDialog, 1500); } else { showPasswordMessage('密码错误'); showToast('密码错误', 'error'); return; } break; } } catch (error) { console.error('密码操作失败:', error); showPasswordMessage('操作失败,请重试'); showToast('操作失败,请重试', 'error'); } }

async function hashPassword(password) { const encoder = new TextEncoder(); const data = encoder.encode(password); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); }

function updatePasswordStatus() { const icon = document.getElementById('password-status-icon'); const label = icon.nextElementSibling; if (isPasswordProtected) { icon.textContent = '🔒'; icon.title = '已启用密码保护'; label.textContent = '已加密'; } else { icon.textContent = '🔓'; icon.title = '未启用密码保护'; label.textContent = '密码保护'; } }

// 在页面加载时检查密码保护状态 document.addEventListener('DOMContentLoaded', async () => { await checkPasswordProtection(); // ... existing DOMContentLoaded code ... });

// 修改复制全部内容功能,添加动效 async function copyAllContent() { const copyAllItem = document.querySelector('.copy-all-item'); const textarea = document.getElementById('content'); const text = textarea.value;

try { await navigator.clipboard.writeText(text); copyAllItem.classList.add('copied'); setTimeout(() => copyAllItem.classList.remove('copied'), 500); showToast('已复制全部内容到剪贴板', 'success'); } catch (err) { console.error('复制失败:', err); showToast('复制失败,请重试', 'error'); } }

async function shareNote() { const preview = document.querySelector('.preview-content'); if (!preview) { showToast('请先开启预览模式', 'warning'); return; }

try { // 生成分享ID const shareId = Array.from({length: 8}, () => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'[Math.floor(Math.random() * 62)]).join('');

// 准备分享数据 const shareData = { content: preview.innerHTML, createTime: new Date().toISOString(), lastEditTime: new Date().toISOString(), visitCount: 0 };

// 保存分享数据 const response = await fetch('/share/' + shareId, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(shareData) });

if (response.ok) { // 生成分享链接 const shareUrl = window.location.origin + '/share/' + shareId;

// 复制链接到剪贴板 await navigator.clipboard.writeText(shareUrl); showToast('分享链接已复制到剪贴板', 'success'); } else { showToast('分享失败,请重试', 'error'); } } catch (error) { console.error('分享失败:', error); showToast('分享失败,请重试', 'error'); } }

// 图片点击放大 document.addEventListener('DOMContentLoaded', () => { const content = document.querySelector('.content'); const overlay = document.getElementById('imageOverlay');

content.addEventListener('click', (e) => { if (e.target.tagName === 'IMG' && !e.target.classList.contains('enlarged')) { e.target.classList.add('enlarged'); overlay.classList.add('active'); document.body.style.overflow = 'hidden'; } }); });

// 关闭放大的图片 function closeEnlargedImage() { const enlargedImage = document.querySelector('.enlarged'); const overlay = document.getElementById('imageOverlay');

if (enlargedImage) { enlargedImage.classList.remove('enlarged'); overlay.classList.remove('active'); document.body.style.overflow = ''; } }

// ESC键关闭放大图片 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeEnlargedImage(); } });

// 图片加载错误处理 document.addEventListener('DOMContentLoaded', () => { const images = document.querySelectorAll('.content img'); images.forEach(img => { img.onerror = () => { img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; }; }); }); </script> </body> </html>; }

/ * 从KV存储获取笔记内容 * @param {string} notePath - 笔记路径 * @returns {Promise<string>} 笔记内容 */ async function getNoteContent(notePath) { const encryptedText = await NOTES_KV.get(notePath); if (!encryptedText) return '';

const key = await generateEncryptionKey(notePath); return await decryptText(encryptedText, key); }

/ * 保存笔记内容到KV存储 * @param {string} notePath - 笔记路径 * @param {string} text - 笔记内容 */ async function saveNoteContent(notePath, text) { const key = await generateEncryptionKey(notePath); const encryptedText = await encryptText(text, key); await NOTES_KV.put(notePath, encryptedText); }

/ * 从KV存储删除笔记 * @param {string} notePath - 要删除的笔记路径 */ async function deleteNoteContent(notePath) { await NOTES_KV.delete(notePath); }

/ * 处理分享页面请求 * @param {string} shareId - 分享ID * @returns {Response} 响应对象 */ async function handleShareRequest(shareId) { // 获取分享数据 const shareKey = 'share_' + shareId; const shareData = await NOTES_KV.get(shareKey);

if (!shareData) { return new Response('分享内容不存在或已过期', { status: 404 }); }

try { const data = JSON.parse(shareData); // 更新访问次数 data.visitCount++; await NOTES_KV.put(shareKey, JSON.stringify(data));

// 生成分享页面 const html = generateShareHTML(shareId, data); return new Response(html, { headers: { 'Content-Type': 'text/html;charset=utf-8' } }); } catch (error) { return new Response('加载分享内容失败', { status: 500 }); } }

/ * 生成分享页面HTML * @param {string} shareId - 分享ID * @param {Object} data - 分享数据 * @returns {string} HTML内容 */ function generateShareHTML(shareId, data) { const shareTime = new Date(data.createTime).toLocaleString('zh-CN');

return <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>分享的笔记</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" media="(prefers-color-scheme: dark)"> <!-- 添加KaTeX依赖 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"> <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script> <!-- 添加Mermaid依赖 --> <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script> <style> :root { --primary-color: #4e92d1; --secondary-color: #6c757d; --bg-color: #ffffff; --text-color: #333333; --border-color: #e0e0e0; --container-bg: #ffffff; --editor-bg: #f8f8f8; --shadow-color: rgba(0, 0, 0, 0.1); --hover-color: #f0f0f0; --link-color: #0366d6; --link-hover-color: #0969da; --link-visited-color: #6f42c1; }

[data-theme="dark"] { --primary-color: #a2c2f5; --secondary-color: #9ca3af; --bg-color: #1a1a1a; --text-color: #f1f1f1; --border-color: #404040; --container-bg: #2a2a2a; --editor-bg: #333333; --shadow-color: rgba(0, 0, 0, 0.3); --hover-color: #3a3a3a; --link-color: #58a6ff; --link-hover-color: #79b8ff; --link-visited-color: #bc8cff; }

* { box-sizing: border-box; margin: 0; padding: 0; }

body { margin: 0; background: var(--bg-color); color: var(--text-color); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 16px; line-height: 1.6; transition: all 0.3s ease; }

.container { width: 100%; max-width: 100%; margin: 0 auto; padding: 20px; min-height: 100vh; background-color: var(--container-bg); }

@media (min-width: 1200px) { .container { max-width: 95%; box-shadow: 0 0 20px var(--shadow-color); } }

.info-bar { display: flex; justify-content: space-between; align-items: center; padding: 15px; background: var(--editor-bg); border-radius: 12px; margin-bottom: 20px; font-size: 14px; border: 1px solid var(--border-color); overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; gap: 15px; min-width: 0; }

.info-bar::-webkit-scrollbar { height: 6px; width: 6px; }

.info-bar::-webkit-scrollbar-track { background: transparent; } .info-bar::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 3px; }

.info-bar::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

.info-left { display: flex; gap: 15px; flex-shrink: 0; margin-right: auto; }

.info-right { display: flex; align-items: center; gap: 15px; flex-shrink: 0; margin-left: auto; }

.info-item { display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 6px; transition: all 0.2s ease; white-space: nowrap; flex-shrink: 0; }

.info-item:hover { background: var(--hover-color); }

.theme-toggle { display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 6px; border: none; color: var(--text-color); cursor: pointer; font-size: 14px; transition: all 0.2s ease; background: transparent; white-space: nowrap; flex-shrink: 0; }

.theme-toggle:hover { background: var(--hover-color); }

.theme-toggle:active { transform: scale(0.95); }

.theme-toggle .sun-icon, .theme-toggle .moon-icon { display: none; font-size: 1.2rem; }

.theme-toggle .label { font-size: 14px; }

[data-theme="dark"] .moon-icon { display: block; }

[data-theme="light"] .sun-icon { display: block; }

@media (max-width: 768px) { .theme-toggle { padding: 4px 8px; }

.theme-toggle .label { font-size: 13px; } }

@media (max-width: 768px) { .info-bar { padding: 10px; gap: 10px; }

.info-left, .info-right { gap: 10px; }

.info-item { padding: 4px 8px; font-size: 13px; } }

.content { padding: 20px; border: 1px solid var(--border-color); border-radius: 12px; background: var(--editor-bg); overflow-x: auto; }

/ Markdown 内容样式 / .content > *:first-child { margin-top: 0; }

.content > *:last-child { margin-bottom: 0; }

.content h1, .content h2, .content h3, .content h4, .content h5, .content h6 { margin-top: 1.8em; margin-bottom: 0.8em; line-height: 1.2; color: var(--text-color); }

.content h1:first-child, .content h2:first-child, .content h3:first-child, .content h4:first-child, .content h5:first-child, .content h6:first-child { margin-top: 0; }

.content p { text-align: left !important; margin: 1.2em 0; line-height: 1.8; }

.content p:has(> a:only-child) { text-align: left !important; }

.content a { display: inline; color: var(--link-color); text-decoration: none; border-bottom: 1px solid transparent; transition: all 0.2s ease; padding: 2px 4px; margin: 0 -4px; border-radius: 4px; text-align: left !important; }

/ 处理单独一行的链接 / .content p > a:only-child { display: inline-block; text-align: left !important; width: fit-content; margin-left: 0; }

/ 处理图片链接的特殊情况 / .content p:has(img) { text-align: center; margin: 2em 0; }

.content a:hover { background: var(--hover-color); color: var(--link-hover-color); border-bottom-color: var(--link-hover-color); }

.content a:visited { color: var(--link-visited-color); }

.content a:visited:hover { background: var(--hover-color); border-bottom-color: var(--link-visited-color); }

.content a[href^="http"]::after { content: "↗"; display: inline; margin-left: 2px; font-size: 0.9em; opacity: 0.6; }

.content ul, .content ol { margin: 1em 0; padding-left: 1.5em; }

.content li { margin: 0.5em 0; }

.content blockquote { margin: 1.2em 0; padding: 1em 1.2em; border-left: 4px solid var(--border-color); background: var(--bg-color); border-radius: 0 4px 4px 0; display: flow-root; width: fit-content; max-width: 100%; box-shadow: 0 2px 4px var(--shadow-color); transition: all 0.2s ease; }

.content blockquote > *:first-child { margin-top: 0; }

.content blockquote > *:last-child { margin-bottom: 0; }

.content blockquote p { margin: 0.8em 0; line-height: 1.6; }

.content blockquote + blockquote { margin-top: -0.5em; }

.content blockquote blockquote { margin: 0.8em 0; border-left-color: var(--secondary-color); background: var(--editor-bg); box-shadow: none; }

.content pre { background: var(--editor-bg); padding: 1.2em 1em; border-radius: 8px; overflow: auto; position: relative; margin: 1.5em 0; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; line-height: 1.5; font-size: 0.95em; border: 1px solid var(--border-color); scrollbar-width: thin; scrollbar-color: var(--secondary-color) transparent; }

.content code { background: var(--editor-bg); padding: 0.2em 0.4em; margin: 0 0.2em; border-radius: 4px; font-size: 0.9em; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; border: 1px solid var(--border-color); }

.content pre code { background: none; padding: 0; margin: 0; font-size: 0.95em; white-space: pre; word-break: normal; word-wrap: normal; line-height: inherit; tab-size: 2; hyphens: none; border: none; }

.content pre::-webkit-scrollbar { width: 6px; height: 6px; }

.content pre::-webkit-scrollbar-track { background: transparent; }

.content pre::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 3px; border: 2px solid var(--editor-bg); }

.content pre::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

.content pre::before { content: attr(data-language); position: absolute; top: 0.5em; right: 0.5em; font-size: 0.85em; color: var(--secondary-color); padding: 0.2em 0.5em; border-radius: 3px; background: var(--container-bg); opacity: 0.8; transition: opacity 0.2s ease; }

.content pre:hover::before { opacity: 1; }

.copy-button { position: absolute; top: 0.5em; right: 0.5em; padding: 0.2em 0.5em; font-size: 0.85em; color: var(--text-color); background: var(--container-bg); border: 1px solid var(--border-color); border-radius: 3px; cursor: pointer; opacity: 0; transition: all 0.2s ease; display: flex; align-items: center; gap: 4px; }

.content pre:hover .copy-button { opacity: 0.8; }

.copy-button:hover { opacity: 1 !important; background: var(--hover-color); }

.copy-button.copied { color: #4caf50; border-color: #4caf50; opacity: 1; }

/ Toast 提示框样式 / .toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 10px; pointer-events: none; }

.toast { background: var(--container-bg); color: var(--text-color); padding: 12px 24px; border-radius: 8px; box-shadow: 0 4px 12px var(--shadow-color); font-size: 14px; display: flex; align-items: center; gap: 8px; opacity: 0; transform: translateX(100%); transition: all 0.3s ease; border: 1px solid var(--border-color); pointer-events: all; max-width: 300px; word-break: break-word; }

.toast.show { opacity: 1; transform: translateX(0); }

.toast.success { border-left: 4px solid #4caf50; }

.toast.error { border-left: 4px solid #f44336; }

.toast-icon { font-size: 18px; flex-shrink: 0; }

.toast-message { flex: 1; margin-right: 8px; }

.toast-close { cursor: pointer; opacity: 0.7; transition: opacity 0.2s ease; padding: 4px; margin: -4px; border-radius: 4px; }

.toast-close:hover { opacity: 1; background: var(--hover-color); }

/ 表格样式 / .content table { width: 100%; border-collapse: collapse; margin: 1.5em 0; overflow-x: auto; display: block; }

.content th, .content td { border: 1px solid var(--border-color); padding: 8px 12px; text-align: left; }

.content th { background-color: var(--hover-color); font-weight: 600; }

.content tr:nth-child(even) { background-color: var(--editor-bg); }

.content tr:hover { background-color: var(--hover-color); }

/ 移动端表格适配 / @media (max-width: 768px) { .content table { font-size: 14px; }

.content th, .content td { padding: 6px 8px; } }

/ 表格滚动条样式 / .content table::-webkit-scrollbar { height: 8px; width: 8px; }

.content table::-webkit-scrollbar-track { background: transparent; }

.content table::-webkit-scrollbar-thumb { background-color: var(--secondary-color); border-radius: 4px; border: 2px solid var(--editor-bg); }

.content table::-webkit-scrollbar-thumb:hover { background-color: var(--text-color); }

/ 图片样式 / .content img { max-width: 100%; height: auto; margin: 1em 0; border-radius: 8px; display: block; box-shadow: 0 2px 8px var(--shadow-color); transition: all 0.3s ease; opacity: 0; animation: fadeIn 0.5s ease forwards; }

/ 图片容器 / .content p:has(img) { text-align: center; margin: 2em 0; }

/ 图片悬停效果 / .content img:hover { transform: scale(1.01); box-shadow: 0 4px 12px var(--shadow-color); }

/ 图片标题样式 / .content img + em { display: block; text-align: center; color: var(--secondary-color); font-size: 0.9em; margin-top: 0.5em; }

/ 移动端图片适配 / @media (max-width: 768px) { .content img { border-radius: 6px; margin: 0.8em 0; }

.content p:has(img) { margin: 1.5em 0; }

/ 禁用移动端图片缩放动画 / .content img:hover { transform: none; box-shadow: 0 2px 8px var(--shadow-color); } }

/ 图片加载动画 / @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/ 图片加载失败样式 / .content img:not([src]), .content img[src=""], .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="] { position: relative; min-height: 100px; background: var(--editor-bg); border: 1px dashed var(--border-color); display: flex; align-items: center; justify-content: center; }

.content img:not([src])::after, .content img[src=""]::after, .content img[src*="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="]::after { content: "图片加载失败"; position: absolute; color: var(--secondary-color); font-size: 0.9em; }

/ 大图查看模式 / .content img.enlarged { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 90vw; max-height: 90vh; object-fit: contain; z-index: 1000; cursor: zoom-out; margin: 0; padding: 0; background: var(--bg-color); box-shadow: 0 0 20px var(--shadow-color); }

/ 大图查看遮罩层 / .image-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); z-index: 999; display: none; opacity: 0; transition: opacity 0.3s ease; }

.image-overlay.active { display: block; opacity: 1; } </style> </head> <body> <div class="container"> <div class="info-bar"> <div class="info-left"> <div class="info-item"> <span>📝 笔记</span> <span>${shareId}</span> </div> <div class="info-item"> <span>🕒 分享于</span> <span>${shareTime}</span> </div> <div class="info-item"> <span>👀 访问</span> <span>${data.visitCount}</span> </div> </div> <div class="info-right"> <button onclick="toggleDarkMode()" class="theme-toggle" title="切换主题"> <span class="sun-icon">☀️</span> <span class="moon-icon">🌙</span> <span class="label">主题</span> </button> </div> </div> <div class="content"> ${data.content} </div> </div>

<div class="image-overlay" id="imageOverlay" onclick="closeEnlargedImage()"></div> <div class="toast-container" id="toast-container"></div>

<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script> <script> // 初始化主题和代码块 document.addEventListener('DOMContentLoaded', () => { const savedTheme = localStorage.getItem('theme') || 'light'; document.documentElement.setAttribute('data-theme', savedTheme);

// 为所有代码块添加复制按钮和语言标签 const codeBlocks = document.querySelectorAll('pre code'); codeBlocks.forEach(code => { const pre = code.parentElement;

// 获取语言类名 const langClass = Array.from(code.classList).find(cl => cl.startsWith('language-')); const language = langClass ? langClass.replace('language-', '') : '代码'; pre.setAttribute('data-language', language);

// 添加复制按钮 const button = document.createElement('button'); button.className = 'copy-button'; button.innerHTML = '<span>📋</span><span>复制</span>'; button.onclick = async (e) => { e.preventDefault(); e.stopPropagation(); await copyCode(code, button); }; pre.appendChild(button); });

// 应用代码高亮 hljs.highlightAll();

// 图片点击放大 const content = document.querySelector('.content'); const overlay = document.getElementById('imageOverlay');

content.addEventListener('click', (e) => { if (e.target.tagName === 'IMG' && !e.target.classList.contains('enlarged')) { e.target.classList.add('enlarged'); overlay.classList.add('active'); document.body.style.overflow = 'hidden'; } });

// 图片加载错误处理 const images = document.querySelectorAll('.content img'); images.forEach(img => { img.onerror = () => { img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; }; }); });

// 复制代码到剪贴板 async function copyCode(codeElement, button) { const originalText = button.innerHTML; const code = codeElement.innerText;

try { await navigator.clipboard.writeText(code); button.innerHTML = '<span>✅</span><span>已复制</span>'; button.classList.add('copied'); showToast('代码已复制到剪贴板', 'success'); } catch (err) { console.error('复制失败:', err); button.innerHTML = '<span>❌</span><span>复制失败</span>'; button.classList.add('error'); showToast('复制失败,请重试', 'error'); }

setTimeout(() => { button.innerHTML = originalText; button.classList.remove('copied', 'error'); }, 2000); }

// 切换暗色模式 function toggleDarkMode() { const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme);

// 更新Mermaid主题 mermaid.initialize({ startOnLoad: false, theme: theme === 'dark' ? 'dark' : 'default', securityLevel: 'loose', fontFamily: 'var(--font-family)', });

// 重新渲染预览内容以更新Mermaid图表 if (previewToggle.checked) { updatePreview(content.value); } }

// 关闭放大的图片 function closeEnlargedImage() { const enlargedImage = document.querySelector('.enlarged'); const overlay = document.getElementById('imageOverlay');

if (enlargedImage) { enlargedImage.classList.remove('enlarged'); overlay.classList.remove('active'); document.body.style.overflow = ''; } }

// ESC键关闭放大图片 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeEnlargedImage(); } });

// Toast 提示框 function showToast(message, type = 'info', duration = 3000) { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = 'toast ' + type;

const icons = { success: '✅', error: '❌', info: 'ℹ️', warning: '⚠️' };

toast.innerHTML = '<span class="toast-icon">' + (icons[type] || icons.info) + '</span>' + '<span class="toast-message">' + message + '</span>' + '<span class="toast-close" onclick="this.parentElement.remove()">✕</span>';

container.appendChild(toast);

void toast.offsetWidth; toast.classList.add('show');

setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, duration); } </script> </body> </html>; }

// 监听所有fetch请求 addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); });