@forwardimpact/pathway 0.25.15 → 0.25.21
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/bin/fit-pathway.js +62 -54
- package/package.json +1 -3
- package/src/commands/agent-io.js +120 -0
- package/src/commands/agent.js +266 -349
- package/src/commands/init.js +2 -2
- package/src/commands/job.js +237 -183
- package/src/components/comparison-radar.js +118 -103
- package/src/components/progression-table.js +244 -208
- package/src/formatters/index.js +0 -19
- package/src/formatters/interview/markdown.js +100 -88
- package/src/formatters/job/description.js +76 -75
- package/src/formatters/job/dom.js +113 -97
- package/src/formatters/level/dom.js +87 -102
- package/src/formatters/questions/markdown.js +37 -33
- package/src/formatters/questions/shared.js +142 -75
- package/src/formatters/skill/dom.js +102 -93
- package/src/lib/comparison-radar-chart.js +256 -0
- package/src/lib/radar-utils.js +199 -0
- package/src/lib/radar.js +25 -662
- package/src/pages/agent-builder-download.js +170 -0
- package/src/pages/agent-builder-preview.js +344 -0
- package/src/pages/agent-builder.js +6 -550
- package/src/pages/progress-comparison.js +110 -0
- package/src/pages/progress.js +11 -111
- package/src/pages/self-assessment-steps.js +494 -0
- package/src/pages/self-assessment.js +54 -504
- package/src/formatters/behaviour/microdata.js +0 -106
- package/src/formatters/discipline/microdata.js +0 -117
- package/src/formatters/driver/microdata.js +0 -91
- package/src/formatters/level/microdata.js +0 -141
- package/src/formatters/microdata-shared.js +0 -184
- package/src/formatters/skill/microdata.js +0 -151
- package/src/formatters/stage/microdata.js +0 -116
- package/src/formatters/track/microdata.js +0 -111
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Behaviour formatting for microdata HTML output
|
|
3
|
-
*
|
|
4
|
-
* Generates clean, class-less HTML with microdata aligned with behaviour.schema.json
|
|
5
|
-
* RDF vocab: https://www.forwardimpact.team/schema/rdf/
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
openTag,
|
|
10
|
-
prop,
|
|
11
|
-
propRaw,
|
|
12
|
-
metaTag,
|
|
13
|
-
section,
|
|
14
|
-
dl,
|
|
15
|
-
ul,
|
|
16
|
-
escapeHtml,
|
|
17
|
-
formatLevelName,
|
|
18
|
-
htmlDocument,
|
|
19
|
-
} from "../microdata-shared.js";
|
|
20
|
-
import { prepareBehavioursList, prepareBehaviourDetail } from "./shared.js";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Format behaviour list as microdata HTML
|
|
24
|
-
* @param {Array} behaviours - Raw behaviour entities
|
|
25
|
-
* @returns {string} HTML with microdata
|
|
26
|
-
*/
|
|
27
|
-
export function behaviourListToMicrodata(behaviours) {
|
|
28
|
-
const { items } = prepareBehavioursList(behaviours);
|
|
29
|
-
|
|
30
|
-
const content = items
|
|
31
|
-
.map(
|
|
32
|
-
(
|
|
33
|
-
behaviour,
|
|
34
|
-
) => `${openTag("article", { itemtype: "Behaviour", itemid: `#${behaviour.id}` })}
|
|
35
|
-
${prop("h2", "name", behaviour.name)}
|
|
36
|
-
${prop("p", "description", behaviour.truncatedDescription)}
|
|
37
|
-
</article>`,
|
|
38
|
-
)
|
|
39
|
-
.join("\n");
|
|
40
|
-
|
|
41
|
-
return htmlDocument(
|
|
42
|
-
"Behaviours",
|
|
43
|
-
`<main>
|
|
44
|
-
<h1>Behaviours</h1>
|
|
45
|
-
${content}
|
|
46
|
-
</main>`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Format behaviour detail as microdata HTML
|
|
52
|
-
* @param {Object} behaviour - Raw behaviour entity
|
|
53
|
-
* @param {Object} context - Additional context
|
|
54
|
-
* @param {Array} context.drivers - All drivers
|
|
55
|
-
* @returns {string} HTML with microdata
|
|
56
|
-
*/
|
|
57
|
-
export function behaviourToMicrodata(behaviour, { drivers }) {
|
|
58
|
-
const view = prepareBehaviourDetail(behaviour, { drivers });
|
|
59
|
-
|
|
60
|
-
if (!view) return "";
|
|
61
|
-
|
|
62
|
-
const sections = [];
|
|
63
|
-
|
|
64
|
-
// Maturity descriptions - uses MaturityDescriptions itemtype
|
|
65
|
-
const maturityPairs = Object.entries(view.maturityDescriptions).map(
|
|
66
|
-
([maturity, desc]) => ({
|
|
67
|
-
term: formatLevelName(maturity),
|
|
68
|
-
definition: desc,
|
|
69
|
-
itemprop: `${maturity.replace(/_([a-z])/g, (_, c) => c.toUpperCase())}Description`,
|
|
70
|
-
}),
|
|
71
|
-
);
|
|
72
|
-
sections.push(
|
|
73
|
-
section(
|
|
74
|
-
"Maturity Levels",
|
|
75
|
-
`${openTag("div", { itemtype: "MaturityDescriptions", itemprop: "maturityDescriptions" })}
|
|
76
|
-
${dl(maturityPairs)}
|
|
77
|
-
</div>`,
|
|
78
|
-
2,
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
// Related drivers
|
|
83
|
-
if (view.relatedDrivers.length > 0) {
|
|
84
|
-
const driverItems = view.relatedDrivers.map(
|
|
85
|
-
(d) => `<a href="#${escapeHtml(d.id)}">${escapeHtml(d.name)}</a>`,
|
|
86
|
-
);
|
|
87
|
-
sections.push(section("Linked to Drivers", ul(driverItems), 2));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const body = `<main>
|
|
91
|
-
${openTag("article", { itemtype: "Behaviour", itemid: `#${view.id}` })}
|
|
92
|
-
${prop("h1", "name", view.name)}
|
|
93
|
-
${metaTag("id", view.id)}
|
|
94
|
-
${propRaw(
|
|
95
|
-
"div",
|
|
96
|
-
"human",
|
|
97
|
-
`${openTag("div", { itemtype: "BehaviourHumanSection" })}
|
|
98
|
-
${prop("p", "description", view.description)}
|
|
99
|
-
${sections.join("\n")}
|
|
100
|
-
</div>`,
|
|
101
|
-
)}
|
|
102
|
-
</article>
|
|
103
|
-
</main>`;
|
|
104
|
-
|
|
105
|
-
return htmlDocument(view.name, body);
|
|
106
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Discipline formatting for microdata HTML output
|
|
3
|
-
*
|
|
4
|
-
* Generates clean, class-less HTML with microdata aligned with discipline.schema.json
|
|
5
|
-
* RDF vocab: https://www.forwardimpact.team/schema/rdf/
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
openTag,
|
|
10
|
-
prop,
|
|
11
|
-
metaTag,
|
|
12
|
-
linkTag,
|
|
13
|
-
section,
|
|
14
|
-
ul,
|
|
15
|
-
escapeHtml,
|
|
16
|
-
htmlDocument,
|
|
17
|
-
} from "../microdata-shared.js";
|
|
18
|
-
import { prepareDisciplinesList, prepareDisciplineDetail } from "./shared.js";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Format discipline list as microdata HTML
|
|
22
|
-
* @param {Array} disciplines - Raw discipline entities
|
|
23
|
-
* @returns {string} HTML with microdata
|
|
24
|
-
*/
|
|
25
|
-
export function disciplineListToMicrodata(disciplines) {
|
|
26
|
-
const { items } = prepareDisciplinesList(disciplines);
|
|
27
|
-
|
|
28
|
-
const content = items
|
|
29
|
-
.map(
|
|
30
|
-
(
|
|
31
|
-
d,
|
|
32
|
-
) => `${openTag("article", { itemtype: "Discipline", itemid: `#${d.id}` })}
|
|
33
|
-
${prop("h2", "specialization", d.name)}
|
|
34
|
-
<p>Core: ${d.coreSkillsCount} | Supporting: ${d.supportingSkillsCount} | Broad: ${d.broadSkillsCount}</p>
|
|
35
|
-
</article>`,
|
|
36
|
-
)
|
|
37
|
-
.join("\n");
|
|
38
|
-
|
|
39
|
-
return htmlDocument(
|
|
40
|
-
"Disciplines",
|
|
41
|
-
`<main>
|
|
42
|
-
<h1>Disciplines</h1>
|
|
43
|
-
${content}
|
|
44
|
-
</main>`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Format discipline detail as microdata HTML
|
|
50
|
-
* @param {Object} discipline - Raw discipline entity
|
|
51
|
-
* @param {Object} context - Additional context
|
|
52
|
-
* @param {Array} context.skills - All skills
|
|
53
|
-
* @param {Array} context.behaviours - All behaviours
|
|
54
|
-
* @param {boolean} [context.showBehaviourModifiers=true] - Whether to show behaviour modifiers section
|
|
55
|
-
* @returns {string} HTML with microdata
|
|
56
|
-
*/
|
|
57
|
-
export function disciplineToMicrodata(
|
|
58
|
-
discipline,
|
|
59
|
-
{ skills, behaviours, showBehaviourModifiers = true } = {},
|
|
60
|
-
) {
|
|
61
|
-
const view = prepareDisciplineDetail(discipline, { skills, behaviours });
|
|
62
|
-
|
|
63
|
-
if (!view) return "";
|
|
64
|
-
|
|
65
|
-
const sections = [];
|
|
66
|
-
|
|
67
|
-
// Core skills - using coreSkills property
|
|
68
|
-
if (view.coreSkills.length > 0) {
|
|
69
|
-
const skillLinks = view.coreSkills.map(
|
|
70
|
-
(s) =>
|
|
71
|
-
`${openTag("span", { itemprop: "coreSkills" })}<a href="#${escapeHtml(s.id)}">${escapeHtml(s.name)}</a></span>`,
|
|
72
|
-
);
|
|
73
|
-
sections.push(section("Core Skills", ul(skillLinks), 2));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Supporting skills - using supportingSkills property
|
|
77
|
-
if (view.supportingSkills.length > 0) {
|
|
78
|
-
const skillLinks = view.supportingSkills.map(
|
|
79
|
-
(s) =>
|
|
80
|
-
`${openTag("span", { itemprop: "supportingSkills" })}<a href="#${escapeHtml(s.id)}">${escapeHtml(s.name)}</a></span>`,
|
|
81
|
-
);
|
|
82
|
-
sections.push(section("Supporting Skills", ul(skillLinks), 2));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Broad skills - using broadSkills property
|
|
86
|
-
if (view.broadSkills.length > 0) {
|
|
87
|
-
const skillLinks = view.broadSkills.map(
|
|
88
|
-
(s) =>
|
|
89
|
-
`${openTag("span", { itemprop: "broadSkills" })}<a href="#${escapeHtml(s.id)}">${escapeHtml(s.name)}</a></span>`,
|
|
90
|
-
);
|
|
91
|
-
sections.push(section("Broad Skills", ul(skillLinks), 2));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Behaviour modifiers - using BehaviourModifier itemtype
|
|
95
|
-
if (showBehaviourModifiers && view.behaviourModifiers.length > 0) {
|
|
96
|
-
const modifierItems = view.behaviourModifiers.map((b) => {
|
|
97
|
-
const modifierStr = b.modifier > 0 ? `+${b.modifier}` : `${b.modifier}`;
|
|
98
|
-
return `${openTag("span", { itemtype: "BehaviourModifier", itemprop: "behaviourModifiers" })}
|
|
99
|
-
${linkTag("targetBehaviour", `#${b.id}`)}
|
|
100
|
-
<a href="#${escapeHtml(b.id)}">${escapeHtml(b.name)}</a>: ${openTag("span", { itemprop: "modifierValue" })}${modifierStr}</span>
|
|
101
|
-
</span>`;
|
|
102
|
-
});
|
|
103
|
-
sections.push(section("Behaviour Modifiers", ul(modifierItems), 2));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const body = `<main>
|
|
107
|
-
${openTag("article", { itemtype: "Discipline", itemid: `#${view.id}` })}
|
|
108
|
-
${prop("h1", "specialization", view.name)}
|
|
109
|
-
${metaTag("id", view.id)}
|
|
110
|
-
${discipline.roleTitle ? prop("p", "roleTitle", discipline.roleTitle) : ""}
|
|
111
|
-
${prop("p", "description", view.description)}
|
|
112
|
-
${sections.join("\n")}
|
|
113
|
-
</article>
|
|
114
|
-
</main>`;
|
|
115
|
-
|
|
116
|
-
return htmlDocument(view.name, body);
|
|
117
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Driver formatting for microdata HTML output
|
|
3
|
-
*
|
|
4
|
-
* Generates clean, class-less HTML with microdata aligned with drivers.schema.json
|
|
5
|
-
* RDF vocab: https://www.forwardimpact.team/schema/rdf/
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
openTag,
|
|
10
|
-
prop,
|
|
11
|
-
metaTag,
|
|
12
|
-
section,
|
|
13
|
-
ul,
|
|
14
|
-
escapeHtml,
|
|
15
|
-
htmlDocument,
|
|
16
|
-
} from "../microdata-shared.js";
|
|
17
|
-
import { prepareDriversList, prepareDriverDetail } from "./shared.js";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Format driver list as microdata HTML
|
|
21
|
-
* @param {Array} drivers - Raw driver entities
|
|
22
|
-
* @returns {string} HTML with microdata
|
|
23
|
-
*/
|
|
24
|
-
export function driverListToMicrodata(drivers) {
|
|
25
|
-
const { items } = prepareDriversList(drivers);
|
|
26
|
-
|
|
27
|
-
const content = items
|
|
28
|
-
.map(
|
|
29
|
-
(
|
|
30
|
-
driver,
|
|
31
|
-
) => `${openTag("article", { itemtype: "Driver", itemid: `#${driver.id}` })}
|
|
32
|
-
${prop("h2", "name", driver.name)}
|
|
33
|
-
${prop("p", "description", driver.truncatedDescription)}
|
|
34
|
-
<p>Skills: ${driver.contributingSkillsCount} | Behaviours: ${driver.contributingBehavioursCount}</p>
|
|
35
|
-
</article>`,
|
|
36
|
-
)
|
|
37
|
-
.join("\n");
|
|
38
|
-
|
|
39
|
-
return htmlDocument(
|
|
40
|
-
"Drivers",
|
|
41
|
-
`<main>
|
|
42
|
-
<h1>Drivers</h1>
|
|
43
|
-
${content}
|
|
44
|
-
</main>`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Format driver detail as microdata HTML
|
|
50
|
-
* @param {Object} driver - Raw driver entity
|
|
51
|
-
* @param {Object} context - Additional context
|
|
52
|
-
* @param {Array} context.skills - All skills
|
|
53
|
-
* @param {Array} context.behaviours - All behaviours
|
|
54
|
-
* @returns {string} HTML with microdata
|
|
55
|
-
*/
|
|
56
|
-
export function driverToMicrodata(driver, { skills, behaviours }) {
|
|
57
|
-
const view = prepareDriverDetail(driver, { skills, behaviours });
|
|
58
|
-
|
|
59
|
-
if (!view) return "";
|
|
60
|
-
|
|
61
|
-
const sections = [];
|
|
62
|
-
|
|
63
|
-
// Contributing skills - using contributingSkills property
|
|
64
|
-
if (view.contributingSkills.length > 0) {
|
|
65
|
-
const skillLinks = view.contributingSkills.map(
|
|
66
|
-
(s) =>
|
|
67
|
-
`${openTag("span", { itemprop: "contributingSkills" })}<a href="#${escapeHtml(s.id)}">${escapeHtml(s.name)}</a></span>`,
|
|
68
|
-
);
|
|
69
|
-
sections.push(section("Contributing Skills", ul(skillLinks), 2));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Contributing behaviours - using contributingBehaviours property
|
|
73
|
-
if (view.contributingBehaviours.length > 0) {
|
|
74
|
-
const behaviourLinks = view.contributingBehaviours.map(
|
|
75
|
-
(b) =>
|
|
76
|
-
`${openTag("span", { itemprop: "contributingBehaviours" })}<a href="#${escapeHtml(b.id)}">${escapeHtml(b.name)}</a></span>`,
|
|
77
|
-
);
|
|
78
|
-
sections.push(section("Contributing Behaviours", ul(behaviourLinks), 2));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const body = `<main>
|
|
82
|
-
${openTag("article", { itemtype: "Driver", itemid: `#${view.id}` })}
|
|
83
|
-
${prop("h1", "name", view.name)}
|
|
84
|
-
${metaTag("id", view.id)}
|
|
85
|
-
${prop("p", "description", view.description)}
|
|
86
|
-
${sections.join("\n")}
|
|
87
|
-
</article>
|
|
88
|
-
</main>`;
|
|
89
|
-
|
|
90
|
-
return htmlDocument(view.name, body);
|
|
91
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Level formatting for microdata HTML output
|
|
3
|
-
*
|
|
4
|
-
* Generates clean, class-less HTML with microdata aligned with levels.schema.json
|
|
5
|
-
* RDF vocab: https://www.forwardimpact.team/schema/rdf/
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
openTag,
|
|
10
|
-
prop,
|
|
11
|
-
metaTag,
|
|
12
|
-
linkTag,
|
|
13
|
-
section,
|
|
14
|
-
dl,
|
|
15
|
-
escapeHtml,
|
|
16
|
-
formatLevelName,
|
|
17
|
-
htmlDocument,
|
|
18
|
-
} from "../microdata-shared.js";
|
|
19
|
-
import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Format level list as microdata HTML
|
|
23
|
-
* @param {Array} levels - Raw level entities
|
|
24
|
-
* @returns {string} HTML with microdata
|
|
25
|
-
*/
|
|
26
|
-
export function levelListToMicrodata(levels) {
|
|
27
|
-
const { items } = prepareLevelsList(levels);
|
|
28
|
-
|
|
29
|
-
const content = items
|
|
30
|
-
.map(
|
|
31
|
-
(g) => `${openTag("article", { itemtype: "Level", itemid: `#${g.id}` })}
|
|
32
|
-
${prop("h2", "id", g.id)}
|
|
33
|
-
<p>${escapeHtml(g.displayName)}</p>
|
|
34
|
-
${g.typicalExperienceRange ? prop("p", "typicalExperienceRange", g.typicalExperienceRange) : ""}
|
|
35
|
-
${metaTag("ordinalRank", String(g.ordinalRank))}
|
|
36
|
-
</article>`,
|
|
37
|
-
)
|
|
38
|
-
.join("\n");
|
|
39
|
-
|
|
40
|
-
return htmlDocument(
|
|
41
|
-
"Levels",
|
|
42
|
-
`<main>
|
|
43
|
-
<h1>Levels</h1>
|
|
44
|
-
${content}
|
|
45
|
-
</main>`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Format level detail as microdata HTML
|
|
51
|
-
* @param {Object} level - Raw level entity
|
|
52
|
-
* @returns {string} HTML with microdata
|
|
53
|
-
*/
|
|
54
|
-
export function levelToMicrodata(level) {
|
|
55
|
-
const view = prepareLevelDetail(level);
|
|
56
|
-
|
|
57
|
-
if (!view) return "";
|
|
58
|
-
|
|
59
|
-
const sections = [];
|
|
60
|
-
|
|
61
|
-
// Titles section
|
|
62
|
-
if (view.professionalTitle || view.managementTitle) {
|
|
63
|
-
const titlePairs = [];
|
|
64
|
-
if (view.professionalTitle) {
|
|
65
|
-
titlePairs.push({
|
|
66
|
-
term: "Professional Track",
|
|
67
|
-
definition: view.professionalTitle,
|
|
68
|
-
itemprop: "professionalTitle",
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
if (view.managementTitle) {
|
|
72
|
-
titlePairs.push({
|
|
73
|
-
term: "Management Track",
|
|
74
|
-
definition: view.managementTitle,
|
|
75
|
-
itemprop: "managementTitle",
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
sections.push(section("Titles", dl(titlePairs), 2));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Base skill proficiencies - using BaseSkillProficiencies itemtype
|
|
82
|
-
if (
|
|
83
|
-
view.baseSkillProficiencies &&
|
|
84
|
-
Object.keys(view.baseSkillProficiencies).length > 0
|
|
85
|
-
) {
|
|
86
|
-
const levelPairs = Object.entries(view.baseSkillProficiencies).map(
|
|
87
|
-
([type, level]) => ({
|
|
88
|
-
term: formatLevelName(type),
|
|
89
|
-
definition: formatLevelName(level),
|
|
90
|
-
itemprop: type,
|
|
91
|
-
}),
|
|
92
|
-
);
|
|
93
|
-
sections.push(
|
|
94
|
-
section(
|
|
95
|
-
"Base Skill Proficiencies",
|
|
96
|
-
`${openTag("div", { itemtype: "BaseSkillProficiencies", itemprop: "baseSkillProficiencies" })}
|
|
97
|
-
${dl(levelPairs)}
|
|
98
|
-
</div>`,
|
|
99
|
-
2,
|
|
100
|
-
),
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Base behaviour maturity - link to BehaviourMaturity
|
|
105
|
-
if (view.baseBehaviourMaturity) {
|
|
106
|
-
const maturityContent = `${linkTag("baseBehaviourMaturity", `#${level.baseBehaviourMaturity}`)}
|
|
107
|
-
<p>${formatLevelName(level.baseBehaviourMaturity)}</p>`;
|
|
108
|
-
sections.push(section("Base Behaviour Maturity", maturityContent, 2));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Expectations - using LevelExpectations itemtype
|
|
112
|
-
if (view.expectations && Object.keys(view.expectations).length > 0) {
|
|
113
|
-
const expectationPairs = Object.entries(view.expectations).map(
|
|
114
|
-
([key, value]) => ({
|
|
115
|
-
term: formatLevelName(key),
|
|
116
|
-
definition: value,
|
|
117
|
-
itemprop: key,
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
sections.push(
|
|
121
|
-
section(
|
|
122
|
-
"Expectations",
|
|
123
|
-
`${openTag("div", { itemtype: "LevelExpectations", itemprop: "expectations" })}
|
|
124
|
-
${dl(expectationPairs)}
|
|
125
|
-
</div>`,
|
|
126
|
-
2,
|
|
127
|
-
),
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const body = `<main>
|
|
132
|
-
${openTag("article", { itemtype: "Level", itemid: `#${view.id}` })}
|
|
133
|
-
<h1>${prop("span", "id", view.id)} — ${escapeHtml(view.displayName)}</h1>
|
|
134
|
-
${metaTag("ordinalRank", String(view.ordinalRank))}
|
|
135
|
-
${view.typicalExperienceRange ? prop("p", "typicalExperienceRange", `Experience: ${view.typicalExperienceRange}`) : ""}
|
|
136
|
-
${sections.join("\n")}
|
|
137
|
-
</article>
|
|
138
|
-
</main>`;
|
|
139
|
-
|
|
140
|
-
return htmlDocument(`${view.id} - ${view.displayName}`, body);
|
|
141
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared microdata HTML utilities
|
|
3
|
-
*
|
|
4
|
-
* Helper functions for generating clean, class-less HTML with microdata attributes
|
|
5
|
-
* aligned with the RDF schema at https://www.forwardimpact.team/schema/rdf/
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const VOCAB_BASE = "https://www.forwardimpact.team/schema/rdf/";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create an opening tag with microdata attributes
|
|
12
|
-
* @param {string} tag - HTML tag name
|
|
13
|
-
* @param {Object} [attrs] - Optional attributes
|
|
14
|
-
* @param {string} [attrs.itemtype] - Microdata type (without vocab prefix)
|
|
15
|
-
* @param {string} [attrs.itemprop] - Microdata property name
|
|
16
|
-
* @param {string} [attrs.itemid] - Microdata item ID
|
|
17
|
-
* @returns {string}
|
|
18
|
-
*/
|
|
19
|
-
export function openTag(tag, attrs = {}) {
|
|
20
|
-
const parts = [tag];
|
|
21
|
-
|
|
22
|
-
if (attrs.itemtype) {
|
|
23
|
-
parts.push(`itemscope`);
|
|
24
|
-
parts.push(`itemtype="${VOCAB_BASE}${attrs.itemtype}"`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (attrs.itemprop) {
|
|
28
|
-
parts.push(`itemprop="${attrs.itemprop}"`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (attrs.itemid) {
|
|
32
|
-
parts.push(`itemid="${attrs.itemid}"`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return `<${parts.join(" ")}>`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Create a self-closing meta element with microdata
|
|
40
|
-
* @param {string} itemprop - Property name
|
|
41
|
-
* @param {string} content - Content value
|
|
42
|
-
* @returns {string}
|
|
43
|
-
*/
|
|
44
|
-
export function metaTag(itemprop, content) {
|
|
45
|
-
return `<meta itemprop="${itemprop}" content="${escapeAttr(content)}">`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Create a link element with microdata
|
|
50
|
-
* @param {string} itemprop - Property name
|
|
51
|
-
* @param {string} href - Link target
|
|
52
|
-
* @returns {string}
|
|
53
|
-
*/
|
|
54
|
-
export function linkTag(itemprop, href) {
|
|
55
|
-
return `<link itemprop="${itemprop}" href="${escapeAttr(href)}">`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Wrap content in an element with itemprop
|
|
60
|
-
* @param {string} tag - HTML tag name
|
|
61
|
-
* @param {string} itemprop - Property name
|
|
62
|
-
* @param {string} content - Content to wrap
|
|
63
|
-
* @returns {string}
|
|
64
|
-
*/
|
|
65
|
-
export function prop(tag, itemprop, content) {
|
|
66
|
-
return `<${tag} itemprop="${itemprop}">${escapeHtml(content)}</${tag}>`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Wrap raw HTML content in an element with itemprop (no escaping)
|
|
71
|
-
* @param {string} tag - HTML tag name
|
|
72
|
-
* @param {string} itemprop - Property name
|
|
73
|
-
* @param {string} html - HTML content to wrap
|
|
74
|
-
* @returns {string}
|
|
75
|
-
*/
|
|
76
|
-
export function propRaw(tag, itemprop, html) {
|
|
77
|
-
return `<${tag} itemprop="${itemprop}">${html}</${tag}>`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Create a section with optional heading
|
|
82
|
-
* @param {string} heading - Section heading text
|
|
83
|
-
* @param {string} content - Section content
|
|
84
|
-
* @param {number} [level=2] - Heading level (2-6)
|
|
85
|
-
* @returns {string}
|
|
86
|
-
*/
|
|
87
|
-
export function section(heading, content, level = 2) {
|
|
88
|
-
const hTag = `h${Math.min(Math.max(level, 1), 6)}`;
|
|
89
|
-
return `<section>
|
|
90
|
-
<${hTag}>${escapeHtml(heading)}</${hTag}>
|
|
91
|
-
${content}
|
|
92
|
-
</section>`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Create an unordered list
|
|
97
|
-
* @param {string[]} items - List items (already HTML)
|
|
98
|
-
* @param {string} [itemprop] - Optional property for list items
|
|
99
|
-
* @returns {string}
|
|
100
|
-
*/
|
|
101
|
-
export function ul(items, itemprop) {
|
|
102
|
-
if (!items.length) return "";
|
|
103
|
-
const lis = items
|
|
104
|
-
.map((item) =>
|
|
105
|
-
itemprop ? `<li itemprop="${itemprop}">${item}</li>` : `<li>${item}</li>`,
|
|
106
|
-
)
|
|
107
|
-
.join("\n");
|
|
108
|
-
return `<ul>\n${lis}\n</ul>`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Create a definition list from key-value pairs
|
|
113
|
-
* @param {Array<{term: string, definition: string, itemprop?: string}>} pairs
|
|
114
|
-
* @returns {string}
|
|
115
|
-
*/
|
|
116
|
-
export function dl(pairs) {
|
|
117
|
-
if (!pairs.length) return "";
|
|
118
|
-
const content = pairs
|
|
119
|
-
.map(({ term, definition, itemprop }) => {
|
|
120
|
-
const dd = itemprop
|
|
121
|
-
? `<dd itemprop="${itemprop}">${escapeHtml(definition)}</dd>`
|
|
122
|
-
: `<dd>${escapeHtml(definition)}</dd>`;
|
|
123
|
-
return `<dt>${escapeHtml(term)}</dt>\n${dd}`;
|
|
124
|
-
})
|
|
125
|
-
.join("\n");
|
|
126
|
-
return `<dl>\n${content}\n</dl>`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Escape HTML special characters
|
|
131
|
-
* @param {string} str
|
|
132
|
-
* @returns {string}
|
|
133
|
-
*/
|
|
134
|
-
export function escapeHtml(str) {
|
|
135
|
-
if (str == null) return "";
|
|
136
|
-
return String(str)
|
|
137
|
-
.replace(/&/g, "&")
|
|
138
|
-
.replace(/</g, "<")
|
|
139
|
-
.replace(/>/g, ">");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Escape attribute value
|
|
144
|
-
* @param {string} str
|
|
145
|
-
* @returns {string}
|
|
146
|
-
*/
|
|
147
|
-
export function escapeAttr(str) {
|
|
148
|
-
if (str == null) return "";
|
|
149
|
-
return String(str)
|
|
150
|
-
.replace(/&/g, "&")
|
|
151
|
-
.replace(/"/g, """)
|
|
152
|
-
.replace(/</g, "<")
|
|
153
|
-
.replace(/>/g, ">");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Format level name for display (capitalize, replace underscores)
|
|
158
|
-
* @param {string} level
|
|
159
|
-
* @returns {string}
|
|
160
|
-
*/
|
|
161
|
-
export function formatLevelName(level) {
|
|
162
|
-
if (!level) return "";
|
|
163
|
-
return level.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Generate a full microdata HTML document
|
|
168
|
-
* @param {string} title - Document title
|
|
169
|
-
* @param {string} body - Body content
|
|
170
|
-
* @returns {string}
|
|
171
|
-
*/
|
|
172
|
-
export function htmlDocument(title, body) {
|
|
173
|
-
return `<!DOCTYPE html>
|
|
174
|
-
<html lang="en">
|
|
175
|
-
<head>
|
|
176
|
-
<meta charset="UTF-8">
|
|
177
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
178
|
-
<title>${escapeHtml(title)}</title>
|
|
179
|
-
</head>
|
|
180
|
-
<body>
|
|
181
|
-
${body}
|
|
182
|
-
</body>
|
|
183
|
-
</html>`;
|
|
184
|
-
}
|