New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@kingdoo/editor

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@kingdoo/editor

Image-only BlockNote rich text editor with S3 upload and optimized loading spinner

latest
Source
npmnpm
Version
0.3.3
Version published
Maintainers
1
Created
Source

LumirEditor

🖼️ 이미지 전용 BlockNote 기반 Rich Text 에디터

npm version License: MIT

📋 목차

✨ 핵심 특징

특징설명
🖼️ 이미지 전용이미지 업로드/드래그앤드롭만 지원 (비디오/오디오/파일 제거)
☁️ S3 연동Presigned URL 기반 S3 업로드 내장
🎯 커스텀 업로더자체 업로드 로직 적용 가능
로딩 스피너이미지 업로드 중 자동 스피너 표시
🚀 애니메이션 최적화기본 애니메이션 비활성화로 성능 향상
📝 TypeScript완전한 타입 안전성
🎨 테마 지원라이트/다크 테마 및 커스텀 테마 지원
📱 반응형모바일/데스크톱 최적화

지원 이미지 형식

PNG, JPEG/JPG, GIF (애니메이션 포함), WebP, BMP, SVG

📦 설치

# npm
npm install @kingdoo/editor

# yarn
yarn add @kingdoo/editor

# pnpm
pnpm add @kingdoo/editor

Peer Dependencies

{
  "react": ">=18.0.0",
  "react-dom": ">=18.0.0"
}

🚀 빠른 시작

1단계: CSS 임포트 (필수)

import "@kingdoo/editor/style.css";

⚠️ 중요: CSS를 임포트하지 않으면 에디터가 정상적으로 렌더링되지 않습니다.

2단계: 기본 사용

import { LumirEditor } from "@kingdoo/editor";
import "@kingdoo/editor/style.css";

export default function App() {
  return (
    <div className="w-full h-[400px]">
      <LumirEditor onContentChange={(blocks) => console.log(blocks)} />
    </div>
  );
}

3단계: Next.js에서 사용 (SSR 비활성화 필수)

"use client";

import dynamic from "next/dynamic";
import "@kingdoo/editor/style.css";

const LumirEditor = dynamic(
  () => import("@kingdoo/editor").then((m) => ({ default: m.LumirEditor })),
  { ssr: false }
);

export default function EditorPage() {
  return (
    <div className="w-full h-[500px]">
      <LumirEditor
        onContentChange={(blocks) => console.log("Content:", blocks)}
      />
    </div>
  );
}

📚 Props 레퍼런스

에디터 옵션 (Editor Options)

Prop타입기본값설명
initialContentDefaultPartialBlock[] | stringundefined초기 콘텐츠 (블록 배열 또는 JSON 문자열)
initialEmptyBlocksnumber3초기 빈 블록 개수
placeholderstringundefined첫 번째 블록의 placeholder 텍스트
uploadFile(file: File) => Promise<string>undefined커스텀 파일 업로드 함수
s3UploadS3UploaderConfigundefinedS3 업로드 설정
tablesTableConfig{...}테이블 기능 설정
heading{ levels?: (1|2|3|4|5|6)[] }{ levels: [1,2,3,4,5,6] }헤딩 레벨 설정
defaultStylesbooleantrue기본 스타일 활성화
disableExtensionsstring[][]비활성화할 확장 기능 목록
tabBehavior"prefer-navigate-ui" | "prefer-indent""prefer-navigate-ui"탭 키 동작
trailingBlockbooleantrue마지막에 빈 블록 자동 추가
allowVideoUploadbooleanfalse비디오 업로드 허용 (기본 비활성)
allowAudioUploadbooleanfalse오디오 업로드 허용 (기본 비활성)
allowFileUploadbooleanfalse일반 파일 업로드 허용 (기본 비활성)

뷰 옵션 (View Options)

Prop타입기본값설명
editablebooleantrue편집 가능 여부
theme"light" | "dark" | ThemeObject"light"에디터 테마
formattingToolbarbooleantrue서식 툴바 표시
linkToolbarbooleantrue링크 툴바 표시
sideMenubooleantrue사이드 메뉴 표시
sideMenuAddButtonbooleanfalse사이드 메뉴 + 버튼 표시 (false시 드래그 핸들만 표시)
emojiPickerbooleantrue이모지 선택기 표시
filePanelbooleantrue파일 패널 표시
tableHandlesbooleantrue테이블 핸들 표시
classNamestring""컨테이너 CSS 클래스

콜백 (Callbacks)

Prop타입설명
onContentChange(blocks: DefaultPartialBlock[]) => void콘텐츠 변경 시 호출
onSelectionChange() => void선택 영역 변경 시 호출

S3UploaderConfig

interface S3UploaderConfig {
  apiEndpoint: string; // Presigned URL API 엔드포인트 (필수)
  env: "development" | "production"; // 환경 (필수)
  path: string; // S3 경로 (필수)
}

TableConfig

interface TableConfig {
  splitCells?: boolean; // 셀 분할 (기본: true)
  cellBackgroundColor?: boolean; // 셀 배경색 (기본: true)
  cellTextColor?: boolean; // 셀 텍스트 색상 (기본: true)
  headers?: boolean; // 헤더 행 (기본: true)
}

🖼️ 이미지 업로드

방법 1: S3 업로드 (권장)

Presigned URL을 사용한 안전한 S3 업로드 방식입니다.

<LumirEditor
  s3Upload={{
    apiEndpoint: "/api/s3/presigned",
    env: "development",
    path: "blog/images",
  }}
  onContentChange={(blocks) => console.log(blocks)}
/>

S3 파일 저장 경로 구조:

{env}/{path}/{filename}
예: development/blog/images/my-image.png

API 엔드포인트 응답 예시:

{
  "presignedUrl": "https://s3.amazonaws.com/bucket/...",
  "publicUrl": "https://cdn.example.com/development/blog/images/my-image.png"
}

방법 2: 커스텀 업로더

자체 업로드 로직을 사용할 때 활용합니다.

<LumirEditor
  uploadFile={async (file) => {
    const formData = new FormData();
    formData.append("image", file);

    const response = await fetch("/api/upload", {
      method: "POST",
      body: formData,
    });

    const data = await response.json();
    return data.url; // 업로드된 이미지의 URL 반환
  }}
/>

방법 3: createS3Uploader 헬퍼 함수

S3 업로더를 직접 생성하여 사용할 수 있습니다.

import { LumirEditor, createS3Uploader } from "@kingdoo/editor";

// S3 업로더 생성
const s3Uploader = createS3Uploader({
  apiEndpoint: "/api/s3/presigned",
  env: "production",
  path: "uploads/images",
});

// 에디터에 적용
<LumirEditor uploadFile={s3Uploader} />;

// 또는 별도로 사용
const imageUrl = await s3Uploader(imageFile);

업로드 우선순위

  • uploadFile prop이 있으면 우선 사용
  • uploadFile이 없고 s3Upload가 있으면 S3 업로드 사용
  • 둘 다 없으면 업로드 실패

🛠️ 유틸리티 API

ContentUtils

콘텐츠 관리 유틸리티 클래스입니다.

import { ContentUtils } from "@kingdoo/editor";

// JSON 문자열 유효성 검증
const isValid = ContentUtils.isValidJSONString('[{"type":"paragraph"}]');
// true

// JSON 문자열을 블록 배열로 파싱
const blocks = ContentUtils.parseJSONContent(jsonString);
// DefaultPartialBlock[] | null

// 기본 빈 블록 생성
const emptyBlock = ContentUtils.createDefaultBlock();
// { type: "paragraph", props: {...}, content: [...], children: [] }

// 콘텐츠 유효성 검증 및 기본값 설정
const validatedContent = ContentUtils.validateContent(content, 3);
// 빈 콘텐츠면 3개의 빈 블록 반환

EditorConfig

에디터 설정 유틸리티 클래스입니다.

import { EditorConfig } from "@kingdoo/editor";

// 테이블 기본 설정 가져오기
const tableConfig = EditorConfig.getDefaultTableConfig({
  splitCells: true,
  headers: false,
});

// 헤딩 기본 설정 가져오기
const headingConfig = EditorConfig.getDefaultHeadingConfig({
  levels: [1, 2, 3],
});

// 비활성화 확장 목록 생성
const disabledExt = EditorConfig.getDisabledExtensions(
  ["codeBlock"], // 사용자 정의 비활성 확장
  false, // allowVideo
  false, // allowAudio
  false // allowFile
);
// ["codeBlock", "video", "audio", "file"]

cn (className 유틸리티)

조건부 className 결합 유틸리티입니다.

import { cn } from "@kingdoo/editor";

<LumirEditor
  className={cn(
    "min-h-[400px] rounded-lg",
    isFullscreen && "fixed inset-0 z-50",
    isDarkMode && "dark-theme"
  )}
/>;

📖 타입 정의

주요 타입 import

import type {
  // 에디터 Props
  LumirEditorProps,

  // 에디터 인스턴스 타입
  EditorType,

  // 블록 관련 타입
  DefaultPartialBlock,
  DefaultBlockSchema,
  DefaultInlineContentSchema,
  DefaultStyleSchema,
  PartialBlock,
  BlockNoteEditor,
} from "@kingdoo/editor";

import type { S3UploaderConfig } from "@kingdoo/editor";

LumirEditorProps 전체 인터페이스

interface LumirEditorProps {
  // === Editor Options ===
  initialContent?: DefaultPartialBlock[] | string;
  initialEmptyBlocks?: number;
  placeholder?: string;
  uploadFile?: (file: File) => Promise<string>;
  s3Upload?: {
    apiEndpoint: string;
    env: "development" | "production";
    path: string;
  };
  allowVideoUpload?: boolean;
  allowAudioUpload?: boolean;
  allowFileUpload?: boolean;
  tables?: {
    splitCells?: boolean;
    cellBackgroundColor?: boolean;
    cellTextColor?: boolean;
    headers?: boolean;
  };
  heading?: { levels?: (1 | 2 | 3 | 4 | 5 | 6)[] };
  defaultStyles?: boolean;
  disableExtensions?: string[];
  tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
  trailingBlock?: boolean;

  // === View Options ===
  editable?: boolean;
  theme?:
    | "light"
    | "dark"
    | Partial<Record<string, unknown>>
    | {
        light: Partial<Record<string, unknown>>;
        dark: Partial<Record<string, unknown>>;
      };
  formattingToolbar?: boolean;
  linkToolbar?: boolean;
  sideMenu?: boolean;
  sideMenuAddButton?: boolean;
  emojiPicker?: boolean;
  filePanel?: boolean;
  tableHandles?: boolean;
  onSelectionChange?: () => void;
  className?: string;

  // === Callbacks ===
  onContentChange?: (content: DefaultPartialBlock[]) => void;
}

💡 사용 예제

기본 에디터

import { LumirEditor } from "@kingdoo/editor";
import "@kingdoo/editor/style.css";

function BasicEditor() {
  return (
    <div className="h-[400px]">
      <LumirEditor />
    </div>
  );
}

초기 콘텐츠 설정

// 방법 1: 블록 배열
<LumirEditor
  initialContent={[
    {
      type: "heading",
      props: { level: 1 },
      content: [{ type: "text", text: "제목입니다", styles: {} }],
    },
    {
      type: "paragraph",
      content: [{ type: "text", text: "본문 내용...", styles: {} }],
    },
  ]}
/>

// 방법 2: JSON 문자열
<LumirEditor
  initialContent='[{"type":"paragraph","content":[{"type":"text","text":"Hello World","styles":{}}]}]'
/>

읽기 전용 모드

<LumirEditor
  editable={false}
  initialContent={savedContent}
  sideMenu={false}
  formattingToolbar={false}
/>

다크 테마

<LumirEditor theme="dark" className="bg-gray-900 rounded-lg" />

S3 이미지 업로드

<LumirEditor
  s3Upload={{
    apiEndpoint: "/api/s3/presigned",
    env: process.env.NODE_ENV as "development" | "production",
    path: "articles/images",
  }}
  onContentChange={(blocks) => {
    // 저장 로직
    saveToDatabase(JSON.stringify(blocks));
  }}
/>

반응형 디자인

<div className="w-full h-64 md:h-96 lg:h-[600px]">
  <LumirEditor className="h-full rounded-md md:rounded-lg shadow-sm md:shadow-md" />
</div>

테이블 설정 커스터마이징

<LumirEditor
  tables={{
    splitCells: true,
    cellBackgroundColor: true,
    cellTextColor: false, // 셀 텍스트 색상 비활성
    headers: true,
  }}
  heading={{
    levels: [1, 2, 3], // H4-H6 비활성
  }}
/>

콘텐츠 저장 및 불러오기

import { useState, useEffect } from "react";
import { LumirEditor, ContentUtils } from "@kingdoo/editor";

function EditorWithSave() {
  const [content, setContent] = useState<string>("");

  // 저장된 콘텐츠 불러오기
  useEffect(() => {
    const saved = localStorage.getItem("editor-content");
    if (saved && ContentUtils.isValidJSONString(saved)) {
      setContent(saved);
    }
  }, []);

  // 콘텐츠 저장
  const handleContentChange = (blocks) => {
    const jsonContent = JSON.stringify(blocks);
    localStorage.setItem("editor-content", jsonContent);
  };

  return (
    <LumirEditor
      initialContent={content}
      onContentChange={handleContentChange}
    />
  );
}

🎨 스타일링 가이드

기본 CSS 구조

/* 메인 컨테이너 */
.lumirEditor {
  width: 100%;
  height: 100%;
  min-width: 200px;
  overflow: auto;
  background-color: #ffffff;
}

/* 에디터 내용 영역 */
.lumirEditor .bn-editor {
  font-family: "Pretendard", "Noto Sans KR", -apple-system, sans-serif;
  padding: 5px 10px 0 25px;
}

/* 문단 블록 */
.lumirEditor [data-content-type="paragraph"] {
  font-size: 14px;
}

/* 업로드 스피너 */
.lumirEditor-upload-overlay {
  ...;
}
.lumirEditor-spinner {
  ...;
}

Tailwind CSS와 함께 사용

import { LumirEditor, cn } from "@kingdoo/editor";

<LumirEditor
  className={cn(
    "min-h-[400px] rounded-xl",
    "border border-gray-200 shadow-lg",
    "focus-within:ring-2 focus-within:ring-blue-500"
  )}
/>;

커스텀 스타일 적용

/* globals.css */
.my-editor .bn-editor {
  padding-left: 30px;
  padding-right: 20px;
  font-size: 16px;
}

.my-editor [data-content-type="heading"] {
  font-weight: 700;
  margin-top: 24px;
}
<LumirEditor className="my-editor" />

⚠️ 주의사항 및 트러블슈팅

필수 체크리스트

항목체크
CSS 임포트import "@kingdoo/editor/style.css";
컨테이너 높이 설정부모 요소에 높이 지정 필수
Next.js SSR 비활성화dynamic(..., { ssr: false }) 사용
React 버전18.0.0 이상 필요

일반적인 문제 해결

1. 에디터가 렌더링되지 않음

// ❌ 잘못된 사용
<LumirEditor />;

// ✅ 올바른 사용 - CSS 임포트 필요
import "@kingdoo/editor/style.css";
<LumirEditor />;

2. Next.js에서 hydration 오류

// ❌ 잘못된 사용
import { LumirEditor } from "@kingdoo/editor";

// ✅ 올바른 사용 - dynamic import 사용
const LumirEditor = dynamic(
  () => import("@kingdoo/editor").then((m) => ({ default: m.LumirEditor })),
  { ssr: false }
);

3. 높이가 0으로 표시됨

// ❌ 잘못된 사용
<LumirEditor />

// ✅ 올바른 사용 - 부모 요소에 높이 설정
<div className="h-[400px]">
  <LumirEditor />
</div>

4. 이미지 업로드 실패

// uploadFile 또는 s3Upload 중 하나 반드시 설정
<LumirEditor
  uploadFile={async (file) => {
    // 업로드 로직
    return imageUrl;
  }}
  // 또는
  s3Upload={{
    apiEndpoint: "/api/s3/presigned",
    env: "development",
    path: "images",
  }}
/>

성능 최적화 팁

  • 애니메이션 기본 비활성: 이미 animations: false로 설정되어 성능 최적화됨
  • 큰 콘텐츠 처리: 초기 콘텐츠가 클 경우 lazy loading 고려
  • 이미지 최적화: 업로드 전 클라이언트에서 이미지 리사이징 권장

🏗️ 프로젝트 구조

@kingdoo/editor/
├── dist/                    # 빌드 출력
│   ├── index.js            # CommonJS 빌드
│   ├── index.mjs           # ESM 빌드
│   ├── index.d.ts          # TypeScript 타입 정의
│   └── style.css           # 스타일시트
├── src/
│   ├── components/
│   │   └── LumirEditor.tsx # 메인 에디터 컴포넌트
│   ├── types/
│   │   ├── editor.ts       # 에디터 타입 정의
│   │   └── index.ts        # 타입 export
│   ├── utils/
│   │   ├── cn.ts           # className 유틸리티
│   │   └── s3-uploader.ts  # S3 업로더
│   ├── index.ts            # 메인 export
│   └── style.css           # 소스 스타일
└── examples/
    └── tailwind-integration.md  # Tailwind 통합 가이드

📄 라이선스

MIT License

🔗 관련 링크

Keywords

editor

FAQs

Package last updated on 11 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts