legal-doc-editor 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -1
- package/dist/LegalEditor.d.ts +7 -1
- package/dist/amount.d.ts +9 -0
- package/dist/citation.d.ts +33 -0
- package/dist/fees.d.ts +58 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +364 -120
- package/dist/style.css +1 -1
- package/dist/templates.d.ts +7 -0
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# legal-doc-editor
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/legal-doc-editor) [](https://github.com/hwondev/legal-doc-editor/actions/workflows/ci.yml) [](./LICENSE)
|
|
4
|
+
|
|
3
5
|
계약서·합의서 같은 **법률문서를 만드는 React 에디터**입니다.
|
|
4
6
|
|
|
5
7
|
- **조·항·호 자동 번호**: 조를 넣거나 옮기면 제1조, ①, 1. 번호가 알아서 다시 매겨져요
|
|
@@ -7,6 +9,9 @@
|
|
|
7
9
|
- **한글(.hwp·.hwpx)·Word(.docx) 열기·저장**: 일반 문단으로 쓴 계약서도 "제N조", "①", "1." 패턴을 인식해서 조·항·호 구조로 바꿔요
|
|
8
10
|
- 저장은 `.docx`와 `.hwpx`예요. `.hwpx`는 한글 2014 이상에서 열리고, 한글에서 `.hwp`로 다시 저장할 수 있어요
|
|
9
11
|
- 파일 변환 라이브러리는 필요할 때만 불러와서 에디터 본체 번들은 가벼워요
|
|
12
|
+
- **판례·법령 인용 링크**: 본문의 `민법 제750조`, `대법원 2016. 4. 28. 선고 2015다12345 판결`, `2024가단157033` 같은 인용을 알아보고 [국가법령정보센터](https://www.law.go.kr) 링크를 붙여요 (API 키가 필요 없어요)
|
|
13
|
+
- **문서 템플릿**: 소장(대여금)·고소장(사기)·내용증명·지급명령 신청서를 `{{변수}}` 빈칸이 들어간 상태로 바로 시작해요
|
|
14
|
+
- **금액 표기**: 선택한 숫자를 `금 37,200,000원`이나 `금 삼천칠백이십만 원정(₩37,200,000)`으로 바꿔요
|
|
10
15
|
- **인쇄·PDF**: 비어 있는 변수는 손으로 쓰는 밑줄 칸으로 인쇄돼요
|
|
11
16
|
|
|
12
17
|
에디터 엔진은 [Tiptap](https://tiptap.dev)(ProseMirror)을, 파일 변환은 [docx](https://github.com/dolanmiu/docx)·[mammoth](https://github.com/mwilliamson/mammoth.js)·[hwp-convert](https://www.npmjs.com/package/hwp-convert)를 씁니다.
|
|
@@ -48,11 +53,71 @@ export default function Page() {
|
|
|
48
53
|
| `ol ol > li` | 호 | 1., 2. |
|
|
49
54
|
| `span[data-var]` 또는 `{{이름}}` | 변수 | 입력값 |
|
|
50
55
|
|
|
56
|
+
### 판례·법령 인용
|
|
57
|
+
|
|
58
|
+
본문에 적힌 인용을 알아보고 밑줄을 그어요. 마우스를 올리면 주소가 보이고, Ctrl(⌘)+클릭하면 국가법령정보센터에서 열려요 (읽기 전용일 때는 그냥 클릭).
|
|
59
|
+
|
|
60
|
+
| 형식 | 예 | 링크 |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| 판결·결정 | `대법원 2016. 4. 28. 선고 2015다12345 판결`, `대법원 2020. 1. 9.자 2019마123 결정` | 판례 검색 |
|
|
63
|
+
| 사건번호 | `2015다12345`, `2018노1234`, `2024가단157033` | 판례 검색 |
|
|
64
|
+
| 법령 조문 | `민법 제750조`, `형법 제347조 제1항`, `특정경제범죄 가중처벌 등에 관한 법률 제3조` | 해당 조문 |
|
|
65
|
+
|
|
66
|
+
**문서 데이터는 바뀌지 않아요.** 화면에만 덧입히는 표시라서 `onChange` HTML, `toPlainHtml`, `.docx`·`.hwpx` 저장 결과는 인용이 없을 때와 똑같아요. 인쇄할 때도 밑줄이 빠져요.
|
|
67
|
+
|
|
68
|
+
날짜(`2026. 9. 2.`), 전화번호, 계좌번호는 인용으로 보지 않아요. 다만 인용 인식은 글자 모양만 보는 것이라 놓치거나 잘못 잡을 수 있고, 링크가 가리키는 조문이 실제로 맞는지는 직접 확인해 주세요.
|
|
69
|
+
|
|
70
|
+
### 판례 검색
|
|
71
|
+
|
|
72
|
+
`searchCases`에 검색 함수를 넘기면 오른쪽 패널에 **판례 검색** 칸이 생겨요. 결과를 누르면 커서 위치에 `대법원 2016. 4. 28. 선고 2015다12345 판결` 같은 인용 문구가 들어가고, 인용 링크도 자동으로 붙어요.
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<LegalEditor searchCases={async (q) => (await fetch(`/api/cases?q=${encodeURIComponent(q)}`)).json()} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
라이브러리는 검색을 직접 부르지 않아요. 국가법령정보 공동활용 API는 발급받은 인증값이 필요해서 서버를 거쳐야 하거든요. 연결 예제는 [`examples/law-go-kr-proxy`](./examples/law-go-kr-proxy)에 있어요.
|
|
79
|
+
|
|
80
|
+
### 인지액·송달료 자동 계산
|
|
81
|
+
|
|
82
|
+
`<LegalEditor autoFees />`로 켜면, 입력값 `소가`(또는 `소송목적의 값`)를 넣었을 때 이름이 `인지액`·`송달료`로 시작하는 변수(예: `{{인지액 산정 필요}}`)를 계산해서 채워요. 직접 입력한 값이 있으면 그 값을 써요.
|
|
83
|
+
지급명령 신청서(`payment-order` 템플릿)는 `청구금액`을 넣으면 `{{독촉절차 인지대}}`·`{{독촉절차 송달료}}`·`{{독촉절차비용}}`(두 금액 합계)을 채워요.
|
|
84
|
+
|
|
85
|
+
| 항목 | 기준 | 근거 |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| 인지액 | 1천만원 미만 × 0.5% / 1억원 미만 × 0.45% + 5천원 / 10억원 미만 × 0.4% + 5만5천원 / 그 이상 × 0.35% + 55만5천원. 100원 미만 버림, 최저 1천원 | 「민사소송 등 인지법」 제2조 |
|
|
88
|
+
| 전자소송 인지액 | 위 계산식 × 0.9 후 100원 미만 버림, 최저 900원 — `autoFees={{ electronic: true }}` | 인지법 제16조, [전자소송포털](https://ecfs.scourt.go.kr/psp/link.on?m=PSP007P01) |
|
|
89
|
+
| 송달료 | 당사자 수 × 회분 × 1회 송달료. 소가 3천만원 이하(소액) 10회분, 넘으면 15회분. 당사자 수 기본 2 — `autoFees={{ parties: 3 }}` | 「송달료규칙의 시행에 따른 업무처리요령」 별표 1, 「소액사건심판규칙」 제1조의2 |
|
|
90
|
+
| 지급명령 인지대 | 소장 인지액 계산식 × 1/10 후 같은 끝자리 처리 (예: 청구금액 300만원 → 1,500원). **전자신청 감액은 아직 반영하지 않음** | 인지법 제7조제2항·제4항 |
|
|
91
|
+
| 지급명령 송달료 | 당사자 수 × 6회분 × 1회 송달료 | 업무처리요령 별표 1 |
|
|
92
|
+
| 1회 송달료 | 5,500원 (2025. 6. 1.부터). 바뀌면 `autoFees={{ unitFee: 5600 }}`처럼 덮어쓰기 | 법원 공지 |
|
|
93
|
+
|
|
94
|
+
> 계산 결과는 **참고용**이에요. 제1심 소장 기준이고 항소·상고·반소는 계산하지 않아요. 실제 납부액은 법원 안내를 확인하세요.
|
|
95
|
+
|
|
96
|
+
### 금액 표기
|
|
97
|
+
|
|
98
|
+
본문에서 금액을 선택하고 툴바의 **금액**·**금액(한글)** 버튼을 누르면 선택한 글의 숫자만 뽑아 바꿔요.
|
|
99
|
+
|
|
100
|
+
| 서식 | 예 |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| 소장식 | `금 37,200,000원` |
|
|
103
|
+
| 계약서식 | `금 삼천칠백이십만 원정(₩37,200,000)` |
|
|
104
|
+
|
|
105
|
+
한글 금액은 만 단위로 끊고, 고쳐 쓰기 어렵도록 **"일"을 빼지 않고** 적어요 (`10,000` → 일만, `11` → 일십일, `410,000,000` → 사억일천만).
|
|
106
|
+
|
|
51
107
|
## API
|
|
52
108
|
|
|
53
109
|
| 이름 | 설명 |
|
|
54
110
|
| --- | --- |
|
|
55
|
-
| `<LegalEditor content values onChange onValuesChange editable />` | 에디터 컴포넌트 |
|
|
111
|
+
| `<LegalEditor content values onChange onValuesChange editable autoFees searchCases />` | 에디터 컴포넌트 |
|
|
112
|
+
| `formatCaseCitation(result)` | 판례 검색 결과 → `대법원 2016. 4. 28. 선고 2015다12345 판결` |
|
|
113
|
+
| `calcStampFee(소가, { electronic })` | 소장 인지액(원) |
|
|
114
|
+
| `calcPaymentOrderStampFee(청구금액)` | 지급명령 신청서 인지대(원) — 종이 신청 기준 |
|
|
115
|
+
| `calcServiceFee({ parties, procedure, unitFee })` | 송달료(원). `procedure`: 소액·단독·합의·항소·상고·조정·독촉 |
|
|
116
|
+
| `withCourtFees(names, values, options)` | 비어 있는 `인지액…`·`송달료…` 변수를 계산값으로 채운 입력값 |
|
|
117
|
+
| `templates` | 기본 문서 템플릿 `{ id, title, description, html }[]` — `<LegalEditor content={templates[0].html} />` |
|
|
118
|
+
| `toKoreanAmount(n)` | `37200000` → `삼천칠백이십만` |
|
|
119
|
+
| `formatAmount(n, '소장' \| '계약서')` | `금 37,200,000원` / `금 삼천칠백이십만 원정(₩37,200,000)` |
|
|
120
|
+
| `parseAmount(text)` | 글에서 숫자만 뽑기 (`금 37,200,000원` → `37200000`) |
|
|
56
121
|
| `fillTemplate(html, values)` | 변수를 채운 완성본 HTML. `.legal-doc` 안에서 렌더하면 번호가 붙어요 |
|
|
57
122
|
| `toDocx(editor.getJSON(), values)` | `.docx` Blob 생성 |
|
|
58
123
|
| `toHwpx(editor.getJSON(), values)` | `.hwpx` Blob 생성 |
|
|
@@ -61,6 +126,8 @@ export default function Page() {
|
|
|
61
126
|
| `normalizeLegalHtml(html)` | "제N조 / ① / 1." 문단을 조·항·호 구조로 변환 |
|
|
62
127
|
| `Variable` | 다른 Tiptap 에디터에 넣어 쓸 수 있는 변수 확장 |
|
|
63
128
|
| `Numbering` | 번호 문단 확장 (`setNumbering(1~4 \| null)`, Tab·Shift+Tab 단계 변경, 맨 앞 Backspace 해제) |
|
|
129
|
+
| `findCitations(text)` | 글에서 판례·법령 인용 찾기 → `{ type: 'case' \| 'law', text, from, to, url }[]` |
|
|
130
|
+
| `CitationLink` | 인용에 밑줄·링크를 덧입히는 확장 (`LegalEditor`에는 이미 들어 있음) |
|
|
64
131
|
|
|
65
132
|
## 로드맵
|
|
66
133
|
|
|
@@ -78,6 +145,10 @@ npm run dev # 데모: http://localhost:5173
|
|
|
78
145
|
npm run build # dist/
|
|
79
146
|
```
|
|
80
147
|
|
|
148
|
+
## 기여
|
|
149
|
+
|
|
150
|
+
[기여 안내](./CONTRIBUTING.md) · [변경 기록](./CHANGELOG.md) · [보안 정책](./SECURITY.md)
|
|
151
|
+
|
|
81
152
|
## 라이선스
|
|
82
153
|
|
|
83
154
|
MIT
|
package/dist/LegalEditor.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Values } from './variable';
|
|
2
|
+
import { type CaseResult } from './citation';
|
|
3
|
+
import { type CourtFeeOptions } from './fees';
|
|
2
4
|
import './legal.css';
|
|
3
5
|
export interface LegalEditorProps {
|
|
4
6
|
/** 템플릿 HTML. {{변수}} 텍스트는 변수로 바뀜 */
|
|
@@ -8,5 +10,9 @@ export interface LegalEditorProps {
|
|
|
8
10
|
onChange?: (html: string) => void;
|
|
9
11
|
onValuesChange?: (values: Values) => void;
|
|
10
12
|
editable?: boolean;
|
|
13
|
+
/** 입력값에 `소가`(소장)나 `청구금액`(지급명령)이 있으면 인지액·송달료 변수를 계산해 채움 (직접 입력한 값이 우선, 참고용) */
|
|
14
|
+
autoFees?: boolean | CourtFeeOptions;
|
|
15
|
+
/** 판례 검색 함수. 넘기면 오른쪽에 판례 검색 칸이 생기고, 결과를 누르면 인용 문구가 커서 위치에 들어감 */
|
|
16
|
+
searchCases?: (query: string) => Promise<CaseResult[]>;
|
|
11
17
|
}
|
|
12
|
-
export declare function LegalEditor({ content, values: initial, onChange, onValuesChange, editable }: LegalEditorProps): import("react").JSX.Element;
|
|
18
|
+
export declare function LegalEditor({ content, values: initial, onChange, onValuesChange, editable, autoFees, searchCases }: LegalEditorProps): import("react").JSX.Element;
|
package/dist/amount.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** 37200000 → "삼천칠백이십만", 410000000 → "사억일천만", 0 → "영" */
|
|
2
|
+
export declare function toKoreanAmount(n: number): string;
|
|
3
|
+
/** "금 37,200,000원" · "37200000" → 37200000 (숫자가 없으면 0) */
|
|
4
|
+
export declare const parseAmount: (text: string) => number;
|
|
5
|
+
/**
|
|
6
|
+
* 소장식: 금 37,200,000원
|
|
7
|
+
* 계약서식: 금 삼천칠백이십만 원정(₩37,200,000)
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatAmount(n: number, style?: '소장' | '계약서'): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
export interface Citation {
|
|
3
|
+
type: 'case' | 'law';
|
|
4
|
+
/** 본문에 적힌 그대로 */
|
|
5
|
+
text: string;
|
|
6
|
+
/** 넘긴 문자열 안에서의 위치 */
|
|
7
|
+
from: number;
|
|
8
|
+
to: number;
|
|
9
|
+
/** 국가법령정보센터 주소 */
|
|
10
|
+
url: string;
|
|
11
|
+
}
|
|
12
|
+
/** 본문 글에서 판례·법령 인용을 찾음. 문서를 바꾸지 않고 위치만 알려줌 */
|
|
13
|
+
export declare function findCitations(text: string): Citation[];
|
|
14
|
+
/** 판례 검색 결과 한 건 — LegalEditor의 `searchCases`가 돌려주는 모양 */
|
|
15
|
+
export interface CaseResult {
|
|
16
|
+
/** 법원명: 대법원, 서울고등법원 … */
|
|
17
|
+
court: string;
|
|
18
|
+
/** 선고일자: "20160428", "2016-04-28", "2016.04.28" 모두 받음 */
|
|
19
|
+
date: string;
|
|
20
|
+
/** 사건번호: 2015다12345 */
|
|
21
|
+
caseNo: string;
|
|
22
|
+
/** 사건명 */
|
|
23
|
+
title: string;
|
|
24
|
+
/** 판결·결정·명령 (없으면 판결) */
|
|
25
|
+
kind?: string;
|
|
26
|
+
summary?: string;
|
|
27
|
+
/** 원문 보기 주소 */
|
|
28
|
+
url: string;
|
|
29
|
+
}
|
|
30
|
+
/** 검색 결과 → 본문 인용 문구: "대법원 2016. 4. 28. 선고 2015다12345 판결", 결정은 "대법원 2020. 1. 9.자 2019마123 결정" */
|
|
31
|
+
export declare function formatCaseCitation({ court, date, caseNo, kind }: CaseResult): string;
|
|
32
|
+
/** 판례·법령 인용에 밑줄과 국가법령정보센터 링크를 덧입힘. 문서 데이터(HTML·JSON)는 그대로 */
|
|
33
|
+
export declare const CitationLink: Extension<any, any>;
|
package/dist/fees.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Values } from './variable';
|
|
2
|
+
/**
|
|
3
|
+
* 소장 인지액. 「민사소송 등 인지법」 제2조(소장)·제16조(전자소송 10분의 9)
|
|
4
|
+
* 출처: https://www.law.go.kr/법령/민사소송 등 인지법 (시행 2025. 3. 1., 확인 2026-09-15)
|
|
5
|
+
* 전자소송 끝자리 처리: 대법원 전자소송포털 '인지액 계산방법' https://ecfs.scourt.go.kr/psp/link.on?m=PSP007P01 (확인 2026-09-15)
|
|
6
|
+
* (소가 × 요율 + 정액) × 0.9를 먼저 구한 뒤 100원 미만 버림, 1천원 미만이면 900원 — 예: 5천만원 → 207,000원
|
|
7
|
+
*/
|
|
8
|
+
export declare function calcStampFee(amount: number, opts?: {
|
|
9
|
+
electronic?: boolean;
|
|
10
|
+
}): number;
|
|
11
|
+
/**
|
|
12
|
+
* 지급명령 신청서 인지대. 「민사소송 등 인지법」 제7조제2항(제2조 금액의 10분의 1)·제4항(제2조제2항 준용)
|
|
13
|
+
* 예: 청구금액 3,000,000원 → (3,000,000 × 0.005) × 0.1 = 1,500원
|
|
14
|
+
* 출처: https://easylaw.go.kr/CSP/CnpClsMain.laf?popMenu=ov&csmSeq=568&ccfNo=3&cciNo=3&cnpClsNo=3 (확인 2026-09-15)
|
|
15
|
+
* ponytail: 종이 신청 기준. 전자신청 10분의 9 감액과 끝자리 처리 순서는 공식 안내로 확인하지 못해 아직 넣지 않음
|
|
16
|
+
*/
|
|
17
|
+
export declare function calcPaymentOrderStampFee(amount: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* 송달료 회분. 「송달료규칙의 시행에 따른 업무처리요령」(재판예규 재일 87-4) 별표 1
|
|
20
|
+
* 출처: https://www.easylaw.go.kr/CSP/CnpClsMain.laf?csmSeq=568&ccfNo=2&cciNo=4&cnpClsNo=3 (확인 2026-09-15)
|
|
21
|
+
* 독촉(지급명령) 6회분: 생활법령정보 지급명령 신청서 작성 (위 calcPaymentOrderStampFee 출처와 같음)
|
|
22
|
+
*/
|
|
23
|
+
export declare const SERVICE_ROUNDS: {
|
|
24
|
+
readonly 소액: 10;
|
|
25
|
+
readonly 단독: 15;
|
|
26
|
+
readonly 합의: 15;
|
|
27
|
+
readonly 항소: 12;
|
|
28
|
+
readonly 상고: 8;
|
|
29
|
+
readonly 조정: 5;
|
|
30
|
+
readonly 독촉: 6;
|
|
31
|
+
};
|
|
32
|
+
export type Procedure = keyof typeof SERVICE_ROUNDS;
|
|
33
|
+
/**
|
|
34
|
+
* 1회 송달료(원). 2025. 6. 1.부터 5,500원 — 별표 1 사건 전체 (확인 2026-09-15)
|
|
35
|
+
* 출처: 법원 공지 https://www.scourt.go.kr/portal/dcboard/DcNewsViewAction.work?seqnum=17688&gubun=41&cbub_code=000420
|
|
36
|
+
* ponytail: 찾기쉬운 생활법령정보는 5,640원으로 적고 있어 공식 공지와 다름 — 새 공지가 나오면 이 값만 바꾸거나 `unitFee`로 덮어씀
|
|
37
|
+
*/
|
|
38
|
+
export declare const SERVICE_UNIT_FEE = 5500;
|
|
39
|
+
export declare function calcServiceFee({ parties, procedure, unitFee }: {
|
|
40
|
+
parties: number;
|
|
41
|
+
procedure: Procedure;
|
|
42
|
+
unitFee?: number;
|
|
43
|
+
}): number;
|
|
44
|
+
/** 소액사건: 소가 3천만원 이하 (소액사건심판규칙 제1조의2) */
|
|
45
|
+
export declare const SMALL_CLAIM_LIMIT = 30000000;
|
|
46
|
+
export interface CourtFeeOptions {
|
|
47
|
+
/** 전자소송이면 인지액 10분의 9 */
|
|
48
|
+
electronic?: boolean;
|
|
49
|
+
/** 원고 + 피고 수 (기본 2) */
|
|
50
|
+
parties?: number;
|
|
51
|
+
unitFee?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 비어 있는 비용 변수를 계산값으로 채움. 직접 입력한 값은 덮어쓰지 않음.
|
|
55
|
+
* - 소장: `소가`(또는 `소송목적의 값`) → 이름이 `인지액`·`송달료`로 시작하는 변수 (소가 3천만원 이하 소액 10회분, 넘으면 15회분)
|
|
56
|
+
* - 지급명령: `청구금액` → `독촉절차 인지대`(소장의 10분의 1)·`독촉절차 송달료`(6회분)·`독촉절차비용`(두 금액 합계)
|
|
57
|
+
*/
|
|
58
|
+
export declare function withCourtFees(names: string[], values: Values, opts?: CourtFeeOptions): Values;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,12 @@ export { LegalEditor } from './LegalEditor';
|
|
|
2
2
|
export type { LegalEditorProps } from './LegalEditor';
|
|
3
3
|
export { Variable, fillTemplate, toChips } from './variable';
|
|
4
4
|
export { Numbering } from './numbering';
|
|
5
|
+
export { CitationLink, findCitations, formatCaseCitation } from './citation';
|
|
6
|
+
export type { Citation, CaseResult } from './citation';
|
|
7
|
+
export { calcStampFee, calcPaymentOrderStampFee, calcServiceFee, withCourtFees, SERVICE_ROUNDS, SERVICE_UNIT_FEE, SMALL_CLAIM_LIMIT } from './fees';
|
|
8
|
+
export type { CourtFeeOptions, Procedure } from './fees';
|
|
9
|
+
export { toKoreanAmount, formatAmount, parseAmount } from './amount';
|
|
10
|
+
export { templates } from './templates';
|
|
11
|
+
export type { Template } from './templates';
|
|
5
12
|
export type { Values } from './variable';
|
|
6
13
|
export { toDocx, toHwpx, toPlainHtml, fromDocx, fromHwp, fromFile, normalizeLegalHtml } from './io';
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,11 @@ import { EditorContent as n, useEditor as r } from "@tiptap/react";
|
|
|
3
3
|
import i from "@tiptap/starter-kit";
|
|
4
4
|
import { TableKit as a } from "@tiptap/extension-table";
|
|
5
5
|
import { Extension as o, Node as s, nodeInputRule as c, nodePasteRule as l } from "@tiptap/core";
|
|
6
|
-
import {
|
|
6
|
+
import { Plugin as u, PluginKey as d } from "@tiptap/pm/state";
|
|
7
|
+
import { Decoration as f, DecorationSet as p } from "@tiptap/pm/view";
|
|
8
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
7
9
|
//#region src/variable.ts
|
|
8
|
-
var
|
|
10
|
+
var ee = s.create({
|
|
9
11
|
name: "variable",
|
|
10
12
|
group: "inline",
|
|
11
13
|
inline: !0,
|
|
@@ -59,9 +61,9 @@ var f = s.create({
|
|
|
59
61
|
getAttributes: (e) => ({ name: e[0].slice(2, -2).trim() })
|
|
60
62
|
})];
|
|
61
63
|
}
|
|
62
|
-
}),
|
|
63
|
-
function
|
|
64
|
-
let n = new DOMParser().parseFromString(
|
|
64
|
+
}), g = (e) => e.replace(/\{\{([^{}<>"]+)\}\}/g, (e, t) => `<span data-var="${t.trim().replace(/&/g, "&")}"></span>`);
|
|
65
|
+
function _(e, t) {
|
|
66
|
+
let n = new DOMParser().parseFromString(g(e), "text/html");
|
|
65
67
|
return n.querySelectorAll("span[data-var]").forEach((e) => {
|
|
66
68
|
let n = e.getAttribute("data-var");
|
|
67
69
|
e.replaceWith(t[n] || `[${n}]`);
|
|
@@ -69,7 +71,7 @@ function m(e, t) {
|
|
|
69
71
|
}
|
|
70
72
|
//#endregion
|
|
71
73
|
//#region src/numbering.ts
|
|
72
|
-
var
|
|
74
|
+
var v = 4, y = o.create({
|
|
73
75
|
name: "numbering",
|
|
74
76
|
priority: 1e3,
|
|
75
77
|
addGlobalAttributes() {
|
|
@@ -79,7 +81,7 @@ var h = 4, g = o.create({
|
|
|
79
81
|
default: null,
|
|
80
82
|
parseHTML: (e) => {
|
|
81
83
|
let t = Number(e.getAttribute("data-num"));
|
|
82
|
-
return t >= 1 && t <=
|
|
84
|
+
return t >= 1 && t <= v ? t : null;
|
|
83
85
|
},
|
|
84
86
|
renderHTML: (e) => e.num ? { "data-num": e.num } : {}
|
|
85
87
|
} }
|
|
@@ -91,7 +93,7 @@ var h = 4, g = o.create({
|
|
|
91
93
|
addKeyboardShortcuts() {
|
|
92
94
|
let e = () => this.editor.getAttributes("paragraph").num, t = (t) => () => {
|
|
93
95
|
let n = e();
|
|
94
|
-
return !!n && this.editor.commands.setNumbering(Math.min(
|
|
96
|
+
return !!n && this.editor.commands.setNumbering(Math.min(v, Math.max(1, n + t)));
|
|
95
97
|
};
|
|
96
98
|
return {
|
|
97
99
|
Tab: t(1),
|
|
@@ -103,17 +105,153 @@ var h = 4, g = o.create({
|
|
|
103
105
|
Enter: () => !!e() && this.editor.state.selection.$from.parent.content.size === 0 && this.editor.commands.setNumbering(null)
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
|
-
}),
|
|
108
|
+
}), b = String.raw`(?<!\d)\d{4}(?![만억천년월일원])[가-힣]{1,3}\d{1,6}(?!\d)`, x = String.raw`(?:[가-힣]{0,6}법원(?:\s*[가-힣]{2,6}지원)?|헌법재판소)`, S = String.raw`\d{4}\.\s*\d{1,2}\.\s*\d{1,2}\.`, C = String.raw`${x}\s*${S}\s*(?:선고|자)\s*(?<caseNo>${b})(?:\s*[,·]\s*${b})*\s*(?:전원합의체\s*)?(?:판결|결정|명령)`, w = String.raw`(?:[가-힣]+(?<![은는이가을를와과로며고여하한된])\s+){0,4}[가-힣]+에\s*관한\s*[가-힣]*법률?|[가-힣]+법률?`, T = String.raw`(?<lawName>${w})\s*제\s*(?<jo>\d+)\s*조(?:\s*의\s*(?<ui>\d+))?(?:\s*제\s*\d+\s*(?:항|호))*`, E = new RegExp(String.raw`${C}|${T}|(?<bare>${b})`, "g"), D = /^[방불합위적탈편문어용수요기비사입공]법$/, O = (...e) => `https://www.law.go.kr/${e.map(encodeURIComponent).join("/")}`;
|
|
109
|
+
function k(e) {
|
|
110
|
+
let t = [];
|
|
111
|
+
for (let n of e.matchAll(E)) {
|
|
112
|
+
let e = n.groups, r = e.caseNo ?? e.bare;
|
|
113
|
+
if (!r && D.test(e.lawName)) continue;
|
|
114
|
+
let i = n.index ?? 0;
|
|
115
|
+
t.push({
|
|
116
|
+
type: r ? "case" : "law",
|
|
117
|
+
text: n[0],
|
|
118
|
+
from: i,
|
|
119
|
+
to: i + n[0].length,
|
|
120
|
+
url: r ? `https://www.law.go.kr/LSW/precSc.do?query=${encodeURIComponent(r)}` : O("법령", e.lawName.replace(/\s+/g, " "), `제${e.jo}조${e.ui ? `의${e.ui}` : ""}`)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return t;
|
|
124
|
+
}
|
|
125
|
+
function A({ court: e, date: t, caseNo: n, kind: r = "" }) {
|
|
126
|
+
let i = t.match(/(\d{4})\D*(\d{1,2})\D*(\d{1,2})/), a = i ? `${i[1]}. ${+i[2]}. ${+i[3]}.` : t, o = /결정/.test(r) ? "결정" : /명령/.test(r) ? "명령" : "판결";
|
|
127
|
+
return o === "판결" ? `${e} ${a} 선고 ${n} 판결` : `${e} ${a}자 ${n} ${o}`;
|
|
128
|
+
}
|
|
129
|
+
var j = new d("citation"), M = (e) => {
|
|
130
|
+
let t = [];
|
|
131
|
+
return e.descendants((e, n) => {
|
|
132
|
+
if (e.isText) for (let r of k(e.text)) t.push(f.inline(n + r.from, n + r.to, {
|
|
133
|
+
nodeName: "a",
|
|
134
|
+
class: "le-cite",
|
|
135
|
+
href: r.url,
|
|
136
|
+
title: r.url,
|
|
137
|
+
target: "_blank",
|
|
138
|
+
rel: "noopener noreferrer"
|
|
139
|
+
}));
|
|
140
|
+
}), p.create(e, t);
|
|
141
|
+
}, te = o.create({
|
|
142
|
+
name: "citationLink",
|
|
143
|
+
addProseMirrorPlugins() {
|
|
144
|
+
return [new u({
|
|
145
|
+
key: j,
|
|
146
|
+
state: {
|
|
147
|
+
init: (e, { doc: t }) => M(t),
|
|
148
|
+
apply: (e, t) => e.docChanged ? M(e.doc) : t
|
|
149
|
+
},
|
|
150
|
+
props: {
|
|
151
|
+
decorations: (e) => j.getState(e),
|
|
152
|
+
handleClick: (e, t, n) => {
|
|
153
|
+
let r = n.target.closest?.("a.le-cite");
|
|
154
|
+
return !r || e.editable && !n.ctrlKey && !n.metaKey ? !1 : (window.open(r.getAttribute("href"), "_blank", "noopener,noreferrer"), !0);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})];
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/fees.ts
|
|
162
|
+
function N(e, t = {}) {
|
|
163
|
+
if (!(e > 0)) return 0;
|
|
164
|
+
let n = P(e);
|
|
165
|
+
return t.electronic ? n < 1e7 ? 900 : Math.floor(n * 9 / 1e7) * 100 : n < 1e7 ? 1e3 : Math.floor(n / 1e6) * 100;
|
|
166
|
+
}
|
|
167
|
+
function P(e) {
|
|
168
|
+
let [t, n] = e < 1e7 ? [50, 0] : e < 1e8 ? [45, 5e3] : e < 1e9 ? [40, 55e3] : [35, 555e3];
|
|
169
|
+
return e * t + n * 1e4;
|
|
170
|
+
}
|
|
171
|
+
function F(e) {
|
|
172
|
+
if (!(e > 0)) return 0;
|
|
173
|
+
let t = P(e);
|
|
174
|
+
return t < 1e8 ? 1e3 : Math.floor(t / 1e7) * 100;
|
|
175
|
+
}
|
|
176
|
+
var I = {
|
|
177
|
+
소액: 10,
|
|
178
|
+
단독: 15,
|
|
179
|
+
합의: 15,
|
|
180
|
+
항소: 12,
|
|
181
|
+
상고: 8,
|
|
182
|
+
조정: 5,
|
|
183
|
+
독촉: 6
|
|
184
|
+
}, L = 5500;
|
|
185
|
+
function R({ parties: e, procedure: t, unitFee: n = L }) {
|
|
186
|
+
return e * I[t] * n;
|
|
187
|
+
}
|
|
188
|
+
var ne = 3e7, re = (e) => `${e.toLocaleString("ko-KR")}원`, ie = (e = "") => Number(e.replace(/[^\d]/g, "")) || 0;
|
|
189
|
+
function ae(e, t, n = {}) {
|
|
190
|
+
let r = (n) => ie(t[e.find((e) => n.test(e)) ?? ""]), i = { ...t }, a = (n, r) => {
|
|
191
|
+
for (let a of e) n.test(a) && !t[a]?.trim() && (i[a] = re(r));
|
|
192
|
+
}, o = n.parties ?? 2, s = r(/^(소가|소송목적의\s*값)/);
|
|
193
|
+
s && (a(/^인지(액|대)/, N(s, n)), a(/^송달료/, R({
|
|
194
|
+
parties: o,
|
|
195
|
+
procedure: s <= 3e7 ? "소액" : "단독",
|
|
196
|
+
unitFee: n.unitFee
|
|
197
|
+
})));
|
|
198
|
+
let c = r(/^청구금액/);
|
|
199
|
+
if (c && e.some((e) => /^독촉절차/.test(e))) {
|
|
200
|
+
let e = /^독촉절차\s*인지(액|대)/, t = /^독촉절차\s*송달료/, i = F(c), s = R({
|
|
201
|
+
parties: o,
|
|
202
|
+
procedure: "독촉",
|
|
203
|
+
unitFee: n.unitFee
|
|
204
|
+
});
|
|
205
|
+
a(e, i), a(t, s), a(/^독촉절차\s*비용/, (r(e) || i) + (r(t) || s));
|
|
206
|
+
}
|
|
207
|
+
return s || c ? i : t;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/amount.ts
|
|
211
|
+
var oe = "영일이삼사오육칠팔구", se = [
|
|
212
|
+
"천",
|
|
213
|
+
"백",
|
|
214
|
+
"십",
|
|
215
|
+
""
|
|
216
|
+
], ce = [
|
|
217
|
+
"",
|
|
218
|
+
"만",
|
|
219
|
+
"억",
|
|
220
|
+
"조",
|
|
221
|
+
"경"
|
|
222
|
+
];
|
|
223
|
+
function z(e) {
|
|
224
|
+
if (!Number.isSafeInteger(e)) throw RangeError(`금액은 정수여야 해요: ${e}`);
|
|
225
|
+
if (e === 0) return "영";
|
|
226
|
+
if (e < 0) return `마이너스 ${z(-e)}`;
|
|
227
|
+
let t = "", n = String(e).padStart(Math.ceil(String(e).length / 4) * 4, "0");
|
|
228
|
+
for (let e = 0; e < n.length / 4; e++) {
|
|
229
|
+
let r = n.slice(e * 4, e * 4 + 4);
|
|
230
|
+
if (r !== "0000") {
|
|
231
|
+
for (let e = 0; e < 4; e++) r[e] !== "0" && (t += oe[+r[e]] + se[e]);
|
|
232
|
+
t += ce[n.length / 4 - 1 - e];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return t;
|
|
236
|
+
}
|
|
237
|
+
var B = (e) => Number(e.replace(/[^\d]/g, "")) || 0;
|
|
238
|
+
function V(e, t = "소장") {
|
|
239
|
+
let n = e.toLocaleString("ko-KR");
|
|
240
|
+
return t === "소장" ? `금 ${n}원` : `금 ${z(e)} 원정(₩${n})`;
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/io.ts
|
|
244
|
+
var le = (e) => e < 20 ? String.fromCharCode(9312 + e) : `${e + 1}.`, H = "가나다라마바사아자차카타파하", U = (e) => {
|
|
107
245
|
let t = "";
|
|
108
|
-
for (; e > 0; e = Math.floor(e / 14)) t =
|
|
246
|
+
for (; e > 0; e = Math.floor(e / 14)) t = H[--e % 14] + t;
|
|
109
247
|
return t;
|
|
110
|
-
},
|
|
248
|
+
}, ue = (e, t) => [
|
|
111
249
|
`${t}.`,
|
|
112
|
-
`${
|
|
250
|
+
`${U(t)}.`,
|
|
113
251
|
`(${t})`,
|
|
114
|
-
`(${
|
|
252
|
+
`(${U(t)})`
|
|
115
253
|
][e - 1];
|
|
116
|
-
function
|
|
254
|
+
function W(e, t) {
|
|
117
255
|
let n = [], r = 0, i = (e = []) => e.map((e) => {
|
|
118
256
|
if (e.type === "hardBreak") return {
|
|
119
257
|
text: "",
|
|
@@ -133,7 +271,7 @@ function x(e, t) {
|
|
|
133
271
|
bold: !0
|
|
134
272
|
})), o = (e, t) => e.content?.forEach((r, a) => r.content?.forEach((r, s) => {
|
|
135
273
|
if (r.type === "orderedList" || r.type === "bulletList") return o(r, t + 1);
|
|
136
|
-
let c = a + (e.attrs?.start ?? 1), l = s > 0 ? "" : e.type === "bulletList" ? "•" : t === 0 ?
|
|
274
|
+
let c = a + (e.attrs?.start ?? 1), l = s > 0 ? "" : e.type === "bulletList" ? "•" : t === 0 ? le(c - 1) : `${c}.`;
|
|
137
275
|
n.push({
|
|
138
276
|
kind: "para",
|
|
139
277
|
label: l,
|
|
@@ -166,7 +304,7 @@ function x(e, t) {
|
|
|
166
304
|
runs: a(i(t.content))
|
|
167
305
|
}) : t.type === "orderedList" || t.type === "bulletList" ? o(t, 0) : l ? (s[l]++, s.fill(0, l + 1), n.push({
|
|
168
306
|
kind: "para",
|
|
169
|
-
label:
|
|
307
|
+
label: ue(l, s[l]),
|
|
170
308
|
depth: l,
|
|
171
309
|
runs: i(t.content)
|
|
172
310
|
})) : t.type === "paragraph" ? n.push({
|
|
@@ -189,29 +327,29 @@ function x(e, t) {
|
|
|
189
327
|
};
|
|
190
328
|
return c(e.content), n;
|
|
191
329
|
}
|
|
192
|
-
var
|
|
330
|
+
var de = (e) => e.replace(/[&<>"]/g, (e) => `&#${e.charCodeAt(0)};`), G = (e) => e.map((e) => {
|
|
193
331
|
if (e.br) return "<br>";
|
|
194
|
-
let t =
|
|
332
|
+
let t = de(e.text);
|
|
195
333
|
return e.bold && (t = `<b>${t}</b>`), e.italics && (t = `<i>${t}</i>`), e.underline && (t = `<u>${t}</u>`), e.strike && (t = `<s>${t}</s>`), t;
|
|
196
334
|
}).join("");
|
|
197
|
-
function
|
|
198
|
-
return
|
|
335
|
+
function K(e, t) {
|
|
336
|
+
return W(e, t).map((e) => {
|
|
199
337
|
if (e.kind === "table") {
|
|
200
|
-
let t = (e) => `<td${e.colspan > 1 ? ` colspan="${e.colspan}"` : ""}${e.rowspan > 1 ? ` rowspan="${e.rowspan}"` : ""}>${
|
|
338
|
+
let t = (e) => `<td${e.colspan > 1 ? ` colspan="${e.colspan}"` : ""}${e.rowspan > 1 ? ` rowspan="${e.rowspan}"` : ""}>${G(e.runs)}</td>`;
|
|
201
339
|
return `<table>${e.rows.map((e) => `<tr>${e.map(t).join("")}</tr>`).join("")}</table>`;
|
|
202
340
|
}
|
|
203
|
-
let t = (e.label ? e.kind === "article" ? `<b>${e.label}</b> ` : `${e.label} ` : "") +
|
|
341
|
+
let t = (e.label ? e.kind === "article" ? `<b>${e.label}</b> ` : `${e.label} ` : "") + G(e.runs);
|
|
204
342
|
if (e.kind === "title") return `<h1>${t}</h1>`;
|
|
205
343
|
if (e.kind === "section") return `<h3>${t}</h3>`;
|
|
206
344
|
let n = e.label ? e.depth - 1 : e.depth;
|
|
207
345
|
return n > 0 ? `<p style="margin-left:${n * 2}em">${t}</p>` : `<p>${t}</p>`;
|
|
208
346
|
}).join("\n");
|
|
209
347
|
}
|
|
210
|
-
function
|
|
211
|
-
for (let n of
|
|
348
|
+
function q(e, t) {
|
|
349
|
+
for (let n of W(e, t)) if (n.kind === "title") return n.runs.map((e) => e.text).join("").trim() || "문서";
|
|
212
350
|
return "문서";
|
|
213
351
|
}
|
|
214
|
-
async function
|
|
352
|
+
async function fe(e, t) {
|
|
215
353
|
let { AlignmentType: n, Document: r, HeadingLevel: i, Packer: a, Paragraph: o, Table: s, TableCell: c, TableRow: l, TextRun: u, WidthType: d } = await import("docx"), f = (e) => new u({
|
|
216
354
|
text: e.text,
|
|
217
355
|
break: e.br ? 1 : void 0,
|
|
@@ -219,7 +357,7 @@ async function E(e, t) {
|
|
|
219
357
|
italics: e.italics,
|
|
220
358
|
strike: e.strike,
|
|
221
359
|
underline: e.underline ? {} : void 0
|
|
222
|
-
}), p =
|
|
360
|
+
}), p = W(e, t).map((e) => e.kind === "table" ? new s({
|
|
223
361
|
width: {
|
|
224
362
|
size: 100,
|
|
225
363
|
type: d.PERCENTAGE
|
|
@@ -243,7 +381,7 @@ async function E(e, t) {
|
|
|
243
381
|
})] : [], ...e.runs.map(f)]
|
|
244
382
|
}));
|
|
245
383
|
return a.toBlob(new r({
|
|
246
|
-
title:
|
|
384
|
+
title: q(e, t),
|
|
247
385
|
styles: {
|
|
248
386
|
default: { document: { run: {
|
|
249
387
|
font: "바탕",
|
|
@@ -268,18 +406,18 @@ async function E(e, t) {
|
|
|
268
406
|
sections: [{ children: p }]
|
|
269
407
|
}));
|
|
270
408
|
}
|
|
271
|
-
async function
|
|
272
|
-
let { htmlToHwpx: n } = await import("hwp-convert"), r = await n(
|
|
409
|
+
async function pe(e, t) {
|
|
410
|
+
let { htmlToHwpx: n } = await import("hwp-convert"), r = await n(K(e, t), { title: q(e, t) });
|
|
273
411
|
return new Blob([r], { type: "application/hwp+zip" });
|
|
274
412
|
}
|
|
275
|
-
var
|
|
413
|
+
var J = /^\s*제\s*\d+\s*조(?:의\s*\d+)?\s*/, me = /^\s*[①-⑳]\s*/, Y = /^\s*(\d+)\.\s*/, he = /^(청구취지|청구원인|신청취지|신청이유|고소취지|고소이유|고발취지|고발이유|입증방법|증명방법|증거방법|증거서류|첨부서류|당사자관계|사건개요|고소인|피고소인|범죄사실|증거자료|관련사건의수사및재판여부|기타)$/, ge = (e) => e.length <= 20 && (/^[가-힣](\s+[가-힣]){3,7}$/.test(e) || he.test(e.replace(/\s/g, ""))), _e = /\[\s*([^[\]{}<>"\d=]{1,20}?)\s*\]/g, ve = [
|
|
276
414
|
[/^\s*(\d{1,2})\.(?!\d)\s*/, Number],
|
|
277
|
-
[/^\s*([가나다라마바사아자차카타파하])\.\s*/, (e) =>
|
|
415
|
+
[/^\s*([가나다라마바사아자차카타파하])\.\s*/, (e) => H.indexOf(e) + 1],
|
|
278
416
|
[/^\s*\((\d{1,2})\)\s*/, Number],
|
|
279
|
-
[/^\s*\(([가나다라마바사아자차카타파하])\)\s*/, (e) =>
|
|
417
|
+
[/^\s*\(([가나다라마바사아자차카타파하])\)\s*/, (e) => H.indexOf(e) + 1]
|
|
280
418
|
];
|
|
281
|
-
function
|
|
282
|
-
for (let [t, [n, r]] of
|
|
419
|
+
function ye(e) {
|
|
420
|
+
for (let [t, [n, r]] of ve.entries()) {
|
|
283
421
|
let i = e.match(n);
|
|
284
422
|
if (i) return {
|
|
285
423
|
level: t + 1,
|
|
@@ -289,23 +427,23 @@ function F(e) {
|
|
|
289
427
|
}
|
|
290
428
|
return null;
|
|
291
429
|
}
|
|
292
|
-
function
|
|
430
|
+
function X(e, t) {
|
|
293
431
|
let n = typeof t == "number" ? t : e.textContent.match(t)[0].length, r = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_TEXT);
|
|
294
432
|
for (let e = r.nextNode(); e && n > 0; e = r.nextNode()) {
|
|
295
433
|
let t = Math.min(n, e.data.length);
|
|
296
434
|
e.data = e.data.slice(t), n -= t;
|
|
297
435
|
}
|
|
298
436
|
}
|
|
299
|
-
function
|
|
437
|
+
function Z(e) {
|
|
300
438
|
let t = new DOMParser().parseFromString(e, "text/html");
|
|
301
439
|
for (let e of [...t.body.children]) e.tagName === "P" && !e.textContent.trim() && !e.querySelector("img") && e.remove();
|
|
302
440
|
let n = t.createTreeWalker(t.body, NodeFilter.SHOW_TEXT);
|
|
303
441
|
for (let e = n.nextNode(); e; e = n.nextNode()) {
|
|
304
442
|
let t = e.parentElement?.closest("p,li,td,th,h1,h2,h3")?.textContent?.trim();
|
|
305
|
-
e.data = e.data.replace(
|
|
443
|
+
e.data = e.data.replace(_e, (e, n) => e.trim() === t ? e : `{{${n.trim()}}}`);
|
|
306
444
|
}
|
|
307
445
|
let r = t.body.firstElementChild, i = r?.textContent.trim() ?? "";
|
|
308
|
-
if (!t.querySelector("h1") && r?.tagName === "P" && i.length <= 30 && !/[.다]$/.test(i) && !
|
|
446
|
+
if (!t.querySelector("h1") && r?.tagName === "P" && i.length <= 30 && !/[.다]$/.test(i) && !J.test(i)) {
|
|
309
447
|
let e = t.createElement("h1");
|
|
310
448
|
e.textContent = i, r.replaceWith(e);
|
|
311
449
|
}
|
|
@@ -324,62 +462,62 @@ function L(e) {
|
|
|
324
462
|
];
|
|
325
463
|
for (let e of [...t.body.children]) {
|
|
326
464
|
let n = e.textContent ?? "", r = n.replace(/\s+/g, " ").trim();
|
|
327
|
-
if (e.tagName === "P" &&
|
|
465
|
+
if (e.tagName === "P" && ge(r)) {
|
|
328
466
|
let n = t.createElement("h3");
|
|
329
467
|
n.textContent = r, e.replaceWith(n), a = null, o.fill(0), s.fill(0);
|
|
330
|
-
} else if (e.tagName === "P" &&
|
|
468
|
+
} else if (e.tagName === "P" && J.test(n)) {
|
|
331
469
|
let n = t.createElement("h2");
|
|
332
|
-
n.innerHTML = e.innerHTML,
|
|
333
|
-
} else if (e.tagName === "P" &&
|
|
470
|
+
n.innerHTML = e.innerHTML, X(n, J), e.replaceWith(n), a = null, o.fill(0), s.fill(0);
|
|
471
|
+
} else if (e.tagName === "P" && me.test(n)) {
|
|
334
472
|
let r = n.trim().charCodeAt(0) - 9311;
|
|
335
|
-
(!a || a.children.length + Number(a.getAttribute("start") ?? 1) !== r) && (e.before(a = t.createElement("ol")), r > 1 && a.setAttribute("start", String(r))),
|
|
473
|
+
(!a || a.children.length + Number(a.getAttribute("start") ?? 1) !== r) && (e.before(a = t.createElement("ol")), r > 1 && a.setAttribute("start", String(r))), X(e, me);
|
|
336
474
|
let i = t.createElement("li");
|
|
337
475
|
i.append(e), a.append(i);
|
|
338
|
-
} else if (e.tagName === "P" && a &&
|
|
476
|
+
} else if (e.tagName === "P" && a && Y.test(n) && Number(n.match(Y)[1]) === (a.lastElementChild.querySelector(":scope > ol")?.children.length ?? 0) + 1) {
|
|
339
477
|
let n = a.lastElementChild, r = n.querySelector(":scope > ol") ?? n.appendChild(t.createElement("ol"));
|
|
340
|
-
|
|
478
|
+
X(e, Y);
|
|
341
479
|
let i = t.createElement("li");
|
|
342
480
|
i.append(e), r.append(i);
|
|
343
481
|
} else {
|
|
344
482
|
a = null;
|
|
345
|
-
let t = e.tagName === "P" ?
|
|
483
|
+
let t = e.tagName === "P" ? ye(n) : null;
|
|
346
484
|
if (!t) continue;
|
|
347
|
-
s[t.level] && t.n === s[t.level] + 1 ? s[t.level]++ : t.n === o[t.level] + 1 ? (o[t.level]++, o.fill(0, t.level + 1), s.fill(0),
|
|
485
|
+
s[t.level] && t.n === s[t.level] + 1 ? s[t.level]++ : t.n === o[t.level] + 1 ? (o[t.level]++, o.fill(0, t.level + 1), s.fill(0), X(e, t.length), e.setAttribute("data-num", String(t.level))) : s[t.level] = +(t.n === 1);
|
|
348
486
|
}
|
|
349
487
|
}
|
|
350
488
|
return t.body.innerHTML;
|
|
351
489
|
}
|
|
352
|
-
async function
|
|
490
|
+
async function be(e) {
|
|
353
491
|
let { default: t } = await import("mammoth"), { value: n } = await t.convertToHtml({ arrayBuffer: await e.arrayBuffer() });
|
|
354
|
-
return
|
|
492
|
+
return Z(n);
|
|
355
493
|
}
|
|
356
|
-
async function
|
|
494
|
+
async function xe(e) {
|
|
357
495
|
let { detectFormat: t, hwpToHwpx: n, HwpxReader: r } = await import("hwp-convert"), i = new Uint8Array(await e.arrayBuffer());
|
|
358
496
|
t(i) === "hwp" && (i = new Uint8Array(await n(i)));
|
|
359
497
|
let a = new r();
|
|
360
|
-
return await a.loadFromArrayBuffer(i.buffer),
|
|
498
|
+
return await a.loadFromArrayBuffer(i.buffer), Z(await a.extractHtml());
|
|
361
499
|
}
|
|
362
|
-
function
|
|
363
|
-
return /\.docx$/i.test(e.name) ?
|
|
500
|
+
function Q(e) {
|
|
501
|
+
return /\.docx$/i.test(e.name) ? be(e) : /\.hwpx?$/i.test(e.name) ? xe(e) : Promise.reject(/* @__PURE__ */ Error(".docx, .hwp, .hwpx 파일만 열 수 있어요"));
|
|
364
502
|
}
|
|
365
503
|
//#endregion
|
|
366
504
|
//#region src/LegalEditor.tsx
|
|
367
|
-
function
|
|
505
|
+
function $(e) {
|
|
368
506
|
let t = /* @__PURE__ */ new Set();
|
|
369
507
|
return e.state.doc.descendants((e) => {
|
|
370
508
|
e.type.name === "variable" && t.add(e.attrs.name);
|
|
371
509
|
}), [...t];
|
|
372
510
|
}
|
|
373
|
-
function
|
|
511
|
+
function Se() {
|
|
374
512
|
let e = document.documentElement;
|
|
375
513
|
e.classList.add("le-printing"), window.addEventListener("afterprint", () => e.classList.remove("le-printing"), { once: !0 }), window.print();
|
|
376
514
|
}
|
|
377
|
-
function
|
|
515
|
+
function Ce(e, t) {
|
|
378
516
|
let n = document.createElement("a");
|
|
379
517
|
n.href = URL.createObjectURL(e), n.download = t, n.click(), setTimeout(() => URL.revokeObjectURL(n.href), 1e3);
|
|
380
518
|
}
|
|
381
|
-
function
|
|
382
|
-
let [
|
|
519
|
+
function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, editable: u = !0, autoFees: d, searchCases: f }) {
|
|
520
|
+
let [p, _] = t(s), [v, b] = t([]), [x, S] = t(""), [C, w] = t(null), [T, E] = t(""), D = d ? ae(v, p, d === !0 ? {} : d) : p, O = r({
|
|
383
521
|
extensions: [
|
|
384
522
|
i.configure({ heading: { levels: [
|
|
385
523
|
1,
|
|
@@ -387,64 +525,81 @@ function W({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, ed
|
|
|
387
525
|
3
|
|
388
526
|
] } }),
|
|
389
527
|
a,
|
|
390
|
-
|
|
391
|
-
|
|
528
|
+
ee,
|
|
529
|
+
y,
|
|
530
|
+
te
|
|
392
531
|
],
|
|
393
|
-
content:
|
|
394
|
-
editable:
|
|
532
|
+
content: g(o),
|
|
533
|
+
editable: u,
|
|
395
534
|
immediatelyRender: !1,
|
|
396
535
|
editorProps: { attributes: { class: "legal-doc" } },
|
|
397
|
-
onCreate: ({ editor: e }) =>
|
|
536
|
+
onCreate: ({ editor: e }) => b($(e)),
|
|
398
537
|
onUpdate: ({ editor: e }) => {
|
|
399
|
-
|
|
538
|
+
b($(e)), c?.(e.getHTML());
|
|
400
539
|
}
|
|
401
540
|
});
|
|
402
541
|
e(() => {
|
|
403
|
-
|
|
404
|
-
}, [
|
|
405
|
-
let
|
|
542
|
+
O && (O.storage.variable.values = D, O.storage.variable.views.forEach((e) => e()));
|
|
543
|
+
}, [O, D]);
|
|
544
|
+
let k = (e, t) => {
|
|
406
545
|
let n = {
|
|
407
|
-
...
|
|
546
|
+
...p,
|
|
408
547
|
[e]: t
|
|
409
548
|
};
|
|
410
549
|
_(n), l?.(n);
|
|
411
|
-
},
|
|
550
|
+
}, j = () => {
|
|
412
551
|
let e = window.prompt("변수 이름 (예: 갑, 계약일)")?.replace(/[{}<>"]/g, "").trim();
|
|
413
|
-
e &&
|
|
552
|
+
e && O?.chain().focus().insertContent({
|
|
414
553
|
type: "variable",
|
|
415
554
|
attrs: { name: e }
|
|
416
555
|
}).run();
|
|
417
|
-
},
|
|
556
|
+
}, M = async (e) => {
|
|
418
557
|
let t = e.target.files?.[0];
|
|
419
|
-
if (e.target.value = "", t &&
|
|
420
|
-
|
|
558
|
+
if (e.target.value = "", t && O) try {
|
|
559
|
+
O.commands.setContent(g(await Q(t)), { emitUpdate: !0 });
|
|
421
560
|
} catch (e) {
|
|
422
561
|
window.alert(`파일을 열 수 없어요: ${e.message}`);
|
|
423
562
|
}
|
|
424
|
-
},
|
|
425
|
-
let n =
|
|
426
|
-
|
|
427
|
-
},
|
|
428
|
-
|
|
563
|
+
}, N = async (e, t) => {
|
|
564
|
+
let n = O.state.doc.firstChild?.textContent.trim() || "문서";
|
|
565
|
+
Ce(await e(O.getJSON(), D), `${n}.${t}`);
|
|
566
|
+
}, P = async () => {
|
|
567
|
+
let e = x.trim();
|
|
568
|
+
if (f && e) {
|
|
569
|
+
E("검색 중…");
|
|
570
|
+
try {
|
|
571
|
+
w(await f(e)), E("");
|
|
572
|
+
} catch (e) {
|
|
573
|
+
w(null), E(`검색하지 못했어요: ${e.message}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}, F = () => O.chain().focus(), I = (e) => F().setNumbering(O.getAttributes("paragraph").num === e ? null : e).run(), L = (e) => {
|
|
577
|
+
let { from: t, to: n } = O.state.selection, r = B(O.state.doc.textBetween(t, n));
|
|
578
|
+
r && F().insertContentAt({
|
|
579
|
+
from: t,
|
|
580
|
+
to: n
|
|
581
|
+
}, V(r, e)).run();
|
|
582
|
+
};
|
|
583
|
+
return /* @__PURE__ */ h("div", {
|
|
429
584
|
className: "le-root",
|
|
430
|
-
children: [/* @__PURE__ */
|
|
585
|
+
children: [/* @__PURE__ */ h("div", {
|
|
431
586
|
className: "le-main",
|
|
432
|
-
children: [
|
|
587
|
+
children: [u && O && /* @__PURE__ */ h("div", {
|
|
433
588
|
className: "le-toolbar",
|
|
434
589
|
children: [
|
|
435
|
-
/* @__PURE__ */
|
|
590
|
+
/* @__PURE__ */ m("button", {
|
|
436
591
|
type: "button",
|
|
437
|
-
onClick: () =>
|
|
592
|
+
onClick: () => F().toggleHeading({ level: 1 }).run(),
|
|
438
593
|
children: "제목"
|
|
439
594
|
}),
|
|
440
|
-
/* @__PURE__ */
|
|
595
|
+
/* @__PURE__ */ m("button", {
|
|
441
596
|
type: "button",
|
|
442
|
-
onClick: () =>
|
|
597
|
+
onClick: () => F().toggleHeading({ level: 3 }).run(),
|
|
443
598
|
children: "소제목"
|
|
444
599
|
}),
|
|
445
|
-
/* @__PURE__ */
|
|
600
|
+
/* @__PURE__ */ m("button", {
|
|
446
601
|
type: "button",
|
|
447
|
-
onClick: () =>
|
|
602
|
+
onClick: () => F().toggleHeading({ level: 2 }).run(),
|
|
448
603
|
children: "조"
|
|
449
604
|
}),
|
|
450
605
|
[
|
|
@@ -452,78 +607,90 @@ function W({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, ed
|
|
|
452
607
|
"가.",
|
|
453
608
|
"(1)",
|
|
454
609
|
"(가)"
|
|
455
|
-
].map((e, t) => /* @__PURE__ */
|
|
610
|
+
].map((e, t) => /* @__PURE__ */ m("button", {
|
|
456
611
|
type: "button",
|
|
457
612
|
title: "번호 문단 — Tab·Shift+Tab 단계 변경, 맨 앞 Backspace 해제",
|
|
458
|
-
onClick: () =>
|
|
613
|
+
onClick: () => I(t + 1),
|
|
459
614
|
children: e
|
|
460
615
|
}, e)),
|
|
461
|
-
/* @__PURE__ */
|
|
616
|
+
/* @__PURE__ */ m("button", {
|
|
462
617
|
type: "button",
|
|
463
|
-
onClick: () =>
|
|
618
|
+
onClick: () => F().toggleOrderedList().run(),
|
|
464
619
|
children: "항"
|
|
465
620
|
}),
|
|
466
|
-
/* @__PURE__ */
|
|
621
|
+
/* @__PURE__ */ m("button", {
|
|
467
622
|
type: "button",
|
|
468
|
-
onClick: () =>
|
|
623
|
+
onClick: () => F().sinkListItem("listItem").run(),
|
|
469
624
|
children: "호 →"
|
|
470
625
|
}),
|
|
471
|
-
/* @__PURE__ */
|
|
626
|
+
/* @__PURE__ */ m("button", {
|
|
472
627
|
type: "button",
|
|
473
|
-
onClick: () =>
|
|
628
|
+
onClick: () => F().liftListItem("listItem").run(),
|
|
474
629
|
children: "← 내어쓰기"
|
|
475
630
|
}),
|
|
476
|
-
/* @__PURE__ */
|
|
631
|
+
/* @__PURE__ */ m("button", {
|
|
477
632
|
type: "button",
|
|
478
|
-
onClick: () =>
|
|
479
|
-
children: /* @__PURE__ */
|
|
633
|
+
onClick: () => F().toggleBold().run(),
|
|
634
|
+
children: /* @__PURE__ */ m("b", { children: "B" })
|
|
480
635
|
}),
|
|
481
|
-
/* @__PURE__ */
|
|
636
|
+
/* @__PURE__ */ h("button", {
|
|
482
637
|
type: "button",
|
|
483
|
-
onClick:
|
|
638
|
+
onClick: j,
|
|
484
639
|
children: ["{{ }}", " 변수"]
|
|
485
640
|
}),
|
|
486
|
-
/* @__PURE__ */
|
|
641
|
+
/* @__PURE__ */ m("button", {
|
|
487
642
|
type: "button",
|
|
488
|
-
onClick: () =>
|
|
643
|
+
onClick: () => F().insertTable({
|
|
489
644
|
rows: 3,
|
|
490
645
|
cols: 3,
|
|
491
646
|
withHeaderRow: !1
|
|
492
647
|
}).run(),
|
|
493
648
|
children: "표"
|
|
494
649
|
}),
|
|
495
|
-
/* @__PURE__ */
|
|
496
|
-
|
|
650
|
+
/* @__PURE__ */ m("button", {
|
|
651
|
+
type: "button",
|
|
652
|
+
title: "선택한 금액 → 금 37,200,000원",
|
|
653
|
+
onClick: () => L("소장"),
|
|
654
|
+
children: "금액"
|
|
655
|
+
}),
|
|
656
|
+
/* @__PURE__ */ m("button", {
|
|
657
|
+
type: "button",
|
|
658
|
+
title: "선택한 금액 → 금 삼천칠백이십만 원정(₩37,200,000)",
|
|
659
|
+
onClick: () => L("계약서"),
|
|
660
|
+
children: "금액(한글)"
|
|
661
|
+
}),
|
|
662
|
+
/* @__PURE__ */ m("span", { className: "le-spacer" }),
|
|
663
|
+
/* @__PURE__ */ h("label", {
|
|
497
664
|
className: "le-btn",
|
|
498
|
-
children: ["열기", /* @__PURE__ */
|
|
665
|
+
children: ["열기", /* @__PURE__ */ m("input", {
|
|
499
666
|
type: "file",
|
|
500
667
|
accept: ".docx,.hwp,.hwpx",
|
|
501
668
|
hidden: !0,
|
|
502
|
-
onChange:
|
|
669
|
+
onChange: M
|
|
503
670
|
})]
|
|
504
671
|
}),
|
|
505
|
-
/* @__PURE__ */
|
|
672
|
+
/* @__PURE__ */ m("button", {
|
|
506
673
|
type: "button",
|
|
507
|
-
onClick: () =>
|
|
674
|
+
onClick: () => N(fe, "docx"),
|
|
508
675
|
children: "Word 저장"
|
|
509
676
|
}),
|
|
510
|
-
/* @__PURE__ */
|
|
677
|
+
/* @__PURE__ */ m("button", {
|
|
511
678
|
type: "button",
|
|
512
|
-
onClick: () =>
|
|
679
|
+
onClick: () => N(pe, "hwpx"),
|
|
513
680
|
children: "한글 저장"
|
|
514
681
|
}),
|
|
515
|
-
/* @__PURE__ */
|
|
682
|
+
/* @__PURE__ */ m("button", {
|
|
516
683
|
type: "button",
|
|
517
|
-
onClick:
|
|
684
|
+
onClick: Se,
|
|
518
685
|
children: "인쇄 · PDF"
|
|
519
686
|
})
|
|
520
687
|
]
|
|
521
|
-
}), /* @__PURE__ */
|
|
522
|
-
}), /* @__PURE__ */
|
|
688
|
+
}), /* @__PURE__ */ m(n, { editor: O })]
|
|
689
|
+
}), /* @__PURE__ */ h("aside", {
|
|
523
690
|
className: "le-panel",
|
|
524
691
|
children: [
|
|
525
|
-
/* @__PURE__ */
|
|
526
|
-
v.length === 0 && /* @__PURE__ */
|
|
692
|
+
/* @__PURE__ */ m("h3", { children: "입력값" }),
|
|
693
|
+
v.length === 0 && /* @__PURE__ */ h("p", {
|
|
527
694
|
className: "le-hint",
|
|
528
695
|
children: [
|
|
529
696
|
"본문에 ",
|
|
@@ -531,13 +698,90 @@ function W({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, ed
|
|
|
531
698
|
"처럼 입력하면 변수가 생겨요."
|
|
532
699
|
]
|
|
533
700
|
}),
|
|
534
|
-
v.map((e) => /* @__PURE__ */
|
|
535
|
-
value:
|
|
536
|
-
|
|
537
|
-
|
|
701
|
+
v.map((e) => /* @__PURE__ */ h("label", { children: [e, /* @__PURE__ */ m("input", {
|
|
702
|
+
value: p[e] ?? "",
|
|
703
|
+
placeholder: p[e] ? void 0 : D[e],
|
|
704
|
+
onChange: (t) => k(e, t.target.value)
|
|
705
|
+
})] }, e)),
|
|
706
|
+
f && /* @__PURE__ */ h("section", {
|
|
707
|
+
className: "le-cases",
|
|
708
|
+
children: [
|
|
709
|
+
/* @__PURE__ */ m("h3", { children: "판례 검색" }),
|
|
710
|
+
/* @__PURE__ */ h("div", {
|
|
711
|
+
className: "le-cases-bar",
|
|
712
|
+
children: [/* @__PURE__ */ m("input", {
|
|
713
|
+
value: x,
|
|
714
|
+
placeholder: "사건명·사건번호·키워드",
|
|
715
|
+
onChange: (e) => S(e.target.value),
|
|
716
|
+
onKeyDown: (e) => {
|
|
717
|
+
e.key === "Enter" && !e.nativeEvent.isComposing && (e.preventDefault(), P());
|
|
718
|
+
}
|
|
719
|
+
}), /* @__PURE__ */ m("button", {
|
|
720
|
+
type: "button",
|
|
721
|
+
onClick: P,
|
|
722
|
+
children: "검색"
|
|
723
|
+
})]
|
|
724
|
+
}),
|
|
725
|
+
T && /* @__PURE__ */ m("p", {
|
|
726
|
+
className: "le-hint",
|
|
727
|
+
children: T
|
|
728
|
+
}),
|
|
729
|
+
!T && C?.length === 0 && /* @__PURE__ */ m("p", {
|
|
730
|
+
className: "le-hint",
|
|
731
|
+
children: "결과가 없어요."
|
|
732
|
+
}),
|
|
733
|
+
/* @__PURE__ */ m("ul", {
|
|
734
|
+
className: "le-case-list",
|
|
735
|
+
children: C?.map((e) => /* @__PURE__ */ h("li", { children: [/* @__PURE__ */ h("button", {
|
|
736
|
+
type: "button",
|
|
737
|
+
disabled: !u,
|
|
738
|
+
title: "커서 위치에 인용 넣기",
|
|
739
|
+
onClick: () => O?.chain().focus().insertContent(A(e)).run(),
|
|
740
|
+
children: [
|
|
741
|
+
/* @__PURE__ */ m("strong", { children: e.title }),
|
|
742
|
+
/* @__PURE__ */ m("span", { children: A(e) }),
|
|
743
|
+
e.summary && /* @__PURE__ */ m("small", { children: e.summary })
|
|
744
|
+
]
|
|
745
|
+
}), /* @__PURE__ */ m("a", {
|
|
746
|
+
href: e.url,
|
|
747
|
+
target: "_blank",
|
|
748
|
+
rel: "noopener noreferrer",
|
|
749
|
+
children: "원문"
|
|
750
|
+
})] }, `${e.caseNo}-${e.date}`))
|
|
751
|
+
})
|
|
752
|
+
]
|
|
753
|
+
})
|
|
538
754
|
]
|
|
539
755
|
})]
|
|
540
756
|
});
|
|
541
757
|
}
|
|
542
758
|
//#endregion
|
|
543
|
-
|
|
759
|
+
//#region src/templates.ts
|
|
760
|
+
var Te = [
|
|
761
|
+
{
|
|
762
|
+
id: "complaint-loan",
|
|
763
|
+
title: "소장 (대여금)",
|
|
764
|
+
description: "빌려준 돈을 돌려받기 위한 민사 소장. 소가를 넣으면 인지액·송달료가 채워져요(autoFees)",
|
|
765
|
+
html: "\n<h1>소 장</h1>\n<p>원 고 {{원고 이름}}</p>\n<p>주소 {{원고 주소}}</p>\n<p>연락처 {{원고 연락처}}</p>\n<p>피 고 {{피고 이름}}</p>\n<p>주소 {{피고 주소}}</p>\n<p>연락처 {{피고 연락처}}</p>\n<p>대여금 청구의 소</p>\n<p>소가 {{소가}}</p>\n<p>인지액 {{인지액}}</p>\n<p>송달료 {{송달료}}</p>\n<h3>청 구 취 지</h3>\n<p data-num=\"1\">피고는 원고에게 {{청구금액}} 및 이에 대하여 {{변제기 다음 날}}부터 이 사건 소장 부본 송달일까지는 연 5%의, 그 다음 날부터 다 갚는 날까지는 연 12%의 각 비율로 계산한 돈을 지급하라.</p>\n<p data-num=\"1\">소송비용은 피고가 부담한다.</p>\n<p data-num=\"1\">제1항은 가집행할 수 있다.</p>\n<p>라는 판결을 구합니다.</p>\n<h3>청 구 원 인</h3>\n<p data-num=\"1\">당사자의 관계</p>\n<p>원고와 피고는 {{당사자 관계}} 사이입니다.</p>\n<p data-num=\"1\">돈을 빌려준 사실</p>\n<p>원고는 {{대여일}} 피고에게 {{청구금액}}을 변제기 {{변제기}}로 정하여 빌려주었습니다.</p>\n<p data-num=\"1\">피고가 갚지 않은 사실</p>\n<p>피고는 변제기가 지났는데도 원고의 여러 차례 요구에도 위 돈을 갚지 않고 있습니다.</p>\n<p data-num=\"1\">결론</p>\n<p>따라서 원고는 피고에게 위 대여금과 이에 대하여 변제기 다음 날부터 이 사건 소장 부본 송달일까지는 민법이 정한 연 5%의, 그 다음 날부터 다 갚는 날까지는 소송촉진 등에 관한 특례법이 정한 연 12%의 각 비율로 계산한 지연손해금의 지급을 구합니다.</p>\n<h3>입 증 방 법</h3>\n<p data-num=\"1\">갑 제1호증 차용증</p>\n<p data-num=\"1\">갑 제2호증 계좌이체 내역</p>\n<h3>첨 부 서 류</h3>\n<p data-num=\"1\">위 입증방법 각 1통</p>\n<p data-num=\"1\">소장 부본 1통</p>\n<p data-num=\"1\">송달료 납부서 1통</p>\n<p>{{작성일}}</p>\n<p>원고 {{원고 이름}} (서명 또는 날인)</p>\n<p>{{관할 법원}} 귀중</p>\n"
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
id: "criminal-complaint-fraud",
|
|
769
|
+
title: "고소장 (사기)",
|
|
770
|
+
description: "돈을 속아서 보낸 경우 수사기관에 내는 고소장. 경찰청 표준 고소장의 항목 순서",
|
|
771
|
+
html: "\n<h1>고 소 장</h1>\n<h3>고소인</h3>\n<p>성명 {{고소인 이름}}</p>\n<p>주민등록번호 {{고소인 주민등록번호}}</p>\n<p>주소 {{고소인 주소}}</p>\n<p>직업 {{고소인 직업}}</p>\n<p>전화 {{고소인 연락처}}</p>\n<h3>피고소인</h3>\n<p>성명 {{피고소인 이름}}</p>\n<p>주민등록번호 {{피고소인 주민등록번호}} (모르면 비워 둠)</p>\n<p>주소 {{피고소인 주소}}</p>\n<p>직업 {{피고소인 직업}}</p>\n<p>전화 {{피고소인 연락처}}</p>\n<p>기타사항 {{고소인과 피고소인의 관계}}</p>\n<h3>고 소 취 지</h3>\n<p>고소인은 피고소인을 사기죄(형법 제347조 제1항)로 고소하오니 처벌하여 주시기 바랍니다.</p>\n<h3>범 죄 사 실</h3>\n<p>피고소인은 {{범행 일시}} {{범행 장소}}에서 고소인에게 {{거짓말한 내용}}이라고 거짓말하여, 이에 속은 고소인으로부터 {{피해 금액}}을 {{받은 방법}}으로 받아 편취하였습니다.</p>\n<h3>고 소 이 유</h3>\n<p data-num=\"1\">고소인과 피고소인의 관계</p>\n<p>{{관계와 알게 된 경위}}</p>\n<p data-num=\"1\">범행 경위</p>\n<p>{{범행 경위}}</p>\n<p data-num=\"1\">피해 상황과 고소에 이르게 된 이유</p>\n<p>{{피해 상황}}</p>\n<h3>증 거 자 료</h3>\n<p data-num=\"1\">{{증거 1}}</p>\n<p data-num=\"1\">{{증거 2}}</p>\n<p>증거가 많으면 내용을 별지에 구체적으로 적고, 증거서류·증거물은 고소장을 낼 때 함께 제출합니다.</p>\n<h3>관련 사건의 수사 및 재판 여부</h3>\n<p>중복 고소 여부: {{있음 또는 없음}}</p>\n<p>관련 형사사건 수사 유무: {{수사 중 또는 없음}}</p>\n<p>관련 민사소송 유무: {{진행 중 또는 없음}}</p>\n<h3>기타</h3>\n<p>이 고소장에 적은 내용은 고소인이 알고 있는 지식과 경험을 바탕으로 모두 사실대로 작성하였으며, 만일 허위 사실을 고소하였을 때에는 형법 제156조 무고죄로 처벌받을 것임을 서약합니다.</p>\n<p>{{작성일}}</p>\n<p>고소인 {{고소인 이름}} (인)</p>\n<p>{{경찰서}} 귀중</p>\n"
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: "certified-letter",
|
|
775
|
+
title: "내용증명",
|
|
776
|
+
description: "돈을 돌려달라고 요구하는 내용증명 우편. 소송 전에 요구 사실을 남겨 둘 때",
|
|
777
|
+
html: "\n<h1>내 용 증 명</h1>\n<p>발신인 {{발신인 이름}}</p>\n<p>주소 {{발신인 주소}}</p>\n<p>수신인 {{수신인 이름}}</p>\n<p>주소 {{수신인 주소}}</p>\n<p>제목 {{제목}}</p>\n<p data-num=\"1\">귀하의 건승을 기원합니다.</p>\n<p data-num=\"1\">발신인은 {{지급일}} 수신인에게 {{금액}}을 {{지급 이유}}(으)로 지급하였고, 수신인은 이를 {{돌려주기로 한 날}}까지 돌려주기로 약속하였습니다.</p>\n<p data-num=\"1\">그러나 수신인은 약속한 날이 지났는데도 위 돈을 돌려주지 않고 있습니다.</p>\n<p data-num=\"1\">이에 발신인은 수신인에게 {{이행 기한}}까지 {{금액}}을 아래 계좌로 지급할 것을 요구합니다.</p>\n<p>계좌 {{은행}} {{계좌번호}} (예금주 {{예금주}})</p>\n<p data-num=\"1\">위 기한까지 지급하지 않으면 별도 통지 없이 민사소송 등 법적 절차를 진행할 예정이며, 그에 따른 비용과 지연손해금도 함께 청구할 것임을 알려드립니다.</p>\n<p>{{작성일}}</p>\n<p>발신인 {{발신인 이름}} (인)</p>\n"
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
id: "payment-order",
|
|
781
|
+
title: "지급명령 신청서",
|
|
782
|
+
description: "재판 없이 법원의 지급명령을 받는 독촉절차 신청서. 인지대는 소장의 10분의 1, 송달료는 당사자 수 × 6회분",
|
|
783
|
+
html: "\n<h1>지 급 명 령 신 청 서</h1>\n<p>채 권 자 {{채권자 이름}}</p>\n<p>주소 {{채권자 주소}}</p>\n<p>연락처 {{채권자 연락처}}</p>\n<p>채 무 자 {{채무자 이름}}</p>\n<p>주소 {{채무자 주소}}</p>\n<p>대여금 청구 독촉사건</p>\n<p>청구금액 {{청구금액}}</p>\n<h3>신 청 취 지</h3>\n<p>채무자는 채권자에게 아래 청구금액 및 독촉절차비용을 지급하라는 명령을 구합니다.</p>\n<p data-num=\"1\">{{청구금액}}</p>\n<p data-num=\"1\">위 금액에 대하여 {{변제기 다음 날}}부터 이 사건 지급명령정본을 송달받은 날까지는 연 5%의, 그 다음 날부터 다 갚는 날까지는 연 12%의 각 비율로 계산한 돈</p>\n<p data-num=\"1\">독촉절차비용 {{독촉절차비용}} (인지대 {{독촉절차 인지대}}, 송달료 {{독촉절차 송달료}})</p>\n<h3>신 청 이 유</h3>\n<p data-num=\"1\">채권자는 {{대여일}} 채무자에게 {{청구금액}}을 변제기 {{변제기}}로 정하여 빌려주었습니다.</p>\n<p data-num=\"1\">그러나 채무자는 변제기가 지났는데도 위 돈을 갚지 않고 있습니다.</p>\n<p data-num=\"1\">따라서 채권자는 채무자에게 위 청구금액과 지연손해금 및 독촉절차비용의 지급을 구하기 위하여 이 신청에 이르렀습니다.</p>\n<h3>첨 부 서 류</h3>\n<p data-num=\"1\">차용증 사본 1통</p>\n<p data-num=\"1\">송달료 납부서 1통</p>\n<p>{{작성일}}</p>\n<p>채권자 {{채권자 이름}} (서명 또는 날인)</p>\n<p>{{관할 법원}} 귀중</p>\n"
|
|
784
|
+
}
|
|
785
|
+
];
|
|
786
|
+
//#endregion
|
|
787
|
+
export { te as CitationLink, we as LegalEditor, y as Numbering, I as SERVICE_ROUNDS, L as SERVICE_UNIT_FEE, ne as SMALL_CLAIM_LIMIT, ee as Variable, F as calcPaymentOrderStampFee, R as calcServiceFee, N as calcStampFee, _ as fillTemplate, k as findCitations, V as formatAmount, A as formatCaseCitation, be as fromDocx, Q as fromFile, xe as fromHwp, Z as normalizeLegalHtml, B as parseAmount, Te as templates, g as toChips, fe as toDocx, pe as toHwpx, z as toKoreanAmount, K as toPlainHtml, ae as withCourtFees };
|
package/dist/style.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
@counter-style le-circled{system:fixed;symbols:"①" "②" "③" "④" "⑤" "⑥" "⑦" "⑧" "⑨" "⑩" "⑪" "⑫" "⑬" "⑭" "⑮" "⑯" "⑰" "⑱" "⑲" "⑳";suffix:" "}.legal-doc{counter-reset:le-article le-n1 le-n2 le-n3 le-n4;color:#111;background:#fff;outline:none;max-width:760px;margin:0 auto;padding:56px 64px;font-family:Noto Serif KR,Nanum Myeongjo,Batang,serif;font-size:15px;line-height:1.85}.legal-doc h1{text-align:center;letter-spacing:.25em;margin:0 0 1.6em;font-size:1.6em}.legal-doc h2{counter-increment:le-article;margin:1.6em 0 .3em;font-size:1em}.legal-doc h2:before{content:"제" counter(le-article) "조 "}.legal-doc p{margin:0 0 .4em}.legal-doc ol{margin:0 0 .4em;padding-left:1.8em;list-style:le-circled}.legal-doc ol ol{list-style:decimal}.legal-doc li p{margin:0}.legal-doc h3{text-align:center;letter-spacing:.3em;margin:1.8em 0 .8em;font-size:1.05em}.legal-doc>h1,.legal-doc>h2,.legal-doc>h3{counter-set:le-n1 le-n2 le-n3 le-n4}.legal-doc>p[data-num]{padding-left:calc(var(--le-num-indent,0em) + 2em);text-indent:-2em}.legal-doc>p[data-num]:before{text-indent:0;min-width:2em;display:inline-block}.legal-doc>p[data-num="1"]{counter-increment:le-n1;counter-set:le-n2 le-n3 le-n4}.legal-doc>p[data-num="2"]{counter-increment:le-n2;counter-set:le-n3 le-n4;--le-num-indent:1.2em}.legal-doc>p[data-num="3"]{counter-increment:le-n3;counter-set:le-n4;--le-num-indent:2.4em}.legal-doc>p[data-num="4"]{counter-increment:le-n4;--le-num-indent:3.6em}.legal-doc>p[data-num="1"]:before{content:counter(le-n1) "."}.legal-doc>p[data-num="2"]:before{content:counter(le-n2, hangul) "."}.legal-doc>p[data-num="3"]:before{content:"(" counter(le-n3) ")"}.legal-doc>p[data-num="4"]:before{content:"(" counter(le-n4, hangul) ")"}.legal-doc .tableWrapper{overflow-x:auto}.legal-doc table{border-collapse:collapse;width:100%;margin:.6em 0;font-size:.93em}.legal-doc td,.legal-doc th{vertical-align:top;border:1px solid #555;padding:3px 6px}.legal-doc td p,.legal-doc th p{margin:0}.legal-doc .selectedCell{background:#e5edff}.le-root{background:#eef0f3;grid-template-columns:minmax(0,1fr) 260px;gap:16px;padding:16px;font:14px/1.5 system-ui,sans-serif;display:grid}.le-main{min-width:0}.le-toolbar{z-index:1;background:#fff;border-radius:8px;flex-wrap:wrap;gap:4px;max-width:760px;margin:0 auto 8px;padding:6px;display:flex;position:sticky;top:0;box-shadow:0 1px 2px #0001}.le-toolbar button,.le-toolbar .le-btn{cursor:pointer;font:inherit;background:#fff;border:1px solid #d0d4da;border-radius:6px;padding:4px 10px}.le-toolbar button:hover{background:#f3f5f8}.le-spacer{flex:1}.le-main .legal-doc{min-height:900px;box-shadow:0 1px 4px #0002}.le-var{color:#1d3f94;background:#e5edff;border-radius:3px;padding:0 3px}.le-var--empty{color:#8a5a00;background:#fff1c2}.le-panel{background:#fff;border-radius:8px;align-self:start;padding:14px;position:sticky;top:16px;box-shadow:0 1px 2px #0001}.le-panel h3{margin:0 0 10px;font-size:14px}.le-panel label{color:#444;gap:3px;margin-bottom:10px;font-size:13px;display:grid}.le-panel input{font:inherit;border:1px solid #d0d4da;border-radius:6px;padding:6px 8px}.le-hint{color:#777;margin:0;font-size:13px}@media (width<=800px){.le-root{grid-template-columns:1fr}.legal-doc{padding:32px 24px}}@media print{.le-printing body *{visibility:hidden}.le-printing .legal-doc,.le-printing .legal-doc *{visibility:visible}.le-printing .legal-doc{width:100%;max-width:none;box-shadow:none;padding:0;position:absolute;top:0;left:0}.le-printing .le-var{color:inherit;background:0 0;padding:0}.le-printing .le-var--empty{color:#0000;border-bottom:1px solid #000}}
|
|
1
|
+
@counter-style le-circled{system:fixed;symbols:"①" "②" "③" "④" "⑤" "⑥" "⑦" "⑧" "⑨" "⑩" "⑪" "⑫" "⑬" "⑭" "⑮" "⑯" "⑰" "⑱" "⑲" "⑳";suffix:" "}.legal-doc{counter-reset:le-article le-n1 le-n2 le-n3 le-n4;color:#111;background:#fff;outline:none;max-width:760px;margin:0 auto;padding:56px 64px;font-family:Noto Serif KR,Nanum Myeongjo,Batang,serif;font-size:15px;line-height:1.85}.legal-doc h1{text-align:center;letter-spacing:.25em;margin:0 0 1.6em;font-size:1.6em}.legal-doc h2{counter-increment:le-article;margin:1.6em 0 .3em;font-size:1em}.legal-doc h2:before{content:"제" counter(le-article) "조 "}.legal-doc p{margin:0 0 .4em}.legal-doc ol{margin:0 0 .4em;padding-left:1.8em;list-style:le-circled}.legal-doc ol ol{list-style:decimal}.legal-doc li p{margin:0}.legal-doc h3{text-align:center;letter-spacing:.3em;margin:1.8em 0 .8em;font-size:1.05em}.legal-doc>h1,.legal-doc>h2,.legal-doc>h3{counter-set:le-n1 le-n2 le-n3 le-n4}.legal-doc>p[data-num]{padding-left:calc(var(--le-num-indent,0em) + 2em);text-indent:-2em}.legal-doc>p[data-num]:before{text-indent:0;min-width:2em;display:inline-block}.legal-doc>p[data-num="1"]{counter-increment:le-n1;counter-set:le-n2 le-n3 le-n4}.legal-doc>p[data-num="2"]{counter-increment:le-n2;counter-set:le-n3 le-n4;--le-num-indent:1.2em}.legal-doc>p[data-num="3"]{counter-increment:le-n3;counter-set:le-n4;--le-num-indent:2.4em}.legal-doc>p[data-num="4"]{counter-increment:le-n4;--le-num-indent:3.6em}.legal-doc>p[data-num="1"]:before{content:counter(le-n1) "."}.legal-doc>p[data-num="2"]:before{content:counter(le-n2, hangul) "."}.legal-doc>p[data-num="3"]:before{content:"(" counter(le-n3) ")"}.legal-doc>p[data-num="4"]:before{content:"(" counter(le-n4, hangul) ")"}.legal-doc .le-cite{color:inherit;text-underline-offset:3px;cursor:pointer;-webkit-text-decoration:underline dotted #3a56b4;text-decoration:underline dotted #3a56b4}.legal-doc .le-cite:hover{background:#eef2ff}.legal-doc .tableWrapper{overflow-x:auto}.legal-doc table{border-collapse:collapse;width:100%;margin:.6em 0;font-size:.93em}.legal-doc td,.legal-doc th{vertical-align:top;border:1px solid #555;padding:3px 6px}.legal-doc td p,.legal-doc th p{margin:0}.legal-doc .selectedCell{background:#e5edff}.le-root{background:#eef0f3;grid-template-columns:minmax(0,1fr) 260px;gap:16px;padding:16px;font:14px/1.5 system-ui,sans-serif;display:grid}.le-main{min-width:0}.le-toolbar{z-index:1;background:#fff;border-radius:8px;flex-wrap:wrap;gap:4px;max-width:760px;margin:0 auto 8px;padding:6px;display:flex;position:sticky;top:0;box-shadow:0 1px 2px #0001}.le-toolbar button,.le-toolbar .le-btn{cursor:pointer;font:inherit;background:#fff;border:1px solid #d0d4da;border-radius:6px;padding:4px 10px}.le-toolbar button:hover{background:#f3f5f8}.le-spacer{flex:1}.le-main .legal-doc{min-height:900px;box-shadow:0 1px 4px #0002}.le-var{color:#1d3f94;background:#e5edff;border-radius:3px;padding:0 3px}.le-var--empty{color:#8a5a00;background:#fff1c2}.le-panel{background:#fff;border-radius:8px;align-self:start;padding:14px;position:sticky;top:16px;box-shadow:0 1px 2px #0001}.le-panel h3{margin:0 0 10px;font-size:14px}.le-panel label{color:#444;gap:3px;margin-bottom:10px;font-size:13px;display:grid}.le-panel input{font:inherit;border:1px solid #d0d4da;border-radius:6px;padding:6px 8px}.le-hint{color:#777;margin:0;font-size:13px}.le-cases{border-top:1px solid #e3e6ea;margin-top:16px;padding-top:12px}.le-cases-bar{gap:4px;margin-bottom:8px;display:flex}.le-cases-bar input{flex:1;min-width:0}.le-cases-bar button,.le-case-list button{cursor:pointer;font:inherit;background:#fff;border:1px solid #d0d4da;border-radius:6px;padding:4px 8px}.le-case-list{gap:6px;margin:8px 0 0;padding:0;list-style:none;display:grid}.le-case-list li{gap:2px;display:grid}.le-case-list button{text-align:left;gap:2px;font-size:12px;display:grid}.le-case-list button:hover:not(:disabled){background:#f3f5f8}.le-case-list span,.le-case-list small{color:#555}.le-case-list a{color:#3a56b4;justify-self:end;font-size:12px}@media (width<=800px){.le-root{grid-template-columns:1fr}.legal-doc{padding:32px 24px}}@media print{.le-printing body *{visibility:hidden}.le-printing .legal-doc,.le-printing .legal-doc *{visibility:visible}.le-printing .legal-doc{width:100%;max-width:none;box-shadow:none;padding:0;position:absolute;top:0;left:0}.le-printing .le-var{color:inherit;background:0 0;padding:0}.le-printing .le-cite{background:0 0;text-decoration:none}.le-printing .le-var--empty{color:#0000;border-bottom:1px solid #000}}
|
|
2
2
|
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "legal-doc-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "법률문서(계약서·소장·고소장) 생성용 React 에디터 — 조항·번호 자동 매김, {{변수}} 채우기, 한글(hwp·hwpx)·Word(docx) 열기·저장",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,7 +9,20 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://github.com/hwondev/legal-doc-editor#readme",
|
|
11
11
|
"bugs": "https://github.com/hwondev/legal-doc-editor/issues",
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"react",
|
|
14
|
+
"editor",
|
|
15
|
+
"legal",
|
|
16
|
+
"contract",
|
|
17
|
+
"korean",
|
|
18
|
+
"hwp",
|
|
19
|
+
"hwpx",
|
|
20
|
+
"docx",
|
|
21
|
+
"tiptap",
|
|
22
|
+
"법률문서",
|
|
23
|
+
"계약서",
|
|
24
|
+
"소장"
|
|
25
|
+
],
|
|
13
26
|
"type": "module",
|
|
14
27
|
"files": [
|
|
15
28
|
"dist"
|