2025-07-17 21:52:52 +01:00
{{template "base.html" .}}
{{define "title"}}Password Pusher - Secure Text Sharing{{end}}
{{define "head"}}
{{if .Success}}
2025-07-22 07:28:30 +01:00
< meta name = "success-data" content = '{"id":"{{.ID}}","url":"{{.PushURL}}","expiresAt":"{{.ExpiresAt}}","maxViews":"{{.MaxViews}}"}' >
2025-07-17 21:52:52 +01:00
{{end}}
{{end}}
{{define "content"}}
< div class = "container" >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
{{if .Success}}
2025-07-19 12:58:22 +01:00
< 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">
2025-07-22 07:28:30 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / div >
2025-07-22 07:28:30 +01:00
< p class = "text-green-300 text-sm mb-4" > 🕒 Expires: {{.ExpiresAt}} or after {{.MaxViews}} views.< / p >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / div >
{{else}}
2025-07-19 12:58:22 +01:00
< 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" >
2025-07-17 21:52:52 +01:00
{{if .CSRFToken}}
< input type = "hidden" name = "csrf_token" value = "{{.CSRFToken}}" >
{{end}}
2025-07-19 12:58:22 +01:00
<!-- 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 >
2025-07-17 21:52:52 +01:00
< / div >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / div >
< / div >
2025-07-19 12:58:22 +01:00
<!-- 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"
2025-07-22 07:28:30 +01:00
oninput="updateExpiryDisplay(this.value)"
onchange="saveSettingWithDelay('expiry_days', this.value)">
2025-07-19 12:58:22 +01:00
< 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"
2025-07-22 07:28:30 +01:00
oninput="updateViewsDisplay(this.value)"
onchange="saveSettingWithDelay('max_views', this.value)">
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
2025-07-19 12:58:22 +01:00
<!-- 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
2025-07-22 07:28:30 +01:00
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)">
2025-07-19 12:58:22 +01:00
< 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"
2025-07-22 07:28:30 +01:00
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)">
2025-07-19 12:58:22 +01:00
< 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"
2025-07-22 07:28:30 +01:00
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)">
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / div >
< / div >
2025-07-19 12:58:22 +01:00
<!-- Action buttons -->
2025-07-22 07:28:30 +01:00
< div class = "text-center pt-6" >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / form >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / ul >
< / div >
{{end}}
<!-- History Section -->
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / div >
< / div >
< div id = "historyContainer" >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / div >
2025-07-19 12:58:22 +01:00
< 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" >
2025-07-17 21:52:52 +01:00
< tr >
2025-07-19 12:58:22 +01:00
< 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 >
2025-07-17 21:52:52 +01:00
< / tr >
< / thead >
2025-07-19 12:58:22 +01:00
< tbody id = "historyTableBody" class = "divide-y divide-gray-700" >
2025-07-17 21:52:52 +01:00
< tr class = "empty-row" >
2025-07-19 12:58:22 +01:00
< td colspan = "6" class = "px-4 py-8 text-center text-gray-400" >
2025-07-17 21:52:52 +01:00
📭 No history found. Enable "Save to my history" when creating links.
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / div >
< / div >
< / div >
< script >
2025-07-19 12:58:22 +01:00
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)';
}
}
2025-07-17 21:52:52 +01:00
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');
}
2025-07-22 07:28:30 +01:00
// 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);
}
}
2025-07-17 21:52:52 +01:00
function copyToClipboard() {
const urlInput = document.getElementById('pushUrl');
const btn = document.querySelector('.copy-btn');
2025-07-19 12:58:22 +01:00
if (!urlInput) {
console.error('pushUrl input not found');
return;
}
if (!btn) {
console.error('copy-btn button not found');
return;
}
2025-07-17 21:52:52 +01:00
const originalText = btn.textContent;
2025-07-19 12:58:22 +01:00
// 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);
}
}
2025-07-17 21:52:52 +01:00
}
2025-07-22 07:28:30 +01:00
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);
}
}
}
2025-07-17 21:52:52 +01:00
// Save settings to cookies when form is submitted
2025-07-19 12:58:22 +01:00
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);
});
}
2025-07-17 21:52:52 +01:00
// 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();
2025-07-19 12:58:22 +01:00
// Always display existing history, but only validate with server if tracking is enabled
2025-07-17 21:52:52 +01:00
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 {
2025-07-19 12:58:22 +01:00
// Just display the history without server validation
2025-07-17 21:52:52 +01:00
displayHistory(history);
}
}
function displayHistory(history) {
const tbody = document.getElementById('historyTableBody');
const emptyRow = tbody.querySelector('.empty-row');
2025-07-19 12:58:22 +01:00
// Always clear existing rows except empty row first
const existingRows = tbody.querySelectorAll('tr:not(.empty-row)');
existingRows.forEach(row => row.remove());
2025-07-17 21:52:52 +01:00
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
2025-07-19 12:58:22 +01:00
actions += `< button onclick = "removeFromHistory('${item.id}')" class = "btn btn-danger btn-xs" title = "Remove from browser history(only)" > 🗑️< / button > `;
2025-07-17 21:52:52 +01:00
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=/;";
2025-07-19 12:58:22 +01:00
// Immediately update the display to show empty state
displayHistory([]);
// Show success feedback
clearBtn.textContent = '✅ Cleared!';
// Restore original text after showing success
2025-07-17 21:52:52 +01:00
setTimeout(() => {
2025-07-19 12:58:22 +01:00
clearBtn.textContent = originalText;
clearBtn.disabled = false;
}, 1500);
2025-07-17 21:52:52 +01:00
}
}
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();
2025-07-19 12:58:22 +01:00
// 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);
2025-07-17 21:52:52 +01:00
}
}
}
2025-07-19 12:58:22 +01:00
// Load and display history (this will show the updated history including any new item)
loadHistory();
2025-07-17 21:52:52 +01:00
// 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');
2025-07-19 12:58:22 +01:00
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;
2025-07-17 21:52:52 +01:00
updateExpiryDisplay(savedExpiry);
}
2025-07-19 12:58:22 +01:00
if (savedViews & & maxViewsElement) {
maxViewsElement.value = savedViews;
2025-07-17 21:52:52 +01:00
updateViewsDisplay(savedViews);
}
2025-07-19 12:58:22 +01:00
if (savedRequireClick !== null & & requireClickElement) {
requireClickElement.checked = savedRequireClick === 'true';
2025-07-17 21:52:52 +01:00
}
2025-07-19 12:58:22 +01:00
if (savedAutoDelete !== null & & autoDeleteElement) {
autoDeleteElement.checked = savedAutoDelete === 'true';
2025-07-17 21:52:52 +01:00
}
2025-07-19 12:58:22 +01:00
if (savedTrackHistory !== null & & trackHistoryElement) {
trackHistoryElement.checked = savedTrackHistory === 'true';
2025-07-17 21:52:52 +01:00
}
});
< / script >
{{end}}