text-scripts / index.html
Honda219's picture
🐳 17/06 - 20:37 - dont alphabetize the pinned
3705510 verified
Raw
History Blame Contribute Delete
87 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Script Manager</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.rich-text-toolbar {
display: flex;
gap: 4px;
padding: 8px;
background: #f3f4f6;
border-radius: 4px;
margin-bottom: 8px;
}
.rich-text-button {
padding: 4px 8px;
background: white;
border: 1px solid #d1d5db;
border-radius: 4px;
cursor: pointer;
}
.rich-text-button:hover {
background: #e5e7eb;
}
.label-item {
transition: all 0.2s ease;
user-select: none;
position: relative;
}
.label-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.script-card {
transition: all 0.2s ease;
}
.script-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.copy-notification {
position: fixed;
bottom: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 12px 24px;
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
}
.copy-notification.show {
opacity: 1;
}
.highlight {
background-color: #ffeb3b;
padding: 0 2px;
border-radius: 2px;
}
.category-tag {
display: inline-flex;
align-items: center;
background-color: #e0f2fe;
color: #0369a1;
padding: 2px 8px;
border-radius: 9999px;
font-size: 0.75rem;
margin-right: 4px;
margin-bottom: 4px;
}
.category-tag button {
margin-left: 4px;
color: #0369a1;
background: none;
border: none;
cursor: pointer;
font-size: 0.6rem;
}
.category-selector {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 8px;
}
.category-option {
display: flex;
align-items: center;
padding: 2px 8px;
background-color: #f3f4f6;
border-radius: 9999px;
cursor: pointer;
font-size: 0.75rem;
}
.category-option.selected {
background-color: #3b82f6;
color: white;
}
.category-option input {
margin-right: 4px;
}
.move-btn {
opacity: 0.5;
transition: opacity 0.2s ease;
}
.label-item:hover .move-btn {
opacity: 1;
}
.move-btn:hover {
color: #3b82f6;
}
.toggle-dates-btn {
transition: all 0.2s ease;
}
.toggle-dates-btn.active {
color: #3b82f6;
transform: scale(1.1);
}
/* Style for formatted text preview */
.formatted-text {
white-space: pre-line;
}
.formatted-text b, .formatted-text strong {
font-weight: bold;
}
.formatted-text i, .formatted-text em {
font-style: italic;
}
.formatted-text u {
text-decoration: underline;
}
/* Color picker styles */
.color-picker {
position: relative;
display: inline-block;
}
.color-preview {
width: 20px;
height: 20px;
border-radius: 4px;
border: 1px solid #d1d5db;
cursor: pointer;
}
.color-palette {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: none;
background: white;
padding: 8px;
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 180px;
}
.color-palette.show {
display: flex;
flex-direction: column;
gap: 8px;
}
.color-options-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 4px;
}
.color-option {
width: 20px;
height: 20px;
border-radius: 4px;
cursor: pointer;
border: 1px solid #d1d5db;
}
.color-option:hover {
transform: scale(1.1);
}
.custom-color-section {
border-top: 1px solid #e5e7eb;
padding-top: 8px;
}
.custom-color-input {
display: flex;
align-items: center;
gap: 4px;
}
.custom-color-input input {
flex: 1;
padding: 4px;
border: 1px solid #d1d5db;
border-radius: 4px;
}
.custom-color-input button {
padding: 4px 8px;
background: #3b82f6;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.custom-color-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 4px;
}
/* Category filter buttons */
.category-filter-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.category-filter-btn {
padding: 4px 12px;
border-radius: 9999px;
background-color: #e5e7eb;
color: #4b5563;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #d1d5db;
}
.category-filter-btn:hover {
background-color: #d1d5db;
}
.category-filter-btn.active {
background-color: #3b82f6;
color: white;
border-color: #3b82f6;
}
.category-filter-btn.all-categories {
background-color: #10b981;
color: white;
border-color: #10b981;
}
/* Import/Export buttons */
.data-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
.data-actions button {
padding: 4px 8px;
font-size: 0.75rem;
border-radius: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.import-export-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-800">Script Manager</h1>
<p class="text-gray-600">Save and organize your frequently used scripts</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<!-- Labels Section -->
<div class="lg:col-span-1 bg-white p-6 rounded-lg shadow">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800">Labels</h2>
<div class="flex gap-2">
<button id="sortLabelsBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-3 py-1 rounded" title="Sort A-Z">
<i class="fas fa-sort-alpha-down"></i>
</button>
<button id="addLabelBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<div id="addLabelForm" class="hidden mb-4">
<div class="flex gap-2">
<input id="newLabelInput" type="text" placeholder="New label" class="flex-1 px-3 py-2 border rounded">
<button id="saveLabelBtn" class="bg-green-500 hover:bg-green-600 text-white px-3 py-2 rounded">
<i class="fas fa-check"></i>
</button>
<button id="cancelLabelBtn" class="bg-red-500 hover:bg-red-600 text-white px-3 py-2 rounded">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div id="labelsContainer" class="space-y-2">
<!-- Labels will be rendered here -->
</div>
<!-- Import/Export buttons for labels -->
<div class="data-actions mt-4">
<button id="exportLabelsBtn" class="bg-purple-500 hover:bg-purple-600 text-white">
<i class="fas fa-file-export mr-1"></i> Export Labels
</button>
<button id="importLabelsBtn" class="bg-indigo-500 hover:bg-indigo-600 text-white">
<i class="fas fa-file-import mr-1"></i> Import Labels
</button>
<input type="file" id="labelsFileInput" class="hidden" accept=".json">
</div>
</div>
<!-- Main Content -->
<div class="lg:col-span-3 space-y-6">
<!-- Pinned Scripts -->
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-blue-500">Pinned Scripts</h2>
<button id="togglePinnedDatesBtn" class="toggle-dates-btn text-gray-500 hover:text-blue-500" title="Toggle dates">
<i class="fa-solid fa-eye-slash"></i>
</button>
</div>
<div id="pinnedScriptsContainer" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Pinned scripts will be rendered here -->
</div>
</div>
<!-- Time Zone Converter -->
<div class="bg-white p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold text-blue-500 mb-4">Appointment Time Converter</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Client Name</label>
<input type="text" id="clientName" class="w-full px-3 py-2 border rounded" placeholder="First Last">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Client Email</label>
<input type="email" id="clientEmail" class="w-full px-3 py-2 border rounded" placeholder="email@example.com">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="appointmentDate" class="w-full px-3 py-2 border rounded">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Time (EST)</label>
<select id="appointmentTime" class="w-full px-3 py-2 border rounded"></select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Convert to Timezone</label>
<select id="timezoneSelect" class="w-full px-3 py-2 border rounded">
<option value="EST">Eastern (EST)</option>
<option value="CST">Central (CST)</option>
<option value="MST">Mountain (MST)</option>
<option value="PST">Pacific (PST)</option>
</select>
</div>
</div>
<div class="flex gap-2">
<button id="generateScriptBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
Generate Script
</button>
<button id="downloadCalendarBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded hidden">
<i class="fas fa-calendar-plus mr-2"></i>Add to Calendar
</button>
</div>
<div id="generatedScriptContainer" class="mt-4 hidden">
<div class="flex justify-between items-center mb-2">
<h3 class="font-medium">Generated Script</h3>
<button id="copyGeneratedScriptBtn" class="text-blue-500 hover:text-blue-700">
<i class="fas fa-copy mr-1"></i> Copy
</button>
</div>
<div id="generatedScript" class="bg-gray-100 p-4 rounded whitespace-pre-line"></div>
</div>
</div>
<!-- All Scripts -->
<div class="bg-white p-6 rounded-lg shadow">
<div class="import-export-container">
<div class="flex items-center gap-4">
<h2 class="text-xl font-semibold text-gray-800">All Scripts</h2>
<button id="toggleDatesBtn" class="toggle-dates-btn text-gray-500 hover:text-blue-500" title="Toggle dates">
<i class="fa-solid fa-eye-slash"></i>
</button>
</div>
<div class="flex gap-2">
<div class="relative">
<input type="text" id="searchInput" placeholder="Search scripts..."
class="pl-8 pr-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<button id="addScriptBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded flex items-center gap-2">
<i class="fas fa-plus"></i> Add Script
</button>
</div>
</div>
<!-- Category filter buttons -->
<div class="category-filter-buttons">
<button class="category-filter-btn all-categories" data-category="all">All Categories</button>
<!-- Category buttons will be rendered here -->
</div>
<!-- Import/Export buttons for scripts -->
<div class="data-actions mb-4">
<button id="exportScriptsBtn" class="bg-purple-500 hover:bg-purple-600 text-white">
<i class="fas fa-file-export mr-1"></i> Export Scripts
</button>
<button id="importScriptsBtn" class="bg-indigo-500 hover:bg-indigo-600 text-white">
<i class="fas fa-file-import mr-1"></i> Import Scripts
</button>
<button id="exportAllDataBtn" class="bg-blue-500 hover:bg-blue-600 text-white">
<i class="fas fa-download mr-1"></i> Export All Data
</button>
<button id="importAllDataBtn" class="bg-green-500 hover:bg-green-600 text-white">
<i class="fas fa-upload mr-1"></i> Import All Data
</button>
<input type="file" id="scriptsFileInput" class="hidden" accept=".json">
<input type="file" id="allDataFileInput" class="hidden" accept=".json">
</div>
<div id="scriptsContainer" class="space-y-4">
<!-- Scripts will be rendered here -->
</div>
</div>
</div>
</div>
<!-- Script Modal -->
<div id="scriptModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center p-4 border-b">
<h3 class="text-lg font-semibold" id="modalTitle">Add New Script</h3>
<button id="closeModalBtn" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<form id="scriptForm" class="p-4">
<input type="hidden" id="scriptId">
<div class="mb-4">
<label for="scriptTitle" class="block text-sm font-medium text-gray-700 mb-1">Title</label>
<input type="text" id="scriptTitle", class="w-full px-3 py-2 border rounded" required>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Categories</label>
<div class="category-selector" id="categorySelector">
<!-- Category options will be rendered here -->
</div>
</div>
<div class="mb-4">
<label class="flex items-center">
<input type="checkbox" id="scriptPinned" class="rounded text-blue-500">
<span class="ml-2 text-sm text-gray-700">Pin this script</span>
</label>
</div>
<div class="mb-4">
<label for="scriptContent" class="block text-sm font-medium text-gray-700 mb-1">Content</label>
<div class="rich-text-toolbar">
<button type="button" id="boldBtn" class="rich-text-button"><i class="fas fa-bold"></i></button>
<button type="button" id="italicBtn" class="rich-text-button"><i class="fas fa-italic"></i></button>
<button type="button" id="underlineBtn" class="rich-text-button"><i class="fas fa-underline"></i></button>
<div class="color-picker">
<div class="rich-text-button" id="colorPickerBtn">
<i class="fas fa-palette"></i>
</div>
<div class="color-palette" id="colorPalette">
<div class="color-options-grid">
<div class="color-option" style="background-color: #000000;" data-color="#000000"></div>
<div class="color-option" style="background-color: #ffffff; border: 1px solid #000;" data-color="#ffffff"></div>
<div class="color-option" style="background-color: #ff0000;" data-color="#ff0000"></div>
<div class="color-option" style="background-color: #00ff00;" data-color="#00ff00"></div>
<div class="color-option" style="background-color: #0000ff;" data-color="#0000ff"></div>
<div class="color-option" style="background-color: #ffff00;" data-color="#ffff00"></div>
<div class="color-option" style="background-color: #ff00ff;" data-color="#ff00ff"></div>
<div class="color-option" style="background-color: #00ffff;" data-color="#00ffff"></div>
<div class="color-option" style="background-color: #800000;" data-color="#800000"></div>
<div class="color-option" style="background-color: #008000;" data-color="#008000"></div>
<div class="color-option" style="background-color: #000080;" data-color="#000080"></div>
<div class="color-option" style="background-color: #808000;" data-color="#808000"></div>
<div class="color-option" style="background-color: #800080;" data-color="#800080"></div>
<div class="color-option" style="background-color: #008080;" data-color="#008080"></div>
<div class="color-option" style="background-color: #c0c0c0;" data-color="#c0c0c0"></div>
<div class="color-option" style="background-color: #808080;" data-color="#808080"></div>
<div class="color-option" style="background-color: #ffa500;" data-color="#ffa500"></div>
<div class="color-option" style="background-color: #ff6347;" data-color="#ff6347"></div>
<div class="color-option" style="background-color: #ff69b4;" data-color="#ff69b4"></div>
<div class="color-option" style="background-color: #9370db;" data-color="#9370db"></div>
<div class="color-option" style="background-color: #7fffd4;" data-color="#7fffd4"></div>
<div class="color-option" style="background-color: #ffd700;" data-color="#ffd700"></div>
<div class="color-option" style="background-color: #cd5c5c;" data-color="#cd5c5c"></div>
<div class="color-option" style="background-color: #4b0082;" data-color="#4b0082"></div>
<div class="color-option" style="background-color: #de4319;" data-color="#de4319"></div>
</div>
<div class="custom-color-section">
<div class="custom-color-input">
<input type="text", id="customColorInput" placeholder="#hex or color name" class="flex-1">
<button type="button" id="applyCustomColor">Apply</button>
</div>
</div>
</div>
</div>
<button type="button" id="placeholderBtn" class="rich-text-button">[Variable]</button>
</div>
<textarea id="scriptContent" rows="8" class="w-full px-3 py-2 border rounded" required></textarea>
<div class="mt-2 text-sm text-gray-500">Preview:</div>
<div id="contentPreview" class="formatted-text p-2 border rounded bg-gray-50 mt-1"></div>
</div>
<div class="flex justify-end gap-2 pt-4 border-t">
<button type="button" id="cancelScriptBtn" class="px-4 py-2 border rounded hover:bg-gray-100">Cancel</button>
<button type="submit" id="saveScriptBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">Save</button>
</div>
</form>
</div>
</div>
<!-- Copy Notification -->
<div id="copyNotification" class="copy-notification">
<i class="fas fa-check-circle mr-2"></i> Script copied to clipboard!
</div>
<script>
// Initialize data from localStorage or with default values
let scripts = JSON.parse(localStorage.getItem('scripts')) || [
{
id: 1,
title: "Welcome Message",
content: "Hello [Name], thank you for reaching out! How can I assist you today?",
categories: ["intro"],
pinned: true,
createdAt: new Date().toISOString(),
updatedAt: null
},
{
id: 2,
title: "Follow-up Email",
content: "Dear [Name],\n\nI wanted to follow up on our previous conversation. Please let me know if you have any questions or need further assistance.\n\nBest regards,\n[Your Name]",
categories: ["email"],
pinned: false,
createdAt: new Date().toISOString(),
updatedAt: null
},
{
id: 3,
title: "Document Request",
content: "Hi [Name], could you please send me the following documents: [list documents]. Thank you!",
categories: ["documents_requested"],
pinned: true,
createdAt: new Date().toISOString(),
updatedAt: null
}
];
let labels = JSON.parse(localStorage.getItem('labels')) || [
"⦁ NEED TO LABEL🎁",
"⦁ EASTERN 0️⃣",
"⦁ CENTRAL 1️⃣",
"⦁ MOUNTAIN 2️⃣",
"⦁ PACIFIC 3️⃣",
"⦁ CASH OUT💰",
"⦁ CODE WORD🔐",
"⦁ CONVENTIONAL 🍦",
"⦁ CREDIT TRIGGER 😱",
"⦁ DEAD 🤮",
"⦁ DIVORCE➗",
"⦁ FHA �🎈",
"⦁ FUNDED 💲",
"⦁ HELOC 🤖",
"⦁ HOT 🔥",
"⦁ LOST CONTACT 💔",
"⦁ MICHIGAN 〽",
"⦁ NFL FAN 🏈",
"⦁ NOT READY ❌",
"⦁ PROSPECT 📈",
"⦁ PURCHASE 🏠",
"⦁ SPRINGEQ 🌷",
"⦁ SUB ✔",
"⦁ TBD ⏲",
"⦁ VA ⚓",
"⦁ WIFE 👧",
"⦁ XFER ⏩"
];
const allCategories = [
"Call Script", "documents_requested", "Funny", "General",
"intro", "Lost Contact/Gaslight", "Power Play", "Preapproval/Purchase", "VA IRRRL"
];
// Predefined colors that match the color palette options
const predefinedColors = [
'#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00',
'#ff00ff', '#00ffff', '#800000', '#008000', '#000080', '#808000',
'#800080', '#008080', '#c0c0c0', '#808080', '#ffa500', '#ff6347',
'#ff69b4', '#9370db', '#7fffd4', '#ffd700', '#cd5c5c', '#4b0082',
'#f08080'
];
// DOM Elements
const scriptForm = document.getElementById('scriptForm');
const scriptModal = document.getElementById('scriptModal');
const scriptTitle = document.getElementById('scriptTitle');
const scriptContent = document.getElementById('scriptContent');
const scriptPinned = document.getElementById('scriptPinned');
const scriptId = document.getElementById('scriptId');
const modalTitle = document.getElementById('modalTitle');
const addLabelBtn = document.getElementById('addLabelBtn');
const addLabelForm = document.getElementById('addLabelForm');
const newLabelInput = document.getElementById('newLabelInput');
const labelsContainer = document.getElementById('labelsContainer');
const scriptsContainer = document.getElementById('scriptsContainer');
const pinnedScriptsContainer = document.getElementById('pinnedScriptsContainer');
const addScriptBtn = document.getElementById('addScriptBtn');
const copyNotification = document.getElementById('copyNotification');
const searchInput = document.getElementById('searchInput');
const categorySelector = document.getElementById('categorySelector');
const saveLabelBtn = document.getElementById('saveLabelBtn');
const cancelLabelBtn = document.getElementById('cancelLabelBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const cancelScriptBtn = document.getElementById('cancelScriptBtn');
const saveScriptBtn = document.getElementById('saveScriptBtn');
const boldBtn = document.getElementById('boldBtn');
const italicBtn = document.getElementById('italicBtn');
const underlineBtn = document.getElementById('underlineBtn');
const placeholderBtn = document.getElementById('placeholderBtn');
const toggleDatesBtn = document.getElementById('toggleDatesBtn');
const togglePinnedDatesBtn = document.getElementById('togglePinnedDatesBtn');
const contentPreview = document.getElementById('contentPreview');
const colorPickerBtn = document.getElementById('colorPickerBtn');
const colorPalette = document.getElementById('colorPalette');
const customColorInput = document.getElementById('customColorInput');
const applyCustomColor = document.getElementById('applyCustomColor');
// New elements for time zone converter
const appointmentDate = document.getElementById('appointmentDate');
const appointmentTime = document.getElementById('appointmentTime');
const timezoneSelect = document.getElementById('timezoneSelect');
const generateScriptBtn = document.getElementById('generateScriptBtn');
const downloadCalendarBtn = document.getElementById('downloadCalendarBtn');
const generatedScriptContainer = document.getElementById('generatedScriptContainer');
const generatedScript = document.getElementById('generatedScript');
const copyGeneratedScriptBtn = document.getElementById('copyGeneratedScriptBtn');
// Import/Export elements
const exportLabelsBtn = document.getElementById('exportLabelsBtn');
const importLabelsBtn = document.getElementById('importLabelsBtn');
const labelsFileInput = document.getElementById('labelsFileInput');
const exportScriptsBtn = document.getElementById('exportScriptsBtn');
const importScriptsBtn = document.getElementById('importScriptsBtn');
const scriptsFileInput = document.getElementById('scriptsFileInput');
const exportAllDataBtn = document.getElementById('exportAllDataBtn');
const importAllDataBtn = document.getElementById('importAllDataBtn');
const allDataFileInput = document.getElementById('allDataFileInput');
// Category filter elements
const categoryFilterButtons = document.querySelector('.category-filter-buttons');
// State
let showDates = false;
let currentColor = '#000000';
let selectedCategory = 'all'; // For category filtering
let currentAppointmentDate = null; // Store the current appointment date for calendar download
// Save data to localStorage
function saveData() {
localStorage.setItem('scripts', JSON.stringify(scripts));
localStorage.setItem('labels', JSON.stringify(labels));
}
// Populate appointment time dropdown with 15-minute increments
function populateTimeDropdown() {
const timeSelect = document.getElementById('appointmentTime');
timeSelect.innerHTML = '';
for (let h = 0; h < 24; h++) {
for (let m = 0; m < 60; m += 15) {
const hours24 = h.toString().padStart(2, '0');
const mins = m.toString().padStart(2, '0');
const value = `${hours24}:${mins}`;
const ampm = h >= 12 ? 'PM' : 'AM';
let displayHour = h % 12;
displayHour = displayHour ? displayHour : 12;
const displayTime = `${displayHour}:${mins} ${ampm}`;
const option = document.createElement('option');
option.value = value;
option.textContent = displayTime;
timeSelect.appendChild(option);
}
}
}
// Initialize the app
document.addEventListener('DOMContentLoaded', function() {
renderScripts();
renderPinnedScripts();
renderLabels();
renderCategoryButtons();
setupEventListeners();
// Populate time dropdown with 15-minute increments
populateTimeDropdown();
// Set default date to today
const today = new Date();
const formattedDate = today.toISOString().split('T')[0];
appointmentDate.value = formattedDate;
// Set default time to current time rounded to next 15 minutes
const minutes = today.getMinutes();
const roundedMinutes = Math.ceil(minutes / 15) * 15;
today.setMinutes(roundedMinutes);
const hours = today.getHours().toString().padStart(2, '0');
const mins = today.getMinutes().toString().padStart(2, '0');
appointmentTime.value = `${hours}:${mins}`;
});
// Set up event listeners
function setupEventListeners() {
scriptForm.addEventListener('submit', handleFormSubmit);
addLabelBtn.addEventListener('click', showAddLabelForm);
saveLabelBtn.addEventListener('click', saveNewLabel);
cancelLabelBtn.addEventListener('click', hideAddLabelForm);
addScriptBtn.addEventListener('click', showAddScriptModal);
searchInput.addEventListener('input', handleSearch);
closeModalBtn.addEventListener('click', closeModalHandler);
cancelScriptBtn.addEventListener('click', closeModalHandler);
boldBtn.addEventListener('click', () => applyFormatting('bold'));
italicBtn.addEventListener('click', () => applyFormatting('italic'));
underlineBtn.addEventListener('click', () => applyFormatting('underline'));
placeholderBtn.addEventListener('click', insertPlaceholder);
toggleDatesBtn.addEventListener('click', toggleDatesVisibility);
togglePinnedDatesBtn.addEventListener('click', toggleDatesVisibility);
scriptContent.addEventListener('input', updateContentPreview);
// Color picker events
colorPickerBtn.addEventListener('click', toggleColorPalette);
document.addEventListener('click', (e) => {
if (!colorPalette.contains(e.target) && e.target !== colorPickerBtn) {
colorPalette.classList.remove('show');
}
});
// Color selection
document.querySelectorAll('.color-option').forEach(option => {
option.addEventListener('click', () => {
currentColor = option.dataset.color;
applyFormatting('color', currentColor);
colorPalette.classList.remove('show');
});
});
// Custom color
applyCustomColor.addEventListener('click', () => {
const color = customColorInput.value.trim();
if (color) {
currentColor = color;
applyFormatting('color', currentColor);
colorPalette.classList.remove('show');
}
});
// Time zone converter events
generateScriptBtn.addEventListener('click', generateAppointmentScript);
copyGeneratedScriptBtn.addEventListener('click', copyGeneratedScript);
downloadCalendarBtn.addEventListener('click', downloadCalendarFile);
// Sort labels button
document.getElementById('sortLabelsBtn').addEventListener('click', sortLabelsAlphabetically);
// Import/Export events
exportLabelsBtn.addEventListener('click', exportLabels);
importLabelsBtn.addEventListener('click', () => labelsFileInput.click());
labelsFileInput.addEventListener('change', importLabels);
exportScriptsBtn.addEventListener('click', exportScripts);
importScriptsBtn.addEventListener('click', () => scriptsFileInput.click());
scriptsFileInput.addEventListener('change', importScripts);
exportAllDataBtn.addEventListener('click', exportAllData);
importAllDataBtn.addEventListener('click', () => allDataFileInput.click());
allDataFileInput.addEventListener('change', importAllData);
}
// Export labels to JSON file
function exportLabels() {
const data = JSON.stringify(labels, null, 2);
downloadFile(data, 'labels.json', 'application/json');
}
// Import labels from JSON file
function importLabels(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const importedLabels = JSON.parse(e.target.result);
if (Array.isArray(importedLabels)) {
if (confirm('This will replace your current labels. Continue?')) {
// Migration: convert string labels to objects
labels = importedLabels.map(item => {
if (typeof item === 'string') {
return { text: item, pinned: false };
}
return item;
});
saveData();
renderLabels();
showCopyNotification('Labels imported successfully!');
}
} else {
alert('Invalid labels file format');
}
} catch (error) {
alert('Error parsing labels file: ' + error.message);
}
};
reader.readAsText(file);
event.target.value = ''; // Reset file input
}
// Export scripts to JSON file
function exportScripts() {
const data = JSON.stringify(scripts, null, 2);
downloadFile(data, 'scripts.json', 'application/json');
}
// Import scripts from JSON file
function importScripts(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const importedScripts = JSON.parse(e.target.result);
if (Array.isArray(importedScripts)) {
if (confirm('This will replace your current scripts. Continue?')) {
scripts = importedScripts;
saveData();
renderScripts();
renderPinnedScripts();
showCopyNotification('Scripts imported successfully!');
}
} else {
alert('Invalid scripts file format');
}
} catch (error) {
alert('Error parsing scripts file: ' + error.message);
}
};
reader.readAsText(file);
event.target.value = ''; // Reset file input
}
// Export all data (scripts and labels) to JSON file
function exportAllData() {
const data = {
scripts: scripts,
labels: labels
};
const jsonData = JSON.stringify(data, null, 2);
downloadFile(jsonData, 'script-manager-data.json', 'application/json');
}
// Import all data (scripts and labels) from JSON file
function importAllData(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const importedData = JSON.parse(e.target.result);
if (importedData.scripts && importedData.labels) {
if (confirm('This will replace ALL your current data. Continue?')) {
scripts = importedData.scripts;
labels = importedData.labels;
saveData();
renderScripts();
renderPinnedScripts();
renderLabels();
showCopyNotification('All data imported successfully!');
}
} else {
alert('Invalid data file format - missing scripts or labels');
}
} catch (error) {
alert('Error parsing data file: ' + error.message);
}
};
reader.readAsText(file);
event.target.value = ''; // Reset file input
}
// Helper function to download a file
function downloadFile(data, filename, type) {
const blob = new Blob([data], { type: type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Generate iCalendar (.ics) file for the appointment
function downloadCalendarFile() {
if (!currentAppointmentDate) {
alert('No appointment time has been generated yet');
return;
}
const clientName = document.getElementById('clientName').value.trim();
const clientEmail = document.getElementById('clientEmail').value.trim();
// Format the date for the iCalendar file
const icsDate = formatDateForICS(currentAppointmentDate);
// Create the iCalendar content
const icsContent = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Script Manager//Appointment//EN',
'BEGIN:VEVENT',
`UID:${Date.now()}@scriptmanager.com`,
`DTSTAMP:${icsDate}`,
`DTSTART:${icsDate}`,
`DTEND:${icsDate}`,
`SUMMARY:Appointment with Derrin at UML${clientName ? ' - ' + clientName : ''}`,
'DESCRIPTION:Your appointment with Derrin at UML',
'LOCATION:Phone/Online',
clientEmail ? `ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="${clientName || 'Client'}":mailto:${clientEmail}` : '',
'END:VEVENT',
'END:VCALENDAR'
].filter(line => line !== '').join('\r\n');
// Create a blob and download it
const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'appointment.ics';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Format date for iCalendar (UTC format)
function formatDateForICS(date) {
const pad = n => n < 10 ? '0' + n : n;
return date.getUTCFullYear() +
pad(date.getUTCMonth() + 1) +
pad(date.getUTCDate()) +
'T' +
pad(date.getUTCHours()) +
pad(date.getUTCMinutes()) +
pad(date.getUTCSeconds()) +
'Z';
}
// Render category filter buttons
function renderCategoryButtons() {
// Clear existing buttons except "All Categories"
const allCategoriesBtn = categoryFilterButtons.querySelector('.all-categories');
categoryFilterButtons.innerHTML = '';
categoryFilterButtons.appendChild(allCategoriesBtn);
// Add buttons for each category
allCategories.forEach(category => {
const button = document.createElement('button');
button.className = 'category-filter-btn';
button.textContent = getCategoryName(category);
button.dataset.category = category;
if (selectedCategory === category) {
button.classList.add('active');
}
button.addEventListener('click', () => {
selectedCategory = category;
renderScripts();
updateActiveCategoryButtons();
});
categoryFilterButtons.appendChild(button);
});
}
// Update active state of category buttons
function updateActiveCategoryButtons() {
document.querySelectorAll('.category-filter-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.category === selectedCategory) {
btn.classList.add('active');
}
});
// Special handling for "All Categories" button
const allCategoriesBtn = categoryFilterButtons.querySelector('.all-categories');
if (selectedCategory === 'all') {
allCategoriesBtn.classList.add('active');
} else {
allCategoriesBtn.classList.remove('active');
}
}
// Add event listener for "All Categories" button
document.querySelector('.all-categories').addEventListener('click', () => {
selectedCategory = 'all';
renderScripts();
updateActiveCategoryButtons();
});
// Generate appointment script with time zone conversion
function generateAppointmentScript() {
if (!appointmentDate.value || !appointmentTime.value) {
alert('Please select both date and time');
return;
}
// Parse input date and time
const dateParts = appointmentDate.value.split('-');
const timeParts = appointmentTime.value.split(':');
// Create date object in EST (assuming input is EST)
const estDate = new Date(
parseInt(dateParts[0]),
parseInt(dateParts[1]) - 1,
parseInt(dateParts[2]),
parseInt(timeParts[0]),
parseInt(timeParts[1])
);
// Store the EST date for calendar download
currentAppointmentDate = new Date(estDate);
// Convert to selected timezone
const targetTimezone = timezoneSelect.value;
let convertedTime = '';
let timezoneLabel = '';
switch(targetTimezone) {
case 'EST':
// No conversion needed
convertedTime = formatTime(estDate);
timezoneLabel = 'EST';
break;
case 'CST':
// Subtract 1 hour
estDate.setHours(estDate.getHours() - 1);
convertedTime = formatTime(estDate);
timezoneLabel = 'CST';
break;
case 'MST':
// Subtract 2 hours
estDate.setHours(estDate.getHours() - 2);
convertedTime = formatTime(estDate);
timezoneLabel = 'MST';
break;
case 'PST':
// Subtract 3 hours
estDate.setHours(estDate.getHours() - 3);
convertedTime = formatTime(estDate);
timezoneLabel = 'PST';
break;
}
// Format date as MM/DD/YYYY
const formattedDate = `${(estDate.getMonth() + 1).toString().padStart(2, '0')}/${estDate.getDate().toString().padStart(2, '0')}/${estDate.getFullYear()}`;
// Get day of the week
const dayOfWeek = estDate.toLocaleDateString('en-US', { weekday: 'long' });
// Get client name if provided
const clientName = document.getElementById('clientName').value.trim();
const greeting = clientName ? `Hello ${clientName},` : 'Hello,';
// Generate the script
const script = `${greeting} your appointment with Derrin at UML is booked for ${dayOfWeek} ${formattedDate} at ${convertedTime} ${timezoneLabel}. Please reply with C to confirm or R to reschedule. Call or text this number (313) 490-3990 to reschedule.`;
// Display the generated script
generatedScript.textContent = script;
generatedScriptContainer.classList.remove('hidden');
downloadCalendarBtn.classList.remove('hidden');
}
// Format time as AM/PM
function formatTime(date) {
let hours = date.getHours();
const minutes = date.getMinutes();
const ampm = hours >= 12 ? 'pm' : 'am';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
return `${hours}:${minutes.toString().padStart(2, '0')} ${ampm}`;
}
// Copy generated script to clipboard
function copyGeneratedScript() {
navigator.clipboard.writeText(generatedScript.textContent).then(() => {
showCopyNotification();
}).catch(err => {
console.error('Failed to copy text: ', err);
});
}
// Toggle color palette visibility
function toggleColorPalette() {
colorPalette.classList.toggle('show');
}
// Apply formatting to selected text
function applyFormatting(formatType, color = null) {
const textarea = scriptContent;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
const beforeText = textarea.value.substring(0, start);
const afterText = textarea.value.substring(end);
let formattedText = '';
let cursorOffset = 0;
switch(formatType) {
case 'bold':
formattedText = `**${selectedText}**`;
cursorOffset = 2;
break;
case 'italic':
formattedText = `_${selectedText}_`;
cursorOffset = 1;
break;
case 'underline':
formattedText = `<u>${selectedText}</u>`;
cursorOffset = 4;
break;
case 'color':
// Check if the color is predefined or custom
const isPredefined = predefinedColors.includes(color.toLowerCase());
if (isPredefined) {
formattedText = `<span class="text-color-${color.replace('#', '')}">${selectedText}</span>`;
} else {
formattedText = `<span style="color: ${color}">${selectedText}</span>`;
}
cursorOffset = 0;
break;
}
if (start !== end) {
// Text is selected, wrap it with formatting
textarea.value = beforeText + formattedText + afterText;
// Set cursor position after the formatted text
textarea.setSelectionRange(
start + cursorOffset,
end + cursorOffset
);
} else {
// No text selected, insert formatting tags and place cursor between them
textarea.value = beforeText + formattedText + afterText;
textarea.setSelectionRange(
start + cursorOffset,
start + cursorOffset
);
}
textarea.focus();
updateContentPreview();
}
// Update content preview
function updateContentPreview() {
// Convert markdown-style formatting to HTML
let previewText = scriptContent.value
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // bold
.replace(/\_(.*?)\_/g, '<em>$1</em>') // italic
.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>') // underline
.replace(/<span class="text-color-(.*?)">(.*?)<\/span>/g, '<span style="color: #$1">$2</span>') // predefined colors
.replace(/<span style="color: (.*?)">(.*?)<\/span>/g, '<span style="color: $1">$2</span>') // custom colors
.replace(/\n/g, '<br>') // line breaks
.replace(/\[(.*?)\]/g, '<span class="bg-blue-100 text-blue-800 px-1 rounded">[$1]</span>'); // placeholders
contentPreview.innerHTML = previewText;
}
// Toggle dates visibility
function toggleDatesVisibility() {
showDates = !showDates;
if (showDates) {
toggleDatesBtn.classList.add('active');
togglePinnedDatesBtn.classList.add('active');
togglePinnedDatesBtn.innerHTML = '<i class="fa-solid fa-eye-slash"></i>';
} else {
toggleDatesBtn.classList.remove('active');
togglePinnedDatesBtn.classList.remove('active');
togglePinnedDatesBtn.innerHTML = '<i class="fa-solid fa-eye"></i>';
}
renderScripts();
renderPinnedScripts();
}
// Move label up in the list
function moveLabelUp(index) {
if (index > 0) {
// Swap with previous item
[labels[index], labels[index - 1]] = [labels[index - 1], labels[index]];
saveData();
renderLabels();
}
}
// Move label down in the list
function moveLabelDown(index) {
if (index < labels.length - 1) {
// Swap with next item
[labels[index], labels[index + 1]] = [labels[index + 1], labels[index]];
saveData();
renderLabels();
}
}
// Render category selector
function renderCategorySelector(selectedCategories = []) {
categorySelector.innerHTML = '';
allCategories.forEach(category => {
const isSelected = selectedCategories.includes(category);
const option = document.createElement('div');
option.className = `category-option ${isSelected ? 'selected' : ''}`;
option.innerHTML = `
<input type="checkbox" id="cat-${category}" value="${category}" ${isSelected ? 'checked' : ''} style="display: none;">
${getCategoryName(category)}
`;
option.addEventListener('click', () => {
const checkbox = option.querySelector('input');
checkbox.checked = !checkbox.checked;
option.classList.toggle('selected');
});
categorySelector.appendChild(option);
});
}
// Get selected categories
function getSelectedCategories() {
const checkboxes = categorySelector.querySelectorAll('input:checked');
return Array.from(checkboxes).map(cb => cb.value);
}
// Handle search input
function handleSearch() {
const searchTerm = searchInput.value.trim().toLowerCase();
if (searchTerm === '') {
renderScripts();
return;
}
const filteredScripts = scripts.filter(script => {
return script.title.toLowerCase().includes(searchTerm) ||
script.content.toLowerCase().includes(searchTerm);
});
renderScripts(filteredScripts, searchTerm);
}
// Highlight search term in text
function highlightText(text, searchTerm) {
if (!searchTerm) return text;
const regex = new RegExp(searchTerm, 'gi');
return text.replace(regex, match => `<span class="highlight">${match}</span>`);
}
// Render all scripts (with optional filtering for search)
function renderScripts(scriptsToRender = null, searchTerm = '') {
scriptsContainer.innerHTML = '';
let scriptsToDisplay = scriptsToRender || scripts;
// Apply category filter if a specific category is selected
if (selectedCategory !== 'all') {
scriptsToDisplay = scriptsToDisplay.filter(script => {
return script.categories.includes(selectedCategory);
});
}
scriptsToDisplay.sort((a, b) => {
return new Date(b.createdAt) - new Date(a.createdAt);
}).forEach(script => {
const scriptElement = document.createElement('div');
scriptElement.className = 'script-card bg-gray-50 p-4 rounded-lg border';
// Highlight search terms in title and content
const highlightedTitle = highlightText(script.title, searchTerm);
const highlightedContent = highlightText(script.content, searchTerm);
// Render categories as tags
const categoriesHtml = script.categories.map(cat =>
`<span class="category-tag">${getCategoryName(cat)}</span>`
).join('');
// Date information
const dateInfo = showDates ? `
<div class="mt-3 text-xs text-gray-400">
Created: ${formatDate(script.createdAt)}
${script.updatedAt ? `<br>Updated: ${formatDate(script.updatedAt)}</br>` : ''}
</div>
` : '';
scriptElement.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h3 class="font-medium text-lg">${highlightedTitle}</h3>
<div class="text-sm text-gray-500 mb-2">${categoriesHtml}</div>
</div>
<div class="flex gap-2">
<button onclick="copyScript(${script.id})" class="text-gray-400 hover:text-blue-500" title="Copy script">
<i class="fas fa-copy"></i>
</button>
<button onclick="togglePinScript(${script.id})" class="text-gray-400 hover:text-yellow-500" title="${script.pinned ? 'Unpin' : 'Pin'}">
<i class="fas ${script.pinned ? 'fa-star text-yellow-400' : 'fa-star'}"></i>
</button>
<button onclick="editScript(${script.id})" class="text-gray-400 hover:text-blue-500" title="Edit">
<i class="fas fa-edit"></i>
</button>
<button onclick="deleteScript(${script.id})" class="text-gray-400 hover:text-red-500" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="mt-2 whitespace-pre-line text-gray-700">${formatScriptContent(highlightedContent)}</div>
${dateInfo}
`;
scriptsContainer.appendChild(scriptElement);
});
if (scriptsToDisplay.length === 0) {
scriptsContainer.innerHTML = `
<div class="text-center py-8 text-gray-500">
<i class="fas fa-search text-2xl mb-2"></i>
<p>No scripts found matching your ${selectedCategory !== 'all' ? 'category filter' : 'search'}.</p>
</div>
`;
}
}
// Format script content with HTML tags
function formatScriptContent(content) {
return content
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // bold
.replace(/\_(.*?)\_/g, '<em>$1</em>') // italic
.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>') // underline
.replace(/<span class="text-color-(.*?)">(.*?)<\/span>/g, '<span style="color: #$1">$2</span>') // predefined colors
.replace(/<span style="color: (.*?)">(.*?)<\/span>/g, '<span style="color: $1">$2</span>') // custom colors
.replace(/\n/g, '<br>') // line breaks
.replace(/\[(.*?)\]/g, '<span class="bg-blue-100 text-blue-800 px-1 rounded">[$1]</span>'); // placeholders
}
// Render pinned scripts
function renderPinnedScripts() {
pinnedScriptsContainer.innerHTML = '';
const pinnedScripts = scripts.filter(script => script.pinned);
if (pinnedScripts.length === 0) {
pinnedScriptsContainer.innerHTML = `
<div class="col-span-2 text-center py-8 text-gray-500">
<i class="fas fa-star text-2xl mb-2"></i>
<p>No pinned scripts yet. Pin your favorite scripts for quick access.</p>
</div>
`;
return;
}
pinnedScripts.forEach(script => {
const scriptElement = document.createElement('div');
scriptElement.className = 'script-card bg-gray-50 p-4 rounded-lg border';
// Render categories as tags
const categoriesHtml = script.categories.map(cat =>
`<span class="category-tag">${getCategoryName(cat)}</span>`
).join('');
// Date information
const dateInfo = showDates ? `
<div class="mt-3 text-xs text-gray-400">
Created: ${formatDate(script.createdAt)}
${script.updatedAt ? `<br>Updated: ${formatDate(script.updatedAt)}</br>` : ''}
</div>
` : '';
scriptElement.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h3 class="font-medium text-lg">${script.title}</h3>
<div class="text-sm text-gray-500 mb-2">${categoriesHtml}</div>
</div>
<div class="flex gap-2">
<button onclick="copyScript(${script.id})" class="text-gray-400 hover:text-blue-500" title="Copy script">
<i class="fas fa-copy"></i>
</button>
<button onclick="moveScriptUp(${script.id})" class="move-btn text-gray-500 hover:text-blue-500" title="Move up">
<i class="fas fa-arrow-up text-xs"></i>
</button>
<button onclick="moveScriptDown(${script.id})" class="move-btn text-gray-500 hover:text-blue-500" title="Move down">
<i class="fas fa-arrow-down text-xs"></i>
</button>
<button onclick="togglePinScript(${script.id})" class="text-yellow-500 hover:text-yellow-600" title="Unpin">
<i class="fas fa-star"></i>
</button>
<button onclick="editScript(${script.id})" class="text-gray-400 hover:text-blue-500" title="Edit">
<i class="fas fa-edit"></i>
</button>
<button onclick="deleteScript(${script.id})" class="text-gray-400 hover:text-red-500" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="mt-2 whitespace-pre-line text-gray-700">${formatScriptContent(script.content)}</div>
${dateInfo}
`;
pinnedScriptsContainer.appendChild(scriptElement);
});
}
// Render labels with up/down arrows and pin support
function renderLabels() {
labelsContainer.innerHTML = '';
// Sort: pinned labels first, then unpinned, maintaining relative order
const sortedLabels = labels.map((label, originalIndex) => ({ ...label, originalIndex }));
sortedLabels.sort((a, b) => {
if (a.pinned && !b.pinned) return -1;
if (!a.pinned && b.pinned) return 1;
return 0;
});
sortedLabels.forEach((label) => {
const index = label.originalIndex;
const labelElement = document.createElement('div');
labelElement.className = `label-item p-3 rounded flex justify-between items-center ${label.pinned ? 'bg-yellow-50 border-l-4 border-yellow-400' : 'bg-gray-100'}`;
labelElement.dataset.labelIndex = index;
labelElement.innerHTML = `
<div class="flex items-center">
<span>${label.text}</span>
</div>
<div class="flex gap-2">
<button onclick="togglePinLabel(${index})" class="${label.pinned ? 'text-yellow-500 hover:text-yellow-600' : 'text-gray-400 hover:text-yellow-500'}" title="${label.pinned ? 'Unpin label' : 'Pin label to top'}">
<i class="fas fa-thumbtack text-xs"></i>
</button>
<button onclick="moveLabelUp(${index})" class="move-btn text-gray-500 hover:text-blue-500" title="Move up">
<i class="fas fa-arrow-up text-xs"></i>
</button>
<button onclick="moveLabelDown(${index})" class="move-btn text-gray-500 hover:text-blue-500" title="Move down">
<i class="fas fa-arrow-down text-xs"></i>
</button>
<button onclick="editLabel(${index})" class="text-gray-500 hover:text-blue-500">
<i class="fas fa-edit text-xs"></i>
</button>
<button onclick="deleteLabel(${index})" class="text-gray-500 hover:text-red-500">
<i class="fas fa-trash text-xs"></i>
</button>
</div>
`;
labelsContainer.appendChild(labelElement);
});
}
// Copy script to clipboard
function copyScript(id) {
const script = scripts.find(s => s.id === id);
if (script) {
// Remove formatting tags when copying to clipboard
const cleanContent = script.content
.replace(/\*\*/g, '')
.replace(/_/g, '')
.replace(/<u>/g, '')
.replace(/<\/u>/g, '')
.replace(/<span class="text-color-(.*?)">/g, '')
.replace(/<span style="color: (.*?)">/g, '')
.replace(/<\/span>/g, '');
const textToCopy = `${script.title}\n\n${cleanContent.replace(/<[^>]*>/g, '')}`;
navigator.clipboard.writeText(textToCopy).then(() => {
showCopyNotification();
}).catch(err => {
console.error('Failed to copy text: ', err);
});
}
}
// Show copy notification
function showCopyNotification(message = 'Script copied to clipboard!') {
copyNotification.innerHTML = `<i class="fas fa-check-circle mr-2"></i> ${message}`;
copyNotification.classList.add('show');
setTimeout(() => {
copyNotification.classList.remove('show');
}, 2000);
}
// Show add script modal
function showAddScriptModal() {
scriptId.value = '';
scriptTitle.value = '';
scriptContent.value = '';
scriptPinned.checked = false;
modalTitle.textContent = 'Add New Script';
renderCategorySelector();
scriptModal.classList.remove('hidden');
contentPreview.innerHTML = '';
}
// Edit script
function editScript(id) {
const script = scripts.find(s => s.id === id);
if (script) {
scriptId.value = script.id;
scriptTitle.value = script.title;
scriptContent.value = script.content;
scriptPinned.checked = script.pinned;
modalTitle.textContent = 'Edit Script';
renderCategorySelector(script.categories);
scriptModal.classList.remove('hidden');
updateContentPreview();
}
}
// Handle form submission
function handleFormSubmit(e) {
e.preventDefault();
const id = scriptId.value ? parseInt(scriptId.value) : Date.now();
const title = scriptTitle.value.trim();
const content = scriptContent.value.trim();
const categories = getSelectedCategories();
const pinned = scriptPinned.checked;
if (!title || !content || categories.length === 0) {
alert('Please fill in all fields and select at least one category');
return;
}
const now = new Date().toISOString();
if (scriptId.value) {
// Update existing script
const index = scripts.findIndex(s => s.id === id);
if (index !== -1) {
scripts[index] = {
...scripts[index],
title,
content,
categories,
pinned,
updatedAt: now
};
}
} else {
// Add new script
scripts.push({
id,
title,
content,
categories,
pinned,
createdAt: now,
updatedAt: null
});
}
saveData();
renderScripts();
renderPinnedScripts();
closeModalHandler();
}
// Delete script
function deleteScript(id) {
if (confirm('Are you sure you want to delete this script?')) {
scripts = scripts.filter(script => script.id !== id);
saveData();
renderScripts();
renderPinnedScripts();
}
}
// Toggle pin status
function togglePinScript(id) {
const script = scripts.find(s => s.id === id);
if (script) {
script.pinned = !script.pinned;
script.updatedAt = new Date().toISOString();
saveData();
renderScripts();
renderPinnedScripts();
}
}
// Close modal
function closeModalHandler() {
scriptModal.classList.add('hidden');
}
// Move script up in pinned list
function moveScriptUp(id) {
const pinnedScripts = scripts.filter(script => script.pinned);
const scriptIndex = pinnedScripts.findIndex(s => s.id === id);
if (scriptIndex > 0) {
// Find the actual indices in the main scripts array
const scriptMainIndex = scripts.findIndex(s => s.id === id);
const prevScriptId = pinnedScripts[scriptIndex - 1].id;
const prevScriptMainIndex = scripts.findIndex(s => s.id === prevScriptId);
// Reorder directly in the scripts array
[scripts[scriptMainIndex], scripts[prevScriptMainIndex]] =
[scripts[prevScriptMainIndex], scripts[scriptMainIndex]];
// Update timestamps
scripts[scriptMainIndex].updatedAt = new Date().toISOString();
scripts[prevScriptMainIndex].updatedAt = new Date().toISOString();
saveData();
renderPinnedScripts();
}
}
// Move script down in pinned list
function moveScriptDown(id) {
const pinnedScripts = scripts.filter(script => script.pinned);
const scriptIndex = pinnedScripts.findIndex(s => s.id === id);
if (scriptIndex < pinnedScripts.length - 1) {
// Find the actual indices in the main scripts array
const scriptMainIndex = scripts.findIndex(s => s.id === id);
const nextScriptId = pinnedScripts[scriptIndex + 1].id;
const nextScriptMainIndex = scripts.findIndex(s => s.id === nextScriptId);
// Reorder directly in the scripts array
[scripts[scriptMainIndex], scripts[nextScriptMainIndex]] =
[scripts[nextScriptMainIndex], scripts[scriptMainIndex]];
// Update timestamps
scripts[scriptMainIndex].updatedAt = new Date().toISOString();
scripts[nextScriptMainIndex].updatedAt = new Date().toISOString();
saveData();
renderPinnedScripts();
}
}
// Show add label form
function showAddLabelForm() {
addLabelForm.classList.remove('hidden');
newLabelInput.focus();
}
// Hide add label form
function hideAddLabelForm() {
addLabelForm.classList.add('hidden');
newLabelInput.value = '';
}
// Save new label
function saveNewLabel() {
const newLabel = newLabelInput.value.trim();
if (newLabel) {
labels.push({ text: newLabel, pinned: false });
saveData();
renderLabels();
hideAddLabelForm();
}
}
// Edit label
function editLabel(index) {
const currentLabel = labels[index].text;
const labelItem = document.querySelector(`.label-item[data-label-index="${index}"]`);
const input = document.createElement('input');
input.type = 'text';
input.className = 'label-edit-input w-full px-2 py-1 border rounded';
input.value = currentLabel;
labelItem.innerHTML = '';
labelItem.appendChild(input);
input.focus();
const saveEdit = () => {
const newLabel = input.value.trim();
if (newLabel) {
labels[index].text = newLabel;
saveData();
renderLabels();
}
};
input.addEventListener('blur', saveEdit);
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
saveEdit();
}
});
}
// Toggle pin status for a label
function togglePinLabel(index) {
labels[index].pinned = !labels[index].pinned;
saveData();
renderLabels();
}
// Delete label
function deleteLabel(index) {
if (confirm('Are you sure you want to delete this label?')) {
labels.splice(index, 1);
saveData();
renderLabels();
}
}
// Insert placeholder
function insertPlaceholder() {
const textarea = scriptContent;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const beforeText = textarea.value.substring(0, start);
const afterText = textarea.value.substring(end);
textarea.value = beforeText + '[Variable]' + afterText;
textarea.focus();
textarea.setSelectionRange(start + '[Variable]'.length, start + '[Variable]'.length);
updateContentPreview();
}
// Get category name
function getCategoryName(category) {
const categories = {
'intro': 'Introduction',
'General': 'General',
'documents_requested': 'Documents Request',
'VA IRRRL': 'VA IRRRL',
'Lost Contact/Gaslight': 'Lost Contact',
'Preapproval/Purchase': 'Preapproval',
'Funny': 'Funny',
'Call Script': 'Call Script'
};
return categories[category] || category;
}
// Format date
function formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
// Sort labels alphabetically (pinned labels keep their order; only unpinned are sorted)
function sortLabelsAlphabetically() {
const pinnedLabels = labels.filter(l => l.pinned);
const unpinnedLabels = labels.filter(l => !l.pinned).sort((a, b) => a.text.localeCompare(b.text, undefined, { sensitivity: 'base' }));
labels = [...pinnedLabels, ...unpinnedLabels];
saveData();
renderLabels();
showCopyNotification('Labels sorted A-Z!');
}
// Make functions available globally
window.copyScript = copyScript;
window.moveScriptUp = moveScriptUp;
window.moveScriptDown = moveScriptDown;
window.togglePinScript = togglePinScript;
window.editScript = editScript;
window.deleteScript = deleteScript;
window.editLabel = editLabel;
window.deleteLabel = deleteLabel;
window.moveLabelUp = moveLabelUp;
window.moveLabelDown = moveLabelDown;
window.togglePinLabel = togglePinLabel;
window.closeModalHandler = closeModalHandler;
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Honda219/text-scripts" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>