pict-section-inlinedocumentation 0.0.4 → 0.0.7

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.
@@ -11,32 +11,100 @@ const _ViewConfiguration =
11
11
 
12
12
  CSS: /*css*/`
13
13
  .pict-inline-doc-nav {
14
- padding: 1em 0;
14
+ display: flex;
15
+ flex-direction: column;
16
+ height: 100%;
17
+ }
18
+ .pict-inline-doc-nav-collapsed-header {
19
+ display: flex;
20
+ align-items: center;
21
+ padding: 0.5em 0.8em;
22
+ cursor: pointer;
23
+ border-bottom: 1px solid var(--theme-color-border-light, #EAE3D8);
24
+ background: var(--theme-color-background-secondary, #F7F5F0);
25
+ user-select: none;
26
+ }
27
+ .pict-inline-doc-nav-collapsed-header:hover {
28
+ background: var(--theme-color-background-tertiary, #EDE8DF);
29
+ }
30
+ .pict-inline-doc-nav-chevron {
31
+ font-size: 0.6em;
32
+ transition: transform 0.2s ease;
33
+ color: var(--theme-color-text-muted, #8A7F72);
34
+ display: inline-flex;
35
+ align-items: center;
36
+ margin-right: 0.5em;
37
+ }
38
+ .pict-inline-doc-nav-chevron.expanded {
39
+ transform: rotate(90deg);
40
+ }
41
+ .pict-inline-doc-nav-current-title {
42
+ font-size: 0.9em;
43
+ font-weight: 500;
44
+ color: var(--theme-color-text-primary, #3D3229);
45
+ overflow: hidden;
46
+ text-overflow: ellipsis;
47
+ white-space: nowrap;
48
+ flex: 1;
49
+ }
50
+ .pict-inline-doc-nav-outline {
51
+ display: none;
52
+ overflow-y: auto;
53
+ }
54
+ .pict-inline-doc-nav-outline.expanded {
55
+ display: block;
56
+ }
57
+ .pict-inline-doc-nav-filter {
58
+ padding: 0.3em 0.6em;
59
+ border-bottom: 1px solid var(--theme-color-border-light, #EAE3D8);
60
+ }
61
+ .pict-inline-doc-nav-filter input {
62
+ width: 100%;
63
+ box-sizing: border-box;
64
+ padding: 0.3em 0.5em;
65
+ border: 1px solid var(--theme-color-border-default, #DDD6CA);
66
+ border-radius: 3px;
67
+ font-size: 0.85em;
68
+ outline: none;
69
+ }
70
+ .pict-inline-doc-nav-filter input:focus {
71
+ border-color: var(--theme-color-brand-primary, #2E7D74);
15
72
  }
16
73
  .pict-inline-doc-nav-group {
17
- margin-bottom: 0.5em;
74
+ margin-bottom: 0;
18
75
  }
19
76
  .pict-inline-doc-nav-group-header {
20
77
  display: flex;
21
78
  align-items: center;
22
- padding: 0.4em 1em;
79
+ padding: 0.4em 0.8em;
23
80
  font-weight: 600;
24
- font-size: 0.85em;
25
- color: #5E5549;
81
+ font-size: 0.7em;
82
+ color: var(--theme-color-text-secondary, #5E5549);
26
83
  text-transform: uppercase;
27
84
  letter-spacing: 0.03em;
28
85
  cursor: pointer;
29
86
  user-select: none;
30
87
  }
31
88
  .pict-inline-doc-nav-group-header:hover {
32
- color: #3D3229;
89
+ color: var(--theme-color-text-primary, #3D3229);
90
+ background: var(--theme-color-background-tertiary, #F0ECE4);
33
91
  }
34
92
  .pict-inline-doc-nav-group-toggle {
35
- margin-right: 0.4em;
36
- font-size: 0.7em;
93
+ margin-right: 0.35em;
94
+ display: inline-flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ width: 0.85em;
98
+ height: 0.85em;
99
+ color: currentColor;
37
100
  transition: transform 0.15s ease;
38
101
  }
39
- .pict-inline-doc-nav-group.collapsed .pict-inline-doc-nav-group-toggle {
102
+ .pict-inline-doc-nav-group-toggle svg {
103
+ width: 100%;
104
+ height: 100%;
105
+ display: block;
106
+ }
107
+ .pict-inline-doc-nav-group-toggle.collapsed {
40
108
  transform: rotate(-90deg);
41
109
  }
42
110
  .pict-inline-doc-nav-group.collapsed .pict-inline-doc-nav-group-items {
@@ -44,30 +112,110 @@ const _ViewConfiguration =
44
112
  }
45
113
  .pict-inline-doc-nav-item {
46
114
  display: block;
47
- padding: 0.3em 1em 0.3em 1.8em;
48
- color: #5E5549;
115
+ padding: 0.25em 0.8em 0.25em 1.6em;
116
+ color: var(--theme-color-text-secondary, #5E5549);
49
117
  text-decoration: none;
50
- font-size: 0.9em;
118
+ font-size: 0.85em;
51
119
  cursor: pointer;
52
120
  border-left: 3px solid transparent;
53
121
  transition: background 0.1s ease, border-color 0.1s ease;
54
122
  }
55
123
  .pict-inline-doc-nav-item:hover {
56
- background: #EDE8DF;
57
- color: #3D3229;
124
+ background: var(--theme-color-background-tertiary, #EDE8DF);
58
125
  }
59
126
  .pict-inline-doc-nav-item.active {
60
127
  background: #E8E3D8;
61
- color: #2E7D74;
62
- border-left-color: #2E7D74;
128
+ color: var(--theme-color-brand-primary, #2E7D74);
129
+ border-left-color: var(--theme-color-brand-primary, #2E7D74);
63
130
  font-weight: 500;
64
131
  }
132
+ .pict-inline-doc-nav-heading {
133
+ display: block;
134
+ padding: 0.15em 0.8em 0.15em 2.4em;
135
+ color: var(--theme-color-text-muted, #8A7F72);
136
+ font-size: 0.78em;
137
+ cursor: pointer;
138
+ border-left: 3px solid transparent;
139
+ transition: background 0.1s ease, color 0.1s ease;
140
+ }
141
+ .pict-inline-doc-nav-heading:hover {
142
+ background: var(--theme-color-background-tertiary, #EDE8DF);
143
+ color: var(--theme-color-text-secondary, #5E5549);
144
+ }
145
+ .pict-inline-doc-nav-heading.h3 {
146
+ padding-left: 3.2em;
147
+ font-size: 0.72em;
148
+ }
149
+ /* Search icon in collapsed header */
150
+ .pict-inline-doc-nav-search-icon {
151
+ display: inline-flex;
152
+ align-items: center;
153
+ color: var(--theme-color-text-muted, #8A7F72);
154
+ opacity: 0.5;
155
+ transition: opacity 0.2s;
156
+ flex-shrink: 0;
157
+ margin-left: 0.3em;
158
+ }
159
+ .pict-inline-doc-nav-search-icon:hover {
160
+ opacity: 1;
161
+ color: var(--theme-color-brand-primary, #2E7D74);
162
+ }
163
+ /* Search results section */
164
+ .pict-inline-doc-nav-search-results {
165
+ border-bottom: 1px solid var(--theme-color-border-light, #EAE3D8);
166
+ padding: 0.3em 0;
167
+ }
168
+ .pict-inline-doc-nav-search-status {
169
+ padding: 0.2em 0.8em;
170
+ font-size: 0.7em;
171
+ color: var(--theme-color-text-muted, #8A7F72);
172
+ text-transform: uppercase;
173
+ letter-spacing: 0.03em;
174
+ }
175
+ .pict-inline-doc-nav-search-result {
176
+ display: flex;
177
+ align-items: baseline;
178
+ padding: 0.25em 0.8em 0.25em 1.2em;
179
+ cursor: pointer;
180
+ font-size: 0.82em;
181
+ color: var(--theme-color-text-primary, #3D3229);
182
+ text-decoration: none;
183
+ transition: background 0.1s ease;
184
+ gap: 0.5em;
185
+ }
186
+ .pict-inline-doc-nav-search-result:hover {
187
+ background: var(--theme-color-background-tertiary, #EDE8DF);
188
+ }
189
+ .pict-inline-doc-nav-search-result-title {
190
+ flex: 1;
191
+ overflow: hidden;
192
+ text-overflow: ellipsis;
193
+ white-space: nowrap;
194
+ }
195
+ .pict-inline-doc-nav-search-result-group {
196
+ font-size: 0.75em;
197
+ color: var(--theme-color-text-muted, #8A7F72);
198
+ white-space: nowrap;
199
+ }
200
+ /* External link indicator */
201
+ .pict-inline-doc-nav-item-external {
202
+ color: var(--theme-color-text-muted, #8A7F72);
203
+ }
204
+ .pict-inline-doc-nav-item-external:hover {
205
+ color: var(--theme-color-brand-primary, #2E7D74);
206
+ }
207
+ .pict-inline-doc-nav-external-icon {
208
+ display: inline;
209
+ margin-left: 0.3em;
210
+ opacity: 0.5;
211
+ vertical-align: -0.05em;
212
+ }
65
213
  .pict-inline-doc-nav-topic-badge {
66
214
  display: inline-block;
67
215
  margin: 0.5em 1em;
68
216
  padding: 0.3em 0.7em;
69
217
  background: #2E7D74;
70
- color: #fff;
218
+ color: var(--theme-color-background-panel, #fff);
71
219
  border-radius: 4px;
72
220
  font-size: 0.8em;
73
221
  font-weight: 500;
@@ -85,7 +233,7 @@ const _ViewConfiguration =
85
233
  align-items: center;
86
234
  gap: 0.3em;
87
235
  padding: 0.3em 1em;
88
- border-bottom: 1px solid #EAE3D8;
236
+ border-bottom: 1px solid var(--theme-color-border-light, #EAE3D8);
89
237
  }
90
238
  .pict-inline-doc-nav-toolbar-btn {
91
239
  display: inline-flex;
@@ -93,29 +241,29 @@ const _ViewConfiguration =
93
241
  justify-content: center;
94
242
  width: 28px;
95
243
  height: 28px;
96
- border: 1px solid #DDD6CA;
244
+ border: 1px solid var(--theme-color-border-default, #DDD6CA);
97
245
  border-radius: 3px;
98
- background: #fff;
99
- color: #5E5549;
246
+ background: var(--theme-color-background-panel, #fff);
247
+ color: var(--theme-color-text-secondary, #5E5549);
100
248
  font-size: 0.9em;
101
249
  cursor: pointer;
102
250
  transition: background 0.1s, border-color 0.1s;
103
251
  }
104
252
  .pict-inline-doc-nav-toolbar-btn:hover {
105
- background: #F0ECE4;
106
- border-color: #C4BDB3;
253
+ background: var(--theme-color-background-tertiary, #F0ECE4);
254
+ border-color: var(--theme-color-border-default, #C4BDB3);
107
255
  }
108
256
  .pict-inline-doc-nav-toolbar-btn.accent {
109
- border-color: #2E7D74;
110
- color: #2E7D74;
257
+ border-color: var(--theme-color-brand-primary, #2E7D74);
258
+ color: var(--theme-color-brand-primary, #2E7D74);
111
259
  }
112
260
  .pict-inline-doc-nav-toolbar-btn.accent:hover {
113
261
  background: #F0F9F7;
114
262
  }
115
263
  .pict-inline-doc-nav-toolbar-btn.active {
116
264
  background: #2E7D74;
117
- color: #fff;
118
- border-color: #2E7D74;
265
+ color: var(--theme-color-background-panel, #fff);
266
+ border-color: var(--theme-color-brand-primary, #2E7D74);
119
267
  }
120
268
  .pict-inline-doc-nav-toolbar-btn.active:hover {
121
269
  background: #266D65;
@@ -123,57 +271,6 @@ const _ViewConfiguration =
123
271
  .pict-inline-doc-nav-toolbar-spacer {
124
272
  flex: 1;
125
273
  }
126
- /* Compact (horizontal) nav mode */
127
- .pict-inline-doc-compact .pict-inline-doc-nav {
128
- display: flex;
129
- flex-wrap: wrap;
130
- align-items: center;
131
- padding: 0.4em 0.5em;
132
- gap: 0.2em 0;
133
- }
134
- .pict-inline-doc-compact .pict-inline-doc-nav-group {
135
- margin-bottom: 0;
136
- display: flex;
137
- align-items: center;
138
- }
139
- .pict-inline-doc-compact .pict-inline-doc-nav-group-header {
140
- padding: 0.25em 0.5em;
141
- font-size: 0.75em;
142
- }
143
- .pict-inline-doc-compact .pict-inline-doc-nav-group-toggle {
144
- display: none;
145
- }
146
- .pict-inline-doc-compact .pict-inline-doc-nav-group-items {
147
- display: flex !important;
148
- flex-wrap: wrap;
149
- gap: 0;
150
- }
151
- .pict-inline-doc-compact .pict-inline-doc-nav-item {
152
- padding: 0.25em 0.6em;
153
- font-size: 0.8em;
154
- border-left: none;
155
- border-bottom: 2px solid transparent;
156
- white-space: nowrap;
157
- }
158
- .pict-inline-doc-compact .pict-inline-doc-nav-item.active {
159
- border-left: none;
160
- border-bottom-color: #2E7D74;
161
- }
162
- .pict-inline-doc-compact .pict-inline-doc-nav-topic-badge {
163
- margin: 0 0.5em;
164
- padding: 0.2em 0.5em;
165
- font-size: 0.75em;
166
- }
167
- .pict-inline-doc-compact .pict-inline-doc-nav-toolbar {
168
- padding: 0.2em 0.5em;
169
- border-bottom: none;
170
- border-right: 1px solid #EAE3D8;
171
- }
172
- .pict-inline-doc-compact .pict-inline-doc-nav-toolbar-btn {
173
- width: 24px;
174
- height: 24px;
175
- font-size: 0.8em;
176
- }
177
274
  `,
178
275
 
179
276
  Templates:
@@ -230,68 +327,234 @@ class InlineDocumentationNavView extends libPictView
230
327
  return;
231
328
  }
232
329
 
233
- let tmpHTML = '';
330
+ let tmpProvider = this.pict.providers['Pict-InlineDocumentation'];
331
+ let tmpHeadings = [];
332
+ if (tmpProvider && typeof tmpProvider._extractHeadings === 'function')
333
+ {
334
+ tmpHeadings = tmpProvider._extractHeadings();
335
+ }
336
+
234
337
  let tmpCurrentPath = tmpState.CurrentPath || '';
235
- let tmpActiveTopic = tmpState.Topic;
236
- let tmpTopicDef = null;
237
- let tmpTopicDocuments = null;
338
+ let tmpIsCollapsed = tmpState.NavCollapsed !== false;
339
+ let tmpFilterText = tmpState.NavFilterText || '';
340
+ let tmpCurrentDocName = this._resolveCurrentDocName(tmpState, tmpCurrentPath);
341
+
342
+ let tmpHTML = '';
343
+
344
+ let tmpSearchQuery = tmpState.SearchQuery || '';
345
+ let tmpSearchResults = tmpState.SearchResults || [];
346
+
347
+ // 1. Collapsed header with search icon
348
+ let tmpChevronClass = 'pict-inline-doc-nav-chevron' + (tmpIsCollapsed ? '' : ' expanded');
349
+ tmpHTML += '<div class="pict-inline-doc-nav-collapsed-header">';
350
+ tmpHTML += '<span class="' + tmpChevronClass + '" id="InlineDoc-Nav-CollapseToggle">';
351
+ tmpHTML += '<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6,3 11,8 6,13"/></svg>';
352
+ tmpHTML += '</span>';
353
+ tmpHTML += '<span class="pict-inline-doc-nav-current-title" id="InlineDoc-Nav-TitleToggle">' + this._escapeHTML(tmpCurrentDocName) + '</span>';
354
+ tmpHTML += '<span class="pict-inline-doc-nav-search-icon" id="InlineDoc-Nav-SearchBtn" title="Search documentation">';
355
+ tmpHTML += '<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="7" cy="7" r="4.5"/><line x1="10.5" y1="10.5" x2="14" y2="14"/></svg>';
356
+ tmpHTML += '</span>';
357
+ tmpHTML += '</div>';
358
+
359
+ // 2. Outline body
360
+ let tmpOutlineClass = 'pict-inline-doc-nav-outline' + (tmpIsCollapsed ? '' : ' expanded');
361
+ tmpHTML += '<div class="' + tmpOutlineClass + '" id="InlineDoc-Nav-Outline">';
362
+
363
+ // Search / filter input
364
+ let tmpPlaceholder = tmpState.SearchIndexLoaded ? 'Search documentation...' : 'Filter...';
365
+ tmpHTML += '<div class="pict-inline-doc-nav-filter">';
366
+ tmpHTML += '<input type="text" id="InlineDoc-Nav-FilterInput" placeholder="' + tmpPlaceholder + '" value="' + this._escapeHTML(tmpSearchQuery || tmpFilterText) + '" />';
367
+ tmpHTML += '</div>';
368
+
369
+ // Search results (when full-text search is active)
370
+ if (tmpSearchResults.length > 0 && tmpSearchQuery)
371
+ {
372
+ tmpHTML += '<div class="pict-inline-doc-nav-search-results">';
373
+ tmpHTML += '<div class="pict-inline-doc-nav-search-status">' + tmpSearchResults.length + ' result' + (tmpSearchResults.length !== 1 ? 's' : '') + '</div>';
374
+ for (let i = 0; i < tmpSearchResults.length && i < 15; i++)
375
+ {
376
+ let tmpResult = tmpSearchResults[i];
377
+ tmpHTML += '<a class="pict-inline-doc-nav-search-result" data-search-path="' + this._escapeHTML(tmpResult.DocPath) + '">';
378
+ tmpHTML += '<span class="pict-inline-doc-nav-search-result-title">' + this._escapeHTML(tmpResult.Title) + '</span>';
379
+ if (tmpResult.Group)
380
+ {
381
+ tmpHTML += '<span class="pict-inline-doc-nav-search-result-group">' + this._escapeHTML(tmpResult.Group) + '</span>';
382
+ }
383
+ tmpHTML += '</a>';
384
+ }
385
+ tmpHTML += '</div>';
386
+ }
387
+
388
+ // Topic badge
389
+ tmpHTML += this._renderTopicBadge(tmpState);
390
+
391
+ // Toolbar
392
+ tmpHTML += this._renderToolbar(tmpState);
393
+
394
+ // Group tree
395
+ tmpHTML += this._renderGroupTree(tmpState, tmpCurrentPath, tmpHeadings, tmpFilterText);
396
+
397
+ tmpHTML += '</div>';
398
+
399
+ tmpContainer.innerHTML = tmpHTML;
400
+
401
+ // Wire up click handlers
402
+ this._wireClickHandlers(tmpContainer);
403
+ }
404
+
405
+ /**
406
+ * Resolve the display name for the currently loaded document.
407
+ *
408
+ * Searches SidebarGroups for a matching item name; falls back to the path.
409
+ *
410
+ * @param {object} pState - The InlineDocumentation state
411
+ * @param {string} pCurrentPath - The current document path
412
+ * @returns {string} The resolved document name
413
+ */
414
+ _resolveCurrentDocName(pState, pCurrentPath)
415
+ {
416
+ if (!pCurrentPath)
417
+ {
418
+ return 'Documentation';
419
+ }
420
+
421
+ let tmpGroups = pState.SidebarGroups || [];
422
+
423
+ for (let i = 0; i < tmpGroups.length; i++)
424
+ {
425
+ let tmpGroup = tmpGroups[i];
426
+
427
+ // Check if the group itself matches
428
+ if (tmpGroup.Path && tmpGroup.Path === pCurrentPath)
429
+ {
430
+ return tmpGroup.Name || pCurrentPath;
431
+ }
432
+
433
+ let tmpItems = tmpGroup.Items || [];
434
+ for (let j = 0; j < tmpItems.length; j++)
435
+ {
436
+ if (tmpItems[j].Path === pCurrentPath)
437
+ {
438
+ return tmpItems[j].Name || pCurrentPath;
439
+ }
440
+ }
441
+ }
442
+
443
+ return pCurrentPath;
444
+ }
445
+
446
+ /**
447
+ * Render the topic badge HTML if a topic is active.
448
+ *
449
+ * @param {object} pState - The InlineDocumentation state
450
+ * @returns {string} HTML string for the topic badge, or empty string
451
+ */
452
+ _renderTopicBadge(pState)
453
+ {
454
+ let tmpActiveTopic = pState.Topic;
455
+
456
+ if (!tmpActiveTopic || !pState.Topics || !pState.Topics[tmpActiveTopic])
457
+ {
458
+ return '';
459
+ }
460
+
461
+ let tmpTopicDef = pState.Topics[tmpActiveTopic];
462
+ let tmpTopicName = tmpTopicDef.TopicTitle || tmpTopicDef.Name || tmpActiveTopic;
463
+
464
+ let tmpHTML = '<div class="pict-inline-doc-nav-topic-badge">'
465
+ + this._escapeHTML(tmpTopicName)
466
+ + '<span class="pict-inline-doc-nav-topic-clear" id="InlineDoc-Nav-ClearTopic">'
467
+ + '<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">'
468
+ + '<line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/>'
469
+ + '</svg></span>'
470
+ + '</div>';
471
+
472
+ return tmpHTML;
473
+ }
238
474
 
239
- // If a topic is active, show a badge and filter documents.
240
- // Supports both formats:
241
- // pict_documentation_topics.json: { TopicCode, TopicTitle, TopicHelpFilePath }
242
- // legacy: { Name, Documents: [] }
243
- if (tmpActiveTopic && tmpState.Topics && tmpState.Topics[tmpActiveTopic])
475
+ /**
476
+ * Render the topic management toolbar HTML.
477
+ *
478
+ * @param {object} pState - The InlineDocumentation state
479
+ * @returns {string} HTML string for the toolbar, or empty string
480
+ */
481
+ _renderToolbar(pState)
482
+ {
483
+ if (!pState.TopicManagerEnabled)
244
484
  {
245
- tmpTopicDef = tmpState.Topics[tmpActiveTopic];
485
+ return '';
486
+ }
487
+
488
+ let tmpHTML = '<div class="pict-inline-doc-nav-toolbar">';
489
+ tmpHTML += '<button class="pict-inline-doc-nav-toolbar-btn" id="InlineDoc-Nav-ManageTopics" title="Manage Topics">'
490
+ + '<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">'
491
+ + '<circle cx="8" cy="8" r="2.5"/>'
492
+ + '<path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41"/>'
493
+ + '</svg></button>';
494
+
495
+ if (pState.CurrentRoute)
496
+ {
497
+ tmpHTML += '<button class="pict-inline-doc-nav-toolbar-btn accent" id="InlineDoc-Nav-BindTopic" title="Bind topic to current route">'
498
+ + '<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">'
499
+ + '<path d="M6.5 9.5a3.5 3.5 0 005 0l2-2a3.5 3.5 0 00-5-5l-1 1"/>'
500
+ + '<path d="M9.5 6.5a3.5 3.5 0 00-5 0l-2 2a3.5 3.5 0 005 5l1-1"/>'
501
+ + '</svg></button>';
502
+ }
246
503
 
247
- // Determine the display name (TopicTitle or Name)
248
- let tmpTopicName = tmpTopicDef.TopicTitle || tmpTopicDef.Name || tmpActiveTopic;
504
+ let tmpTooltipEditActive = pState.TooltipEditMode ? ' active' : '';
505
+ tmpHTML += '<button class="pict-inline-doc-nav-toolbar-btn' + tmpTooltipEditActive + '" id="InlineDoc-Nav-TooltipEditMode" title="Toggle tooltip edit mode">'
506
+ + '<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">'
507
+ + '<path d="M14 10a1.5 1.5 0 01-1.5 1.5H4l-3 3V3A1.5 1.5 0 012.5 1.5h10A1.5 1.5 0 0114 3z"/>'
508
+ + '</svg></button>';
509
+
510
+ tmpHTML += '<span class="pict-inline-doc-nav-toolbar-spacer"></span>';
511
+ tmpHTML += '</div>';
512
+
513
+ return tmpHTML;
514
+ }
515
+
516
+ /**
517
+ * Build the group/item/heading tree HTML.
518
+ *
519
+ * @param {object} pState - The InlineDocumentation state
520
+ * @param {string} pCurrentPath - The current document path
521
+ * @param {Array} pHeadings - Array of { Text, Slug, Level } from _extractHeadings
522
+ * @param {string} pFilterText - The current filter text
523
+ * @returns {string} HTML string for the group tree
524
+ */
525
+ _renderGroupTree(pState, pCurrentPath, pHeadings, pFilterText)
526
+ {
527
+ let tmpHTML = '';
528
+ let tmpGroups = pState.SidebarGroups || [];
529
+ let tmpActiveTopic = pState.Topic;
530
+ let tmpTopicDocuments = null;
531
+ let tmpFilterLower = (pFilterText || '').toLowerCase();
532
+
533
+ // Resolve topic document filter
534
+ if (tmpActiveTopic && pState.Topics && pState.Topics[tmpActiveTopic])
535
+ {
536
+ let tmpTopicDef = pState.Topics[tmpActiveTopic];
249
537
 
250
- // Determine matching documents
251
538
  if (tmpTopicDef.TopicHelpFilePath)
252
539
  {
253
- // pict_documentation_topics.json format — single file per topic
254
540
  tmpTopicDocuments = [tmpTopicDef.TopicHelpFilePath];
255
541
  }
256
542
  else if (tmpTopicDef.Documents)
257
543
  {
258
- // Legacy format — array of document paths
259
544
  tmpTopicDocuments = tmpTopicDef.Documents;
260
545
  }
261
546
  else
262
547
  {
263
548
  tmpTopicDocuments = [];
264
549
  }
265
-
266
- tmpHTML += '<div class="pict-inline-doc-nav-topic-badge">'
267
- + this._escapeHTML(tmpTopicName)
268
- + '<span class="pict-inline-doc-nav-topic-clear" id="InlineDoc-Nav-ClearTopic"><svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg></span>'
269
- + '</div>';
270
- }
271
-
272
- // Topic management toolbar
273
- if (tmpState.TopicManagerEnabled)
274
- {
275
- tmpHTML += '<div class="pict-inline-doc-nav-toolbar">';
276
- tmpHTML += '<button class="pict-inline-doc-nav-toolbar-btn" id="InlineDoc-Nav-ManageTopics" title="Manage Topics"><svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41"/></svg></button>';
277
- if (tmpState.CurrentRoute)
278
- {
279
- tmpHTML += '<button class="pict-inline-doc-nav-toolbar-btn accent" id="InlineDoc-Nav-BindTopic" title="Bind topic to current route"><svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 9.5a3.5 3.5 0 005 0l2-2a3.5 3.5 0 00-5-5l-1 1"/><path d="M9.5 6.5a3.5 3.5 0 00-5 0l-2 2a3.5 3.5 0 005 5l1-1"/></svg></button>';
280
- }
281
- let tmpTooltipEditActive = tmpState.TooltipEditMode ? ' active' : '';
282
- tmpHTML += '<button class="pict-inline-doc-nav-toolbar-btn' + tmpTooltipEditActive + '" id="InlineDoc-Nav-TooltipEditMode" title="Toggle tooltip edit mode"><svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 10a1.5 1.5 0 01-1.5 1.5H4l-3 3V3A1.5 1.5 0 012.5 1.5h10A1.5 1.5 0 0114 3z"/></svg></button>';
283
- tmpHTML += '<span class="pict-inline-doc-nav-toolbar-spacer"></span>';
284
- tmpHTML += '</div>';
285
550
  }
286
551
 
287
- let tmpGroups = tmpState.SidebarGroups || [];
288
-
289
552
  for (let i = 0; i < tmpGroups.length; i++)
290
553
  {
291
554
  let tmpGroup = tmpGroups[i];
292
555
  let tmpGroupItems = tmpGroup.Items || [];
293
556
 
294
- // If topic filter is active, only include items that match
557
+ // Apply topic filter
295
558
  if (tmpTopicDocuments)
296
559
  {
297
560
  tmpGroupItems = tmpGroupItems.filter((pItem) =>
@@ -299,7 +562,6 @@ class InlineDocumentationNavView extends libPictView
299
562
  return tmpTopicDocuments.indexOf(pItem.Path) >= 0;
300
563
  });
301
564
 
302
- // Also check if the group-level path matches
303
565
  let tmpGroupMatches = tmpTopicDocuments.indexOf(tmpGroup.Path) >= 0;
304
566
 
305
567
  if (tmpGroupItems.length < 1 && !tmpGroupMatches)
@@ -308,22 +570,68 @@ class InlineDocumentationNavView extends libPictView
308
570
  }
309
571
  }
310
572
 
311
- tmpHTML += '<div class="pict-inline-doc-nav-group" data-group="' + this._escapeHTML(tmpGroup.Key) + '">';
573
+ // Apply text filter match item names AND headings of the active document
574
+ if (tmpFilterLower)
575
+ {
576
+ tmpGroupItems = tmpGroupItems.filter((pItem) =>
577
+ {
578
+ if ((pItem.Name || '').toLowerCase().indexOf(tmpFilterLower) >= 0)
579
+ {
580
+ return true;
581
+ }
582
+ // For the active document, also check heading text
583
+ if (pItem.Path === pCurrentPath && pHeadings.length > 0)
584
+ {
585
+ for (let h = 0; h < pHeadings.length; h++)
586
+ {
587
+ if ((pHeadings[h].Text || '').toLowerCase().indexOf(tmpFilterLower) >= 0)
588
+ {
589
+ return true;
590
+ }
591
+ }
592
+ }
593
+ return false;
594
+ });
595
+
596
+ let tmpGroupNameMatches = (tmpGroup.Name || '').toLowerCase().indexOf(tmpFilterLower) >= 0;
597
+
598
+ if (tmpGroupItems.length < 1 && !tmpGroupNameMatches)
599
+ {
600
+ continue;
601
+ }
602
+ }
603
+
604
+ let tmpGroupKey = tmpGroup.Key || tmpGroup.Name || ('group-' + i);
605
+ let tmpIsGroupCollapsed = pState.CollapsedGroups && pState.CollapsedGroups[tmpGroupKey];
606
+ let tmpGroupClass = 'pict-inline-doc-nav-group' + (tmpIsGroupCollapsed ? ' collapsed' : '');
607
+ let tmpToggleClass = 'pict-inline-doc-nav-group-toggle' + (tmpIsGroupCollapsed ? ' collapsed' : '');
608
+
609
+ tmpHTML += '<div class="' + tmpGroupClass + '" data-group="' + this._escapeHTML(tmpGroupKey) + '">';
312
610
  tmpHTML += '<div class="pict-inline-doc-nav-group-header">';
313
- tmpHTML += '<span class="pict-inline-doc-nav-group-toggle">&#x25BC;</span>';
611
+ tmpHTML += '<span class="' + tmpToggleClass + '" aria-hidden="true">'
612
+ + '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" '
613
+ + 'stroke-linecap="round" stroke-linejoin="round">'
614
+ + '<polyline points="3 6 8 11 13 6"/>'
615
+ + '</svg></span>';
314
616
  tmpHTML += this._escapeHTML(tmpGroup.Name);
315
617
  tmpHTML += '</div>';
316
618
 
317
619
  tmpHTML += '<div class="pict-inline-doc-nav-group-items">';
318
620
 
319
- // If the group itself has a path (it's a link), show it as the first item
621
+ // If the group itself has a path, show it as the first item
320
622
  if (tmpGroup.Path)
321
623
  {
322
- let tmpActive = (tmpCurrentPath === tmpGroup.Path) ? ' active' : '';
624
+ let tmpActive = (pCurrentPath === tmpGroup.Path) ? ' active' : '';
323
625
  tmpHTML += '<a class="pict-inline-doc-nav-item' + tmpActive
324
626
  + '" data-doc-path="' + this._escapeHTML(tmpGroup.Path) + '">'
325
627
  + this._escapeHTML(tmpGroup.Name)
326
628
  + '</a>';
629
+
630
+ // If this is the active item, render heading sub-items
631
+ if (pCurrentPath === tmpGroup.Path)
632
+ {
633
+ tmpHTML += this._renderHeadingSubItems(pHeadings, tmpFilterLower);
634
+ }
327
635
  }
328
636
 
329
637
  for (let j = 0; j < tmpGroupItems.length; j++)
@@ -335,31 +643,223 @@ class InlineDocumentationNavView extends libPictView
335
643
  continue;
336
644
  }
337
645
 
338
- let tmpActive = (tmpCurrentPath === tmpItem.Path) ? ' active' : '';
339
- tmpHTML += '<a class="pict-inline-doc-nav-item' + tmpActive
340
- + '" data-doc-path="' + this._escapeHTML(tmpItem.Path) + '">'
341
- + this._escapeHTML(tmpItem.Name)
342
- + '</a>';
646
+ if (tmpItem.External && tmpItem.ExternalURL)
647
+ {
648
+ // External link opens in a new tab
649
+ tmpHTML += '<a class="pict-inline-doc-nav-item pict-inline-doc-nav-item-external'
650
+ + '" data-external-url="' + this._escapeHTML(tmpItem.ExternalURL) + '">'
651
+ + this._escapeHTML(tmpItem.Name)
652
+ + '<svg class="pict-inline-doc-nav-external-icon" width="0.75em" height="0.75em" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4a1 1 0 01-1 1H3a1 1 0 01-1-1V5a1 1 0 011-1h4"/><polyline points="8,2 14,2 14,8"/><line x1="14" y1="2" x2="7" y2="9"/></svg>'
653
+ + '</a>';
654
+ }
655
+ else
656
+ {
657
+ let tmpActive = (pCurrentPath === tmpItem.Path) ? ' active' : '';
658
+ tmpHTML += '<a class="pict-inline-doc-nav-item' + tmpActive
659
+ + '" data-doc-path="' + this._escapeHTML(tmpItem.Path) + '">'
660
+ + this._escapeHTML(tmpItem.Name)
661
+ + '</a>';
662
+
663
+ // If this is the active item, render heading sub-items
664
+ if (pCurrentPath === tmpItem.Path)
665
+ {
666
+ tmpHTML += this._renderHeadingSubItems(pHeadings, tmpFilterLower);
667
+ }
668
+ }
343
669
  }
344
670
 
345
671
  tmpHTML += '</div>';
346
672
  tmpHTML += '</div>';
347
673
  }
348
674
 
349
- tmpContainer.innerHTML = tmpHTML;
675
+ return tmpHTML;
676
+ }
350
677
 
351
- // Wire up click handlers
352
- this._wireClickHandlers(tmpContainer);
678
+ /**
679
+ * Render heading sub-items (h2 and h3) beneath the active nav item.
680
+ *
681
+ * @param {Array} pHeadings - Array of { Text, Slug, Level }
682
+ * @param {string} pFilterText - Lowercase filter text
683
+ * @returns {string} HTML string for heading sub-items
684
+ */
685
+ _renderHeadingSubItems(pHeadings, pFilterText)
686
+ {
687
+ if (!pHeadings || pHeadings.length < 1)
688
+ {
689
+ return '';
690
+ }
691
+
692
+ let tmpHTML = '';
693
+
694
+ for (let i = 0; i < pHeadings.length; i++)
695
+ {
696
+ let tmpHeading = pHeadings[i];
697
+ let tmpText = tmpHeading.Text || '';
698
+
699
+ // Apply filter
700
+ if (pFilterText && tmpText.toLowerCase().indexOf(pFilterText) < 0)
701
+ {
702
+ continue;
703
+ }
704
+
705
+ let tmpLevelClass = (tmpHeading.Level === 3) ? ' h3' : '';
706
+ tmpHTML += '<a class="pict-inline-doc-nav-heading' + tmpLevelClass
707
+ + '" data-heading-slug="' + this._escapeHTML(tmpHeading.Slug) + '">'
708
+ + this._escapeHTML(tmpText)
709
+ + '</a>';
710
+ }
711
+
712
+ return tmpHTML;
353
713
  }
354
714
 
355
715
  /**
356
- * Wire click handlers on navigation items and group headers.
716
+ * Wire click handlers on navigation items, group headers, and controls.
357
717
  *
358
718
  * @param {HTMLElement} pContainer - The nav container element
359
719
  */
360
720
  _wireClickHandlers(pContainer)
361
721
  {
362
722
  let tmpProvider = this.pict.providers['Pict-InlineDocumentation'];
723
+ let tmpState = this.pict.AppData.InlineDocumentation;
724
+
725
+ let tmpSelf = this;
726
+
727
+ // Collapse toggle (chevron)
728
+ let tmpCollapseToggle = pContainer.querySelector('#InlineDoc-Nav-CollapseToggle');
729
+ if (tmpCollapseToggle)
730
+ {
731
+ tmpCollapseToggle.addEventListener('click', () =>
732
+ {
733
+ if (tmpState)
734
+ {
735
+ tmpState.NavCollapsed = !tmpState.NavCollapsed;
736
+ }
737
+ tmpSelf._renderNavigation();
738
+ });
739
+ }
740
+
741
+ // Title click also toggles
742
+ let tmpTitleToggle = pContainer.querySelector('#InlineDoc-Nav-TitleToggle');
743
+ if (tmpTitleToggle)
744
+ {
745
+ tmpTitleToggle.addEventListener('click', () =>
746
+ {
747
+ if (tmpState)
748
+ {
749
+ tmpState.NavCollapsed = !tmpState.NavCollapsed;
750
+ }
751
+ tmpSelf._renderNavigation();
752
+ });
753
+ }
754
+
755
+ // Search icon — expands outline and focuses search input
756
+ let tmpSearchBtn = pContainer.querySelector('#InlineDoc-Nav-SearchBtn');
757
+ if (tmpSearchBtn)
758
+ {
759
+ tmpSearchBtn.addEventListener('click', (pEvent) =>
760
+ {
761
+ pEvent.stopPropagation();
762
+ if (tmpState)
763
+ {
764
+ tmpState.NavCollapsed = false;
765
+ }
766
+ tmpSelf._renderNavigation();
767
+
768
+ let tmpInput = document.getElementById('InlineDoc-Nav-FilterInput');
769
+ if (tmpInput)
770
+ {
771
+ tmpInput.focus();
772
+ }
773
+ });
774
+ }
775
+
776
+ // Search / filter input
777
+ let tmpFilterInput = pContainer.querySelector('#InlineDoc-Nav-FilterInput');
778
+ if (tmpFilterInput)
779
+ {
780
+ let tmpDebounceTimer = null;
781
+
782
+ tmpFilterInput.addEventListener('input', (pEvent) =>
783
+ {
784
+ let tmpValue = pEvent.target.value || '';
785
+
786
+ if (tmpState)
787
+ {
788
+ tmpState.NavFilterText = tmpValue;
789
+ }
790
+
791
+ // If search index is loaded, debounce full-text search
792
+ if (tmpState && tmpState.SearchIndexLoaded && tmpProvider && typeof tmpProvider.search === 'function')
793
+ {
794
+ if (tmpDebounceTimer) clearTimeout(tmpDebounceTimer);
795
+ tmpDebounceTimer = setTimeout(() =>
796
+ {
797
+ tmpState.SearchQuery = tmpValue;
798
+ tmpState.SearchResults = tmpValue.trim() ? tmpProvider.search(tmpValue) : [];
799
+ tmpSelf._renderNavigation();
800
+
801
+ let tmpNewInput = document.getElementById('InlineDoc-Nav-FilterInput');
802
+ if (tmpNewInput)
803
+ {
804
+ tmpNewInput.focus();
805
+ let tmpLen = tmpNewInput.value.length;
806
+ tmpNewInput.setSelectionRange(tmpLen, tmpLen);
807
+ }
808
+ }, 250);
809
+ }
810
+ else
811
+ {
812
+ // No search index — immediate client-side filter only
813
+ tmpSelf._renderNavigation();
814
+
815
+ let tmpNewInput = document.getElementById('InlineDoc-Nav-FilterInput');
816
+ if (tmpNewInput)
817
+ {
818
+ tmpNewInput.focus();
819
+ let tmpLen = tmpNewInput.value.length;
820
+ tmpNewInput.setSelectionRange(tmpLen, tmpLen);
821
+ }
822
+ }
823
+ });
824
+ }
825
+
826
+ // Search result clicks
827
+ let tmpSearchResults = pContainer.querySelectorAll('.pict-inline-doc-nav-search-result[data-search-path]');
828
+ for (let i = 0; i < tmpSearchResults.length; i++)
829
+ {
830
+ let tmpResult = tmpSearchResults[i];
831
+ tmpResult.addEventListener('click', (pEvent) =>
832
+ {
833
+ pEvent.preventDefault();
834
+ let tmpPath = tmpResult.getAttribute('data-search-path');
835
+ if (tmpProvider && tmpPath)
836
+ {
837
+ if (tmpState)
838
+ {
839
+ tmpState.SearchQuery = '';
840
+ tmpState.SearchResults = [];
841
+ tmpState.NavFilterText = '';
842
+ }
843
+ tmpProvider.loadDocument(tmpPath);
844
+ }
845
+ });
846
+ }
847
+
848
+ // External links — open in new tab
849
+ let tmpExternalLinks = pContainer.querySelectorAll('[data-external-url]');
850
+ for (let i = 0; i < tmpExternalLinks.length; i++)
851
+ {
852
+ let tmpExtLink = tmpExternalLinks[i];
853
+ tmpExtLink.addEventListener('click', (pEvent) =>
854
+ {
855
+ pEvent.preventDefault();
856
+ let tmpURL = tmpExtLink.getAttribute('data-external-url');
857
+ if (tmpURL)
858
+ {
859
+ window.open(tmpURL, '_blank');
860
+ }
861
+ });
862
+ }
363
863
 
364
864
  // Document links
365
865
  let tmpLinks = pContainer.querySelectorAll('.pict-inline-doc-nav-item[data-doc-path]');
@@ -372,11 +872,32 @@ class InlineDocumentationNavView extends libPictView
372
872
  let tmpPath = tmpLink.getAttribute('data-doc-path');
373
873
  if (tmpProvider && tmpPath)
374
874
  {
875
+ // Clear filter when navigating
876
+ if (tmpState)
877
+ {
878
+ tmpState.NavFilterText = '';
879
+ }
375
880
  tmpProvider.loadDocument(tmpPath);
376
881
  }
377
882
  });
378
883
  }
379
884
 
885
+ // Heading links
886
+ let tmpHeadingLinks = pContainer.querySelectorAll('.pict-inline-doc-nav-heading[data-heading-slug]');
887
+ for (let i = 0; i < tmpHeadingLinks.length; i++)
888
+ {
889
+ let tmpHeadingLink = tmpHeadingLinks[i];
890
+ tmpHeadingLink.addEventListener('click', (pEvent) =>
891
+ {
892
+ pEvent.preventDefault();
893
+ let tmpSlug = tmpHeadingLink.getAttribute('data-heading-slug');
894
+ if (tmpProvider && tmpSlug)
895
+ {
896
+ tmpProvider._scrollToAnchor(tmpSlug);
897
+ }
898
+ });
899
+ }
900
+
380
901
  // Group collapse toggle
381
902
  let tmpHeaders = pContainer.querySelectorAll('.pict-inline-doc-nav-group-header');
382
903
  for (let i = 0; i < tmpHeaders.length; i++)
@@ -388,6 +909,33 @@ class InlineDocumentationNavView extends libPictView
388
909
  if (tmpGroup)
389
910
  {
390
911
  tmpGroup.classList.toggle('collapsed');
912
+
913
+ // Persist collapse state
914
+ let tmpGroupKey = tmpGroup.getAttribute('data-group');
915
+ if (tmpState && tmpGroupKey)
916
+ {
917
+ if (!tmpState.CollapsedGroups)
918
+ {
919
+ tmpState.CollapsedGroups = {};
920
+ }
921
+ let tmpToggle = tmpGroup.querySelector('.pict-inline-doc-nav-group-toggle');
922
+ if (tmpGroup.classList.contains('collapsed'))
923
+ {
924
+ tmpState.CollapsedGroups[tmpGroupKey] = true;
925
+ if (tmpToggle)
926
+ {
927
+ tmpToggle.classList.add('collapsed');
928
+ }
929
+ }
930
+ else
931
+ {
932
+ delete tmpState.CollapsedGroups[tmpGroupKey];
933
+ if (tmpToggle)
934
+ {
935
+ tmpToggle.classList.remove('collapsed');
936
+ }
937
+ }
938
+ }
391
939
  }
392
940
  });
393
941
  }