@flyo/nitro-next 1.8.0 → 1.9.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/dist/client.d.mts CHANGED
@@ -36,6 +36,13 @@ declare function FlyoClientWrapper({ children, }: {
36
36
  /**
37
37
  * WYSIWYG component for rendering ProseMirror/TipTap JSON content
38
38
  *
39
+ * Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert
40
+ * nodes to HTML. All consecutive non-custom nodes are joined into a single
41
+ * HTML string so no extra wrapper `<div>` elements are added around each node.
42
+ *
43
+ * The component wraps all output in a single `<div>` with an optional
44
+ * `className` (defaults to `"wysiwyg"`).
45
+ *
39
46
  * @example
40
47
  * ```tsx
41
48
  * import { FlyoWysiwyg } from '@flyo/nitro-next/client';
@@ -45,6 +52,7 @@ declare function FlyoClientWrapper({ children, }: {
45
52
  * return (
46
53
  * <FlyoWysiwyg
47
54
  * json={block.content.json}
55
+ * className="wysiwyg"
48
56
  * components={{
49
57
  * image: CustomImage
50
58
  * }}
@@ -53,8 +61,9 @@ declare function FlyoClientWrapper({ children, }: {
53
61
  * }
54
62
  * ```
55
63
  */
56
- declare function FlyoWysiwyg({ json, components, }: {
64
+ declare function FlyoWysiwyg({ json, className, components, }: {
57
65
  json: WysiwygJson;
66
+ className?: string;
58
67
  components?: Record<string, React.ComponentType<{
59
68
  node: WysiwygNode;
60
69
  }>>;
@@ -111,4 +120,4 @@ declare function FlyoMetric({ entity }: {
111
120
  entity: Entity;
112
121
  }): null;
113
122
 
114
- export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, editable, isProd };
123
+ export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, type WysiwygJson, type WysiwygNode, editable, isProd };
package/dist/client.d.ts CHANGED
@@ -36,6 +36,13 @@ declare function FlyoClientWrapper({ children, }: {
36
36
  /**
37
37
  * WYSIWYG component for rendering ProseMirror/TipTap JSON content
38
38
  *
39
+ * Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert
40
+ * nodes to HTML. All consecutive non-custom nodes are joined into a single
41
+ * HTML string so no extra wrapper `<div>` elements are added around each node.
42
+ *
43
+ * The component wraps all output in a single `<div>` with an optional
44
+ * `className` (defaults to `"wysiwyg"`).
45
+ *
39
46
  * @example
40
47
  * ```tsx
41
48
  * import { FlyoWysiwyg } from '@flyo/nitro-next/client';
@@ -45,6 +52,7 @@ declare function FlyoClientWrapper({ children, }: {
45
52
  * return (
46
53
  * <FlyoWysiwyg
47
54
  * json={block.content.json}
55
+ * className="wysiwyg"
48
56
  * components={{
49
57
  * image: CustomImage
50
58
  * }}
@@ -53,8 +61,9 @@ declare function FlyoClientWrapper({ children, }: {
53
61
  * }
54
62
  * ```
55
63
  */
56
- declare function FlyoWysiwyg({ json, components, }: {
64
+ declare function FlyoWysiwyg({ json, className, components, }: {
57
65
  json: WysiwygJson;
66
+ className?: string;
58
67
  components?: Record<string, React.ComponentType<{
59
68
  node: WysiwygNode;
60
69
  }>>;
@@ -111,4 +120,4 @@ declare function FlyoMetric({ entity }: {
111
120
  entity: Entity;
112
121
  }): null;
113
122
 
114
- export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, editable, isProd };
123
+ export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, type WysiwygJson, type WysiwygNode, editable, isProd };
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  "use strict";
2
3
  "use client";
3
4
  var __defProp = Object.defineProperty;
@@ -84,6 +85,7 @@ function FlyoClientWrapper({
84
85
  }
85
86
  function FlyoWysiwyg({
86
87
  json,
88
+ className = "wysiwyg",
87
89
  components = {}
88
90
  }) {
89
91
  let nodes = [];
@@ -96,13 +98,31 @@ function FlyoWysiwyg({
96
98
  nodes = [json];
97
99
  }
98
100
  }
99
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: nodes.map((node, index) => {
101
+ const hasCustomComponents = nodes.some((node) => components[node.type]);
102
+ if (!hasCustomComponents) {
103
+ const html = nodes.map((node) => (0, import_nitro_js_bridge.wysiwyg)(node)).join("");
104
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, dangerouslySetInnerHTML: { __html: html } });
105
+ }
106
+ const groups = [];
107
+ for (const node of nodes) {
100
108
  const Component = components[node.type];
101
109
  if (Component) {
102
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { node }, index);
110
+ groups.push({ type: "custom", component: Component, node });
111
+ } else {
112
+ const html = (0, import_nitro_js_bridge.wysiwyg)(node);
113
+ const last = groups[groups.length - 1];
114
+ if (last && last.type === "html") {
115
+ last.html += html;
116
+ } else {
117
+ groups.push({ type: "html", html });
118
+ }
119
+ }
120
+ }
121
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: groups.map((group, index) => {
122
+ if (group.type === "custom") {
123
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(group.component, { node: group.node }, index);
103
124
  }
104
- const html = (0, import_nitro_js_bridge.wysiwyg)(node);
105
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: html } }, index);
125
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: group.html } }, index);
106
126
  }) });
107
127
  }
108
128
  function FlyoCdnLoader({ src, width }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\ninterface WysiwygNode {\n type: string;\n content?: WysiwygNode[];\n [key: string]: unknown;\n}\n\n/**\n * Type for WYSIWYG JSON that can be a node, array of nodes, or doc structure\n */\ntype WysiwygJson = WysiwygNode | WysiwygNode[] | { type: 'doc'; content: WysiwygNode[] };\n\n/**\n * Helper function to get editable props\n */\nexport function editable(block: Block): { 'data-flyo-uid'?: string } {\n if (typeof block.uid === 'string' && block.uid.trim() !== '') {\n return { 'data-flyo-uid': block.uid };\n }\n return {};\n}\n\n/**\n * Internal client component that sets up live editing functionality\n */\nexport function FlyoClientWrapper({ \n children,\n}: { \n children: React.ReactNode;\n}) {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n reload();\n\n const wireAll = () => {\n const elements = document.querySelectorAll('[data-flyo-uid]');\n elements.forEach((el) => {\n const uid = el.getAttribute('data-flyo-uid');\n if (uid && el instanceof HTMLElement) {\n highlightAndClick(uid, el);\n }\n });\n };\n\n wireAll();\n\n const observer = new MutationObserver((mutations) => {\n const hasRelevantChanges = mutations.some(mutation => \n Array.from(mutation.addedNodes).some(node => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n return element.hasAttribute('data-flyo-uid') || \n element.querySelector('[data-flyo-uid]');\n }\n return false;\n })\n );\n\n if (hasRelevantChanges) {\n wireAll();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n return <>{children}</>;\n}\n\n/**\n * WYSIWYG component for rendering ProseMirror/TipTap JSON content\n * \n * @example\n * ```tsx\n * import { FlyoWysiwyg } from '@flyo/nitro-next/client';\n * import CustomImage from './CustomImage';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <FlyoWysiwyg \n * json={block.content.json} \n * components={{\n * image: CustomImage\n * }} \n * />\n * );\n * }\n * ```\n */\nexport function FlyoWysiwyg({\n json,\n components = {},\n}: {\n json: WysiwygJson;\n components?: Record<string, React.ComponentType<{ node: WysiwygNode }>>;\n}) {\n let nodes: WysiwygNode[] = [];\n\n if (json) {\n if (Array.isArray(json)) {\n nodes = json;\n } else if ('type' in json && json.type === 'doc' && Array.isArray(json.content)) {\n nodes = json.content;\n } else {\n nodes = [json as WysiwygNode];\n }\n }\n\n return (\n <>\n {nodes.map((node: WysiwygNode, index: number) => {\n const Component = components[node.type];\n if (Component) {\n return <Component key={index} node={node} />;\n }\n \n const html = wysiwyg(node);\n return <div key={index} dangerouslySetInnerHTML={{ __html: html }} />;\n })}\n </>\n );\n}\n\n/**\n * Image loader for Flyo CDN that automatically handles image transformations.\n * Adds Flyo CDN host if not already present and applies width transformations.\n * \n * @param src - The image source URL (relative or absolute)\n * @param width - The desired width for the image\n * @returns Transformed image URL with Flyo CDN parameters\n * \n * @example\n * ```tsx\n * <Image\n * loader={FlyoCdnLoader}\n * src=\"me.png\"\n * alt=\"Picture\"\n * width={500}\n * height={500}\n * />\n * ```\n */\nexport function FlyoCdnLoader({ src, width }: ImageLoaderProps): string {\n let imageUrl = src;\n\n // If src doesn't contain the Flyo CDN host, prefix it\n if (!src.includes(FLYO_CDN_HOST)) {\n // Remove leading slash if present to avoid double slashes\n const cleanSrc = src.startsWith('/') ? src.slice(1) : src;\n imageUrl = `https://${FLYO_CDN_HOST}/${cleanSrc}`;\n }\n\n // Append Flyo CDN transformation parameters\n return `${imageUrl}/thumb/${width}xnull?format=webp`;\n}\n\n/**\n * FlyoMetric component for tracking entity metrics in production\n * \n * Automatically sends a metric tracking request to the Flyo API when:\n * - The environment is production (NODE_ENV === 'production')\n * - The entity has a metric API URL configured\n * \n * @param entity - The entity object containing entity_metric.api\n * \n * @example\n * ```tsx\n * import { FlyoMetric } from '@flyo/nitro-next/client';\n * \n * export default function BlogPost(props: RouteParams) {\n * return nitroEntityRoute(props, {\n * resolver,\n * render: (entity: Entity) => (\n * <>\n * <FlyoMetric entity={entity} />\n * <article>\n * <h1>{entity.entity?.entity_title}</h1>\n * </article>\n * </>\n * )\n * });\n * }\n * ```\n */\nexport function FlyoMetric({ entity }: { entity: Entity }) {\n useEffect(() => {\n // Only track metrics in production and if API URL is available\n if (isProd && entity?.entity?.entity_metric?.api) {\n fetch(entity.entity.entity_metric.api);\n }\n }, [entity]);\n\n // This component doesn't render anything\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAA0B;AAC1B,6BAAkD;AAyFzC;AArFT,IAAM,gBAAgB;AAKf,IAAM,SAAS,QAAQ,IAAI,aAAa;AAmBxC,SAAS,SAAS,OAA4C;AACnE,MAAI,OAAO,MAAM,QAAQ,YAAY,MAAM,IAAI,KAAK,MAAM,IAAI;AAC5D,WAAO,EAAE,iBAAiB,MAAM,IAAI;AAAA,EACtC;AACA,SAAO,CAAC;AACV;AAKO,SAAS,kBAAkB;AAAA,EAChC;AACF,GAEG;AACD,8BAAU,MAAM;AACd,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,uCAAO;AAEP,UAAM,UAAU,MAAM;AACpB,YAAM,WAAW,SAAS,iBAAiB,iBAAiB;AAC5D,eAAS,QAAQ,CAAC,OAAO;AACvB,cAAM,MAAM,GAAG,aAAa,eAAe;AAC3C,YAAI,OAAO,cAAc,aAAa;AACpC,wDAAkB,KAAK,EAAE;AAAA,QAC3B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ;AAER,UAAM,WAAW,IAAI,iBAAiB,CAAC,cAAc;AACnD,YAAM,qBAAqB,UAAU;AAAA,QAAK,cACxC,MAAM,KAAK,SAAS,UAAU,EAAE,KAAK,UAAQ;AAC3C,cAAI,KAAK,aAAa,KAAK,cAAc;AACvC,kBAAM,UAAU;AAChB,mBAAO,QAAQ,aAAa,eAAe,KACpC,QAAQ,cAAc,iBAAiB;AAAA,UAChD;AACA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAEA,UAAI,oBAAoB;AACtB,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAED,aAAS,QAAQ,SAAS,MAAM;AAAA,MAC9B,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC;AAED,WAAO,MAAM;AACX,eAAS,WAAW;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,2EAAG,UAAS;AACrB;AAsBO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,aAAa,CAAC;AAChB,GAGG;AACD,MAAI,QAAuB,CAAC;AAE5B,MAAI,MAAM;AACR,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,cAAQ;AAAA,IACV,WAAW,UAAU,QAAQ,KAAK,SAAS,SAAS,MAAM,QAAQ,KAAK,OAAO,GAAG;AAC/E,cAAQ,KAAK;AAAA,IACf,OAAO;AACL,cAAQ,CAAC,IAAmB;AAAA,IAC9B;AAAA,EACF;AAEA,SACE,2EACG,gBAAM,IAAI,CAAC,MAAmB,UAAkB;AAC/C,UAAM,YAAY,WAAW,KAAK,IAAI;AACtC,QAAI,WAAW;AACb,aAAO,4CAAC,aAAsB,QAAP,KAAmB;AAAA,IAC5C;AAEA,UAAM,WAAO,gCAAQ,IAAI;AACzB,WAAO,4CAAC,SAAgB,yBAAyB,EAAE,QAAQ,KAAK,KAA/C,KAAkD;AAAA,EACrE,CAAC,GACH;AAEJ;AAqBO,SAAS,cAAc,EAAE,KAAK,MAAM,GAA6B;AACtE,MAAI,WAAW;AAGf,MAAI,CAAC,IAAI,SAAS,aAAa,GAAG;AAEhC,UAAM,WAAW,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AACtD,eAAW,WAAW,aAAa,IAAI,QAAQ;AAAA,EACjD;AAGA,SAAO,GAAG,QAAQ,UAAU,KAAK;AACnC;AA8BO,SAAS,WAAW,EAAE,OAAO,GAAuB;AACzD,8BAAU,MAAM;AAEd,QAAI,UAAU,QAAQ,QAAQ,eAAe,KAAK;AAChD,YAAM,OAAO,OAAO,cAAc,GAAG;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\nexport interface WysiwygNode {\n type: string;\n content?: WysiwygNode[];\n [key: string]: unknown;\n}\n\n/**\n * Type for WYSIWYG JSON that can be a node, array of nodes, or doc structure\n */\nexport type WysiwygJson = WysiwygNode | WysiwygNode[] | { type: 'doc'; content: WysiwygNode[] };\n\n/**\n * Helper function to get editable props\n */\nexport function editable(block: Block): { 'data-flyo-uid'?: string } {\n if (typeof block.uid === 'string' && block.uid.trim() !== '') {\n return { 'data-flyo-uid': block.uid };\n }\n return {};\n}\n\n/**\n * Internal client component that sets up live editing functionality\n */\nexport function FlyoClientWrapper({ \n children,\n}: { \n children: React.ReactNode;\n}) {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n reload();\n\n const wireAll = () => {\n const elements = document.querySelectorAll('[data-flyo-uid]');\n elements.forEach((el) => {\n const uid = el.getAttribute('data-flyo-uid');\n if (uid && el instanceof HTMLElement) {\n highlightAndClick(uid, el);\n }\n });\n };\n\n wireAll();\n\n const observer = new MutationObserver((mutations) => {\n const hasRelevantChanges = mutations.some(mutation => \n Array.from(mutation.addedNodes).some(node => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n return element.hasAttribute('data-flyo-uid') || \n element.querySelector('[data-flyo-uid]');\n }\n return false;\n })\n );\n\n if (hasRelevantChanges) {\n wireAll();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n return <>{children}</>;\n}\n\n/**\n * WYSIWYG component for rendering ProseMirror/TipTap JSON content\n * \n * Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert\n * nodes to HTML. All consecutive non-custom nodes are joined into a single\n * HTML string so no extra wrapper `<div>` elements are added around each node.\n * \n * The component wraps all output in a single `<div>` with an optional\n * `className` (defaults to `\"wysiwyg\"`).\n * \n * @example\n * ```tsx\n * import { FlyoWysiwyg } from '@flyo/nitro-next/client';\n * import CustomImage from './CustomImage';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <FlyoWysiwyg \n * json={block.content.json} \n * className=\"wysiwyg\"\n * components={{\n * image: CustomImage\n * }} \n * />\n * );\n * }\n * ```\n */\nexport function FlyoWysiwyg({\n json,\n className = 'wysiwyg',\n components = {},\n}: {\n json: WysiwygJson;\n className?: string;\n components?: Record<string, React.ComponentType<{ node: WysiwygNode }>>;\n}) {\n let nodes: WysiwygNode[] = [];\n\n if (json) {\n if (Array.isArray(json)) {\n nodes = json;\n } else if ('type' in json && json.type === 'doc' && Array.isArray(json.content)) {\n nodes = json.content;\n } else {\n nodes = [json as WysiwygNode];\n }\n }\n\n // If no custom components are provided, render all nodes as a single HTML block\n const hasCustomComponents = nodes.some((node) => components[node.type]);\n\n if (!hasCustomComponents) {\n const html = nodes.map((node) => wysiwyg(node)).join('');\n return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />;\n }\n\n // When custom components are used, group consecutive non-custom nodes\n // into single HTML blocks to avoid extra wrapper elements.\n const groups: ({ type: 'custom'; component: React.ComponentType<{ node: WysiwygNode }>; node: WysiwygNode } | { type: 'html'; html: string })[] = [];\n\n for (const node of nodes) {\n const Component = components[node.type];\n if (Component) {\n groups.push({ type: 'custom', component: Component, node });\n } else {\n const html = wysiwyg(node);\n const last = groups[groups.length - 1];\n if (last && last.type === 'html') {\n last.html += html;\n } else {\n groups.push({ type: 'html', html });\n }\n }\n }\n\n return (\n <div className={className}>\n {groups.map((group, index) => {\n if (group.type === 'custom') {\n return <group.component key={index} node={group.node} />;\n }\n return <div key={index} dangerouslySetInnerHTML={{ __html: group.html }} />;\n })}\n </div>\n );\n}\n\n/**\n * Image loader for Flyo CDN that automatically handles image transformations.\n * Adds Flyo CDN host if not already present and applies width transformations.\n * \n * @param src - The image source URL (relative or absolute)\n * @param width - The desired width for the image\n * @returns Transformed image URL with Flyo CDN parameters\n * \n * @example\n * ```tsx\n * <Image\n * loader={FlyoCdnLoader}\n * src=\"me.png\"\n * alt=\"Picture\"\n * width={500}\n * height={500}\n * />\n * ```\n */\nexport function FlyoCdnLoader({ src, width }: ImageLoaderProps): string {\n let imageUrl = src;\n\n // If src doesn't contain the Flyo CDN host, prefix it\n if (!src.includes(FLYO_CDN_HOST)) {\n // Remove leading slash if present to avoid double slashes\n const cleanSrc = src.startsWith('/') ? src.slice(1) : src;\n imageUrl = `https://${FLYO_CDN_HOST}/${cleanSrc}`;\n }\n\n // Append Flyo CDN transformation parameters\n return `${imageUrl}/thumb/${width}xnull?format=webp`;\n}\n\n/**\n * FlyoMetric component for tracking entity metrics in production\n * \n * Automatically sends a metric tracking request to the Flyo API when:\n * - The environment is production (NODE_ENV === 'production')\n * - The entity has a metric API URL configured\n * \n * @param entity - The entity object containing entity_metric.api\n * \n * @example\n * ```tsx\n * import { FlyoMetric } from '@flyo/nitro-next/client';\n * \n * export default function BlogPost(props: RouteParams) {\n * return nitroEntityRoute(props, {\n * resolver,\n * render: (entity: Entity) => (\n * <>\n * <FlyoMetric entity={entity} />\n * <article>\n * <h1>{entity.entity?.entity_title}</h1>\n * </article>\n * </>\n * )\n * });\n * }\n * ```\n */\nexport function FlyoMetric({ entity }: { entity: Entity }) {\n useEffect(() => {\n // Only track metrics in production and if API URL is available\n if (isProd && entity?.entity?.entity_metric?.api) {\n fetch(entity.entity.entity_metric.api);\n }\n }, [entity]);\n\n // This component doesn't render anything\n return null;\n}\n"],"mappings":"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","names":[]}
package/dist/client.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ "use client";
2
3
 
3
4
  // src/client.tsx
4
5
  import { useEffect } from "react";
@@ -56,6 +57,7 @@ function FlyoClientWrapper({
56
57
  }
57
58
  function FlyoWysiwyg({
58
59
  json,
60
+ className = "wysiwyg",
59
61
  components = {}
60
62
  }) {
61
63
  let nodes = [];
@@ -68,13 +70,31 @@ function FlyoWysiwyg({
68
70
  nodes = [json];
69
71
  }
70
72
  }
71
- return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node, index) => {
73
+ const hasCustomComponents = nodes.some((node) => components[node.type]);
74
+ if (!hasCustomComponents) {
75
+ const html = nodes.map((node) => wysiwyg(node)).join("");
76
+ return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
77
+ }
78
+ const groups = [];
79
+ for (const node of nodes) {
72
80
  const Component = components[node.type];
73
81
  if (Component) {
74
- return /* @__PURE__ */ jsx(Component, { node }, index);
82
+ groups.push({ type: "custom", component: Component, node });
83
+ } else {
84
+ const html = wysiwyg(node);
85
+ const last = groups[groups.length - 1];
86
+ if (last && last.type === "html") {
87
+ last.html += html;
88
+ } else {
89
+ groups.push({ type: "html", html });
90
+ }
91
+ }
92
+ }
93
+ return /* @__PURE__ */ jsx("div", { className, children: groups.map((group, index) => {
94
+ if (group.type === "custom") {
95
+ return /* @__PURE__ */ jsx(group.component, { node: group.node }, index);
75
96
  }
76
- const html = wysiwyg(node);
77
- return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }, index);
97
+ return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: group.html } }, index);
78
98
  }) });
79
99
  }
80
100
  function FlyoCdnLoader({ src, width }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\ninterface WysiwygNode {\n type: string;\n content?: WysiwygNode[];\n [key: string]: unknown;\n}\n\n/**\n * Type for WYSIWYG JSON that can be a node, array of nodes, or doc structure\n */\ntype WysiwygJson = WysiwygNode | WysiwygNode[] | { type: 'doc'; content: WysiwygNode[] };\n\n/**\n * Helper function to get editable props\n */\nexport function editable(block: Block): { 'data-flyo-uid'?: string } {\n if (typeof block.uid === 'string' && block.uid.trim() !== '') {\n return { 'data-flyo-uid': block.uid };\n }\n return {};\n}\n\n/**\n * Internal client component that sets up live editing functionality\n */\nexport function FlyoClientWrapper({ \n children,\n}: { \n children: React.ReactNode;\n}) {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n reload();\n\n const wireAll = () => {\n const elements = document.querySelectorAll('[data-flyo-uid]');\n elements.forEach((el) => {\n const uid = el.getAttribute('data-flyo-uid');\n if (uid && el instanceof HTMLElement) {\n highlightAndClick(uid, el);\n }\n });\n };\n\n wireAll();\n\n const observer = new MutationObserver((mutations) => {\n const hasRelevantChanges = mutations.some(mutation => \n Array.from(mutation.addedNodes).some(node => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n return element.hasAttribute('data-flyo-uid') || \n element.querySelector('[data-flyo-uid]');\n }\n return false;\n })\n );\n\n if (hasRelevantChanges) {\n wireAll();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n return <>{children}</>;\n}\n\n/**\n * WYSIWYG component for rendering ProseMirror/TipTap JSON content\n * \n * @example\n * ```tsx\n * import { FlyoWysiwyg } from '@flyo/nitro-next/client';\n * import CustomImage from './CustomImage';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <FlyoWysiwyg \n * json={block.content.json} \n * components={{\n * image: CustomImage\n * }} \n * />\n * );\n * }\n * ```\n */\nexport function FlyoWysiwyg({\n json,\n components = {},\n}: {\n json: WysiwygJson;\n components?: Record<string, React.ComponentType<{ node: WysiwygNode }>>;\n}) {\n let nodes: WysiwygNode[] = [];\n\n if (json) {\n if (Array.isArray(json)) {\n nodes = json;\n } else if ('type' in json && json.type === 'doc' && Array.isArray(json.content)) {\n nodes = json.content;\n } else {\n nodes = [json as WysiwygNode];\n }\n }\n\n return (\n <>\n {nodes.map((node: WysiwygNode, index: number) => {\n const Component = components[node.type];\n if (Component) {\n return <Component key={index} node={node} />;\n }\n \n const html = wysiwyg(node);\n return <div key={index} dangerouslySetInnerHTML={{ __html: html }} />;\n })}\n </>\n );\n}\n\n/**\n * Image loader for Flyo CDN that automatically handles image transformations.\n * Adds Flyo CDN host if not already present and applies width transformations.\n * \n * @param src - The image source URL (relative or absolute)\n * @param width - The desired width for the image\n * @returns Transformed image URL with Flyo CDN parameters\n * \n * @example\n * ```tsx\n * <Image\n * loader={FlyoCdnLoader}\n * src=\"me.png\"\n * alt=\"Picture\"\n * width={500}\n * height={500}\n * />\n * ```\n */\nexport function FlyoCdnLoader({ src, width }: ImageLoaderProps): string {\n let imageUrl = src;\n\n // If src doesn't contain the Flyo CDN host, prefix it\n if (!src.includes(FLYO_CDN_HOST)) {\n // Remove leading slash if present to avoid double slashes\n const cleanSrc = src.startsWith('/') ? src.slice(1) : src;\n imageUrl = `https://${FLYO_CDN_HOST}/${cleanSrc}`;\n }\n\n // Append Flyo CDN transformation parameters\n return `${imageUrl}/thumb/${width}xnull?format=webp`;\n}\n\n/**\n * FlyoMetric component for tracking entity metrics in production\n * \n * Automatically sends a metric tracking request to the Flyo API when:\n * - The environment is production (NODE_ENV === 'production')\n * - The entity has a metric API URL configured\n * \n * @param entity - The entity object containing entity_metric.api\n * \n * @example\n * ```tsx\n * import { FlyoMetric } from '@flyo/nitro-next/client';\n * \n * export default function BlogPost(props: RouteParams) {\n * return nitroEntityRoute(props, {\n * resolver,\n * render: (entity: Entity) => (\n * <>\n * <FlyoMetric entity={entity} />\n * <article>\n * <h1>{entity.entity?.entity_title}</h1>\n * </article>\n * </>\n * )\n * });\n * }\n * ```\n */\nexport function FlyoMetric({ entity }: { entity: Entity }) {\n useEffect(() => {\n // Only track metrics in production and if API URL is available\n if (isProd && entity?.entity?.entity_metric?.api) {\n fetch(entity.entity.entity_metric.api);\n }\n }, [entity]);\n\n // This component doesn't render anything\n return null;\n}\n"],"mappings":";;;AAEA,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB,SAAS,cAAa;AAyFzC;AArFT,IAAM,gBAAgB;AAKf,IAAM,SAAS,QAAQ,IAAI,aAAa;AAmBxC,SAAS,SAAS,OAA4C;AACnE,MAAI,OAAO,MAAM,QAAQ,YAAY,MAAM,IAAI,KAAK,MAAM,IAAI;AAC5D,WAAO,EAAE,iBAAiB,MAAM,IAAI;AAAA,EACtC;AACA,SAAO,CAAC;AACV;AAKO,SAAS,kBAAkB;AAAA,EAChC;AACF,GAEG;AACD,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,WAAO;AAEP,UAAM,UAAU,MAAM;AACpB,YAAM,WAAW,SAAS,iBAAiB,iBAAiB;AAC5D,eAAS,QAAQ,CAAC,OAAO;AACvB,cAAM,MAAM,GAAG,aAAa,eAAe;AAC3C,YAAI,OAAO,cAAc,aAAa;AACpC,4BAAkB,KAAK,EAAE;AAAA,QAC3B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ;AAER,UAAM,WAAW,IAAI,iBAAiB,CAAC,cAAc;AACnD,YAAM,qBAAqB,UAAU;AAAA,QAAK,cACxC,MAAM,KAAK,SAAS,UAAU,EAAE,KAAK,UAAQ;AAC3C,cAAI,KAAK,aAAa,KAAK,cAAc;AACvC,kBAAM,UAAU;AAChB,mBAAO,QAAQ,aAAa,eAAe,KACpC,QAAQ,cAAc,iBAAiB;AAAA,UAChD;AACA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAEA,UAAI,oBAAoB;AACtB,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAED,aAAS,QAAQ,SAAS,MAAM;AAAA,MAC9B,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC;AAED,WAAO,MAAM;AACX,eAAS,WAAW;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,gCAAG,UAAS;AACrB;AAsBO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,aAAa,CAAC;AAChB,GAGG;AACD,MAAI,QAAuB,CAAC;AAE5B,MAAI,MAAM;AACR,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,cAAQ;AAAA,IACV,WAAW,UAAU,QAAQ,KAAK,SAAS,SAAS,MAAM,QAAQ,KAAK,OAAO,GAAG;AAC/E,cAAQ,KAAK;AAAA,IACf,OAAO;AACL,cAAQ,CAAC,IAAmB;AAAA,IAC9B;AAAA,EACF;AAEA,SACE,gCACG,gBAAM,IAAI,CAAC,MAAmB,UAAkB;AAC/C,UAAM,YAAY,WAAW,KAAK,IAAI;AACtC,QAAI,WAAW;AACb,aAAO,oBAAC,aAAsB,QAAP,KAAmB;AAAA,IAC5C;AAEA,UAAM,OAAO,QAAQ,IAAI;AACzB,WAAO,oBAAC,SAAgB,yBAAyB,EAAE,QAAQ,KAAK,KAA/C,KAAkD;AAAA,EACrE,CAAC,GACH;AAEJ;AAqBO,SAAS,cAAc,EAAE,KAAK,MAAM,GAA6B;AACtE,MAAI,WAAW;AAGf,MAAI,CAAC,IAAI,SAAS,aAAa,GAAG;AAEhC,UAAM,WAAW,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AACtD,eAAW,WAAW,aAAa,IAAI,QAAQ;AAAA,EACjD;AAGA,SAAO,GAAG,QAAQ,UAAU,KAAK;AACnC;AA8BO,SAAS,WAAW,EAAE,OAAO,GAAuB;AACzD,YAAU,MAAM;AAEd,QAAI,UAAU,QAAQ,QAAQ,eAAe,KAAK;AAChD,YAAM,OAAO,OAAO,cAAc,GAAG;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\nexport interface WysiwygNode {\n type: string;\n content?: WysiwygNode[];\n [key: string]: unknown;\n}\n\n/**\n * Type for WYSIWYG JSON that can be a node, array of nodes, or doc structure\n */\nexport type WysiwygJson = WysiwygNode | WysiwygNode[] | { type: 'doc'; content: WysiwygNode[] };\n\n/**\n * Helper function to get editable props\n */\nexport function editable(block: Block): { 'data-flyo-uid'?: string } {\n if (typeof block.uid === 'string' && block.uid.trim() !== '') {\n return { 'data-flyo-uid': block.uid };\n }\n return {};\n}\n\n/**\n * Internal client component that sets up live editing functionality\n */\nexport function FlyoClientWrapper({ \n children,\n}: { \n children: React.ReactNode;\n}) {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n reload();\n\n const wireAll = () => {\n const elements = document.querySelectorAll('[data-flyo-uid]');\n elements.forEach((el) => {\n const uid = el.getAttribute('data-flyo-uid');\n if (uid && el instanceof HTMLElement) {\n highlightAndClick(uid, el);\n }\n });\n };\n\n wireAll();\n\n const observer = new MutationObserver((mutations) => {\n const hasRelevantChanges = mutations.some(mutation => \n Array.from(mutation.addedNodes).some(node => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n return element.hasAttribute('data-flyo-uid') || \n element.querySelector('[data-flyo-uid]');\n }\n return false;\n })\n );\n\n if (hasRelevantChanges) {\n wireAll();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n return <>{children}</>;\n}\n\n/**\n * WYSIWYG component for rendering ProseMirror/TipTap JSON content\n * \n * Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert\n * nodes to HTML. All consecutive non-custom nodes are joined into a single\n * HTML string so no extra wrapper `<div>` elements are added around each node.\n * \n * The component wraps all output in a single `<div>` with an optional\n * `className` (defaults to `\"wysiwyg\"`).\n * \n * @example\n * ```tsx\n * import { FlyoWysiwyg } from '@flyo/nitro-next/client';\n * import CustomImage from './CustomImage';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <FlyoWysiwyg \n * json={block.content.json} \n * className=\"wysiwyg\"\n * components={{\n * image: CustomImage\n * }} \n * />\n * );\n * }\n * ```\n */\nexport function FlyoWysiwyg({\n json,\n className = 'wysiwyg',\n components = {},\n}: {\n json: WysiwygJson;\n className?: string;\n components?: Record<string, React.ComponentType<{ node: WysiwygNode }>>;\n}) {\n let nodes: WysiwygNode[] = [];\n\n if (json) {\n if (Array.isArray(json)) {\n nodes = json;\n } else if ('type' in json && json.type === 'doc' && Array.isArray(json.content)) {\n nodes = json.content;\n } else {\n nodes = [json as WysiwygNode];\n }\n }\n\n // If no custom components are provided, render all nodes as a single HTML block\n const hasCustomComponents = nodes.some((node) => components[node.type]);\n\n if (!hasCustomComponents) {\n const html = nodes.map((node) => wysiwyg(node)).join('');\n return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />;\n }\n\n // When custom components are used, group consecutive non-custom nodes\n // into single HTML blocks to avoid extra wrapper elements.\n const groups: ({ type: 'custom'; component: React.ComponentType<{ node: WysiwygNode }>; node: WysiwygNode } | { type: 'html'; html: string })[] = [];\n\n for (const node of nodes) {\n const Component = components[node.type];\n if (Component) {\n groups.push({ type: 'custom', component: Component, node });\n } else {\n const html = wysiwyg(node);\n const last = groups[groups.length - 1];\n if (last && last.type === 'html') {\n last.html += html;\n } else {\n groups.push({ type: 'html', html });\n }\n }\n }\n\n return (\n <div className={className}>\n {groups.map((group, index) => {\n if (group.type === 'custom') {\n return <group.component key={index} node={group.node} />;\n }\n return <div key={index} dangerouslySetInnerHTML={{ __html: group.html }} />;\n })}\n </div>\n );\n}\n\n/**\n * Image loader for Flyo CDN that automatically handles image transformations.\n * Adds Flyo CDN host if not already present and applies width transformations.\n * \n * @param src - The image source URL (relative or absolute)\n * @param width - The desired width for the image\n * @returns Transformed image URL with Flyo CDN parameters\n * \n * @example\n * ```tsx\n * <Image\n * loader={FlyoCdnLoader}\n * src=\"me.png\"\n * alt=\"Picture\"\n * width={500}\n * height={500}\n * />\n * ```\n */\nexport function FlyoCdnLoader({ src, width }: ImageLoaderProps): string {\n let imageUrl = src;\n\n // If src doesn't contain the Flyo CDN host, prefix it\n if (!src.includes(FLYO_CDN_HOST)) {\n // Remove leading slash if present to avoid double slashes\n const cleanSrc = src.startsWith('/') ? src.slice(1) : src;\n imageUrl = `https://${FLYO_CDN_HOST}/${cleanSrc}`;\n }\n\n // Append Flyo CDN transformation parameters\n return `${imageUrl}/thumb/${width}xnull?format=webp`;\n}\n\n/**\n * FlyoMetric component for tracking entity metrics in production\n * \n * Automatically sends a metric tracking request to the Flyo API when:\n * - The environment is production (NODE_ENV === 'production')\n * - The entity has a metric API URL configured\n * \n * @param entity - The entity object containing entity_metric.api\n * \n * @example\n * ```tsx\n * import { FlyoMetric } from '@flyo/nitro-next/client';\n * \n * export default function BlogPost(props: RouteParams) {\n * return nitroEntityRoute(props, {\n * resolver,\n * render: (entity: Entity) => (\n * <>\n * <FlyoMetric entity={entity} />\n * <article>\n * <h1>{entity.entity?.entity_title}</h1>\n * </article>\n * </>\n * )\n * });\n * }\n * ```\n */\nexport function FlyoMetric({ entity }: { entity: Entity }) {\n useEffect(() => {\n // Only track metrics in production and if API URL is available\n if (isProd && entity?.entity?.entity_metric?.api) {\n fetch(entity.entity.entity_metric.api);\n }\n }, [entity]);\n\n // This component doesn't render anything\n return null;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flyo/nitro-next",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Connecting Flyo Headless Content Hub into your Next.js project.",
5
5
  "homepage": "https://dev.flyo.cloud/nitro",
6
6
  "keywords": [