Skip to content

Commit a83e1c1

Browse files
committed
#3459 navigationwidget: allow link refactoring
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 9604456 commit a83e1c1

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
- You can now double-click, press `F2`, or right-click and select "Rename heading"
88
on any heading in the "Headings" tab of the Navigation panel to rename it
99
- The heading text in your note will update automatically while preserving the heading level
10-
- This provides a quick way to reorganize your note structure without manually editing the text
10+
- When you rename a heading, QOwnNotes will scan all notes that link to the current note and prompt you to update any links that reference the old heading
11+
- This ensures that note links with heading fragments (e.g., `[Link](note.md#old-heading)`) remain valid after renaming
12+
- This provides a quick way to reorganize your note structure without manually editing the text or breaking backlinks
1113

1214
## 26.2.0
1315

src/mainwindow.cpp

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9639,8 +9639,6 @@ void MainWindow::onNavigationWidgetPositionClicked(int position) {
96399639
*/
96409640
void MainWindow::onNavigationWidgetHeadingRenamed(int position, const QString &oldText,
96419641
const QString &newText) {
9642-
Q_UNUSED(oldText)
9643-
96449642
QOwnNotesMarkdownTextEdit *textEdit = activeNoteTextEdit();
96459643
QTextDocument *doc = textEdit->document();
96469644

@@ -9673,6 +9671,92 @@ void MainWindow::onNavigationWidgetHeadingRenamed(int position, const QString &o
96739671

96749672
// Reparse the navigation to reflect the change
96759673
// This will be triggered automatically by the text change event
9674+
9675+
// Check for backlinks that reference this heading and ask user if they want to update them
9676+
updateBacklinksAfterHeadingRename(oldText, newText);
9677+
}
9678+
9679+
/**
9680+
* Updates backlinks after a heading has been renamed
9681+
* Scans all notes that link to the current note and updates heading references
9682+
*/
9683+
void MainWindow::updateBacklinksAfterHeadingRename(const QString &oldHeading,
9684+
const QString &newHeading) {
9685+
Note currentNote = this->currentNote;
9686+
9687+
// Generate the old and new heading fragments (URL encoded)
9688+
QString oldFragment = Note::generateTextForLink(oldHeading);
9689+
QString newFragment = Note::generateTextForLink(newHeading);
9690+
9691+
// Find all notes that have backlinks to the current note
9692+
QVector<int> backlinkNoteIds = currentNote.findBacklinkedNoteIds();
9693+
9694+
if (backlinkNoteIds.isEmpty()) {
9695+
return;
9696+
}
9697+
9698+
// Collect notes that actually contain links with the old heading fragment
9699+
QVector<Note> notesWithHeadingLinks;
9700+
9701+
for (int noteId : backlinkNoteIds) {
9702+
Note backlinkNote = Note::fetch(noteId);
9703+
QString noteText = backlinkNote.getNoteText();
9704+
9705+
// Check if the note contains a link with the old heading fragment
9706+
// Links can be in the form:
9707+
// - [text](relative/path.md#old-heading)
9708+
// - <relative/path.md#old-heading>
9709+
// - [text](note://note-name#old-heading)
9710+
// - <note://note-name#old-heading>
9711+
9712+
if (noteText.contains(QStringLiteral("#") + oldFragment)) {
9713+
notesWithHeadingLinks.append(backlinkNote);
9714+
}
9715+
}
9716+
9717+
if (notesWithHeadingLinks.isEmpty()) {
9718+
return;
9719+
}
9720+
9721+
// Ask the user if they want to update the backlinks
9722+
QString message;
9723+
if (notesWithHeadingLinks.size() == 1) {
9724+
message = tr("The heading \"%1\" is referenced in 1 note. "
9725+
"Do you want to update the link to use the new heading \"%2\"?")
9726+
.arg(oldHeading, newHeading);
9727+
} else {
9728+
message = tr("The heading \"%1\" is referenced in %2 notes. "
9729+
"Do you want to update all links to use the new heading \"%3\"?")
9730+
.arg(oldHeading)
9731+
.arg(notesWithHeadingLinks.size())
9732+
.arg(newHeading);
9733+
}
9734+
9735+
if (Utils::Gui::question(this, tr("Update backlinks"), message,
9736+
QStringLiteral("update-heading-backlinks")) == QMessageBox::Yes) {
9737+
// User clicked "Yes"
9738+
int updatedCount = 0;
9739+
9740+
for (Note &backlinkNote : notesWithHeadingLinks) {
9741+
QString noteText = backlinkNote.getNoteText();
9742+
9743+
// Replace all occurrences of the old heading fragment with the new one
9744+
QString oldFragmentPattern = QStringLiteral("#") + oldFragment;
9745+
QString newFragmentPattern = QStringLiteral("#") + newFragment;
9746+
9747+
if (noteText.contains(oldFragmentPattern)) {
9748+
noteText.replace(oldFragmentPattern, newFragmentPattern);
9749+
backlinkNote.storeNewText(std::move(noteText));
9750+
backlinkNote.storeNoteTextFileToDisk();
9751+
updatedCount++;
9752+
}
9753+
}
9754+
9755+
// Show a confirmation message
9756+
if (updatedCount > 0) {
9757+
showStatusBarMessage(tr("Updated heading links in %n note(s)", "", updatedCount), 5000);
9758+
}
9759+
}
96769760
}
96779761

96789762
/**

src/mainwindow.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ class MainWindow : public QMainWindow {
275275
void onNavigationWidgetHeadingRenamed(int position, const QString &oldText,
276276
const QString &newText);
277277

278+
void updateBacklinksAfterHeadingRename(const QString &oldHeading, const QString &newHeading);
279+
278280
private slots:
279281

280282
void on_noteTextEdit_textChanged();

0 commit comments

Comments
 (0)