mirror of
https://github.com/kennethreitz/kennethreitz.org.git
synced 2026-06-05 22:50:17 +00:00
Remove link preview tooltip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -958,351 +958,5 @@
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Link Preview Tooltip -->
|
||||
<div id="link-preview-tooltip" class="link-preview-tooltip">
|
||||
<div id="preview-content"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.link-preview-tooltip {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
z-index: 10000;
|
||||
width: 400px;
|
||||
max-width: calc(100vw - 4rem);
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 1.25rem;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
transition: opacity 0.08s ease, transform 0.08s ease;
|
||||
}
|
||||
|
||||
.link-preview-tooltip:hover {
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.link-preview-tooltip.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
|
||||
a.link-preview-active {
|
||||
background: rgba(243, 156, 18, 0.15);
|
||||
box-shadow: 0 0 8px rgba(243, 156, 18, 0.4);
|
||||
border-radius: 3px;
|
||||
padding: 0.1rem 0.2rem;
|
||||
margin: -0.1rem -0.2rem;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.preview-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.preview-meta {
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.preview-excerpt {
|
||||
font-size: 0.875rem;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
body.dark-mode .link-preview-tooltip {
|
||||
background: rgba(26, 26, 26, 0.98);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
body.dark-mode .preview-title {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
body.dark-mode .preview-meta {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
body.dark-mode .preview-excerpt {
|
||||
color: #bbb;
|
||||
border-top-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.link-preview-tooltip {
|
||||
background: rgba(26, 26, 26, 0.98);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.preview-meta {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.preview-excerpt {
|
||||
color: #bbb;
|
||||
border-top-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.link-preview-tooltip {
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
width: calc(100vw - 2rem);
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tooltip = document.getElementById('link-preview-tooltip');
|
||||
const previewContent = document.getElementById('preview-content');
|
||||
let currentLink = null;
|
||||
let hideTimeout = null;
|
||||
const cache = {};
|
||||
|
||||
// Find all links in article content (internal and external)
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Disable tooltips on index/archive pages
|
||||
const path = window.location.pathname;
|
||||
if (path.match(/\/(sidenotes|outlines|connections|quotes|terms|archive)/)) {
|
||||
return; // Don't attach tooltip listeners on these pages
|
||||
}
|
||||
|
||||
const articleLinks = document.querySelectorAll('article a[href]');
|
||||
|
||||
articleLinks.forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
// Skip links in nav, breadcrumbs, etc.
|
||||
if (link.closest('nav') || link.closest('.breadcrumbs')) return;
|
||||
|
||||
// Skip links in special index pages (sidenotes, outlines, etc.)
|
||||
if (link.closest('.sidenotes-list') || link.closest('.outline-list') || link.closest('.sidenote-entry')) return;
|
||||
|
||||
// Skip sidenote links (links with # anchors to same page)
|
||||
if (link.classList.contains('sidenote-link') || link.classList.contains('outline-link')) return;
|
||||
|
||||
// Skip anchor links and mailto/tel links
|
||||
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) return;
|
||||
|
||||
// Skip any link that contains a hash (likely anchor link)
|
||||
if (href.includes('#')) return;
|
||||
|
||||
// Skip alternate format links (.md, .pdf)
|
||||
if (href.endsWith('.md') || href.endsWith('.pdf')) return;
|
||||
|
||||
// Support internal links and external http/https links
|
||||
if (href.startsWith('/') || href.startsWith('http://') || href.startsWith('https://')) {
|
||||
link.addEventListener('mouseenter', handleLinkHover);
|
||||
link.addEventListener('mouseleave', handleLinkLeave);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function handleLinkHover(e) {
|
||||
const link = e.currentTarget;
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
clearTimeout(hideTimeout);
|
||||
currentLink = link;
|
||||
|
||||
// Show tooltip after slight delay
|
||||
setTimeout(() => {
|
||||
if (currentLink === link) {
|
||||
showPreview(link, href);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleLinkLeave() {
|
||||
currentLink = null;
|
||||
tooltip.classList.remove('visible');
|
||||
hideTimeout = setTimeout(() => {
|
||||
tooltip.style.display = 'none';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
async function showPreview(link, href) {
|
||||
// Check cache first
|
||||
if (cache[href]) {
|
||||
displayPreview(link, cache[href]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle external links differently (can't fetch due to CORS)
|
||||
const isExternal = href.startsWith('http://') || href.startsWith('https://');
|
||||
|
||||
if (isExternal) {
|
||||
try {
|
||||
const url = new URL(href);
|
||||
const data = {
|
||||
title: link.textContent || url.hostname,
|
||||
excerpt: `External link: ${url.hostname}`,
|
||||
url: href,
|
||||
isExternal: true
|
||||
};
|
||||
cache[href] = data;
|
||||
displayPreview(link, data);
|
||||
} catch (e) {
|
||||
console.error('Invalid URL:', e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch internal page
|
||||
const response = await fetch(href);
|
||||
const html = await response.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
// Extract metadata
|
||||
const title = doc.querySelector('h1')?.textContent ||
|
||||
doc.querySelector('title')?.textContent ||
|
||||
'Untitled';
|
||||
|
||||
// Try to find reading time badge
|
||||
const readingTimeBadge = doc.querySelector('.reading-time-badge');
|
||||
const readingTime = readingTimeBadge?.textContent || null;
|
||||
|
||||
// Try to find post date
|
||||
const dateElement = doc.querySelector('.post-subtitle');
|
||||
const dateMatch = dateElement?.textContent.match(/[A-Z][a-z]+ \d{1,2}, \d{4}/);
|
||||
const date = dateMatch ? dateMatch[0] : null;
|
||||
|
||||
// Get first meaningful paragraph
|
||||
const paragraphs = doc.querySelectorAll('article section p:not(.subtitle):not(.sidenote):not(.marginnote)');
|
||||
let excerpt = '';
|
||||
for (const p of paragraphs) {
|
||||
const text = p.textContent.trim();
|
||||
if (text.length > 50) {
|
||||
excerpt = text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (excerpt.length > 200) {
|
||||
excerpt = excerpt.substring(0, 200).trim() + '...';
|
||||
}
|
||||
|
||||
// Try to get icon
|
||||
const icon = doc.querySelector('.post-title-icon')?.src ||
|
||||
doc.querySelector('.archive-post-icon')?.src ||
|
||||
doc.querySelector('.article-icon')?.src ||
|
||||
null;
|
||||
|
||||
// Calculate word count
|
||||
const articleText = doc.querySelector('article section')?.textContent || '';
|
||||
const wordCount = Math.round(articleText.trim().split(/\s+/).filter(w => w.length > 0).length);
|
||||
|
||||
const data = {
|
||||
title,
|
||||
readingTime,
|
||||
date,
|
||||
excerpt,
|
||||
icon,
|
||||
wordCount
|
||||
};
|
||||
|
||||
cache[href] = data;
|
||||
displayPreview(link, data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch preview:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function displayPreview(link, data) {
|
||||
let html = '<div class="preview-header">';
|
||||
|
||||
if (data.icon) {
|
||||
html += `<img src="${data.icon}" class="preview-icon" alt="">`;
|
||||
}
|
||||
|
||||
html += `<div>`;
|
||||
html += `<div class="preview-title">${escapeHtml(data.title || 'Untitled')}</div>`;
|
||||
|
||||
const metaParts = [];
|
||||
if (data.readingTime) metaParts.push(data.readingTime);
|
||||
else if (data.wordCount && data.wordCount > 0) {
|
||||
const estReadingTime = Math.ceil(data.wordCount / 200);
|
||||
metaParts.push(`~${estReadingTime} min read`);
|
||||
}
|
||||
if (data.wordCount && data.wordCount > 0) metaParts.push(`${data.wordCount} words`);
|
||||
if (data.date) metaParts.push(data.date);
|
||||
|
||||
if (metaParts.length > 0) {
|
||||
html += `<div class="preview-meta">${metaParts.join(' • ')}</div>`;
|
||||
}
|
||||
|
||||
html += `</div></div>`;
|
||||
|
||||
if (data.excerpt && data.excerpt.length > 0) {
|
||||
html += `<div class="preview-excerpt">${escapeHtml(data.excerpt)}</div>`;
|
||||
}
|
||||
|
||||
previewContent.innerHTML = html;
|
||||
|
||||
// Show tooltip in fixed position
|
||||
tooltip.style.display = 'block';
|
||||
|
||||
// Trigger animation
|
||||
setTimeout(() => {
|
||||
tooltip.classList.add('visible');
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user