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)
|
alt_text = match.group(1)
|
||||||
media_path = match.group(2)
|
media_path = match.group(2)
|
||||||
|
|
||||||
# Skip external URLs and already-embedded base64
|
# Handle external URLs
|
||||||
if media_path.startswith(('http://', 'https://', 'data:')):
|
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)
|
return match.group(0)
|
||||||
|
|
||||||
# Skip empty paths (from failed wikilink conversion)
|
# Skip empty paths (from failed wikilink conversion)
|
||||||
|
|
|
||||||
|
|
@ -2432,9 +2432,9 @@ function noteApp() {
|
||||||
const href = link.getAttribute('href');
|
const href = link.getAttribute('href');
|
||||||
if (!href) return;
|
if (!href) return;
|
||||||
|
|
||||||
// Check if it's an external link
|
// 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:')) {
|
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//') || href.startsWith('mailto:') || href.startsWith('/api/')) {
|
||||||
return; // Let external links work normally
|
return; // Let external links and API paths work normally
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent default navigation for internal links
|
// Prevent default navigation for internal links
|
||||||
|
|
@ -2606,6 +2606,7 @@ function noteApp() {
|
||||||
const wasCurrentNote = this.currentNote === draggedNotePath;
|
const wasCurrentNote = this.currentNote === draggedNotePath;
|
||||||
|
|
||||||
await this.loadNotes();
|
await this.loadNotes();
|
||||||
|
await this.loadSharedNotePaths(); // Refresh shared paths after move
|
||||||
|
|
||||||
if (wasCurrentNote) {
|
if (wasCurrentNote) {
|
||||||
this.currentNote = newPath;
|
this.currentNote = newPath;
|
||||||
|
|
@ -2668,6 +2669,7 @@ function noteApp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.loadNotes();
|
await this.loadNotes();
|
||||||
|
await this.loadSharedNotePaths(); // Refresh shared paths after folder move
|
||||||
// Update current note path if it was in the moved folder
|
// Update current note path if it was in the moved folder
|
||||||
if (this.currentNote && this.currentNote.startsWith(oldPrefix)) {
|
if (this.currentNote && this.currentNote.startsWith(oldPrefix)) {
|
||||||
this.currentNote = this.currentNote.replace(oldPrefix, newPrefix);
|
this.currentNote = this.currentNote.replace(oldPrefix, newPrefix);
|
||||||
|
|
@ -4021,7 +4023,8 @@ function noteApp() {
|
||||||
case 'video':
|
case 'video':
|
||||||
return `<div class="media-embed media-video"><video controls preload="none" poster="" src="${mediaSrc}" title="${safeAlt}"></video></div>`;
|
return `<div class="media-embed media-video"><video controls preload="none" poster="" src="${mediaSrc}" title="${safeAlt}"></video></div>`;
|
||||||
case 'document':
|
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
|
default: // image
|
||||||
return `<img src="${mediaSrc}" alt="${safeAlt}" title="${safeAlt}">`;
|
return `<img src="${mediaSrc}" alt="${safeAlt}" title="${safeAlt}">`;
|
||||||
}
|
}
|
||||||
|
|
@ -4110,11 +4113,11 @@ function noteApp() {
|
||||||
images.forEach(img => {
|
images.forEach(img => {
|
||||||
let src = img.getAttribute('src');
|
let src = img.getAttribute('src');
|
||||||
if (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
|
// Transform relative paths to /api/media/ for serving
|
||||||
// Skip external URLs and already-transformed paths
|
if (isLocal && !src.startsWith('/api/media/')) {
|
||||||
if (!src.startsWith('http://') && !src.startsWith('https://') &&
|
|
||||||
!src.startsWith('//') && !src.startsWith('/api/media/') &&
|
|
||||||
!src.startsWith('data:')) {
|
|
||||||
// URL-encode path segments to handle spaces and special characters
|
// URL-encode path segments to handle spaces and special characters
|
||||||
const encodedPath = src.split('/').map(segment => {
|
const encodedPath = src.split('/').map(segment => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -4132,25 +4135,42 @@ function noteApp() {
|
||||||
const altText = img.getAttribute('alt') || src.split('/').pop().replace(/\.[^/.]+$/, '');
|
const altText = img.getAttribute('alt') || src.split('/').pop().replace(/\.[^/.]+$/, '');
|
||||||
const safeAlt = altText.replace(/"/g, '"');
|
const safeAlt = altText.replace(/"/g, '"');
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
// Only convert LOCAL media to embedded elements (security)
|
||||||
const wrapper = document.createElement('div');
|
// External non-image media gets styled links instead
|
||||||
wrapper.className = 'media-embed media-audio';
|
if (isLocal || src.startsWith('/api/media/')) {
|
||||||
wrapper.innerHTML = `<audio controls preload="none" src="${src}" title="${safeAlt}"></audio><span class="media-caption">${safeAlt}</span>`;
|
if (mediaType === 'audio') {
|
||||||
img.replaceWith(wrapper);
|
const wrapper = document.createElement('div');
|
||||||
return;
|
wrapper.className = 'media-embed media-audio';
|
||||||
} else if (mediaType === 'video') {
|
wrapper.innerHTML = `<audio controls preload="none" src="${src}" title="${safeAlt}"></audio><span class="media-caption">${safeAlt}</span>`;
|
||||||
const wrapper = document.createElement('div');
|
img.replaceWith(wrapper);
|
||||||
wrapper.className = 'media-embed media-video';
|
return;
|
||||||
wrapper.innerHTML = `<video controls preload="none" poster="" src="${src}" title="${safeAlt}"></video>`;
|
} else if (mediaType === 'video') {
|
||||||
img.replaceWith(wrapper);
|
const wrapper = document.createElement('div');
|
||||||
return;
|
wrapper.className = 'media-embed media-video';
|
||||||
} else if (mediaType === 'document') {
|
wrapper.innerHTML = `<video controls preload="none" poster="" src="${src}" title="${safeAlt}"></video>`;
|
||||||
const wrapper = document.createElement('div');
|
img.replaceWith(wrapper);
|
||||||
wrapper.className = 'media-embed media-pdf';
|
return;
|
||||||
wrapper.innerHTML = `<iframe src="${src}" title="${safeAlt}" loading="lazy"></iframe>`;
|
} else if (mediaType === 'document') {
|
||||||
img.replaceWith(wrapper);
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
// External audio/video: leave as broken image for security
|
||||||
}
|
}
|
||||||
|
|
||||||
// For regular images, set title attribute
|
// For regular images, set title attribute
|
||||||
|
|
|
||||||
|
|
@ -484,17 +484,50 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PDF embeds */
|
/* PDF embeds (iframe) */
|
||||||
.markdown-preview .media-pdf {
|
.markdown-preview .media-pdf {
|
||||||
border: 1px solid var(--border-primary);
|
border: 1px solid var(--border-primary);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--bg-secondary);
|
||||||
}
|
}
|
||||||
.markdown-preview .media-pdf iframe {
|
.markdown-preview .media-pdf iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 600px;
|
height: 600px;
|
||||||
border: none;
|
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 */
|
/* Task lists */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue