pict-section-inlinedocumentation 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +83 -0
- package/docs/_cover.md +15 -0
- package/docs/_sidebar.md +24 -0
- package/docs/_topbar.md +8 -0
- package/docs/_version.json +7 -0
- package/docs/api-reference.md +185 -0
- package/docs/architecture.md +103 -0
- package/docs/css/docuserve.css +327 -0
- package/docs/embedding-level1-sidebar.md +92 -0
- package/docs/embedding-level2-routes.md +86 -0
- package/docs/embedding-level3-tooltips.md +97 -0
- package/docs/embedding-level4-autogen.md +126 -0
- package/docs/index.html +39 -0
- package/docs/overview.md +42 -0
- package/docs/quickstart.md +95 -0
- package/docs/reference.md +73 -0
- package/docs/retold-catalog.json +181 -0
- package/docs/retold-keyword-index.json +4374 -0
- package/example_applications/basic/docs/README.md +40 -0
- package/example_applications/basic/docs/_sidebar.md +4 -0
- package/example_applications/basic/docs/_topics.json +10 -0
- package/example_applications/basic/docs/advanced-topics.md +47 -0
- package/example_applications/basic/docs/getting-started.md +70 -0
- package/example_applications/basic/index.html +100 -0
- package/example_applications/bookshop/.quackage.json +10 -0
- package/example_applications/bookshop/Pict-Application-Bookshop-Configuration.json +15 -0
- package/example_applications/bookshop/Pict-Application-Bookshop.js +218 -0
- package/example_applications/bookshop/data/BookshopData.json +65 -0
- package/example_applications/bookshop/data/pict_documentation_topics.json +46 -0
- package/example_applications/bookshop/docs/_sidebar.md +6 -0
- package/example_applications/bookshop/docs/book-detail.md +21 -0
- package/example_applications/bookshop/docs/book-list.md +21 -0
- package/example_applications/bookshop/docs/search-filter.md +18 -0
- package/example_applications/bookshop/docs/store.md +29 -0
- package/example_applications/bookshop/docs/welcome.md +23 -0
- package/example_applications/bookshop/html/index.html +236 -0
- package/example_applications/bookshop/package.json +34 -0
- package/example_applications/bookshop/views/PictView-Bookshop-BookList.js +324 -0
- package/example_applications/bookshop/views/PictView-Bookshop-HelpToggle.js +44 -0
- package/example_applications/bookshop/views/PictView-Bookshop-Store.js +271 -0
- package/package.json +55 -0
- package/source/Pict-Section-InlineDocumentation.js +10 -0
- package/source/providers/Pict-Provider-InlineDocumentation.js +1995 -0
- package/source/views/Pict-View-InlineDocumentation-Content.js +542 -0
- package/source/views/Pict-View-InlineDocumentation-Layout.js +206 -0
- package/source/views/Pict-View-InlineDocumentation-Nav.js +475 -0
- package/source/views/Pict-View-InlineDocumentation-TopicManager.js +1623 -0
- package/test/Browser_Integration_tests.js +1449 -0
- package/test/Pict-Section-InlineDocumentation_test.js +1285 -0
|
@@ -0,0 +1,1623 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
const _ViewConfiguration =
|
|
4
|
+
{
|
|
5
|
+
ViewIdentifier: "InlineDoc-TopicManager",
|
|
6
|
+
|
|
7
|
+
AutoRender: false,
|
|
8
|
+
|
|
9
|
+
CSS: /*css*/`
|
|
10
|
+
.pict-inline-doc-tm-topic-list {
|
|
11
|
+
max-height: 400px;
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
margin: 0 -0.5em;
|
|
14
|
+
}
|
|
15
|
+
.pict-inline-doc-tm-topic-item {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
padding: 0.6em 0.8em;
|
|
19
|
+
border-bottom: 1px solid #EAE3D8;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
transition: background 0.1s;
|
|
22
|
+
}
|
|
23
|
+
.pict-inline-doc-tm-topic-item:hover {
|
|
24
|
+
background: #F5F0E8;
|
|
25
|
+
}
|
|
26
|
+
.pict-inline-doc-tm-topic-item:last-child {
|
|
27
|
+
border-bottom: none;
|
|
28
|
+
}
|
|
29
|
+
.pict-inline-doc-tm-topic-info {
|
|
30
|
+
flex: 1;
|
|
31
|
+
min-width: 0;
|
|
32
|
+
}
|
|
33
|
+
.pict-inline-doc-tm-topic-title {
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
color: #3D3229;
|
|
36
|
+
font-size: 0.95em;
|
|
37
|
+
}
|
|
38
|
+
.pict-inline-doc-tm-topic-meta {
|
|
39
|
+
font-size: 0.8em;
|
|
40
|
+
color: #8A7F72;
|
|
41
|
+
margin-top: 0.15em;
|
|
42
|
+
}
|
|
43
|
+
.pict-inline-doc-tm-topic-actions {
|
|
44
|
+
display: flex;
|
|
45
|
+
gap: 0.3em;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
}
|
|
48
|
+
.pict-inline-doc-tm-action-btn {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
width: 28px;
|
|
53
|
+
height: 28px;
|
|
54
|
+
border: 1px solid #DDD6CA;
|
|
55
|
+
border-radius: 3px;
|
|
56
|
+
background: #fff;
|
|
57
|
+
color: #5E5549;
|
|
58
|
+
font-size: 0.85em;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
transition: background 0.1s, border-color 0.1s;
|
|
61
|
+
}
|
|
62
|
+
.pict-inline-doc-tm-action-btn:hover {
|
|
63
|
+
background: #F0ECE4;
|
|
64
|
+
border-color: #C4BDB3;
|
|
65
|
+
}
|
|
66
|
+
.pict-inline-doc-tm-action-btn.danger:hover {
|
|
67
|
+
background: #FDE8E8;
|
|
68
|
+
border-color: #E57373;
|
|
69
|
+
color: #C62828;
|
|
70
|
+
}
|
|
71
|
+
.pict-inline-doc-tm-new-topic {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
padding: 0.7em;
|
|
76
|
+
margin-top: 0.5em;
|
|
77
|
+
border: 1px dashed #DDD6CA;
|
|
78
|
+
border-radius: 4px;
|
|
79
|
+
color: #2E7D74;
|
|
80
|
+
font-size: 0.9em;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
transition: background 0.1s, border-color 0.1s;
|
|
84
|
+
}
|
|
85
|
+
.pict-inline-doc-tm-new-topic:hover {
|
|
86
|
+
background: #F0F9F7;
|
|
87
|
+
border-color: #2E7D74;
|
|
88
|
+
}
|
|
89
|
+
.pict-inline-doc-tm-empty {
|
|
90
|
+
text-align: center;
|
|
91
|
+
padding: 2em 1em;
|
|
92
|
+
color: #8A7F72;
|
|
93
|
+
font-size: 0.9em;
|
|
94
|
+
}
|
|
95
|
+
.pict-inline-doc-tm-form-group {
|
|
96
|
+
margin-bottom: 0.8em;
|
|
97
|
+
}
|
|
98
|
+
.pict-inline-doc-tm-form-label {
|
|
99
|
+
display: block;
|
|
100
|
+
font-size: 0.8em;
|
|
101
|
+
font-weight: 600;
|
|
102
|
+
color: #5E5549;
|
|
103
|
+
text-transform: uppercase;
|
|
104
|
+
letter-spacing: 0.03em;
|
|
105
|
+
margin-bottom: 0.3em;
|
|
106
|
+
}
|
|
107
|
+
.pict-inline-doc-tm-form-input {
|
|
108
|
+
width: 100%;
|
|
109
|
+
padding: 0.45em 0.6em;
|
|
110
|
+
font-size: 0.9em;
|
|
111
|
+
color: #3D3229;
|
|
112
|
+
background: #FDFCFA;
|
|
113
|
+
border: 1px solid #DDD6CA;
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
box-sizing: border-box;
|
|
116
|
+
}
|
|
117
|
+
.pict-inline-doc-tm-form-input:focus {
|
|
118
|
+
outline: none;
|
|
119
|
+
border-color: #2E7D74;
|
|
120
|
+
box-shadow: 0 0 0 2px rgba(46, 125, 116, 0.15);
|
|
121
|
+
}
|
|
122
|
+
.pict-inline-doc-tm-form-input[readonly] {
|
|
123
|
+
background: #F5F0E8;
|
|
124
|
+
color: #8A7F72;
|
|
125
|
+
}
|
|
126
|
+
.pict-inline-doc-tm-form-hint {
|
|
127
|
+
font-size: 0.75em;
|
|
128
|
+
color: #8A7F72;
|
|
129
|
+
margin-top: 0.2em;
|
|
130
|
+
}
|
|
131
|
+
.pict-inline-doc-tm-routes-section {
|
|
132
|
+
margin-top: 0.5em;
|
|
133
|
+
}
|
|
134
|
+
.pict-inline-doc-tm-route-chips {
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-wrap: wrap;
|
|
137
|
+
gap: 0.3em;
|
|
138
|
+
margin-bottom: 0.5em;
|
|
139
|
+
}
|
|
140
|
+
.pict-inline-doc-tm-route-chip {
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
padding: 0.2em 0.5em;
|
|
144
|
+
background: #E8E3D8;
|
|
145
|
+
border-radius: 12px;
|
|
146
|
+
font-size: 0.82em;
|
|
147
|
+
color: #3D3229;
|
|
148
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
149
|
+
}
|
|
150
|
+
.pict-inline-doc-tm-route-chip-remove {
|
|
151
|
+
margin-left: 0.4em;
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
color: #8A7F72;
|
|
154
|
+
font-size: 0.9em;
|
|
155
|
+
line-height: 1;
|
|
156
|
+
}
|
|
157
|
+
.pict-inline-doc-tm-route-chip-remove:hover {
|
|
158
|
+
color: #C62828;
|
|
159
|
+
}
|
|
160
|
+
.pict-inline-doc-tm-route-actions {
|
|
161
|
+
display: flex;
|
|
162
|
+
flex-wrap: wrap;
|
|
163
|
+
gap: 0.3em;
|
|
164
|
+
}
|
|
165
|
+
.pict-inline-doc-tm-route-action-btn {
|
|
166
|
+
display: inline-flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
padding: 0.25em 0.5em;
|
|
169
|
+
border: 1px solid #DDD6CA;
|
|
170
|
+
border-radius: 3px;
|
|
171
|
+
background: #fff;
|
|
172
|
+
color: #5E5549;
|
|
173
|
+
font-size: 0.8em;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
transition: background 0.1s;
|
|
176
|
+
}
|
|
177
|
+
.pict-inline-doc-tm-route-action-btn:hover {
|
|
178
|
+
background: #F0ECE4;
|
|
179
|
+
}
|
|
180
|
+
.pict-inline-doc-tm-route-action-btn.accent {
|
|
181
|
+
border-color: #2E7D74;
|
|
182
|
+
color: #2E7D74;
|
|
183
|
+
}
|
|
184
|
+
.pict-inline-doc-tm-route-action-btn.accent:hover {
|
|
185
|
+
background: #F0F9F7;
|
|
186
|
+
}
|
|
187
|
+
.pict-inline-doc-tm-route-input-row {
|
|
188
|
+
display: none;
|
|
189
|
+
align-items: center;
|
|
190
|
+
gap: 0.3em;
|
|
191
|
+
margin-top: 0.4em;
|
|
192
|
+
}
|
|
193
|
+
.pict-inline-doc-tm-route-input-row.visible {
|
|
194
|
+
display: flex;
|
|
195
|
+
}
|
|
196
|
+
.pict-inline-doc-tm-route-input {
|
|
197
|
+
flex: 1;
|
|
198
|
+
padding: 0.35em 0.5em;
|
|
199
|
+
font-size: 0.85em;
|
|
200
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
201
|
+
color: #3D3229;
|
|
202
|
+
background: #FDFCFA;
|
|
203
|
+
border: 1px solid #DDD6CA;
|
|
204
|
+
border-radius: 3px;
|
|
205
|
+
}
|
|
206
|
+
.pict-inline-doc-tm-route-input:focus {
|
|
207
|
+
outline: none;
|
|
208
|
+
border-color: #2E7D74;
|
|
209
|
+
}
|
|
210
|
+
.pict-inline-doc-tm-wc-container {
|
|
211
|
+
padding: 0.5em 0;
|
|
212
|
+
}
|
|
213
|
+
.pict-inline-doc-tm-wc-label {
|
|
214
|
+
font-size: 0.85em;
|
|
215
|
+
color: #5E5549;
|
|
216
|
+
margin-bottom: 0.6em;
|
|
217
|
+
}
|
|
218
|
+
.pict-inline-doc-tm-wc-segments {
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-wrap: wrap;
|
|
221
|
+
align-items: center;
|
|
222
|
+
gap: 0.15em;
|
|
223
|
+
margin-bottom: 0.8em;
|
|
224
|
+
}
|
|
225
|
+
.pict-inline-doc-tm-wc-slash {
|
|
226
|
+
color: #8A7F72;
|
|
227
|
+
font-size: 1.1em;
|
|
228
|
+
font-weight: 300;
|
|
229
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
230
|
+
}
|
|
231
|
+
.pict-inline-doc-tm-wc-segment {
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
padding: 0.4em 0.7em;
|
|
235
|
+
border: 1px solid #DDD6CA;
|
|
236
|
+
border-radius: 4px;
|
|
237
|
+
background: #fff;
|
|
238
|
+
color: #3D3229;
|
|
239
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
240
|
+
font-size: 0.9em;
|
|
241
|
+
cursor: pointer;
|
|
242
|
+
transition: background 0.15s, border-color 0.15s, opacity 0.15s;
|
|
243
|
+
user-select: none;
|
|
244
|
+
}
|
|
245
|
+
.pict-inline-doc-tm-wc-segment:hover {
|
|
246
|
+
border-color: #2E7D74;
|
|
247
|
+
background: #F0F9F7;
|
|
248
|
+
}
|
|
249
|
+
.pict-inline-doc-tm-wc-segment.selected {
|
|
250
|
+
background: #2E7D74;
|
|
251
|
+
color: #fff;
|
|
252
|
+
border-color: #2E7D74;
|
|
253
|
+
}
|
|
254
|
+
.pict-inline-doc-tm-wc-segment.after-wildcard {
|
|
255
|
+
opacity: 0.35;
|
|
256
|
+
border-style: dashed;
|
|
257
|
+
cursor: default;
|
|
258
|
+
}
|
|
259
|
+
.pict-inline-doc-tm-wc-segment.after-wildcard:hover {
|
|
260
|
+
border-color: #DDD6CA;
|
|
261
|
+
background: #fff;
|
|
262
|
+
}
|
|
263
|
+
.pict-inline-doc-tm-wc-wildcard-star {
|
|
264
|
+
display: inline-flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
padding: 0.4em 0.6em;
|
|
267
|
+
color: #2E7D74;
|
|
268
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
269
|
+
font-size: 1.1em;
|
|
270
|
+
font-weight: 700;
|
|
271
|
+
}
|
|
272
|
+
.pict-inline-doc-tm-wc-preview {
|
|
273
|
+
padding: 0.5em 0.7em;
|
|
274
|
+
background: #F5F0E8;
|
|
275
|
+
border: 1px solid #E5DED4;
|
|
276
|
+
border-radius: 4px;
|
|
277
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
278
|
+
font-size: 0.9em;
|
|
279
|
+
color: #3D3229;
|
|
280
|
+
}
|
|
281
|
+
.pict-inline-doc-tm-wc-preview-label {
|
|
282
|
+
font-size: 0.75em;
|
|
283
|
+
color: #8A7F72;
|
|
284
|
+
text-transform: uppercase;
|
|
285
|
+
letter-spacing: 0.03em;
|
|
286
|
+
margin-bottom: 0.3em;
|
|
287
|
+
}
|
|
288
|
+
.pict-inline-doc-tm-bind-route-display {
|
|
289
|
+
padding: 0.5em 0.7em;
|
|
290
|
+
background: #F5F0E8;
|
|
291
|
+
border: 1px solid #E5DED4;
|
|
292
|
+
border-radius: 4px;
|
|
293
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
294
|
+
font-size: 0.9em;
|
|
295
|
+
color: #3D3229;
|
|
296
|
+
margin-bottom: 0.8em;
|
|
297
|
+
}
|
|
298
|
+
.pict-inline-doc-tm-bind-topic-list {
|
|
299
|
+
max-height: 250px;
|
|
300
|
+
overflow-y: auto;
|
|
301
|
+
margin-bottom: 0.6em;
|
|
302
|
+
}
|
|
303
|
+
.pict-inline-doc-tm-bind-topic-option {
|
|
304
|
+
display: flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
padding: 0.5em 0.6em;
|
|
307
|
+
border-bottom: 1px solid #EAE3D8;
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
transition: background 0.1s;
|
|
310
|
+
}
|
|
311
|
+
.pict-inline-doc-tm-bind-topic-option:hover {
|
|
312
|
+
background: #F5F0E8;
|
|
313
|
+
}
|
|
314
|
+
.pict-inline-doc-tm-bind-topic-option.selected {
|
|
315
|
+
background: #F0F9F7;
|
|
316
|
+
}
|
|
317
|
+
.pict-inline-doc-tm-bind-radio {
|
|
318
|
+
width: 16px;
|
|
319
|
+
height: 16px;
|
|
320
|
+
border: 2px solid #DDD6CA;
|
|
321
|
+
border-radius: 50%;
|
|
322
|
+
margin-right: 0.6em;
|
|
323
|
+
flex-shrink: 0;
|
|
324
|
+
position: relative;
|
|
325
|
+
}
|
|
326
|
+
.pict-inline-doc-tm-bind-topic-option.selected .pict-inline-doc-tm-bind-radio {
|
|
327
|
+
border-color: #2E7D74;
|
|
328
|
+
}
|
|
329
|
+
.pict-inline-doc-tm-bind-topic-option.selected .pict-inline-doc-tm-bind-radio::after {
|
|
330
|
+
content: '';
|
|
331
|
+
position: absolute;
|
|
332
|
+
top: 3px;
|
|
333
|
+
left: 3px;
|
|
334
|
+
width: 6px;
|
|
335
|
+
height: 6px;
|
|
336
|
+
background: #2E7D74;
|
|
337
|
+
border-radius: 50%;
|
|
338
|
+
}
|
|
339
|
+
.pict-inline-doc-tm-bind-route-type {
|
|
340
|
+
display: flex;
|
|
341
|
+
gap: 0.5em;
|
|
342
|
+
margin-bottom: 0.5em;
|
|
343
|
+
}
|
|
344
|
+
.pict-inline-doc-tm-bind-route-type-btn {
|
|
345
|
+
flex: 1;
|
|
346
|
+
padding: 0.5em;
|
|
347
|
+
border: 1px solid #DDD6CA;
|
|
348
|
+
border-radius: 4px;
|
|
349
|
+
background: #fff;
|
|
350
|
+
color: #5E5549;
|
|
351
|
+
font-size: 0.85em;
|
|
352
|
+
text-align: center;
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
transition: background 0.1s, border-color 0.1s;
|
|
355
|
+
}
|
|
356
|
+
.pict-inline-doc-tm-bind-route-type-btn:hover {
|
|
357
|
+
background: #F0ECE4;
|
|
358
|
+
}
|
|
359
|
+
.pict-inline-doc-tm-bind-route-type-btn.selected {
|
|
360
|
+
background: #2E7D74;
|
|
361
|
+
color: #fff;
|
|
362
|
+
border-color: #2E7D74;
|
|
363
|
+
}
|
|
364
|
+
.pict-inline-doc-tm-sidebar-list {
|
|
365
|
+
max-height: 300px;
|
|
366
|
+
overflow-y: auto;
|
|
367
|
+
}
|
|
368
|
+
.pict-inline-doc-tm-sidebar-item {
|
|
369
|
+
padding: 0.4em 0.6em;
|
|
370
|
+
cursor: pointer;
|
|
371
|
+
font-size: 0.9em;
|
|
372
|
+
color: #3D3229;
|
|
373
|
+
border-bottom: 1px solid #EAE3D8;
|
|
374
|
+
transition: background 0.1s;
|
|
375
|
+
}
|
|
376
|
+
.pict-inline-doc-tm-sidebar-item:hover {
|
|
377
|
+
background: #F5F0E8;
|
|
378
|
+
}
|
|
379
|
+
.pict-inline-doc-tm-sidebar-item .path {
|
|
380
|
+
font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
381
|
+
font-size: 0.85em;
|
|
382
|
+
color: #8A7F72;
|
|
383
|
+
margin-left: 0.5em;
|
|
384
|
+
}
|
|
385
|
+
.pict-inline-doc-tm-validation-error {
|
|
386
|
+
color: #C62828;
|
|
387
|
+
font-size: 0.8em;
|
|
388
|
+
margin-top: 0.3em;
|
|
389
|
+
}
|
|
390
|
+
`,
|
|
391
|
+
|
|
392
|
+
Templates: [],
|
|
393
|
+
Renderables: []
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
class InlineDocumentationTopicManagerView extends libPictView
|
|
397
|
+
{
|
|
398
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
399
|
+
{
|
|
400
|
+
super(pFable, pOptions, pServiceHash);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get the modal view instance if available.
|
|
405
|
+
*
|
|
406
|
+
* @returns {Object|null} The PictSectionModal view, or null
|
|
407
|
+
*/
|
|
408
|
+
_getModal()
|
|
409
|
+
{
|
|
410
|
+
return this.pict.views['PictSectionModal'] || this.pict.views['Pict-Section-Modal'] || null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get the inline documentation provider.
|
|
415
|
+
*
|
|
416
|
+
* @returns {Object|null} The provider instance
|
|
417
|
+
*/
|
|
418
|
+
_getProvider()
|
|
419
|
+
{
|
|
420
|
+
return this.pict.providers['Pict-InlineDocumentation'] || null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// -- Topic List --
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Show the topic manager modal with the full topic list.
|
|
427
|
+
*/
|
|
428
|
+
showTopicManager()
|
|
429
|
+
{
|
|
430
|
+
let tmpModal = this._getModal();
|
|
431
|
+
let tmpProvider = this._getProvider();
|
|
432
|
+
|
|
433
|
+
if (!tmpModal)
|
|
434
|
+
{
|
|
435
|
+
this.log.warn('InlineDocumentation TopicManager: Pict-Section-Modal view is not registered.');
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!tmpProvider)
|
|
440
|
+
{
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let tmpTopics = tmpProvider.getTopicList();
|
|
445
|
+
let tmpContent = this._buildTopicListHTML(tmpTopics);
|
|
446
|
+
|
|
447
|
+
tmpModal.show(
|
|
448
|
+
{
|
|
449
|
+
title: 'Manage Topics',
|
|
450
|
+
content: tmpContent,
|
|
451
|
+
closeable: true,
|
|
452
|
+
width: '520px',
|
|
453
|
+
buttons:
|
|
454
|
+
[
|
|
455
|
+
{ Hash: 'close', Label: 'Close', Style: 'primary' }
|
|
456
|
+
],
|
|
457
|
+
onOpen: (pDialog) =>
|
|
458
|
+
{
|
|
459
|
+
this._wireTopicListHandlers(pDialog);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Build the HTML for the topic list modal.
|
|
466
|
+
*
|
|
467
|
+
* @param {Array} pTopics - Array from getTopicList()
|
|
468
|
+
* @returns {string} HTML content
|
|
469
|
+
*/
|
|
470
|
+
_buildTopicListHTML(pTopics)
|
|
471
|
+
{
|
|
472
|
+
if (!pTopics || pTopics.length < 1)
|
|
473
|
+
{
|
|
474
|
+
return '<div class="pict-inline-doc-tm-empty">No topics defined yet.</div>'
|
|
475
|
+
+ '<div class="pict-inline-doc-tm-new-topic" data-action="new-topic">+ New Topic</div>';
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let tmpHTML = '<div class="pict-inline-doc-tm-topic-list">';
|
|
479
|
+
|
|
480
|
+
for (let i = 0; i < pTopics.length; i++)
|
|
481
|
+
{
|
|
482
|
+
let tmpTopic = pTopics[i];
|
|
483
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-item" data-topic-code="' + this._escapeHTML(tmpTopic.TopicCode) + '">';
|
|
484
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-info">';
|
|
485
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-title">' + this._escapeHTML(tmpTopic.TopicTitle) + '</div>';
|
|
486
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-meta">';
|
|
487
|
+
tmpHTML += this._escapeHTML(tmpTopic.TopicCode);
|
|
488
|
+
if (tmpTopic.TopicHelpFilePath)
|
|
489
|
+
{
|
|
490
|
+
tmpHTML += ' · ' + this._escapeHTML(tmpTopic.TopicHelpFilePath);
|
|
491
|
+
}
|
|
492
|
+
tmpHTML += ' · ' + tmpTopic.RouteCount + ' route' + (tmpTopic.RouteCount !== 1 ? 's' : '');
|
|
493
|
+
tmpHTML += '</div>';
|
|
494
|
+
tmpHTML += '</div>';
|
|
495
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-actions">';
|
|
496
|
+
tmpHTML += '<button class="pict-inline-doc-tm-action-btn" data-action="edit" data-topic-code="' + this._escapeHTML(tmpTopic.TopicCode) + '" title="Edit">✎</button>';
|
|
497
|
+
tmpHTML += '<button class="pict-inline-doc-tm-action-btn danger" data-action="delete" data-topic-code="' + this._escapeHTML(tmpTopic.TopicCode) + '" title="Delete">✕</button>';
|
|
498
|
+
tmpHTML += '</div>';
|
|
499
|
+
tmpHTML += '</div>';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
tmpHTML += '</div>';
|
|
503
|
+
tmpHTML += '<div class="pict-inline-doc-tm-new-topic" data-action="new-topic">+ New Topic</div>';
|
|
504
|
+
|
|
505
|
+
return tmpHTML;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Wire click handlers for the topic list modal.
|
|
510
|
+
*
|
|
511
|
+
* @param {HTMLElement} pDialog - The modal dialog element
|
|
512
|
+
*/
|
|
513
|
+
_wireTopicListHandlers(pDialog)
|
|
514
|
+
{
|
|
515
|
+
let tmpSelf = this;
|
|
516
|
+
let tmpModal = this._getModal();
|
|
517
|
+
let tmpProvider = this._getProvider();
|
|
518
|
+
|
|
519
|
+
// Edit buttons
|
|
520
|
+
let tmpEditBtns = pDialog.querySelectorAll('[data-action="edit"]');
|
|
521
|
+
for (let i = 0; i < tmpEditBtns.length; i++)
|
|
522
|
+
{
|
|
523
|
+
tmpEditBtns[i].addEventListener('click', (pEvent) =>
|
|
524
|
+
{
|
|
525
|
+
pEvent.stopPropagation();
|
|
526
|
+
let tmpCode = tmpEditBtns[i].getAttribute('data-topic-code');
|
|
527
|
+
// Dismiss the list modal, then open editor
|
|
528
|
+
if (tmpModal && tmpModal.dismissModals)
|
|
529
|
+
{
|
|
530
|
+
tmpModal.dismissModals();
|
|
531
|
+
}
|
|
532
|
+
setTimeout(() => { tmpSelf.showTopicEditor(tmpCode); }, 250);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Delete buttons
|
|
537
|
+
let tmpDeleteBtns = pDialog.querySelectorAll('[data-action="delete"]');
|
|
538
|
+
for (let i = 0; i < tmpDeleteBtns.length; i++)
|
|
539
|
+
{
|
|
540
|
+
tmpDeleteBtns[i].addEventListener('click', (pEvent) =>
|
|
541
|
+
{
|
|
542
|
+
pEvent.stopPropagation();
|
|
543
|
+
let tmpCode = tmpDeleteBtns[i].getAttribute('data-topic-code');
|
|
544
|
+
|
|
545
|
+
if (tmpModal && tmpModal.confirm)
|
|
546
|
+
{
|
|
547
|
+
tmpModal.confirm(
|
|
548
|
+
'Are you sure you want to delete the topic "' + tmpCode + '"?',
|
|
549
|
+
{ title: 'Delete Topic', dangerous: true }
|
|
550
|
+
).then((pConfirmed) =>
|
|
551
|
+
{
|
|
552
|
+
if (pConfirmed && tmpProvider)
|
|
553
|
+
{
|
|
554
|
+
tmpProvider.removeTopic(tmpCode);
|
|
555
|
+
tmpProvider.saveTopics();
|
|
556
|
+
|
|
557
|
+
// Re-render nav
|
|
558
|
+
let tmpNavView = tmpSelf.pict.views['InlineDoc-Nav'];
|
|
559
|
+
if (tmpNavView)
|
|
560
|
+
{
|
|
561
|
+
tmpNavView.render();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (tmpModal.toast)
|
|
565
|
+
{
|
|
566
|
+
tmpModal.toast('Topic deleted.', { type: 'success' });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Re-open the list
|
|
570
|
+
setTimeout(() => { tmpSelf.showTopicManager(); }, 250);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// New topic button
|
|
578
|
+
let tmpNewBtn = pDialog.querySelector('[data-action="new-topic"]');
|
|
579
|
+
if (tmpNewBtn)
|
|
580
|
+
{
|
|
581
|
+
tmpNewBtn.addEventListener('click', () =>
|
|
582
|
+
{
|
|
583
|
+
if (tmpModal && tmpModal.dismissModals)
|
|
584
|
+
{
|
|
585
|
+
tmpModal.dismissModals();
|
|
586
|
+
}
|
|
587
|
+
setTimeout(() => { tmpSelf.showTopicEditor(null); }, 250);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// -- Topic Editor --
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Show the topic editor modal for creating or editing a topic.
|
|
596
|
+
*
|
|
597
|
+
* @param {string|null} pTopicCode - Topic code to edit, or null for new
|
|
598
|
+
*/
|
|
599
|
+
showTopicEditor(pTopicCode)
|
|
600
|
+
{
|
|
601
|
+
let tmpModal = this._getModal();
|
|
602
|
+
let tmpProvider = this._getProvider();
|
|
603
|
+
|
|
604
|
+
if (!tmpModal || !tmpProvider)
|
|
605
|
+
{
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let tmpState = this.pict.AppData.InlineDocumentation;
|
|
610
|
+
let tmpIsNew = !pTopicCode;
|
|
611
|
+
let tmpTopic = null;
|
|
612
|
+
|
|
613
|
+
if (pTopicCode && tmpState.Topics && tmpState.Topics[pTopicCode])
|
|
614
|
+
{
|
|
615
|
+
// Clone for editing
|
|
616
|
+
tmpTopic = JSON.parse(JSON.stringify(tmpState.Topics[pTopicCode]));
|
|
617
|
+
}
|
|
618
|
+
else
|
|
619
|
+
{
|
|
620
|
+
tmpTopic =
|
|
621
|
+
{
|
|
622
|
+
TopicCode: '',
|
|
623
|
+
TopicTitle: '',
|
|
624
|
+
TopicHelpFilePath: '',
|
|
625
|
+
Routes: []
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
let tmpContent = this._buildTopicEditorHTML(tmpTopic, tmpIsNew);
|
|
630
|
+
|
|
631
|
+
// Track editor routes state in closure
|
|
632
|
+
let tmpEditorRoutes = tmpTopic.Routes ? tmpTopic.Routes.slice() : [];
|
|
633
|
+
|
|
634
|
+
tmpModal.show(
|
|
635
|
+
{
|
|
636
|
+
title: tmpIsNew ? 'New Topic' : 'Edit Topic',
|
|
637
|
+
content: tmpContent,
|
|
638
|
+
closeable: true,
|
|
639
|
+
width: '500px',
|
|
640
|
+
buttons:
|
|
641
|
+
[
|
|
642
|
+
{ Hash: 'cancel', Label: 'Cancel' },
|
|
643
|
+
{ Hash: 'save', Label: 'Save', Style: 'primary' }
|
|
644
|
+
],
|
|
645
|
+
onOpen: (pDialog) =>
|
|
646
|
+
{
|
|
647
|
+
this._wireTopicEditorHandlers(pDialog, tmpTopic, tmpIsNew, tmpEditorRoutes);
|
|
648
|
+
}
|
|
649
|
+
}).then((pResult) =>
|
|
650
|
+
{
|
|
651
|
+
if (pResult === 'save')
|
|
652
|
+
{
|
|
653
|
+
this._handleTopicEditorSave(tmpTopic, tmpIsNew, tmpEditorRoutes);
|
|
654
|
+
}
|
|
655
|
+
else
|
|
656
|
+
{
|
|
657
|
+
// Return to list on cancel
|
|
658
|
+
setTimeout(() => { this.showTopicManager(); }, 250);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Build the HTML for the topic editor form.
|
|
665
|
+
*
|
|
666
|
+
* @param {Object} pTopic - The topic data
|
|
667
|
+
* @param {boolean} pIsNew - Whether this is a new topic
|
|
668
|
+
* @returns {string} HTML content
|
|
669
|
+
*/
|
|
670
|
+
_buildTopicEditorHTML(pTopic, pIsNew)
|
|
671
|
+
{
|
|
672
|
+
let tmpHTML = '';
|
|
673
|
+
|
|
674
|
+
// Topic Code
|
|
675
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-group">';
|
|
676
|
+
tmpHTML += '<label class="pict-inline-doc-tm-form-label">Topic Code</label>';
|
|
677
|
+
if (pIsNew)
|
|
678
|
+
{
|
|
679
|
+
tmpHTML += '<input type="text" class="pict-inline-doc-tm-form-input" id="tm-editor-code" value="" placeholder="MY-TOPIC-CODE" />';
|
|
680
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-hint">Uppercase letters, numbers, and hyphens only.</div>';
|
|
681
|
+
}
|
|
682
|
+
else
|
|
683
|
+
{
|
|
684
|
+
tmpHTML += '<input type="text" class="pict-inline-doc-tm-form-input" id="tm-editor-code" value="' + this._escapeHTML(pTopic.TopicCode) + '" readonly />';
|
|
685
|
+
}
|
|
686
|
+
tmpHTML += '<div class="pict-inline-doc-tm-validation-error" id="tm-editor-code-error"></div>';
|
|
687
|
+
tmpHTML += '</div>';
|
|
688
|
+
|
|
689
|
+
// Topic Title
|
|
690
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-group">';
|
|
691
|
+
tmpHTML += '<label class="pict-inline-doc-tm-form-label">Title</label>';
|
|
692
|
+
tmpHTML += '<input type="text" class="pict-inline-doc-tm-form-input" id="tm-editor-title" value="' + this._escapeHTML(pTopic.TopicTitle || pTopic.Name || '') + '" placeholder="My Topic Title" />';
|
|
693
|
+
tmpHTML += '<div class="pict-inline-doc-tm-validation-error" id="tm-editor-title-error"></div>';
|
|
694
|
+
tmpHTML += '</div>';
|
|
695
|
+
|
|
696
|
+
// Help File Path
|
|
697
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-group">';
|
|
698
|
+
tmpHTML += '<label class="pict-inline-doc-tm-form-label">Help Document</label>';
|
|
699
|
+
tmpHTML += '<div style="display:flex;gap:0.3em;align-items:center;">';
|
|
700
|
+
tmpHTML += '<input type="text" class="pict-inline-doc-tm-form-input" id="tm-editor-helpfile" value="' + this._escapeHTML(pTopic.TopicHelpFilePath || '') + '" placeholder="help-topic.md" style="flex:1;" />';
|
|
701
|
+
tmpHTML += '<button class="pict-inline-doc-tm-route-action-btn" id="tm-editor-browse-sidebar" title="Browse sidebar documents">Browse</button>';
|
|
702
|
+
tmpHTML += '</div>';
|
|
703
|
+
tmpHTML += '</div>';
|
|
704
|
+
|
|
705
|
+
// Routes
|
|
706
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-group">';
|
|
707
|
+
tmpHTML += '<label class="pict-inline-doc-tm-form-label">Routes</label>';
|
|
708
|
+
tmpHTML += '<div class="pict-inline-doc-tm-routes-section">';
|
|
709
|
+
tmpHTML += '<div class="pict-inline-doc-tm-route-chips" id="tm-editor-route-chips">';
|
|
710
|
+
tmpHTML += this._buildRouteChipsHTML(pTopic.Routes || []);
|
|
711
|
+
tmpHTML += '</div>';
|
|
712
|
+
tmpHTML += '<div class="pict-inline-doc-tm-route-actions">';
|
|
713
|
+
tmpHTML += '<button class="pict-inline-doc-tm-route-action-btn" id="tm-editor-add-route">+ Add Route</button>';
|
|
714
|
+
|
|
715
|
+
let tmpState = this.pict.AppData.InlineDocumentation;
|
|
716
|
+
if (tmpState && tmpState.CurrentRoute)
|
|
717
|
+
{
|
|
718
|
+
tmpHTML += '<button class="pict-inline-doc-tm-route-action-btn accent" id="tm-editor-add-current-route">+ Current Route</button>';
|
|
719
|
+
tmpHTML += '<button class="pict-inline-doc-tm-route-action-btn accent" id="tm-editor-build-wildcard">Build Wildcard</button>';
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
tmpHTML += '</div>';
|
|
723
|
+
tmpHTML += '<div class="pict-inline-doc-tm-route-input-row" id="tm-editor-route-input-row">';
|
|
724
|
+
tmpHTML += '<input type="text" class="pict-inline-doc-tm-route-input" id="tm-editor-route-input" placeholder="/my/route" />';
|
|
725
|
+
tmpHTML += '<button class="pict-inline-doc-tm-route-action-btn accent" id="tm-editor-route-input-add">Add</button>';
|
|
726
|
+
tmpHTML += '</div>';
|
|
727
|
+
tmpHTML += '</div>';
|
|
728
|
+
tmpHTML += '</div>';
|
|
729
|
+
|
|
730
|
+
return tmpHTML;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Build HTML for route chips.
|
|
735
|
+
*
|
|
736
|
+
* @param {Array} pRoutes - Array of route pattern strings
|
|
737
|
+
* @returns {string} HTML for the chips
|
|
738
|
+
*/
|
|
739
|
+
_buildRouteChipsHTML(pRoutes)
|
|
740
|
+
{
|
|
741
|
+
if (!pRoutes || pRoutes.length < 1)
|
|
742
|
+
{
|
|
743
|
+
return '<span style="font-size:0.8em;color:#8A7F72;">No routes bound.</span>';
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
let tmpHTML = '';
|
|
747
|
+
for (let i = 0; i < pRoutes.length; i++)
|
|
748
|
+
{
|
|
749
|
+
tmpHTML += '<span class="pict-inline-doc-tm-route-chip">';
|
|
750
|
+
tmpHTML += this._escapeHTML(pRoutes[i]);
|
|
751
|
+
tmpHTML += '<span class="pict-inline-doc-tm-route-chip-remove" data-route="' + this._escapeHTML(pRoutes[i]) + '">×</span>';
|
|
752
|
+
tmpHTML += '</span>';
|
|
753
|
+
}
|
|
754
|
+
return tmpHTML;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Refresh the route chips in an open editor dialog.
|
|
759
|
+
*
|
|
760
|
+
* @param {Array} pRoutes - Current routes array
|
|
761
|
+
*/
|
|
762
|
+
_refreshRouteChips(pRoutes)
|
|
763
|
+
{
|
|
764
|
+
if (typeof document === 'undefined')
|
|
765
|
+
{
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
let tmpContainer = document.getElementById('tm-editor-route-chips');
|
|
770
|
+
if (tmpContainer)
|
|
771
|
+
{
|
|
772
|
+
tmpContainer.innerHTML = this._buildRouteChipsHTML(pRoutes);
|
|
773
|
+
this._wireRouteChipRemoveHandlers(pRoutes);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Wire remove handlers on route chips.
|
|
779
|
+
*
|
|
780
|
+
* @param {Array} pRoutes - The mutable routes array
|
|
781
|
+
*/
|
|
782
|
+
_wireRouteChipRemoveHandlers(pRoutes)
|
|
783
|
+
{
|
|
784
|
+
if (typeof document === 'undefined')
|
|
785
|
+
{
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
let tmpSelf = this;
|
|
790
|
+
let tmpRemoveBtns = document.querySelectorAll('.pict-inline-doc-tm-route-chip-remove');
|
|
791
|
+
for (let i = 0; i < tmpRemoveBtns.length; i++)
|
|
792
|
+
{
|
|
793
|
+
tmpRemoveBtns[i].addEventListener('click', (pEvent) =>
|
|
794
|
+
{
|
|
795
|
+
pEvent.stopPropagation();
|
|
796
|
+
let tmpRoute = tmpRemoveBtns[i].getAttribute('data-route');
|
|
797
|
+
let tmpIdx = pRoutes.indexOf(tmpRoute);
|
|
798
|
+
if (tmpIdx >= 0)
|
|
799
|
+
{
|
|
800
|
+
pRoutes.splice(tmpIdx, 1);
|
|
801
|
+
}
|
|
802
|
+
tmpSelf._refreshRouteChips(pRoutes);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Wire all handlers for the topic editor form.
|
|
809
|
+
*
|
|
810
|
+
* @param {HTMLElement} pDialog - The modal dialog element
|
|
811
|
+
* @param {Object} pTopic - The topic data
|
|
812
|
+
* @param {boolean} pIsNew - Whether this is a new topic
|
|
813
|
+
* @param {Array} pEditorRoutes - Mutable routes array for this editor session
|
|
814
|
+
*/
|
|
815
|
+
_wireTopicEditorHandlers(pDialog, pTopic, pIsNew, pEditorRoutes)
|
|
816
|
+
{
|
|
817
|
+
let tmpSelf = this;
|
|
818
|
+
let tmpProvider = this._getProvider();
|
|
819
|
+
let tmpState = this.pict.AppData.InlineDocumentation;
|
|
820
|
+
|
|
821
|
+
// Route chip remove handlers
|
|
822
|
+
this._wireRouteChipRemoveHandlers(pEditorRoutes);
|
|
823
|
+
|
|
824
|
+
// Add Route button — show input row
|
|
825
|
+
let tmpAddRouteBtn = document.getElementById('tm-editor-add-route');
|
|
826
|
+
if (tmpAddRouteBtn)
|
|
827
|
+
{
|
|
828
|
+
tmpAddRouteBtn.addEventListener('click', () =>
|
|
829
|
+
{
|
|
830
|
+
let tmpRow = document.getElementById('tm-editor-route-input-row');
|
|
831
|
+
if (tmpRow)
|
|
832
|
+
{
|
|
833
|
+
tmpRow.classList.toggle('visible');
|
|
834
|
+
let tmpInput = document.getElementById('tm-editor-route-input');
|
|
835
|
+
if (tmpInput)
|
|
836
|
+
{
|
|
837
|
+
tmpInput.focus();
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Add route from text input
|
|
844
|
+
let tmpRouteInputAddBtn = document.getElementById('tm-editor-route-input-add');
|
|
845
|
+
if (tmpRouteInputAddBtn)
|
|
846
|
+
{
|
|
847
|
+
tmpRouteInputAddBtn.addEventListener('click', () =>
|
|
848
|
+
{
|
|
849
|
+
let tmpInput = document.getElementById('tm-editor-route-input');
|
|
850
|
+
if (tmpInput && tmpInput.value.trim())
|
|
851
|
+
{
|
|
852
|
+
let tmpRoute = tmpInput.value.trim();
|
|
853
|
+
if (tmpRoute.charAt(0) !== '/')
|
|
854
|
+
{
|
|
855
|
+
tmpRoute = '/' + tmpRoute;
|
|
856
|
+
}
|
|
857
|
+
if (pEditorRoutes.indexOf(tmpRoute) < 0)
|
|
858
|
+
{
|
|
859
|
+
pEditorRoutes.push(tmpRoute);
|
|
860
|
+
tmpSelf._refreshRouteChips(pEditorRoutes);
|
|
861
|
+
}
|
|
862
|
+
tmpInput.value = '';
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Enter key on route input
|
|
868
|
+
let tmpRouteInput = document.getElementById('tm-editor-route-input');
|
|
869
|
+
if (tmpRouteInput)
|
|
870
|
+
{
|
|
871
|
+
tmpRouteInput.addEventListener('keydown', (pEvent) =>
|
|
872
|
+
{
|
|
873
|
+
if (pEvent.key === 'Enter')
|
|
874
|
+
{
|
|
875
|
+
pEvent.preventDefault();
|
|
876
|
+
if (tmpRouteInputAddBtn)
|
|
877
|
+
{
|
|
878
|
+
tmpRouteInputAddBtn.click();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Add Current Route button
|
|
885
|
+
let tmpAddCurrentBtn = document.getElementById('tm-editor-add-current-route');
|
|
886
|
+
if (tmpAddCurrentBtn && tmpState && tmpState.CurrentRoute)
|
|
887
|
+
{
|
|
888
|
+
tmpAddCurrentBtn.addEventListener('click', () =>
|
|
889
|
+
{
|
|
890
|
+
let tmpRoute = tmpState.CurrentRoute;
|
|
891
|
+
if (pEditorRoutes.indexOf(tmpRoute) < 0)
|
|
892
|
+
{
|
|
893
|
+
pEditorRoutes.push(tmpRoute);
|
|
894
|
+
tmpSelf._refreshRouteChips(pEditorRoutes);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Build Wildcard button
|
|
900
|
+
let tmpWildcardBtn = document.getElementById('tm-editor-build-wildcard');
|
|
901
|
+
if (tmpWildcardBtn && tmpProvider && tmpState && tmpState.CurrentRoute)
|
|
902
|
+
{
|
|
903
|
+
tmpWildcardBtn.addEventListener('click', () =>
|
|
904
|
+
{
|
|
905
|
+
let tmpModal = tmpSelf._getModal();
|
|
906
|
+
if (tmpModal && tmpModal.dismissModals)
|
|
907
|
+
{
|
|
908
|
+
tmpModal.dismissModals();
|
|
909
|
+
}
|
|
910
|
+
setTimeout(() =>
|
|
911
|
+
{
|
|
912
|
+
tmpSelf.showWildcardBuilder(tmpState.CurrentRoute, (pPattern) =>
|
|
913
|
+
{
|
|
914
|
+
if (pPattern && pEditorRoutes.indexOf(pPattern) < 0)
|
|
915
|
+
{
|
|
916
|
+
pEditorRoutes.push(pPattern);
|
|
917
|
+
}
|
|
918
|
+
// Re-open the editor
|
|
919
|
+
tmpSelf._reopenEditorAfterSubflow(pTopic, pIsNew, pEditorRoutes);
|
|
920
|
+
});
|
|
921
|
+
}, 250);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Browse Sidebar button
|
|
926
|
+
let tmpBrowseBtn = document.getElementById('tm-editor-browse-sidebar');
|
|
927
|
+
if (tmpBrowseBtn)
|
|
928
|
+
{
|
|
929
|
+
tmpBrowseBtn.addEventListener('click', () =>
|
|
930
|
+
{
|
|
931
|
+
let tmpModal = tmpSelf._getModal();
|
|
932
|
+
if (tmpModal && tmpModal.dismissModals)
|
|
933
|
+
{
|
|
934
|
+
tmpModal.dismissModals();
|
|
935
|
+
}
|
|
936
|
+
setTimeout(() =>
|
|
937
|
+
{
|
|
938
|
+
tmpSelf._showSidebarPicker((pPath) =>
|
|
939
|
+
{
|
|
940
|
+
if (pPath)
|
|
941
|
+
{
|
|
942
|
+
pTopic.TopicHelpFilePath = pPath;
|
|
943
|
+
}
|
|
944
|
+
tmpSelf._reopenEditorAfterSubflow(pTopic, pIsNew, pEditorRoutes);
|
|
945
|
+
});
|
|
946
|
+
}, 250);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Re-open the topic editor after returning from a sub-flow (wildcard builder, sidebar picker).
|
|
953
|
+
*
|
|
954
|
+
* Captures current form values from the DOM before the modal was dismissed,
|
|
955
|
+
* then reconstructs the editor with updated state.
|
|
956
|
+
*
|
|
957
|
+
* @param {Object} pTopic - The topic data (may have been updated by sub-flow)
|
|
958
|
+
* @param {boolean} pIsNew - Whether this is a new topic
|
|
959
|
+
* @param {Array} pEditorRoutes - Current routes for this editor session
|
|
960
|
+
*/
|
|
961
|
+
_reopenEditorAfterSubflow(pTopic, pIsNew, pEditorRoutes)
|
|
962
|
+
{
|
|
963
|
+
let tmpModal = this._getModal();
|
|
964
|
+
let tmpProvider = this._getProvider();
|
|
965
|
+
|
|
966
|
+
if (!tmpModal || !tmpProvider)
|
|
967
|
+
{
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Rebuild the topic from whatever was captured
|
|
972
|
+
let tmpContent = this._buildTopicEditorHTML(
|
|
973
|
+
{
|
|
974
|
+
TopicCode: pTopic.TopicCode,
|
|
975
|
+
TopicTitle: pTopic.TopicTitle,
|
|
976
|
+
TopicHelpFilePath: pTopic.TopicHelpFilePath,
|
|
977
|
+
Routes: pEditorRoutes
|
|
978
|
+
}, pIsNew);
|
|
979
|
+
|
|
980
|
+
tmpModal.show(
|
|
981
|
+
{
|
|
982
|
+
title: pIsNew ? 'New Topic' : 'Edit Topic',
|
|
983
|
+
content: tmpContent,
|
|
984
|
+
closeable: true,
|
|
985
|
+
width: '500px',
|
|
986
|
+
buttons:
|
|
987
|
+
[
|
|
988
|
+
{ Hash: 'cancel', Label: 'Cancel' },
|
|
989
|
+
{ Hash: 'save', Label: 'Save', Style: 'primary' }
|
|
990
|
+
],
|
|
991
|
+
onOpen: (pDialog) =>
|
|
992
|
+
{
|
|
993
|
+
this._wireTopicEditorHandlers(pDialog, pTopic, pIsNew, pEditorRoutes);
|
|
994
|
+
}
|
|
995
|
+
}).then((pResult) =>
|
|
996
|
+
{
|
|
997
|
+
if (pResult === 'save')
|
|
998
|
+
{
|
|
999
|
+
this._handleTopicEditorSave(pTopic, pIsNew, pEditorRoutes);
|
|
1000
|
+
}
|
|
1001
|
+
else
|
|
1002
|
+
{
|
|
1003
|
+
setTimeout(() => { this.showTopicManager(); }, 250);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Handle saving from the topic editor.
|
|
1010
|
+
*
|
|
1011
|
+
* Reads form values, validates, and persists.
|
|
1012
|
+
*
|
|
1013
|
+
* @param {Object} pTopic - The original topic data
|
|
1014
|
+
* @param {boolean} pIsNew - Whether this is a new topic
|
|
1015
|
+
* @param {Array} pEditorRoutes - Current routes from the editor
|
|
1016
|
+
*/
|
|
1017
|
+
_handleTopicEditorSave(pTopic, pIsNew, pEditorRoutes)
|
|
1018
|
+
{
|
|
1019
|
+
let tmpProvider = this._getProvider();
|
|
1020
|
+
let tmpModal = this._getModal();
|
|
1021
|
+
let tmpState = this.pict.AppData.InlineDocumentation;
|
|
1022
|
+
|
|
1023
|
+
if (!tmpProvider)
|
|
1024
|
+
{
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Read form values from DOM (they're still present briefly during dismiss)
|
|
1029
|
+
let tmpCode = '';
|
|
1030
|
+
let tmpTitle = '';
|
|
1031
|
+
let tmpHelpFile = '';
|
|
1032
|
+
|
|
1033
|
+
if (typeof document !== 'undefined')
|
|
1034
|
+
{
|
|
1035
|
+
let tmpCodeInput = document.getElementById('tm-editor-code');
|
|
1036
|
+
let tmpTitleInput = document.getElementById('tm-editor-title');
|
|
1037
|
+
let tmpHelpInput = document.getElementById('tm-editor-helpfile');
|
|
1038
|
+
|
|
1039
|
+
if (tmpCodeInput) { tmpCode = tmpCodeInput.value.trim(); }
|
|
1040
|
+
if (tmpTitleInput) { tmpTitle = tmpTitleInput.value.trim(); }
|
|
1041
|
+
if (tmpHelpInput) { tmpHelpFile = tmpHelpInput.value.trim(); }
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Validation
|
|
1045
|
+
let tmpErrors = [];
|
|
1046
|
+
|
|
1047
|
+
if (pIsNew)
|
|
1048
|
+
{
|
|
1049
|
+
if (!tmpCode)
|
|
1050
|
+
{
|
|
1051
|
+
tmpErrors.push('Topic Code is required.');
|
|
1052
|
+
}
|
|
1053
|
+
else if (!/^[A-Z0-9][A-Z0-9-]*$/.test(tmpCode))
|
|
1054
|
+
{
|
|
1055
|
+
tmpErrors.push('Topic Code must use uppercase letters, numbers, and hyphens only.');
|
|
1056
|
+
}
|
|
1057
|
+
else if (tmpState.Topics && tmpState.Topics[tmpCode])
|
|
1058
|
+
{
|
|
1059
|
+
tmpErrors.push('A topic with code "' + tmpCode + '" already exists.');
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
else
|
|
1063
|
+
{
|
|
1064
|
+
tmpCode = pTopic.TopicCode;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (!tmpTitle)
|
|
1068
|
+
{
|
|
1069
|
+
tmpErrors.push('Title is required.');
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (tmpErrors.length > 0)
|
|
1073
|
+
{
|
|
1074
|
+
if (tmpModal && tmpModal.toast)
|
|
1075
|
+
{
|
|
1076
|
+
tmpModal.toast(tmpErrors.join(' '), { type: 'error' });
|
|
1077
|
+
}
|
|
1078
|
+
// Re-open editor with current values
|
|
1079
|
+
pTopic.TopicTitle = tmpTitle;
|
|
1080
|
+
pTopic.TopicHelpFilePath = tmpHelpFile;
|
|
1081
|
+
if (pIsNew)
|
|
1082
|
+
{
|
|
1083
|
+
pTopic.TopicCode = tmpCode;
|
|
1084
|
+
}
|
|
1085
|
+
setTimeout(() => { this._reopenEditorAfterSubflow(pTopic, pIsNew, pEditorRoutes); }, 300);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Apply changes
|
|
1090
|
+
if (pIsNew)
|
|
1091
|
+
{
|
|
1092
|
+
tmpProvider.addTopic(tmpCode,
|
|
1093
|
+
{
|
|
1094
|
+
TopicCode: tmpCode,
|
|
1095
|
+
TopicTitle: tmpTitle,
|
|
1096
|
+
TopicHelpFilePath: tmpHelpFile,
|
|
1097
|
+
Routes: pEditorRoutes
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
else
|
|
1101
|
+
{
|
|
1102
|
+
tmpProvider.updateTopic(tmpCode,
|
|
1103
|
+
{
|
|
1104
|
+
TopicTitle: tmpTitle,
|
|
1105
|
+
TopicHelpFilePath: tmpHelpFile,
|
|
1106
|
+
Routes: pEditorRoutes
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
tmpProvider.saveTopics();
|
|
1111
|
+
|
|
1112
|
+
// Re-render nav
|
|
1113
|
+
let tmpNavView = this.pict.views['InlineDoc-Nav'];
|
|
1114
|
+
if (tmpNavView)
|
|
1115
|
+
{
|
|
1116
|
+
tmpNavView.render();
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (tmpModal && tmpModal.toast)
|
|
1120
|
+
{
|
|
1121
|
+
tmpModal.toast('Topic saved.', { type: 'success' });
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Return to topic list
|
|
1125
|
+
setTimeout(() => { this.showTopicManager(); }, 300);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// -- Wildcard Builder --
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Show the wildcard builder modal.
|
|
1132
|
+
*
|
|
1133
|
+
* Displays route segments as clickable blocks and lets the user
|
|
1134
|
+
* visually choose where the wildcard starts.
|
|
1135
|
+
*
|
|
1136
|
+
* @param {string} pCurrentRoute - The route to build a pattern for
|
|
1137
|
+
* @param {Function} fOnSelect - Callback receiving the selected pattern (or null on cancel)
|
|
1138
|
+
*/
|
|
1139
|
+
showWildcardBuilder(pCurrentRoute, fOnSelect)
|
|
1140
|
+
{
|
|
1141
|
+
let tmpModal = this._getModal();
|
|
1142
|
+
let tmpProvider = this._getProvider();
|
|
1143
|
+
|
|
1144
|
+
if (!tmpModal || !tmpProvider)
|
|
1145
|
+
{
|
|
1146
|
+
if (typeof fOnSelect === 'function') { fOnSelect(null); }
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
let tmpSegments = tmpProvider.getRouteSegments(pCurrentRoute);
|
|
1151
|
+
|
|
1152
|
+
if (tmpSegments.length < 1)
|
|
1153
|
+
{
|
|
1154
|
+
if (tmpModal.toast)
|
|
1155
|
+
{
|
|
1156
|
+
tmpModal.toast('No route segments to build a wildcard from.', { type: 'error' });
|
|
1157
|
+
}
|
|
1158
|
+
if (typeof fOnSelect === 'function') { fOnSelect(null); }
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Default selection: last segment before end (or first if only one)
|
|
1163
|
+
let tmpSelectedIndex = Math.max(0, tmpSegments.length - 2);
|
|
1164
|
+
let tmpContent = this._buildWildcardBuilderHTML(tmpSegments, pCurrentRoute, tmpSelectedIndex);
|
|
1165
|
+
|
|
1166
|
+
tmpModal.show(
|
|
1167
|
+
{
|
|
1168
|
+
title: 'Build Wildcard Pattern',
|
|
1169
|
+
content: tmpContent,
|
|
1170
|
+
closeable: true,
|
|
1171
|
+
width: '520px',
|
|
1172
|
+
buttons:
|
|
1173
|
+
[
|
|
1174
|
+
{ Hash: 'cancel', Label: 'Cancel' },
|
|
1175
|
+
{ Hash: 'exact', Label: 'Use Exact Route' },
|
|
1176
|
+
{ Hash: 'pattern', Label: 'Use Pattern', Style: 'primary' }
|
|
1177
|
+
],
|
|
1178
|
+
onOpen: (pDialog) =>
|
|
1179
|
+
{
|
|
1180
|
+
this._wireWildcardBuilderHandlers(pDialog, tmpSegments, tmpSelectedIndex);
|
|
1181
|
+
}
|
|
1182
|
+
}).then((pResult) =>
|
|
1183
|
+
{
|
|
1184
|
+
if (pResult === 'pattern')
|
|
1185
|
+
{
|
|
1186
|
+
// Get the current selection
|
|
1187
|
+
let tmpPreview = (typeof document !== 'undefined') ? document.getElementById('tm-wc-preview-value') : null;
|
|
1188
|
+
let tmpPattern = tmpPreview ? tmpPreview.textContent : tmpSegments[tmpSelectedIndex].WildcardPattern;
|
|
1189
|
+
if (typeof fOnSelect === 'function') { fOnSelect(tmpPattern); }
|
|
1190
|
+
}
|
|
1191
|
+
else if (pResult === 'exact')
|
|
1192
|
+
{
|
|
1193
|
+
if (typeof fOnSelect === 'function') { fOnSelect(pCurrentRoute); }
|
|
1194
|
+
}
|
|
1195
|
+
else
|
|
1196
|
+
{
|
|
1197
|
+
if (typeof fOnSelect === 'function') { fOnSelect(null); }
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Build the HTML for the wildcard builder.
|
|
1204
|
+
*
|
|
1205
|
+
* @param {Array} pSegments - Segment objects from getRouteSegments()
|
|
1206
|
+
* @param {string} pCurrentRoute - The original route
|
|
1207
|
+
* @param {number} pSelectedIndex - The initially selected segment index
|
|
1208
|
+
* @returns {string} HTML content
|
|
1209
|
+
*/
|
|
1210
|
+
_buildWildcardBuilderHTML(pSegments, pCurrentRoute, pSelectedIndex)
|
|
1211
|
+
{
|
|
1212
|
+
let tmpHTML = '<div class="pict-inline-doc-tm-wc-container">';
|
|
1213
|
+
|
|
1214
|
+
tmpHTML += '<div class="pict-inline-doc-tm-wc-label">Click a segment to set the wildcard boundary. Everything after the selected segment will match any path.</div>';
|
|
1215
|
+
|
|
1216
|
+
tmpHTML += '<div class="pict-inline-doc-tm-wc-segments" id="tm-wc-segments">';
|
|
1217
|
+
|
|
1218
|
+
for (let i = 0; i < pSegments.length; i++)
|
|
1219
|
+
{
|
|
1220
|
+
let tmpClass = 'pict-inline-doc-tm-wc-segment';
|
|
1221
|
+
if (i === pSelectedIndex)
|
|
1222
|
+
{
|
|
1223
|
+
tmpClass += ' selected';
|
|
1224
|
+
}
|
|
1225
|
+
else if (i > pSelectedIndex)
|
|
1226
|
+
{
|
|
1227
|
+
tmpClass += ' after-wildcard';
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
tmpHTML += '<span class="pict-inline-doc-tm-wc-slash">/</span>';
|
|
1231
|
+
tmpHTML += '<span class="' + tmpClass + '" data-segment-index="' + i + '">';
|
|
1232
|
+
tmpHTML += this._escapeHTML(pSegments[i].Segment);
|
|
1233
|
+
tmpHTML += '</span>';
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
tmpHTML += '<span class="pict-inline-doc-tm-wc-slash">/</span>';
|
|
1237
|
+
tmpHTML += '<span class="pict-inline-doc-tm-wc-wildcard-star" id="tm-wc-star">*</span>';
|
|
1238
|
+
|
|
1239
|
+
tmpHTML += '</div>';
|
|
1240
|
+
|
|
1241
|
+
tmpHTML += '<div class="pict-inline-doc-tm-wc-preview-label">Pattern</div>';
|
|
1242
|
+
tmpHTML += '<div class="pict-inline-doc-tm-wc-preview" id="tm-wc-preview-value">';
|
|
1243
|
+
tmpHTML += this._escapeHTML(pSegments[pSelectedIndex].WildcardPattern);
|
|
1244
|
+
tmpHTML += '</div>';
|
|
1245
|
+
|
|
1246
|
+
tmpHTML += '</div>';
|
|
1247
|
+
|
|
1248
|
+
return tmpHTML;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Wire click handlers for the wildcard builder segments.
|
|
1253
|
+
*
|
|
1254
|
+
* @param {HTMLElement} pDialog - The modal dialog element
|
|
1255
|
+
* @param {Array} pSegments - Segment objects
|
|
1256
|
+
* @param {number} pInitialIndex - Initially selected index
|
|
1257
|
+
*/
|
|
1258
|
+
_wireWildcardBuilderHandlers(pDialog, pSegments, pInitialIndex)
|
|
1259
|
+
{
|
|
1260
|
+
let tmpSelectedIndex = pInitialIndex;
|
|
1261
|
+
|
|
1262
|
+
let tmpUpdateSelection = (pNewIndex) =>
|
|
1263
|
+
{
|
|
1264
|
+
tmpSelectedIndex = pNewIndex;
|
|
1265
|
+
let tmpSegmentEls = pDialog.querySelectorAll('.pict-inline-doc-tm-wc-segment');
|
|
1266
|
+
for (let i = 0; i < tmpSegmentEls.length; i++)
|
|
1267
|
+
{
|
|
1268
|
+
let tmpIdx = parseInt(tmpSegmentEls[i].getAttribute('data-segment-index'), 10);
|
|
1269
|
+
tmpSegmentEls[i].classList.remove('selected', 'after-wildcard');
|
|
1270
|
+
if (tmpIdx === pNewIndex)
|
|
1271
|
+
{
|
|
1272
|
+
tmpSegmentEls[i].classList.add('selected');
|
|
1273
|
+
}
|
|
1274
|
+
else if (tmpIdx > pNewIndex)
|
|
1275
|
+
{
|
|
1276
|
+
tmpSegmentEls[i].classList.add('after-wildcard');
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
let tmpPreview = pDialog.querySelector('#tm-wc-preview-value');
|
|
1281
|
+
if (tmpPreview)
|
|
1282
|
+
{
|
|
1283
|
+
tmpPreview.textContent = pSegments[pNewIndex].WildcardPattern;
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
let tmpSegmentEls = pDialog.querySelectorAll('.pict-inline-doc-tm-wc-segment');
|
|
1288
|
+
for (let i = 0; i < tmpSegmentEls.length; i++)
|
|
1289
|
+
{
|
|
1290
|
+
tmpSegmentEls[i].addEventListener('click', () =>
|
|
1291
|
+
{
|
|
1292
|
+
let tmpIdx = parseInt(tmpSegmentEls[i].getAttribute('data-segment-index'), 10);
|
|
1293
|
+
tmpUpdateSelection(tmpIdx);
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// -- Bind Topic to Route --
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Show the quick-bind flow for connecting a topic to the current route.
|
|
1302
|
+
*/
|
|
1303
|
+
showBindTopicToRoute()
|
|
1304
|
+
{
|
|
1305
|
+
let tmpModal = this._getModal();
|
|
1306
|
+
let tmpProvider = this._getProvider();
|
|
1307
|
+
|
|
1308
|
+
if (!tmpModal || !tmpProvider)
|
|
1309
|
+
{
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
let tmpState = this.pict.AppData.InlineDocumentation;
|
|
1314
|
+
if (!tmpState || !tmpState.CurrentRoute)
|
|
1315
|
+
{
|
|
1316
|
+
if (tmpModal.toast)
|
|
1317
|
+
{
|
|
1318
|
+
tmpModal.toast('No current route to bind to.', { type: 'error' });
|
|
1319
|
+
}
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
let tmpCurrentRoute = tmpState.CurrentRoute;
|
|
1324
|
+
let tmpTopics = tmpProvider.getTopicList();
|
|
1325
|
+
let tmpContent = this._buildBindHTML(tmpCurrentRoute, tmpTopics);
|
|
1326
|
+
|
|
1327
|
+
// Track selection state
|
|
1328
|
+
let tmpSelectedTopicCode = null;
|
|
1329
|
+
let tmpRouteType = 'exact'; // 'exact' or 'wildcard'
|
|
1330
|
+
|
|
1331
|
+
tmpModal.show(
|
|
1332
|
+
{
|
|
1333
|
+
title: 'Bind Topic to Route',
|
|
1334
|
+
content: tmpContent,
|
|
1335
|
+
closeable: true,
|
|
1336
|
+
width: '480px',
|
|
1337
|
+
buttons:
|
|
1338
|
+
[
|
|
1339
|
+
{ Hash: 'cancel', Label: 'Cancel' },
|
|
1340
|
+
{ Hash: 'bind', Label: 'Bind Route', Style: 'primary' }
|
|
1341
|
+
],
|
|
1342
|
+
onOpen: (pDialog) =>
|
|
1343
|
+
{
|
|
1344
|
+
this._wireBindHandlers(pDialog, tmpTopics, tmpCurrentRoute,
|
|
1345
|
+
(pCode) => { tmpSelectedTopicCode = pCode; },
|
|
1346
|
+
(pType) => { tmpRouteType = pType; }
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}).then((pResult) =>
|
|
1350
|
+
{
|
|
1351
|
+
if (pResult !== 'bind' || !tmpSelectedTopicCode)
|
|
1352
|
+
{
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
if (tmpSelectedTopicCode === '__NEW__')
|
|
1357
|
+
{
|
|
1358
|
+
// Open new topic editor with current route pre-filled
|
|
1359
|
+
this.showTopicEditor(null);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (tmpRouteType === 'wildcard')
|
|
1364
|
+
{
|
|
1365
|
+
// Open wildcard builder, then bind
|
|
1366
|
+
this.showWildcardBuilder(tmpCurrentRoute, (pPattern) =>
|
|
1367
|
+
{
|
|
1368
|
+
if (pPattern)
|
|
1369
|
+
{
|
|
1370
|
+
tmpProvider.addRouteToTopic(tmpSelectedTopicCode, pPattern);
|
|
1371
|
+
tmpProvider.saveTopics();
|
|
1372
|
+
|
|
1373
|
+
let tmpNavView = this.pict.views['InlineDoc-Nav'];
|
|
1374
|
+
if (tmpNavView) { tmpNavView.render(); }
|
|
1375
|
+
|
|
1376
|
+
if (tmpModal.toast)
|
|
1377
|
+
{
|
|
1378
|
+
tmpModal.toast('Route bound to topic.', { type: 'success' });
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
else
|
|
1384
|
+
{
|
|
1385
|
+
// Exact match bind
|
|
1386
|
+
tmpProvider.addRouteToTopic(tmpSelectedTopicCode, tmpCurrentRoute);
|
|
1387
|
+
tmpProvider.saveTopics();
|
|
1388
|
+
|
|
1389
|
+
let tmpNavView = this.pict.views['InlineDoc-Nav'];
|
|
1390
|
+
if (tmpNavView) { tmpNavView.render(); }
|
|
1391
|
+
|
|
1392
|
+
if (tmpModal.toast)
|
|
1393
|
+
{
|
|
1394
|
+
tmpModal.toast('Route bound to topic.', { type: 'success' });
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* Build the HTML for the bind-topic-to-route modal.
|
|
1402
|
+
*
|
|
1403
|
+
* @param {string} pCurrentRoute - The current route
|
|
1404
|
+
* @param {Array} pTopics - Topic list
|
|
1405
|
+
* @returns {string} HTML content
|
|
1406
|
+
*/
|
|
1407
|
+
_buildBindHTML(pCurrentRoute, pTopics)
|
|
1408
|
+
{
|
|
1409
|
+
let tmpHTML = '';
|
|
1410
|
+
|
|
1411
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-route-display">' + this._escapeHTML(pCurrentRoute) + '</div>';
|
|
1412
|
+
|
|
1413
|
+
// Route type selection
|
|
1414
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-group">';
|
|
1415
|
+
tmpHTML += '<label class="pict-inline-doc-tm-form-label">Route Match Type</label>';
|
|
1416
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-route-type">';
|
|
1417
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-route-type-btn selected" data-route-type="exact">Exact Match</div>';
|
|
1418
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-route-type-btn" data-route-type="wildcard">Wildcard Pattern</div>';
|
|
1419
|
+
tmpHTML += '</div>';
|
|
1420
|
+
tmpHTML += '</div>';
|
|
1421
|
+
|
|
1422
|
+
// Topic selection
|
|
1423
|
+
tmpHTML += '<div class="pict-inline-doc-tm-form-group">';
|
|
1424
|
+
tmpHTML += '<label class="pict-inline-doc-tm-form-label">Select Topic</label>';
|
|
1425
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-topic-list">';
|
|
1426
|
+
|
|
1427
|
+
for (let i = 0; i < pTopics.length; i++)
|
|
1428
|
+
{
|
|
1429
|
+
let tmpTopic = pTopics[i];
|
|
1430
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-topic-option" data-topic-code="' + this._escapeHTML(tmpTopic.TopicCode) + '">';
|
|
1431
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-radio"></div>';
|
|
1432
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-info">';
|
|
1433
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-title">' + this._escapeHTML(tmpTopic.TopicTitle) + '</div>';
|
|
1434
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-meta">' + this._escapeHTML(tmpTopic.TopicCode) + '</div>';
|
|
1435
|
+
tmpHTML += '</div>';
|
|
1436
|
+
tmpHTML += '</div>';
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Create new option
|
|
1440
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-topic-option" data-topic-code="__NEW__">';
|
|
1441
|
+
tmpHTML += '<div class="pict-inline-doc-tm-bind-radio"></div>';
|
|
1442
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-info">';
|
|
1443
|
+
tmpHTML += '<div class="pict-inline-doc-tm-topic-title" style="color:#2E7D74;">+ Create New Topic</div>';
|
|
1444
|
+
tmpHTML += '</div>';
|
|
1445
|
+
tmpHTML += '</div>';
|
|
1446
|
+
|
|
1447
|
+
tmpHTML += '</div>';
|
|
1448
|
+
tmpHTML += '</div>';
|
|
1449
|
+
|
|
1450
|
+
return tmpHTML;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Wire handlers for the bind-topic-to-route modal.
|
|
1455
|
+
*
|
|
1456
|
+
* @param {HTMLElement} pDialog - The modal dialog element
|
|
1457
|
+
* @param {Array} pTopics - Topic list
|
|
1458
|
+
* @param {string} pCurrentRoute - The current route
|
|
1459
|
+
* @param {Function} fOnTopicSelect - Called with selected topic code
|
|
1460
|
+
* @param {Function} fOnRouteTypeSelect - Called with 'exact' or 'wildcard'
|
|
1461
|
+
*/
|
|
1462
|
+
_wireBindHandlers(pDialog, pTopics, pCurrentRoute, fOnTopicSelect, fOnRouteTypeSelect)
|
|
1463
|
+
{
|
|
1464
|
+
// Topic selection
|
|
1465
|
+
let tmpTopicOptions = pDialog.querySelectorAll('.pict-inline-doc-tm-bind-topic-option');
|
|
1466
|
+
for (let i = 0; i < tmpTopicOptions.length; i++)
|
|
1467
|
+
{
|
|
1468
|
+
tmpTopicOptions[i].addEventListener('click', () =>
|
|
1469
|
+
{
|
|
1470
|
+
// Deselect all
|
|
1471
|
+
for (let j = 0; j < tmpTopicOptions.length; j++)
|
|
1472
|
+
{
|
|
1473
|
+
tmpTopicOptions[j].classList.remove('selected');
|
|
1474
|
+
}
|
|
1475
|
+
tmpTopicOptions[i].classList.add('selected');
|
|
1476
|
+
fOnTopicSelect(tmpTopicOptions[i].getAttribute('data-topic-code'));
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// Route type selection
|
|
1481
|
+
let tmpRouteTypeBtns = pDialog.querySelectorAll('.pict-inline-doc-tm-bind-route-type-btn');
|
|
1482
|
+
for (let i = 0; i < tmpRouteTypeBtns.length; i++)
|
|
1483
|
+
{
|
|
1484
|
+
tmpRouteTypeBtns[i].addEventListener('click', () =>
|
|
1485
|
+
{
|
|
1486
|
+
for (let j = 0; j < tmpRouteTypeBtns.length; j++)
|
|
1487
|
+
{
|
|
1488
|
+
tmpRouteTypeBtns[j].classList.remove('selected');
|
|
1489
|
+
}
|
|
1490
|
+
tmpRouteTypeBtns[i].classList.add('selected');
|
|
1491
|
+
fOnRouteTypeSelect(tmpRouteTypeBtns[i].getAttribute('data-route-type'));
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// -- Sidebar Picker --
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Show a sidebar document picker modal.
|
|
1500
|
+
*
|
|
1501
|
+
* @param {Function} fOnSelect - Callback receiving the selected path (or null)
|
|
1502
|
+
*/
|
|
1503
|
+
_showSidebarPicker(fOnSelect)
|
|
1504
|
+
{
|
|
1505
|
+
let tmpModal = this._getModal();
|
|
1506
|
+
let tmpState = this.pict.AppData.InlineDocumentation;
|
|
1507
|
+
|
|
1508
|
+
if (!tmpModal)
|
|
1509
|
+
{
|
|
1510
|
+
if (typeof fOnSelect === 'function') { fOnSelect(null); }
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
let tmpGroups = (tmpState && tmpState.SidebarGroups) ? tmpState.SidebarGroups : [];
|
|
1515
|
+
let tmpContent = this._buildSidebarPickerHTML(tmpGroups);
|
|
1516
|
+
|
|
1517
|
+
tmpModal.show(
|
|
1518
|
+
{
|
|
1519
|
+
title: 'Select Document',
|
|
1520
|
+
content: tmpContent,
|
|
1521
|
+
closeable: true,
|
|
1522
|
+
width: '400px',
|
|
1523
|
+
buttons:
|
|
1524
|
+
[
|
|
1525
|
+
{ Hash: 'cancel', Label: 'Cancel' }
|
|
1526
|
+
],
|
|
1527
|
+
onOpen: (pDialog) =>
|
|
1528
|
+
{
|
|
1529
|
+
let tmpItems = pDialog.querySelectorAll('.pict-inline-doc-tm-sidebar-item');
|
|
1530
|
+
for (let i = 0; i < tmpItems.length; i++)
|
|
1531
|
+
{
|
|
1532
|
+
tmpItems[i].addEventListener('click', () =>
|
|
1533
|
+
{
|
|
1534
|
+
let tmpPath = tmpItems[i].getAttribute('data-path');
|
|
1535
|
+
if (tmpModal.dismissModals)
|
|
1536
|
+
{
|
|
1537
|
+
tmpModal.dismissModals();
|
|
1538
|
+
}
|
|
1539
|
+
if (typeof fOnSelect === 'function') { fOnSelect(tmpPath); }
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}).then((pResult) =>
|
|
1544
|
+
{
|
|
1545
|
+
if (pResult === 'cancel' || pResult === null)
|
|
1546
|
+
{
|
|
1547
|
+
if (typeof fOnSelect === 'function') { fOnSelect(null); }
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* Build the HTML for the sidebar document picker.
|
|
1554
|
+
*
|
|
1555
|
+
* @param {Array} pGroups - SidebarGroups array
|
|
1556
|
+
* @returns {string} HTML content
|
|
1557
|
+
*/
|
|
1558
|
+
_buildSidebarPickerHTML(pGroups)
|
|
1559
|
+
{
|
|
1560
|
+
let tmpHTML = '<div class="pict-inline-doc-tm-sidebar-list">';
|
|
1561
|
+
let tmpHasItems = false;
|
|
1562
|
+
|
|
1563
|
+
for (let i = 0; i < pGroups.length; i++)
|
|
1564
|
+
{
|
|
1565
|
+
let tmpGroup = pGroups[i];
|
|
1566
|
+
|
|
1567
|
+
if (tmpGroup.Path)
|
|
1568
|
+
{
|
|
1569
|
+
tmpHasItems = true;
|
|
1570
|
+
tmpHTML += '<div class="pict-inline-doc-tm-sidebar-item" data-path="' + this._escapeHTML(tmpGroup.Path) + '">';
|
|
1571
|
+
tmpHTML += this._escapeHTML(tmpGroup.Name);
|
|
1572
|
+
tmpHTML += '<span class="path">' + this._escapeHTML(tmpGroup.Path) + '</span>';
|
|
1573
|
+
tmpHTML += '</div>';
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
let tmpItems = tmpGroup.Items || [];
|
|
1577
|
+
for (let j = 0; j < tmpItems.length; j++)
|
|
1578
|
+
{
|
|
1579
|
+
if (tmpItems[j].Path)
|
|
1580
|
+
{
|
|
1581
|
+
tmpHasItems = true;
|
|
1582
|
+
tmpHTML += '<div class="pict-inline-doc-tm-sidebar-item" data-path="' + this._escapeHTML(tmpItems[j].Path) + '">';
|
|
1583
|
+
tmpHTML += this._escapeHTML(tmpItems[j].Name);
|
|
1584
|
+
tmpHTML += '<span class="path">' + this._escapeHTML(tmpItems[j].Path) + '</span>';
|
|
1585
|
+
tmpHTML += '</div>';
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (!tmpHasItems)
|
|
1591
|
+
{
|
|
1592
|
+
tmpHTML += '<div class="pict-inline-doc-tm-empty">No sidebar documents found.</div>';
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
tmpHTML += '</div>';
|
|
1596
|
+
return tmpHTML;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// -- Utilities --
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* Escape HTML special characters.
|
|
1603
|
+
*
|
|
1604
|
+
* @param {string} pText - Text to escape
|
|
1605
|
+
* @returns {string} Escaped text
|
|
1606
|
+
*/
|
|
1607
|
+
_escapeHTML(pText)
|
|
1608
|
+
{
|
|
1609
|
+
if (!pText)
|
|
1610
|
+
{
|
|
1611
|
+
return '';
|
|
1612
|
+
}
|
|
1613
|
+
return pText
|
|
1614
|
+
.replace(/&/g, '&')
|
|
1615
|
+
.replace(/</g, '<')
|
|
1616
|
+
.replace(/>/g, '>')
|
|
1617
|
+
.replace(/"/g, '"');
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
module.exports = InlineDocumentationTopicManagerView;
|
|
1622
|
+
|
|
1623
|
+
module.exports.default_configuration = _ViewConfiguration;
|