How to get the rendered markdown html?

so i’m new to obsidian’s plugin api. i have been trying to make a simple plugin to get the rendered html version of the markdown out of the view and save it to the clipboard. i have a few really cool plugins and i’d love to quickly copy paste my notes around the web.

my issue is the MarkdownView passed to the editor callback only seems to have the visible portion of the html in: view.contentEl.innerHTML. i played around trying to force a full render by calling view.previewMode.rerender(true); before line, w/o any luck. am i looking at the wrong object? here’s a snip of the plugin:

this.addCommand({
	id: "html-copy",
	name: "copy renders file html",
	editorCallback: async (editor: Editor, view: MarkdownView) => {
		let dom = view.contentEl.innerHTML; // only gets part
		navigator.clipboard.writeText(dom);
		new Notice(html saved to the clipboard, 3500);
	},
});

i also tried the get method on the previewMode object, but that surprisingly returned the markdown itself.

view.previewMode.rerender(true);
const dom = view.previewMode.get();
navigator.clipboard.writeText(dom);
new Notice(html saved to the clipboard, 3500);

i appreciate any suggestions or incites

i tried using find selecting the root .mardown-reading-view and i get the same clipping issue. i noticed if i scroll whatever is in view is what’s returned.

navigator.clipboard.writeText(view.contentEl.find(".markdown-reading-view").innerHTML);

Thats a good Idea for a plugin !

Although I havent tried to read what is returned by MarkdownRenderer.render() API, but I have used it a lot to render HTML of the part of Raw text I wanted to render inside any Modal or View. Since I am appending it inside the HTML DIV, so i believe it is returning HTML code. But You can try it if that works.

The only issue will be you will need to pass the parent MD file and the content yourself to the API. (Well, this could be even a feature, like the user can select a part of the content from the view, and then right click to get the HTML for the same and you can get that specific part as HTML inside the clipboard)

Check-out this post, on how to use it : I want to support some math notations,replace $$,$$ to \[, \] - #2 by Tu2_atmanand

1 Like

appreciate it!

i was reading this actually last night, but i couldn’t figure out what component i needed to pass. your example of creating a blank one is very helpful. i’ll give it a go, thanx!

1 Like

i got it! thanx for your help @Tu2_atmanand !

for future readers:

import {
	App,
	Editor,
	Notice,
	Plugin,
	PluginSettingTab,
	MarkdownView,
	Component,
	MarkdownRenderer,
} from "obsidian";

export default class md2html extends Plugin {
	async getHtml(md: string, file: string, view: MarkdownView) {
		const component = new Component();
		component.load();
		const renderDiv = view.contentEl;
		await MarkdownRenderer.render(this.app, md, renderDiv, file, component);
		return renderDiv.innerHTML
			.split('markdown-reading-view')[1]
			.split('</div></div></div>')[1];
	}
	async onload() {
		this.addCommand({
			id: "md2html-new",
			name: "convert to new file",
			editorCallback: async (_editor: Editor, view: MarkdownView) => {
				const file = this.app.workspace.getActiveFile()?.name || "nop";
				const dom = await this.getHtml(view.getViewData(), file, view);
				this.app.vault.create("html-" + file, dom);
				new Notice("document converted to new html file", 3500);
			},
		});
		this.addCommand({
			id: "md2html-clip",
			name: "convert to clipboard",
			editorCallback: async (_editor: Editor, view: MarkdownView) => {
				const file = this.app.workspace.getActiveFile()?.name || "nop";
				navigator.clipboard.writeText(
					await this.getHtml(view.getViewData(), file, view),
				);
				new Notice("document html saved to the clipboard", 3500);
			},
		});
	}
};
2 Likes

i’m still working on refining the results. you can follow my work or checkout the plugin here: GitHub - xero/obsidian-md2html: plugin to convert markdown to html inside obsidian.md

I think your implementation can be slightly simplified

2 Likes

@mnaoumov that’s a great solution and works if you want to just render some markdown. but i want to get all the rendered results of my plugins as well. things like shiki, expressive code, dataview tablets, etc.

i’ve done a bunch more testing, trying a new approach, this code ALMOST works:

...
	async onload() {
		this.addCommand({
			id: "md2html-clip",
			name: "note html to clipboard",
			callback: () => {
				const html = this.app.workspace.getActiveViewOfType(MarkdownView)?.contentEl.innerHTML;

				if (html !== "") {
					navigator.clipboard.writeText(html);
					new Notice("html copied to the clipboard", 3456);
				}
			},
		});
....

the problem is it only returns what’s viewable on the screen at the time.
i tired adding:

editor.scrollTo(0, max);
view.previewMode.applyScroll(max);

with no luck.

is there a way to force the reading mode to render everything?

e.g. i tried poking around the api, but couldn’t find a way to force resize the viewport then rerender

@xer0 That’s an interesting problem

Try the following

thanx so much @mnaoumov

i had to add a ts-ignore statement [cite] to get it to build:

// @ts-expect-error, not typed
view.editor.cm

but this worked like a charm! now i just need to work out how to force scroll the view to the bottom.

If you use npm install obsidian-typings@beta --save-dev and register the typings according to their README, you won’t need @ts-expect-error

1 Like

did you try

view.editor.scrollTo(0, Number.MAX_SAFE_INTEGER);
1 Like

@mnaoumov i did indeed (i did max-1) and it didn’t seem to help.

what did help though, was adding a sleep(x) in between the 1st measure() and getting the innerHTML.

it still seems to work better if you manually scroll around the document and get it all rended 1st. trying to force it hasn’t gottem me anywhere. but that’s ok. doing it this way still works.

thanx again for all your help on this. i gave you a shoutout on github :smiley:

this was my forced scrolling test:

const cm = view.editor.cm;
cm.focus();
cm.viewState.printing = true;
cm.measure();
sleep(1000000);
view.editor.scrollTo(0, 0);
sleep(1000000);
view.editor.scrollTo(0, Number.MAX_SAFE_INTEGER);
sleep(1000000);
html = view?.contentEl.innerHTML;
cm.viewState.printing = false;
cm.measure();

while this get’s the full document (in live preview mode), only the originally visible parts are rendered. the rest is markdown still. in reading mode it doesn’t scroll, so it has no effect. so i left this out of the last release.

Sleep without await doesn’t do anything. I don’t think you meant to wait 1000000 Ms ~ 20 min

1 Like

weird that my LSP didn’t tell me that sleep was async. thanks for that again my friend. I was trying increasingly larger values, since like you said, it wasn’t doing anything.

I use linter for that, so it doesn’t allow me to have async functions without awaiting.