์ปดํฌ๋ํธ
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 | ์ ์ด ๋ชจ๋ ์ฝํ ์ธ (controlled) |
initialContent | string | JSONContent | ๋น์ ์ด ๋ชจ๋ ์ด๊ธฐ ์ฝํ ์ธ |
editable | boolean | ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ (๊ธฐ๋ณธ๊ฐ: true) |
placeholder | string | ๋น ์๋ํฐ placeholder ํ ์คํธ |
locale | InkioLocaleInput | ๋ก์ผ์ผ |
tabBehavior | 'indent' | 'default' | Tab ํค ๋์ (๊ธฐ๋ณธ๊ฐ: '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 }> | ์ด๋ฏธ์ง ์ ๋ก๋ ํธ๋ค๋ฌ |
imageBlock | Partial<ImageBlockOptions> | ImageBlock ํ์ฅ ์ต์ |
comment | false | CommentConfig | ๋๊ธ ๊ธฐ๋ฅ ์ค์ (false๋ก ๋นํ์ฑํ) |
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
initialContent="<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 | ์ ์ด ๋ชจ๋ ์ฝํ ์ธ |
initialContent | string | JSONContent | ๋น์ ์ด ๋ชจ๋ ์ด๊ธฐ ์ฝํ ์ธ |
editable | boolean | ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ |
placeholder | string | placeholder ํ ์คํธ |
locale | InkioLocaleInput | ๋ก์ผ์ผ |
tabBehavior | 'indent' | 'default' | Tab ํค ๋์ |
onUpdate | (content: JSONContent) => void | ์ฝํ ์ธ ๋ณ๊ฒฝ ์ฝ๋ฐฑ |
onCreate | (editor: TiptapEditor) => void | ์๋ํฐ ์ธ์คํด์ค ์ค๋น ์ฝ๋ฐฑ |
onError | InkioErrorHandler | ์๋ฌ ํธ๋ค๋ฌ |
onImageUpload | (file: File) => Promise<string | { src: string }> | ์ด๋ฏธ์ง ์ ๋ก๋ ํธ๋ค๋ฌ |
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 / initialContent | 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 | ๋ก์ผ์ผ |
ui | { className?, style?, bordered?, messages?, icons? } | UI ์ต์ |
comment | false | 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 | ๋ก์ผ์ผ |
ui | { className?, style?, bordered?, messages?, icons? } | UI ์ต์ |
extensions | ExtensionsInput | ํ์ฅ ์ถ๊ฐ/๊ต์ฒด |
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 | ๊ตฌ์กฐ๋ง | ์ปค์คํ ํ ๋ง |
minimal.css๋ ๋ ์ด์์๊ณผ ๊ตฌ์กฐ์ ์คํ์ผ๋ง ํฌํจํฉ๋๋ค. ์์, ํฐํธ, ๋ฐฐ๊ฒฝ ๋ฑ์ ์ง์ ์ ์ํด์ผ ํฉ๋๋ค.
// ์ผ๋ฐ ์ฌ์ฉ
import '@inkio/editor/style.css';
// ์ปค์คํ
ํ
๋ง (๋์์ธ ํ ํฐ์ ์ง์ ์ ์ํ ๋)
import '@inkio/editor/minimal.css';๊ณตํต 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';