pict-section-inlinedocumentation 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -13
- package/diagrams/four-levels-of-embeddedness.excalidraw +717 -0
- package/diagrams/four-levels-of-embeddedness.mmd +4 -0
- package/diagrams/four-levels-of-embeddedness.svg +2 -0
- package/docs/README.md +10 -19
- package/docs/_brand.json +18 -0
- package/docs/_cover.md +6 -0
- package/docs/_sidebar.md +8 -4
- package/docs/_version.json +3 -3
- package/docs/architecture.md +2 -44
- package/docs/diagrams/component-diagram.excalidraw +3546 -0
- package/docs/diagrams/component-diagram.mmd +42 -0
- package/docs/diagrams/component-diagram.svg +2 -0
- package/docs/diagrams/four-levels-of-embeddedness.excalidraw +717 -0
- package/docs/diagrams/four-levels-of-embeddedness.mmd +9 -0
- package/docs/diagrams/four-levels-of-embeddedness.svg +2 -0
- package/docs/diagrams/project-layout.excalidraw +6082 -0
- package/docs/diagrams/project-layout.mmd +25 -0
- package/docs/diagrams/project-layout.svg +2 -0
- package/docs/embedding-level2-routes.md +1 -1
- package/docs/embedding-level4-autogen.md +1 -1
- package/docs/examples/README.md +9 -0
- package/docs/examples/bookshop/README.md +582 -0
- package/docs/examples/bookshop/bookshop_example.js +4519 -0
- package/docs/examples/bookshop/index.html +236 -0
- package/docs/index.html +6 -7
- package/docs/overview.md +4 -4
- package/docs/reference.md +2 -27
- package/docs/retold-catalog.json +77 -178
- package/docs/retold-keyword-index.json +14134 -1917
- package/example_applications/basic/docs/_brand.json +18 -0
- package/example_applications/bookshop/docs/_brand.json +18 -0
- package/example_applications/bookshop/package.json +9 -1
- package/package.json +9 -9
- package/source/providers/Pict-Provider-InlineDocumentation.js +27 -3
- package/source/views/Pict-View-InlineDocumentation-Content.js +19 -15
- package/source/views/Pict-View-InlineDocumentation-Layout.js +115 -3
- package/source/views/Pict-View-InlineDocumentation-Nav.js +98 -17
- package/source/views/Pict-View-InlineDocumentation-TopicManager.js +18 -18
- package/docs/css/docuserve.css +0 -327
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
# Bookshop - Inline Contextual Documentation in Practice
|
|
2
|
+
|
|
3
|
+
<!-- docuserve:example-launch:start -->
|
|
4
|
+
> **[Launch the live app](examples/bookshop/index.html)** - runs in your browser, opens in a new tab.
|
|
5
|
+
<!-- docuserve:example-launch:end -->
|
|
6
|
+
|
|
7
|
+
A small e-commerce demo that puts the documentation **right next to the
|
|
8
|
+
UI it describes**. Browse a book catalog, click a book to open its
|
|
9
|
+
store page, and at every step the right-hand help panel knows which
|
|
10
|
+
route you're on and what to show. Hover an icon for a markdown
|
|
11
|
+
tooltip, press **F1** to toggle the whole panel, click a `?` button
|
|
12
|
+
next to a section header to jump to a specific topic - all backed by
|
|
13
|
+
one provider, one topics manifest, and a folder of plain markdown
|
|
14
|
+
files.
|
|
15
|
+
|
|
16
|
+
This is the reference example for **embedding levels 3 and 4** from
|
|
17
|
+
the inline-documentation module: route-mapped contextual help, plus
|
|
18
|
+
data-attribute-driven tooltips that resolve content from the same
|
|
19
|
+
topics manifest. The application code does no markdown parsing of its
|
|
20
|
+
own - the provider owns everything from file loading to tooltip
|
|
21
|
+
positioning.
|
|
22
|
+
|
|
23
|
+
## What it demonstrates
|
|
24
|
+
|
|
25
|
+
| Capability | Where you see it |
|
|
26
|
+
|------------|------------------|
|
|
27
|
+
| Inline-documentation provider registered as `Pict-InlineDocumentation` | `addProvider('Pict-InlineDocumentation', libInlineDocumentation.default_configuration, libInlineDocumentation)` |
|
|
28
|
+
| Topics manifest pre-loaded into AppData | `AppData.InlineDocumentation.Topics = _TopicsData` before `initializeDocumentation()` |
|
|
29
|
+
| Route-mapped help panel | `tmpDocProvider.navigateToRoute('/books/store/' + pBookID)` switches the topic in the help panel |
|
|
30
|
+
| `data-d-tooltip` attribute -> markdown tooltip | `<span data-d-tooltip="catalog-info" data-d-tooltip-icon></span>` - provider scans on render, attaches hover/focus tooltips |
|
|
31
|
+
| Topic-keyed help buttons | `tmpApp.showHelp('BOOKSHOP-STORE')` triggers a specific topic regardless of current route |
|
|
32
|
+
| F1 keyboard shortcut for the help panel | `document.addEventListener('keydown', ...)` in `_setupKeyboardShortcuts()` |
|
|
33
|
+
| `initializeDocumentation` with save/upload handlers | Demo wires `onSave`, `onTopicsSave`, `onImageUpload` callbacks the provider invokes |
|
|
34
|
+
| Edit-mode toggle | `tmpDocProvider.setEditEnabled(true)` lets the demo edit the topic markdown live |
|
|
35
|
+
| `loadTopicDocument(topicCode)` API | The application's `showHelp(topicCode)` reaches into the provider to swap topics on demand |
|
|
36
|
+
| Markdown-with-help-links | `[Book Catalog Help](help:book-list.md)` - `help:` URLs resolve via the documentation provider, not the browser |
|
|
37
|
+
| Wildcard route matching | `"Routes": ["/books/store/*"]` matches `/books/store/1`, `/books/store/2`, ... |
|
|
38
|
+
| Tooltips co-located with topics | Each topic record carries a `Tooltips: {...}` block that the provider auto-registers |
|
|
39
|
+
|
|
40
|
+
## Key files
|
|
41
|
+
|
|
42
|
+
- `Pict-Application-Bookshop.js` - the application. Registers the
|
|
43
|
+
inline-documentation provider, three views (BookList, Store,
|
|
44
|
+
HelpToggle), bootstraps `AppData.Bookshop` + `AppData.InlineDocumentation`,
|
|
45
|
+
calls `initializeDocumentation` with the demo save callbacks, wires
|
|
46
|
+
the F1 shortcut, and owns the `toggleHelp` / `showHelp` /
|
|
47
|
+
`showBook` / `showBookList` / `filterByGenre` navigation methods.
|
|
48
|
+
- `Pict-Application-Bookshop-Configuration.json` - the Pict
|
|
49
|
+
application stanza. Disables `AutoRenderMainViewportViewAfterInitialize`
|
|
50
|
+
so the application can do the initial render after the provider has
|
|
51
|
+
finished loading its topics.
|
|
52
|
+
- `data/BookshopData.json` - the books seed data
|
|
53
|
+
(`Title` / `Author` / `Price` / `Genre` / `Cover` / `Description` / `InStock`).
|
|
54
|
+
- `data/pict_documentation_topics.json` - the topics manifest. Five
|
|
55
|
+
topics, two of them carrying inline tooltip records that the
|
|
56
|
+
provider auto-registers.
|
|
57
|
+
- `docs/welcome.md`, `book-list.md`, `book-detail.md`, `store.md`,
|
|
58
|
+
`search-filter.md` - the topic content as plain markdown files. Each
|
|
59
|
+
referenced by `TopicHelpFilePath` in the manifest.
|
|
60
|
+
- `views/PictView-Bookshop-BookList.js` - grid of book cards with
|
|
61
|
+
genre filter. Sprinkles `data-d-tooltip="..."` and
|
|
62
|
+
`data-d-tooltip-icon` attributes everywhere; calls
|
|
63
|
+
`tmpDocProvider.scanTooltips()` after every render.
|
|
64
|
+
- `views/PictView-Bookshop-Store.js` - single-book store page. Same
|
|
65
|
+
pattern as BookList - `data-d-tooltip` attributes plus a `?` help
|
|
66
|
+
button that triggers `showHelp('BOOKSHOP-STORE')`.
|
|
67
|
+
- `views/PictView-Bookshop-HelpToggle.js` - a stub view for the
|
|
68
|
+
help-toggle slot, included for symmetry with future expansion.
|
|
69
|
+
- `html/index.html` - defines the layout shell: a header bar with the
|
|
70
|
+
help button, a content area, a slide-in help panel on the right, and
|
|
71
|
+
an F1 hint pinned to the bottom-right.
|
|
72
|
+
|
|
73
|
+
## The data model
|
|
74
|
+
|
|
75
|
+
Two AppData roots, populated in `onAfterInitializeAsync`:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
this.pict.AppData.Bookshop =
|
|
79
|
+
{
|
|
80
|
+
Books: _BookshopData.Books, // the catalog
|
|
81
|
+
CurrentBook: null, // selected book when in Store view
|
|
82
|
+
CurrentView: 'BookList', // 'BookList' or 'Store'
|
|
83
|
+
GenreFilter: '', // current genre filter, '' = show all
|
|
84
|
+
HelpVisible: false, // is the right-side panel open?
|
|
85
|
+
HelpTopicCode: 'BOOKSHOP-WELCOME' // initial topic
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.pict.AppData.InlineDocumentation = {};
|
|
89
|
+
this.pict.AppData.InlineDocumentation.Topics = _TopicsData;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The provider keys its state under `AppData.InlineDocumentation.*`. Pre-loading
|
|
93
|
+
`Topics` before calling `initializeDocumentation` is a stash-then-init
|
|
94
|
+
pattern: the provider picks the topics up out of AppData rather than
|
|
95
|
+
needing them in the constructor, which makes hot-reload and
|
|
96
|
+
edit-mode-driven topic changes trivial.
|
|
97
|
+
|
|
98
|
+
The five-topic manifest (`pict_documentation_topics.json`) is the
|
|
99
|
+
canonical shape:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"BOOKSHOP-STORE": {
|
|
104
|
+
"TopicCode": "BOOKSHOP-STORE",
|
|
105
|
+
"TopicHelpFilePath": "store.md",
|
|
106
|
+
"TopicTitle": "The Store Page",
|
|
107
|
+
"Routes": ["/books/store/*"],
|
|
108
|
+
"Tooltips": {
|
|
109
|
+
"genre-badge": { "Content": "Books are categorized by genre to help you find similar titles.\n\nSee the [full catalog](help:book-list.md) to browse by genre." },
|
|
110
|
+
"store-price": { "Content": "The listed price is the final price in **USD**, tax included.\n\n[Pricing details](help:store.md#pricing)" }
|
|
111
|
+
// ... three more
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
// ... four more topics
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`Routes` is an array of route patterns the topic matches; `*` is a
|
|
119
|
+
wildcard. `Tooltips` is a `data-d-tooltip` -> `{Content}` map that the
|
|
120
|
+
provider auto-registers when the topic loads.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Feature 1 - Initializing the documentation provider
|
|
125
|
+
|
|
126
|
+
The application's constructor adds the provider with its default
|
|
127
|
+
configuration. The actual initialisation happens in
|
|
128
|
+
`onAfterInitializeAsync`, where the demo wires three callbacks the
|
|
129
|
+
provider invokes:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
|
|
133
|
+
tmpDocProvider.initializeDocumentation(
|
|
134
|
+
{
|
|
135
|
+
DocsBaseURL: 'docs/',
|
|
136
|
+
onSave: (pSaveData, fSaveCallback) =>
|
|
137
|
+
{
|
|
138
|
+
// Demo save handler - log to console
|
|
139
|
+
this.log.info(`Bookshop: Saving document [${pSaveData.Path}] (${pSaveData.Content.length} chars)`);
|
|
140
|
+
// In a real app, this would PUT to an API
|
|
141
|
+
return fSaveCallback(null);
|
|
142
|
+
},
|
|
143
|
+
onTopicsSave: (pTopics, fSaveCallback) =>
|
|
144
|
+
{
|
|
145
|
+
// Demo topics save handler - log to console
|
|
146
|
+
this.log.info(`Bookshop: Saving topics (${Object.keys(pTopics).length} topics)`);
|
|
147
|
+
// In a real app, this would PUT to an API
|
|
148
|
+
return fSaveCallback(null);
|
|
149
|
+
},
|
|
150
|
+
onImageUpload: (pFile, pDocumentPath, fCallback) =>
|
|
151
|
+
{
|
|
152
|
+
// Demo image upload handler - log to console
|
|
153
|
+
this.log.info(`Bookshop: Image upload [${pFile.name}] (${pFile.size} bytes) for document [${pDocumentPath}]`);
|
|
154
|
+
// In a real app, this would POST the file to a server
|
|
155
|
+
return fCallback('Demo mode: no upload server configured');
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
() =>
|
|
159
|
+
{
|
|
160
|
+
// Enable edit mode for demo
|
|
161
|
+
tmpDocProvider.setEditEnabled(true);
|
|
162
|
+
|
|
163
|
+
// Render the main book list
|
|
164
|
+
this.pict.views['Bookshop-BookList'].render();
|
|
165
|
+
|
|
166
|
+
// Load help for the initial route
|
|
167
|
+
tmpDocProvider.navigateToRoute('/books');
|
|
168
|
+
|
|
169
|
+
// Set up F1 keyboard listener
|
|
170
|
+
this._setupKeyboardShortcuts();
|
|
171
|
+
|
|
172
|
+
return super.onAfterInitializeAsync(fCallback);
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Three things happen in the initialisation callback:
|
|
177
|
+
|
|
178
|
+
1. **`setEditEnabled(true)`** - switches the help panel into edit mode,
|
|
179
|
+
so clicking the **Edit** button in the panel reveals a markdown
|
|
180
|
+
editor for the current topic. The `onSave` callback fires on Save;
|
|
181
|
+
the demo just logs to console.
|
|
182
|
+
2. **`navigateToRoute('/books')`** - primes the help panel with the
|
|
183
|
+
topic that matches the initial route. This is how route-aware help
|
|
184
|
+
works: the application tells the provider what URL the user is on;
|
|
185
|
+
the provider looks up the matching topic from the manifest's
|
|
186
|
+
`Routes` arrays.
|
|
187
|
+
3. **`_setupKeyboardShortcuts()`** - registers a `keydown` listener for
|
|
188
|
+
the F1 key. The legitimate exception to the no-`addEventListener`
|
|
189
|
+
rule: this is a window-level shortcut, not a per-view re-rendered
|
|
190
|
+
element.
|
|
191
|
+
|
|
192
|
+
The initial render only happens *inside* this callback because the
|
|
193
|
+
provider's initialisation is async (it loads markdown files via fetch
|
|
194
|
+
in real deployments). Rendering the BookList view earlier would risk
|
|
195
|
+
the help panel being empty when the user first sees the page.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Feature 2 - Topics manifest with route mapping
|
|
200
|
+
|
|
201
|
+
The manifest is the single source of truth for *which topic shows
|
|
202
|
+
when*. Each topic has a `Routes` array:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
"BOOKSHOP-WELCOME": {
|
|
206
|
+
"TopicCode": "BOOKSHOP-WELCOME",
|
|
207
|
+
"TopicHelpFilePath": "welcome.md",
|
|
208
|
+
"TopicTitle": "Welcome to the Bookshop",
|
|
209
|
+
"Routes": ["/"]
|
|
210
|
+
},
|
|
211
|
+
"BOOKSHOP-BOOKLIST": {
|
|
212
|
+
"TopicCode": "BOOKSHOP-BOOKLIST",
|
|
213
|
+
"TopicHelpFilePath": "book-list.md",
|
|
214
|
+
"TopicTitle": "Browsing the Book Catalog",
|
|
215
|
+
"Routes": ["/books", "/books/catalog"]
|
|
216
|
+
},
|
|
217
|
+
"BOOKSHOP-STORE": {
|
|
218
|
+
"TopicCode": "BOOKSHOP-STORE",
|
|
219
|
+
"TopicHelpFilePath": "store.md",
|
|
220
|
+
"TopicTitle": "The Store Page",
|
|
221
|
+
"Routes": ["/books/store/*"]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`Routes: ["/"]` is the literal root; `["/books", "/books/catalog"]` is
|
|
226
|
+
two aliases for the same topic; `["/books/store/*"]` is a wildcard
|
|
227
|
+
that matches every store page regardless of the book id.
|
|
228
|
+
|
|
229
|
+
The application calls `navigateToRoute(...)` whenever it changes
|
|
230
|
+
"page":
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
showBook(pBookID)
|
|
234
|
+
{
|
|
235
|
+
let tmpState = this.pict.AppData.Bookshop;
|
|
236
|
+
let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < tmpState.Books.length; i++)
|
|
239
|
+
{
|
|
240
|
+
if (tmpState.Books[i].IDBook === pBookID)
|
|
241
|
+
{
|
|
242
|
+
tmpState.CurrentBook = tmpState.Books[i];
|
|
243
|
+
tmpState.CurrentView = 'Store';
|
|
244
|
+
this.pict.views['Bookshop-Store'].render();
|
|
245
|
+
|
|
246
|
+
// Update help for the store route
|
|
247
|
+
tmpDocProvider.navigateToRoute('/books/store/' + pBookID);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The route is fabricated - the bookshop is a single-page app with no
|
|
255
|
+
real URL changes. The route is just a string the application invents
|
|
256
|
+
to tell the documentation provider *what context* the user is in. In a
|
|
257
|
+
router-driven app the same call lives in the router's `onRouteChange`
|
|
258
|
+
callback.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Feature 3 - `data-d-tooltip` attributes for inline tooltips
|
|
263
|
+
|
|
264
|
+
The BookList view sprinkles tooltip attributes through its HTML:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
// Section header with help button
|
|
268
|
+
tmpHTML += '<div class="bookshop-section-header">';
|
|
269
|
+
tmpHTML += '<h2 class="bookshop-section-title" data-d-tooltip="catalog-title">Book Catalog</h2>';
|
|
270
|
+
tmpHTML += '<span data-d-tooltip="catalog-info" data-d-tooltip-icon></span>';
|
|
271
|
+
tmpHTML += '<button class="bookshop-help-btn" id="Bookshop-Help-BookList" title="Help: Book Catalog">?</button>';
|
|
272
|
+
tmpHTML += '</div>';
|
|
273
|
+
|
|
274
|
+
// Filter bar with help button
|
|
275
|
+
tmpHTML += '<div class="bookshop-filter-bar">';
|
|
276
|
+
tmpHTML += '<label data-d-tooltip="genre-filter">Genre:</label>';
|
|
277
|
+
// ...
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`data-d-tooltip="catalog-info"` is the tooltip key. The provider
|
|
281
|
+
matches it against the current topic's `Tooltips` block:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
"Tooltips": {
|
|
285
|
+
"catalog-title": { "Content": "Browse our full collection of books. Click any book to view details and purchase.\n\nSee [Book Catalog Help](help:book-list.md) for the full guide." },
|
|
286
|
+
"catalog-info": { "Content": "**Tip:** Use the genre filter to narrow results, or click a book card to visit its store page.\n\nLearn more: [Searching & Filtering](help:search-filter.md)" },
|
|
287
|
+
"genre-filter": { "Content": "Filter the catalog by literary genre. Select **All Genres** to see everything.\n\nFor advanced filtering tips, see [Search & Filter Guide](help:search-filter.md)." }
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`data-d-tooltip-icon` (no value) tells the provider to **render a
|
|
292
|
+
small info icon into that span**. Elements without `data-d-tooltip-icon`
|
|
293
|
+
just gain hover behaviour on the existing content. The provider scans
|
|
294
|
+
on demand:
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
tmpContainer.innerHTML = tmpHTML;
|
|
298
|
+
|
|
299
|
+
// Wire click handlers
|
|
300
|
+
this._wireHandlers(tmpContainer);
|
|
301
|
+
|
|
302
|
+
// Scan for tooltip placeholders
|
|
303
|
+
let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
|
|
304
|
+
if (tmpDocProvider)
|
|
305
|
+
{
|
|
306
|
+
tmpDocProvider.scanTooltips();
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Calling `scanTooltips()` after every render is the contract - the
|
|
311
|
+
provider walks the DOM, finds every `data-d-tooltip` attribute, and
|
|
312
|
+
re-attaches its hover/focus tooltips. New elements added by a
|
|
313
|
+
re-render are picked up; orphaned references are cleaned up. The
|
|
314
|
+
provider also installs a MutationObserver as a fallback so
|
|
315
|
+
dynamically-injected nodes pick up tooltips even without an explicit
|
|
316
|
+
scan call.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Feature 4 - Topic-keyed help buttons
|
|
321
|
+
|
|
322
|
+
The `?` button next to each section header bypasses route mapping and
|
|
323
|
+
jumps straight to a specific topic:
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
let tmpHelpBookList = pContainer.querySelector('#Bookshop-Help-BookList');
|
|
327
|
+
if (tmpHelpBookList)
|
|
328
|
+
{
|
|
329
|
+
tmpHelpBookList.addEventListener('click', (pEvent) =>
|
|
330
|
+
{
|
|
331
|
+
pEvent.stopPropagation();
|
|
332
|
+
if (tmpApp)
|
|
333
|
+
{
|
|
334
|
+
tmpApp.showHelp('BOOKSHOP-BOOKLIST');
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The application's `showHelp` opens the panel (if it isn't already
|
|
341
|
+
open) and calls the provider's `loadTopicDocument` with the topic
|
|
342
|
+
code:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
showHelp(pTopicCode)
|
|
346
|
+
{
|
|
347
|
+
let tmpState = this.pict.AppData.Bookshop;
|
|
348
|
+
let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
|
|
349
|
+
|
|
350
|
+
tmpState.HelpTopicCode = pTopicCode;
|
|
351
|
+
|
|
352
|
+
// Ensure help panel is visible
|
|
353
|
+
if (!tmpState.HelpVisible)
|
|
354
|
+
{
|
|
355
|
+
tmpState.HelpVisible = true;
|
|
356
|
+
let tmpHelpPanel = document.getElementById('Bookshop-Help-Panel');
|
|
357
|
+
let tmpContentArea = document.getElementById('Bookshop-Content-Area');
|
|
358
|
+
if (tmpHelpPanel) { tmpHelpPanel.classList.add('visible'); }
|
|
359
|
+
if (tmpContentArea) { tmpContentArea.classList.add('help-open'); }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Load the topic document
|
|
363
|
+
tmpDocProvider.loadTopicDocument(pTopicCode);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`loadTopicDocument(pTopicCode)` is the provider's "show this exact
|
|
368
|
+
topic" API. It loads the markdown file declared in `TopicHelpFilePath`,
|
|
369
|
+
renders it into the help panel, and **registers any `Tooltips` block
|
|
370
|
+
attached to that topic** so they activate for the current view.
|
|
371
|
+
|
|
372
|
+
`navigateToRoute('...')` is the "show whatever matches this route"
|
|
373
|
+
API. Use it when the application changes context. `loadTopicDocument`
|
|
374
|
+
is for explicit user requests - clicking a `?` button, opening a
|
|
375
|
+
"how do I..." link, restoring a deep-linked help URL.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Feature 5 - F1 keyboard shortcut
|
|
380
|
+
|
|
381
|
+
F1 is the universal "help me" key. The application registers it once
|
|
382
|
+
at boot:
|
|
383
|
+
|
|
384
|
+
```js
|
|
385
|
+
_setupKeyboardShortcuts()
|
|
386
|
+
{
|
|
387
|
+
if (typeof document === 'undefined')
|
|
388
|
+
{
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
document.addEventListener('keydown', (pEvent) =>
|
|
393
|
+
{
|
|
394
|
+
if (pEvent.key === 'F1')
|
|
395
|
+
{
|
|
396
|
+
pEvent.preventDefault();
|
|
397
|
+
this.toggleHelp();
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
`toggleHelp()` flips `AppData.Bookshop.HelpVisible` and toggles the
|
|
404
|
+
`visible` class on the panel + the `help-open` class on the content
|
|
405
|
+
area:
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
toggleHelp()
|
|
409
|
+
{
|
|
410
|
+
let tmpState = this.pict.AppData.Bookshop;
|
|
411
|
+
tmpState.HelpVisible = !tmpState.HelpVisible;
|
|
412
|
+
|
|
413
|
+
let tmpHelpPanel = document.getElementById('Bookshop-Help-Panel');
|
|
414
|
+
let tmpContentArea = document.getElementById('Bookshop-Content-Area');
|
|
415
|
+
|
|
416
|
+
if (tmpState.HelpVisible)
|
|
417
|
+
{
|
|
418
|
+
if (tmpHelpPanel) { tmpHelpPanel.classList.add('visible'); }
|
|
419
|
+
if (tmpContentArea) { tmpContentArea.classList.add('help-open'); }
|
|
420
|
+
}
|
|
421
|
+
else
|
|
422
|
+
{
|
|
423
|
+
if (tmpHelpPanel) { tmpHelpPanel.classList.remove('visible'); }
|
|
424
|
+
if (tmpContentArea) { tmpContentArea.classList.remove('help-open'); }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The slide-in animation lives in CSS (the panel's `transform: translateX(100%)`
|
|
430
|
+
toggles to `translateX(0)` on `.visible`); the content area's
|
|
431
|
+
`margin-right` animates from `0` to `380px` on `.help-open` to make
|
|
432
|
+
room.
|
|
433
|
+
|
|
434
|
+
Window-level keyboard handlers and `MutationObserver` callbacks are
|
|
435
|
+
the explicit exceptions to Pict's "no `addEventListener`" rule. There's
|
|
436
|
+
no inline-handler equivalent for keyboard shortcuts on the document,
|
|
437
|
+
and the listener is installed once at boot - it does not get torn
|
|
438
|
+
down by view re-renders.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Feature 6 - Markdown topics with `help:` links
|
|
443
|
+
|
|
444
|
+
Each topic's markdown file is plain CommonMark, with one extension:
|
|
445
|
+
the `help:` URL scheme is intercepted by the provider so cross-topic
|
|
446
|
+
links don't navigate the browser.
|
|
447
|
+
|
|
448
|
+
```markdown
|
|
449
|
+
# Browsing the Book Catalog
|
|
450
|
+
|
|
451
|
+
The catalog shows every book currently available, with cover, title,
|
|
452
|
+
author, and price. Click any card to open the [store page for that
|
|
453
|
+
book](help:store.md).
|
|
454
|
+
|
|
455
|
+
## Filtering by Genre
|
|
456
|
+
|
|
457
|
+
The genre filter narrows the list. Pick **All Genres** to clear it.
|
|
458
|
+
For more advanced searches see the [Search & Filter guide](help:search-filter.md).
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
`[Search & Filter guide](help:search-filter.md)` resolves to a click
|
|
462
|
+
that calls `loadTopicDocument(...)` for the topic whose
|
|
463
|
+
`TopicHelpFilePath` is `search-filter.md`. The browser stays on the
|
|
464
|
+
same page; the help panel swaps content.
|
|
465
|
+
|
|
466
|
+
Tooltip content uses the same syntax - every `Tooltips` entry's
|
|
467
|
+
`Content` field is markdown that may contain `help:` links, so a hover
|
|
468
|
+
tooltip can deep-link into a long-form topic.
|
|
469
|
+
|
|
470
|
+
The demo's `DocsBaseURL: 'docs/'` is the directory prefix the
|
|
471
|
+
provider prepends to every `TopicHelpFilePath`. In production this
|
|
472
|
+
typically points at a server endpoint or a CDN; locally it's just
|
|
473
|
+
the `docs/` folder copied next to the bundle.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Feature 7 - Edit-mode demo with save callbacks
|
|
478
|
+
|
|
479
|
+
`setEditEnabled(true)` turns the help panel's content area into an
|
|
480
|
+
editable region. The user can edit a topic's markdown, click Save, and
|
|
481
|
+
the provider invokes the `onSave` callback the host supplied:
|
|
482
|
+
|
|
483
|
+
```js
|
|
484
|
+
onSave: (pSaveData, fSaveCallback) =>
|
|
485
|
+
{
|
|
486
|
+
// Demo save handler - log to console
|
|
487
|
+
this.log.info(`Bookshop: Saving document [${pSaveData.Path}] (${pSaveData.Content.length} chars)`);
|
|
488
|
+
// In a real app, this would PUT to an API
|
|
489
|
+
return fSaveCallback(null);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
`pSaveData` is `{ Path, Content }`. The demo just logs and resolves
|
|
494
|
+
the callback with no error. In production the same callback typically
|
|
495
|
+
does:
|
|
496
|
+
|
|
497
|
+
```js
|
|
498
|
+
return fetch('/api/docs/' + pSaveData.Path, { method: 'PUT', body: pSaveData.Content })
|
|
499
|
+
.then(() => fSaveCallback(null))
|
|
500
|
+
.catch((e) => fSaveCallback(e));
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
`onTopicsSave` fires when the user edits the topics manifest itself
|
|
504
|
+
(via the topic-manager view); `onImageUpload` fires when a user drops
|
|
505
|
+
an image into the markdown editor. All three callbacks are async; the
|
|
506
|
+
provider waits for the host's `fSaveCallback(err)` before declaring
|
|
507
|
+
the save complete. The demo's `onImageUpload` deliberately errors out
|
|
508
|
+
with `'Demo mode: no upload server configured'` to show the panel's
|
|
509
|
+
inline error state.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Running the example
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
cd example_applications/bookshop
|
|
517
|
+
npm install
|
|
518
|
+
npm run build # quack build -> quack copy -> dist/
|
|
519
|
+
# Open dist/index.html in a browser, or serve dist/ statically:
|
|
520
|
+
# cd dist && python3 -m http.server 8080
|
|
521
|
+
# visit http://localhost:8080
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
The build's `copyFiles` is what stages the markdown documents and the
|
|
525
|
+
topics manifest into `dist/docs/`, where the provider's
|
|
526
|
+
`DocsBaseURL: 'docs/'` finds them at runtime.
|
|
527
|
+
|
|
528
|
+
## Things to try in the running app
|
|
529
|
+
|
|
530
|
+
- **Hover the `?` icons next to "Book Catalog" and "Genre"** - markdown
|
|
531
|
+
tooltips appear, with `help:` links you can click to jump to the
|
|
532
|
+
full topic.
|
|
533
|
+
- **Click the help button in the header** (top-right `?` Help) - the
|
|
534
|
+
panel slides in from the right. The content area shrinks to make
|
|
535
|
+
room.
|
|
536
|
+
- **Press F1** - same thing. F1 again to close.
|
|
537
|
+
- **Click any book card** - the store page loads, *and* the help
|
|
538
|
+
panel content swaps to the store topic because
|
|
539
|
+
`navigateToRoute('/books/store/' + id)` matched the wildcard route.
|
|
540
|
+
- **Click the `?` button next to a section header** -
|
|
541
|
+
`showHelp('BOOKSHOP-...')` opens the panel (if closed) and loads the
|
|
542
|
+
specific topic regardless of current route.
|
|
543
|
+
- **Hover the price** on the store page - a tooltip explains
|
|
544
|
+
pricing and links to the longer topic.
|
|
545
|
+
- **Click the Edit button in the help panel** - the content area
|
|
546
|
+
becomes editable. Make a change, click Save, watch the console log
|
|
547
|
+
the `onSave` callback firing with the document path and content
|
|
548
|
+
length.
|
|
549
|
+
- **Filter by Genre** - the catalog updates immediately. The help
|
|
550
|
+
panel doesn't change because the route is still `/books`.
|
|
551
|
+
|
|
552
|
+
## Takeaways
|
|
553
|
+
|
|
554
|
+
1. **The application doesn't render documentation.** It tells the
|
|
555
|
+
provider what route the user is on and what tooltip keys are
|
|
556
|
+
relevant; the provider does everything from markdown parsing to
|
|
557
|
+
hover/focus event wiring to error rendering.
|
|
558
|
+
2. **The topics manifest is the contract.** Route mapping, tooltip
|
|
559
|
+
binding, and topic content all flow from the same JSON file. Adding
|
|
560
|
+
a new section is "add a topic with its `Routes` and `Tooltips`"; no
|
|
561
|
+
code change.
|
|
562
|
+
3. **`scanTooltips()` after every render is the rule.** Re-renders
|
|
563
|
+
wipe the DOM; the provider needs to know to re-attach handlers.
|
|
564
|
+
Calling it is cheap (it's idempotent) and forgetting it manifests
|
|
565
|
+
as "tooltips work the first time then go silent after a filter."
|
|
566
|
+
4. **`navigateToRoute` and `loadTopicDocument` are different verbs.**
|
|
567
|
+
Route changes follow user navigation; explicit help buttons override
|
|
568
|
+
the route for targeted jumps. Both end up calling the same provider
|
|
569
|
+
internals; the distinction is who's driving.
|
|
570
|
+
5. **Save callbacks let the host own persistence.** The provider
|
|
571
|
+
reads, parses, edits, and re-renders markdown - but a host that
|
|
572
|
+
keeps its docs on a CMS, in a Git repo, or in S3 plugs into the
|
|
573
|
+
exact same callbacks. No subclassing required.
|
|
574
|
+
|
|
575
|
+
## Related documentation
|
|
576
|
+
|
|
577
|
+
- [Overview](../../overview.md) - what inline documentation does and why
|
|
578
|
+
- [Quickstart](../../quickstart.md) - minimum-viable wiring
|
|
579
|
+
- [Embedding Level 3 - Hand-Authored Tooltips](../../embedding-level3-tooltips.md) - the `data-d-tooltip` pattern the bookshop uses
|
|
580
|
+
- [Embedding Level 4 - Auto-Generated Tooltips](../../embedding-level4-autogen.md) - the next step after this example
|
|
581
|
+
- [API Reference](../../api-reference.md) - every provider method (`initializeDocumentation`, `navigateToRoute`, `loadTopicDocument`, `scanTooltips`, `setEditEnabled`, ...)
|
|
582
|
+
- [Architecture](../../architecture.md) - how the provider, layout view, and topics manifest fit together
|