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 `