### TypeScript Project Build Configuration (JSON) Source: https://context7.com/liamcain/obsidian-lapel/llms.txt Configuration for the TypeScript project, including build scripts and development dependencies. It specifies the project name, version, and author, along with scripts for linting, development builds (`dev`), and production builds (`build`) using esbuild. Key development dependencies include CodeMirror 6 libraries, Obsidian, TypeScript, and ESLint. ```json { "name": "obsidian-lapel", "version": "0.1.6", "description": "Dress up your editor with decorations that mark each of your headings.", "author": "liamcain", "main": "main.js", "license": "MIT", "scripts": { "lint": "eslint . --ext .ts", "dev": "node esbuild.config.mjs", "build": "yarn run lint && node esbuild.config.mjs production" }, "devDependencies": { "@codemirror/state": "6.4.1", "@codemirror/language": "https://github.com/lishid/cm-language", "@codemirror/view": "https://github.com/lishid/cm-view", "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "5.13.0", "@typescript-eslint/parser": "5.13.0", "builtin-modules": "^3.2.0", "esbuild": "^0.14.45", "eslint": "8.3.0", "obsidian": "latest", "tslib": "2.3.1", "typescript": "4.5.2" } } ``` -------------------------------- ### Initialize Lapel Obsidian Plugin and Register Editor Extension (TypeScript) Source: https://context7.com/liamcain/obsidian-lapel/llms.txt This snippet shows the main plugin class for Lapel, which extends Obsidian's Plugin. It handles loading settings, creating and registering the heading marker CodeMirror 6 extension, and managing updates to plugin settings, including hot-reloading the editor extension when relevant settings change. ```typescript import { Extension } from "@codemirror/state"; import { Plugin } from "obsidian"; import { headingMarkerPlugin } from "./headingWidget"; import { DEFAULT_SETTINGS, LapelSettings, LapelSettingsTab } from "./settings"; export default class LapelPlugin extends Plugin { public settings: LapelSettings; private extensions: Extension[] = []; async onload(): Promise { // Load persisted settings from disk await this.loadSettings(); // Create the heading marker extension with current settings this.extensions.push(headingMarkerPlugin(this.settings.showBeforeLineNumbers)); // Register the extension with Obsidian's editor this.registerEditorExtension(this.extensions); // Add settings tab to preferences this.registerSettingsTab(); } async loadSettings() { // Merge saved settings with defaults this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } private registerSettingsTab() { this.addSettingTab(new LapelSettingsTab(this.app, this)); } public async updateSettings( tx: (old: LapelSettings) => Partial ): Promise { const changedSettings = tx(this.settings); const newSettings = Object.assign({}, this.settings, changedSettings); // Hot-reload if marker position changed if (this.settings.showBeforeLineNumbers !== changedSettings.showBeforeLineNumbers) { const updatedExt = headingMarkerPlugin(newSettings.showBeforeLineNumbers); this.extensions[0] = updatedExt; this.app.workspace.updateOptions(); } this.settings = newSettings; await this.saveData(this.settings); } } ``` -------------------------------- ### Obsidian Plugin Manifest Configuration (JSON) Source: https://context7.com/liamcain/obsidian-lapel/llms.txt Standard Obsidian plugin manifest file (`manifest.json`). It defines essential metadata for the plugin, including its unique ID, name, description, version, author information, and compatibility requirements like minimum application version and whether it's desktop-only. ```json { "id": "lapel", "name": "Lapel", "description": "Dress up your editor with decorations that mark each of your headings.", "version": "0.1.6", "author": "Liam Cain", "authorUrl": "https://github.com/liamcain/", "isDesktopOnly": false, "minAppVersion": "1.0.0" } ``` -------------------------------- ### Settings UI for Heading Marker Position (TypeScript) Source: https://context7.com/liamcain/obsidian-lapel/llms.txt Provides the settings interface and UI for the Obsidian Lapel plugin. It defines the `LapelSettings` interface and `DEFAULT_SETTINGS`. The `LapelSettingsTab` class extends `PluginSettingTab` to create a settings pane in Obsidian, allowing users to toggle the position of heading markers relative to line numbers using a `Setting` component. ```typescript import { App, PluginSettingTab, Setting } from "obsidian"; import LapelPlugin from "src"; export interface LapelSettings { showBeforeLineNumbers: boolean; } export const DEFAULT_SETTINGS: LapelSettings = { showBeforeLineNumbers: true, }; export class LapelSettingsTab extends PluginSettingTab { plugin: LapelPlugin; constructor(app: App, plugin: LapelPlugin) { super(app, plugin); this.plugin = plugin; } display(): void { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h3", { text: "Appearance" }); new Setting(containerEl) .setName("Show before line numbers") .setDesc( "Toggle whether the heading markers are shown before or after the line numbers in the gutter." ) .addToggle((toggle) toggle .setValue(this.plugin.settings.showBeforeLineNumbers) .onChange(async (value) => { // Update settings and hot-reload extension this.plugin.updateSettings(() => ({ showBeforeLineNumbers: value, })); }) ); } } // Example settings usage: // User toggles setting in UI -> onChange fires // -> plugin.updateSettings() called with new value // -> Extension reloaded with updated precedence // -> Markers appear before/after line numbers ``` -------------------------------- ### Create Heading Marker Plugin - TypeScript Source: https://context7.com/liamcain/obsidian-lapel/llms.txt Generates a CodeMirror extension to display heading markers in the editor gutter. It handles rendering, viewport optimization, and interactive click events for changing heading levels. Requires `@codemirror/view`, `@codemirror/language`, `@codemirror/state`, and `obsidian`. ```typescript import { EditorView, ViewPlugin, ViewUpdate, gutter, GutterMarker } from "@codemirror/view"; import { editorLivePreviewField, Menu } from "obsidian"; import { syntaxTree, lineClassNodeProp} from "@codemirror/language"; import { Prec, RangeSet, RangeSetBuilder } from "@codemirror/state"; const headingLevels = [1, 2, 3, 4, 5, 6]; const MARKER_CSS_CLASS = "cm-heading-marker"; export function headingMarkerPlugin(showBeforeLineNumbers: boolean) { const markers = ViewPlugin.fromClass( class { view: EditorView; markers: RangeSet; constructor(view: EditorView) { this.view = view; this.markers = this.buildMarkers(view); } buildMarkers(view: EditorView) { const {viewport} = view; const builder = new RangeSetBuilder(); // Parse syntax tree only in viewport syntaxTree(view.state).iterate({ from: viewport.from, to: viewport.to, enter: ({type, from, to}) => { const headingExp = /header-(\d)$/.exec(type.prop(lineClassNodeProp) ?? ""); if (headingExp) { const headingLevel = Number(headingExp[1]); const d = new HeadingMarker(view, headingLevel, from, to); builder.add(from, to, d); } }, }); return builder.finish(); } update(update: ViewUpdate) { // Only show in Live Preview mode if (!update.state.field(editorLivePreviewField)) { this.markers = RangeSet.empty; return; } // Rebuild on document or viewport changes if (update.docChanged || update.viewportChanged) { this.markers = this.buildMarkers(this.view); } } } ); const gutterPrec = showBeforeLineNumbers ? Prec.high : Prec.low; return [ markers, gutterPrec( gutter({ class: "cm-lapel", markers(view) { return view.plugin(markers)?.markers || RangeSet.empty; }, domEventHandlers: { click: (view, block, evt: MouseEvent) => { if (evt.targetNode?.instanceOf(HTMLElement)) { const el = evt.targetNode; if (!el.hasClass(MARKER_CSS_CLASS)) return false; if (el.hasClass('has-active-menu')) return true; // Find current heading level let currentLevel = 0; view.plugin(markers)?.markers.between(block.from, block.to, (_f, _t, value) => { currentLevel = value.headingLevel; }); // Create level-switching menu const menu = new Menu(); for (const level of headingLevels) { menu.addItem((item) => item .setIcon("lucide-heading-" + level) .setTitle(`Heading ${level}`) .setChecked(level === currentLevel) .onClick(() => { const line = view.state.doc.lineAt(block.from); const lineContents = line.text.replace(/^#{1,6} /, ""); view.dispatch({ changes: { from: line.from, to: line.to, insert: `${"#".repeat(level)} ${lineContents}`, }, }); }) ); } menu.setParentElement(el).showAtMouseEvent(evt); return true; } return false; }, mousedown: (_view, _line, evt: MouseEvent) => { if (evt.targetNode?.instanceOf(HTMLElement)) { return evt.targetNode.hasClass(MARKER_CSS_CLASS); } return false; }, }, }) ), ]; } ``` -------------------------------- ### Custom GutterMarker for Heading Levels (TypeScript) Source: https://context7.com/liamcain/obsidian-lapel/llms.txt Implements a custom GutterMarker for CodeMirror that displays individual heading level indicators. It accepts the editor view, heading level, and position as arguments. The `toDOM` method creates a `div` element with a class and a data attribute for the heading level, allowing for CSS targeting. The `eq` method prevents unnecessary redraws by comparing heading levels. ```typescript import { EditorView, GutterMarker } from "@codemirror/view"; class HeadingMarker extends GutterMarker { constructor( readonly view: EditorView, readonly headingLevel: number, readonly from: number, readonly to: number ) { super(); } toDOM() { const markerEl = createDiv({ cls: "cm-heading-marker" }); // Add data attribute for CSS customization markerEl.dataset.level = String(this.headingLevel); return markerEl; } // Prevent unnecessary redraws eq(other: HeadingMarker) { return this.headingLevel === other.headingLevel; } } // Usage: Markers are automatically created by the ViewPlugin // DOM output:
// DOM output:
// etc. for levels 1-6 ``` -------------------------------- ### CSS for Heading Marker Customization Source: https://context7.com/liamcain/obsidian-lapel/llms.txt Defines CSS rules for customizing the appearance of heading markers. It includes base styling, specific styles for different heading levels (H1-H6) using data attributes, custom emoji markers, hover effects, and an active menu state. ```css /* Default marker styling - plugin provides base structure */ .cm-heading-marker { cursor: pointer; user-select: none; } /* Customize specific heading levels */ .cm-heading-marker[data-level="1"] { --heading-marker: "H1"; color: #ff0000; font-weight: bold; } .cm-heading-marker[data-level="2"] { --heading-marker: "H2"; color: #ff6600; } .cm-heading-marker[data-level="3"] { --heading-marker: "H3"; color: #ffaa00; } /* Custom emoji markers */ .cm-heading-marker[data-level="4"] { --heading-marker: "📘"; } .cm-heading-marker[data-level="5"] { --heading-marker: "📗"; } .cm-heading-marker[data-level="6"] { --heading-marker: "☰"; } /* Hover effects */ .cm-heading-marker:hover { background-color: var(--background-modifier-hover); } /* Active menu state */ .cm-heading-marker.has-active-menu { background-color: var(--background-modifier-active-hover); } ``` -------------------------------- ### Customize H6 Heading Marker with CSS Source: https://github.com/liamcain/obsidian-lapel/blob/main/README.md This CSS snippet targets H6 headings and changes their marker to a '☰' symbol. It utilizes the `data-level` attribute to specifically select H6 elements and the `--heading-marker` CSS variable for customization. This allows for unique visual styling of specific heading levels. ```css .cm-heading-marker[data-level="6"] { --heading-marker: "☰"; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.