diff --git a/mobile/android/app/release/output-metadata.json b/mobile/android/app/release/output-metadata.json new file mode 100755 index 00000000..bdc8b02f --- /dev/null +++ b/mobile/android/app/release/output-metadata.json @@ -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" +} \ No newline at end of file diff --git a/plugs/markdown/markdown_render.test.ts b/plugs/markdown/markdown_render.test.ts index fb625b85..d85cabed 100644 --- a/plugs/markdown/markdown_render.test.ts +++ b/plugs/markdown/markdown_render.test.ts @@ -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, }); diff --git a/plugs/markdown/markdown_render.ts b/plugs/markdown/markdown_render.ts index b6c87cd4..d770d580 100644 --- a/plugs/markdown/markdown_render.ts +++ b/plugs/markdown/markdown_render.ts @@ -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; }; function cleanTags(values: (Tag | null)[]): Tag[] { @@ -390,11 +392,36 @@ function render( } } -export function renderMarkdownToHtml( +async function traverseTag( + t: Tag, + fn: (t: Tag) => Promise, +): Promise { + 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); } diff --git a/plugs/markdown/preview.ts b/plugs/markdown/preview.ts index e61ee4eb..0fe662a2 100644 --- a/plugs/markdown/preview.ts +++ b/plugs/markdown/preview.ts @@ -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 => { + if (!url.includes("://")) { + try { + return await space.readAttachment(url); + } catch (e: any) { + console.error(e); + return url; + } + } + return url; + }, }); await editor.showPanel( "rhs", diff --git a/plugs/markdown/widget.ts b/plugs/markdown/widget.ts index 9efe10f4..50ef7c8e 100644 --- a/plugs/markdown/widget.ts +++ b/plugs/markdown/widget.ts @@ -7,7 +7,7 @@ export async function markdownWidget( ): Promise { const mdTree = await parseMarkdown(bodyText); - const html = renderMarkdownToHtml(mdTree, { + const html = await renderMarkdownToHtml(mdTree, { smartHardBreak: true, }); return Promise.resolve({ diff --git a/web/cm_plugins/table.ts b/web/cm_plugins/table.ts index 5bca2e42..69bc9232 100644 --- a/web/cm_plugins/table.ts +++ b/web/cm_plugins/table.ts @@ -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 => { + 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),