์ปดํฌ๋ํธ
Editor
@inkio/editor โ Editor
Notion-like ์๋ํฐ. bubble menu, floating menu, slash command, block handle์ด ๊ธฐ๋ณธ์ผ๋ก ํ์ฑํ๋ฉ๋๋ค.
import { Editor } from '@inkio/editor';
import '@inkio/editor/style.css';| Prop | ํ์ | ์ค๋ช |
|---|---|---|
content | string | JSONContent | ์ด๊ธฐ ๋ฌธ์ (๋ง์ดํธ ํ ์๋ํฐ๊ฐ ์ํ ์์ ) |
editable | boolean | ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ (๊ธฐ๋ณธ๊ฐ: true) |
placeholder | string | ๋น ์๋ํฐ placeholder ํ ์คํธ |
locale | InkioLocaleInput | ๋ก์ผ์ผ |
theme | 'light' | 'dark' | ์์ ํ ๋ง (๋ฏธ์ง์ ์ ๋ถ๋ชจ ํ๊ฒฝ์ ๋ฐ๋ฆ) |
tabBehavior | 'indent' | 'default' | Tab ํค ๋์ (๊ธฐ๋ณธ๊ฐ: 'indent'). 'indent'์์๋ ๋ฆฌ์คํธ ๋ฐ์์๋ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ํฌ์ปค์ค ์ด๋์ ์ ์งํฉ๋๋ค |
onUpdate | (content: JSONContent) => void | ์ฝํ ์ธ ๋ณ๊ฒฝ ์ฝ๋ฐฑ |
onCreate | (editor: TiptapEditor) => void | ์๋ํฐ ์ธ์คํด์ค ์ค๋น ์ฝ๋ฐฑ |
onError | InkioErrorHandler | ์๋ฌ ํธ๋ค๋ฌ |
ui | EditorUiOptions | UI ์ต์ (์๋ ์ฐธ๊ณ ) |
hashtagItems | (params: { query: string }) => HashTagItem[] | Promise<HashTagItem[]> | ํด์ํ๊ทธ ์ ์ |
mentionItems | (params: { query: string }) => MentionItem[] | Promise<MentionItem[]> | ๋ฉ์ ์ ์ |
slashCommands | (query: string) => SlashCommandItem[] | Promise<SlashCommandItem[]> | ์ฌ๋์ ์ปค๋งจ๋ ์ค๋ฒ๋ผ์ด๋ |
transformSlashCommands | SlashCommandTransform | ์ฌ๋์ ์ปค๋งจ๋ ๋ณํ |
onWikiLinkClick | (href: string) => void | ์ํค ๋งํฌ ํด๋ฆญ ํธ๋ค๋ฌ |
onImageUpload | (file: File) => Promise<string | { src: string }> | ์ด๋ฏธ์ง ์ ๋ก๋ ํธ๋ค๋ฌ. ๋ฐํ URL์ ์์ ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํด์ผ ์ ์ฅ๋ฉ๋๋ค |
imageBlock | Partial<ImageBlockOptions> | ImageBlock ํ์ฅ ์ต์ |
comment | CommentConfig | ๋๊ธ ๊ธฐ๋ฅ ์ค์ (๋ฏธ์ง์ ์ ๊บผ์ง) |
bookmark | false | { onResolveBookmark? } | ๋ถ๋งํฌ ๊ธฐ๋ฅ ์ค์ |
blockHandle | boolean | ๋ธ๋ก ํธ๋ค ํ์ ์ฌ๋ถ (์ฝ๊ธฐ ์ ์ฉ์์๋ ํ์ยท๋์ํ์ง ์์) |
wikiLink | boolean | ์ํค ๋งํฌ ํ์ฑํ ์ฌ๋ถ |
table | boolean | ํ ์ด๋ธ ํ์ฑํ ์ฌ๋ถ |
callout | boolean | Callout ํ์ฑํ ์ฌ๋ถ |
toggleList | boolean | ํ ๊ธ ๋ฆฌ์คํธ ํ์ฑํ ์ฌ๋ถ |
extensions | ExtensionsInput | ํ์ฅ ์ถ๊ฐ/๊ต์ฒด |
EditorUiOptions:
| ์ต์ | ํ์ | ์ค๋ช |
|---|---|---|
showBubbleMenu | boolean | bubble menu ํ์ (๊ธฐ๋ณธ๊ฐ: true) |
showFloatingMenu | boolean | floating menu ํ์ (๊ธฐ๋ณธ๊ฐ: true) |
showTableMenu | boolean | table menu ํ์ (๊ธฐ๋ณธ๊ฐ: true) |
fill | boolean | ๋ถ๋ชจ ๋์ด๋ฅผ ์ฑ์ |
autoresize | boolean | ์ฝํ ์ธ ๋์ด์ ๋ง๊ฒ ์๋ ์กฐ์ |
bordered | boolean | ํ ๋๋ฆฌ ํ์ |
className | string | ๋ฃจํธ ์๋ฆฌ๋จผํธ ํด๋์ค |
style | React.CSSProperties | ๋ฃจํธ ์๋ฆฌ๋จผํธ ์คํ์ผ |
messages | InkioMessageOverrides | UI ๋ฉ์์ง ์ค๋ฒ๋ผ์ด๋ |
icons | Partial<InkioIconRegistry> | ์์ด์ฝ ์ค๋ฒ๋ผ์ด๋ |
bubbleMenu | Omit<BubbleMenuProps, 'editor'> | BubbleMenu ์ธ๋ถ ์ต์ |
floatingMenu | Omit<FloatingMenuProps, 'editor'> | FloatingMenu ์ธ๋ถ ์ต์ |
tableMenu | Omit<TableMenuProps, 'editor'> | TableMenu ์ธ๋ถ ์ต์ |
export function EditorPage() {
return (
<Editor
content="<p>Hello Inkio</p>"
hashtagItems={({ query }) => [
{ id: query || 'inkio', label: `#${query || 'inkio'}` },
]}
mentionItems={async ({ query }) => searchUsers(query)}
onImageUpload={async (file) => URL.createObjectURL(file)}
ui={{ showBubbleMenu: true, showFloatingMenu: true, fill: true }}
/>
);
}@inkio/simple โ Editor
Classic toolbar-first ์๋ํฐ. toolbar๊ฐ ๊ธฐ๋ณธ์ผ๋ก ํ์ฑํ๋ฉ๋๋ค.
import { Editor } from '@inkio/simple';
import '@inkio/simple/style.css';| Prop | ํ์ | ์ค๋ช |
|---|---|---|
content | string | JSONContent | ์ด๊ธฐ ๋ฌธ์ (๋ง์ดํธ ํ ์๋ํฐ๊ฐ ์ํ ์์ ) |
editable | boolean | ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ |
placeholder | string | placeholder ํ ์คํธ |
locale | InkioLocaleInput | ๋ก์ผ์ผ |
theme | 'light' | 'dark' | ์์ ํ ๋ง (๋ฏธ์ง์ ์ ๋ถ๋ชจ ํ๊ฒฝ์ ๋ฐ๋ฆ) |
tabBehavior | 'indent' | 'default' | Tab ํค ๋์ (๋ฆฌ์คํธ ๋ฐ์์๋ ๊ธฐ๋ณธ ํฌ์ปค์ค ์ด๋ ์ ์ง) |
onUpdate | (content: JSONContent) => void | ์ฝํ ์ธ ๋ณ๊ฒฝ ์ฝ๋ฐฑ |
onCreate | (editor: TiptapEditor) => void | ์๋ํฐ ์ธ์คํด์ค ์ค๋น ์ฝ๋ฐฑ |
onError | InkioErrorHandler | ์๋ฌ ํธ๋ค๋ฌ |
onImageUpload | (file: File) => Promise<string | { src: string }> | ์ด๋ฏธ์ง ์ ๋ก๋ ํธ๋ค๋ฌ. ๋ฐํ URL์ ์์ ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํด์ผ ์ ์ฅ๋ฉ๋๋ค |
imageBlock | Partial<ImageBlockOptions> | ImageBlock ํ์ฅ ์ต์ |
ui | SimpleEditorUiOptions | UI ์ต์ |
extensions | ExtensionsInput | ํ์ฅ ์ถ๊ฐ/๊ต์ฒด |
SimpleEditorUiOptions (@inkio/simple):
showToolbar (๊ธฐ๋ณธ๊ฐ: true), showBubbleMenu, showFloatingMenu, showTableMenu, fill, autoresize, bordered, className, style, messages, icons, toolbar, bubbleMenu, floatingMenu, tableMenu
ํจํค์ง๋ณ prop ๋น๊ต
| Prop | @inkio/editor | @inkio/simple |
|---|---|---|
content | O | O |
onUpdate, onCreate, onError | O | O |
ui.showToolbar | - (ํญ์ off) | O |
ui.showBubbleMenu | O (๊ธฐ๋ณธ on) | O (๊ธฐ๋ณธ off) |
ui.showFloatingMenu | O (๊ธฐ๋ณธ on) | O (๊ธฐ๋ณธ off) |
hashtagItems | O | - |
mentionItems | O | - |
slashCommands | O | - |
onWikiLinkClick | O | - |
comment | O | - |
bookmark | O | - |
blockHandle | O | - |
wikiLink | O | - |
onImageUpload | O | O |
Viewer
@inkio/editor โ Viewer
@inkio/editor์ Viewer๋ ๋๊ธ(comment) ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค.
import { Viewer } from '@inkio/editor';| Prop | ํ์ | ์ค๋ช |
|---|---|---|
content | string | JSONContent | ํ์ํ ์ฝํ ์ธ (ํ์) |
locale | InkioLocaleInput | ๋ก์ผ์ผ |
theme | 'light' | 'dark' | ์์ ํ ๋ง (๋ฏธ์ง์ ์ ๋ถ๋ชจ ํ๊ฒฝ์ ๋ฐ๋ฆ) |
ui | { className?, style?, bordered?, messages?, icons? } | UI ์ต์ |
comment | ViewerCommentOptions | ๋๊ธ ํ์ ์ค์ (๋ฏธ์ง์ ์ ๊บผ์ง) |
extensions | ExtensionsInput | ํ์ฅ ์ถ๊ฐ/๊ต์ฒด |
onCreate | (editor: TiptapEditor) => void | ์๋ํฐ ์ธ์คํด์ค ์ค๋น ์ฝ๋ฐฑ |
ViewerCommentOptions:
{
getComments: (commentId: string) => CommentData | null;
onReply?: (commentId: string, text: string) => void;
onResolve?: (commentId: string) => void;
}<Viewer
content={content}
comment={{
getComments: (id) => store.getComment(id),
onReply: (id, text) => store.reply(id, text),
onResolve: (id) => store.resolve(id),
}}
/>@inkio/simple โ Viewer
import { Viewer } from '@inkio/simple';| Prop | ํ์ | ์ค๋ช |
|---|---|---|
content | string | JSONContent | ํ์ํ ์ฝํ ์ธ (ํ์) |
locale | InkioLocaleInput | ๋ก์ผ์ผ |
theme | 'light' | 'dark' | ์์ ํ ๋ง (๋ฏธ์ง์ ์ ๋ถ๋ชจ ํ๊ฒฝ์ ๋ฐ๋ฆ) |
ui | { className?, style?, bordered?, messages?, icons? } | UI ์ต์ |
extensions | ExtensionsInput | ํ์ฅ ์ถ๊ฐ/๊ต์ฒด |
Viewer์์๋ ๋ฌธ์ ํธ์ง ์ก์ ์ด ๋นํ์ฑํ๋ฉ๋๋ค. ์ฝ์์ ์์/์์ด์ฝ ํด๋ฐ์ ๋ธ๋ก ํธ๋ค์ ํ์๋์ง ์๊ณ , ํ ์ผ(์ฒดํฌ๋ฐ์ค)ยท์ด๋ฏธ์ง ์ปจํธ๋กค๋ ์ํ๋ฅผ ๋ฐ๊พธ์ง ์์ต๋๋ค. ๋๊ธ(reply/resolve)์ ์ฝ๊ธฐ ์ ์ฉ ์ธํฐ๋์ ์ด๋ผ ๊ทธ๋๋ก ๋์ํฉ๋๋ค.
@inkio/core โ StaticViewer (์ฝ๊ธฐ ์ ์ฉ ๊ฒฝ๋ ๊ฒฝ๋ก)
๋๊ธยท์ ํ ๊ฐ์ ์ธํฐ๋์
์ด ํ์ ์๋ ๋ชฉ๋ก/๋ฏธ๋ฆฌ๋ณด๊ธฐ์๋ StaticViewer๋ฅผ
์ฌ์ฉํ์ธ์. Tiptap ์ธ์คํด์ค๋ฅผ ๋ง์ดํธํ์ง ์๊ณ ์ด๊ท ๋ ์ ์ HTML๋ง ๋ ๋๋งํ๋ฏ๋ก
<Viewer> ๋๋น ๋ฒ๋ค์ด ๊ฐ๋ณ์ต๋๋ค.
import { StaticViewer } from '@inkio/core/static';| Prop | ํ์ | ์ค๋ช |
|---|---|---|
content | string | JSONContent | ํ์ํ ์ฝํ ์ธ (ํ์) |
theme | 'light' | 'dark' | ์์ ํ ๋ง (๋ฏธ์ง์ ์ ๋ถ๋ชจ ํ๊ฒฝ์ ๋ฐ๋ฆ) |
ui | { className?, style?, bordered? } | UI ์ต์ |
extensions | ExtensionsInput | ํ์ฅ ์ถ๊ฐ/๊ต์ฒด |
locale/messages/icons/onCreate๋ ์์ต๋๋ค. ์ ์ ๋ ๋๋ง์๋
์ธํฐ๋ํฐ๋ธ UI๊ฐ ์๊ณ ์๋ํฐ ์ธ์คํด์ค๊ฐ ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์ธํฐ๋ํฐ๋ธ ์ฝ๊ธฐ ์ ์ฉ(๋๊ธ ๋ฑ)์ด ํ์ํ๋ฉด <Viewer>๋ฅผ ์ฌ์ฉํ์ธ์.
core ์คํค๋ง์ ์๋ ๋
ธ๋๋ฅผ ๋ด์ ์ฝํ
์ธ (๋๊ธยท๋ฉ์
ยท๋ถ๋งํฌ ๋ฑ advanced ํ์ฅ
ํ์
)๋ ๋น ์
๋ก ํด๋ฐฑ๋ฉ๋๋ค. ๊ทธ๋ฐ ์ฝํ
์ธ ๋ฅผ ์ ์ ์ผ๋ก ๋ ๋๋งํ๋ ค๋ฉด ํด๋น
์คํค๋ง๋ฅผ extensions๋ก ์ ๋ฌํ์ธ์.
ToC (๋ชฉ์ฐจ)
Inkio๋ ๋ ๊ฐ์ง ๋ชฉ์ฐจ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
Option 1: TocBlock (์ธ๋ผ์ธ ๋ธ๋ก)
@inkio/core์ ๊ธฐ๋ณธ ํฌํจ๋๋ ์ธ๋ผ์ธ ๋ชฉ์ฐจ ๋ธ๋ก์
๋๋ค. /toc ์ฌ๋์ ์ปค๋งจ๋๋ก ์๋ํฐ ์์ ์ง์ ์ฝ์
ํ๋ฉฐ, ํค๋ฉ์ด ์ถ๊ฐยท์์ ยท์ญ์ ๋๋ฉด ์๋์ผ๋ก ์
๋ฐ์ดํธ๋ฉ๋๋ค. ๋ฐฐ๊ฒฝยทํ
๋๋ฆฌ ์๋ Notion ์คํ์ผ ๋ฏธ๋๋ฉ ๋์์ธ์
๋๋ค.
๋นํ์ฑํํ๋ ค๋ฉด getExtensions์ tocBlock: false๋ฅผ ์ ๋ฌํฉ๋๋ค:
import { getExtensions } from '@inkio/core';
const extensions = getExtensions({ tocBlock: false });์์ธํ ๋ด์ฉ์ TocBlock ํ์ฅ์ ์ฐธ๊ณ ํ์ธ์.
Option 2: <ToC> ์ปดํฌ๋ํธ (์ค๋ฒ๋ ์ด ๋ฏธ๋๋งต)
์๋ํฐ ์ค๋ฅธ์ชฝ ๊ฐ์ฅ์๋ฆฌ์ Notion ์คํ์ผ ๋ฏธ๋๋งต ๋ฐ๋ฅผ ํ์ํ๋ ์ค๋ฒ๋ ์ด ์ปดํฌ๋ํธ์ ๋๋ค. ๋ฐ์ ๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ์ฌ๋ผ์ด๋ ์ ๋๋ฉ์ด์ ๊ณผ ํจ๊ป ์ ์ฒด ํค๋ฉ ํจ๋์ด ํผ์ณ์ง๋๋ค.
์๋ํฐ๋ฅผ ๊ฐ์ธ๋ ์ปจํ
์ด๋์ position: relative๋ฅผ ์ค์ ํ๊ณ , ๊ทธ ์์ <ToC>๋ฅผ ๋ฐฐ์นํฉ๋๋ค.
import { ToC } from '@inkio/editor';
// ๋๋
import { ToC } from '@inkio/core';| Prop | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|
source | TiptapEditor | null | - | ์๋ํฐ ์ธ์คํด์ค |
maxLevel | number | 3 | ํฌํจํ ์ต๋ ํค๋ฉ ๋ ๋ฒจ (h1-h3) |
className | string | - | ๋ฃจํธ ์๋ฆฌ๋จผํธ ํด๋์ค |
style | React.CSSProperties | - | ๋ฃจํธ ์๋ฆฌ๋จผํธ ์คํ์ผ |
import { useState } from 'react';
import { Editor, ToC } from '@inkio/editor';
import '@inkio/editor/style.css';
function MyEditor() {
const [editor, setEditor] = useState(null);
return (
<div style={{ position: 'relative' }}>
<Editor onCreate={setEditor} />
<ToC source={editor} />
</div>
);
}ํค๋ฉ ํด๋ฆญ ์ ์คํฌ๋กค ์คํ์ ์ ์กฐ์ ํ๋ ค๋ฉด CSS ๋ณ์๋ฅผ ์ฌ์ฉํ์ธ์:
:root {
--inkio-scroll-offset: 80px; /* sticky header ๋์ด ๋ฑ */
}CSS
| ํ์ผ | ํฌํจ ๋ด์ฉ | ์ฉ๋ |
|---|---|---|
@inkio/editor/style.css | ์ ์ฒด ์คํ์ผ (advanced ํ๋ฆฌ์ ํฌํจ) | ์ผ๋ฐ ์ฌ์ฉ |
@inkio/simple/style.css | core ์คํ์ผ๋ง | classic ์๋ํฐ |
@inkio/editor/minimal.css | ๊ตฌ์กฐ๋ง (์์ยทํฐํธ ์์) | ์ปค์คํ ํ ๋ง |
@inkio/simple/minimal.css | ๊ตฌ์กฐ๋ง | ์ปค์คํ ํ ๋ง |
์กฐํฉ๋ณ import ๋งคํธ๋ฆญ์ค
| ์กฐํฉ | import | ๋น๊ณ |
|---|---|---|
@inkio/simple ๋จ๋
| simple/style.css | โ |
@inkio/editor ๋จ๋
| editor/style.css | advanced ์ฅ์ ํฌํจ |
core + advanced ์ง์ ์กฐํฉ | core/style.css | ๋ฉ์
pill ๋ฑ advanced ์ฅ์์ editor/style.css์๋ง ์์ |
image-editor (๋จ๋
ยท์ฐ๊ฒฐ ๊ณตํต) | image-editor/style.css | ์์ฒด ์คํ์ผ์ํธ ํ์ |
collab (CollabPresence) | ๋ถํ์ | ์ธ๋ผ์ธ ์คํ์ผ๋ง ์ฌ์ฉ |
| ์ปค์คํ ํ ๋ง | ๊ฐ ํจํค์ง์ minimal.css + --inkio-* ํ ํฐ ์ฌ์ ์ | ์์ยทํฐํธ๋ ์ง์ ์ ์ |
minimal.css๋ ๋ ์ด์์๊ณผ ๊ตฌ์กฐ์ ์คํ์ผ๋ง ํฌํจํฉ๋๋ค. ์์, ํฐํธ, ๋ฐฐ๊ฒฝ ๋ฑ์ ์ง์ ์ ์ํด์ผ ํฉ๋๋ค.
// ์ผ๋ฐ ์ฌ์ฉ
import '@inkio/editor/style.css';
// ์ปค์คํ
ํ
๋ง (๋์์ธ ํ ํฐ์ ์ง์ ์ ์ํ ๋)
import '@inkio/editor/minimal.css';์ปค์คํ ๋ฒ์ ์ง๋
| ๋์ | ๋ฐฉ๋ฒ | ๋น๊ณ |
|---|---|---|
| ๋ฌธ๊ตฌ | ui.messages | ํ์
๋ณ override, en*Messages์์ ์ถ๋ฐ |
| ์์ด์ฝ | ui.icons | Partial<InkioIconRegistry>, ์ผ๋ถ๋ง ๊ต์ฒด ๊ฐ๋ฅ |
| ์์ยทํฐํธยท๊ฐ๊ฒฉ | CSS ๋ณ์ (--inkio-*) | minimal.css + ํ ํฐ ์ฌ์ ์ |
| ์ ์ฒด ํ ๋ง | theme prop ๋๋ CSS | 'light'/'dark', ๋ฏธ์ง์ ์ ๋ถ๋ชจ ์ถ์ข
|
| ๋ฉ๋ด ๊ตฌ์ฑ | ui.show* + toolbar/bubbleMenu ์ธ๋ถ ์ต์
| preset๋ณ ๊ธฐ๋ณธ๊ฐ ์์ด (์ ๋น๊ตํ) |
| ์๋ํฐ ๋์ | extensions ์ถ๊ฐ/๊ต์ฒด | ExtensionsInput ๊ท์น ์ค์ |
์ธ๋ถ์์ ๊ฐ์ ๋ฐ๊พธ๋ ค๋ฉด: onCreate๋ก ๋ฐ์ ์ธ์คํด์ค์
editor.commands.setContent()๋ฅผ ์ฐ์ธ์ (content prop์ ์ด๊ธฐ๊ฐ ์ ์ฉ).
๋ฐ๊ฟ ์ ์๋ ๊ฒ: StaticViewer์ ์ธํฐ๋์
(์ ์ ๋ ๋ ์ ์ฉ).
๊ณตํต UI override
ui prop์ผ๋ก UI ๊ด๋ จ ์ต์
์ ๋ฌถ์ด ์ ๋ฌํฉ๋๋ค.
import { Editor, type InkioMessageOverrides } from '@inkio/editor';
const messages: InkioMessageOverrides = {
core: {
suggestion: { empty: 'No matching items.' },
},
};
export function Page() {
return (
<Editor
ui={{
messages,
showBubbleMenu: true,
showFloatingMenu: true,
fill: true,
}}
/>
);
}๋ถ๋ชจ ๋์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ์๋ํฐ๋ฅผ ์ฑ์ฐ๋ ค๋ฉด ui.fill์ ์ฌ์ฉํฉ๋๋ค. ๋ถ๋ชจ ์ปจํ
์ด๋์ ๊ณ์ฐ๋ ๋์ด๊ฐ ์์ด์ผ ๋์ํฉ๋๋ค.
<section style={{ height: '640px' }}>
<Editor ui={{ fill: true }} />
</section>๊ธฐ๋ณธ ๋ฉ์์ง export
import { enCoreMessages } from '@inkio/editor';
import { enCommentMessages } from '@inkio/advanced';
import { enImageEditorMessages } from '@inkio/image-editor';