@depart-os/text-diff
v0.4.1
Published
Inline-tag (1-round) text diff utilities for React (Korean/English) — used by depart-os proposal review flow.
Downloads
226
Maintainers
Readme
@depart-os/text-diff
인라인 태그 기반 1라운드 텍스트 diff 유틸리티. 한국어·영어 텍스트의 변경 부분을 <del>/<ins> 태그로 단일 string 에 마킹하고, React 컴포넌트로 모드별 렌더한다.
Install
pnpm add @depart-os/text-diff
# or: npm install @depart-os/text-diff / yarn add @depart-os/text-diff저장 포맷
원본: "신호일 수 있어요"
수정후: "신호<del>일 수 있어요</del><ins>입니다</ins>"<del>: 원본에 있다가 삭제<ins>: 새로 추가- 태그 밖 : 양쪽 공통
1라운드 전용. 같은 문서를 두 번 이상 누적 수정하는 워크플로우는 지원하지 않는다. 매 수정마다 항상 최초 원본 vs 새 수정본 으로 새 태그 string 을 만든다.
Usage
빌더 측 — 수정 전송 시 태그 string 생성
import { generateTagged } from "@depart-os/text-diff";
async function submitEdit(originalContent: string, newContent: string) {
const tagged = generateTagged(originalContent, newContent);
// tagged === "신호<del>일 수 있어요</del><ins>입니다</ins>"
await api.put("/captions/123", { content: tagged });
}받는 쪽 — 모드별 렌더
import { WordDiff } from "@depart-os/text-diff";
function ReviewPanel({ content }: { content: string }) {
return (
<>
<section>
<h3>변경사항</h3>
<WordDiff content={content} mode="diff" />
</section>
<section>
<h3>수정본</h3>
<WordDiff content={content} mode="after" />
</section>
<section>
<h3>원본</h3>
<WordDiff content={content} mode="before" />
</section>
</>
);
}추가된 단어는 파란 톤(bg-blue-100), 삭제된 단어는 분홍 톤(bg-rose-50)에 취소선으로 표시된다.
양방향 협업 (편집 ↔ 컨펌)
A 측이 수정해서 B 측에 보내고, B 가 다시 수정해서 A 에 보내는 ping-pong 흐름:
import { extractAfter, generateTagged } from "@depart-os/text-diff";
// 받았을 때 — textarea 초기값은 직전 상대 시안의 plain
function startEditing(serverContent: string) {
return extractAfter(serverContent);
}
// 보낼 때 — 새 tagged = 직전 상대 시안 vs 내 새 시안
async function submitMyEdit(serverContent: string, myDraft: string) {
const baseline = extractAfter(serverContent);
const newTagged = generateTagged(baseline, myDraft);
await api.put("/...", { content: newTagged });
}서버는 매번 새 tagged 로 덮어씀. DB 컬럼 1개로 무한 라운드 가능. extractAfter 가 plain 도 그대로 통과시키므로 기존 plain 데이터와도 자동 호환.
Plain text 호환
태그 없는 plain text 를 넘기면 모든 모드에서 그대로 렌더된다 (변경 표시 없음). 기존 데이터와 신규 데이터를 한 컬럼에 섞어 보관해도 안전하다.
Tailwind v4 설정
이 패키지는 Tailwind v3/v4 클래스 문자열을 그대로 출력한다. 소비측 프로젝트의 Tailwind 가 패키지 dist 도 스캔해야 색이 입혀진다.
Tailwind v4 (CSS-first) — globals.css 에서:
@import "tailwindcss";
@source '../../node_modules/@depart-os/text-diff/dist';@source 의 상대 경로는 그 CSS 파일의 위치 기준으로 조정한다.
Tailwind v3 — tailwind.config.js 의 content 에 추가:
export default {
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/@depart-os/text-diff/dist/**/*.{js,mjs,cjs}",
],
};API
generateTagged(before: string, after: string): string
LCS 기반 워드 단위 diff 를 계산하고 <del>/<ins> 태그로 직렬화한 단일 string 을 반환. 한글/영문/공백/구두점을 각각의 토큰으로 분리.
extractBefore(content: string): string
tagged string 에서 "직전 baseline" plain text 를 추출. <ins> 블록은 제거하고 <del> 내용만 살림. plain text 입력은 no-op 으로 그대로 반환.
extractBefore("신호<del>일 수 있어요</del><ins>입니다</ins>");
// → '신호일 수 있어요'extractAfter(content: string): string
tagged string 에서 "최신 시안" plain text 를 추출. <del> 블록은 제거하고 <ins> 내용만 살림. plain text 입력은 no-op 으로 그대로 반환.
extractAfter("신호<del>일 수 있어요</del><ins>입니다</ins>");
// → '신호입니다'라운드트립 보장: extractBefore(generateTagged(a, b)) === a && extractAfter(generateTagged(a, b)) === b.
<WordDiff content mode className? />
태그 문자열을 파싱해서 mode 별로 렌더.
| mode | 동작 |
| ---------- | --------------------------------------------------------- |
| 'diff' | <del> 분홍+취소선, <ins> 파란 강조, eq 일반 |
| 'after' | <del> 제외, <ins> 내용만 살림 → 수정 후 평문처럼 보임 |
| 'before' | <ins> 제외, <del> 내용만 살림 → 수정 전 평문처럼 보임 |
className 은 컨테이너 <p> 에 추가된다.
인라인 서식 (굵게·기울임·밑줄)
v0.3.0 부터 인라인 서식 3종을 diff 와 공존시켜 저장·렌더할 수 있다.
- 서식은
<strong>/<em>/<u>태그로 문자열에 저장된다. - 중첩 규칙: diff 태그(
<del>/<ins>)가 바깥, 서식 태그가 안쪽. 예:<ins><strong>x</strong></ins> - 서식만 바뀐 단어도 변경으로 취급되어
<del>(이전 서식) +<ins>(새 서식)으로 표시된다. - 서식 태그가 없는 문자열은 v0.2.0 과 완전히 동일하게 동작한다 (마이그레이션 불필요).
- 태그 외 텍스트의
& < >는 HTML 엔티티로 이스케이프되어 저장되고, 렌더 시 안전하게 복원된다.
import { InlineEditor, WordDiff } from '@depart-os/text-diff'
// 편집: contentEditable + 굵게·기울임·밑줄 툴바
<InlineEditor value={value} onChange={setValue} />
// 표시: 서식이 diff 하이라이트 안에 중첩 렌더됨
<WordDiff content={saved} mode="diff" /><InlineEditor value onChange className? placeholder? marks? />
contentEditable 기반 인라인 에디터. 툴바 버튼(굵게·기울임·밑줄)으로 서식을 토글하고, onChange 로 정규화된 태그 문자열을 돌려준다. 붙여넣기는 평문으로만 삽입된다. marks 로 노출할 버튼을 제한할 수 있다 (기본 ['b','i','u']).
domToTagged(root: HTMLElement): string
편집 영역 DOM 을 정규 태그 문자열로 직렬화. <b>/<i>/인라인 스타일(bold·italic·underline)을 <strong>/<em>/<u> 로 정규화하고 텍스트를 이스케이프한다.
taggedToHtml(content: string): string
저장 문자열(잔여 <del>/<ins> 포함 가능)을 after 기준의 편집용 innerHTML 로 변환.
Sanitization
<del>/<ins> 는 HTML5 표준 시맨틱 태그라 DOMPurify 등 대부분의 sanitizer 기본 whitelist 에 포함된다. 통상 별도 작업 없이 통과한다. 보수적인 환경이면 운영 적용 전 PUT → GET 라운드트립을 한 번 확인.
License
MIT
