🆕 Add render history feature with localStorage persistence
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Markdown Renderer</title>
|
||||
<title>Markdown Render</title>
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> -->
|
||||
<script src="https://cdn.hatter.ink/doc/18837_2D3503C6AA42672F4699D5608BC15B60/marked.min.js"></script>
|
||||
<style>
|
||||
@@ -320,13 +320,153 @@
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.history-toggle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.history-toggle:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.history-panel {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.history-panel.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #f8f9ff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.history-header h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.history-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.history-actions button {
|
||||
padding: 4px 10px;
|
||||
background: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.history-actions button:hover {
|
||||
background: #e8eaff;
|
||||
border-color: #667eea;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.history-item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.history-item-source {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.history-item-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.history-item-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ccc;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.history-item-delete:hover {
|
||||
color: #d63031;
|
||||
background: #ffe6e6;
|
||||
}
|
||||
|
||||
.history-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Markdown Renderer</h1>
|
||||
<h1>Markdown Render
|
||||
<button class="history-toggle" id="historyToggle" title="History">🕑</button>
|
||||
</h1>
|
||||
<p class="subtitle">Enter a URL or drop a markdown file</p>
|
||||
|
||||
<div class="history-panel" id="historyPanel">
|
||||
<div class="history-header">
|
||||
<h3>Render History</h3>
|
||||
<div class="history-actions">
|
||||
<button id="clearHistory">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="history-list" id="historyList"></div>
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
<input type="text" class="url-input" id="urlInput" placeholder="https://example.com/file.md">
|
||||
<button class="submit-btn" id="submitBtn">Render</button>
|
||||
@@ -372,8 +512,14 @@
|
||||
const copyMdBtn = document.getElementById('copyMdBtn');
|
||||
const rawBtn = document.getElementById('rawBtn');
|
||||
const toast = document.getElementById('toast');
|
||||
const historyToggle = document.getElementById('historyToggle');
|
||||
const historyPanel = document.getElementById('historyPanel');
|
||||
const historyList = document.getElementById('historyList');
|
||||
const clearHistory = document.getElementById('clearHistory');
|
||||
|
||||
let currentRawText = '';
|
||||
const HISTORY_KEY = 'markdown_render_history';
|
||||
const MAX_HISTORY = 50;
|
||||
|
||||
// Extension system: loaders registry
|
||||
// Loaders are functions that take a URL/source and return text content.
|
||||
@@ -426,6 +572,7 @@
|
||||
renderedContent.innerHTML = html;
|
||||
sourceLabel.textContent = source || 'Dropped file';
|
||||
renderOutput.classList.add('visible');
|
||||
addToHistory(source || 'Dropped file', text);
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
@@ -517,6 +664,106 @@
|
||||
window.open(url, '_blank');
|
||||
});
|
||||
|
||||
// --- History ---
|
||||
function getHistory() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]');
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function saveHistory(items) {
|
||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(items));
|
||||
}
|
||||
|
||||
function addToHistory(source, text) {
|
||||
const items = getHistory();
|
||||
// Remove existing entry with same source to avoid duplicates
|
||||
const existing = items.findIndex(i => i.source === source);
|
||||
if (existing !== -1) items.splice(existing, 1);
|
||||
items.unshift({
|
||||
source,
|
||||
text,
|
||||
time: new Date().toISOString()
|
||||
});
|
||||
if (items.length > MAX_HISTORY) items.length = MAX_HISTORY;
|
||||
saveHistory(items);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function deleteHistoryItem(index) {
|
||||
const items = getHistory();
|
||||
items.splice(index, 1);
|
||||
saveHistory(items);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function clearAllHistory() {
|
||||
localStorage.removeItem(HISTORY_KEY);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function formatTime(iso) {
|
||||
const d = new Date(iso);
|
||||
const now = new Date();
|
||||
const diff = now - d;
|
||||
if (diff < 60000) return 'Just now';
|
||||
if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
|
||||
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
|
||||
return d.toLocaleDateString();
|
||||
}
|
||||
|
||||
function renderHistory() {
|
||||
const items = getHistory();
|
||||
if (items.length === 0) {
|
||||
historyList.innerHTML = '<div class="history-empty">No render history yet</div>';
|
||||
return;
|
||||
}
|
||||
historyList.innerHTML = items.map((item, i) => `
|
||||
<div class="history-item" data-index="${i}">
|
||||
<div class="history-item-info">
|
||||
<div class="history-item-source">${escapeHtml(item.source)}</div>
|
||||
<div class="history-item-time">${formatTime(item.time)}</div>
|
||||
</div>
|
||||
<button class="history-item-delete" data-index="${i}" title="Delete">×</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
historyList.addEventListener('click', (e) => {
|
||||
const deleteBtn = e.target.closest('.history-item-delete');
|
||||
if (deleteBtn) {
|
||||
e.stopPropagation();
|
||||
deleteHistoryItem(parseInt(deleteBtn.dataset.index));
|
||||
return;
|
||||
}
|
||||
const item = e.target.closest('.history-item');
|
||||
if (item) {
|
||||
const idx = parseInt(item.dataset.index);
|
||||
const entry = getHistory()[idx];
|
||||
if (entry) {
|
||||
hideError();
|
||||
renderMarkdown(entry.text, entry.source);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
historyToggle.addEventListener('click', () => {
|
||||
historyPanel.classList.toggle('visible');
|
||||
renderHistory();
|
||||
});
|
||||
|
||||
clearHistory.addEventListener('click', () => {
|
||||
clearAllHistory();
|
||||
});
|
||||
|
||||
renderHistory();
|
||||
|
||||
// Handle URL parameter: ?url=...
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const urlParam = params.get('url');
|
||||
|
||||
Reference in New Issue
Block a user