npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mesoft/tiptap-editor

v1.0.1

Published

Universal Tiptap editor component with S3 presigned URL upload support for React, Next.js, and Svelte

Downloads

15

Readme

@mesoft/tiptap-editor

React, Next.js, Svelte를 지원하는 S3 사전 서명 URL 업로드 기능이 포함된 범용 Tiptap 에디터 컴포넌트입니다.

주요 기능

  • 🎨 리치 텍스트 편집 - Tiptap으로 구동되는 완전한 기능의 WYSIWYG 에디터
  • 📤 S3 업로드 지원 - Data URL로 자동 폴백되는 내장 S3 사전 서명 URL 업로드
  • 프레임워크 독립적 - React, Next.js, Svelte에서 작동
  • 🎯 TypeScript - 타입 정의가 포함된 완전한 TypeScript 지원
  • 🎭 슬래시 명령어 - 키보드 단축키로 빠른 서식 지정
  • 🌙 다크 모드 - 자동 다크 모드 지원
  • 📱 반응형 - 모바일 친화적인 툴바 및 에디터
  • 🔧 커스터마이징 가능 - 커스텀 업로드 핸들러 및 설정으로 확장 가능

설치

npm 레지스트리에서 설치 (npm에 발행된 경우)

npm install @mesoft/tiptap-editor

GitHub에서 직접 설치

npm 레지스트리에 발행되지 않은 경우, GitHub 저장소에서 직접 설치할 수 있습니다:

npm install github:mesoftkor/tiptap-editor

또는 package.json에 직접 추가:

{
  "dependencies": {
    "@mesoft/tiptap-editor": "github:mesoftkor/tiptap-editor"
  }
}

사용법

Svelte / SvelteKit

<script lang="ts">
  import { RichTextEditor, type UploadConfig } from '@mesoft/tiptap-editor/svelte';

  let content = $state('');

  // S3 업로드 설정
  const uploadConfig: UploadConfig = {
    getPresignedUrl: async (filename, contentType, category, token) => {
      const response = await fetch('/api/uploads/presign', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({ filename, contentType, category })
      });
      const data = await response.json();
      return {
        uploadUrl: data.upload_url,
        publicUrl: data.file_url
      };
    },
    token: 'your-auth-token' // 선택사항
  };
</script>

<RichTextEditor
  bind:value={content}
  uploadCategory="notice"
  minHeight="400px"
  {uploadConfig}
/>

React

import React, { useState } from "react";
import { RichTextEditor, type UploadConfig } from "@mesoft/tiptap-editor/react";

function MyComponent() {
    const [content, setContent] = useState("");

    // S3 업로드 설정
    const uploadConfig: UploadConfig = {
        getPresignedUrl: async (filename, contentType, category, token) => {
            const response = await fetch("/api/uploads/presign", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    Authorization: `Bearer ${token}`,
                },
                body: JSON.stringify({ filename, contentType, category }),
            });
            const data = await response.json();
            return {
                uploadUrl: data.upload_url,
                publicUrl: data.file_url,
            };
        },
        token: "your-auth-token", // 선택사항
    };

    return (
        <RichTextEditor
            value={content}
            onChange={setContent}
            uploadCategory="notice"
            minHeight="400px"
            uploadConfig={uploadConfig}
        />
    );
}

Next.js (App Router)

"use client";

import { useState } from "react";
import { RichTextEditor, type UploadConfig } from "@mesoft/tiptap-editor/react";

export default function EditorPage() {
    const [content, setContent] = useState("");

    // S3 업로드 설정
    const uploadConfig: UploadConfig = {
        getPresignedUrl: async (filename, contentType, category) => {
            const response = await fetch("/api/uploads/presign", {
                method: "POST",
                body: JSON.stringify({ filename, contentType, category }),
            });
            const data = await response.json();
            return {
                uploadUrl: data.upload_url,
                publicUrl: data.file_url,
            };
        },
    };

    return (
        <div>
            <h1>My Editor</h1>
            <RichTextEditor value={content} onChange={setContent} uploadConfig={uploadConfig} />
        </div>
    );
}

API Reference

Props

RichTextEditor / TiptapEditor

| Prop | Type | Default | 설명 | | ------------------------------ | ----------------------------------------------- | ------------------------ | ----------------------------- | | value / content | string | '' | 에디터 내용 (HTML) | | onChange / onContentChange | (content: string) => void | - | 내용 변경 시 콜백 | | placeholder | string | '내용을 입력하세요...' | 플레이스홀더 텍스트 | | editable | boolean | true | 편집 활성화/비활성화 | | minHeight | string | '200px' | 에디터 최소 높이 | | uploadCategory | UploadCategory | 'temp' | S3 업로드 카테고리 | | uploadConfig | UploadConfig | - | S3 업로드 설정 | | initialImages | string[] | [] | 초기 이미지 URL (편집 모드용) | | onUnusedImagesChange | (urls: string[]) => void | - | 사용하지 않는 이미지 콜백 | | onImageUpload | (file: File) => Promise<string \| ImageAttrs> | - | 커스텀 이미지 업로드 핸들러 |

Types

UploadCategory

type UploadCategory = "profile" | "product" | "temp" | "document" | "notice";

UploadConfig

interface UploadConfig {
    getPresignedUrl: (
        filename: string,
        contentType: string,
        category: UploadCategory,
        token?: string
    ) => Promise<{
        uploadUrl: string;
        publicUrl: string;
        headers?: Record<string, string>;
    }>;
    deleteImages?: (
        imageUrls: string[],
        token?: string
    ) => Promise<{
        success: boolean;
        deletedCount: number;
        message?: string;
    }>;
    token?: string;
    convertToCustomDomain?: (s3Url: string) => string;
}

고급 사용법

커스텀 이미지 업로드 핸들러

const customUploadHandler = async (file: File) => {
    // 커스텀 업로드 로직
    const formData = new FormData();
    formData.append("file", file);

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

    const { url } = await response.json();

    return url; // 또는 return { src: url, alt: 'description' }
};

<RichTextEditor value={content} onChange={setContent} onImageUpload={customUploadHandler} />;

사용하지 않는 이미지 추적

const [unusedImages, setUnusedImages] = useState<string[]>([]);

<RichTextEditor value={content} onChange={setContent} initialImages={existingImageUrls} onUnusedImagesChange={setUnusedImages} />;

// 저장 시 사용하지 않는 이미지 삭제
const handleSave = async () => {
    if (unusedImages.length > 0) {
        await deleteImagesFromS3(unusedImages);
    }
    // 내용 저장...
};

읽기 전용 모드

<RichTextEditor value={content} onChange={() => {}} editable={false} />

키보드 단축키

  • 굵게: Ctrl/Cmd + B
  • 기울임: Ctrl/Cmd + I
  • 밑줄: Ctrl/Cmd + U
  • 취소선: Ctrl/Cmd + Shift + X
  • 코드: Ctrl/Cmd + E
  • 실행 취소: Ctrl/Cmd + Z
  • 다시 실행: Ctrl/Cmd + Shift + Z
  • 슬래시 명령어: / 입력하여 명령 메뉴 열기

슬래시 명령어

에디터에서 /를 입력하면 명령 메뉴가 열립니다:

  • / → 제목 1, 2, 3
  • / → 단락
  • / → 글머리 기호 목록
  • / → 번호 매기기 목록
  • / → 인용문
  • / → 코드 블록
  • / → 구분선

백엔드 API 예제

S3 사전 서명 URL 생성을 위한 백엔드 엔드포인트 예제입니다:

// API Route: /api/uploads/presign
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3Client = new S3Client({
    region: "ap-northeast-2",
    credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    },
});

export async function POST(request: Request) {
    const { filename, contentType, category } = await request.json();

    const key = `${category}/${Date.now()}-${filename}`;
    const bucket = "your-bucket-name";

    const command = new PutObjectCommand({
        Bucket: bucket,
        Key: key,
        ContentType: contentType,
    });

    const uploadUrl = await getSignedUrl(s3Client, command, {
        expiresIn: 3600,
    });

    const fileUrl = `https://${bucket}.s3.ap-northeast-2.amazonaws.com/${key}`;

    return Response.json({
        upload_url: uploadUrl,
        file_url: fileUrl,
        key,
        bucket,
    });
}

스타일링

에디터는 기본 스타일이 포함되어 있습니다. 커스터마이징하려면 다음 방법을 사용할 수 있습니다:

  1. CSS 변수 덮어쓰기 (권장):
:root {
    --mesoft-editor-border: #e2e8f0;
    --mesoft-editor-bg: white;
    --mesoft-editor-text: #1e293b;
}
  1. 스타일 가져오기 및 확장:
@import "@mesoft/tiptap-editor/styles.css";

.mesoft-tiptap-wrapper {
    /* 커스텀 스타일 */
}

브라우저 지원

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

버전 관리

이 프로젝트는 Semantic Versioning을 따릅니다. 버전 관리 및 릴리즈 프로세스는 VERSIONING.md를 참조하세요.

변경사항은 CHANGELOG.md에서 확인할 수 있습니다.

라이선스

MIT

기여하기

기여자를 환영합니다! 이슈를 열거나 풀 리퀘스트를 제출해 주세요.

지원

문제나 질문이 있으시면 다음을 방문해 주세요:

  • GitHub Issues: [mesoft/tiptap-editor/issues]
  • 문서: [docs.mesoft.kr]

크레딧

구현 기술: