pict-docuserve 0.0.8 → 0.0.11
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict-docuserve",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Pict Documentation Server - A single-page documentation viewer built on Pict",
|
|
5
5
|
"main": "source/Pict-Application-Docuserve.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"build-docs": "npx quack build && npx quack copy && node source/cli/Docuserve-CLI-Run.js inject ./docs && node example_applications/build-examples.js stage-docs",
|
|
19
19
|
"serve-docs": "node source/cli/Docuserve-CLI-Run.js serve ./docs",
|
|
20
20
|
"serve-examples": "node example_applications/build-examples.js",
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "npx mocha -u tdd --exit -R spec",
|
|
22
22
|
"tests": "npx mocha -u tdd --exit -R spec --grep",
|
|
23
23
|
"coverage": "npx nyc --reporter=lcov --reporter=text-lcov npx mocha -- -u tdd -R spec"
|
|
24
24
|
},
|
|
@@ -29,10 +29,13 @@
|
|
|
29
29
|
"pict": "^1.0.343",
|
|
30
30
|
"pict-application": "^1.0.30",
|
|
31
31
|
"pict-provider": "^1.0.7",
|
|
32
|
+
"pict-section-content": "file:../pict-section-content",
|
|
32
33
|
"pict-service-commandlineutility": "^1.0.17",
|
|
33
34
|
"pict-view": "^1.0.64"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"chai": "^6.2.2",
|
|
38
|
+
"mocha": "^11.7.5",
|
|
36
39
|
"quackage": "^1.0.47"
|
|
37
40
|
},
|
|
38
41
|
"copyFilesSettings": {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const libPictProvider = require('pict-provider');
|
|
2
2
|
const libLunr = require('lunr');
|
|
3
|
+
const libPictSectionContent = require('pict-section-content');
|
|
4
|
+
const libPictContentProvider = libPictSectionContent.PictContentProvider;
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Documentation Provider for Docuserve
|
|
@@ -16,6 +18,42 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
16
18
|
|
|
17
19
|
this._Catalog = null;
|
|
18
20
|
this._ContentCache = {};
|
|
21
|
+
|
|
22
|
+
// Create an instance of the content provider for markdown parsing
|
|
23
|
+
this._ContentProvider = this.pict.addProvider('Pict-Content', libPictContentProvider.default_configuration, libPictContentProvider);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a link resolver closure for the content provider.
|
|
28
|
+
*
|
|
29
|
+
* Wraps docuserve-specific link resolution (catalog-aware routing,
|
|
30
|
+
* GitHub URL matching) into a callback compatible with the
|
|
31
|
+
* pict-section-content link resolver pattern.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} [pCurrentGroup] - The current group key
|
|
34
|
+
* @param {string} [pCurrentModule] - The current module name
|
|
35
|
+
* @param {string} [pCurrentDocPath] - The current document path
|
|
36
|
+
* @returns {Function} A link resolver callback
|
|
37
|
+
*/
|
|
38
|
+
_createLinkResolver(pCurrentGroup, pCurrentModule, pCurrentDocPath)
|
|
39
|
+
{
|
|
40
|
+
return (pHref, pLinkText) =>
|
|
41
|
+
{
|
|
42
|
+
// Convert internal doc links to hash routes
|
|
43
|
+
if (pHref.match(/^\//) || pHref.match(/^[^:]+\.md/))
|
|
44
|
+
{
|
|
45
|
+
let tmpRoute = this.convertDocLink(pHref, pCurrentGroup, pCurrentModule, pCurrentDocPath);
|
|
46
|
+
return { href: tmpRoute };
|
|
47
|
+
}
|
|
48
|
+
// Check if this is a GitHub URL that matches a catalog module
|
|
49
|
+
let tmpCatalogRoute = this.resolveGitHubURLToRoute(pHref);
|
|
50
|
+
if (tmpCatalogRoute)
|
|
51
|
+
{
|
|
52
|
+
return { href: tmpCatalogRoute };
|
|
53
|
+
}
|
|
54
|
+
// Use default behavior for other links
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
19
57
|
}
|
|
20
58
|
|
|
21
59
|
/**
|
|
@@ -495,7 +533,7 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
495
533
|
return tmpCallback();
|
|
496
534
|
}
|
|
497
535
|
|
|
498
|
-
this.pict.AppData.Docuserve.ErrorPageHTML = this.parseMarkdown(pMarkdown);
|
|
536
|
+
this.pict.AppData.Docuserve.ErrorPageHTML = this._ContentProvider.parseMarkdown(pMarkdown);
|
|
499
537
|
this.pict.AppData.Docuserve.ErrorPageLoaded = true;
|
|
500
538
|
return tmpCallback();
|
|
501
539
|
})
|
|
@@ -714,13 +752,13 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
714
752
|
if (this.pict.AppData.Docuserve.ErrorPageLoaded && this.pict.AppData.Docuserve.ErrorPageHTML)
|
|
715
753
|
{
|
|
716
754
|
// Replace the {{path}} placeholder with the actual requested path
|
|
717
|
-
return this.pict.AppData.Docuserve.ErrorPageHTML.replace(/\{\{path\}\}/g, this.escapeHTML(tmpPath));
|
|
755
|
+
return this.pict.AppData.Docuserve.ErrorPageHTML.replace(/\{\{path\}\}/g, this._ContentProvider.escapeHTML(tmpPath));
|
|
718
756
|
}
|
|
719
757
|
|
|
720
758
|
// Default fallback
|
|
721
759
|
return '<div class="docuserve-not-found">'
|
|
722
760
|
+ '<h2>Page Not Found</h2>'
|
|
723
|
-
+ '<p>The document <code>' + this.escapeHTML(tmpPath) + '</code> could not be loaded.</p>'
|
|
761
|
+
+ '<p>The document <code>' + this._ContentProvider.escapeHTML(tmpPath) + '</code> could not be loaded.</p>'
|
|
724
762
|
+ '<p><a href="#/Home">Return to the home page</a></p>'
|
|
725
763
|
+ '</div>';
|
|
726
764
|
}
|
|
@@ -994,6 +1032,57 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
994
1032
|
return null;
|
|
995
1033
|
}
|
|
996
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* Resolve a GitHub repository URL to an internal hash route.
|
|
1037
|
+
*
|
|
1038
|
+
* If the URL matches a module in the loaded catalog, returns the
|
|
1039
|
+
* corresponding #/doc/ route so the link navigates within docuserve
|
|
1040
|
+
* instead of leaving to GitHub.
|
|
1041
|
+
*
|
|
1042
|
+
* @param {string} pURL - A GitHub URL (e.g. "https://github.com/stevenvelozo/fable")
|
|
1043
|
+
* @returns {string|null} The hash route (e.g. "#/doc/fable/fable") or null if not a catalog module
|
|
1044
|
+
*/
|
|
1045
|
+
resolveGitHubURLToRoute(pURL)
|
|
1046
|
+
{
|
|
1047
|
+
if (!this._Catalog || !this._Catalog.Groups || !pURL)
|
|
1048
|
+
{
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Match https://github.com/{org}/{repo} with optional trailing path/slash
|
|
1053
|
+
let tmpMatch = pURL.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)/);
|
|
1054
|
+
if (!tmpMatch)
|
|
1055
|
+
{
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
let tmpOrg = tmpMatch[1];
|
|
1060
|
+
let tmpRepo = tmpMatch[2];
|
|
1061
|
+
|
|
1062
|
+
// Only resolve URLs that match the catalog's GitHub org
|
|
1063
|
+
if (tmpOrg !== this._Catalog.GitHubOrg)
|
|
1064
|
+
{
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Search catalog for a module with a matching Repo
|
|
1069
|
+
for (let i = 0; i < this._Catalog.Groups.length; i++)
|
|
1070
|
+
{
|
|
1071
|
+
let tmpGroup = this._Catalog.Groups[i];
|
|
1072
|
+
|
|
1073
|
+
for (let j = 0; j < tmpGroup.Modules.length; j++)
|
|
1074
|
+
{
|
|
1075
|
+
let tmpModule = tmpGroup.Modules[j];
|
|
1076
|
+
if (tmpModule.Repo === tmpRepo)
|
|
1077
|
+
{
|
|
1078
|
+
return '#/doc/' + tmpGroup.Key + '/' + tmpModule.Name;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
997
1086
|
/**
|
|
998
1087
|
* Get the module-specific sidebar entries for a given group/module.
|
|
999
1088
|
*
|
|
@@ -1071,7 +1160,7 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
1071
1160
|
return tmpCallback('Document not found', this.getErrorPageHTML(pURL));
|
|
1072
1161
|
}
|
|
1073
1162
|
|
|
1074
|
-
let tmpHTML = this.parseMarkdown(pMarkdown, pCurrentGroup, pCurrentModule, pCurrentDocPath);
|
|
1163
|
+
let tmpHTML = this._ContentProvider.parseMarkdown(pMarkdown, this._createLinkResolver(pCurrentGroup, pCurrentModule, pCurrentDocPath));
|
|
1075
1164
|
this._ContentCache[pURL] = tmpHTML;
|
|
1076
1165
|
return tmpCallback(null, tmpHTML);
|
|
1077
1166
|
})
|
|
@@ -1098,357 +1187,6 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
1098
1187
|
this.fetchDocument(tmpURL, fCallback, pCurrentGroup, pCurrentModule, pCurrentDocPath);
|
|
1099
1188
|
}
|
|
1100
1189
|
|
|
1101
|
-
/**
|
|
1102
|
-
* Parse a markdown string into HTML.
|
|
1103
|
-
*
|
|
1104
|
-
* This is a basic markdown parser that handles the most common constructs:
|
|
1105
|
-
* headings, paragraphs, code blocks, inline code, links, bold, italic,
|
|
1106
|
-
* lists, blockquotes, and horizontal rules.
|
|
1107
|
-
*
|
|
1108
|
-
* @param {string} pMarkdown - The raw markdown text
|
|
1109
|
-
* @param {string} [pCurrentGroup] - The current group key for link resolution
|
|
1110
|
-
* @param {string} [pCurrentModule] - The current module name for link resolution
|
|
1111
|
-
* @param {string} [pCurrentDocPath] - The current document path for link resolution
|
|
1112
|
-
* @returns {string} The parsed HTML
|
|
1113
|
-
*/
|
|
1114
|
-
parseMarkdown(pMarkdown, pCurrentGroup, pCurrentModule, pCurrentDocPath)
|
|
1115
|
-
{
|
|
1116
|
-
if (!pMarkdown)
|
|
1117
|
-
{
|
|
1118
|
-
return '';
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
let tmpLines = pMarkdown.split('\n');
|
|
1122
|
-
let tmpHTML = [];
|
|
1123
|
-
let tmpInCodeBlock = false;
|
|
1124
|
-
let tmpCodeFenceLength = 0;
|
|
1125
|
-
let tmpCodeLang = '';
|
|
1126
|
-
let tmpCodeLines = [];
|
|
1127
|
-
let tmpInList = false;
|
|
1128
|
-
let tmpListType = '';
|
|
1129
|
-
let tmpInBlockquote = false;
|
|
1130
|
-
let tmpBlockquoteLines = [];
|
|
1131
|
-
let tmpInMathBlock = false;
|
|
1132
|
-
let tmpMathLines = [];
|
|
1133
|
-
|
|
1134
|
-
for (let i = 0; i < tmpLines.length; i++)
|
|
1135
|
-
{
|
|
1136
|
-
let tmpLine = tmpLines[i];
|
|
1137
|
-
|
|
1138
|
-
// Display math blocks ($$...$$) — skip if inside a code block
|
|
1139
|
-
if (!tmpInCodeBlock && tmpLine.trim().match(/^\$\$/))
|
|
1140
|
-
{
|
|
1141
|
-
if (tmpInMathBlock)
|
|
1142
|
-
{
|
|
1143
|
-
// End math block
|
|
1144
|
-
tmpHTML.push('<div class="docuserve-katex-display">' + tmpMathLines.join('\n') + '</div>');
|
|
1145
|
-
tmpInMathBlock = false;
|
|
1146
|
-
tmpMathLines = [];
|
|
1147
|
-
}
|
|
1148
|
-
else
|
|
1149
|
-
{
|
|
1150
|
-
// Close any open list or blockquote
|
|
1151
|
-
if (tmpInList)
|
|
1152
|
-
{
|
|
1153
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1154
|
-
tmpInList = false;
|
|
1155
|
-
}
|
|
1156
|
-
if (tmpInBlockquote)
|
|
1157
|
-
{
|
|
1158
|
-
tmpHTML.push('<blockquote>' + this.parseMarkdown(tmpBlockquoteLines.join('\n'), pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</blockquote>');
|
|
1159
|
-
tmpInBlockquote = false;
|
|
1160
|
-
tmpBlockquoteLines = [];
|
|
1161
|
-
}
|
|
1162
|
-
tmpInMathBlock = true;
|
|
1163
|
-
}
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
if (tmpInMathBlock)
|
|
1168
|
-
{
|
|
1169
|
-
tmpMathLines.push(tmpLine);
|
|
1170
|
-
continue;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// Code blocks (fenced) — track fence length so ````x```` nests around ```y```
|
|
1174
|
-
let tmpFenceMatch = tmpLine.match(/^(`{3,})/);
|
|
1175
|
-
if (tmpFenceMatch)
|
|
1176
|
-
{
|
|
1177
|
-
let tmpFenceLen = tmpFenceMatch[1].length;
|
|
1178
|
-
|
|
1179
|
-
if (tmpInCodeBlock)
|
|
1180
|
-
{
|
|
1181
|
-
// Only close if the closing fence is at least as long as the opening
|
|
1182
|
-
if (tmpFenceLen >= tmpCodeFenceLength && tmpLine.trim() === tmpFenceMatch[1])
|
|
1183
|
-
{
|
|
1184
|
-
// End code block
|
|
1185
|
-
if (tmpCodeLang === 'mermaid')
|
|
1186
|
-
{
|
|
1187
|
-
// Mermaid diagrams: output raw content for client-side rendering
|
|
1188
|
-
tmpHTML.push('<pre class="mermaid">' + tmpCodeLines.join('\n') + '</pre>');
|
|
1189
|
-
}
|
|
1190
|
-
else
|
|
1191
|
-
{
|
|
1192
|
-
tmpHTML.push('<pre><code class="language-' + this.escapeHTML(tmpCodeLang) + '">' + this.escapeHTML(tmpCodeLines.join('\n')) + '</code></pre>');
|
|
1193
|
-
}
|
|
1194
|
-
tmpInCodeBlock = false;
|
|
1195
|
-
tmpCodeFenceLength = 0;
|
|
1196
|
-
tmpCodeLang = '';
|
|
1197
|
-
tmpCodeLines = [];
|
|
1198
|
-
continue;
|
|
1199
|
-
}
|
|
1200
|
-
else
|
|
1201
|
-
{
|
|
1202
|
-
// Inner fence with fewer backticks — treat as content
|
|
1203
|
-
tmpCodeLines.push(tmpLine);
|
|
1204
|
-
continue;
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
else
|
|
1208
|
-
{
|
|
1209
|
-
// Close any open list or blockquote
|
|
1210
|
-
if (tmpInList)
|
|
1211
|
-
{
|
|
1212
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1213
|
-
tmpInList = false;
|
|
1214
|
-
}
|
|
1215
|
-
if (tmpInBlockquote)
|
|
1216
|
-
{
|
|
1217
|
-
tmpHTML.push('<blockquote>' + this.parseMarkdown(tmpBlockquoteLines.join('\n'), pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</blockquote>');
|
|
1218
|
-
tmpInBlockquote = false;
|
|
1219
|
-
tmpBlockquoteLines = [];
|
|
1220
|
-
}
|
|
1221
|
-
// Start code block — record fence length
|
|
1222
|
-
tmpCodeFenceLength = tmpFenceLen;
|
|
1223
|
-
tmpCodeLang = tmpLine.replace(/^`{3,}/, '').trim();
|
|
1224
|
-
tmpInCodeBlock = true;
|
|
1225
|
-
continue;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
if (tmpInCodeBlock)
|
|
1230
|
-
{
|
|
1231
|
-
tmpCodeLines.push(tmpLine);
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
// Blockquotes
|
|
1236
|
-
if (tmpLine.match(/^>\s?/))
|
|
1237
|
-
{
|
|
1238
|
-
if (!tmpInBlockquote)
|
|
1239
|
-
{
|
|
1240
|
-
// Close any open list
|
|
1241
|
-
if (tmpInList)
|
|
1242
|
-
{
|
|
1243
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1244
|
-
tmpInList = false;
|
|
1245
|
-
}
|
|
1246
|
-
tmpInBlockquote = true;
|
|
1247
|
-
tmpBlockquoteLines = [];
|
|
1248
|
-
}
|
|
1249
|
-
tmpBlockquoteLines.push(tmpLine.replace(/^>\s?/, ''));
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
else if (tmpInBlockquote)
|
|
1253
|
-
{
|
|
1254
|
-
tmpHTML.push('<blockquote>' + this.parseMarkdown(tmpBlockquoteLines.join('\n'), pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</blockquote>');
|
|
1255
|
-
tmpInBlockquote = false;
|
|
1256
|
-
tmpBlockquoteLines = [];
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
// Horizontal rule
|
|
1260
|
-
if (tmpLine.match(/^(-{3,}|\*{3,}|_{3,})\s*$/))
|
|
1261
|
-
{
|
|
1262
|
-
if (tmpInList)
|
|
1263
|
-
{
|
|
1264
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1265
|
-
tmpInList = false;
|
|
1266
|
-
}
|
|
1267
|
-
tmpHTML.push('<hr>');
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// Headings
|
|
1272
|
-
let tmpHeadingMatch = tmpLine.match(/^(#{1,6})\s+(.+)/);
|
|
1273
|
-
if (tmpHeadingMatch)
|
|
1274
|
-
{
|
|
1275
|
-
if (tmpInList)
|
|
1276
|
-
{
|
|
1277
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1278
|
-
tmpInList = false;
|
|
1279
|
-
}
|
|
1280
|
-
let tmpLevel = tmpHeadingMatch[1].length;
|
|
1281
|
-
let tmpText = this.parseInline(tmpHeadingMatch[2], pCurrentGroup, pCurrentModule, pCurrentDocPath);
|
|
1282
|
-
let tmpID = tmpHeadingMatch[2].toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-');
|
|
1283
|
-
tmpHTML.push('<h' + tmpLevel + ' id="' + tmpID + '">' + tmpText + '</h' + tmpLevel + '>');
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
// Unordered list items
|
|
1288
|
-
let tmpULMatch = tmpLine.match(/^(\s*)[-*+]\s+(.*)/);
|
|
1289
|
-
if (tmpULMatch)
|
|
1290
|
-
{
|
|
1291
|
-
if (!tmpInList || tmpListType !== 'ul')
|
|
1292
|
-
{
|
|
1293
|
-
if (tmpInList)
|
|
1294
|
-
{
|
|
1295
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1296
|
-
}
|
|
1297
|
-
tmpHTML.push('<ul>');
|
|
1298
|
-
tmpInList = true;
|
|
1299
|
-
tmpListType = 'ul';
|
|
1300
|
-
}
|
|
1301
|
-
tmpHTML.push('<li>' + this.parseInline(tmpULMatch[2], pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</li>');
|
|
1302
|
-
continue;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
// Ordered list items
|
|
1306
|
-
let tmpOLMatch = tmpLine.match(/^(\s*)\d+\.\s+(.*)/);
|
|
1307
|
-
if (tmpOLMatch)
|
|
1308
|
-
{
|
|
1309
|
-
if (!tmpInList || tmpListType !== 'ol')
|
|
1310
|
-
{
|
|
1311
|
-
if (tmpInList)
|
|
1312
|
-
{
|
|
1313
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1314
|
-
}
|
|
1315
|
-
tmpHTML.push('<ol>');
|
|
1316
|
-
tmpInList = true;
|
|
1317
|
-
tmpListType = 'ol';
|
|
1318
|
-
}
|
|
1319
|
-
tmpHTML.push('<li>' + this.parseInline(tmpOLMatch[2], pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</li>');
|
|
1320
|
-
continue;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// Close list if we've left list items
|
|
1324
|
-
if (tmpInList && tmpLine.trim() !== '')
|
|
1325
|
-
{
|
|
1326
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1327
|
-
tmpInList = false;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// Empty line
|
|
1331
|
-
if (tmpLine.trim() === '')
|
|
1332
|
-
{
|
|
1333
|
-
continue;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// Table detection
|
|
1337
|
-
if (tmpLine.match(/^\|/) && i + 1 < tmpLines.length && tmpLines[i + 1].match(/^\|[\s-:|]+\|/))
|
|
1338
|
-
{
|
|
1339
|
-
// Close any open list
|
|
1340
|
-
if (tmpInList)
|
|
1341
|
-
{
|
|
1342
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1343
|
-
tmpInList = false;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
let tmpTableHTML = '<table>';
|
|
1347
|
-
|
|
1348
|
-
// Header row
|
|
1349
|
-
let tmpHeaders = tmpLine.split('|').filter((pCell) => { return pCell.trim() !== ''; });
|
|
1350
|
-
tmpTableHTML += '<thead><tr>';
|
|
1351
|
-
for (let h = 0; h < tmpHeaders.length; h++)
|
|
1352
|
-
{
|
|
1353
|
-
tmpTableHTML += '<th>' + this.parseInline(tmpHeaders[h].trim(), pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</th>';
|
|
1354
|
-
}
|
|
1355
|
-
tmpTableHTML += '</tr></thead>';
|
|
1356
|
-
|
|
1357
|
-
// Skip separator row
|
|
1358
|
-
i++;
|
|
1359
|
-
|
|
1360
|
-
// Body rows
|
|
1361
|
-
tmpTableHTML += '<tbody>';
|
|
1362
|
-
while (i + 1 < tmpLines.length && tmpLines[i + 1].match(/^\|/))
|
|
1363
|
-
{
|
|
1364
|
-
i++;
|
|
1365
|
-
let tmpCells = tmpLines[i].split('|').filter((pCell) => { return pCell.trim() !== ''; });
|
|
1366
|
-
tmpTableHTML += '<tr>';
|
|
1367
|
-
for (let c = 0; c < tmpCells.length; c++)
|
|
1368
|
-
{
|
|
1369
|
-
tmpTableHTML += '<td>' + this.parseInline(tmpCells[c].trim(), pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</td>';
|
|
1370
|
-
}
|
|
1371
|
-
tmpTableHTML += '</tr>';
|
|
1372
|
-
}
|
|
1373
|
-
tmpTableHTML += '</tbody></table>';
|
|
1374
|
-
tmpHTML.push(tmpTableHTML);
|
|
1375
|
-
continue;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// Regular paragraph
|
|
1379
|
-
tmpHTML.push('<p>' + this.parseInline(tmpLine, pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</p>');
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// Close any trailing open elements
|
|
1383
|
-
if (tmpInList)
|
|
1384
|
-
{
|
|
1385
|
-
tmpHTML.push(tmpListType === 'ul' ? '</ul>' : '</ol>');
|
|
1386
|
-
}
|
|
1387
|
-
if (tmpInBlockquote)
|
|
1388
|
-
{
|
|
1389
|
-
tmpHTML.push('<blockquote>' + this.parseMarkdown(tmpBlockquoteLines.join('\n'), pCurrentGroup, pCurrentModule, pCurrentDocPath) + '</blockquote>');
|
|
1390
|
-
}
|
|
1391
|
-
if (tmpInCodeBlock)
|
|
1392
|
-
{
|
|
1393
|
-
tmpHTML.push('<pre><code>' + this.escapeHTML(tmpCodeLines.join('\n')) + '</code></pre>');
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
return tmpHTML.join('\n');
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Parse inline markdown elements (bold, italic, code, links, images).
|
|
1401
|
-
*
|
|
1402
|
-
* @param {string} pText - The text to parse
|
|
1403
|
-
* @param {string} [pCurrentGroup] - The current group key for link resolution
|
|
1404
|
-
* @param {string} [pCurrentModule] - The current module name for link resolution
|
|
1405
|
-
* @param {string} [pCurrentDocPath] - The current document path for link resolution
|
|
1406
|
-
* @returns {string} HTML with inline elements
|
|
1407
|
-
*/
|
|
1408
|
-
parseInline(pText, pCurrentGroup, pCurrentModule, pCurrentDocPath)
|
|
1409
|
-
{
|
|
1410
|
-
if (!pText)
|
|
1411
|
-
{
|
|
1412
|
-
return '';
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
let tmpResult = pText;
|
|
1416
|
-
|
|
1417
|
-
// Inline code (backticks) - handle first to avoid interfering with other patterns
|
|
1418
|
-
tmpResult = tmpResult.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
1419
|
-
|
|
1420
|
-
// Inline LaTeX equations ($...$) — must be processed before other inline patterns
|
|
1421
|
-
// Match single $ delimiters that aren't adjacent to spaces (to avoid false positives with currency)
|
|
1422
|
-
tmpResult = tmpResult.replace(/\$([^\$\s][^\$]*?[^\$\s])\$/g, '<span class="docuserve-katex-inline">$1</span>');
|
|
1423
|
-
// Also match single-character inline math like $x$
|
|
1424
|
-
tmpResult = tmpResult.replace(/\$([^\$\s])\$/g, '<span class="docuserve-katex-inline">$1</span>');
|
|
1425
|
-
|
|
1426
|
-
// Images
|
|
1427
|
-
tmpResult = tmpResult.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
|
|
1428
|
-
|
|
1429
|
-
// Links
|
|
1430
|
-
tmpResult = tmpResult.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (pMatch, pLinkText, pHref) =>
|
|
1431
|
-
{
|
|
1432
|
-
// Convert internal doc links to hash routes
|
|
1433
|
-
if (pHref.match(/^\//) || pHref.match(/^[^:]+\.md/))
|
|
1434
|
-
{
|
|
1435
|
-
let tmpRoute = this.convertDocLink(pHref, pCurrentGroup, pCurrentModule, pCurrentDocPath);
|
|
1436
|
-
return '<a href="' + tmpRoute + '">' + pLinkText + '</a>';
|
|
1437
|
-
}
|
|
1438
|
-
return '<a href="' + pHref + '" target="_blank" rel="noopener">' + pLinkText + '</a>';
|
|
1439
|
-
});
|
|
1440
|
-
|
|
1441
|
-
// Bold
|
|
1442
|
-
tmpResult = tmpResult.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
1443
|
-
tmpResult = tmpResult.replace(/__([^_]+)__/g, '<strong>$1</strong>');
|
|
1444
|
-
|
|
1445
|
-
// Italic
|
|
1446
|
-
tmpResult = tmpResult.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
1447
|
-
tmpResult = tmpResult.replace(/_([^_]+)_/g, '<em>$1</em>');
|
|
1448
|
-
|
|
1449
|
-
return tmpResult;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
1190
|
/**
|
|
1453
1191
|
* Convert a docsify-style internal link to a hash route for docuserve.
|
|
1454
1192
|
*
|
|
@@ -1506,25 +1244,6 @@ class DocuserveDocumentationProvider extends libPictProvider
|
|
|
1506
1244
|
return '#/page/' + tmpPath;
|
|
1507
1245
|
}
|
|
1508
1246
|
|
|
1509
|
-
/**
|
|
1510
|
-
* Escape HTML special characters.
|
|
1511
|
-
*
|
|
1512
|
-
* @param {string} pText - The text to escape
|
|
1513
|
-
* @returns {string} The escaped text
|
|
1514
|
-
*/
|
|
1515
|
-
escapeHTML(pText)
|
|
1516
|
-
{
|
|
1517
|
-
if (!pText)
|
|
1518
|
-
{
|
|
1519
|
-
return '';
|
|
1520
|
-
}
|
|
1521
|
-
return pText
|
|
1522
|
-
.replace(/&/g, '&')
|
|
1523
|
-
.replace(/</g, '<')
|
|
1524
|
-
.replace(/>/g, '>')
|
|
1525
|
-
.replace(/"/g, '"')
|
|
1526
|
-
.replace(/'/g, ''');
|
|
1527
|
-
}
|
|
1528
1247
|
}
|
|
1529
1248
|
|
|
1530
1249
|
module.exports = DocuserveDocumentationProvider;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const libPictContentView = require('pict-section-content');
|
|
2
2
|
|
|
3
3
|
const _ViewConfiguration =
|
|
4
4
|
{
|
|
@@ -10,139 +10,6 @@ const _ViewConfiguration =
|
|
|
10
10
|
AutoRender: false,
|
|
11
11
|
|
|
12
12
|
CSS: /*css*/`
|
|
13
|
-
.docuserve-content {
|
|
14
|
-
padding: 2em 3em;
|
|
15
|
-
max-width: 900px;
|
|
16
|
-
margin: 0 auto;
|
|
17
|
-
}
|
|
18
|
-
.docuserve-content-loading {
|
|
19
|
-
display: flex;
|
|
20
|
-
align-items: center;
|
|
21
|
-
justify-content: center;
|
|
22
|
-
min-height: 200px;
|
|
23
|
-
color: #8A7F72;
|
|
24
|
-
font-size: 1em;
|
|
25
|
-
}
|
|
26
|
-
.docuserve-content h1 {
|
|
27
|
-
font-size: 2em;
|
|
28
|
-
color: #3D3229;
|
|
29
|
-
border-bottom: 1px solid #DDD6CA;
|
|
30
|
-
padding-bottom: 0.3em;
|
|
31
|
-
margin-top: 0;
|
|
32
|
-
}
|
|
33
|
-
.docuserve-content h2 {
|
|
34
|
-
font-size: 1.5em;
|
|
35
|
-
color: #3D3229;
|
|
36
|
-
border-bottom: 1px solid #EAE3D8;
|
|
37
|
-
padding-bottom: 0.25em;
|
|
38
|
-
margin-top: 1.5em;
|
|
39
|
-
}
|
|
40
|
-
.docuserve-content h3 {
|
|
41
|
-
font-size: 1.25em;
|
|
42
|
-
color: #3D3229;
|
|
43
|
-
margin-top: 1.25em;
|
|
44
|
-
}
|
|
45
|
-
.docuserve-content h4, .docuserve-content h5, .docuserve-content h6 {
|
|
46
|
-
color: #5E5549;
|
|
47
|
-
margin-top: 1em;
|
|
48
|
-
}
|
|
49
|
-
.docuserve-content p {
|
|
50
|
-
line-height: 1.7;
|
|
51
|
-
color: #423D37;
|
|
52
|
-
margin: 0.75em 0;
|
|
53
|
-
}
|
|
54
|
-
.docuserve-content a {
|
|
55
|
-
color: #2E7D74;
|
|
56
|
-
text-decoration: none;
|
|
57
|
-
}
|
|
58
|
-
.docuserve-content a:hover {
|
|
59
|
-
text-decoration: underline;
|
|
60
|
-
}
|
|
61
|
-
.docuserve-content pre {
|
|
62
|
-
background: #3D3229;
|
|
63
|
-
color: #E8E0D4;
|
|
64
|
-
padding: 1.25em;
|
|
65
|
-
border-radius: 6px;
|
|
66
|
-
overflow-x: auto;
|
|
67
|
-
line-height: 1.5;
|
|
68
|
-
font-size: 0.9em;
|
|
69
|
-
}
|
|
70
|
-
.docuserve-content code {
|
|
71
|
-
background: #F0ECE4;
|
|
72
|
-
padding: 0.15em 0.4em;
|
|
73
|
-
border-radius: 3px;
|
|
74
|
-
font-size: 0.9em;
|
|
75
|
-
color: #9E6B47;
|
|
76
|
-
}
|
|
77
|
-
.docuserve-content pre code {
|
|
78
|
-
background: none;
|
|
79
|
-
padding: 0;
|
|
80
|
-
color: inherit;
|
|
81
|
-
font-size: inherit;
|
|
82
|
-
}
|
|
83
|
-
.docuserve-content blockquote {
|
|
84
|
-
border-left: 4px solid #2E7D74;
|
|
85
|
-
margin: 1em 0;
|
|
86
|
-
padding: 0.5em 1em;
|
|
87
|
-
background: #F7F5F0;
|
|
88
|
-
color: #5E5549;
|
|
89
|
-
}
|
|
90
|
-
.docuserve-content blockquote p {
|
|
91
|
-
margin: 0.25em 0;
|
|
92
|
-
}
|
|
93
|
-
.docuserve-content ul, .docuserve-content ol {
|
|
94
|
-
padding-left: 2em;
|
|
95
|
-
line-height: 1.8;
|
|
96
|
-
}
|
|
97
|
-
.docuserve-content li {
|
|
98
|
-
margin: 0.25em 0;
|
|
99
|
-
color: #423D37;
|
|
100
|
-
}
|
|
101
|
-
.docuserve-content hr {
|
|
102
|
-
border: none;
|
|
103
|
-
border-top: 1px solid #DDD6CA;
|
|
104
|
-
margin: 2em 0;
|
|
105
|
-
}
|
|
106
|
-
.docuserve-content table {
|
|
107
|
-
width: 100%;
|
|
108
|
-
border-collapse: collapse;
|
|
109
|
-
margin: 1em 0;
|
|
110
|
-
}
|
|
111
|
-
.docuserve-content table th {
|
|
112
|
-
background: #F5F0E8;
|
|
113
|
-
border: 1px solid #DDD6CA;
|
|
114
|
-
padding: 0.6em 0.8em;
|
|
115
|
-
text-align: left;
|
|
116
|
-
font-weight: 600;
|
|
117
|
-
color: #3D3229;
|
|
118
|
-
}
|
|
119
|
-
.docuserve-content table td {
|
|
120
|
-
border: 1px solid #DDD6CA;
|
|
121
|
-
padding: 0.5em 0.8em;
|
|
122
|
-
color: #423D37;
|
|
123
|
-
}
|
|
124
|
-
.docuserve-content table tr:nth-child(even) {
|
|
125
|
-
background: #F7F5F0;
|
|
126
|
-
}
|
|
127
|
-
.docuserve-content img {
|
|
128
|
-
max-width: 100%;
|
|
129
|
-
height: auto;
|
|
130
|
-
}
|
|
131
|
-
.docuserve-content pre.mermaid {
|
|
132
|
-
background: #fff;
|
|
133
|
-
color: #3D3229;
|
|
134
|
-
text-align: center;
|
|
135
|
-
padding: 1em;
|
|
136
|
-
}
|
|
137
|
-
.docuserve-content .docuserve-katex-display {
|
|
138
|
-
text-align: center;
|
|
139
|
-
margin: 1em 0;
|
|
140
|
-
padding: 0.5em;
|
|
141
|
-
overflow-x: auto;
|
|
142
|
-
}
|
|
143
|
-
.docuserve-content .docuserve-katex-inline {
|
|
144
|
-
display: inline;
|
|
145
|
-
}
|
|
146
13
|
.docuserve-not-found {
|
|
147
14
|
text-align: center;
|
|
148
15
|
padding: 3em 1em;
|
|
@@ -167,8 +34,8 @@ const _ViewConfiguration =
|
|
|
167
34
|
{
|
|
168
35
|
Hash: "Docuserve-Content-Template",
|
|
169
36
|
Template: /*html*/`
|
|
170
|
-
<div class="
|
|
171
|
-
<div class="
|
|
37
|
+
<div class="pict-content" id="Docuserve-Content-Body">
|
|
38
|
+
<div class="pict-content-loading">Loading documentation...</div>
|
|
172
39
|
</div>
|
|
173
40
|
`
|
|
174
41
|
}
|
|
@@ -185,7 +52,7 @@ const _ViewConfiguration =
|
|
|
185
52
|
]
|
|
186
53
|
};
|
|
187
54
|
|
|
188
|
-
class DocuserveContentView extends
|
|
55
|
+
class DocuserveContentView extends libPictContentView
|
|
189
56
|
{
|
|
190
57
|
constructor(pFable, pOptions, pServiceHash)
|
|
191
58
|
{
|
|
@@ -199,101 +66,7 @@ class DocuserveContentView extends libPictView
|
|
|
199
66
|
*/
|
|
200
67
|
displayContent(pHTMLContent)
|
|
201
68
|
{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Scroll to top of content area
|
|
205
|
-
let tmpContentContainer = document.getElementById('Docuserve-Content-Container');
|
|
206
|
-
if (tmpContentContainer)
|
|
207
|
-
{
|
|
208
|
-
tmpContentContainer.scrollTop = 0;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Post-render: initialize Mermaid diagrams if mermaid is available
|
|
212
|
-
this.renderMermaidDiagrams();
|
|
213
|
-
|
|
214
|
-
// Post-render: render KaTeX equations if katex is available
|
|
215
|
-
this.renderKaTeXEquations();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Render any Mermaid diagram blocks in the content area.
|
|
220
|
-
* Mermaid blocks are `<pre class="mermaid">` elements produced by parseMarkdown.
|
|
221
|
-
*/
|
|
222
|
-
renderMermaidDiagrams()
|
|
223
|
-
{
|
|
224
|
-
if (typeof mermaid === 'undefined')
|
|
225
|
-
{
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
let tmpContentBody = document.getElementById('Docuserve-Content-Body');
|
|
230
|
-
if (!tmpContentBody)
|
|
231
|
-
{
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let tmpMermaidElements = tmpContentBody.querySelectorAll('pre.mermaid');
|
|
236
|
-
if (tmpMermaidElements.length < 1)
|
|
237
|
-
{
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// mermaid.run() will process all pre.mermaid elements in the container
|
|
242
|
-
try
|
|
243
|
-
{
|
|
244
|
-
mermaid.run({ nodes: tmpMermaidElements });
|
|
245
|
-
}
|
|
246
|
-
catch (pError)
|
|
247
|
-
{
|
|
248
|
-
this.log.error('Mermaid rendering error: ' + pError.message);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Render KaTeX inline and display math elements in the content area.
|
|
254
|
-
* Inline: `<span class="docuserve-katex-inline">`
|
|
255
|
-
* Display: `<div class="docuserve-katex-display">`
|
|
256
|
-
*/
|
|
257
|
-
renderKaTeXEquations()
|
|
258
|
-
{
|
|
259
|
-
if (typeof katex === 'undefined')
|
|
260
|
-
{
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
let tmpContentBody = document.getElementById('Docuserve-Content-Body');
|
|
265
|
-
if (!tmpContentBody)
|
|
266
|
-
{
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Render inline math
|
|
271
|
-
let tmpInlineElements = tmpContentBody.querySelectorAll('.docuserve-katex-inline');
|
|
272
|
-
for (let i = 0; i < tmpInlineElements.length; i++)
|
|
273
|
-
{
|
|
274
|
-
try
|
|
275
|
-
{
|
|
276
|
-
katex.render(tmpInlineElements[i].textContent, tmpInlineElements[i], { throwOnError: false, displayMode: false });
|
|
277
|
-
}
|
|
278
|
-
catch (pError)
|
|
279
|
-
{
|
|
280
|
-
this.log.warn('KaTeX inline error: ' + pError.message);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Render display math
|
|
285
|
-
let tmpDisplayElements = tmpContentBody.querySelectorAll('.docuserve-katex-display');
|
|
286
|
-
for (let i = 0; i < tmpDisplayElements.length; i++)
|
|
287
|
-
{
|
|
288
|
-
try
|
|
289
|
-
{
|
|
290
|
-
katex.render(tmpDisplayElements[i].textContent, tmpDisplayElements[i], { throwOnError: false, displayMode: true });
|
|
291
|
-
}
|
|
292
|
-
catch (pError)
|
|
293
|
-
{
|
|
294
|
-
this.log.warn('KaTeX display error: ' + pError.message);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
69
|
+
super.displayContent(pHTMLContent, 'Docuserve-Content-Body');
|
|
297
70
|
}
|
|
298
71
|
|
|
299
72
|
/**
|
|
@@ -301,7 +74,7 @@ class DocuserveContentView extends libPictView
|
|
|
301
74
|
*/
|
|
302
75
|
showLoading()
|
|
303
76
|
{
|
|
304
|
-
|
|
77
|
+
super.showLoading('Loading documentation...', 'Docuserve-Content-Body');
|
|
305
78
|
}
|
|
306
79
|
}
|
|
307
80
|
|