Reorganize PHP internals and static assets
Move shared PHP code into private/, move JavaScript files into js/, and block direct access to private/. Remove unused API key and cache artifacts from the working tree.
This commit is contained in:
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function byId(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function renderPrompt(data, select, output) {
|
||||
const selectedId = select.value;
|
||||
const prompt = (data.prompts || []).find(function (item) {
|
||||
return String(item.id) === String(selectedId);
|
||||
});
|
||||
|
||||
if (!prompt) {
|
||||
output.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
output.value = String(prompt.content || '').split('{{bootstrap-racket-link}}').join(data.link || '');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const dataEl = byId('bootstrapPromptData');
|
||||
const select = byId('bootstrapPromptSelect');
|
||||
const output = byId('bootstrapPromptOutput');
|
||||
|
||||
if (!dataEl || !select || !output) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = { link: '', prompts: [] };
|
||||
|
||||
try {
|
||||
data = JSON.parse(dataEl.textContent || '{}');
|
||||
} catch (e) {
|
||||
data = { link: '', prompts: [] };
|
||||
}
|
||||
|
||||
select.addEventListener('change', function () {
|
||||
renderPrompt(data, select, output);
|
||||
});
|
||||
|
||||
renderPrompt(data, select, output);
|
||||
});
|
||||
}());
|
||||
@@ -0,0 +1,56 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function fallbackCopy(text) {
|
||||
const input = document.createElement('textarea');
|
||||
input.value = text;
|
||||
input.setAttribute('readonly', 'readonly');
|
||||
input.style.position = 'fixed';
|
||||
input.style.left = '-9999px';
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} finally {
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
}
|
||||
|
||||
function copyText(text) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
fallbackCopy(text);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function showCopied(button) {
|
||||
const original = button.dataset.copyLabel || button.textContent;
|
||||
const copied = button.dataset.copiedLabel || 'Copied';
|
||||
|
||||
button.textContent = copied;
|
||||
window.setTimeout(function () {
|
||||
button.textContent = original;
|
||||
}, 1400);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.js-copy-button').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
let text = button.dataset.copyText || '';
|
||||
const targetId = button.dataset.copyTarget || '';
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
|
||||
if (target) {
|
||||
text = target.value || target.textContent || '';
|
||||
}
|
||||
|
||||
copyText(text).then(function () {
|
||||
showCopied(button);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}());
|
||||
@@ -0,0 +1,516 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
let promptData = { personal: {}, default: {}, can_edit_defaults: false };
|
||||
let currentKind = null;
|
||||
let currentPrompt = null;
|
||||
let currentVersions = [];
|
||||
let versionIndex = 0;
|
||||
let uiText = {
|
||||
prompt_not_found: 'Prompt not found',
|
||||
no_previous_versions: 'No previous versions stored.',
|
||||
no_lines_for_view: 'No lines for this view.',
|
||||
restore_version_confirm: 'Restore version',
|
||||
delete_version_confirm: 'Delete version',
|
||||
default_prompt_prefix: 'Default prompt: ',
|
||||
prompt_prefix: 'Prompt: ',
|
||||
created: 'created',
|
||||
updated: 'updated',
|
||||
default_prompt: 'default prompt',
|
||||
version: 'version',
|
||||
showing_version: 'showing version',
|
||||
of: 'of',
|
||||
old: 'old',
|
||||
new: 'new'
|
||||
};
|
||||
|
||||
function byId(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function readPromptData() {
|
||||
const el = byId('promptDataJson');
|
||||
const textEl = byId('promptTextJson');
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
promptData = JSON.parse(el.textContent || '{}');
|
||||
} catch (e) {
|
||||
console.error('Could not parse prompt data JSON', e);
|
||||
promptData = { personal: {}, default: {} };
|
||||
}
|
||||
|
||||
if (!textEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uiText = Object.assign(uiText, JSON.parse(textEl.textContent || '{}'));
|
||||
} catch (e) {
|
||||
console.error('Could not parse prompt text JSON', e);
|
||||
}
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function fmtTs(ts) {
|
||||
if (!ts) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const d = new Date(ts * 1000);
|
||||
return d.toISOString().replace('T', ' ').slice(0, 19);
|
||||
}
|
||||
|
||||
function promptActionUrl(kind) {
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = '/prompts';
|
||||
url.searchParams.set('mode', kind === 'default' ? 'defaults' : 'personal');
|
||||
return url.pathname + url.search;
|
||||
}
|
||||
|
||||
function setEditing(editing) {
|
||||
const shell = byId('promptModalForm');
|
||||
const mayEdit = currentKind !== 'default' || promptData.can_edit_defaults;
|
||||
editing = editing && mayEdit;
|
||||
|
||||
shell.classList.toggle('edit-mode', editing);
|
||||
shell.classList.toggle('can-edit', mayEdit);
|
||||
shell.classList.toggle('read-only-default', currentKind === 'default' && !mayEdit);
|
||||
|
||||
byId('modalName').readOnly = !editing;
|
||||
byId('modalContent').readOnly = !editing;
|
||||
byId('modalDefaultKey').readOnly = !editing;
|
||||
byId('modalLanguage').disabled = !editing;
|
||||
}
|
||||
|
||||
function openEditModal() {
|
||||
if (!currentPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentKind === 'default' && !promptData.can_edit_defaults) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEditing(true);
|
||||
byId('promptModalBackdrop').classList.add('open');
|
||||
renderVersionPane();
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
byId('promptModalBackdrop').classList.remove('open');
|
||||
if (currentPrompt) {
|
||||
resetEditorFields();
|
||||
renderVersionPane();
|
||||
}
|
||||
}
|
||||
|
||||
function resetEditorFields() {
|
||||
byId('modalName').value = currentPrompt.name;
|
||||
byId('modalLanguage').value = currentPrompt.language;
|
||||
byId('modalContent').value = currentPrompt.content;
|
||||
byId('modalDefaultKey').value = currentPrompt.default_key || '';
|
||||
setEditing(false);
|
||||
}
|
||||
|
||||
function openPromptEditor(kind, id) {
|
||||
currentKind = kind;
|
||||
currentPrompt = promptData[kind] ? promptData[kind][id] : null;
|
||||
|
||||
if (!currentPrompt) {
|
||||
alert(uiText.prompt_not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
currentVersions = currentPrompt.versions || [];
|
||||
currentVersions.sort(function (a, b) {
|
||||
return b.version_no - a.version_no;
|
||||
});
|
||||
|
||||
/*
|
||||
* The newest stored version is often the current snapshot. If possible,
|
||||
* start by showing the one before that.
|
||||
*/
|
||||
versionIndex = currentVersions.length > 1 ? 1 : 0;
|
||||
|
||||
byId('modalTitle').textContent =
|
||||
(kind === 'default' ? uiText.default_prompt_prefix : uiText.prompt_prefix) +
|
||||
currentPrompt.name;
|
||||
byId('viewerTitle').textContent =
|
||||
(kind === 'default' ? uiText.default_prompt_prefix : uiText.prompt_prefix) +
|
||||
currentPrompt.name;
|
||||
|
||||
byId('modalMeta').textContent =
|
||||
uiText.created + ' ' + fmtTs(currentPrompt.created_at) +
|
||||
' · ' + uiText.updated + ' ' + fmtTs(currentPrompt.updated_at) +
|
||||
(kind === 'default' && !promptData.can_edit_defaults ? ' · ' + uiText.default_prompt : '');
|
||||
byId('viewerMeta').textContent = byId('modalMeta').textContent;
|
||||
byId('viewerContent').textContent = currentPrompt.content;
|
||||
|
||||
byId('defaultKeyRow').style.display = kind === 'default' ? 'block' : 'none';
|
||||
|
||||
if (kind === 'default') {
|
||||
byId('modalAction').value = 'update_default';
|
||||
byId('modalDefaultId').value = currentPrompt.id;
|
||||
byId('modalPromptId').value = '';
|
||||
} else {
|
||||
byId('modalAction').value = 'update_prompt';
|
||||
byId('modalPromptId').value = currentPrompt.id;
|
||||
byId('modalDefaultId').value = '';
|
||||
}
|
||||
|
||||
byId('promptModalForm').action = promptActionUrl(kind);
|
||||
byId('modalAuxForm').action = promptActionUrl(kind);
|
||||
|
||||
byId('promptViewer').classList.remove('is-empty');
|
||||
byId('promptViewer').classList.toggle('is-default-prompt', kind === 'default');
|
||||
byId('promptModalBackdrop').classList.toggle('is-default-prompt', kind === 'default');
|
||||
byId('editPromptButton').style.display =
|
||||
kind === 'default' && !promptData.can_edit_defaults ? 'none' : '';
|
||||
document.querySelectorAll('.prompt-select').forEach(function (button) {
|
||||
button.classList.toggle(
|
||||
'selected',
|
||||
button.dataset.kind === kind && button.dataset.id === String(id)
|
||||
);
|
||||
});
|
||||
|
||||
resetEditorFields();
|
||||
renderVersionPane();
|
||||
}
|
||||
|
||||
function currentSelectedVersion() {
|
||||
if (currentVersions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (versionIndex < 0) {
|
||||
versionIndex = 0;
|
||||
}
|
||||
|
||||
if (versionIndex >= currentVersions.length) {
|
||||
versionIndex = currentVersions.length - 1;
|
||||
}
|
||||
|
||||
return currentVersions[versionIndex];
|
||||
}
|
||||
|
||||
function olderVersion() {
|
||||
if (currentVersions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (versionIndex < currentVersions.length - 1) {
|
||||
versionIndex++;
|
||||
renderVersionPane();
|
||||
}
|
||||
}
|
||||
|
||||
function newerVersion() {
|
||||
if (currentVersions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minIndex = currentVersions.length > 1 ? 1 : 0;
|
||||
|
||||
if (versionIndex > minIndex) {
|
||||
versionIndex--;
|
||||
renderVersionPane();
|
||||
}
|
||||
}
|
||||
|
||||
function renderVersionPane() {
|
||||
const version = currentSelectedVersion();
|
||||
const contentEl = byId('versionContent');
|
||||
const metaEl = byId('selectedVersionMeta');
|
||||
const indicatorEl = byId('versionIndicator');
|
||||
|
||||
if (!version) {
|
||||
contentEl.innerHTML = '<span class="diff-muted">' +
|
||||
esc(uiText.no_previous_versions) +
|
||||
'</span>';
|
||||
metaEl.textContent = '';
|
||||
indicatorEl.textContent = '';
|
||||
return;
|
||||
}
|
||||
|
||||
metaEl.textContent =
|
||||
uiText.version + ' ' + version.version_no +
|
||||
' · ' + fmtTs(version.created_at) +
|
||||
(version.note ? ' · ' + version.note : '');
|
||||
|
||||
indicatorEl.textContent =
|
||||
uiText.showing_version + ' ' + version.version_no +
|
||||
' (' + (versionIndex + 1) + ' ' + uiText.of + ' ' + currentVersions.length + ')';
|
||||
|
||||
const currentText = byId('modalContent').value;
|
||||
const oldText = version.content;
|
||||
const mode = byId('diffMode').value;
|
||||
|
||||
if (mode === 'plain') {
|
||||
contentEl.innerHTML = esc(oldText);
|
||||
return;
|
||||
}
|
||||
|
||||
contentEl.innerHTML = renderLineDiff(oldText, currentText, mode);
|
||||
}
|
||||
|
||||
function linesOf(s) {
|
||||
return String(s).split(/\r?\n/);
|
||||
}
|
||||
|
||||
function lcsTable(a, b) {
|
||||
const m = a.length;
|
||||
const n = b.length;
|
||||
const dp = Array.from({ length: m + 1 }, function () {
|
||||
return Array(n + 1).fill(0);
|
||||
});
|
||||
|
||||
for (let i = m - 1; i >= 0; i--) {
|
||||
for (let j = n - 1; j >= 0; j--) {
|
||||
if (a[i] === b[j]) {
|
||||
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||
} else {
|
||||
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dp;
|
||||
}
|
||||
|
||||
function diffOps(oldLines, newLines) {
|
||||
const dp = lcsTable(oldLines, newLines);
|
||||
const ops = [];
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
|
||||
while (i < oldLines.length && j < newLines.length) {
|
||||
if (oldLines[i] === newLines[j]) {
|
||||
ops.push({ type: 'same', oldLine: oldLines[i], newLine: newLines[j] });
|
||||
i++;
|
||||
j++;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
ops.push({ type: 'deleted', oldLine: oldLines[i] });
|
||||
i++;
|
||||
} else {
|
||||
ops.push({ type: 'added', newLine: newLines[j] });
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < oldLines.length) {
|
||||
ops.push({ type: 'deleted', oldLine: oldLines[i] });
|
||||
i++;
|
||||
}
|
||||
|
||||
while (j < newLines.length) {
|
||||
ops.push({ type: 'added', newLine: newLines[j] });
|
||||
j++;
|
||||
}
|
||||
|
||||
return pairChangedLines(ops);
|
||||
}
|
||||
|
||||
function pairChangedLines(ops) {
|
||||
const paired = [];
|
||||
|
||||
for (let k = 0; k < ops.length; k++) {
|
||||
const a = ops[k];
|
||||
const b = ops[k + 1];
|
||||
|
||||
if (a && b && a.type === 'deleted' && b.type === 'added') {
|
||||
paired.push({
|
||||
type: 'changed',
|
||||
oldLine: a.oldLine,
|
||||
newLine: b.newLine
|
||||
});
|
||||
k++;
|
||||
} else if (a && b && a.type === 'added' && b.type === 'deleted') {
|
||||
paired.push({
|
||||
type: 'changed',
|
||||
oldLine: b.oldLine,
|
||||
newLine: a.newLine
|
||||
});
|
||||
k++;
|
||||
} else {
|
||||
paired.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
return paired;
|
||||
}
|
||||
|
||||
function renderLineDiff(oldText, newText, mode) {
|
||||
const ops = diffOps(linesOf(oldText), linesOf(newText));
|
||||
const out = [];
|
||||
|
||||
for (const op of ops) {
|
||||
if (mode !== 'all' && mode !== op.type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (op.type === 'same') {
|
||||
out.push('<span class="diff-line diff-same">' + esc(op.oldLine) + '</span>');
|
||||
} else if (op.type === 'added') {
|
||||
out.push('<span class="diff-line diff-added">+ ' + esc(op.newLine) + '</span>');
|
||||
} else if (op.type === 'deleted') {
|
||||
out.push('<span class="diff-line diff-deleted">- ' + esc(op.oldLine) + '</span>');
|
||||
} else if (op.type === 'changed') {
|
||||
out.push(
|
||||
'<span class="diff-line diff-changed">~ ' +
|
||||
esc(uiText.old) +
|
||||
': ' +
|
||||
esc(op.oldLine) +
|
||||
'</span>'
|
||||
);
|
||||
out.push(
|
||||
'<span class="diff-line diff-changed">~ ' +
|
||||
esc(uiText.new) +
|
||||
': ' +
|
||||
esc(op.newLine) +
|
||||
'</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (out.length === 0) {
|
||||
return '<span class="diff-muted">' + esc(uiText.no_lines_for_view) + '</span>';
|
||||
}
|
||||
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
function configureAux(action, versionNo) {
|
||||
const aux = byId('modalAuxForm');
|
||||
|
||||
byId('auxAction').value = action;
|
||||
byId('auxVersionNo').value = versionNo || '';
|
||||
byId('auxVersionNote').value = 'modal snapshot';
|
||||
|
||||
if (currentKind === 'default') {
|
||||
byId('auxDefaultId').value = currentPrompt.id;
|
||||
byId('auxPromptId').value = '';
|
||||
} else {
|
||||
byId('auxPromptId').value = currentPrompt.id;
|
||||
byId('auxDefaultId').value = '';
|
||||
}
|
||||
|
||||
return aux;
|
||||
}
|
||||
|
||||
function storeSnapshot() {
|
||||
if (!currentPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = currentKind === 'default'
|
||||
? 'create_default_version'
|
||||
: 'create_version';
|
||||
|
||||
configureAux(action, '').submit();
|
||||
}
|
||||
|
||||
function restoreSelectedVersion() {
|
||||
const version = currentSelectedVersion();
|
||||
|
||||
if (!currentPrompt || !version) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(uiText.restore_version_confirm + ' ' + version.version_no + '?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = currentKind === 'default'
|
||||
? 'restore_default_version'
|
||||
: 'restore_version';
|
||||
|
||||
configureAux(action, version.version_no).submit();
|
||||
}
|
||||
|
||||
function deleteSelectedVersion() {
|
||||
const version = currentSelectedVersion();
|
||||
|
||||
if (!currentPrompt || !version) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(uiText.delete_version_confirm + ' ' + version.version_no + '?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = currentKind === 'default'
|
||||
? 'delete_default_version'
|
||||
: 'delete_version';
|
||||
|
||||
configureAux(action, version.version_no).submit();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.querySelectorAll('.prompt-tab').forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
const tabName = tab.dataset.tab;
|
||||
|
||||
document.querySelectorAll('.prompt-tab').forEach(function (other) {
|
||||
const active = other === tab;
|
||||
other.classList.toggle('active', active);
|
||||
other.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.prompt-tab-panel').forEach(function (panel) {
|
||||
panel.classList.toggle('active', panel.id === 'tab-' + tabName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-open-prompt').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
openPromptEditor(button.dataset.kind, button.dataset.id);
|
||||
});
|
||||
});
|
||||
|
||||
byId('editPromptButton').addEventListener('click', function () {
|
||||
openEditModal();
|
||||
});
|
||||
byId('cancelEditButton').addEventListener('click', function () {
|
||||
closeEditModal();
|
||||
});
|
||||
byId('versionNewerButton').addEventListener('click', newerVersion);
|
||||
byId('versionOlderButton').addEventListener('click', olderVersion);
|
||||
byId('diffMode').addEventListener('change', renderVersionPane);
|
||||
byId('modalContent').addEventListener('input', renderVersionPane);
|
||||
byId('snapshotButton').addEventListener('click', storeSnapshot);
|
||||
byId('restoreVersionButton').addEventListener('click', restoreSelectedVersion);
|
||||
byId('deleteVersionButton').addEventListener('click', deleteSelectedVersion);
|
||||
|
||||
byId('promptModalBackdrop').addEventListener('click', function (ev) {
|
||||
if (ev.target === byId('promptModalBackdrop')) {
|
||||
closeEditModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Escape') {
|
||||
closeEditModal();
|
||||
}
|
||||
});
|
||||
|
||||
setEditing(false);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
readPromptData();
|
||||
bindEvents();
|
||||
});
|
||||
}());
|
||||
Reference in New Issue
Block a user