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 @@ + + + + \ No newline at end of file diff --git a/src/components/misc/SubgroupCard.vue b/src/components/misc/SubgroupCard.vue new file mode 100644 index 00000000..021536ca --- /dev/null +++ b/src/components/misc/SubgroupCard.vue @@ -0,0 +1,178 @@ + + + + \ No newline at end of file diff --git a/src/components/plugins/CollapsibleProperties.vue b/src/components/plugins/CollapsibleProperties.vue index 9de75b14..37566602 100644 --- a/src/components/plugins/CollapsibleProperties.vue +++ b/src/components/plugins/CollapsibleProperties.vue @@ -61,7 +61,8 @@ extractTypeInfo, className, type JSONProperty, - aggregateAllOf + aggregateAllOf, + isDynamic } from "../../utils/schemaUtils"; import ChevronDown from "vue-material-design-icons/ChevronDown.vue"; import Collapsible from "../misc/Collapsible.vue"; @@ -104,18 +105,6 @@ } ); - const 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; - }; - function sortedAndAggregated(schema: Record): Record { const requiredKeys = []; const nonRequiredKeys = []; diff --git a/src/components/plugins/PluginIndex.vue b/src/components/plugins/PluginIndex.vue index 15989a2a..5e5dc1ee 100644 --- a/src/components/plugins/PluginIndex.vue +++ b/src/components/plugins/PluginIndex.vue @@ -1,50 +1,91 @@ + + \ No newline at end of file diff --git a/src/components/plugins/PropertyDetail.vue b/src/components/plugins/PropertyDetail.vue index 71ac417d..e7f10e4f 100644 --- a/src/components/plugins/PropertyDetail.vue +++ b/src/components/plugins/PropertyDetail.vue @@ -149,10 +149,12 @@ \ No newline at end of file diff --git a/src/components/plugins_v2/CollapsibleV2.vue b/src/components/plugins_v2/CollapsibleV2.vue new file mode 100644 index 00000000..6b0236fd --- /dev/null +++ b/src/components/plugins_v2/CollapsibleV2.vue @@ -0,0 +1,121 @@ + + + + + \ No newline at end of file diff --git a/src/components/plugins_v2/DefinitionCollapsible.vue b/src/components/plugins_v2/DefinitionCollapsible.vue new file mode 100644 index 00000000..c97899cf --- /dev/null +++ b/src/components/plugins_v2/DefinitionCollapsible.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/components/plugins_v2/PropertyBadges.vue b/src/components/plugins_v2/PropertyBadges.vue new file mode 100644 index 00000000..abf4c924 --- /dev/null +++ b/src/components/plugins_v2/PropertyBadges.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/plugins_v2/PropertyDetailV2.vue b/src/components/plugins_v2/PropertyDetailV2.vue new file mode 100644 index 00000000..53b23022 --- /dev/null +++ b/src/components/plugins_v2/PropertyDetailV2.vue @@ -0,0 +1,127 @@ + + + + + \ No newline at end of file diff --git a/src/components/plugins_v2/PropertyMeta.vue b/src/components/plugins_v2/PropertyMeta.vue new file mode 100644 index 00000000..135cf8b3 --- /dev/null +++ b/src/components/plugins_v2/PropertyMeta.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/src/components/plugins_v2/SchemaToHtmlV2.vue b/src/components/plugins_v2/SchemaToHtmlV2.vue new file mode 100644 index 00000000..51ea6b1a --- /dev/null +++ b/src/components/plugins_v2/SchemaToHtmlV2.vue @@ -0,0 +1,213 @@ + + + + + \ 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; +}