mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-25 08:15:55 +02:00
Fix note unfiling and relative image paths (#222)
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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('/') || '.';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user