hdoc-tools 0.62.4 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/editor/dist/assets/{index-BtxvGZHW.js → index-Dknn5g5A.js} +5 -4
  2. package/editor/dist/index.html +13 -13
  3. package/hdoc-build.js +5 -3
  4. package/hdoc-content-routes.js +136 -4
  5. package/hdoc-serve.js +26 -3
  6. package/hdoc-validate-interbook.js +8 -0
  7. package/hdoc-validate.js +92 -35
  8. package/package.json +1 -1
  9. package/ui/css/theme-default/styles/base.css +86 -21
  10. package/ui/css/theme-default/styles/components/api-doc.css +6 -3
  11. package/ui/css/theme-default/styles/components/content.css +140 -39
  12. package/ui/css/theme-default/styles/components/custom-block.css +1 -1
  13. package/ui/css/theme-default/styles/components/htl-doc.css +243 -64
  14. package/ui/css/theme-default/styles/components/htl-library.css +152 -0
  15. package/ui/css/theme-default/styles/components/htl-search.css +267 -0
  16. package/ui/css/theme-default/styles/components/sidebar.css +59 -16
  17. package/ui/css/theme-default/styles/fonts.css +17 -3
  18. package/ui/css/theme-default/styles/htldoc.layouts.css +756 -87
  19. package/ui/css/theme-default/styles/vars.css +40 -35
  20. package/ui/favicon.svg +64 -0
  21. package/ui/images/hornbill-logo-full-reversed.svg +92 -0
  22. package/ui/images/hornbill-logo-full.svg +92 -0
  23. package/ui/images/hug_library.jpg +0 -0
  24. package/ui/images/mcp-catalog.svg +9 -0
  25. package/ui/images/products/hornbill-square.svg +1 -0
  26. package/ui/index.html +1106 -342
  27. package/ui/js/bootstrap.js +89 -0
  28. package/ui/js/doc.hornbill.js +3156 -751
  29. package/ui/js/hb.vue.js +121 -0
  30. package/ui/js/highlightjs/highlight.pack.js +1513 -2
  31. package/ui/js/highlightjs/styles/vs2015-accessible.css +141 -0
  32. package/ui/js/highlightjs-badge.js +41 -58
  33. package/ui/js/mermaid.min.js +1447 -1295
  34. package/ui/js/webcomponents/hdocApprove.js +115 -0
  35. package/ui/js/highlightjs/styles/brown-paper.css +0 -64
  36. package/ui/js/highlightjs/styles/brown-papersq.png +0 -0
  37. package/ui/js/highlightjs/styles/codepen-embed.css +0 -60
  38. package/ui/js/highlightjs/styles/color-brewer.css +0 -71
  39. package/ui/js/highlightjs/styles/darcula.css +0 -77
  40. package/ui/js/highlightjs/styles/dark.css +0 -63
  41. package/ui/js/highlightjs/styles/darkula.css +0 -6
  42. package/ui/js/highlightjs/styles/default.css +0 -99
  43. package/ui/js/highlightjs/styles/dracula.css +0 -76
  44. package/ui/js/highlightjs/styles/far.css +0 -71
  45. package/ui/js/highlightjs/styles/foundation.css +0 -88
  46. package/ui/js/highlightjs/styles/github-gist.css +0 -71
  47. package/ui/js/highlightjs/styles/github-mm.css +0 -71
  48. package/ui/js/highlightjs/styles/github.css +0 -99
  49. package/ui/js/highlightjs/styles/googlecode.css +0 -89
  50. package/ui/js/highlightjs/styles/grayscale.css +0 -101
  51. package/ui/js/highlightjs/styles/idea.css +0 -97
  52. package/ui/js/highlightjs/styles/ir-black.css +0 -73
  53. package/ui/js/highlightjs/styles/kavadocs.css +0 -71
  54. package/ui/js/highlightjs/styles/kavadocsdark.css +0 -120
  55. package/ui/js/highlightjs/styles/kimbie.dark.css +0 -74
  56. package/ui/js/highlightjs/styles/kimbie.light.css +0 -74
  57. package/ui/js/highlightjs/styles/magula.css +0 -70
  58. package/ui/js/highlightjs/styles/mono-blue.css +0 -59
  59. package/ui/js/highlightjs/styles/monokai-sublime.css +0 -83
  60. package/ui/js/highlightjs/styles/monokai.css +0 -70
  61. package/ui/js/highlightjs/styles/obsidian.css +0 -88
  62. package/ui/js/highlightjs/styles/paraiso-dark.css +0 -72
  63. package/ui/js/highlightjs/styles/paraiso-light.css +0 -72
  64. package/ui/js/highlightjs/styles/railscasts.css +0 -106
  65. package/ui/js/highlightjs/styles/rainbow.css +0 -85
  66. package/ui/js/highlightjs/styles/solarized-dark.css +0 -84
  67. package/ui/js/highlightjs/styles/solarized-light.css +0 -84
  68. package/ui/js/highlightjs/styles/sunburst.css +0 -102
  69. package/ui/js/highlightjs/styles/twilight.css +0 -97
  70. package/ui/js/highlightjs/styles/vs.css +0 -68
  71. package/ui/js/highlightjs/styles/vs2015.css +0 -117
  72. package/ui/js/highlightjs/styles/xcode.css +0 -104
  73. package/ui/js/highlightjs/styles/zenburn.css +0 -80
package/ui/index.html CHANGED
@@ -1,52 +1,130 @@
1
+ <!DOCTYPE html>
1
2
  <html lang="en-US">
2
-
3
- <head>
4
-
5
3
  <head>
6
- <base href="">
7
- <script>
8
- var siteBaseLocation = window.location.protocol + '//' + window.location.host + "/";
9
- var baseTag = document.getElementsByTagName('base')[0];
10
- baseTag.href = siteBaseLocation;
11
- </script>
12
-
13
- <title>Hornbill ESP Platform Documentation | Hornbill Platform</title>
14
- <meta charset="utf-8">
15
- <meta name="viewport" content="width=device-width,initial-scale=1">
16
- <meta name="description" content="Internal documentation for the Hornbill ESP Platform">
17
-
18
-
19
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
20
-
21
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
22
- integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
23
- <script src="https://code.jquery.com/jquery-3.6.1.min.js"
24
- integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
25
- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
26
- integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
27
- crossorigin="anonymous"></script>
28
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js"
29
- integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF"
30
- crossorigin="anonymous"></script>
31
- <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
32
-
33
- <style>
34
- .hb-hidden {
35
- visibility: hidden !important;
36
- }
37
- </style>
38
- <script>
39
-
40
- class APIParamName extends HTMLElement {
41
- constructor() {
42
- super();
43
- // GS: Use this to set a role on the element for ARIO screen-reader directives
44
- this.setAttribute("role", "title");
45
- }
46
- // Element functionality written in here
47
- }
48
- customElements.define("api-param-name", APIParamName);
49
-
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <!-- Those two are FIRST, ahead of this comment and of everything else, because both are
7
+ position-sensitive and both were broken by sitting further down the file:
8
+
9
+ charset - the spec requires the encoding declaration inside the first 1024 bytes. The
10
+ inline script and stylesheet below had pushed it to byte 15013, so the browser was
11
+ guessing the encoding for the whole document and could in principle restart the parse.
12
+ Even a comment above it eats into that budget; that is why this text is here and not
13
+ above.
14
+
15
+ viewport - until this is parsed, a mobile browser lays the page out in its DEFAULT 980px
16
+ layout viewport. Anything painted before that point is laid out for 980px and then shown
17
+ zoomed into the real screen, so a position:fixed/inset:0 element centred in it appears
18
+ offset right and down by the zoom ratio, anchored top-left - and then jumps back to the
19
+ middle when the meta is finally applied. That was a real reported defect against the boot
20
+ splash on a throttled mobile connection, where the parse is slow enough for a paint to
21
+ land in that window.
22
+
23
+ Keep these two above everything. //-- SG -->
24
+
25
+ <base href="/"><!-- literal root base: the preload scanner resolves <link>/<script> hrefs
26
+ before the inline script below can rewrite it, so this must already be correct. The script
27
+ still runs - siteBaseLocation is read by js/doc.hornbill.js - it just is not load-bearing
28
+ for URL resolution any more. -->
29
+ <script>
30
+ var siteBaseLocation = window.location.protocol + '//' + window.location.host + "/";
31
+ var baseTag = document.getElementsByTagName('base')[0];
32
+ baseTag.href = siteBaseLocation;
33
+
34
+ //-- THEME, resolved here at PARSE time and nowhere else. This used to live at the top of
35
+ //-- js/bootstrap.js, which is injected on DOMContentLoaded and then has to be fetched -
36
+ //-- so the html.dark class landed a whole network round trip after the first paint and
37
+ //-- every dark-mode user got a WHITE page for the entire boot window. Must stay in this
38
+ //-- inline block, before any stylesheet or body content, or the flash comes back.
39
+ //-- bootstrap.js keeps a copy of this logic as a fallback, guarded on hbThemeResolved,
40
+ //-- for any page that loads it without this script. //-- SG
41
+ var ThemePreference = localStorage.getItem('hdocbook-theme-appearance') || '';
42
+ var hbUrlTheme = new URLSearchParams(window.location.search).get('theme');
43
+ if (hbUrlTheme === 'dark' || hbUrlTheme === 'light') ThemePreference = hbUrlTheme;
44
+ if (!ThemePreference || ThemePreference === 'auto'
45
+ ? window.matchMedia('(prefers-color-scheme: dark)').matches
46
+ : ThemePreference === 'dark')
47
+ {
48
+ ThemePreference = "dark";
49
+ document.documentElement.classList.add('dark');
50
+ }
51
+ window.hbThemeResolved = true;
52
+
53
+ //-- Start the boot-splash logo download NOW, keyed on the theme just resolved above.
54
+ //-- The splash logo is a class-driven CSS background (see .hbBootLogo) because <img src>
55
+ //-- cannot follow html.dark - but background images are only discovered once style
56
+ //-- resolves, which waits on every render-blocking stylesheet. Measured on Slow 3G: the
57
+ //-- last of 15 stylesheets finished at 13405ms and the logo was not even REQUESTED until
58
+ //-- 13608ms, so the splash painted with an empty logo box and the logo popped in after.
59
+ //-- This preload starts the fetch in parallel with the CSS instead, which is what the
60
+ //-- preload scanner used to do for the <img> - only theme-correct. //-- SG
61
+ var hbLogoPreload = document.createElement('link');
62
+ hbLogoPreload.rel = 'preload';
63
+ hbLogoPreload.as = 'image';
64
+ hbLogoPreload.href = document.documentElement.classList.contains('dark')
65
+ ? 'images/hornbill-logo-full-reversed.svg'
66
+ : 'images/hornbill-logo-full.svg';
67
+ document.head.appendChild(hbLogoPreload);
68
+
69
+ //-- ===================== NON-BLOCKING CSS =====================
70
+ //-- Every stylesheet below is loaded with media="print" and flipped to media="all" on
71
+ //-- load, which takes it off the critical path: the browser no longer withholds the
72
+ //-- first paint until all 15 have arrived. That is what lets the boot splash - which is
73
+ //-- styled purely from the inline <style> in this file and needs no external CSS at all -
74
+ //-- appear while they are still in flight.
75
+ //--
76
+ //-- The catch, and the reason for the gate below: the APP must not paint unstyled. It is
77
+ //-- hidden by .hb-hidden until revealApp(), so the fix is to make revealApp() wait for
78
+ //-- the stylesheets rather than to make the browser wait. Nothing else in the boot cares.
79
+ //--
80
+ //-- Ready is declared on whichever comes first:
81
+ //-- - every marked <link> has fired load or error, or
82
+ //-- - window load (guarantees every subresource has resolved either way), or
83
+ //-- - a 30s absolute backstop, so a stylesheet that hangs forever cannot strand the
84
+ //-- splash on screen permanently.
85
+ //-- The count cannot simply be hardcoded: onload can fire from cache before the counting
86
+ //-- script at the end of <head> has run, so the check needs both halves and runs again
87
+ //-- when the total becomes known. //-- SG
88
+ window.hbCssApplied = 0;
89
+ window.hbCssTotal = 0;
90
+ window.hbCssTotalKnown = false;
91
+ window.hbCssReady = false;
92
+ window.hbCssWaiters = [];
93
+
94
+ function hbCssMarkReady()
95
+ {
96
+ if (window.hbCssReady) return;
97
+ window.hbCssReady = true;
98
+ var waiters = window.hbCssWaiters;
99
+ window.hbCssWaiters = [];
100
+ for (var i = 0; i < waiters.length; i++) waiters[i]();
101
+ }
102
+
103
+ function hbCssCheck()
104
+ {
105
+ if (window.hbCssTotalKnown && window.hbCssApplied >= window.hbCssTotal) hbCssMarkReady();
106
+ }
107
+
108
+ //-- called from the onload/onerror attribute on every marked <link>
109
+ function hbOnCssLoad(link)
110
+ {
111
+ if (link && link.media === 'print') link.media = 'all';
112
+ window.hbCssApplied++;
113
+ hbCssCheck();
114
+ }
115
+
116
+ window.addEventListener('load', hbCssMarkReady);
117
+ setTimeout(hbCssMarkReady, 30000);
118
+
119
+
120
+ document.addEventListener("DOMContentLoaded", function (event)
121
+ {
122
+ var element = document.createElement("script");
123
+ element.src = "js/bootstrap.js";
124
+ document.body.appendChild(element);
125
+ });
126
+
127
+
50
128
  class ApiParamOptional extends HTMLElement {
51
129
  connectedCallback() {
52
130
  const text = (this.textContent || '').trim();
@@ -58,363 +136,1049 @@
58
136
  }
59
137
  customElements.define('api-param-optional', ApiParamOptional);
60
138
 
61
- var ThemePreference = localStorage.getItem('hdocbook-theme-appearance') || '';
62
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
63
- if (!ThemePreference || ThemePreference === 'auto' ? prefersDark : ThemePreference === 'dark') {
64
- ThemePreference = "dark";
65
- document.documentElement.classList.add('dark')
139
+ </script>
140
+
141
+ <style>
142
+
143
+ /* ===================== BOOT STATE =====================
144
+ Everything in this block runs before any external stylesheet is guaranteed to have
145
+ arrived, so it uses LITERAL colours, not var(--htl-*) - a var() that resolves to
146
+ nothing paints black-on-black. The literals mirror vars.css: --htl-c-white /
147
+ --htl-c-black-soft for the grounds, --htl-c-indigo / --htl-c-text-dark-2 for text.
148
+ Keep them in step by hand if the palette moves.
149
+
150
+ Paint the page ground immediately. Without this the UA white shows through until
151
+ base.css lands, which is a white flash for dark-mode users even with the theme class
152
+ now resolved at parse time. */
153
+ html { background-color: #ffffff; }
154
+ html.dark { background-color: #242424; }
155
+
156
+ #hbBootSplash {
157
+ position: fixed;
158
+ inset: 0;
159
+ z-index: 20000;
160
+ display: flex;
161
+ flex-direction: column;
162
+ align-items: center;
163
+ justify-content: center;
164
+ gap: 28px;
165
+ background-color: #ffffff;
166
+ color: #213547;
167
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
168
+ /* Deliberately NOT visible on arrival. A splash that flashes for 80ms on a warm
169
+ cache looks worse than no splash at all, so it fades in only once the boot has
170
+ demonstrably taken longer than a fast load: opacity 0 held for 400ms, then a
171
+ 200ms fade. Cached loads reveal the app inside that hold and the user never sees
172
+ this element. Doing the delay in CSS rather than a setTimeout keeps it working
173
+ even if the JS wave is what is slow.
174
+
175
+ 400ms is measured, not guessed: a warm-cache local load reveals at ~285ms, and
176
+ at the original 250ms hold that got 35ms into the fade - a faint but real ghost
177
+ at opacity 0.134. Do not lower it without re-measuring; the cost of being too
178
+ early (a flash on every fast load) is worse than the cost of being too late
179
+ (400ms more blank on a slow one, which reads as normal latency). */
180
+ opacity: 0;
181
+ animation: hbBootSplashIn 200ms ease-out 400ms forwards;
182
+ }
183
+ html.dark #hbBootSplash { background-color: #242424; color: #DEDEDE; }
184
+
185
+ @keyframes hbBootSplashIn { to { opacity: 1; } }
186
+
187
+ /* Reserved box: width/height fixed to the logo's own 2053.3 x 283.5 aspect ratio
188
+ (7.24:1), so the row does not reflow when the image lands.
189
+
190
+ A class-driven background-image, NOT an <img>. This was an <img> inside a <picture>
191
+ keyed on prefers-color-scheme, which is wrong: the effective theme is the html.dark
192
+ CLASS (localStorage or ?theme= can override the OS setting), and <img src> cannot be
193
+ driven by a class. Anyone whose OS prefers dark but who has chosen the light theme
194
+ got the reversed logo - white wordmark on a white ground, i.e. an invisible logo
195
+ next to a floating icon.
196
+ A background-image costs nothing extra: only the URL in the rule that MATCHES is
197
+ fetched, so this is still one file, and the rule is in this inline <style> in <head>
198
+ so the fetch starts at parse time just as the preload scanner would have started
199
+ it. Mirrors how .toolbar-logo-image does it in htldoc.layouts.css. //-- SG */
200
+ #hbBootSplash .hbBootLogo {
201
+ width: 260px;
202
+ height: 36px;
203
+ display: block;
204
+ background: url(images/hornbill-logo-full.svg) no-repeat center / contain;
205
+ }
206
+ html.dark #hbBootSplash .hbBootLogo {
207
+ background-image: url(images/hornbill-logo-full-reversed.svg);
66
208
  }
67
209
 
68
- function loadJS(arrFiles, callback) {
69
- // Head tag
70
- var head = document.getElementsByTagName('head')[0]
71
-
72
- // Creating script element
73
- var loadedCount = 0;
74
- for (let x = 0; x < arrFiles.length; x++) {
75
- var script = document.createElement('script');
76
- script.src = arrFiles[x];
77
- script.type = 'text/javascript';
78
- script.async = false;
79
-
80
- script.onload = function () {
81
- loadedCount++;
82
- if (loadedCount === arrFiles.length) callback();
83
- }
210
+ #hbBootSplash .hbBootLabel {
211
+ font-size: 15px;
212
+ letter-spacing: 0.08em;
213
+ text-transform: uppercase;
214
+ opacity: 0.75;
215
+ }
84
216
 
85
- // Adding script element
86
- head.append(script);
87
- }
217
+ /* LOCAL PREVIEW PATCH: amber "Preview" pill next to the toolbar's
218
+ Documentation label, so a local hdoc serve can't be mistaken for
219
+ the live site. Re-apply on re-sync from the htdocs viewer. */
220
+ .toolbar-layout .hb-preview-badge {
221
+ display: inline-block;
222
+ margin-left: 8px;
223
+ padding: 0 10px;
224
+ border-radius: 999px;
225
+ font-size: 11px;
226
+ font-weight: 700;
227
+ letter-spacing: 0.08em;
228
+ text-transform: uppercase;
229
+ line-height: 18px;
230
+ vertical-align: 2px;
231
+ color: #6b4400;
232
+ background: #ffd777;
233
+ }
234
+ html.dark .toolbar-layout .hb-preview-badge {
235
+ color: #ffe2a0;
236
+ background: #7a5200;
88
237
  }
89
238
 
90
- function loadCss(arrFiles, callback) {
91
- // Head tag
92
- var head = document.getElementsByTagName('head')[0]
93
-
94
- // Creating link element
95
- var loadedCount = 0;
96
- for (let x = 0; x < arrFiles.length; x++) {
97
- // Creating link element
98
- var style = document.createElement('link');
99
- style.href = arrFiles[x];
100
- style.type = 'text/css';
101
- style.rel = 'stylesheet';
102
- style.async = false;
103
-
104
- style.onload = function () {
105
- loadedCount++;
106
- if (loadedCount === arrFiles.length) callback();
107
- }
239
+ /* Indeterminate, because there is nothing real to measure here - the boot is a chain
240
+ of fetches of unknown length, and a fake percentage would be a lie. */
241
+ #hbBootSplash .hbBootBar {
242
+ position: relative;
243
+ width: 200px;
244
+ height: 3px;
245
+ border-radius: 3px;
246
+ overflow: hidden;
247
+ background-color: #d1d1d1;
248
+ }
249
+ html.dark #hbBootSplash .hbBootBar { background-color: #3a3a3a; }
250
+
251
+ #hbBootSplash .hbBootBar::after {
252
+ content: "";
253
+ position: absolute;
254
+ top: 0;
255
+ bottom: 0;
256
+ width: 40%;
257
+ border-radius: 3px;
258
+ background-color: #00527f;
259
+ animation: hbBootBar 1.1s ease-in-out infinite;
260
+ }
261
+ html.dark #hbBootSplash .hbBootBar::after { background-color: #7cc4f5; }
108
262
 
109
- head.append(style);
263
+ @keyframes hbBootBar {
264
+ 0% { left: -40%; }
265
+ 100% { left: 100%; }
266
+ }
110
267
 
111
- }
268
+ /* Stall message. Pure CSS on a long animation-delay rather than a setTimeout, so it
269
+ still appears when the thing that failed IS the JS - a splash that spins forever
270
+ with no explanation is worse than the blank page this replaced. */
271
+ #hbBootSplash .hbBootStalled {
272
+ max-width: 320px;
273
+ text-align: center;
274
+ font-size: 13px;
275
+ line-height: 1.5;
276
+ opacity: 0;
277
+ animation: hbBootSplashIn 400ms ease-out 20s forwards;
112
278
  }
113
279
 
280
+ @media (prefers-reduced-motion: reduce) {
281
+ /* No sliding bar and no fades - but keep the delays, so a fast load still does not
282
+ flash the splash, and keep the bar itself as a static track so the layout holds. */
283
+ #hbBootSplash { animation: hbBootSplashIn 1ms linear 400ms forwards; }
284
+ #hbBootSplash .hbBootBar::after { animation: none; left: 0; width: 100%; opacity: 0.5; }
285
+ #hbBootSplash .hbBootStalled { animation: hbBootSplashIn 1ms linear 20s forwards; }
286
+ }
114
287
 
115
- </script>
288
+ /* Removal is a class, not a node removal, so the fade can run. hbHideBootSplash()
289
+ in js/doc.hornbill.js adds this and then display:none's the element on transitionend
290
+ (with a timer fallback), because a splash left at opacity 0 over the whole viewport
291
+ would eat every click. */
292
+ #hbBootSplash.hbBootSplashGone {
293
+ opacity: 0;
294
+ animation: none;
295
+ transition: opacity 200ms ease-out;
296
+ pointer-events: none;
297
+ }
116
298
 
117
- <!-- template for nav item section -->
118
- <script type="text/x-template" id="nav-section-template">
119
- <section class="DocSidebarGroup" v-bind:class="{collapsible: asection.items && asection.items.length}">
120
- <div class="title" v-on:click="toggleNavCollapse(asection)">
121
- <div class="action">
122
- <div v-if="asection.items && asection.items.length" class="icon">
123
- <i v-bind:class="{'bi bi-chevron-right': !asection.expand,'bi bi-chevron-down': asection.expand}" class="bi"></i>
124
- </div>
125
- </div>
126
- <a class="title-text"><i v-if="asection.draft" class="bi bi-exclamation-triangle-fill text-danger" title="This section is set to draft."></i><i v-if="asection.inline" class="bi bi-files-alt text-warning" title="This section is set to inline."></i> {{asection.text}}</a>
127
- </div>
128
- <div v-show="asection.expand && asection.items" v-for="(navSectionItem, index) in asection.items" class="items">
299
+ /* opacity is NOT overridable by a descendant - visibility is. A single
300
+ visibility:visible anywhere in the subtree used to punch a hole straight
301
+ through this guard and paint the raw uncompiled template ({{mustaches}},
302
+ every v-if branch at once) until Vue mounted. Keep both: visibility also
303
+ takes the subtree out of the a11y tree and hit-testing. //-- SG */
304
+ .hb-hidden{visibility:hidden !important;opacity:0 !important;}
305
+ /* Distinct from hb-hidden above, deliberately. hb-hidden is the pre-Vue boot
306
+ guard and is removed once, from the mount host. This one is view state -
307
+ content hidden while the next document loads - so it is driven by a Vue
308
+ binding on the elements themselves. Keeping one class for both jobs is what
309
+ made the reveal a global $(".hb-hidden") sweep. */
310
+ .hb-content-loading{visibility:hidden;}
311
+
312
+ .skip-link {
313
+ position: absolute;
314
+ top: -40px;
315
+ left: 0;
316
+ background: var(--htl-c-brand, #0066cc);
317
+ color: #fff;
318
+ padding: 8px 16px;
319
+ z-index: 10000;
320
+ text-decoration: none;
321
+ font-weight: 600;
322
+ }
323
+ .skip-link:focus {
324
+ top: 0;
325
+ }
129
326
 
130
- <!-- if navSectionItem has no items show clickable link -->
131
- <div v-if="!navSectionItem.items">
132
- <a class="DocLink link noitems" v-bind:href="navSectionItem.link" v-bind:target="navSectionItem.target || ''" style="padding-left: 0px;">
133
- <span class="link-text"><i v-if="navSectionItem.draft" class="bi bi-exclamation-triangle-fill text-danger" title="This page is set to draft."></i><i v-if="asection.inline" class="bi bi-files-alt text-warning" title="This section is set to inline."></i> {{navSectionItem.text}} <i v-if="navSectionItem.target" class="bi bi-box-arrow-up-right"></i> </span>
134
- </a>
135
- </div>
327
+ /* .caveat-text (the hdoc-approve signature style) moved to
328
+ css/theme-default/styles/components/htl-doc.css so the inline/embedded view -
329
+ which can also mount hdoc-approve - gets it too. */
330
+ </style>
136
331
 
137
- <nav-section-component v-if="navSectionItem.items" v-bind:asection="navSectionItem"></nav-section-component>
332
+ <!-- charset and viewport used to be here; moved to the very top of <head> - see the note
333
+ there. Do not add them back in this position. -->
334
+ <link rel="shortcut icon" type="image/svg+xml" href="/favicon.svg">
335
+ <!--[[META-LINKS]]-->
336
+
337
+ <!-- ALL stylesheets below carry data-hb-css + media="print" + the onload/onerror flip, which
338
+ is what makes them non-render-blocking. See the NON-BLOCKING CSS note in the inline
339
+ <script> at the top of this <head>: paint no longer waits for them, and revealApp() does
340
+ instead, so the app still never appears unstyled.
341
+ media="print" is the mechanism, not a mistake - a print stylesheet does not block the
342
+ screen render, and hbOnCssLoad() flips it to "all" the moment it lands.
343
+ Adding a stylesheet here? Copy the whole attribute set. Miss it and that file goes back
344
+ to blocking the first paint; miss only data-hb-css and the reveal gate stops counting
345
+ it, which is the safer half of the failure. Cascade order is unaffected: it follows
346
+ document order, not load order. -->
347
+
348
+ <!-- caveat stylesheet - for doc signee signatures -->
349
+ <link rel="preconnect" href="https://fonts.googleapis.com">
350
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
351
+ <link href="https://fonts.googleapis.com/css2?family=Caveat&display=swap" rel="stylesheet" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
352
+
353
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css" integrity="sha384-Bk5cbLkZQ5raZ0+H2/+VbfYx3WpvxvQK4zqXZr7sYODuaX7bKXoSOnipQxkaS8sv" crossorigin="anonymous" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
354
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
355
+
356
+ <!-- Theme CSS, static rather than injected by js/bootstrap.js.
357
+ No leading slash, so these resolve against the LITERAL <base href="/"> at the top of this
358
+ <head>. The literal is load-bearing: the HTML preload scanner resolves these hrefs before
359
+ any inline script has run, so while <base> was script-assigned every one of them 404'd
360
+ against the document's own directory first and only succeeded when the parser
361
+ re-resolved them - twelve wasted requests per page load.
362
+ Must stay AFTER the Bootstrap stylesheet above to keep the cascade order the JS loader
363
+ produced, and in exactly this order: fonts -> vars -> base -> layouts -> components. -->
364
+ <link rel="stylesheet" href="css/theme-default/styles/fonts.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
365
+ <link rel="stylesheet" href="css/theme-default/styles/vars.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
366
+ <link rel="stylesheet" href="css/theme-default/styles/base.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
367
+ <link rel="stylesheet" href="css/theme-default/styles/htldoc.layouts.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
368
+ <link rel="stylesheet" href="css/theme-default/styles/components/htl-search.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
369
+ <link rel="stylesheet" href="css/theme-default/styles/components/htl-library.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
370
+ <link rel="stylesheet" href="css/theme-default/styles/components/htl-doc.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
371
+ <link rel="stylesheet" href="css/theme-default/styles/components/api-doc.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
372
+ <link rel="stylesheet" href="css/theme-default/styles/components/sidebar.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
373
+ <link rel="stylesheet" href="css/theme-default/styles/components/content.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
374
+ <link rel="stylesheet" href="css/theme-default/styles/components/custom-block.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
375
+ <link rel="stylesheet" href="js/highlightjs/styles/vs2015-accessible.css" data-hb-css media="print" onload="hbOnCssLoad(this)" onerror="hbOnCssLoad(this)">
376
+
377
+ <!-- Counted from the DOM rather than hardcoded, so adding or removing a stylesheet above
378
+ cannot silently break the reveal gate. Must stay AFTER the last data-hb-css link.
379
+ hbCssCheck() is called again here because a cached stylesheet can fire onload before this
380
+ script runs, in which case the count was already complete but the total was not yet
381
+ known. //-- SG -->
382
+ <script>
383
+ window.hbCssTotal = document.querySelectorAll('link[data-hb-css]').length;
384
+ window.hbCssTotalKnown = true;
385
+ hbCssCheck();
386
+ </script>
387
+
388
+ <!-- defer on all three, and it is load-bearing for the boot splash. Without it these are
389
+ PARSER-blocking: the parser stops dead at the first one and does not reach <body> until
390
+ all three have come down from the CDN, so the splash markup does not exist yet and there
391
+ is nothing to paint. Measured on Slow 3G: first-paint 8548ms.
392
+ defer is safe for the ordering this site needs - deferred scripts run in document order
393
+ and all of them finish before DOMContentLoaded, which is when js/bootstrap.js is
394
+ injected. So Popper, Bootstrap and Vue are all present before loadJS() runs, exactly as
395
+ before. Do not swap defer for async: async would let Vue execute after
396
+ js/doc.hornbill.js. //-- SG -->
397
+ <script defer src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
398
+ <script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js" integrity="sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y" crossorigin="anonymous"></script>
399
+ <!-- Vue 3 global PRODUCTION build. Still the full build: vue.global.prod.js keeps the runtime
400
+ template compiler, which this site needs because every template is in-DOM markup or a
401
+ <script type="text/x-template"> block. It only drops dev warnings and dev-only proxies.
402
+ 165KB vs 588KB for vue.global.js.
403
+ To debug Vue itself, swap .prod.js back to .js AND set integrity to
404
+ sha384-xZGBZ8/+QRtMg/kJMPyo4v1otVc8kxjAeiKnaCg+yBlIxeE+11fWfYLJAM6BdWbc -->
405
+ <script defer src="https://cdn.jsdelivr.net/npm/vue@3.5.40/dist/vue.global.prod.js" integrity="sha384-yjSqnKX+01fhYzLUMRlCkMrHMTdKxwYcGIk5HZ6ug+4nfJn10+F7YsKRuGR5kQac" crossorigin="anonymous"></script>
406
+
407
+ <!-- template for nav item section -->
408
+ <script type="text/x-template" id="nav-section-template">
409
+ <section class="DocSidebarGroup" v-bind:class="{collapsible: asection.items && asection.items.length}">
410
+ <div class="navSectionGroupItem title" v-on:click="toggleNavCollapse(asection)">
411
+ <div class="action">
412
+ <div v-if="asection.items && asection.items.length" class="icon">
413
+ <i v-bind:class="{'bi bi-chevron-right': !asection.expand,'bi bi-chevron-down': asection.expand}" class="bi"></i>
414
+ </div>
415
+ </div>
416
+ <span class="title-text">{{asection.text}}</span>
417
+ </div>
418
+ <!-- A collapsed group now renders NOTHING instead of hidden DOM. With v-show, every
419
+ descendant of every collapsed group stayed in the document: on a large book that
420
+ was 205 nav links of which only 21 were visible, and the sidebar accounted for
421
+ ~81% of all elements on the page.
422
+ The <template> wrapper is deliberate and safe *because it carries v-if* - Vue
423
+ treats it as a fragment and emits no element of its own, so the
424
+ .DocSidebarGroup .items structure the CSS targets is byte-for-byte unchanged.
425
+ Never make this a bare <template>; without a directive it compiles to a real
426
+ element that the UA stylesheet hides. //-- SG -->
427
+ <template v-if="asection.expand && asection.items">
428
+ <div v-for="(navSectionItem, index) in asection.items" class="items">
429
+
430
+ <div v-if="!navSectionItem.items">
431
+ <a class="DocLink link noitems" v-bind:href="navSectionItem.link" v-bind:target="navSectionItem.target || ''" style="padding-left: 0px;">
432
+ <span class="link-text">{{navSectionItem.text}} <i v-if="navSectionItem.target" class="bi bi-box-arrow-up-right"></i> </span>
433
+ </a>
434
+ </div>
435
+
436
+ <nav-section-component v-if="navSectionItem.items" v-bind:asection="navSectionItem"></nav-section-component>
437
+
438
+ </div>
439
+ </template>
440
+ </section>
441
+ </script>
442
+
443
+ <!--[[ANALYTICS]]-->
444
+
445
+ <!-- eof template-->
446
+
447
+ </head>
448
+
449
+ <body>
450
+
451
+ <!-- ===================== BOOT SPLASH =====================
452
+ Covers the gap between first paint and revealApp(). The app template below is hidden
453
+ by .hb-hidden until Vue mounts, which on a slow link left a blank page for the whole
454
+ chain: document -> bootstrap.js (DOMContentLoaded) -> 5 JS files -> book info fetch.
455
+
456
+ FIRST element in <body> on purpose, so the preload scanner sees the logo before any of
457
+ that. Styled entirely from the inline <style> above - it must not depend on a
458
+ stylesheet that may still be in flight.
459
+
460
+ aria-live is deliberately absent; role="status" alone announces the label once. The
461
+ whole thing is removed from the a11y tree by hbHideBootSplash(). -->
462
+ <div id="hbBootSplash" role="status" aria-label="Loading Hornbill Documentation">
463
+ <!-- role="img" + aria-label, because the logo is a CSS background and so carries no
464
+ alt text of its own. See the .hbBootLogo rule above for why it is a background and
465
+ not an <img>. -->
466
+ <div class="hbBootLogo" role="img" aria-label="Hornbill"></div>
467
+ <span class="hbBootLabel">Documentation</span>
468
+ <div class="hbBootBar"></div>
469
+ <p class="hbBootStalled">This is taking longer than usual. If nothing appears, refresh the page.</p>
470
+ </div>
138
471
 
472
+ <a href="#DocContent" class="skip-link">Skip to main content</a>
473
+
474
+ <!-- data-root-click, not v-on:click: Vue 3 compiles this element's innerHTML as the app
475
+ template and does not compile the element itself, so a directive here would be
476
+ ignored. hbBindRootHandler() in js/doc.hornbill.js attaches it after mount. -->
477
+ <div id="vDocDevApp" class="hb-hidden Layout vh-100 hb-container-vertical overflow-hidden" data-root-click="resetMobileMenu">
478
+ <!-- top toolbar -->
479
+ <div class="toolbar-layout hb-container-horizontal" v-if="!docApp.loadingBookInfo">
480
+
481
+ <!-- LOCAL PREVIEW PATCH: logo is inert (no library home locally), and the
482
+ toolbar is labelled as a preview so it can't be mistaken for the live
483
+ site. Re-apply on re-sync from the htdocs viewer. -->
484
+ <div class="toolbar-logo">
485
+ <a class="toolbar-logo-image" style="cursor: default;" aria-label="Hornbill Documentation"></a>
486
+ </div>
487
+
488
+ <div class="hb-center-v">
489
+ <h1 class="documentation-home">Documentation <span class="hb-preview-badge">Preview</span></h1>
490
+ </div>
491
+ <div class="toolbar-middle hb-center-v hb-container-expand"></div>
492
+ <div v-if="docApp.book.docId" class="toolbar-right toolbar-search-bar hb-center-v">
493
+
494
+ <div class="position-relative">
495
+ <!-- LOCAL PREVIEW PATCH: hdoc serve has no search backend, so the box is
496
+ disabled (production htdocs copy keeps it live). Re-apply on re-sync. -->
497
+ <input disabled title="Search is not available in the hdoc serve local preview" v-model="docApp.searchText" placeholder="Search" type="search" maxlength="200" aria-label="Search documentation (not available in local preview)" autocomplete="off" class="form-control shadow-none rounded-pill ps-3 pe-4" v-on:keyup.enter="toolbarSearch()">
498
+ <i v-if="!docApp.searchText" class="text-secondary position-absolute bi bi-search" style="display: flex;top: 10px;right: 12px;"></i>
499
+ <!-- Enter on a single character sends nothing (length gate in searchDocuments)
500
+ and deliberately leaves the article on screen, so this is the only
501
+ feedback there is. Self-clearing after a few seconds - see
502
+ showSearchTooShortHint(). role=status so screen readers announce it
503
+ without stealing focus. -->
504
+ <div v-if="docApp.searchTooShortHint" class="search-tooshort-hint" role="status">Enter at least two characters to search</div>
505
+ </div>
506
+
507
+ </div>
508
+ <div class="toolbar-right hb-center-v">
509
+ <!-- narrow-viewport search: the inline search box (.toolbar-search-bar above) is
510
+ hidden below 602px, so this button takes the user to the search page instead.
511
+ Hidden at wider widths by .show-on-mobile-only (htldoc.layouts.css).
512
+ LOCAL PREVIEW PATCH: hidden entirely — no search backend in hdoc serve. -->
513
+ <button v-if="false" class="show-on-mobile-only me-2 btn btn-sm rounded-circle toolbar-icon-btn" v-on:click="openSearchView()" aria-label="Search documentation" :class="{'btn-light':docApp.ThemePreference!=='dark','btn-secondary':docApp.ThemePreference=='dark'}"><i class="bi bi-search"></i></button>
514
+
515
+ <!-- MCP server catalog. Not admin-gated: the endpoints it lists are
516
+ open, so the catalog is useful to anyone wiring up an agent.
517
+ LOCAL PREVIEW PATCH: disabled — no MCP catalog backend in hdoc serve. -->
518
+ <button disabled class="me-2 btn btn-sm rounded-circle toolbar-icon-btn" aria-label="Connect an AI assistant (not available in local preview)" title="Not available in the hdoc serve local preview" :class="{'btn-light':docApp.ThemePreference!=='dark','btn-secondary':docApp.ThemePreference=='dark'}">
519
+ <span class="hb-icon-mcp" aria-hidden="true"></span>
520
+ </button>
521
+
522
+ <!-- theme switch -->
523
+ <button v-if="docApp.ThemePreference!=='dark'" class="btn btn-sm btn-light rounded-circle toolbar-icon-btn" v-on:click="switchViewTheme()" aria-label="Switch to dark theme"><i class="bi bi-moon-fill"></i></button>
524
+ <button v-if="docApp.ThemePreference==='dark'" class="btn btn-sm btn-secondary rounded-circle toolbar-icon-btn" v-on:click="switchViewTheme()" aria-label="Switch to light theme"><i class="bi bi-brightness-high"></i></button>
525
+
526
+ <!-- settings (admin/persona). No "close settings" twin here: the /settings
527
+ page is served by settings/index.html, so this app is never on it -->
528
+ <button v-if="docApp.userSession && docApp.userSession.isAdmin" class="ms-2 btn btn-sm rounded-circle toolbar-icon-btn" v-on:click="switchSettingsView()" aria-label="Open settings" :class="{'btn-light':docApp.ThemePreference!=='dark','btn-secondary':docApp.ThemePreference=='dark'}">
529
+ <i class="bi bi-gear"></i>
530
+ </button>
531
+
532
+
533
+ </div>
534
+ <div v-if="docApp.userSession && docApp.userSession.profileImage" class="toolbar-right hb-center-v">
535
+
536
+ <!-- user info -->
537
+ <div class="userprofile dropdown">
538
+
539
+ <span class="dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
540
+ <img class="svg-prof" v-bind:src="docApp.userSession.profileImage" :alt="docApp.userSession.userName + ' profile'" style="width:32px;height:32px;">
541
+ </span>
542
+ <ul class="dropdown-menu p-0">
543
+ <div class="panel-header border-0 position-relative">
544
+ <div class="position-relative">
545
+ <div class="text-center fw-bolder lh-3">{{docApp.userSession.userId}}</div>
546
+ <div style="height:20px"></div>
547
+ </div>
548
+ <div class="position-absolute top-100 start-50 translate-middle">
549
+ <img class="svg-prof" v-bind:src="docApp.userSession.profileImage" :alt="docApp.userSession.userName + ' profile'" style="width:64px;height:64px;">
550
+ </div>
551
+ </div>
552
+ <div class="panel-content border-0 border-top p-3 pt-5 fw-light lh-normal">
553
+ <div class="text-center fw-bolder p-1">{{docApp.userSession.userName}}</div>
554
+ <div class="text-center fw-bolder p-1 fs-12">{{docApp.userSession.email}}</div>
555
+ </div>
556
+ <div class="panel-content p-3 border-0 border-top fw-light lh-normal">
557
+ <div class="lh-normal fs-12"><i class="bi bi-database-lock"></i> <span style="display:inline-block;width:48px">Issuer</span> : {{docApp.userSession.issuerInstance}}</div>
558
+ <div class="lh-normal fs-12" v-bind:title="docApp.userSession.expiresDateTime"><i class="bi bi-hourglass-split"></i> <span style="display:inline-block;width:48px">Expires</span> : {{new Date(docApp.userSession.validUntil).toLocaleDateString()}}</div>
559
+ </div>
560
+ </ul>
561
+ </div>
562
+ </div>
563
+ </div>
564
+
565
+
566
+
567
+ <!-- book title band. The mobile burger lives HERE, not in the top toolbar - it
568
+ toggles the BOOK page navigation, so it belongs with the book context. CSS
569
+ hides it at >=1020px (sidebar.css). resetMobileMenu still matches it by class
570
+ to decide whether a click should dismiss the nav, so the class matters, but its
571
+ position in the DOM does not. The open state itself is docApp.bookNavOpen. -->
572
+ <div v-if="docApp.book.docId" class="title-band hb-bg-mute p-3 border-bottom">
573
+ <button class="mobile-menu-btn" aria-label="Toggle book navigation" :aria-expanded="docApp.bookNavOpen ? 'true' : 'false'" v-on:click="toggleMobileMenu()"><i class="bi bi-list-ul"></i></button>
574
+ <div class="hb-container-expand">
575
+ <h2 class="title">{{docApp.title}}</h2>
576
+ <span class="description2">{{docApp.description}}</span>
577
+ </div>
139
578
  </div>
140
- </section>
141
- </script>
142
- <!-- eof template-->
143
579
 
144
- </head>
580
+ <div class="main-layout hb-container-vertical hb-container-expand">
145
581
 
146
- <body>
582
+ <!-- ============ MCP server catalog (/mcp) ============ -->
583
+ <!-- Rendered inside .HTL-doc, the same typography scope a book page uses,
584
+ with plain semantic markup and no component classes. Headings, body
585
+ text, code spans and tables then come from the article styles the
586
+ rest of the site already has - this page IS documentation, so it
587
+ should read as a page of it rather than carry its own look -->
588
+ <div v-if="docApp.mcpCatalogMode" class="McpCatalog hb-container-expand hb-scroll hb-container-vertical">
589
+ <div class="HTL-doc content-wrapper">
147
590
 
148
- <div id="vDocDevApp" class="hb-hidden Layout hb-container-vertical" v-on:click="resetMobileMenu($event)">
149
- <!-- top toolbar -->
150
- <div class="toolbar-layout hb-container-horizontal">
591
+ <h1>Connect an AI assistant</h1>
151
592
 
152
- <div class="hb-center-v">
153
- <button class="mobile-menu-btn" v-on:click="toggleMobileMenu()"><i class="bi bi-list-ul"></i></button>
154
- </div>
155
- <div class="toolbar-logo">
156
- <div class="toolbar-logo-image"></div>
157
- </div>
158
- <div class="toolbar-split"></div>
159
- <div class="toolbar-middle hb-center-v hb-container-expand">
160
-
161
- <h2 class="title">{{docApp.title}}</h2>
162
- <h5 class="description">{{docApp.description}}</h5>
163
-
164
- <!-- could generate nav bar based on library settings -->
165
- <!--
166
- <ul class="nav-bar-nav-list">
167
- <li class="nav-bar-item active">
168
- <a>Documentation</a>
169
- </li>
170
- </ul>
171
- -->
172
- </div>
173
- <div class="toolbar-right hb-center-v">
174
-
175
- <!-- theme switch -->
176
- <label class="theme-switch">
177
- <input class="theme-switch-checkbox" type="checkbox" v-on:change="switchViewTheme()">
178
- <span class="slider round">
179
- </span>
180
- </label>
181
- </div>
182
- <div v-if="docApp.userSession" class="toolbar-right hb-center-v">
183
- <!-- user info -->
184
- <div class="userprofile dropdown">
185
- <span class="dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
186
- <img class="svg-prof" v-bind:src="docApp.userSession.profileImage"
187
- style="width:32;height:32;"></img>
188
- </span>
189
- <ul class="dropdown-menu p-0">
593
+ <p>
594
+ Your AI assistant can read this documentation directly, so it answers from
595
+ the current content instead of guessing. Add one of the servers below to your
596
+ client &mdash; they use the Model Context Protocol (MCP). Each one covers a
597
+ different body of content, so add the one you need.
598
+ </p>
190
599
 
600
+ <p v-if="docApp.mcpCatalog.error">
601
+ The catalog did not load. Refresh the page to try again.
602
+ </p>
191
603
 
604
+ <p v-if="!docApp.mcpCatalog.error && !docApp.mcpCatalog.servers.length">
605
+ Loading&hellip;
606
+ </p>
192
607
 
193
- <div class="panel-header border-0 position-relative">
194
- <div class="position-relative">
195
- <div class="text-center fw-bolder lh-3">{{docApp.userSession.userId}}</div>
196
- <div style="height:20px"></div>
197
- </div>
198
- <div class="position-absolute top-100 start-50 translate-middle">
199
- <img class="svg-prof" v-bind:src="docApp.userSession.profileImage"
200
- style="width:64px;height:64px;"></img>
201
- </div>
202
- </div>
203
- <div class="panel-content border-0 border-top p-3 pt-5 fw-light lh-normal">
204
- <div class="text-center fw-bolder p-1">{{docApp.userSession.userName}}</div>
205
- <div class="text-center fw-bolder p-1 fs-12">{{docApp.userSession.email}}</div>
206
- </div>
207
- <div class="panel-content p-3 border-0 border-top fw-light lh-normal">
208
- <div class="lh-normal fs-12"><i class="bi bi-database-lock"></i> <span
209
- style="display:inline-block;width:48px">Issuer</span> :
210
- {{docApp.userSession.issuerInstance}}</div>
211
- <div class="lh-normal fs-12" v-bind:title="docApp.userSession.expiresDateTime"><i
212
- class="bi bi-hourglass-split"></i> <span
213
- style="display:inline-block;width:48px">Expires</span> : {{new
214
- Date(docApp.userSession.validUntil).toLocaleDateString()}}</div>
215
- </div>
216
- </ul>
217
- </div>
218
- </div>
219
- </div>
608
+ <template v-for="server in docApp.mcpCatalog.servers" :key="server.name">
220
609
 
221
- <!-- 100% horizontal col below toolbar and auto adjust vertical to take rest of avail height -->
222
- <div class="main-layout hb-container-horizontal hb-container-expand hb-hidden">
610
+ <h2>{{server.name}}</h2>
223
611
 
224
- <!-- the left hand nav col -->
225
- <aside class="DocSidebar hb-container-vertical hb-hidden">
612
+ <p>{{server.summary}}</p>
226
613
 
227
- <nav class="hdoc-nav hb-container-expand" id="DocSidebarNav" aria-labelledby="sidebar-aria-label"
228
- tabindex="-1">
229
- <div v-for="(navSection,index) in docApp.navSections" v-bind:class="{'group':navSection.items, 'nogrouplink':!navSection.items}">
614
+ <p>
615
+ <span class="mcp-server-count">
616
+ <span v-if="server.documentCount">{{server.documentCount.toLocaleString()}} pages</span>
617
+ <span v-if="!server.documentCount">Not published to this site</span>
618
+ </span>
619
+ </p>
620
+
621
+ <!-- The two fields every MCP client asks for. The name is the
622
+ server's own identifier, so it is the one to use - saying so
623
+ saves the reader inventing one and keeps names consistent
624
+ between people setting this up -->
625
+ <dl class="mcp-connect">
626
+ <dt>Name</dt>
627
+ <dd>
628
+ <code>{{server.name}}</code>
629
+ <button type="button" class="mcp-copy" title="Copy to clipboard"
630
+ :aria-label="'Copy the name ' + server.name + ' to the clipboard'"
631
+ v-on:click="mcpCopy(server.path + ':name', server.name)">
632
+ <i class="bi" :class="docApp.mcpCopied[server.path + ':name'] ? 'bi-check2' : 'bi-clipboard'"></i>
633
+ <span class="visually-hidden" v-if="docApp.mcpCopied[server.path + ':name']">Copied</span>
634
+ </button>
635
+ </dd>
636
+
637
+ <dt>URL</dt>
638
+ <dd>
639
+ <code>{{mcpServerUrl(server.path)}}</code>
640
+ <button type="button" class="mcp-copy" title="Copy to clipboard"
641
+ :aria-label="'Copy the URL for ' + server.name + ' to the clipboard'"
642
+ v-on:click="mcpCopy(server.path + ':url', mcpServerUrl(server.path))">
643
+ <i class="bi" :class="docApp.mcpCopied[server.path + ':url'] ? 'bi-check2' : 'bi-clipboard'"></i>
644
+ <span class="visually-hidden" v-if="docApp.mcpCopied[server.path + ':url']">Copied</span>
645
+ </button>
646
+ </dd>
647
+ </dl>
648
+
649
+ <table>
650
+ <thead>
651
+ <tr><th>Tool</th><th>What it does</th></tr>
652
+ </thead>
653
+ <tbody>
654
+ <template v-for="tool in server.tools" :key="tool.name">
655
+ <tr>
656
+ <td><code>{{tool.name}}</code></td>
657
+ <td><template v-for="(part,pi) in mcpToolDescriptionParts(tool.description)" :key="pi"><code v-if="part.code">{{part.text}}</code><template v-else>{{part.text}}</template></template></td>
658
+ </tr>
659
+ </template>
660
+ </tbody>
661
+ </table>
662
+
663
+ </template>
230
664
 
231
- <div v-show="!navSection.items">
232
- <a class="DocLink link noitems" v-bind:href="navSection.link"
233
- v-bind:target="navSection.target || ''" style="padding-left: 0px;">
234
- <span class="link-text">{{navSection.text}} <i v-if="navSection.target"
235
- class="bi bi-box-arrow-up-right"></i> </span>
236
- </a>
237
- </div>
665
+ </div>
666
+ </div>
238
667
 
239
- <div v-show="navSection.items">
240
- <nav-section-component v-show="navSection.items" :key="updateCounter"
241
- v-bind:asection="navSection"></nav-section-component>
668
+ <div v-if="!docApp.mcpCatalogMode && !docApp.loadingBookInfo && (docApp.searchMode || (!docApp.book.docId && docApp.selectedLibraryGroup))">
669
+ <!-- library banner - can make this a search and hero menu -->
670
+ <div class="library-banner-hug hb-bg-mute pad-20">
671
+
672
+ <div class="mt-4 mb-4">
673
+ <h1 class="display-4">How can we help?</h1>
242
674
  </div>
243
- </div>
244
675
 
245
- <div v-if="docApp.hasInlinePopupDocuments" class="nogrouplink">
246
- <div>
247
- <a class="DocLink link noitems" v-on:click="showInlineDocumentList($event)">
248
- <span class="link-text">
249
- Inline Help Documents
250
- </span>
251
- </a>
676
+ <div class="mt-4 mw-800">
677
+
678
+ <div class="hb-container-horizontal">
679
+ <div class="hb-container-expand">
680
+ <div class="input-group input-group-lg">
681
+ <span class="input-group-text white"><i class="bi bi-search"></i></span>
682
+ <input v-model="docApp.altSearchText" placeholder="Try 'workflow', 'assets', 'API keys'..." type="search" maxlength="200" aria-label="Search documentation" autocomplete="off" class="form-control border-start-0 shadow-none" v-on:keyup="searchDocuments(docApp.altSearchText,false,$event)" v-on:search="searchDocuments(docApp.altSearchText,false,$event)">
683
+ </div>
684
+ </div>
252
685
  </div>
253
686
  </div>
254
687
 
255
- </nav>
688
+ </div>
689
+ </div>
256
690
 
257
- </aside>
691
+ <div class="hb-container-expand hb-container-horizontal">
258
692
 
259
- <!-- the right hand content col -->
260
- <div class="DocContent hb-container-expand hb-container-vertical" id="DocContent">
261
- <div class="HTL-doc content-wrapper hb-container-expand">
693
+ <!-- container showing search results -->
694
+ <div v-if="docApp.searchMode && !docApp.loadingBookInfo" class="LibrarySearch hb-container-expand hb-container-vertical">
262
695
 
263
- <div v-show="!docApp.showInlineDocumentsMarkup" class="injected-document-container hb-container-horizontal">
696
+
697
+ <div class="hb-container-expand hb-container-horizontal">
698
+
699
+ <!-- left hannd filter options on product (and other options like tags?) -->
700
+ <!-- No hb-hidden here, and none anywhere else inside this template either:
701
+ the boot guard lives only on the #vDocDevApp mount host, which is the one
702
+ element Vue does not own. Vue rewrites the class attribute of anything it
703
+ renders, so a static hb-hidden in here is put straight back on the next
704
+ re-render and the removeClass never sticks - which is precisely how this
705
+ panel came to open at visibility:hidden and look dead. It is redundant in
706
+ any case: visibility inherits from the host. See revealApp(). -->
707
+ <!-- is-searching: dim the facet lists while a query is in flight - their counts
708
+ belong to the previous response. Unlike the results list this keeps pointer
709
+ events: picking another filter mid-flight is valid (the abort controller
710
+ supersedes the in-flight request). -->
711
+ <aside class="DocSidebar hb-container-vertical p-3 overflow-auto" :class="{'open': docApp.searchFiltersOpen, 'is-searching': docApp.boolSearchInProgress}">
712
+
713
+ <div class="d-flex align-items-center justify-content-between">
714
+ <h2 class="fw-bold fs-5 mb-0">Filter</h2>
715
+ <!-- only rendered below 1020px, where this panel is an overlay -->
716
+ <button type="button" class="search-filter-close btn btn-sm btn-light rounded-circle toolbar-icon-btn" v-on:click="closeSearchFilters()" aria-label="Close filters"><i class="bi bi-x-lg"></i></button>
717
+ </div>
718
+ <div v-if="docApp.searchFilterBySpecificBookId || docApp.hasFilterByProducts || docApp.searchAudienceFilter" class="pt-2 ">
719
+ <span class="HTL-actionlink fw-normal fs-6 c-pointer" v-on:click="clearSearchFilters($event)">Clear All</span>
720
+ </div>
264
721
 
265
- <div class="injected-document-content">
266
- <div v-if="docApp.error === ''">
267
- <div class="document-header">
722
+ <!-- audience filter. Internal/dev only (docApp.showAudienceFilter) - never
723
+ shown on the public site, which hosts public books only. Server-side
724
+ filter: re-runs the search with &audience= -->
725
+ <div v-if="docApp.showAudienceFilter" class="border-top mt-4">
726
+ <h2 class="mt-3 fw-bold fs-5"><label class="fw-bold" for="audience_filter_select">Audience</label></h2>
727
+ <select v-model="docApp.searchAudienceFilter" v-on:change="filterSearchByAudience($event)" class="form-select form-select-sm mt-2 fs-6" id="audience_filter_select">
728
+ <option v-for="aud in docApp.searchAudienceOptions" :key="aud.value" :value="aud.value">{{aud.label}}</option>
729
+ </select>
730
+ </div>
268
731
 
269
- <!-- breadcrumb-->
270
- <div class="hb-container-horizontal">
271
- <div class="hb-center-v hb-container-expand">
272
- <ul class="ps-0 nav-bar-nav-list noeffects after-fslash overflow-ellipsis">
273
- <li class="mt-0 nav-bar-item"
274
- v-for="(crumb,$index) in docApp.bookBreadCrumb">
275
- <a class="ps-0 pe-0 text-decoration-none"
276
- v-bind:href="crumb.link">{{crumb.title}}</a>
732
+ <!-- content type filter. NOT gated like Audience: reference material
733
+ outnumbers prose everywhere (about 85% of this corpus), so the
734
+ public site needs this as much as the internal one. Server-side
735
+ SOFT filter - re-runs the search with &dt=, and docTypesInfo keeps
736
+ reporting whole-corpus counts so the options below always show what
737
+ is being hidden -->
738
+ <!-- no border-top here: this sits directly beneath the Audience
739
+ select, whose own bottom edge already reads as a rule, and two
740
+ lines that close together look like a mistake -->
741
+ <!-- shown whenever any doc type is present, even a single one - a
742
+ facet that appears and disappears between searches reads as
743
+ broken, and with the filter active (dt defaults to
744
+ Documentation) the single entry still tells the user what they
745
+ are looking at -->
746
+ <div v-if="docApp.searchResponse && docApp.searchResponse.docTypesInfo && docApp.searchResponse.docTypesInfo.length" class="mt-4">
747
+ <h2 class="mt-3 fw-bold fs-5">Information type</h2>
748
+ </div>
749
+ <ul v-if="docApp.searchResponse && docApp.searchResponse.docTypesInfo && docApp.searchResponse.docTypesInfo.length">
750
+ <li class="searchFilterProducts mt-2">
751
+ <div class="form-check fs-5">
752
+ <input v-model="docApp.searchDocTypeFilter" class="form-check-input" v-on:change="filterSearchByDocType('',$event)" type="radio" value="" id="all_doctypes">
753
+ <label class="form-check-label fs-6 c-pointer" for="all_doctypes">All</label>
754
+ </div>
755
+ </li>
756
+ <template v-for="dt in docApp.searchResponse.docTypesInfo" :key="dt.docType">
757
+ <li class="searchFilterProducts mt-2">
758
+ <div class="form-check fs-5">
759
+ <input v-model="docApp.searchDocTypeFilter" class="form-check-input" v-on:change="filterSearchByDocType(String(dt.docType),$event)" type="radio" :value="String(dt.docType)" :id="'doctype_'+dt.docType">
760
+ <div style="width: 85%;display: inline-flex;">
761
+ <label class="form-check-label fs-6 c-pointer text-truncate" :for="'doctype_'+dt.docType" :title="dt.name">{{dt.name}}</label>
762
+ </div>
763
+ <span class="badge rounded-pill hb-bg-mute text-body float-end">{{dt.foundCount}}</span>
764
+ </div>
765
+ </li>
766
+ </template>
767
+ </ul>
768
+
769
+ <!-- book filter -->
770
+ <div v-if="docApp.searchResponse && docApp.searchResponse.booksInfo && docApp.searchResponse.booksInfo.length" class="border-top mt-4">
771
+ <h2 class="mt-3 fw-bold fs-5">Books</h2>
772
+ </div>
773
+ <ul v-if="docApp.searchResponse && docApp.searchResponse.booksInfo && docApp.searchResponse.booksInfo.length">
774
+ <li class="searchFilterProducts mt-2">
775
+ <div class="form-check fs-5">
776
+ <input v-model="docApp.searchFilterBySpecificBookId" class="form-check-input" v-on:change="filterSearchByBook({bookId:'',title:''},$event)" type="radio" value="" id="all_books">
777
+ <label class="form-check-label fs-6 c-pointer" for="all_books">
778
+ All
779
+ </label>
780
+ </div>
781
+ </li>
782
+ <template v-for="(book,index) in docApp.searchResponse.booksInfo" :key="book.bookId">
783
+ <li v-if="!book.productFilterDisable || book.bookId===docApp.searchFilterBySpecificBookId" class="searchFilterProducts mt-2">
784
+ <div class="form-check fs-5">
785
+ <input v-model="docApp.searchFilterBySpecificBookId" class="form-check-input" v-on:change="filterSearchByBook(book,$event)" :disabled="book.productFilterDisable" type="radio" :value="book.bookId" :id="book.bookId">
786
+ <div style="width: 85%;display: inline-flex;">
787
+ <label :title="book.title" class="overflow-ellipsis form-check-label fs-6 c-pointer" :for="book.bookId">
788
+ {{book.title}}
789
+ </label>
790
+ </div>
791
+
792
+
793
+ <span class="badge rounded-pill fw-light fs-7 float-end" :class="{'bg-primary':docApp.searchFilterBySpecificBookId===book.bookId,'bg-secondary':docApp.searchFilterBySpecificBookId!==book.bookId}">
794
+ <span v-if="!book.productFilterDisable">{{book.foundCount}}</span>
795
+ <span v-if="book.productFilterDisable">0</span>
796
+ </span>
797
+
798
+ </div>
799
+ </li>
800
+ </template>
801
+ </ul>
802
+
803
+
804
+ <!-- product filter -->
805
+ <div class="mt-3 border-top">
806
+ <h2 class="mt-3 fw-bold fs-5">Products</h2>
807
+ </div>
808
+ <ul>
809
+ <template v-for="(product,index) in docApp.products" :key="product.id">
810
+ <li v-if="docApp.searchFilteredProducts[product.id] && docApp.searchFilteredProducts[product.id].returnedCount" class="searchFilterProducts mt-2">
811
+
812
+ <div class="form-check fs-5">
813
+ <input class="form-check-input" v-on:change="filterSearchByProducts(product,$event)" type="checkbox" :value="product.id" :checked="product.checked" :id="product.id">
814
+ <div style="width: 85%;display: inline-flex;">
815
+ <label :title="product.name" class="overflow-ellipsis form-check-label fs-6 c-pointer" :for="product.id">
816
+ {{product.name}}
817
+ </label>
818
+ </div>
819
+
820
+
821
+ <span v-if="docApp.searchFilteredProducts[product.id]"
822
+ class="badge rounded-pill fw-light fs-7 float-end"
823
+ :class="{'bg-primary':docApp.searchFilteredProducts[product.id].checked,'bg-secondary':!docApp.searchFilteredProducts[product.id].checked}">{{docApp.searchFilteredProducts[product.id].returnedCount}}
824
+ </span>
825
+
826
+ </div>
827
+
828
+
829
+ </li>
830
+ </template>
831
+ </ul>
832
+ </aside><!-- left hannd filter options on product (and other options like tags?) -->
833
+
834
+ <!-- right hand search results-->
835
+ <div class="hb-container-expand hb-scroll">
836
+
837
+ <div v-if="docApp.searchError" class="p-3 searchResultsContainer">
838
+ <div class="mb-2 alert alert-danger">{{docApp.searchError}}</div>
839
+ </div>
840
+
841
+ <div v-if="docApp.altSearchText" class="p-3 searchResultsContainer">
842
+ <div>
843
+ <div v-if="docApp.hasFilterByProducts" class="mb-2">
844
+ <template v-for="(product,index) in docApp.products" :key="product.id"><span v-if="docApp.searchFilteredProducts[product.id] && docApp.searchFilteredProducts[product.id].checked" v-on:click="filterSearchByProducts(product,$event)" class="fs-7 p-2 me-2 badge bg-secondary c-pointer fw-normal">{{product.name}} <i class="ml-2 pt-4 bi bi-x"></i></span></template>
845
+ </div>
846
+
847
+ <!-- one-character query: nothing was sent (a single unquoted character can
848
+ only match noise, so searchDocuments gates on length) and the previous
849
+ answer has been cleared, so say why the results area is empty instead
850
+ of showing a blank panel -->
851
+ <h3 class="pb-4 border-bottom fw-bold fs-5" v-if="docApp.searchTooShort && !docApp.boolSearchInProgress">
852
+ Keep typing
853
+ <span class="fw-lighter">&nbsp;- a search needs at least two characters</span>
854
+ </h3>
855
+
856
+ <h3 class="pb-4 border-bottom fw-bold fs-5" v-if="docApp.boolSearchInProgress">
857
+ Searching
858
+ <span v-if="docApp.searchFilterBySpecificBookId && docApp.searchFilterBySpecificBookTitle"> in {{docApp.searchFilterBySpecificBookTitle}}</span>
859
+ </h3>
860
+
861
+ <!-- narrow viewports only: the filter sidebar is hidden below
862
+ 1020px and this view has no burger, so without this the
863
+ filters cannot be reached at all -->
864
+ <button type="button" class="search-filter-btn btn btn-sm btn-outline-secondary mb-3" v-on:click="toggleSearchFilters()" aria-label="Show search filters">
865
+ <i class="bi bi-funnel"></i> Filters
866
+ </button>
867
+
868
+ <h3 class="pb-4 border-bottom fw-bold fs-5" v-if="docApp.searchResponse && docApp.currentResultsSearchText" :class="{'is-searching': docApp.boolSearchInProgress}">
869
+ <!-- while a query is in flight the list below still holds the PREVIOUS
870
+ results, so don't put a count against the new search term -->
871
+ <template v-if="docApp.boolSearchInProgress">Searching for:</template>
872
+ <template v-else><span>{{docApp.searchResultFilteredItems.length}}</span> results for:</template>
873
+ <!-- the leading &nbsp; entities are load-bearing: Vue 3's whitespace
874
+ condensing strips the newline-indent between these elements, which
875
+ rendered as "for:email" -->
876
+ <span class="fw-lighter">&nbsp;{{docApp.currentResultsSearchText}}</span>
877
+ <span v-if="docApp.searchFilterBySpecificBookId && docApp.searchFilterBySpecificBookTitle">&nbsp;in {{docApp.searchFilterBySpecificBookTitle}}</span>
878
+ <!-- informational, not an error - themed text-3 rather than bootstrap's text-danger red -->
879
+ <div class="fw-normal pt-2 HTL-fs-sm" style="color:var(--htl-c-text-3)" v-if="docApp.searchResponse && docApp.searchResponse.returnedResultsLimited">Found {{docApp.searchResponse.totalResultsAvailable}} matches. Showing the top {{docApp.searchResponse.results ? docApp.searchResponse.results.length : 0}}. Use a more specific search to further narrow down the results.</div>
880
+ <!-- content-type rescue. The filter defaults to Documentation, so it
881
+ must never dead-end a search that has an answer elsewhere: when the
882
+ filter would show nothing we widen automatically and say so. No
883
+ "show all" offer beyond that - the server caps results at 300, so a
884
+ link promising the full hidden count could never deliver it; the
885
+ Information type facet already shows what is being hidden -->
886
+ <div class="fw-normal pt-2 HTL-fs-sm" style="color:var(--htl-c-text-3)" v-if="docApp.searchDocTypeRescue && docApp.searchDocTypeRescue.auto">
887
+ No documentation pages match this search, so all content types are shown.
888
+ <span v-if="docApp.searchDocTypeRescue.best">Most matches are in {{docApp.searchDocTypeRescue.best.name}}.</span>
889
+ </div>
890
+ <!-- unknown bid in a pasted/stale url: the filter was cleared
891
+ automatically (see evaluateBookFilterRescue) - explain, and show
892
+ the bad id so the user can see what was wrong with the link -->
893
+ <div class="fw-normal pt-2 HTL-fs-sm" style="color:var(--htl-c-text-3)" v-if="docApp.searchBookRescue">
894
+ There is no book with the ID &quot;{{docApp.searchBookRescue.bid}}&quot;, so the book filter from this link was ignored and results from all books are shown.
895
+ </div>
896
+ </h3>
897
+
898
+ <span class="text-danger" v-if="!docApp.boolSearchInProgress && docApp.searchResultFilteredItems.length == 0 && docApp.validSearch">You have an odd number of " characters in your search terms - each one needs closing with a matching " character!</span>
899
+ </div>
900
+
901
+ <!-- Search-in-progress placeholder. Shown only when there is nothing to keep on
902
+ screen - a first search, or one that returned nothing. It used to replace the
903
+ results list on EVERY query, which destroyed and rebuilt all 300 rows per
904
+ search (3700-5800 elements churned) and made the :key on the results list
905
+ worthless, since no row ever survived to be reused. The list now stays
906
+ mounted and is dimmed via .is-searching instead. -->
907
+ <ul class="searchResults mb-4" v-if="docApp.boolSearchInProgress && !docApp.searchResultFilteredItems.length">
908
+ <!-- was two blocks gated on ThemePreference=='dark' / !=='dark' with byte-identical
909
+ markup, so the condition never changed the output - the skeleton is themed
910
+ purely by CSS. Collapsed to one loop, which also removes a v-if and v-for on
911
+ the same element (Vue 3 evaluates v-if first). -->
912
+ <li class="p-3" v-for="index in 8" :key="index">
913
+ <h2 class="title fs-5 mb-2 col-5"><div class="HTL-skeleton HTL-skeleton-text HTL-skeleton-text-100"></div></h2>
914
+ <div class="HTL-fs-sm mb-1 col-7"><div class="HTL-skeleton HTL-skeleton-text HTL-skeleton-text-100"></div></div>
277
915
  </li>
278
916
  </ul>
279
- </div>
280
- <div class="toolbar-right hb-center-v">
917
+
918
+ <!-- actual search results -->
919
+ <ul v-if="docApp.currentResultsSearchText && docApp.searchResultFilteredItems && docApp.searchResultFilteredItems.length"
920
+ class="searchResults searchResultsList"
921
+ :class="{'is-searching': docApp.boolSearchInProgress}"
922
+ :aria-busy="docApp.boolSearchInProgress ? 'true' : 'false'">
923
+ <li class="p-3" v-for="(resultItem,index) in docApp.searchResultFilteredItems" :key="resultItem.url">
924
+ <h2 class="title fs-5 mb-2"><a v-bind:href="resultItem.url" :bookid="resultItem.bookId" class="searchresult-href">{{resultItem.title}}</a>
925
+ <!-- which search channel(s) matched this result: keyword (FTS5), semantic (vector), or both.
926
+ Internal/dev only (docApp.showSearchMatchBadges) - never shown on the public site -->
927
+ <span v-if="docApp.showSearchMatchBadges && resultItem.matchedBy" class="search-match-badge" :class="'search-match-' + resultItem.matchedBy"
928
+ :title="resultItem.matchedBy === 'semantic' ? 'Found by semantic (meaning-based) search' : (resultItem.matchedBy === 'both' ? 'Found by both keyword and semantic search' : 'Found by keyword search')"
929
+ >{{resultItem.matchedBy === 'semantic' ? 'semantic' : (resultItem.matchedBy === 'both' ? 'keyword + semantic' : 'keyword')}}</span>
930
+ </h2>
931
+ <div class="text-success HTL-fs-sm">{{resultItem.url}}</div>
932
+ <p class="HTL-fs-sm" v-html="resultItem.content"></p>
281
933
 
282
- <a class="toolbar-action" v-bind:href="docApp.documentGithubUrl"
283
- target="_blank">
284
- <i class="bi bi-pencil"></i>
285
- </a>
934
+ </li>
935
+ </ul>
936
+ </div>
937
+ </div><!-- right hand search results-->
286
938
 
287
- <a class="toolbar-action">
288
- <i class="bi bi-three-dots-vertical"></i>
289
- </a>
939
+ </div>
940
+ </div>
941
+
942
+ <!-- container showing full library of contents -->
943
+ <div v-if="!docApp.mcpCatalogMode && !docApp.searchMode && !docApp.loadingBookInfo && !docApp.book.docId && docApp.selectedLibraryGroup" class="LibraryContent hb-container-expand hb-container-vertical" id="LibraryContent">
944
+
945
+
946
+ <div class="hb-container-expand hb-scroll">
947
+
948
+ <div class="position-relative">
949
+ <div v-if="!docApp.libraryHomeViewProduct" class="position-absolute top-0 end-0 mt-2 me-2">
950
+ <div class="dropdown">
951
+ <button class="btn btn btn-light dropdown-toggle" type="button" id="dropdownMenu2" data-bs-toggle="dropdown" aria-expanded="false">
952
+ <i class="bi bi-filter"></i> {{docApp.selectedLibraryGroup.title}}
953
+ </button>
954
+ <ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
955
+ <li v-for="(groupMode,index) in docApp.groupModes " :key="groupMode.mode"><button v-on:click="organiseLibraryByGroup(groupMode,true)" class="dropdown-item" type="button">{{groupMode.title}}</button></li>
956
+ </ul>
290
957
  </div>
291
958
  </div>
959
+ </div>
292
960
 
293
- <div class="document-title"></div>
294
-
295
- <!-- doc frontmatter info -->
296
- <div class="hb-container-horizontal">
297
- <div class="hb-center-v hb-container-expand">
298
- <ul
299
- class="ps-0 nav-bar-nav-list noeffects after-bullets overflow-ellipsis text-light-2">
300
- <li class="ps-0 mt-0 nav-bar-item">
301
- Article
302
- </li>
303
- <li class="ps-0 mt-0 nav-bar-item">
304
- YYYY-MM-DD
305
- </li>
306
- <li class="ps-0 mt-0 nav-bar-item">
307
- (n) minutes to read
308
- </li>
309
- <li class="ps-0 mt-0 nav-bar-item">
310
- <a class="link c-pointer">(n) contributors</a>
311
- </li>
312
- </ul>
961
+ <div class="library-content-panel">
962
+ <!-- show specific product books in columns (when have more than 8 books in a pproduct)-->
963
+ <div v-if="docApp.libraryHomeViewProduct">
964
+ <div v-if="docApp.libraryHomeViewProduct.books.length" class="pad-20 mt-3">
965
+
966
+ <div class="lib-header mb-4">
967
+ <h1 class="mb-2">{{docApp.libraryHomeViewProduct.title || docApp.libraryHomeViewProduct.id}}</h1>
968
+ <p>{{docApp.libraryHomeViewProduct.description}}</p>
969
+ </div>
970
+
971
+ <!-- break list of books into 3 columns -->
972
+ <ol class="document-list list-unstyled card-columns">
973
+ <li v-for="(book, index) in docApp.libraryHomeViewProduct.books" :key="book.docId">
974
+ <a :href="book.docId" class="title" v-on:click="loadLibraryBook(book,false,$event)" style="display:inline-block">{{book.title}}</a>
975
+ </li>
976
+ </ol>
977
+ </div>
313
978
  </div>
314
- <div class="toolbar-right hb-center-v">
979
+
980
+ <!-- group lib items by list columns of 3 - will shrink to 2 and then 1 on mobile -->
981
+ <div v-if="!docApp.libraryHomeViewProduct && docApp.selectedLibraryGroup.layout=='column-list'">
982
+ <template v-for="(group, groupKey) in docApp.libraryGroups" :key="groupKey">
983
+ <div v-if="group.books.length" class="pad-20 mt-3">
984
+
985
+ <div class="lib-header mb-4">
986
+ <h1 class="mb-2">{{group.title || group.id}}</h1>
987
+ <p>{{group.description}}</p>
988
+ </div>
989
+
990
+ <!-- break list of books into 3 columns -->
991
+ <ol class="document-list list-unstyled card-columns">
992
+ <li v-for="(book, index) in group.books" :key="book.docId">
993
+ <a :href="book.docId" class="title" v-on:click="loadLibraryBook(book,false,$event)" style="display:inline-block">{{book.title}}</a>
994
+ </li>
995
+ </ol>
996
+ </div>
997
+ </template>
315
998
  </div>
999
+
1000
+ <!-- grouping by tiles -->
1001
+ <div v-if="!docApp.libraryHomeViewProduct && docApp.selectedLibraryGroup.layout=='tiles'" class="columns-container mt-5">
1002
+
1003
+ <div class="columns">
1004
+
1005
+ <template v-for="(group, groupKey) in docApp.libraryGroups" :key="groupKey">
1006
+ <div v-if="group.books.length" class="column pad-20">
1007
+ <div class="h-100 w-100 d-inline-block shadow-sm border pad-20">
1008
+
1009
+ <!-- flex header: icon and title share a centreline, divider separates the
1010
+ header from the book list (product-tile-* in htl-library.css). The
1011
+ background shorthand must include "/ contain" - the shorthand resets
1012
+ background-size, so a stylesheet rule cannot win against this inline
1013
+ !important -->
1014
+ <div class="product-tile-header">
1015
+ <i class="product-tile-icon" :style="'background: url('+group.icon+') no-repeat left center / contain !important'"></i>
1016
+ <h2 class="mb-0">{{group.title}}</h2>
1017
+ </div>
1018
+
1019
+ <template v-for="(book, index) in group.books" :key="book.docId">
1020
+ <div v-if="index<8" class="d-block">
1021
+ <div class="hb-container-horizontal">
1022
+ <!-- hb-center-v, not a magic margin-top: centres the chevron on
1023
+ the row whatever the text size -->
1024
+ <span class="hb-center-v">
1025
+ <i class="bi bi-chevron-right ms-2 book-link-chevron"></i>
1026
+ </span>
1027
+ <span class="align-middle hb-container-expand">
1028
+ <a :href="book.docId" class="title" v-on:click="loadLibraryBook(book,false,$event)">{{book.title}}</a>
1029
+ </span>
1030
+
1031
+ </div>
1032
+ </div>
1033
+ </template>
1034
+ <div class="d-block" v-if="group.books.length>7">
1035
+ <div class="hb-container-horizontal">
1036
+ <span class="hb-center-v">
1037
+ <i class="bi bi-chevron-right ms-2 book-link-chevron"></i>
1038
+ </span>
1039
+ <span class="align-middle hb-container-expand">
1040
+ <a class="title" v-on:click="loadProductHome(group,true)" :aria-label="'View all ' + group.title + ' documentation'">More...</a>
1041
+ </span>
1042
+ </div>
1043
+ </div>
1044
+
1045
+ </div>
1046
+
1047
+ </div>
1048
+ </template>
1049
+
1050
+ </div>
1051
+ </div>
316
1052
  </div>
317
1053
 
318
- </div>
319
- <div class="document-body"></div>
1054
+
320
1055
  </div>
1056
+ </div>
1057
+
1058
+
1059
+ <!-- document view -->
1060
+ <!-- the left hand nav col -->
1061
+ <aside v-show="docApp.book.docId" class="DocSidebar hb-container-vertical" :class="{'open': docApp.bookNavOpen}">
1062
+
1063
+ <nav class="hdoc-nav hb-container-expand hb-scroll" id="DocSidebarNav" aria-label="Documentation navigation" tabindex="-1">
1064
+ <div v-for="(navSection,index) in docApp.navSections" v-bind:class="{'group':navSection.items, 'nogrouplink':!navSection.items}">
1065
+
1066
+ <!-- v-if/v-else, not two v-shows: these branches are mutually exclusive,
1067
+ so v-show rendered BOTH for every top-level nav entry and hid one. //-- SG -->
1068
+ <div v-if="!navSection.items">
1069
+ <a class="DocLink link noitems" v-bind:href="navSection.link" v-bind:target="navSection.target || ''" style="padding-left: 0px;">
1070
+ <span class="link-text">{{navSection.text}} <i v-if="navSection.target" class="bi bi-box-arrow-up-right"></i> </span>
1071
+ </a>
1072
+ </div>
321
1073
 
322
- <div v-if="docApp.error !== ''">
323
- <div class="alert alert-danger" role="alert">
324
- <h1>Book Navigation Render Failed!</h1>
325
- <p>This is likely due to invalid JSON in the <strong>hdocbook.json</strong> file in your book. Please fix the error below, then refresh your browser to reload the book.</p>
326
- <p><code>{{docApp.error}}</code></p>
1074
+ <!-- No :key="updateCounter" here any more. Changing that key destroyed and
1075
+ recreated the ENTIRE recursive nav tree on every navigation - measured at
1076
+ 1026 elements torn down and 1026 rebuilt, ~13ms, producing a tree identical
1077
+ to the one just discarded. It existed because `expand` used to be
1078
+ non-reactive; it is seeded reactively in renderNavigation() now, so
1079
+ expanding a group patches in place. //-- SG -->
1080
+ <div v-else>
1081
+ <nav-section-component v-bind:asection="navSection"></nav-section-component>
1082
+ </div>
327
1083
  </div>
328
- </div>
329
-
330
- </div>
331
-
1084
+ </nav>
1085
+
1086
+ </aside>
1087
+
1088
+ <div v-show="docApp.book.docId" class="DocContent hb-container-expand hb-scroll hb-container-vertical" id="DocContent" :class="docApp.layoutClass" tabindex="-1">
1089
+
1090
+ <div class="HTL-doc content-wrapper hb-container-expand" :class="{'hb-content-loading': docApp.contentLoading}">
1091
+
1092
+ <div class="injected-document-container hb-container-horizontal">
1093
+
1094
+
1095
+ <div class="injected-document-content">
1096
+ <div class="vue-document-body"></div>
1097
+ <div class="document-body"><!--[[HDOC-CONTENT]]--></div>
1098
+ <div class="document-footer" :class="{'hb-content-loading': docApp.contentLoading}">
1099
+ <div class="hb-center-v">
1100
+ <small>
1101
+ <ul class="ps-0 nav-bar-nav-list noeffects after-bullets overflow-ellipsis text-light-2">
1102
+ <li class="ps-0 mt-0 nav-bar-item" aria-label="Version">Version {{docApp.book.version}}</li>
1103
+ <li class="ps-0 mt-0 nav-bar-item" aria-label="Node/Build">Node {{docApp.node}} / {{docApp.build}}</li>
1104
+ </ul>
1105
+ </small>
1106
+ </div>
1107
+ </div>
1108
+ </div>
1109
+
1110
+ <div v-if="docApp.keepTocLayout" class="injected-document-toc">
1111
+ <div v-if="docApp.tableOfContents.length" class="panel-header">
1112
+ <span class="icon">
1113
+ <i class="bi bi-list-nested"></i>
1114
+ </span>
1115
+ <span class="title">In This Document</span>
1116
+ </div>
1117
+ <div v-if="docApp.tableOfContents.length" class="panel-content">
1118
+ <div v-for="tocItem in docApp.tableOfContents" :key="tocItem.href" v-bind:class="{'ms-3':tocItem.tagName==='H3'}">
1119
+ <a :href="tocItem.href">{{tocItem.eleText}}</a>
1120
+ </div>
1121
+ </div>
1122
+ </div>
332
1123
 
333
- <!-- table of contents -->
334
- <div v-if="docApp.keepTocLayout" class="injected-document-toc">
335
- <div v-if="docApp.tableOfContents.length" class="panel-header">
336
- <span class="icon">
337
- <i class="bi bi-list-ul"></i>
338
- </span>
339
- <span class="title">In This Document</span>
340
- </div>
341
- <div v-if="docApp.tableOfContents.length" class="panel-content">
342
- <div v-for="tocItem in docApp.tableOfContents"
343
- v-bind:class="{'ms-3':tocItem.tagName=='H3'}">
344
- <a :href="tocItem.href">{{tocItem.eleText}}</a>
1124
+ <!-- narrow-screen TOC: floating button + slide-in panel (shown below the inline-TOC breakpoint) -->
1125
+ <div v-if="docApp.keepTocLayout && docApp.tableOfContents.length" class="toc-mobile">
1126
+ <button type="button" class="toc-mobile-btn" v-on:click.stop="toggleTocMenu()" :aria-expanded="docApp.tocMobileOpen ? 'true' : 'false'" aria-controls="tocMobilePanel" aria-label="Toggle table of contents">
1127
+ <i class="bi" v-bind:class="docApp.tocMobileOpen ? 'bi-x-lg' : 'bi-list-nested'" aria-hidden="true"></i>
1128
+ </button>
1129
+ <div class="toc-mobile-backdrop" v-show="docApp.tocMobileOpen" v-on:click="closeTocMenu()"></div>
1130
+ <nav id="tocMobilePanel" class="toc-mobile-panel" v-bind:class="{open:docApp.tocMobileOpen}" aria-label="Table of contents">
1131
+ <div class="panel-header">
1132
+ <span class="icon">
1133
+ <i class="bi bi-list-nested" aria-hidden="true"></i>
1134
+ </span>
1135
+ <span class="title">In This Document</span>
1136
+ </div>
1137
+ <div class="panel-content">
1138
+ <div v-for="tocItem in docApp.tableOfContents" :key="tocItem.href" v-bind:class="{'ms-3':tocItem.tagName==='H3'}">
1139
+ <a :href="tocItem.href" v-on:click="closeTocMenu()">{{tocItem.eleText}}</a>
1140
+ </div>
1141
+ </div>
1142
+ </nav>
1143
+ </div>
345
1144
  </div>
346
1145
  </div>
347
1146
  </div>
1147
+
1148
+ </div>
1149
+ </div>
348
1150
 
349
- </div>
350
-
351
- <!-- show inline document links -->
352
- <div v-if="docApp.showInlineDocumentsMarkup" class="hb-container-vertical">
353
-
354
- <!-- the problem is the test server doesn't serve inline documents like the live/proper one ?? -->
355
- <div class="modal fade" id="inlineModal" tabindex="-1" role="dialog" aria-hidden="true">
356
- <div class="modal-dialog" role="document">
357
- <div class="modal-content">
358
- <iframe v-if="docApp.inlinePopupDocumentHref" v-bind:src="docApp.inlinePopupDocumentHref"></iframe>
359
- </div>
360
- </div>
1151
+
1152
+ <!-- Contributors modal. The trigger is NOT in this file - the SERVER-RENDERED article
1153
+ header emits <a data-bs-target="#contributersModal">N contributors</a> inside
1154
+ .document-body, so a grep of this tree finds no opener. Do not remove this as
1155
+ "dead"; it looked exactly that way once already. aria-labelledby points at the
1156
+ TITLE's id - the h1 must not reuse the dialog's own id. -->
1157
+ <div v-if="docApp.bookInfo && docApp.bookInfo.contributors && docApp.bookInfo.contributors.length" class="modal fade" id="contributersModal" tabindex="-1" aria-labelledby="contributersModalTitle" aria-hidden="true">
1158
+ <div class="modal-dialog modal-dialog-scrollable">
1159
+ <div class="modal-content">
1160
+ <div class="modal-header border-bottom">
1161
+ <h1 class="modal-title fs-5" id="contributersModalTitle">Contributors to this article</h1>
361
1162
  </div>
362
-
363
-
364
- <div class="p-4">
365
-
366
- <div class="document-header">
367
- <div class="document-title"><h1>Inline Help Documents (DOES NOT WORK) - NOTE DEV SERVER NEEDS _inline.html PROCESSING - NEED TO SPEAK TO GERRY</h1></div>
368
- </div>
369
-
370
- <div class="p-2">
371
- <div v-for="(navSectionItem,index) in docApp.inlineDocumentItems">
372
-
373
- <a class="DocLink link noitems" href="#" v-on:click="popupInlineDocument($event,navSectionItem)" style="padding-left: 0px;">
374
- <span class="link-text"><i v-if="navSectionItem.draft" class="bi bi-exclamation-triangle-fill text-danger" title="This page is set to draft."></i><i class="bi bi-files-alt text-warning"></i> {{navSectionItem.text}}</span>
1163
+ <div class="modal-body">
1164
+ <ul class="border rounder is-vertically-scrollable">
1165
+ <li v-for="(person,$index) in docApp.bookInfo.contributors" :key="person.html_url || person.name" class="display-flex p-2 border-bottom">
1166
+ <a aria-hidden="true" class="rounded-circle" tabindex="-1" v-bind:href="person.html_url" v-bind:title="person.name">
1167
+ <div class="d-inline-block">
1168
+ <img width="32px" alt="" v-bind:src="person.avatar_url+'&size=32'">
1169
+ </div>
375
1170
  </a>
376
-
377
- </div>
378
- </div>
1171
+ <a class="align-self-center padding-left-xs ms-2" v-bind:href="person.html_url">{{person.name}}</a>
1172
+ </li>
1173
+ </ul>
379
1174
  </div>
380
1175
  </div>
381
-
382
-
383
1176
  </div>
384
1177
  </div>
385
1178
 
386
- </div>
387
-
388
- </div> <!-- <div class="main hb-vertical-expand"> -->
389
-
390
-
391
-
392
- </body>
393
- <script>
394
-
395
- //-- we load these like this as we have set the BASE location of the document - so have to load these after doc
396
- loadCss([
397
- "css/theme-default/styles/fonts.css",
398
- "css/theme-default/styles/vars.css",
399
- "css/theme-default/styles/base.css",
400
- "css/theme-default/styles/htldoc.layouts.css",
401
-
402
- "css/theme-default/styles/components/htl-doc.css",
403
- "css/theme-default/styles/components/sidebar.css",
404
- "css/theme-default/styles/components/content.css",
405
- "css/theme-default/styles/components/custom-block.css",
406
- "css/theme-default/styles/components/api-doc.css",
407
-
408
- "js/highlightjs/styles/vs2015.css"
409
- ], function () {
410
- //-- required js + 3rd party libs
411
- setTimeout(function () {
412
- loadJS(["js/doc.hornbill.js", "js/highlightjs/highlight.pack.js", "js/highlightjs-badge.js"], function () {
413
- intialiseApp();
414
- });
415
- }, 50);
416
- });
1179
+ </div> <!-- <div vDocDevApp> -->
1180
+
417
1181
 
418
- </script>
419
1182
 
420
- </html>
1183
+ </body>
1184
+ </html>