CoolFace
Apppublic

AnnLior/Text_Processing

sourceHugging Faceupdated 24d agoView on Hugging Face
0likes
index.html304 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="ru">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>Обработка стихов — курсив, выделение и адрес</title>7<style>8:root {9  --bg: #f4f5f7;10  --panel: #ffffff;11  --border: #d9dce1;12  --text: #202124;13  --muted: #6b7280;14  --input: #ffffff;15  --btn: #111827;16  --btn-text: #ffffff;17  --code: #f8f9fa;18}19.dark {20  --bg: #1a1b1e;21  --panel: #252629;22  --border: #3a3b3e;23  --text: #e4e4e7;24  --muted: #9ca3af;25  --input: #1a1b1e;26  --btn: #3b82f6;27  --btn-text: #ffffff;28  --code: #1f2023;29}30* { box-sizing: border-box; }31body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; background: var(--bg); color: var(--text); transition: background .2s, color .2s; }32.container { max-width: 1200px; margin: 0 auto; padding: 24px; }33.top { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }34h1 { margin: 0 0 6px; font-size: 26px; }35.sub { color: var(--muted); margin: 0 0 16px; line-height: 1.5; }36textarea { width: 100%; min-height: 300px; resize: vertical; border: 1px solid var(--border); border-radius: 10px; padding: 12px; font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; background: var(--input); color: var(--text); }37.controls { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin: 14px 0; }38label { font-size: 13px; display: flex; align-items: center; gap: 6px; cursor: pointer; }39button { border: 0; border-radius: 8px; padding: 10px 16px; font-size: 14px; cursor: pointer; font-weight: 600; }40.primary { background: var(--btn); color: var(--btn-text); }41.ghost { background: var(--panel); color: var(--text); border: 1px solid var(--border); }42.theme { background: var(--panel); color: var(--text); border: 1px solid var(--border); }43.row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 8px; }44@media (max-width: 800px) { .row { grid-template-columns: 1fr; } }45section { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }46section h2 { margin: 0 0 12px; font-size: 17px; }47.preview { min-height: 260px; max-height: 560px; overflow: auto; border: 1px solid var(--border); border-radius: 10px; padding: 14px; line-height: 1.75; font-size: 15px; background: var(--input); color: var(--text); }48.preview p { margin: 0 0 12px; }49.preview p:last-child { margin-bottom: 0; }50.preview em { font-style: italic; }51.preview mark { border-radius: 3px; padding: 0 2px; }52.codehead { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; gap: 10px; flex-wrap: wrap; }53#code { width: 100%; min-height: 260px; max-height: 560px; resize: vertical; font: 12.5px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; background: var(--code); border: 1px solid var(--border); border-radius: 10px; padding: 12px; color: var(--text); }54.stats { font-size: 13px; color: var(--muted); }55</style>56</head>57<body>58<div class="container">59  <div class="top">60    <h1>Обработка стихов</h1>61    <button class="theme" id="theme" onclick="toggleTheme()">🌙 Тёмная</button>62  </div>63  <p class="sub">64    Вставьте текст...65  </p>66 67  <textarea id="input" placeholder="Вставьте текст… Например:&#10;4 Не делай себе кумира и никакого изображения…&#10;7 Не произноси имени Господа… (Исх. 20:1-7)."></textarea>68 69  <div class="controls">70    <label><input type="checkbox" id="handleBr" checked> Воспринимать &lt;br&gt; как перенос строки</label>71    <label><input type="checkbox" id="stripTags" checked> Убирать остальные HTML-теги</label>72    <button class="primary" id="run">Обработать</button>73    <button class="ghost" id="example">Пример</button>74    <button class="ghost" id="clear">Очистить</button>75  </div>76 77  <div class="row">78    <section>79      <h2>Предпросмотр</h2>80      <div class="preview" id="preview">Текст появится здесь.</div>81    </section>82    <section>83      <h2>HTML-код</h2>84      <div class="codehead">85        <span class="stats" id="stats"></span>86        <button class="ghost" id="copy">📋 Копировать</button>87      </div>88      <textarea id="code" readonly></textarea>89    </section>90  </div>91</div>92 93<script>94const $ = id => document.getElementById(id);95 96function toggleTheme() {97  document.body.classList.toggle("dark");98  const dark = document.body.classList.contains("dark");99  try { localStorage.setItem("theme", dark ? "dark" : "light"); } catch (e) {}100  $("theme").textContent = dark ? "☀️ Светлая" : "🌙 Тёмная";101}102window.toggleTheme = toggleTheme;103try {104  if (localStorage.getItem("theme") === "dark") {105    document.body.classList.add("dark");106    $("theme").textContent = "☀️ Светлая";107  }108} catch (e) {}109 110function escapeHtml(s) {111  return String(s)112    .replace(/&/g, "&amp;")113    .replace(/</g, "&lt;")114    .replace(/>/g, "&gt;")115    .replace(/"/g, "&quot;");116}117 118function decodeEntities(s) {119  const ta = document.createElement("textarea");120  ta.innerHTML = s;121  return ta.value;122}123 124/* Цвета выделения по номеру тега <a id="N" name="N"> */125const HL_COLORS = {126  1: "#ccffcc", // салатовый127  2: "#cce5ff", // голубой128  3: "#fff59a", // жёлтый129  4: "#ffd1dc", // розовый130  5: "#ffc4a3"  // коралловый131};132 133/* Замена плейсхолдеров на цветные <mark> */134function applyHighlights(s) {135  let out = s.replace(/@@HL(\d+)@@/g, (m, n) => {136    const c = HL_COLORS[n];137    return c ? `<mark style="background-color:${c}">` : "";138  });139  return out.split("@@/HL@@").join("</mark>");140}141 142/* Протаскивает открытое выделение через следующие строки,143   пока не встретится закрывающий плейсхолдер @@/HL@@. */144function propagateHighlights(lines) {145  const result = [];146  let active = null;147  for (let line of lines) {148    if (!line.trim()) {149      result.push(line);150      continue;151    }152    let processed = line;153    if (active !== null && !/^@@HL\d+@@/.test(processed)) {154      processed = "@@HL" + active + "@@" + processed;155    }156    let state = active;157    const re = /@@HL(\d+)@@|@@\/HL@@/g;158    let m;159    while ((m = re.exec(processed)) !== null) {160      state = (m[0] === "@@/HL@@") ? null : Number(m[1]);161    }162    result.push(processed);163    active = state;164  }165  return result;166}167 168/* Обработка одной строки */169function processLine(line) {170  if (!line) return "";171  172  // Сначала сохраняем все плейсхолдеры выделения173  let lead = "";174  let body = line;175  const leadMatch = line.match(/^((?:@@HL\d+@@|@@\/HL@@)+)/);176  if (leadMatch) {177    lead = leadMatch[1];178    body = line.slice(lead.length);179  }180  let tail = "";181  const tailMatch = body.match(/((?:@@\/HL@@|@@HL\d+@@)+)$/);182  if (tailMatch) {183    tail = tailMatch[1];184    body = body.slice(0, body.length - tail.length);185  }186  187  // Ищем строку, начинающуюся с цифры188  const m = body.match(/^(\d+[.)]?)\s+(.*)$/);189  let out;190  191  if (!m) {192    out = escapeHtml(lead + body + tail);193  } else {194    const number = m[1];195    let rest = m[2];196    197    // Ищем скобки в конце строки (включая точку после скобок)198    const parenMatch = rest.match(/^(.*?)\s*(\([^)]*\)\.?)\s*$/);199    200    if (parenMatch) {201      const beforeParen = parenMatch[1].trim();202      const paren = parenMatch[2];203      204      if (beforeParen) {205        // Текст до скобок — курсивом, скобки — обычным текстом206        out = `${lead}<em>${escapeHtml(number)} ${escapeHtml(beforeParen)}</em> ${escapeHtml(paren)}${tail}`;207      } else {208        // Только скобки — всё обычным текстом209        out = escapeHtml(lead + number + ' ' + rest + tail);210      }211    } else {212      // Нет скобок — всё курсивом213      out = `${lead}<em>${escapeHtml(number)} ${escapeHtml(rest)}</em>${tail}`;214    }215  }216  217  // Применяем подсветку218  return applyHighlights(out);219}220 221function process() {222  let raw = $("input").value;223  raw = decodeEntities(raw);224  225  if ($("handleBr").checked) raw = raw.replace(/<br\s*\/?>/gi, "\n");226  227  // Сохраняем теги выделения228  raw = raw.replace(/<a\s+[^>]*id="?(\d+)"?[^>]*>/gi, "@@HL$1@@");229  raw = raw.replace(/<\/a>/gi, "@@/HL@@");230  231  if ($("stripTags").checked) {232    raw = raw.replace(/<[^>]+>/g, "");233  }234  235  raw = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n");236 237  raw = propagateHighlights(raw.split("\n")).join("\n");238 239  const blocks = raw.split(/\n\s*\n+/).map(b => b.trim()).filter(Boolean);240  const paragraphs = blocks.map(block => {241    const lines = block.split("\n").map(l => l.trim()).filter(Boolean);242    return lines.map(processLine).join("<br />");243  });244  245  const htmlPreview = paragraphs.map(p => `<p>${p}</p>`).join("\n");246  const htmlCode = paragraphs.join("<br />");247 248  $("preview").innerHTML = htmlPreview;249  250  // Восстанавливаем теги <a> для HTML-кода251  let codeHtml = htmlCode;252  codeHtml = codeHtml.replace(/<mark style="background-color:#[^"]*">/g, (match) => {253    const colorMap = {254      '#ccffcc': '1',255      '#cce5ff': '2',256      '#fff59a': '3',257      '#ffd1dc': '4',258      '#ffc4a3': '5'259    };260    const color = match.match(/#[^"]*/)[0];261    const id = colorMap[color] || '1';262    return `<a id="${id}" name="${id}">`;263  });264  codeHtml = codeHtml.replace(/<\/mark>/g, '</a>');265  266  $("code").value = codeHtml;267  268  const verseCount = (htmlPreview.match(/<em>/g) || []).length;269  $("stats").textContent = `Стихов: ${verseCount} · Абзацев: ${paragraphs.length}`;270}271window.process = process;272 273$("run").addEventListener("click", process);274$("input").addEventListener("keydown", e => {275  if ((e.ctrlKey || e.metaKey) && e.key === "Enter") process();276});277 278$("clear").addEventListener("click", () => {279  $("input").value = "";280  $("preview").textContent = "Текст появится здесь.";281  $("code").value = "";282  $("stats").textContent = "";283});284 285$("example").addEventListener("click", () => {286  $("input").value = ""287  process();288});289 290$("copy").addEventListener("click", async () => {291  const code = $("code").value;292  if (!code) return;293  try {294    await navigator.clipboard.writeText(code);295    $("copy").textContent = "✅ Скопировано";296    setTimeout(() => $("copy").textContent = "📋 Копировать", 1500);297  } catch (e) {298    $("code").select();299    document.execCommand("copy");300  }301});302</script>303</body>304</html>