pict-docuserve 1.3.3 → 1.3.4
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/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +267 -458
- package/dist/pict-docuserve.js +73 -17
- package/dist/pict-docuserve.js.map +1 -1
- package/dist/pict-docuserve.min.js +2 -2
- package/dist/pict-docuserve.min.js.map +1 -1
- package/package.json +3 -3
- package/source/Pict-Application-Docuserve.js +2 -2
- package/source/cli/Docuserve-CLI-Program.js +2 -1
- package/source/cli/commands/Docuserve-Command-CheckLinks.js +428 -0
- package/source/cli/commands/Docuserve-Command-StageExamples.js +32 -27
- package/source/providers/Pict-Provider-Docuserve-Documentation.js +150 -15
- package/source/views/PictView-Docuserve-Splash.js +126 -0
|
@@ -44,27 +44,44 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
44
44
|
{
|
|
45
45
|
return (pHref, pLinkText) =>
|
|
46
46
|
{
|
|
47
|
+
let tmpHref = String(pHref || '');
|
|
48
|
+
let tmpIsModuleMode = (this.getDocsMode() === 'module');
|
|
49
|
+
|
|
47
50
|
// Built example applications (and other static .html pages) are
|
|
48
51
|
// served as plain files alongside the docs. Link straight to
|
|
49
|
-
// them in a new tab rather than SPA-routing through #/page/.
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
// them in a new tab rather than SPA-routing through #/page/. In
|
|
53
|
+
// module mode the href is resolved against the current
|
|
54
|
+
// document's directory, exactly the way a .md link is — so every
|
|
55
|
+
// relative link in the docs shares one base.
|
|
56
|
+
if (!tmpHref.match(/^[a-z][a-z0-9+.-]*:/i) && tmpHref.match(/\.html($|[?#])/i))
|
|
57
|
+
{
|
|
58
|
+
let tmpAssetHref = tmpIsModuleMode ? this._toModuleAssetHref(tmpHref, pCurrentDocPath) : tmpHref;
|
|
59
|
+
return { href: tmpAssetHref, target: '_blank', rel: 'noopener' };
|
|
55
60
|
}
|
|
56
61
|
// Convert internal doc links to hash routes
|
|
57
|
-
if (
|
|
62
|
+
if (tmpHref.match(/^\//) || tmpHref.match(/^[^:]+\.md/))
|
|
58
63
|
{
|
|
59
|
-
let tmpRoute = this.convertDocLink(
|
|
64
|
+
let tmpRoute = this.convertDocLink(tmpHref, pCurrentGroup, pCurrentModule, pCurrentDocPath);
|
|
60
65
|
return { href: tmpRoute };
|
|
61
66
|
}
|
|
62
67
|
// Check if this is a GitHub URL that matches a catalog module
|
|
63
|
-
let tmpCatalogRoute = this.resolveGitHubURLToRoute(
|
|
68
|
+
let tmpCatalogRoute = this.resolveGitHubURLToRoute(tmpHref);
|
|
64
69
|
if (tmpCatalogRoute)
|
|
65
70
|
{
|
|
66
71
|
return { href: tmpCatalogRoute };
|
|
67
72
|
}
|
|
73
|
+
// Module mode: a remaining relative link (a directory, a media
|
|
74
|
+
// file, a .json) is resolved against the current document's
|
|
75
|
+
// directory too, so it shares the one base every other link uses
|
|
76
|
+
// rather than silently falling back to the docs root.
|
|
77
|
+
if (tmpIsModuleMode
|
|
78
|
+
&& tmpHref
|
|
79
|
+
&& (tmpHref.charAt(0) !== '#')
|
|
80
|
+
&& (tmpHref.indexOf('//') !== 0)
|
|
81
|
+
&& !tmpHref.match(/^[a-z][a-z0-9+.-]*:/i))
|
|
82
|
+
{
|
|
83
|
+
return { href: this._toModuleAssetHref(tmpHref, pCurrentDocPath) };
|
|
84
|
+
}
|
|
68
85
|
// Use default behavior for other links
|
|
69
86
|
return null;
|
|
70
87
|
};
|
|
@@ -316,15 +333,38 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
316
333
|
Tagline: '',
|
|
317
334
|
Description: '',
|
|
318
335
|
Highlights: [],
|
|
319
|
-
Actions: []
|
|
336
|
+
Actions: [],
|
|
337
|
+
ExamplesMarkdown: ''
|
|
320
338
|
};
|
|
321
339
|
|
|
322
340
|
let tmpLines = pMarkdown.split('\n');
|
|
341
|
+
let tmpInExamples = false;
|
|
323
342
|
|
|
324
343
|
for (let i = 0; i < tmpLines.length; i++)
|
|
325
344
|
{
|
|
326
345
|
let tmpLine = tmpLines[i].trim();
|
|
327
346
|
|
|
347
|
+
// Generated examples region — collected verbatim for the splash's
|
|
348
|
+
// "Interactive Examples" section, never parsed as cover fields.
|
|
349
|
+
if (tmpLine === '<!-- docuserve:examples:start -->')
|
|
350
|
+
{
|
|
351
|
+
tmpInExamples = true;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (tmpLine === '<!-- docuserve:examples:end -->')
|
|
355
|
+
{
|
|
356
|
+
tmpInExamples = false;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (tmpInExamples)
|
|
360
|
+
{
|
|
361
|
+
if (tmpLine)
|
|
362
|
+
{
|
|
363
|
+
tmpCover.ExamplesMarkdown += (tmpCover.ExamplesMarkdown ? '\n' : '') + tmpLine;
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
328
368
|
if (!tmpLine)
|
|
329
369
|
{
|
|
330
370
|
continue;
|
|
@@ -783,14 +823,38 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
783
823
|
|
|
784
824
|
/**
|
|
785
825
|
* Module-mode link resolution: every internal documentation reference is
|
|
786
|
-
* a local page.
|
|
826
|
+
* a local page. Relative links (./sibling.md, ../other.md, bare names)
|
|
827
|
+
* resolve against the directory of the document that contains them — the
|
|
828
|
+
* way the links read on disk — while a /-rooted href resolves against the
|
|
829
|
+
* docs root. "." and ".." segments are collapsed; ".." is clamped at the
|
|
830
|
+
* docs root so a link can never escape above it.
|
|
787
831
|
*
|
|
788
832
|
* @param {string} pHref - The raw link href
|
|
833
|
+
* @param {string} [pCurrentDocPath] - Docs-root-relative path of the
|
|
834
|
+
* document the link lives in (e.g.
|
|
835
|
+
* "examples/gradebook/README.md"). Absent for root-level
|
|
836
|
+
* contexts such as the sidebar.
|
|
789
837
|
* @returns {string} A #/page/ hash route (#/Home for an empty path)
|
|
790
838
|
*/
|
|
791
|
-
_toModulePageRoute(pHref)
|
|
839
|
+
_toModulePageRoute(pHref, pCurrentDocPath)
|
|
792
840
|
{
|
|
793
|
-
let
|
|
841
|
+
let tmpHref = String(pHref || '').trim();
|
|
842
|
+
|
|
843
|
+
// A /-rooted href resolves against the docs root; every other href
|
|
844
|
+
// resolves against the current document's directory.
|
|
845
|
+
let tmpBaseDir = '';
|
|
846
|
+
if (tmpHref.charAt(0) === '/')
|
|
847
|
+
{
|
|
848
|
+
tmpHref = tmpHref.replace(/^\/+/, '');
|
|
849
|
+
}
|
|
850
|
+
else if (pCurrentDocPath)
|
|
851
|
+
{
|
|
852
|
+
let tmpDirParts = String(pCurrentDocPath).split('/');
|
|
853
|
+
tmpDirParts.pop();
|
|
854
|
+
tmpBaseDir = tmpDirParts.join('/');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
let tmpPath = this._resolveRelativeDocPath(tmpBaseDir, tmpHref);
|
|
794
858
|
if (!tmpPath)
|
|
795
859
|
{
|
|
796
860
|
return '#/Home';
|
|
@@ -798,6 +862,76 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
798
862
|
return '#/page/' + tmpPath.replace(/\.md$/i, '');
|
|
799
863
|
}
|
|
800
864
|
|
|
865
|
+
/**
|
|
866
|
+
* Resolve a relative href against a base directory, collapsing "." and
|
|
867
|
+
* ".." segments. ".." is clamped at the docs root — it can never escape
|
|
868
|
+
* above it. Both arguments are POSIX-style docs-root-relative paths.
|
|
869
|
+
*
|
|
870
|
+
* @param {string} pBaseDir - The directory the href is relative to.
|
|
871
|
+
* @param {string} pHref - The href to resolve.
|
|
872
|
+
* @returns {string} The resolved docs-root-relative path (no leading slash).
|
|
873
|
+
*/
|
|
874
|
+
_resolveRelativeDocPath(pBaseDir, pHref)
|
|
875
|
+
{
|
|
876
|
+
let tmpSegments = [];
|
|
877
|
+
let tmpCombined = (pBaseDir ? pBaseDir + '/' : '') + String(pHref || '');
|
|
878
|
+
let tmpParts = tmpCombined.split('/');
|
|
879
|
+
for (let i = 0; i < tmpParts.length; i++)
|
|
880
|
+
{
|
|
881
|
+
let tmpPart = tmpParts[i];
|
|
882
|
+
if ((tmpPart === '') || (tmpPart === '.'))
|
|
883
|
+
{
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (tmpPart === '..')
|
|
887
|
+
{
|
|
888
|
+
if (tmpSegments.length > 0)
|
|
889
|
+
{
|
|
890
|
+
tmpSegments.pop();
|
|
891
|
+
}
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
tmpSegments.push(tmpPart);
|
|
895
|
+
}
|
|
896
|
+
return tmpSegments.join('/');
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Module-mode resolution for a non-routed link — a built .html page, a
|
|
901
|
+
* media file, a directory. Resolves the href against the current
|
|
902
|
+
* document's directory (a /-rooted href against the docs root), the same
|
|
903
|
+
* way _toModulePageRoute resolves a .md link, and returns a plain
|
|
904
|
+
* docs-root-relative href. The browser resolves that href against the
|
|
905
|
+
* docs-root index.html, so it points at the right file from any page.
|
|
906
|
+
*
|
|
907
|
+
* @param {string} pHref - The raw link href
|
|
908
|
+
* @param {string} [pCurrentDocPath] - Docs-root-relative path of the
|
|
909
|
+
* document the link lives in.
|
|
910
|
+
* @returns {string} A docs-root-relative href.
|
|
911
|
+
*/
|
|
912
|
+
_toModuleAssetHref(pHref, pCurrentDocPath)
|
|
913
|
+
{
|
|
914
|
+
let tmpHref = String(pHref || '').trim();
|
|
915
|
+
if (!tmpHref)
|
|
916
|
+
{
|
|
917
|
+
return tmpHref;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
let tmpBaseDir = '';
|
|
921
|
+
if (tmpHref.charAt(0) === '/')
|
|
922
|
+
{
|
|
923
|
+
tmpHref = tmpHref.replace(/^\/+/, '');
|
|
924
|
+
}
|
|
925
|
+
else if (pCurrentDocPath)
|
|
926
|
+
{
|
|
927
|
+
let tmpDirParts = String(pCurrentDocPath).split('/');
|
|
928
|
+
tmpDirParts.pop();
|
|
929
|
+
tmpBaseDir = tmpDirParts.join('/');
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return this._resolveRelativeDocPath(tmpBaseDir, tmpHref);
|
|
933
|
+
}
|
|
934
|
+
|
|
801
935
|
/**
|
|
802
936
|
* Check whether a group/module pair exists in the loaded catalog.
|
|
803
937
|
*
|
|
@@ -1661,10 +1795,11 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
1661
1795
|
*/
|
|
1662
1796
|
convertDocLink(pHref, pCurrentGroup, pCurrentModule, pCurrentDocPath)
|
|
1663
1797
|
{
|
|
1664
|
-
// Single-module docs site: every internal reference is a local page
|
|
1798
|
+
// Single-module docs site: every internal reference is a local page,
|
|
1799
|
+
// resolved relative to the current document's directory.
|
|
1665
1800
|
if (this.getDocsMode() === 'module')
|
|
1666
1801
|
{
|
|
1667
|
-
return this._toModulePageRoute(pHref);
|
|
1802
|
+
return this._toModulePageRoute(pHref, pCurrentDocPath);
|
|
1668
1803
|
}
|
|
1669
1804
|
|
|
1670
1805
|
// Strip leading ./ prefix for relative paths
|
|
@@ -111,6 +111,70 @@ const _ViewConfiguration =
|
|
|
111
111
|
border-color: var(--theme-color-brand-primary-hover, #236660);
|
|
112
112
|
color: var(--theme-color-brand-primary, #2E7D74);
|
|
113
113
|
}
|
|
114
|
+
.docuserve-splash-examples {
|
|
115
|
+
max-width: 900px;
|
|
116
|
+
width: 100%;
|
|
117
|
+
margin-bottom: 2.5em;
|
|
118
|
+
}
|
|
119
|
+
/* No staged examples — collapse the section entirely. */
|
|
120
|
+
.docuserve-splash-examples:empty {
|
|
121
|
+
display: none;
|
|
122
|
+
margin: 0;
|
|
123
|
+
}
|
|
124
|
+
.docuserve-splash-examples-heading {
|
|
125
|
+
font-size: 0.95em;
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
text-transform: uppercase;
|
|
128
|
+
letter-spacing: 0.08em;
|
|
129
|
+
color: var(--theme-color-text-muted, #8A7F72);
|
|
130
|
+
margin: 0 0 0.85em 0;
|
|
131
|
+
}
|
|
132
|
+
.docuserve-splash-examples table {
|
|
133
|
+
width: 100%;
|
|
134
|
+
border-collapse: collapse;
|
|
135
|
+
background: var(--theme-color-background-panel, #FFFFFF);
|
|
136
|
+
border: 1px solid var(--theme-color-border-default, #DDD6CA);
|
|
137
|
+
border-radius: 8px;
|
|
138
|
+
overflow: hidden;
|
|
139
|
+
}
|
|
140
|
+
.docuserve-splash-examples thead th {
|
|
141
|
+
text-align: left;
|
|
142
|
+
font-size: 0.72em;
|
|
143
|
+
font-weight: 700;
|
|
144
|
+
text-transform: uppercase;
|
|
145
|
+
letter-spacing: 0.06em;
|
|
146
|
+
color: var(--theme-color-text-muted, #8A7F72);
|
|
147
|
+
padding: 0.7em 1.1em;
|
|
148
|
+
background: var(--theme-color-background-tertiary, #F4EFE6);
|
|
149
|
+
}
|
|
150
|
+
.docuserve-splash-examples tbody td {
|
|
151
|
+
padding: 0.7em 1.1em;
|
|
152
|
+
border-top: 1px solid var(--theme-color-border-default, #DDD6CA);
|
|
153
|
+
font-size: 0.9em;
|
|
154
|
+
color: var(--theme-color-text-secondary, #5E5549);
|
|
155
|
+
text-align: left;
|
|
156
|
+
}
|
|
157
|
+
.docuserve-splash-examples tbody tr:hover td {
|
|
158
|
+
background: var(--theme-color-background-tertiary, #F4EFE6);
|
|
159
|
+
}
|
|
160
|
+
.docuserve-splash-examples a {
|
|
161
|
+
color: var(--theme-color-brand-primary, #2E7D74);
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
text-decoration: none;
|
|
164
|
+
}
|
|
165
|
+
.docuserve-splash-examples a:hover {
|
|
166
|
+
text-decoration: underline;
|
|
167
|
+
}
|
|
168
|
+
/* docs/README.md content rendered beneath the hero. */
|
|
169
|
+
.docuserve-splash-readme {
|
|
170
|
+
max-width: 820px;
|
|
171
|
+
margin: 0 auto;
|
|
172
|
+
padding: 3.5em 2em 5em 2em;
|
|
173
|
+
text-align: left;
|
|
174
|
+
}
|
|
175
|
+
.docuserve-splash-readme:empty {
|
|
176
|
+
display: none;
|
|
177
|
+
}
|
|
114
178
|
`,
|
|
115
179
|
|
|
116
180
|
Templates:
|
|
@@ -123,8 +187,10 @@ const _ViewConfiguration =
|
|
|
123
187
|
<div class="docuserve-splash-tagline" id="Docuserve-Splash-Tagline"></div>
|
|
124
188
|
<div class="docuserve-splash-description" id="Docuserve-Splash-Description"></div>
|
|
125
189
|
<div class="docuserve-splash-highlights" id="Docuserve-Splash-Highlights"></div>
|
|
190
|
+
<div class="docuserve-splash-examples" id="Docuserve-Splash-Examples"></div>
|
|
126
191
|
<div class="docuserve-splash-actions" id="Docuserve-Splash-Actions"></div>
|
|
127
192
|
</div>
|
|
193
|
+
<div class="docuserve-splash-readme" id="Docuserve-Splash-Readme"></div>
|
|
128
194
|
`
|
|
129
195
|
}
|
|
130
196
|
],
|
|
@@ -154,12 +220,17 @@ class DocusserveSplashView extends libPictView
|
|
|
154
220
|
if (tmpDocuserve.CoverLoaded && tmpDocuserve.Cover)
|
|
155
221
|
{
|
|
156
222
|
this.renderFromCover(tmpDocuserve.Cover);
|
|
223
|
+
this.renderExamples(tmpDocuserve.Cover);
|
|
157
224
|
}
|
|
158
225
|
else
|
|
159
226
|
{
|
|
160
227
|
this.renderFromCatalog(tmpDocuserve);
|
|
161
228
|
}
|
|
162
229
|
|
|
230
|
+
// Render docs/README.md beneath the hero — the splash fills the
|
|
231
|
+
// viewport above the fold, the README content follows on scroll.
|
|
232
|
+
this.renderReadme();
|
|
233
|
+
|
|
163
234
|
return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
|
|
164
235
|
}
|
|
165
236
|
|
|
@@ -270,6 +341,61 @@ class DocusserveSplashView extends libPictView
|
|
|
270
341
|
this.pict.ContentAssignment.assignContent('#Docuserve-Splash-Actions', '');
|
|
271
342
|
}
|
|
272
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Render the "Interactive Examples" section of the splash from the
|
|
346
|
+
* examples region of _cover.md. When the cover carries no examples the
|
|
347
|
+
* section is left empty — CSS collapses it — so it appears only when a
|
|
348
|
+
* module has staged interactive examples.
|
|
349
|
+
*
|
|
350
|
+
* @param {Object} pCover - The parsed cover data.
|
|
351
|
+
*/
|
|
352
|
+
renderExamples(pCover)
|
|
353
|
+
{
|
|
354
|
+
let tmpExamplesMarkdown = (pCover && pCover.ExamplesMarkdown) ? pCover.ExamplesMarkdown : '';
|
|
355
|
+
let tmpDocProvider = this.pict.providers['Docuserve-Documentation'];
|
|
356
|
+
|
|
357
|
+
if (!tmpExamplesMarkdown || !tmpDocProvider || !tmpDocProvider._ContentProvider)
|
|
358
|
+
{
|
|
359
|
+
this.pict.ContentAssignment.assignContent('#Docuserve-Splash-Examples', '');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let tmpLinkResolver = tmpDocProvider._createLinkResolver('', '', '');
|
|
364
|
+
let tmpExamplesHTML = tmpDocProvider._ContentProvider.parseMarkdown(tmpExamplesMarkdown, tmpLinkResolver);
|
|
365
|
+
this.pict.ContentAssignment.assignContent('#Docuserve-Splash-Examples',
|
|
366
|
+
'<h2 class="docuserve-splash-examples-heading">Interactive Examples</h2>' + tmpExamplesHTML);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Render docs/README.md beneath the splash hero. The landing page is the
|
|
371
|
+
* full-viewport splash above the fold and the module's README content on
|
|
372
|
+
* scroll. A missing or unreadable README simply leaves the section empty.
|
|
373
|
+
*/
|
|
374
|
+
renderReadme()
|
|
375
|
+
{
|
|
376
|
+
let tmpDocProvider = this.pict.providers['Docuserve-Documentation'];
|
|
377
|
+
let tmpDocsBase = this.pict.AppData.Docuserve.DocsBaseURL || '';
|
|
378
|
+
|
|
379
|
+
fetch(tmpDocsBase + 'README.md')
|
|
380
|
+
.then((pResponse) => (pResponse.ok ? pResponse.text() : null))
|
|
381
|
+
.then((pMarkdown) =>
|
|
382
|
+
{
|
|
383
|
+
if (!pMarkdown || !tmpDocProvider || !tmpDocProvider._ContentProvider)
|
|
384
|
+
{
|
|
385
|
+
this.pict.ContentAssignment.assignContent('#Docuserve-Splash-Readme', '');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
let tmpLinkResolver = tmpDocProvider._createLinkResolver('', '', 'README.md');
|
|
389
|
+
let tmpImageResolver = tmpDocProvider._createImageResolver(tmpDocsBase + 'README.md');
|
|
390
|
+
let tmpHTML = tmpDocProvider._ContentProvider.parseMarkdown(pMarkdown, tmpLinkResolver, tmpImageResolver);
|
|
391
|
+
this.pict.ContentAssignment.assignContent('#Docuserve-Splash-Readme', '<div class="pict-content">' + tmpHTML + '</div>');
|
|
392
|
+
})
|
|
393
|
+
.catch(() =>
|
|
394
|
+
{
|
|
395
|
+
this.pict.ContentAssignment.assignContent('#Docuserve-Splash-Readme', '');
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
273
399
|
/**
|
|
274
400
|
* Sanitize a title string, preserving only <small> tags.
|
|
275
401
|
* All other HTML is escaped.
|