Fixes #100 implements a custom Markdown renderer
parent
b8e27b216e
commit
3d671e8195
|
@ -48,11 +48,11 @@ export function mdExtensionStyleTags({ nodeType, tag }: MDExt): {
|
|||
}
|
||||
|
||||
export function loadMarkdownExtensions(system: System<any>): MDExt[] {
|
||||
let mdExtensions: MDExt[] = [];
|
||||
for (let plug of system.loadedPlugs.values()) {
|
||||
let manifest = plug.manifest as Manifest;
|
||||
const mdExtensions: MDExt[] = [];
|
||||
for (const plug of system.loadedPlugs.values()) {
|
||||
const manifest = plug.manifest as Manifest;
|
||||
if (manifest.syntax) {
|
||||
for (let [nodeType, def] of Object.entries(manifest.syntax)) {
|
||||
for (const [nodeType, def] of Object.entries(manifest.syntax)) {
|
||||
mdExtensions.push({
|
||||
nodeType,
|
||||
tag: Tag.define(),
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
|
||||
import type { SyntaxNode } from "./deps.ts";
|
||||
import type { Language } from "./deps.ts";
|
||||
import type { ParseTree } from "$sb/lib/tree.ts";
|
||||
import type { Language, SyntaxNode } from "./deps.ts";
|
||||
|
||||
export function lezerToParseTree(
|
||||
text: string,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { ParseTree } from "./lib/tree.ts";
|
||||
import { ParsedQuery } from "./lib/query.ts";
|
||||
import type { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { ParsedQuery } from "$sb/lib/query.ts";
|
||||
|
||||
export type AppEvent =
|
||||
| "page:click"
|
||||
|
|
|
@ -87,8 +87,8 @@ export function replaceRange(
|
|||
return syscall("editor.replaceRange", from, to, text);
|
||||
}
|
||||
|
||||
export function moveCursor(pos: number): Promise<void> {
|
||||
return syscall("editor.moveCursor", pos);
|
||||
export function moveCursor(pos: number, center = false): Promise<void> {
|
||||
return syscall("editor.moveCursor", pos, center);
|
||||
}
|
||||
|
||||
export function insertAtCursor(text: string): Promise<void> {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { syscall } from "./syscall.ts";
|
||||
import { syscall } from "$sb/silverbullet-syscall/syscall.ts";
|
||||
|
||||
import type { ParseTree } from "$sb/lib/tree.ts";
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { editor, space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
|
||||
|
||||
import { editor, space, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { renderDirectives } from "./directives.ts";
|
||||
|
||||
export async function updateDirectivesOnPageCommand() {
|
||||
const currentPage = await editor.getCurrentPage();
|
||||
await editor.save();
|
||||
if (
|
||||
await invokeFunction(
|
||||
await system.invokeFunction(
|
||||
"server",
|
||||
"updateDirectivesOnPage",
|
||||
currentPage,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// This is some shocking stuff. My profession would kill me for this.
|
||||
|
||||
import { YAML } from "../../common/deps.ts";
|
||||
import * as YAML from "yaml";
|
||||
import { jsonToMDTable, renderTemplate } from "./util.ts";
|
||||
|
||||
// Enables plugName.functionName(arg1, arg2) syntax in JS expressions
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
document.getElementById("root").addEventListener("click", (e) => {
|
||||
// console.log("Got click", e.target)
|
||||
const dataSet = e.target.dataset;
|
||||
if(dataSet["onclick"]) {
|
||||
sendEvent("preview:click", dataSet["onclick"]);
|
||||
} else if(dataSet["pos"]) {
|
||||
sendEvent("preview:click", JSON.stringify(["pos", dataSet["pos"]]));
|
||||
}
|
||||
})
|
|
@ -8,11 +8,24 @@ body {
|
|||
padding-right: 20px;
|
||||
}
|
||||
|
||||
table.front-matter {
|
||||
border: 1px solid #555;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
table.front-matter .key {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
ul li p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
background-color: #333;
|
||||
color: #eee;
|
||||
|
@ -48,3 +61,7 @@ hr:after {
|
|||
content: "···";
|
||||
letter-spacing: 1em;
|
||||
}
|
||||
|
||||
span.highlight {
|
||||
background-color: yellow;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { assertEquals } from "https://deno.land/std@0.152.0/testing/asserts.ts";
|
||||
import { renderHtml } from "./html_render.ts";
|
||||
|
||||
Deno.test("HTML Render", () => {
|
||||
assertEquals(
|
||||
renderHtml({
|
||||
name: "b",
|
||||
body: "hello",
|
||||
}),
|
||||
`<b>hello</b>`,
|
||||
);
|
||||
assertEquals(
|
||||
renderHtml({
|
||||
name: "a",
|
||||
attrs: {
|
||||
href: "https://example.com",
|
||||
},
|
||||
body: "hello",
|
||||
}),
|
||||
`<a href="https://example.com">hello</a>`,
|
||||
);
|
||||
assertEquals(
|
||||
renderHtml({
|
||||
name: "span",
|
||||
body: "<>",
|
||||
}),
|
||||
`<span><></span>`,
|
||||
);
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
export const Fragment = "FRAGMENT";
|
||||
|
||||
export type Tag = {
|
||||
name: string;
|
||||
attrs?: Record<string, string | undefined>;
|
||||
body: Tag[] | string;
|
||||
} | string;
|
||||
|
||||
function htmlEscape(s: string): string {
|
||||
return s.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
export function renderHtml(t: Tag | null): string {
|
||||
if (!t) {
|
||||
return "";
|
||||
}
|
||||
if (typeof t === "string") {
|
||||
return htmlEscape(t);
|
||||
}
|
||||
const attrs = t.attrs
|
||||
? " " + Object.entries(t.attrs)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([k, v]) => `${k}="${htmlEscape(v!)}"`).join(
|
||||
" ",
|
||||
)
|
||||
: "";
|
||||
const body = typeof t.body === "string"
|
||||
? htmlEscape(t.body)
|
||||
: t.body.map(renderHtml).join("");
|
||||
if (t.name === Fragment) {
|
||||
return body;
|
||||
}
|
||||
if (t.body) {
|
||||
return `<${t.name}${attrs}>${body}</${t.name}>`;
|
||||
} else {
|
||||
return `<${t.name}${attrs}/>`;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ name: markdown
|
|||
imports:
|
||||
- https://get.silverbullet.md/global.plug.json
|
||||
assets:
|
||||
- "*.css"
|
||||
- "assets/*"
|
||||
functions:
|
||||
toggle:
|
||||
path: "./markdown.ts:togglePreview"
|
||||
|
@ -18,3 +18,8 @@ functions:
|
|||
- editor:updated
|
||||
- editor:pageLoaded
|
||||
- editor:pageReloaded
|
||||
previewClickHandler:
|
||||
path: "./preview.ts:previewClickHandler"
|
||||
env: client
|
||||
events:
|
||||
- preview:click
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import buildMarkdown from "../../common/parser.ts";
|
||||
import { parse } from "../../common/parse_tree.ts";
|
||||
import { renderHtml } from "./html_render.ts";
|
||||
import { System } from "../../plugos/system.ts";
|
||||
|
||||
import corePlug from "../../dist_bundle/_plug/core.plug.json" assert {
|
||||
type: "json",
|
||||
};
|
||||
import tasksPlug from "../../dist_bundle/_plug/tasks.plug.json" assert {
|
||||
type: "json",
|
||||
};
|
||||
import { createSandbox } from "../../plugos/environments/deno_sandbox.ts";
|
||||
import { loadMarkdownExtensions } from "../../common/markdown_ext.ts";
|
||||
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
||||
import { assertEquals } from "https://deno.land/std@0.152.0/testing/asserts.ts";
|
||||
|
||||
Deno.test("Markdown render", async () => {
|
||||
const system = new System<any>("server");
|
||||
await system.load(corePlug, createSandbox);
|
||||
await system.load(tasksPlug, createSandbox);
|
||||
const lang = buildMarkdown(loadMarkdownExtensions(system));
|
||||
const testFile = Deno.readTextFileSync(
|
||||
new URL("test/example.md", import.meta.url).pathname,
|
||||
);
|
||||
const tree = parse(lang, testFile);
|
||||
renderMarkdownToHtml(tree, {
|
||||
failOnUnknown: true,
|
||||
renderFrontMatter: true,
|
||||
});
|
||||
// console.log("HTML", html);
|
||||
});
|
||||
|
||||
Deno.test("Smart hard break test", () => {
|
||||
const example = `**Hello**
|
||||
*world!*`;
|
||||
const lang = buildMarkdown([]);
|
||||
const tree = parse(lang, example);
|
||||
const html = renderMarkdownToHtml(tree, {
|
||||
failOnUnknown: true,
|
||||
smartHardBreak: true,
|
||||
});
|
||||
assertEquals(
|
||||
html,
|
||||
`<p><strong>Hello</strong><br/><em>world!</em></p>`,
|
||||
);
|
||||
});
|
|
@ -0,0 +1,350 @@
|
|||
import {
|
||||
findNodeOfType,
|
||||
ParseTree,
|
||||
renderToText,
|
||||
traverseTree,
|
||||
} from "$sb/lib/tree.ts";
|
||||
import * as YAML from "yaml";
|
||||
import { Fragment, renderHtml, Tag } from "./html_render.ts";
|
||||
|
||||
type MarkdownRenderOptions = {
|
||||
failOnUnknown?: true;
|
||||
smartHardBreak?: true;
|
||||
annotationPositions?: true;
|
||||
renderFrontMatter?: true;
|
||||
};
|
||||
|
||||
function cleanTags(values: (Tag | null)[]): Tag[] {
|
||||
const result: Tag[] = [];
|
||||
for (const value of values) {
|
||||
if (value) {
|
||||
result.push(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function preprocess(t: ParseTree, options: MarkdownRenderOptions = {}) {
|
||||
traverseTree(t, (node) => {
|
||||
if (node.type === "Paragraph" && options.smartHardBreak) {
|
||||
for (const child of node.children!) {
|
||||
// If at the paragraph level there's a newline, let's turn it into a hard break
|
||||
if (!child.type && child.text === "\n") {
|
||||
child.type = "HardBreak";
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function posPreservingRender(
|
||||
t: ParseTree,
|
||||
options: MarkdownRenderOptions = {},
|
||||
): Tag | null {
|
||||
const tag = render(t, options);
|
||||
if (!options.annotationPositions) {
|
||||
return tag;
|
||||
}
|
||||
if (!tag) {
|
||||
return null;
|
||||
}
|
||||
if (typeof tag === "string") {
|
||||
return tag;
|
||||
}
|
||||
if (t.from) {
|
||||
if (!tag.attrs) {
|
||||
tag.attrs = {};
|
||||
}
|
||||
tag.attrs["data-pos"] = "" + t.from;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
function render(
|
||||
t: ParseTree,
|
||||
options: MarkdownRenderOptions = {},
|
||||
): Tag | null {
|
||||
if (t.type?.endsWith("Mark") || t.type?.endsWith("Delimiter")) {
|
||||
return null;
|
||||
}
|
||||
switch (t.type) {
|
||||
case "Document":
|
||||
return {
|
||||
name: Fragment,
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "FrontMatter":
|
||||
if (options.renderFrontMatter) {
|
||||
const yamlCode = renderToText(t.children![1]);
|
||||
const parsedYaml = YAML.parse(yamlCode) as Record<string, any>;
|
||||
const rows: Tag[] = [];
|
||||
for (const [k, v] of Object.entries(parsedYaml)) {
|
||||
rows.push({
|
||||
name: "tr",
|
||||
body: [
|
||||
{ name: "td", attrs: { class: "key" }, body: k },
|
||||
{
|
||||
name: "td",
|
||||
attrs: { class: "value" },
|
||||
body: YAML.stringify(v),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return {
|
||||
name: "table",
|
||||
attrs: {
|
||||
class: "front-matter",
|
||||
},
|
||||
body: rows,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case "CommentBlock":
|
||||
// Remove, for now
|
||||
return null;
|
||||
case "ATXHeading1":
|
||||
return {
|
||||
name: "h1",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "ATXHeading2":
|
||||
return {
|
||||
name: "h2",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "ATXHeading3":
|
||||
return {
|
||||
name: "h3",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "ATXHeading4":
|
||||
return {
|
||||
name: "h4",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "ATXHeading5":
|
||||
return {
|
||||
name: "h5",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "Paragraph":
|
||||
return {
|
||||
name: "p",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
// Code blocks
|
||||
case "FencedCode":
|
||||
case "CodeBlock": {
|
||||
return {
|
||||
name: "pre",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
}
|
||||
case "CodeText":
|
||||
return t.children![0].text!;
|
||||
case "Blockquote":
|
||||
return {
|
||||
name: "blockquote",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "HardBreak":
|
||||
return {
|
||||
name: "br",
|
||||
body: "",
|
||||
};
|
||||
// Basic styling
|
||||
case "Emphasis":
|
||||
return {
|
||||
name: "em",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "Highlight":
|
||||
return {
|
||||
name: "span",
|
||||
attrs: {
|
||||
class: "highlight",
|
||||
},
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "InlineCode":
|
||||
return {
|
||||
name: "tt",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "BulletList":
|
||||
return {
|
||||
name: "ul",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "OrderedList":
|
||||
return {
|
||||
name: "ol",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "ListItem":
|
||||
return {
|
||||
name: "li",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "StrongEmphasis":
|
||||
return {
|
||||
name: "strong",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "HorizontalRule":
|
||||
return {
|
||||
name: "hr",
|
||||
body: "",
|
||||
};
|
||||
case "Link": {
|
||||
const linkText = t.children![1].text!;
|
||||
const url = findNodeOfType(t, "URL")!.children![0].text!;
|
||||
return {
|
||||
name: "a",
|
||||
attrs: {
|
||||
href: url,
|
||||
},
|
||||
body: linkText,
|
||||
};
|
||||
}
|
||||
case "Image": {
|
||||
const altText = t.children![1].text!;
|
||||
let url = findNodeOfType(t, "URL")!.children![0].text!;
|
||||
if (url.indexOf("://") === -1) {
|
||||
url = `fs/${url}`;
|
||||
}
|
||||
return {
|
||||
name: "img",
|
||||
attrs: {
|
||||
src: url,
|
||||
alt: altText,
|
||||
},
|
||||
body: "",
|
||||
};
|
||||
}
|
||||
|
||||
// Custom stuff
|
||||
case "WikiLink": {
|
||||
// console.log("WikiLink", JSON.stringify(t, null, 2));
|
||||
const ref = findNodeOfType(t, "WikiLinkPage")!.children![0].text!;
|
||||
return {
|
||||
name: "a",
|
||||
attrs: {
|
||||
href: `/${ref}`,
|
||||
},
|
||||
body: ref,
|
||||
};
|
||||
}
|
||||
case "NakedURL": {
|
||||
const url = t.children![0].text!;
|
||||
return {
|
||||
name: "a",
|
||||
attrs: {
|
||||
href: url,
|
||||
},
|
||||
body: url,
|
||||
};
|
||||
}
|
||||
case "Hashtag":
|
||||
return {
|
||||
name: "strong",
|
||||
body: t.children![0].text!,
|
||||
};
|
||||
|
||||
case "Task":
|
||||
return {
|
||||
name: "span",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "TaskMarker":
|
||||
return {
|
||||
name: "input",
|
||||
attrs: {
|
||||
type: "checkbox",
|
||||
checked: t.children![0].text !== "[ ]" ? "checked" : undefined,
|
||||
"data-onclick": JSON.stringify(["task", t.to]),
|
||||
},
|
||||
body: "",
|
||||
};
|
||||
case "NamedAnchor":
|
||||
return {
|
||||
name: "a",
|
||||
attrs: {
|
||||
name: t.children![0].text?.substring(1),
|
||||
},
|
||||
body: "",
|
||||
};
|
||||
case "CommandLink": {
|
||||
const commandText = t.children![0].text!.substring(
|
||||
2,
|
||||
t.children![0].text!.length - 2,
|
||||
);
|
||||
|
||||
return {
|
||||
name: "button",
|
||||
attrs: {
|
||||
"data-onclick": JSON.stringify(["command", commandText]),
|
||||
},
|
||||
body: commandText,
|
||||
};
|
||||
}
|
||||
|
||||
case "DeadlineDate":
|
||||
return renderToText(t);
|
||||
|
||||
// Tables
|
||||
case "Table":
|
||||
return {
|
||||
name: "table",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "TableHeader":
|
||||
return {
|
||||
name: "thead",
|
||||
body: [
|
||||
{
|
||||
name: "tr",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
},
|
||||
],
|
||||
};
|
||||
case "TableCell":
|
||||
return {
|
||||
name: "td",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
case "TableRow":
|
||||
return {
|
||||
name: "tr",
|
||||
body: cleanTags(mapRender(t.children!)),
|
||||
};
|
||||
// Text
|
||||
case undefined:
|
||||
return t.text!;
|
||||
default:
|
||||
if (options.failOnUnknown) {
|
||||
console.error("Not handling", JSON.stringify(t, null, 2));
|
||||
throw new Error(`Unknown markdown node type ${t.type}`);
|
||||
} else {
|
||||
// Falling back to rendering verbatim
|
||||
console.warn("Not handling", JSON.stringify(t, null, 2));
|
||||
return renderToText(t);
|
||||
}
|
||||
}
|
||||
|
||||
function mapRender(children: ParseTree[]) {
|
||||
return children.map((t) => posPreservingRender(t, options));
|
||||
}
|
||||
}
|
||||
|
||||
export function renderMarkdownToHtml(
|
||||
t: ParseTree,
|
||||
options: MarkdownRenderOptions = {},
|
||||
) {
|
||||
preprocess(t, options);
|
||||
const htmlTree = posPreservingRender(t, options);
|
||||
return renderHtml(htmlTree);
|
||||
}
|
|
@ -1,28 +1,40 @@
|
|||
import MarkdownIt from "https://esm.sh/markdown-it@13.0.1";
|
||||
import taskLists from "https://esm.sh/markdown-it-task-lists@2.1.1";
|
||||
|
||||
import { clientStore, editor } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { clientStore, editor, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { asset } from "$sb/plugos-syscall/mod.ts";
|
||||
import { cleanMarkdown } from "./util.ts";
|
||||
|
||||
const md = new MarkdownIt({
|
||||
linkify: true,
|
||||
html: false,
|
||||
typographer: true,
|
||||
}).use(taskLists);
|
||||
import { parseMarkdown } from "../../plug-api/silverbullet-syscall/markdown.ts";
|
||||
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
||||
|
||||
export async function updateMarkdownPreview() {
|
||||
if (!(await clientStore.get("enableMarkdownPreview"))) {
|
||||
return;
|
||||
}
|
||||
const text = await editor.getText();
|
||||
const cleanMd = await cleanMarkdown(text);
|
||||
const css = await asset.readAsset("styles.css");
|
||||
const mdTree = await parseMarkdown(text);
|
||||
// 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, {
|
||||
smartHardBreak: true,
|
||||
annotationPositions: true,
|
||||
renderFrontMatter: true,
|
||||
});
|
||||
await editor.showPanel(
|
||||
"rhs",
|
||||
2,
|
||||
`<html><head><style>${css}</style></head><body>${
|
||||
md.render(cleanMd)
|
||||
}</body></html>`,
|
||||
`<html><head><style>${css}</style></head><body><div id="root">${html}</div></body></html>`,
|
||||
js,
|
||||
);
|
||||
}
|
||||
|
||||
export async function previewClickHandler(e: any) {
|
||||
const [eventName, arg] = JSON.parse(e);
|
||||
// console.log("Got click", eventName, arg);
|
||||
switch (eventName) {
|
||||
case "pos":
|
||||
// console.log("Moving cursor to", +arg);
|
||||
await editor.moveCursor(+arg, true);
|
||||
break;
|
||||
case "command":
|
||||
await system.invokeCommand(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
name: Sup
|
||||
---
|
||||
# Hello world
|
||||
This is **bold** and _italic_, or *italic*. And a **_mix_**. And ==highlight==!
|
||||
|
||||
This is one line
|
||||
and this another.
|
||||
|
||||
Lists:
|
||||
* This
|
||||
* Is a
|
||||
* list
|
||||
* And here we go nested
|
||||
1. This is a numbered
|
||||
2. Two
|
||||
* And different
|
||||
* Bla
|
||||
* More bla
|
||||
|
||||
And:
|
||||
|
||||
1. Numbered
|
||||
2. Two
|
||||
|
||||
## Second heading
|
||||
|
||||
And some
|
||||
|
||||
```
|
||||
Code
|
||||
bla
|
||||
bla
|
||||
|
||||
bla
|
||||
```
|
||||
|
||||
And like this:
|
||||
|
||||
More code
|
||||
Bla
|
||||
|
||||
And a blockquote:
|
||||
|
||||
> Sup yo
|
||||
> Empty line
|
||||
> Second part
|
||||
|
||||
<!-- this is a comment -->
|
||||
|
||||
And more custom stuff
|
||||
[[Page link]]
|
||||
|
||||
{[Command button]}
|
||||
|
||||
* [ ] #next Task
|
||||
* [x] #next Task 2
|
||||
* [ ] Task with dealine 📅 2022-05-06 fef
|
||||
|
||||
|
||||
|
||||
https://community.mattermost.com
|
||||
|
||||
$anchor
|
||||
|
||||
[A link](https://silverbullet.md)
|
||||
|
||||
## Tables
|
||||
|
||||
|type |actor_login|created_at |payload_ref |
|
||||
|---------|--------|--------------------|----------------------|
|
||||
|PushEvent|avb|2022-10-27T08:27:48Z|refs/heads/master |
|
||||
|PushEvent|avb|2022-10-27T04:31:27Z|refs/heads/jitterSched|
|
||||
|
||||
|
||||
Here is something
|
||||
|
||||
---
|
||||
|
||||
A new thing.
|
||||
|
||||
![alt text](https://image.jpg)
|
|
@ -88,6 +88,14 @@ export function taskToggle(event: ClickEvent) {
|
|||
return taskToggleAtPos(event.pos);
|
||||
}
|
||||
|
||||
export function previewTaskToggle(eventString: string) {
|
||||
const [eventName, pos] = JSON.parse(eventString);
|
||||
if (eventName === "task") {
|
||||
console.log("Gotta toggle a task at", pos);
|
||||
return taskToggleAtPos(+pos);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
|
||||
let changeTo = "[x]";
|
||||
if (node.children![0].text === "[x]" || node.children![0].text === "[X]") {
|
||||
|
@ -139,6 +147,7 @@ export async function taskToggleAtPos(pos: number) {
|
|||
addParentPointers(mdTree);
|
||||
|
||||
const node = nodeAtPos(mdTree, pos);
|
||||
// console.log("Got this node", node?.type);
|
||||
if (node && node.type === "TaskMarker") {
|
||||
await toggleTaskMarker(node, pos);
|
||||
}
|
||||
|
|
|
@ -47,3 +47,8 @@ functions:
|
|||
key: Alt-+
|
||||
contexts:
|
||||
- DeadlineDate
|
||||
previewTaskToggle:
|
||||
env: client
|
||||
path: ./task.ts:previewTaskToggle
|
||||
events:
|
||||
- preview:click
|
|
@ -83,8 +83,10 @@ export function Panel({
|
|||
editor.dispatchAppEvent(data.name, ...data.args);
|
||||
}
|
||||
};
|
||||
console.log("Registering event handler");
|
||||
globalThis.addEventListener("message", messageListener);
|
||||
return () => {
|
||||
console.log("Unregistering event handler");
|
||||
globalThis.removeEventListener("message", messageListener);
|
||||
};
|
||||
}, []);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Editor } from "../editor.tsx";
|
||||
import { Transaction } from "../deps.ts";
|
||||
import { EditorView, Transaction } from "../deps.ts";
|
||||
import { SysCallMapping } from "../../plugos/system.ts";
|
||||
import { FilterOption } from "../../common/types.ts";
|
||||
|
||||
|
@ -113,12 +113,24 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
|
|||
},
|
||||
});
|
||||
},
|
||||
"editor.moveCursor": (_ctx, pos: number) => {
|
||||
"editor.moveCursor": (_ctx, pos: number, center = false) => {
|
||||
editor.editorView!.dispatch({
|
||||
selection: {
|
||||
anchor: pos,
|
||||
},
|
||||
});
|
||||
if (center) {
|
||||
editor.editorView!.dispatch({
|
||||
effects: [
|
||||
EditorView.scrollIntoView(
|
||||
pos,
|
||||
{
|
||||
y: "center",
|
||||
},
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
"editor.setSelection": (_ctx, from: number, to: number) => {
|
||||
const editorView = editor.editorView!;
|
||||
|
|
Loading…
Reference in New Issue