短语笔记CSV导出

本地笔记追加同步到GitHub为纯文本,无同步弹窗,结果写控制台,保留其他弹窗提示,移动端兼容!

// ==UserScript==
// @name         短语笔记CSV导出
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  本地笔记追加同步到GitHub为纯文本,无同步弹窗,结果写控制台,保留其他弹窗提示,移动端兼容!
// @author       ChatGPT
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      api.github.com
// ==/UserScript==

(function () {
  'use strict';

  const NOTE_KEY = 'mobile_note_list';
  const GITHUB_USERNAME = 'moodHappy';
  const REPO_NAME = 'HelloWorld';
  const FILE_PATH = 'Notes/phrase_note.json'; // 内容纯文本,后缀json无影响
  const TOKEN = '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';

  // Function to show a temporary message at the highest z-index
  function showTemporaryMessage(msg, duration = 3000) {
    const messageDiv = document.createElement('div');
    messageDiv.style.cssText = `
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background-color: rgba(0, 0, 0, 0.8); /* Slightly darker background for better visibility */
      color: white;
      padding: 18px 30px; /* Slightly larger padding */
      border-radius: 10px; /* Slightly more rounded corners */
      z-index: 2147483641; /* Set to a very high z-index */
      font-size: 18px; /* Larger font size */
      text-align: center;
      opacity: 0;
      transition: opacity 0.4s ease-in-out; /* Slightly longer transition */
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); /* Add a subtle shadow */
    `;
    messageDiv.textContent = msg;
    document.body.appendChild(messageDiv);

    // Fade in
    setTimeout(() => {
      messageDiv.style.opacity = '1';
    }, 10);

    // Fade out and remove
    setTimeout(() => {
      messageDiv.style.opacity = '0';
      messageDiv.addEventListener('transitionend', () => messageDiv.remove());
    }, duration);

    console.log('[笔记]', msg);
  }

  // Original showMessage for critical alerts (e.g., selection issues) that require user action
  function showMessage(msg) {
    alert(msg);
    console.log('[笔记]', msg);
  }

  function logSync(msg) {
    console.log('[同步日志]', msg);
  }

  function getNotes() {
    try {
      return JSON.parse(GM_getValue(NOTE_KEY, '[]'));
    } catch (e) {
      return [];
    }
  }

  function setNotes(notes) {
    GM_setValue(NOTE_KEY, JSON.stringify(notes));
  }

  async function storeNote() { // Made async to await syncToGitHub
    const selection = window.getSelection().toString().trim();
    if (!selection) return showMessage('❗请先选中文本。'); // Still uses alert for user action

    const formatted = selection.replace(/["“”]:?|:/, '|').replace(/["“”]/g, '');
    if (!formatted.includes('|')) return showMessage('❗格式不符,请选中形如 xxx:"yyy" 的文本'); // Still uses alert for user action

    const notes = getNotes();
    notes.push(formatted);
    setNotes(notes);
    // Changed to showTemporaryMessage for "note saved successfully"
    showTemporaryMessage(`✅ 已保存笔记:\n${formatted}`, 3000);

    if (notes.length >= 5) {
      exportNotes(notes); // This will now also trigger syncToGitHub
      setNotes([]);
      showTemporaryMessage('📤 自动导出并清空本地笔记(5条)', 3000); // Changed to showTemporaryMessage
    }
  }

  function showNoteCount() {
    const notes = getNotes();
    showTemporaryMessage(`📊 当前已保存 ${notes.length} 条笔记`, 3000); // Changed to showTemporaryMessage
  }

  async function manualExport() { // Made async to await syncToGitHub
    const notes = getNotes();
    if (notes.length === 0) return showTemporaryMessage('⚠️ 当前没有需要导出的笔记', 3000); // Changed to showTemporaryMessage
    exportNotes(notes); // This will now also trigger syncToGitHub
    setNotes([]);
    showTemporaryMessage(`📤 手动导出并清空 ${notes.length} 条笔记`, 3000); // Changed to showTemporaryMessage
  }

  async function exportNotes(notes) { // Made async to await syncToGitHub
    const csv = '\uFEFF' + notes.join('\n');
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'notes_' + new Date().toISOString().replace(/[:T]/g, '-').slice(0, 19) + '.csv';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(a.href);

    // Trigger GitHub sync after successful CSV export
    await syncToGitHub();
  }

  async function syncToGitHub() {
    const newNotes = getNotes();
    if (newNotes.length === 0) {
      logSync('⚠️ 没有笔记需要同步');
      showTemporaryMessage('⚠️ 没有笔记需要同步', 3000); // Also show temp message for no notes
      return;
    }

    logSync('☁️ 开始同步到 GitHub(追加模式)...');
    showTemporaryMessage('☁️ 正在同步笔记到 GitHub...', 5000); // Show a syncing message

    const url = `https://api.github.com/repos/${GITHUB_USERNAME}/${REPO_NAME}/contents/${FILE_PATH}`;
    const headers = {
      'Authorization': `Bearer ${TOKEN}`,
      'Accept': 'application/vnd.github+json'
    };

    try {
      let oldText = '';
      let sha = '';

      const res = await fetch(url, { headers });
      if (res.status === 200) {
        const data = await res.json();
        sha = data.sha;
        oldText = decodeURIComponent(escape(atob(data.content.replace(/\n/g, ''))));
      }

      const oldLines = oldText.split('\n').filter(line => line.trim());
      const allNotesSet = new Set([...newNotes, ...oldLines]);
      const combinedText = '\uFEFF' + Array.from(allNotesSet).join('\n');
      const encodedContent = btoa(unescape(encodeURIComponent(combinedText)));

      const uploadBody = {
        message: '📤 追加同步笔记',
        content: encodedContent,
        committer: {
          name: GITHUB_USERNAME,
          email: `${GITHUB_USERNAME}@users.noreply.github.com`
        }
      };
      if (sha) uploadBody.sha = sha;

      const putRes = await fetch(url, {
        method: 'PUT',
        headers,
        body: JSON.stringify(uploadBody)
      });

      if (putRes.ok) {
        logSync('✅ 成功追加同步到 GitHub 并清空本地笔记');
        showTemporaryMessage('✅ 笔记已成功同步到 GitHub!', 3000); // Success message
        setNotes([]);
      } else {
        const errText = await putRes.text();
        console.error('❌ 上传失败:', errText);
        logSync(`❌ 同步失败:${putRes.status}`);
        showTemporaryMessage('❌ 笔记同步到 GitHub 失败!', 3000); // Failure message
      }
    } catch (err) {
      console.error('❌ 异常:', err);
      logSync('❌ 网络错误或Token无效');
      showTemporaryMessage('❌ 笔记同步到 GitHub 失败!网络错误或Token无效。', 3000); // Error message
    }
  }

  // 菜单命令
  GM_registerMenuCommand('📌 存储笔记(选中文本)', storeNote);
  GM_registerMenuCommand('📊 查看笔记数量', showNoteCount);
  GM_registerMenuCommand('📤 手动导出CSV', manualExport);
  GM_registerMenuCommand('☁️ 同步到GitHub(追加模式)', syncToGitHub);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址