legal-doc-editor 0.4.0 → 0.6.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 +23 -6
- package/dist/LegalEditor.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +150 -113
- package/dist/io.d.ts +17 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
- **조·항·호 자동 번호**: 조를 넣거나 옮기면 제1조, ①, 1. 번호가 알아서 다시 매겨져요
|
|
8
8
|
- **`{{변수}}` 채우기**: 본문에 `{{갑}}`을 입력하면 입력칸이 생기고, 값을 넣으면 문서에 반영돼요
|
|
9
|
-
- **한글(.hwp·.hwpx)·Word(.docx) 열기·저장**: 일반 문단으로 쓴 계약서도 "제N조", "①", "1." 패턴을 인식해서 조·항·호 구조로 바꿔요
|
|
10
|
-
- 저장은 `.docx
|
|
9
|
+
- **한글(.hwp·.hwpx)·Word(.docx·.doc) 열기·저장**: 일반 문단으로 쓴 계약서도 "제N조", "①", "1." 패턴을 인식해서 조·항·호 구조로 바꿔요
|
|
10
|
+
- 저장은 `.docx`, `.hwpx`, `.hwp`(HWP 5.0)예요. `.hwpx`는 한글 2014 이상에서 열려요
|
|
11
|
+
- 옛 Word `.doc`(97–2003)는 열기만 돼요. 글자와 표만 옮기고 굵게 같은 글자 서식과 그림은 옮기지 않아요
|
|
11
12
|
- 파일 변환 라이브러리는 필요할 때만 불러와서 에디터 본체 번들은 가벼워요
|
|
12
13
|
- **판례·법령 인용 링크**: 본문의 `민법 제750조`, `대법원 2016. 4. 28. 선고 2015다12345 판결`, `2024가단157033` 같은 인용을 알아보고 [국가법령정보센터](https://www.law.go.kr) 링크를 붙여요 (API 키가 필요 없어요)
|
|
13
14
|
- **조항 라이브러리**: 비밀유지·계약해지·관할법원 같은 자주 쓰는 조항을 골라 커서 위치에 넣어요. 조 번호는 알아서 다시 매겨져요
|
|
@@ -68,6 +69,21 @@ export default function Page() {
|
|
|
68
69
|
|
|
69
70
|
날짜(`2026. 9. 2.`), 전화번호, 계좌번호는 인용으로 보지 않아요. 다만 인용 인식은 글자 모양만 보는 것이라 놓치거나 잘못 잡을 수 있고, 링크가 가리키는 조문이 실제로 맞는지는 직접 확인해 주세요.
|
|
70
71
|
|
|
72
|
+
### .hwp 저장
|
|
73
|
+
|
|
74
|
+
툴바의 **한글(.hwp) 저장**은 `.hwpx`를 만든 뒤 [@rhwp/core](https://github.com/edwardkim/rhwp)(Rust→WASM, MIT)로 HWP 5.0 파일로 바꿔요.
|
|
75
|
+
|
|
76
|
+
- WASM이 약 10MB라 **처음 `.hwp`로 저장할 때만** 불러와요. 에디터 본체 번들에는 들어가지 않아요.
|
|
77
|
+
- Vite를 쓰면 사전 번들링이 WASM 경로를 잃지 않도록 제외해 주세요.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// vite.config.ts
|
|
81
|
+
export default defineConfig({ optimizeDeps: { exclude: ['@rhwp/core'] } })
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- 그래도 WASM을 못 찾으면 `node_modules/@rhwp/core/rhwp_bg.wasm`을 정적 폴더에 복사하고 주소를 넘겨요: `<LegalEditor hwpWasmUrl="/rhwp_bg.wasm" />`
|
|
85
|
+
- 만든 파일은 다시 읽었을 때 제목·조항·번호·표·글자가 그대로인지 검사하지만, **한글 프로그램에서 열리는지는 직접 확인**해 주세요.
|
|
86
|
+
|
|
71
87
|
### 조항 라이브러리
|
|
72
88
|
|
|
73
89
|
`clauses`를 넘기면 오른쪽 패널에 조항 목록이 생겨요. 검색칸에서 제목이나 분류로 찾고, 누르면 커서 위치에 조항이 들어가요. 조항 안의 `{{변수}}`는 입력칸에 바로 나타나고 뒤쪽 조 번호는 알아서 밀려요.
|
|
@@ -123,7 +139,7 @@ import { LegalEditor, clauses } from 'legal-doc-editor'
|
|
|
123
139
|
|
|
124
140
|
| 이름 | 설명 |
|
|
125
141
|
| --- | --- |
|
|
126
|
-
| `<LegalEditor content values onChange onValuesChange editable autoFees searchCases clauses />` | 에디터 컴포넌트 |
|
|
142
|
+
| `<LegalEditor content values onChange onValuesChange editable autoFees searchCases clauses hwpWasmUrl />` | 에디터 컴포넌트 |
|
|
127
143
|
| `clauses` | 기본 조항 목록 `{ id, title, category, html }[]` |
|
|
128
144
|
| `formatCaseCitation(result)` | 판례 검색 결과 → `대법원 2016. 4. 28. 선고 2015다12345 판결` |
|
|
129
145
|
| `calcStampFee(소가, { electronic })` | 소장 인지액(원) |
|
|
@@ -137,8 +153,9 @@ import { LegalEditor, clauses } from 'legal-doc-editor'
|
|
|
137
153
|
| `fillTemplate(html, values)` | 변수를 채운 완성본 HTML. `.legal-doc` 안에서 렌더하면 번호가 붙어요 |
|
|
138
154
|
| `toDocx(editor.getJSON(), values)` | `.docx` Blob 생성 |
|
|
139
155
|
| `toHwpx(editor.getJSON(), values)` | `.hwpx` Blob 생성 |
|
|
156
|
+
| `toHwp(editor.getJSON(), values, { wasm })` | `.hwp`(HWP 5.0) Blob 생성. `wasm`은 WASM 주소나 바이트(선택) |
|
|
140
157
|
| `toPlainHtml(editor.getJSON(), values)` | 번호가 텍스트로 들어간 독립 HTML |
|
|
141
|
-
| `fromFile(file)` | `.docx` / `.hwp` / `.hwpx`를 에디터용 HTML로 변환 (`fromDocx`, `fromHwp`도 있음) |
|
|
158
|
+
| `fromFile(file)` | `.docx` / `.doc` / `.hwp` / `.hwpx`를 에디터용 HTML로 변환 (`fromDocx`, `fromDoc`, `fromHwp`도 있음) |
|
|
142
159
|
| `normalizeLegalHtml(html)` | "제N조 / ① / 1." 문단을 조·항·호 구조로 변환 |
|
|
143
160
|
| `Variable` | 다른 Tiptap 에디터에 넣어 쓸 수 있는 변수 확장 |
|
|
144
161
|
| `Numbering` | 번호 문단 확장 (`setNumbering(1~4 \| null)`, Tab·Shift+Tab 단계 변경, 맨 앞 Backspace 해제) |
|
|
@@ -148,8 +165,8 @@ import { LegalEditor, clauses } from 'legal-doc-editor'
|
|
|
148
165
|
## 로드맵
|
|
149
166
|
|
|
150
167
|
- [x] HWP / HWPX 열기, HWPX 저장
|
|
151
|
-
- [
|
|
152
|
-
- [
|
|
168
|
+
- [x] `.hwp` 바이너리로 바로 저장 ([rhwp](https://github.com/edwardkim/rhwp))
|
|
169
|
+
- [x] 옛 Word `.doc` 열기 ([@file-viewer/doc](https://www.npmjs.com/package/@file-viewer/doc))
|
|
153
170
|
- [ ] 표
|
|
154
171
|
- [ ] 조항 라이브러리(자주 쓰는 조항 끼워 넣기)
|
|
155
172
|
|
package/dist/LegalEditor.d.ts
CHANGED
|
@@ -17,5 +17,7 @@ export interface LegalEditorProps {
|
|
|
17
17
|
searchCases?: (query: string) => Promise<CaseResult[]>;
|
|
18
18
|
/** 조항 목록. 넘기면 오른쪽에 조항 라이브러리가 생기고, 누르면 커서 위치에 조항이 들어감 (기본 제공 목록: `clauses`) */
|
|
19
19
|
clauses?: Clause[];
|
|
20
|
+
/** .hwp 저장에 쓰는 @rhwp/core WASM 주소. 번들러가 WASM 경로를 못 찾을 때만 지정 (예: '/rhwp_bg.wasm') */
|
|
21
|
+
hwpWasmUrl?: string;
|
|
20
22
|
}
|
|
21
|
-
export declare function LegalEditor({ content, values: initial, onChange, onValuesChange, editable, autoFees, searchCases, clauses }: LegalEditorProps): import("react").JSX.Element;
|
|
23
|
+
export declare function LegalEditor({ content, values: initial, onChange, onValuesChange, editable, autoFees, searchCases, clauses, hwpWasmUrl }: LegalEditorProps): import("react").JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export { clauses } from './clauses';
|
|
|
12
12
|
export type { Clause } from './clauses';
|
|
13
13
|
export type { Template } from './templates';
|
|
14
14
|
export type { Values } from './variable';
|
|
15
|
-
export { toDocx, toHwpx, toPlainHtml, fromDocx, fromHwp, fromFile, normalizeLegalHtml } from './io';
|
|
15
|
+
export { toDocx, toHwpx, toHwp, toPlainHtml, fromDocx, fromDoc, fromHwp, fromFile, normalizeLegalHtml } from './io';
|
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ var g = s.create({
|
|
|
62
62
|
})];
|
|
63
63
|
}
|
|
64
64
|
}), _ = (e) => e.replace(/\{\{([^{}<>"]+)\}\}/g, (e, t) => `<span data-var="${t.trim().replace(/&/g, "&")}"></span>`);
|
|
65
|
-
function
|
|
65
|
+
function ee(e, t) {
|
|
66
66
|
let n = new DOMParser().parseFromString(_(e), "text/html");
|
|
67
67
|
return n.querySelectorAll("span[data-var]").forEach((e) => {
|
|
68
68
|
let n = e.getAttribute("data-var");
|
|
@@ -71,7 +71,7 @@ function v(e, t) {
|
|
|
71
71
|
}
|
|
72
72
|
//#endregion
|
|
73
73
|
//#region src/numbering.ts
|
|
74
|
-
var
|
|
74
|
+
var v = 4, y = o.create({
|
|
75
75
|
name: "numbering",
|
|
76
76
|
priority: 1e3,
|
|
77
77
|
addGlobalAttributes() {
|
|
@@ -81,7 +81,7 @@ var y = 4, b = o.create({
|
|
|
81
81
|
default: null,
|
|
82
82
|
parseHTML: (e) => {
|
|
83
83
|
let t = Number(e.getAttribute("data-num"));
|
|
84
|
-
return t >= 1 && t <=
|
|
84
|
+
return t >= 1 && t <= v ? t : null;
|
|
85
85
|
},
|
|
86
86
|
renderHTML: (e) => e.num ? { "data-num": e.num } : {}
|
|
87
87
|
} }
|
|
@@ -93,7 +93,7 @@ var y = 4, b = o.create({
|
|
|
93
93
|
addKeyboardShortcuts() {
|
|
94
94
|
let e = () => this.editor.getAttributes("paragraph").num, t = (t) => () => {
|
|
95
95
|
let n = e();
|
|
96
|
-
return !!n && this.editor.commands.setNumbering(Math.min(
|
|
96
|
+
return !!n && this.editor.commands.setNumbering(Math.min(v, Math.max(1, n + t)));
|
|
97
97
|
};
|
|
98
98
|
return {
|
|
99
99
|
Tab: t(1),
|
|
@@ -105,31 +105,31 @@ var y = 4, b = o.create({
|
|
|
105
105
|
Enter: () => !!e() && this.editor.state.selection.$from.parent.content.size === 0 && this.editor.commands.setNumbering(null)
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
}),
|
|
109
|
-
function
|
|
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*)?(?:판결|결정|명령)`, te = String.raw`(?:[가-힣]+(?<![은는이가을를와과로며고여하한된])\s+){0,4}[가-힣]+에\s*관한\s*[가-힣]*법률?|[가-힣]+법률?`, w = String.raw`(?<lawName>${te})\s*제\s*(?<jo>\d+)\s*조(?:\s*의\s*(?<ui>\d+))?(?:\s*제\s*\d+\s*(?:항|호))*`, T = new RegExp(String.raw`${C}|${w}|(?<bare>${b})`, "g"), E = /^[방불합위적탈편문어용수요기비사입공]법$/, D = (...e) => `https://www.law.go.kr/${e.map(encodeURIComponent).join("/")}`;
|
|
109
|
+
function O(e) {
|
|
110
110
|
let t = [];
|
|
111
|
-
for (let n of e.matchAll(
|
|
111
|
+
for (let n of e.matchAll(T)) {
|
|
112
112
|
let e = n.groups, r = e.caseNo ?? e.bare;
|
|
113
|
-
if (!r &&
|
|
113
|
+
if (!r && E.test(e.lawName)) continue;
|
|
114
114
|
let i = n.index ?? 0;
|
|
115
115
|
t.push({
|
|
116
116
|
type: r ? "case" : "law",
|
|
117
117
|
text: n[0],
|
|
118
118
|
from: i,
|
|
119
119
|
to: i + n[0].length,
|
|
120
|
-
url: r ? `https://www.law.go.kr/LSW/precSc.do?query=${encodeURIComponent(r)}` :
|
|
120
|
+
url: r ? `https://www.law.go.kr/LSW/precSc.do?query=${encodeURIComponent(r)}` : D("법령", e.lawName.replace(/\s+/g, " "), `제${e.jo}조${e.ui ? `의${e.ui}` : ""}`)
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
return t;
|
|
124
124
|
}
|
|
125
|
-
function
|
|
125
|
+
function k({ court: e, date: t, caseNo: n, kind: r = "" }) {
|
|
126
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
127
|
return o === "판결" ? `${e} ${a} 선고 ${n} 판결` : `${e} ${a}자 ${n} ${o}`;
|
|
128
128
|
}
|
|
129
|
-
var
|
|
129
|
+
var A = new d("citation"), j = (e) => {
|
|
130
130
|
let t = [];
|
|
131
131
|
return e.descendants((e, n) => {
|
|
132
|
-
if (e.isText) for (let r of
|
|
132
|
+
if (e.isText) for (let r of O(e.text)) t.push(f.inline(n + r.from, n + r.to, {
|
|
133
133
|
nodeName: "a",
|
|
134
134
|
class: "le-cite",
|
|
135
135
|
href: r.url,
|
|
@@ -138,17 +138,17 @@ var j = new d("citation"), M = (e) => {
|
|
|
138
138
|
rel: "noopener noreferrer"
|
|
139
139
|
}));
|
|
140
140
|
}), p.create(e, t);
|
|
141
|
-
},
|
|
141
|
+
}, M = o.create({
|
|
142
142
|
name: "citationLink",
|
|
143
143
|
addProseMirrorPlugins() {
|
|
144
144
|
return [new u({
|
|
145
|
-
key:
|
|
145
|
+
key: A,
|
|
146
146
|
state: {
|
|
147
|
-
init: (e, { doc: t }) =>
|
|
148
|
-
apply: (e, t) => e.docChanged ?
|
|
147
|
+
init: (e, { doc: t }) => j(t),
|
|
148
|
+
apply: (e, t) => e.docChanged ? j(e.doc) : t
|
|
149
149
|
},
|
|
150
150
|
props: {
|
|
151
|
-
decorations: (e) =>
|
|
151
|
+
decorations: (e) => A.getState(e),
|
|
152
152
|
handleClick: (e, t, n) => {
|
|
153
153
|
let r = n.target.closest?.("a.le-cite");
|
|
154
154
|
return !r || e.editable && !n.ctrlKey && !n.metaKey ? !1 : (window.open(r.getAttribute("href"), "_blank", "noopener,noreferrer"), !0);
|
|
@@ -159,21 +159,21 @@ var j = new d("citation"), M = (e) => {
|
|
|
159
159
|
});
|
|
160
160
|
//#endregion
|
|
161
161
|
//#region src/fees.ts
|
|
162
|
-
function
|
|
162
|
+
function N(e, t = {}) {
|
|
163
163
|
if (!(e > 0)) return 0;
|
|
164
|
-
let n =
|
|
164
|
+
let n = P(e);
|
|
165
165
|
return t.electronic ? n < 1e7 ? 900 : Math.floor(n * 9 / 1e7) * 100 : n < 1e7 ? 1e3 : Math.floor(n / 1e6) * 100;
|
|
166
166
|
}
|
|
167
|
-
function
|
|
167
|
+
function P(e) {
|
|
168
168
|
let [t, n] = e < 1e7 ? [50, 0] : e < 1e8 ? [45, 5e3] : e < 1e9 ? [40, 55e3] : [35, 555e3];
|
|
169
169
|
return e * t + n * 1e4;
|
|
170
170
|
}
|
|
171
|
-
function
|
|
171
|
+
function F(e) {
|
|
172
172
|
if (!(e > 0)) return 0;
|
|
173
|
-
let t =
|
|
173
|
+
let t = P(e);
|
|
174
174
|
return t < 1e8 ? 1e3 : Math.floor(t / 1e7) * 100;
|
|
175
175
|
}
|
|
176
|
-
var
|
|
176
|
+
var I = {
|
|
177
177
|
소액: 10,
|
|
178
178
|
단독: 15,
|
|
179
179
|
합의: 15,
|
|
@@ -181,23 +181,23 @@ var L = {
|
|
|
181
181
|
상고: 8,
|
|
182
182
|
조정: 5,
|
|
183
183
|
독촉: 6
|
|
184
|
-
},
|
|
185
|
-
function
|
|
186
|
-
return e *
|
|
184
|
+
}, L = 5500;
|
|
185
|
+
function R({ parties: e, procedure: t, unitFee: n = L }) {
|
|
186
|
+
return e * I[t] * n;
|
|
187
187
|
}
|
|
188
|
-
var
|
|
188
|
+
var z = 3e7, ne = (e) => `${e.toLocaleString("ko-KR")}원`, B = (e = "") => Number(e.replace(/[^\d]/g, "")) || 0;
|
|
189
189
|
function re(e, t, n = {}) {
|
|
190
|
-
let r = (n) =>
|
|
191
|
-
for (let a of e) n.test(a) && !t[a]?.trim() && (i[a] =
|
|
190
|
+
let r = (n) => B(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] = ne(r));
|
|
192
192
|
}, o = n.parties ?? 2, s = r(/^(소가|소송목적의\s*값)/);
|
|
193
|
-
s && (a(/^인지(액|대)/,
|
|
193
|
+
s && (a(/^인지(액|대)/, N(s, n)), a(/^송달료/, R({
|
|
194
194
|
parties: o,
|
|
195
195
|
procedure: s <= 3e7 ? "소액" : "단독",
|
|
196
196
|
unitFee: n.unitFee
|
|
197
197
|
})));
|
|
198
198
|
let c = r(/^청구금액/);
|
|
199
199
|
if (c && e.some((e) => /^독촉절차/.test(e))) {
|
|
200
|
-
let e = /^독촉절차\s*인지(액|대)/, t = /^독촉절차\s*송달료/, i =
|
|
200
|
+
let e = /^독촉절차\s*인지(액|대)/, t = /^독촉절차\s*송달료/, i = F(c), s = R({
|
|
201
201
|
parties: o,
|
|
202
202
|
procedure: "독촉",
|
|
203
203
|
unitFee: n.unitFee
|
|
@@ -234,24 +234,24 @@ function V(e) {
|
|
|
234
234
|
}
|
|
235
235
|
return t;
|
|
236
236
|
}
|
|
237
|
-
var
|
|
238
|
-
function
|
|
237
|
+
var se = (e) => Number(e.replace(/[^\d]/g, "")) || 0;
|
|
238
|
+
function H(e, t = "소장") {
|
|
239
239
|
let n = e.toLocaleString("ko-KR");
|
|
240
240
|
return t === "소장" ? `금 ${n}원` : `금 ${V(e)} 원정(₩${n})`;
|
|
241
241
|
}
|
|
242
242
|
//#endregion
|
|
243
243
|
//#region src/io.ts
|
|
244
|
-
var
|
|
244
|
+
var ce = (e) => e < 20 ? String.fromCharCode(9312 + e) : `${e + 1}.`, U = "가나다라마바사아자차카타파하", W = (e) => {
|
|
245
245
|
let t = "";
|
|
246
|
-
for (; e > 0; e = Math.floor(e / 14)) t =
|
|
246
|
+
for (; e > 0; e = Math.floor(e / 14)) t = U[--e % 14] + t;
|
|
247
247
|
return t;
|
|
248
|
-
},
|
|
248
|
+
}, le = (e, t) => [
|
|
249
249
|
`${t}.`,
|
|
250
|
-
`${
|
|
250
|
+
`${W(t)}.`,
|
|
251
251
|
`(${t})`,
|
|
252
|
-
`(${
|
|
252
|
+
`(${W(t)})`
|
|
253
253
|
][e - 1];
|
|
254
|
-
function
|
|
254
|
+
function G(e, t) {
|
|
255
255
|
let n = [], r = 0, i = (e = []) => e.map((e) => {
|
|
256
256
|
if (e.type === "hardBreak") return {
|
|
257
257
|
text: "",
|
|
@@ -271,7 +271,7 @@ function K(e, t) {
|
|
|
271
271
|
bold: !0
|
|
272
272
|
})), o = (e, t) => e.content?.forEach((r, a) => r.content?.forEach((r, s) => {
|
|
273
273
|
if (r.type === "orderedList" || r.type === "bulletList") return o(r, t + 1);
|
|
274
|
-
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 ? ce(c - 1) : `${c}.`;
|
|
275
275
|
n.push({
|
|
276
276
|
kind: "para",
|
|
277
277
|
label: l,
|
|
@@ -304,7 +304,7 @@ function K(e, t) {
|
|
|
304
304
|
runs: a(i(t.content))
|
|
305
305
|
}) : t.type === "orderedList" || t.type === "bulletList" ? o(t, 0) : l ? (s[l]++, s.fill(0, l + 1), n.push({
|
|
306
306
|
kind: "para",
|
|
307
|
-
label:
|
|
307
|
+
label: le(l, s[l]),
|
|
308
308
|
depth: l,
|
|
309
309
|
runs: i(t.content)
|
|
310
310
|
})) : t.type === "paragraph" ? n.push({
|
|
@@ -327,29 +327,29 @@ function K(e, t) {
|
|
|
327
327
|
};
|
|
328
328
|
return c(e.content), n;
|
|
329
329
|
}
|
|
330
|
-
var
|
|
330
|
+
var K = (e) => e.replace(/[&<>"]/g, (e) => `&#${e.charCodeAt(0)};`), ue = (e) => e.map((e) => {
|
|
331
331
|
if (e.br) return "<br>";
|
|
332
|
-
let t =
|
|
332
|
+
let t = K(e.text);
|
|
333
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;
|
|
334
334
|
}).join("");
|
|
335
|
-
function
|
|
336
|
-
return
|
|
335
|
+
function de(e, t) {
|
|
336
|
+
return G(e, t).map((e) => {
|
|
337
337
|
if (e.kind === "table") {
|
|
338
|
-
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}"` : ""}>${ue(e.runs)}</td>`;
|
|
339
339
|
return `<table>${e.rows.map((e) => `<tr>${e.map(t).join("")}</tr>`).join("")}</table>`;
|
|
340
340
|
}
|
|
341
|
-
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} ` : "") + ue(e.runs);
|
|
342
342
|
if (e.kind === "title") return `<h1>${t}</h1>`;
|
|
343
343
|
if (e.kind === "section") return `<h3>${t}</h3>`;
|
|
344
344
|
let n = e.label ? e.depth - 1 : e.depth;
|
|
345
345
|
return n > 0 ? `<p style="margin-left:${n * 2}em">${t}</p>` : `<p>${t}</p>`;
|
|
346
346
|
}).join("\n");
|
|
347
347
|
}
|
|
348
|
-
function
|
|
349
|
-
for (let n of
|
|
348
|
+
function fe(e, t) {
|
|
349
|
+
for (let n of G(e, t)) if (n.kind === "title") return n.runs.map((e) => e.text).join("").trim() || "문서";
|
|
350
350
|
return "문서";
|
|
351
351
|
}
|
|
352
|
-
async function
|
|
352
|
+
async function pe(e, t) {
|
|
353
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({
|
|
354
354
|
text: e.text,
|
|
355
355
|
break: e.br ? 1 : void 0,
|
|
@@ -357,7 +357,7 @@ async function fe(e, t) {
|
|
|
357
357
|
italics: e.italics,
|
|
358
358
|
strike: e.strike,
|
|
359
359
|
underline: e.underline ? {} : void 0
|
|
360
|
-
}), p =
|
|
360
|
+
}), p = G(e, t).map((e) => e.kind === "table" ? new s({
|
|
361
361
|
width: {
|
|
362
362
|
size: 100,
|
|
363
363
|
type: d.PERCENTAGE
|
|
@@ -381,7 +381,7 @@ async function fe(e, t) {
|
|
|
381
381
|
})] : [], ...e.runs.map(f)]
|
|
382
382
|
}));
|
|
383
383
|
return a.toBlob(new r({
|
|
384
|
-
title:
|
|
384
|
+
title: fe(e, t),
|
|
385
385
|
styles: {
|
|
386
386
|
default: { document: { run: {
|
|
387
387
|
font: "바탕",
|
|
@@ -406,18 +406,31 @@ async function fe(e, t) {
|
|
|
406
406
|
sections: [{ children: p }]
|
|
407
407
|
}));
|
|
408
408
|
}
|
|
409
|
-
async function
|
|
410
|
-
let { htmlToHwpx: n } = await import("hwp-convert"), r = await n(
|
|
409
|
+
async function q(e, t) {
|
|
410
|
+
let { htmlToHwpx: n } = await import("hwp-convert"), r = await n(de(e, t), { title: fe(e, t) });
|
|
411
411
|
return new Blob([r], { type: "application/hwp+zip" });
|
|
412
412
|
}
|
|
413
|
-
var J
|
|
413
|
+
var J;
|
|
414
|
+
async function me(e, t, n = {}) {
|
|
415
|
+
let r = new Uint8Array(await (await q(e, t)).arrayBuffer());
|
|
416
|
+
J ??= import("@rhwp/core").then(async (e) => (await e.default(n.wasm === void 0 ? void 0 : { module_or_path: n.wasm }), e));
|
|
417
|
+
let { HwpDocument: i } = await J.catch((e) => {
|
|
418
|
+
throw J = void 0, e;
|
|
419
|
+
}), a = new i(r);
|
|
420
|
+
try {
|
|
421
|
+
return new Blob([a.exportHwp()], { type: "application/x-hwp" });
|
|
422
|
+
} finally {
|
|
423
|
+
a.free();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
var Y = /^\s*제\s*\d+\s*조(?:의\s*\d+)?\s*/, he = /^\s*[①-⑳]\s*/, X = /^\s*(\d+)\.\s*/, ge = /^(청구취지|청구원인|신청취지|신청이유|고소취지|고소이유|고발취지|고발이유|입증방법|증명방법|증거방법|증거서류|첨부서류|당사자관계|사건개요|고소인|피고소인|범죄사실|증거자료|관련사건의수사및재판여부|기타)$/, _e = (e) => e.length <= 20 && (/^[가-힣](\s+[가-힣]){3,7}$/.test(e) || ge.test(e.replace(/\s/g, ""))), ve = /\[\s*([^[\]{}<>"\d=]{1,20}?)\s*\]/g, ye = [
|
|
414
427
|
[/^\s*(\d{1,2})\.(?!\d)\s*/, Number],
|
|
415
|
-
[/^\s*([가나다라마바사아자차카타파하])\.\s*/, (e) =>
|
|
428
|
+
[/^\s*([가나다라마바사아자차카타파하])\.\s*/, (e) => U.indexOf(e) + 1],
|
|
416
429
|
[/^\s*\((\d{1,2})\)\s*/, Number],
|
|
417
|
-
[/^\s*\(([가나다라마바사아자차카타파하])\)\s*/, (e) =>
|
|
430
|
+
[/^\s*\(([가나다라마바사아자차카타파하])\)\s*/, (e) => U.indexOf(e) + 1]
|
|
418
431
|
];
|
|
419
|
-
function
|
|
420
|
-
for (let [t, [n, r]] of
|
|
432
|
+
function be(e) {
|
|
433
|
+
for (let [t, [n, r]] of ye.entries()) {
|
|
421
434
|
let i = e.match(n);
|
|
422
435
|
if (i) return {
|
|
423
436
|
level: t + 1,
|
|
@@ -427,23 +440,23 @@ function ye(e) {
|
|
|
427
440
|
}
|
|
428
441
|
return null;
|
|
429
442
|
}
|
|
430
|
-
function
|
|
443
|
+
function Z(e, t) {
|
|
431
444
|
let n = typeof t == "number" ? t : e.textContent.match(t)[0].length, r = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_TEXT);
|
|
432
445
|
for (let e = r.nextNode(); e && n > 0; e = r.nextNode()) {
|
|
433
446
|
let t = Math.min(n, e.data.length);
|
|
434
447
|
e.data = e.data.slice(t), n -= t;
|
|
435
448
|
}
|
|
436
449
|
}
|
|
437
|
-
function
|
|
450
|
+
function Q(e) {
|
|
438
451
|
let t = new DOMParser().parseFromString(e, "text/html");
|
|
439
452
|
for (let e of [...t.body.children]) e.tagName === "P" && !e.textContent.trim() && !e.querySelector("img") && e.remove();
|
|
440
453
|
let n = t.createTreeWalker(t.body, NodeFilter.SHOW_TEXT);
|
|
441
454
|
for (let e = n.nextNode(); e; e = n.nextNode()) {
|
|
442
455
|
let t = e.parentElement?.closest("p,li,td,th,h1,h2,h3")?.textContent?.trim();
|
|
443
|
-
e.data = e.data.replace(
|
|
456
|
+
e.data = e.data.replace(ve, (e, n) => e.trim() === t ? e : `{{${n.trim()}}}`);
|
|
444
457
|
}
|
|
445
458
|
let r = t.body.firstElementChild, i = r?.textContent.trim() ?? "";
|
|
446
|
-
if (!t.querySelector("h1") && r?.tagName === "P" && i.length <= 30 && !/[.다]$/.test(i) && !
|
|
459
|
+
if (!t.querySelector("h1") && r?.tagName === "P" && i.length <= 30 && !/[.다]$/.test(i) && !Y.test(i)) {
|
|
447
460
|
let e = t.createElement("h1");
|
|
448
461
|
e.textContent = i, r.replaceWith(e);
|
|
449
462
|
}
|
|
@@ -462,62 +475,76 @@ function Z(e) {
|
|
|
462
475
|
];
|
|
463
476
|
for (let e of [...t.body.children]) {
|
|
464
477
|
let n = e.textContent ?? "", r = n.replace(/\s+/g, " ").trim();
|
|
465
|
-
if (e.tagName === "P" &&
|
|
478
|
+
if (e.tagName === "P" && _e(r)) {
|
|
466
479
|
let n = t.createElement("h3");
|
|
467
480
|
n.textContent = r, e.replaceWith(n), a = null, o.fill(0), s.fill(0);
|
|
468
|
-
} else if (e.tagName === "P" &&
|
|
481
|
+
} else if (e.tagName === "P" && Y.test(n)) {
|
|
469
482
|
let n = t.createElement("h2");
|
|
470
|
-
n.innerHTML = e.innerHTML,
|
|
471
|
-
} else if (e.tagName === "P" &&
|
|
483
|
+
n.innerHTML = e.innerHTML, Z(n, Y), e.replaceWith(n), a = null, o.fill(0), s.fill(0);
|
|
484
|
+
} else if (e.tagName === "P" && he.test(n)) {
|
|
472
485
|
let r = n.trim().charCodeAt(0) - 9311;
|
|
473
|
-
(!a || a.children.length + Number(a.getAttribute("start") ?? 1) !== r) && (e.before(a = t.createElement("ol")), r > 1 && a.setAttribute("start", String(r))),
|
|
486
|
+
(!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, he);
|
|
474
487
|
let i = t.createElement("li");
|
|
475
488
|
i.append(e), a.append(i);
|
|
476
|
-
} else if (e.tagName === "P" && a &&
|
|
489
|
+
} else if (e.tagName === "P" && a && X.test(n) && Number(n.match(X)[1]) === (a.lastElementChild.querySelector(":scope > ol")?.children.length ?? 0) + 1) {
|
|
477
490
|
let n = a.lastElementChild, r = n.querySelector(":scope > ol") ?? n.appendChild(t.createElement("ol"));
|
|
478
|
-
|
|
491
|
+
Z(e, X);
|
|
479
492
|
let i = t.createElement("li");
|
|
480
493
|
i.append(e), r.append(i);
|
|
481
494
|
} else {
|
|
482
495
|
a = null;
|
|
483
|
-
let t = e.tagName === "P" ?
|
|
496
|
+
let t = e.tagName === "P" ? be(n) : null;
|
|
484
497
|
if (!t) continue;
|
|
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),
|
|
498
|
+
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);
|
|
486
499
|
}
|
|
487
500
|
}
|
|
488
501
|
return t.body.innerHTML;
|
|
489
502
|
}
|
|
490
|
-
async function
|
|
503
|
+
async function $(e) {
|
|
491
504
|
let { default: t } = await import("mammoth"), { value: n } = await t.convertToHtml({ arrayBuffer: await e.arrayBuffer() });
|
|
492
|
-
return
|
|
505
|
+
return Q(n);
|
|
493
506
|
}
|
|
494
507
|
async function xe(e) {
|
|
495
508
|
let { detectFormat: t, hwpToHwpx: n, HwpxReader: r } = await import("hwp-convert"), i = new Uint8Array(await e.arrayBuffer());
|
|
496
509
|
t(i) === "hwp" && (i = new Uint8Array(await n(i)));
|
|
497
510
|
let a = new r();
|
|
498
|
-
return await a.loadFromArrayBuffer(i.buffer),
|
|
511
|
+
return await a.loadFromArrayBuffer(i.buffer), Q(await a.extractHtml());
|
|
499
512
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
513
|
+
async function Se(e) {
|
|
514
|
+
let { parseMsDoc: t } = await import("@file-viewer/doc");
|
|
515
|
+
return Q(we(t(new Uint8Array(await e.arrayBuffer())).blocks));
|
|
516
|
+
}
|
|
517
|
+
var Ce = (e) => e.replace(/[\x00-\x08\x0c\x0e-\x1f]/g, "");
|
|
518
|
+
function we(e) {
|
|
519
|
+
let t = (e) => Ce(e).split("\r").filter((e) => e.trim()).map((e) => `<p>${K(e).replace(/\v/g, "<br>")}</p>`).join("");
|
|
520
|
+
return e.map((e) => {
|
|
521
|
+
if (e.type === "paragraph") return t(e.text);
|
|
522
|
+
if (e.type !== "table") return "";
|
|
523
|
+
let n = (e) => `<td${(e.colspan ?? 1) > 1 ? ` colspan="${e.colspan}"` : ""}${(e.rowspan ?? 1) > 1 ? ` rowspan="${e.rowspan}"` : ""}>${e.paragraphs.map((e) => K(Ce(e.text).replace(/[\r\v]/g, ""))).filter(Boolean).join("<br>")}</td>`;
|
|
524
|
+
return `<table>${e.rows.map((e) => `<tr>${e.cells.filter((e) => !e.hidden).map(n).join("")}</tr>`).join("")}</table>`;
|
|
525
|
+
}).join("");
|
|
526
|
+
}
|
|
527
|
+
function Te(e) {
|
|
528
|
+
return /\.docx$/i.test(e.name) ? $(e) : /\.doc$/i.test(e.name) ? Se(e) : /\.hwpx?$/i.test(e.name) ? xe(e) : Promise.reject(/* @__PURE__ */ Error(".docx, .doc, .hwp, .hwpx 파일만 열 수 있어요"));
|
|
502
529
|
}
|
|
503
530
|
//#endregion
|
|
504
531
|
//#region src/LegalEditor.tsx
|
|
505
|
-
function
|
|
532
|
+
function Ee(e) {
|
|
506
533
|
let t = /* @__PURE__ */ new Set();
|
|
507
534
|
return e.state.doc.descendants((e) => {
|
|
508
535
|
e.type.name === "variable" && t.add(e.attrs.name);
|
|
509
536
|
}), [...t];
|
|
510
537
|
}
|
|
511
|
-
function
|
|
538
|
+
function De() {
|
|
512
539
|
let e = document.documentElement;
|
|
513
540
|
e.classList.add("le-printing"), window.addEventListener("afterprint", () => e.classList.remove("le-printing"), { once: !0 }), window.print();
|
|
514
541
|
}
|
|
515
|
-
function
|
|
542
|
+
function Oe(e, t) {
|
|
516
543
|
let n = document.createElement("a");
|
|
517
544
|
n.href = URL.createObjectURL(e), n.download = t, n.click(), setTimeout(() => URL.revokeObjectURL(n.href), 1e3);
|
|
518
545
|
}
|
|
519
|
-
function
|
|
520
|
-
let [v,
|
|
546
|
+
function ke({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, editable: u = !0, autoFees: d, searchCases: f, clauses: p, hwpWasmUrl: ee }) {
|
|
547
|
+
let [v, b] = t(s), [x, S] = t([]), [C, te] = t(""), [w, T] = t(null), [E, D] = t(""), [O, A] = t(""), j = d ? re(x, v, d === !0 ? {} : d) : v, N = r({
|
|
521
548
|
extensions: [
|
|
522
549
|
i.configure({ heading: { levels: [
|
|
523
550
|
1,
|
|
@@ -526,43 +553,47 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
526
553
|
] } }),
|
|
527
554
|
a,
|
|
528
555
|
g,
|
|
529
|
-
|
|
530
|
-
|
|
556
|
+
y,
|
|
557
|
+
M
|
|
531
558
|
],
|
|
532
559
|
content: _(o),
|
|
533
560
|
editable: u,
|
|
534
561
|
immediatelyRender: !1,
|
|
535
562
|
editorProps: { attributes: { class: "legal-doc" } },
|
|
536
|
-
onCreate: ({ editor: e }) => S(
|
|
563
|
+
onCreate: ({ editor: e }) => S(Ee(e)),
|
|
537
564
|
onUpdate: ({ editor: e }) => {
|
|
538
|
-
S(
|
|
565
|
+
S(Ee(e)), c?.(e.getHTML());
|
|
539
566
|
}
|
|
540
567
|
});
|
|
541
568
|
e(() => {
|
|
542
|
-
|
|
543
|
-
}, [
|
|
569
|
+
N && (N.storage.variable.values = j, N.storage.variable.views.forEach((e) => e()));
|
|
570
|
+
}, [N, j]);
|
|
544
571
|
let P = (e, t) => {
|
|
545
572
|
let n = {
|
|
546
573
|
...v,
|
|
547
574
|
[e]: t
|
|
548
575
|
};
|
|
549
|
-
|
|
576
|
+
b(n), l?.(n);
|
|
550
577
|
}, F = () => {
|
|
551
578
|
let e = window.prompt("변수 이름 (예: 갑, 계약일)")?.replace(/[{}<>"]/g, "").trim();
|
|
552
|
-
e &&
|
|
579
|
+
e && N?.chain().focus().insertContent({
|
|
553
580
|
type: "variable",
|
|
554
581
|
attrs: { name: e }
|
|
555
582
|
}).run();
|
|
556
583
|
}, I = async (e) => {
|
|
557
584
|
let t = e.target.files?.[0];
|
|
558
|
-
if (e.target.value = "", t &&
|
|
559
|
-
|
|
585
|
+
if (e.target.value = "", t && N) try {
|
|
586
|
+
N.commands.setContent(_(await Te(t)), { emitUpdate: !0 });
|
|
560
587
|
} catch (e) {
|
|
561
588
|
window.alert(`파일을 열 수 없어요: ${e.message}`);
|
|
562
589
|
}
|
|
563
590
|
}, L = async (e, t) => {
|
|
564
|
-
let n =
|
|
565
|
-
|
|
591
|
+
let n = N.state.doc.firstChild?.textContent.trim() || "문서";
|
|
592
|
+
try {
|
|
593
|
+
Oe(await e(N.getJSON(), j), `${n}.${t}`);
|
|
594
|
+
} catch (e) {
|
|
595
|
+
window.alert(`저장하지 못했어요: ${e.message}`);
|
|
596
|
+
}
|
|
566
597
|
}, R = async () => {
|
|
567
598
|
let e = C.trim();
|
|
568
599
|
if (f && e) {
|
|
@@ -573,18 +604,18 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
573
604
|
T(null), D(`검색하지 못했어요: ${e.message}`);
|
|
574
605
|
}
|
|
575
606
|
}
|
|
576
|
-
}, z = () =>
|
|
577
|
-
let { from: t, to: n } =
|
|
607
|
+
}, z = () => N.chain().focus(), ne = (e) => z().setNumbering(N.getAttributes("paragraph").num === e ? null : e).run(), B = (e) => {
|
|
608
|
+
let { from: t, to: n } = N.state.selection, r = se(N.state.doc.textBetween(t, n));
|
|
578
609
|
r && z().insertContentAt({
|
|
579
610
|
from: t,
|
|
580
611
|
to: n
|
|
581
|
-
},
|
|
612
|
+
}, H(r, e)).run();
|
|
582
613
|
};
|
|
583
614
|
return /* @__PURE__ */ h("div", {
|
|
584
615
|
className: "le-root",
|
|
585
616
|
children: [/* @__PURE__ */ h("div", {
|
|
586
617
|
className: "le-main",
|
|
587
|
-
children: [u &&
|
|
618
|
+
children: [u && N && /* @__PURE__ */ h("div", {
|
|
588
619
|
className: "le-toolbar",
|
|
589
620
|
children: [
|
|
590
621
|
/* @__PURE__ */ m("button", {
|
|
@@ -610,7 +641,7 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
610
641
|
].map((e, t) => /* @__PURE__ */ m("button", {
|
|
611
642
|
type: "button",
|
|
612
643
|
title: "번호 문단 — Tab·Shift+Tab 단계 변경, 맨 앞 Backspace 해제",
|
|
613
|
-
onClick: () =>
|
|
644
|
+
onClick: () => ne(t + 1),
|
|
614
645
|
children: e
|
|
615
646
|
}, e)),
|
|
616
647
|
/* @__PURE__ */ m("button", {
|
|
@@ -664,28 +695,34 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
664
695
|
className: "le-btn",
|
|
665
696
|
children: ["열기", /* @__PURE__ */ m("input", {
|
|
666
697
|
type: "file",
|
|
667
|
-
accept: ".docx,.hwp,.hwpx",
|
|
698
|
+
accept: ".docx,.doc,.hwp,.hwpx",
|
|
668
699
|
hidden: !0,
|
|
669
700
|
onChange: I
|
|
670
701
|
})]
|
|
671
702
|
}),
|
|
672
703
|
/* @__PURE__ */ m("button", {
|
|
673
704
|
type: "button",
|
|
674
|
-
onClick: () => L(
|
|
705
|
+
onClick: () => L(pe, "docx"),
|
|
675
706
|
children: "Word 저장"
|
|
676
707
|
}),
|
|
677
708
|
/* @__PURE__ */ m("button", {
|
|
678
709
|
type: "button",
|
|
679
|
-
onClick: () => L(
|
|
680
|
-
children: "한글 저장"
|
|
710
|
+
onClick: () => L(q, "hwpx"),
|
|
711
|
+
children: "한글(.hwpx) 저장"
|
|
712
|
+
}),
|
|
713
|
+
/* @__PURE__ */ m("button", {
|
|
714
|
+
type: "button",
|
|
715
|
+
title: "HWP 5.0 — 처음 저장할 때 변환기(약 10MB)를 불러와요",
|
|
716
|
+
onClick: () => L((e, t) => me(e, t, { wasm: ee }), "hwp"),
|
|
717
|
+
children: "한글(.hwp) 저장"
|
|
681
718
|
}),
|
|
682
719
|
/* @__PURE__ */ m("button", {
|
|
683
720
|
type: "button",
|
|
684
|
-
onClick:
|
|
721
|
+
onClick: De,
|
|
685
722
|
children: "인쇄 · PDF"
|
|
686
723
|
})
|
|
687
724
|
]
|
|
688
|
-
}), /* @__PURE__ */ m(n, { editor:
|
|
725
|
+
}), /* @__PURE__ */ m(n, { editor: N })]
|
|
689
726
|
}), /* @__PURE__ */ h("aside", {
|
|
690
727
|
className: "le-panel",
|
|
691
728
|
children: [
|
|
@@ -710,7 +747,7 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
710
747
|
/* @__PURE__ */ m("input", {
|
|
711
748
|
value: O,
|
|
712
749
|
placeholder: "조항 찾기 (예: 해지)",
|
|
713
|
-
onChange: (e) =>
|
|
750
|
+
onChange: (e) => A(e.target.value)
|
|
714
751
|
}),
|
|
715
752
|
/* @__PURE__ */ m("ul", {
|
|
716
753
|
className: "le-clause-list",
|
|
@@ -718,7 +755,7 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
718
755
|
type: "button",
|
|
719
756
|
disabled: !u,
|
|
720
757
|
title: "커서 위치에 조항 넣기",
|
|
721
|
-
onClick: () =>
|
|
758
|
+
onClick: () => N?.chain().focus().insertContent(_(e.html), { parseOptions: { preserveWhitespace: !1 } }).run(),
|
|
722
759
|
children: [/* @__PURE__ */ m("small", { children: e.category }), e.title]
|
|
723
760
|
}) }, e.id))
|
|
724
761
|
})
|
|
@@ -733,7 +770,7 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
733
770
|
children: [/* @__PURE__ */ m("input", {
|
|
734
771
|
value: C,
|
|
735
772
|
placeholder: "사건명·사건번호·키워드",
|
|
736
|
-
onChange: (e) =>
|
|
773
|
+
onChange: (e) => te(e.target.value),
|
|
737
774
|
onKeyDown: (e) => {
|
|
738
775
|
e.key === "Enter" && !e.nativeEvent.isComposing && (e.preventDefault(), R());
|
|
739
776
|
}
|
|
@@ -757,10 +794,10 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
757
794
|
type: "button",
|
|
758
795
|
disabled: !u,
|
|
759
796
|
title: "커서 위치에 인용 넣기",
|
|
760
|
-
onClick: () =>
|
|
797
|
+
onClick: () => N?.chain().focus().insertContent(k(e)).run(),
|
|
761
798
|
children: [
|
|
762
799
|
/* @__PURE__ */ m("strong", { children: e.title }),
|
|
763
|
-
/* @__PURE__ */ m("span", { children:
|
|
800
|
+
/* @__PURE__ */ m("span", { children: k(e) }),
|
|
764
801
|
e.summary && /* @__PURE__ */ m("small", { children: e.summary })
|
|
765
802
|
]
|
|
766
803
|
}), /* @__PURE__ */ m("a", {
|
|
@@ -778,7 +815,7 @@ function we({ content: o = "", values: s = {}, onChange: c, onValuesChange: l, e
|
|
|
778
815
|
}
|
|
779
816
|
//#endregion
|
|
780
817
|
//#region src/templates.ts
|
|
781
|
-
var
|
|
818
|
+
var Ae = [
|
|
782
819
|
{
|
|
783
820
|
id: "complaint-loan",
|
|
784
821
|
title: "소장 (대여금)",
|
|
@@ -803,7 +840,7 @@ var Te = [
|
|
|
803
840
|
description: "재판 없이 법원의 지급명령을 받는 독촉절차 신청서. 인지대는 소장의 10분의 1, 송달료는 당사자 수 × 6회분",
|
|
804
841
|
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"
|
|
805
842
|
}
|
|
806
|
-
],
|
|
843
|
+
], je = [
|
|
807
844
|
{
|
|
808
845
|
id: "purpose",
|
|
809
846
|
title: "목적",
|
|
@@ -872,4 +909,4 @@ var Te = [
|
|
|
872
909
|
}
|
|
873
910
|
];
|
|
874
911
|
//#endregion
|
|
875
|
-
export {
|
|
912
|
+
export { M as CitationLink, ke as LegalEditor, y as Numbering, I as SERVICE_ROUNDS, L as SERVICE_UNIT_FEE, z as SMALL_CLAIM_LIMIT, g as Variable, F as calcPaymentOrderStampFee, R as calcServiceFee, N as calcStampFee, je as clauses, ee as fillTemplate, O as findCitations, H as formatAmount, k as formatCaseCitation, Se as fromDoc, $ as fromDocx, Te as fromFile, xe as fromHwp, Q as normalizeLegalHtml, se as parseAmount, Ae as templates, _ as toChips, pe as toDocx, me as toHwp, q as toHwpx, V as toKoreanAmount, de as toPlainHtml, re as withCourtFees };
|
package/dist/io.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JSONContent } from '@tiptap/core';
|
|
2
|
+
import type { MsDocBlock } from '@file-viewer/doc';
|
|
2
3
|
import type { Values } from './variable';
|
|
3
4
|
/** 번호가 텍스트로 박힌 독립 HTML — CSS 없이도 조·항·호가 보여서 메일·다른 변환기에 넘기기 좋음 */
|
|
4
5
|
export declare function toPlainHtml(doc: JSONContent, values: Values): string;
|
|
@@ -6,6 +7,14 @@ export declare function toPlainHtml(doc: JSONContent, values: Values): string;
|
|
|
6
7
|
export declare function toDocx(doc: JSONContent, values: Values): Promise<Blob>;
|
|
7
8
|
/** 에디터 JSON + 입력값 → .hwpx Blob (한글 2014 이상에서 열림) */
|
|
8
9
|
export declare function toHwpx(doc: JSONContent, values: Values): Promise<Blob>;
|
|
10
|
+
/**
|
|
11
|
+
* 에디터 JSON + 입력값 → .hwp(HWP 5.0) Blob. toHwpx 결과를 @rhwp/core(WASM, 약 10MB — 처음 저장할 때만 불러옴)로 변환
|
|
12
|
+
* `wasm`: WASM 파일 주소나 바이트. 번들러가 WASM 경로를 못 찾을 때 넘김 (예: public에 복사한 '/rhwp_bg.wasm')
|
|
13
|
+
* ponytail: 한글 프로그램에서 열리는지는 사람이 확인해야 함 — 여기선 hwp-convert로 다시 읽어 글자·구조만 검증함
|
|
14
|
+
*/
|
|
15
|
+
export declare function toHwp(doc: JSONContent, values: Values, opts?: {
|
|
16
|
+
wasm?: string | URL | BufferSource;
|
|
17
|
+
}): Promise<Blob>;
|
|
9
18
|
/**
|
|
10
19
|
* 문단으로만 된 법률문서 HTML(한글·워드에서 온 것)을 에디터 구조로 정리.
|
|
11
20
|
* - 빈 줄 문단 제거, 짧은 첫 줄 → 제목(h1), "청 구 취 지" 같은 소제목 → h3, [빈칸] → {{변수}}
|
|
@@ -19,5 +28,12 @@ export declare function normalizeLegalHtml(html: string): string;
|
|
|
19
28
|
export declare function fromDocx(file: Blob): Promise<string>;
|
|
20
29
|
/** .hwp / .hwpx → 에디터용 HTML. .hwp는 hwpx로 바꾼 뒤 읽음 (암호화·배포용·HWP 3.0은 에러) */
|
|
21
30
|
export declare function fromHwp(file: Blob): Promise<string>;
|
|
22
|
-
/**
|
|
31
|
+
/** .doc(Word 97–2003) → 에디터용 HTML. @file-viewer/doc로 읽고 글자·표만 옮김 */
|
|
32
|
+
export declare function fromDoc(file: Blob): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* .doc 파싱 결과 → 단순 HTML. 한 문단 블록 안에 단락 기호(\r)로 여러 문단이 붙어 오는 경우가 있어 \r로 나누고, \v(줄 바꿈)는 <br>로
|
|
35
|
+
* ponytail: 글자 서식(굵게 등)·그림은 옮기지 않음 — 필요하면 inlines의 글자 속성을 읽어 확장
|
|
36
|
+
*/
|
|
37
|
+
export declare function docBlocksToHtml(blocks: MsDocBlock[]): string;
|
|
38
|
+
/** 확장자로 골라 여는 헬퍼: .docx / .doc / .hwp / .hwpx */
|
|
23
39
|
export declare function fromFile(file: File): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "legal-doc-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "법률문서(계약서·소장·고소장) 생성용 React 에디터 — 조항·번호 자동 매김, {{변수}} 채우기, 한글(hwp·hwpx)·Word(docx) 열기·저장",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
"react-dom": ">=18"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@file-viewer/doc": "^3.1.1",
|
|
54
|
+
"@rhwp/core": "^0.8.6",
|
|
53
55
|
"@tiptap/core": "^3.31.3",
|
|
54
56
|
"@tiptap/extension-table": "^3.31.3",
|
|
55
57
|
"@tiptap/pm": "^3.31.3",
|