Skip to content

Commit 05f6fef

Browse files
committed
[INTERNAL] DemoKit: add hierarchical subsection support for API
reference sections Implement support for multi-level section structure in API documentation, allowing sections to contain subsections organized in folder hierarchies. - Sections can now have subsections (e.g., FAQ.md + FAQ/ folder) - Main section content displays as "Overview" subsection - Subsection files automatically loaded from matching directories - Dropdown navigation shows only subsection names, not parent section - The new dynamic sections/subsections are bookmark-able the page automatically scrolls down to the section Example structure: sections/Component/FAQ.md "Overview" subsection sections/Component/FAQ/Example1.md "Example1" subsection sections/Component/FAQ/Example2.md "Example2" subsection JIRA: BGSOFUIPIRIN-6925 Change-Id: I13671f809e2d14b3628b812f5dae1bfcd8d354e9
1 parent 467424b commit 05f6fef

File tree

2 files changed

+88
-34
lines changed

2 files changed

+88
-34
lines changed

lib/jsdoc/transformApiJson.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,15 +1009,41 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
10091009
const componentDir = path.join(sSectionsDir, sComponentPath);
10101010
if (fs.existsSync(componentDir)) {
10111011
try {
1012+
const dirContents = fs.readdirSync(componentDir, { withFileTypes: true });
1013+
1014+
// Separate files and directories in one pass
1015+
const mdFiles = [];
1016+
const dirNames = new Set();
1017+
dirContents.forEach((dirent) => {
1018+
if (dirent.isFile() && dirent.name.endsWith('.md')) {
1019+
mdFiles.push(dirent.name.replace(/\.md$/, ''));
1020+
} else if (dirent.isDirectory()) {
1021+
dirNames.add(dirent.name);
1022+
}
1023+
});
10121024

1013-
const customSections = fs.readdirSync(componentDir, { withFileTypes: true })
1014-
.filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
1015-
.map((dirent) => dirent.name.replace(/\.md$/, ''));
1016-
1017-
// Store list of available sections for this symbol
1018-
if (customSections.length > 0) {
1019-
symbol.customSections = customSections;
1025+
// Build customSections array with subsection support
1026+
const customSections = mdFiles.map(function(sectionName) {
1027+
// Check if there's a matching directory for this section
1028+
if (dirNames.has(sectionName)) {
1029+
try {
1030+
const subsectionFiles = fs.readdirSync(path.join(componentDir, sectionName), { withFileTypes: true })
1031+
.filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
1032+
.map((dirent) => dirent.name.replace(/\.md$/, ''));
1033+
1034+
if (subsectionFiles.length > 0) {
1035+
return { name: sectionName, hasSubsections: true, subsections: subsectionFiles };
1036+
}
1037+
} catch (error) {
1038+
log.error('Error scanning subsection directory:', path.join(componentDir, sectionName), error);
1039+
}
10201040
}
1041+
return sectionName;
1042+
});
1043+
1044+
if (customSections.length > 0) {
1045+
symbol.customSections = customSections;
1046+
}
10211047

10221048
} catch (error) {
10231049
log.error('Error scanning component sections directory:', componentDir, error);

src/sap.ui.documentation/src/sap/ui/documentation/sdk/controller/SubApiDetail.controller.js

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -127,35 +127,51 @@ sap.ui.define([
127127
var sLibName = this._oEntityData.lib,
128128
sLibPath = sLibName.replace(/\./g, '/'),
129129
sComponentPath = this._oEntityData.name.replace(sLibName, "").replace(/^[.]/, "").replace(/\./g, '/'),
130-
aSectionTypes = (this._oControlData.customSections || []).map(function(sectionType) {
130+
aSectionTypes = (this._oControlData.customSections || []).map(function(section) {
131+
var sType = typeof section === 'string' ? section : section.name;
131132
return {
132-
type: sectionType,
133-
property: sectionType + "Content",
134-
displayTitle: sectionType.charAt(0).toUpperCase() + sectionType.slice(1),
135-
sectionId: sectionType.replace(/[$#/]/g, ".") + "_section"
133+
type: sType,
134+
property: sType + "Content",
135+
displayTitle: sType.charAt(0).toUpperCase() + sType.slice(1),
136+
sectionId: sType.toLowerCase(),
137+
hasSubsections: section.hasSubsections || false,
138+
subsections: section.subsections || null
136139
};
137140
});
138141

139-
Promise.all(aSectionTypes.map(function(oSection) {
140-
var sUrl = './docs/api/' + sLibPath + '/demokit/sections/' + sComponentPath + '/' + oSection.type + '.html';
142+
var fnLoadContent = function(sUrl, sProperty) {
141143
return new Promise(function(resolve) {
142144
jQuery.ajax({
143145
url: sUrl,
144146
success: function(data) {
145-
this._oModel.setProperty("/" + oSection.property, data);
146-
resolve(oSection);
147+
this._oModel.setProperty("/" + sProperty, data);
148+
resolve(true);
147149
}.bind(this),
148-
error: function() {
149-
resolve(null);
150-
}
150+
error: function() { resolve(false); }
151151
});
152-
}.bind(this));
153-
}.bind(this))).then(function(results) {
154-
results.forEach(function(oSection) {
155-
if (oSection) {
156-
this._createAndAddSection(oSection);
157-
}
158152
}.bind(this));
153+
}.bind(this);
154+
155+
var aContentPromises = aSectionTypes.map(function(oSection) {
156+
var aPromises = [];
157+
var sBaseUrl = './docs/api/' + sLibPath + '/demokit/sections/' + sComponentPath + '/';
158+
159+
if (oSection.hasSubsections) {
160+
// Load main + subsections
161+
aPromises.push(fnLoadContent(sBaseUrl + oSection.type + '.html', oSection.type + "_mainContent"));
162+
oSection.subsections.forEach(function(sub) {
163+
aPromises.push(fnLoadContent(sBaseUrl + oSection.type + '/' + sub + '.html', oSection.type + "_" + sub + "Content"));
164+
});
165+
} else {
166+
// Load single section
167+
aPromises.push(fnLoadContent(sBaseUrl + oSection.type + '.html', oSection.property));
168+
}
169+
170+
return Promise.all(aPromises).then(function() { return oSection; });
171+
});
172+
173+
Promise.all(aContentPromises).then(function(aSections) {
174+
aSections.forEach(this._createAndAddSection, this);
159175
}.bind(this));
160176

161177
this.setModel(this._oModel);
@@ -754,18 +770,30 @@ sap.ui.define([
754770
* @private
755771
*/
756772
_createAndAddSection: function(oSectionConfig) {
757-
var oHTMLControl = new HTML({content: "{/" + oSectionConfig.property + "}"});
758-
oHTMLControl.setModel(this._oModel);
773+
var fnCreateSubSection = function(sProperty, sTitle) {
774+
var oHTML = new HTML({content: "{/" + sProperty + "}"});
775+
oHTML.setModel(this._oModel);
776+
return new ObjectPageSubSection(sTitle ? {title: sTitle, blocks: [oHTML]} : {blocks: [oHTML]});
777+
}.bind(this);
778+
779+
var aSubSections = [];
780+
if (oSectionConfig.hasSubsections) {
781+
aSubSections.push(fnCreateSubSection(oSectionConfig.type + "_mainContent", "Overview"));
782+
oSectionConfig.subsections.forEach(function(sub) {
783+
aSubSections.push(fnCreateSubSection(oSectionConfig.type + "_" + sub + "Content", sub));
784+
});
785+
} else {
786+
aSubSections.push(fnCreateSubSection(oSectionConfig.property));
787+
}
759788

760-
var oPageSection = new ObjectPageSection({
789+
var oPageSection = new ObjectPageSection({
761790
id: this.createId(oSectionConfig.sectionId),
762791
title: oSectionConfig.displayTitle,
763-
titleUppercase: false,
764-
subSections: [new ObjectPageSubSection({blocks: [oHTMLControl]})]
765-
});
766-
oPageSection.addStyleClass("sectionContent");
767-
768-
this._objectPage.addSection(oPageSection);
792+
titleUppercase: false,
793+
subSections: aSubSections
794+
});
795+
oPageSection.addStyleClass("sectionContent");
796+
this._objectPage.addSection(oPageSection);
769797
},
770798
onAnnotationsLinkPress: function () {
771799
this.scrollToEntity("annotations", "Summary");

0 commit comments

Comments
 (0)