@@ -9666,7 +9666,7 @@ void MainWindow::onNavigationWidgetHeadingRenamed(int position, const QString &o
96669666 QString newBlockText = headingPrefix + " " + newText;
96679667
96689668 // Check for backlinks that reference this heading and ask user if they want to update them
9669- // We need to do this before we update the text in the document, otherwise the QMessageBox
9669+ // We need to do this BEFORE we update the text in the document, otherwise the QMessageBox
96709670 // dialog that confirms this change will crash the app
96719671 updateBacklinksAfterHeadingRename (oldText, newText);
96729672
@@ -9681,6 +9681,7 @@ void MainWindow::onNavigationWidgetHeadingRenamed(int position, const QString &o
96819681/* *
96829682 * Updates backlinks after a heading has been renamed
96839683 * Scans all notes that link to the current note and updates heading references
9684+ * Also checks the current note itself for TOC links (e.g., [text](#old-heading))
96849685 */
96859686void MainWindow::updateBacklinksAfterHeadingRename (const QString &oldHeading,
96869687 const QString &newHeading) {
@@ -9689,16 +9690,23 @@ void MainWindow::updateBacklinksAfterHeadingRename(const QString &oldHeading,
96899690 QString oldFragment = QString (QUrl::toPercentEncoding (oldHeading));
96909691 QString newFragment = QString (QUrl::toPercentEncoding (newHeading));
96919692
9692- // Find all notes that have backlinks to the current note
9693- QVector<int > backlinkNoteIds = this ->currentNote .findBacklinkedNoteIds ();
9694-
9695- if (backlinkNoteIds.isEmpty ()) {
9696- return ;
9697- }
9693+ // Get the relative file path of the current note for precise link matching
9694+ QString currentNoteRelativePath = this ->currentNote .relativeNoteFilePath ();
96989695
96999696 // Collect notes that actually contain links with the old heading fragment
97009697 QVector<Note> notesWithHeadingLinks;
97019698
9699+ // First, check the current note itself for links to its own headings (e.g., TOC)
9700+ // NOTE: We check the current text from the UI since this function is called BEFORE
9701+ // the heading is actually updated in the document (to avoid QMessageBox crashes)
9702+ QString currentNoteText = activeNoteTextEdit ()->toPlainText ();
9703+ if (currentNoteText.contains (QStringLiteral (" #" ) + oldFragment)) {
9704+ notesWithHeadingLinks.append (this ->currentNote );
9705+ }
9706+
9707+ // Find all notes that have backlinks to the current note
9708+ QVector<int > backlinkNoteIds = this ->currentNote .findBacklinkedNoteIds ();
9709+
97029710 for (int noteId : backlinkNoteIds) {
97039711 Note backlinkNote = Note::fetch (noteId);
97049712 QString noteText = backlinkNote.getNoteText ();
@@ -9734,14 +9742,75 @@ void MainWindow::updateBacklinksAfterHeadingRename(const QString &oldHeading,
97349742
97359743 for (Note &backlinkNote : notesWithHeadingLinks) {
97369744 QString noteText = backlinkNote.getNoteText ();
9737-
9738- // Replace all occurrences of the old heading fragment with the new one
9739- QString oldFragmentPattern = QStringLiteral (" #" ) + oldFragment;
9740- QString newFragmentPattern = QStringLiteral (" #" ) + newFragment;
9741-
9742- if (noteText.contains (oldFragmentPattern)) {
9743- noteText.replace (oldFragmentPattern, newFragmentPattern);
9744- backlinkNote.storeNewText (std::move (noteText));
9745+ QString originalNoteText = noteText;
9746+
9747+ // Build patterns to match the full link including the note path
9748+ // We need to handle both URL-encoded and unencoded paths since links can be either
9749+ // For example: "My Note.md" or "My%20Note.md"
9750+
9751+ // Get both encoded and unencoded versions of the path
9752+ QString currentNoteRelativePathEncoded =
9753+ QString (QUrl::toPercentEncoding (currentNoteRelativePath));
9754+
9755+ // Escape both versions for use in regex (dots, slashes, etc.)
9756+ QString escapedRelativePath = QRegularExpression::escape (currentNoteRelativePath);
9757+ QString escapedRelativePathEncoded =
9758+ QRegularExpression::escape (currentNoteRelativePathEncoded);
9759+ QString oldFragmentEscaped = QRegularExpression::escape (oldFragment);
9760+
9761+ // Pattern 1: Markdown links with unencoded path [text](path#old-heading)
9762+ QRegularExpression markdownLinkPattern (QStringLiteral (R"( \[([^\]]*)\]\(%1#%2\))" )
9763+ .arg (escapedRelativePath, oldFragmentEscaped));
9764+ QString markdownLinkReplacement =
9765+ QStringLiteral (R"( [\1](%1#%2))" ).arg (currentNoteRelativePathEncoded, newFragment);
9766+ noteText.replace (markdownLinkPattern, markdownLinkReplacement);
9767+
9768+ // Pattern 2: Markdown links with encoded path [text](path%20with%20spaces#old-heading)
9769+ QRegularExpression markdownLinkPatternEncoded (
9770+ QStringLiteral (R"( \[([^\]]*)\]\(%1#%2\))" )
9771+ .arg (escapedRelativePathEncoded, oldFragmentEscaped));
9772+ noteText.replace (markdownLinkPatternEncoded, markdownLinkReplacement);
9773+
9774+ // Pattern 3: Autolinks with unencoded path <path#old-heading>
9775+ QRegularExpression autolinkPattern (
9776+ QStringLiteral (R"( <(%1#%2)>)" ).arg (escapedRelativePath, oldFragmentEscaped));
9777+ QString autolinkReplacement =
9778+ QStringLiteral (R"( <%1#%2>)" ).arg (currentNoteRelativePathEncoded, newFragment);
9779+ noteText.replace (autolinkPattern, autolinkReplacement);
9780+
9781+ // Pattern 4: Autolinks with encoded path <path%20with%20spaces#old-heading>
9782+ QRegularExpression autolinkPatternEncoded (
9783+ QStringLiteral (R"( <(%1#%2)>)" ).arg (escapedRelativePathEncoded, oldFragmentEscaped));
9784+ noteText.replace (autolinkPatternEncoded, autolinkReplacement);
9785+
9786+ // Pattern 5: Bare markdown links without path (same note references)
9787+ // For links within the same note: [text](#old-heading)
9788+ if (backlinkNote.getId () == this ->currentNote .getId ()) {
9789+ QRegularExpression sameNoteLinkPattern (
9790+ QStringLiteral (R"( \[([^\]]*)\]\(#%1\))" ).arg (oldFragmentEscaped));
9791+ QString sameNoteLinkReplacement = QStringLiteral (R"( [\1](#%1))" ).arg (newFragment);
9792+ noteText.replace (sameNoteLinkPattern, sameNoteLinkReplacement);
9793+
9794+ // Also handle autolinks within the same note: <#old-heading>
9795+ QRegularExpression sameNoteAutolinkPattern (
9796+ QStringLiteral (R"( <#%1>)" ).arg (oldFragmentEscaped));
9797+ QString sameNoteAutolinkReplacement = QStringLiteral (R"( <#%1>)" ).arg (newFragment);
9798+ noteText.replace (sameNoteAutolinkPattern, sameNoteAutolinkReplacement);
9799+ }
9800+
9801+ // Only update the note if changes were made
9802+ if (noteText != originalNoteText) {
9803+ // If this is the current note, update the text edit directly
9804+ // (since the heading rename hasn't been applied yet in the document)
9805+ if (backlinkNote.getId () == this ->currentNote .getId ()) {
9806+ // Update the text in the editor
9807+ activeNoteTextEdit ()->setPlainText (noteText);
9808+ // Store the changes
9809+ this ->currentNote .storeNewText (std::move (noteText));
9810+ } else {
9811+ // For other notes, just store the text to disk
9812+ backlinkNote.storeNewText (std::move (noteText));
9813+ }
97459814 updatedCount++;
97469815 }
97479816 }
0 commit comments