Sam Lai commited on
Commit
b4466be
·
1 Parent(s): a37339a

added application file

Browse files
Files changed (4) hide show
  1. app.py +11 -0
  2. dockerfile +13 -0
  3. requirements.txt +6 -0
  4. static/index.html +1196 -0
app.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+
3
+ app = FastAPI()
4
+
5
+
6
+
7
+ app.mount("/", StaticFiles(directory="static", html=True), name="static")
8
+
9
+ @app.get("/")
10
+ def index() -> FileResponse:
11
+ return FileResponse(path="/app/static/index.html", media_type="text/html")
dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.74.*
2
+ requests==2.27.*
3
+ sentencepiece==0.1.*
4
+ torch==1.11.*
5
+ transformers==4.*
6
+ uvicorn[standard]==0.17.*
static/index.html ADDED
@@ -0,0 +1,1196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html><html><head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ <title>Nested Object Editor & Viewer</title>
5
+ <style>
6
+ body {
7
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
8
+ margin: 0;
9
+ padding: 20px;
10
+ background-color: #f0f8ff;
11
+ display: flex;
12
+ flex-direction: column;
13
+ min-height: 100vh;
14
+ }
15
+ .tree-container {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: 20px;
19
+ overflow-x: auto;
20
+ padding-bottom: 20px;
21
+ flex-grow: 1;
22
+ }
23
+ .tree {
24
+ display: flex;
25
+ flex-direction: column;
26
+ min-width: fit-content;
27
+ }
28
+ .tree-item {
29
+ margin: 5px 0;
30
+ padding: 10px;
31
+ border-radius: 5px;
32
+ transition: all 0.3s ease;
33
+ background-color: white;
34
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
35
+ min-width: 300px;
36
+ }
37
+ .tree-item:hover {
38
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
39
+ }
40
+ .tree-content {
41
+ display: flex;
42
+ align-items: center;
43
+ flex-wrap: nowrap;
44
+ min-width: 100%;
45
+ }
46
+ .tree-actions {
47
+ display: none;
48
+ margin-left: auto;
49
+ white-space: nowrap;
50
+ }
51
+
52
+ .tree-content:hover > .tree-actions {
53
+ display: flex;
54
+ }
55
+
56
+ .tree-content:hover > .tree-actions.hidden {
57
+ display: none;
58
+ }
59
+
60
+ .tree-actions.hidden {
61
+ display: none;
62
+ }
63
+
64
+
65
+ .icon-button {
66
+ background: #2ecc71;
67
+ border: none;
68
+ cursor: pointer;
69
+ padding: 6px;
70
+ margin: 0 2px;
71
+ transition: transform 0.2s ease, background-color 0.2s ease;
72
+ border-radius: 50%;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ width: 28px;
77
+ height: 28px;
78
+ flex-shrink: 0;
79
+ }
80
+ .icon-button:hover {
81
+ transform: scale(1.1);
82
+ background-color: #27ae60;
83
+ }
84
+ .icon-button svg {
85
+ width: 16px;
86
+ height: 16px;
87
+ fill: white;
88
+ }
89
+ input[type="text"], select {
90
+ padding: 5px;
91
+ margin: 5px;
92
+ border: 1px solid #ccc;
93
+ border-radius: 3px;
94
+ }
95
+ .expand-collapse {
96
+ cursor: pointer;
97
+ margin-right: 10px;
98
+ width: 20px;
99
+ text-align: center;
100
+ font-weight: bold;
101
+ flex-shrink: 0;
102
+ }
103
+ .collapsed > .child-tree {
104
+ display: none;
105
+ }
106
+ .child-tree {
107
+ border-left: 2px solid #e0e0e0;
108
+ padding-left: 10px;
109
+ transition: padding-left 0.3s ease;
110
+ }
111
+ .color-dot {
112
+ width: 16px;
113
+ height: 16px;
114
+ border-radius: 50%;
115
+ margin-right: 10px;
116
+ flex-shrink: 0;
117
+ }
118
+ #add-sibling-tree {
119
+ background-color: #2ecc71;
120
+ border: none;
121
+ color: white;
122
+ width: 40px;
123
+ height: 40px;
124
+ text-align: center;
125
+ text-decoration: none;
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ font-size: 24px;
130
+ margin: 20px 0;
131
+ transition-duration: 0.4s;
132
+ cursor: pointer;
133
+ border-radius: 50%;
134
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
135
+ }
136
+ #add-sibling-tree:hover {
137
+ background-color: #27ae60;
138
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
139
+ }
140
+ .fields-container {
141
+ margin-top: 5px;
142
+ display: flex;
143
+ flex-direction: column;
144
+ width: calc(100% - 26px);
145
+ padding-left: 26px;
146
+ }
147
+ .field {
148
+ display: flex;
149
+ align-items: center;
150
+ margin: 2px 0;
151
+ padding: 3px;
152
+ border-radius: 3px;
153
+ transition: background-color 0.3s ease;
154
+ font-size: 0.85em;
155
+ background-color: #f5f5f5;
156
+ max-width: 100%;
157
+ box-sizing: border-box;
158
+ }
159
+ .field:hover {
160
+ background-color: #e8e8e8;
161
+ }
162
+ .field-name {
163
+ font-weight: bold;
164
+ margin-right: 5px;
165
+ min-width: 100px;
166
+ color: #555;
167
+ flex-shrink: 0;
168
+ }
169
+ .field-value {
170
+ flex-grow: 1;
171
+ color: #333;
172
+ word-break: break-word;
173
+ overflow: hidden;
174
+ text-overflow: ellipsis;
175
+ }
176
+ .field-value a {
177
+ color: #4a90e2;
178
+ text-decoration: none;
179
+ }
180
+ .field-value a:hover {
181
+ text-decoration: underline;
182
+ }
183
+ .field-actions {
184
+ display: none;
185
+ margin-left: 5px;
186
+ flex-shrink: 0;
187
+ }
188
+ .field:hover .field-actions {
189
+ display: flex;
190
+ }
191
+ .alert-icon-container {
192
+ display: none;
193
+ flex-grow: 1;
194
+ line-height: 2.5rem;
195
+ text-align: right;
196
+ }
197
+
198
+ .alert-icon-container.show {
199
+ display:block;
200
+ }
201
+
202
+
203
+ .alert-icon {
204
+ width: 2rem;
205
+ vertical-align: middle;
206
+ }
207
+
208
+ .modal {
209
+ display: none;
210
+ position: fixed;
211
+ z-index: 1;
212
+ left: 0;
213
+ top: 0;
214
+ width: 100%;
215
+ height: 100%;
216
+ overflow: auto;
217
+ background-color: rgba(0,0,0,0.4);
218
+ }
219
+ .modal-content {
220
+ background-color: #fefefe;
221
+ margin: 15% auto;
222
+ padding: 20px;
223
+ border: 1px solid #888;
224
+ width: 300px;
225
+ border-radius: 5px;
226
+ }
227
+ .close {
228
+ color: #aaa;
229
+ float: right;
230
+ font-size: 28px;
231
+ font-weight: bold;
232
+ cursor: pointer;
233
+ }
234
+ .close:hover,
235
+ .close:focus {
236
+ color: black;
237
+ text-decoration: none;
238
+ cursor: pointer;
239
+ }
240
+ #customFieldName {
241
+ display: none;
242
+ }
243
+ .edit-mode input {
244
+ border: none;
245
+ background: transparent;
246
+ font-size: inherit;
247
+ font-family: inherit;
248
+ padding: 0;
249
+ margin: 0;
250
+ width: 100%;
251
+ outline: none;
252
+ }
253
+ .edit-actions {
254
+ display: none;
255
+ }
256
+ .edit-mode .edit-actions {
257
+ display: flex;
258
+ }
259
+ .edit-mode .tree-actions {
260
+ display: none;
261
+ }
262
+ .edit-actions .icon-button {
263
+ background: transparent;
264
+ padding: 0;
265
+ width: auto;
266
+ height: auto;
267
+ }
268
+ .edit-actions .icon-button:hover {
269
+ background: transparent;
270
+ }
271
+ .edit-actions .icon-button svg {
272
+ width: 20px;
273
+ height: 20px;
274
+ }
275
+ .save-button svg {
276
+ fill: none;
277
+ stroke: #2ecc71;
278
+ stroke-width: 2;
279
+ }
280
+ .cancel-button svg {
281
+ fill: none;
282
+ stroke: #e74c3c;
283
+ stroke-width: 2;
284
+ }
285
+ .tree-label {
286
+ flex-grow: 1;
287
+ white-space: nowrap;
288
+ overflow: hidden;
289
+ text-overflow: ellipsis;
290
+ }
291
+ #output-container {
292
+ margin-top: 20px;
293
+ padding: 10px;
294
+ background-color: #fff;
295
+ border-radius: 5px;
296
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
297
+ }
298
+
299
+
300
+ #output-text-field, #output-json-field, #output-markdown-field {
301
+ width: calc(100% - 5px);
302
+ box-sizing: border-box;
303
+ margin: 0;
304
+ white-space: pre;
305
+ overflow-x: auto;
306
+ height: 150px;
307
+ resize: vertical;
308
+ padding: 10px;
309
+ border: 1px solid #ccc;
310
+ border-radius: 5px;
311
+ font-family: 'Courier New', Courier, monospace;
312
+ display: none;
313
+ font-size: 14px;
314
+ }
315
+
316
+ #output-text-field.active, #output-json-field.active, #output-markdown-field.active {
317
+ display: block;
318
+ }
319
+
320
+ .output-tabs {
321
+ display: flex;
322
+ border-bottom: 1px solid #ccc;
323
+ margin-bottom: 10px;
324
+ }
325
+ .output-tab.active {
326
+ background-color: #fff;
327
+ border-bottom: 1px solid #fff;
328
+ margin-bottom: -1px;
329
+ }
330
+
331
+ .output-tab {
332
+ padding: 10px 20px;
333
+ cursor: pointer;
334
+ background-color: #f0f0f0;
335
+ border: 1px solid #ccc;
336
+ border-bottom: none;
337
+ border-radius: 5px 5px 0 0;
338
+ margin-right: 5px;
339
+ }
340
+
341
+
342
+
343
+ </style>
344
+ </head>
345
+ <body>
346
+ <div class="tree-container" id="main-container">
347
+ <div class="tree">
348
+ <div class="tree-item">
349
+ <div class="tree-content">
350
+ <span class="expand-collapse">▼</span>
351
+ <div class="color-dot" style="background-color: #ff4757;"></div>
352
+ <span class="tree-label">Object 1</span>
353
+ <input type="text" class="edit-input" style="display: none;">
354
+ <div class="tree-actions">
355
+ <button class="icon-button" onclick="addChildFromButton(this)" title="Add Child">
356
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
357
+ <line x1="12" y1="5" x2="12" y2="19"></line>
358
+ <line x1="5" y1="12" x2="19" y2="12"></line>
359
+ </svg>
360
+ </button>
361
+ <button class="icon-button" onclick="editItem(this)" title="Edit">
362
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
363
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
364
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
365
+ </svg>
366
+ </button>
367
+ <button class="icon-button" onclick="deleteItem(this)" title="Delete">
368
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
369
+ <polyline points="3 6 5 6 21 6"></polyline>
370
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
371
+ </svg>
372
+ </button>
373
+ <button class="icon-button" onclick="showAddFieldModal(this)" title="Add Field">
374
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
375
+ <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
376
+ </svg>
377
+ </button>
378
+ </div>
379
+ <div class="edit-actions" style="display: none;">
380
+ <button class="icon-button save-button" onclick="saveEdit(this)" title="Save">
381
+ <svg viewBox="0 0 24 24" fill="none" stroke="#2ecc71" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
382
+ <polyline points="20 6 9 17 4 12"></polyline>
383
+ </svg>
384
+ </button>
385
+ <button class="icon-button cancel-button" onclick="cancelEdit(this)" title="Cancel">
386
+ <svg viewBox="0 0 24 24" fill="none" stroke="#e74c3c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
387
+ <line x1="18" y1="6" x2="6" y2="18"></line>
388
+ <line x1="6" y1="6" x2="18" y2="18"></line>
389
+ </svg>
390
+ </button>
391
+ </div>
392
+ </div>
393
+ <div class="fields-container"></div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ <button id="add-sibling-tree" onclick="addTreeFromBtn()" title="Add Sibling Tree">
398
+ <svg viewBox="0 0 24 24" fill="white" width="24" height="24">
399
+ <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path>
400
+ </svg>
401
+ </button>
402
+
403
+ <div id="output-container">
404
+ <div class="output-tabs">
405
+ <div id="output-text-tab" class="output-tab active" onclick="onSwitchTab('text')" >Text Output</div>
406
+ <div id="output-json-tab" class="output-tab" onclick="onSwitchTab('json')" >JSON Output</div>
407
+ <div id="output-markdown-tab" class="output-tab" onclick="onSwitchTab('markdown')" >MD Output</div>
408
+ <div id="output-export-btn" style="color:#fff;border-radius:15%;width:auto;line-height:2.5em; margin:0.3em 0.2em" class="icon-button" >
409
+ Export
410
+ <button style="background: transparent;padding: 4; border:0;width: auto;height: auto;display:inline; vertical-align: middle;">
411
+ <svg fill="#ffffff" version="1.1" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
412
+ <path fill="#ffffff" d="M20,24H0V0h14.41L20,5.59v4.38h-2V8h-6V2H2v20h18V24z M14,6h3.59L14,2.41V6z M18.71,20.71l-1.41-1.41L19.59,17H11v-2h8.59
413
+ l-2.29-2.29l1.41-1.41L23.41,16L18.71,20.71z"/>
414
+ </svg>
415
+ </button>
416
+ </div>
417
+ <div id='parse-error' class="alert-icon-container">
418
+ Invalid Input!
419
+ <svg fill="#f00" viewBox="0 0 24 24" class="alert-icon">
420
+ <path d="M12.205 3.839a0.239 0.239 0 0 0 -0.08 -0.08c-0.113 -0.069 -0.261 -0.033 -0.33 0.08L1.881 20.083a0.24 0.24 0 0 0 -0.035
421
+ 0.125c0 0.133 0.107 0.24 0.24 0.24h19.828c0.044 0 0.087 -0.012 0.125 -0.035 0.113 -0.069 0.149 -0.217 0.08 -0.33L12.205 3.839zm1.024
422
+ -0.625L23.143 19.458c0.414 0.679 0.2 1.565 -0.479 1.979a1.44 1.44 0 0 1 -0.75 0.211H2.086c-0.795 0 -1.44 -0.645 -1.44 -1.44a1.44 1.44 0 0 1 0.211
423
+ -0.75l9.914 -16.244c0.414 -0.679 1.3 -0.893 1.979 -0.479a1.44 1.44 0 0 1 0.479 0.479M12 18.24c0.53 0 0.96 -0.43 0.96 -0.96s-0.43 -0.96 -0.96 -0.96
424
+ -0.96 0.43 -0.96 0.96 0.43 0.96 0.96 0.96m0 -10.32c-0.53 0 -0.96 0.43 -0.96 0.96v5.28c0 0.53 0.43 0.96 0.96 0.96s0.96 -0.43 0.96 -0.96V8.88c0 -0.53
425
+ -0.43 -0.96 -0.96 -0.96"/>
426
+ </svg>
427
+
428
+
429
+ </div>
430
+ </div>
431
+
432
+ <textarea id="output-text-field" class="active" placeholder="Objects output will appear here..."></textarea>
433
+ <textarea id="output-json-field" readonly="" placeholder="Objects output will appear here..."></textarea>
434
+ <textarea id="output-markdown-field" readonly="" placeholder="Objects output will appear here..."></textarea>
435
+ </div>
436
+
437
+ <div id="addFieldModal" class="modal">
438
+ <div class="modal-content">
439
+ <span class="close">×</span>
440
+ <h2>Add Field</h2>
441
+ <select id="fieldCategory" onchange="toggleCustomField()">
442
+ <option value="">Select a category</option>
443
+ <option value="state of the art">State of the Art</option>
444
+ <option value="product examples">Product Examples</option>
445
+ <option value="cost range">Cost Range</option>
446
+ <option value="links">Links</option>
447
+ <option value="application">Application</option>
448
+ <option value="maturity">Maturity</option>
449
+ <option value="custom">Custom</option>
450
+ </select>
451
+ <input type="text" id="customFieldName" placeholder="Enter custom field name">
452
+ <input type="text" id="fieldValue" placeholder="Enter field value">
453
+ <button onclick="addFieldFromModal()">Add Field</button>
454
+ </div>
455
+ </div>
456
+
457
+ <script>
458
+ const colors = [
459
+ '#ff4757', '#ffa502', '#2ed573', '#1e90ff', '#5352ed', '#8e44ad'
460
+ ];
461
+ let currentTreeItem;
462
+ let objectCounter = 1;
463
+ const MAX_FIRST_LEVEL_INDENT = 40;
464
+ const MIN_INDENT = 10;
465
+ const MIN_TREE_ITEM_WIDTH = 300;
466
+
467
+ function createTreeItem(content, level) {
468
+ const item = document.createElement('div');
469
+ item.className = 'tree-item';
470
+ const color = colors[level % colors.length];
471
+ item.innerHTML = `
472
+ <div class="tree-content">
473
+ <span class="expand-collapse">▼</span>
474
+ <div class="color-dot" style="background-color: ${color};"></div>
475
+ <span class="tree-label">${content}</span>
476
+ <input type="text" class="edit-input" style="display: none;">
477
+ <div class="tree-actions">
478
+ <button class="icon-button" onclick="addChildFromButton(this)" title="Add Child">
479
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
480
+ <line x1="12" y1="5" x2="12" y2="19"></line>
481
+ <line x1="5" y1="12" x2="19" y2="12"></line>
482
+ </svg>
483
+ </button>
484
+ <button class="icon-button" onclick="editItem(this)" title="Edit">
485
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
486
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
487
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
488
+ </svg>
489
+ </button>
490
+ <button class="icon-button" onclick="deleteItem(this)" title="Delete">
491
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
492
+ <polyline points="3 6 5 6 21 6"></polyline>
493
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
494
+ </svg>
495
+ </button>
496
+ <button class="icon-button" onclick="showAddFieldModal(this)" title="Add Field">
497
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
498
+ <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
499
+ </svg>
500
+ </button>
501
+ </div>
502
+ <div class="edit-actions" style="display: none;">
503
+ <button class="icon-button save-button" onclick="saveEdit(this)" title="Save">
504
+ <svg viewBox="0 0 24 24" fill="none" stroke="#2ecc71" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
505
+ <polyline points="20 6 9 17 4 12"></polyline>
506
+ </svg>
507
+ </button>
508
+ <button class="icon-button cancel-button" onclick="cancelEdit(this)" title="Cancel">
509
+ <svg viewBox="0 0 24 24" fill="none" stroke="#e74c3c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
510
+ <line x1="18" y1="6" x2="6" y2="18"></line>
511
+ <line x1="6" y1="6" x2="18" y2="18"></line>
512
+ </svg>
513
+ </button>
514
+ </div>
515
+ </div>
516
+ <div class="fields-container"></div>
517
+ `;
518
+ return item;
519
+ }
520
+
521
+
522
+
523
+ function addChildFromButton(button) {
524
+ const parentItem = button.closest('.tree-item');
525
+ objectCounter++;
526
+ const newContent = `Object ${objectCounter}`;
527
+ addChild(parentItem, newContent);
528
+ updateOutput();
529
+ }
530
+
531
+ function addChild(parentItem, childName) {
532
+ let childTree = parentItem.querySelector('.child-tree');
533
+ if (!childTree) {
534
+ childTree = document.createElement('div');
535
+ childTree.className = 'child-tree';
536
+ parentItem.appendChild(childTree);
537
+ }
538
+ const parentLevel = getItemLevel(parentItem);
539
+ const newLevel = parentLevel + 1;
540
+ const newItem = createTreeItem(childName, newLevel);
541
+ childTree.appendChild(newItem);
542
+ updateExpandCollapse(parentItem);
543
+ updateExpandCollapse(newItem);
544
+ updateIndents();
545
+ return newItem;
546
+ }
547
+
548
+ // Add field name and value into a tree member
549
+ function addFieldProperty(currentTreeItem,fieldCategory,fieldValue) {
550
+ const fieldsContainer = currentTreeItem.querySelector('.fields-container');
551
+ const fieldDiv = document.createElement('div');
552
+ fieldDiv.className = 'field';
553
+ fieldDiv.innerHTML = `
554
+ <span class="field-name">${fieldCategory}:</span>
555
+ <span class="field-value">${formatFieldValue(fieldValue)}</span>
556
+ <div class="field-actions">
557
+ <button class="icon-button" onclick="editField(this)" title="Edit Field">
558
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
559
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
560
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
561
+ </svg>
562
+ </button>
563
+ <button class="icon-button" onclick="deleteField(this)" title="Delete Field">
564
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
565
+ <polyline points="3 6 5 6 21 6"></polyline>
566
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
567
+ </svg>
568
+ </button>
569
+ </div>
570
+ `;
571
+ fieldsContainer.appendChild(fieldDiv);
572
+ }
573
+
574
+ function editItem(button) {
575
+ const treeContent = button.closest('.tree-content');
576
+ const label = treeContent.querySelector('.tree-label');
577
+ const input = treeContent.querySelector('.edit-input');
578
+ const treeActions = treeContent.querySelector('.tree-actions');
579
+ const editActions = treeContent.querySelector('.edit-actions');
580
+
581
+ label.style.display = 'none';
582
+ input.style.display = 'inline-block';
583
+ input.value = label.textContent;
584
+ treeActions.classList.add('hidden');
585
+ editActions.style.display = 'flex';
586
+
587
+ input.focus();
588
+ treeContent.classList.add('edit-mode');
589
+ }
590
+
591
+ function saveEdit(button) {
592
+ const treeContent = button.closest('.tree-content');
593
+ const label = treeContent.querySelector('.tree-label');
594
+ const input = treeContent.querySelector('.edit-input');
595
+ const treeActions = treeContent.querySelector('.tree-actions');
596
+ const editActions = treeContent.querySelector('.edit-actions');
597
+
598
+ label.textContent = input.value;
599
+ label.style.display = 'inline';
600
+ input.style.display = 'none';
601
+ treeActions.classList.remove('hidden');
602
+ editActions.style.display = 'none';
603
+
604
+ treeContent.classList.remove('edit-mode');
605
+ updateOutput();
606
+ }
607
+
608
+ function cancelEdit(button) {
609
+ const treeContent = button.closest('.tree-content');
610
+ const label = treeContent.querySelector('.tree-label');
611
+ const input = treeContent.querySelector('.edit-input');
612
+ const treeActions = treeContent.querySelector('.tree-actions');
613
+ const editActions = treeContent.querySelector('.edit-actions');
614
+
615
+ label.style.display = 'inline';
616
+ input.style.display = 'none';
617
+
618
+ treeActions.classList.remove('hidden');
619
+ editActions.style.display = 'none';
620
+
621
+ treeContent.classList.remove('edit-mode');
622
+ }
623
+
624
+ function deleteItem(button) {
625
+ const item = button.closest('.tree-item');
626
+ if (confirm('Are you sure you want to delete this item and its children?')) {
627
+ item.remove();
628
+ updateIndents();
629
+ updateOutput();
630
+ }
631
+ }
632
+
633
+ function showAddFieldModal(button) {
634
+ currentTreeItem = button.closest('.tree-item');
635
+ const modal = document.getElementById('addFieldModal');
636
+ modal.style.display = 'block';
637
+ }
638
+
639
+ function toggleCustomField() {
640
+ const fieldCategory = document.getElementById('fieldCategory');
641
+ const customFieldName = document.getElementById('customFieldName');
642
+ customFieldName.style.display = fieldCategory.value === 'custom' ? 'block' : 'none';
643
+ }
644
+
645
+ function addFieldFromModal() {
646
+ let fieldCategory = document.getElementById('fieldCategory').value;
647
+ const customFieldName = document.getElementById('customFieldName');
648
+ const fieldValue = document.getElementById('fieldValue').value;
649
+
650
+ if (fieldCategory === 'custom') {
651
+ fieldCategory = customFieldName.value;
652
+ }
653
+
654
+ if (fieldCategory && fieldValue) {
655
+ addFieldProperty(currentTreeItem, fieldCategory, fieldValue);
656
+ closeModal();
657
+ updateOutput();
658
+ } else {
659
+ alert('Please select a category (or enter a custom name) and enter a value.');
660
+ }
661
+ }
662
+
663
+ function formatFieldValue(value) {
664
+ // if field value is ends with a image or vector file , png, jpg, svg, bmp, try to test and display it too
665
+ const imageRegex = /(\.png|\.jpg|\.svg|\.bmp)$/;
666
+ if (imageRegex.test(value)) {
667
+ const imageDiv = document.createElement('div');
668
+ const image = document.createElement('img');
669
+ const imageLink = document.createElement('div');
670
+
671
+ image.src = value;
672
+ image.style = 'width: 100%; max-width: 300px;';
673
+ imageDiv.appendChild(image);
674
+ // add hidden text content when returning the image;
675
+ imageLink.innerHTML = `<a href="${value}" target="_blank">${value}</a>`;
676
+ imageDiv.appendChild(imageLink);
677
+ return imageDiv.outerHTML;
678
+ }
679
+
680
+ // if field value is a video streaming link, i.e. youtube link or is a media file, loads it too
681
+ const videoRegex = /^(https?:\/\/(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|user\/\S+|[^/]+\?v=)|youtu\.be\/|vimeo\.com\/\d+))|(?:\.mp4|\.webm|\.ogg|\.mov|\.avi|\.wmv|\.flv|\.3gp|\.m4v|\.mkv|\.m3u8|\.ts|\.3g2|\.3gp2|\.m2ts|\.mts|\.m2t|\.ts|\.mxf|\.mks|\.webm|\.mpd|\.m3u8|\.m4s|\.f4m|\.ism|\.ismc|\.isma)$/;
682
+ if (videoRegex.test(value)) {
683
+ function getVideoID(url) {
684
+ var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
685
+ var match = url.match(regExp);
686
+
687
+ if (match && match[2].length == 11) {
688
+ return match[2];
689
+ } else {
690
+ return '';
691
+ }
692
+ }
693
+
694
+ const videoDiv = document.createElement('div');
695
+ let videoElement;
696
+ const videoLink = document.createElement('div');
697
+
698
+ // Check if the value is a YouTube or other video streaming link
699
+ if (/^(https?:\/\/(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|user\/\S+|[^/]+\?v=)|youtu\.be\/|vimeo\.com\/\d+))/.test(value)) {
700
+ videoElement = document.createElement('iframe');
701
+ videoID = getVideoID(value);
702
+ videoElement.src = videoID ?'https://www.youtube.com/embed/' + videoID : value;
703
+ videoElement.width = '100%';
704
+ videoElement.height = '300';
705
+ videoElement.frameborder = '0';
706
+ videoElement.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture';
707
+ videoElement.allowfullscreen = true;
708
+ } else {
709
+ videoElement = document.createElement('video');
710
+ videoElement.src = value;
711
+ videoElement.controls = true;
712
+ videoElement.style = 'width: 100%; max-width: 300px;';
713
+ }
714
+
715
+ videoDiv.appendChild(videoElement);
716
+ videoLink.innerHTML = `<a href="${value}" target="_blank">${value}</a>`;
717
+ videoDiv.appendChild(videoLink);
718
+ return videoDiv.outerHTML;
719
+ }
720
+
721
+ const urlRegex = /^(https?:\/\/[^\s]+)$/;
722
+ if (urlRegex.test(value)) {
723
+ return `<a href="${value}" target="_blank">${value}</a>`;
724
+ }
725
+
726
+ return value;
727
+ }
728
+
729
+ function editField(button) {
730
+ const fieldDiv = button.closest('.field');
731
+ const fieldName = fieldDiv.querySelector('.field-name').textContent.slice(0, -1);
732
+ const fieldValueSpan = fieldDiv.querySelector('.field-value');
733
+ const currentValue = fieldValueSpan.textContent || fieldValueSpan.querySelector('a').href;
734
+ const newValue = prompt(`Edit ${fieldName}:`, currentValue);
735
+ if (newValue !== null) {
736
+ fieldValueSpan.innerHTML = formatFieldValue(newValue);
737
+ updateOutput();
738
+ }
739
+ }
740
+
741
+ function deleteField(button) {
742
+ const fieldDiv = button.closest('.field');
743
+ if (confirm('Are you sure you want to delete this field?')) {
744
+ fieldDiv.remove();
745
+ updateOutput();
746
+ }
747
+ }
748
+
749
+ function updateExpandCollapse(item) {
750
+ const expandCollapseSpan = item.querySelector('.expand-collapse');
751
+ const childTree = item.querySelector('.child-tree');
752
+ if (childTree && childTree.children.length > 0) {
753
+ expandCollapseSpan.style.visibility = 'visible';
754
+ expandCollapseSpan.textContent = item.classList.contains('collapsed') ? '▶' : '▼';
755
+ } else {
756
+ expandCollapseSpan.style.visibility = 'hidden';
757
+ }
758
+ }
759
+
760
+ function toggleCollapse(event) {
761
+ if (event.target.classList.contains('expand-collapse')) {
762
+ const item = event.target.closest('.tree-item');
763
+ item.classList.toggle('collapsed');
764
+ updateExpandCollapse(item);
765
+ updateOutput();
766
+ }
767
+ }
768
+
769
+ function addTreeFromBtn() {
770
+ addTree();
771
+ updateOutput();
772
+ }
773
+
774
+ function addTree(itemName='') {
775
+ const container = document.getElementById('main-container');
776
+ const newTree = document.createElement('div');
777
+ newTree.className = 'tree';
778
+ itemName = itemName? itemName : `Object ${++objectCounter}`;
779
+ const newTreeItem=createTreeItem(itemName, 0);
780
+ newTree.appendChild(newTreeItem);
781
+ container.appendChild(newTree);
782
+ updateExpandCollapse(newTree);
783
+ updateIndents();
784
+ return newTreeItem;
785
+ }
786
+
787
+ function getItemLevel(item) {
788
+ return item.closest('.tree').querySelectorAll('.child-tree').length;
789
+ }
790
+
791
+ function closeModal() {
792
+ const modal = document.getElementById('addFieldModal');
793
+ modal.style.display = 'none';
794
+ document.getElementById('fieldCategory').value = '';
795
+ document.getElementById('customFieldName').value = '';
796
+ document.getElementById('customFieldName').style.display = 'none';
797
+ document.getElementById('fieldValue').value = '';
798
+ }
799
+
800
+ function updateIndents() {
801
+ const trees = document.querySelectorAll('.tree');
802
+ let maxDepth = 0;
803
+
804
+ trees.forEach(tree => {
805
+ const depth = getTreeDepth(tree);
806
+ maxDepth = Math.max(maxDepth, depth);
807
+ });
808
+
809
+ const availableWidth = Math.max(window.innerWidth - 40, MIN_TREE_ITEM_WIDTH * (maxDepth + 1));
810
+ let baseIndent = Math.min(MAX_FIRST_LEVEL_INDENT, (availableWidth - MIN_TREE_ITEM_WIDTH) / maxDepth);
811
+
812
+ document.querySelectorAll('.child-tree').forEach(childTree => {
813
+ const level = getItemLevel(childTree);
814
+ const indentWidth = Math.max(MIN_INDENT, baseIndent * (maxDepth - level + 1) / maxDepth);
815
+ childTree.style.paddingLeft = `${indentWidth}px`;
816
+ });
817
+
818
+ document.querySelectorAll('.tree-item').forEach(item => {
819
+ item.style.minWidth = `${MIN_TREE_ITEM_WIDTH}px`;
820
+ });
821
+
822
+ const mainContainer = document.getElementById('main-container');
823
+ mainContainer.style.overflowX = availableWidth > window.innerWidth ? 'scroll' : 'hidden';
824
+ }
825
+
826
+ function getTreeDepth(element, depth = 0) {
827
+ const childTree = element.querySelector(':scope > .tree-item > .child-tree');
828
+ if (!childTree) return depth;
829
+ return getTreeDepth(childTree, depth + 1);
830
+ }
831
+
832
+ //Update Tree function
833
+ function updateTree() {
834
+ const textAreaFormat = document.querySelector('.output-tab.active').id.split('-')[1];
835
+ const changedTextArea = document.getElementById(`output-${textAreaFormat}-field`);
836
+ const change = changedTextArea.value;
837
+
838
+
839
+ const mainContainer = document.getElementById('main-container');
840
+ const backup = mainContainer.innerHTML;
841
+
842
+ try {
843
+ mainContainer.innerHTML = '';
844
+ if (textAreaFormat === 'json') {
845
+ createTreesFromJson(change);
846
+ }
847
+ if (textAreaFormat ==='markdown') {
848
+
849
+ createTreesFromMarkdown(change);
850
+ }
851
+ if (textAreaFormat === 'text')
852
+ {
853
+
854
+ createTreesFromIndentText(change);
855
+ }
856
+ } catch (error) {
857
+ console.log(error);
858
+ document.getElementById('parse-error').classList.add('show');
859
+ mainContainer.innerHTML = backup;
860
+ return;
861
+ }
862
+
863
+ document.getElementById('parse-error').classList.remove('show');
864
+ updateIndents();
865
+ updateCollapseExpandState();
866
+ return;
867
+ }
868
+
869
+ function createTreesFromIndentText(text) {
870
+ const lines = text.split('\n');
871
+ const rootNode = document.getElementById('main-container');
872
+ let currentIndent = -1;
873
+ let currentNode = rootNode;
874
+
875
+ for (let i = 0; i < lines.length; i++) {
876
+ let line = lines[i];
877
+ if (line.trim() === '') {
878
+ continue;
879
+ }
880
+ const lineIndent = getLineIndent(line);
881
+ let lineTr = line.trim();
882
+
883
+ const itemName = lineTr.indexOf('-')==0 ? lineTr.slice(lineTr.indexOf('-')+1).trim() : ''
884
+ const fieldName = lineTr.indexOf(':')>0 ? lineTr.split(':',1)[0].trim() : '';
885
+ const fieldValue = lineTr.slice(lineTr.indexOf(':')+1).trim();
886
+ //main container - > tree -> treeItem -> childTree -> ChildTreeItem
887
+ if (itemName) {
888
+ if (currentIndent < lineIndent) {
889
+ if (currentNode.isSameNode(rootNode)) {
890
+ currentNode=addTree(itemName);
891
+ } else if (currentNode) {
892
+ currentNode=addChild(currentNode,itemName);
893
+ }
894
+ currentIndent = lineIndent;
895
+ } else if (currentIndent >= lineIndent) {
896
+ // Move up the tree
897
+ while (currentIndent >= lineIndent) {
898
+ currentNode = currentNode.parentNode.parentNode ;
899
+ currentIndent--;
900
+ }
901
+ if (currentNode.isSameNode(rootNode)) {
902
+ currentNode=addTree(itemName);
903
+ } else if (currentNode) {
904
+ currentNode=addChild(currentNode,itemName);
905
+ }
906
+ currentIndent = lineIndent;
907
+ }
908
+ } else if (fieldName) {
909
+ addFieldProperty(currentNode, fieldName, fieldValue);
910
+ }
911
+
912
+ }
913
+ return;
914
+ }
915
+
916
+
917
+
918
+
919
+ function getLineIndent(line) {
920
+ const match = line.match(/^\t*/);
921
+ console.log(match);
922
+ return match ? (match[0].length) : 0;
923
+ }
924
+
925
+
926
+ //Update collapse expand state of the whole tree
927
+ function updateCollapseExpandState() {
928
+ // Updates the expand/collapse state of all tree items
929
+ document.querySelectorAll('.tree-item').forEach(updateExpandCollapse);
930
+ }
931
+
932
+
933
+ // Update output function
934
+ function updateOutput(outputFormat='') {
935
+ outputFormat=outputFormat?outputFormat:document.querySelector('.output-tab.active').id.split('-')[1];
936
+ const outputField = document.getElementById(`output-${outputFormat}-field`);
937
+ const trees = document.querySelectorAll('.tree');
938
+ let output = '';
939
+ if (outputFormat === 'json') {
940
+ output += '{\n';
941
+ }
942
+
943
+ trees.forEach((tree) => {
944
+ if (outputFormat === 'text') {
945
+ output += generateTreeOutputIndent(tree, 0);
946
+ output += '\n';
947
+ }
948
+ else if (outputFormat === 'json')
949
+ {
950
+ output += generateTreeOutputJson(tree, 0);
951
+ output += '\n';
952
+ }
953
+ else if (outputFormat === "markdown")
954
+ {
955
+ output += generateTreeOutputMarkdown(tree, 0);
956
+ output += '\n';
957
+ }
958
+ });
959
+
960
+ if (outputFormat === 'json') {
961
+ output += '}\n';
962
+ }
963
+
964
+ outputField.value = output;
965
+ document.getElementById('parse-error').classList.remove('show');
966
+ }
967
+
968
+ // Generate the output for the tree in pure indented text format.
969
+ function generateTreeOutputIndent(element, level) {
970
+ let output = '';
971
+ const items = element.children;
972
+
973
+ for (let item of items) {
974
+ if (item.classList.contains('tree-item')) {
975
+ const label = item.querySelector('.tree-label').textContent;
976
+ const indent = '\t'.repeat(level);
977
+ output += `${indent}- ${label}\n`;
978
+
979
+ const fields = item.querySelector('.fields-container').querySelectorAll('.field');
980
+ fields.forEach(field => {
981
+ const fieldName = field.querySelector('.field-name').textContent.slice(0, -1);
982
+ const fieldValue = field.querySelector('.field-value').textContent;
983
+ output += `${indent} ${fieldName}: ${fieldValue}\n`;
984
+ });
985
+
986
+ const childTree = item.querySelector('.child-tree');
987
+ if (childTree) {
988
+ output += generateTreeOutputIndent(childTree, level + 1);
989
+ }
990
+ }
991
+ }
992
+
993
+ return output;
994
+ }
995
+
996
+ // Generate a JSON string from the tree
997
+ function generateTreeOutputJson(element, level) {
998
+ let output = '';
999
+ const items = element.children;
1000
+ const indent = level * 2;
1001
+
1002
+
1003
+ for (let item of items) {
1004
+ if (item.classList.contains('tree-item')) {
1005
+ const label = item.querySelector('.tree-label').textContent;
1006
+ output += `${' '.repeat(indent+2)}\"${label}\": {\n`;
1007
+ const fields = item.querySelector('.fields-container').querySelectorAll('.field');
1008
+ fields.forEach(field => {
1009
+ const fieldName = field.querySelector('.field-name').textContent.slice(0, -1);
1010
+ const fieldValue = field.querySelector('.field-value').textContent;
1011
+ output += `${' '.repeat(indent + 4)}\"${fieldName}\": \"${fieldValue}\",\n`;})
1012
+ }
1013
+ const childTree = item.querySelector('.child-tree');
1014
+ if (childTree) {
1015
+ output += generateTreeOutputJson(childTree, level + 1);
1016
+ }
1017
+ output += `${' '.repeat(indent+2)}},\n`;
1018
+ }
1019
+
1020
+
1021
+ return output;
1022
+ }
1023
+
1024
+ // Generate the output in markdown format
1025
+ function generateTreeOutputMarkdown(element, level){
1026
+ let output = '';
1027
+ const items = element.children;
1028
+ const indent = level * 2;
1029
+
1030
+
1031
+ for (let item of items) {
1032
+ if (item.classList.contains('tree-item')) {
1033
+ const label = item.querySelector('.tree-label').textContent;
1034
+ output += `${' '.repeat(indent+2)}<details open>\n${' '.repeat(indent+4)}<summary>${label}</summary>\n${' '.repeat(indent+4)}<blockquote>\n`;
1035
+ const fields = item.querySelector('.fields-container').querySelectorAll('.field');
1036
+ fields.forEach(field => {
1037
+ const fieldName = field.querySelector('.field-name').textContent.slice(0, -1);
1038
+ const fieldValue = field.querySelector('.field-value').textContent;
1039
+ output += `\n${fieldName}: \`\`\`${fieldValue}\`\`\`\n`;})
1040
+
1041
+ const childTree = item.querySelector('.child-tree');
1042
+ if (childTree) {
1043
+ output += generateTreeOutputMarkdown(childTree, level + 1);
1044
+ }
1045
+ output += `${' '.repeat(indent+4)}</blockquote>\n${' '.repeat(indent+2)}</details>\n`;
1046
+ }
1047
+
1048
+
1049
+ }
1050
+ return output;
1051
+ }
1052
+
1053
+ // update display and output when tabs are selected or switched
1054
+ function onSwitchTab(tab) {
1055
+ const textOutput = document.getElementById('output-text-field');
1056
+ const jsonOutput = document.getElementById('output-json-field');
1057
+ const markdownOutput = document.getElementById('output-markdown-field');
1058
+ const textOutputTab = document.getElementById('output-text-tab');
1059
+ const jsonOutputTab = document.getElementById('output-json-tab');
1060
+ const markdownOutputTab = document.getElementById('output-markdown-tab');
1061
+
1062
+ if (tab === 'text') {
1063
+ textOutput.classList.add('active');
1064
+ jsonOutput.classList.remove('active');
1065
+ markdownOutput.classList.remove('active');
1066
+ textOutputTab.classList.add('active');
1067
+ jsonOutputTab.classList.remove('active');
1068
+ markdownOutputTab.classList.remove('active');
1069
+ updateOutput();
1070
+ } else if (tab === 'json') {
1071
+ textOutput.classList.remove('active');
1072
+ jsonOutput.classList.add('active');
1073
+ markdownOutput.classList.remove('active');
1074
+ textOutputTab.classList.remove('active');
1075
+ jsonOutputTab.classList.add('active');
1076
+ markdownOutputTab.classList.remove('active');
1077
+ updateOutput();
1078
+ } else if (tab === 'markdown') {
1079
+ textOutput.classList.remove('active');
1080
+ jsonOutput.classList.remove('active');
1081
+ markdownOutput.classList.add('active');
1082
+ textOutputTab.classList.remove('active');
1083
+ jsonOutputTab.classList.remove('active');
1084
+ markdownOutputTab.classList.add('active');
1085
+ updateOutput();
1086
+ }
1087
+ }
1088
+ async function exportData(fileHandle) {
1089
+ const fileData = await fileHandle.getFile();
1090
+ const format = fileData.name.split('.').pop();
1091
+
1092
+
1093
+ let exportContent;
1094
+ let fileExtension;
1095
+
1096
+ switch (format) {
1097
+ case 'json':
1098
+ updateOutput('json');
1099
+ exportContent = document.getElementById('output-json-field').value;
1100
+ fileExtension = '.json';
1101
+ break;
1102
+ case 'md':
1103
+ updateOutput('markdown');
1104
+ exportContent = document.getElementById('output-markdown-field').value;
1105
+ fileExtension = '.md';
1106
+ break;
1107
+ case 'txt':
1108
+ updateOutput('text');
1109
+ exportContent = document.getElementById('output-text-field').value;
1110
+ fileExtension = '.txt';
1111
+ break;
1112
+ case 'html':
1113
+ exportContent = `<!DOCTYPE html>
1114
+ <html lang="en">
1115
+ <head>
1116
+ <meta charset="UTF-8">
1117
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1118
+ <title>Exported Data</title>
1119
+ <style>
1120
+ /* Add any necessary styles here */
1121
+ </style>
1122
+ </head>
1123
+ <body>
1124
+ <h1>Exported Data</h1>
1125
+ </body>
1126
+ </html>`;
1127
+ fileExtension = '.html';
1128
+ break;
1129
+ default:
1130
+ return;
1131
+ }
1132
+ const blob = new Blob([exportContent], { type: 'text/plain' });
1133
+ const writable = await fileHandle.createWritable();
1134
+ await writable.write(blob);
1135
+ await writable.close();
1136
+ return;
1137
+ }
1138
+
1139
+ // Add the export button
1140
+ const exportButton = document.getElementById('output-export-btn');
1141
+ exportButton.addEventListener('click', async () => {
1142
+ async function getNewFileHandle() {
1143
+ const opts = {
1144
+ types: [
1145
+ {
1146
+ description: "Text, Json, Markdown or Html",
1147
+ accept: { "text/plain": [".txt",".json",".md",".html"] },
1148
+ },
1149
+ ],
1150
+ };
1151
+ return await window.showSaveFilePicker(opts);
1152
+ }
1153
+ const filehandler = await getNewFileHandle();
1154
+
1155
+
1156
+ await exportData(filehandler);
1157
+ });
1158
+
1159
+ document.querySelectorAll(`textarea`).forEach((textArea) => {
1160
+ textArea.addEventListener(`keydown`, (e) => {
1161
+ if (e.keyCode === 9) {
1162
+ let selectionStart=textArea.selectionStart;
1163
+ textArea.value = `${textArea.value.substring(0, textArea.selectionStart)}\t${textArea.value.substring(textArea.selectionEnd)}`;
1164
+ textArea.selectionEnd = selectionStart + 1;
1165
+ textArea.selectionStart = textArea.selectionEnd;
1166
+ updateTree();
1167
+ e.preventDefault();
1168
+ }
1169
+ }, false);
1170
+ });
1171
+
1172
+ // Toggles the collapse/expand state of a tree item
1173
+ document.addEventListener('click', toggleCollapse);
1174
+ // Closes the modal when the close button is clicked
1175
+ document.querySelector('.close').onclick = closeModal;
1176
+ // Closes the modal when the background is clicked
1177
+ window.onclick = function(event) {
1178
+ const modal = document.getElementById('addFieldModal');
1179
+ if (event.target == modal) {
1180
+ closeModal();
1181
+ }
1182
+ }
1183
+ // Updates the indents of all tree items
1184
+ window.addEventListener('resize', updateIndents);
1185
+ // Updates the tree when the input is changed
1186
+ document.getElementById('output-text-field').addEventListener('input', updateTree);
1187
+ // Update the tree when DOM content is loaded
1188
+ document.addEventListener('DOMContentLoaded', () => {
1189
+ updateIndents();
1190
+ updateOutput();
1191
+ document.querySelectorAll('.tree-item').forEach(updateExpandCollapse);
1192
+ });
1193
+
1194
+ </script>
1195
+
1196
+ </body></html>