Fix note unfiling and relative image paths (#222)

This commit is contained in:
Yuri Karamian
2026-07-20 02:03:15 +02:00
parent 28ef37f354
commit fd55c00015
3 changed files with 63 additions and 11 deletions
+14 -11
View File
@@ -48,6 +48,7 @@
import { WrapSelectedText } from '$lib/editor/extensions/wrapSelectedText'; import { WrapSelectedText } from '$lib/editor/extensions/wrapSelectedText';
import { calloutGroup, calloutIcon, calloutLabel, CALLOUT_MENU, transformCalloutBlockquotes, serializeCallout } from '$lib/editor/callouts'; import { calloutGroup, calloutIcon, calloutLabel, CALLOUT_MENU, transformCalloutBlockquotes, serializeCallout } from '$lib/editor/callouts';
import { wrapTextareaSelection } from '$lib/editor/source/selectionPairs'; import { wrapTextareaSelection } from '$lib/editor/source/selectionPairs';
import { relativePath } from '$lib/utils/paths';
import GraphView from './GraphView.svelte'; import GraphView from './GraphView.svelte';
import TagSuggestInput from './TagSuggestInput.svelte'; import TagSuggestInput from './TagSuggestInput.svelte';
import { isMobile, isAndroid } from '$lib/platform'; import { isMobile, isAndroid } from '$lib/platform';
@@ -3697,7 +3698,7 @@
} }
} }
// Convert asset:// URLs back to relative paths for saving // Convert asset:// URLs back to relative paths for saving
if (!src.startsWith('asset:') && !src.startsWith('http://asset.localhost')) return src; if (!src.startsWith('asset:') && !src.startsWith('http://asset.localhost') && !src.startsWith('https://asset.localhost')) return src;
let absPath = ''; let absPath = '';
try { try {
const url = new URL(src); const url = new URL(src);
@@ -3707,18 +3708,20 @@
} }
// Clean up any leading double/triple slashes (URL parsing artifact) // Clean up any leading double/triple slashes (URL parsing artifact)
absPath = absPath.replace(/^\/{2,}/, '/'); absPath = absPath.replace(/^\/{2,}/, '/');
// Make relative to note directory (matches how resolveImageSrc works) absPath = absPath.replace(/^\/([A-Za-z]:\/)/, '$1').replace(/\\/g, '/');
// Preserve note-relative paths, including parent-directory segments.
const notePath = $activeNotePath; const notePath = $activeNotePath;
if (notePath) { const vaultRoot = $appConfig?.active_vault?.replace(/\\/g, '/').replace(/\/$/, '');
const noteDir = notePath.substring(0, notePath.lastIndexOf('/'));
if (absPath.startsWith(noteDir + '/')) {
return absPath.substring(noteDir.length + 1);
}
}
// Fallback: make relative to vault root
const vaultRoot = $appConfig?.active_vault;
if (vaultRoot && absPath.startsWith(vaultRoot + '/')) { if (vaultRoot && absPath.startsWith(vaultRoot + '/')) {
return absPath.substring(vaultRoot.length + 1); const vaultRelative = absPath.substring(vaultRoot.length + 1);
// HelixNotes-managed attachments have explicit vault-root semantics.
if (vaultRelative.startsWith('.helixnotes/')) return vaultRelative;
if (notePath) {
const normalizedNotePath = notePath.replace(/\\/g, '/');
const noteDir = normalizedNotePath.substring(0, normalizedNotePath.lastIndexOf('/'));
return relativePath(noteDir, absPath);
}
return vaultRelative;
} }
return absPath; return absPath;
} }
+21
View File
@@ -787,6 +787,19 @@
} }
} }
function isFiled(note: NoteEntry): boolean {
const vaultRoot = $appConfig?.active_vault?.replace(/\\/g, '/').replace(/\/$/, '');
if (!vaultRoot) return false;
const normalizedPath = note.path.replace(/\\/g, '/');
const noteDir = normalizedPath.substring(0, normalizedPath.lastIndexOf('/'));
return noteDir !== vaultRoot;
}
function unfileNote(note: NoteEntry) {
const vaultRoot = $appConfig?.active_vault;
if (vaultRoot) handleMoveNote(note, vaultRoot);
}
function clampMenu(x: number, y: number, menuWidth = 220, menuHeight = 400): { x: number; y: number } { function clampMenu(x: number, y: number, menuWidth = 220, menuHeight = 400): { x: number; y: number } {
if (x + menuWidth > window.innerWidth) x = window.innerWidth - menuWidth - 8; if (x + menuWidth > window.innerWidth) x = window.innerWidth - menuWidth - 8;
if (y + menuHeight > window.innerHeight) y = window.innerHeight - menuHeight - 8; if (y + menuHeight > window.innerHeight) y = window.innerHeight - menuHeight - 8;
@@ -1535,6 +1548,14 @@
</svg> </svg>
Move to... Move to...
</button> </button>
{#if isFiled(contextMenu.note)}
<button onclick={() => unfileNote(contextMenu!.note)}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/><path d="M9 13h6"/>
</svg>
Unfile Note
</button>
{/if}
{#if !isMobile} {#if !isMobile}
<button onclick={async () => { const n = contextMenu!.note; contextMenu = null; await selectNote(n); setTimeout(() => window.print(), 300); }}> <button onclick={async () => { const n = contextMenu!.note; contextMenu = null; await selectNote(n); setTimeout(() => window.print(), 300); }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+28
View File
@@ -0,0 +1,28 @@
function pathParts(path: string): string[] {
return path.replace(/\\/g, '/').split('/').filter(Boolean);
}
function pathRoot(path: string): string {
const normalized = path.replace(/\\/g, '/');
const drive = normalized.match(/^([A-Za-z]:)\//);
if (drive) return drive[1].toLowerCase();
return normalized.startsWith('/') ? '/' : '';
}
export function relativePath(fromDirectory: string, targetPath: string): string {
const from = pathParts(fromDirectory);
const target = pathParts(targetPath);
if (pathRoot(fromDirectory) !== pathRoot(targetPath)) return targetPath.replace(/\\/g, '/');
const windowsPath = /^[A-Za-z]:[\\/]/.test(fromDirectory) || /^[A-Za-z]:[\\/]/.test(targetPath);
const equal = windowsPath
? (left: string, right: string) => left.toLowerCase() === right.toLowerCase()
: (left: string, right: string) => left === right;
let common = 0;
while (common < from.length && common < target.length && equal(from[common], target[common])) {
common++;
}
const result = [...Array(from.length - common).fill('..'), ...target.slice(common)];
return result.join('/') || '.';
}