Skip to Content
์ปดํฌ๋„ŒํŠธ

์ปดํฌ๋„ŒํŠธ

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ํƒ€์ž…์„ค๋ช…
contentstring | JSONContent์ œ์–ด ๋ชจ๋“œ ์ฝ˜ํ…์ธ  (controlled)
initialContentstring | JSONContent๋น„์ œ์–ด ๋ชจ๋“œ ์ดˆ๊ธฐ ์ฝ˜ํ…์ธ 
editablebooleanํŽธ์ง‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: true)
placeholderstring๋นˆ ์—๋””ํ„ฐ placeholder ํ…์ŠคํŠธ
localeInkioLocaleInput๋กœ์ผ€์ผ
tabBehavior'indent' | 'default'Tab ํ‚ค ๋™์ž‘ (๊ธฐ๋ณธ๊ฐ’: 'indent')
onUpdate(content: JSONContent) => void์ฝ˜ํ…์ธ  ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ
onCreate(editor: TiptapEditor) => void์—๋””ํ„ฐ ์ธ์Šคํ„ด์Šค ์ค€๋น„ ์ฝœ๋ฐฑ
onErrorInkioErrorHandler์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ
uiEditorUiOptionsUI ์˜ต์…˜ (์•„๋ž˜ ์ฐธ๊ณ )
hashtagItems(params: { query: string }) => HashTagItem[] | Promise<HashTagItem[]>ํ•ด์‹œํƒœ๊ทธ ์ œ์•ˆ
mentionItems(params: { query: string }) => MentionItem[] | Promise<MentionItem[]>๋ฉ˜์…˜ ์ œ์•ˆ
slashCommands(query: string) => SlashCommandItem[] | Promise<SlashCommandItem[]>์Šฌ๋ž˜์‹œ ์ปค๋งจ๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ
transformSlashCommandsSlashCommandTransform์Šฌ๋ž˜์‹œ ์ปค๋งจ๋“œ ๋ณ€ํ™˜
onWikiLinkClick(href: string) => void์œ„ํ‚ค ๋งํฌ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ
onImageUpload(file: File) => Promise<string | { src: string }>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ
imageBlockPartial<ImageBlockOptions>ImageBlock ํ™•์žฅ ์˜ต์…˜
commentfalse | CommentConfig๋Œ“๊ธ€ ๊ธฐ๋Šฅ ์„ค์ • (false๋กœ ๋น„ํ™œ์„ฑํ™”)
bookmarkfalse | { onResolveBookmark? }๋ถ๋งˆํฌ ๊ธฐ๋Šฅ ์„ค์ •
blockHandleboolean๋ธ”๋ก ํ•ธ๋“ค ํ‘œ์‹œ ์—ฌ๋ถ€
wikiLinkboolean์œ„ํ‚ค ๋งํฌ ํ™œ์„ฑํ™” ์—ฌ๋ถ€
tablebooleanํ…Œ์ด๋ธ” ํ™œ์„ฑํ™” ์—ฌ๋ถ€
calloutbooleanCallout ํ™œ์„ฑํ™” ์—ฌ๋ถ€
toggleListbooleanํ† ๊ธ€ ๋ฆฌ์ŠคํŠธ ํ™œ์„ฑํ™” ์—ฌ๋ถ€
extensionsExtensionsInputํ™•์žฅ ์ถ”๊ฐ€/๊ต์ฒด

EditorUiOptions:

์˜ต์…˜ํƒ€์ž…์„ค๋ช…
showBubbleMenubooleanbubble menu ํ‘œ์‹œ (๊ธฐ๋ณธ๊ฐ’: true)
showFloatingMenubooleanfloating menu ํ‘œ์‹œ (๊ธฐ๋ณธ๊ฐ’: true)
showTableMenubooleantable menu ํ‘œ์‹œ (๊ธฐ๋ณธ๊ฐ’: true)
fillboolean๋ถ€๋ชจ ๋†’์ด๋ฅผ ์ฑ„์›€
autoresizeboolean์ฝ˜ํ…์ธ  ๋†’์ด์— ๋งž๊ฒŒ ์ž๋™ ์กฐ์ ˆ
borderedbooleanํ…Œ๋‘๋ฆฌ ํ‘œ์‹œ
classNamestring๋ฃจํŠธ ์—˜๋ฆฌ๋จผํŠธ ํด๋ž˜์Šค
styleReact.CSSProperties๋ฃจํŠธ ์—˜๋ฆฌ๋จผํŠธ ์Šคํƒ€์ผ
messagesInkioMessageOverridesUI ๋ฉ”์‹œ์ง€ ์˜ค๋ฒ„๋ผ์ด๋“œ
iconsPartial<InkioIconRegistry>์•„์ด์ฝ˜ ์˜ค๋ฒ„๋ผ์ด๋“œ
bubbleMenuOmit<BubbleMenuProps, 'editor'>BubbleMenu ์„ธ๋ถ€ ์˜ต์…˜
floatingMenuOmit<FloatingMenuProps, 'editor'>FloatingMenu ์„ธ๋ถ€ ์˜ต์…˜
tableMenuOmit<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ํƒ€์ž…์„ค๋ช…
contentstring | JSONContent์ œ์–ด ๋ชจ๋“œ ์ฝ˜ํ…์ธ 
initialContentstring | JSONContent๋น„์ œ์–ด ๋ชจ๋“œ ์ดˆ๊ธฐ ์ฝ˜ํ…์ธ 
editablebooleanํŽธ์ง‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€
placeholderstringplaceholder ํ…์ŠคํŠธ
localeInkioLocaleInput๋กœ์ผ€์ผ
tabBehavior'indent' | 'default'Tab ํ‚ค ๋™์ž‘
onUpdate(content: JSONContent) => void์ฝ˜ํ…์ธ  ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ
onCreate(editor: TiptapEditor) => void์—๋””ํ„ฐ ์ธ์Šคํ„ด์Šค ์ค€๋น„ ์ฝœ๋ฐฑ
onErrorInkioErrorHandler์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ
onImageUpload(file: File) => Promise<string | { src: string }>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ
imageBlockPartial<ImageBlockOptions>ImageBlock ํ™•์žฅ ์˜ต์…˜
uiSimpleEditorUiOptionsUI ์˜ต์…˜
extensionsExtensionsInputํ™•์žฅ ์ถ”๊ฐ€/๊ต์ฒด

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 / initialContentOO
onUpdate, onCreate, onErrorOO
ui.showToolbar- (ํ•ญ์ƒ off)O
ui.showBubbleMenuO (๊ธฐ๋ณธ on)O (๊ธฐ๋ณธ off)
ui.showFloatingMenuO (๊ธฐ๋ณธ on)O (๊ธฐ๋ณธ off)
hashtagItemsO-
mentionItemsO-
slashCommandsO-
onWikiLinkClickO-
commentO-
bookmarkO-
blockHandleO-
wikiLinkO-
onImageUploadOO

Viewer

@inkio/editor โ€” Viewer

@inkio/editor์˜ Viewer๋Š” ๋Œ“๊ธ€(comment) ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

import { Viewer } from '@inkio/editor';
Propํƒ€์ž…์„ค๋ช…
contentstring | JSONContentํ‘œ์‹œํ•  ์ฝ˜ํ…์ธ  (ํ•„์ˆ˜)
localeInkioLocaleInput๋กœ์ผ€์ผ
ui{ className?, style?, bordered?, messages?, icons? }UI ์˜ต์…˜
commentfalse | ViewerCommentOptions๋Œ“๊ธ€ ํ‘œ์‹œ ์„ค์ •
extensionsExtensionsInputํ™•์žฅ ์ถ”๊ฐ€/๊ต์ฒด
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ํƒ€์ž…์„ค๋ช…
contentstring | JSONContentํ‘œ์‹œํ•  ์ฝ˜ํ…์ธ  (ํ•„์ˆ˜)
localeInkioLocaleInput๋กœ์ผ€์ผ
ui{ className?, style?, bordered?, messages?, icons? }UI ์˜ต์…˜
extensionsExtensionsInputํ™•์žฅ ์ถ”๊ฐ€/๊ต์ฒด

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ํƒ€์ž…๊ธฐ๋ณธ๊ฐ’์„ค๋ช…
sourceTiptapEditor | null-์—๋””ํ„ฐ ์ธ์Šคํ„ด์Šค
maxLevelnumber3ํฌํ•จํ•  ์ตœ๋Œ€ ํ—ค๋”ฉ ๋ ˆ๋ฒจ (h1-h3)
classNamestring-๋ฃจํŠธ ์—˜๋ฆฌ๋จผํŠธ ํด๋ž˜์Šค
styleReact.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.csscore ์Šคํƒ€์ผ๋งŒ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';
Last updated on