<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Prompt Organizer</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f9f9f9;
color: #333;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
box-sizing: border-box;
padding-bottom: 70px; /* Space for fixed footer */
position: relative; /* Needed for absolute positioning of footer */
}
#app-title {
color: #4CAF50;
margin-top: 2rem;
margin-bottom: 2rem;
text-align: center;
}
#prompt-list-container {
width: 95%;
max-width: 1200px;
margin-bottom: 80px; /* Margin to prevent overlap with footer */
}
#prompt-list {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
margin-top: 1rem;
}
.prompt-card {
background-color: #fff;
padding: 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
.prompt-card:hover {
transform: translateY(-0.5rem);
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.prompt-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.prompt-header h3{
margin: 0;
color: #2c3e50;
}
.prompt-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.prompt-tag {
background-color: #e0e0e0;
padding: 0.25rem 0.5rem;
border-radius: 1rem;
font-size: 0.8rem;
color: #555;
}
.prompt-tag:hover {
background-color: #d0d0d0;
}
.prompt-description {
margin-bottom: 1rem;
color: #444;
line-height: 1.5;
}
.prompt-actions {
display: flex;
gap: 0.5rem;
align-items: center; /* Vertically align items */
}
.edit-btn, .delete-btn {
padding: 0.5rem 1rem;
border-radius: 0.3rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
font-family: 'Inter', sans-serif;
font-size: 0.9rem;
display: flex; /* Use flexbox for icon alignment */
align-items: center; /* Vertically center icon and text */
gap: 0.5rem; /* Space between icon and text */
}
.edit-btn {
background-color: #4CAF50;
color: white;
}
.edit-btn:hover {
background-color: #45a049;
}
.delete-btn {
background-color: #f44336;
color: white;
}
.delete-btn:hover {
background-color: #d32f2f;
}
#add-prompt-btn {
position: fixed; /* Fix button to bottom right */
bottom: 1rem;
right: 1rem;
padding: 0.75rem 1.5rem;
background-color: #007BFF;
color: white;
border: none;
border-radius: 0.3rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
font-family: 'Inter', sans-serif;
display: flex; /* Use flexbox for icon alignment */
align-items: center; /* Vertically center icon and text */
gap: 0.5rem; /* Space between icon and text */
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
#add-prompt-btn:hover {
background-color: #0056b3;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.5);
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
padding: 2rem;
border-radius: 0.5rem;
width: 95%;
max-width: 600px;
position: relative;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
border-bottom: 1px solid #ddd;
padding-bottom: 1rem;
}
.modal-header h2 {
margin: 0;
color: #2c3e50;
}
.close-modal-btn {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
cursor: pointer;
color: #888;
transition: color 0.2s ease-in-out;
border: none;
background: none;
padding: 0;
}
.close-modal-btn:hover {
color: #333;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-weight: 500;
font-size: 0.9rem;
}
.form-group input, .form-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 0.3rem;
font-size: 1rem;
font-family: 'Inter', sans-serif;
transition: border-color 0.2s ease-in-out;
}
.form-group input:focus, .form-group textarea:focus {
outline: none;
border-color: #4CAF50;
}
.form-group textarea {
resize: vertical;
height: 100px;
}
#modal-save-btn {
padding: 0.75rem 1.5rem;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 0.3rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
font-family: 'Inter', sans-serif;
display: block;
margin-left: auto;
margin-top: 1rem;
}
#modal-save-btn:hover {
background-color: #45a049;
}
#filter-controls {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 2rem;
justify-content: space-between; /* Distribute items evenly */
align-items: center; /* Vertically align items */
}
#filter-controls .input-group {
display: flex;
flex-direction: column;
min-width: 200px; /* Ensure a minimum width for each group */
}
#filter-controls .input-group label {
margin-bottom: 0.25rem;
color: #555;
font-weight: 500;
font-size: 0.9rem;
}
#filter-controls .input-group input,
#filter-controls .input-group select {
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 0.3rem;
font-size: 1rem;
font-family: 'Inter', sans-serif;
transition: border-color 0.2s ease-in-out;
width: 100%; /* Make input and select fill their container */
box-sizing: border-box; /* Include padding and border in element's total width and height */
}
#filter-controls .input-group input:focus,
#filter-controls .input-group select:focus {
outline: none;
border-color: #4CAF50;
}
#no-prompts-message {
text-align: center;
color: #777;
margin-top: 2rem;
font-size: 1.2rem;
}
.icon {
width: 1rem; /* Icon size */
height: 1rem;
}
/* Styles for icons - using simple shapes, you can replace with actual SVG if desired */
.add-icon {
/* Plus sign */
display: inline-block;
width: 1rem;
height: 1rem;
border: 2px solid white;
border-radius: 50%;
position: relative;
}
.add-icon::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 60%;
height: 20%;
background-color: white;
transform: translate(-50%, -50%);
}
.add-icon::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20%;
height: 60%;
background-color: white;
transform: translate(-50%, -50%);
}
.edit-icon {
/* Pencil Icon */
display: inline-block;
width: 1rem;
height: 1rem;
position: relative;
}
.edit-icon::before {
content: '';
position: absolute;
left: 25%;
top: 10%;
width: 60%;
height: 8%;
background-color: white;
border-radius: 0.2rem;
transform: rotate(45deg);
}
.edit-icon::after{
content: '';
position: absolute;
left: 15%;
top: 30%;
width: 8%;
height: 60%;
background-color: white;
border-radius: 0.2rem;
}
.delete-icon {
/* Trash Can Icon */
display: inline-block;
width: 1rem;
height: 1rem;
position: relative;
}
.delete-icon::before {
content: '';
position: absolute;
bottom: 20%;
left: 20%;
width: 60%;
height: 10%;
background-color: white;
border-radius: 0.2rem;
}
.delete-icon::after{
content: '';
position: absolute;
top: 20%;
left: 40%;
width: 20%;
height: 70%;
background-color: white;
border-top-left-radius: 0.3rem;
border-top-right-radius: 0.3rem;
}
.delete-icon_top{
content: '';
position: absolute;
top: 10%;
left: 30%;
width: 40%;
height: 10%;
background-color: white;
border-radius: 0.3rem;
}
</style>
</head>
<body>
<h1 id="app-title">Prompt Organizer</h1>
<div id="filter-controls">
<div class="input-group">
<label for="search-input">Search Prompts</label>
<input type="text" id="search-input" placeholder="Enter keywords...">
</div>
<div class="input-group">
<label for="tag-filter">Filter by Tag</label>
<select id="tag-filter">
<option value="">All Tags</option>
</select>
</div>
<div class="input-group">
<label for="sort-select">Sort by</label>
<select id="sort-select">
<option value="created-asc">Date Created (Asc)</option>
<option value="created-desc">Date Created (Desc)</option>
<option value="modified-asc">Date Modified (Asc)</option>
<option value="modified-desc">Date Modified (Desc)</option>
</select>
</div>
</div>
<div id="prompt-list-container">
<div id="prompt-list">
</div>
<p id="no-prompts-message" style="display:none;">No prompts found.</p>
</div>
<button id="add-prompt-btn">
<span class="add-icon"></span>
Add Prompt
</button>
<div id="prompt-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Edit Prompt</h2>
<button id="close-modal-btn" class="close-modal-btn">×</button>
</div>
<form id="edit-prompt-form">
<div class="form-group">
<label for="prompt-title">Title</label>
<input type="text" id="prompt-title" placeholder="Enter prompt title" required>
</div>
<div class="form-group">
<label for="prompt-description">Description</label>
<textarea id="prompt-description" placeholder="Enter prompt description"></textarea>
</div>
<div class="form-group">
<label for="prompt-tags">Tags (comma-separated)</label>
<input type="text" id="prompt-tags" placeholder="Enter tags, separated by commas">
</div>
<div class="form-group">
<label for="prompt-text">Prompt</label>
<textarea id="prompt-text" placeholder="Enter your prompt here" required></textarea>
</div>
<button id="modal-save-btn" type="submit">Save Prompt</button>
</form>
</div>
</div>
<script>
const promptList = document.getElementById('prompt-list');
const addPromptBtn = document.getElementById('add-prompt-btn');
const promptModal = document.getElementById('prompt-modal');
const closeModalBtn = document.getElementById('close-modal-btn');
const editPromptForm = document.getElementById('edit-prompt-form');
const modalTitleInput = document.getElementById('prompt-title');
const modalDescriptionInput = document.getElementById('prompt-description');
const modalTagsInput = document.getElementById('prompt-tags');
const modalTextInput = document.getElementById('prompt-text');
const tagFilterSelect = document.getElementById('tag-filter');
const searchInput = document.getElementById('search-input');
const sortSelect = document.getElementById('sort-select');
const noPromptsMessage = document.getElementById('no-prompts-message');
let prompts = [];
let editingIndex = null;
// Helper Functions
/**
* Creates a tag element for display
* @param {string} tag - The tag text
* @returns {HTMLSpanElement} - The tag element
*/
function createTagElement(tag) {
const tagElement = document.createElement('span');
tagElement.classList.add('prompt-tag');
tagElement.textContent = tag;
return tagElement;
}
/**
* Renders the prompt list based on current filters and sorting
*/
function renderPromptList() {
const filteredPrompts = filterAndSortPrompts(prompts);
promptList.innerHTML = ''; // Clear the list
if (filteredPrompts.length === 0) {
noPromptsMessage.style.display = 'block';
} else {
noPromptsMessage.style.display = 'none';
filteredPrompts.forEach((prompt, index) => {
const promptCard = document.createElement('div');
promptCard.classList.add('prompt-card');
const promptHeader = document.createElement('div');
promptHeader.classList.add('prompt-header');
const titleElement = document.createElement('h3');
titleElement.textContent = prompt.title;
const actionsDiv = document.createElement('div');
actionsDiv.classList.add('prompt-actions');
const editButton = document.createElement('button');
editButton.classList.add('edit-btn');
editButton.innerHTML = `<span class="edit-icon"></span>Edit`;
editButton.addEventListener('click', () => openModal(index));
const deleteButton = document.createElement('button');
deleteButton.classList.add('delete-btn');
deleteButton.innerHTML = `<span class="delete-icon"></span>Delete`;
deleteButton.addEventListener('click', () => deletePrompt(index));
actionsDiv.appendChild(editButton);
actionsDiv.appendChild(deleteButton);
promptHeader.appendChild(titleElement);
promptHeader.appendChild(actionsDiv);
const tagsElement = document.createElement('div');
tagsElement.classList.add('prompt-tags');
prompt.tags.forEach(tag => {
tagsElement.appendChild(createTagElement(tag));
});
const descriptionElement = document.createElement('p');
descriptionElement.classList.add('prompt-description');
descriptionElement.textContent = prompt.description;
promptCard.appendChild(promptHeader);
promptCard.appendChild(tagsElement);
promptCard.appendChild(descriptionElement);
promptList.appendChild(promptCard);
});
}
}
/**
* Filters and sorts prompts based on search, tag, and sort criteria
* @param {Array<Prompt>} prompts - The array of prompts to filter and sort
* @returns {Array<Prompt>} - The filtered and sorted array of prompts
*/
function filterAndSortPrompts(prompts) {
const searchTerm = searchInput.value.toLowerCase();
const selectedTag = tagFilterSelect.value;
const sortValue = sortSelect.value;
let filteredPrompts = prompts;
// Filter by search term
if (searchTerm) {
filteredPrompts = filteredPrompts.filter(prompt =>
prompt.title.toLowerCase().includes(searchTerm) ||
prompt.description.toLowerCase().includes(searchTerm) ||
prompt.text.toLowerCase().includes(searchTerm)
);
}
// Filter by tag
if (selectedTag) {
filteredPrompts = filteredPrompts.filter(prompt =>
prompt.tags.includes(selectedTag)
);
}
// Sort prompts
if (sortValue === 'created-asc') {
filteredPrompts.sort((a, b) => a.createdAt - b.createdAt);
} else if (sortValue === 'created-desc') {
filteredPrompts.sort((a, b) => b.createdAt - a.createdAt);
} else if (sortValue === 'modified-asc') {
filteredPrompts.sort((a, b) => a.modifiedAt - b.modifiedAt);
} else if (sortValue === 'modified-desc') {
filteredPrompts.sort((a, b) => b.modifiedAt - a.modifiedAt);
}
return filteredPrompts;
}
/**
* Opens the modal for editing or adding a prompt
* @param {number|null} index - The index of the prompt to edit, or null to add a new prompt
*/
function openModal(index = null) {
editingIndex = index;
if (index !== null) {
const prompt = prompts[index];
modalTitleInput.value = prompt.title;
modalDescriptionInput.value = prompt.description;
modalTagsInput.value = prompt.tags.join(', ');
modalTextInput.value = prompt.text;
} else {
modalTitleInput.value = '';
modalDescriptionInput.value = '';
modalTagsInput.value = '';
modalTextInput.value = '';
}
promptModal.style.display = 'flex';
}
/**
* Closes the modal
*/
function closeModal() {
promptModal.style.display = 'none';
}
/**
* Saves the prompt data (new or edited)
*/
function savePrompt() {
const title = modalTitleInput.value.trim();
const description = modalDescriptionInput.value.trim();
const tags = modalTagsInput.value.split(',').map(tag => tag.trim()).filter(tag => tag);
const text = modalTextInput.value.trim();
if (!title || !text) {
alert('Title and Prompt are required.'); // Basic validation
return;
}
if (editingIndex !== null) {
// Update existing prompt
prompts[editingIndex].title = title;
prompts[editingIndex].description = description;
prompts[editingIndex].tags = tags;
prompts[editingIndex].text = text;
prompts[editingIndex].modifiedAt = new Date();
} else {
// Add new prompt
const newPrompt = {
title,
description,
tags,
text,
createdAt: new Date(),
modifiedAt: new Date()
};
prompts.push(newPrompt);
}
savePromptsToLocalStorage();
updateTagFilterOptions();
renderPromptList();
closeModal();
}
/**
* Deletes a prompt
* @param {number} index - The index of the prompt to delete
*/
function deletePrompt(index) {
if (confirm('Are you sure you want to delete this prompt?')) {
prompts.splice(index, 1);
savePromptsToLocalStorage();
updateTagFilterOptions();
renderPromptList();
}
}
/**
* Saves prompts to localStorage
*/
function savePromptsToLocalStorage() {
localStorage.setItem('prompts', JSON.stringify(prompts));
}
/**
* Loads prompts from localStorage
*/
function loadPromptsFromLocalStorage() {
const savedPrompts = localStorage.getItem('prompts');
if (savedPrompts) {
try{
prompts = JSON.parse(savedPrompts);
} catch(e){
console.error("Error parsing saved prompts", e);
prompts = [];
}
} else {
// Initialize with default prompts only if no existing data
const defaultPrompts = [
{
title: "Example Prompt 1",
description: "This is a sample prompt for generating creative content.",
tags: ["creative","example"],
text: "Write a short story about a cat exploring a new planet.",
createdAt: new Date(),
modifiedAt: new Date()
},
{
title: "Example Prompt 2",
description: "A prompt for summarizing a long article.",
tags: ["summary", "example"],
text: "Summarize the following article in three concise paragraphs: [article text here]",
createdAt: new Date(),
modifiedAt: new Date()
},
{
title: "Example Prompt 3",
description: "Prompt to generate marketing copy.",
tags: ["marketing", "example"],
text: "Write compelling marketing copy for a new brand of coffee, highlighting its unique flavor profile and ethical sourcing.",
createdAt: new Date(),
modifiedAt: new Date()
},
];
prompts = defaultPrompts;
savePromptsToLocalStorage(); //save default prompts
}
}
/**
* Updates the tag filter dropdown based on the available tags
*/
function updateTagFilterOptions() {
const allTags = new Set();
prompts.forEach(prompt => {
prompt.tags.forEach(tag => allTags.add(tag));
});
tagFilterSelect.innerHTML = '<option value="">All Tags</option>'; // Clear existing options
allTags.forEach(tag => {
const option = document.createElement('option');
option.value = tag;
option.textContent = tag;
tagFilterSelect.appendChild(option);
});
}
// Event Listeners
addPromptBtn.addEventListener('click', openModal);
closeModalBtn.addEventListener('click', closeModal);
editPromptForm.addEventListener('submit', (event) => {
event.preventDefault();
savePrompt();
});
searchInput.addEventListener('input', renderPromptList);
tagFilterSelect.addEventListener('change', renderPromptList);
sortSelect.addEventListener('change', renderPromptList);
// Initial setup
loadPromptsFromLocalStorage();
updateTagFilterOptions();
renderPromptList();
</script>
</body>
</html>