Files
GoNetKit/web/pwpush.html

859 lines
35 KiB
HTML

{{template "base.html" .}}
{{define "title"}}Password Pusher - Secure Text Sharing{{end}}
{{define "head"}}
{{if .Success}}
<meta name="success-data" content='{"id":"{{.ID}}","url":"{{.PushURL}}","expiresAt":"{{.ExpiresAt}}","maxViews":"{{.MaxViews}}"}'>
{{end}}
{{end}}
{{define "content"}}
<div class="container">
<div class="text-center mb-8">
<a href="/pwpush" class="inline-block">
<h1 class="text-2xl md:text-3xl font-bold text-gray-100 hover:text-blue-400 transition-colors cursor-pointer mb-4">
🔐 Password Pusher
</h1>
</a>
<p class="text-lg text-gray-300 max-w-2xl mx-auto">
Share sensitive text securely with automatic expiration and view limits.
</p>
</div>
{{if .Success}}
<div class="bg-green-800 border border-green-600 rounded-lg p-6 mb-8 max-w-4xl mx-auto">
<h3 class="text-xl font-bold text-green-100 mb-4">✅ Secure Link Created!</h3>
<p class="text-green-200 mb-4">Your text has been encrypted and stored securely. Share this link:</p>
<div class="flex flex-col sm:flex-row gap-3 mb-4 p-4 bg-green-900/50 rounded-lg border border-green-700">
<input type="text"
id="pushUrl"
value="{{.PushURL}}"
readonly
onclick="this.select()"
class="flex-1 p-3 bg-gray-900 border-2 border-gray-600 rounded-lg text-gray-100 font-mono text-sm focus:border-green-500 focus:outline-none">
<div class="flex flex-row items-stretch justify-center gap-3">
<button onclick="copyToClipboard()"
class="copy-btn bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg font-medium transition-colors whitespace-nowrap">
📋 Copy URL
</button>
<button onclick="copyAsMessage()"
class="copy-message-btn bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition-colors whitespace-nowrap">
💬 Copy as Message
</button>
</div>
</div>
<p class="text-green-300 text-sm mb-4">🕒 Expires: {{.ExpiresAt}} or after {{.MaxViews}} views.</p>
<a href="/pwpush"
class="inline-block bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition-colors">
Create Another Link
</a>
</div>
{{else}}
<form id="pushForm" method="post" class="bg-gray-800 rounded-lg p-6 mb-8 max-w-4xl mx-auto border border-gray-700 shadow-lg">
{{if .CSRFToken}}
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
{{end}}
<!-- Main content area -->
<div class="space-y-6 mb-8">
<div class="w-full">
<label for="text" class="block text-sm font-semibold text-gray-200 mb-2">📝 Text to Share:</label>
<textarea name="text" id="text"
class="w-full p-4 bg-gray-900 border-2 border-gray-600 rounded-lg text-gray-100 font-mono resize-y focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none transition-all"
rows="6"
placeholder="Enter your password, secret, or sensitive text here..."
required></textarea>
<small class="block mt-1 text-xs text-gray-400">Enter any sensitive information you want to share securely.</small>
</div>
<div class="w-full">
<label for="protection_password" class="block text-sm font-semibold text-gray-200 mb-2">🔑 Password Protection (Optional):</label>
<input type="password"
name="password"
id="protection_password"
class="w-full p-3 bg-gray-900 border-2 border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none transition-all"
placeholder="Leave empty for no password protection">
<small class="block mt-1 text-xs text-gray-400">If set, viewers must enter this password to access the content. This bypasses the click-to-reveal feature.</small>
</div>
</div>
<!-- Advanced Settings Toggle -->
<div class="mb-8">
<button type="button"
onclick="toggleAdvancedSettings()"
class="flex items-center justify-between w-full p-2 bg-gray-900 rounded-lg border border-gray-700 hover:bg-gray-800 transition-colors">
<span class="flex items-center space-x-2 text-gray-200 font-medium">
<span class="text-lg">⚙️</span>
<span>Advanced Settings <small class="text-gray-500">(Expiry, Click to reveal, History, Deletion)</small></span>
</span>
<span id="advancedToggleIcon" class="text-gray-400 transform transition-transform duration-200"></span>
</button>
<div id="advancedSettings" class="hidden mt-4 space-y-6">
<!-- Settings grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 p-6 bg-gray-900 rounded-lg border border-gray-700">
<div class="space-y-3">
<label for="expiry_days" class="block text-sm font-semibold text-gray-200">⏰ Expires After:</label>
<input type="range"
name="expiry_days"
id="expiry_days"
min="1" max="90" value="7"
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer slider"
oninput="updateExpiryDisplay(this.value)"
onchange="saveSettingWithDelay('expiry_days', this.value)">
<div class="flex justify-between items-center text-sm">
<span id="expiryDisplay" class="font-bold text-blue-400">7 days</span>
<small class="text-gray-500">Max: 3 months</small>
</div>
</div>
<div class="space-y-3">
<label for="max_views" class="block text-sm font-semibold text-gray-200">👁️ Maximum Views:</label>
<input type="range"
name="max_views"
id="max_views"
min="1" max="100" value="10"
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer slider"
oninput="updateViewsDisplay(this.value)"
onchange="saveSettingWithDelay('max_views', this.value)">
<div class="flex justify-between items-center text-sm">
<span id="viewsDisplay" class="font-bold text-blue-400">10 views</span>
<small class="text-gray-500">Max: 100 views</small>
</div>
</div>
</div>
<!-- Options -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 p-6 bg-gray-900 rounded-lg border border-gray-700">
<div class="space-y-2">
<label class="flex items-center space-x-3 cursor-pointer">
<input type="checkbox"
name="require_click"
checked
class="w-5 h-5 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
onchange="saveSettingImmediately('require_click', this.checked)">
<span class="text-sm font-medium text-gray-200">🛡️ Require click to reveal</span>
</label>
<small class="block text-xs text-gray-400 ml-8">Hides content from web crawlers and requires user interaction</small>
</div>
<div class="space-y-2">
<label class="flex items-center space-x-3 cursor-pointer">
<input type="checkbox"
name="auto_delete"
class="w-5 h-5 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
onchange="saveSettingImmediately('auto_delete', this.checked)">
<span class="text-sm font-medium text-gray-200">🗑️ Allow manual deletion</span>
</label>
<small class="block text-xs text-gray-400 ml-8">Adds a delete button when content is viewed (viewer can choose to delete)</small>
</div>
<div class="space-y-2">
<label class="flex items-center space-x-3 cursor-pointer">
<input type="checkbox"
name="track_history"
id="track_history"
class="w-5 h-5 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
onchange="saveSettingImmediately('track_history', this.checked)">
<span class="text-sm font-medium text-gray-200">📚 Save to my history</span>
</label>
<small class="block text-xs text-gray-400 ml-8">Keep a record of your created links (stored in browser)</small>
</div>
</div>
</div>
</div>
<!-- Action buttons -->
<div class="text-center pt-6">
<button type="submit"
class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-bold py-4 px-8 rounded-lg text-lg transition-all duration-200 transform hover:scale-105 hover:shadow-lg focus:outline-none focus:ring-4 focus:ring-blue-500/50">
🔒 Create Secure Link
</button>
</div>
</form>
<div class="bg-gray-800 rounded-lg p-6 mb-8 max-w-4xl mx-auto border border-gray-700">
<h3 class="text-lg font-bold text-gray-200 mb-4">🔒 How it works:</h3>
<ul class="space-y-2 text-gray-300">
<li class="flex items-start space-x-2">
<span class="text-blue-400 font-bold"></span>
<span><strong class="text-gray-200">Encryption:</strong> Do not remove <code>?k=...</code> from link, otherwise you cannot retrieve content.</span>
</li>
<li class="flex items-start space-x-2">
<span class="text-blue-400 font-bold"></span>
<span><strong class="text-gray-200">Automatic Expiry:</strong> Links expire after set time or view limit is reached</span>
</li>
<li class="flex items-start space-x-2">
<span class="text-blue-400 font-bold"></span>
<span><strong class="text-gray-200">One-time Use:</strong> Optional auto-delete after viewing with button click.</span>
</li>
<li class="flex items-start space-x-2">
<span class="text-blue-400 font-bold"></span>
<span><strong class="text-gray-200">No Registration:</strong> No account required</span>
</li>
<li class="flex items-start space-x-2">
<span class="text-blue-400 font-bold"></span>
<span><strong class="text-gray-200">Browser History:</strong> Optionally track your links locally, in web browser only.</span>
</li>
</ul>
</div>
{{end}}
<!-- History Section -->
<div class="bg-gray-800 rounded-lg p-6 max-w-6xl mx-auto border border-gray-700">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6">
<h2 class="text-xl font-bold text-gray-200 mb-4 sm:mb-0">📚 My Recent Links</h2>
<div class="flex space-x-2">
<button onclick="refreshHistory()"
class="bg-gray-700 hover:bg-gray-600 text-gray-200 px-4 py-2 rounded-lg text-sm font-medium transition-colors">
🔄 Refresh
</button>
<button onclick="clearHistory()"
class="bg-red-700 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
🗑️ Clear All
</button>
</div>
</div>
<div id="historyContainer">
<div class="flex flex-wrap gap-4 mb-6">
<span class="bg-gray-700 text-gray-300 px-3 py-1 rounded-full text-sm">
Total: <span id="totalCount" class="font-bold text-white">0</span>
</span>
<span class="bg-green-700 text-green-100 px-3 py-1 rounded-full text-sm">
Active: <span id="activeCount" class="font-bold text-white">0</span>
</span>
<span class="bg-red-700 text-red-100 px-3 py-1 rounded-full text-sm">
Expired: <span id="expiredCount" class="font-bold text-white">0</span>
</span>
</div>
<div class="overflow-x-auto">
<table id="historyTable" class="w-full text-sm text-gray-300">
<thead class="bg-gray-900 text-gray-200 uppercase text-xs">
<tr>
<th class="px-4 py-3 text-left">📅 Created</th>
<th class="px-4 py-3 text-left">⏰ Expires</th>
<th class="px-4 py-3 text-left">👁️ Views</th>
<th class="px-4 py-3 text-left">📝 Local Notes</th>
<th class="px-4 py-3 text-left">🔗 Status</th>
<th class="px-4 py-3 text-left">⚡ Actions</th>
</tr>
</thead>
<tbody id="historyTableBody" class="divide-y divide-gray-700">
<tr class="empty-row">
<td colspan="6" class="px-4 py-8 text-center text-gray-400">
📭 No history found. Enable "Save to my history" when creating links.
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function toggleAdvancedSettings() {
const advancedSettings = document.getElementById('advancedSettings');
const toggleIcon = document.getElementById('advancedToggleIcon');
if (advancedSettings.classList.contains('hidden')) {
advancedSettings.classList.remove('hidden');
toggleIcon.textContent = '▲';
toggleIcon.style.transform = 'rotate(180deg)';
} else {
advancedSettings.classList.add('hidden');
toggleIcon.textContent = '▼';
toggleIcon.style.transform = 'rotate(0deg)';
}
}
function updateExpiryDisplay(days) {
const display = document.getElementById('expiryDisplay');
if (days == 1) {
display.textContent = '1 day';
} else if (days <= 7) {
display.textContent = days + ' days';
} else if (days <= 30) {
const weeks = Math.floor(days / 7);
const remainingDays = days % 7;
if (remainingDays === 0) {
display.textContent = weeks + (weeks === 1 ? ' week' : ' weeks');
} else {
display.textContent = weeks + 'w ' + remainingDays + 'd';
}
} else {
const months = Math.floor(days / 30);
const remainingDays = days % 30;
if (remainingDays === 0) {
display.textContent = months + (months === 1 ? ' month' : ' months');
} else {
display.textContent = months + 'm ' + remainingDays + 'd';
}
}
}
function updateViewsDisplay(views) {
const display = document.getElementById('viewsDisplay');
display.textContent = views + (views === '1' ? ' view' : ' views');
}
// Auto-save settings functionality
let saveTimeouts = {};
function saveSettingWithDelay(settingName, value) {
// Clear any existing timeout for this setting
if (saveTimeouts[settingName]) {
clearTimeout(saveTimeouts[settingName]);
}
// Set a new timeout to save after user stops changing the value
saveTimeouts[settingName] = setTimeout(() => {
saveSettingImmediately(settingName, value);
}, 1000); // Wait 1 second after user stops moving slider
}
function saveSettingImmediately(settingName, value) {
setCookie('pwpush_' + settingName, value, 30);
// Show success notification
if (typeof showPopup === 'function') {
showPopup('Setting "' + settingName.replace('_', ' ') + '" saved automatically!', 'success', 2000);
}
}
function copyToClipboard() {
const urlInput = document.getElementById('pushUrl');
const btn = document.querySelector('.copy-btn');
if (!urlInput) {
console.error('pushUrl input not found');
return;
}
if (!btn) {
console.error('copy-btn button not found');
return;
}
const originalText = btn.textContent;
// Use modern clipboard API if available, fallback to execCommand
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(urlInput.value).then(() => {
btn.textContent = '✅ Copied!';
btn.style.background = '#4caf50';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
fallbackCopy();
});
} else {
fallbackCopy();
}
function fallbackCopy() {
urlInput.select();
urlInput.setSelectionRange(0, 99999); // For mobile devices
try {
document.execCommand('copy');
btn.textContent = '✅ Copied!';
btn.style.background = '#4caf50';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
} catch (err) {
console.error('Failed to copy: ', err);
btn.textContent = '❌ Failed';
btn.style.background = '#f44336';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
}
}
}
function copyAsMessage() {
const urlInput = document.getElementById('pushUrl');
const btn = document.querySelector('.copy-message-btn');
if (!urlInput) {
console.error('pushUrl input not found');
return;
}
if (!btn) {
console.error('copy-message-btn button not found');
return;
}
// Get the data from meta tag
const successData = document.querySelector('meta[name="success-data"]');
if (!successData) {
console.error('No success data found');
return;
}
const data = JSON.parse(successData.content);
const url = data.url;
const expiresAt = data.expiresAt;
const maxViews = data.maxViews;
// Create the message
const message = `The secret has been shared via the link below, it will expire on ${expiresAt} or after ${maxViews} views, whichever comes first:
${url}`;
const originalText = btn.textContent;
// Use modern clipboard API if available, fallback to execCommand
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(message).then(() => {
btn.textContent = '✅ Message Copied!';
btn.style.background = '#4caf50';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy message: ', err);
fallbackCopyMessage();
});
} else {
fallbackCopyMessage();
}
function fallbackCopyMessage() {
// Create a temporary textarea to copy the message
const textArea = document.createElement('textarea');
textArea.value = message;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
btn.textContent = '✅ Message Copied!';
btn.style.background = '#4caf50';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
} catch (err) {
console.error('Failed to copy message: ', err);
btn.textContent = '❌ Failed';
btn.style.background = '#f44336';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
} finally {
document.body.removeChild(textArea);
}
}
}
// Save settings to cookies when form is submitted
const pushForm = document.getElementById('pushForm');
if (pushForm) {
pushForm.addEventListener('submit', function() {
const expiryDays = document.getElementById('expiry_days').value;
const maxViews = document.getElementById('max_views').value;
const requireClick = document.querySelector('input[name="require_click"]').checked;
const autoDelete = document.querySelector('input[name="auto_delete"]').checked;
const trackHistory = document.querySelector('input[name="track_history"]').checked;
setCookie('pwpush_expiry_days', expiryDays, 30);
setCookie('pwpush_max_views', maxViews, 30);
setCookie('pwpush_require_click', requireClick, 30);
setCookie('pwpush_auto_delete', autoDelete, 30);
setCookie('pwpush_track_history', trackHistory, 30);
});
}
// History functionality
function refreshHistory() {
// Show loading state
const refreshBtn = document.querySelector('button[onclick="refreshHistory()"]');
const originalText = refreshBtn.textContent;
refreshBtn.textContent = '🔄 Validating...';
refreshBtn.disabled = true;
setTimeout(() => {
loadHistory();
refreshBtn.textContent = originalText;
refreshBtn.disabled = false;
}, 100);
}
function validateLinksWithServer(history) {
// Only validate links that are not already marked as expired/deleted
const activeLinks = history.filter(item => !item.isDeleted && new Date(item.expiresAt) > new Date());
if (activeLinks.length === 0) {
return Promise.resolve(history); // No active links to validate
}
const ids = activeLinks.map(item => item.id);
return fetch('/pwpush/api/status/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids: ids })
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to validate links');
}
return response.json();
})
.then(statuses => {
// Update history items with server status
const statusMap = {};
statuses.forEach(status => {
statusMap[status.id] = status;
});
return history.map(item => {
const serverStatus = statusMap[item.id];
if (serverStatus) {
// Update with server data
item.exists = serverStatus.exists;
item.isDeleted = serverStatus.is_deleted;
item.viewCount = serverStatus.current_views;
item.maxViews = serverStatus.max_views;
// Mark as expired if server says so
if (serverStatus.is_expired || !serverStatus.exists) {
item.isExpired = true;
}
// If link doesn't exist on server, mark for removal
if (!serverStatus.exists) {
item.shouldRemove = true;
}
}
return item;
}).filter(item => !item.shouldRemove); // Remove non-existent links
})
.catch(error => {
console.warn('Failed to validate links with server:', error);
return history; // Return original history if validation fails
});
}
function loadHistory() {
let history = getHistoryFromCookie();
// Always display existing history, but only validate with server if tracking is enabled
const trackHistory = getCookie('pwpush_track_history');
if (trackHistory === 'true' && history.length > 0) {
// Validate with server and update display
validateLinksWithServer(history).then(validatedHistory => {
// Save updated history back to cookies
if (validatedHistory.length !== history.length) {
saveHistoryToCookie(validatedHistory);
history = validatedHistory;
}
displayHistory(history);
});
} else {
// Just display the history without server validation
displayHistory(history);
}
}
function displayHistory(history) {
const tbody = document.getElementById('historyTableBody');
const emptyRow = tbody.querySelector('.empty-row');
// Always clear existing rows except empty row first
const existingRows = tbody.querySelectorAll('tr:not(.empty-row)');
existingRows.forEach(row => row.remove());
if (history.length === 0) {
if (emptyRow) emptyRow.style.display = '';
updateStats(0, 0, 0);
return;
}
if (emptyRow) emptyRow.style.display = 'none';
let activeCount = 0;
let expiredCount = 0;
history.forEach(item => {
const isExpired = item.isExpired || item.isDeleted || new Date(item.expiresAt) < new Date();
if (isExpired) expiredCount++;
else activeCount++;
const row = createHistoryRow(item, isExpired);
tbody.appendChild(row);
});
updateStats(history.length, activeCount, expiredCount);
}
function createHistoryRow(item, isExpired) {
const row = document.createElement('tr');
row.className = isExpired ? 'expired' : 'active';
// Determine status text and actions based on server state
let statusText = '✅ Active';
let statusClass = 'active';
let actions = '';
if (item.isDeleted) {
statusText = '🗑️ Deleted';
statusClass = 'deleted';
actions = '<span class="expired-text">Deleted on server</span>';
} else if (isExpired) {
statusText = '❌ Expired';
statusClass = 'expired';
actions = '<span class="expired-text">Expired</span>';
} else {
actions = `<button onclick="copyLink('${item.url}')" class="btn btn-secondary btn-xs" title="Copy Link">📋</button>
<a href="${item.url}" class="btn btn-primary btn-xs" target="_blank" title="View">👁️</a>
<button onclick="editNotes('${item.id}')" class="btn btn-secondary btn-xs" title="Edit Notes">✏️</button>`;
}
// Always show remove button for cleaning up history
actions += `<button onclick="removeFromHistory('${item.id}')" class="btn btn-danger btn-xs" title="Remove from browser history(only)">🗑️</button>`;
row.innerHTML = `
<td class="date-cell">
${formatDate(item.createdAt)}
</td>
<td class="date-cell">
${formatDate(item.expiresAt)}
</td>
<td class="views-cell">
${item.viewCount || 0}/${item.maxViews}
</td>
<td class="notes-cell">
<span class="notes-text">${item.notes ? item.notes.substring(0, 30) + (item.notes.length > 30 ? '...' : '') : '-'}</span>
</td>
<td class="status-cell">
<span class="status-badge ${statusClass}">
${statusText}
</span>
</td>
<td class="actions-cell">
${actions}
</td>
`;
return row;
}
function updateStats(total, active, expired) {
document.getElementById('totalCount').textContent = total;
document.getElementById('activeCount').textContent = active;
document.getElementById('expiredCount').textContent = expired;
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
function copyLink(url) {
const textArea = document.createElement('textarea');
textArea.value = url;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
// Show success feedback
const btn = event.target;
const originalText = btn.textContent;
btn.textContent = '✅';
btn.style.background = '#4caf50';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 1500);
}
function removeFromHistory(pushId) {
if (confirm('Remove this item from your history?')) {
const history = getHistoryFromCookie();
const updatedHistory = history.filter(h => h.id !== pushId);
saveHistoryToCookie(updatedHistory);
loadHistory();
}
}
function editNotes(pushId) {
const history = getHistoryFromCookie();
const item = history.find(h => h.id === pushId);
if (!item) return;
const currentNotes = item.notes || '';
const newNotes = prompt('Add notes for this link (for your reference only):', currentNotes);
if (newNotes !== null) { // User didn't cancel
item.notes = newNotes;
saveHistoryToCookie(history);
loadHistory();
}
}
function clearHistory() {
if (confirm('Clear all history? This cannot be undone.')) {
// Show loading state
const clearBtn = document.querySelector('button[onclick="clearHistory()"]');
const originalText = clearBtn.textContent;
clearBtn.textContent = '🗑️ Clearing...';
clearBtn.disabled = true;
// Clear the cookie
document.cookie = "pwpush_history=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
// Immediately update the display to show empty state
displayHistory([]);
// Show success feedback
clearBtn.textContent = '✅ Cleared!';
// Restore original text after showing success
setTimeout(() => {
clearBtn.textContent = originalText;
clearBtn.disabled = false;
}, 1500);
}
}
function getHistoryFromCookie() {
const cookie = document.cookie
.split('; ')
.find(row => row.startsWith('pwpush_history='));
if (!cookie) return [];
try {
return JSON.parse(decodeURIComponent(cookie.split('=')[1]));
} catch (e) {
return [];
}
}
function saveHistoryToCookie(history) {
const expires = new Date();
expires.setTime(expires.getTime() + (30 * 24 * 60 * 60 * 1000)); // 30 days
document.cookie = `pwpush_history=${encodeURIComponent(JSON.stringify(history))};expires=${expires.toUTCString()};path=/`;
}
function getCookie(name) {
const value = "; " + document.cookie;
const parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
return null;
}
function setCookie(name, value, days) {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + "=" + value + ";expires=" + expires.toUTCString() + ";path=/";
}
// Load history on page load
document.addEventListener('DOMContentLoaded', function() {
// Check if this is a success page with a new link created
const successData = document.querySelector('meta[name="success-data"]');
if (successData) {
const trackHistory = getCookie('pwpush_track_history');
if (trackHistory === 'true') {
// Add the new link to history
const data = JSON.parse(successData.content);
const newItem = {
id: data.id,
url: data.url,
createdAt: new Date().toISOString(),
expiresAt: data.expiresAt,
maxViews: parseInt(getCookie('pwpush_max_views')) || 10,
viewCount: 0,
previewText: 'Content hidden for security'
};
let history = getHistoryFromCookie();
// Check if this item already exists to prevent duplicates
const existingIndex = history.findIndex(item => item.id === newItem.id);
if (existingIndex === -1) {
history.unshift(newItem); // Add to beginning only if it doesn't exist
// Keep only last 50 items
if (history.length > 50) {
history = history.slice(0, 50);
}
saveHistoryToCookie(history);
}
}
}
// Load and display history (this will show the updated history including any new item)
loadHistory();
// Load saved preferences
const savedExpiry = getCookie('pwpush_expiry_days');
const savedViews = getCookie('pwpush_max_views');
const savedRequireClick = getCookie('pwpush_require_click');
const savedAutoDelete = getCookie('pwpush_auto_delete');
const savedTrackHistory = getCookie('pwpush_track_history');
const expiryDaysElement = document.getElementById('expiry_days');
const maxViewsElement = document.getElementById('max_views');
const requireClickElement = document.querySelector('input[name="require_click"]');
const autoDeleteElement = document.querySelector('input[name="auto_delete"]');
const trackHistoryElement = document.querySelector('input[name="track_history"]');
if (savedExpiry && expiryDaysElement) {
expiryDaysElement.value = savedExpiry;
updateExpiryDisplay(savedExpiry);
}
if (savedViews && maxViewsElement) {
maxViewsElement.value = savedViews;
updateViewsDisplay(savedViews);
}
if (savedRequireClick !== null && requireClickElement) {
requireClickElement.checked = savedRequireClick === 'true';
}
if (savedAutoDelete !== null && autoDeleteElement) {
autoDeleteElement.checked = savedAutoDelete === 'true';
}
if (savedTrackHistory !== null && trackHistoryElement) {
trackHistoryElement.checked = savedTrackHistory === 'true';
}
});
</script>
{{end}}