legal-markdown-js 0.1.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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +168 -0
  3. package/dist/browser.d.ts +87 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/browser.js +157 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/cli/index.d.ts +34 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +221 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/cli/service.d.ts +128 -0
  12. package/dist/cli/service.d.ts.map +1 -0
  13. package/dist/cli/service.js +284 -0
  14. package/dist/cli/service.js.map +1 -0
  15. package/dist/constants/index.d.ts +229 -0
  16. package/dist/constants/index.d.ts.map +1 -0
  17. package/dist/constants/index.js +232 -0
  18. package/dist/constants/index.js.map +1 -0
  19. package/dist/core/exporters/metadata-exporter.d.ts +104 -0
  20. package/dist/core/exporters/metadata-exporter.d.ts.map +1 -0
  21. package/dist/core/exporters/metadata-exporter.js +201 -0
  22. package/dist/core/exporters/metadata-exporter.js.map +1 -0
  23. package/dist/core/index.d.ts +40 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/index.js +56 -0
  26. package/dist/core/index.js.map +1 -0
  27. package/dist/core/parsers/yaml-parser.d.ts +149 -0
  28. package/dist/core/parsers/yaml-parser.d.ts.map +1 -0
  29. package/dist/core/parsers/yaml-parser.js +321 -0
  30. package/dist/core/parsers/yaml-parser.js.map +1 -0
  31. package/dist/core/processors/clause-processor.d.ts +74 -0
  32. package/dist/core/processors/clause-processor.d.ts.map +1 -0
  33. package/dist/core/processors/clause-processor.js +213 -0
  34. package/dist/core/processors/clause-processor.js.map +1 -0
  35. package/dist/core/processors/date-processor.d.ts +90 -0
  36. package/dist/core/processors/date-processor.d.ts.map +1 -0
  37. package/dist/core/processors/date-processor.js +336 -0
  38. package/dist/core/processors/date-processor.js.map +1 -0
  39. package/dist/core/processors/header-processor.d.ts +104 -0
  40. package/dist/core/processors/header-processor.d.ts.map +1 -0
  41. package/dist/core/processors/header-processor.js +585 -0
  42. package/dist/core/processors/header-processor.js.map +1 -0
  43. package/dist/core/processors/import-processor.d.ts +116 -0
  44. package/dist/core/processors/import-processor.d.ts.map +1 -0
  45. package/dist/core/processors/import-processor.js +236 -0
  46. package/dist/core/processors/import-processor.js.map +1 -0
  47. package/dist/core/processors/mixin-processor.d.ts +93 -0
  48. package/dist/core/processors/mixin-processor.d.ts.map +1 -0
  49. package/dist/core/processors/mixin-processor.js +378 -0
  50. package/dist/core/processors/mixin-processor.js.map +1 -0
  51. package/dist/core/processors/reference-processor.d.ts +115 -0
  52. package/dist/core/processors/reference-processor.d.ts.map +1 -0
  53. package/dist/core/processors/reference-processor.js +273 -0
  54. package/dist/core/processors/reference-processor.js.map +1 -0
  55. package/dist/errors/index.d.ts +234 -0
  56. package/dist/errors/index.d.ts.map +1 -0
  57. package/dist/errors/index.js +267 -0
  58. package/dist/errors/index.js.map +1 -0
  59. package/dist/extensions/batch-processor.d.ts +197 -0
  60. package/dist/extensions/batch-processor.d.ts.map +1 -0
  61. package/dist/extensions/batch-processor.js +392 -0
  62. package/dist/extensions/batch-processor.js.map +1 -0
  63. package/dist/extensions/formatters/index.d.ts +99 -0
  64. package/dist/extensions/formatters/index.d.ts.map +1 -0
  65. package/dist/extensions/formatters/index.js +128 -0
  66. package/dist/extensions/formatters/index.js.map +1 -0
  67. package/dist/extensions/index.d.ts +53 -0
  68. package/dist/extensions/index.d.ts.map +1 -0
  69. package/dist/extensions/index.js +71 -0
  70. package/dist/extensions/index.js.map +1 -0
  71. package/dist/extensions/latex-parser.d.ts +111 -0
  72. package/dist/extensions/latex-parser.d.ts.map +1 -0
  73. package/dist/extensions/latex-parser.js +563 -0
  74. package/dist/extensions/latex-parser.js.map +1 -0
  75. package/dist/extensions/rst-parser.d.ts +112 -0
  76. package/dist/extensions/rst-parser.d.ts.map +1 -0
  77. package/dist/extensions/rst-parser.js +268 -0
  78. package/dist/extensions/rst-parser.js.map +1 -0
  79. package/dist/extensions/template-loops.d.ts +61 -0
  80. package/dist/extensions/template-loops.d.ts.map +1 -0
  81. package/dist/extensions/template-loops.js +418 -0
  82. package/dist/extensions/template-loops.js.map +1 -0
  83. package/dist/extensions/utilities/index.d.ts +114 -0
  84. package/dist/extensions/utilities/index.d.ts.map +1 -0
  85. package/dist/extensions/utilities/index.js +137 -0
  86. package/dist/extensions/utilities/index.js.map +1 -0
  87. package/dist/extensions/validators/index.d.ts +65 -0
  88. package/dist/extensions/validators/index.d.ts.map +1 -0
  89. package/dist/extensions/validators/index.js +88 -0
  90. package/dist/extensions/validators/index.js.map +1 -0
  91. package/dist/generators/html-generator.d.ts +184 -0
  92. package/dist/generators/html-generator.d.ts.map +1 -0
  93. package/dist/generators/html-generator.js +361 -0
  94. package/dist/generators/html-generator.js.map +1 -0
  95. package/dist/generators/pdf-generator.d.ts +165 -0
  96. package/dist/generators/pdf-generator.d.ts.map +1 -0
  97. package/dist/generators/pdf-generator.js +275 -0
  98. package/dist/generators/pdf-generator.js.map +1 -0
  99. package/dist/helpers/date-helpers.d.ts +216 -0
  100. package/dist/helpers/date-helpers.d.ts.map +1 -0
  101. package/dist/helpers/date-helpers.js +402 -0
  102. package/dist/helpers/date-helpers.js.map +1 -0
  103. package/dist/helpers/index.d.ts +87 -0
  104. package/dist/helpers/index.d.ts.map +1 -0
  105. package/dist/helpers/index.js +149 -0
  106. package/dist/helpers/index.js.map +1 -0
  107. package/dist/helpers/number-helpers.d.ts +269 -0
  108. package/dist/helpers/number-helpers.d.ts.map +1 -0
  109. package/dist/helpers/number-helpers.js +406 -0
  110. package/dist/helpers/number-helpers.js.map +1 -0
  111. package/dist/helpers/string-helpers.d.ts +391 -0
  112. package/dist/helpers/string-helpers.d.ts.map +1 -0
  113. package/dist/helpers/string-helpers.js +549 -0
  114. package/dist/helpers/string-helpers.js.map +1 -0
  115. package/dist/index.d.ts +229 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +384 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/legal-markdown.umd.min.js +2 -0
  120. package/dist/legal-markdown.umd.min.js.LICENSE.txt +14 -0
  121. package/dist/legal-markdown.umd.min.js.map +1 -0
  122. package/dist/lib/index.d.ts +150 -0
  123. package/dist/lib/index.d.ts.map +1 -0
  124. package/dist/lib/index.js +265 -0
  125. package/dist/lib/index.js.map +1 -0
  126. package/dist/parsers/content-detector.d.ts +131 -0
  127. package/dist/parsers/content-detector.d.ts.map +1 -0
  128. package/dist/parsers/content-detector.js +220 -0
  129. package/dist/parsers/content-detector.js.map +1 -0
  130. package/dist/parsers/fallback-parsers.d.ts +14 -0
  131. package/dist/parsers/fallback-parsers.d.ts.map +1 -0
  132. package/dist/parsers/fallback-parsers.js +188 -0
  133. package/dist/parsers/fallback-parsers.js.map +1 -0
  134. package/dist/parsers/implementations/pandoc-native.d.ts +13 -0
  135. package/dist/parsers/implementations/pandoc-native.d.ts.map +1 -0
  136. package/dist/parsers/implementations/pandoc-native.js +63 -0
  137. package/dist/parsers/implementations/pandoc-native.js.map +1 -0
  138. package/dist/parsers/implementations/pandoc-wasm.d.ts +14 -0
  139. package/dist/parsers/implementations/pandoc-wasm.d.ts.map +1 -0
  140. package/dist/parsers/implementations/pandoc-wasm.js +64 -0
  141. package/dist/parsers/implementations/pandoc-wasm.js.map +1 -0
  142. package/dist/parsers/pandoc-factory.d.ts +97 -0
  143. package/dist/parsers/pandoc-factory.d.ts.map +1 -0
  144. package/dist/parsers/pandoc-factory.js +146 -0
  145. package/dist/parsers/pandoc-factory.js.map +1 -0
  146. package/dist/parsers/pandoc-loader.d.ts +24 -0
  147. package/dist/parsers/pandoc-loader.d.ts.map +1 -0
  148. package/dist/parsers/pandoc-loader.js +124 -0
  149. package/dist/parsers/pandoc-loader.js.map +1 -0
  150. package/dist/parsers/pandoc-parser.d.ts +27 -0
  151. package/dist/parsers/pandoc-parser.d.ts.map +1 -0
  152. package/dist/parsers/pandoc-parser.js +3 -0
  153. package/dist/parsers/pandoc-parser.js.map +1 -0
  154. package/dist/styles/default.css +125 -0
  155. package/dist/styles/headers.css +146 -0
  156. package/dist/styles/highlight.css +171 -0
  157. package/dist/tracking/field-tracker.d.ts +206 -0
  158. package/dist/tracking/field-tracker.d.ts.map +1 -0
  159. package/dist/tracking/field-tracker.js +247 -0
  160. package/dist/tracking/field-tracker.js.map +1 -0
  161. package/dist/types.d.ts +186 -0
  162. package/dist/types.d.ts.map +1 -0
  163. package/dist/types.js +33 -0
  164. package/dist/types.js.map +1 -0
  165. package/dist/utils/logger.d.ts +107 -0
  166. package/dist/utils/logger.d.ts.map +1 -0
  167. package/dist/utils/logger.js +122 -0
  168. package/dist/utils/logger.js.map +1 -0
  169. package/dist/web/bundle-standalone.js +28 -0
  170. package/dist/web/bundle.js +17 -0
  171. package/dist/web/index.html +1465 -0
  172. package/dist/web/legal-markdown.umd.min.js +2 -0
  173. package/dist/web/standalone.html +390 -0
  174. package/dist/web/styles.css +874 -0
  175. package/package.json +118 -0
@@ -0,0 +1,1465 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Legal Markdown JS - Playground</title>
8
+ <link rel="stylesheet" href="styles.css">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="app-container">
13
+ <header class="app-header">
14
+ <h1>Legal Markdown JS - Playground</h1>
15
+ <div class="header-actions">
16
+ <button class="theme-toggle" title="Toggle theme">🌙</button>
17
+ <button class="help-btn" title="Help">❓</button>
18
+ </div>
19
+ </header>
20
+
21
+ <main class="editor-container">
22
+ <div class="input-panel">
23
+ <div class="panel-header">
24
+ <h3>Input</h3>
25
+ <div class="panel-actions">
26
+ <label class="file-input-label">
27
+ 📁 Upload File
28
+ <input type="file" class="file-input" accept=".md,.txt" style="display: none;">
29
+ </label>
30
+ <div class="example-dropdown">
31
+ <select class="example-select">
32
+ <option value="">📄 Examples</option>
33
+ <option value="service-agreement">Service Agreement</option>
34
+ <option value="lease-contract">Lease Contract</option>
35
+ <option value="purchase-ticket">Purchase Ticket</option>
36
+ <option value="nda">Non-Disclosure Agreement</option>
37
+ <option value="employment-contract">Employment Contract</option>
38
+ </select>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <textarea class="editor" placeholder="Enter your Legal Markdown content here...
43
+
44
+ Select an example from the dropdown above to get started, or write your own content using YAML front matter and Legal Markdown syntax.
45
+
46
+ Example structure:
47
+ ---
48
+ title: Document Title
49
+ variable: value
50
+ ---
51
+
52
+ # {{title}}
53
+
54
+ Your content with {{variable}} substitutions..."></textarea>
55
+ </div>
56
+
57
+ <div class="column-resizer" id="resizer1"></div>
58
+
59
+ <!-- CSS Editor Panel -->
60
+ <div class="css-panel">
61
+ <div class="panel-header">
62
+ <h3>Custom CSS</h3>
63
+ <div class="panel-actions">
64
+ <label class="file-input-label">
65
+ 📁 Upload CSS
66
+ <input type="file" class="css-file-input" accept=".css" style="display: none;">
67
+ </label>
68
+ <div class="example-dropdown">
69
+ <select class="css-example-select">
70
+ <option value="">🎨 CSS Examples</option>
71
+ <option value="modern">Modern Style</option>
72
+ <option value="classic">Classic Legal</option>
73
+ <option value="minimal">Minimal</option>
74
+ </select>
75
+ </div>
76
+ <button class="css-reset secondary">🔄</button>
77
+ </div>
78
+ </div>
79
+ <div class="css-content">
80
+ <textarea class="css-editor" placeholder="Enter your custom CSS here...
81
+
82
+ /* Example: Change document colors */
83
+ body {
84
+ background: #f9f9f9;
85
+ color: #333;
86
+ }
87
+
88
+ h1 {
89
+ color: #2c3e50;
90
+ border-bottom: 2px solid #3498db;
91
+ }
92
+
93
+ /* Example: Customize spacing */
94
+ p {
95
+ margin-bottom: 1.2em;
96
+ line-height: 1.6;
97
+ }
98
+
99
+ /* Your custom styles here... */"></textarea>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="column-resizer" id="resizer2"></div>
104
+
105
+ <div class="output-panel">
106
+ <div class="panel-header">
107
+ <h3>Preview</h3>
108
+ <div class="panel-actions">
109
+ <button class="toggle-styles-btn" title="Toggle base styles">🎨</button>
110
+ <button class="process-btn">▶️</button>
111
+ <button class="download-btn secondary">💾</button>
112
+ <button class="download-pdf-btn secondary">📄</button>
113
+ <button class="print-btn secondary">🖨️</button>
114
+ </div>
115
+ </div>
116
+ <div class="preview-container">
117
+ <div class="preview-placeholder">
118
+ <p>👈 Enter your Legal Markdown content and click Process to see the result</p>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </main>
123
+
124
+ <footer class="options-panel">
125
+ <button class="options-toggle">⚙️ Processing Options</button>
126
+ <div class="options-content">
127
+ <div class="options-grid">
128
+ <div class="option-group">
129
+ <label>Processing Control</label>
130
+ <div class="checkbox-group">
131
+ <label class="checkbox-item">
132
+ <input type="checkbox" id="debug"> Debug mode
133
+ <span class="tooltip">
134
+ <span class="tooltip-icon">ℹ️</span>
135
+ <span class="tooltip-text">Shows detailed processing information in the console, including step-by-step parsing and variable substitution details.</span>
136
+ </span>
137
+ </label>
138
+ <label class="checkbox-item">
139
+ <input type="checkbox" id="yamlOnly"> YAML only
140
+ <span class="tooltip">
141
+ <span class="tooltip-icon">ℹ️</span>
142
+ <span class="tooltip-text">Processes only the YAML front matter without rendering the markdown content. Useful for testing metadata extraction.</span>
143
+ </span>
144
+ </label>
145
+ <label class="checkbox-item">
146
+ <input type="checkbox" id="noHeaders"> No headers
147
+ <span class="tooltip">
148
+ <span class="tooltip-icon">ℹ️</span>
149
+ <span class="tooltip-text">Disables automatic header numbering (l., ll., lll.) that's typically used in legal documents.</span>
150
+ </span>
151
+ </label>
152
+ <label class="checkbox-item">
153
+ <input type="checkbox" id="enableFieldTracking" checked> Field tracking
154
+ <span class="tooltip">
155
+ <span class="tooltip-icon">ℹ️</span>
156
+ <span class="tooltip-text">Tracks all variable substitutions and provides a detailed report of which fields were processed and their values.</span>
157
+ </span>
158
+ </label>
159
+ </div>
160
+ </div>
161
+
162
+ <div class="option-group">
163
+ <label>Advanced Options</label>
164
+ <div class="checkbox-group">
165
+ <label class="checkbox-item">
166
+ <input type="checkbox" id="noClauses"> No clauses
167
+ <span class="tooltip">
168
+ <span class="tooltip-icon">ℹ️</span>
169
+ <span class="tooltip-text">Disables processing of structured clauses and conditional blocks (e.g., [text]{condition}). Content will be processed as plain text.</span>
170
+ </span>
171
+ </label>
172
+ <label class="checkbox-item">
173
+ <input type="checkbox" id="noReferences"> No references
174
+ <span class="tooltip">
175
+ <span class="tooltip-icon">ℹ️</span>
176
+ <span class="tooltip-text">Disables processing of cross-references and citation links within the document. References will appear as plain text.</span>
177
+ </span>
178
+ </label>
179
+ <label class="checkbox-item">
180
+ <input type="checkbox" id="noMixins"> No mixins
181
+ <span class="tooltip">
182
+ <span class="tooltip-icon">ℹ️</span>
183
+ <span class="tooltip-text">Disables processing of mixins and helper functions (e.g., {{formatDate()}}, {{capitalize()}}). Only basic variable substitution will work.</span>
184
+ </span>
185
+ </label>
186
+ <label class="checkbox-item">
187
+ <input type="checkbox" id="noImports"> No imports
188
+ <span class="tooltip">
189
+ <span class="tooltip-icon">ℹ️</span>
190
+ <span class="tooltip-text">Disables importing of external markdown files. Import statements will be ignored and appear as plain text.</span>
191
+ </span>
192
+ </label>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="option-group">
197
+ <label>Header Options</label>
198
+ <div class="checkbox-group">
199
+ <label class="checkbox-item">
200
+ <input type="checkbox" id="noReset"> No reset
201
+ <span class="tooltip">
202
+ <span class="tooltip-icon">ℹ️</span>
203
+ <span class="tooltip-text">Prevents resetting header counters. Header numbering will continue from previous values instead of starting fresh.</span>
204
+ </span>
205
+ </label>
206
+ <label class="checkbox-item">
207
+ <input type="checkbox" id="noIndent"> No indent
208
+ <span class="tooltip">
209
+ <span class="tooltip-icon">ℹ️</span>
210
+ <span class="tooltip-text">Disables automatic indentation of nested headers and sub-clauses. All content will be left-aligned.</span>
211
+ </span>
212
+ </label>
213
+ <label class="checkbox-item">
214
+ <input type="checkbox" id="throwOnYamlError"> Throw on YAML error
215
+ <span class="tooltip">
216
+ <span class="tooltip-icon">ℹ️</span>
217
+ <span class="tooltip-text">Stops processing and shows an error if YAML front matter is malformed. Otherwise, continues with partial parsing.</span>
218
+ </span>
219
+ </label>
220
+ </div>
221
+ </div>
222
+
223
+ <div class="option-group">
224
+ <label>Export Options</label>
225
+ <div class="checkbox-group">
226
+ <label class="checkbox-item">
227
+ <input type="checkbox" id="exportYaml"> Export YAML
228
+ <span class="tooltip">
229
+ <span class="tooltip-icon">ℹ️</span>
230
+ <span class="tooltip-text">Exports document metadata as a YAML file alongside the processed markdown. Useful for data exchange and backup.</span>
231
+ </span>
232
+ </label>
233
+ <label class="checkbox-item">
234
+ <input type="checkbox" id="exportJson"> Export JSON
235
+ <span class="tooltip">
236
+ <span class="tooltip-icon">ℹ️</span>
237
+ <span class="tooltip-text">Exports document metadata as a JSON file alongside the processed markdown. Useful for integration with other systems.</span>
238
+ </span>
239
+ </label>
240
+ </div>
241
+ <input type="text" id="title" placeholder="Document title (optional)">
242
+ </div>
243
+ </div>
244
+ </div>
245
+ </footer>
246
+
247
+ <div id="messageArea"></div>
248
+ </div>
249
+
250
+ <script src="./legal-markdown.umd.min.js"></script>
251
+ <script>
252
+ class LegalMarkdownApp {
253
+ constructor() {
254
+ this.processedResult = null;
255
+ this.metadataResult = null;
256
+ this.theme = localStorage.getItem('theme') || 'light';
257
+ this.customCSS = '';
258
+ this.baseStylesEnabled = true; // Track base styles state
259
+ this.initializeTheme();
260
+ this.initializeElements();
261
+ this.bindEvents();
262
+ this.updateBaseStyles(); // Initialize base styles
263
+ this.loadExample('service-agreement');
264
+ }
265
+
266
+ initializeTheme() {
267
+ document.documentElement.setAttribute('data-theme', this.theme);
268
+ const themeToggle = document.querySelector('.theme-toggle');
269
+ if (themeToggle) {
270
+ themeToggle.textContent = this.theme === 'dark' ? '☀️' : '🌙';
271
+ }
272
+ }
273
+
274
+ initializeElements() {
275
+ this.editor = document.querySelector('.editor');
276
+ this.preview = document.querySelector('.preview-container');
277
+ this.processBtn = document.querySelector('.process-btn');
278
+ this.toggleStylesBtn = document.querySelector('.toggle-styles-btn');
279
+ this.downloadBtn = document.querySelector('.download-btn');
280
+ this.downloadPdfBtn = document.querySelector('.download-pdf-btn');
281
+ this.printBtn = document.querySelector('.print-btn');
282
+ this.themeToggle = document.querySelector('.theme-toggle');
283
+ this.optionsToggle = document.querySelector('.options-toggle');
284
+ this.optionsContent = document.querySelector('.options-content');
285
+ this.fileInput = document.querySelector('.file-input');
286
+ this.exampleSelect = document.querySelector('.example-select');
287
+ this.messageArea = document.getElementById('messageArea');
288
+
289
+ // CSS Editor elements
290
+ this.cssReset = document.querySelector('.css-reset');
291
+ this.cssContent = document.querySelector('.css-content');
292
+ this.cssEditor = document.querySelector('.css-editor');
293
+ this.cssFileInput = document.querySelector('.css-file-input');
294
+ this.cssExampleSelect = document.querySelector('.css-example-select');
295
+
296
+ // Column resizers
297
+ this.resizer1 = document.getElementById('resizer1');
298
+ this.resizer2 = document.getElementById('resizer2');
299
+ }
300
+
301
+ bindEvents() {
302
+ this.processBtn.addEventListener('click', () => this.processContent());
303
+ this.toggleStylesBtn.addEventListener('click', () => this.toggleBaseStyles());
304
+ this.downloadBtn.addEventListener('click', () => this.downloadDocument());
305
+ this.downloadPdfBtn.addEventListener('click', () => this.downloadPDF());
306
+ this.printBtn.addEventListener('click', () => this.printDocument());
307
+ this.themeToggle.addEventListener('click', () => this.toggleTheme());
308
+ this.optionsToggle.addEventListener('click', () => this.toggleOptions());
309
+ this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
310
+ this.exampleSelect.addEventListener('change', (e) => this.loadExample(e.target.value));
311
+
312
+ // CSS Editor events
313
+ this.cssReset.addEventListener('click', () => this.resetCSS());
314
+ this.cssFileInput.addEventListener('change', (e) => this.handleCSSFileUpload(e));
315
+ this.cssExampleSelect.addEventListener('change', (e) => this.loadCSSExample(e.target.value));
316
+
317
+ // Auto-apply CSS on input (debounced)
318
+ this.cssEditor.addEventListener('input',
319
+ this.debounce(() => this.applyCSS(), 500)
320
+ );
321
+
322
+ // Column resizing
323
+ this.initColumnResizing();
324
+
325
+ // Help button
326
+ const helpBtn = document.querySelector('.help-btn');
327
+ if (helpBtn) {
328
+ helpBtn.addEventListener('click', () => this.showHelp());
329
+ }
330
+
331
+ // Auto-process on input (debounced)
332
+ this.editor.addEventListener('input',
333
+ this.debounce(() => this.processContent(), 1000)
334
+ );
335
+
336
+ // Keyboard shortcuts
337
+ document.addEventListener('keydown', (e) => this.handleKeyboardShortcuts(e));
338
+ }
339
+
340
+ async processContent() {
341
+ const content = this.editor.value;
342
+ if (!content.trim()) {
343
+ this.showMessage('Please enter some content to process.', 'error');
344
+ this.clearPreview();
345
+ return;
346
+ }
347
+
348
+ // Validate content structure
349
+ const validation = this.validateContent(content);
350
+ if (!validation.isValid) {
351
+ this.showMessage(validation.message, 'warning');
352
+ if (validation.severity === 'error') {
353
+ this.clearPreview();
354
+ return;
355
+ }
356
+ }
357
+
358
+ try {
359
+ this.showProcessing();
360
+
361
+ const options = this.getProcessingOptions();
362
+ const result = window.LegalMarkdown.processLegalMarkdown(content, options);
363
+
364
+ this.processedResult = result.content;
365
+ this.metadataResult = result.metadata;
366
+
367
+ this.displayResult(result);
368
+ this.showProcessingStats(result);
369
+
370
+ } catch (error) {
371
+ console.error('Processing error:', error);
372
+ this.handleProcessingError(error);
373
+ } finally {
374
+ this.hideProcessing();
375
+ }
376
+ }
377
+
378
+ getProcessingOptions() {
379
+ return {
380
+ debug: document.getElementById('debug').checked,
381
+ yamlOnly: document.getElementById('yamlOnly').checked,
382
+ noHeaders: document.getElementById('noHeaders').checked,
383
+ noClauses: document.getElementById('noClauses').checked,
384
+ noReferences: document.getElementById('noReferences').checked,
385
+ noMixins: document.getElementById('noMixins').checked,
386
+ noImports: document.getElementById('noImports').checked,
387
+ noReset: document.getElementById('noReset').checked,
388
+ noIndent: document.getElementById('noIndent').checked,
389
+ throwOnYamlError: document.getElementById('throwOnYamlError').checked,
390
+ enableFieldTracking: document.getElementById('enableFieldTracking').checked,
391
+ exportMetadata: document.getElementById('exportYaml').checked || document.getElementById('exportJson').checked,
392
+ exportFormat: document.getElementById('exportYaml').checked ? 'yaml' : 'json'
393
+ };
394
+ }
395
+
396
+ displayResult(result) {
397
+ this.preview.innerHTML = `
398
+ <div class="legal-document">
399
+ ${this.formatMarkdownForDisplay(result.content)}
400
+ </div>
401
+ `;
402
+
403
+ // Apply base styles if enabled
404
+ this.updateBaseStyles();
405
+ // Apply custom CSS if present
406
+ this.updatePreviewWithCSS();
407
+
408
+ if (result.fieldReport) {
409
+ console.log('Field Report:', result.fieldReport);
410
+ }
411
+ }
412
+
413
+ formatMarkdownForDisplay(content) {
414
+ // Basic markdown to HTML conversion for display
415
+ return content
416
+ .replace(/^# (.*$)/gm, '<h1>$1</h1>')
417
+ .replace(/^## (.*$)/gm, '<h2>$1</h2>')
418
+ .replace(/^### (.*$)/gm, '<h3>$1</h3>')
419
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
420
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
421
+ .replace(/\n\n/g, '</p><p>')
422
+ .replace(/^(.+)$/gm, '<p>$1</p>')
423
+ .replace(/<p><\/p>/g, '')
424
+ .replace(/<p>(<h[1-6]>.*<\/h[1-6]>)<\/p>/g, '$1');
425
+ }
426
+
427
+ downloadDocument() {
428
+ if (!this.processedResult) {
429
+ this.showMessage('Please process a document first.', 'error');
430
+ return;
431
+ }
432
+
433
+ const title = document.getElementById('title').value || 'legal-document';
434
+ const filename = `${title.replace(/[^a-zA-Z0-9]/g, '-')}.md`;
435
+ this.downloadFile(this.processedResult, filename);
436
+ }
437
+
438
+ printDocument() {
439
+ if (!this.processedResult) {
440
+ this.showMessage('Please process a document first.', 'error');
441
+ return;
442
+ }
443
+
444
+ const printContent = this.preview.innerHTML;
445
+ const title = document.getElementById('title').value || 'Legal Document';
446
+
447
+ const printWindow = window.open('', '_blank');
448
+ printWindow.document.write(`
449
+ <!DOCTYPE html>
450
+ <html>
451
+ <head>
452
+ <title>${title}</title>
453
+ <style>
454
+ ${this.getContractCSS()}
455
+ </style>
456
+ </head>
457
+ <body onload="window.print(); window.close();">
458
+ ${printContent}
459
+ </body>
460
+ </html>
461
+ `);
462
+ printWindow.document.close();
463
+ }
464
+
465
+ async downloadPDF() {
466
+ if (!this.processedResult) {
467
+ this.showMessage('Please process a document first.', 'error');
468
+ return;
469
+ }
470
+
471
+ const title = document.getElementById('title').value || 'legal-document';
472
+ const printContent = this.preview.innerHTML;
473
+
474
+ try {
475
+ // Show loading message
476
+ this.showMessage('Generating PDF... Please wait.', 'info');
477
+
478
+ // Create a hidden iframe for PDF generation
479
+ const iframe = document.createElement('iframe');
480
+ iframe.style.display = 'none';
481
+ document.body.appendChild(iframe);
482
+
483
+ iframe.contentDocument.open();
484
+ iframe.contentDocument.write(`
485
+ <!DOCTYPE html>
486
+ <html>
487
+ <head>
488
+ <title>${title}</title>
489
+ <style>
490
+ ${this.getContractCSS()}
491
+ @media print {
492
+ body { margin: 0; }
493
+ @page { margin: 15mm; }
494
+ }
495
+ </style>
496
+ </head>
497
+ <body>
498
+ ${printContent}
499
+ </body>
500
+ </html>
501
+ `);
502
+ iframe.contentDocument.close();
503
+
504
+ // Wait for content to load
505
+ await new Promise(resolve => setTimeout(resolve, 500));
506
+
507
+ // Focus the iframe and trigger print
508
+ iframe.contentWindow.focus();
509
+ iframe.contentWindow.print();
510
+
511
+ // Clean up
512
+ setTimeout(() => {
513
+ document.body.removeChild(iframe);
514
+ this.showMessage('PDF generation initiated. Use your browser\'s print dialog to save as PDF.', 'success');
515
+ }, 1000);
516
+
517
+ } catch (error) {
518
+ console.error('PDF generation error:', error);
519
+ this.showMessage('PDF generation failed. Please use the Print button instead.', 'error');
520
+ }
521
+ }
522
+
523
+ getContractCSS() {
524
+ const baseCSS = `
525
+ body {
526
+ font-family: Helvetica, Arial, sans-serif;
527
+ font-size: 12pt;
528
+ line-height: 1.3;
529
+ color: #333;
530
+ max-width: 210mm;
531
+ margin: 0 auto;
532
+ padding: 20mm 15mm;
533
+ background-color: white;
534
+ }
535
+
536
+ h1 {
537
+ color: #353954;
538
+ font-size: 1.6em;
539
+ line-height: 1.5em;
540
+ margin: 8px 8px 32px;
541
+ text-align: center;
542
+ border-bottom: solid #02370a;
543
+ padding: 0 5% 0.3em;
544
+ }
545
+
546
+ h2 {
547
+ font-size: 14pt;
548
+ margin-top: 20px;
549
+ margin-bottom: 15px;
550
+ border-bottom: 1px solid #41badf;
551
+ }
552
+
553
+ h3 {
554
+ font-size: 10pt;
555
+ font-weight: bold;
556
+ border-bottom: 1px solid #41badf;
557
+ margin-top: 30px;
558
+ margin-bottom: 8px;
559
+ }
560
+
561
+ p {
562
+ margin-bottom: 15px;
563
+ text-align: justify;
564
+ }
565
+
566
+ @media print {
567
+ @page {
568
+ size: A4;
569
+ margin: 20mm 10mm 10mm 10mm;
570
+ }
571
+
572
+ body {
573
+ font-size: 11pt;
574
+ color: black;
575
+ }
576
+ }
577
+ `;
578
+
579
+ // Add custom CSS if present
580
+ if (this.customCSS.trim()) {
581
+ return baseCSS + '\n\n/* Custom CSS */\n' + this.customCSS;
582
+ }
583
+
584
+ return baseCSS;
585
+ }
586
+
587
+ toggleTheme() {
588
+ this.theme = this.theme === 'light' ? 'dark' : 'light';
589
+ localStorage.setItem('theme', this.theme);
590
+ this.initializeTheme();
591
+ }
592
+
593
+ toggleOptions() {
594
+ this.optionsContent.classList.toggle('open');
595
+ this.optionsToggle.textContent = this.optionsContent.classList.contains('open') ?
596
+ '🔽 Processing Options' :
597
+ '⚙️ Processing Options';
598
+ }
599
+
600
+ handleFileUpload(event) {
601
+ const file = event.target.files[0];
602
+ if (!file) return;
603
+
604
+ // Validate file type
605
+ const allowedTypes = ['text/plain', 'text/markdown', ''];
606
+ const allowedExtensions = ['.md', '.txt', '.markdown'];
607
+
608
+ const fileExtension = file.name.toLowerCase().substr(file.name.lastIndexOf('.'));
609
+ if (!allowedTypes.includes(file.type) && !allowedExtensions.includes(fileExtension)) {
610
+ this.showMessage('Please select a valid text file (.md, .txt, .markdown)', 'error');
611
+ return;
612
+ }
613
+
614
+ // Validate file size (max 1MB)
615
+ const maxSize = 1024 * 1024; // 1MB
616
+ if (file.size > maxSize) {
617
+ this.showMessage('File too large. Maximum size is 1MB.', 'error');
618
+ return;
619
+ }
620
+
621
+ const reader = new FileReader();
622
+ reader.onload = (e) => {
623
+ try {
624
+ const content = e.target.result;
625
+ this.editor.value = content;
626
+ this.exampleSelect.value = ''; // Clear example selection
627
+ this.processContent();
628
+ this.showMessage(`📁 File "${file.name}" loaded successfully`, 'success');
629
+ } catch (error) {
630
+ this.showMessage('Error reading file: ' + error.message, 'error');
631
+ }
632
+ };
633
+
634
+ reader.onerror = () => {
635
+ this.showMessage('Error reading file. Please try again.', 'error');
636
+ };
637
+
638
+ reader.readAsText(file);
639
+ }
640
+
641
+ getExamples() {
642
+ return {
643
+ 'service-agreement': {
644
+ title: 'Service Agreement',
645
+ content: `---
646
+ title: Service Agreement
647
+ client: Acme Corporation
648
+ provider: Professional Services LLC
649
+ effective_date: 2024-01-01
650
+ payment_terms: 30
651
+ jurisdiction: California
652
+ ---
653
+
654
+ # {{title}}
655
+
656
+ This {{title}} is entered into on {{effective_date}} between:
657
+
658
+ **CLIENT**: {{client}}
659
+ **PROVIDER**: {{provider}}
660
+
661
+ l. Terms and Conditions
662
+ ll. Services
663
+ The Provider agrees to perform professional services as detailed in the attached statement of work.
664
+
665
+ ll. Payment Terms
666
+ Payment is due within {{payment_terms}} days of invoice date.
667
+
668
+ ll. Jurisdiction
669
+ This Agreement shall be governed by the laws of {{jurisdiction}}.
670
+
671
+ l. Conclusion
672
+ This concludes the {{title}} between the parties.`
673
+ },
674
+
675
+ 'lease-contract': {
676
+ title: 'Office Lease Agreement',
677
+ content: `---
678
+ title: OFFICE SPACE LEASE AGREEMENT
679
+ contract:
680
+ signing_date: 2024-01-15
681
+ signing_city: San Francisco
682
+ lessor:
683
+ company_name: Property Management LLC
684
+ registered_address: 123 Main Street, San Francisco, CA 94105
685
+ tax_id: 12-3456789
686
+ representative:
687
+ full_name: John Smith
688
+ id_number: 123456789
689
+ lessee:
690
+ company_name: Tech Startup Inc
691
+ registered_address: 456 Innovation Way, San Francisco, CA 94107
692
+ tax_id: 98-7654321
693
+ representative:
694
+ full_name: Jane Doe
695
+ id_number: ""
696
+ property:
697
+ address: 789 Business Plaza, Suite 500, San Francisco, CA 94108
698
+ area_sqm: 250
699
+ parking_spots: 5
700
+ payment:
701
+ monthly_rent:
702
+ text: Five Thousand
703
+ number: 5000
704
+ late_fee_applies: true
705
+ late_fee_percentage: 1.5
706
+ maintenance_included: true
707
+ maintenance:
708
+ included_services:
709
+ - Daily cleaning
710
+ - HVAC maintenance
711
+ - Security services
712
+ ---
713
+
714
+ # {{title}}
715
+
716
+ This agreement is entered into on {{contract.signing_date}} in {{contract.signing_city}}.
717
+
718
+ ## Parties
719
+
720
+ Between:
721
+
722
+ **{{lessor.company_name}}**, with registered office at {{lessor.registered_address}}, and Tax ID {{lessor.tax_id}}, hereinafter referred to as "THE LESSOR".
723
+
724
+ Represented by {{lessor.representative.full_name}} with ID {{lessor.representative.id_number}}.
725
+
726
+ And:
727
+
728
+ **{{lessee.company_name}}**, with registered office at {{lessee.registered_address}}, and Tax ID {{lessee.tax_id}}, hereinafter referred to as "THE LESSEE".
729
+
730
+ Represented by {{lessee.representative.full_name}} with ID {{lessee.representative.id_number ? lessee.representative.id_number : "[ID Required]"}}.
731
+
732
+ ## Property Details
733
+
734
+ - **Address**: {{property.address}}
735
+ - **Area**: {{property.area_sqm}} square meters
736
+ - **Parking Spots**: {{property.parking_spots}}
737
+ - **Monthly Rent**: {{payment.monthly_rent.text}} ({{payment.monthly_rent.number}}) USD
738
+
739
+ [## Late Payment Clause
740
+
741
+ Late payments will incur a fee of {{payment.late_fee_percentage}}% per month.]{payment.late_fee_applies}
742
+
743
+ [## Maintenance
744
+
745
+ The following maintenance services are included:
746
+ {{#maintenance_included}}
747
+ {{#each maintenance.included_services}}
748
+ - {{this}}
749
+ {{/each}}
750
+ {{/maintenance_included}}]{maintenance_included}`
751
+ },
752
+
753
+ 'purchase-ticket': {
754
+ title: 'Purchase Ticket',
755
+ content: '---\n' +
756
+ 'storeName: TechMart Store\n' +
757
+ 'cashierName: Alice Johnson\n' +
758
+ 'ticketNumber: "12345"\n' +
759
+ 'receiptId: RCP-2024-12345\n' +
760
+ 'taxRate: 8.5\n' +
761
+ 'items:\n' +
762
+ ' - name: Laptop Computer\n' +
763
+ ' price: 999.99\n' +
764
+ ' onSale: false\n' +
765
+ ' - name: Wireless Mouse\n' +
766
+ ' price: 29.99\n' +
767
+ ' onSale: true\n' +
768
+ ' - name: USB Cable\n' +
769
+ ' price: null\n' +
770
+ 'subtotal: 1029.98\n' +
771
+ 'taxAmount: 87.55\n' +
772
+ 'total: 1117.53\n' +
773
+ 'loyaltyMember: true\n' +
774
+ 'pointsEarned: 112\n' +
775
+ 'pointsBalance: 1523\n' +
776
+ '---\n' +
777
+ '\n' +
778
+ '# Purchase Ticket - {{ticketNumber}}\n' +
779
+ '\n' +
780
+ '**Date:** @today **Store:** {{storeName}}\n' +
781
+ '**Cashier:** {{cashierName}}\n' +
782
+ '\n' +
783
+ '## Purchased Items\n' +
784
+ '\n' +
785
+ '{{#items}}\n' +
786
+ '- {{name}} {{price ? "$" + price : "[Price Missing]"}} {{onSale ? "(ON SALE!)" : ""}}\n' +
787
+ '{{/items}}\n' +
788
+ '\n' +
789
+ '## Payment Details\n' +
790
+ '\n' +
791
+ '- Subtotal: ${{subtotal}}\n' +
792
+ '- Tax ({{taxRate}}%): ${{taxAmount}}\n' +
793
+ '- **Total**: ${{total}}\n' +
794
+ '\n' +
795
+ '{{#loyaltyMember}}\n' +
796
+ '## Customer Loyalty\n' +
797
+ '\n' +
798
+ '- Member Points Earned: {{pointsEarned}}\n' +
799
+ '- Current Balance: {{pointsBalance}}\n' +
800
+ '{{/loyaltyMember}}\n' +
801
+ '\n' +
802
+ '---\n' +
803
+ '\n' +
804
+ 'Thank you for shopping with us!\n' +
805
+ 'Receipt ID: {{receiptId}}'
806
+ },
807
+
808
+ 'nda': {
809
+ title: 'Non-Disclosure Agreement',
810
+ content: `---
811
+ title: NON-DISCLOSURE AGREEMENT
812
+ disclosing_party: TechCorp Inc.
813
+ receiving_party: John Developer
814
+ effective_date: 2024-01-01
815
+ duration_months: 24
816
+ jurisdiction: Delaware
817
+ ---
818
+
819
+ # {{title}}
820
+
821
+ This {{title}} is entered into on {{effective_date}} between:
822
+
823
+ **DISCLOSING PARTY**: {{disclosing_party}}
824
+ **RECEIVING PARTY**: {{receiving_party}}
825
+
826
+ l. Definition of Confidential Information
827
+ ll. Confidential Information includes all technical data, trade secrets, know-how, research, product plans, products, services, customers, customer lists, markets, software, developments, inventions, processes, formulas, technology, designs, drawings, engineering, hardware configuration information, marketing, finances, or other business information.
828
+
829
+ l. Obligations of Receiving Party
830
+ ll. Protection of Confidential Information
831
+ The Receiving Party agrees to hold and maintain the Confidential Information in strict confidence.
832
+
833
+ ll. Non-Disclosure
834
+ The Receiving Party agrees not to disclose any Confidential Information to third parties without prior written consent.
835
+
836
+ ll. Use Restriction
837
+ The Receiving Party agrees to use the Confidential Information solely for the purpose of evaluating potential business opportunities.
838
+
839
+ l. Term
840
+ This Agreement shall remain in effect for {{duration_months}} months from the effective date.
841
+
842
+ l. Governing Law
843
+ This Agreement shall be governed by the laws of {{jurisdiction}}.
844
+
845
+ l. Signatures
846
+ By signing below, both parties agree to the terms and conditions of this Agreement.
847
+
848
+ **{{disclosing_party}}**
849
+
850
+ Signature: ___________________________ Date: ___________
851
+
852
+ **{{receiving_party}}**
853
+
854
+ Signature: ___________________________ Date: ___________`
855
+ },
856
+
857
+ 'employment-contract': {
858
+ title: 'Employment Contract',
859
+ content: '---\n' +
860
+ 'title: EMPLOYMENT AGREEMENT\n' +
861
+ 'company: TechCorp Inc.\n' +
862
+ 'employee: Jane Smith\n' +
863
+ 'position: Senior Software Engineer\n' +
864
+ 'start_date: 2024-02-01\n' +
865
+ 'salary: 95000\n' +
866
+ 'benefits_package: Standard\n' +
867
+ 'probation_period: 3\n' +
868
+ 'vacation_days: 20\n' +
869
+ '---\n' +
870
+ '\n' +
871
+ '# {{title}}\n' +
872
+ '\n' +
873
+ 'This {{title}} is entered into between:\n' +
874
+ '\n' +
875
+ '**COMPANY**: {{company}}\n' +
876
+ '**EMPLOYEE**: {{employee}}\n' +
877
+ '\n' +
878
+ 'l. Position and Duties\n' +
879
+ 'll. Position\n' +
880
+ 'The Employee is hereby employed as {{position}}.\n' +
881
+ '\n' +
882
+ 'll. Start Date\n' +
883
+ 'Employment shall commence on {{start_date}}.\n' +
884
+ '\n' +
885
+ 'll. Probationary Period\n' +
886
+ 'The Employee shall serve a probationary period of {{probation_period}} months.\n' +
887
+ '\n' +
888
+ 'l. Compensation and Benefits\n' +
889
+ 'll. Salary\n' +
890
+ 'The Employee shall receive an annual salary of ${{salary}}.\n' +
891
+ '\n' +
892
+ 'll. Benefits\n' +
893
+ 'The Employee shall be entitled to the {{benefits_package}} benefits package.\n' +
894
+ '\n' +
895
+ 'll. Vacation\n' +
896
+ 'The Employee shall be entitled to {{vacation_days}} vacation days per year.\n' +
897
+ '\n' +
898
+ 'l. Termination\n' +
899
+ 'll. Either party may terminate this agreement with 30 days written notice.\n' +
900
+ '\n' +
901
+ 'l. Confidentiality\n' +
902
+ 'll. The Employee agrees to maintain the confidentiality of all company information.\n' +
903
+ '\n' +
904
+ 'l. Governing Law\n' +
905
+ 'This Agreement shall be governed by the laws of the state in which the Company is located.\n' +
906
+ '\n' +
907
+ '**Signatures**\n' +
908
+ '\n' +
909
+ 'Company Representative: ___________________________ Date: ___________\n' +
910
+ '\n' +
911
+ 'Employee: ___________________________ Date: ___________'
912
+ }
913
+ };
914
+ }
915
+
916
+ loadExample(exampleKey) {
917
+ if (!exampleKey) return;
918
+
919
+ const examples = this.getExamples();
920
+ const example = examples[exampleKey];
921
+
922
+ if (example) {
923
+ this.editor.value = example.content;
924
+ this.exampleSelect.value = exampleKey;
925
+ this.processContent();
926
+ } else {
927
+ this.showMessage('Example not found.', 'error');
928
+ }
929
+ }
930
+
931
+ validateContent(content) {
932
+ const validation = {
933
+ isValid: true,
934
+ message: '',
935
+ severity: 'info'
936
+ };
937
+
938
+ // Check for YAML front matter
939
+ const hasYamlFrontMatter = content.trim().startsWith('---');
940
+ if (!hasYamlFrontMatter) {
941
+ validation.isValid = false;
942
+ validation.message = 'No YAML front matter detected. Consider adding document metadata with ---';
943
+ validation.severity = 'warning';
944
+ return validation;
945
+ }
946
+
947
+ // Check for balanced YAML front matter
948
+ const yamlMatches = content.match(/^---\s*\n([\s\S]*?)\n---\s*$/m);
949
+ if (!yamlMatches) {
950
+ validation.isValid = false;
951
+ validation.message = 'YAML front matter is not properly formatted. Ensure it starts and ends with ---';
952
+ validation.severity = 'error';
953
+ return validation;
954
+ }
955
+
956
+ // Check for common YAML errors
957
+ const yamlContent = yamlMatches[1];
958
+ if (yamlContent.includes('\t')) {
959
+ validation.isValid = false;
960
+ validation.message = 'YAML contains tabs. Use spaces for indentation instead.';
961
+ validation.severity = 'error';
962
+ return validation;
963
+ }
964
+
965
+ // Check for template variables
966
+ const templateVars = content.match(/\{\{([^}]+)\}\}/g);
967
+ if (templateVars) {
968
+ const uniqueVars = [...new Set(templateVars.map(v => v.replace(/[{}]/g, '').trim()))];
969
+ if (uniqueVars.length > 10) {
970
+ validation.isValid = false;
971
+ validation.message = `Document contains ${uniqueVars.length} template variables. Consider breaking into sections.`;
972
+ validation.severity = 'warning';
973
+ }
974
+ }
975
+
976
+ // Check for unbalanced brackets
977
+ const openBrackets = (content.match(/\{\{/g) || []).length;
978
+ const closeBrackets = (content.match(/\}\}/g) || []).length;
979
+ if (openBrackets !== closeBrackets) {
980
+ validation.isValid = false;
981
+ validation.message = `Unbalanced template brackets: ${openBrackets} opening, ${closeBrackets} closing`;
982
+ validation.severity = 'error';
983
+ return validation;
984
+ }
985
+
986
+ return validation;
987
+ }
988
+
989
+ handleProcessingError(error) {
990
+ let userMessage = 'Error processing document: ';
991
+ let suggestions = [];
992
+
993
+ if (error.message.includes('YAML')) {
994
+ userMessage += 'YAML parsing error. ';
995
+ suggestions.push('Check your YAML syntax in the front matter');
996
+ suggestions.push('Ensure proper indentation with spaces, not tabs');
997
+ suggestions.push('Verify all YAML keys have values');
998
+ } else if (error.message.includes('template')) {
999
+ userMessage += 'Template processing error. ';
1000
+ suggestions.push('Check that all variables are defined in YAML front matter');
1001
+ suggestions.push('Verify template syntax: {{variable}}');
1002
+ } else if (error.message.includes('reference')) {
1003
+ userMessage += 'Reference processing error. ';
1004
+ suggestions.push('Check reference syntax and file paths');
1005
+ } else {
1006
+ userMessage += error.message;
1007
+ }
1008
+
1009
+ if (suggestions.length > 0) {
1010
+ userMessage += '\n\nSuggestions:\n• ' + suggestions.join('\n• ');
1011
+ }
1012
+
1013
+ this.showMessage(userMessage, 'error');
1014
+ this.clearPreview();
1015
+ }
1016
+
1017
+ showProcessingStats(result) {
1018
+ let stats = [];
1019
+
1020
+ if (result.metadata) {
1021
+ const yamlKeys = Object.keys(result.metadata).length;
1022
+ stats.push(`${yamlKeys} YAML variables`);
1023
+ }
1024
+
1025
+ if (result.fieldReport) {
1026
+ stats.push(`${result.fieldReport.length} field substitutions`);
1027
+ }
1028
+
1029
+ const wordCount = result.content.split(/\s+/).filter(word => word.length > 0).length;
1030
+ stats.push(`${wordCount} words`);
1031
+
1032
+ if (stats.length > 0) {
1033
+ this.showMessage(`✅ Processing complete: ${stats.join(', ')}`, 'success');
1034
+ } else {
1035
+ this.showMessage('Document processed successfully!', 'success');
1036
+ }
1037
+ }
1038
+
1039
+ clearPreview() {
1040
+ this.preview.innerHTML = `
1041
+ <div class="preview-placeholder">
1042
+ <p>👈 Enter your Legal Markdown content and click Process to see the result</p>
1043
+ </div>
1044
+ `;
1045
+ }
1046
+
1047
+ showHelp() {
1048
+ const helpContent = `📖 Legal Markdown Help
1049
+
1050
+ 🚀 Quick Start:
1051
+ • Select an example from the dropdown to get started
1052
+ • Or write your own content with YAML front matter
1053
+ • Use {{variable}} syntax for template substitutions
1054
+
1055
+ 📝 Document Structure:
1056
+ ---
1057
+ title: Your Document Title
1058
+ variable: value
1059
+ ---
1060
+
1061
+ # {{title}}
1062
+
1063
+ Your content with {{variable}} substitutions...
1064
+
1065
+ ⌨️ Keyboard Shortcuts:
1066
+ • Ctrl+Enter: Process document
1067
+ • Ctrl+P: Print document
1068
+ • Ctrl+S: Download Markdown
1069
+ • Ctrl+D: Download PDF
1070
+
1071
+ 🔧 Processing Options:
1072
+ • Field Tracking: Highlight variable substitutions
1073
+ • Debug Mode: Show detailed processing information
1074
+ • Export Options: Save metadata as YAML or JSON
1075
+
1076
+ 💡 Tips:
1077
+ • Use proper YAML indentation (spaces, not tabs)
1078
+ • Check template brackets are balanced {{}}
1079
+ • Use legal numbering: l., ll., lll. for sections`;
1080
+
1081
+ this.showMessage(helpContent, 'info');
1082
+ }
1083
+
1084
+ handleKeyboardShortcuts(event) {
1085
+ if (event.ctrlKey || event.metaKey) {
1086
+ switch (event.key) {
1087
+ case 'Enter':
1088
+ event.preventDefault();
1089
+ this.processContent();
1090
+ break;
1091
+ case 'p':
1092
+ event.preventDefault();
1093
+ this.printDocument();
1094
+ break;
1095
+ case 's':
1096
+ event.preventDefault();
1097
+ this.downloadDocument();
1098
+ break;
1099
+ case 'd':
1100
+ event.preventDefault();
1101
+ this.downloadPDF();
1102
+ break;
1103
+ }
1104
+ }
1105
+ }
1106
+
1107
+ showProcessing() {
1108
+ this.processBtn.disabled = true;
1109
+ this.processBtn.textContent = '⏳ Processing...';
1110
+ this.preview.classList.add('loading');
1111
+ }
1112
+
1113
+ hideProcessing() {
1114
+ this.processBtn.disabled = false;
1115
+ this.processBtn.textContent = '▶️ Process';
1116
+ this.preview.classList.remove('loading');
1117
+ }
1118
+
1119
+ showMessage(message, type) {
1120
+ const messageDiv = document.createElement('div');
1121
+ messageDiv.className = `message ${type}`;
1122
+
1123
+ // Handle multi-line messages
1124
+ if (message.includes('\n')) {
1125
+ const lines = message.split('\n');
1126
+ const mainMessage = lines[0];
1127
+ const details = lines.slice(1).join('\n');
1128
+
1129
+ messageDiv.innerHTML = `
1130
+ <div class="message-main">${this.escapeHtml(mainMessage)}</div>
1131
+ <div class="message-details">${this.escapeHtml(details)}</div>
1132
+ `;
1133
+ } else {
1134
+ messageDiv.textContent = message;
1135
+ }
1136
+
1137
+ this.messageArea.innerHTML = '';
1138
+ this.messageArea.appendChild(messageDiv);
1139
+
1140
+ // Auto-hide success messages, keep errors/warnings visible longer
1141
+ const hideDelay = type === 'success' ? 3000 : 8000;
1142
+ setTimeout(() => {
1143
+ if (this.messageArea.contains(messageDiv)) {
1144
+ messageDiv.style.opacity = '0';
1145
+ setTimeout(() => {
1146
+ if (this.messageArea.contains(messageDiv)) {
1147
+ this.messageArea.removeChild(messageDiv);
1148
+ }
1149
+ }, 300);
1150
+ }
1151
+ }, hideDelay);
1152
+ }
1153
+
1154
+ escapeHtml(text) {
1155
+ const div = document.createElement('div');
1156
+ div.textContent = text;
1157
+ return div.innerHTML;
1158
+ }
1159
+
1160
+ downloadFile(content, filename) {
1161
+ const blob = new Blob([content], {
1162
+ type: 'text/plain'
1163
+ });
1164
+ const url = URL.createObjectURL(blob);
1165
+ const a = document.createElement('a');
1166
+ a.href = url;
1167
+ a.download = filename;
1168
+ document.body.appendChild(a);
1169
+ a.click();
1170
+ document.body.removeChild(a);
1171
+ URL.revokeObjectURL(url);
1172
+ }
1173
+
1174
+ debounce(func, wait) {
1175
+ let timeout;
1176
+ return function executedFunction(...args) {
1177
+ const later = () => {
1178
+ clearTimeout(timeout);
1179
+ func(...args);
1180
+ };
1181
+ clearTimeout(timeout);
1182
+ timeout = setTimeout(later, wait);
1183
+ };
1184
+ }
1185
+
1186
+ toggleCSSEditor() {
1187
+ const isVisible = this.cssContent.style.display !== 'none';
1188
+
1189
+ if (isVisible) {
1190
+ // Hide CSS editor
1191
+ this.cssContent.style.display = 'none';
1192
+ this.cssReset.style.display = 'none';
1193
+ this.cssApply.style.display = 'none';
1194
+ this.cssToggle.textContent = '📝 Edit CSS';
1195
+ } else {
1196
+ // Show CSS editor
1197
+ this.cssContent.style.display = 'block';
1198
+ this.cssReset.style.display = 'inline-block';
1199
+ this.cssApply.style.display = 'inline-block';
1200
+ this.cssToggle.textContent = '🔽 Hide CSS';
1201
+ }
1202
+ }
1203
+
1204
+ resetCSS() {
1205
+ this.cssEditor.value = '';
1206
+ this.customCSS = '';
1207
+ this.applyCSS();
1208
+ this.showMessage('CSS reset successfully', 'success');
1209
+ }
1210
+
1211
+ applyCSS() {
1212
+ this.customCSS = this.cssEditor.value;
1213
+ this.updatePreviewWithCSS();
1214
+ // No mostrar mensaje en auto-apply para evitar spam
1215
+ }
1216
+
1217
+ handleCSSFileUpload(event) {
1218
+ const file = event.target.files[0];
1219
+ if (!file) return;
1220
+
1221
+ if (!file.name.toLowerCase().endsWith('.css')) {
1222
+ this.showMessage('Please select a valid CSS file (.css)', 'error');
1223
+ return;
1224
+ }
1225
+
1226
+ const reader = new FileReader();
1227
+ reader.onload = (e) => {
1228
+ try {
1229
+ const content = e.target.result;
1230
+ this.cssEditor.value = content;
1231
+ this.cssExampleSelect.value = '';
1232
+ this.applyCSS();
1233
+ this.showMessage(`📁 CSS file "${file.name}" loaded successfully`, 'success');
1234
+ } catch (error) {
1235
+ this.showMessage('Error reading CSS file: ' + error.message, 'error');
1236
+ }
1237
+ };
1238
+
1239
+ reader.onerror = () => {
1240
+ this.showMessage('Error reading CSS file. Please try again.', 'error');
1241
+ };
1242
+
1243
+ reader.readAsText(file);
1244
+ }
1245
+
1246
+ getCSSExamples() {
1247
+ return {
1248
+ 'modern': `/* Modern Clean Style */
1249
+ .preview-container {
1250
+ font-family: 'Segoe UI', system-ui, sans-serif;
1251
+ line-height: 1.6;
1252
+ color: #2c3e50;
1253
+ }
1254
+
1255
+ h1 {
1256
+ color: #2980b9;
1257
+ border-bottom: 3px solid #3498db;
1258
+ padding-bottom: 10px;
1259
+ font-weight: 300;
1260
+ }
1261
+
1262
+ h2 {
1263
+ color: #34495e;
1264
+ border-left: 4px solid #3498db;
1265
+ padding-left: 16px;
1266
+ margin-top: 2em;
1267
+ }
1268
+
1269
+ p {
1270
+ margin-bottom: 1.5em;
1271
+ text-align: left;
1272
+ }`,
1273
+
1274
+ 'classic': `/* Classic Legal Document */
1275
+ .preview-container {
1276
+ font-family: 'Times New Roman', serif;
1277
+ line-height: 1.8;
1278
+ color: #000;
1279
+ }
1280
+
1281
+ h1 {
1282
+ text-align: center;
1283
+ text-transform: uppercase;
1284
+ letter-spacing: 2px;
1285
+ border-bottom: double 3px #000;
1286
+ padding-bottom: 15px;
1287
+ }
1288
+
1289
+ h2 {
1290
+ font-variant: small-caps;
1291
+ border-bottom: 1px solid #666;
1292
+ margin-top: 2em;
1293
+ }
1294
+
1295
+ p {
1296
+ text-align: justify;
1297
+ text-indent: 2em;
1298
+ margin-bottom: 1em;
1299
+ }`,
1300
+
1301
+ 'minimal': `/* Minimal Style */
1302
+ .preview-container {
1303
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
1304
+ line-height: 1.7;
1305
+ color: #333;
1306
+ max-width: none;
1307
+ }
1308
+
1309
+ h1 {
1310
+ font-size: 1.8em;
1311
+ font-weight: 600;
1312
+ color: #000;
1313
+ border: none;
1314
+ margin-bottom: 2em;
1315
+ }
1316
+
1317
+ h2 {
1318
+ font-size: 1.3em;
1319
+ font-weight: 500;
1320
+ color: #555;
1321
+ border: none;
1322
+ margin-top: 2.5em;
1323
+ margin-bottom: 1em;
1324
+ }
1325
+
1326
+ p {
1327
+ margin-bottom: 1.2em;
1328
+ text-align: left;
1329
+ }`
1330
+ };
1331
+ }
1332
+
1333
+ loadCSSExample(exampleKey) {
1334
+ if (!exampleKey) return;
1335
+
1336
+ const examples = this.getCSSExamples();
1337
+ const example = examples[exampleKey];
1338
+
1339
+ if (example) {
1340
+ this.cssEditor.value = example;
1341
+ this.cssExampleSelect.value = exampleKey;
1342
+ this.applyCSS();
1343
+ this.showMessage(`🎨 CSS example "${exampleKey}" loaded`, 'success');
1344
+ } else {
1345
+ this.showMessage('CSS example not found.', 'error');
1346
+ }
1347
+ }
1348
+
1349
+ initColumnResizing() {
1350
+ const container = document.querySelector('.editor-container');
1351
+ let isResizing = false;
1352
+ let currentResizer = null;
1353
+ let startX = 0;
1354
+ let startWidths = [1, 1, 1]; // Start with equal widths
1355
+
1356
+ const resizers = [this.resizer1, this.resizer2];
1357
+
1358
+ resizers.forEach((resizer, index) => {
1359
+ if (!resizer) return;
1360
+
1361
+ resizer.addEventListener('mousedown', (e) => {
1362
+ isResizing = true;
1363
+ currentResizer = resizer;
1364
+ startX = e.clientX;
1365
+
1366
+ // Parse current fr values from the style
1367
+ const currentStyle = container.style.gridTemplateColumns;
1368
+ if (currentStyle && currentStyle.includes('fr')) {
1369
+ const matches = currentStyle.match(/(\d*\.?\d+)fr/g);
1370
+ if (matches && matches.length === 3) {
1371
+ startWidths = matches.map(match => parseFloat(match.replace('fr', '')));
1372
+ }
1373
+ }
1374
+
1375
+ document.addEventListener('mousemove', handleMouseMove);
1376
+ document.addEventListener('mouseup', handleMouseUp);
1377
+ e.preventDefault();
1378
+ });
1379
+ });
1380
+
1381
+ const handleMouseMove = (e) => {
1382
+ if (!isResizing || !currentResizer) return;
1383
+
1384
+ const deltaX = e.clientX - startX;
1385
+ const containerWidth = container.offsetWidth;
1386
+ // Convert pixels to fraction units (balanced sensitivity)
1387
+ const deltaFraction = (deltaX / containerWidth) * 4;
1388
+
1389
+ let newWidths = [...startWidths];
1390
+ const minWidth = 0.3; // Minimum 30% width
1391
+
1392
+ if (currentResizer === this.resizer1) {
1393
+ // Resizing between columns 1 and 2
1394
+ newWidths[0] = Math.max(minWidth, startWidths[0] + deltaFraction);
1395
+ newWidths[1] = Math.max(minWidth, startWidths[1] - deltaFraction);
1396
+ } else if (currentResizer === this.resizer2) {
1397
+ // Resizing between columns 2 and 3
1398
+ newWidths[1] = Math.max(minWidth, startWidths[1] + deltaFraction);
1399
+ newWidths[2] = Math.max(minWidth, startWidths[2] - deltaFraction);
1400
+ }
1401
+
1402
+ // Apply the new column widths
1403
+ container.style.gridTemplateColumns = `${newWidths[0]}fr 4px ${newWidths[1]}fr 4px ${newWidths[2]}fr`;
1404
+ };
1405
+
1406
+ const handleMouseUp = () => {
1407
+ isResizing = false;
1408
+ currentResizer = null;
1409
+ document.removeEventListener('mousemove', handleMouseMove);
1410
+ document.removeEventListener('mouseup', handleMouseUp);
1411
+ };
1412
+ }
1413
+
1414
+ updatePreviewWithCSS() {
1415
+ // Remove existing custom style tag if it exists
1416
+ const existingStyle = document.getElementById('customCSS');
1417
+ if (existingStyle) {
1418
+ existingStyle.remove();
1419
+ }
1420
+
1421
+ // Add new custom CSS if provided
1422
+ if (this.customCSS.trim()) {
1423
+ const styleTag = document.createElement('style');
1424
+ styleTag.id = 'customCSS';
1425
+ styleTag.textContent = `
1426
+ .preview-container {
1427
+ ${this.customCSS}
1428
+ }
1429
+ `;
1430
+ document.head.appendChild(styleTag);
1431
+ }
1432
+ }
1433
+
1434
+ toggleBaseStyles() {
1435
+ this.baseStylesEnabled = !this.baseStylesEnabled;
1436
+ this.updateBaseStyles();
1437
+
1438
+ // Update button appearance
1439
+ this.toggleStylesBtn.style.opacity = this.baseStylesEnabled ? '1' : '0.5';
1440
+ this.toggleStylesBtn.title = this.baseStylesEnabled ?
1441
+ 'Disable base styles' : 'Enable base styles';
1442
+
1443
+ this.showMessage(
1444
+ this.baseStylesEnabled ? 'Base styles enabled' : 'Base styles disabled',
1445
+ 'info'
1446
+ );
1447
+ }
1448
+
1449
+ updateBaseStyles() {
1450
+ if (this.baseStylesEnabled) {
1451
+ this.preview.classList.add('styled-preview');
1452
+ } else {
1453
+ this.preview.classList.remove('styled-preview');
1454
+ }
1455
+ }
1456
+ }
1457
+
1458
+ // Initialize app when DOM is ready
1459
+ document.addEventListener('DOMContentLoaded', () => {
1460
+ new LegalMarkdownApp();
1461
+ });
1462
+ </script>
1463
+ </body>
1464
+
1465
+ </html>