diff --git a/manifest.json b/manifest.json index 6f6b068..ed392fb 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-embedded-note-titles", "name": "Embedded Note Titles", - "version": "1.2.10", + "version": "1.3.0", "minAppVersion": "0.15.0", "description": "Inserts the note file name as an H1 heading above each note.", "author": "mgmeyers", diff --git a/src/HeadingsManager.ts b/src/HeadingsManager.ts index 25dfab0..26cda03 100644 --- a/src/HeadingsManager.ts +++ b/src/HeadingsManager.ts @@ -1,4 +1,4 @@ -import { App, MarkdownView, WorkspaceLeaf, debounce } from "obsidian"; +import { App, MarkdownView, WorkspaceLeaf } from "obsidian"; import { Settings } from "./settings"; import { getMatchedCSSRules } from "./getMatchedCSSRules"; @@ -101,199 +101,6 @@ function applyRefStyles(heading: HTMLElement, ref: RefSizing | null) { } } -export class LegacyCodemirrorHeadingsManager { - headings: { - [id: string]: { - leaf: WorkspaceLeaf; - resizeWatcher?: ResizeObserver; - }; - } = {}; - - codeMirrorSizerRef: RefSizing | null = null; - codeMirrorSizerInvalid: boolean = true; - - getSettings: () => Settings; - - constructor(getSettings: () => Settings) { - this.getSettings = getSettings; - } - - getCodeMirrorSizerStyles() { - const sizerEl = document.getElementsByClassName("CodeMirror-sizer"); - const lineEl = document.getElementsByClassName("CodeMirror-line"); - - if (sizerEl.length && lineEl.length) { - const sizer = sizerEl[0] as HTMLElement; - - const { marginLeft, paddingRight, borderRightWidth } = sizer.style; - - // If codemirror hasn't applied styles to the div yet, let's consider it - // invalid so we can check it again later - if (marginLeft !== "0px" && paddingRight !== "0px") { - this.codeMirrorSizerInvalid = false; - } - - const inline: RefSizing = { - marginLeft, - marginRight: borderRightWidth, - paddingRight, - }; - - const sizerRef = getRefSizing(sizer); - - const line = lineEl[0] as HTMLElement; - const lineRef = getRefSizing(line); - - // Combine inline styles with CSS styles - this.codeMirrorSizerRef = { - ...inline, - ...sizerRef, - }; - - if (lineRef.paddingLeft) { - this.codeMirrorSizerRef.paddingLeft = this.codeMirrorSizerRef - .paddingLeft - ? `calc(${this.codeMirrorSizerRef.paddingLeft} + ${lineRef.paddingLeft})` - : lineRef.paddingLeft; - } - - if (lineRef.paddingRight) { - this.codeMirrorSizerRef.paddingRight = this.codeMirrorSizerRef - .paddingRight - ? `calc(${this.codeMirrorSizerRef.paddingRight} + ${lineRef.paddingRight})` - : lineRef.paddingRight; - } - } - } - - // Once the codemirror heading styles have been validated, loop through and update everything - updateCodeMirrorHeadings() { - Object.keys(this.headings).forEach((id) => { - const h1Edit = document.getElementById(`${id}-edit`); - applyRefStyles(h1Edit, this.codeMirrorSizerRef); - }); - } - - // Clean up headings once a pane has been closed or the plugin has been disabled - removeHeading(id: string) { - if (!this.headings[id]) return; - - const h1Edit = document.getElementById(`${id}-edit`); - - if (h1Edit) h1Edit.remove(); - - this.headings[id].resizeWatcher?.disconnect(); - - delete this.headings[id].resizeWatcher; - delete this.headings[id]; - } - - createHeading(id: string, leaf: WorkspaceLeaf) { - // CodeMirror adds margin and padding only after the editor is visible - if ( - this.codeMirrorSizerInvalid && - leaf.getViewState().state?.mode === "source" - ) { - this.getCodeMirrorSizerStyles(); - - if (!this.codeMirrorSizerInvalid) { - this.updateCodeMirrorHeadings(); - } - } - - if (this.headings[id]) return; - - const title = getTitleForView( - leaf.view.app, - this.getSettings(), - leaf.view as MarkdownView - ); - - const viewContent = - leaf.view.containerEl.getElementsByClassName("CodeMirror-scroll"); - - const lines = - leaf.view.containerEl.getElementsByClassName("CodeMirror-lines"); - - if (!this.codeMirrorSizerRef) { - this.getCodeMirrorSizerStyles(); - } - - if (viewContent.length) { - // Create the codemirror heading - const editEl = viewContent[0] as HTMLDivElement; - const h1Edit = document.createElement("h1"); - - applyRefStyles(h1Edit, this.codeMirrorSizerRef); - - h1Edit.setText(title); - h1Edit.id = `${id}-edit`; - h1Edit.classList.add("embedded-note-title", "embedded-note-title__edit"); - - if (title === "") { - h1Edit.classList.add("embedded-note-title__hidden"); - } - - editEl.prepend(h1Edit); - - const onResize = debounce( - (entries: any) => { - if (lines.length) { - const linesEl = lines[0] as HTMLDivElement; - const height = Math.ceil(entries[0].borderBoxSize[0].blockSize); - - linesEl.style.paddingTop = `${height}px`; - h1Edit.style.marginBottom = `-${height}px`; - } - }, - 20, - true - ); - - // We need to push the content down when the pane resizes so the heading - // doesn't cover the content - const resizeWatcher = new (window as any).ResizeObserver(onResize); - - resizeWatcher.observe(h1Edit); - - this.headings[id] = { leaf, resizeWatcher }; - } - } - - // Generate a unique ID for a leaf - getLeafId(leaf: WorkspaceLeaf) { - return "title-" + Math.random().toString(36).substr(2, 9); - } - - // Iterate through all leafs and generate headings if needed - createHeadings(app: App) { - const seen: { [k: string]: boolean } = {}; - - app.workspace.getLeavesOfType("markdown").forEach((leaf) => { - const id = this.getLeafId(leaf); - - if (id) { - this.createHeading(id, leaf); - seen[id] = true; - } - }); - - Object.keys(this.headings).forEach((id) => { - if (!seen[id]) { - this.removeHeading(id); - } - }); - } - - cleanup() { - this.codeMirrorSizerRef = null; - - Object.keys(this.headings).forEach((id) => { - this.removeHeading(id); - }); - } -} - export class PreviewHeadingsManager { headings: { [id: string]: { @@ -308,8 +115,8 @@ export class PreviewHeadingsManager { this.getSettings = getSettings; } - getPreviewSizerStyles() { - const el = document.getElementsByClassName("markdown-preview-sizer"); + getPreviewSizerStyles(doc: Document) { + const el = doc.getElementsByClassName("markdown-preview-sizer"); if (el.length) { this.previewSizerRef = getRefSizing(el[0] as HTMLElement); @@ -320,7 +127,8 @@ export class PreviewHeadingsManager { removeHeading(id: string) { if (!this.headings[id]) return; - const h1Preview = document.getElementById(`${id}-preview`); + const doc = this.headings[id].leaf.view.containerEl.ownerDocument; + const h1Preview = doc.getElementById(`${id}-preview`); if (h1Preview) h1Preview.remove(); @@ -330,6 +138,8 @@ export class PreviewHeadingsManager { createHeading(id: string, leaf: WorkspaceLeaf) { if (this.headings[id]) return; + const doc = leaf.view.containerEl.ownerDocument; + const title = getTitleForView( leaf.view.app, this.getSettings(), @@ -341,13 +151,15 @@ export class PreviewHeadingsManager { ); if (!this.previewSizerRef) { - this.getPreviewSizerStyles(); + this.getPreviewSizerStyles(doc); } let previewEl: HTMLDivElement; for (let i = 0, len = previewContent.length; i < len; i++) { - if (previewContent[i].parentElement.parentElement.hasClass("view-content")) { + if ( + previewContent[i].parentElement.parentElement.hasClass("view-content") + ) { previewEl = previewContent[i] as HTMLDivElement; break; } @@ -355,7 +167,7 @@ export class PreviewHeadingsManager { if (previewEl) { // Create the preview heading - const h1Preview = document.createElement("h1"); + const h1Preview = doc.createElement("h1"); applyRefStyles(h1Preview, this.previewSizerRef); @@ -378,7 +190,7 @@ export class PreviewHeadingsManager { // Generate a unique ID for a leaf getLeafId(leaf: WorkspaceLeaf) { - return "title-" + Math.random().toString(36).substr(2, 9); + return "title-" + Math.random().toString(36).substring(2, 9); } // Iterate through all leafs and generate headings if needed diff --git a/src/getMatchedCSSRules.ts b/src/getMatchedCSSRules.ts index 3cdbb29..fd3b741 100644 --- a/src/getMatchedCSSRules.ts +++ b/src/getMatchedCSSRules.ts @@ -12,16 +12,12 @@ function toArray(list: any) { } // handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same -function getSheetRules(stylesheet: CSSStyleSheet) { - var sheet_media = stylesheet.media && stylesheet.media.mediaText; +function getSheetRules(win: Window, stylesheet: CSSStyleSheet) { + const sheet_media = stylesheet.media && stylesheet.media.mediaText; // if this sheet is disabled skip it if (stylesheet.disabled) return []; // if this sheet's media is specified and doesn't match the viewport then skip it - if ( - sheet_media && - sheet_media.length && - !window.matchMedia(sheet_media).matches - ) + if (sheet_media && sheet_media.length && !win.matchMedia(sheet_media).matches) return []; // get the style rules of this sheet try { @@ -32,16 +28,17 @@ function getSheetRules(stylesheet: CSSStyleSheet) { } function _find(string: string, re: RegExp) { - var matches = string.match(re); + const matches = string.match(re); return matches ? matches.length : 0; } // calculates the specificity of a given `selector` function calculateScore(selector: string) { - var score = [0, 0, 0], - parts = selector.split(" "), - part, - match; + const score = [0, 0, 0]; + const parts = selector.split(" "); + let part; + let match; + //TODO: clean the ':not' part since the last ELEMENT_RE will pick it up while (((part = parts.shift()), typeof part == "string")) { // find all pseudo-elements @@ -77,10 +74,10 @@ function calculateScore(selector: string) { // returns the heights possible specificity score an element can get from a give rule's selectorText function getSpecificityScore(element: HTMLElement, selectorText: string) { - var selectors = selectorText.split(","), - selector, - score, - result = 0; + const selectors = selectorText.split(","); + let selector; + let score; + let result = 0; while ((selector = selectors.shift())) { if (element.matches(selector)) { @@ -111,7 +108,8 @@ function sortBySpecificity(element: HTMLElement, rules: CSSStyleRule[]) { } export function getMatchedCSSRules(element: HTMLElement): CSSStyleRule[] { - let styleSheets = toArray(window.document.styleSheets); + const win = element.ownerDocument.defaultView; + let styleSheets = toArray(element.ownerDocument.styleSheets); let sheet; let rules; let rule; @@ -121,20 +119,20 @@ export function getMatchedCSSRules(element: HTMLElement): CSSStyleRule[] { // we iterate them from the beginning to follow proper cascade order while ((sheet = styleSheets.shift())) { // get the style rules of this sheet - rules = getSheetRules(sheet); + rules = getSheetRules(win, sheet); // loop the rules in order of appearance while ((rule = rules.shift())) { // if this is an @import rule if (rule.styleSheet) { // insert the imported stylesheet's rules at the beginning of this stylesheet's rules - rules = getSheetRules(rule.styleSheet).concat(rules); + rules = getSheetRules(win, rule.styleSheet).concat(rules); // and skip this rule continue; } // if there's no stylesheet attribute BUT there IS a media attribute it's a media rule else if (rule.media) { // insert the contained rules of this media rule to the beginning of this stylesheet's rules - rules = getSheetRules(rule).concat(rules); + rules = getSheetRules(win, rule).concat(rules); // and skip it continue; } diff --git a/src/main.ts b/src/main.ts index 3e43ca8..1eb85ea 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,10 +8,7 @@ import { TFile, } from "obsidian"; import { getDailyNoteSettings } from "obsidian-daily-notes-interface"; -import { - LegacyCodemirrorHeadingsManager, - PreviewHeadingsManager, -} from "./HeadingsManager"; +import { PreviewHeadingsManager } from "./HeadingsManager"; import { Settings } from "./settings"; import { buildTitleDecoration, updateTitle } from "./titleDecoration"; @@ -19,7 +16,6 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { settings: Settings; isLegacyEditor: boolean; - legacyCodemirrorHeadingsManager: LegacyCodemirrorHeadingsManager; previewHeadingsManager: PreviewHeadingsManager; observer: ResizeObserver; @@ -38,10 +34,7 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { this.previewHeadingsManager = new PreviewHeadingsManager(getSettings); this.isLegacyEditor = (this.app.vault as any).getConfig("legacyEditor"); - if (this.isLegacyEditor) { - this.legacyCodemirrorHeadingsManager = - new LegacyCodemirrorHeadingsManager(getSettings); - } else { + if (!this.isLegacyEditor) { this.observedTitles = new Map(); this.observer = new ResizeObserver((entries) => { entries.forEach((entry) => { @@ -72,7 +65,7 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { this.registerEvent( this.app.metadataCache.on("changed", (file) => { const frontmatterKey = this.settings.titleMetadataField; - const hideOnH1 = this.settings.hideOnH1 + const hideOnH1 = this.settings.hideOnH1; if (frontmatterKey || hideOnH1) { notifyFileChange(file); @@ -84,14 +77,18 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { this.registerEvent( this.app.metadataCache.on("changed", (file) => { const frontmatterKey = this.settings.titleMetadataField; - const hideOnH1 = this.settings.hideOnH1 + const hideOnH1 = this.settings.hideOnH1; if (frontmatterKey || hideOnH1) { const cache = this.app.metadataCache.getFileCache(file); - if (hideOnH1 || frontmatterKey && cache?.frontmatter && cache.frontmatter[frontmatterKey]) { + if ( + hideOnH1 || + (frontmatterKey && + cache?.frontmatter && + cache.frontmatter[frontmatterKey]) + ) { setTimeout(() => { - this.legacyCodemirrorHeadingsManager?.createHeadings(this.app); this.previewHeadingsManager.createHeadings(this.app); }, 0); } @@ -102,7 +99,6 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { this.registerEvent( this.app.workspace.on("layout-change", () => { setTimeout(() => { - this.legacyCodemirrorHeadingsManager?.createHeadings(this.app); this.previewHeadingsManager.createHeadings(this.app); }, 0); @@ -126,28 +122,23 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { // Listen for CSS changes so we can recalculate heading styles this.registerEvent( this.app.workspace.on("css-change", () => { - this.legacyCodemirrorHeadingsManager?.cleanup(); this.previewHeadingsManager.cleanup(); setTimeout(() => { - this.legacyCodemirrorHeadingsManager?.createHeadings(this.app); this.previewHeadingsManager.createHeadings(this.app); }, 0); }) ); - this.app.workspace.layoutReady - ? this.app.workspace.trigger("layout-change") - : this.app.workspace.onLayoutReady(() => { - // Trigger layout-change to ensure headings are created when the app loads - this.app.workspace.trigger("layout-change"); - }); + this.app.workspace.onLayoutReady(() => { + // Trigger layout-change to ensure headings are created when the app loads + this.app.workspace.trigger("layout-change"); + }); } onunload() { document.body.classList.remove("embedded-note-titles"); - this.legacyCodemirrorHeadingsManager?.cleanup(); this.previewHeadingsManager.cleanup(); this.observer.disconnect(); this.observedTitles.forEach((_, el) => { @@ -187,11 +178,9 @@ export default class EmbeddedNoteTitlesPlugin extends Plugin { }); } - this.legacyCodemirrorHeadingsManager?.cleanup(); this.previewHeadingsManager.cleanup(); setTimeout(() => { - this.legacyCodemirrorHeadingsManager?.createHeadings(this.app); this.previewHeadingsManager.createHeadings(this.app); }, 0); diff --git a/src/titleDecoration.ts b/src/titleDecoration.ts index 4608692..0aa0998 100644 --- a/src/titleDecoration.ts +++ b/src/titleDecoration.ts @@ -47,7 +47,6 @@ export function getTitleForView( view: MarkdownView ) { const frontmatterKey = settings.titleMetadataField; - const file = view.file; let title = file?.basename; @@ -118,7 +117,9 @@ export function buildTitleDecoration( }, }); - view.contentDOM.before(this.header); + setTimeout(() => { + view.contentDOM.before(this.header); + }) plugin.observeTitle(this.header, (entry) => { if (entry.borderBoxSize[0]) {