AltcoinTalks Local Draft Saver

Automatically save drafts locally when you click Preview, keep multiple drafts per topic.

// ==UserScript==
// @name         AltcoinTalks Local Draft Saver
// @namespace    https://www.altcoinstalks.com/
// @version      1.0
// @description  Automatically save drafts locally when you click Preview, keep multiple drafts per topic.
// @author       Royal Cap
// @match        https://www.altcoinstalks.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // === Helpers ===
  function findMessageElements() {
    const textarea = document.querySelector('textarea[name=message], textarea#message, textarea');
    const subject = document.querySelector('input[name=subject], input#subject');
    return { textarea, subject };
  }

  function getContextKey() {
    const match = window.location.href.match(/topic=(\d+)/);
    return match ? `altc_drafts_topic_${match[1]}` : 'altc_drafts_general';
  }

  function loadDrafts() { try { return JSON.parse(localStorage.getItem('altc_drafts_v2')) || {}; } catch { return {}; } }
  function saveDrafts(obj) { try { localStorage.setItem('altc_drafts_v2', JSON.stringify(obj)); } catch (e) { console.error(e); } }
  function getDraftList(key) { const all = loadDrafts(); return all[key] || []; }
  function addDraft(key, draft, max=10) { const all = loadDrafts(); const arr = all[key] || []; arr.unshift(draft); if (arr.length > max) arr.length = max; all[key] = arr; saveDrafts(all); }

  function showToast(text) {
    let el = document.getElementById('altc-toast');
    if (!el) {
      el = document.createElement('div'); el.id = 'altc-toast';
      Object.assign(el.style, {position:'fixed', right:'14px', bottom:'14px', background:'#222', color:'#fff', padding:'8px 12px', borderRadius:'6px', zIndex:99999, transition:'opacity .35s'});
      document.body.appendChild(el);
    }
    el.textContent = text; el.style.opacity='1'; clearTimeout(el.dataset.t); el.dataset.t = setTimeout(()=> el.style.opacity='0',1800);
  }

  // === UI insertion next to Preview/Post ===
  function insertControlsNextToButtons() {
    // find likely preview/post button
    const previewBtn = document.querySelector("input[name='preview'], button[name='preview'], input[type=submit][value*='Preview'], button[type=submit][name='preview']");
    const postBtn = document.querySelector("input[name='post'], input[name='postmodify'], input[type=submit][value='Post'], input[type=submit][value='Save']");
    const ref = previewBtn || postBtn || document.querySelector('form[action*="action=post"] input[type=submit], form input[type=submit]');

    if (!ref) return false;

    // avoid inserting duplicates
    if (document.getElementById('altc-draft-controls')) return true;

    const wrapper = document.createElement('span');
    wrapper.id = 'altc-draft-controls';
    wrapper.style.marginLeft = '8px';

    const viewBtn = document.createElement('button');
    viewBtn.type = 'button';
    viewBtn.textContent = 'Drafts';
    viewBtn.title = 'View saved drafts';
    viewBtn.addEventListener('click', showDraftsModal);

    wrapper.append(viewBtn);

    ref.parentNode.insertBefore(wrapper, ref.nextSibling);
    return true;
  }

  // === Save logic ===
  function saveCurrentDraft() {
    const { textarea, subject } = findMessageElements();
    if (!textarea) { showToast('Editor not found'); return; }
    const key = getContextKey();
    addDraft(key, { subject: subject?.value || '', message: textarea.value, savedAt: new Date().toISOString() });
    showToast('Draft saved locally');
  }

  // === Draft modal ===
  function showDraftsModal() {
    const overlay = document.createElement('div');
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:99998';
    const modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:min(780px,92%);max-height:78%;overflow:auto;background:#fff;padding:14px;border-radius:8px;z-index:99999';

    const h = document.createElement('h3'); h.textContent = 'Saved Drafts';
    modal.appendChild(h);

    const key = getContextKey();
    const drafts = getDraftList(key);
    const list = document.createElement('div');

    if (!drafts.length) {
      const p = document.createElement('div'); p.textContent = 'No drafts yet for this topic.'; list.appendChild(p);
    } else {
      drafts.forEach((d, idx) => {
        const row = document.createElement('div');
        row.style.borderBottom = '1px solid #eee';
        row.style.padding = '8px 0';

        const meta = document.createElement('div');
        meta.textContent = `${new Date(d.savedAt).toLocaleString()} — ${d.subject || '(no subject)'}`;
        meta.style.fontSize = '12px';
        meta.style.color = '#555';

        const excerpt = document.createElement('div');
        excerpt.textContent = (d.message || '').slice(0,300) + ((d.message||'').length>300 ? '…' : '');
        excerpt.style.margin = '6px 0';

        const load = document.createElement('button');
        load.type = 'button';
        load.textContent = 'Load';
        load.addEventListener('click', () => {
          const { textarea, subject } = findMessageElements();
          if (subject) subject.value = d.subject || '';
          if (textarea) textarea.value = d.message || '';
          showToast('Draft loaded');
          close();
        });

        const del = document.createElement('button');
        del.type = 'button';
        del.textContent = 'Delete';
        del.style.marginLeft = '6px';
        del.addEventListener('click', () => {
          if (!confirm('Delete this draft?')) return;
          deleteDraftAtIndex(key, idx);
          close();
          showDraftsModal();
        });

        row.append(meta, excerpt, load, del);
        list.appendChild(row);
      });
    }

    const closeBtn = document.createElement('button');
    closeBtn.type = 'button';
    closeBtn.textContent = 'Close';
    closeBtn.style.marginTop = '8px';
    closeBtn.addEventListener('click', close);
    modal.append(list, closeBtn);
    document.body.append(overlay, modal);

    function close() { modal.remove(); overlay.remove(); }
  }

  function deleteDraftAtIndex(key, idx) { const all = loadDrafts(); const arr = all[key]||[]; arr.splice(idx,1); all[key]=arr; saveDrafts(all); }

  // === Hook preview buttons to auto-save before their default action ===
  function attachPreviewSaveHook() {
    const selectors = [
      "input[name='preview']",
      "button[name='preview']",
      "input[type=submit][value*='Preview']",
      "button[type=submit][name='preview']",
      "input[data-value='Preview']"
    ];
    const nodes = selectors.map(s => Array.from(document.querySelectorAll(s))).flat();
    if (!nodes.length) return false;

    nodes.forEach(btn => {
      if (btn.dataset.altcHooked) return; btn.dataset.altcHooked = '1';
      // capture so we save before other listeners that may submit
      btn.addEventListener('click', function () {
        try { saveCurrentDraft(); } catch (e) { console.error(e); }
        // let the forum handle preview/post normally
      }, true);
    });

    return true;
  }

  // === Initialization ===
  function init() {
    insertControlsNextToButtons();
    attachPreviewSaveHook();

    // Retry once for dynamic pages
    setTimeout(() => { insertControlsNextToButtons(); attachPreviewSaveHook(); }, 1200);
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();

})();