Promote/demote all selected headers

I have a new-and-improved script now :slightly_smiling_face:

Features

  • can be used to set headings to specific level, or increase/decrease
  • multi-line: increase/decrease all headings in multi-line selection
  • “promote” defaults to heading level 3 if no heading in selection
  • preserve cursor position / selection

Script

/* Set the heading to the specified level.
 * If the level is negative, will decrease heading size by that much.
 * If the level is 0 or undefined, will increase the heading by one, or turn a non-heading into a level-3 heading
 */
function setHeading(level) {
	const editor = app.workspace.activeLeaf.view.editor;
	const cAnchor = editor.getCursor('anchor');
	const cHead = editor.getCursor('head');
	const top = {line: Math.min(cAnchor.line, cHead.line), ch: 0};
	const bottom = {line: Math.max(cAnchor.line, cHead.line), ch: Infinity};
	const lineRange = editor.getRange(top, bottom);

	let replacement = lineRange;

	if (!level) {
		// promote all headings in lineRange. If no heading in lineRange, promote all lines to h3
		if (!lineRange.match(/^#+ /m)) {
			replacement = lineRange.replace(/^/gm, "### ");
		}
		else { // otherwise promote by 1
			replacement = lineRange.replace(/^#(#+) /gm, "$1 ");
		}
	}
	else if (level < -1)
		console.error("Heading level of less than -1 is not supported, demoting heading(s) by one only.  Aborting.");
	else if (level == -1) {
		replacement = lineRange.replace(/^#+ /gm, "#$&");
	}
	else {
		// set lineRange to header level
		console.log(`set header to level: ${level}.`);
		if (!lineRange.match(/^#+ /m)) {
			console.log('no headings found, replacing all lines')
			replacement = lineRange.replace(/^/gm, "#".repeat(level)+" ");
		}
		else {
			console.log('replace certain headings')
			replacement = lineRange.replace(/^#+ (.*)/gm, "#".repeat(level) + " $1");
		}
	}

	editor.replaceRange(replacement, top, bottom);

	// adjust new selection
	let lines = lineRange.split('\n');
	let newLines = replacement.split('\n');
	[cAnchor, cHead].forEach(p => {
		let i = p.line - top.line;
		if (p.ch)
			p.ch += newLines[i].length - lines[i].length;
	});
	editor.setSelection(cAnchor, cHead);

	return;
}

module.exports = setHeading;

Instructions

  1. Install Templater
  2. Create a file called “setHeading.js” in your vault (I suggest putting it in a folder called “Scripts”)
    • copy and paste the content (Script) from above
    • you will have to use a text editor other than Obsidian to create the js file
  3. Create two markdown files in your templates folder with the following content:
    <%*
    tp.user.setHeading();
    return;
    %>
    
    and
    <%*
    tp.user.setHeading(-1);
    return;
    %>
    
    • you can also use numbers 1 – 6 to set headings to a specific level
  4. Name the files something like “Increase Headings” and “Decrease Headings”
  5. In Templater Settings, make sure you can see tp.user.setHeading in the User Script Functions section.
  6. Add a Template Hotkey for each of the templates you just created and set to your desired shortcuts in Obsidian Settings > Hotkeys
  7. Enjoy :slightly_smiling_face:
7 Likes