|
|
|
@@ -1,99 +1,570 @@
|
|
|
|
import {App, Editor, MarkdownView, Modal, Notice, Plugin} from 'obsidian';
|
|
|
|
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, Modal, MarkdownFileInfo } from 'obsidian';
|
|
|
|
import {DEFAULT_SETTINGS, MyPluginSettings, SampleSettingTab} from "./settings";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remember to rename these classes and interfaces!
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Settings Interface
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
export default class MyPlugin extends Plugin {
|
|
|
|
interface BindThemSettings {
|
|
|
|
settings: MyPluginSettings;
|
|
|
|
debug: boolean;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onload() {
|
|
|
|
const DEFAULT_SETTINGS: BindThemSettings = {
|
|
|
|
|
|
|
|
debug: false
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Utility Types and Functions
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface EditorPosition {
|
|
|
|
|
|
|
|
line: number;
|
|
|
|
|
|
|
|
ch: number;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface EditorSelection {
|
|
|
|
|
|
|
|
anchor: EditorPosition;
|
|
|
|
|
|
|
|
head: EditorPosition;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface EditorRange {
|
|
|
|
|
|
|
|
from: EditorPosition;
|
|
|
|
|
|
|
|
to: EditorPosition;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getMainSelection(editor: Editor): EditorSelection {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
anchor: editor.getCursor('anchor'),
|
|
|
|
|
|
|
|
head: editor.getCursor('head')
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectionToRange(selection: EditorSelection): EditorRange {
|
|
|
|
|
|
|
|
const sortedPositions = [selection.anchor, selection.head].sort((a, b) => {
|
|
|
|
|
|
|
|
if (a.line !== b.line) return a.line - b.line;
|
|
|
|
|
|
|
|
return a.ch - b.ch;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return { from: sortedPositions[0], to: sortedPositions[1] };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectionToLine(editor: Editor, selection: EditorSelection): EditorSelection {
|
|
|
|
|
|
|
|
const range = selectionToRange(selection);
|
|
|
|
|
|
|
|
const toLength = editor.getLine(range.to.line).length;
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
anchor: { line: range.from.line, ch: 0 },
|
|
|
|
|
|
|
|
head: { line: range.to.line, ch: toLength }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getSelectionBoundaries(selection: EditorSelection): EditorRange & { hasTrailingNewline: boolean } {
|
|
|
|
|
|
|
|
let { anchor: from, head: to } = selection;
|
|
|
|
|
|
|
|
if (from.line > to.line || (from.line === to.line && from.ch > to.ch)) {
|
|
|
|
|
|
|
|
[from, to] = [to, from];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return { from, to, hasTrailingNewline: to.line > from.line && to.ch === 0 };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getLineStartPos(line: number): EditorPosition {
|
|
|
|
|
|
|
|
return { line, ch: 0 };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getLineEndPos(line: number, editor: Editor): EditorPosition {
|
|
|
|
|
|
|
|
return { line, ch: editor.getLine(line).length };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getLeadingWhitespace(lineContent: string): string {
|
|
|
|
|
|
|
|
const indentation = lineContent.match(/^\s+/);
|
|
|
|
|
|
|
|
return indentation ? indentation[0] : '';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isLetterOrDigit(char: string): boolean {
|
|
|
|
|
|
|
|
return /\p{L}\p{M}*/u.test(char) || /\d/.test(char);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function wordRangeAtPos(pos: EditorPosition, lineContent: string): EditorSelection {
|
|
|
|
|
|
|
|
let start = pos.ch;
|
|
|
|
|
|
|
|
let end = pos.ch;
|
|
|
|
|
|
|
|
while (start > 0 && isLetterOrDigit(lineContent.charAt(start - 1))) start--;
|
|
|
|
|
|
|
|
while (end < lineContent.length && isLetterOrDigit(lineContent.charAt(end))) end++;
|
|
|
|
|
|
|
|
return { anchor: { line: pos.line, ch: start }, head: { line: pos.line, ch: end } };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Better Formatting (from obsidian-tweaks)
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BetterFormatting {
|
|
|
|
|
|
|
|
static PRE_WIDOW_EXPAND_CHARS = '#$@';
|
|
|
|
|
|
|
|
static POST_WIDOW_EXPAND_CHARS = '!?::';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private expandRangeByWords(editor: Editor, range: EditorRange, symbolStart: string, symbolEnd: string): EditorRange {
|
|
|
|
|
|
|
|
const wordAtFrom = editor.wordAt(range.from);
|
|
|
|
|
|
|
|
const wordAtTo = editor.wordAt(range.to);
|
|
|
|
|
|
|
|
let from = wordAtFrom ? wordAtFrom.from : range.from;
|
|
|
|
|
|
|
|
let to = wordAtTo ? wordAtTo.to : range.to;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Expand left
|
|
|
|
|
|
|
|
while (from.ch > 0) {
|
|
|
|
|
|
|
|
const newFrom = { line: from.line, ch: from.ch - 1 };
|
|
|
|
|
|
|
|
const preChar = editor.getRange(newFrom, from);
|
|
|
|
|
|
|
|
if (symbolStart.includes(preChar) || !BetterFormatting.PRE_WIDOW_EXPAND_CHARS.includes(preChar)) break;
|
|
|
|
|
|
|
|
from = newFrom;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Expand right
|
|
|
|
|
|
|
|
while (to.ch < editor.getLine(to.line).length) {
|
|
|
|
|
|
|
|
const newTo = { line: to.line, ch: to.ch + 1 };
|
|
|
|
|
|
|
|
const postChar = editor.getRange(to, newTo);
|
|
|
|
|
|
|
|
if (symbolEnd.includes(postChar) || !BetterFormatting.POST_WIDOW_EXPAND_CHARS.includes(postChar)) break;
|
|
|
|
|
|
|
|
to = newTo;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check for existing wrapping symbols
|
|
|
|
|
|
|
|
if (from.ch >= symbolStart.length) {
|
|
|
|
|
|
|
|
const newFrom = { line: from.line, ch: from.ch - symbolStart.length };
|
|
|
|
|
|
|
|
if (editor.getRange(newFrom, from) === symbolStart) from = newFrom;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const newTo = { line: to.line, ch: to.ch + symbolEnd.length };
|
|
|
|
|
|
|
|
if (editor.getRange(to, newTo) === symbolEnd) to = newTo;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { from, to };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleWrapper(editor: Editor, symbolStart: string, symbolEnd?: string): void {
|
|
|
|
|
|
|
|
if (symbolEnd === undefined) symbolEnd = symbolStart;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const selections = editor.listSelections();
|
|
|
|
|
|
|
|
const mainSelection = getMainSelection(editor);
|
|
|
|
|
|
|
|
const mainRange = this.expandRangeByWords(editor, selectionToRange(mainSelection), symbolStart, symbolEnd);
|
|
|
|
|
|
|
|
const text = editor.getRange(mainRange.from, mainRange.to);
|
|
|
|
|
|
|
|
const isWrapped = text.startsWith(symbolStart) && text.endsWith(symbolEnd);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const changes: { from: EditorPosition; to?: EditorPosition; text: string }[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isWrapped) {
|
|
|
|
|
|
|
|
changes.push(
|
|
|
|
|
|
|
|
{ from: mainRange.from, to: { line: mainRange.from.line, ch: mainRange.from.ch + symbolStart.length }, text: '' },
|
|
|
|
|
|
|
|
{ from: { line: mainRange.to.line, ch: mainRange.to.ch - symbolEnd.length }, to: mainRange.to, text: '' }
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
changes.push(
|
|
|
|
|
|
|
|
{ from: mainRange.from, text: symbolStart },
|
|
|
|
|
|
|
|
{ from: mainRange.to, text: symbolEnd }
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
editor.transaction({ changes });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Directional Copy & Move (from obsidian-tweaks)
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum Direction { Up = 'Up', Down = 'Down', Left = 'Left', Right = 'Right' }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DirectionalCopy {
|
|
|
|
|
|
|
|
directionalCopy(editor: Editor, direction: Direction): void {
|
|
|
|
|
|
|
|
const selections = editor.listSelections();
|
|
|
|
|
|
|
|
const vertical = direction === Direction.Up || direction === Direction.Down;
|
|
|
|
|
|
|
|
const processedSelections = vertical ? selections.map(s => selectionToLine(editor, s)) : selections;
|
|
|
|
|
|
|
|
const changes: { from: EditorPosition; to?: EditorPosition; text: string }[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const selection of processedSelections) {
|
|
|
|
|
|
|
|
const range = selectionToRange(selection);
|
|
|
|
|
|
|
|
const content = editor.getRange(range.from, range.to);
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
|
|
|
|
case Direction.Up: changes.push({ from: range.from, text: content + '\n' }); break;
|
|
|
|
|
|
|
|
case Direction.Down: changes.push({ from: range.to, text: '\n' + content }); break;
|
|
|
|
|
|
|
|
case Direction.Left: changes.push({ from: range.from, text: content }); break;
|
|
|
|
|
|
|
|
case Direction.Right: changes.push({ from: range.to, text: content }); break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
editor.transaction({ changes });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DirectionalMove {
|
|
|
|
|
|
|
|
directionalMove(editor: Editor, direction: Direction): void {
|
|
|
|
|
|
|
|
const selections = editor.listSelections();
|
|
|
|
|
|
|
|
const changes: { from: EditorPosition; to?: EditorPosition; text: string }[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const selection of selections) {
|
|
|
|
|
|
|
|
const range = selectionToRange(selection);
|
|
|
|
|
|
|
|
if (direction === Direction.Left && range.from.ch > 0) {
|
|
|
|
|
|
|
|
const delFrom = { line: range.from.line, ch: range.from.ch - 1 };
|
|
|
|
|
|
|
|
const char = editor.getRange(delFrom, range.from);
|
|
|
|
|
|
|
|
changes.push({ from: delFrom, to: range.from, text: '' });
|
|
|
|
|
|
|
|
changes.push({ from: range.to, text: char });
|
|
|
|
|
|
|
|
} else if (direction === Direction.Right) {
|
|
|
|
|
|
|
|
const delTo = { line: range.to.line, ch: range.to.ch + 1 };
|
|
|
|
|
|
|
|
const char = editor.getRange(range.to, delTo);
|
|
|
|
|
|
|
|
changes.push({ from: range.to, to: delTo, text: '' });
|
|
|
|
|
|
|
|
changes.push({ from: range.from, text: char });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
editor.transaction({ changes });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Toggle Heading (merged from obsidian-tweaks and heading-toggler)
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ToggleHeading {
|
|
|
|
|
|
|
|
private static HEADING_REGEX = /^(#*)( *)(.*)/;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleHeading(editor: Editor, heading: number): void {
|
|
|
|
|
|
|
|
const selections = editor.listSelections();
|
|
|
|
|
|
|
|
const mainRange = selectionToRange(getMainSelection(editor));
|
|
|
|
|
|
|
|
const mainText = editor.getLine(mainRange.from.line);
|
|
|
|
|
|
|
|
const mainMatch = mainText.match(ToggleHeading.HEADING_REGEX);
|
|
|
|
|
|
|
|
const mainHeading = mainMatch ? mainMatch[1].length : 0;
|
|
|
|
|
|
|
|
const targetHeading = heading === mainHeading ? 0 : heading;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const changes: { from: EditorPosition; to?: EditorPosition; text: string }[] = [];
|
|
|
|
|
|
|
|
const linesSet = new Set<number>();
|
|
|
|
|
|
|
|
for (const sel of selections) {
|
|
|
|
|
|
|
|
const range = selectionToRange(sel);
|
|
|
|
|
|
|
|
for (let line = range.from.line; line <= range.to.line; line++) linesSet.add(line);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const line of Array.from(linesSet).sort((a, b) => a - b)) {
|
|
|
|
|
|
|
|
const text = editor.getLine(line);
|
|
|
|
|
|
|
|
if (text === '') continue;
|
|
|
|
|
|
|
|
const match = text.match(ToggleHeading.HEADING_REGEX);
|
|
|
|
|
|
|
|
if (!match) continue;
|
|
|
|
|
|
|
|
const from = { line, ch: 0 };
|
|
|
|
|
|
|
|
const to = { line, ch: match[1].length + match[2].length };
|
|
|
|
|
|
|
|
changes.push({ from, to, text: targetHeading === 0 ? '' : '#'.repeat(targetHeading) + ' ' });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
editor.transaction({ changes });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleHeadingWithStrip(editor: Editor, level: number): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
const line = editor.getLine(cursor.line);
|
|
|
|
|
|
|
|
const headingRegex = /^(#+)\s(.*)/;
|
|
|
|
|
|
|
|
const match = line.match(headingRegex);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let content = line;
|
|
|
|
|
|
|
|
let currentLevel = 0;
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
|
|
|
|
currentLevel = match[1].length;
|
|
|
|
|
|
|
|
content = match[2];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
content = content.replace(/(\*\*|__)(.*?)\1/g, '$2').replace(/(\*|_)(.*?)\1/g, '$2');
|
|
|
|
|
|
|
|
const newText = currentLevel === level ? content : '#'.repeat(level) + ' ' + content;
|
|
|
|
|
|
|
|
editor.setLine(cursor.line, newText);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Editor Shortcuts (from obsidian-editor-shortcuts)
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum CaseType { Upper = 'upper', Lower = 'lower', Title = 'title', Next = 'next' }
|
|
|
|
|
|
|
|
const LOWERCASE_ARTICLES = ['the', 'a', 'an'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function toTitleCase(text: string): string {
|
|
|
|
|
|
|
|
return text.split(/(\s+)/).map((word, i, all) => {
|
|
|
|
|
|
|
|
if (i > 0 && i < all.length - 1 && LOWERCASE_ARTICLES.includes(word.toLowerCase())) return word.toLowerCase();
|
|
|
|
|
|
|
|
return word.charAt(0).toUpperCase() + word.substring(1).toLowerCase();
|
|
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Go To Line Modal
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GoToLineModal extends Modal {
|
|
|
|
|
|
|
|
private onSubmit: (line: number) => void;
|
|
|
|
|
|
|
|
private lineCount: number;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(app: App, lineCount: number, onSubmit: (line: number) => void) {
|
|
|
|
|
|
|
|
super(app);
|
|
|
|
|
|
|
|
this.lineCount = lineCount;
|
|
|
|
|
|
|
|
this.onSubmit = onSubmit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onOpen(): void {
|
|
|
|
|
|
|
|
const { contentEl } = this;
|
|
|
|
|
|
|
|
contentEl.createEl('h2', { text: 'Go to line' });
|
|
|
|
|
|
|
|
const input = contentEl.createEl('input', { attr: { type: 'number', min: '1', max: String(this.lineCount), placeholder: `Enter line number (1-${this.lineCount})` } });
|
|
|
|
|
|
|
|
const button = contentEl.createEl('button', { text: 'Go' });
|
|
|
|
|
|
|
|
button.onclick = () => {
|
|
|
|
|
|
|
|
const line = parseInt(input.value) - 1;
|
|
|
|
|
|
|
|
if (line >= 0 && line < this.lineCount) { this.onSubmit(line); this.close(); }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
input.onkeydown = (e) => { if (e.key === 'Enter') button.click(); };
|
|
|
|
|
|
|
|
input.focus();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onClose(): void { this.contentEl.empty(); }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// Main Plugin Class
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default class BindThemPlugin extends Plugin {
|
|
|
|
|
|
|
|
settings!: BindThemSettings;
|
|
|
|
|
|
|
|
private betterFormatting = new BetterFormatting();
|
|
|
|
|
|
|
|
private directionalCopy = new DirectionalCopy();
|
|
|
|
|
|
|
|
private directionalMove = new DirectionalMove();
|
|
|
|
|
|
|
|
private toggleHeading = new ToggleHeading();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async onload(): Promise<void> {
|
|
|
|
await this.loadSettings();
|
|
|
|
await this.loadSettings();
|
|
|
|
|
|
|
|
|
|
|
|
// This creates an icon in the left ribbon.
|
|
|
|
// Better Formatting Commands
|
|
|
|
this.addRibbonIcon('dice', 'Sample', (evt: MouseEvent) => {
|
|
|
|
const formattingCommands: [string, string, string | undefined][] = [
|
|
|
|
// Called when the user clicks the icon.
|
|
|
|
['bold-underscore', '__', '__'], ['bold-asterisk', '**', '**'],
|
|
|
|
new Notice('This is a notice!');
|
|
|
|
['italics-underscore', '_', '_'], ['italics-asterisk', '*', '*'],
|
|
|
|
});
|
|
|
|
['code', '`', '`'], ['comment', '%%', '%%'], ['highlight', '==', '=='],
|
|
|
|
|
|
|
|
['strikethrough', '~~', '~~'], ['math-inline', '$', '$'], ['math-block', '$$', '$$']
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const [id, start, end] of formattingCommands) {
|
|
|
|
|
|
|
|
this.addCommand({
|
|
|
|
|
|
|
|
id: `toggle-${id}`,
|
|
|
|
|
|
|
|
name: `Toggle ${id.replace('-', ' ')}`,
|
|
|
|
|
|
|
|
editorCallback: (editor) => this.betterFormatting.toggleWrapper(editor, start, end)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
|
|
|
// Directional Copy Commands
|
|
|
|
const statusBarItemEl = this.addStatusBarItem();
|
|
|
|
for (const dir of [Direction.Up, Direction.Down, Direction.Left, Direction.Right]) {
|
|
|
|
statusBarItemEl.setText('Status bar text');
|
|
|
|
this.addCommand({
|
|
|
|
|
|
|
|
id: `copy-${dir.toLowerCase()}`,
|
|
|
|
|
|
|
|
name: `Copy ${dir}`,
|
|
|
|
|
|
|
|
editorCallback: (editor) => this.directionalCopy.directionalCopy(editor, dir)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This adds a simple command that can be triggered anywhere
|
|
|
|
// Directional Move Commands
|
|
|
|
this.addCommand({
|
|
|
|
for (const dir of [Direction.Left, Direction.Right]) {
|
|
|
|
id: 'open-modal-simple',
|
|
|
|
this.addCommand({
|
|
|
|
name: 'Open modal (simple)',
|
|
|
|
id: `move-${dir.toLowerCase()}`,
|
|
|
|
callback: () => {
|
|
|
|
name: `Move ${dir}`,
|
|
|
|
new SampleModal(this.app).open();
|
|
|
|
editorCallback: (editor) => this.directionalMove.directionalMove(editor, dir)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Toggle Heading Commands
|
|
|
|
|
|
|
|
for (let i = 1; i <= 6; i++) {
|
|
|
|
|
|
|
|
this.addCommand({
|
|
|
|
|
|
|
|
id: `toggle-heading-${i}`,
|
|
|
|
|
|
|
|
name: `Toggle heading ${i}`,
|
|
|
|
|
|
|
|
editorCallback: (editor) => this.toggleHeading.toggleHeading(editor, i)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
this.addCommand({
|
|
|
|
|
|
|
|
id: `toggle-heading-strip-${i}`,
|
|
|
|
|
|
|
|
name: `Toggle heading ${i} (strip formatting)`,
|
|
|
|
|
|
|
|
editorCallback: (editor) => this.toggleHeading.toggleHeadingWithStrip(editor, i)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Editor Shortcuts
|
|
|
|
|
|
|
|
this.addCommand({ id: 'insert-line-above', name: 'Insert line above', editorCallback: (e) => this.insertLine(e, 'above') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'insert-line-below', name: 'Insert line below', editorCallback: (e) => this.insertLine(e, 'below') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'delete-line', name: 'Delete line', editorCallback: (e) => this.deleteLine(e) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'delete-to-start-of-line', name: 'Delete to start of line', editorCallback: (e) => this.deleteToBoundary(e, 'start') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'delete-to-end-of-line', name: 'Delete to end of line', editorCallback: (e) => this.deleteToBoundary(e, 'end') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'join-lines', name: 'Join lines', editorCallback: (e) => this.joinLines(e) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'duplicate-line', name: 'Duplicate line', editorCallback: (e) => this.copyLine(e, 'down') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'copy-line-up', name: 'Copy line up', editorCallback: (e) => this.copyLine(e, 'up') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'copy-line-down', name: 'Copy line down', editorCallback: (e) => this.copyLine(e, 'down') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'select-word', name: 'Select word', editorCallback: (e) => this.selectWord(e) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'select-line', name: 'Select line', editorCallback: (e) => this.selectLine(e) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Case Transformation
|
|
|
|
|
|
|
|
this.addCommand({ id: 'transform-uppercase', name: 'Transform to uppercase', editorCallback: (e) => this.transformCase(e, CaseType.Upper) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'transform-lowercase', name: 'Transform to lowercase', editorCallback: (e) => this.transformCase(e, CaseType.Lower) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'transform-titlecase', name: 'Transform to title case', editorCallback: (e) => this.transformCase(e, CaseType.Title) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'toggle-case', name: 'Toggle case', editorCallback: (e) => this.transformCase(e, CaseType.Next) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Navigation
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-line-start', name: 'Go to line start', editorCallback: (e) => e.setCursor({ line: e.getCursor().line, ch: 0 }) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-line-end', name: 'Go to line end', editorCallback: (e) => e.setCursor({ line: e.getCursor().line, ch: e.getLine(e.getCursor().line).length }) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-first-line', name: 'Go to first line', editorCallback: (e) => e.setCursor({ line: 0, ch: 0 }) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-last-line', name: 'Go to last line', editorCallback: (e) => e.setCursor({ line: e.lineCount() - 1, ch: 0 }) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-line-number', name: 'Go to line number', editorCallback: (e) => new GoToLineModal(this.app, e.lineCount(), (line) => e.setCursor({ line, ch: 0 })).open() });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-next-heading', name: 'Go to next heading', editorCallback: (e) => this.goToHeading(e, 'next') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-prev-heading', name: 'Go to previous heading', editorCallback: (e) => this.goToHeading(e, 'prev') });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cursor Movement
|
|
|
|
|
|
|
|
this.addCommand({ id: 'move-cursor-up', name: 'Move cursor up', editorCallback: (e) => e.exec('goUp') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'move-cursor-down', name: 'Move cursor down', editorCallback: (e) => e.exec('goDown') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'move-cursor-left', name: 'Move cursor left', editorCallback: (e) => e.exec('goLeft') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'move-cursor-right', name: 'Move cursor right', editorCallback: (e) => e.exec('goRight') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-prev-word', name: 'Go to previous word', editorCallback: (e) => e.exec('goWordLeft') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'go-to-next-word', name: 'Go to next word', editorCallback: (e) => e.exec('goWordRight') });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'insert-cursor-above', name: 'Insert cursor above', editorCallback: (e) => this.insertCursor(e, -1) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'insert-cursor-below', name: 'Insert cursor below', editorCallback: (e) => this.insertCursor(e, 1) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// File Operations
|
|
|
|
|
|
|
|
this.addCommand({ id: 'duplicate-file', name: 'Duplicate file', editorCallback: (e, v) => this.duplicateFile(e, v as MarkdownView) });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'new-adjacent-file', name: 'New adjacent file', editorCallback: (e, v) => this.newAdjacentFile(e, v as MarkdownView) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
|
|
|
|
this.addCommand({ id: 'toggle-line-numbers', name: 'Toggle line numbers', callback: () => { const val = this.app.vault.getConfig('showLineNumber'); (this.app.vault as unknown as { setConfig: (k: string, v: unknown) => void }).setConfig('showLineNumber', !val); } });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'undo', name: 'Undo', editorCallback: (e) => e.undo() });
|
|
|
|
|
|
|
|
this.addCommand({ id: 'redo', name: 'Redo', editorCallback: (e) => e.redo() });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.addSettingTab(new BindThemSettingTab(this.app, this));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Editor Helper Methods
|
|
|
|
|
|
|
|
private insertLine(editor: Editor, where: 'above' | 'below'): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
const line = editor.getLine(cursor.line);
|
|
|
|
|
|
|
|
const indent = getLeadingWhitespace(line);
|
|
|
|
|
|
|
|
if (where === 'above') {
|
|
|
|
|
|
|
|
editor.replaceRange(indent + '\n', { line: cursor.line, ch: 0 });
|
|
|
|
|
|
|
|
editor.setCursor({ line: cursor.line, ch: indent.length });
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
editor.replaceRange('\n' + indent, { line: cursor.line, ch: line.length });
|
|
|
|
|
|
|
|
editor.setCursor({ line: cursor.line + 1, ch: indent.length });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private deleteLine(editor: Editor): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
const from = { line: cursor.line, ch: 0 };
|
|
|
|
|
|
|
|
const to = { line: cursor.line + 1, ch: 0 };
|
|
|
|
|
|
|
|
editor.replaceRange('', from, cursor.line === editor.lineCount() - 1 ? { line: cursor.line, ch: editor.getLine(cursor.line).length } : to);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private deleteToBoundary(editor: Editor, boundary: 'start' | 'end'): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
if (boundary === 'start') {
|
|
|
|
|
|
|
|
editor.replaceRange('', { line: cursor.line, ch: 0 }, cursor);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
editor.replaceRange('', cursor, { line: cursor.line, ch: editor.getLine(cursor.line).length });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private joinLines(editor: Editor): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
if (cursor.line >= editor.lineCount() - 1) return;
|
|
|
|
|
|
|
|
const currentLine = editor.getLine(cursor.line);
|
|
|
|
|
|
|
|
const nextLine = editor.getLine(cursor.line + 1).replace(/^\s*[-+*>\d.]+\s*/, '');
|
|
|
|
|
|
|
|
const separator = currentLine.endsWith(' ') || nextLine.startsWith(' ') ? '' : ' ';
|
|
|
|
|
|
|
|
editor.replaceRange(separator + nextLine, { line: cursor.line, ch: currentLine.length }, { line: cursor.line + 1, ch: editor.getLine(cursor.line + 1).length });
|
|
|
|
|
|
|
|
editor.setCursor({ line: cursor.line, ch: currentLine.length + separator.length });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private copyLine(editor: Editor, direction: 'up' | 'down'): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
const line = editor.getLine(cursor.line);
|
|
|
|
|
|
|
|
if (direction === 'up') {
|
|
|
|
|
|
|
|
editor.replaceRange(line + '\n', { line: cursor.line, ch: 0 });
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
editor.replaceRange('\n' + line, { line: cursor.line, ch: line.length });
|
|
|
|
|
|
|
|
editor.setCursor({ line: cursor.line + 1, ch: cursor.ch });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private selectWord(editor: Editor): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
const line = editor.getLine(cursor.line);
|
|
|
|
|
|
|
|
const wordRange = wordRangeAtPos(cursor, line);
|
|
|
|
|
|
|
|
editor.setSelection(wordRange.anchor, wordRange.head);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private selectLine(editor: Editor): void {
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
editor.setSelection({ line: cursor.line, ch: 0 }, { line: cursor.line, ch: editor.getLine(cursor.line).length });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private transformCase(editor: Editor, caseType: CaseType): void {
|
|
|
|
|
|
|
|
const selection = editor.getSelection();
|
|
|
|
|
|
|
|
const cursor = editor.getCursor();
|
|
|
|
|
|
|
|
let text = selection || editor.getRange(...Object.values(wordRangeAtPos(cursor, editor.getLine(cursor.line))) as [EditorPosition, EditorPosition]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let result: string;
|
|
|
|
|
|
|
|
switch (caseType) {
|
|
|
|
|
|
|
|
case CaseType.Upper: result = text.toUpperCase(); break;
|
|
|
|
|
|
|
|
case CaseType.Lower: result = text.toLowerCase(); break;
|
|
|
|
|
|
|
|
case CaseType.Title: result = toTitleCase(text); break;
|
|
|
|
|
|
|
|
case CaseType.Next: result = text === text.toUpperCase() ? text.toLowerCase() : text === text.toLowerCase() ? toTitleCase(text) : text.toUpperCase(); break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (selection) editor.replaceSelection(result);
|
|
|
|
|
|
|
|
else { const range = wordRangeAtPos(cursor, editor.getLine(cursor.line)); editor.replaceRange(result, range.anchor, range.head); }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private insertCursor(editor: Editor, offset: number): void {
|
|
|
|
|
|
|
|
const selections = editor.listSelections();
|
|
|
|
|
|
|
|
const newSelections: EditorSelection[] = [];
|
|
|
|
|
|
|
|
for (const sel of selections) {
|
|
|
|
|
|
|
|
const newLine = sel.head.line + offset;
|
|
|
|
|
|
|
|
if (newLine >= 0 && newLine < editor.lineCount()) {
|
|
|
|
|
|
|
|
newSelections.push({ anchor: { line: sel.anchor.line + offset, ch: Math.min(sel.anchor.ch, editor.getLine(newLine).length) }, head: { line: newLine, ch: Math.min(sel.head.ch, editor.getLine(newLine).length) } });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// This adds an editor command that can perform some operation on the current editor instance
|
|
|
|
editor.setSelections([...selections, ...newSelections]);
|
|
|
|
this.addCommand({
|
|
|
|
|
|
|
|
id: 'replace-selected',
|
|
|
|
|
|
|
|
name: 'Replace selected content',
|
|
|
|
|
|
|
|
editorCallback: (editor: Editor, view: MarkdownView) => {
|
|
|
|
|
|
|
|
editor.replaceSelection('Sample editor command');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
|
|
|
|
|
|
|
this.addCommand({
|
|
|
|
|
|
|
|
id: 'open-modal-complex',
|
|
|
|
|
|
|
|
name: 'Open modal (complex)',
|
|
|
|
|
|
|
|
checkCallback: (checking: boolean) => {
|
|
|
|
|
|
|
|
// Conditions to check
|
|
|
|
|
|
|
|
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
|
|
|
|
|
|
if (markdownView) {
|
|
|
|
|
|
|
|
// If checking is true, we're simply "checking" if the command can be run.
|
|
|
|
|
|
|
|
// If checking is false, then we want to actually perform the operation.
|
|
|
|
|
|
|
|
if (!checking) {
|
|
|
|
|
|
|
|
new SampleModal(this.app).open();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This command will only show up in Command Palette when the check function returns true
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This adds a settings tab so the user can configure various aspects of the plugin
|
|
|
|
|
|
|
|
this.addSettingTab(new SampleSettingTab(this.app, this));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
|
|
|
|
|
|
|
// Using this function will automatically remove the event listener when this plugin is disabled.
|
|
|
|
|
|
|
|
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
|
|
|
|
|
|
|
new Notice("Click");
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
|
|
|
|
|
|
|
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onunload() {
|
|
|
|
private goToHeading(editor: Editor, direction: 'next' | 'prev'): void {
|
|
|
|
|
|
|
|
const file = this.app.workspace.getActiveFile();
|
|
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
const cache = this.app.metadataCache.getFileCache(file);
|
|
|
|
|
|
|
|
if (!cache?.headings?.length) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cursorLine = editor.getCursor().line;
|
|
|
|
|
|
|
|
let targetLine = direction === 'next' ? editor.lineCount() - 1 : 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const h of cache.headings) {
|
|
|
|
|
|
|
|
const hLine = h.position.end.line;
|
|
|
|
|
|
|
|
if (direction === 'next' && hLine > cursorLine && hLine < targetLine) targetLine = hLine;
|
|
|
|
|
|
|
|
if (direction === 'prev' && hLine < cursorLine && hLine > targetLine) targetLine = hLine;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
editor.setCursor({ line: targetLine, ch: 0 });
|
|
|
|
|
|
|
|
editor.scrollIntoView({ from: { line: targetLine, ch: 0 }, to: { line: targetLine, ch: 0 } });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async loadSettings() {
|
|
|
|
private async duplicateFile(editor: Editor, view: MarkdownView): Promise<void> {
|
|
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData() as Partial<MyPluginSettings>);
|
|
|
|
const file = this.app.workspace.getActiveFile();
|
|
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
const newPath = (file.parent?.path ?? '') + '/' + file.basename + ' (copy).' + file.extension;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const newFile = await this.app.vault.copy(file, newPath);
|
|
|
|
|
|
|
|
await view.leaf.openFile(newFile);
|
|
|
|
|
|
|
|
} catch (e) { new Notice(String(e)); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async saveSettings() {
|
|
|
|
private async newAdjacentFile(editor: Editor, view: MarkdownView): Promise<void> {
|
|
|
|
await this.saveData(this.settings);
|
|
|
|
const file = this.app.workspace.getActiveFile();
|
|
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
const newPath = (file.parent?.path ?? '') + '/Untitled.md';
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const newFile = await this.app.vault.create(newPath, '');
|
|
|
|
|
|
|
|
await view.leaf.openFile(newFile);
|
|
|
|
|
|
|
|
} catch (e) { new Notice(String(e)); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async loadSettings(): Promise<void> { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); }
|
|
|
|
|
|
|
|
async saveSettings(): Promise<void> { await this.saveData(this.settings); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class SampleModal extends Modal {
|
|
|
|
// ============================================================
|
|
|
|
constructor(app: App) {
|
|
|
|
// Settings Tab
|
|
|
|
super(app);
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BindThemSettingTab extends PluginSettingTab {
|
|
|
|
|
|
|
|
private plugin: BindThemPlugin;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(app: App, plugin: BindThemPlugin) {
|
|
|
|
|
|
|
|
super(app, plugin);
|
|
|
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onOpen() {
|
|
|
|
display(): void {
|
|
|
|
let {contentEl} = this;
|
|
|
|
const { containerEl } = this;
|
|
|
|
contentEl.setText('Woah!');
|
|
|
|
containerEl.empty();
|
|
|
|
|
|
|
|
new Setting(containerEl).setName('Debug mode').setDesc('Enable debug logging').addToggle(t => t.setValue(this.plugin.settings.debug).onChange(async v => { this.plugin.settings.debug = v; await this.plugin.saveSettings(); }));
|
|
|
|
|
|
|
|
new Setting(containerEl).setName('About').setDesc('BindThem combines features from obsidian-tweaks, obsidian-editor-shortcuts, and heading-toggler.').setHeading();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
onClose() {
|
|
|
|
|
|
|
|
const {contentEl} = this;
|
|
|
|
|
|
|
|
contentEl.empty();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|