@eventcatalog/core 2.57.0 → 2.57.2

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.
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "2.57.0";
40
+ var version = "2.57.2";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-24JFJA4V.js";
4
- import "../chunk-6ILF3VK6.js";
3
+ } from "../chunk-IZVKIJ4Q.js";
4
+ import "../chunk-C2EHTPRH.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "2.57.0";
109
+ var version = "2.57.2";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-NVY3KN4D.js";
4
- import "../chunk-24JFJA4V.js";
5
- import "../chunk-6ILF3VK6.js";
3
+ } from "../chunk-A7SEN2P6.js";
4
+ import "../chunk-IZVKIJ4Q.js";
5
+ import "../chunk-C2EHTPRH.js";
6
6
  import "../chunk-UPONRQSN.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-24JFJA4V.js";
3
+ } from "./chunk-IZVKIJ4Q.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.57.0";
2
+ var version = "2.57.2";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-6ILF3VK6.js";
3
+ } from "./chunk-C2EHTPRH.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.57.0";
28
+ var version = "2.57.2";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-6ILF3VK6.js";
3
+ } from "./chunk-C2EHTPRH.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -157,7 +157,7 @@ var import_axios = __toESM(require("axios"), 1);
157
157
  var import_os = __toESM(require("os"), 1);
158
158
 
159
159
  // package.json
160
- var version = "2.57.0";
160
+ var version = "2.57.2";
161
161
 
162
162
  // src/constants.ts
163
163
  var VERSION = version;
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-PLNJC7NZ.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-NVY3KN4D.js";
10
- import "./chunk-24JFJA4V.js";
9
+ } from "./chunk-A7SEN2P6.js";
10
+ import "./chunk-IZVKIJ4Q.js";
11
11
  import {
12
12
  catalogToAstro,
13
13
  checkAndConvertMdToMdx
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-55D645EH.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-6ILF3VK6.js";
18
+ } from "./chunk-C2EHTPRH.js";
19
19
  import {
20
20
  getProjectOutDir,
21
21
  isAuthEnabled,
@@ -2,6 +2,7 @@ import { defineConfig } from 'astro/config';
2
2
  import tailwind from '@astrojs/tailwind';
3
3
  import mdx from '@astrojs/mdx';
4
4
  import react from '@astrojs/react';
5
+ import { searchForWorkspaceRoot } from 'vite';
5
6
  import { mermaid } from "./src/remark-plugins/mermaid"
6
7
  import { plantuml } from "./src/remark-plugins/plantuml"
7
8
  import { join } from 'node:path';
@@ -94,7 +95,11 @@ export default defineConfig({
94
95
  },
95
96
  server: {
96
97
  fs: {
97
- allow: ['..', './node_modules/@fontsource']
98
+ allow: [
99
+ '..',
100
+ './node_modules/@fontsource',
101
+ searchForWorkspaceRoot(process.cwd()),
102
+ ]
98
103
  }
99
104
  },
100
105
  worker: {
@@ -1,7 +1,8 @@
1
1
  import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
2
2
  import { ChevronDownIcon } from '@heroicons/react/20/solid';
3
3
  import { getIconForCollection as getIconForCollectionOriginal } from '@utils/collections/icons';
4
- import { useMemo } from 'react';
4
+ import { useMemo, useState } from 'react';
5
+ import * as icons from 'lucide-react'; // Import all icons
5
6
 
6
7
  import './PillListFlat.styles.css';
7
8
 
@@ -14,17 +15,80 @@ interface Props {
14
15
  label: string;
15
16
  badge?: string;
16
17
  href?: string;
18
+ target?: '_blank' | '_self';
17
19
  tag?: string;
18
20
  color?: string;
19
21
  collection?: string;
20
22
  description?: string;
21
23
  icon?: string;
24
+ subgroup?: string | undefined;
22
25
  }[];
23
26
  emptyMessage?: string;
24
27
  }
25
28
 
26
29
  const PillList = ({ title, pills, emptyMessage, color = 'gray', limit = 10, ...props }: Props) => {
27
30
  const getIconForCollection = useMemo(() => getIconForCollectionOriginal, []);
31
+ const [collapsedSubgroups, setCollapsedSubgroups] = useState<Set<string>>(new Set());
32
+
33
+ const groupedPills = useMemo(() => {
34
+ const grouped = new Map<string, typeof pills>();
35
+ const ungrouped: typeof pills = [];
36
+
37
+ pills.forEach((pill) => {
38
+ if (pill.subgroup) {
39
+ if (!grouped.has(pill.subgroup)) {
40
+ grouped.set(pill.subgroup, []);
41
+ }
42
+ grouped.get(pill.subgroup)!.push(pill);
43
+ } else {
44
+ ungrouped.push(pill);
45
+ }
46
+ });
47
+
48
+ return { grouped, ungrouped };
49
+ }, [pills]);
50
+
51
+ const toggleSubgroup = (subgroupName: string) => {
52
+ setCollapsedSubgroups((prev) => {
53
+ const newSet = new Set(prev);
54
+ if (newSet.has(subgroupName)) {
55
+ newSet.delete(subgroupName);
56
+ } else {
57
+ newSet.add(subgroupName);
58
+ }
59
+ return newSet;
60
+ });
61
+ };
62
+
63
+ const renderPillItem = (item: (typeof pills)[0]) => {
64
+ const href = item.href ?? '#';
65
+ const Icon = item.collection ? getIconForCollection(item.collection) : null;
66
+ const PillIcon = item.icon ? (icons as any)[item.icon] : null;
67
+
68
+ return (
69
+ <li
70
+ className=" has-tooltip rounded-md text-gray-600 group px-1 w-full hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white hover:font-normal "
71
+ key={item.href}
72
+ >
73
+ <a className={`leading-3`} href={href} target={item.target ?? '_self'}>
74
+ <span className="space-x-2 flex items-center">
75
+ {Icon && !PillIcon && <Icon className="h-4 w-4 shrink-0" />}
76
+ {PillIcon && <PillIcon className="h-4 w-4 shrink-0" />}
77
+ <span className="font-light text-sm truncate">
78
+ {item.label} {item.tag && <>({item.tag})</>}
79
+ </span>
80
+ {item.label.length > 24 && (
81
+ <span className="tooltip rounded relative shadow-lg p-1 font-normal text-xs bg-white text-black ml-[30px] mt-12">
82
+ {item.label} {item.tag && <>({item.tag})</>}
83
+ </span>
84
+ )}
85
+ </span>
86
+ {item.description && <span className="text-[9px] block ml-6 mt-1 leading-0">{item.description}</span>}
87
+ </a>
88
+ </li>
89
+ );
90
+ };
91
+
28
92
  return (
29
93
  <div className="">
30
94
  <div className="mx-auto w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
@@ -34,39 +98,56 @@ const PillList = ({ title, pills, emptyMessage, color = 'gray', limit = 10, ...p
34
98
  <ChevronDownIcon className="size-5 ml-2 fill-black/60 group-data-[hover]:fill-black/50 group-data-[open]:rotate-180" />
35
99
  </DisclosureButton>
36
100
  <DisclosurePanel className="mt-2 text-sm/5 text-black/50">
37
- <ul role="list" className="space-y-2">
38
- {pills.map((item) => {
39
- const href = item.href ?? '#';
40
- const Icon = item.collection ? getIconForCollection(item.collection) : null;
101
+ <div className="space-y-2">
102
+ {groupedPills.ungrouped.length > 0 && (
103
+ <ul role="list" className="space-y-2">
104
+ {groupedPills.ungrouped.map(renderPillItem)}
105
+ </ul>
106
+ )}
41
107
 
108
+ {Array.from(groupedPills.grouped.entries()).map(([subgroupName, subgroupPills]) => {
109
+ const isCollapsed = collapsedSubgroups.has(subgroupName);
42
110
  return (
43
- <li
44
- className=" has-tooltip rounded-md text-gray-600 group px-1 w-full hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white hover:font-normal "
45
- key={item.href}
46
- >
47
- <a className={`leading-3`} href={href}>
48
- <span className="space-x-2 flex items-center">
49
- {Icon && <Icon className="h-4 w-4 shrink-0" />}
50
- <span className="font-light text-sm truncate">
51
- {item.label} {item.tag && <>({item.tag})</>}
52
- </span>
53
- {item.label.length > 24 && (
54
- <span className="tooltip rounded relative shadow-lg p-1 font-normal text-xs bg-white text-black ml-[30px] mt-12">
55
- {item.label} ({item.tag})
56
- </span>
57
- )}
58
- </span>
59
- {item.description && <span className="text-[9px] block ml-6 mt-1 leading-0">{item.description}</span>}
60
- </a>
61
- </li>
111
+ <div key={subgroupName} className="space-y-">
112
+ <div className="flex items-center">
113
+ <button
114
+ onClick={(e) => {
115
+ e.stopPropagation();
116
+ toggleSubgroup(subgroupName);
117
+ }}
118
+ className="p-1 hover:bg-gray-100 rounded-md"
119
+ >
120
+ <div className={`transition-transform duration-150 ${isCollapsed ? '' : 'rotate-180'}`}>
121
+ <ChevronDownIcon className="h-3 w-3 text-gray-500" />
122
+ </div>
123
+ </button>
124
+ <button
125
+ onClick={(e) => {
126
+ e.stopPropagation();
127
+ toggleSubgroup(subgroupName);
128
+ }}
129
+ className="flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md text-gray-900 uppercase"
130
+ >
131
+ {subgroupName} ({subgroupPills.length})
132
+ </button>
133
+ </div>
134
+ <div
135
+ className={`overflow-hidden transition-[height] duration-150 ease-out ${isCollapsed ? 'h-0' : 'h-auto'}`}
136
+ >
137
+ <ul role="list" className="space-y-2 border-l border-gray-200/80 ml-[9px] pl-4 pt-2">
138
+ {subgroupPills.map(renderPillItem)}
139
+ </ul>
140
+ </div>
141
+ </div>
62
142
  );
63
143
  })}
144
+
64
145
  {pills.length === 0 && emptyMessage && (
65
- <li className="inline mr-2 leading-tight text-xs">
146
+ <div className="inline mr-2 leading-tight text-xs">
66
147
  <span className="text-gray-400">{emptyMessage}</span>
67
- </li>
148
+ </div>
68
149
  )}
69
- </ul>
150
+ </div>
70
151
  </DisclosurePanel>
71
152
  </Disclosure>
72
153
  </div>
@@ -0,0 +1,158 @@
1
+ ---
2
+ import type { ComponentType } from 'react';
3
+ import * as Icons from '@heroicons/react/24/outline';
4
+ import * as SolidIcons from '@heroicons/react/24/solid';
5
+ import * as LucideIcons from 'lucide-react';
6
+
7
+ interface AttachmentObject {
8
+ url: string;
9
+ title?: string;
10
+ type?: string;
11
+ description?: string;
12
+ summary?: string;
13
+ icon?: string;
14
+ }
15
+
16
+ type Attachment = string | AttachmentObject;
17
+
18
+ interface Props {
19
+ title?: string;
20
+ description?: string;
21
+ columns?: number;
22
+ data: {
23
+ attachments: Attachment[];
24
+ };
25
+ }
26
+
27
+ const { title = 'Attachments', description, columns = 2, ...props } = Astro.props;
28
+ const { attachments } = props.data;
29
+
30
+ function getIconForAttachment(attachment: AttachmentObject): ComponentType<{ className?: string }> | null {
31
+ // If custom icon is provided, try to find it
32
+ if (attachment.icon) {
33
+ const customIcon =
34
+ (LucideIcons as any)[attachment.icon] || (Icons as any)[attachment.icon] || (SolidIcons as any)[attachment.icon];
35
+ if (customIcon) return customIcon;
36
+ }
37
+
38
+ // Default to link icon for all attachments
39
+ return Icons.LinkIcon;
40
+ }
41
+
42
+ function normalizeAttachment(attachment: Attachment): AttachmentObject {
43
+ if (typeof attachment === 'string') {
44
+ // Extract filename from URL for title
45
+ const urlParts = attachment.split('/');
46
+ const filename = urlParts[urlParts.length - 1];
47
+ const title = filename.includes('.') ? filename.split('.')[0] : filename || 'Link';
48
+
49
+ return {
50
+ url: attachment,
51
+ title: title.replace(/[-_]/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
52
+ };
53
+ }
54
+ return attachment;
55
+ }
56
+
57
+ function isExternalUrl(url: string): boolean {
58
+ return url.startsWith('http://') || url.startsWith('https://');
59
+ }
60
+
61
+ const normalizedAttachments = attachments.map(normalizeAttachment);
62
+
63
+ // Group attachments by type
64
+ const groupedAttachments = normalizedAttachments.reduce(
65
+ (groups, attachment) => {
66
+ const type = attachment.type || 'Other';
67
+ if (!groups[type]) {
68
+ groups[type] = [];
69
+ }
70
+ groups[type].push(attachment);
71
+ return groups;
72
+ },
73
+ {} as Record<string, AttachmentObject[]>
74
+ );
75
+
76
+ const sortedGroups = Object.entries(groupedAttachments).sort(([a], [b]) => {
77
+ if (a === 'Other') return 1;
78
+ if (b === 'Other') return -1;
79
+ return a.localeCompare(b);
80
+ });
81
+ ---
82
+
83
+ <section class="not-prose my-8">
84
+ {title && <h3 class="flex items-center gap-2 text-3xl font-bold mb-4">{title}</h3>}
85
+
86
+ {description && <p class="text-gray-600 mb-6 prose prose-md">{description}</p>}
87
+
88
+ {
89
+ normalizedAttachments.length === 0 ? (
90
+ <div class="text-gray-500 text-sm italic">No attachments available.</div>
91
+ ) : (
92
+ <div class="space-y-6">
93
+ {sortedGroups.map(([groupType, groupAttachments], index) => (
94
+ <div key={groupType + index}>
95
+ <h4 class="text-sm font-medium text-gray-900 mb-3 uppercase tracking-wider">
96
+ {groupType} ({groupAttachments.length})
97
+ </h4>
98
+ <div class={`grid grid-cols-1 ${columns === 1 ? '' : `md:grid-cols-${Math.min(columns, 3)}`} gap-4`}>
99
+ {groupAttachments.map((attachment) => {
100
+ const IconComponent = getIconForAttachment(attachment);
101
+ const isExternal = isExternalUrl(attachment.url);
102
+
103
+ return (
104
+ <a
105
+ href={attachment.url}
106
+ target={isExternal ? '_blank' : '_self'}
107
+ rel={isExternal ? 'noopener noreferrer' : undefined}
108
+ class="group block bg-white border border-gray-200 rounded-lg p-4 transition-all duration-200 hover:shadow-md hover:border-purple-300 hover:bg-purple-50/30 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
109
+ >
110
+ <div class="flex items-start gap-3">
111
+ <div class="flex-shrink-0 p-2 bg-gray-100 rounded-lg group-hover:bg-purple-100 transition-colors duration-200">
112
+ {IconComponent && <IconComponent className="w-5 h-5 text-gray-600 group-hover:text-purple-600" />}
113
+ </div>
114
+
115
+ <div class="flex-grow min-w-0">
116
+ <div class="flex items-center gap-2">
117
+ <h5 class="text-sm font-medium text-gray-900 group-hover:text-purple-900 truncate">
118
+ {attachment.title || 'Attachment'}
119
+ </h5>
120
+ {isExternal && (
121
+ <Icons.ArrowTopRightOnSquareIcon className="w-3 h-3 text-gray-400 group-hover:text-purple-500 flex-shrink-0" />
122
+ )}
123
+ </div>
124
+
125
+ {attachment.summary && (
126
+ <p class="mt-1 text-xs text-gray-600 group-hover:text-purple-700 line-clamp-2">{attachment.summary}</p>
127
+ )}
128
+
129
+ {attachment.description && (
130
+ <p class="mt-1 text-xs text-gray-600 group-hover:text-purple-700 line-clamp-2">
131
+ {attachment.description}
132
+ </p>
133
+ )}
134
+
135
+ <div class="mt-2 text-xs text-gray-400 group-hover:text-purple-500 truncate">
136
+ {attachment.url.replace(/^https?:\/\//, '')}
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </a>
141
+ );
142
+ })}
143
+ </div>
144
+ </div>
145
+ ))}
146
+ </div>
147
+ )
148
+ }
149
+ </section>
150
+
151
+ <style>
152
+ .line-clamp-2 {
153
+ display: -webkit-box;
154
+ -webkit-line-clamp: 2;
155
+ -webkit-box-orient: vertical;
156
+ overflow: hidden;
157
+ }
158
+ </style>
@@ -23,20 +23,36 @@ function startsWithProtocol(str: string) {
23
23
  }
24
24
  ---
25
25
 
26
- <a
27
- href={startsWithProtocol(href) ? href : buildUrl(href)}
28
- target={openWindow ? '_blank' : '_self'}
29
- class="block bg-white border border-gray-200 rounded-lg p-6 transition-all duration-300 ease-in-out hover:shadow-md hover:border-primary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-primary focus:ring-white"
30
- >
31
- <div class="flex flex-col h-full space-y-8">
32
- {IconComponent && <IconComponent className={`w-6 h-6 ${iconColor}`} />}
33
- <div>
34
- <h2 class="text-gray-800 text-lg font-semibold transition-colors duration-300 ease-in-out group-hover:text-gray-300">
35
- {title}
36
- </h2>
37
- <p class="text-gray-600 transition-colors duration-300 ease-in-out group-hover:text-gray-200 m-0 font-light text-sm">
38
- {description}
39
- </p>
26
+ <div class="group">
27
+ <!-- Card with just the icon -->
28
+ <a
29
+ href={startsWithProtocol(href) ? href : buildUrl(href)}
30
+ target={openWindow ? '_blank' : '_self'}
31
+ class="block relative bg-white border border-gray-200 rounded-xl p-8 h-48 transition-all duration-300 ease-out hover:border-purple-300 hover:shadow-lg hover:shadow-purple-500/10 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 overflow-hidden"
32
+ >
33
+ <!-- Icon centered in card with grid background -->
34
+ <div class="relative h-full flex items-center justify-center">
35
+ <!-- Squared paper grid behind icon -->
36
+ <div
37
+ class="absolute inset-0 opacity-15"
38
+ style="background-image:
39
+ linear-gradient(rgba(147, 51, 234, 0.2) 1px, transparent 1px),
40
+ linear-gradient(90deg, rgba(147, 51, 234, 0.2) 1px, transparent 1px);
41
+ background-size: 16px 16px;"
42
+ >
43
+ </div>
44
+
45
+ {IconComponent && <IconComponent className="relative w-16 h-16 text-purple-600 stroke-1" />}
40
46
  </div>
47
+ </a>
48
+
49
+ <!-- Title and description outside card -->
50
+ <div class="mt-4 space-y-0">
51
+ <h2 class="text-gray-900 text-lg font-semibold">
52
+ {title}
53
+ </h2>
54
+ <p class="text-gray-600 text-sm leading-relaxed">
55
+ {description}
56
+ </p>
41
57
  </div>
42
- </a>
58
+ </div>
@@ -2,9 +2,9 @@
2
2
  const { title, columns = 2 } = Astro.props;
3
3
  ---
4
4
 
5
- <section class="not-prose">
6
- {title && <h2 class="text-2xl font-bold text-gray-800 mb-4">{title}</h2>}
7
- <div class={`grid grid-cols-1 md:grid-cols-${columns} gap-4 w-full not-prose`}>
5
+ <section class="not-prose my-8">
6
+ {title && <h2 class="text-2xl font-bold text-gray-800 mb-6">{title}</h2>}
7
+ <div class={`grid grid-cols-1 md:grid-cols-${columns} gap-6 w-full not-prose`}>
8
8
  <slot />
9
9
  </div>
10
10
  </section>
@@ -13,6 +13,7 @@ import Admonition from '@components/MDX/Admonition';
13
13
  import OpenAPI from '@components/MDX/OpenAPI/OpenAPI.astro';
14
14
  import AsyncAPI from '@components/MDX/AsyncAPI/AsyncAPI.astro';
15
15
  import ChannelInformation from '@components/MDX/ChannelInformation/ChannelInformation';
16
+ import Attachments from '@components/MDX/Attachments.astro';
16
17
  import MessageTable from '@components/MDX/MessageTable/MessageTable.astro';
17
18
  import ResourceGroupTable from '@components/MDX/ResourceGroupTable/ResourceGroupTable.astro';
18
19
  import EntityPropertiesTable from '@components/MDX/EntityPropertiesTable/EntityPropertiesTable.astro';
@@ -34,6 +35,7 @@ import RemoteSchema from '@components/MDX/RemoteSchema.astro';
34
35
 
35
36
  const components = (props: any) => {
36
37
  return {
38
+ Attachments: (mdxProp: any) => jsx(Attachments, { ...props, ...mdxProp }),
37
39
  Accordion,
38
40
  AccordionGroup,
39
41
  Admonition,
@@ -27,6 +27,19 @@ const parameters = Object.keys(channelParameters).map((key) => ({
27
27
  ...channelParameters[key],
28
28
  }));
29
29
 
30
+ const attachments = channel.data.attachments || [];
31
+
32
+ const attachmentsList = attachments.map((a) => {
33
+ const attachmentIsURL = typeof a === 'string';
34
+ return {
35
+ label: attachmentIsURL ? a : (a.title ?? a.url),
36
+ href: attachmentIsURL ? a : a.url,
37
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
38
+ target: '_blank' as const,
39
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
40
+ };
41
+ });
42
+
30
43
  const paramsList = parameters.map((param) => ({
31
44
  label: param.key,
32
45
  description: param?.description,
@@ -139,6 +152,18 @@ const shouldRenderSideBarSection = (section: string) => {
139
152
  )
140
153
  }
141
154
 
155
+ {
156
+ channel.data.attachments && shouldRenderSideBarSection('attachments') && (
157
+ <PillListFlat
158
+ title={`Attachments (${attachmentsList.length})`}
159
+ pills={attachmentsList}
160
+ emptyMessage={`This channel does not have any attachments.`}
161
+ color="pink"
162
+ client:load
163
+ />
164
+ )
165
+ }
166
+
142
167
  {
143
168
  ownersList.length > 0 && shouldRenderSideBarSection('owners') && (
144
169
  <OwnersList
@@ -25,6 +25,8 @@ const subDomains = (domain.data.domains as CollectionEntry<'domains'>[]) || [];
25
25
  // @ts-ignore
26
26
  const entities = (domain.data.entities as CollectionEntry<'entities'>[]) || [];
27
27
 
28
+ const attachments = domain.data.attachments || [];
29
+
28
30
  const ubiquitousLanguage = await getUbiquitousLanguage(domain);
29
31
  const hasUbiquitousLanguage = ubiquitousLanguage.length > 0;
30
32
  const ubiquitousLanguageDictionary = hasUbiquitousLanguage ? ubiquitousLanguage[0].data.dictionary : [];
@@ -106,6 +108,18 @@ const ownersList = filteredOwners.map((o) => ({
106
108
  href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
107
109
  }));
108
110
 
111
+ const attachmentsList = attachments.map((a) => {
112
+ const attachmentIsURL = typeof a === 'string';
113
+
114
+ return {
115
+ label: attachmentIsURL ? a : (a.title ?? a.url),
116
+ href: attachmentIsURL ? a : a.url,
117
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
118
+ target: '_blank' as const,
119
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
120
+ };
121
+ });
122
+
109
123
  const shouldRenderSideBarSection = (section: string) => {
110
124
  if (!domain.data.detailsPanel) {
111
125
  return true;
@@ -208,6 +222,17 @@ const shouldRenderSideBarSection = (section: string) => {
208
222
  <VersionList versions={domain.data.versions} collectionItem={domain} />
209
223
  )
210
224
  }
225
+ {
226
+ domain.data.attachments && shouldRenderSideBarSection('attachments') && (
227
+ <PillListFlat
228
+ title={`Attachments (${attachmentsList.length})`}
229
+ pills={attachmentsList}
230
+ emptyMessage={`This domain does not have any attachments.`}
231
+ color="pink"
232
+ client:load
233
+ />
234
+ )
235
+ }
211
236
  {
212
237
  domain.data.repository && shouldRenderSideBarSection('repository') && (
213
238
  <RepositoryList repository={domain.data.repository?.url} language={domain.data.repository?.language} />
@@ -23,6 +23,19 @@ const services = (entity.data.services as CollectionEntry<'services'>[]) || [];
23
23
  // @ts-ignore
24
24
  const domains = (entity.data.domains as CollectionEntry<'domains'>[]) || [];
25
25
 
26
+ const attachments = entity.data.attachments || [];
27
+
28
+ const attachmentsList = attachments.map((a) => {
29
+ const attachmentIsURL = typeof a === 'string';
30
+ return {
31
+ label: attachmentIsURL ? a : (a.title ?? a.url),
32
+ href: attachmentIsURL ? a : a.url,
33
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
34
+ target: '_blank' as const,
35
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
36
+ };
37
+ });
38
+
26
39
  const ownersList = filteredOwners.map((o) => ({
27
40
  label: o.data.name,
28
41
  type: o.collection,
@@ -87,6 +100,17 @@ const shouldRenderSideBarSection = (section: string) => {
87
100
  <VersionList versions={entity.data.versions} collectionItem={entity} />
88
101
  )
89
102
  }
103
+ {
104
+ entity.data.attachments && shouldRenderSideBarSection('attachments') && (
105
+ <PillListFlat
106
+ title={`Attachments (${attachmentsList.length})`}
107
+ pills={attachmentsList}
108
+ emptyMessage={`This entity does not have any attachments.`}
109
+ color="pink"
110
+ client:load
111
+ />
112
+ )
113
+ }
90
114
  {
91
115
  filteredOwners.length > 0 && shouldRenderSideBarSection('owners') && (
92
116
  <OwnersList
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import OwnersList from '@components/Lists/OwnersList';
3
3
  import VersionList from '@components/Lists/VersionList.astro';
4
+ import PillListFlat from '@components/Lists/PillListFlat';
4
5
  import { buildUrl } from '@utils/url-builder';
5
6
  import { getOwner } from '@utils/collections/owners';
6
7
  import type { CollectionEntry } from 'astro:content';
@@ -30,6 +31,19 @@ const ownersList = filteredOwners.map((o) => ({
30
31
 
31
32
  const isRSSEnabled = config.rss?.enabled;
32
33
 
34
+ const attachments = flow.data.attachments || [];
35
+
36
+ const attachmentsList = attachments.map((a) => {
37
+ const attachmentIsURL = typeof a === 'string';
38
+ return {
39
+ label: attachmentIsURL ? a : (a.title ?? a.url),
40
+ href: attachmentIsURL ? a : a.url,
41
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
42
+ target: '_blank' as const,
43
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
44
+ };
45
+ });
46
+
33
47
  const shouldRenderSideBarSection = (section: string) => {
34
48
  if (!flow.data.detailsPanel) {
35
49
  return true;
@@ -51,6 +65,17 @@ const shouldRenderSideBarSection = (section: string) => {
51
65
  <VersionList versions={flow.data.versions} collectionItem={flow} />
52
66
  )
53
67
  }
68
+ {
69
+ flow.data.attachments && shouldRenderSideBarSection('attachments') && (
70
+ <PillListFlat
71
+ title={`Attachments (${attachmentsList.length})`}
72
+ pills={attachmentsList}
73
+ emptyMessage={`This flow does not have any attachments.`}
74
+ color="pink"
75
+ client:load
76
+ />
77
+ )
78
+ }
54
79
 
55
80
  {
56
81
  shouldRenderSideBarSection('owners') && (
@@ -29,6 +29,19 @@ const filteredOwners = owners.filter((o) => o !== undefined);
29
29
 
30
30
  const resourceGroups = message.data?.resourceGroups || [];
31
31
 
32
+ const attachments = message.data.attachments || [];
33
+
34
+ const attachmentsList = attachments.map((a) => {
35
+ const attachmentIsURL = typeof a === 'string';
36
+ return {
37
+ label: attachmentIsURL ? a : (a.title ?? a.url),
38
+ href: attachmentIsURL ? a : a.url,
39
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
40
+ target: '_blank' as const,
41
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
42
+ };
43
+ });
44
+
32
45
  const producerList = producers.map((p) => ({
33
46
  label: `${p.data.name}`,
34
47
  tag: `v${p.data.version}`,
@@ -153,6 +166,18 @@ const shouldRenderSideBarSection = (section: string) => {
153
166
  )
154
167
  }
155
168
 
169
+ {
170
+ message.data.attachments && shouldRenderSideBarSection('attachments') && (
171
+ <PillListFlat
172
+ title={`Attachments (${attachmentsList.length})`}
173
+ pills={attachmentsList}
174
+ emptyMessage={`This ${type} does not have any attachments.`}
175
+ color="pink"
176
+ client:load
177
+ />
178
+ )
179
+ }
180
+
156
181
  {
157
182
  ownersList.length > 0 && shouldRenderSideBarSection('owners') && (
158
183
  <OwnersList
@@ -35,6 +35,19 @@ const resourceGroups = service.data?.resourceGroups || [];
35
35
 
36
36
  const domainsServiceBelongsTo = await getDomainsForService(service);
37
37
 
38
+ const attachments = service.data.attachments || [];
39
+
40
+ const attachmentsList = attachments.map((a) => {
41
+ const attachmentIsURL = typeof a === 'string';
42
+ return {
43
+ label: attachmentIsURL ? a : (a.title ?? a.url),
44
+ href: attachmentIsURL ? a : a.url,
45
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
46
+ target: '_blank' as const,
47
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
48
+ };
49
+ });
50
+
38
51
  const sendsList = sends
39
52
  .sort((a, b) => a.collection.localeCompare(b.collection))
40
53
  .map((p) => ({
@@ -141,6 +154,18 @@ const shouldRenderSideBarSection = (section: string) => {
141
154
  )
142
155
  }
143
156
 
157
+ {
158
+ service.data.attachments && shouldRenderSideBarSection('attachments') && (
159
+ <PillListFlat
160
+ title={`Attachments (${attachmentsList.length})`}
161
+ pills={attachmentsList}
162
+ emptyMessage={`This service does not have any attachments.`}
163
+ color="pink"
164
+ client:load
165
+ />
166
+ )
167
+ }
168
+
144
169
  {
145
170
  service.data.specifications && shouldRenderSideBarSection('specifications') && (
146
171
  <SpecificationsList collectionItem={service} />
@@ -147,6 +147,20 @@ const baseSchema = z.object({
147
147
  ])
148
148
  .optional(),
149
149
  visualiser: z.boolean().optional(),
150
+ attachments: z
151
+ .array(
152
+ z.union([
153
+ z.string().url(), // simple case
154
+ z.object({
155
+ url: z.string().url(),
156
+ title: z.string().optional(),
157
+ type: z.string().optional(), // e.g. "architecture-record", "diagram"
158
+ description: z.string().optional(),
159
+ icon: z.string().optional(),
160
+ }),
161
+ ])
162
+ )
163
+ .optional(),
150
164
  // Used by eventcatalog
151
165
  versions: z.array(z.string()).optional(),
152
166
  latestVersion: z.string().optional(),
@@ -259,6 +273,7 @@ const messageDetailsPanelPropertySchema = z.object({
259
273
  repository: detailPanelPropertySchema.optional(),
260
274
  owners: detailPanelPropertySchema.optional(),
261
275
  changelog: detailPanelPropertySchema.optional(),
276
+ attachments: detailPanelPropertySchema.optional(),
262
277
  });
263
278
 
264
279
  const events = defineCollection({
@@ -411,6 +426,7 @@ const domains = defineCollection({
411
426
  versions: detailPanelPropertySchema.optional(),
412
427
  owners: detailPanelPropertySchema.optional(),
413
428
  changelog: detailPanelPropertySchema.optional(),
429
+ attachments: detailPanelPropertySchema.optional(),
414
430
  })
415
431
  .optional(),
416
432
  })
@@ -451,6 +467,7 @@ const channels = defineCollection({
451
467
  repository: detailPanelPropertySchema.optional(),
452
468
  owners: detailPanelPropertySchema.optional(),
453
469
  changelog: detailPanelPropertySchema.optional(),
470
+ attachments: detailPanelPropertySchema.optional(),
454
471
  })
455
472
  .optional(),
456
473
  })
@@ -522,6 +539,7 @@ const entities = defineCollection({
522
539
  versions: detailPanelPropertySchema.optional(),
523
540
  owners: detailPanelPropertySchema.optional(),
524
541
  changelog: detailPanelPropertySchema.optional(),
542
+ attachments: detailPanelPropertySchema.optional(),
525
543
  })
526
544
  .optional(),
527
545
  })
@@ -267,17 +267,37 @@ nodeGraphs.push({
267
267
  <h2 class="text-lg pt-2 text-gray-500 font-light">{props.data.summary}</h2>
268
268
  {
269
269
  badges && (
270
- <div class="flex flex-wrap py-2 pt-4">
270
+ <div class="flex flex-wrap gap-3 py-4">
271
271
  {badges.map((badge: any) => {
272
272
  return (
273
- <a href={badge.url || '#'} class="pb-2">
273
+ <a href={badge.url || '#'} class="group transition-all duration-200 hover:scale-105">
274
274
  <span
275
275
  id={badge.id || ''}
276
- class={`text-sm font-light text-gray-500 px-2 py-1 rounded-md mr-2 bg-gradient-to-b from-${badge.backgroundColor}-100 to-${badge.backgroundColor}-200 space-x-1 border border-${badge.backgroundColor}-200 text-${badge.textColor}-800 flex items-center ${badge.class ? badge.class : ''} `}
276
+ class={`
277
+ inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium
278
+ bg-${badge.backgroundColor || 'white'}-50 border border-${badge.backgroundColor || 'gray'}-200
279
+ text-${badge.textColor || 'gray'}-700 shadow-sm
280
+ hover:bg-${badge.backgroundColor || 'purple'}-100 hover:border-${badge.backgroundColor || 'purple'}-300
281
+ hover:shadow-md hover:text-${badge.textColor || 'purple'}-800
282
+ transition-all duration-200 ease-out
283
+ ${badge.class ? badge.class : ''}
284
+ `}
277
285
  >
278
- {badge.icon && <badge.icon className="w-4 h-4 inline-block mr-1 " />}
279
- {badge.iconURL && <img src={badge.iconURL} class="w-5 h-5 inline-block " />}
280
- <span>{badge.content}</span>
286
+ {badge.icon && (
287
+ <badge.icon
288
+ className={`w-4 h-4 flex-shrink-0 text-${badge.textColor || 'gray'}-600 group-hover:text-${badge.textColor || 'purple'}-700 transition-colors duration-200`}
289
+ />
290
+ )}
291
+ {badge.iconURL && (
292
+ <img
293
+ src={badge.iconURL}
294
+ class="w-4 h-4 flex-shrink-0 opacity-80 group-hover:opacity-100 transition-opacity duration-200"
295
+ alt=""
296
+ />
297
+ )}
298
+ <span class={`group-hover:text-${badge.textColor || 'purple'}-800 transition-colors duration-200`}>
299
+ {badge.content}
300
+ </span>
281
301
  </span>
282
302
  </a>
283
303
  );
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "2.57.0",
9
+ "version": "2.57.2",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
@@ -38,7 +38,7 @@
38
38
  "@eventcatalog/license": "^0.0.6",
39
39
  "@eventcatalog/linter": "^0.0.2",
40
40
  "@eventcatalog/sdk": "^2.2.7",
41
- "@eventcatalog/visualizer": "^0.0.5",
41
+ "@eventcatalog/visualizer": "^0.0.6",
42
42
  "@fontsource/inter": "^5.2.5",
43
43
  "@headlessui/react": "^2.0.3",
44
44
  "@heroicons/react": "^2.1.3",
@@ -129,6 +129,7 @@
129
129
  "prettier": "^3.3.3",
130
130
  "prettier-plugin-astro": "^0.14.1",
131
131
  "tsup": "^8.1.0",
132
+ "vite": "^7.1.7",
132
133
  "vite-tsconfig-paths": "^4.3.2",
133
134
  "vitest": "2.1.6"
134
135
  },