pict-section-inlinedocumentation 0.0.4 → 0.0.5

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