legal-doc-editor 0.2.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 CHANGED
@@ -67,15 +67,28 @@ export default function Page() {
67
67
 
68
68
  날짜(`2026. 9. 2.`), 전화번호, 계좌번호는 인용으로 보지 않아요. 다만 인용 인식은 글자 모양만 보는 것이라 놓치거나 잘못 잡을 수 있고, 링크가 가리키는 조문이 실제로 맞는지는 직접 확인해 주세요.
69
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
+
70
80
  ### 인지액·송달료 자동 계산
71
81
 
72
82
  `<LegalEditor autoFees />`로 켜면, 입력값 `소가`(또는 `소송목적의 값`)를 넣었을 때 이름이 `인지액`·`송달료`로 시작하는 변수(예: `{{인지액 산정 필요}}`)를 계산해서 채워요. 직접 입력한 값이 있으면 그 값을 써요.
83
+ 지급명령 신청서(`payment-order` 템플릿)는 `청구금액`을 넣으면 `{{독촉절차 인지대}}`·`{{독촉절차 송달료}}`·`{{독촉절차비용}}`(두 금액 합계)을 채워요.
73
84
 
74
85
  | 항목 | 기준 | 근거 |
75
86
  | --- | --- | --- |
76
87
  | 인지액 | 1천만원 미만 × 0.5% / 1억원 미만 × 0.45% + 5천원 / 10억원 미만 × 0.4% + 5만5천원 / 그 이상 × 0.35% + 55만5천원. 100원 미만 버림, 최저 1천원 | 「민사소송 등 인지법」 제2조 |
77
88
  | 전자소송 인지액 | 위 계산식 × 0.9 후 100원 미만 버림, 최저 900원 — `autoFees={{ electronic: true }}` | 인지법 제16조, [전자소송포털](https://ecfs.scourt.go.kr/psp/link.on?m=PSP007P01) |
78
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 |
79
92
  | 1회 송달료 | 5,500원 (2025. 6. 1.부터). 바뀌면 `autoFees={{ unitFee: 5600 }}`처럼 덮어쓰기 | 법원 공지 |
80
93
 
81
94
  > 계산 결과는 **참고용**이에요. 제1심 소장 기준이고 항소·상고·반소는 계산하지 않아요. 실제 납부액은 법원 안내를 확인하세요.
@@ -95,9 +108,11 @@ export default function Page() {
95
108
 
96
109
  | 이름 | 설명 |
97
110
  | --- | --- |
98
- | `<LegalEditor content values onChange onValuesChange editable autoFees />` | 에디터 컴포넌트 |
111
+ | `<LegalEditor content values onChange onValuesChange editable autoFees searchCases />` | 에디터 컴포넌트 |
112
+ | `formatCaseCitation(result)` | 판례 검색 결과 → `대법원 2016. 4. 28. 선고 2015다12345 판결` |
99
113
  | `calcStampFee(소가, { electronic })` | 소장 인지액(원) |
100
- | `calcServiceFee({ parties, procedure, unitFee })` | 송달료(원). `procedure`: 소액·단독·합의·항소·상고·조정 |
114
+ | `calcPaymentOrderStampFee(청구금액)` | 지급명령 신청서 인지대(원) 종이 신청 기준 |
115
+ | `calcServiceFee({ parties, procedure, unitFee })` | 송달료(원). `procedure`: 소액·단독·합의·항소·상고·조정·독촉 |
101
116
  | `withCourtFees(names, values, options)` | 비어 있는 `인지액…`·`송달료…` 변수를 계산값으로 채운 입력값 |
102
117
  | `templates` | 기본 문서 템플릿 `{ id, title, description, html }[]` — `<LegalEditor content={templates[0].html} />` |
103
118
  | `toKoreanAmount(n)` | `37200000` → `삼천칠백이십만` |
@@ -1,4 +1,5 @@
1
1
  import { type Values } from './variable';
2
+ import { type CaseResult } from './citation';
2
3
  import { type CourtFeeOptions } from './fees';
3
4
  import './legal.css';
4
5
  export interface LegalEditorProps {
@@ -9,7 +10,9 @@ export interface LegalEditorProps {
9
10
  onChange?: (html: string) => void;
10
11
  onValuesChange?: (values: Values) => void;
11
12
  editable?: boolean;
12
- /** 입력값에 `소가`가 있으면 `인지액…`·`송달료…` 변수를 계산해 채움 (직접 입력한 값이 우선, 참고용) */
13
+ /** 입력값에 `소가`(소장)나 `청구금액`(지급명령)이 있으면 인지액·송달료 변수를 계산해 채움 (직접 입력한 값이 우선, 참고용) */
13
14
  autoFees?: boolean | CourtFeeOptions;
15
+ /** 판례 검색 함수. 넘기면 오른쪽에 판례 검색 칸이 생기고, 결과를 누르면 인용 문구가 커서 위치에 들어감 */
16
+ searchCases?: (query: string) => Promise<CaseResult[]>;
14
17
  }
15
- export declare function LegalEditor({ content, values: initial, onChange, onValuesChange, editable, autoFees }: LegalEditorProps): import("react").JSX.Element;
18
+ export declare function LegalEditor({ content, values: initial, onChange, onValuesChange, editable, autoFees, searchCases }: LegalEditorProps): import("react").JSX.Element;
@@ -11,5 +11,23 @@ export interface Citation {
11
11
  }
12
12
  /** 본문 글에서 판례·법령 인용을 찾음. 문서를 바꾸지 않고 위치만 알려줌 */
13
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;
14
32
  /** 판례·법령 인용에 밑줄과 국가법령정보센터 링크를 덧입힘. 문서 데이터(HTML·JSON)는 그대로 */
15
33
  export declare const CitationLink: Extension<any, any>;
package/dist/fees.d.ts CHANGED
@@ -8,9 +8,17 @@ import type { Values } from './variable';
8
8
  export declare function calcStampFee(amount: number, opts?: {
9
9
  electronic?: boolean;
10
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;
11
18
  /**
12
19
  * 송달료 회분. 「송달료규칙의 시행에 따른 업무처리요령」(재판예규 재일 87-4) 별표 1
13
20
  * 출처: https://www.easylaw.go.kr/CSP/CnpClsMain.laf?csmSeq=568&ccfNo=2&cciNo=4&cnpClsNo=3 (확인 2026-09-15)
21
+ * 독촉(지급명령) 6회분: 생활법령정보 지급명령 신청서 작성 (위 calcPaymentOrderStampFee 출처와 같음)
14
22
  */
15
23
  export declare const SERVICE_ROUNDS: {
16
24
  readonly 소액: 10;
@@ -19,6 +27,7 @@ export declare const SERVICE_ROUNDS: {
19
27
  readonly 항소: 12;
20
28
  readonly 상고: 8;
21
29
  readonly 조정: 5;
30
+ readonly 독촉: 6;
22
31
  };
23
32
  export type Procedure = keyof typeof SERVICE_ROUNDS;
24
33
  /**
@@ -42,7 +51,8 @@ export interface CourtFeeOptions {
42
51
  unitFee?: number;
43
52
  }
44
53
  /**
45
- * 입력값에 `소가`(또는 `소송목적의 값`)가 있으면 이름이 `인지액`·`송달료`로 시작하는 변수를 계산값으로 채움.
46
- * 직접 입력한 값은 덮어쓰지 않음. 제1심 소장 기준(소가 3천만원 이하 소액 10회분, 넘으면 15회분)
54
+ * 비어 있는 비용 변수를 계산값으로 채움. 직접 입력한 값은 덮어쓰지 않음.
55
+ * - 소장: `소가`(또는 `소송목적의 값`) 이름이 `인지액`·`송달료`로 시작하는 변수 (소가 3천만원 이하 소액 10회분, 넘으면 15회분)
56
+ * - 지급명령: `청구금액` → `독촉절차 인지대`(소장의 10분의 1)·`독촉절차 송달료`(6회분)·`독촉절차비용`(두 금액 합계)
47
57
  */
48
58
  export declare function withCourtFees(names: string[], values: Values, opts?: CourtFeeOptions): Values;
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ 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 } from './citation';
6
- export type { Citation } from './citation';
7
- export { calcStampFee, calcServiceFee, withCourtFees, SERVICE_ROUNDS, SERVICE_UNIT_FEE, SMALL_CLAIM_LIMIT } from './fees';
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
8
  export type { CourtFeeOptions, Procedure } from './fees';
9
9
  export { toKoreanAmount, formatAmount, parseAmount } from './amount';
10
10
  export { templates } from './templates';
package/dist/index.js CHANGED
@@ -106,7 +106,7 @@ var v = 4, y = o.create({
106
106
  };
107
107
  }
108
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 te(e) {
109
+ function k(e) {
110
110
  let t = [];
111
111
  for (let n of e.matchAll(E)) {
112
112
  let e = n.groups, r = e.caseNo ?? e.bare;
@@ -122,10 +122,14 @@ function te(e) {
122
122
  }
123
123
  return t;
124
124
  }
125
- var ne = new d("citation"), k = (e) => {
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) => {
126
130
  let t = [];
127
131
  return e.descendants((e, n) => {
128
- if (e.isText) for (let r of te(e.text)) t.push(f.inline(n + r.from, n + r.to, {
132
+ if (e.isText) for (let r of k(e.text)) t.push(f.inline(n + r.from, n + r.to, {
129
133
  nodeName: "a",
130
134
  class: "le-cite",
131
135
  href: r.url,
@@ -134,17 +138,17 @@ var ne = new d("citation"), k = (e) => {
134
138
  rel: "noopener noreferrer"
135
139
  }));
136
140
  }), p.create(e, t);
137
- }, A = o.create({
141
+ }, te = o.create({
138
142
  name: "citationLink",
139
143
  addProseMirrorPlugins() {
140
144
  return [new u({
141
- key: ne,
145
+ key: j,
142
146
  state: {
143
- init: (e, { doc: t }) => k(t),
144
- apply: (e, t) => e.docChanged ? k(e.doc) : t
147
+ init: (e, { doc: t }) => M(t),
148
+ apply: (e, t) => e.docChanged ? M(e.doc) : t
145
149
  },
146
150
  props: {
147
- decorations: (e) => ne.getState(e),
151
+ decorations: (e) => j.getState(e),
148
152
  handleClick: (e, t, n) => {
149
153
  let r = n.target.closest?.("a.le-cite");
150
154
  return !r || e.editable && !n.ctrlKey && !n.metaKey ? !1 : (window.open(r.getAttribute("href"), "_blank", "noopener,noreferrer"), !0);
@@ -155,80 +159,99 @@ var ne = new d("citation"), k = (e) => {
155
159
  });
156
160
  //#endregion
157
161
  //#region src/fees.ts
158
- function j(e, t = {}) {
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) {
159
172
  if (!(e > 0)) return 0;
160
- let [n, r] = e < 1e7 ? [50, 0] : e < 1e8 ? [45, 5e3] : e < 1e9 ? [40, 55e3] : [35, 555e3], i = e * n + r * 1e4;
161
- return t.electronic ? i < 1e7 ? 900 : Math.floor(i * 9 / 1e7) * 100 : i < 1e7 ? 1e3 : Math.floor(i / 1e6) * 100;
173
+ let t = P(e);
174
+ return t < 1e8 ? 1e3 : Math.floor(t / 1e7) * 100;
162
175
  }
163
- var M = {
176
+ var I = {
164
177
  소액: 10,
165
178
  단독: 15,
166
179
  합의: 15,
167
180
  항소: 12,
168
181
  상고: 8,
169
- 조정: 5
170
- }, N = 5500;
171
- function P({ parties: e, procedure: t, unitFee: n = N }) {
172
- return e * M[t] * n;
182
+ 조정: 5,
183
+ 독촉: 6
184
+ }, L = 5500;
185
+ function R({ parties: e, procedure: t, unitFee: n = L }) {
186
+ return e * I[t] * n;
173
187
  }
174
- var re = 3e7, F = (e) => `${e.toLocaleString("ko-KR")}원`, ie = (e = "") => Number(e.replace(/[^\d]/g, "")) || 0;
175
- function I(e, t, n = {}) {
176
- let r = e.find((e) => /^(소가|소송목적의\s*값)/.test(e)), i = r ? ie(t[r]) : 0;
177
- if (!i) return t;
178
- let a = F(j(i, n)), o = F(P({
179
- parties: n.parties ?? 2,
180
- procedure: i <= 3e7 ? "소액" : "단독",
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 ? "소액" : "단독",
181
196
  unitFee: n.unitFee
182
- })), s = { ...t };
183
- for (let n of e) t[n]?.trim() || (/^인지(액|대)/.test(n) ? s[n] = a : /^송달료/.test(n) && (s[n] = o));
184
- return s;
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;
185
208
  }
186
209
  //#endregion
187
210
  //#region src/amount.ts
188
- var ae = "영일이삼사오육칠팔구", oe = [
211
+ var oe = "영일이삼사오육칠팔구", se = [
189
212
  "천",
190
213
  "백",
191
214
  "십",
192
215
  ""
193
- ], se = [
216
+ ], ce = [
194
217
  "",
195
218
  "만",
196
219
  "억",
197
220
  "조",
198
221
  "경"
199
222
  ];
200
- function L(e) {
223
+ function z(e) {
201
224
  if (!Number.isSafeInteger(e)) throw RangeError(`금액은 정수여야 해요: ${e}`);
202
225
  if (e === 0) return "영";
203
- if (e < 0) return `마이너스 ${L(-e)}`;
226
+ if (e < 0) return `마이너스 ${z(-e)}`;
204
227
  let t = "", n = String(e).padStart(Math.ceil(String(e).length / 4) * 4, "0");
205
228
  for (let e = 0; e < n.length / 4; e++) {
206
229
  let r = n.slice(e * 4, e * 4 + 4);
207
230
  if (r !== "0000") {
208
- for (let e = 0; e < 4; e++) r[e] !== "0" && (t += ae[+r[e]] + oe[e]);
209
- t += se[n.length / 4 - 1 - e];
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];
210
233
  }
211
234
  }
212
235
  return t;
213
236
  }
214
- var R = (e) => Number(e.replace(/[^\d]/g, "")) || 0;
215
- function z(e, t = "소장") {
237
+ var B = (e) => Number(e.replace(/[^\d]/g, "")) || 0;
238
+ function V(e, t = "소장") {
216
239
  let n = e.toLocaleString("ko-KR");
217
- return t === "소장" ? `금 ${n}원` : `금 ${L(e)} 원정(₩${n})`;
240
+ return t === "소장" ? `금 ${n}원` : `금 ${z(e)} 원정(₩${n})`;
218
241
  }
219
242
  //#endregion
220
243
  //#region src/io.ts
221
- var ce = (e) => e < 20 ? String.fromCharCode(9312 + e) : `${e + 1}.`, B = "가나다라마바사아자차카타파하", V = (e) => {
244
+ var le = (e) => e < 20 ? String.fromCharCode(9312 + e) : `${e + 1}.`, H = "가나다라마바사아자차카타파하", U = (e) => {
222
245
  let t = "";
223
- for (; e > 0; e = Math.floor(e / 14)) t = B[--e % 14] + t;
246
+ for (; e > 0; e = Math.floor(e / 14)) t = H[--e % 14] + t;
224
247
  return t;
225
- }, le = (e, t) => [
248
+ }, ue = (e, t) => [
226
249
  `${t}.`,
227
- `${V(t)}.`,
250
+ `${U(t)}.`,
228
251
  `(${t})`,
229
- `(${V(t)})`
252
+ `(${U(t)})`
230
253
  ][e - 1];
231
- function H(e, t) {
254
+ function W(e, t) {
232
255
  let n = [], r = 0, i = (e = []) => e.map((e) => {
233
256
  if (e.type === "hardBreak") return {
234
257
  text: "",
@@ -248,7 +271,7 @@ function H(e, t) {
248
271
  bold: !0
249
272
  })), o = (e, t) => e.content?.forEach((r, a) => r.content?.forEach((r, s) => {
250
273
  if (r.type === "orderedList" || r.type === "bulletList") return o(r, t + 1);
251
- let c = a + (e.attrs?.start ?? 1), l = s > 0 ? "" : e.type === "bulletList" ? "•" : t === 0 ? ce(c - 1) : `${c}.`;
274
+ let c = a + (e.attrs?.start ?? 1), l = s > 0 ? "" : e.type === "bulletList" ? "•" : t === 0 ? le(c - 1) : `${c}.`;
252
275
  n.push({
253
276
  kind: "para",
254
277
  label: l,
@@ -281,7 +304,7 @@ function H(e, t) {
281
304
  runs: a(i(t.content))
282
305
  }) : t.type === "orderedList" || t.type === "bulletList" ? o(t, 0) : l ? (s[l]++, s.fill(0, l + 1), n.push({
283
306
  kind: "para",
284
- label: le(l, s[l]),
307
+ label: ue(l, s[l]),
285
308
  depth: l,
286
309
  runs: i(t.content)
287
310
  })) : t.type === "paragraph" ? n.push({
@@ -304,29 +327,29 @@ function H(e, t) {
304
327
  };
305
328
  return c(e.content), n;
306
329
  }
307
- var ue = (e) => e.replace(/[&<>"]/g, (e) => `&#${e.charCodeAt(0)};`), U = (e) => e.map((e) => {
330
+ var de = (e) => e.replace(/[&<>"]/g, (e) => `&#${e.charCodeAt(0)};`), G = (e) => e.map((e) => {
308
331
  if (e.br) return "<br>";
309
- let t = ue(e.text);
332
+ let t = de(e.text);
310
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;
311
334
  }).join("");
312
- function W(e, t) {
313
- return H(e, t).map((e) => {
335
+ function K(e, t) {
336
+ return W(e, t).map((e) => {
314
337
  if (e.kind === "table") {
315
- let t = (e) => `<td${e.colspan > 1 ? ` colspan="${e.colspan}"` : ""}${e.rowspan > 1 ? ` rowspan="${e.rowspan}"` : ""}>${U(e.runs)}</td>`;
338
+ let t = (e) => `<td${e.colspan > 1 ? ` colspan="${e.colspan}"` : ""}${e.rowspan > 1 ? ` rowspan="${e.rowspan}"` : ""}>${G(e.runs)}</td>`;
316
339
  return `<table>${e.rows.map((e) => `<tr>${e.map(t).join("")}</tr>`).join("")}</table>`;
317
340
  }
318
- let t = (e.label ? e.kind === "article" ? `<b>${e.label}</b> ` : `${e.label} ` : "") + U(e.runs);
341
+ let t = (e.label ? e.kind === "article" ? `<b>${e.label}</b> ` : `${e.label} ` : "") + G(e.runs);
319
342
  if (e.kind === "title") return `<h1>${t}</h1>`;
320
343
  if (e.kind === "section") return `<h3>${t}</h3>`;
321
344
  let n = e.label ? e.depth - 1 : e.depth;
322
345
  return n > 0 ? `<p style="margin-left:${n * 2}em">${t}</p>` : `<p>${t}</p>`;
323
346
  }).join("\n");
324
347
  }
325
- function G(e, t) {
326
- for (let n of H(e, t)) if (n.kind === "title") return n.runs.map((e) => e.text).join("").trim() || "문서";
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() || "문서";
327
350
  return "문서";
328
351
  }
329
- async function K(e, t) {
352
+ async function fe(e, t) {
330
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({
331
354
  text: e.text,
332
355
  break: e.br ? 1 : void 0,
@@ -334,7 +357,7 @@ async function K(e, t) {
334
357
  italics: e.italics,
335
358
  strike: e.strike,
336
359
  underline: e.underline ? {} : void 0
337
- }), p = H(e, t).map((e) => e.kind === "table" ? new s({
360
+ }), p = W(e, t).map((e) => e.kind === "table" ? new s({
338
361
  width: {
339
362
  size: 100,
340
363
  type: d.PERCENTAGE
@@ -358,7 +381,7 @@ async function K(e, t) {
358
381
  })] : [], ...e.runs.map(f)]
359
382
  }));
360
383
  return a.toBlob(new r({
361
- title: G(e, t),
384
+ title: q(e, t),
362
385
  styles: {
363
386
  default: { document: { run: {
364
387
  font: "바탕",
@@ -383,18 +406,18 @@ async function K(e, t) {
383
406
  sections: [{ children: p }]
384
407
  }));
385
408
  }
386
- async function q(e, t) {
387
- let { htmlToHwpx: n } = await import("hwp-convert"), r = await n(W(e, t), { title: G(e, t) });
409
+ async function pe(e, t) {
410
+ let { htmlToHwpx: n } = await import("hwp-convert"), r = await n(K(e, t), { title: q(e, t) });
388
411
  return new Blob([r], { type: "application/hwp+zip" });
389
412
  }
390
- var J = /^\s*제\s*\d+\s*조(?:의\s*\d+)?\s*/, Y = /^\s*[①-⑳]\s*/, X = /^\s*(\d+)\.\s*/, de = /^(청구취지|청구원인|신청취지|신청이유|고소취지|고소이유|고발취지|고발이유|입증방법|증명방법|증거방법|증거서류|첨부서류|당사자관계|사건개요|고소인|피고소인|범죄사실|증거자료|관련사건의수사및재판여부|기타)$/, fe = (e) => e.length <= 20 && (/^[가-힣](\s+[가-힣]){3,7}$/.test(e) || de.test(e.replace(/\s/g, ""))), pe = /\[\s*([^[\]{}<>"\d=]{1,20}?)\s*\]/g, me = [
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 = [
391
414
  [/^\s*(\d{1,2})\.(?!\d)\s*/, Number],
392
- [/^\s*([가나다라마바사아자차카타파하])\.\s*/, (e) => B.indexOf(e) + 1],
415
+ [/^\s*([가나다라마바사아자차카타파하])\.\s*/, (e) => H.indexOf(e) + 1],
393
416
  [/^\s*\((\d{1,2})\)\s*/, Number],
394
- [/^\s*\(([가나다라마바사아자차카타파하])\)\s*/, (e) => B.indexOf(e) + 1]
417
+ [/^\s*\(([가나다라마바사아자차카타파하])\)\s*/, (e) => H.indexOf(e) + 1]
395
418
  ];
396
- function he(e) {
397
- for (let [t, [n, r]] of me.entries()) {
419
+ function ye(e) {
420
+ for (let [t, [n, r]] of ve.entries()) {
398
421
  let i = e.match(n);
399
422
  if (i) return {
400
423
  level: t + 1,
@@ -404,20 +427,20 @@ function he(e) {
404
427
  }
405
428
  return null;
406
429
  }
407
- function Z(e, t) {
430
+ function X(e, t) {
408
431
  let n = typeof t == "number" ? t : e.textContent.match(t)[0].length, r = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_TEXT);
409
432
  for (let e = r.nextNode(); e && n > 0; e = r.nextNode()) {
410
433
  let t = Math.min(n, e.data.length);
411
434
  e.data = e.data.slice(t), n -= t;
412
435
  }
413
436
  }
414
- function Q(e) {
437
+ function Z(e) {
415
438
  let t = new DOMParser().parseFromString(e, "text/html");
416
439
  for (let e of [...t.body.children]) e.tagName === "P" && !e.textContent.trim() && !e.querySelector("img") && e.remove();
417
440
  let n = t.createTreeWalker(t.body, NodeFilter.SHOW_TEXT);
418
441
  for (let e = n.nextNode(); e; e = n.nextNode()) {
419
442
  let t = e.parentElement?.closest("p,li,td,th,h1,h2,h3")?.textContent?.trim();
420
- e.data = e.data.replace(pe, (e, n) => e.trim() === t ? e : `{{${n.trim()}}}`);
443
+ e.data = e.data.replace(_e, (e, n) => e.trim() === t ? e : `{{${n.trim()}}}`);
421
444
  }
422
445
  let r = t.body.firstElementChild, i = r?.textContent.trim() ?? "";
423
446
  if (!t.querySelector("h1") && r?.tagName === "P" && i.length <= 30 && !/[.다]$/.test(i) && !J.test(i)) {
@@ -439,62 +462,62 @@ function Q(e) {
439
462
  ];
440
463
  for (let e of [...t.body.children]) {
441
464
  let n = e.textContent ?? "", r = n.replace(/\s+/g, " ").trim();
442
- if (e.tagName === "P" && fe(r)) {
465
+ if (e.tagName === "P" && ge(r)) {
443
466
  let n = t.createElement("h3");
444
467
  n.textContent = r, e.replaceWith(n), a = null, o.fill(0), s.fill(0);
445
468
  } else if (e.tagName === "P" && J.test(n)) {
446
469
  let n = t.createElement("h2");
447
- n.innerHTML = e.innerHTML, Z(n, J), e.replaceWith(n), a = null, o.fill(0), s.fill(0);
448
- } else if (e.tagName === "P" && Y.test(n)) {
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)) {
449
472
  let r = n.trim().charCodeAt(0) - 9311;
450
- (!a || a.children.length + Number(a.getAttribute("start") ?? 1) !== r) && (e.before(a = t.createElement("ol")), r > 1 && a.setAttribute("start", String(r))), Z(e, Y);
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);
451
474
  let i = t.createElement("li");
452
475
  i.append(e), a.append(i);
453
- } else if (e.tagName === "P" && a && X.test(n) && Number(n.match(X)[1]) === (a.lastElementChild.querySelector(":scope > ol")?.children.length ?? 0) + 1) {
476
+ } else if (e.tagName === "P" && a && Y.test(n) && Number(n.match(Y)[1]) === (a.lastElementChild.querySelector(":scope > ol")?.children.length ?? 0) + 1) {
454
477
  let n = a.lastElementChild, r = n.querySelector(":scope > ol") ?? n.appendChild(t.createElement("ol"));
455
- Z(e, X);
478
+ X(e, Y);
456
479
  let i = t.createElement("li");
457
480
  i.append(e), r.append(i);
458
481
  } else {
459
482
  a = null;
460
- let t = e.tagName === "P" ? he(n) : null;
483
+ let t = e.tagName === "P" ? ye(n) : null;
461
484
  if (!t) continue;
462
- 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), Z(e, t.length), e.setAttribute("data-num", String(t.level))) : s[t.level] = +(t.n === 1);
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);
463
486
  }
464
487
  }
465
488
  return t.body.innerHTML;
466
489
  }
467
- async function ge(e) {
490
+ async function be(e) {
468
491
  let { default: t } = await import("mammoth"), { value: n } = await t.convertToHtml({ arrayBuffer: await e.arrayBuffer() });
469
- return Q(n);
492
+ return Z(n);
470
493
  }
471
- async function $(e) {
494
+ async function xe(e) {
472
495
  let { detectFormat: t, hwpToHwpx: n, HwpxReader: r } = await import("hwp-convert"), i = new Uint8Array(await e.arrayBuffer());
473
496
  t(i) === "hwp" && (i = new Uint8Array(await n(i)));
474
497
  let a = new r();
475
- return await a.loadFromArrayBuffer(i.buffer), Q(await a.extractHtml());
498
+ return await a.loadFromArrayBuffer(i.buffer), Z(await a.extractHtml());
476
499
  }
477
- function _e(e) {
478
- return /\.docx$/i.test(e.name) ? ge(e) : /\.hwpx?$/i.test(e.name) ? $(e) : Promise.reject(/* @__PURE__ */ Error(".docx, .hwp, .hwpx 파일만 열 수 있어요"));
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 파일만 열 수 있어요"));
479
502
  }
480
503
  //#endregion
481
504
  //#region src/LegalEditor.tsx
482
- function ve(e) {
505
+ function $(e) {
483
506
  let t = /* @__PURE__ */ new Set();
484
507
  return e.state.doc.descendants((e) => {
485
508
  e.type.name === "variable" && t.add(e.attrs.name);
486
509
  }), [...t];
487
510
  }
488
- function ye() {
511
+ function Se() {
489
512
  let e = document.documentElement;
490
513
  e.classList.add("le-printing"), window.addEventListener("afterprint", () => e.classList.remove("le-printing"), { once: !0 }), window.print();
491
514
  }
492
- function be(e, t) {
515
+ function Ce(e, t) {
493
516
  let n = document.createElement("a");
494
517
  n.href = URL.createObjectURL(e), n.download = t, n.click(), setTimeout(() => URL.revokeObjectURL(n.href), 1e3);
495
518
  }
496
- function xe({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, editable: u = !0, autoFees: d }) {
497
- let [f, p] = t(s), [_, v] = t([]), b = d ? I(_, f, d === !0 ? {} : d) : f, x = r({
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({
498
521
  extensions: [
499
522
  i.configure({ heading: { levels: [
500
523
  1,
@@ -504,69 +527,79 @@ function xe({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
504
527
  a,
505
528
  ee,
506
529
  y,
507
- A
530
+ te
508
531
  ],
509
532
  content: g(o),
510
533
  editable: u,
511
534
  immediatelyRender: !1,
512
535
  editorProps: { attributes: { class: "legal-doc" } },
513
- onCreate: ({ editor: e }) => v(ve(e)),
536
+ onCreate: ({ editor: e }) => b($(e)),
514
537
  onUpdate: ({ editor: e }) => {
515
- v(ve(e)), c?.(e.getHTML());
538
+ b($(e)), c?.(e.getHTML());
516
539
  }
517
540
  });
518
541
  e(() => {
519
- x && (x.storage.variable.values = b, x.storage.variable.views.forEach((e) => e()));
520
- }, [x, b]);
521
- let S = (e, t) => {
542
+ O && (O.storage.variable.values = D, O.storage.variable.views.forEach((e) => e()));
543
+ }, [O, D]);
544
+ let k = (e, t) => {
522
545
  let n = {
523
- ...f,
546
+ ...p,
524
547
  [e]: t
525
548
  };
526
- p(n), l?.(n);
527
- }, C = () => {
549
+ _(n), l?.(n);
550
+ }, j = () => {
528
551
  let e = window.prompt("변수 이름 (예: 갑, 계약일)")?.replace(/[{}<>"]/g, "").trim();
529
- e && x?.chain().focus().insertContent({
552
+ e && O?.chain().focus().insertContent({
530
553
  type: "variable",
531
554
  attrs: { name: e }
532
555
  }).run();
533
- }, w = async (e) => {
556
+ }, M = async (e) => {
534
557
  let t = e.target.files?.[0];
535
- if (e.target.value = "", t && x) try {
536
- x.commands.setContent(g(await _e(t)), { emitUpdate: !0 });
558
+ if (e.target.value = "", t && O) try {
559
+ O.commands.setContent(g(await Q(t)), { emitUpdate: !0 });
537
560
  } catch (e) {
538
561
  window.alert(`파일을 열 수 없어요: ${e.message}`);
539
562
  }
540
- }, T = async (e, t) => {
541
- let n = x.state.doc.firstChild?.textContent.trim() || "문서";
542
- be(await e(x.getJSON(), b), `${n}.${t}`);
543
- }, E = () => x.chain().focus(), D = (e) => E().setNumbering(x.getAttributes("paragraph").num === e ? null : e).run(), O = (e) => {
544
- let { from: t, to: n } = x.state.selection, r = R(x.state.doc.textBetween(t, n));
545
- r && E().insertContentAt({
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({
546
579
  from: t,
547
580
  to: n
548
- }, z(r, e)).run();
581
+ }, V(r, e)).run();
549
582
  };
550
583
  return /* @__PURE__ */ h("div", {
551
584
  className: "le-root",
552
585
  children: [/* @__PURE__ */ h("div", {
553
586
  className: "le-main",
554
- children: [u && x && /* @__PURE__ */ h("div", {
587
+ children: [u && O && /* @__PURE__ */ h("div", {
555
588
  className: "le-toolbar",
556
589
  children: [
557
590
  /* @__PURE__ */ m("button", {
558
591
  type: "button",
559
- onClick: () => E().toggleHeading({ level: 1 }).run(),
592
+ onClick: () => F().toggleHeading({ level: 1 }).run(),
560
593
  children: "제목"
561
594
  }),
562
595
  /* @__PURE__ */ m("button", {
563
596
  type: "button",
564
- onClick: () => E().toggleHeading({ level: 3 }).run(),
597
+ onClick: () => F().toggleHeading({ level: 3 }).run(),
565
598
  children: "소제목"
566
599
  }),
567
600
  /* @__PURE__ */ m("button", {
568
601
  type: "button",
569
- onClick: () => E().toggleHeading({ level: 2 }).run(),
602
+ onClick: () => F().toggleHeading({ level: 2 }).run(),
570
603
  children: "조"
571
604
  }),
572
605
  [
@@ -577,37 +610,37 @@ function xe({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
577
610
  ].map((e, t) => /* @__PURE__ */ m("button", {
578
611
  type: "button",
579
612
  title: "번호 문단 — Tab·Shift+Tab 단계 변경, 맨 앞 Backspace 해제",
580
- onClick: () => D(t + 1),
613
+ onClick: () => I(t + 1),
581
614
  children: e
582
615
  }, e)),
583
616
  /* @__PURE__ */ m("button", {
584
617
  type: "button",
585
- onClick: () => E().toggleOrderedList().run(),
618
+ onClick: () => F().toggleOrderedList().run(),
586
619
  children: "항"
587
620
  }),
588
621
  /* @__PURE__ */ m("button", {
589
622
  type: "button",
590
- onClick: () => E().sinkListItem("listItem").run(),
623
+ onClick: () => F().sinkListItem("listItem").run(),
591
624
  children: "호 →"
592
625
  }),
593
626
  /* @__PURE__ */ m("button", {
594
627
  type: "button",
595
- onClick: () => E().liftListItem("listItem").run(),
628
+ onClick: () => F().liftListItem("listItem").run(),
596
629
  children: "← 내어쓰기"
597
630
  }),
598
631
  /* @__PURE__ */ m("button", {
599
632
  type: "button",
600
- onClick: () => E().toggleBold().run(),
633
+ onClick: () => F().toggleBold().run(),
601
634
  children: /* @__PURE__ */ m("b", { children: "B" })
602
635
  }),
603
636
  /* @__PURE__ */ h("button", {
604
637
  type: "button",
605
- onClick: C,
638
+ onClick: j,
606
639
  children: ["{{ }}", " 변수"]
607
640
  }),
608
641
  /* @__PURE__ */ m("button", {
609
642
  type: "button",
610
- onClick: () => E().insertTable({
643
+ onClick: () => F().insertTable({
611
644
  rows: 3,
612
645
  cols: 3,
613
646
  withHeaderRow: !1
@@ -617,13 +650,13 @@ function xe({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
617
650
  /* @__PURE__ */ m("button", {
618
651
  type: "button",
619
652
  title: "선택한 금액 → 금 37,200,000원",
620
- onClick: () => O("소장"),
653
+ onClick: () => L("소장"),
621
654
  children: "금액"
622
655
  }),
623
656
  /* @__PURE__ */ m("button", {
624
657
  type: "button",
625
658
  title: "선택한 금액 → 금 삼천칠백이십만 원정(₩37,200,000)",
626
- onClick: () => O("계약서"),
659
+ onClick: () => L("계약서"),
627
660
  children: "금액(한글)"
628
661
  }),
629
662
  /* @__PURE__ */ m("span", { className: "le-spacer" }),
@@ -633,31 +666,31 @@ function xe({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
633
666
  type: "file",
634
667
  accept: ".docx,.hwp,.hwpx",
635
668
  hidden: !0,
636
- onChange: w
669
+ onChange: M
637
670
  })]
638
671
  }),
639
672
  /* @__PURE__ */ m("button", {
640
673
  type: "button",
641
- onClick: () => T(K, "docx"),
674
+ onClick: () => N(fe, "docx"),
642
675
  children: "Word 저장"
643
676
  }),
644
677
  /* @__PURE__ */ m("button", {
645
678
  type: "button",
646
- onClick: () => T(q, "hwpx"),
679
+ onClick: () => N(pe, "hwpx"),
647
680
  children: "한글 저장"
648
681
  }),
649
682
  /* @__PURE__ */ m("button", {
650
683
  type: "button",
651
- onClick: ye,
684
+ onClick: Se,
652
685
  children: "인쇄 · PDF"
653
686
  })
654
687
  ]
655
- }), /* @__PURE__ */ m(n, { editor: x })]
688
+ }), /* @__PURE__ */ m(n, { editor: O })]
656
689
  }), /* @__PURE__ */ h("aside", {
657
690
  className: "le-panel",
658
691
  children: [
659
692
  /* @__PURE__ */ m("h3", { children: "입력값" }),
660
- _.length === 0 && /* @__PURE__ */ h("p", {
693
+ v.length === 0 && /* @__PURE__ */ h("p", {
661
694
  className: "le-hint",
662
695
  children: [
663
696
  "본문에 ",
@@ -665,18 +698,66 @@ function xe({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
665
698
  "처럼 입력하면 변수가 생겨요."
666
699
  ]
667
700
  }),
668
- _.map((e) => /* @__PURE__ */ h("label", { children: [e, /* @__PURE__ */ m("input", {
669
- value: f[e] ?? "",
670
- placeholder: f[e] ? void 0 : b[e],
671
- onChange: (t) => S(e, t.target.value)
672
- })] }, e))
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
+ })
673
754
  ]
674
755
  })]
675
756
  });
676
757
  }
677
758
  //#endregion
678
759
  //#region src/templates.ts
679
- var Se = [
760
+ var Te = [
680
761
  {
681
762
  id: "complaint-loan",
682
763
  title: "소장 (대여금)",
@@ -703,4 +784,4 @@ var Se = [
703
784
  }
704
785
  ];
705
786
  //#endregion
706
- export { A as CitationLink, xe as LegalEditor, y as Numbering, M as SERVICE_ROUNDS, N as SERVICE_UNIT_FEE, re as SMALL_CLAIM_LIMIT, ee as Variable, P as calcServiceFee, j as calcStampFee, _ as fillTemplate, te as findCitations, z as formatAmount, ge as fromDocx, _e as fromFile, $ as fromHwp, Q as normalizeLegalHtml, R as parseAmount, Se as templates, g as toChips, K as toDocx, q as toHwpx, L as toKoreanAmount, W as toPlainHtml, I as withCourtFees };
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 .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}@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}}
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.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "법률문서(계약서·소장·고소장) 생성용 React 에디터 — 조항·번호 자동 매김, {{변수}} 채우기, 한글(hwp·hwpx)·Word(docx) 열기·저장",
5
5
  "license": "MIT",
6
6
  "repository": {