@graph-artifact/core 0.1.4 → 0.1.6

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.
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { memo, useMemo } from 'react';
3
- import { renderMarkdown } from '../renderMarkdown.js';
3
+ import ReactMarkdown from 'react-markdown';
4
4
  import { useTheme } from '../ThemeContext.js';
5
5
  import { getConfig } from '../config.js';
6
6
  /**
@@ -50,6 +50,10 @@ ${scope} a:hover { text-decoration: underline; }
50
50
  ${scope} hr { border: none; border-top: ${theme.borderWidth.sm} solid ${theme.color.gray2}; margin: ${theme.space[4]} 0; }
51
51
  `;
52
52
  }
53
+ function normalizeNotesMarkdown(md) {
54
+ // Preserve intentional line breaks without remark plugins.
55
+ return md.replace(/\r\n/g, '\n').trim().replace(/\n/g, ' \n');
56
+ }
53
57
  export const NodeDetail = memo(function NodeDetail({ nodeId, nodeLabel, meta, onClose }) {
54
58
  const theme = useTheme();
55
59
  const { library } = getConfig();
@@ -61,6 +65,10 @@ export const NodeDetail = memo(function NodeDetail({ nodeId, nodeLabel, meta, on
61
65
  const hasDetails = details && Object.keys(details).length > 0;
62
66
  const hasContent = hasNotes || hasDetails || status;
63
67
  const markdownCss = useMemo(() => hasNotes ? buildMarkdownStyles(theme) : '', [hasNotes, theme]);
68
+ const markdownComponents = useMemo(() => ({
69
+ // Ensure links are safe/new-tab, even if consumer overrides global CSS.
70
+ a: ({ href, children }) => (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", children: children })),
71
+ }), []);
64
72
  return (_jsxs("div", { style: {
65
73
  position: 'absolute', top: 0, right: 0,
66
74
  width: 380, height: '100%',
@@ -105,7 +113,7 @@ ${markdownCss}` }), _jsx("div", { style: {
105
113
  width: detail.statusDotSize, height: detail.statusDotSize,
106
114
  borderRadius: theme.radius.full,
107
115
  backgroundColor: statusStyle.text,
108
- } }), status] })), hasNotes && (_jsx("div", { "data-graph-detail-notes": "", dangerouslySetInnerHTML: { __html: renderMarkdown(meta.notes) } })), hasDetails && (_jsxs("div", { style: { marginTop: theme.space[5] }, children: [_jsx("h3", { style: {
116
+ } }), status] })), hasNotes && (_jsx("div", { "data-graph-detail-notes": "", children: _jsx(ReactMarkdown, { components: markdownComponents, children: normalizeNotesMarkdown(meta.notes) }) })), hasDetails && (_jsxs("div", { style: { marginTop: theme.space[5] }, children: [_jsx("h3", { style: {
109
117
  fontSize: theme.font.size.xs, fontWeight: theme.font.weight.semibold, color: theme.color.gray3,
110
118
  textTransform: 'uppercase', letterSpacing: detail.letterSpacing, marginBottom: theme.space[2],
111
119
  }, children: "Details" }), _jsx("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: theme.font.size.base }, children: _jsx("tbody", { children: Object.entries(details).map(([key, value]) => (_jsxs("tr", { style: { borderBottom: `${theme.borderWidth.sm} solid ${theme.color.darkBg3}` }, children: [_jsx("td", { style: {
@@ -12,11 +12,34 @@ const invisibleHandle = {
12
12
  background: 'transparent',
13
13
  pointerEvents: 'none',
14
14
  };
15
+ function decodeHtmlEntities(src) {
16
+ // Keep this intentionally small; labels should be plain text.
17
+ return src
18
+ .replace(/ /gi, ' ')
19
+ .replace(/&/gi, '&')
20
+ .replace(/&lt;/gi, '<')
21
+ .replace(/&gt;/gi, '>')
22
+ .replace(/&quot;/gi, '"')
23
+ .replace(/&#39;/g, "'");
24
+ }
25
+ function htmlSubsetToMarkdown(src) {
26
+ // Mermaid labels often contain a tiny HTML subset: <br/>, <b>, <i>, etc.
27
+ // Convert the common cases to markdown so react-markdown renders them,
28
+ // and strip any remaining tags (avoid raw HTML rendering).
29
+ let s = src;
30
+ s = s.replace(/<\s*br\s*\/?\s*>/gi, '\n');
31
+ s = s.replace(/<\s*\/?\s*(strong|b)\s*>/gi, '**');
32
+ s = s.replace(/<\s*\/?\s*(em|i)\s*>/gi, '*');
33
+ s = s.replace(/<\s*\/?\s*code\s*>/gi, '`');
34
+ // Strip any remaining tags defensively
35
+ s = s.replace(/<[^>]*>/g, '');
36
+ return decodeHtmlEntities(s);
37
+ }
15
38
  function normalizeLabelMarkdown(label) {
16
39
  // Mermaid treats "\n" inside labels as a hard line break.
17
40
  // react-markdown doesn't always preserve raw newlines, so force
18
41
  // CommonMark hard-breaks by adding two trailing spaces.
19
- return label
42
+ return htmlSubsetToMarkdown(label)
20
43
  .replace(/\\n/g, '\n')
21
44
  .trim()
22
45
  .replace(/\n/g, ' \n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graph-artifact/core",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Composable Mermaid-like parser, layout engine, and React renderer for interactive diagram artifacts.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,8 +50,8 @@
50
50
  "build": "rm -rf dist && tsc",
51
51
  "typecheck": "tsc --noEmit",
52
52
  "smoke:core": "node scripts/smoke-core.mjs",
53
- "test": "npm run typecheck && npm run smoke:core",
54
- "prepack": "npm run build && npm run test"
53
+ "test": "npm run build && npm run typecheck && npm run smoke:core",
54
+ "prepack": "npm run test"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^18.0.0 || ^19.0.0",