open local PDFs in iframes, externals as links...
This commit is contained in:
parent
1751e84dd3
commit
37d7c0eba9
|
|
@ -211,8 +211,23 @@ def process_media_for_export(markdown_content: str, note_folder: Path, notes_dir
|
|||
alt_text = match.group(1)
|
||||
media_path = match.group(2)
|
||||
|
||||
# Skip external URLs and already-embedded base64
|
||||
if media_path.startswith(('http://', 'https://', 'data:')):
|
||||
# Handle external URLs
|
||||
if media_path.startswith(('http://', 'https://')):
|
||||
# Check if it's a PDF - generate styled external link
|
||||
media_type = get_media_type(media_path)
|
||||
if media_type == 'document':
|
||||
display_name = alt_text or Path(media_path).stem
|
||||
safe_name = display_name.replace('"', '"').replace('<', '<').replace('>', '>')
|
||||
safe_url = media_path.replace('"', '"')
|
||||
return f'''<a href="{safe_url}" target="_blank" rel="noopener noreferrer" style="display:flex;flex-direction:column;gap:0.25rem;padding:1rem 1.25rem;margin:1rem 0;background:linear-gradient(135deg,var(--bg-tertiary,#f8f9fa) 0%,var(--bg-secondary,#e9ecef) 100%);border:1px solid var(--border-primary,#dee2e6);border-radius:0.5rem;color:var(--text-primary,#212529);text-decoration:none;">
|
||||
<span style="font-weight:600;">📄 {safe_name}</span>
|
||||
<span style="font-size:0.75rem;color:var(--text-secondary,#6c757d);">Opens in new tab</span>
|
||||
</a>'''
|
||||
# Other external media: keep as-is (will show as broken image)
|
||||
return match.group(0)
|
||||
|
||||
# Skip already-embedded base64
|
||||
if media_path.startswith('data:'):
|
||||
return match.group(0)
|
||||
|
||||
# Skip empty paths (from failed wikilink conversion)
|
||||
|
|
|
|||
|
|
@ -2432,9 +2432,9 @@ function noteApp() {
|
|||
const href = link.getAttribute('href');
|
||||
if (!href) return;
|
||||
|
||||
// Check if it's an external link
|
||||
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//') || href.startsWith('mailto:')) {
|
||||
return; // Let external links work normally
|
||||
// Check if it's an external link or API path (media files, etc.)
|
||||
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//') || href.startsWith('mailto:') || href.startsWith('/api/')) {
|
||||
return; // Let external links and API paths work normally
|
||||
}
|
||||
|
||||
// Prevent default navigation for internal links
|
||||
|
|
@ -2606,6 +2606,7 @@ function noteApp() {
|
|||
const wasCurrentNote = this.currentNote === draggedNotePath;
|
||||
|
||||
await this.loadNotes();
|
||||
await this.loadSharedNotePaths(); // Refresh shared paths after move
|
||||
|
||||
if (wasCurrentNote) {
|
||||
this.currentNote = newPath;
|
||||
|
|
@ -2668,6 +2669,7 @@ function noteApp() {
|
|||
}
|
||||
|
||||
await this.loadNotes();
|
||||
await this.loadSharedNotePaths(); // Refresh shared paths after folder move
|
||||
// Update current note path if it was in the moved folder
|
||||
if (this.currentNote && this.currentNote.startsWith(oldPrefix)) {
|
||||
this.currentNote = this.currentNote.replace(oldPrefix, newPrefix);
|
||||
|
|
@ -4021,7 +4023,8 @@ function noteApp() {
|
|||
case 'video':
|
||||
return `<div class="media-embed media-video"><video controls preload="none" poster="" src="${mediaSrc}" title="${safeAlt}"></video></div>`;
|
||||
case 'document':
|
||||
return `<div class="media-embed media-pdf"><iframe src="${mediaSrc}" title="${safeAlt}" loading="lazy"></iframe></div>`;
|
||||
// Local PDFs: show iframe preview
|
||||
return `<div class="media-embed media-pdf"><iframe src="${mediaSrc}" title="${safeAlt}"></iframe></div>`;
|
||||
default: // image
|
||||
return `<img src="${mediaSrc}" alt="${safeAlt}" title="${safeAlt}">`;
|
||||
}
|
||||
|
|
@ -4110,11 +4113,11 @@ function noteApp() {
|
|||
images.forEach(img => {
|
||||
let src = img.getAttribute('src');
|
||||
if (src) {
|
||||
const isExternal = src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//');
|
||||
const isLocal = !isExternal && !src.startsWith('data:');
|
||||
|
||||
// Transform relative paths to /api/media/ for serving
|
||||
// Skip external URLs and already-transformed paths
|
||||
if (!src.startsWith('http://') && !src.startsWith('https://') &&
|
||||
!src.startsWith('//') && !src.startsWith('/api/media/') &&
|
||||
!src.startsWith('data:')) {
|
||||
if (isLocal && !src.startsWith('/api/media/')) {
|
||||
// URL-encode path segments to handle spaces and special characters
|
||||
const encodedPath = src.split('/').map(segment => {
|
||||
try {
|
||||
|
|
@ -4132,25 +4135,42 @@ function noteApp() {
|
|||
const altText = img.getAttribute('alt') || src.split('/').pop().replace(/\.[^/.]+$/, '');
|
||||
const safeAlt = altText.replace(/"/g, '"');
|
||||
|
||||
if (mediaType === 'audio') {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'media-embed media-audio';
|
||||
wrapper.innerHTML = `<audio controls preload="none" src="${src}" title="${safeAlt}"></audio><span class="media-caption">${safeAlt}</span>`;
|
||||
img.replaceWith(wrapper);
|
||||
return;
|
||||
} else if (mediaType === 'video') {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'media-embed media-video';
|
||||
wrapper.innerHTML = `<video controls preload="none" poster="" src="${src}" title="${safeAlt}"></video>`;
|
||||
img.replaceWith(wrapper);
|
||||
return;
|
||||
} else if (mediaType === 'document') {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'media-embed media-pdf';
|
||||
wrapper.innerHTML = `<iframe src="${src}" title="${safeAlt}" loading="lazy"></iframe>`;
|
||||
img.replaceWith(wrapper);
|
||||
// Only convert LOCAL media to embedded elements (security)
|
||||
// External non-image media gets styled links instead
|
||||
if (isLocal || src.startsWith('/api/media/')) {
|
||||
if (mediaType === 'audio') {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'media-embed media-audio';
|
||||
wrapper.innerHTML = `<audio controls preload="none" src="${src}" title="${safeAlt}"></audio><span class="media-caption">${safeAlt}</span>`;
|
||||
img.replaceWith(wrapper);
|
||||
return;
|
||||
} else if (mediaType === 'video') {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'media-embed media-video';
|
||||
wrapper.innerHTML = `<video controls preload="none" poster="" src="${src}" title="${safeAlt}"></video>`;
|
||||
img.replaceWith(wrapper);
|
||||
return;
|
||||
} else if (mediaType === 'document') {
|
||||
// Local PDFs: show iframe preview
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'media-embed media-pdf';
|
||||
wrapper.innerHTML = `<iframe src="${src}" title="${safeAlt}"></iframe>`;
|
||||
img.replaceWith(wrapper);
|
||||
return;
|
||||
}
|
||||
} else if (isExternal && mediaType === 'document') {
|
||||
// External PDFs: styled link (opens in new tab)
|
||||
const link = document.createElement('a');
|
||||
link.href = src;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
link.className = 'pdf-link';
|
||||
link.title = `Open ${safeAlt}`;
|
||||
link.innerHTML = `<span class="pdf-link-content">📄 ${safeAlt}</span><span class="pdf-link-note">Opens in new tab</span>`;
|
||||
img.replaceWith(link);
|
||||
return;
|
||||
}
|
||||
// External audio/video: leave as broken image for security
|
||||
}
|
||||
|
||||
// For regular images, set title attribute
|
||||
|
|
|
|||
|
|
@ -484,17 +484,50 @@
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* PDF embeds */
|
||||
/* PDF embeds (iframe) */
|
||||
.markdown-preview .media-pdf {
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
.markdown-preview .media-pdf iframe {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border: none;
|
||||
background: #525659;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* PDF links (for external PDFs) */
|
||||
.markdown-preview .pdf-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
padding: 1rem 1.25rem;
|
||||
margin: 1rem 0;
|
||||
background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 0.5rem;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.markdown-preview .pdf-link:hover {
|
||||
background: var(--bg-secondary);
|
||||
border-color: var(--accent-primary);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.markdown-preview .pdf-link:hover .pdf-link-content {
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
.markdown-preview .pdf-link-content {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.markdown-preview .pdf-link-note {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Task lists */
|
||||
|
|
|
|||
Loading…
Reference in New Issue