140 lines
3.4 KiB
TypeScript
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
|
|
)
|
|
}
|
|
} |