@forwardimpact/pathway 0.4.0 → 0.6.0
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/{pathway.js → fit-pathway.js} +65 -153
- package/package.json +18 -41
- package/{app → src}/commands/agent.js +5 -2
- package/{app → src}/commands/behaviour.js +1 -1
- package/{app → src}/commands/command-factory.js +2 -2
- package/{app → src}/commands/discipline.js +1 -1
- package/{app → src}/commands/driver.js +2 -2
- package/{app → src}/commands/grade.js +2 -2
- package/{app → src}/commands/job.js +3 -3
- package/{app → src}/commands/serve.js +26 -4
- package/{app → src}/commands/site.js +24 -4
- package/{app → src}/commands/skill.js +3 -3
- package/{app → src}/commands/stage.js +1 -1
- package/{app → src}/commands/track.js +2 -2
- package/{app → src}/components/card.js +11 -1
- package/{app → src}/components/checklist.js +1 -1
- package/src/components/code-display.js +153 -0
- package/{app → src}/components/comparison-radar.js +1 -1
- package/{app → src}/components/detail.js +1 -1
- package/src/components/markdown-textarea.js +153 -0
- package/{app → src}/components/skill-matrix.js +1 -1
- package/{app → src}/css/bundles/app.css +14 -0
- package/{app → src}/css/components/badges.css +15 -8
- package/{app → src}/css/components/forms.css +23 -13
- package/{app → src}/css/components/surfaces.css +49 -3
- package/{app → src}/css/components/typography.css +1 -2
- package/{app → src}/css/pages/agent-builder.css +11 -102
- package/{app → src}/css/pages/detail.css +11 -1
- package/{app → src}/css/tokens.css +3 -0
- package/{app → src}/formatters/agent/dom.js +26 -71
- package/{app → src}/formatters/agent/profile.js +11 -6
- package/{app → src}/formatters/behaviour/dom.js +1 -1
- package/{app → src}/formatters/discipline/dom.js +1 -1
- package/{app → src}/formatters/driver/dom.js +1 -1
- package/{app → src}/formatters/grade/dom.js +7 -7
- package/{app → src}/formatters/grade/markdown.js +1 -1
- package/{app → src}/formatters/interview/dom.js +1 -1
- package/{app → src}/formatters/interview/markdown.js +1 -1
- package/{app → src}/formatters/interview/shared.js +3 -3
- package/{app → src}/formatters/job/description.js +1 -1
- package/{app → src}/formatters/job/dom.js +3 -3
- package/{app → src}/formatters/job/markdown.js +1 -1
- package/{app → src}/formatters/json-ld.js +1 -1
- package/{app → src}/formatters/progress/shared.js +3 -3
- package/{app → src}/formatters/skill/dom.js +69 -57
- package/{app → src}/formatters/skill/markdown.js +1 -1
- package/{app → src}/formatters/skill/shared.js +5 -3
- package/{app → src}/formatters/stage/microdata.js +2 -2
- package/{app → src}/formatters/stage/shared.js +3 -3
- package/{app → src}/formatters/tool/shared.js +6 -0
- package/{app → src}/formatters/track/dom.js +1 -1
- package/{app → src}/formatters/track/markdown.js +1 -1
- package/{app → src}/formatters/track/shared.js +4 -1
- package/{app → src}/handout-main.js +16 -12
- package/src/handout.html +43 -0
- package/{app → src}/index.html +23 -2
- package/{app → src}/lib/card-mappers.js +28 -1
- package/{app → src}/lib/job-cache.js +1 -1
- package/{app → src}/lib/render.js +1 -1
- package/{app → src}/pages/agent-builder.js +120 -76
- package/{app → src}/pages/assessment-results.js +1 -1
- package/{app → src}/pages/interview.js +1 -1
- package/{app → src}/pages/job-builder.js +1 -1
- package/{app → src}/pages/job.js +1 -1
- package/{app → src}/pages/landing.js +5 -2
- package/{app → src}/pages/self-assessment.js +1 -1
- package/{app → src}/pages/skill.js +1 -1
- package/{app → src}/pages/stage.js +5 -5
- package/{app → src}/pages/tool.js +1 -1
- package/{app → src}/slide-main.js +2 -2
- package/{app → src}/slides/chapter.js +8 -8
- package/{app → src}/slides/index.js +3 -3
- package/{app → src}/slides/job.js +1 -1
- package/{app → src}/slides/overview.js +9 -9
- package/{app → src}/slides/skill.js +1 -0
- package/{app → src}/slides.html +16 -1
- package/templates/agent.template.md +44 -13
- package/templates/job.template.md +14 -20
- package/templates/skill.template.md +20 -23
- package/LICENSE +0 -201
- package/README.md +0 -104
- package/app/components/markdown-textarea.js +0 -132
- package/app/handout.html +0 -28
- package/app/model/agent.js +0 -738
- package/app/model/checklist.js +0 -103
- package/app/model/derivation.js +0 -766
- package/app/model/index-generator.js +0 -65
- package/app/model/interview.js +0 -539
- package/app/model/job.js +0 -228
- package/app/model/levels.js +0 -601
- package/app/model/loader.js +0 -599
- package/app/model/matching.js +0 -888
- package/app/model/modifiers.js +0 -158
- package/app/model/profile.js +0 -259
- package/app/model/progression.js +0 -507
- package/app/model/schema-validation.js +0 -438
- package/app/model/validation.js +0 -2130
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
- package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
- package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
- package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
- package/examples/agents/.vscode/settings.json +0 -8
- package/examples/behaviours/_index.yaml +0 -8
- package/examples/behaviours/outcome_ownership.yaml +0 -43
- package/examples/behaviours/polymathic_knowledge.yaml +0 -41
- package/examples/behaviours/precise_communication.yaml +0 -39
- package/examples/behaviours/relentless_curiosity.yaml +0 -37
- package/examples/behaviours/systems_thinking.yaml +0 -40
- package/examples/capabilities/_index.yaml +0 -8
- package/examples/capabilities/business.yaml +0 -189
- package/examples/capabilities/delivery.yaml +0 -303
- package/examples/capabilities/people.yaml +0 -68
- package/examples/capabilities/reliability.yaml +0 -412
- package/examples/capabilities/scale.yaml +0 -378
- package/examples/copilot-setup-steps.yaml +0 -25
- package/examples/devcontainer.yaml +0 -21
- package/examples/disciplines/_index.yaml +0 -6
- package/examples/disciplines/data_engineering.yaml +0 -78
- package/examples/disciplines/engineering_management.yaml +0 -63
- package/examples/disciplines/software_engineering.yaml +0 -78
- package/examples/drivers.yaml +0 -202
- package/examples/framework.yaml +0 -69
- package/examples/grades.yaml +0 -115
- package/examples/questions/behaviours/outcome_ownership.yaml +0 -51
- package/examples/questions/behaviours/polymathic_knowledge.yaml +0 -47
- package/examples/questions/behaviours/precise_communication.yaml +0 -54
- package/examples/questions/behaviours/relentless_curiosity.yaml +0 -50
- package/examples/questions/behaviours/systems_thinking.yaml +0 -52
- package/examples/questions/skills/architecture_design.yaml +0 -53
- package/examples/questions/skills/cloud_platforms.yaml +0 -47
- package/examples/questions/skills/code_quality.yaml +0 -48
- package/examples/questions/skills/data_modeling.yaml +0 -45
- package/examples/questions/skills/devops.yaml +0 -46
- package/examples/questions/skills/full_stack_development.yaml +0 -47
- package/examples/questions/skills/sre_practices.yaml +0 -43
- package/examples/questions/skills/stakeholder_management.yaml +0 -48
- package/examples/questions/skills/team_collaboration.yaml +0 -42
- package/examples/questions/skills/technical_writing.yaml +0 -42
- package/examples/self-assessments.yaml +0 -64
- package/examples/stages.yaml +0 -131
- package/examples/tracks/_index.yaml +0 -5
- package/examples/tracks/platform.yaml +0 -49
- package/examples/tracks/sre.yaml +0 -48
- package/examples/vscode-settings.yaml +0 -17
- /package/{app → src}/commands/index.js +0 -0
- /package/{app → src}/commands/init.js +0 -0
- /package/{app → src}/commands/interview.js +0 -0
- /package/{app → src}/commands/progress.js +0 -0
- /package/{app → src}/commands/questions.js +0 -0
- /package/{app → src}/commands/tool.js +0 -0
- /package/{app → src}/components/action-buttons.js +0 -0
- /package/{app → src}/components/behaviour-profile.js +0 -0
- /package/{app → src}/components/builder.js +0 -0
- /package/{app → src}/components/error-page.js +0 -0
- /package/{app → src}/components/grid.js +0 -0
- /package/{app → src}/components/list.js +0 -0
- /package/{app → src}/components/modifier-table.js +0 -0
- /package/{app → src}/components/nav.js +0 -0
- /package/{app → src}/components/progression-table.js +0 -0
- /package/{app → src}/components/radar-chart.js +0 -0
- /package/{app → src}/css/base.css +0 -0
- /package/{app → src}/css/bundles/handout.css +0 -0
- /package/{app → src}/css/bundles/slides.css +0 -0
- /package/{app → src}/css/components/buttons.css +0 -0
- /package/{app → src}/css/components/layout.css +0 -0
- /package/{app → src}/css/components/nav.css +0 -0
- /package/{app → src}/css/components/progress.css +0 -0
- /package/{app → src}/css/components/states.css +0 -0
- /package/{app → src}/css/components/tables.css +0 -0
- /package/{app → src}/css/components/utilities.css +0 -0
- /package/{app → src}/css/pages/assessment-results.css +0 -0
- /package/{app → src}/css/pages/interview-builder.css +0 -0
- /package/{app → src}/css/pages/job-builder.css +0 -0
- /package/{app → src}/css/pages/landing.css +0 -0
- /package/{app → src}/css/pages/lifecycle.css +0 -0
- /package/{app → src}/css/pages/progress-builder.css +0 -0
- /package/{app → src}/css/pages/self-assessment.css +0 -0
- /package/{app → src}/css/reset.css +0 -0
- /package/{app → src}/css/views/handout.css +0 -0
- /package/{app → src}/css/views/print.css +0 -0
- /package/{app → src}/css/views/slide-animations.css +0 -0
- /package/{app → src}/css/views/slide-base.css +0 -0
- /package/{app → src}/css/views/slide-sections.css +0 -0
- /package/{app → src}/css/views/slide-tables.css +0 -0
- /package/{app → src}/formatters/agent/skill.js +0 -0
- /package/{app → src}/formatters/behaviour/markdown.js +0 -0
- /package/{app → src}/formatters/behaviour/microdata.js +0 -0
- /package/{app → src}/formatters/behaviour/shared.js +0 -0
- /package/{app → src}/formatters/discipline/markdown.js +0 -0
- /package/{app → src}/formatters/discipline/microdata.js +0 -0
- /package/{app → src}/formatters/discipline/shared.js +0 -0
- /package/{app → src}/formatters/driver/microdata.js +0 -0
- /package/{app → src}/formatters/driver/shared.js +0 -0
- /package/{app → src}/formatters/grade/microdata.js +0 -0
- /package/{app → src}/formatters/grade/shared.js +0 -0
- /package/{app → src}/formatters/index.js +0 -0
- /package/{app → src}/formatters/microdata-shared.js +0 -0
- /package/{app → src}/formatters/progress/dom.js +0 -0
- /package/{app → src}/formatters/progress/markdown.js +0 -0
- /package/{app → src}/formatters/questions/json.js +0 -0
- /package/{app → src}/formatters/questions/markdown.js +0 -0
- /package/{app → src}/formatters/questions/shared.js +0 -0
- /package/{app → src}/formatters/questions/yaml.js +0 -0
- /package/{app → src}/formatters/shared.js +0 -0
- /package/{app → src}/formatters/skill/microdata.js +0 -0
- /package/{app → src}/formatters/stage/dom.js +0 -0
- /package/{app → src}/formatters/stage/index.js +0 -0
- /package/{app → src}/formatters/track/microdata.js +0 -0
- /package/{app → src}/lib/cli-output.js +0 -0
- /package/{app → src}/lib/error-boundary.js +0 -0
- /package/{app → src}/lib/errors.js +0 -0
- /package/{app → src}/lib/form-controls.js +0 -0
- /package/{app → src}/lib/markdown.js +0 -0
- /package/{app → src}/lib/radar.js +0 -0
- /package/{app → src}/lib/reactive.js +0 -0
- /package/{app → src}/lib/router-core.js +0 -0
- /package/{app → src}/lib/router-pages.js +0 -0
- /package/{app → src}/lib/router-slides.js +0 -0
- /package/{app → src}/lib/state.js +0 -0
- /package/{app → src}/lib/template-loader.js +0 -0
- /package/{app → src}/lib/utils.js +0 -0
- /package/{app → src}/lib/yaml-loader.js +0 -0
- /package/{app → src}/main.js +0 -0
- /package/{app → src}/pages/behaviour.js +0 -0
- /package/{app → src}/pages/discipline.js +0 -0
- /package/{app → src}/pages/driver.js +0 -0
- /package/{app → src}/pages/grade.js +0 -0
- /package/{app → src}/pages/interview-builder.js +0 -0
- /package/{app → src}/pages/progress-builder.js +0 -0
- /package/{app → src}/pages/progress.js +0 -0
- /package/{app → src}/pages/track.js +0 -0
- /package/{app → src}/slides/behaviour.js +0 -0
- /package/{app → src}/slides/discipline.js +0 -0
- /package/{app → src}/slides/driver.js +0 -0
- /package/{app → src}/slides/grade.js +0 -0
- /package/{app → src}/slides/interview.js +0 -0
- /package/{app → src}/slides/progress.js +0 -0
- /package/{app → src}/slides/track.js +0 -0
- /package/{app → src}/types.js +0 -0
|
@@ -14,7 +14,7 @@ import { createEntityCommand } from "./command-factory.js";
|
|
|
14
14
|
import { trackToMarkdown } from "../formatters/track/markdown.js";
|
|
15
15
|
import { sortTracksByName } from "../formatters/track/shared.js";
|
|
16
16
|
import { formatTable } from "../lib/cli-output.js";
|
|
17
|
-
import { getConceptEmoji } from "
|
|
17
|
+
import { getConceptEmoji } from "@forwardimpact/schema/levels";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Format track summary output
|
|
@@ -63,5 +63,5 @@ export const runTrackCommand = createEntityCommand({
|
|
|
63
63
|
sortItems: sortTracksByName,
|
|
64
64
|
formatSummary,
|
|
65
65
|
formatDetail,
|
|
66
|
-
|
|
66
|
+
emojiIcon: "🛤️",
|
|
67
67
|
});
|
|
@@ -13,6 +13,7 @@ import { div, h3, p, span } from "../lib/render.js";
|
|
|
13
13
|
* @param {HTMLElement[]} [options.badges] - Badges to display
|
|
14
14
|
* @param {HTMLElement[]} [options.meta] - Meta information
|
|
15
15
|
* @param {HTMLElement} [options.content] - Additional content
|
|
16
|
+
* @param {HTMLElement} [options.icon] - Icon element to display
|
|
16
17
|
* @param {string} [options.className] - Additional CSS class
|
|
17
18
|
* @returns {HTMLElement}
|
|
18
19
|
*/
|
|
@@ -23,13 +24,22 @@ export function createCard({
|
|
|
23
24
|
badges = [],
|
|
24
25
|
meta = [],
|
|
25
26
|
content,
|
|
27
|
+
icon,
|
|
26
28
|
className = "",
|
|
27
29
|
}) {
|
|
28
30
|
const isClickable = !!href;
|
|
29
31
|
|
|
32
|
+
const titleContent = icon
|
|
33
|
+
? div(
|
|
34
|
+
{ className: "card-title-with-icon" },
|
|
35
|
+
icon,
|
|
36
|
+
h3({ className: "card-title" }, title),
|
|
37
|
+
)
|
|
38
|
+
: h3({ className: "card-title" }, title);
|
|
39
|
+
|
|
30
40
|
const cardHeader = div(
|
|
31
41
|
{ className: "card-header" },
|
|
32
|
-
|
|
42
|
+
titleContent,
|
|
33
43
|
badges.length > 0 ? div({ className: "card-badges" }, ...badges) : null,
|
|
34
44
|
);
|
|
35
45
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Display Component
|
|
3
|
+
*
|
|
4
|
+
* Reusable read-only code block with copy buttons and syntax highlighting.
|
|
5
|
+
* Used for markdown content, agent profiles, skills, and code snippets.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* global Prism */
|
|
9
|
+
import { div, p, span, button } from "../lib/render.js";
|
|
10
|
+
|
|
11
|
+
const COPY_LABEL = "📋 Copy";
|
|
12
|
+
const COPY_HTML_LABEL = "Copy as HTML";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a copy button that copies content to clipboard
|
|
16
|
+
* @param {string} content - The text content to copy
|
|
17
|
+
* @returns {HTMLElement}
|
|
18
|
+
*/
|
|
19
|
+
export function createCopyButton(content) {
|
|
20
|
+
const btn = button(
|
|
21
|
+
{
|
|
22
|
+
className: "btn btn-sm copy-btn",
|
|
23
|
+
onClick: async () => {
|
|
24
|
+
try {
|
|
25
|
+
await navigator.clipboard.writeText(content);
|
|
26
|
+
btn.textContent = "✓ Copied!";
|
|
27
|
+
btn.classList.add("copied");
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
btn.textContent = COPY_LABEL;
|
|
30
|
+
btn.classList.remove("copied");
|
|
31
|
+
}, 2000);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error("Failed to copy:", err);
|
|
34
|
+
btn.textContent = "Copy failed";
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
btn.textContent = COPY_LABEL;
|
|
37
|
+
}, 2000);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
COPY_LABEL,
|
|
42
|
+
);
|
|
43
|
+
return btn;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a copy button that copies HTML to clipboard (for rich text pasting)
|
|
48
|
+
* @param {string} html - The HTML content to copy
|
|
49
|
+
* @returns {HTMLElement}
|
|
50
|
+
*/
|
|
51
|
+
function createCopyHtmlButton(html) {
|
|
52
|
+
const btn = button(
|
|
53
|
+
{
|
|
54
|
+
className: "btn btn-sm btn-secondary copy-btn",
|
|
55
|
+
onClick: async () => {
|
|
56
|
+
try {
|
|
57
|
+
const blob = new Blob([html], { type: "text/html" });
|
|
58
|
+
const clipboardItem = new ClipboardItem({ "text/html": blob });
|
|
59
|
+
await navigator.clipboard.write([clipboardItem]);
|
|
60
|
+
btn.textContent = "✓ Copied!";
|
|
61
|
+
btn.classList.add("copied");
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
64
|
+
btn.classList.remove("copied");
|
|
65
|
+
}, 2000);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error("Failed to copy:", err);
|
|
68
|
+
btn.textContent = "Copy failed";
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
71
|
+
}, 2000);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
COPY_HTML_LABEL,
|
|
76
|
+
);
|
|
77
|
+
return btn;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a code display component with syntax highlighting and copy button
|
|
82
|
+
* @param {Object} options
|
|
83
|
+
* @param {string} options.content - The code content to display
|
|
84
|
+
* @param {string} [options.language="markdown"] - Language for syntax highlighting
|
|
85
|
+
* @param {string} [options.filename] - Optional filename to display in header
|
|
86
|
+
* @param {string} [options.description] - Optional description text
|
|
87
|
+
* @param {Function} [options.toHtml] - Function to convert content to HTML (enables "Copy as HTML" button)
|
|
88
|
+
* @param {number} [options.minHeight] - Optional minimum height in pixels
|
|
89
|
+
* @param {number} [options.maxHeight] - Optional maximum height in pixels
|
|
90
|
+
* @returns {HTMLElement}
|
|
91
|
+
*/
|
|
92
|
+
export function createCodeDisplay({
|
|
93
|
+
content,
|
|
94
|
+
language = "markdown",
|
|
95
|
+
filename,
|
|
96
|
+
description,
|
|
97
|
+
toHtml,
|
|
98
|
+
minHeight,
|
|
99
|
+
maxHeight,
|
|
100
|
+
}) {
|
|
101
|
+
// Create highlighted code block
|
|
102
|
+
const pre = document.createElement("pre");
|
|
103
|
+
pre.className = "code-display";
|
|
104
|
+
if (minHeight) pre.style.minHeight = `${minHeight}px`;
|
|
105
|
+
if (maxHeight) {
|
|
106
|
+
pre.style.maxHeight = `${maxHeight}px`;
|
|
107
|
+
pre.style.overflowY = "auto";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const code = document.createElement("code");
|
|
111
|
+
if (language) {
|
|
112
|
+
code.className = `language-${language}`;
|
|
113
|
+
}
|
|
114
|
+
code.textContent = content;
|
|
115
|
+
pre.appendChild(code);
|
|
116
|
+
|
|
117
|
+
// Apply Prism highlighting if available and language specified
|
|
118
|
+
if (language && typeof Prism !== "undefined") {
|
|
119
|
+
Prism.highlightElement(code);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build header content
|
|
123
|
+
const headerLeft = [];
|
|
124
|
+
if (filename) {
|
|
125
|
+
headerLeft.push(span({ className: "code-display-filename" }, filename));
|
|
126
|
+
}
|
|
127
|
+
if (description) {
|
|
128
|
+
headerLeft.push(p({ className: "text-muted" }, description));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Build buttons
|
|
132
|
+
const buttons = [createCopyButton(content)];
|
|
133
|
+
if (toHtml) {
|
|
134
|
+
buttons.push(createCopyHtmlButton(toHtml(content)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Only show header if there's content for it
|
|
138
|
+
const hasHeader = headerLeft.length > 0 || buttons.length > 0;
|
|
139
|
+
|
|
140
|
+
return div(
|
|
141
|
+
{ className: "code-display-container" },
|
|
142
|
+
hasHeader
|
|
143
|
+
? div(
|
|
144
|
+
{ className: "code-display-header" },
|
|
145
|
+
headerLeft.length > 0
|
|
146
|
+
? div({ className: "code-display-info" }, ...headerLeft)
|
|
147
|
+
: null,
|
|
148
|
+
div({ className: "button-group" }, ...buttons),
|
|
149
|
+
)
|
|
150
|
+
: null,
|
|
151
|
+
pre,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getBehaviourMaturityIndex,
|
|
14
14
|
formatLevel,
|
|
15
15
|
} from "../lib/render.js";
|
|
16
|
-
import { getCapabilityIndex } from "
|
|
16
|
+
import { getCapabilityIndex } from "@forwardimpact/schema/levels";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Create a comparison skill radar chart
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Display Component
|
|
3
|
+
*
|
|
4
|
+
* Reusable read-only code block with copy buttons and syntax highlighting.
|
|
5
|
+
* Used for markdown content, agent profiles, skills, and code snippets.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* global Prism */
|
|
9
|
+
import { div, p, span, button } from "../lib/render.js";
|
|
10
|
+
|
|
11
|
+
const COPY_LABEL = "📋 Copy";
|
|
12
|
+
const COPY_HTML_LABEL = "Copy as HTML";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a copy button that copies content to clipboard
|
|
16
|
+
* @param {string} content - The text content to copy
|
|
17
|
+
* @returns {HTMLElement}
|
|
18
|
+
*/
|
|
19
|
+
export function createCopyButton(content) {
|
|
20
|
+
const btn = button(
|
|
21
|
+
{
|
|
22
|
+
className: "btn btn-sm copy-btn",
|
|
23
|
+
onClick: async () => {
|
|
24
|
+
try {
|
|
25
|
+
await navigator.clipboard.writeText(content);
|
|
26
|
+
btn.textContent = "✓ Copied!";
|
|
27
|
+
btn.classList.add("copied");
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
btn.textContent = COPY_LABEL;
|
|
30
|
+
btn.classList.remove("copied");
|
|
31
|
+
}, 2000);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error("Failed to copy:", err);
|
|
34
|
+
btn.textContent = "Copy failed";
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
btn.textContent = COPY_LABEL;
|
|
37
|
+
}, 2000);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
COPY_LABEL,
|
|
42
|
+
);
|
|
43
|
+
return btn;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a copy button that copies HTML to clipboard (for rich text pasting)
|
|
48
|
+
* @param {string} html - The HTML content to copy
|
|
49
|
+
* @returns {HTMLElement}
|
|
50
|
+
*/
|
|
51
|
+
function createCopyHtmlButton(html) {
|
|
52
|
+
const btn = button(
|
|
53
|
+
{
|
|
54
|
+
className: "btn btn-sm btn-secondary copy-btn",
|
|
55
|
+
onClick: async () => {
|
|
56
|
+
try {
|
|
57
|
+
const blob = new Blob([html], { type: "text/html" });
|
|
58
|
+
const clipboardItem = new ClipboardItem({ "text/html": blob });
|
|
59
|
+
await navigator.clipboard.write([clipboardItem]);
|
|
60
|
+
btn.textContent = "✓ Copied!";
|
|
61
|
+
btn.classList.add("copied");
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
64
|
+
btn.classList.remove("copied");
|
|
65
|
+
}, 2000);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error("Failed to copy:", err);
|
|
68
|
+
btn.textContent = "Copy failed";
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
71
|
+
}, 2000);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
COPY_HTML_LABEL,
|
|
76
|
+
);
|
|
77
|
+
return btn;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a code display component with syntax highlighting and copy button
|
|
82
|
+
* @param {Object} options
|
|
83
|
+
* @param {string} options.content - The code content to display
|
|
84
|
+
* @param {string} [options.language="markdown"] - Language for syntax highlighting
|
|
85
|
+
* @param {string} [options.filename] - Optional filename to display in header
|
|
86
|
+
* @param {string} [options.description] - Optional description text
|
|
87
|
+
* @param {Function} [options.toHtml] - Function to convert content to HTML (enables "Copy as HTML" button)
|
|
88
|
+
* @param {number} [options.minHeight] - Optional minimum height in pixels
|
|
89
|
+
* @param {number} [options.maxHeight] - Optional maximum height in pixels
|
|
90
|
+
* @returns {HTMLElement}
|
|
91
|
+
*/
|
|
92
|
+
export function createCodeDisplay({
|
|
93
|
+
content,
|
|
94
|
+
language = "markdown",
|
|
95
|
+
filename,
|
|
96
|
+
description,
|
|
97
|
+
toHtml,
|
|
98
|
+
minHeight,
|
|
99
|
+
maxHeight,
|
|
100
|
+
}) {
|
|
101
|
+
// Create highlighted code block
|
|
102
|
+
const pre = document.createElement("pre");
|
|
103
|
+
pre.className = "code-display";
|
|
104
|
+
if (minHeight) pre.style.minHeight = `${minHeight}px`;
|
|
105
|
+
if (maxHeight) {
|
|
106
|
+
pre.style.maxHeight = `${maxHeight}px`;
|
|
107
|
+
pre.style.overflowY = "auto";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const code = document.createElement("code");
|
|
111
|
+
if (language) {
|
|
112
|
+
code.className = `language-${language}`;
|
|
113
|
+
}
|
|
114
|
+
code.textContent = content;
|
|
115
|
+
pre.appendChild(code);
|
|
116
|
+
|
|
117
|
+
// Apply Prism highlighting if available and language specified
|
|
118
|
+
if (language && typeof Prism !== "undefined") {
|
|
119
|
+
Prism.highlightElement(code);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build header content
|
|
123
|
+
const headerLeft = [];
|
|
124
|
+
if (filename) {
|
|
125
|
+
headerLeft.push(span({ className: "code-display-filename" }, filename));
|
|
126
|
+
}
|
|
127
|
+
if (description) {
|
|
128
|
+
headerLeft.push(p({ className: "text-muted" }, description));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Build buttons
|
|
132
|
+
const buttons = [createCopyButton(content)];
|
|
133
|
+
if (toHtml) {
|
|
134
|
+
buttons.push(createCopyHtmlButton(toHtml(content)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Only show header if there's content for it
|
|
138
|
+
const hasHeader = headerLeft.length > 0 || buttons.length > 0;
|
|
139
|
+
|
|
140
|
+
return div(
|
|
141
|
+
{ className: "code-display-container" },
|
|
142
|
+
hasHeader
|
|
143
|
+
? div(
|
|
144
|
+
{ className: "code-display-header" },
|
|
145
|
+
headerLeft.length > 0
|
|
146
|
+
? div({ className: "code-display-info" }, ...headerLeft)
|
|
147
|
+
: null,
|
|
148
|
+
div({ className: "button-group" }, ...buttons),
|
|
149
|
+
)
|
|
150
|
+
: null,
|
|
151
|
+
pre,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { getSkillLevelIndex } from "../lib/render.js";
|
|
19
19
|
import { createLevelCell } from "./detail.js";
|
|
20
20
|
import { createBadge } from "./card.js";
|
|
21
|
-
import { SKILL_LEVEL_ORDER } from "
|
|
21
|
+
import { SKILL_LEVEL_ORDER } from "@forwardimpact/schema/levels";
|
|
22
22
|
import { truncate } from "../formatters/shared.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -38,3 +38,17 @@
|
|
|
38
38
|
@import "../pages/self-assessment.css" layer(pages);
|
|
39
39
|
@import "../pages/assessment-results.css" layer(pages);
|
|
40
40
|
@import "../pages/progress-builder.css" layer(pages);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Prism overrides (unlayered to beat Prism's unlayered CSS)
|
|
44
|
+
*/
|
|
45
|
+
pre.code-display[class*="language-"] {
|
|
46
|
+
font-family: var(--font-family-mono);
|
|
47
|
+
font-size: var(--font-size-xs);
|
|
48
|
+
background-color: var(--color-surface);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pre.code-display > code[class*="language-"] {
|
|
52
|
+
font-family: inherit;
|
|
53
|
+
font-size: inherit;
|
|
54
|
+
}
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
@layer components {
|
|
8
|
-
/* Base badge */
|
|
8
|
+
/* Base badge - 28px height matches tool icons for consistent row heights */
|
|
9
9
|
.badge {
|
|
10
|
-
display: inline-
|
|
11
|
-
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
height: 28px;
|
|
14
|
+
padding: 0 var(--space-sm);
|
|
12
15
|
border-radius: var(--radius-md);
|
|
13
16
|
font-size: var(--font-size-xs);
|
|
14
17
|
font-weight: 500;
|
|
@@ -78,10 +81,13 @@
|
|
|
78
81
|
color: #5b21b6;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
/* Level badge base style */
|
|
84
|
+
/* Level badge base style - 28px height matches tool icons */
|
|
82
85
|
.level-badge {
|
|
83
|
-
display: inline-
|
|
84
|
-
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
height: 28px;
|
|
90
|
+
padding: 0 var(--space-sm);
|
|
85
91
|
border-radius: var(--radius-md);
|
|
86
92
|
font-size: var(--font-size-sm);
|
|
87
93
|
font-weight: 500;
|
|
@@ -142,11 +148,12 @@
|
|
|
142
148
|
padding: var(--space-xs) var(--space-sm);
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
/* Modifier tags -
|
|
151
|
+
/* Modifier tags - 28px height matches badges and tool icons */
|
|
146
152
|
.modifier {
|
|
147
153
|
display: inline-flex;
|
|
148
154
|
align-items: center;
|
|
149
|
-
|
|
155
|
+
height: 28px;
|
|
156
|
+
padding: 0 var(--space-sm);
|
|
150
157
|
border-radius: var(--radius-sm);
|
|
151
158
|
font-size: var(--font-size-sm);
|
|
152
159
|
font-weight: 600;
|
|
@@ -103,14 +103,14 @@
|
|
|
103
103
|
color: var(--color-primary);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/*
|
|
107
|
-
.
|
|
106
|
+
/* Code display - unified component for code/markdown with copy buttons */
|
|
107
|
+
.code-display-container {
|
|
108
108
|
display: flex;
|
|
109
109
|
flex-direction: column;
|
|
110
|
-
gap: var(--space-
|
|
110
|
+
gap: var(--space-sm);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
.
|
|
113
|
+
.code-display-header {
|
|
114
114
|
display: flex;
|
|
115
115
|
justify-content: space-between;
|
|
116
116
|
align-items: center;
|
|
@@ -118,21 +118,32 @@
|
|
|
118
118
|
gap: var(--space-md);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
.
|
|
122
|
-
|
|
121
|
+
.code-display-info {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: var(--space-xs);
|
|
123
125
|
flex: 1;
|
|
124
126
|
min-width: 200px;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
.
|
|
129
|
+
.code-display-info .text-muted {
|
|
130
|
+
margin: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.code-display-filename {
|
|
134
|
+
font-family: var(--font-family-mono);
|
|
135
|
+
font-size: var(--font-size-xs);
|
|
136
|
+
color: var(--color-text-muted);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.code-display {
|
|
128
140
|
width: 100%;
|
|
129
141
|
margin: 0;
|
|
130
142
|
padding: var(--space-md);
|
|
131
|
-
font-family:
|
|
132
|
-
|
|
133
|
-
font-size: var(--font-size-sm);
|
|
143
|
+
font-family: var(--font-family-mono);
|
|
144
|
+
font-size: var(--font-size-xs);
|
|
134
145
|
line-height: 1.6;
|
|
135
|
-
background-color: var(--color-surface)
|
|
146
|
+
background-color: var(--color-surface);
|
|
136
147
|
border: 1px solid var(--color-border);
|
|
137
148
|
border-radius: var(--radius-md);
|
|
138
149
|
overflow: auto;
|
|
@@ -141,10 +152,9 @@
|
|
|
141
152
|
word-wrap: break-word;
|
|
142
153
|
}
|
|
143
154
|
|
|
144
|
-
.
|
|
155
|
+
.code-display code {
|
|
145
156
|
background: transparent;
|
|
146
157
|
padding: 0;
|
|
147
|
-
font-size: inherit;
|
|
148
158
|
color: inherit;
|
|
149
159
|
}
|
|
150
160
|
}
|
|
@@ -40,6 +40,30 @@
|
|
|
40
40
|
font-size: var(--font-size-lg);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
.card-title-with-icon {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(--space-sm);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.card-title-with-icon .card-title {
|
|
50
|
+
margin: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Tool icons */
|
|
54
|
+
.tool-icon {
|
|
55
|
+
width: 28px;
|
|
56
|
+
height: 28px;
|
|
57
|
+
flex-shrink: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Tool name cell with icon */
|
|
61
|
+
.tool-name-cell {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: var(--space-sm);
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
.card-description {
|
|
44
68
|
color: var(--color-text-muted);
|
|
45
69
|
margin: 0;
|
|
@@ -122,7 +146,7 @@
|
|
|
122
146
|
.section-title {
|
|
123
147
|
font-size: var(--font-size-xl);
|
|
124
148
|
font-weight: 600;
|
|
125
|
-
margin: 0 0 var(--space-
|
|
149
|
+
margin: 0 0 var(--space-md);
|
|
126
150
|
color: var(--color-text);
|
|
127
151
|
}
|
|
128
152
|
|
|
@@ -209,11 +233,33 @@
|
|
|
209
233
|
font-weight: 500;
|
|
210
234
|
}
|
|
211
235
|
|
|
236
|
+
/*
|
|
237
|
+
* List items: 60px row height = 16px padding-top + 28px content + 16px padding-bottom
|
|
238
|
+
* Uses bottom-border only for 1px gaps between items (like table rows)
|
|
239
|
+
* Left/right borders and rounded corners on first/last items match table styling
|
|
240
|
+
*/
|
|
212
241
|
.list-item {
|
|
213
|
-
|
|
242
|
+
display: flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
gap: var(--space-sm);
|
|
245
|
+
min-height: 60px;
|
|
246
|
+
padding: var(--space-md) var(--space-lg);
|
|
214
247
|
background: var(--color-surface);
|
|
215
|
-
border-radius: var(--radius-md);
|
|
216
248
|
border: 1px solid var(--color-border);
|
|
249
|
+
border-top: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* First list-item in a sequence (handles heading before list items) */
|
|
253
|
+
.list-item:first-child,
|
|
254
|
+
:not(.list-item) + .list-item {
|
|
255
|
+
border-top: 1px solid var(--color-border);
|
|
256
|
+
border-top-left-radius: var(--radius-lg);
|
|
257
|
+
border-top-right-radius: var(--radius-lg);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.list-item:last-child {
|
|
261
|
+
border-bottom-left-radius: var(--radius-lg);
|
|
262
|
+
border-bottom-right-radius: var(--radius-lg);
|
|
217
263
|
}
|
|
218
264
|
|
|
219
265
|
/* Accent borders */
|
|
@@ -111,8 +111,7 @@
|
|
|
111
111
|
|
|
112
112
|
/* Code inline */
|
|
113
113
|
.code-inline {
|
|
114
|
-
font-family:
|
|
115
|
-
ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
114
|
+
font-family: var(--font-family-mono);
|
|
116
115
|
font-size: 0.9em;
|
|
117
116
|
background: var(--color-bg);
|
|
118
117
|
padding: 0.1em 0.4em;
|