diff --git a/src/components/index.ts b/src/components/index.ts
index 5f187b19..8867167d 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -8,9 +8,13 @@ export {default as Topology} from "./topology/Topology.vue";
export {default as Status} from "./misc/Status.vue";
export {default as TaskIcon} from "./misc/TaskIcon.vue";
export {default as Collapsible} from "./misc/Collapsible.vue";
+export {default as CollapsibleV2} from "./plugins_v2/CollapsibleV2.vue";
+export {default as SubgroupCard} from "./misc/SubgroupCard.vue";
+export {default as ElementCard} from "./misc/ElementCard.vue";
// plugins
export {default as SchemaToHtml} from "./plugins/SchemaToHtml.vue";
+export {default as SchemaToHtmlV2} from "./plugins_v2/SchemaToHtmlV2.vue";
export {default as PluginIndex} from "./plugins/PluginIndex.vue";
// content
diff --git a/src/components/misc/ElementCard.vue b/src/components/misc/ElementCard.vue
new file mode 100644
index 00000000..7e9eca4e
--- /dev/null
+++ b/src/components/misc/ElementCard.vue
@@ -0,0 +1,132 @@
+
+
+
+ This plugin is currently in beta. While it is considered safe for use, please be aware that its API
+ could change in ways that are not compatible with earlier versions in future releases, or it might
+ become unsupported.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/composables/usePluginElementCounts.ts b/src/composables/usePluginElementCounts.ts
new file mode 100644
index 00000000..ae4e4457
--- /dev/null
+++ b/src/composables/usePluginElementCounts.ts
@@ -0,0 +1,16 @@
+import {computed, unref, type Ref} from "vue";
+import {extractPluginElements, type Plugin} from "../utils/plugins";
+
+/**
+ * Composable to compute plugin element counts and groupings.
+ * Provides reactive computed values for elements by type and total count.
+ * @param plugin - The plugin to analyze.
+ * @returns Object with elementsByType (computed) and total (computed) counts.
+ */
+export function usePluginElementCounts(plugin: Plugin | Ref) {
+ const elementsByType = computed(() => extractPluginElements(unref(plugin)));
+
+ const total = computed(() => Object.values(elementsByType.value).reduce((sum, arr) => sum + arr.length, 0));
+
+ return {elementsByType, total} as const;
+}
diff --git a/src/index.ts b/src/index.ts
index 6a3e5e5d..bbd2a25f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,10 +7,11 @@ export {default as getMDCParser} from "./composables/getMDCParser";
export * from "./utils/constants";
export * from "./utils/url";
export * from "./utils/plugins";
+export {usePluginElementCounts} from "./composables/usePluginElementCounts";
export {default as RotatingDotsIcon} from "./assets/icons/RotatingDots.vue";
export type {YamlElement} from "./utils/YamlUtilsLegacy";
-export type {Plugin} from "./utils/plugins";
+export type {Plugin, PluginMetadata, PluginElement} from "./utils/plugins";
export type {JSONSchema, JSONProperty} from "./utils/schemaUtils";
import "./scss/ks-theme-light.scss";
diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss
index 77ed7de2..8193649b 100644
--- a/src/scss/_variables.scss
+++ b/src/scss/_variables.scss
@@ -38,6 +38,75 @@ $white-3: #B9B9BA;
$black-2: #161617;
$black-3: #252526;
+$primary-1: #4B0AAA !default;
+
+// extended whites
+$white-1: #E1E1E1 !default;
+$white-2: #E5E4F7 !default;
+$white-4: #CCC6D2 !default;
+$white-5: #7C7C7C !default;
+$white-6: #F0F0FF !default;
+
+// extended blacks
+$black-1: #070708 !default;
+$black-4: #111113 !default;
+$black-5: #333336 !default;
+$black-6: #3D3D3F !default;
+$black-7: #0D0D0C !default;
+$black-8: #8B8B8D !default;
+$black-9: #1D1D1E !default;
+$black-10: #646465 !default;
+
+// greens/blues
+$green-1: #BFE1B0 !default;
+$indigo-1: #1B0229 !default;
+
+// extended purples (many small variant tokens used across docs)
+$purple-1: #382369 !default;
+$purple-2: #FBC7F4 !default;
+$purple-3: #9580EE !default;
+$purple-4: linear-gradient(160.34deg, rgba(130, 0, 255, 0.12) 5.3%, rgba(130, 0, 255, 0) 75.43%), #201838 !default;
+$purple-5: #362762 !default;
+$purple-6: #6113BC !default;
+$purple-7: #1A1223 !default;
+$purple-8: #EEEDFF !default;
+$purple-10: #150E1C !default;
+$purple-11: #332C3B !default;
+$purple-12: #432A71 !default;
+$purple-13: #e5e4f7 !default;
+$purple-14: #8e20f9 !default;
+$purple-15: #9237fb !default;
+$purple-16: #281A35 !default;
+$purple-17: #F5F5FF !default;
+$purple-18: #7E719F !default;
+$purple-19: conic-gradient(from 90deg at 50% 50%, #BE79FF 0deg, #7136F6 360deg) !default;
+$purple-20: #291E39 !default;
+$purple-21: #A42DCD !default;
+$purple-22: #FCF7FE !default;
+$purple-23: #EDE8F3 !default;
+$purple-24: #15023F !default;
+$purple-26: #DCCDEB !default;
+$purple-27: #8200FF !default;
+$purple-28: #2D313E !default;
+$purple-29: #F1F5FF !default;
+$purple-31: #2A1940 !default;
+$purple-32: #CFCEFF !default;
+$purple-33: #110221 !default;
+$purple-34: #10051F !default;
+$purple-35: #736BCD !default;
+$purple-37: #A396FF !default;
+$purple-38: #7078EF !default;
+$purple-39: #817CFF !default;
+$purple-40: #5818D8 !default;
+$purple-41: #7A38FF !default;
+$purple-42: #909AF6 !default;
+$purple-43: #7733FF !default;
+
+// gray variant used in some places (palette value)
+$gray-300-alt: #9797A6 !default;
+
+$purple-50: #E0E0FF !default;
+
// fonts
$font-size-base: 1rem !default;
$font-family-sans-serif: "Public Sans", sans-serif;
diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts
index a38e0b1a..0827d063 100644
--- a/src/utils/Utils.ts
+++ b/src/utils/Utils.ts
@@ -85,6 +85,10 @@ export function distinctFilter(value: any, index: number, array: any[]) {
return array.indexOf(value) === index;
}
+export function sanitizeForMarkdown(str: string): string {
+ return str.replace(/(```)(?:bash|yaml|js|console|json)(\n) *([\s\S]*?```)/g, "$1$2$3").replace(/(?;
} & {
[pluginElement: string]: PluginElement[];
@@ -29,6 +47,63 @@ export function isEntryAPluginElementPredicate(key: string, value: any): value i
}
export function subGroupName(subGroupWrapper: Plugin) {
- const result = subGroupWrapper.title.replace(/\.([a-zA-Z])/g, (_, capture) => ` ${capture.toUpperCase()}`);
+ const title = subGroupWrapper.title ?? "";
+ const result = title.replace(/\.([a-zA-Z])/g, (_, capture) => ` ${capture.toUpperCase()}`);
return result.charAt(0).toUpperCase() + result.slice(1);
}
+
+export function extractPluginElements(plugin: Plugin): Record {
+ return Object.fromEntries(
+ Object.entries(plugin)
+ .filter(([key, value]) => isEntryAPluginElementPredicate(key, value))
+ .map(([key, value]) => [key.replace(/[A-Z]/g, match => ` ${match}`), (value as PluginElement[]).filter(({deprecated}) => !deprecated).map(({cls}) => cls)])
+ );
+}
+
+export function filterPluginsWithoutDeprecated(plugins: Plugin[]): Plugin[] {
+ return plugins.flatMap(plugin => {
+ const filteredEntries = Object.entries(plugin)
+ .filter(([key, value]) => isEntryAPluginElementPredicate(key, value))
+ .map(([key, value]) => [key, (value as PluginElement[]).filter(element => !element.deprecated)])
+ .filter(([, elements]) => elements.length > 0);
+
+ return filteredEntries.length > 0 ? [{...plugin, ...Object.fromEntries(filteredEntries)}] : [];
+ });
+}
+
+export type PluginMappings = {
+ clsToSubgroup: Record;
+ clsToPlugin: Record;
+ shortNameToCls: Record;
+};
+
+/**
+ * Builds mappings from plugin data for efficient lookups of subgroups, plugins, and short names.
+ * @param plugins - Array of plugins to process.
+ * @returns Object containing clsToSubgroup, clsToPlugin, and shortNameToCls mappings.
+ */
+export function buildPluginMappings(plugins: Plugin[]): PluginMappings {
+ const mappings: PluginMappings = {clsToSubgroup: {}, clsToPlugin: {}, shortNameToCls: {}};
+
+ for (const plugin of (plugins ?? [])) {
+ const elements = extractPluginElements(plugin);
+ const group = plugin.group ?? plugin.name ?? "";
+ const pluginSlug = slugify(group);
+ const subgroupSlug = plugin.subGroup ? slugify(subGroupName(plugin)) : undefined;
+
+ Object.values(elements).forEach(names => {
+ names.forEach(cls => {
+ if (subgroupSlug) mappings.clsToSubgroup[cls] = subgroupSlug;
+ mappings.clsToPlugin[cls] = {slug: pluginSlug, raw: group};
+
+ const short = (cls.split(".").pop() ?? cls).toLowerCase();
+ mappings.shortNameToCls[short] = mappings.shortNameToCls[short] ?? [];
+ if (!mappings.shortNameToCls[short].includes(cls)) {
+ mappings.shortNameToCls[short].push(cls);
+ }
+ });
+ });
+ }
+
+ return mappings;
+}
diff --git a/src/utils/schemaUtils.ts b/src/utils/schemaUtils.ts
index 6da15ef8..29bff50d 100644
--- a/src/utils/schemaUtils.ts
+++ b/src/utils/schemaUtils.ts
@@ -62,7 +62,9 @@ function extractTypesOrRef(propType: JSONProperty): string[] | undefined {
if (propType.$ref) {
const ref = propType.$ref;
- return ["#" + ref.slice(8)];
+ const parts = ref.split("/");
+ const key = parts[parts.length - 1];
+ return ["#" + key];
}
return undefined;
@@ -142,3 +144,45 @@ export function extractTypeInfo(property: JSONProperty): ExtractedTypes {
return result;
}
+
+export function extractReferencedDefinitions(
+ property: JSONProperty,
+ definitions: Record | undefined,
+ visitedKeys: Set = new Set()
+): Array<{ key: string, title: string, properties: Record }> {
+ if (!definitions) return [];
+
+ const typeInfo = extractTypeInfo(property);
+ const defKeys: string[] = [];
+
+ typeInfo.types.forEach(type => {
+ if (type.startsWith("#")) {
+ const key = type.slice(1);
+ if (definitions[key] && !visitedKeys.has(key) && !defKeys.includes(key)) {
+ defKeys.push(key);
+ }
+ }
+ });
+
+ if (typeInfo.subType?.startsWith("#")) {
+ const key = typeInfo.subType.slice(1);
+ if (definitions[key] && !visitedKeys.has(key) && !defKeys.includes(key)) {
+ defKeys.push(key);
+ }
+ }
+
+ return defKeys.map(key => {
+ const def = definitions[key];
+ return {
+ key,
+ title: def?.title ?? key.split("_")[0],
+ properties: def?.properties ?? {}
+ };
+ });
+}
+
+export function isDynamic(property: JSONProperty): boolean {
+ if (property["$dynamic"] === true) return true;
+ if (property["$dynamic"] === false) return false;
+ return property.anyOf?.some(prop => prop["$dynamic"] === true) ?? false;
+}