144 lines
5.2 KiB
JavaScript
144 lines
5.2 KiB
JavaScript
import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm';
|
||
|
||
const env = document.getElementById('supabase-env');
|
||
const supabaseUrl = env.dataset.url;
|
||
const supabaseKey = env.dataset.key;
|
||
|
||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||
|
||
const promptContainer = document.getElementById('prompt-container');
|
||
const errorBox = document.getElementById('prompt-error');
|
||
|
||
const formatDate = (dateStr) => {
|
||
if (!dateStr) return "–";
|
||
const date = new Date(dateStr);
|
||
return isNaN(date.getTime())
|
||
? "Invalid date"
|
||
: date.toLocaleDateString('en-US', {
|
||
month: 'short',
|
||
day: 'numeric',
|
||
year: 'numeric',
|
||
});
|
||
};
|
||
|
||
function renderPromptCard(prompt) {
|
||
const sortedTags = (prompt.tags ?? []).slice().sort((a, b) =>
|
||
a.toLowerCase().localeCompare(b.toLowerCase())
|
||
);
|
||
const tagSpans = sortedTags.map(tag =>
|
||
`<span class="text-sm bg-gray-200 text-gray-800 px-2 py-1 pt-0 rounded">${tag}</span>`
|
||
).join('');
|
||
const escapedDescription = (prompt.description ?? '').replace(/\n/g, '<br />');
|
||
const notes = prompt.notes ?? '';
|
||
|
||
return `
|
||
<div class="prompt-card border border-gray-400 rounded p-4 bg-gray-700 text-gray-200 shadow-sm flex flex-col gap-2 min-h-[12rem] max-h-fit"
|
||
data-type="${prompt.type}" data-tags="${(prompt.tags ?? []).join(',')}">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<h3 class="text-xl font-semibold">${prompt.title}</h3>
|
||
<span class="text-sm font-medium px-2 py-1 rounded ${
|
||
prompt.type === 'System'
|
||
? 'bg-blue-100 text-blue-700'
|
||
: 'bg-green-100 text-green-700'
|
||
}">${prompt.type}</span>
|
||
</div>
|
||
<p class="text-md mb-4">${notes}</p>
|
||
<div class="flex flex-wrap gap-2 mt-2 mb-4">${tagSpans}</div>
|
||
<details name="prompt-details">
|
||
<summary class="cursor-pointer font-semibold mt-2 text-lg">View Details</summary>
|
||
<div class="text-md border-t mt-2 pt-2">
|
||
<div class="flex justify-between mx-2 items-center border-b border-b-gray-400 pb-2 mb-2">
|
||
<h3 class="text-xl font-semibold">Prompt</h3>
|
||
|
||
<a class="bg-green-600 text-white px-2 py-0 rounded text-sm hover:bg-green-700 transition-colors duration-300" href="/edit?slug=${prompt.slug}">Edit</a>
|
||
</div>
|
||
|
||
<p class="my-2 px-2 text-balance">${escapedDescription}</p>
|
||
|
||
<hr class="my-2" />
|
||
<p class="text-sm"><strong>Created:</strong> ${formatDate(prompt.created_at)} • <strong>Updated:</strong> ${formatDate(prompt.updated_at)}</p>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function updateFiltersFromTags(prompts) {
|
||
const sortedTags = Array.from(new Set(
|
||
prompts.flatMap(p => p.tags ?? [])
|
||
)).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||
const tagSet = new Set(sortedTags);
|
||
const tagContainer = document.querySelector('#filters fieldset > div');
|
||
if (!tagContainer) return;
|
||
|
||
tagContainer.innerHTML = '';
|
||
|
||
tagSet.forEach(tag => {
|
||
const label = document.createElement('label');
|
||
label.className = "relative inline-flex items-center cursor-pointer";
|
||
label.innerHTML = `
|
||
<input type="checkbox" name="tag" value="${tag}" class="sr-only peer">
|
||
<span class="px-3 py-1 pt-0 rounded-full border border-gray-300 peer-checked:bg-blue-600 peer-checked:text-white hover:bg-blue-600 hover:text-white transition-colors duration-300 text-sm">${tag}</span>
|
||
`;
|
||
tagContainer.appendChild(label);
|
||
});
|
||
}
|
||
|
||
function filterCards() {
|
||
const form = document.getElementById('filter-form');
|
||
const cards = document.querySelectorAll('.prompt-card');
|
||
const params = new URLSearchParams(new FormData(form));
|
||
const type = params.get('type');
|
||
const query = params.get('q')?.toLowerCase() || '';
|
||
const tagParams = params.getAll('tag');
|
||
|
||
let visibleCount = 0;
|
||
|
||
cards.forEach(card => {
|
||
const cardType = card.dataset.type;
|
||
const cardTags = card.dataset.tags.split(',');
|
||
const cardText = card.innerText.toLowerCase();
|
||
|
||
const matchesType = !type || type === cardType;
|
||
const matchesTags = tagParams.length === 0 || tagParams.some(t => cardTags.includes(t));
|
||
const matchesSearch = !query || cardText.includes(query);
|
||
|
||
const show = matchesType && matchesTags && matchesSearch;
|
||
card.style.display = show ? 'block' : 'none';
|
||
if (show) visibleCount++;
|
||
});
|
||
|
||
const countLabel = document.getElementById('prompt-count');
|
||
if (countLabel) {
|
||
countLabel.textContent = visibleCount === 1
|
||
? '1 prompt shown'
|
||
: `${visibleCount} prompts shown`;
|
||
}
|
||
}
|
||
|
||
async function loadPrompts() {
|
||
const { data: prompts, error } = await supabase.from('prompts').select('*').order('title');
|
||
|
||
if (error) {
|
||
promptContainer.innerHTML = '';
|
||
errorBox.textContent = error.message;
|
||
errorBox.classList.remove('hidden');
|
||
return;
|
||
}
|
||
|
||
promptContainer.innerHTML = prompts.map(renderPromptCard).join('');
|
||
updateFiltersFromTags(prompts);
|
||
filterCards(); // initial run
|
||
|
||
const form = document.getElementById('filter-form');
|
||
form?.addEventListener('input', filterCards);
|
||
|
||
const clearBtn = document.getElementById('clear-filters');
|
||
clearBtn?.addEventListener('click', () => {
|
||
form.reset();
|
||
filterCards();
|
||
});
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', loadPrompts);
|