From 0cb1867ac765f4972dd00c98d1e3d3467e45a58c Mon Sep 17 00:00:00 2001 From: Elias <36868565+Prodigist@users.noreply.github.com> Date: Sat, 1 Feb 2025 11:49:02 +0000 Subject: [PATCH] Partial clean up of code for public release, Improved modular scope import, allowing easier user addition to module imports, added README --- README.md | 308 +++++++++++++++++------ main.tsx | 21 +- src/components/componentRegistry.tsx | 133 ++++++++++ src/components/ui/card - Copy - Copy.tsx | 0 src/components/ui/index.tsx | 5 + 5 files changed, 383 insertions(+), 84 deletions(-) create mode 100644 src/components/componentRegistry.tsx delete mode 100644 src/components/ui/card - Copy - Copy.tsx create mode 100644 src/components/ui/index.tsx diff --git a/README.md b/README.md index c773152..7877a52 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,248 @@ -# Obsidian Sample Plugin +# ReactiveNotes - Dynamic React Components in Obsidian -This is a sample plugin for Obsidian (https://obsidian.md). +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Obsidian Downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%22reactive-notes%22%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json) -This project uses TypeScript to provide type checking and documentation. -The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does. +ReactiveNotes transforms your Obsidian notes into interactive documents by enabling full React component capabilities directly within your markdown files. -This sample plugin demonstrates some of the basic functionality the plugin API can do. -- Adds a ribbon icon, which shows a Notice when clicked. -- Adds a command "Open Sample Modal" which opens a Modal. -- Adds a plugin setting tab to the settings page. -- Registers a global click event and output 'click' to the console. -- Registers a global interval which logs 'setInterval' to the console. +## Core Capabilities -## First time developing plugins? +### 1. Interactive Components +- Write and use React components directly in markdown +- Full React 18 feature support including Hooks +- Real-time component updates +- TypeScript support +- Hot reloading during development -Quick starting guide for new plugin devs: +### 2. Data Visualization +Built-in libraries: +- **Recharts**: Full suite of chart components + - Line, Bar, Area, Pie charts + - Custom tooltips and legends + - Responsive containers +- **Lightweight Charts**: Professional-grade financial charts + - Candlestick charts + - Technical indicators + - Custom overlays -- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with. -- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it). -- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder. -- Install NodeJS, then run `npm i` in the command line under your repo folder. -- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`. -- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`. -- Reload Obsidian to load the new version of your plugin. -- Enable plugin in settings window. -- For updates to the Obsidian API run `npm update` in the command line under your repo folder. +### 3. Canvas Manipulation +- Direct canvas access for custom drawing +- Real-time canvas updates +- Image processing capabilities +- Custom overlays and annotations +- Interactive drawing tools -## Releasing new releases +### 4. Data Processing +- Real-time data manipulation +- CSV/Excel file processing +- Data transformation utilities +- Pattern recognition +- Statistical analysis -- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. -- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible. -- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases -- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release. -- Publish the release. +### 5. State Management +- Persistent storage between sessions +- State synchronization across components +- Frontmatter integration +- Cross-note state management -> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`. -> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json` +### 6. UI Components +Ready-to-use components from shadcn/ui: +- Cards +- Tabs +- Dialogs +- Forms +- Buttons +- Alerts -## Adding your plugin to the community plugin list +## Integration Features -- Check the [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines). -- Publish an initial version. -- Make sure you have a `README.md` file in the root of your repo. -- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin. - -## How to use - -- Clone this repo. -- Make sure your NodeJS is at least v16 (`node --version`). -- `npm i` or `yarn` to install dependencies. -- `npm run dev` to start compilation in watch mode. - -## Manually installing the plugin - -- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`. - -## Improve code quality with eslint (optional) -- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code. -- To use eslint with this project, make sure to install eslint from terminal: - - `npm install -g eslint` -- To use eslint to analyze this project use this command: - - `eslint main.ts` - - eslint will then create a report with suggestions for code improvement by file and line number. -- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: - - `eslint .\src\` - -## Funding URL - -You can include funding URLs where people who use your plugin can financially support it. - -The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file: - -```json -{ - "fundingUrl": "https://buymeacoffee.com" -} +### 1. File System Integration +```javascript +// Access and manipulate files +const fileContent = await window.fs.readFile('example.csv'); +const jsonData = await window.fs.readFile('data.json', { encoding: 'utf8' }); ``` -If you have multiple URLs, you can also do: +### 2. Theme Integration +```javascript +// Automatic theme detection +const theme = getTheme(); // 'dark' or 'light' -```json -{ - "fundingUrl": { - "Buy Me a Coffee": "https://buymeacoffee.com", - "GitHub Sponsor": "https://github.com/sponsors", - "Patreon": "https://www.patreon.com/" - } -} +// Theme-aware styling +const styles = { + background: theme === 'dark' ? '#1a1b1e' : '#ffffff', + color: theme === 'dark' ? '#ffffff' : '#000000' +}; ``` -## API Documentation +### 3. Data Persistence +```javascript +// Persistent storage hook +const [value, setValue] = useStorage('key', defaultValue); -See https://github.com/obsidianmd/obsidian-api +// Automatic state persistence +const [count, setCount] = useStorage('counter', 0); +``` + +## Component Types + +### 1. Visualization Components +```react +// Interactive data visualization +const DataVisualization = () => { + return ( + + + + + + + + ); +}; +``` + +### 2. Canvas Components +```react +// Interactive canvas usage +const CanvasComponent = () => { + const canvasRef = useRef(null); + + useEffect(() => { + const ctx = canvasRef.current.getContext('2d'); + // Custom drawing and animations + }, []); + + return ; +}; +``` + +### 3. Network Visualizations +```react +// Force-directed graphs +const NetworkGraph = () => { + return ( + + ); +}; +``` + +## Technical Features + +### 1. React Integration +- Full React 18 support +- Hooks support +- Error Boundaries +- Suspense support +- TypeScript enabled + +### 2. Performance +- Efficient re-rendering +- Automatic cleanup +- Memory leak prevention +- Resource management +- Lazy loading support + +### 3. Development Tools +- Hot reloading +- Error reporting +- Debug mode +- Performance monitoring + +## API Access + +### 1. Available Libraries +- React + Hooks +- Recharts +- Lightweight Charts +- d3-force +- Lucide icons +- shadcn/ui components + +### 2. Utility Functions +- File system operations +- Data processing +- Theme detection +- State persistence +- Math operations + +### 3. Component Registry +- Custom component registration +- Component sharing +- Dynamic loading +- Type safety + +## Best Practices + +### 1. Component Design +```react +const ResponsiveComponent = () => { + // Use relative units + return ( +
+ + {/* Component content */} + +
+ ); +}; +``` + +### 2. State Management +```react +const StatefulComponent = () => { + // Use persistent storage for important state + const [data, setData] = useStorage('data-key', initialData); + + // Use local state for UI-only state + const [isOpen, setIsOpen] = useState(false); +}; +``` + +### 3. Error Handling +```react +const SafeComponent = () => { + return ( + }> + {/* Component content */} + + ); +}; +``` + +## Limitations + +- No external package imports (use provided libraries) +- Limited to frontend functionality +- Storage is note-specific through frontmatter +- Component code must be self-contained + +## Future Plans + +- Additional visualization libraries +- Enhanced state management +- Component sharing mechanism +- Template system +- Enhanced debugging tools + +## Contributing + +Contributions welcome! Please read our [Contributing Guide](CONTRIBUTING.md). + +## Support + +- Report bugs through [GitHub Issues](https://github.com/YourUsername/ReactiveNotes/issues) +- Request features in [Discussions](https://github.com/YourUsername/ReactiveNotes/discussions) +- Join our [Discord community](your-discord-link) + +## License + +MIT License - see [LICENSE](LICENSE) for details + +--- + +

Built with ❤️ for the Obsidian community

\ No newline at end of file diff --git a/main.tsx b/main.tsx index 1b15010..5b761eb 100644 --- a/main.tsx +++ b/main.tsx @@ -130,6 +130,7 @@ class ReactComponentChild extends MarkdownRenderChild { code = code.replace(/export\s+const\s+/, 'const '); code = code.replace(/export\s+function\s+/, 'function '); code = code.replace(/export\s+class\s+/, 'class '); + // Remove type annotations //code = code.replace(/:\s*[A-Za-z<>[\]]+/g, ''); @@ -162,19 +163,25 @@ class ReactComponentChild extends MarkdownRenderChild { throw new Error('No React component found'); } + // Find component name and rename if it's 'Component' + const safeName = componentName === 'WrappedComponent' ? 'UserComponent' : componentName; + + if (componentName === 'WrappedComponent') { + code = code.replace(/\bComponent\b/, safeName); + } // Combine everything // added Component assignment code = ` ${chartWrapper} ${code} - const Component = (() => { + const WrappedComponent = (() => { // Add error handling for component existence - if (typeof ${componentName} === 'undefined') { - throw new Error(\`Component "${componentName}" was matched but is undefined. Code context: ${code.slice(0, 100)}...\`); + if (typeof ${safeName} === 'undefined') { + throw new Error(\`Component "${safeName}" was matched but is undefined. Code context: ${code.slice(0, 100)}...\`); } - const isChartComponent = ${componentName}.toString().includes('ResponsiveContainer') || - ${componentName}.toString().includes('svg'); - return isChartComponent ? withChartContainer(${componentName}) : ${componentName}; + const isChartComponent = ${safeName}.toString().includes('ResponsiveContainer') || + ${safeName}.toString().includes('svg'); + return isChartComponent ? withChartContainer(${safeName}) : ${safeName}; })(); `; @@ -182,7 +189,7 @@ class ReactComponentChild extends MarkdownRenderChild { return ` (async () => { ${code} - return Component; + return WrappedComponent; })() `; } diff --git a/src/components/componentRegistry.tsx b/src/components/componentRegistry.tsx new file mode 100644 index 0000000..bb654cb --- /dev/null +++ b/src/components/componentRegistry.tsx @@ -0,0 +1,133 @@ +// src/components/ComponentRenderer.tsx +import React, { useState, useEffect } from 'react'; +import { transform } from '@babel/standalone'; +import { ErrorBoundary } from './ErrorBoundary'; +import { App } from 'obsidian'; +import { useStorage } from '../hooks/useStorage'; +import { ComponentRegistry } from '../core/registry'; +import { StorageManager } from '../core/storage'; + +interface ComponentRendererProps { + code: string; + scopeId: string; + inline?: boolean; + storage: StorageManager; // Add storage prop + children?: React.ReactNode; +} + +interface TransformError { + message: string; + line?: number; + column?: number; +} + +const ErrorDisplay: React.FC<{ error: TransformError }> = ({ error }) => { + return ( +
+

Error in component:

+
+                {error.message}
+                {error.line && error.column && 
+                    `\nAt line ${error.line}, column ${error.column}`
+                }
+            
+
+ ); +}; + +export const ComponentRenderer: React.FC = ({ + code, + scopeId, + storage, // Get storage from props + inline = false +}) => { + const [Component, setComponent] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const createComponent = async () => { + try { + const transformedCode = transform(code, { + presets: ['env','react'], + plugins: [ + '@babel/plugin-syntax-jsx', // Add JSX syntax plugin explicitly + '@babel/plugin-transform-react-jsx', // Transforms JSX into JS + '@babel/plugin-proposal-class-properties', // Handles class properties + '@babel/plugin-proposal-object-rest-spread', // Handles object spread syntax + ], + filename: `component-${scopeId}`, + sourceType: 'module', + configFile: false, + babelrc: false + }).code; + console.log('Transformed Code:', transformedCode); + // Create storage hook bound to this storage instance + const boundUseStorage = (key: string, defaultValue: T) => + useStorage(key, defaultValue, storage); + + // Enhanced scope with registry and storage + const scope = { + React, + useState: React.useState, + useEffect: React.useEffect, + useRef: React.useRef, + useStorage: boundUseStorage, // Add bound storage hook + ...ComponentRegistry, + }; + + const componentFn = new Function( + ...Object.keys(scope), + `try { + ${transformedCode} + return Component; + } catch (err) { + console.error('Error in component execution:', err); + throw err; + }` + ); + + const ComponentType = componentFn(...Object.values(scope)); + setComponent(() => ComponentType); + setError(null); + } catch (err) { + console.error('Component transformation failed:', err); + setError({ + message: err instanceof Error ? err.message : 'Unknown error', + line: (err as any).loc?.line, + column: (err as any).loc?.column + }); + setComponent(null); + } + }; + + createComponent(); + }, [code, scopeId, storage]); // Add storage to dependencies + + const Wrapper: React.FC<{ children: React.ReactNode }> = + inline ? ({ children }) => ( + {children} + ) : ({ children }) => ( +
{children}
+ ); + + if (error) { + return ; + } + + if (!Component) { + return null; + } + + return ( + +
e.stopPropagation()} + > + + + +
+
+ ); +}; \ No newline at end of file diff --git a/src/components/ui/card - Copy - Copy.tsx b/src/components/ui/card - Copy - Copy.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/ui/index.tsx b/src/components/ui/index.tsx new file mode 100644 index 0000000..345b8ef --- /dev/null +++ b/src/components/ui/index.tsx @@ -0,0 +1,5 @@ +// src/components/ui/index.ts +export { Card, CardHeader, CardTitle, CardContent } from './card'; +export { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs'; +export { Switch } from './switch'; +// Add other component exports as needed \ No newline at end of file