Advanced Rename and Delete Handler

by Michael Naumov
5
4
3
2
1
Score: 58/100

Description

The Advanced Rename and Delete Handler plugin takes over rename and delete handling across a vault so links, attachment files and attachment folders stay in sync when notes move or change names. It updates link display text when that text still matches the old file name, leaves custom link text alone and can remove attachments or empty folders left behind after deletion when you enable those options. Shared attachments are never deleted with one note and can be reassigned to another note, with a prompt when priorities do not settle the owner. You can treat endings like .excalidraw.md as attachments instead of notes, limit handling with include and exclude paths and migrate or read settings through a versioned cross plugin API.

Reviews

No reviews yet.

Stats

12
stars
25,341
downloads
0
forks
52
days
0
days
0
days
0
total PRs
0
open PRs
0
closed PRs
0
merged PRs
5
total issues
0
open issues
5
closed issues
241
commits

RequirementsExperimental

  • BRAT plugin

Latest Version

7 hours ago

Changelog

  • build(deps): clear the brace-expansion, sharp and fast-uri advisories
  • test(rename-link-update): wait for the metadata cache to re-parse the rewritten link
  • test(delete-empty-folders): wait for the sweep to pass the fixture before teardown
  • feat(settings)!: match path entries case-insensitively, as globs or flagged regular expressions
  • docs: mark plugin names as code
  • test(android): rely on the harness's write-loss guard
  • build(deps): bump @mnaoumov/obsidian-lib to 4.3.0

Full Changelog: https://github.com/mnaoumov/obsidian-advanced-rename-and-delete-handler/compare/2.3.0...3.0.0

README file from

Github

Advanced Rename and Delete Handler

Buy Me a Coffee GitHub release GitHub downloads Coverage: 100%

Obsidian updates the links pointing at a note when you rename it, and stops there. The images you pasted into that note stay behind under the old name. Deleting the note leaves them behind entirely, referenced by nothing, in a folder named after something that no longer exists.

This plugin takes over renaming and deleting for the whole vault: links follow the note, the files it owns travel with it, and what a deletion leaves behind is cleaned up on terms you choose.

It is the single owner of that behavior in a vault. Several plugins used to carry their own copy of this handler, and two handlers acting on one rename corrupt links between them. Rather than compete, this plugin stands aside while one of them is enabled at a version that still owns its handler: it stays enabled and does nothing, and its settings tab says which plugin it is waiting for and what version to update to. Update that plugin and this one picks the work back up on its own — no restart, and nothing here to switch back on.

Demo vault

The documentation is a demo vault. Every feature has a note that explains what it does and why you would want it, with buttons that perform the rename or the deletion and then print the vault as a tree, so you see the effect rather than read a description of it.

Start reading here — it is plain markdown, so it works on GitHub with nothing installed.

A copy of the vault ships with every release. You can access it via any of the following:

  1. Running the Advanced Rename and Delete Handler: Open demo vault command.
  2. Downloading advanced-rename-and-delete-handler-demo-vault.zip from the Releases. It unzips into a single advanced-rename-and-delete-handler-demo-vault-<version> folder.
  3. Browsing its source in demo-vault/ in this repository.

What it does

  • Nothing, until you turn it on. A fresh install leaves renames and deletions to Obsidian and says so once, with a button to the settings. Other plugins ask you to install this one, and being asked to install something should never change how your vault behaves. 01 Renaming a note
  • It tells you which plugins need it. Its settings tab lists the enabled plugins that declare this one as a dependency, each with a button to that plugin's own settings — the answer to "why is this in my vault" before you remove it.
  • Links follow a renamed or moved note, including the display text of a link that was showing the old file name — while a link somebody gave their own words to is left alone. 01 Renaming a note
  • Attachments travel with the note that owns them, folder and all, when it is renamed or moved to another folder. 01 Renaming a note
  • Deleting a note can clean up after it — the attachments only that note used, and the attachment folders the deletion leaves empty, plus the note's own folder if you ask for it. Off by default, because each option removes something. A Delete empty folders command sweeps the whole vault for the ones already sitting there, left by deletions made before you turned any of this on. 02 Deleting a note
  • An attachment two notes share is never deleted with one of them, and can be moved to the note that still uses it rather than left in a folder belonging to a note that is gone. When several notes could adopt it and your priority list settles nothing, the plugin names them and asks rather than guessing. An attachment that is really a folder — a _files tree, a drawing's sidecar folder — moves whole, when your attachment-location plugin says so. 03 Shared attachments
  • An Excalidraw drawing is treated as an attachment, not a note, whether it is stored as .excalidraw.md or as a plain .md carrying its excalidraw-plugin property, along with any other ending or property: match you add. 04 What counts as a note
  • The plugin can be confined to part of the vault with include and exclude path lists. 05 Limiting the scope

For plugin developers

This plugin publishes an API for the plugins that used to handle renames and deletions themselves and no longer do: they propose the settings they held so a vault keeps behaving the way it did, and go on reading those settings back afterwards. Its types are in api.d.ts at the root of this repository — it imports nothing at all, so copy it into your plugin as it is.

Beside it, api-contract.ts holds the published API's contract, ADVANCED_RENAME_AND_DELETE_HANDLER_API_CONTRACT: for every method, a schema over its arguments and a schema over what it returns. It is the constant this plugin publishes with the API, so copy it next to api.d.ts and pass it as the contract, and both sides check the same definition. Its schemas are Standard Schema objects built with @mnaoumov/typescript-lib/api-contract, which carries the generic half (the contract types, the schema builders and the guard builders, in its api-contract, type-guards and object-utils entry points), so copying it means adding that package (npm install @mnaoumov/typescript-lib, 1.3.0 or later). Apart from that package, the file imports only ./api.d.ts. (It is not named api.ts: TypeScript would read api.d.ts as that file's own declarations.)

This plugin owns those settings, so it owns the dialog too: your proposal is shown next to the current values, and the user approves, edits or declines it row by row. Nothing is written unless they press OK.

The API is published through the @mnaoumov/obsidian-lib cross-plugin registry, which gives you version negotiation, a handle that is revoked when this plugin unloads, and a wait that ends when this plugin loads — rather than a lookup that returns undefined because it ran first. The contract version is 1.2.0 and moves independently of the plugin's own version; ask for '^1'.

If your plugin already uses @mnaoumov/obsidian-lib, watchPluginApi is the whole of it:

import { watchPluginApi } from '@mnaoumov/obsidian-lib/plugin/plugin-api';

import type { AdvancedRenameAndDeleteHandlerApi } from './api.d.ts';

import { ADVANCED_RENAME_AND_DELETE_HANDLER_API_CONTRACT } from './api-contract.ts';

const ref = watchPluginApi<AdvancedRenameAndDeleteHandlerApi>({
  apiVersionRange: '^1',
  app: this.app,
  component: this,
  contract: ADVANCED_RENAME_AND_DELETE_HANDLER_API_CONTRACT,
  pluginId: 'advanced-rename-and-delete-handler'
});

const api = await ref.whenAvailable();
const result = await api.migrateSettings({
  proposedSettings: {
    shouldHandleRenames: true,
    treatAsAttachmentExtensions: ['.excalidraw.md']
  },
  sourcePluginId: this.manifest.id
});

if (result.isApplied) {
  // Record your own one-shot flag, so the offer is not repeated.
}
  • proposedSettings names only what you held. Every member is optional, and a proposal that matches what this plugin already holds is dropped rather than shown, so a user is never asked about a row that would change nothing.
  • result.isApplied is false when the user cancelled and nothing was written — do NOT record your migration as done in that case. It is true when they approved, and also when the proposal changed nothing and no dialog was needed.
  • The call resolves only once the dialog is closed, so awaiting it is how you learn the answer. Two plugins proposing at once are queued, never stacked.
  • A value of the wrong type is refused rather than written, so a mistake surfaces as an error instead of a corrupted data.json.
  • The settings you may propose are the members of HandedOverSettings in api.d.ts, each documented there.

If your plugin does not use @mnaoumov/obsidian-lib — and is not going to — you still need no dependency on anything. The registry, and the two events a plugin announces itself through, are a documented wire protocol reachable with the obsidian module alone; Plugin API protocol is that route written out, and api.d.ts types it just as well, since nothing in that file imports the library either. Look the API up each time you use it rather than keeping it in a field: the raw object does not know when this plugin has unloaded.

Reading the settings back

Handing the settings over does not end your interest in them: the same values drive features of your own that have nothing to do with a rename or a delete. Rather than keeping a shadow copy, read them from here.

All three members are synchronous, so you can call them from a checkCallback(isChecking), a settings row's disabled / visible predicate, or a loop over vault files — none of which can await. Hold the ref, not the API object, and read ref.value each time: it is null before this plugin loads and after it unloads, and correct again on a re-enable.

const ref = watchPluginApi<AdvancedRenameAndDeleteHandlerApi>({
  apiVersionRange: '^1',
  app: this.app,
  component: this,
  contract: ADVANCED_RENAME_AND_DELETE_HANDLER_API_CONTRACT,
  pluginId: 'advanced-rename-and-delete-handler'
});

// Inside a `checkCallback`, a `visible` predicate, or a loop over vault files.
const api = ref.value;
if (api && !api.isPathIgnored(file.path) && !api.isTreatedAsAttachment(file.path)) {
  const { emptyFolderBehavior, notePriorities } = api.getSettings();
  // ...
}
  • getSettings() returns every setting you may propose, as plain data, read live on every call, so there is nothing to invalidate and nothing to subscribe to. The arrays are copies — writing to one changes nothing here.
  • isPathIgnored(path) answers whether this plugin leaves the path alone, per the include and exclude lists.
  • isTreatedAsAttachment(path) answers whether the path names an attachment despite its extension, by an ending such as .excalidraw.md or a property: entry matched against its frontmatter. A Markdown file Obsidian has not indexed yet matches no property: entry.
  • Use the two predicates rather than re-matching the arrays yourself. Every plugin bundles its own copy of @mnaoumov/obsidian-lib, so running the lists through your copy of the matching code is two copies that can drift apart; asking here keeps the matching in one place.
  • These arrived in contract 1.1.0, and shouldDeleteEmptyNoteFolders in getSettings() in 1.2.0. Both are purely additive, so '^1' still gets you them — but a vault running an older release will hand you an API without them, which is what watchPluginApi's shape check is for.

Declaring this plugin as a dependency

If your plugin cannot work without this one, say so rather than failing quietly once a user removes it. With @mnaoumov/obsidian-lib's PluginBase, declare it and your onloadImpl does not run until this plugin is installed, enabled and new enough — your plugin explains what is missing and installs it in one click, and finishes loading the moment it arrives:

protected override getPluginDependencies(): PluginDependency[] {
  return [
    {
      apiVersionRange: '^1.1.0',
      pluginId: 'advanced-rename-and-delete-handler',
      pluginName: 'Advanced Rename and Delete Handler',
      reason: 'Moves each note\'s attachment folder with it when the note is renamed.'
    }
  ];
}

Your plugin then appears in this plugin's settings tab under Plugins that depend on this one. Because this plugin does nothing until something is turned on, asking a user to install it is harmless; hand your old values over with migrateSettings (above) for the switches your users relied on.

Installation

The plugin is available in the official Community Plugins repository.

Beta versions

To install the latest beta release of this plugin (regardless if it is available in the official Community Plugins repository or not), follow these steps:

  1. Ensure you have the BRAT plugin installed and enabled.
  2. Click Install via BRAT.
  3. An Obsidian pop-up window should appear. In the window, click the Add plugin button once and wait a few seconds for the plugin to install.

Debugging

By default, debug messages for this plugin are hidden.

To show them, install Advanced Debug Mode, run Advanced Debug Mode: Open plugin debug messages and tick Advanced Rename and Delete Handler. To flip only this plugin, run Advanced Debug Mode: Toggle debug messages for a plugin... and pick Advanced Rename and Delete Handler.

Without Advanced Debug Mode, run the following command in the DevTools Console:

window.DEBUG.enable('advanced-rename-and-delete-handler');

For more details, refer to the documentation.

Changelog

All notable changes to this project will be documented in the CHANGELOG.

Contributing

Contributions are welcome — see CONTRIBUTING to get set up.

Support

My other Obsidian resources

See my other Obsidian resources.

License

© Michael Naumov

Similar Plugins

info
• Similar plugins are suggested based on the common tags between the plugins.
File Cleaner
5 years ago by Johnson0907
A file cleaner plugin for Obsidian.
Janitor
4 years ago by Gabriele Cannata
Performs various maintenance tasks on the Obsidian vault
Attachment Management
3 years ago by trganda
Attachment Management of Obsidian
Note archiver
3 years ago by thenomadlad
Attachment Manager
3 years ago by chenfeicqq
Attachment folder name binding note name, automatically rename, automatically delete, show/hide.
Excalidraw CN
3 years ago by Korbin Zhao
支持中文手写的 Excalidraw Obsidian 插件。A Obsidian plugin of Excalidraw supporting Chinese handwrite font.
Remove Empty Folders
3 years ago by fnya
Remove Empty Folders for Obsidian
Note Folder Autorename
6 years ago by PJ Eby
Obsidian plugin to support folder-overview notes by keeping their folder in sync
Clear Unused Images
5 years ago by Ozan Tellioglu
Obsidian plugin to clear the images that are not used in note files anymore
PDF Paste
2 years ago by Cormac
Move Files
a year ago by Nitish Khurana
Sync-safe file names
a year ago by j-maas
Ensure your Obsidian files can always be synced across all your devices.
Content-Addressed Attachments
7 months ago by NateScarlet
Stores obsidian attachments using content-based addressing (similar to IPFS)
Markdown Cleaner
5 months ago by gao-qian-long
Obsidian格式化markdown插件
Math+
5 months ago by ocapraro
This is an Obsidian plugin for taking math notes using Excalidraw.
Excalidraw Extras
4 months ago by zsviczian
Companion Obsidian.md plugin hosting extra add-on optional features for the main Excalidraw-Obsidian plugin
Find Orphaned Images
3 months ago by josmarcristello
Find Orphaned Images is an Obsidian plugin designed to help you keep your vault clean and organized by identifying and managing images that are not linked anywhere in your notes.
Attachment Uploader
3 months ago by zhuxining
An attachment uploader plugin for Obsidian
Mobile PDF Exporter
2 months ago by Murat
One-click selectable preview PDF export plugin for Obsidian mobile and desktop.
Image Manager
2 months ago by David V. Kimball
Insert, rename, and sort external images by transforming them into local files within your notes.
Excalidraw Math Plotter
2 months ago by ahmeddawoud3
Plot mathematical function graphs directly onto Excalidraw canvases.
Just Simple Excalidraw
2 months ago by David Hurt
A local-first Excalidraw editor for native .excalidraw files in Obsidian, preserving the original's brilliant simplicity.
Excalink
2 months ago by Aditya Kumar
Excalink is an Obsidian plugin that enables IntelliSense-style autocompletion for groups and named frames inside .excalidraw.md files. When you type [[filename#, it suggests all available frame names, allowing fast, precise linking to parts of your drawings.
Oriko
2 months ago by trevware
A pannable, zoomable wall of your web clippings. Sort them into grids, filter by any property, and clip from your phone. Media is downloaded into your vault, so clippings keep their images and videos even if the source page is deleted.
Mappy
23 days ago by hiroyaiizuka
An Obsidian plugin to view and edit Markdown notes as linked, illustrated mind maps.