Fixes #360
parent
495012f796
commit
c1528eff08
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "md.silverbullet",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
|
@ -30,12 +30,12 @@ Deno.test("Markdown render", async () => {
|
|||
// console.log("HTML", html);
|
||||
});
|
||||
|
||||
Deno.test("Smart hard break test", () => {
|
||||
Deno.test("Smart hard break test", async () => {
|
||||
const example = `**Hello**
|
||||
*world!*`;
|
||||
const lang = buildMarkdown([]);
|
||||
const tree = parse(lang, example);
|
||||
const html = renderMarkdownToHtml(tree, {
|
||||
const html = await renderMarkdownToHtml(tree, {
|
||||
failOnUnknown: true,
|
||||
smartHardBreak: true,
|
||||
});
|
||||
|
|
|
@ -13,6 +13,8 @@ type MarkdownRenderOptions = {
|
|||
annotationPositions?: true;
|
||||
renderFrontMatter?: true;
|
||||
attachmentUrlPrefix?: string;
|
||||
// When defined, use to inline images as data: urls
|
||||
inlineAttachments?: (url: string) => Promise<string>;
|
||||
};
|
||||
|
||||
function cleanTags(values: (Tag | null)[]): Tag[] {
|
||||
|
@ -390,11 +392,36 @@ function render(
|
|||
}
|
||||
}
|
||||
|
||||
export function renderMarkdownToHtml(
|
||||
async function traverseTag(
|
||||
t: Tag,
|
||||
fn: (t: Tag) => Promise<void>,
|
||||
): Promise<void> {
|
||||
await fn(t);
|
||||
if (typeof t === "string") {
|
||||
return;
|
||||
}
|
||||
if (t.body) {
|
||||
for (const child of t.body) {
|
||||
await traverseTag(child, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderMarkdownToHtml(
|
||||
t: ParseTree,
|
||||
options: MarkdownRenderOptions = {},
|
||||
) {
|
||||
preprocess(t, options);
|
||||
const htmlTree = posPreservingRender(t, options);
|
||||
if (htmlTree && options.inlineAttachments) {
|
||||
await traverseTag(htmlTree, async (t) => {
|
||||
if (typeof t === "string") {
|
||||
return;
|
||||
}
|
||||
if (t.name === "img") {
|
||||
t.attrs!.src = await options.inlineAttachments!(t.attrs!.src!);
|
||||
}
|
||||
});
|
||||
}
|
||||
return renderHtml(htmlTree);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { clientStore, editor, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import {
|
||||
clientStore,
|
||||
editor,
|
||||
space,
|
||||
system,
|
||||
} from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { asset } from "$sb/plugos-syscall/mod.ts";
|
||||
import { parseMarkdown } from "../../plug-api/silverbullet-syscall/markdown.ts";
|
||||
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
||||
|
@ -12,11 +17,21 @@ export async function updateMarkdownPreview() {
|
|||
// const cleanMd = await cleanMarkdown(text);
|
||||
const css = await asset.readAsset("assets/styles.css");
|
||||
const js = await asset.readAsset("assets/handler.js");
|
||||
const html = renderMarkdownToHtml(mdTree, {
|
||||
const html = await renderMarkdownToHtml(mdTree, {
|
||||
smartHardBreak: true,
|
||||
annotationPositions: true,
|
||||
renderFrontMatter: true,
|
||||
attachmentUrlPrefix: "fs/",
|
||||
inlineAttachments: async (url): Promise<string> => {
|
||||
if (!url.includes("://")) {
|
||||
try {
|
||||
return await space.readAttachment(url);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
},
|
||||
});
|
||||
await editor.showPanel(
|
||||
"rhs",
|
||||
|
|
|
@ -7,7 +7,7 @@ export async function markdownWidget(
|
|||
): Promise<WidgetContent> {
|
||||
const mdTree = await parseMarkdown(bodyText);
|
||||
|
||||
const html = renderMarkdownToHtml(mdTree, {
|
||||
const html = await renderMarkdownToHtml(mdTree, {
|
||||
smartHardBreak: true,
|
||||
});
|
||||
return Promise.resolve({
|
||||
|
|
|
@ -15,11 +15,12 @@ import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts";
|
|||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { lezerToParseTree } from "../../common/markdown_parser/parse_tree.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import { urlToPathname } from "../../plugos/util.ts";
|
||||
|
||||
class TableViewWidget extends WidgetType {
|
||||
constructor(
|
||||
readonly pos: number,
|
||||
readonly editorView: EditorView,
|
||||
readonly editor: Editor,
|
||||
readonly t: ParseTree,
|
||||
) {
|
||||
super();
|
||||
|
@ -32,17 +33,31 @@ class TableViewWidget extends WidgetType {
|
|||
// Pulling data-pos to put the cursor in the right place, falling back
|
||||
// to the start of the table.
|
||||
const dataAttributes = (e.target as any).dataset;
|
||||
this.editorView.dispatch({
|
||||
this.editor.editorView!.dispatch({
|
||||
selection: {
|
||||
anchor: dataAttributes.pos ? +dataAttributes.pos : this.pos,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
dom.innerHTML = renderMarkdownToHtml(this.t, {
|
||||
renderMarkdownToHtml(this.t, {
|
||||
// Annotate every element with its position so we can use it to put
|
||||
// the cursor there when the user clicks on the table.
|
||||
annotationPositions: true,
|
||||
inlineAttachments: async (url): Promise<string> => {
|
||||
if (!url.includes("://")) {
|
||||
try {
|
||||
const d = await this.editor.space.readAttachment(url, "dataurl");
|
||||
return d.data as string;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
},
|
||||
}).then((html) => {
|
||||
dom.innerHTML = html;
|
||||
});
|
||||
return dom;
|
||||
}
|
||||
|
@ -90,7 +105,7 @@ export function tablePlugin(editor: Editor) {
|
|||
Decoration.widget({
|
||||
widget: new TableViewWidget(
|
||||
from,
|
||||
editor.editorView!,
|
||||
editor,
|
||||
lezerToParseTree(text, node.node),
|
||||
),
|
||||
}).range(from),
|
||||
|
|
Loading…
Reference in New Issue