Files
BindThem/src/ToggleHeading.ts
Olivier Legendre fcbf1492b7
Some checks failed
Node.js build / build (20.x) (push) Has been cancelled
Node.js build / build (22.x) (push) Has been cancelled
update and fix
2026-05-15 16:23:28 -04:00

140 lines
3.4 KiB
TypeScript

import {
App,
Editor,
EditorChange,
EditorPosition,
EditorTransaction,
MarkdownFileInfo,
MarkdownView,
} from 'obsidian'
import BindThemPlugin from './main'
import { Heading } from './Entities'
import { getMainSelection, selectionToRange } from './Utils'
import { HEADING_REGEX } from './Constants'
/**
* ToggleHeading provides commands to toggle markdown heading levels
*/
export class ToggleHeading {
public app: App
private plugin: BindThemPlugin
constructor(app: App, plugin: BindThemPlugin) {
this.app = app
this.plugin = plugin
}
/**
* Create a change to set the heading level for a specific line
*/
private setHeading(
editor: Editor,
heading: Heading,
line: number
): EditorChange {
const headingStr = '#'.repeat(heading)
const text = editor.getLine(line)
const matches = HEADING_REGEX.exec(text)!
const from: EditorPosition = {
line: line,
ch: 0,
}
const to: EditorPosition = {
line: line,
ch: matches[1].length + matches[2].length,
}
const replacementStr = heading === Heading.NORMAL ? '' : headingStr + ' '
return {
from: from,
to: to,
text: replacementStr,
}
}
/**
* Get the heading level of a specific line
*/
private getHeadingOfLine(editor: Editor, line: number): Heading {
const text = editor.getLine(line)
const matches = HEADING_REGEX.exec(text)!
return matches[1].length as Heading
}
/**
* Toggle heading for the current selection(s)
* If the line already has the specified heading, remove it (set to NORMAL)
*/
public toggleHeading(
editor: Editor,
view: MarkdownView | MarkdownFileInfo,
heading: Heading
): void {
const selections = editor.listSelections()
const mainSelection = getMainSelection(editor)
const mainRange = selectionToRange(mainSelection)
const mainHeading = this.getHeadingOfLine(editor, mainRange.from.line)
const targetHeading = heading === mainHeading ? Heading.NORMAL : heading
const changes: Array<EditorChange> = []
// Collect unique lines from all selections
const linesToSet: Set<number> = new Set()
for (const selection of selections) {
const range = selectionToRange(selection)
for (let line = range.from.line; line <= range.to.line; line++) {
linesToSet.add(line)
}
}
// Create changes for each line
for (const line of linesToSet) {
if (editor.getLine(line) === '') {
continue
}
changes.push(this.setHeading(editor, targetHeading, line))
}
const transaction: EditorTransaction = {
changes: changes,
}
editor.transaction(transaction, 'ToggleHeading' + heading.toString())
}
/**
* Toggle heading with stripping of inline formatting
* This removes bold/italic formatting when toggling headings
*/
public toggleHeadingWithStrip(
editor: Editor,
view: MarkdownView | MarkdownFileInfo,
level: number
): void {
const cursor = editor.getCursor()
const line = editor.getLine(cursor.line)
const match = line.match(/^(#+)\s(.*)/)
let content = line
let currentLevel = 0
if (match) {
currentLevel = match[1].length
content = match[2]
}
// Strip bold and italic formatting
content = content
.replace(/(\*\*|__)(.*?)\1/g, '$2')
.replace(/(\*|_)(.*?)\1/g, '$2')
editor.setLine(
cursor.line,
currentLevel === level ? content : '#'.repeat(level) + ' ' + content
)
}
}