From 2e1ac6dd3319fb4dab8606e6fda65639605fa95f Mon Sep 17 00:00:00 2001
From: aXenDeveloper
Date: Sun, 6 Sep 2026 15:59:12 +0200
Subject: [PATCH 1/4] feat: Add emoji picker to editor
---
apps/web/content/docs/ui/editor.mdx | 171 +++-
apps/web/public/logo_vitnode_icon.svg | 11 +
apps/web/src/routes/__root.tsx | 6 +-
apps/web/src/styles.css | 1 +
apps/web/src/vitnode.config.ts | 14 +
.../root/src/routes/__root.tsx | 6 +-
.../copy-of-vitnode-app/root/src/styles.css | 1 +
packages/vitnode/package.json | 10 +
.../src/components/editor-provider.tsx | 42 +
.../src/components/tiptap/drag-handle.tsx | 21 +
.../tiptap/emoji/custom-emoji.test.ts | 78 ++
.../components/tiptap/emoji/custom-emoji.ts | 39 +
.../tiptap/emoji/emoji-suggestion-list.tsx | 112 +++
.../tiptap/emoji/emoji-suggestion.ts | 49 ++
.../components/tiptap/emoji/match-emojis.ts | 43 +
.../src/components/tiptap/extension.test.ts | 176 ++++
.../src/components/tiptap/extension.ts | 61 +-
.../src/components/tiptap/tiptap-editor.tsx | 70 ++
.../tiptap/toolbar/actions/audio-action.tsx | 53 ++
.../toolbar/actions/audio/audio-form.tsx | 42 +
.../tiptap/toolbar/actions/blocks-action.tsx | 81 ++
.../tiptap/toolbar/actions/color-action.tsx | 122 +++
.../tiptap/toolbar/actions/emoji-action.tsx | 49 ++
.../toolbar/actions/emoji/emoji-picker.tsx | 124 +++
.../toolbar/actions/font-size-action.tsx | 73 ++
.../tiptap/toolbar/actions/table-action.tsx | 140 ++++
.../text-format-more/text-format-more.tsx | 28 +-
.../tiptap/toolbar/tiptap-toolbar.tsx | 44 +-
packages/vitnode/src/components/ui/editor.tsx | 66 +-
packages/vitnode/src/emoji-picker.css | 104 +++
.../src/framework/vite/optimize-deps.ts | 8 +
packages/vitnode/src/locales/en.json | 45 +-
packages/vitnode/src/locales/pl.json | 45 +-
packages/vitnode/src/tiptap.css | 109 ++-
.../vitnode/src/views/layouts/providers.tsx | 11 +-
packages/vitnode/src/vitnode.config.ts | 3 +
plugins/example/src/locales/en.json | 4 +
.../example/src/pages/admin-example-page.tsx | 19 +
plugins/example/src/pages/editor-showcase.ts | 35 +
pnpm-lock.yaml | 754 ++++++++++++------
40 files changed, 2550 insertions(+), 320 deletions(-)
create mode 100644 apps/web/public/logo_vitnode_icon.svg
create mode 100644 packages/vitnode/src/components/editor-provider.tsx
create mode 100644 packages/vitnode/src/components/tiptap/drag-handle.tsx
create mode 100644 packages/vitnode/src/components/tiptap/emoji/custom-emoji.test.ts
create mode 100644 packages/vitnode/src/components/tiptap/emoji/custom-emoji.ts
create mode 100644 packages/vitnode/src/components/tiptap/emoji/emoji-suggestion-list.tsx
create mode 100644 packages/vitnode/src/components/tiptap/emoji/emoji-suggestion.ts
create mode 100644 packages/vitnode/src/components/tiptap/emoji/match-emojis.ts
create mode 100644 packages/vitnode/src/components/tiptap/extension.test.ts
create mode 100644 packages/vitnode/src/components/tiptap/tiptap-editor.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/audio-action.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/audio/audio-form.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/blocks-action.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/color-action.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/emoji-action.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/emoji/emoji-picker.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/font-size-action.tsx
create mode 100644 packages/vitnode/src/components/tiptap/toolbar/actions/table-action.tsx
create mode 100644 packages/vitnode/src/emoji-picker.css
create mode 100644 plugins/example/src/pages/editor-showcase.ts
diff --git a/apps/web/content/docs/ui/editor.mdx b/apps/web/content/docs/ui/editor.mdx
index 13fe7b548..d195c188c 100644
--- a/apps/web/content/docs/ui/editor.mdx
+++ b/apps/web/content/docs/ui/editor.mdx
@@ -8,6 +8,146 @@ icon: PenTool
+import { TypeTable } from 'fumadocs-ui/components/type-table'
+
+## What you get
+
+The toolbar ships with everything below, and every feature is also available
+through its own keyboard shortcut or markdown-style input rule.
+
+| Group | Controls |
+| ---------- | --------------------------------------------------------------------------------------------- |
+| History | Undo, redo |
+| Text | Paragraph, headings 1-4, font size, bold, italic, underline, strike, inline code, text colour |
+| Layout | Left / center / right / justify alignment |
+| Blocks | Bullet list, ordered list, blockquote, code block, horizontal rule |
+| Rich media | Tables, emoji, audio |
+
+A few extras run quietly in the background:
+
+- **Typography** turns `(c)`, `->`, `1/2` and friends into `©`, `→` and `½` as you type.
+- **Emoji** has three ways in - see [Emoji](#emoji) below.
+- **Placeholder** shows a hint while the editor is empty. Override it with the
+ `placeholder` prop.
+- **Drag handle** appears next to the block under your cursor so any block -
+ nested ones included - can be dragged somewhere else. A **drop cursor** shows
+ where it will land.
+- **Trailing node** always keeps an empty paragraph at the end, so you can never
+ get stuck under a table or a code block.
+
+### Emoji
+
+There are three ways to get an emoji into the editor, and they all end up as the
+same node:
+
+- **Type a shortcode.** `:tada:` becomes 🎉 the moment you close the second
+ colon.
+- **Use the `:` menu.** Start typing `:tad` and an autocomplete appears. Arrow
+ keys move, `Enter` or `Tab` inserts, `Esc` closes. Results are ranked, so
+ `:flag` puts flags first rather than everything tagged "flag".
+- **Open the picker.** The toolbar's emoji button opens a searchable,
+ virtualised grid with categories and skin tones, from
+ [Ferruccio's emoji picker](https://emoji.ferrucc.io/). Hovering an emoji
+ previews it in the footer with its name and its shortcode, so the picker also
+ teaches you what to type next time.
+
+Pasting a raw emoji character works too - it is upgraded to a node on the way
+in.
+
+#### Custom emoji
+
+Your own emoji are app-wide, so they live in `vitnode.config.ts` and every
+`Editor` in the app picks them up - no props to pass:
+
+```ts title="src/vitnode.config.ts"
+import { buildConfig } from '@vitnode/core/vitnode.config'
+
+export const vitNodeConfig = buildConfig({
+ editor: {
+ emojis: [
+ {
+ label: 'VitNode',
+ emojis: [
+ {
+ name: 'vitnode',
+ src: '/logo_vitnode_icon.svg',
+ tags: ['logo', 'brand'],
+ },
+ ],
+ },
+ ],
+ },
+ // ...the rest of your config
+})
+```
+
+That one entry gives you all three routes at once: `:vitnode:` converts as you
+type, `:vitn` finds it in the `:` menu, and the picker grows a **VitNode**
+section you can search.
+
+
+
+A custom emoji is stored as `` with
+your image inside, so `EditorContent` renders it on public pages exactly as the
+editor does - as long as `src` is still reachable from there.
+
+### Tables
+
+Insert a 3x3 table with a header row from the table menu, then use the same menu
+to add or remove rows and columns, toggle the header row or column, merge or
+split cells, or delete the table. Columns are resizable by dragging their edge,
+and wide tables scroll horizontally instead of breaking the page.
+
+### Audio
+
+Paste a link to an `mp3`, `wav`, `ogg` (or any other browser-playable file) and
+it renders as a native `
-
+
Docs for your agent
diff --git a/apps/web/src/site/marketing/footer.tsx b/apps/web/src/site/marketing/footer.tsx
index 54d647378..78f52b567 100644
--- a/apps/web/src/site/marketing/footer.tsx
+++ b/apps/web/src/site/marketing/footer.tsx
@@ -4,7 +4,13 @@ import { RouterLink } from '@vitnode/core/tanstack/layout'
import { AUTHOR_NAME, AUTHOR_URL, LICENSE_URL, REPOSITORY_URL } from './links'
import { CanaryPill, GitHubIcon } from './shared'
-const COLUMNS = [
+interface FooterLink {
+ href: string
+ label: string
+ target?: React.HTMLAttributeAnchorTarget
+}
+
+const COLUMNS: { links: FooterLink[]; title: string }[] = [
{
links: [
{ href: '/', label: 'Overview' },
@@ -33,7 +39,7 @@ const COLUMNS = [
{ href: '/discover', label: 'Discover' },
{ href: '/search', label: 'Search' },
{ href: '/docs/dev/contribution', label: 'Contribute' },
- { href: '/llms-full.txt', label: 'Docs for AI agents' },
+ { href: '/llms-full.txt', label: 'Docs for AI agents', target: '_blank' },
],
title: 'Community',
},
@@ -72,11 +78,15 @@ export const SiteFooter = () => (