Merge branch 'main' of github.com:elk-zone/elk into feat/interface-font-selector-ui

This commit is contained in:
wheatjs 2023-01-11 19:13:58 -05:00
commit 799d157424
24 changed files with 189 additions and 117 deletions

3
.github/FUNDING.yml vendored
View file

@ -1 +1,2 @@
github: [antfu, patak-dev, sxzz, danielroe] github: [elk-zone]
open_collective: elk

View file

@ -4,9 +4,13 @@ Hi! We are really excited that you are interested in contributing to Elk. Before
Refer also to https://github.com/antfu/contribute. Refer also to https://github.com/antfu/contribute.
## Set up your local development environment ### Online
The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/) (Note: on Linux in a standard Node 16+ environment, you should follow the instructions to install via Node's `corepack` rather than using the `curl` command). You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
[![Open in Codeflow](https://developer.stackblitz.com/img/open_in_codeflow.svg)](https://pr.new/elk-zone/elk)
### Local Setup
To develop and test the Elk package: To develop and test the Elk package:
@ -14,18 +18,33 @@ To develop and test the Elk package:
2. Ensure using the latest Node.js (16.x) 2. Ensure using the latest Node.js (16.x)
3. Elk uses pnpm v7, you must enable [Corepack](https://github.com/nodejs/corepack) by running `corepack enable`. 3. The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/) v7. To use it you must first enable [Corepack](https://github.com/nodejs/corepack) by running `corepack enable`. (Note: on Linux in a standard Node 16+ environment, you should follow the instructions to install via Node's `corepack` rather than using the `curl` command)
4. Check out a branch where you can work and commit your changes: 4. Check out a branch where you can work and commit your changes:
```shell ```shell
git checkout -b my-new-branch git checkout -b my-new-branch
``` ```
5. Run `pnpm i` in Elk's root folder 1. Run `pnpm i` in Elk's root folder
6. Run `pnpm nuxi prepare` in Elk's root folder 2. Run `pnpm nuxi prepare` in Elk's root folder
7. Run `pnpm dev` in Elk's root folder to start dev server or `pnpm dev:mocked` to start dev server with `@elkdev@universeodon.com` user. 3. Run `pnpm dev` in Elk's root folder to start dev server or `pnpm dev:mocked` to start dev server with `@elkdev@universeodon.com` user.
We recommend installing [ni](https://github.com/antfu/ni#ni), that will use the right package manager in each of your projects. If `ni` is installed, you can instead run:
```
ni
nr dev
```
### Testing
Elk uses [Vitest](https://vitest.dev). You can run the test suite with:
```
nr test
```
### Running PWA on dev server ### Running PWA on dev server

View file

@ -1,11 +1,15 @@
# Elk
*A nimble Mastodon web client*
<p align="center"> <p align="center">
<a href="https://elk.zone" target="_blank" rel="noopener noreferrer"> <a href="https://elk.zone" target="_blank" rel="noopener noreferrer">
<img width="180" height="180" src="./elk.svg" alt="Elk logo"> <img width="160" height="160" src="./public/logo.svg" alt="Elk logo">
</a> </a>
</p> </p>
<h1 align="center"/>Elk <sup><em>alpha</em></sup></h1>
<p align="center">
A nimble Mastodon web client
</p>
<br/> <br/>
<p align="center"> <p align="center">
<a href="https://chat.elk.zone"><img src="https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord" alt="discord chat"></a> <a href="https://chat.elk.zone"><img src="https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord" alt="discord chat"></a>
@ -13,9 +17,15 @@
</p> </p>
<br/> <br/>
# Elk is in early alpha ⚠️ <p align="center">
<a href="https://elk.zone/" target="_blank" rel="noopener noreferrer" >
<img src="./public/elk-og.png" alt="Elk screenshots" height="300">
</a>
</p>
It is already quite usable, but it isn't ready for wide adoption yet. We recommend you to use it if you would like to help us build it. We appreciate your feedback and contributions. Check out the [Open Issues](https://github.com/elk-zone/elk/issues) and jump in the action. Join the [Elk discord server](https://chat.elk.zone) to chat with us and learn more about the project. ## ⚠️ Elk is in Alpha
It is already quite usable, but it isn't ready for wide adoption yet. We recommend you use it if you would like to help us build it. We appreciate your feedback and contributions. Check out the [Open Issues](https://github.com/elk-zone/elk/issues) and jump in the action. Join the [Elk discord server](https://chat.elk.zone) to chat with us and learn more about the project.
The client is deployed on: The client is deployed on:
@ -24,9 +34,9 @@ The client is deployed on:
You can share screenshots on social media but we prefer you avoid sharing this URL directly until the app is more polished. Feel free to share the URL with your friends and invite others you think could be interested in helping to improve Elk. You can share screenshots on social media but we prefer you avoid sharing this URL directly until the app is more polished. Feel free to share the URL with your friends and invite others you think could be interested in helping to improve Elk.
## Sponsors ## 💖 Sponsors
We want to thanks the generous sponsoring and help of: We are grateful for the generous sponsorship and help of:
<a href="https://nuxtlabs.com/" target="_blank" rel="noopener noreferrer" > <a href="https://nuxtlabs.com/" target="_blank" rel="noopener noreferrer" >
<img src="./images/nuxtlabs.svg" alt="NuxtLabs" height="85"> <img src="./images/nuxtlabs.svg" alt="NuxtLabs" height="85">
@ -37,7 +47,11 @@ We want to thanks the generous sponsoring and help of:
</a> </a>
<br><br> <br><br>
And all the companies and individuals sponsoring Elk Team members. If you're enjoying the app, consider sponsoring our team: And all the companies and individuals sponsoring Elk Team and the members. If you're enjoying the app, consider sponsoring us:
- [Elk Team's GitHub Sponsors](https://github.com/sponsors/elk-zone)
Or you can sponsor our core team members individually:
- [Anthony Fu](https://github.com/sponsors/antfu) - [Anthony Fu](https://github.com/sponsors/antfu)
- [Daniel Roe](https://github.com/sponsors/danielroe) - [Daniel Roe](https://github.com/sponsors/danielroe)
@ -46,11 +60,11 @@ And all the companies and individuals sponsoring Elk Team members. If you're enj
We would also appreciate sponsoring other contributors to the Elk project. If someone helps you solve an issue or implement a feature you wanted, supporting them would help make this project and OS more sustainable. We would also appreciate sponsoring other contributors to the Elk project. If someone helps you solve an issue or implement a feature you wanted, supporting them would help make this project and OS more sustainable.
## Roadmap ## 📍 Roadmap
[Open board on Volta](https://volta.net/elk-zone/elk) [Open board on Volta](https://volta.net/elk-zone/elk)
## Contributing ## 🧑‍💻 Contributing
We're really excited that you're interested in contributing to Elk! Before submitting your contribution, please read through the following guide. We're really excited that you're interested in contributing to Elk! Before submitting your contribution, please read through the following guide.
@ -86,7 +100,7 @@ Elk uses [Vitest](https://vitest.dev). You can run the test suite with:
nr test nr test
``` ```
## Stack ## 🦄 Stack
- [Vite](https://vitejs.dev/) - Next Generation Frontend Tooling - [Vite](https://vitejs.dev/) - Next Generation Frontend Tooling
- [Nuxt](https://nuxt.com/) - The Intuitive Web Framework - [Nuxt](https://nuxt.com/) - The Intuitive Web Framework
@ -100,6 +114,6 @@ nr test
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter - [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update and push notifications - [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update and push notifications
## License ## 📄 License
[MIT](./LICENSE) &copy; 2022-PRESENT Elk contributors [MIT](./LICENSE) &copy; 2022-PRESENT Elk contributors

View file

@ -2,6 +2,15 @@
setupPageHeader() setupPageHeader()
provideGlobalCommands() provideGlobalCommands()
const route = useRoute()
if (process.server && !route.path.startsWith('/settings')) {
useHead({
meta: [
{ property: 'og:url', content: `https://main.elk.zone${route.path}` },
],
})
}
// We want to trigger rerendering the page when account changes // We want to trigger rerendering the page when account changes
const key = computed(() => `${currentUser.value?.server ?? currentServer.value}:${currentUser.value?.account.id || ''}`) const key = computed(() => `${currentUser.value?.server ?? currentServer.value}:${currentUser.value?.account.id || ''}`)
</script> </script>

View file

@ -26,6 +26,16 @@ function toggleDark() {
@click="userSettings.zenMode = !userSettings.zenMode" @click="userSettings.zenMode = !userSettings.zenMode"
/> />
</CommonTooltip> </CommonTooltip>
<CommonTooltip :content="$t('settings.about.sponsor_action')">
<NuxtLink
flex
text-lg
i-ri-heart-3-line hover="i-ri-heart-3-fill text-rose"
:aria-label="$t('settings.about.sponsor_action')"
href="https://github.com/sponsors/elk-zone"
target="_blank"
/>
</CommonTooltip>
</div> </div>
<div> <div>
<i18n-t v-if="isHydrated" keypath="nav.built_at"> <i18n-t v-if="isHydrated" keypath="nav.built_at">

View file

@ -62,7 +62,7 @@ useCommand({
/> />
</slot> </slot>
</div> </div>
<div space-y-1> <div flex="~ col gap-0.5">
<p> <p>
<slot> <slot>
<span>{{ text }}</span> <span>{{ text }}</span>

File diff suppressed because one or more lines are too long

View file

@ -55,7 +55,10 @@ export function parseMastodonHTML(
// Handle code blocks // Handle code blocks
html = html html = html
.replace(/>(```|~~~)(\w*)([\s\S]+?)\1/g, (_1, _2, lang: string, raw: string) => { .replace(/>(```|~~~)(\w*)([\s\S]+?)\1/g, (_1, _2, lang: string, raw: string) => {
const code = htmlToText(raw).replace(/</g, '&lt;').replace(/>/g, '&gt;') const code = htmlToText(raw)
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/`/, '&#96;')
const classes = lang ? ` class="language-${lang}"` : '' const classes = lang ? ` class="language-${lang}"` : ''
return `><pre><code${classes}>${code}</code></pre>` return `><pre><code${classes}>${code}</code></pre>`
}) })

View file

@ -37,6 +37,20 @@ export function onReactivated(hook: Function, target?: ComponentInternalInstance
// TODO: Workaround for Nuxt bug: https://github.com/elk-zone/elk/pull/199#issuecomment-1329771961 // TODO: Workaround for Nuxt bug: https://github.com/elk-zone/elk/pull/199#issuecomment-1329771961
export function useHeadFixed<T extends HeadAugmentations>(input: UseHeadInput<T>, options?: HeadEntryOptions): ActiveHeadEntry<UseHeadInput<T>> | void { export function useHeadFixed<T extends HeadAugmentations>(input: UseHeadInput<T>, options?: HeadEntryOptions): ActiveHeadEntry<UseHeadInput<T>> | void {
const deactivated = useDeactivated() const deactivated = useDeactivated()
if (input && typeof input === 'object' && !('value' in input)) {
const title = 'title' in input ? input.title : undefined
if (process.server && title) {
input.meta = input.meta || []
if (Array.isArray(input.meta)) {
input.meta.push(
{ property: 'og:title', content: (typeof input.title === 'function' ? input.title() : input.title) as string },
)
}
}
else if (title) {
(input as any).title = () => isHydrated.value ? typeof title === 'function' ? title() : title : ''
}
}
return useHead(() => { return useHead(() => {
if (deactivated.value) if (deactivated.value)
return {} return {}

View file

@ -236,7 +236,13 @@
"settings": { "settings": {
"about": { "about": {
"label": "About", "label": "About",
"meet_the_team": "Meet the team" "meet_the_team": "Meet the team",
"sponsor_action": "Sponsor us",
"sponsor_action_desc": "To support the team developing Elk",
"sponsors": "Sponsors",
"sponsors_body_1": "Elk is made possible thanks the generous sponsoring and help of:",
"sponsors_body_2": "And all the companies and individuals sponsoring Elk Team and the members.",
"sponsors_body_3": "If you're enjoying the app, consider sponsoring us:"
}, },
"account_settings": { "account_settings": {
"description": "Edit your account settings in Mastodon UI", "description": "Edit your account settings in Mastodon UI",

View file

@ -5,7 +5,6 @@ import {
toNodeListener, toNodeListener,
} from 'h3' } from 'h3'
import { createFetch } from 'ofetch' import { createFetch } from 'ofetch'
import { parseURL } from 'ufo'
import { import {
createCall, createCall,
createFetch as createLocalFetch, createFetch as createLocalFetch,
@ -26,13 +25,10 @@ const handlers = [
}, },
] ]
const { protocol, host } = parseURL(window.location.href)
// @ts-expect-error undeclared global window property // @ts-expect-error undeclared global window property
window.__NUXT__.config = { window.__NUXT__.config = {
// @ts-expect-error undeclared global window property // @ts-expect-error undeclared global window property
...window.__NUXT__.config, ...window.__NUXT__.config,
deployUrl: `${protocol}//${host}`,
storage: {}, storage: {},
} }

View file

@ -71,25 +71,11 @@ export default defineNuxtConfig({
}, },
}, },
runtimeConfig: { runtimeConfig: {
deployUrl: !isCI
? 'http://localhost:5314'
: isPreview
? process.env.DEPLOY_PRIME_URL
: 'https://elk.zone',
cloudflare: { cloudflare: {
accountId: '', accountId: '',
namespaceId: '', namespaceId: '',
apiToken: '', apiToken: '',
}, },
discord: {
inviteUrl: 'https://chat.elk.zone',
},
github: {
// oauth flow
clientId: '',
clientSecret: '',
inviteToken: '',
},
public: { public: {
env: '', // set in build-env module env: '', // set in build-env module
buildInfo: {} as BuildInfo, // set in build-env module buildInfo: {} as BuildInfo, // set in build-env module
@ -132,6 +118,16 @@ export default defineNuxtConfig({
], ],
meta: [ meta: [
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }, { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
// open graph social image
{ property: 'og:title', content: 'Elk' },
{ property: 'og:description', content: 'A nimble Mastodon web client' },
{ property: 'og:type', content: 'website' },
{ property: 'og:image', content: 'https://main.elk.zone/elk-og.png' },
{ property: 'og:image:width', content: '3800' },
{ property: 'og:image:height', content: '1900' },
{ property: 'og:site_name', content: 'Elk' },
{ property: 'twitter:site', content: '@elk_zone' },
{ property: 'twitter:card', content: 'summary_large_image' },
], ],
}, },
}, },

View file

@ -8,7 +8,7 @@ const paginator = useMasto().v1.trends.listStatuses()
const hideNewsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS, false) const hideNewsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS, false)
useHeadFixed({ useHeadFixed({
title: () => isHydrated.value ? `${t('tab.posts')} | ${t('nav.explore')}` : '', title: () => `${t('tab.posts')} | ${t('nav.explore')}`,
}) })
</script> </script>

View file

@ -8,7 +8,7 @@ const paginator = useMasto().v1.trends.listLinks()
const hideNewsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS, false) const hideNewsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS, false)
useHeadFixed({ useHeadFixed({
title: () => isHydrated.value ? `${t('tab.news')} | ${t('nav.explore')}` : '', title: () => `${t('tab.news')} | ${t('nav.explore')}`,
}) })
</script> </script>

View file

@ -11,7 +11,7 @@ const paginator = masto.v1.trends.listTags({
const hideTagsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS, false) const hideTagsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS, false)
useHeadFixed({ useHeadFixed({
title: () => isHydrated.value ? `${t('tab.hashtags')} | ${t('nav.explore')}` : '', title: () => `${t('tab.hashtags')} | ${t('nav.explore')}`,
}) })
</script> </script>

View file

@ -5,7 +5,7 @@ const { t } = useI18n()
const paginator = useMasto().v2.suggestions.list({ limit: 20 }) const paginator = useMasto().v2.suggestions.list({ limit: 20 })
useHeadFixed({ useHeadFixed({
title: () => isHydrated.value ? `${t('tab.for_you')} | ${t('nav.explore')}` : '', title: () => `${t('tab.for_you')} | ${t('nav.explore')}`,
}) })
</script> </script>

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const { t } = useI18n() const { t } = useI18n()
useHeadFixed({ useHeadFixed({
title: () => isHydrated.value ? `${t('tab.notifications_all')} | ${t('nav.notifications')}` : '', title: () => `${t('tab.notifications_all')} | ${t('nav.notifications')}`,
}) })
</script> </script>

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const { t } = useI18n() const { t } = useI18n()
useHeadFixed({ useHeadFixed({
title: () => isHydrated.value ? `${t('tab.notifications_mention')} | ${t('nav.notifications')}` : '', title: () => `${t('tab.notifications_mention')} | ${t('nav.notifications')}`,
}) })
</script> </script>

View file

@ -54,7 +54,7 @@ const handleShowCommit = () => {
<SettingsItem <SettingsItem
:text="$t('nav.show_intro')" :text="$t('nav.show_intro')"
icon="i-ri:article-line" icon="i-ri:article-line"
cursor-pointer cursor-pointer large
@click="openPreviewHelp" @click="openPreviewHelp"
/> />
@ -62,27 +62,58 @@ const handleShowCommit = () => {
text="Mastodon" text="Mastodon"
icon="i-ri:mastodon-line" icon="i-ri:mastodon-line"
to="/m.webtoo.ls/@elk" to="/m.webtoo.ls/@elk"
external target="_blank" external large target="_blank"
/> />
<SettingsItem <SettingsItem
text="Discord" text="Discord"
icon="i-ri:discord-fill" icon="i-ri:discord-fill"
to="https://chat.elk.zone" to="https://chat.elk.zone"
external target="_blank" external large target="_blank"
/> />
<SettingsItem <SettingsItem
text="GitHub" text="GitHub"
icon="i-ri:github-fill" icon="i-ri:github-fill"
to="https://github.com/elk-zone" to="https://github.com/elk-zone"
external target="_blank" external large target="_blank"
/> />
<div h-1px bg-border my2 /> <div h-1px bg-border my2 />
<p px5 py3 font-bold text-lg>
{{ $t('settings.about.sponsors') }}
</p>
<p px5 text-secondary>
{{ $t('settings.about.sponsors_body_1') }}
</p>
<LazySettingsSponsorsList />
<p px5 mb1 text-secondary>
{{ $t('settings.about.sponsors_body_2') }}
</p>
<p px5 mb2 text-secondary>
{{ $t('settings.about.sponsors_body_3') }}
</p>
<SettingsItem
:text="$t('settings.about.sponsor_action')"
to="https://github.com/sponsors/elk-zone"
:description="$t('settings.about.sponsor_action_desc')"
external large target="_blank"
>
<template #icon>
<div i-ri-heart-3-fill text-rose rounded-full w-8 h-8 height="32" width="32" />
</template>
</SettingsItem>
<div h-1px bg-border my2 />
<template v-if="isHydrated"> <template v-if="isHydrated">
<p px5 py3 font-bold text-lg> <p px5 py3 font-bold text-lg>
{{ $t('settings.about.meet_the_team') }} {{ $t('settings.about.meet_the_team') }}
</p> </p>
<SettingsItem <SettingsItem
v-for="team in teams" :key="team.github" v-for="team in teams" :key="team.github"
:text="team.display" :text="team.display"

View file

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
/* eslint-disable no-alert */
import { fileOpen } from 'browser-fs-access' import { fileOpen } from 'browser-fs-access'
import type { UserLogin } from '~/types' import type { UserLogin } from '~/types'

BIN
public/elk-og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 KiB

View file

@ -1,66 +0,0 @@
const query = (accessToken: string, query: string) =>
$fetch<{ data: any }>('https://api.github.com/graphql', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
body: { query },
})
export default defineEventHandler(async (event) => {
const { code } = getQuery(event)
const config = useRuntimeConfig()
if (!code) {
const redirect = `&redirect_uri=${config.deployUrl}/invite`
const loginURL = `https://github.com/login/oauth/authorize?client_id=${config.github.clientId}${redirect}`
await sendRedirect(event, loginURL)
return
}
const { access_token } = await $fetch<{ access_token: string }>(
'https://github.com/login/oauth/access_token',
{
method: 'POST',
body: {
client_id: config.github.clientId,
client_secret: config.github.clientSecret,
code,
},
},
)
if (!access_token) {
throw createError({
statusCode: 422,
statusMessage: 'Authorisation code invalid.',
})
}
const id = await query(access_token, '{ viewer { databaseId } }')
.then(r => r.data?.viewer.databaseId)
if (!id) {
throw createError({
statusCode: 422,
statusMessage: 'Access code invalid.',
})
}
await $fetch(
'https://api.github.com/orgs/elk-zone/invitations',
{
method: 'POST',
body: { invitee_id: id, role: 'direct_member', team_ids: [7042932] },
headers: {
'Accept': 'application/vnd.github+json',
'Authorization': `Bearer ${config.github.inviteToken}`,
'X-GitHub-Api-Version': '2022-11-28',
},
},
)
return sendRedirect(
event,
config.discord.inviteUrl,
)
})

View file

@ -1,5 +1,7 @@
// Vitest Snapshot v1 // Vitest Snapshot v1
exports[`content-rich > block with backticks 1`] = `"<p><pre>[(\`number string) (\`tag string)]</pre></p>"`;
exports[`content-rich > code frame 1`] = ` exports[`content-rich > code frame 1`] = `
"<p>Testing code block</p><p></p><p><pre lang=\\"ts\\">import { useMouse, usePreferredDark } from &#39;@vueuse/core&#39; "<p>Testing code block</p><p></p><p><pre lang=\\"ts\\">import { useMouse, usePreferredDark } from &#39;@vueuse/core&#39;
// tracks mouse position // tracks mouse position

View file

@ -20,6 +20,11 @@ describe('content-rich', () => {
expect(formatted).toMatchSnapshot() expect(formatted).toMatchSnapshot()
}) })
it ('block with backticks', async () => {
const { formatted } = await render('<p>```<br />[(`number string) (`tag string)]<br />```</p>')
expect(formatted).toMatchSnapshot()
})
it('group mention', async () => { it('group mention', async () => {
const { formatted } = await render('<p><span class="h-card"><a href="https://lemmy.ml/c/pilipinas" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>pilipinas</span></a></span></p>', undefined, [{ id: '', username: 'pilipinas', url: 'https://lemmy.ml/c/pilipinas', acct: 'pilipinas@lemmy.ml' }]) const { formatted } = await render('<p><span class="h-card"><a href="https://lemmy.ml/c/pilipinas" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>pilipinas</span></a></span></p>', undefined, [{ id: '', username: 'pilipinas', url: 'https://lemmy.ml/c/pilipinas', acct: 'pilipinas@lemmy.ml' }])
expect(formatted).toMatchSnapshot('html') expect(formatted).toMatchSnapshot('html')