Skip to content

Conversation

chhoumann
Copy link
Owner

@chhoumann chhoumann commented Oct 12, 2025

Description

Fixes #363

QuickAdd now properly respects Obsidian's "New link format" setting (relative/shortest/markdown vs wiki) when appending links and generating links in all contexts, including during user input via file suggester.

Root Cause

The issue was caused by passing an empty string or incorrect context as the sourcePath parameter to generateMarkdownLink():

this.app.fileManager.generateMarkdownLink(file, "")

When sourcePath is empty or incorrect, Obsidian cannot calculate the relative path between source and target files, so it defaults to generating vault-absolute paths instead of respecting the user's configured link format.

Changes

Core Link Generation Fixes

  1. Created reusable link insertion utility (insertFileLinkToActiveView)

    • Centralizes link generation with proper source context
    • Validates MarkdownView availability before insertion
    • Properly handles requireActiveFile option with correct error semantics
    • Returns false gracefully when optional and no view available
    • Located in utilityObsidian.ts
  2. Fixed append-link in Capture and Template engines

    • Pass active file's path to generateMarkdownLink()
    • Added proper error handling for missing active file/view
    • Ensures links respect user's format settings when appending after capture/template
  3. Fixed {{LINKCURRENT}} in captures

    • Override getCurrentFileLink() in CaptureChoiceFormatter
    • Use capture destination path as source context
    • Added sourcePath property to track destination before file creation
    • Added setDestinationFile() and setDestinationSourcePath() methods
    • Clear this.file when setting sourcePath to prevent stale state from previous captures
    • Ensures proper relative links even when destination doesn't exist yet
  4. Fixed file suggester link generation (comprehensive refactor)

    • Added optional sourcePath parameter to FileSuggester constructor
    • Added normalizeFolderPath() helper to handle "/" vs "" and trailing slashes consistently
    • Use fileManager.getNewFileParent() instead of manual string parsing for robust path handling
    • Added resolveRelative() helper for multi-level ../ navigation
    • Updated getSuggestions() to use source folder context even when file doesn't exist
    • Clear currentFile bias when using override to avoid unrelated heading/block suggestions
    • Updated getRelativePathSuggestions() to handle multiple ../ segments correctly
    • Normalize folder paths in attachment suggestions and all filters
    • Added PromptWithContext static methods to GenericInputPrompt and GenericWideInputPrompt
    • Plumbed destination path through formatter → prompt → suggester chain
    • Ensures [[ link insertion uses correct relative paths during captures

Edge Case Handling

  1. Prevented stale state contamination

    • Clear this.file when setting sourcePath to avoid previous capture values
    • Ensures each capture operation has clean state
  2. Fixed newLine placement

    • Check line.length === 0 instead of endsWith('\n')
    • Prevents extra blank lines on empty lines
  3. Improved error handling

    • Distinguish between no active file vs no MarkdownView
    • Return false instead of throwing when requireActiveFile is false
    • Preserve error-throwing behavior when requireActiveFile is true
  4. Robust path handling in file suggester

    • Handles root folder ("/" vs "") consistently
    • Works with non-existent destination files/folders
    • Supports multi-level relative navigation (../../)
    • Prevents path traversal above vault root
    • Normalizes all folder comparisons

Implementation Details

Architecture

  • FileSuggester:
    • Accepts optional sourcePath override via options object
    • Uses Obsidian's fileManager.getNewFileParent() for robust folder derivation
    • Normalizes all folder paths before comparison
    • Handles relative path navigation via string manipulation when folders don't exist
  • CaptureChoiceFormatter:
    • Tracks destination path in sourcePath property
    • Provides getLinkSourcePath() for prompt context
  • CompleteFormatter:
    • Added getLinkSourcePath() protected method (returns null by default)
    • Uses PromptWithContext when source path is available
  • GenericInputPrompt/GenericWideInputPrompt:
    • Added PromptWithContext() static methods
    • Pass linkSourcePath to FileSuggester constructor
  • Link insertion:
    • Centralized in insertFileLinkToActiveView() utility function

Flow for Captures

  1. Existing file: Engine calls setDestinationFile(file) before first format pass
  2. New file: Engine calls setDestinationSourcePath(filePath) before first format pass, clears this.file
  3. During prompts: Formatter passes sourcePath to prompts via PromptWithContext()
  4. File suggester:
    • Receives sourcePath in constructor options
    • Derives folder using getNewFileParent() (works even if file doesn't exist)
    • Uses folder for search context and relative path navigation
    • Generates links relative to destination via getSourcePath()
  5. Link insertion: Uses active file path for proper relative link generation

Path Normalization Strategy

  • All folder paths normalized to remove trailing slashes and convert "/" to ""
  • Root folder always represented as empty string internally
  • Obsidian APIs used where possible (getNewFileParent) instead of manual parsing
  • Multi-level ../ handled with safe bounds checking

Testing

  • ✅ Build passes with bun run build-with-lint
  • ✅ No TypeScript errors
  • ✅ No ESLint violations
  • ✅ Manual testing completed for all scenarios

Manual Testing Scenarios

  1. Link format settings

    • Set "New link format" to "Relative" → links are relative to destination
    • Set to "Shortest path" → links use shortest path
    • Set to "Markdown" → uses markdown links instead of wiki-links
  2. Capture scenarios

    • Existing file capture → proper relative links
    • New file creation → proper relative links (even before file exists)
    • {{LINKCURRENT}} in captures → links to active file from destination context
    • File suggester during capture → links relative to destination folder
    • {{VALUE}} prompt → file suggester uses destination context
  3. File suggester relative paths

    • ./ shows files in destination folder
    • ../ navigates up one level from destination
    • ../../ navigates up two levels (multi-level support)
    • Works when destination file doesn't exist yet
    • Works when destination folder doesn't exist yet
  4. Edge cases

    • No active file with requireActiveFile: true → throws error
    • No active file with requireActiveFile: false → skips link insertion
    • No MarkdownView → proper error handling
    • Multiple captures in sequence → no stale state contamination
    • Root folder files → handled correctly
    • Files without folders → handled correctly
  5. Cross-folder captures

    • Active: Projects/ClientA/file.md
    • Destination: Journal/Inbox.md
    • Link to: Resources/Templates/Meeting.md
    • Result: [[../Resources/Templates/Meeting]] (relative to destination)
    • File suggester shows Resources/ when typing ../Res
  6. New file creation flow

    • Active: Projects/Ideas/Brainstorm.md
    • Destination: Daily/2025-01-15.md (doesn't exist)
    • File suggester context is Daily/ folder (not Projects/Ideas/)
    • ../Resources/... navigation works correctly
    • Links generated relative to Daily/, not active file

Impact

  • Users who configure relative/shortest path links will now see correct link formats
  • {{LINKCURRENT}} in captures now generates proper relative links to active file
  • File suggester respects destination context instead of active file context
  • File suggester works correctly even when destination file doesn't exist yet
  • Relative path navigation (../) works consistently with multi-level support
  • No breaking changes - maintains backward compatibility
  • Improves code quality by reducing duplication and centralizing link logic
  • Robust path handling prevents edge case bugs

Files Changed

  • src/utilityObsidian.ts - Added link insertion utility and fixed newLine behavior
  • src/engine/CaptureChoiceEngine.ts - Set destination before formatting, use utility
  • src/engine/TemplateChoiceEngine.ts - Use link insertion utility
  • src/formatters/captureChoiceFormatter.ts - Track destination path, override link generation, provide context to prompts
  • src/formatters/completeFormatter.ts - Add link source path support, use context in prompts
  • src/gui/suggesters/fileSuggester.ts - Accept source path override, robust path handling, normalization
  • src/gui/GenericInputPrompt/GenericInputPrompt.ts - Add PromptWithContext method
  • src/gui/GenericWideInputPrompt/GenericWideInputPrompt.ts - Add PromptWithContext method

- Pass active file path to generateMarkdownLink instead of empty string
- Add warning when no active file and requireActiveFile is true
- Fixes #363
- Add insertFileLinkToActiveView helper in utilityObsidian.ts
- Eliminates code duplication across CaptureChoiceEngine and TemplateChoiceEngine
- Centralizes link generation with proper source path handling
Copy link

vercel bot commented Oct 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
quickadd Ready Ready Preview Oct 12, 2025 9:06am

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 potential issue that should be addressed.

- Throw error instead of logging warning when no active file exists and requireActiveFile is true
- Maintains existing failure semantics that callers depend on
- Ensures users get explicit feedback when append-link action fails
- Override getCurrentFileLink() in CaptureChoiceFormatter to use destination file path
- Ensures relative links work correctly across different folders
- Reverts completeFormatter.ts to original behavior (empty string)
- Example: Active file Projects/Idea.md captured to Journal/Inbox.md now generates [[Projects/Idea]] instead of [[Idea]]
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

- Change fallback from currentFile.path to empty string when this.file is null
- First format pass (formatContentOnly) happens before this.file is set
- Empty string produces vault-absolute path, which is correct for first pass
- Second pass (formatContentWithFile) sets this.file and generates proper relative link
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 potential issue that should be addressed.

- Add setDestinationFile() method to CaptureChoiceFormatter
- Call setDestinationFile() in CaptureChoiceEngine before formatContentOnly()
- Ensures {{LINKCURRENT}} has correct context in first pass for proper relative links
- Prevents double processing by having the right context from the start
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 potential issue that should be addressed.

- Add sourcePath property to CaptureChoiceFormatter for pre-file-creation context
- Update getCurrentFileLink to prefer sourcePath over file.path
- Call setDestinationSourcePath before formatContentOnly in new file flow
- Fix insertFileLinkToActiveView to return false (not throw) when no active file and requireActiveFile is false
- Fix newLine placement to check line.length === 0 instead of endsWith('\n')
- Ensures proper relative links even when destination file doesn't exist yet
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

Comment 1: Clear this.file when setting sourcePath to prevent stale values from previous captures
Comment 2: Check for MarkdownView availability before insertion to ensure accurate return value
- Pass requireActiveView: false to insertLinkWithPlacement since we've already validated the view exists
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

- Add sourcePath override option to FileSuggester constructor
- Update getSuggestions and getRelativePathSuggestions to use source context
- Add getLinkSourcePath() protected method to formatters
- CaptureChoiceFormatter overrides to return destination path
- Add PromptWithContext static method to GenericInputPrompt
- CompleteFormatter uses PromptWithContext when prompting for field values
- Ensures links inserted via [[ suggester are relative to capture destination, not active file
- Fixes issue where links were relative to wrong folder during captures
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 potential issue that should be addressed.

- Add PromptWithContext to GenericWideInputPrompt (matching GenericInputPrompt)
- Update promptForValue to use PromptWithContext when linkSourcePath is available
- Ensures file suggester during capture input uses destination path for relative links
- Fixes issue where [[file]] inserted during capture was not relative to destination
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

- Add getSourceFolder() helper that extracts folder from path string
- Use source folder for search context even when destination file doesn't exist yet
- Handle relative path navigation (../) using string manipulation
- Ensures suggester context matches link generation context during new file captures
- Fixes inconsistent behavior where suggestions used active file folder but links used destination folder
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

- Add normalizeFolderPath() to handle / vs empty string and trailing slashes consistently
- Use fileManager.getNewFileParent() instead of manual string parsing
- Add resolveRelative() to handle multiple ../ segments correctly
- Clear currentFile bias when sourcePathOverride is set to avoid unrelated heading/block suggestions
- Normalize folder paths in attachment suggestions and filters
- Fixes edge cases: root folder, nested relative paths, non-existent destination folders
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 potential issue that should be addressed.

- Stop processing ../ when already at root folder (empty string)
- Preserve ../ in query instead of silently stripping it
- User gets no results, making it clear they can't navigate above root
- More predictable and easier to understand behavior
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found in the current changes.

@chhoumann chhoumann merged commit 9368ef0 into master Oct 12, 2025
4 checks passed
@chhoumann chhoumann deleted the fix/issue-363-link-format-setting branch October 12, 2025 09:11
github-actions bot pushed a commit that referenced this pull request Oct 16, 2025
# [2.6.0](2.5.0...2.6.0) (2025-10-16)

### Bug Fixes

* correct broken documentation link ([#948](#948)) ([1ac3f2b](1ac3f2b))
* improve AI model parameter handling and validation ([#949](#949)) ([dbc74a7](dbc74a7)), closes [#674](#674)
* initialize user script default values before execution ([#956](#956)) ([3017875](3017875)), closes [#262](#262)
* keep custom suggestions last ([#965](#965)) ([3ca5eef](3ca5eef))
* nested choice reordering not persisting ([#953](#953)) ([acee380](acee380)), closes [#142](#142)
* respect macro member access ([#963](#963)) ([a0b77fe](a0b77fe))
* respect Obsidian 'New link format' setting when appending links ([#958](#958)) ([9368ef0](9368ef0)), closes [#363](#363)
* respect Obsidian's default location for new notes ([#951](#951)) ([c096a2d](c096a2d)), closes [#613](#613)
* restore TITLE behavior when FIELD:title is used ([#967](#967)) ([d89bc7d](d89bc7d)), closes [#966](#966)
* standardize multi-line input font size across themes ([#955](#955)) ([bda1dad](bda1dad)), closes [#270](#270)

### Features

* add {{FILENAMECURRENT}} format syntax ([#954](#954)) ([f66ada5](f66ada5)), closes [#499](#499)
* add |custom modifier for VALUE syntax to allow custom input ([207b0ba](207b0ba)), closes [#461](#461)
* add conditional macro command support ([#959](#959)) ([4e0fc1e](4e0fc1e))
* add toggle for input cancellation notices ([aeb0002](aeb0002))
* improved input validation and autocomplete with full-width layouts ([0b66d43](0b66d43)), closes [#625](#625)
* package export/import workflow ([#97](#97)) ([#961](#961)) ([80da44b](80da44b))
* pre-populate default values in input fields ([db76b9d](db76b9d))
Copy link

🎉 This PR is included in version 2.6.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] QuickAdd does not respect "New link format" Setting when appending links

1 participant