mirror of
https://github.com/dragonish/obsidian-heading-decorator.git
synced 2026-07-22 05:42:05 +00:00
refactor(main): split the main file
This commit is contained in:
parent
c7657ab7d1
commit
619d950ae8
4 changed files with 1667 additions and 1661 deletions
|
|
@ -213,6 +213,31 @@ export function defaultSourceHeadingDecoratorSettings(): SourceHeadingDecoratorS
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default plugin settings.
|
||||
*
|
||||
* @returns default plugin settings.
|
||||
*/
|
||||
export function defalutSettings(): HeadingPluginSettings {
|
||||
return {
|
||||
metadataKeyword: "heading",
|
||||
fileRegexBlacklist: [],
|
||||
folderBlacklist: [],
|
||||
enabledInReading: true,
|
||||
readingSettings: defaultHeadingDecoratorSettings(),
|
||||
enabledInPreview: true,
|
||||
previewSettings: defaultHeadingDecoratorSettings(),
|
||||
enabledInSource: false,
|
||||
sourceSettings: defaultSourceHeadingDecoratorSettings(),
|
||||
enabledInOutline: false,
|
||||
outlineSettings: defaultHeadingDecoratorSettings(),
|
||||
enabledInQuietOutline: false,
|
||||
quietOutlineSettings: defaultHeadingDecoratorSettings(),
|
||||
enabledInFileExplorer: false,
|
||||
fileExplorerSettings: defaultHeadingDecoratorSettings(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unordered level headings from settings.
|
||||
*
|
||||
|
|
|
|||
973
components/plugin.ts
Normal file
973
components/plugin.ts
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
import {
|
||||
Editor,
|
||||
FrontMatterCache,
|
||||
MarkdownView,
|
||||
Plugin,
|
||||
TFile,
|
||||
WorkspaceLeaf,
|
||||
debounce,
|
||||
} from "obsidian";
|
||||
import { EditorView, ViewPlugin } from "@codemirror/view";
|
||||
import type { HeadingPluginSettings, HeadingPluginData } from "../common/data";
|
||||
import {
|
||||
headingsSelector,
|
||||
getUnorderedLevelHeadings,
|
||||
getOrderedCustomIdents,
|
||||
diffLevel,
|
||||
getBoolean,
|
||||
checkEnabledCSS,
|
||||
stringToRegex,
|
||||
defalutSettings,
|
||||
} from "../common/data";
|
||||
import { Counter, Querier } from "../common/counter";
|
||||
import { Heading } from "../common/heading";
|
||||
import {
|
||||
decorateHTMLElement,
|
||||
decorateOutlineElement,
|
||||
cancelOutlineDecorator,
|
||||
decorateFileHeadingElement,
|
||||
cancelFileHeadingDecorator,
|
||||
queryHeadingLevelByElement,
|
||||
getTreeItemLevel,
|
||||
getTreeItemText,
|
||||
getFileHeadingItemLevel,
|
||||
compareHeadingText,
|
||||
} from "../common/dom";
|
||||
import {
|
||||
HeadingViewPlugin,
|
||||
headingDecorationsField,
|
||||
editorModeField,
|
||||
updateEditorMode,
|
||||
} from "./view";
|
||||
import { ViewChildComponent } from "./child";
|
||||
import {
|
||||
quietOutlineHandler,
|
||||
cancelQuietOutlineDecoration,
|
||||
} from "./quiet-outline";
|
||||
import { HeadingSettingTab } from "./setting-tab";
|
||||
|
||||
interface ObsidianEditor extends Editor {
|
||||
cm: EditorView;
|
||||
}
|
||||
|
||||
interface ObsidianWorkspaceLeaf extends WorkspaceLeaf {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface SettingsChangeState {
|
||||
outline?: boolean;
|
||||
quietOutline?: boolean;
|
||||
fileExplorer?: boolean;
|
||||
}
|
||||
|
||||
export class HeadingPlugin extends Plugin {
|
||||
settings: HeadingPluginSettings;
|
||||
|
||||
private outlineIdSet: Set<string> = new Set();
|
||||
private outlineComponents: ViewChildComponent[] = [];
|
||||
|
||||
private quietOutlineIdSet: Set<string> = new Set();
|
||||
private quietOutlineComponents: ViewChildComponent[] = [];
|
||||
|
||||
private fileExplorerIdSet: Set<string> = new Set();
|
||||
private fileExplorerComponents: ViewChildComponent[] = [];
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// Register editor extension
|
||||
this.registerEditorExtension([
|
||||
headingDecorationsField,
|
||||
editorModeField,
|
||||
this.craeteHeadingViewPlugin(this.getPluginData.bind(this)),
|
||||
]);
|
||||
|
||||
// Register markdown post processor
|
||||
this.registerMarkdownPostProcessor(async (element, context) => {
|
||||
if (!this.settings.enabledInReading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadataEnabled = this.getEnabledFromFrontmatter(
|
||||
"reading",
|
||||
context.frontmatter
|
||||
);
|
||||
|
||||
if (metadataEnabled == null) {
|
||||
if (
|
||||
this.settings.readingSettings.enabledInEachNote != undefined &&
|
||||
!this.settings.readingSettings.enabledInEachNote
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getEnabledFromBlacklist(context.sourcePath)) {
|
||||
return;
|
||||
}
|
||||
} else if (!metadataEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headingElements = element.findAll(headingsSelector);
|
||||
if (headingElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
readingSettings: {
|
||||
opacity,
|
||||
position,
|
||||
ordered,
|
||||
orderedDelimiter,
|
||||
orderedTrailingDelimiter,
|
||||
orderedStyleType,
|
||||
orderedSpecifiedString,
|
||||
orderedCustomIdents,
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum = 6,
|
||||
orderedBasedOnExisting,
|
||||
orderedAllowZeroLevel,
|
||||
unorderedLevelHeadings,
|
||||
},
|
||||
} = this.settings;
|
||||
|
||||
if (ordered) {
|
||||
const file = this.getActiveFile(context.sourcePath);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceContent = await this.app.vault.cachedRead(file);
|
||||
const sourceArr = sourceContent.split("\n");
|
||||
if (sourceArr.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ignoreTopLevel = 0;
|
||||
if (orderedIgnoreSingle || orderedBasedOnExisting) {
|
||||
const queier = new Querier(orderedAllowZeroLevel);
|
||||
const heading = new Heading();
|
||||
for (let lineIndex = 1; lineIndex <= sourceArr.length; lineIndex++) {
|
||||
const lineText = sourceArr[lineIndex - 1];
|
||||
const nextLineIndex = lineIndex + 1;
|
||||
const nextLineText =
|
||||
nextLineIndex <= sourceArr.length
|
||||
? sourceArr[nextLineIndex - 1]
|
||||
: "";
|
||||
const level = heading.handler(lineIndex, lineText, nextLineText);
|
||||
if (level === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queier.handler(level);
|
||||
|
||||
ignoreTopLevel = queier.query(
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum
|
||||
);
|
||||
if (ignoreTopLevel === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const counter = new Counter({
|
||||
ordered: true,
|
||||
delimiter: orderedDelimiter,
|
||||
trailingDelimiter: orderedTrailingDelimiter,
|
||||
styleType: orderedStyleType,
|
||||
customIdents: getOrderedCustomIdents(orderedCustomIdents),
|
||||
specifiedString: orderedSpecifiedString,
|
||||
ignoreTopLevel,
|
||||
allowZeroLevel: orderedAllowZeroLevel,
|
||||
});
|
||||
const heading = new Heading();
|
||||
let headingIndex = 1;
|
||||
|
||||
headingElements.forEach((headingElement) => {
|
||||
const sectionInfo = context.getSectionInfo(headingElement);
|
||||
if (!sectionInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineStart = sectionInfo.lineStart + 1;
|
||||
if (lineStart > sourceArr.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (
|
||||
let lineIndex = headingIndex;
|
||||
lineIndex <= lineStart;
|
||||
lineIndex++
|
||||
) {
|
||||
const lineText = sourceArr[lineIndex - 1];
|
||||
const nextLineIndex = lineIndex + 1;
|
||||
const nextLineText =
|
||||
nextLineIndex <= sourceArr.length
|
||||
? sourceArr[nextLineIndex - 1]
|
||||
: "";
|
||||
const level = heading.handler(lineIndex, lineText, nextLineText);
|
||||
if (lineIndex === lineStart) {
|
||||
//? 1. When using the page preview feature to only reference fragments, the
|
||||
//? relative position of rows is incorrect.
|
||||
//? 2. In the split editing scenario, there may be a delay in the information
|
||||
//? on the reading tab.
|
||||
const elementLevel = queryHeadingLevelByElement(headingElement);
|
||||
if (elementLevel === level) {
|
||||
const decoratorContent = counter.decorator(level);
|
||||
decorateHTMLElement(
|
||||
headingElement,
|
||||
decoratorContent,
|
||||
opacity,
|
||||
position
|
||||
);
|
||||
}
|
||||
|
||||
headingIndex = lineIndex + 1;
|
||||
} else {
|
||||
counter.handler(level);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const counter = new Counter({
|
||||
ordered: false,
|
||||
levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings),
|
||||
});
|
||||
|
||||
headingElements.forEach((headingElement) => {
|
||||
const level = queryHeadingLevelByElement(headingElement);
|
||||
const decoratorContent = counter.decorator(level);
|
||||
decorateHTMLElement(
|
||||
headingElement,
|
||||
decoratorContent,
|
||||
opacity,
|
||||
position
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for metadata changes
|
||||
this.registerEvent(
|
||||
this.app.metadataCache.on(
|
||||
"changed",
|
||||
debounce(
|
||||
(file) => {
|
||||
//! Delay trigger rerender
|
||||
if (file && file.path === this.getActiveFile()?.path) {
|
||||
this.rerenderPreviewMarkdown(file);
|
||||
}
|
||||
},
|
||||
250,
|
||||
true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Listen for editor mode changes
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("active-leaf-change", () => {
|
||||
this.handleModeChange();
|
||||
this.loadOutlineComponents();
|
||||
this.loadQuietOutlineComponents();
|
||||
this.loadFileExplorerComponents();
|
||||
})
|
||||
);
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("layout-change", () => {
|
||||
this.handleModeChange();
|
||||
this.loadOutlineComponents();
|
||||
this.loadQuietOutlineComponents();
|
||||
this.loadFileExplorerComponents();
|
||||
})
|
||||
);
|
||||
|
||||
this.loadOutlineComponents();
|
||||
this.loadQuietOutlineComponents();
|
||||
this.loadFileExplorerComponents();
|
||||
|
||||
this.addSettingTab(new HeadingSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload(): void {
|
||||
this.unloadOutlineComponents();
|
||||
this.unloadQuietOutlineComponents();
|
||||
this.unloadFileExplorerComponents();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, defalutSettings(), await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings(state?: SettingsChangeState) {
|
||||
await this.saveData(this.settings);
|
||||
|
||||
if (state) {
|
||||
if (state.outline != undefined) {
|
||||
if (state.outline) {
|
||||
this.loadOutlineComponents();
|
||||
} else {
|
||||
this.unloadOutlineComponents();
|
||||
}
|
||||
}
|
||||
|
||||
if (state.quietOutline != undefined) {
|
||||
if (state.quietOutline) {
|
||||
this.loadQuietOutlineComponents();
|
||||
} else {
|
||||
this.unloadQuietOutlineComponents();
|
||||
}
|
||||
}
|
||||
|
||||
if (state.fileExplorer != undefined) {
|
||||
if (state.fileExplorer) {
|
||||
this.loadFileExplorerComponents();
|
||||
} else {
|
||||
this.unloadFileExplorerComponents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.debouncedSaveSettings();
|
||||
}
|
||||
|
||||
private loadOutlineComponents() {
|
||||
if (this.settings.enabledInOutline) {
|
||||
this.handleOutline();
|
||||
}
|
||||
}
|
||||
|
||||
private unloadOutlineComponents() {
|
||||
this.outlineComponents.forEach((child) => child.detach());
|
||||
this.outlineComponents = [];
|
||||
this.outlineIdSet.clear();
|
||||
}
|
||||
|
||||
private loadQuietOutlineComponents() {
|
||||
if (this.settings.enabledInQuietOutline) {
|
||||
this.handleQuietOutline();
|
||||
}
|
||||
}
|
||||
|
||||
private unloadQuietOutlineComponents() {
|
||||
this.quietOutlineComponents.forEach((child) => child.detach());
|
||||
this.quietOutlineComponents = [];
|
||||
this.quietOutlineIdSet.clear();
|
||||
}
|
||||
|
||||
private loadFileExplorerComponents() {
|
||||
if (this.settings.enabledInFileExplorer) {
|
||||
this.handleHeadingsInExplorer();
|
||||
}
|
||||
}
|
||||
|
||||
private unloadFileExplorerComponents() {
|
||||
this.fileExplorerComponents.forEach((child) => child.detach());
|
||||
this.fileExplorerComponents = [];
|
||||
this.fileExplorerIdSet.clear();
|
||||
}
|
||||
|
||||
private craeteHeadingViewPlugin(
|
||||
getPluginData: () => Promise<HeadingPluginData>
|
||||
) {
|
||||
return ViewPlugin.fromClass(
|
||||
class extends HeadingViewPlugin {
|
||||
constructor(view: EditorView) {
|
||||
super(view, getPluginData);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private handleModeChange() {
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (view) {
|
||||
//? "source" for editing view (Live Preview & Source mode),
|
||||
//? "preview" for reading view.
|
||||
const isLivePreview = view.getMode() === "source";
|
||||
|
||||
//* Get CodeMirror editor instance
|
||||
const cmEditor = (view.editor as ObsidianEditor).cm;
|
||||
if (cmEditor) {
|
||||
cmEditor.dispatch({
|
||||
effects: updateEditorMode.of(isLivePreview),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleOutline() {
|
||||
const leaves = this.app.workspace.getLeavesOfType("outline");
|
||||
leaves.forEach((leaf: ObsidianWorkspaceLeaf) => {
|
||||
const leafId = leaf.id;
|
||||
if (!leafId || this.outlineIdSet.has(leafId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = leaf.view;
|
||||
const viewContent = view.containerEl.querySelector<HTMLElement>(
|
||||
'[data-type="outline"] .view-content'
|
||||
);
|
||||
if (!viewContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.outlineIdSet.add(leafId);
|
||||
|
||||
const vc = new ViewChildComponent(
|
||||
leafId,
|
||||
view,
|
||||
viewContent,
|
||||
() => {
|
||||
const headingElements =
|
||||
viewContent.querySelectorAll<HTMLElement>(".tree-item");
|
||||
if (headingElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = view.getState();
|
||||
if (typeof state.file !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.app.vault.getFileByPath(state.file);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileCache = this.app.metadataCache.getFileCache(file);
|
||||
if (!fileCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheHeadings = fileCache.headings || [];
|
||||
if (cacheHeadings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frontmatter = fileCache.frontmatter;
|
||||
const metadataEnabled = this.getEnabledFromFrontmatter(
|
||||
"outline",
|
||||
frontmatter
|
||||
);
|
||||
|
||||
const {
|
||||
enabledInEachNote,
|
||||
opacity,
|
||||
position,
|
||||
ordered,
|
||||
orderedDelimiter,
|
||||
orderedTrailingDelimiter,
|
||||
orderedStyleType,
|
||||
orderedSpecifiedString,
|
||||
orderedCustomIdents,
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum = 6,
|
||||
orderedBasedOnExisting,
|
||||
orderedAllowZeroLevel,
|
||||
unorderedLevelHeadings,
|
||||
} = this.settings.outlineSettings;
|
||||
|
||||
if (metadataEnabled == null) {
|
||||
if (enabledInEachNote != undefined && !enabledInEachNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getEnabledFromBlacklist(state.file)) {
|
||||
return;
|
||||
}
|
||||
} else if (!metadataEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ignoreTopLevel = 0;
|
||||
if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) {
|
||||
const queier = new Querier(orderedAllowZeroLevel);
|
||||
for (const cacheHeading of cacheHeadings) {
|
||||
queier.handler(cacheHeading.level);
|
||||
ignoreTopLevel = queier.query(
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum
|
||||
);
|
||||
if (ignoreTopLevel === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const counter = new Counter({
|
||||
ordered,
|
||||
delimiter: orderedDelimiter,
|
||||
trailingDelimiter: orderedTrailingDelimiter,
|
||||
styleType: orderedStyleType,
|
||||
customIdents: getOrderedCustomIdents(orderedCustomIdents),
|
||||
specifiedString: orderedSpecifiedString,
|
||||
ignoreTopLevel,
|
||||
allowZeroLevel: orderedAllowZeroLevel,
|
||||
levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings),
|
||||
});
|
||||
|
||||
let lastCacheLevel = 0;
|
||||
let lastReadLevel = 0;
|
||||
for (
|
||||
let i = 0, j = 0;
|
||||
i < headingElements.length && j < cacheHeadings.length;
|
||||
i++, j++
|
||||
) {
|
||||
const readLevel = getTreeItemLevel(headingElements[i]);
|
||||
const readText = getTreeItemText(headingElements[i]);
|
||||
let cacheLevel = cacheHeadings[j].level;
|
||||
if (i > 0) {
|
||||
const diff = diffLevel(readLevel, lastReadLevel);
|
||||
while (
|
||||
j < cacheHeadings.length - 1 &&
|
||||
(diffLevel(cacheLevel, lastCacheLevel) !== diff ||
|
||||
!compareHeadingText(cacheHeadings[j].heading, readText))
|
||||
) {
|
||||
counter.handler(cacheLevel);
|
||||
j++;
|
||||
cacheLevel = cacheHeadings[j].level;
|
||||
}
|
||||
}
|
||||
|
||||
const decoratorContent = counter.decorator(cacheLevel);
|
||||
decorateOutlineElement(
|
||||
headingElements[i],
|
||||
decoratorContent,
|
||||
opacity,
|
||||
position
|
||||
);
|
||||
|
||||
lastCacheLevel = cacheLevel;
|
||||
lastReadLevel = readLevel;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
const headingElements =
|
||||
viewContent.querySelectorAll<HTMLElement>(".tree-item");
|
||||
headingElements.forEach((ele) => {
|
||||
cancelOutlineDecorator(ele);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.outlineComponents.push(vc);
|
||||
view.addChild(vc);
|
||||
view.register(() => {
|
||||
this.outlineComponents = this.outlineComponents.filter(
|
||||
(item) => !item.equal(leafId)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private handleQuietOutline() {
|
||||
const leaves = this.app.workspace.getLeavesOfType("quiet-outline");
|
||||
leaves.forEach((leaf: ObsidianWorkspaceLeaf) => {
|
||||
const leafId = leaf.id;
|
||||
if (!leafId || this.quietOutlineIdSet.has(leafId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = leaf.view;
|
||||
const viewContent = view.containerEl.querySelector<HTMLElement>(
|
||||
'[data-type="quiet-outline"] .view-content'
|
||||
);
|
||||
if (!viewContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.quietOutlineIdSet.add(leafId);
|
||||
|
||||
const vc = new ViewChildComponent(
|
||||
leafId,
|
||||
view,
|
||||
viewContent,
|
||||
() => {
|
||||
const containerElement =
|
||||
viewContent.querySelector<HTMLElement>(".n-tree");
|
||||
if (!containerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headingELements =
|
||||
viewContent.querySelectorAll<HTMLElement>(".n-tree-node");
|
||||
if (headingELements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.getActiveFile();
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileCache = this.app.metadataCache.getFileCache(file);
|
||||
if (!fileCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frontmatter = fileCache.frontmatter;
|
||||
const metadataEnabled = this.getEnabledFromFrontmatter(
|
||||
"quiet-outline",
|
||||
frontmatter
|
||||
);
|
||||
|
||||
const { enabledInEachNote } = this.settings.quietOutlineSettings;
|
||||
|
||||
let enabled = true;
|
||||
if (metadataEnabled == null) {
|
||||
if (enabledInEachNote != undefined && !enabledInEachNote) {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
if (this.getEnabledFromBlacklist(file.path)) {
|
||||
enabled = false;
|
||||
}
|
||||
} else if (!metadataEnabled) {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
cancelQuietOutlineDecoration(containerElement);
|
||||
return;
|
||||
}
|
||||
|
||||
quietOutlineHandler(
|
||||
this.settings.quietOutlineSettings,
|
||||
containerElement,
|
||||
headingELements
|
||||
);
|
||||
},
|
||||
() => {
|
||||
const containerElement =
|
||||
viewContent.querySelector<HTMLElement>(".n-tree");
|
||||
if (containerElement) {
|
||||
cancelQuietOutlineDecoration(containerElement);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.quietOutlineComponents.push(vc);
|
||||
view.addChild(vc);
|
||||
view.register(() => {
|
||||
this.quietOutlineComponents = this.quietOutlineComponents.filter(
|
||||
(item) => !item.equal(leafId)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private handleHeadingsInExplorer() {
|
||||
const leaves = this.app.workspace.getLeavesOfType("file-explorer");
|
||||
leaves.forEach((leaf: ObsidianWorkspaceLeaf) => {
|
||||
const leafId = leaf.id;
|
||||
if (!leafId || this.fileExplorerIdSet.has(leafId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = leaf.view;
|
||||
const navFilesContainer = view.containerEl.querySelector<HTMLElement>(
|
||||
'[data-type="file-explorer"] .nav-files-container'
|
||||
);
|
||||
if (!navFilesContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileExplorerIdSet.add(leafId);
|
||||
|
||||
const vc = new ViewChildComponent(
|
||||
leafId,
|
||||
view,
|
||||
navFilesContainer,
|
||||
() => {
|
||||
const navFileTitles =
|
||||
navFilesContainer.querySelectorAll<HTMLElement>(".nav-file-title");
|
||||
|
||||
navFileTitles.forEach((navFile) => {
|
||||
const headingElements = navFile.querySelectorAll<HTMLElement>(
|
||||
".file-heading-container .clickable-heading"
|
||||
);
|
||||
if (headingElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = navFile.dataset.path;
|
||||
if (!filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.app.vault.getFileByPath(filePath);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileCache = this.app.metadataCache.getFileCache(file);
|
||||
if (!fileCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheHeadings = fileCache.headings || [];
|
||||
if (cacheHeadings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frontmatter = fileCache.frontmatter;
|
||||
const metadataEnabled = this.getEnabledFromFrontmatter(
|
||||
"file-explorer",
|
||||
frontmatter
|
||||
);
|
||||
|
||||
const {
|
||||
enabledInEachNote,
|
||||
opacity,
|
||||
position,
|
||||
ordered,
|
||||
orderedDelimiter,
|
||||
orderedTrailingDelimiter,
|
||||
orderedStyleType,
|
||||
orderedSpecifiedString,
|
||||
orderedCustomIdents,
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum = 6,
|
||||
orderedBasedOnExisting,
|
||||
orderedAllowZeroLevel,
|
||||
unorderedLevelHeadings,
|
||||
} = this.settings.fileExplorerSettings;
|
||||
|
||||
if (metadataEnabled == null) {
|
||||
if (enabledInEachNote != undefined && !enabledInEachNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getEnabledFromBlacklist(filePath)) {
|
||||
return;
|
||||
}
|
||||
} else if (!metadataEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ignoreTopLevel = 0;
|
||||
if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) {
|
||||
const queier = new Querier(orderedAllowZeroLevel);
|
||||
for (const cacheHeading of cacheHeadings) {
|
||||
queier.handler(cacheHeading.level);
|
||||
ignoreTopLevel = queier.query(
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum
|
||||
);
|
||||
if (ignoreTopLevel === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const counter = new Counter({
|
||||
ordered,
|
||||
delimiter: orderedDelimiter,
|
||||
trailingDelimiter: orderedTrailingDelimiter,
|
||||
styleType: orderedStyleType,
|
||||
customIdents: getOrderedCustomIdents(orderedCustomIdents),
|
||||
specifiedString: orderedSpecifiedString,
|
||||
ignoreTopLevel,
|
||||
allowZeroLevel: orderedAllowZeroLevel,
|
||||
levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings),
|
||||
});
|
||||
|
||||
const marginMultiplier =
|
||||
parseInt(
|
||||
getComputedStyle(document.body).getPropertyValue(
|
||||
"--clickable-heading-margin-multiplier"
|
||||
)
|
||||
) || 10;
|
||||
|
||||
for (
|
||||
let i = 0, j = 0;
|
||||
i < headingElements.length && j < cacheHeadings.length;
|
||||
i++, j++
|
||||
) {
|
||||
const readLevel = getFileHeadingItemLevel(
|
||||
headingElements[i],
|
||||
marginMultiplier
|
||||
);
|
||||
const readText = headingElements[i].innerText;
|
||||
let cacheLevel = cacheHeadings[j].level;
|
||||
|
||||
while (
|
||||
j < cacheHeadings.length - 1 &&
|
||||
(cacheLevel !== readLevel ||
|
||||
cacheHeadings[j].heading !== readText)
|
||||
) {
|
||||
counter.handler(cacheLevel);
|
||||
j++;
|
||||
cacheLevel = cacheHeadings[j].level;
|
||||
}
|
||||
|
||||
const decoratorContent = counter.decorator(cacheLevel);
|
||||
decorateFileHeadingElement(
|
||||
headingElements[i],
|
||||
decoratorContent,
|
||||
opacity,
|
||||
position
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
() => {
|
||||
const headingElements =
|
||||
navFilesContainer.querySelectorAll<HTMLElement>(
|
||||
".nav-file-title .file-heading-container .clickable-heading"
|
||||
);
|
||||
headingElements.forEach((ele) => {
|
||||
cancelFileHeadingDecorator(ele);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.fileExplorerComponents.push(vc);
|
||||
view.addChild(vc);
|
||||
view.register(() => {
|
||||
this.fileExplorerComponents = this.fileExplorerComponents.filter(
|
||||
(item) => !item.equal(leafId)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private debouncedSaveSettings = debounce(
|
||||
this.rerenderPreviewMarkdown.bind(this),
|
||||
1000,
|
||||
true
|
||||
);
|
||||
|
||||
/**
|
||||
* Rerender Preview Markdown.
|
||||
* @param file
|
||||
*/
|
||||
private rerenderPreviewMarkdown(file?: TFile) {
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (view) {
|
||||
if (!file || file === view.file) {
|
||||
view.previewMode.rerender(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getPluginData(): Promise<HeadingPluginData> {
|
||||
const {
|
||||
metadataKeyword,
|
||||
enabledInPreview: _enabledInPreview,
|
||||
enabledInSource: _enabledInSource,
|
||||
previewSettings,
|
||||
sourceSettings,
|
||||
} = this.settings;
|
||||
|
||||
let enabledInPreview = _enabledInPreview;
|
||||
let enabledInSource = _enabledInSource;
|
||||
|
||||
if (metadataKeyword) {
|
||||
const file = this.getActiveFile();
|
||||
if (file) {
|
||||
const frontmatter =
|
||||
this.app.metadataCache.getFileCache(file)?.frontmatter;
|
||||
|
||||
if (_enabledInPreview) {
|
||||
enabledInPreview =
|
||||
this.getEnabledFromFrontmatter("preview", frontmatter) ??
|
||||
previewSettings.enabledInEachNote ??
|
||||
!this.getEnabledFromBlacklist(file.path);
|
||||
}
|
||||
|
||||
if (_enabledInSource) {
|
||||
enabledInSource =
|
||||
this.getEnabledFromFrontmatter("source", frontmatter) ??
|
||||
sourceSettings.enabledInEachNote ??
|
||||
!this.getEnabledFromBlacklist(file.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enabledInPreview,
|
||||
enabledInSource,
|
||||
previewSettings,
|
||||
sourceSettings,
|
||||
};
|
||||
}
|
||||
|
||||
private getActiveFile(sourcePath?: string) {
|
||||
if (sourcePath) {
|
||||
return this.app.vault.getFileByPath(sourcePath);
|
||||
} else {
|
||||
return this.app.workspace.getActiveFile();
|
||||
}
|
||||
}
|
||||
|
||||
private getEnabledFromFrontmatter(
|
||||
mode: HeadingMetadataSettingsType,
|
||||
frontmatter?: FrontMatterCache
|
||||
): null | boolean {
|
||||
const keyword = this.settings.metadataKeyword;
|
||||
if (keyword && typeof frontmatter === "object" && frontmatter) {
|
||||
const metadataSettings = frontmatter[keyword];
|
||||
if (typeof metadataSettings === "object" && metadataSettings) {
|
||||
return (
|
||||
getBoolean(metadataSettings[mode]) ?? getBoolean(metadataSettings.all)
|
||||
);
|
||||
}
|
||||
if (metadataSettings != null) {
|
||||
return getBoolean(metadataSettings);
|
||||
} else {
|
||||
const cssclasses = frontmatter.cssclasses;
|
||||
if (Array.isArray(cssclasses)) {
|
||||
const cssStatus: CSSEnabledStatus = {
|
||||
mode: null,
|
||||
all: null,
|
||||
};
|
||||
|
||||
for (const cssItem of cssclasses) {
|
||||
if (typeof cssItem === "string") {
|
||||
const { mode: m, all: a } = checkEnabledCSS(cssItem, mode);
|
||||
if (m != null) {
|
||||
cssStatus.mode = m;
|
||||
}
|
||||
if (a != null) {
|
||||
cssStatus.all = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cssStatus.mode ?? cssStatus.all;
|
||||
} else if (typeof cssclasses === "string") {
|
||||
const { mode: m, all: a } = checkEnabledCSS(cssclasses, mode);
|
||||
return m ?? a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private getEnabledFromBlacklist(filepath: string): boolean {
|
||||
for (const folder of this.settings.folderBlacklist) {
|
||||
if (filepath.startsWith(`${folder}/`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const filename = filepath.substring(
|
||||
filepath.lastIndexOf("/") + 1,
|
||||
filepath.lastIndexOf(".")
|
||||
);
|
||||
for (const regexStr of this.settings.fileRegexBlacklist) {
|
||||
const regex = stringToRegex(regexStr);
|
||||
if (regex) {
|
||||
if (regex.test(filename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
667
components/setting-tab.ts
Normal file
667
components/setting-tab.ts
Normal file
|
|
@ -0,0 +1,667 @@
|
|||
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||
import type { HeadingPlugin } from "./plugin";
|
||||
import type { OrderedCounterStyleType } from "../common/data";
|
||||
import { orderedStyleTypeOptions } from "../common/data";
|
||||
import { FolderSuggest } from "./suggest";
|
||||
|
||||
export class HeadingSettingTab extends PluginSettingTab {
|
||||
plugin: HeadingPlugin;
|
||||
|
||||
constructor(app: App, plugin: HeadingPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
//* metadataKeyword
|
||||
const metadataKeywordSetting = new Setting(containerEl)
|
||||
.setName("Metadata keyword")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Enter the keyword")
|
||||
.setValue(this.plugin.settings.metadataKeyword)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.metadataKeyword = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
const metadataKeywordDesc = createFragment();
|
||||
metadataKeywordDesc.append(
|
||||
"The key name that reads the enabled status from the ",
|
||||
createEl("a", {
|
||||
href: "https://help.obsidian.md/Editing+and+formatting/Properties",
|
||||
text: "properties",
|
||||
}),
|
||||
"."
|
||||
);
|
||||
metadataKeywordSetting.descEl.appendChild(metadataKeywordDesc);
|
||||
|
||||
new Setting(containerEl).setName("Reading view").setHeading();
|
||||
|
||||
//* enabledInReading
|
||||
new Setting(containerEl)
|
||||
.setName("Enabled in reading view")
|
||||
.setDesc("Allow to decorate the heading under the reading view.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabledInReading)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledInReading = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Manage reading view heading decorator")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageHeadingDecoratorSettings("readingSettings");
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Live preview").setHeading();
|
||||
|
||||
//* enabledInPreview
|
||||
new Setting(containerEl)
|
||||
.setName("Enabled in live preview")
|
||||
.setDesc("Allow to decorate the heading under the live preview.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabledInPreview)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledInPreview = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Manage live preview heading decorator")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageHeadingDecoratorSettings("previewSettings");
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Source mode").setHeading();
|
||||
|
||||
//* enabledInSource
|
||||
new Setting(containerEl)
|
||||
.setName("Enabled in source mode")
|
||||
.setDesc("Allow to decorate the heading under the source mode.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabledInSource)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledInSource = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Manage source mode heading decorator")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageHeadingDecoratorSettings("sourceSettings");
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Outline plugin").setHeading();
|
||||
|
||||
//* enabledInOutline
|
||||
new Setting(containerEl)
|
||||
.setName("Enabled in outline plugin")
|
||||
.setDesc("Allow to decorate the heading under the outline plugin.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabledInOutline)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledInOutline = value;
|
||||
await this.plugin.saveSettings({
|
||||
outline: this.plugin.settings.enabledInOutline,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Manage outline plugin heading decorator")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageHeadingDecoratorSettings("outlineSettings");
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Quiet Outline plugin").setHeading();
|
||||
|
||||
//* enabledInQuietOutline
|
||||
const enabledInQuietOutlineSetting = new Setting(containerEl)
|
||||
.setName('Enabled in "Quiet Outline" plugin')
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabledInQuietOutline)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledInQuietOutline = value;
|
||||
await this.plugin.saveSettings({
|
||||
quietOutline: this.plugin.settings.enabledInQuietOutline,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const enabledInQuietOutlineDesc = createFragment();
|
||||
enabledInQuietOutlineDesc.append(
|
||||
"Allow to decorate the heading under the ",
|
||||
createEl("a", {
|
||||
href: "https://github.com/guopenghui/obsidian-quiet-outline",
|
||||
text: "Quiet Outline",
|
||||
}),
|
||||
" plugin."
|
||||
);
|
||||
enabledInQuietOutlineSetting.descEl.appendChild(enabledInQuietOutlineDesc);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Manage "Quiet Outline" plugin heading decorator')
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageHeadingDecoratorSettings("quietOutlineSettings");
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Headings in Explorer plugin")
|
||||
.setHeading();
|
||||
|
||||
//* enabledInFileExplorer
|
||||
const enabledInFileExplorerSetting = new Setting(containerEl)
|
||||
.setName('Enabled in "Headings in Explorer" plugin')
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabledInFileExplorer)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledInFileExplorer = value;
|
||||
await this.plugin.saveSettings({
|
||||
fileExplorer: this.plugin.settings.enabledInFileExplorer,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const enabledInFileExplorerDesc = createFragment();
|
||||
enabledInFileExplorerDesc.append(
|
||||
"Allow to decorate the heading under the ",
|
||||
createEl("a", {
|
||||
href: "https://github.com/patrickchiang/obsidian-headings-in-explorer",
|
||||
text: "Headings in Explorer",
|
||||
}),
|
||||
" plugin."
|
||||
);
|
||||
enabledInFileExplorerSetting.descEl.appendChild(enabledInFileExplorerDesc);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Manage "Headings in Explorer" plugin heading decorator')
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageHeadingDecoratorSettings("fileExplorerSettings");
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Blocklist").setHeading();
|
||||
|
||||
//* folderBlacklist
|
||||
new Setting(containerEl)
|
||||
.setName("Manage folder blocklist")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageFolderBlacklist(true);
|
||||
});
|
||||
});
|
||||
|
||||
//* fileRegexBlacklist
|
||||
new Setting(containerEl)
|
||||
.setName("Manage note name regex blocklist")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Manage").onClick(() => {
|
||||
this.manageFileRegexBlacklist(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private isOpacityValue(value: number): value is OpacityOptions {
|
||||
if (value >= 10 && value <= 100 && value % 10 === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isPositionValue(value: string): value is PostionOptions {
|
||||
return ["before", "after", "before-inside", "after-inside"].includes(value);
|
||||
}
|
||||
|
||||
private manageHeadingDecoratorSettings(
|
||||
settingsType: PluginDecoratorSettingsType
|
||||
) {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
let tabName = "";
|
||||
switch (settingsType) {
|
||||
case "readingSettings":
|
||||
tabName = "Manage reading view heading decorator";
|
||||
break;
|
||||
case "previewSettings":
|
||||
tabName = "Manage live preview heading decorator";
|
||||
break;
|
||||
case "sourceSettings":
|
||||
tabName = "Manage source mode heading decorator";
|
||||
break;
|
||||
case "outlineSettings":
|
||||
tabName = "Manage outline plugin heading decorator";
|
||||
break;
|
||||
case "quietOutlineSettings":
|
||||
tabName = 'Manage "Quiet Outline" plugin heading decorator';
|
||||
break;
|
||||
case "fileExplorerSettings":
|
||||
tabName = 'Manage "Headings in Explorer" plugin heading decorator';
|
||||
break;
|
||||
}
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(tabName)
|
||||
.setHeading()
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Back").onClick(() => {
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
|
||||
//* enabledInEachNote
|
||||
new Setting(containerEl)
|
||||
.setName("Enabled in each note")
|
||||
.setDesc(
|
||||
"Toggle this setting to enable the decoration of headings in each note."
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(
|
||||
this.plugin.settings[settingsType].enabledInEachNote ?? true
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].enabledInEachNote = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl).setName("Effect").setHeading();
|
||||
|
||||
//* ordered
|
||||
new Setting(containerEl)
|
||||
.setName("Ordered")
|
||||
.setDesc(
|
||||
"Toggle this setting to enable the decoration of headings as an ordered or unordered list."
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings[settingsType].ordered)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].ordered = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* opacity
|
||||
new Setting(containerEl)
|
||||
.setName("Opacity")
|
||||
.setDesc(
|
||||
"Set the opacity of the heading decorator. The value is the form of percentage."
|
||||
)
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(10, 100, 10)
|
||||
.setValue(this.plugin.settings[settingsType].opacity)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].opacity = this.isOpacityValue(
|
||||
value
|
||||
)
|
||||
? value
|
||||
: 20;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
.setDynamicTooltip()
|
||||
);
|
||||
|
||||
//* position
|
||||
new Setting(containerEl)
|
||||
.setName("Position")
|
||||
.setDesc("Set the position of the heading decorator.")
|
||||
.addDropdown((dropdown) => {
|
||||
const options: Record<string, string> =
|
||||
settingsType === "outlineSettings" ||
|
||||
settingsType === "quietOutlineSettings" ||
|
||||
settingsType === "fileExplorerSettings"
|
||||
? {
|
||||
before: "Before the heading",
|
||||
after: "After the heading",
|
||||
}
|
||||
: {
|
||||
before: "Before the heading",
|
||||
"before-inside": "Before the heading (inside)",
|
||||
after: "After the heading",
|
||||
"after-inside": "After the heading (inside)",
|
||||
};
|
||||
|
||||
dropdown
|
||||
.addOptions(options)
|
||||
.setValue(this.plugin.settings[settingsType].position)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].position = this.isPositionValue(
|
||||
value
|
||||
)
|
||||
? value
|
||||
: "before";
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Ordered").setHeading();
|
||||
|
||||
//* orderedStyleType
|
||||
new Setting(containerEl)
|
||||
.setName("Style type")
|
||||
.setDesc("Set the style type for ordered list.")
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOptions(orderedStyleTypeOptions)
|
||||
.setValue(this.plugin.settings[settingsType].orderedStyleType)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedStyleType =
|
||||
value as OrderedCounterStyleType;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedDelimiter
|
||||
new Setting(containerEl)
|
||||
.setName("Delimiter")
|
||||
.setDesc("Set the delimiter for ordered list.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings[settingsType].orderedDelimiter)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedDelimiter = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedTrailingDelimiter
|
||||
new Setting(containerEl)
|
||||
.setName("Trailing delimiter")
|
||||
.setDesc("Set whether to add a trailing delimiter for ordered list.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings[settingsType].orderedTrailingDelimiter)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedTrailingDelimiter = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedCustomIdents
|
||||
new Setting(containerEl)
|
||||
.setName("Custom list styles")
|
||||
.setDesc(
|
||||
'For the "Custom list styles" option. Set custom list styles for ordered list. Use spaces to separate entries.'
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings[settingsType].orderedCustomIdents)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedCustomIdents = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedSpecifiedString
|
||||
new Setting(containerEl)
|
||||
.setName("Specified string")
|
||||
.setDesc(
|
||||
'For the "Specified string" option. Set a specified string for ordered list.'
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings[settingsType].orderedSpecifiedString)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedSpecifiedString = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedAllowZeroLevel
|
||||
new Setting(containerEl)
|
||||
.setName("Allow zero level")
|
||||
.setDesc(
|
||||
"If the next heading is more than one level higher, the omitted level is zero instead of one."
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(
|
||||
this.plugin.settings[settingsType].orderedAllowZeroLevel ?? false
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedAllowZeroLevel = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedBasedOnExisting
|
||||
new Setting(containerEl)
|
||||
.setName("Based on the existing highest level")
|
||||
.setDesc(
|
||||
"Use the highest level of headings in the note as the base for ordered list."
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(
|
||||
this.plugin.settings[settingsType].orderedBasedOnExisting ?? false
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedBasedOnExisting = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedIgnoreSingle
|
||||
new Setting(containerEl)
|
||||
.setName("Ignore the single heading at the top-level")
|
||||
.setDesc(
|
||||
"If the top-level has only a single heading, exclude it when building an ordered list."
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings[settingsType].orderedIgnoreSingle)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedIgnoreSingle = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
//* orderedIgnoreMaximum
|
||||
new Setting(containerEl)
|
||||
.setName("The maximum number of ignored")
|
||||
.setDesc(
|
||||
"For enabled: Ignore the single heading at the top-level. The maximum number of ignored headings at the top-level."
|
||||
)
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(1, 6, 1)
|
||||
.setValue(
|
||||
this.plugin.settings[settingsType].orderedIgnoreMaximum ?? 6
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].orderedIgnoreMaximum = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
.setDynamicTooltip()
|
||||
);
|
||||
|
||||
new Setting(containerEl).setName("Unordered").setHeading();
|
||||
|
||||
//* unorderedLevelHeadings
|
||||
new Setting(containerEl)
|
||||
.setName("Level headings")
|
||||
.setDesc(
|
||||
"Set the names for each level. The value is 6 entries separated by spaces."
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings[settingsType].unorderedLevelHeadings)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].unorderedLevelHeadings = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
if (settingsType === "sourceSettings") {
|
||||
new Setting(containerEl).setName("Other").setHeading();
|
||||
|
||||
//* hideNumberSigns
|
||||
new Setting(containerEl)
|
||||
.setName("Hide number signs on inactive lines")
|
||||
.setDesc(
|
||||
"Hide number signs (#) on inactive lines similar to live preview."
|
||||
)
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(
|
||||
this.plugin.settings[settingsType].hideNumberSigns ?? false
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings[settingsType].hideNumberSigns = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//* Scroll back to the top
|
||||
containerEl.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
private manageFolderBlacklist(scrollToTop = false) {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Manage folder blocklist")
|
||||
.setHeading()
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Back").onClick(() => {
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
|
||||
this.plugin.settings.folderBlacklist.forEach((folder, index) => {
|
||||
new Setting(containerEl)
|
||||
.setName(`Folder blocklist ${index + 1}`)
|
||||
.addText((text) => {
|
||||
text.setValue(folder).onChange(async (value) => {
|
||||
this.plugin.settings.folderBlacklist[index] = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
const suggest = new FolderSuggest(this.app, text.inputEl);
|
||||
suggest.onSelect(async (value) => {
|
||||
text.setValue(value);
|
||||
this.plugin.settings.folderBlacklist[index] = value;
|
||||
suggest.close();
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Delete")
|
||||
.setWarning()
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.folderBlacklist.splice(index, 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.manageFolderBlacklist();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).addButton((button) => {
|
||||
button
|
||||
.setButtonText("Add")
|
||||
.setCta()
|
||||
.setTooltip("Add a new folder to the blocklist")
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.folderBlacklist.push("");
|
||||
await this.plugin.saveSettings();
|
||||
this.manageFolderBlacklist();
|
||||
});
|
||||
});
|
||||
|
||||
//* Scroll back to the top
|
||||
if (scrollToTop) {
|
||||
containerEl.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
|
||||
private manageFileRegexBlacklist(scrollToTop = false) {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Manage note name regex blocklist")
|
||||
.setHeading()
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Back").onClick(() => {
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
|
||||
this.plugin.settings.fileRegexBlacklist.forEach((regex, index) => {
|
||||
new Setting(containerEl)
|
||||
.setName(`Note name regex blocklist ${index + 1}`)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g., /^daily.*/i")
|
||||
.setValue(regex)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.fileRegexBlacklist[index] = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
)
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Delete")
|
||||
.setWarning()
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.fileRegexBlacklist.splice(index, 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.manageFileRegexBlacklist();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl).addButton((button) => {
|
||||
button
|
||||
.setButtonText("Add")
|
||||
.setCta()
|
||||
.setTooltip("Add a new note name regex blocklist")
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.fileRegexBlacklist.push("");
|
||||
await this.plugin.saveSettings();
|
||||
this.manageFileRegexBlacklist();
|
||||
});
|
||||
});
|
||||
|
||||
//* Scroll back to the top
|
||||
if (scrollToTop) {
|
||||
containerEl.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue