ΚΤΕΜΑ ΤΕ ΕΣ ΑΙΕΙ ΘΟΚΥΔΙΔΟ

Finds Ancient Greek text, transliterates it into an archaic majuscule style, and applies a custom font.

目前為 2025-08-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name         ΚΤΕΜΑ ΤΕ ΕΣ ΑΙΕΙ ΘΟΚΥΔΙΔΟ
// @namespace    http://lunacy.wtf/
// @version      2.0
// @description  Finds Ancient Greek text, transliterates it into an archaic majuscule style, and applies a custom font.
// @author       Lunacy
// @match        *://*/*
// @exclude      https://lunacy.wtf/*
// @exclude      https://docs.google.com/*
// @exclude      https://suno.com/*
// @license      Proprietary
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ======================= 1. FONT AND STYLE INJECTION (From Script 2) =======================
    // This part injects the custom font and the CSS class that will be used to apply it.
    // The placeholder for the base64 font data must be replaced with your actual font file.
    GM_addStyle(`
      @font-face {
        font-family: 'ArchaicGreekFont';
        /* IMPORTANT: Replace the placeholder below with your actual base64 font data. */
        /* To convert a font: use a tool like https://www.base64encode.org/ to encode your .woff2 file and paste the result here. */
        src: url(data:font/woff2;base64,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) format('woff2');

        font-weight: normal;
        font-style: normal;
      }
      .archaic-greek-text {
        font-family: 'ArchaicGreekFont', sans-serif !important;
      }
    `);


// ======================= 2. CORE TRANSLITERATION LOGIC =======================
const archaicMajusculeMap = {
            // --- VOWELS ---
// ===================== Alpha ===========================
// Lowercase Normal
'α':'Α', 'ἀ':'Α', 'ά':'Α', 'ὰ':'Α', 'ἄ':'Α', 'ἂ':'Α', 'ᾶ':'Α', 'ἆ':'Α',
// Uppercase Normal
'Ἀ':'Α', 'Ἄ':'Α', 'Ἂ':'Α', 'Ἆ':'Α',
// Lowercase Heta
'ἁ':'ͰΑ', 'ᾁ':'ͰΑΙ', 'ἇ':'ͰΑ',  'ἅ':'ͰΑ', 'ἃ':'ͰΑ',
// Iota Subscript
'ᾷ':'ΑΙ', 'ᾴ':'ΑΙ', 'ᾳ':'ΑΙ', 'ᾀ':'ΑΙ',
// Uppercase Heta
'Ἁ':'ͰA', 'Ἅ':'ͰA',



// ===================== Epsilon =========================
// Lowercase Normal
'ε':'Ε', 'ἐ':'Ε', 'έ':'Ε', 'ὲ':'Ε', 'ἔ':'Ε', 'ἒ':'Ε',
// Uppercase Normal
'Ἐ':'Ε', 'Ἔ':'Ε', 'Ἒ':'Ε',
// Lowercase
'ἑ':'ͰΕ', 'ἓ':'ͰΕ', 'ἕ':'ͰΕ',
// Uppercase
'Ἑ':'ͰΕ', 'Ἕ':'ͰΕ',


// ===================== Eta =============================
// I route Eta to Epsilon for its true Archaic Form
// Lowercase Normal
'η':'Ε', 'ἠ':'Ε', 'ή':'Ε', 'ὴ':'Ε', 'ῆ':'Ε', 'ἤ':'Ε', 'ἢ':'Ε', 'ἦ':'Ε',
// Uppercase Normal
'Η':'Ε','Ἠ':'Ε', 'Ἤ':'Ε', 'Ἢ':'Ε', 'Ἦ':'Ε',
// Lowercase
'ἡ':'ͰΕ', 'ᾗ':'ͰΕΙ', 'ἧ':'ͰΕ', 'ἥ':'ͰΕ', 'ἣ':'ͰΕ', 'ᾑ':'ͰΕΙ',
// Iota Subscript
'ῄ':'ΕΙ', 'ᾖ':'ΕΙ', 'ῇ':'ΕΙ', 'ῃ':'ΕΙ','ᾐ':'ΕΙ',
// Uppercase
'Ἡ':'ͰΕ',


// ===================== Iota =============================
// Lowercase Normal
'ι':'Ι', 'ἰ':'Ι', 'ί':'Ι', 'ὶ':'Ι', 'ῖ':'Ι', 'ἴ':'Ι', 'ἶ':'Ι', 'ϊ':'Ι', 'ΐ':'Ι',
// Uppercase Normal
'Ἰ':'Ι', 'Ἴ':'Ι', 'Ἲ':'Ι', 'Ἶ':'Ι',
// Lowercase
'ἳ':'ͰΙ', 'ἱ':'ͰΙ', 'ἵ':'ͰΙ', 'ἷ':'ͰΙ',
// Uppercase
'Ἱ':'ͰΙ', 'Ἵ':'ͰΙ',

// ===================== Omicron ============================
// Lowercase Normal
'ο':'Ο', 'ὀ':'Ο', 'ό':'Ο', 'ὸ':'Ο', 'ὄ':'Ο', 'ὂ':'Ο',
// Uppercase Normal
'Ὀ':'Ο', 'Ὄ':'Ο', 'Ὂ':'Ο',
// Lowercase
'ὁ':'ͰΟ', 'ὅ':'ͰΟ', 'ὃ':'ͰΟ',
// Uppercase
'Ὁ':'ͰΟ', 'Ὅ':'ͰΟ',

// ===================== Omega ==============================
// Omega must be routed to Omikron since Omega didn't exist
// Lowercase Normal
'ω':'Ο' ,'ὠ':'Ο', 'ώ':'Ο', 'ὼ':'Ο', 'ῶ':'Ο', 'ὦ':'Ο', 'ὢ':'Ο',
// Uppercase Normal
'Ω':'Ο' ,'Ὠ':'Ο', 'Ὤ':'Ο', 'Ὢ':'Ο', 'Ὦ':'Ο',
// Lowercase
'ὡ':'ͰΟ', 'ὧ':'ͰΟ', 'ὥ':'ͰΟ',
// Iota Subscript
'ᾧ':'ͰΟΙ','ῳ':'ΟΙ', 'ῷ':'ΟΙ', 'ᾠ':'ΟΙ', 'ᾤ':'ΟΙ', 'ῴ':'ΟΙ',
// Uppercase
'Ὡ':'ͰΟ',


// ===================== Ypsilon ==============================
// Lowercase Normal
'υ':'Υ', 'ὐ':'Υ', 'ύ':'Υ', 'ὺ':'Υ', 'ὔ':'Υ', 'ὒ':'Υ', 'ῦ':'Υ', 'ὖ':'Υ',
// Uppercase Normal

// Lowercase
'ὕ':'ͰΥ', 'ὑ':'ͰΥ', 'ὗ':'ͰΥ', 'ὕ':'ͰΥ',
// Uppercase
'Ὑ':'ͰΥ', 'Ὓ':'ͰΥ', 'Ὗ':'ͰΥ', 'Ὕ':'ͰΥ',

// ===================== CONSONANTS ==============================
'β':'Β', 'γ':'Γ', 'δ':'Δ', 'ζ':'Ζ', 'θ':'Θ', 'κ':'Κ', 'λ':'Λ', 'μ':'Μ', 'ν':'Ν', 'π':'Π', 'ρ':'Ρ', 'ς':'Σ', 'σ':'Σ', 'τ':'Τ', 'φ':'Φ', 'χ':'Χ', 'ͱ':'Ͱ',

// ===================== Rho ==============================
'ῥ':'ͰΡ',

// ===================== COMPOUND CONSONANTS (Digraphs)  ==============================
'ξ':'ΧΣ', 'Ξ':'ΧΣ', 'ψ':'ΦΣ', 'Ψ':'ΦΣ',
        };




// ======================= STATE 2: MACRONS ON =======================
        const archaicMap_MacronsOn = {
// ===================== Alpha Macron Collection ===========================
// Lowercase Normal
'ᾶ':'Ā', 'ἆ':'Ā',
// Uppercase Normal
'Ἆ':'Ā',
// Lowercase Heta
'ἇ':'ͰĀ',
// Iota Subscript
'ᾷ':'ĀΙ',



// ===================== Eta Macron Collection ===========================
// I route Eta to Epsilon for its true Archaic Form
// Lowercase Normal
'η':'Ē', 'ἠ':'Ē', 'ή':'Ē', 'ὴ':'Ē', 'ῆ':'Ē', 'ἤ':'Ē', 'ἢ':'Ē', 'ἦ':'Ē',
// Uppercase Normal
'Η':'Ē','Ἠ':'Ē', 'Ἤ':'Ē', 'Ἢ':'Ē', 'Ἦ':'Ē',
// Lowercase
'ἡ':'ͰĒ', 'ᾗ':'ͰĒΙ', 'ἧ':'ͰĒ', 'ἥ':'ͰĒ', 'ἣ':'ͰĒ', 'ᾑ':'ͰĒΙ',
// Iota Subscript
'ῄ':'ĒΙ', 'ᾖ':'ĒΙ', 'ῇ':'ĒΙ', 'ῃ':'ĒΙ','ᾐ':'ĒΙ',
// Uppercase
'Ἡ':'ͰĒ',

// ===================== Iota Macron Collection ===========================
// Lowercase Normal
'ῖ':'Ī', 'ἶ':'Ī',
// Uppercase Normal
'Ἶ':'Ī',
// Lowercase
'ἷ':'ͰĪ',

// ===================== Omega Macron Collection ===========================
// Omega must be routed to Omikron since Omega didn't exist
// Lowercase Normal
'ω':'Ō' ,'ὠ':'Ō', 'ώ':'Ō', 'ὼ':'Ō', 'ῶ':'Ō', 'ὦ':'Ō', 'ὢ':'Ō',
// Uppercase Normal
'Ω':'Ō' ,'Ὠ':'Ō', 'Ὤ':'Ō', 'Ὢ':'Ō', 'Ὦ':'Ō',
// Lowercase
'ὡ':'ͰŌ', 'ὧ':'ͰŌ', 'ὥ':'ͰŌ',
// Iota Subscript
'ᾧ':'ͰŌΙ','ῳ':'ŌΙ', 'ῷ':'ŌΙ', 'ᾠ':'ŌΙ', 'ᾤ':'ŌΙ', 'ῴ':'ŌΙ',
// Uppercase
'Ὡ':'ͰŌ',

// ===================== Ypsilon Macron Collection ===========================
// Lowercase Normal
'ῦ':'Ȳ', 'ὖ':'Ȳ',
// Lowercase
'ὗ':'ͰȲ',
// Uppercase
'Ὗ':'ͰȲ',
        };

// ====== 0) EDITABLE-FIELD GUARD (add near the top) ======
const EDITABLE_SELECTOR =
  'input, textarea, select, option, [contenteditable]:not([contenteditable="false"]), [role="textbox"], .ProseMirror, .ql-editor, .public-DraftEditor-content';

function isEditableRoot(el) {
  if (!el) return false;
  if (el.isContentEditable) return true;
  return !!el.closest(EDITABLE_SELECTOR);
}





// ====== MACRON TOGGLE STATE ======
let MACRONS_ON = false;

// Use macrons map when ON; otherwise fall back to your base map
function mapChar(ch) {
  if (MACRONS_ON && archaicMap_MacronsOn[ch] != null) return archaicMap_MacronsOn[ch];
  return (archaicMajusculeMap[ch] != null) ? archaicMajusculeMap[ch] : ch;
}





const diphthongVowelBases = new Set(['α','ε','ο','η','ω','Α','Ε','Ο','Η','Ω']);
const roughBreathingSecond = new Set([
  // iota with rough breathing
  'ἱ','ἵ','ἳ','ἷ','Ἱ','Ἵ','Ἳ','Ἷ',
  // upsilon with rough breathing
  'ὑ','ὕ','ὓ','ὗ','Ὑ','Ὕ','Ὓ','Ὗ'
]);

function convertToArchaic(text) {
  const normalizedInput = text.normalize('NFC');
// ΟΥ collapse (exclude diaeresis: ϋ/ΰ/Ϋ/ΰ/ῢ etc.)
const OU_ROUGH_LOWER = /ο[ὑὕὓὗ]/g;     // ο + rough ὑ/ὕ/ὓ/ὗ
const OU_ROUGH_UPPER = /Ο[ὙὝὛὟ]/g;     // Ο + rough Ὑ/Ὕ/Ὓ/Ὗ

const OU_SMOOTH_LOWER = /ο[υύὺῦὐὔὒὖ]/g; // ο + smooth/tonos/circumflex forms (no diaeresis)
const OU_SMOOTH_UPPER = /Ο[ΥΎῪ]/g;       // Ο + smooth/tonos/varia (no diaeresis)

  // Collapse OU to a single O/Ō; attach BIG HETA for rough OU
  const processedText = normalizedInput
    .replace(OU_ROUGH_UPPER, MACRONS_ON ? 'ͰŌ' : 'ͰΟ')
    .replace(OU_ROUGH_LOWER, MACRONS_ON ? 'ͰŌ' : 'ͰΟ')
    .replace(OU_SMOOTH_UPPER, MACRONS_ON ? 'Ō' : 'Ο')
    .replace(OU_SMOOTH_LOWER, MACRONS_ON ? 'Ō' : 'Ο');

  let finalOutput = '';
  for (let i = 0; i < processedText.length; i++) {
    const char = processedText[i];
    const prevChar = i > 0 ? processedText[i - 1] : null;

    // Diphthong with rough-breathing second (ι/υ) — BIG HETA, using mapped previous output
    if (prevChar && diphthongVowelBases.has(prevChar) && roughBreathingSecond.has(char)) {
      const prevOut = mapChar(prevChar);
      finalOutput = finalOutput.slice(0, -prevOut.length);
      const currNoBreath = mapChar(char).replace(/[ͱͰ]/g, '');
      finalOutput += 'Ͱ' + prevOut + currNoBreath;
      continue;
    }

    finalOutput += mapChar(char);
  }

  // Safety: promote any stray small heta; purge any Ω that might sneak in
  finalOutput = finalOutput.replace(/ͱ/g, 'Ͱ').replace(/[Ωω]/g, m => (m === 'Ω' ? 'Ο' : 'ο'));
  return finalOutput;
}


// ======================= 3. GREEK TEXT FILTER (hardened) =======================

 const ALL_GREEK_CHARS_REGEX = /([\u0100\u0112\u012a\u014c\u016a\u0232\u0370-\u03FF\u1F00-\u1FFF]+)/gu;
//const ALL_GREEK_CHARS_REGEX = /([\u0100\u0112\u012a\u014c\u016a\u0232\u0370\u0391\u0392\u0393\u0394\u0395\u0396\u0398\u0399\u039a\u039b\u039c\u039d\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u{10144}\u{10145}]+)/gu;
function processAndWrap(rootNode) {
  if (![Node.ELEMENT_NODE, Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(rootNode.nodeType)) {
    return;
  }

const walker = document.createTreeWalker(
  rootNode,
  NodeFilter.SHOW_TEXT,
  {
    acceptNode(node) {
      const p = node.parentElement;
      if (!p) return NodeFilter.FILTER_REJECT;

      // NEW: never process inside live/editable fields
      if (isEditableRoot(p)) return NodeFilter.FILTER_REJECT;

      const tag = p.tagName ? p.tagName.toUpperCase() : '';
      if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA') {
        return NodeFilter.FILTER_REJECT;
      }
      if (p.closest('.archaic-greek-text')) {
        return NodeFilter.FILTER_REJECT;
      }

      if (ALL_GREEK_CHARS_REGEX.test(node.nodeValue)) {
        ALL_GREEK_CHARS_REGEX.lastIndex = 0;
        return NodeFilter.FILTER_ACCEPT;
      }
      return NodeFilter.FILTER_SKIP;
    }
  }
);

  const nodesToProcess = [];
  let currentNode;
  while ((currentNode = walker.nextNode())) {
    nodesToProcess.push(currentNode);
  }

  for (const node of nodesToProcess) {
    const text = node.nodeValue;
    const fragment = document.createDocumentFragment();
    let lastIndex = 0;

text.replace(ALL_GREEK_CHARS_REGEX, (match, _p1, offset) => {
  if (offset > lastIndex) {
    fragment.appendChild(document.createTextNode(text.substring(lastIndex, offset)));
  }

  // ▼ Put these lines here ▼
  const converted = convertToArchaic(match);
  const span = document.createElement('span');
  span.className = 'archaic-greek-text';
  span.setAttribute('data-original', match);   // store original once
  // (equivalent: span.dataset.original = match)
  span.textContent = converted;
  fragment.appendChild(span);
  // ▲ Up to here ▲

  lastIndex = offset + match.length;
});


    if (lastIndex < text.length) {
      fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
    }

    if (node.parentNode) {
      node.parentNode.replaceChild(fragment, node);
    }
  }
}



function reconvertAll(root = document) {
  root.querySelectorAll('.archaic-greek-text').forEach(span => {
    const original = span.getAttribute('data-original');
    if (original != null) {
      span.textContent = convertToArchaic(original);
    }
  });
}



// ======================= 4. DYNAMIC CONTENT HANDLING (reliable + throttled) =======================

// Initial pass
processAndWrap(document.body);

// Queue + throttle
const pending = new Set();
let flushTimer = null;
const FLUSH_DELAY = 50;           // ms; raise if pages are very chatty
const BATCH_SIZE = 20;            // nodes per tick; tune for your machine

// ====== inside enqueue(node) ======
function enqueue(node) {
  let el = null;
  if (node.nodeType === Node.ELEMENT_NODE) el = node;
  else if (node.nodeType === Node.TEXT_NODE) el = node.parentElement;

  if (!el) return;
  if (!document.body.contains(el)) return;

  // NEW: skip anything that is or lives inside an editable field
  if (isEditableRoot(el)) return;

  pending.add(el);
}


function minimizeRoots(nodes) {
  // drop descendants when an ancestor is already queued
  const set = new Set(nodes);
  return nodes.filter(n => {
    let p = n.parentElement;
    while (p) {
      if (set.has(p)) return false;
      p = p.parentElement;
    }
    return true;
  });
}

function scheduleFlush() {
  if (flushTimer) return;
  flushTimer = setTimeout(flush, FLUSH_DELAY);
}

function flush() {
  flushTimer = null;

  // Snapshot + clear pending
  const unique = Array.from(pending);
  pending.clear();

  const roots = minimizeRoots(unique);
  let index = 0;

  function step() {
    const end = Math.min(index + BATCH_SIZE, roots.length);
    for (; index < end; index++) {
      // Important: do NOT unwrap existing '.archaic-greek-text' here;
      // processAndWrap already skips inside them, so we avoid rework/jank.
      processAndWrap(roots[index]);
    }
    if (index < roots.length) {
      // Yield to the event loop to keep the UI responsive
      setTimeout(step, 16);
    }
  }
  step();
}



(function injectMacronToggle() {
  const btn = document.createElement('button');
  btn.type = 'button';
  btn.id = 'macronToggle';
  btn.textContent = 'Macrons: OFF';
  btn.style.cssText = [
    'position:fixed',
    'right:14px',
    'bottom:14px',
    'z-index:2147483647',
    'padding:8px 12px',
    'border-radius:10px',
    'border:1px solid rgba(255,255,255,.2)',
    'background:#111',
    'color:#eee',
    'font:600 12px system-ui,-apple-system,Segoe UI,Roboto,sans-serif',
    'box-shadow:0 2px 8px rgba(0,0,0,.3)',
    'cursor:pointer',
    'opacity:.85'
  ].join(';');

  btn.addEventListener('mouseenter', () => btn.style.opacity = '1');
  btn.addEventListener('mouseleave', () => btn.style.opacity = '.85');

  btn.addEventListener('click', () => {
    MACRONS_ON = !MACRONS_ON;
    btn.textContent = 'Macrons: ' + (MACRONS_ON ? 'ON' : 'OFF');
    reconvertAll();
  });

  document.documentElement.appendChild(btn);
})();




const observer = new MutationObserver((mutations) => {
  // Pull any queued-but-not-delivered records to avoid drops
  const all = mutations.concat(observer.takeRecords());

  for (const m of all) {
    if (m.type === 'childList') {
      m.addedNodes.forEach(enqueue);
      // If a framework replaces text by toggling .textContent on an existing node,
      // the 'childList' target often *is* the container whose text changed:
      enqueue(m.target);
    } else if (m.type === 'characterData') {
      enqueue(m.target);          // text node
      enqueue(m.target.parentElement); // its container
    }
  }

  scheduleFlush();
});

observer.observe(document.body, {
  childList: true,
  characterData: true,
  subtree: true
});



})();

QingJ © 2025

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