Site Builder
Editing:
promptinator.js
writable 0666
/* Promptinator – 2025‑02‑XX * Parses bracket short‑codes ([A‑Label‑opts~def~] etc.), * renders tokens, lets the user edit via pop‑ups, * and keeps state for each token. * * FIX 2025‑02‑XX: * – Allow empty “ops” field so [A‑Label‑] is treated the same as * [A‑Label] instead of mis‑aligning captures. */ export default class Promptinator { static init({ mount, defaultPrompt }) { const prompt = mount.dataset.prompt || defaultPrompt || mount.textContent.trim(); let template = prompt; /* cmd = A | B | C | D | I (or '') * lab = label * ops = pipe‑separated options string (may be empty) * def = default / placeholder * * NOTE (+ ➜ *) in 3rd capture lets “ops” be empty. */ const RX = /\[(?:([A-DI]?)-)?([^~|\-\]]*)(?:-([^~\]]*))?(?:~([^~]+)~)?\]/ig; const state = {}; // tokenIndex ➜ current value const meta = []; // [{ cmd, lab, opts, def }, ...] const allowEdit = mount.getAttribute('edit') !== '0' && mount.getAttribute('edit') !== 'false'; const allowThink = !mount.hasAttribute('think') || (mount.getAttribute('think') !== '0' && mount.getAttribute('think') !== 'false'); /* ------------------------------------------------ render() */ function render() { meta.length = 0; let tokenIndex = 0; /* 1. First pass: build meta + initial state */ template.replace(RX, (_, cmd = '', lab = '', ops = '', def = '') => { lab = lab.trim(); const opts = ops.split('|').filter(Boolean); if (!(tokenIndex in state)) { state[tokenIndex] = cmd === 'C' ? (def ? def.split(',').map(v => v.trim()) : opts.slice(0, 1)) : (def || opts[0] || lab); } meta.push({ cmd, lab, opts, def }); tokenIndex++; return ''; }); /* 2. Second pass: replace tokens with <span class="token"> */ tokenIndex = 0; const html = template.replace(RX, (_, cmd = '', lab = '', ops = '', def = '') => { lab = lab.trim(); const val = state[tokenIndex]; let tokenHtml; if (cmd === 'C') { tokenHtml = (Array.isArray(val) ? val : [val]) .map(v => `<span class="token" data-i="${tokenIndex}" data-label="${lab}">[${v}]</span>`) .join(', '); } else { tokenHtml = `<span class="token" data-i="${tokenIndex}" data-label="${lab}">[${val}]</span>`; } tokenIndex++; return tokenHtml; }); /* 3. Build AI‑assistant buttons (optional) */ const aiButtons = allowThink ? ` <div class="ai-buttons" style="margin-top:1em;display:flex;gap:.6em;"> <a class="ai-btn chatgpt" target="_blank" style="display:inline-flex;align-items:center;gap:.3em;background:#5c3bff;color:#fff;font-weight:600;border-radius:7px;padding:.4em 1.2em;text-decoration:none;"> 🧠 ChatGPT </a> <a class="ai-btn perplexity" target="_blank" style="display:inline-flex;align-items:center;gap:.3em;background:#22a8e5;color:#fff;font-weight:600;border-radius:7px;padding:.4em 1.2em;text-decoration:none;"> 🔎 Perplexity </a> <a class="ai-btn copilot" target="_blank" style="display:inline-flex;align-items:center;gap:.3em;background:#10c775;color:#fff;font-weight:600;border-radius:7px;padding:.4em 1.2em;text-decoration:none;"> 🤖 Copilot </a> </div>` : ''; promptText.innerHTML = html.replace(/\n/g, '<br>') + aiButtons; /* 4. Update AI links if displayed */ if (allowThink) { let i = 0; const fullPrompt = template.replace(RX, (_, cmd = '', lab, ops = '', def = '') => { const v = state[i++]; return Array.isArray(v) ? v.join(', ') : v; }); promptText.querySelector('.chatgpt') .href = 'https://chat.openai.com/?prompt=' + encodeURIComponent(fullPrompt); promptText.querySelector('.perplexity').href = 'https://www.perplexity.ai/search?q=' + encodeURIComponent(fullPrompt); promptText.querySelector('.copilot') .href = 'https://copilot.microsoft.com/?q=' + encodeURIComponent(fullPrompt); } } /* ------------------------------------------------ mount UI */ const editor = document.createElement('div'); editor.innerHTML = ` <div style="display:flex;align-items:baseline;gap:.5em;"> ${allowEdit ? `<span id="edit-link" title="Edit prompt" style="cursor:pointer;font-size:1em;color:#5c3bff;display:inline-flex;align-items:center;margin-right:.25em;">✏️</span>` : ''} <div class="prompt-text" style="flex:1 1 auto;"></div> </div> ${allowEdit ? `<form id="edit-form" style="display:none;margin-top:.6em;"> <textarea style="width:100%;font-family:monospace;min-height:90px;"></textarea> <div style="text-align:right;margin-top:.7em;"><button type="submit">Save & Update</button></div> </form>` : ''} `; mount.innerHTML = ''; mount.append(editor); const promptText = editor.querySelector('.prompt-text'); /* Edit panel handlers ------------------------------------- */ if (allowEdit) { const editLink = editor.querySelector('#edit-link'); const editForm = editor.querySelector('#edit-form'); const textarea = editForm.querySelector('textarea'); editLink.onclick = () => { editForm.style.display = editForm.style.display === '' ? 'none' : ''; textarea.value = template; }; editForm.onsubmit = e => { e.preventDefault(); template = textarea.value.trim(); Object.keys(state).forEach(k => delete state[k]); editForm.style.display = 'none'; render(); }; } /* Initial paint */ render(); /* ------------------------------------------------ token click */ mount.addEventListener('click', e => { if (!e.target.matches('.token')) return; const idx = Number(e.target.dataset.i); const info = meta[idx]; // exact same order const value = state[idx]; Promptinator._popup( info, Array.isArray(value) ? value.join(', ') : value, v => { // commit callback state[idx] = info.cmd === 'C' ? v.split(',').map(s => s.trim()).filter(Boolean) : v; render(); } ); }); } /* --- end init --- */ /* ------------------------------------------------ pop‑up editor */ static _popup(meta, cur, done) { let dlg = document.getElementById('promptinator-dlg'); if (!dlg) { dlg = document.createElement('dialog'); dlg.id = 'promptinator-dlg'; document.body.append(dlg); dlg.addEventListener('click', e => { if (e.target === dlg) dlg.close(); }); dlg.addEventListener('cancel', e => { e.preventDefault(); dlg.close(); }); } const niceLabel = meta.lab && meta.lab.trim() ? meta.lab.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) : (cur || { A:'Text Input', B:'Choice', C:'Options', D:'Dropdown', I:'URL Picker' }[meta.cmd] || 'Input'); /* Build control by command type --------------------------- */ let inner = ''; switch (meta.cmd) { case 'A': // textarea inner = `<label>${niceLabel}<textarea style="width:100%;font-size:1rem;" rows="4">${cur}</textarea></label>`; break; case 'B': // radio buttons inner = `<fieldset><legend>${niceLabel}</legend>` + meta.opts.map(o => `<label><input type="radio" name="r" value="${o}" ${o === cur ? 'checked' : ''}> ${o}</label>` ).join('') + `</fieldset>`; break; case 'C': // checkboxes inner = `<fieldset><legend>${niceLabel}</legend>` + meta.opts.map(o => `<label><input type="checkbox" value="${o}" ${cur.split(', ').includes(o) ? 'checked' : ''}> ${o}</label>` ).join('') + `</fieldset><button>Done</button>`; break; case 'D': { // dropdown / select let opts = meta.opts; if (opts.length === 1 && /^\d+$/.test(opts[0])) { opts = Array.from({ length: +opts[0] }, (_, i) => String(i + 1)); } inner = `<label>${niceLabel}<select>` + opts.map(o => `<option${o === cur ? ' selected' : ''}>${o}</option>`).join('') + `</select></label>`; break; } case 'I': // URL picker + preview inner = `<label>${niceLabel} <select><option disabled selected>${cur}</option>` + meta.opts.map(u => `<option>${u}</option>`).join('') + `</select> </label> <iframe hidden></iframe> <button>Use site</button>`; break; default: // plain text input (no control letter) inner = `<label>${niceLabel}<input type="text" style="width:100%;font-size:1rem;" value="${cur}"></label>`; } dlg.innerHTML = `<div class="popup-body">${inner}</div>`; dlg.showModal(); const pb = dlg.querySelector('.popup-body'); const commit = v => { done(v); dlg.close(); }; /* Wire events --------------------------------------------- */ if (meta.cmd === 'A') { const ta = pb.querySelector('textarea'); ta.focus(); ta.onblur = e => commit(e.target.value); return; } if (meta.cmd === 'B') pb.onclick = e => { if (e.target.name === 'r') commit(e.target.value); }; if (meta.cmd === 'C') pb.querySelector('button').onclick = () => { const vals = [...pb.querySelectorAll('input:checked')].map(i => i.value); commit(vals.join(', ') || cur); }; if (meta.cmd === 'D') pb.querySelector('select').onchange = e => commit(e.target.value); if (meta.cmd === 'I') { const sel = pb.querySelector('select'); const fr = pb.querySelector('iframe'); sel.onchange = () => { fr.hidden = false; fr.src = sel.value; }; pb.querySelector('button').onclick = () => commit(sel.value || cur); } if (!/[A-DI]/.test(meta.cmd)) { // default input const inp = pb.querySelector('input'); inp.focus(); inp.onblur = () => commit(inp.value); inp.onkeydown = e => { if (e.key === 'Enter') commit(inp.value); }; } } }
Save changes
Create folder
writable 0777
Create
Cancel