Skip to content

Commit

Permalink
Markdown text
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Jan 11, 2025
1 parent a2e066f commit dbd412f
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 26 deletions.
43 changes: 22 additions & 21 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,26 @@
}
},
"cSpell.words": [
"activitypub",
"botkit",
"denokv",
"Fedify",
"fediverse",
"halfyear",
"hongminhee",
"inlines",
"logtape",
"Minhee",
"Misskey",
"multikey",
"nodeinfo",
"phensley",
"Tailscale",
"unfollow",
"unfollowed",
"unfollows",
"uuidv7",
"vitepress"
]
"activitypub",
"botkit",
"denokv",
"Fedify",
"fediverse",
"halfyear",
"hongminhee",
"inlines",
"linkify",
"logtape",
"Minhee",
"Misskey",
"multikey",
"nodeinfo",
"phensley",
"Tailscale",
"unfollow",
"unfollowed",
"unfollows",
"uuidv7",
"vitepress"
]
}
2 changes: 2 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
},
"imports": {
"@fedify/fedify": "jsr:@fedify/fedify@^1.3.3",
"@fedify/markdown-it-mention": "jsr:@fedify/markdown-it-mention@^0.2.0",
"@hongminhee/x-forwarded-fetch": "jsr:@hongminhee/x-forwarded-fetch@^0.2.0",
"@logtape/logtape": "jsr:@logtape/logtape@^0.8.0",
"@phensley/language-tag": "npm:@phensley/language-tag@^1.9.2",
"@std/assert": "jsr:@std/assert@^1.0.10",
"@std/html": "jsr:@std/html@^1.0.3",
"@std/uuid": "jsr:@std/uuid@^1.0.4",
"markdown-it": "npm:markdown-it@^14.1.0",
"xss": "npm:xss@^1.0.15"
},
"lock": false,
Expand Down
99 changes: 95 additions & 4 deletions docs/concepts/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ The above code will create a text like this:
Paragraphs
----------
The `text()` template string tag creates a block `Text` object.
You can create a paragraph by simply writing a text. If you want to create
multiple paragraphs, you can split them with two or more consecutive line
breaks. For example:
Expand Down Expand Up @@ -249,7 +251,8 @@ Emphases

BotKit provides two kinds of emphasis: `strong()` emphasizes which is usually
rendered as **bold**, and `em()` emphasizes which is usually rendered as
_italic_. For example:
_italic_. Both are inlines, so you can put them inside the interpolation.
For example:

~~~~ typescript
text`You can emphasize ${strong("this")} or ${em("this")}!`
Expand All @@ -273,7 +276,8 @@ The above code will create a text like this:
Links
-----

You can make a link to a URL. For example:
You can make a link to a URL by using the `link()` function. It returns
an inline `Text` object that represents a link. For example:

~~~~ typescript
text`Here's a link: ${link("https://fedify.dev/")}.`
Expand Down Expand Up @@ -308,7 +312,7 @@ Mentions
--------

You can mention another fediverse account by using the `mention()` function.
For example:
It returns an inline `Text` object that represents a mention. For example:

~~~~ typescript
text`Hello, ${mention("@[email protected]")}!`
Expand Down Expand Up @@ -349,7 +353,8 @@ Code
----

You can include a code in the text using the `code()` function,
which is usually rendered as monospaced font. For example:
which is usually rendered as monospaced font. It is an inline construct.
For example:

~~~~ typescript
text`Here's a code: ${code("console.log('Hello, world!')")}.`
Expand All @@ -361,3 +366,89 @@ The above code will create a text like this:
> [!CAUTION]
> It is not a code block, but an inline code.

Markdown
--------

Sometimes you have a Markdown text and want to render it as a `Text` object.
You can use the `markdown()` function to convert the Markdown text to the `Text`
object. It is a block construct. For example:

~~~~ typescript
markdown(`
Here's a Markdown text.
- I can have a list.
- I can have a **bold** text.
- I can have an _italic_ text.
`)
~~~~

The above code will create a text like this:

> Here's a Markdown text.
>
> - I can have a list.
> - I can have a **bold** text.
> - I can have an _italic_ text.
You can also put the `markdown()` function inside the interpolation:

~~~~ typescript
text`The following is a Markdown text:
${markdown(`
Here's a Markdown text.
- I can have a list.
- I can have a **bold** text.
- I can have an _italic_ text.
`)
`
~~~~
The above code will create a text like this:
> The following is a Markdown text:
>
> Here's a Markdown text.
>
> - I can have a list.
> - I can have a **bold** text.
> - I can have an _italic_ text.
Besides the standard Markdown syntax, the `markdown()` function also supports
the following mentioning syntax for the fediverse:
~~~~ typescript
markdown(`Hello, @fedify@hollo.social!`)
~~~~
The above code will create a text like this:
> Hello, [@[email protected]](https://hollo.social/@fedify)!
> [!NOTE]
> The `markdown()` function does not only format the mention but also notifies
> the mentioned account. The mentioned account will receive a notification
> about the mention. If you want to just link to the account without
> notifying, use the normal link syntax instead:
>
> ~~~~ typescript
> markdown(`Hello, [@fedify@hollo.social](https://hollo.social/@fedify)!`)
> ~~~~
If you want `@`-syntax to be treated as a normal text, turn off the syntax
by setting the `mentions` option to `false`:
~~~~ typescript
markdown(`Hello, @[email protected]!`, { mentions: false })
~~~~
The above code will create a text like this:
> Hello, @fedify@hollo.social!
> [!NOTE]
> The `markdown()` function does not support raw HTML syntax.
53 changes: 52 additions & 1 deletion src/text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ import {
} from "@std/assert";
import type { BotWithVoidContextData } from "./bot.ts";
import type { Session } from "./session.ts";
import { code, em, isText, link, mention, type Text, text } from "./text.ts";
import {
code,
em,
isText,
link,
markdown,
mention,
type Text,
text,
} from "./text.ts";

const defaultDocumentLoader = getDocumentLoader();

Expand Down Expand Up @@ -409,3 +418,45 @@ Deno.test("code()", async () => {
assertEquals(await Array.fromAsync(t.getTags(session)), []);
assertEquals(t.getCachedObjects(), []);
});

Deno.test("markdown()", async () => {
const session = bot.getSession("https://example.com");
const md = `Here's a Markdown text.
- I can have a list.
- I can have a **bold** text.
- I can have an _italic_ text.
- I can mention @[email protected].`;
const t: Text<"block", void> = markdown(md);
assertEquals(
(await Array.fromAsync(t.getHtml(session))).join(""),
"<p>Here's a Markdown text.</p>\n<ul>\n" +
"<li>I can have a list.</li>\n" +
"<li>I can have a <strong>bold</strong> text.</li>\n" +
"<li>I can have an <em>italic</em> text.</li>\n" +
"<li>I can mention " +
'<a translate="no" class="h-card u-url mention" target="_blank" href="https://hollo.social/@fedify">' +
'<span class="at">@</span><span class="user">fedify</span>' +
'<span class="at">@</span><span class="domain">hollo.social</span></a>.</li>\n' +
"</ul>\n",
);
const tags = await Array.fromAsync(t.getTags(session));
assertEquals(tags.length, 1);
assertInstanceOf(tags[0], Mention);
assertEquals(tags[0].name, "@[email protected]");
assertEquals(tags[0].href, new URL("https://hollo.social/@fedify"));
const cache = t.getCachedObjects();
assertEquals(cache.length, 1);
assertInstanceOf(cache[0], Person);
assertEquals(cache[0].id, new URL("https://hollo.social/@fedify"));

const t2: Text<"block", void> = markdown("@[email protected]", {
mentions: false,
});
assertEquals(
(await Array.fromAsync(t2.getHtml(session))).join(""),
"<p>@[email protected]</p>\n",
);
assertEquals(await Array.fromAsync(t2.getTags(session)), []);
assertEquals(t2.getCachedObjects(), []);
});
Loading

0 comments on commit dbd412f

Please sign in to comment.