Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Треды Threads — ветки</title>
<style>
  body { font-family: sans-serif; max-width: 780px; margin: 20px auto; padding: 0 10px; background:#f7f7f8; color:#222; }
  h2, h3 { text-align:center; }
  .status { text-align:center; color:#1faa59; font-size:13px; min-height:18px; margin-bottom:12px; }
  .hint { background:#fff; border:1px solid #e2e2e2; border-radius:8px; padding:12px 14px; font-size:13px; color:#555; margin-bottom:14px; line-height:1.5; }
  .hint code { background:#eee; padding:1px 5px; border-radius:4px; }
  #input { width:100%; min-height:160px; box-sizing:border-box; font-size:14px; padding:10px; border-radius:8px; border:1px solid #ccc; }
  .controls { margin:10px 0; display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
  input[type=text] { padding:9px 10px; font-size:14px; border:1px solid #ccc; border-radius:6px; flex:1; min-width:180px; }
  button { padding:9px 14px; font-size:14px; cursor:pointer; border:none; border-radius:6px; background:#2d7ff9; color:#fff; }
  button.done { background:#1faa59; }
  button.clear, button.del { background:#999; }
  button.collect { background:#7a52e0; }
  button.del { background:#e23; }
  button:disabled { background:#ccc; cursor:not-allowed; }
  .block { margin-bottom:12px; border:1px solid #e2e2e2; border-radius:8px; padding:10px; background:#fff; }
  .block-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:4px; }
  .block-head h4 { margin:0; color:#444; }
  .count { font-size:13px; color:#1faa59; font-weight:bold; }
  .count.over { color:#e23; }
  .block textarea { width:100%; min-height:80px; box-sizing:border-box; font-size:14px; padding:8px; border-radius:6px; border:1px solid #ddd; }
  hr { margin:26px 0; border:none; border-top:1px solid #ddd; }
  .saved { background:#fff; border:1px solid #e2e2e2; border-radius:8px; padding:12px 14px; margin-bottom:10px; }
  .saved-head { display:flex; justify-content:space-between; align-items:center; gap:8px; flex-wrap:wrap; }
  .saved-head b { font-size:15px; }
  .saved-meta { font-size:12px; color:#888; }
  .saved-btns { display:flex; gap:6px; flex-wrap:wrap; }
  .saved-btns button { padding:6px 10px; font-size:13px; }
  #savedList:empty::after { content:"Пока нет сохранённых веток."; color:#aaa; font-size:13px; }
  .counter-top { text-align:center; font-size:13px; color:#666; margin-bottom:10px; }
</style>
</head>
<body>

<h2>Треды Threads</h2>
<div class="status" id="status">Готово к работе</div>

<div class="hint">
  1. Вставь весь тред в поле. Между подпостами ставь <code>---</code> (три дефиса на отдельной строке).<br>
  2. Нажми «Разбить» — увидишь подпосты со счётчиком символов.<br>
  3. <b>Правь текст прямо в окошках подпостов.</b> Перед сохранением жми «Собрать изменения в поле» (или просто «Сохранить» — правки подтянутся автоматически).<br>
  4. Впиши название и «Сохранить в ветку». Максимум 10 веток.<br>
  <b>Счётчик краснеет, если подпост длиннее 500 символов.</b>
</div>

<textarea id="input" placeholder="Вставь сюда весь тред.

Пример:
Пост первый...
---
Пост второй...
---
Пост третий..."></textarea>

<div class="controls">
  <button onclick="split()">Разбить на посты</button>
  <button class="collect" onclick="collectToInput(true)">Собрать изменения в поле</button>
  <button class="clear" onclick="clearInput()">Очистить поле</button>
</div>

<div id="output"></div>

<div class="controls">
  <input type="text" id="branchName" placeholder="Название ветки (напр. История про Диму)">
  <button id="saveBtn" onclick="saveBranch()">Сохранить в ветку</button>
</div>

<hr>

<h3>Сохранённые ветки</h3>
<div class="counter-top" id="counterTop"></div>
<div id="savedList"></div>

<script>
  const SEPARATOR_RE = /^\s*---\s*$/m;
  const LIMIT = 500;
  const MAX_BRANCHES = 10;
  const STORE_KEY = 'threads_branches';
  const DRAFT_KEY = 'threads_draft';

  const input = document.getElementById('input');
  let editingId = null; // id ветки, которую редактируем (null = новая)

  // --- черновик текста ---
  const draft = localStorage.getItem(DRAFT_KEY);
  if (draft) input.value = draft;
  input.addEventListener('input', () => localStorage.setItem(DRAFT_KEY, input.value));

  // --- разбивка на подпосты ---
  function getParts() {
    return input.value
      .split(SEPARATOR_RE)
      .map(p => p.trim())
      .filter(p => p.length > 0);
  }

  // Есть ли сейчас разбитые подпосты на экране
  function hasPostBlocks() {
    return document.querySelectorAll('#output .block textarea').length > 0;
  }

  // Собрать текст из окошек подпостов обратно в большое поле.
  // showStatus=true — показать сообщение и перерисовать счётчики.
  function collectToInput(showStatus) {
    const areas = document.querySelectorAll('#output .block textarea');
    if (areas.length === 0) {
      if (showStatus) setStatus('Нет подпостов — сначала «Разбить на посты»');
      return false;
    }
    const parts = Array.from(areas)
      .map(a => a.value.trim())
      .filter(p => p.length > 0);
    const joined = parts.join('\n---\n');
    input.value = joined;
    localStorage.setItem(DRAFT_KEY, joined);
    if (showStatus) {
      // перерисуем блоки, чтобы номера/счётчики совпали с новым содержимым
      split();
      setStatus('Изменения из подпостов собраны в поле ✅');
    }
    return true;
  }

  function split() {
    const parts = getParts();
    const out = document.getElementById('output');
    out.innerHTML = '';
    if (parts.length === 0) { setStatus('Пусто — вставь текст с разделителями ---'); return; }

    parts.forEach((text, i) => {
      const id = 'post' + i;
      const over = text.length > LIMIT;
      const block = document.createElement('div');
      block.className = 'block';
      block.innerHTML = `
        <div class="block-head">
          <h4>Пост ${i + 1} / ${parts.length}</h4>
          <span class="count ${over ? 'over' : ''}" id="c${i}">${text.length} / ${LIMIT}</span>
        </div>
        <textarea id="${id}">${escapeHtml(text)}</textarea>
        <button onclick="copyText(document.getElementById('${id}').value, this)">Копировать</button>
      `;
      out.appendChild(block);
      const ta = block.querySelector('textarea');
      ta.addEventListener('input', () => {
        const c = document.getElementById('c' + i);
        c.textContent = ta.value.length + ' / ' + LIMIT;
        c.classList.toggle('over', ta.value.length > LIMIT);
      });
    });
    setStatus('Разбито на ' + parts.length + ' подпостов ✅');
  }

  function clearInput() {
    input.value = '';
    localStorage.removeItem(DRAFT_KEY);
    document.getElementById('output').innerHTML = '';
    document.getElementById('branchName').value = '';
    editingId = null;
    document.getElementById('saveBtn').textContent = 'Сохранить в ветку';
    setStatus('Поле очищено');
  }

  // --- хранилище веток ---
  function loadBranches() {
    try { return JSON.parse(localStorage.getItem(STORE_KEY)) || []; }
    catch(e) { return []; }
  }
  function storeBranches(arr) {
    localStorage.setItem(STORE_KEY, JSON.stringify(arr));
  }

  function saveBranch() {
    // ГЛАВНОЕ ИСПРАВЛЕНИЕ: если есть разбитые подпосты — сперва собираем
    // правки из окошек обратно в большое поле, потом сохраняем.
    if (hasPostBlocks()) {
      collectToInput(false);
    }

    const name = document.getElementById('branchName').value.trim();
    const parts = getParts();
    if (!name) { setStatus('Впиши название ветки'); return; }
    if (parts.length === 0) { setStatus('Нечего сохранять — поле пустое'); return; }

    let branches = loadBranches();

    if (editingId !== null) {
      // обновляем существующую
      const idx = branches.findIndex(b => b.id === editingId);
      if (idx !== -1) {
        branches[idx].name = name;
        branches[idx].text = input.value;
        storeBranches(branches);
        setStatus('Ветка обновлена ✅ (правки из подпостов сохранены)');
        editingId = null;
        document.getElementById('saveBtn').textContent = 'Сохранить в ветку';
        renderBranches();
        return;
      }
    }

    if (branches.length >= MAX_BRANCHES) {
      setStatus('Достигнут лимит 10 веток — удали лишнее');
      return;
    }
    branches.push({ id: Date.now(), name, text: input.value });
    storeBranches(branches);
    setStatus('Ветка «' + name + '» сохранена ✅');
    document.getElementById('branchName').value = '';
    renderBranches();
  }

  function openBranch(id) {
    const b = loadBranches().find(x => x.id === id);
    if (!b) return;
    input.value = b.text;
    localStorage.setItem(DRAFT_KEY, b.text);
    document.getElementById('branchName').value = b.name;
    editingId = id;
    document.getElementById('saveBtn').textContent = 'Обновить ветку';
    split();
    setStatus('Загружена ветка «' + b.name + '» — правь прямо в подпостах и жми «Обновить ветку»');
    window.scrollTo({top:0, behavior:'smooth'});
  }

  function deleteBranch(id) {
    if (!confirm('Удалить эту ветку?')) return;
    storeBranches(loadBranches().filter(b => b.id !== id));
    if (editingId === id) { editingId = null; document.getElementById('saveBtn').textContent = 'Сохранить в ветку'; }
    renderBranches();
    setStatus('Ветка удалена');
  }

  function copyAll(id) {
    const b = loadBranches().find(x => x.id === id);
    if (!b) return;
    const parts = b.text.split(SEPARATOR_RE).map(p => p.trim()).filter(Boolean);
    const total = parts.length;
    const numbered = parts.map((p, i) => `${i+1}/${total}\n${p}`).join('\n\n———\n\n');
    copyText(numbered, event.target);
  }

  function renderBranches() {
    const branches = loadBranches();
    const list = document.getElementById('savedList');
    list.innerHTML = '';
    document.getElementById('counterTop').textContent = branches.length + ' / ' + MAX_BRANCHES + ' веток';
    document.getElementById('saveBtn').disabled = (branches.length >= MAX_BRANCHES && editingId === null);

    branches.forEach(b => {
      const count = b.text.split(SEPARATOR_RE).map(p=>p.trim()).filter(Boolean).length;
      const div = document.createElement('div');
      div.className = 'saved';
      div.innerHTML = `
        <div class="saved-head">
          <div>
            <b>${escapeHtml(b.name)}</b>
            <div class="saved-meta">${count} подпостов</div>
          </div>
          <div class="saved-btns">
            <button onclick="openBranch(${b.id})">Открыть</button>
            <button onclick="copyAll(${b.id})">Копировать всё</button>
            <button class="del" onclick="deleteBranch(${b.id})">Удалить</button>
          </div>
        </div>
      `;
      list.appendChild(div);
    });
  }

  // --- утилиты ---
  function copyText(text, btn) {
    navigator.clipboard.writeText(text).then(() => {
      const old = btn.textContent;
      btn.textContent = '✅';
      btn.classList.add('done');
      setTimeout(() => { btn.textContent = old; btn.classList.remove('done'); }, 1400);
    });
  }
  function setStatus(t) { document.getElementById('status').textContent = t; }
  function escapeHtml(s) {
    return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }

  // старт
  renderBranches();
  if (input.value.trim()) split();
</script>
</body>
</html>