Siteβ―Builder
Editing:
promptinator---ok.js
writable 0666
// promptinator.js ββ robust, multi-instance prompt tokenizer with inline edit icon and "edit" control export default class Promptinator { static init({ mount, defaultPrompt }) { // Use prompt from data attribute, argument, or text content const prompt = mount.dataset.prompt || defaultPrompt || mount.textContent.trim(); let template = prompt; const RX = /\[(?:([A-DI]?)-)?([^~|\-\]]+)(?:-([^~\]]+))?(?:~([^~]+)~)?\]/ig; const state = {}, meta = []; // Allow or disallow edit box based on attribute const allowEdit = mount.getAttribute('edit') !== '0' && mount.getAttribute('edit') !== 'false'; // --- Render function (updates meta, state, and prompt display) --- function render() { meta.length = 0; template.replace(RX, (_, cmd = '', lab, ops = '', def = '') => { meta.push({ cmd, lab, ops: ops.split('|').filter(Boolean), def }); state[lab] = def || lab; return ''; }); // Render tokens const html = template.replace(RX, (_, cmd = '', lab) => { const idx = meta.findIndex(m => m.lab === lab); return `<span class="token" data-i="${idx}">[${state[lab]}]</span>`; }); promptText.innerHTML = html.replace(/\n/g, '<br>'); } // --- Editor box, now INLINE before prompt --- const editor = document.createElement('div'); // Icon and edit box only if allowed let editIcon = 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 style="font-size:1.05em;vertical-align:middle;line-height:1;display:inline-block;">βοΈ</span> </span>` : ''; let formBox = 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>` : ''; editor.innerHTML = ` <div style="display:flex;align-items:baseline;gap:.5em;"> ${editIcon} <div class="prompt-text" style="flex:1 1 auto;"></div> </div> ${formBox} `; mount.innerHTML = ''; mount.append(editor); const promptText = editor.querySelector('.prompt-text'); let editLink, editForm, textarea; if (allowEdit) { editLink = editor.querySelector('#edit-link'); editForm = editor.querySelector('#edit-form'); 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(); editForm.style.display = 'none'; render(); }; } // --- Initial render --- render(); // --- Token click to popup --- mount.addEventListener('click', e => { if (!e.target.matches('.token')) return; const idx = Number(e.target.dataset.i); const info = meta[idx]; Promptinator._popup(info, state[info.lab], v => { state[info.lab] = v; e.target.textContent = '[' + v + ']'; }); }); } static _popup(meta, cur, done) { // singleton dialog 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 pretty = meta.lab.replace(/[-_]/g, ' ') .replace(/\b\w/g, c => c.toUpperCase()); let inner = ''; switch (meta.cmd) { case 'A': inner = `<label>${pretty}<textarea style="width:100%;font-size:1rem;" rows=4>${cur}</textarea></label>`; break; case 'B': inner = `<fieldset><legend>${pretty}</legend>` + meta.ops.map(o => ` <label><input type=radio name=r value="${o}" ${o === cur ? 'checked' : ''}> ${o}</label> `).join('') + `</fieldset>`; break; case 'C': inner = `<fieldset><legend>${pretty}</legend>` + meta.ops.map(o => ` <label><input type=checkbox value="${o}" ${cur.split(', ').includes(o) ? 'checked' : ''}> ${o}</label> `).join('') + `</fieldset><button>Done</button>`; break; case 'D': { let opts = meta.ops; if (opts.length === 1 && /^\d+$/.test(opts[0])) { opts = Array.from({ length: +opts[0] }, (_, i) => String(i + 1)); } inner = `<label>${pretty}<select>${opts.map(o => `<option${o === cur ? ' selected' : ''}>${o}</option>` ).join('')}</select></label>`; break; } case 'I': inner = `<label>${pretty} <select><option disabled selected>${cur}</option>` + meta.ops.map(u => `<option>${u}</option>`).join('') + `</select> </label> <iframe hidden></iframe> <button>Use site</button>`; break; default: inner = `<label>${pretty}<input type="text" style="width:100%;font-size:1rem;" value="${cur}"></label>`; } dlg.innerHTML = `<div class="popup-body">${inner}</div>`; dlg.showModal(); const commit = v => { done(v); dlg.close(); }; const pb = dlg.querySelector('.popup-body'); if (meta.cmd === 'A') { const ta = pb.querySelector('textarea'); ta.focus(); ta.addEventListener('blur', 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)) { const inp = pb.querySelector('input'); inp.focus(); inp.onblur = e => commit(e.target.value); inp.onkeydown = e => { if (e.key === 'Enter') commit(inp.value); }; } } }
Save changes
Create folder
writable 0777
Create
Cancel