@jotx-labs/react-renderer 2.2.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.
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GenericTiptapAdapter = void 0;
7
+ const registry_1 = require("@jotx/registry");
8
+ const core_1 = require("@tiptap/core");
9
+ const react_1 = require("@tiptap/react");
10
+ /**
11
+ * Generic Tiptap Adapter
12
+ * Automatically generates Tiptap node extensions from the Block Registry.
13
+ */
14
+ class GenericTiptapAdapter {
15
+ /**
16
+ * Generates a list of Tiptap Extensions based on the Registry
17
+ */
18
+ getExtensions() {
19
+ const extensions = [];
20
+ // 1. Core Blocks (Document, Text, Paragraph are standard Tiptap)
21
+ // We assume StarterKit handles these, or we explicitly define them if overridden.
22
+ // 2. Registry Blocks
23
+ const blocks = registry_1.registry.getAll();
24
+ // Skip standard Tiptap extensions that we handle manually or via StarterKit
25
+ // 'code' is a mark in Tiptap but might be a block in our registry or just a name collision.
26
+ // We must avoid defining a Node with the same name as an existing Mark.
27
+ const SKIP_LIST = [
28
+ 'doc', 'text', 'paragraph',
29
+ 'code', 'codeBlock', 'link', 'bold', 'italic', 'strike', 'underline',
30
+ 'taskItem', 'taskList', 'listItem', 'orderedList', 'bulletList',
31
+ 'heading', 'blockquote', 'hardBreak', 'horizontalRule',
32
+ 'image', 'video'
33
+ ];
34
+ blocks.forEach(def => {
35
+ if (SKIP_LIST.includes(def.type))
36
+ return;
37
+ const Ext = core_1.Node.create({
38
+ name: def.type,
39
+ group: 'block', // TODO: Handle inline vs block based on definition
40
+ content: def.syntax.hasChildren ? 'block+' : (def.syntax.hasText ? 'text*' : ''),
41
+ // Define attributes based on schema
42
+ addAttributes() {
43
+ const attrs = {};
44
+ if (def.schema && def.schema.properties) {
45
+ Object.entries(def.schema.properties).forEach(([key, prop]) => {
46
+ attrs[key] = {
47
+ default: prop.default,
48
+ // Keep the attribute in the HTML for serialization reference
49
+ keepOnSplit: false
50
+ };
51
+ });
52
+ }
53
+ return attrs;
54
+ },
55
+ parseHTML() {
56
+ return [
57
+ {
58
+ tag: `div[data-type="${def.type}"]`,
59
+ },
60
+ ];
61
+ },
62
+ renderHTML({ HTMLAttributes }) {
63
+ return ['div', (0, core_1.mergeAttributes)(HTMLAttributes, { 'data-type': def.type, 'class': `jotx-block ${def.type}` }), 0];
64
+ },
65
+ // React Node View
66
+ addNodeView() {
67
+ if (def.ui && def.ui.component) {
68
+ // We need a wrapper to handle the lazy load result or standard component
69
+ // This part is tricky because addNodeView expects a React component class/function directly usually
70
+ // But we can use ReactNodeViewRenderer to wrap it.
71
+ // Simplification: We assume the UI kit provides the component directly or via a specific loader
72
+ // For now, let's return a placeholder if not ready, or delegate to a DynamicNodeView
73
+ return (0, react_1.ReactNodeViewRenderer)(GenericNodeViewWrapper);
74
+ }
75
+ return null;
76
+ },
77
+ });
78
+ extensions.push(Ext);
79
+ });
80
+ return extensions;
81
+ }
82
+ }
83
+ exports.GenericTiptapAdapter = GenericTiptapAdapter;
84
+ // Temporary Wrapper until we implement the full React Component Loader
85
+ const react_2 = __importDefault(require("react"));
86
+ const react_3 = require("@tiptap/react");
87
+ const GenericNodeViewWrapper = (props) => {
88
+ const def = registry_1.registry.get(props.node.type.name);
89
+ const [Component, setComponent] = react_2.default.useState(null);
90
+ react_2.default.useEffect(() => {
91
+ if (def?.ui?.component) {
92
+ def.ui.component().then((mod) => {
93
+ // Handle default export vs named
94
+ setComponent(() => mod.default || mod);
95
+ }).catch((err) => {
96
+ console.error(`Failed to load component for ${def.type}`, err);
97
+ });
98
+ }
99
+ }, [def]);
100
+ if (!def)
101
+ return react_2.default.createElement("div", null,
102
+ "Unknown Block: ",
103
+ props.node.type.name,
104
+ " ");
105
+ return (react_2.default.createElement(react_3.NodeViewWrapper, { className: `jotx-node-view ${def.type}` },
106
+ Component ? (react_2.default.createElement(Component, { ...props.node.attrs })) : (react_2.default.createElement("div", { className: "loading-placeholder" },
107
+ "Loading ",
108
+ def.ui?.label || def.type,
109
+ "...")),
110
+ def.syntax.hasChildren && react_2.default.createElement(react_3.NodeViewContent, { className: "content" })));
111
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generic Tiptap Adapter
3
+ * Automatically generates Tiptap node extensions from the Block Registry.
4
+ */
5
+ export declare class GenericTiptapAdapter {
6
+ /**
7
+ * Generates a list of Tiptap Extensions based on the Registry
8
+ */
9
+ getExtensions(): any[];
10
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generic Tiptap Adapter
3
+ * Automatically generates Tiptap node extensions from the Block Registry.
4
+ */
5
+ export declare class GenericTiptapAdapter {
6
+ /**
7
+ * Generates a list of Tiptap Extensions based on the Registry
8
+ */
9
+ getExtensions(): any[];
10
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GenericTiptapAdapter = void 0;
7
+ const registry_1 = require("@jotx/registry");
8
+ const core_1 = require("@tiptap/core");
9
+ const react_1 = require("@tiptap/react");
10
+ /**
11
+ * Generic Tiptap Adapter
12
+ * Automatically generates Tiptap node extensions from the Block Registry.
13
+ */
14
+ class GenericTiptapAdapter {
15
+ /**
16
+ * Generates a list of Tiptap Extensions based on the Registry
17
+ */
18
+ getExtensions() {
19
+ const extensions = [];
20
+ // 1. Core Blocks (Document, Text, Paragraph are standard Tiptap)
21
+ // We assume StarterKit handles these, or we explicitly define them if overridden.
22
+ // 2. Registry Blocks
23
+ const blocks = registry_1.registry.getAll();
24
+ // Skip standard Tiptap extensions that we handle manually or via StarterKit
25
+ // 'code' is a mark in Tiptap but might be a block in our registry or just a name collision.
26
+ // We must avoid defining a Node with the same name as an existing Mark.
27
+ const SKIP_LIST = [
28
+ 'doc', 'text', 'paragraph',
29
+ 'code', 'codeBlock', 'link', 'bold', 'italic', 'strike', 'underline',
30
+ 'taskItem', 'taskList', 'listItem', 'orderedList', 'bulletList',
31
+ 'heading', 'blockquote', 'hardBreak', 'horizontalRule',
32
+ 'image', 'video'
33
+ ];
34
+ blocks.forEach(def => {
35
+ if (SKIP_LIST.includes(def.type))
36
+ return;
37
+ const Ext = core_1.Node.create({
38
+ name: def.type,
39
+ group: 'block', // TODO: Handle inline vs block based on definition
40
+ content: def.syntax.hasChildren ? 'block+' : (def.syntax.hasText ? 'text*' : ''),
41
+ // Define attributes based on schema
42
+ addAttributes() {
43
+ const attrs = {};
44
+ if (def.schema && def.schema.properties) {
45
+ Object.entries(def.schema.properties).forEach(([key, prop]) => {
46
+ attrs[key] = {
47
+ default: prop.default,
48
+ // Keep the attribute in the HTML for serialization reference
49
+ keepOnSplit: false
50
+ };
51
+ });
52
+ }
53
+ return attrs;
54
+ },
55
+ parseHTML() {
56
+ return [
57
+ {
58
+ tag: `div[data-type="${def.type}"]`,
59
+ },
60
+ ];
61
+ },
62
+ renderHTML({ HTMLAttributes }) {
63
+ return ['div', (0, core_1.mergeAttributes)(HTMLAttributes, { 'data-type': def.type, 'class': `jotx-block ${def.type}` }), 0];
64
+ },
65
+ // React Node View
66
+ addNodeView() {
67
+ if (def.ui && def.ui.component) {
68
+ // We need a wrapper to handle the lazy load result or standard component
69
+ // This part is tricky because addNodeView expects a React component class/function directly usually
70
+ // But we can use ReactNodeViewRenderer to wrap it.
71
+ // Simplification: We assume the UI kit provides the component directly or via a specific loader
72
+ // For now, let's return a placeholder if not ready, or delegate to a DynamicNodeView
73
+ return (0, react_1.ReactNodeViewRenderer)(GenericNodeViewWrapper);
74
+ }
75
+ return null;
76
+ },
77
+ });
78
+ extensions.push(Ext);
79
+ });
80
+ return extensions;
81
+ }
82
+ }
83
+ exports.GenericTiptapAdapter = GenericTiptapAdapter;
84
+ // Temporary Wrapper until we implement the full React Component Loader
85
+ const react_2 = __importDefault(require("react"));
86
+ const react_3 = require("@tiptap/react");
87
+ const GenericNodeViewWrapper = (props) => {
88
+ const def = registry_1.registry.get(props.node.type.name);
89
+ const [Component, setComponent] = react_2.default.useState(null);
90
+ react_2.default.useEffect(() => {
91
+ if (def?.ui?.component) {
92
+ def.ui.component().then((mod) => {
93
+ // Handle default export vs named
94
+ setComponent(() => mod.default || mod);
95
+ }).catch((err) => {
96
+ console.error(`Failed to load component for ${def.type}`, err);
97
+ });
98
+ }
99
+ }, [def]);
100
+ if (!def)
101
+ return react_2.default.createElement("div", null,
102
+ "Unknown Block: ",
103
+ props.node.type.name,
104
+ " ");
105
+ return (react_2.default.createElement(react_3.NodeViewWrapper, { className: `jotx-node-view ${def.type}` },
106
+ Component ? (react_2.default.createElement(Component, { ...props.node.attrs })) : (react_2.default.createElement("div", { className: "loading-placeholder" },
107
+ "Loading ",
108
+ def.ui?.label || def.type,
109
+ "...")),
110
+ def.syntax.hasChildren && react_2.default.createElement(react_3.NodeViewContent, { className: "content" })));
111
+ };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pluginLoader = exports.PluginLoader = void 0;
4
+ const registry_1 = require("@jotx/registry");
5
+ /**
6
+ * PluginLoader
7
+ * Handles validation and registration of external plugins.
8
+ */
9
+ class PluginLoader {
10
+ /**
11
+ * Load a plugin manifest into the registry
12
+ */
13
+ load(manifest) {
14
+ console.log(`🔌 Loading plugin: ${manifest.name} v${manifest.version}`);
15
+ manifest.contributes.forEach(def => {
16
+ this.validateDefinition(def);
17
+ registry_1.registry.register(def);
18
+ console.log(` ✅ Registered block: ${def.type}`);
19
+ });
20
+ }
21
+ /**
22
+ * Basic validation of block definition
23
+ */
24
+ validateDefinition(def) {
25
+ if (!def.type) {
26
+ throw new Error(`Block definition missing 'type'`);
27
+ }
28
+ if (!def.syntax) {
29
+ throw new Error(`Block ${def.type} missing 'syntax' definition`);
30
+ }
31
+ if (!def.schema) {
32
+ throw new Error(`Block ${def.type} missing 'schema' definition`);
33
+ }
34
+ // TODO: More rigorous schema validation (Zod?)
35
+ }
36
+ }
37
+ exports.PluginLoader = PluginLoader;
38
+ exports.pluginLoader = new PluginLoader();
@@ -0,0 +1,21 @@
1
+ import { BlockDefinition } from '@jotx/registry';
2
+ export interface PluginManifest {
3
+ name: string;
4
+ version: string;
5
+ contributes: BlockDefinition[];
6
+ }
7
+ /**
8
+ * PluginLoader
9
+ * Handles validation and registration of external plugins.
10
+ */
11
+ export declare class PluginLoader {
12
+ /**
13
+ * Load a plugin manifest into the registry
14
+ */
15
+ load(manifest: PluginManifest): void;
16
+ /**
17
+ * Basic validation of block definition
18
+ */
19
+ private validateDefinition;
20
+ }
21
+ export declare const pluginLoader: PluginLoader;
@@ -0,0 +1,21 @@
1
+ import { BlockDefinition } from '@jotx/registry';
2
+ export interface PluginManifest {
3
+ name: string;
4
+ version: string;
5
+ contributes: BlockDefinition[];
6
+ }
7
+ /**
8
+ * PluginLoader
9
+ * Handles validation and registration of external plugins.
10
+ */
11
+ export declare class PluginLoader {
12
+ /**
13
+ * Load a plugin manifest into the registry
14
+ */
15
+ load(manifest: PluginManifest): void;
16
+ /**
17
+ * Basic validation of block definition
18
+ */
19
+ private validateDefinition;
20
+ }
21
+ export declare const pluginLoader: PluginLoader;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pluginLoader = exports.PluginLoader = void 0;
4
+ const registry_1 = require("@jotx/registry");
5
+ /**
6
+ * PluginLoader
7
+ * Handles validation and registration of external plugins.
8
+ */
9
+ class PluginLoader {
10
+ /**
11
+ * Load a plugin manifest into the registry
12
+ */
13
+ load(manifest) {
14
+ console.log(`🔌 Loading plugin: ${manifest.name} v${manifest.version}`);
15
+ manifest.contributes.forEach(def => {
16
+ this.validateDefinition(def);
17
+ registry_1.registry.register(def);
18
+ console.log(` ✅ Registered block: ${def.type}`);
19
+ });
20
+ }
21
+ /**
22
+ * Basic validation of block definition
23
+ */
24
+ validateDefinition(def) {
25
+ if (!def.type) {
26
+ throw new Error(`Block definition missing 'type'`);
27
+ }
28
+ if (!def.syntax) {
29
+ throw new Error(`Block ${def.type} missing 'syntax' definition`);
30
+ }
31
+ if (!def.schema) {
32
+ throw new Error(`Block ${def.type} missing 'schema' definition`);
33
+ }
34
+ // TODO: More rigorous schema validation (Zod?)
35
+ }
36
+ }
37
+ exports.PluginLoader = PluginLoader;
38
+ exports.pluginLoader = new PluginLoader();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PluginLoader_1 = require("../PluginLoader");
4
+ const registry_1 = require("@jotx/registry");
5
+ describe('PluginLoader', () => {
6
+ beforeEach(() => {
7
+ registry_1.registry.clear();
8
+ });
9
+ it('should load blocks into the registry', () => {
10
+ // 1. Define a Mock Plugin
11
+ const mockPlugin = {
12
+ name: 'jotx-dummy-plugin',
13
+ version: '1.0.0',
14
+ contributes: [
15
+ {
16
+ type: 'youtube',
17
+ syntax: { keyword: 'youtube', hasChildren: false },
18
+ schema: {
19
+ properties: {
20
+ url: { type: 'string', required: true }
21
+ }
22
+ },
23
+ ui: {
24
+ label: 'YouTube Video'
25
+ }
26
+ }
27
+ ]
28
+ };
29
+ // 2. Load it
30
+ PluginLoader_1.pluginLoader.load(mockPlugin);
31
+ // 3. Verify Registry
32
+ const def = registry_1.registry.get('youtube');
33
+ expect(def).toBeDefined();
34
+ expect(def?.syntax.keyword).toBe('youtube');
35
+ });
36
+ });
File without changes
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ describe('Simple Test', () => {
3
+ it('should pass', () => {
4
+ expect(true).toBe(true);
5
+ });
6
+ });
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./GenericTiptapAdapter"), exports);
@@ -0,0 +1 @@
1
+ export * from './GenericTiptapAdapter';
@@ -0,0 +1 @@
1
+ export * from './GenericTiptapAdapter';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./GenericTiptapAdapter"), exports);
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PluginLoader_1 = require("./PluginLoader");
4
+ const registry_1 = require("@jotx/registry");
5
+ async function verify() {
6
+ console.log('🚀 Starting PluginLoader verification...');
7
+ registry_1.registry.clear();
8
+ // 1. Define a Mock Plugin
9
+ const mockPlugin = {
10
+ name: 'jotx-dummy-plugin',
11
+ version: '1.0.0',
12
+ contributes: [
13
+ {
14
+ type: 'youtube',
15
+ syntax: { keyword: 'youtube', hasChildren: false },
16
+ schema: {
17
+ properties: {
18
+ url: { type: 'string', required: true }
19
+ }
20
+ },
21
+ ui: {
22
+ label: 'YouTube Video'
23
+ }
24
+ }
25
+ ]
26
+ };
27
+ // 2. Load it
28
+ PluginLoader_1.pluginLoader.load(mockPlugin);
29
+ // 3. Verify Registry
30
+ const def = registry_1.registry.get('youtube');
31
+ if (def && def.syntax.keyword === 'youtube') {
32
+ console.log('✅ PluginLoader registered block successfully!');
33
+ }
34
+ else {
35
+ console.error('❌ PluginLoader failed to register block.');
36
+ process.exit(1);
37
+ }
38
+ }
39
+ verify().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PluginLoader_1 = require("./PluginLoader");
4
+ const registry_1 = require("@jotx/registry");
5
+ async function verify() {
6
+ console.log('🚀 Starting PluginLoader verification...');
7
+ registry_1.registry.clear();
8
+ // 1. Define a Mock Plugin
9
+ const mockPlugin = {
10
+ name: 'jotx-dummy-plugin',
11
+ version: '1.0.0',
12
+ contributes: [
13
+ {
14
+ type: 'youtube',
15
+ syntax: { keyword: 'youtube', hasChildren: false },
16
+ schema: {
17
+ properties: {
18
+ url: { type: 'string', required: true }
19
+ }
20
+ },
21
+ ui: {
22
+ label: 'YouTube Video'
23
+ }
24
+ }
25
+ ]
26
+ };
27
+ // 2. Load it
28
+ PluginLoader_1.pluginLoader.load(mockPlugin);
29
+ // 3. Verify Registry
30
+ const def = registry_1.registry.get('youtube');
31
+ if (def && def.syntax.keyword === 'youtube') {
32
+ console.log('✅ PluginLoader registered block successfully!');
33
+ }
34
+ else {
35
+ console.error('❌ PluginLoader failed to register block.');
36
+ process.exit(1);
37
+ }
38
+ }
39
+ verify().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@jotx-labs/react-renderer",
3
+ "version": "2.2.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "description": "Generic React renderer for jotx documents",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "watch": "tsc -w"
13
+ },
14
+ "keywords": [
15
+ "jotx",
16
+ "react",
17
+ "renderer"
18
+ ],
19
+ "author": "balajiboominathan",
20
+ "license": "Apache-2.0",
21
+ "peerDependencies": {
22
+ "react": "^18.0.0",
23
+ "react-dom": "^18.0.0"
24
+ },
25
+ "dependencies": {
26
+ "@jotx/core": "file:../core",
27
+ "@jotx/registry": "file:../registry",
28
+ "@tiptap/core": "^2.1.0",
29
+ "@tiptap/react": "^2.1.0",
30
+ "@tiptap/starter-kit": "^2.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/jest": "^30.0.0",
34
+ "@types/react": "^18.0.0",
35
+ "@types/react-dom": "^18.0.0",
36
+ "jest": "^30.2.0",
37
+ "jest-environment-jsdom": "^30.2.0",
38
+ "ts-jest": "^29.4.6",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.0.0"
41
+ }
42
+ }