obsidian#MarkdownRenderer TypeScript Examples
The following examples show how to use
obsidian#MarkdownRenderer.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: markdown.tsx From obsidian-dataview with MIT License | 7 votes |
/** Hacky preact component which wraps Obsidian's markdown renderer into a neat component. */
export function RawMarkdown({
content,
sourcePath,
inline = true,
style,
cls,
onClick,
}: {
content: string;
sourcePath: string;
inline?: boolean;
style?: string;
cls?: string;
onClick?: (e: preact.JSX.TargetedMouseEvent<HTMLElement>) => void;
}) {
const container = useRef<HTMLElement | null>(null);
const component = useContext(DataviewContext).component;
useEffect(() => {
if (!container.current) return;
container.current.innerHTML = "";
MarkdownRenderer.renderMarkdown(content, container.current, sourcePath, component).then(() => {
if (!container.current || !inline) return;
// Unwrap any created paragraph elements if we are inline.
let paragraph = container.current.querySelector("p");
while (paragraph) {
let children = paragraph.childNodes;
paragraph.replaceWith(...Array.from(children));
paragraph = container.current.querySelector("p");
}
});
}, [content, sourcePath, container.current]);
return <span ref={container} style={style} class={cls} onClick={onClick}></span>;
}
Example #2
Source File: renderer.ts From obsidian-pandoc with MIT License | 6 votes |
// Note: parentFiles is for internal use (to prevent recursively embedded notes)
// inputFile must be an absolute file path
export default async function render (plugin: PandocPlugin, view: MarkdownView,
inputFile: string, outputFormat: string, parentFiles: string[] = []):
Promise<{ html: string, metadata: { [index: string]: string } }>
{
// Use Obsidian's markdown renderer to render to a hidden <div>
const markdown = view.data;
const wrapper = document.createElement('div');
wrapper.style.display = 'hidden';
document.body.appendChild(wrapper);
await MarkdownRenderer.renderMarkdown(markdown, wrapper, path.dirname(inputFile), view);
// Post-process the HTML in-place
await postProcessRenderedHTML(plugin, inputFile, wrapper, outputFormat,
parentFiles, await mermaidCSS(plugin.settings, plugin.vaultBasePath()));
let html = wrapper.innerHTML;
document.body.removeChild(wrapper);
// If it's a top level note, make the HTML a standalone document - inject CSS, a <title>, etc.
const metadata = getYAMLMetadata(markdown);
metadata.title ??= fileBaseName(inputFile);
if (parentFiles.length === 0) {
html = await standaloneHTML(plugin.settings, html, metadata.title, plugin.vaultBasePath());
}
return { html, metadata };
}
Example #3
Source File: render.ts From obsidian-dataview with MIT License | 6 votes |
/** Render simple fields compactly, removing wrapping content like paragraph and span. */
export async function renderCompactMarkdown(
markdown: string,
container: HTMLElement,
sourcePath: string,
component: Component
) {
let subcontainer = container.createSpan();
await MarkdownRenderer.renderMarkdown(markdown, subcontainer, sourcePath, component);
let paragraph = subcontainer.querySelector(":scope > p");
if (subcontainer.children.length == 1 && paragraph) {
while (paragraph.firstChild) {
subcontainer.appendChild(paragraph.firstChild);
}
subcontainer.removeChild(paragraph);
}
}
Example #4
Source File: view.ts From obsidian-fantasy-calendar with MIT License | 6 votes |
async display() {
this.contentEl.empty();
this.contentEl.createEl("h4", { text: this.event.name });
await MarkdownRenderer.renderMarkdown(
this.event.description,
this.contentEl,
this.event.note,
null
);
}
Example #5
Source File: ICSSettingsTab.ts From obsidian-ics with MIT License | 6 votes |
export function getCalendarElement(
icsName: string,
icsUrl: string,
): HTMLElement {
let calendarElement, titleEl;
calendarElement = createDiv({
cls: `calendar calendar-${icsName}`,
});
titleEl = calendarElement.createEl("summary", {
cls: `calendar-name ${icsName}`
});
//get markdown
const markdownHolder = createDiv();
MarkdownRenderer.renderMarkdown(icsName, markdownHolder, "", null);
const calendarTitleContent = createDiv();
markdownHolder.children[0]?.tagName === "P" ?
createDiv() :
markdownHolder.children[0];
//get children of markdown element, then remove them
const markdownElements = Array.from(
markdownHolder.children[0]?.childNodes || []
);
calendarTitleContent.innerHTML = "";
calendarTitleContent.addClass("calendar-title-content");
//add markdown children back
const calendarTitleMarkdown = calendarTitleContent.createDiv(
"calendar-title-markdown"
);
for (let i = 0; i < markdownElements.length; i++) {
calendarTitleMarkdown.appendChild(markdownElements[i]);
}
titleEl.appendChild(calendarTitleContent || createDiv());
return calendarElement;
}
Example #6
Source File: flashcard-modal.tsx From obsidian-spaced-repetition with MIT License | 6 votes |
// slightly modified version of the renderMarkdown function in
// https://github.com/mgmeyers/obsidian-kanban/blob/main/src/KanbanView.tsx
async renderMarkdownWrapper(
markdownString: string,
containerEl: HTMLElement,
recursiveDepth = 0
): Promise<void> {
if (recursiveDepth > 4) return;
MarkdownRenderer.renderMarkdown(
markdownString,
containerEl,
this.currentCard.note.path,
this.plugin
);
containerEl.findAll(".internal-embed").forEach((el) => {
const link = this.parseLink(el.getAttribute("src"));
// file does not exist, display dead link
if (!link.target) {
el.innerText = link.text;
} else if (link.target instanceof TFile) {
if (link.target.extension !== "md") {
this.embedMediaFile(el, link.target);
} else {
el.innerText = "";
this.renderTransclude(el, link, recursiveDepth);
}
}
});
}
Example #7
Source File: main.ts From obsidian-jupyter with MIT License | 6 votes |
async postprocessor(src: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
// Render the code using the default renderer for python.
await MarkdownRenderer.renderMarkdown('```python\n' + src + '```', el, '',
this.app.workspace.activeLeaf.view);
// Needed for positioning of the button and hiding Jupyter prompts.
el.classList.add('obsidian-jupyter');
// Add a button to run the code.
let button = el.querySelector('pre').createEl('button', {
type: 'button',
text: 'Run',
cls: 'copy-code-button',
});
button.setAttribute('style', `right: 32pt`);
button.addEventListener('click', () => {
button.innerText = 'Running...';
this.getJupyterClient(ctx).request({
command: 'execute',
source: `${this.settings.setupScript}\n${src}`,
}).then(response => {
// Find the div to paste the output into or create it if necessary.
let output = el.querySelector('div.obsidian-jupyter-output');
if (output == null) {
output = el.createEl('div');
output.classList.add('obsidian-jupyter-output');
}
// Paste the output and reset the button.
setInnerHTML(output, response);
button.innerText = 'Run';
});
});
}
Example #8
Source File: main.ts From obsidian-admonition with MIT License | 5 votes |
/**
* .callout.admonition.is-collapsible.is-collapsed
* .callout-title
* .callout-icon
* .callout-title-inner
* .callout-fold
* .callout-content
*/
getAdmonitionElement(
type: string,
title: string,
icon: AdmonitionIconDefinition,
color?: string,
collapse?: string
): HTMLElement {
const admonition = createDiv({
cls: `callout admonition admonition-${type} admonition-plugin ${
!title?.trim().length ? "no-title" : ""
}`,
attr: {
style: color ? `--callout-color: ${color};` : '',
"data-callout": type,
"data-callout-fold": ""
}
});
const titleEl = admonition.createDiv({
cls: `callout-title admonition-title ${
!title?.trim().length ? "no-title" : ""
}`
});
if (title && title.trim().length) {
//build icon element
const iconEl = titleEl.createDiv(
"callout-icon admonition-title-icon"
);
if (icon && icon.name && icon.type) {
iconEl.appendChild(
this.iconManager.getIconNode(icon) ?? createDiv()
);
}
//get markdown
const titleInnerEl = titleEl.createDiv(
"callout-title-inner admonition-title-content"
);
MarkdownRenderer.renderMarkdown(title, titleInnerEl, "", null);
if (
titleInnerEl.firstElementChild &&
titleInnerEl.firstElementChild instanceof HTMLParagraphElement
) {
titleInnerEl.setChildrenInPlace(
Array.from(titleInnerEl.firstElementChild.childNodes)
);
}
}
//add them to title element
if (collapse) {
admonition.addClass("is-collapsible");
if (collapse == "closed") {
admonition.addClass("is-collapsed");
}
}
if (!this.data.dropShadow) {
admonition.addClass("no-drop");
}
return admonition;
}
Example #9
Source File: main.ts From obsidian-admonition with MIT License | 5 votes |
renderAdmonitionContent(
admonitionElement: HTMLElement,
type: string,
content: string,
ctx: MarkdownPostProcessorContext,
sourcePath: string,
src: string
) {
let markdownRenderChild = new MarkdownRenderChild(admonitionElement);
markdownRenderChild.containerEl = admonitionElement;
if (ctx && !(typeof ctx == "string")) {
ctx.addChild(markdownRenderChild);
}
if (content && content?.trim().length) {
/**
* Render the content as markdown and append it to the admonition.
*/
const contentEl = this.getAdmonitionContentElement(
type,
admonitionElement,
content
);
if (/^`{3,}mermaid/m.test(content)) {
const wasCollapsed = !admonitionElement.hasAttribute("open");
if (admonitionElement instanceof HTMLDetailsElement) {
admonitionElement.setAttribute("open", "open");
}
setImmediate(() => {
MarkdownRenderer.renderMarkdown(
content,
contentEl,
sourcePath,
markdownRenderChild
);
if (
admonitionElement instanceof HTMLDetailsElement &&
wasCollapsed
) {
admonitionElement.removeAttribute("open");
}
});
} else {
MarkdownRenderer.renderMarkdown(
content,
contentEl,
sourcePath,
markdownRenderChild
);
}
if (
(!content.length || contentEl.textContent.trim() == "") &&
this.data.hideEmpty
)
admonitionElement.addClass("no-content");
const taskLists = contentEl.querySelectorAll<HTMLInputElement>(
".task-list-item-checkbox"
);
if (taskLists?.length) {
const split = src.split("\n");
let slicer = 0;
taskLists.forEach((task) => {
const line = split
.slice(slicer)
.findIndex((l) => /^[ \t>]*\- \[.\]/.test(l));
if (line == -1) return;
task.dataset.line = `${line + slicer + 1}`;
slicer = line + slicer + 1;
});
}
}
}
Example #10
Source File: defineGenericAnnotation.tsx From obsidian-annotator with GNU Affero General Public License v3.0 | 4 votes |
function patchSidebarMarkdownRendering(iframe: HTMLIFrameElement, filePath: string, plugin: AnnotatorPlugin) {
type HTMLElementConstructor = typeof window.HTMLElement;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const IframeElement = (iframe.contentWindow as any).Element;
const existingKeys = new Set([...Object.getOwnPropertyNames(IframeElement.prototype)]);
for (const key in Element.prototype) {
try {
if (!existingKeys.has(key)) {
IframeElement.prototype[key] = Element.prototype[key];
}
} catch (e) {}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class ObsidianMarkdown extends ((iframe.contentWindow as any).HTMLElement as HTMLElementConstructor) {
markdown: string;
// Whenever an attibute is changed, this function is called. A switch statement is a good way to handle the various attributes.
// Note that this also gets called the first time the attribute is set, so we do not need any special initialisation code.
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'markdownbase64') {
this.markdown = b64_to_utf8(newValue);
(async () => {
MarkdownRenderer.renderMarkdown(this.markdown, this, filePath, null);
const maxDepth = 10;
const patchEmbeds = (el: HTMLElement, filePath: string, depth: number) => {
if (depth > maxDepth) return;
[...el.findAll('.internal-embed')].forEach(async (el: HTMLElement) => {
const src = el.getAttribute('src');
const target =
typeof src === 'string' && plugin.app.metadataCache.getFirstLinkpathDest(src, filePath);
if (target instanceof TFile) {
el.innerText = '';
switch (target.extension) {
case 'md':
el.innerHTML = `<div class="markdown-embed"><div class="markdown-embed-title">${target.basename}</div><div class="markdown-embed-content node-insert-event markdown-embed-page"><div class="markdown-preview-view"></div></div><div class="markdown-embed-link" aria-label="Open link"><svg viewBox="0 0 100 100" class="link" width="20" height="20"><path fill="currentColor" stroke="currentColor" d="M74,8c-4.8,0-9.3,1.9-12.7,5.3l-10,10c-2.9,2.9-4.7,6.6-5.1,10.6C46,34.6,46,35.3,46,36c0,2.7,0.6,5.4,1.8,7.8l3.1-3.1 C50.3,39.2,50,37.6,50,36c0-3.7,1.5-7.3,4.1-9.9l10-10c2.6-2.6,6.2-4.1,9.9-4.1s7.3,1.5,9.9,4.1c2.6,2.6,4.1,6.2,4.1,9.9 s-1.5,7.3-4.1,9.9l-10,10C71.3,48.5,67.7,50,64,50c-1.6,0-3.2-0.3-4.7-0.8l-3.1,3.1c2.4,1.1,5,1.8,7.8,1.8c4.8,0,9.3-1.9,12.7-5.3 l10-10C90.1,35.3,92,30.8,92,26s-1.9-9.3-5.3-12.7C83.3,9.9,78.8,8,74,8L74,8z M62,36c-0.5,0-1,0.2-1.4,0.6l-24,24 c-0.5,0.5-0.7,1.2-0.6,1.9c0.2,0.7,0.7,1.2,1.4,1.4c0.7,0.2,1.4,0,1.9-0.6l24-24c0.6-0.6,0.8-1.5,0.4-2.2C63.5,36.4,62.8,36,62,36 z M36,46c-4.8,0-9.3,1.9-12.7,5.3l-10,10c-3.1,3.1-5,7.2-5.2,11.6c0,0.4,0,0.8,0,1.2c0,4.8,1.9,9.3,5.3,12.7 C16.7,90.1,21.2,92,26,92s9.3-1.9,12.7-5.3l10-10C52.1,73.3,54,68.8,54,64c0-2.7-0.6-5.4-1.8-7.8l-3.1,3.1 c0.5,1.5,0.8,3.1,0.8,4.7c0,3.7-1.5,7.3-4.1,9.9l-10,10C33.3,86.5,29.7,88,26,88s-7.3-1.5-9.9-4.1S12,77.7,12,74 c0-3.7,1.5-7.3,4.1-9.9l10-10c2.6-2.6,6.2-4.1,9.9-4.1c1.6,0,3.2,0.3,4.7,0.8l3.1-3.1C41.4,46.6,38.7,46,36,46L36,46z"></path></svg></div></div>`;
const previewEl = el.getElementsByClassName(
'markdown-preview-view'
)[0] as HTMLElement;
MarkdownRenderer.renderMarkdown(
await plugin.app.vault.cachedRead(target),
previewEl,
target.path,
null
);
await patchEmbeds(previewEl, target.path, depth + 1);
el.addClasses(['is-loaded']);
break;
default:
el.createEl(
'img',
{ attr: { src: plugin.app.vault.getResourcePath(target) } },
img => {
if (el.hasAttribute('width'))
img.setAttribute('width', el.getAttribute('width'));
if (el.hasAttribute('alt'))
img.setAttribute('alt', el.getAttribute('alt'));
}
);
el.addClasses(['image-embed', 'is-loaded']);
break;
}
}
});
};
patchEmbeds(this, filePath, 1);
})();
}
}
// We need to specify which attributes will be watched for changes. If an attribute is not included here, attributeChangedCallback will never be called for it
static get observedAttributes() {
return ['markdownbase64'];
}
}
// Now that our class is defined, we can register it
iframe.contentWindow.customElements.define('obsidian-markdown', ObsidianMarkdown);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(iframe.contentWindow as any).renderObsidianMarkdown = markdown => {
return `<obsidian-markdown markdownbase64="${utf8_to_b64(markdown)}" />`;
};
}
Example #11
Source File: ItemModal.ts From obsidian-rss with GNU General Public License v3.0 | 4 votes |
async display(): Promise<void> {
this.modalEl.addClass("rss-modal");
const {contentEl} = this;
contentEl.empty();
//don't add any scrolling to modal content
contentEl.style.height = "100%";
contentEl.style.overflowY = "hidden";
const topButtons = contentEl.createDiv('topButtons');
let actions = Array.of(Action.CREATE_NOTE, Action.PASTE, Action.COPY, Action.OPEN);
if (this.save) {
this.readButton = new ButtonComponent(topButtons)
.setIcon(this.item.read ? 'eye-off' : 'eye')
.setTooltip(this.item.read ? t("mark_as_unread") : t("mark_as_read"))
.onClick(async () => {
await this.markAsRead();
});
this.readButton.buttonEl.setAttribute("tabindex", "-1");
this.readButton.buttonEl.addClass("rss-button");
this.favoriteButton = new ButtonComponent(topButtons)
.setIcon(this.item.favorite ? 'star-glyph' : 'star')
.setTooltip(this.item.favorite ? t("remove_from_favorites") : t("mark_as_favorite"))
.onClick(async () => {
await this.markAsFavorite();
});
this.favoriteButton.buttonEl.setAttribute("tabindex", "-1");
this.favoriteButton.buttonEl.addClass("rss-button");
actions = Array.of(Action.TAGS, ...actions);
}
actions.forEach((action) => {
const button = new ButtonComponent(topButtons)
.setIcon(action.icon)
.setTooltip(action.name)
.onClick(async () => {
await action.processor(this.plugin, this.item);
});
button.buttonEl.setAttribute("tabindex", "-1");
button.buttonEl.addClass("rss-button");
});
//@ts-ignore
if (this.app.plugins.plugins["obsidian-tts"]) {
const ttsButton = new ButtonComponent(topButtons)
.setIcon("headphones")
.setTooltip(t("read_article_tts"))
.onClick(async () => {
const content = htmlToMarkdown(this.item.content);
//@ts-ignore
await this.app.plugins.plugins["obsidian-tts"].ttsService.say(this.item.title, content, this.item.language);
});
ttsButton.buttonEl.addClass("rss-button");
}
const prevButton = new ButtonComponent(topButtons)
.setIcon("left-arrow-with-tail")
.setTooltip(t("previous"))
.onClick(() => {
this.previous();
});
prevButton.buttonEl.addClass("rss-button");
const nextButton = new ButtonComponent(topButtons)
.setIcon("right-arrow-with-tail")
.setTooltip(t("next"))
.onClick(() => {
this.next();
});
nextButton.buttonEl.addClass("rss-button");
const title = contentEl.createEl('h1', 'rss-title');
title.addClass("rss-selectable");
title.setText(this.item.title);
const subtitle = contentEl.createEl("h3", "rss-subtitle");
subtitle.addClass("rss-selectable");
if (this.item.creator) {
subtitle.appendText(this.item.creator);
}
if (this.item.pubDate) {
subtitle.appendText(" - " + window.moment(this.item.pubDate).format(this.plugin.settings.dateFormat));
}
const tagEl = contentEl.createSpan("tags");
this.item.tags.forEach((tag) => {
const tagA = tagEl.createEl("a");
tagA.setText(tag);
tagA.addClass("tag", "rss-tag");
});
const content = contentEl.createDiv('rss-content');
content.addClass("rss-scrollable-content", "rss-selectable");
if (this.item.enclosure && this.plugin.settings.displayMedia) {
if (this.item.enclosureType.toLowerCase().contains("audio")) {
const audio = content.createEl("audio", {attr: {controls: "controls"}});
audio.createEl("source", {attr: {src: this.item.enclosure, type: this.item.enclosureType}});
}
if (this.item.enclosureType.toLowerCase().contains("video")) {
const video = content.createEl("video", {attr: {controls: "controls", width: "100%", height: "100%"}});
video.createEl("source", {attr: {src: this.item.enclosure, type: this.item.enclosureType}});
}
//embedded yt player
if (this.item.enclosure && this.item.id.startsWith("yt:")) {
content.createEl("iframe", {
attr: {
type: "text/html",
src: "https://www.youtube.com/embed/" + this.item.enclosure,
width: "100%",
height: "100%",
allowFullscreen: "true"
}
});
}
}
if (this.item.content) {
//prepend empty yaml to fix rendering errors
const markdown = "---\n---" + rssToMd(this.plugin, this.item.content);
await MarkdownRenderer.renderMarkdown(markdown, content, "", this.plugin);
this.item.highlights.forEach(highlight => {
if (content.innerHTML.includes(highlight)) {
const newNode = contentEl.createEl("mark");
newNode.innerHTML = highlight;
content.innerHTML = content.innerHTML.replace(highlight, newNode.outerHTML);
newNode.remove();
} else {
console.log("Highlight not included");
console.log(highlight);
}
});
content.addEventListener('contextmenu', (event) => {
event.preventDefault();
const selection = document.getSelection();
const range = selection.getRangeAt(0);
const div = contentEl.createDiv();
const htmlContent = range.cloneContents();
const html = htmlContent.cloneNode(true);
div.appendChild(html);
const selected = div.innerHTML;
div.remove();
const menu = new Menu(this.app);
let previousHighlight: HTMLElement;
if (this.item.highlights.includes(range.startContainer.parentElement.innerHTML)) {
previousHighlight = range.startContainer.parentElement;
}
if (this.item.highlights.includes(range.startContainer.parentElement.parentElement.innerHTML)) {
previousHighlight = range.startContainer.parentElement.parentElement;
}
if(previousHighlight) {
menu.addItem(item => {
item
.setIcon("highlight-glyph")
.setTitle(t("highlight_remove"))
.onClick(async () => {
const replacement = contentEl.createSpan();
replacement.innerHTML = previousHighlight.innerHTML;
previousHighlight.replaceWith(replacement);
this.item.highlights.remove(previousHighlight.innerHTML);
const feedContents = this.plugin.settings.items;
await this.plugin.writeFeedContent(() => {
return feedContents;
});
});
});
}else if(!this.item.highlights.includes(selected) && selected.length > 0) {
menu.addItem(item => {
item
.setIcon("highlight-glyph")
.setTitle(t("highlight"))
.onClick(async () => {
const newNode = contentEl.createEl("mark");
newNode.innerHTML = selected;
range.deleteContents();
range.insertNode(newNode);
this.item.highlights.push(selected);
const feedContents = this.plugin.settings.items;
await this.plugin.writeFeedContent(() => {
return feedContents;
});
//cleaning up twice to remove nested elements
this.removeDanglingElements(contentEl);
this.removeDanglingElements(contentEl);
});
});
}
if(selected.length > 0) {
menu
.addItem(item => {
item
.setIcon("documents")
.setTitle(t("copy_to_clipboard"))
.onClick(async () => {
await copy(selection.toString());
});
});
//@ts-ignore
if (this.app.plugins.plugins["obsidian-tts"]) {
menu.addItem(item => {
item
.setIcon("headphones")
.setTitle(t("read_article_tts"))
.onClick(() => {
//@ts-ignore
const tts = this.app.plugins.plugins["obsidian-tts"].ttsService;
tts.say("", selection.toString());
});
});
}
}
menu.showAtMouseEvent(event);
});
}
}