feat(gutter): add independent settings, font size dropdown, and 100% default opacity

Give the gutter its own HeadingDecoratorSettings following the same
pattern as other view types (toggle + config panel). Add a font size
dropdown mapped to Obsidian's --font-ui-* CSS variables. Default gutter
opacity is now 100% instead of the standard 20%.
This commit is contained in:
Victor Araújo 2026-02-17 18:35:56 -03:00
parent 95a85495d3
commit a2501103c4
9 changed files with 124 additions and 9 deletions

View file

@ -142,6 +142,8 @@ export type HeadingPluginSettings = {
sourceHideNumberSigns: boolean;
useGutter: boolean;
gutterPosition: GutterPosition;
gutterFontSize: GutterFontSize;
enabledGutterSettings: boolean;
enabledInOutline: boolean;
enabledOutlineSettings: boolean;
enabledInQuietOutline: boolean;
@ -164,6 +166,8 @@ export type HeadingPluginData = Omit<
| "enabledSourceSettings"
| "useGutter"
| "gutterPosition"
| "gutterFontSize"
| "enabledGutterSettings"
| "enabledInOutline"
| "outlineSettings"
| "enabledInQuietOutline"
@ -321,6 +325,9 @@ export function defaultSettings(): HeadingPluginSettings {
sourceHideNumberSigns: false,
useGutter: false,
gutterPosition: "before-line-numbers",
gutterFontSize: "ui-small",
enabledGutterSettings: false,
gutterSettings: { ...defaultHeadingDecoratorSettings(), opacity: 100 },
enabledInOutline: false,
enabledOutlineSettings: false,
outlineSettings: defaultHeadingDecoratorSettings(),

View file

@ -89,12 +89,9 @@ export function createHeadingGutterExtension(
}
const doc = view.state.doc;
const settings = isLivePreviewMode
? pluginData.previewSettings
: pluginData.sourceSettings;
const { opacity } = settings;
const counter = createCounterFromSettings(settings, doc);
const { gutterSettings } = pluginData;
const { opacity } = gutterSettings;
const counter = createCounterFromSettings(gutterSettings, doc);
const builder = new RangeSetBuilder<HeadingGutterMarker>();
const heading = new Heading();

View file

@ -170,6 +170,7 @@ export class HeadingPlugin extends Plugin {
// Register editor extension
this.editorExtensions = this.buildEditorExtensions();
this.registerEditorExtension(this.editorExtensions);
this.applyGutterFontSize();
// Register markdown post processor
this.registerMarkdownPostProcessor((element, context) => {
@ -317,6 +318,7 @@ export class HeadingPlugin extends Plugin {
this.revokes.forEach((revoke) => revoke());
this.revokes = [];
document.body.style.removeProperty("--heading-decorator-gutter-font-size");
}
async saveSettings() {
@ -348,7 +350,14 @@ export class HeadingPlugin extends Plugin {
} else {
this.unloadFileExplorerComponents();
}
} else if (path === "useGutter" || path === "gutterPosition") {
} else if (path === "gutterFontSize") {
this.applyGutterFontSize();
} else if (
path === "useGutter" ||
path === "gutterPosition" ||
path === "enabledGutterSettings" ||
path.startsWith("gutterSettings")
) {
this.swapEditorExtensions();
} else if (
path === "enabledOutlineSettings" ||
@ -392,6 +401,9 @@ export class HeadingPlugin extends Plugin {
if (!this.settings.enabledFileExplorerSettings) {
this.debouncedRerenderFileExplorerDecorator();
}
if (this.settings.useGutter && !this.settings.enabledGutterSettings) {
this.swapEditorExtensions();
}
}
}
@ -459,6 +471,19 @@ export class HeadingPlugin extends Plugin {
this.app.workspace.updateOptions();
}
private applyGutterFontSize(): void {
const cssVarMap: Record<GutterFontSize, string> = {
"ui-smaller": "var(--font-ui-smaller)",
"ui-small": "var(--font-ui-small)",
"ui-medium": "var(--font-ui-medium)",
"ui-large": "var(--font-ui-large)",
};
document.body.style.setProperty(
"--heading-decorator-gutter-font-size",
cssVarMap[this.settings.gutterFontSize] ?? "var(--font-ui-small)"
);
}
private createInlineViewPlugin(
getPluginData: () => Promise<HeadingPluginData>
) {
@ -871,6 +896,8 @@ export class HeadingPlugin extends Plugin {
sourceHideNumberSigns,
previewSettings: _previewSettings,
sourceSettings: _sourceSettings,
enabledGutterSettings,
gutterSettings: _gutterSettings,
} = this.settings;
let enabledInPreview = _enabledInPreview;
@ -881,6 +908,9 @@ export class HeadingPlugin extends Plugin {
const sourceSettings = enabledSourceSettings
? _sourceSettings
: commonSettings;
const gutterSettings = enabledGutterSettings
? _gutterSettings
: commonSettings;
const file = this.getActiveFile();
if (file) {
@ -911,6 +941,7 @@ export class HeadingPlugin extends Plugin {
sourceHideNumberSigns,
previewSettings,
sourceSettings,
gutterSettings,
};
}

View file

@ -113,6 +113,53 @@ export class HeadingSettingTab extends PluginSettingTab {
})
);
//* gutterFontSize
gutterPositionManager.add(
new Setting(containerEl)
.setName(i18n.t("setting.gutterFontSize"))
.setDesc(i18n.t("setting.gutterFontSizeDesc"))
.addDropdown((dropdown) => {
dropdown
.addOptions({
"ui-smaller": i18n.t("setting.gutterFontSizeSmaller"),
"ui-small": i18n.t("setting.gutterFontSizeSmall"),
"ui-medium": i18n.t("setting.gutterFontSizeMedium"),
"ui-large": i18n.t("setting.gutterFontSizeLarge"),
})
.setValue(settings.gutterFontSize)
.onChange((value) => {
settings.gutterFontSize = this.isGutterFontSize(value)
? value
: "ui-small";
this.plugin.saveSettings();
});
})
);
//* enabledGutterSettings
let gutterConfigBtn: ButtonOrUndefined;
gutterPositionManager.add(
new Setting(containerEl)
.setName(i18n.t("setting.enabledInGutterConfig"))
.setDesc(i18n.t("setting.enabledInGutterConfigDesc"))
.addToggle((toggle) => {
toggle.setValue(settings.enabledGutterSettings).onChange((value) => {
settings.enabledGutterSettings = value;
gutterConfigBtn?.setDisabled(!value);
this.plugin.saveSettings();
});
})
.addButton((button) => {
gutterConfigBtn = button;
button
.setButtonText(i18n.t("button.config"))
.onClick(() => {
this.manageHeadingDecoratorSettings("gutterSettings");
})
.setDisabled(!settings.enabledGutterSettings);
})
);
if (!settings.useGutter) {
gutterPositionManager.hide();
}
@ -495,6 +542,10 @@ export class HeadingSettingTab extends PluginSettingTab {
return ["before-line-numbers", "after-line-numbers"].includes(value);
}
private isGutterFontSize(value: string): value is GutterFontSize {
return ["ui-smaller", "ui-small", "ui-medium", "ui-large"].includes(value);
}
private manageHeadingDecoratorSettings(
settingsType: PluginDecoratorSettingsType
) {
@ -528,6 +579,9 @@ export class HeadingSettingTab extends PluginSettingTab {
case "fileExplorerSettings":
tabName = i18n.t("setting.enabledInFileExplorerConfig");
break;
case "gutterSettings":
tabName = i18n.t("setting.enabledInGutterConfig");
break;
}
new Setting(containerEl)

View file

@ -161,6 +161,14 @@
"gutterPositionDesc": "Whether the gutter column appears before or after line numbers.",
"beforeLineNumbers": "Before line numbers",
"afterLineNumbers": "After line numbers",
"gutterFontSize": "Gutter font size",
"gutterFontSizeDesc": "Set the font size for heading decorations in the gutter column.",
"gutterFontSizeSmaller": "Smaller (12px)",
"gutterFontSizeSmall": "Small (13px)",
"gutterFontSizeMedium": "Medium (15px)",
"gutterFontSizeLarge": "Large (20px)",
"enabledInGutterConfig": "Heading decorator for gutter",
"enabledInGutterConfigDesc": "Ignore the generic heading decorator config and set the heading decorator for the gutter display.",
"logic": "Logic",
"h1": "H1",
"h2": "H2",

View file

@ -161,6 +161,14 @@
"gutterPositionDesc": "邊欄列顯示在行號之前還是之後。",
"beforeLineNumbers": "在行號之前",
"afterLineNumbers": "在行號之後",
"gutterFontSize": "邊欄字型大小",
"gutterFontSizeDesc": "設定邊欄中標題裝飾器的字型大小。",
"gutterFontSizeSmaller": "較小 (12px)",
"gutterFontSizeSmall": "小 (13px)",
"gutterFontSizeMedium": "中等 (15px)",
"gutterFontSizeLarge": "大 (20px)",
"enabledInGutterConfig": "邊欄的標題裝飾器",
"enabledInGutterConfigDesc": "忽略通用的標題裝飾器配置,單獨配置邊欄的標題裝飾器。",
"logic": "邏輯控制",
"h1": "H1 級別",
"h2": "H2 級別",

View file

@ -161,6 +161,14 @@
"gutterPositionDesc": "边栏列显示在行号之前还是之后。",
"beforeLineNumbers": "在行号之前",
"afterLineNumbers": "在行号之后",
"gutterFontSize": "边栏字体大小",
"gutterFontSizeDesc": "设置边栏中标题装饰器的字体大小。",
"gutterFontSizeSmaller": "较小 (12px)",
"gutterFontSizeSmall": "小 (13px)",
"gutterFontSizeMedium": "中等 (15px)",
"gutterFontSizeLarge": "大 (20px)",
"enabledInGutterConfig": "边栏的标题装饰器",
"enabledInGutterConfigDesc": "忽略通用的标题装饰器配置,单独配置边栏的标题装饰器。",
"logic": "逻辑控制",
"h1": "H1 级别",
"h2": "H2 级别",

View file

@ -446,7 +446,7 @@ body {
/* gutter start */
.heading-decorator-gutter {
font-size: var(--font-small);
font-size: var(--heading-decorator-gutter-font-size, var(--font-small));
letter-spacing: 0.015em;
text-align: end;
min-width: 4ch;

4
types.d.ts vendored
View file

@ -14,6 +14,7 @@ type DecoratorMode = "orderd" | "independent" | "splice" | "unordered";
type OpacityOptions = 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100;
type PostionOptions = "before" | "before-inside" | "after" | "after-inside";
type GutterPosition = "before-line-numbers" | "after-line-numbers";
type GutterFontSize = "ui-smaller" | "ui-small" | "ui-medium" | "ui-large";
type RenderPolicy = "partial" | "full";
type PluginDecoratorSettingsType =
@ -23,7 +24,8 @@ type PluginDecoratorSettingsType =
| "sourceSettings"
| "outlineSettings"
| "quietOutlineSettings"
| "fileExplorerSettings";
| "fileExplorerSettings"
| "gutterSettings";
type HeadingMetadataSettingsType =
| "reading"