mirror of
https://github.com/dragonish/obsidian-heading-decorator.git
synced 2026-07-22 05:42:05 +00:00
refactor(reading): extract the processing code of the reading view into a file
This commit is contained in:
parent
55729c1e13
commit
d420ddd3e2
5 changed files with 258 additions and 241 deletions
|
|
@ -265,6 +265,35 @@ export function getOrderedCustomIdents(value: string) {
|
|||
return value.split(/\s+/g).filter((v) => v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for a given position.
|
||||
*
|
||||
* @param position The position to get the class name for.
|
||||
* @param ignoreInsideFlag Whether to ignore the inside flag.
|
||||
* @returns The class name.
|
||||
*/
|
||||
export function getPositionClassName(
|
||||
position: PostionOptions,
|
||||
ignoreInsideFlag?: boolean
|
||||
): string {
|
||||
switch (position) {
|
||||
case "before":
|
||||
return beforeDecoratorClassName;
|
||||
case "after":
|
||||
return afterDecoratorClassName;
|
||||
case "before-inside":
|
||||
return ignoreInsideFlag
|
||||
? beforeDecoratorClassName
|
||||
: beforeInsideDecoratorClassName;
|
||||
case "after-inside":
|
||||
return ignoreInsideFlag
|
||||
? afterDecoratorClassName
|
||||
: afterInsideDecoratorClassName;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff level between two numbers.
|
||||
*
|
||||
|
|
|
|||
116
common/dom.ts
116
common/dom.ts
|
|
@ -1,116 +0,0 @@
|
|||
import {
|
||||
headingDecoratorClassName,
|
||||
readingHeadingDecoratorClassName,
|
||||
beforeDecoratorClassName,
|
||||
beforeInsideDecoratorClassName,
|
||||
afterDecoratorClassName,
|
||||
afterInsideDecoratorClassName,
|
||||
} from "./data";
|
||||
|
||||
/**
|
||||
* Query the heading level of an HTML element.
|
||||
*
|
||||
* @param element
|
||||
* @returns Return `-1` for non-heading elements.
|
||||
*/
|
||||
export function queryHeadingLevelByElement(element: HTMLElement): number {
|
||||
switch (element.tagName.toLowerCase()) {
|
||||
case "h1":
|
||||
return 1;
|
||||
case "h2":
|
||||
return 2;
|
||||
case "h3":
|
||||
return 3;
|
||||
case "h4":
|
||||
return 4;
|
||||
case "h5":
|
||||
return 5;
|
||||
case "h6":
|
||||
return 6;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for a given position.
|
||||
*
|
||||
* @param position The position to get the class name for.
|
||||
* @param ignoreInsideFlag Whether to ignore the inside flag.
|
||||
* @returns The class name.
|
||||
*/
|
||||
export function getPositionClassName(
|
||||
position: PostionOptions,
|
||||
ignoreInsideFlag?: boolean
|
||||
): string {
|
||||
switch (position) {
|
||||
case "before":
|
||||
return beforeDecoratorClassName;
|
||||
case "after":
|
||||
return afterDecoratorClassName;
|
||||
case "before-inside":
|
||||
return ignoreInsideFlag
|
||||
? beforeDecoratorClassName
|
||||
: beforeInsideDecoratorClassName;
|
||||
case "after-inside":
|
||||
return ignoreInsideFlag
|
||||
? afterDecoratorClassName
|
||||
: afterInsideDecoratorClassName;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate an HTML element with a given content, opacity and position.
|
||||
*
|
||||
* @param element The HTML element to decorate.
|
||||
* @param content The content to decorate with.
|
||||
* @param opacity The opacity of the decorator.
|
||||
* @param position The position of the decorator.
|
||||
*/
|
||||
export function decorateHTMLElement(
|
||||
element: HTMLElement,
|
||||
content: string,
|
||||
opacity: OpacityOptions,
|
||||
position: PostionOptions
|
||||
): void {
|
||||
if (content) {
|
||||
const span = element.createSpan({
|
||||
cls: [
|
||||
headingDecoratorClassName,
|
||||
readingHeadingDecoratorClassName,
|
||||
getPositionClassName(position),
|
||||
],
|
||||
text: content,
|
||||
attr: {
|
||||
"data-decorator-opacity": `${opacity}%`,
|
||||
},
|
||||
});
|
||||
|
||||
if (position === "before-inside") {
|
||||
const headingCollapseIndicator = element.find(
|
||||
".heading-collapse-indicator"
|
||||
);
|
||||
if (headingCollapseIndicator) {
|
||||
headingCollapseIndicator.after(span);
|
||||
} else {
|
||||
const firstChild = element.firstChild;
|
||||
if (firstChild) {
|
||||
firstChild.before(span);
|
||||
} else {
|
||||
element.appendChild(span);
|
||||
}
|
||||
}
|
||||
} else if (position === "before") {
|
||||
const firstChild = element.firstChild;
|
||||
if (firstChild) {
|
||||
firstChild.before(span);
|
||||
} else {
|
||||
element.appendChild(span);
|
||||
}
|
||||
} else {
|
||||
element.appendChild(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,16 +11,11 @@ import { EditorView, ViewPlugin } from "@codemirror/view";
|
|||
import type { HeadingPluginSettings, HeadingPluginData } from "../common/data";
|
||||
import {
|
||||
headingsSelector,
|
||||
getUnorderedLevelHeadings,
|
||||
getOrderedCustomIdents,
|
||||
getBoolean,
|
||||
checkEnabledCSS,
|
||||
stringToRegex,
|
||||
defalutSettings,
|
||||
} from "../common/data";
|
||||
import { Counter, Querier } from "../common/counter";
|
||||
import { Heading } from "../common/heading";
|
||||
import { decorateHTMLElement, queryHeadingLevelByElement } from "../common/dom";
|
||||
import {
|
||||
HeadingEditorViewPlugin,
|
||||
headingDecorationsField,
|
||||
|
|
@ -28,6 +23,7 @@ import {
|
|||
updateEditorMode,
|
||||
} from "./editor";
|
||||
import { ViewChildComponent } from "./child";
|
||||
import { readingOrderedHandler, readingUnorderedHandler } from "./reading";
|
||||
import { outlineHandler, cancelOutlineDecoration } from "./outline";
|
||||
import {
|
||||
quietOutlineHandler,
|
||||
|
|
@ -197,24 +193,7 @@ export class HeadingPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
readingSettings: {
|
||||
opacity,
|
||||
position,
|
||||
ordered,
|
||||
orderedDelimiter,
|
||||
orderedTrailingDelimiter,
|
||||
orderedStyleType,
|
||||
orderedSpecifiedString,
|
||||
orderedCustomIdents,
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum = 6,
|
||||
orderedBasedOnExisting,
|
||||
orderedAllowZeroLevel,
|
||||
unorderedLevelHeadings,
|
||||
},
|
||||
} = this.settings;
|
||||
|
||||
const { ordered } = this.settings.readingSettings;
|
||||
if (ordered) {
|
||||
const file = this.getActiveFile(context.sourcePath);
|
||||
if (!file) {
|
||||
|
|
@ -227,108 +206,14 @@ export class HeadingPlugin extends Plugin {
|
|||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
readingOrderedHandler(
|
||||
this.settings.readingSettings,
|
||||
context,
|
||||
headingElements,
|
||||
sourceArr
|
||||
);
|
||||
} 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
|
||||
);
|
||||
});
|
||||
readingUnorderedHandler(this.settings.readingSettings, headingElements);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
219
components/reading.ts
Normal file
219
components/reading.ts
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import { MarkdownPostProcessorContext } from "obsidian";
|
||||
import type { HeadingDecoratorSettings } from "../common/data";
|
||||
import {
|
||||
headingDecoratorClassName,
|
||||
readingHeadingDecoratorClassName,
|
||||
getOrderedCustomIdents,
|
||||
getPositionClassName,
|
||||
getUnorderedLevelHeadings,
|
||||
} from "../common/data";
|
||||
import { Heading } from "../common/heading";
|
||||
import { Querier, Counter } from "../common/counter";
|
||||
|
||||
/**
|
||||
* Handles ordered headings of reading view.
|
||||
*
|
||||
* @param settings The heading decorator settings.
|
||||
* @param context The markdown post processor context.
|
||||
* @param headingElements The heading elements to be processed.
|
||||
* @param sourceArr The source content of the file as an array of lines.
|
||||
*/
|
||||
export function readingOrderedHandler(
|
||||
settings: HeadingDecoratorSettings,
|
||||
context: MarkdownPostProcessorContext,
|
||||
headingElements: HTMLElement[],
|
||||
sourceArr: string[]
|
||||
): void {
|
||||
const {
|
||||
opacity,
|
||||
position,
|
||||
orderedDelimiter,
|
||||
orderedTrailingDelimiter,
|
||||
orderedStyleType,
|
||||
orderedSpecifiedString,
|
||||
orderedCustomIdents,
|
||||
orderedIgnoreSingle,
|
||||
orderedIgnoreMaximum = 6,
|
||||
orderedBasedOnExisting,
|
||||
orderedAllowZeroLevel,
|
||||
} = settings;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unordered headings of reading view.
|
||||
*
|
||||
* @param settings The heading decorator settings.
|
||||
* @param headingElements The unordered heading elements to be processed.
|
||||
*/
|
||||
export function readingUnorderedHandler(
|
||||
settings: HeadingDecoratorSettings,
|
||||
headingElements: HTMLElement[]
|
||||
): void {
|
||||
const { opacity, position, unorderedLevelHeadings } = settings;
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the heading level of an HTML element.
|
||||
*
|
||||
* @param element
|
||||
* @returns Return `-1` for non-heading elements.
|
||||
*/
|
||||
function queryHeadingLevelByElement(element: HTMLElement): number {
|
||||
switch (element.tagName.toLowerCase()) {
|
||||
case "h1":
|
||||
return 1;
|
||||
case "h2":
|
||||
return 2;
|
||||
case "h3":
|
||||
return 3;
|
||||
case "h4":
|
||||
return 4;
|
||||
case "h5":
|
||||
return 5;
|
||||
case "h6":
|
||||
return 6;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate an HTML element with a given content, opacity and position.
|
||||
*
|
||||
* @param element The HTML element to decorate.
|
||||
* @param content The content to decorate with.
|
||||
* @param opacity The opacity of the decorator.
|
||||
* @param position The position of the decorator.
|
||||
*/
|
||||
function decorateHTMLElement(
|
||||
element: HTMLElement,
|
||||
content: string,
|
||||
opacity: OpacityOptions,
|
||||
position: PostionOptions
|
||||
): void {
|
||||
if (content) {
|
||||
const span = element.createSpan({
|
||||
cls: [
|
||||
headingDecoratorClassName,
|
||||
readingHeadingDecoratorClassName,
|
||||
getPositionClassName(position),
|
||||
],
|
||||
text: content,
|
||||
attr: {
|
||||
"data-decorator-opacity": `${opacity}%`,
|
||||
},
|
||||
});
|
||||
|
||||
if (position === "before-inside") {
|
||||
const headingCollapseIndicator = element.find(
|
||||
".heading-collapse-indicator"
|
||||
);
|
||||
if (headingCollapseIndicator) {
|
||||
headingCollapseIndicator.after(span);
|
||||
} else {
|
||||
const firstChild = element.firstChild;
|
||||
if (firstChild) {
|
||||
firstChild.before(span);
|
||||
} else {
|
||||
element.appendChild(span);
|
||||
}
|
||||
}
|
||||
} else if (position === "before") {
|
||||
const firstChild = element.firstChild;
|
||||
if (firstChild) {
|
||||
firstChild.before(span);
|
||||
} else {
|
||||
element.appendChild(span);
|
||||
}
|
||||
} else {
|
||||
element.appendChild(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ import {
|
|||
headingDecoratorClassName,
|
||||
previewHeadingDecoratorClassName,
|
||||
sourceHeadingDecoratorClassName,
|
||||
getPositionClassName,
|
||||
} from "../common/data";
|
||||
import { getPositionClassName } from "../common/dom";
|
||||
|
||||
export class HeadingWidget extends WidgetType {
|
||||
readonly isLivePreviwMode: boolean;
|
||||
|
|
|
|||
Loading…
Reference in a new issue