diff --git a/README.md b/README.md index 4688d98..ebda8ec 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ export default Greeting; **Component Requirements:** - Must include a default export - Must be self-contained in one code block -- Component name should match default export +- Component ro Render must be the first component within codeblock ### Component Structure @@ -120,12 +120,30 @@ import * as XLSX from 'xlsx'; // Excel files import * as mathjs from 'mathjs'; // Mathematics import { format, parseISO } from 'date-fns'; // Date handling -// Interface components -import { Card, Tabs, Switch } from '@/components/ui'; -import { TrendingUp, Activity, Settings } from 'lucide-react'; // 70+ icons + // Interface components + import { Card, Tabs, Switch } from '@/components/ui'; + import { TrendingUp, Activity, Settings } from 'lucide-react'; // 70+ icons ``` -See the full [component reference](./COMPONENTS.md) for a complete list of available libraries, components and utilities. -Even if needed libraries are not provided within this list CDN imports are always an option. + See the full [component reference](./COMPONENTS.md) for a complete list of available libraries, components and utilities. + Even if needed libraries are not provided within this list CDN imports are always an option. + + ### Available in Component Scope + + All components have access to these objects and functions: + + ```javascript + // Note context + noteContext.frontmatter // All frontmatter properties (not just react_data) + noteContext.path // Full path to current note + noteContext.basename // Note filename without extension + + // Utilities + getTheme() // Returns 'dark' or 'light' + readFile(path, exts) // File reading utility + getFrontmatter(key?,defaultValue?,notePath?) // If properties are null returns entire frontmatter or current note if path not provided + vaultImport(path, index) // Preprocessor directive to include code from other notes (not a runtime function) + ``` + ## 💻 Core Features @@ -299,6 +317,47 @@ const styles = { // Or use 'dark:' within your components ``` +### 8. Vault Code Imports + +Import React code from other notes in your vault: + +```javascript +// Import code from another note +vaultImport('Components/SharedComponents.md', 0); // 0 = first code block +vaultImport('Hooks/CustomHooks.md', 1); // 1 = second code block + +const MyComponent = () => { + // Use imported components and hooks from these files + const data = useImportedHook(); + return ; +}; +``` + + +### 9. Definition Storage Blocks + +Not all react code blocks need to be valid React you can use React code blocks to store and organize utility functions, hooks, and other definitions: + +```javascript +// This displays as a definition storage block instead of rendering +const utilities = () => ({ + formatDate: (date) => date.toLocaleDateString(), + calculateSum: (arr) => arr.reduce((a, b) => a + b, 0), + parseData: (raw) => JSON.parse(raw) +}); + +export default utilities; +``` +![Object Definitions Codeblock](assets/codeStorageBox.gif) +When a code block returns an object instead of JSX, it automatically displays as a storage block showing all exported definitions. If you have multiple objects containing definitions only the first object will be previewed. +Perfect for: + +Utility function collections +Custom hook libraries +Configuration objects +Shared constants + +Import these definitions into other components using vaultImport(). ## 🎨 Component Examples @@ -426,6 +485,9 @@ Knowledge progression dashboard using persistant storage and ability to make new ![alt text](assets/IntegratedUsage.gif) +**Games utilising event listeners for key action mapping** +![alt text](assets/gamingObsidian.gif) + ## 🛠️Development Guide ### Component Structure ```react diff --git a/assets/codeStorageBox.gif b/assets/codeStorageBox.gif new file mode 100644 index 0000000..dfb2d5b Binary files /dev/null and b/assets/codeStorageBox.gif differ diff --git a/main.tsx b/main.tsx index 20fea6b..2730664 100644 --- a/main.tsx +++ b/main.tsx @@ -19,7 +19,7 @@ class ReactComponentChild extends MarkdownRenderChild { private noteFile: TFile; private ctx: MarkdownPostProcessorContext; private app: App; - + private processedPaths = new Set(); constructor(containerEl: HTMLElement, plugin: Plugin, ctx: MarkdownPostProcessorContext) { super(containerEl); @@ -50,13 +50,41 @@ class ReactComponentChild extends MarkdownRenderChild { } - private async getFrontmatterData(key: string, defaultValue: T): Promise { - if (!this.noteFile) return defaultValue; - - const cache = this.app.metadataCache.getFileCache(this.noteFile); - const frontmatter = cache?.frontmatter; - return frontmatter?.react_data?.[key] ?? defaultValue; - } + private async getFrontmatter( + key: string | null = null, + defaultValue: T | null = null, + notePath: string | null = null + ): Promise | null> { + try { + // Get the file - either current note or specified path + const file = notePath + ? this.app.vault.getAbstractFileByPath(notePath) + : this.noteFile; + + if (!file || !(file instanceof TFile)) { + console.error(`Invalid file: ${notePath}`); + return defaultValue; + } + + const cache = this.app.metadataCache.getFileCache(file); + const frontmatter = cache?.frontmatter; + + if (!frontmatter) { + return defaultValue; + } + + // Return specific key if requested + if (key) { + return frontmatter[key] ?? defaultValue; + } + + // Otherwise return entire frontmatter + return frontmatter; + } catch (error) { + console.error('Error getting frontmatter:', error); + return defaultValue; + } + } private async updateFrontmatterData(key: string, value: T): Promise { if (!this.noteFile) return; @@ -77,24 +105,63 @@ class ReactComponentChild extends MarkdownRenderChild { throw error; } } - - // Add wrapper component for error boundary private RenderWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { + React.useEffect(() => { + // Capture the original console.error when the component mounts + const originalConsoleError = console.error; + + // Override console.error + console.error = (...args: any[]) => { + const errorMessage = args[0]?.toString() || ''; + // Check if the error message is the one we want to suppress + if (errorMessage.includes('Objects are not valid as a React child')) { + // Suppress this specific error by not calling the original console.error + return; + } + // For all other errors, call the original console.error + originalConsoleError.apply(console, args); + }; + + // Cleanup function: Restore the original console.error when the component unmounts + return () => { + console.error = originalConsoleError; + }; + }, []); // Empty dependency array ensures this effect runs only once on mount and cleanup on unmount. + return ( ( -
-

Error in component:

-
-                            {error.message}
-                        
-
- )} + fallback={({ error }) => { + // The ErrorBoundary will catch the error and display a fallback UI. + // The console log for "Objects are not valid as a React child" should have been suppressed. + if (error.message?.includes('Objects are not valid as a React child')) { + const objectMatch = error.message.match(/found: object with keys {([^}]+)}/); + const keys = objectMatch ? objectMatch[1].split(',').map(k => k.trim()) : ['unknown']; + return ( +
+
+ 🧩 Component Rendered an Object +
+
A React component attempted to render a plain JavaScript object. Objects need to be mapped to renderable elements.
+
Object keys found:
+
    {keys.map(key =>
  • {key}
  • )}
+
+ ); + } + // Default error display for other types of errors + return ( +
+

Error in component:

+
{error.message}
+ {error.stack &&
Stack Trace
{error.stack}
} +
+ ); + }} > {children}
); }; + private needsThree(code: string): boolean { // Check if the code contains any Three.js specific imports or usage // More comprehensive detection of THREE usage @@ -123,7 +190,7 @@ class ReactComponentChild extends MarkdownRenderChild { return hasThreeImports || hasThreeUsage || commonThreeClasses; } - private preprocessCode(code: string): string { + private async preprocessCode(code: string): Promise { // Replace CDN imports with script loading code = code.replace( @@ -138,49 +205,58 @@ class ReactComponentChild extends MarkdownRenderChild { code = code.replace(/import\s*\([^)]*\);?\s*$/gm, ''); // Handle both JS/TS exports - code = code.replace(/export\s+default\s+/, ''); - code = code.replace(/export\s+const\s+/, 'const '); - code = code.replace(/export\s+function\s+/, 'function '); - code = code.replace(/export\s+class\s+/, 'class '); + code = code.replace(/export\s+default\s+/gm, ''); + code = code.replace(/export\s+const\s+/gm, 'const '); + code = code.replace(/export\s+function\s+/gm, 'function '); + code = code.replace(/export\s+class\s+/gm, 'class '); // Remove type annotations //code = code.replace(/:\s*[A-Za-z<>[\]]+/g, ''); //code = code.replace(/<[A-Za-z,\s]+>/g, ''); - // Create a HOC wrapper for chart components - const chartWrapper = ` - const withChartContainer = (WrappedComponent) => { - return function ChartContainer(props) { - const [mounted, setMounted] = React.useState(false); - - React.useEffect(() => { - const timer = setTimeout(() => setMounted(true), 100); - return () => clearTimeout(timer); - }, []); - - return React.createElement( - 'div', - { style: { width: '100%', minHeight: '400px' } }, - mounted ? React.createElement(WrappedComponent, props) : null - ); - }; - }; - `; + // Find the component name - const componentMatch = code.match(/(?:function\s+(\w+)|(?:const|class)\s+(\w+)\s*=\s*(?:(?:\([^)]*\)|)\s*=>|function\s*\(|React\.memo\(|React\.forwardRef\(|class\s+extends\s+React\.Component))/); - const componentName = componentMatch ? (componentMatch[1] || componentMatch[2]) : 'EmptyComponent'; - console.log('Found component:', componentName); // Debug info - if (!componentMatch) { - throw new Error('No React component found'); - } + const componentMatch = code.match(/^(?![\s]*\/\/).*?(?:function\s+(\w+)|const\s+(\w+)\s*=\s*(?:(?:\([^)]*\)|)\s*=>|function\s*\(|React\.memo\(|React\.forwardRef\(|class\s+extends\s+React\.Component))/m); + const storeMatch = !componentMatch ? code.match(/(?:const\s+(\w+)\s*=\s*{|class\s+(\w+)\s*{)/) : null; - // Find component name and rename if it's 'Component' - const safeName = componentName === 'WrappedComponent' ? 'UserComponent' : componentName; + if (!componentMatch) { + // If no component found, look for object literals or other exports + if (!storeMatch) { + throw new Error('No React component found'); + } + } + // Set safeName using either match + const componentName = componentMatch + ? (componentMatch[1] || componentMatch[2]) + : (storeMatch ? (storeMatch[1]|| storeMatch[2]) : 'EmptyComponent'); + console.log('Found component:', componentName); + + // Find component name and rename if it's 'WrappedComponent' + const safeName = componentName === 'WrappedComponent' ? 'UserComponent' : componentName; - if (componentName === 'WrappedComponent') { - code = code.replace(/\bComponent\b/, safeName); - } + if (componentName === 'WrappedComponent') { + code = code.replace(/\bWrappedComponent\b/, safeName); + } + // Create a HOC wrapper for chart components + const chartWrapper = ` + const withChartContainer = (WrappedComponent) => { + return function ChartContainer(props) { + const [mounted, setMounted] = React.useState(false); + + React.useEffect(() => { + const timer = setTimeout(() => setMounted(true), 100); + return () => clearTimeout(timer); + }, []); + + return React.createElement( + 'div', + { style: { width: '100%', minHeight: '400px' } }, + mounted ? React.createElement(WrappedComponent, props) : null + ); + }; + }; + `; // Combine everything // added Component assignment code = ` @@ -189,14 +265,122 @@ class ReactComponentChild extends MarkdownRenderChild { const WrappedComponent = (() => { // Add error handling for component existence if (typeof ${safeName} === 'undefined') { - throw new Error(\`Component "${safeName}" was matched but is undefined. Code context: ${code.slice(0, 100)}...\`); + throw new Error(\`Component "${safeName}" was matched but is undefined. Code context: ${code.slice(0, 100).replace(/`/g, '\\`')}...\`); } - const isChartComponent = ${safeName}.toString().includes('ResponsiveContainer') || + // Simple check - is it a valid React component? + const isValidComponent = (() => { + // Class component + if (${safeName}.prototype?.isReactComponent) return true; + + // Function that returns React elements + if (typeof ${safeName} === 'function') { + try { + const result = ${safeName}({}); + return React.isValidElement(result); + } catch { + // If it throws, check source code + return ${safeName}.toString().includes('createElement') || + ${safeName}.toString().includes('<'); + } + } + + // Direct objects are never valid components + return false; + })(); + + // If not a valid component, treat as a storage block + if (!isValidComponent) { + return function StorageBlockWrapper() { + // Get the definitions - handle all cases + let definitions; + + if (typeof ${safeName} === 'function') { + try { + // Try as function + definitions = ${safeName}({}); + } catch (e) { + try { + // Try as class + definitions = new ${safeName}(); + } catch (e2) { + // Last resort + definitions = ${safeName}; + } + } + } else { + // Already an object (like IIFE or object literal) + definitions = ${safeName}; + } + + // Ensure it's an object + if (!definitions || typeof definitions !== 'object') { + definitions = { error: "Couldn't extract definitions" }; + } + + const keys = Object.keys(definitions); + return React.createElement('div', { + style: { + padding: '12px', + backgroundColor: 'var(--background-modifier-form-field)', + borderRadius: '6px', + border: '1px solid var(--background-modifier-border)', + fontFamily: 'var(--font-monospace)' + } + }, [ + React.createElement('div', { + style: { + fontWeight: 'bold', + marginBottom: '8px', + display: 'flex', + alignItems: 'center', + gap: '8px' + } + }, [ + React.createElement('span', null, '📦'), + React.createElement('span', null, 'Definitions Storage Container:') + ]), + React.createElement('div', { + style: { + padding: '8px', + marginBottom: '12px', + color: 'var(--text-on-accent)', + borderRadius: '4px', + display: 'flex', + alignItems: 'center', + gap: '8px' + } + }, [ + React.createElement('code', { + style: { + padding: '2px 6px', + backgroundColor: 'rgba(255,255,255,0.2)', + borderRadius: '3px', + fontWeight: 'bold' + } + }, '${safeName}') + ]), + React.createElement('div', { + style: { fontSize: '0.9em', opacity: 0.8 } + }, 'Exported definitions:'), + React.createElement('ul', { + style: { + margin: '8px 0', + paddingLeft: '20px' + } + }, keys.map(key => + React.createElement('li', { key }, + \`\${key}: \${typeof definitions[key]}\` + ) + )) + ]); + }; + } + const isChartComponent = ${safeName}.toString().includes('ResponsiveContainer') || ${safeName}.toString().includes('svg'); - return isChartComponent ? withChartContainer(${safeName}) : ${safeName}; + return isChartComponent ? withChartContainer(${safeName}) : ${safeName}; })(); `; - + code = await this.processVaultImports(code); // Wrap code in async IIFE to allow for await return ` (async () => { @@ -222,6 +406,80 @@ private getThreeJs() { } } +private async processVaultImports(code: string, depth = 0): Promise { + if (depth > 10) { + console.warn('Maximum import depth reached (10)'); + return code; + } + const vaultImportRegex = /vaultImport\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*(\d+))?\s*\)/g; + + // Create a map to track processed imports (avoid duplicates) + const processedImports = new Map(); + + // Find all vaultImport calls + const matches = [...code.matchAll(vaultImportRegex)]; + + for (const match of matches) { + const [fullMatch, path, index = '0'] = match; + const importKey = `${path}#${index}`; + + // Skip if already processed + if (processedImports.has(importKey)) continue; + + try { + const file = this.app.vault.getAbstractFileByPath(path); + if (!file || !(file instanceof TFile)) continue; + + const content = await this.app.vault.read(file); + const blocks = content.match(/```react\n([\s\S]*?)```/g) || []; + + if (blocks[parseInt(index)]) { + let blockCode = blocks[parseInt(index)] + .replace(/```react\n/, '') + .replace(/```$/, ''); + + // Check for nested imports + const importKey = `${path}#${index}`; + if (!this.processedPaths.has(importKey)) { + this.processedPaths.add(importKey); + // Recursively process nested imports + blockCode = await this.processVaultImports(blockCode, depth + 1); + } + blockCode = blockCode.replace( + /import\s+(\w+)\s+from\s+['"]https:\/\/cdnjs\.cloudflare\.com\/([^'"]+)['"]/g, + (match, importName, cdnPath) => ` + const script = document.createElement('script'); + script.src = 'https://cdnjs.cloudflare.com/${cdnPath}'; + document.body.appendChild(script);`); + blockCode = blockCode.replace(/import\s+.*?['"]\s*;?\s*$/gm, '') + .replace(/import\s*{[^}]*}\s*from\s*['"][^'"]*['"];?\s*$/gm, '') + .replace(/import\s*\([^)]*\);?\s*$/gm, ''); + + // Strip exports + blockCode = blockCode + .replace(/export\s+default\s+/gm, '') + .replace(/export\s+const\s+/gm, 'const ') + .replace(/export\s+function\s+/gm, 'function ') + .replace(/export\s+class\s+/gm, 'class ') + .replace(/export\s+{[^}]*}\s*;?/gm, '') + + + const importedCode = `\n// Imported from ${path}#${index}\n${blockCode}\n`; + processedImports.set(importKey, importedCode); + } + } catch (error) { + console.error(`Error importing from ${path}:`, error); + } + } + + // Append all unique imports + const appendedCode = Array.from(processedImports.values()).join('\n'); + + // Remove ALL vaultImport calls in one pass + const cleanedCode = code.replace(vaultImportRegex, ''); + return appendedCode+cleanedCode; +} + async render(code: string) { console.time('component-render'); try { @@ -241,10 +499,10 @@ private getThreeJs() { console.time('preprocess'); // Preprocess code const needsThree=this.needsThree(code); - const processedCode = this.preprocessCode(code); - //console.log('Processed code:', processedCode); + const processedCode = await this.preprocessCode(code); console.timeEnd('preprocess'); + console.time('tailwind'); // Process Tailwind //Could potentially generate tailwind css based on specific user code using build:css script defined in config @@ -268,14 +526,12 @@ private getThreeJs() { }).code; console.timeEnd('babel'); //console.log('Transformed code:', transformedCode); - - // Create bound storage hook // Create bound storage hook with frontmatter support const boundUseStorage = (key: string, defaultValue: T) => { const [value, setValue] = React.useState(defaultValue); React.useEffect(() => { - this.getFrontmatterData(key, defaultValue).then(setValue); + this.getFrontmatter(key, defaultValue).then(setValue); }, [key]); const updateValue = React.useCallback( @@ -312,99 +568,13 @@ private getThreeJs() { }), //lazy load THREE if needed ...needsThree ? this.getThreeJs() : undefined, -readFile: async (path=null, extensions = ['txt', 'md', 'json','csv']) => { - return new Promise((resolve) => { - let isResolved = false; - let cancelTimeoutHandle: number | undefined = undefined; // To store timeout handle for cancellation - // Create a SuggestModal subclass that lists vault files - - // If a specific path is provided, read that file directly without showing modal - if (path) { - const file = this.app.vault.getAbstractFileByPath(path); - if (file && file instanceof TFile) { - this.app.vault.cachedRead(file).then(content => { - isResolved = true; - resolve({ - path: file.path, - name: file.name, - content: content - }); - }).catch(err => { - console.error("Failed to read file:", err); - isResolved = true; - resolve(null); - }); - } else { - console.error("File not found:", path); - isResolved = true; - resolve(null); - } - return; - } - class FileSuggestModal extends SuggestModal { - getSuggestions(query:string) { - // Get all files with the right extension - const files = this.app.vault.getFiles() - .filter(file => extensions.includes(file.extension)); - - // Filter by query if provided - if (query) { - return files.filter(file => - file.path.toLowerCase().includes(query.toLowerCase()) - ); - } - return files; - } - - renderSuggestion(file:TFile, el: HTMLElement) { - el.createEl("div", { text: file.name }); - el.createEl("small", { text: file.path }); - } - - onChooseSuggestion(file:TFile, evt: MouseEvent|KeyboardEvent) { - console.log("File content:"); - if (cancelTimeoutHandle !== undefined) { - clearTimeout(cancelTimeoutHandle); - cancelTimeoutHandle = undefined; - } - this.app.vault.cachedRead(file).then(content => {isResolved = true; - resolve({ - path: file.path, - name: file.basename, - extension: file.extension, - content: content - }); - }).catch(err => { - console.error("Failed to read file:", err); - resolve(null);});} - } - - // Show the modal - const modal = new FileSuggestModal(this.app); - modal.setPlaceholder("Select a file"); - modal.onClose = () => { - // If onClose is called, and a suggestion hasn't ALREADY been chosen and processed, - // this implies a cancellation or an edge case where onClose fires very quickly. - // We use a setTimeout to yield to the event loop, giving onChooseSuggestion - // a chance to run if it was triggered by the same user action that closed the modal. - if (cancelTimeoutHandle !== undefined) { // Clear previous timeout if any (e.g. rapid open/close) - clearTimeout(cancelTimeoutHandle); - } - cancelTimeoutHandle = window.setTimeout(() => { // window.setTimeout for NodeJS.Timeout type - // After yielding, if the promise hasn't been resolved by onChooseSuggestion - // and a suggestion wasn't flagged as chosen, then resolve as null (cancellation). - if (!isResolved) { - resolve(null); - } - // If suggestionChosen IS true here, it means onChooseSuggestion ran, - // set the flag, and it's responsible for resolving (or has already). - // If promiseResolved IS true, it's already handled. - }, 0); // Yield to the event loop. - }; - modal.open(); - - }); - } + readFile: async (path = null, extensions = ['txt', 'md', 'json', 'csv']) => { + return await this.readFile(this.app, path, extensions); + }, + // Frontmatter access utility + getFrontmatter: async (notePath = null, key = null, defaultValue = null) => { + return await this.getFrontmatter(notePath, key, defaultValue); + }, }; // Execute the code and get the component @@ -426,6 +596,102 @@ readFile: async (path=null, extensions = ['txt', 'md', 'json','csv']) => { console.timeEnd('component-render'); } + async readFile( + app: App, + path: string | null = null, + extensions: string[] = ['txt', 'md', 'json', 'csv'] + ): Promise<{ path: string; name: string; extension?: string; content: string } | null> { + return new Promise((resolve) => { + let isResolved = false; + let cancelTimeoutHandle: number | undefined = undefined; // To store timeout handle for cancellation + // Create a SuggestModal subclass that lists vault files + + // If a specific path is provided, read that file directly without showing modal + if (path) { + const file = this.app.vault.getAbstractFileByPath(path); + if (file && file instanceof TFile) { + this.app.vault.cachedRead(file).then(content => { + isResolved = true; + resolve({ + path: file.path, + name: file.name, + content: content + }); + }).catch(err => { + console.error("Failed to read file:", err); + isResolved = true; + resolve(null); + }); + } else { + console.error("File not found:", path); + isResolved = true; + resolve(null); + } + return; + } + class FileSuggestModal extends SuggestModal { + getSuggestions(query:string) { + // Get all files with the right extension + const files = this.app.vault.getFiles() + .filter(file => extensions.includes(file.extension)); + + // Filter by query if provided + if (query) { + return files.filter(file => + file.path.toLowerCase().includes(query.toLowerCase()) + ); + } + return files; + } + + renderSuggestion(file:TFile, el: HTMLElement) { + el.createEl("div", { text: file.name }); + el.createEl("small", { text: file.path }); + } + + onChooseSuggestion(file:TFile, evt: MouseEvent|KeyboardEvent) { + console.log("File content:"); + if (cancelTimeoutHandle !== undefined) { + clearTimeout(cancelTimeoutHandle); + cancelTimeoutHandle = undefined; + } + this.app.vault.cachedRead(file).then(content => {isResolved = true; + resolve({ + path: file.path, + name: file.basename, + extension: file.extension, + content: content + }); + }).catch(err => { + console.error("Failed to read file:", err); + resolve(null);});} + } + + // Show the modal + const modal = new FileSuggestModal(this.app); + modal.setPlaceholder("Select a file"); + modal.onClose = () => { + // We use a setTimeout to yield to the event loop, giving onChooseSuggestion + // a chance to run if it was triggered by the same user action that closed the modal. + if (cancelTimeoutHandle !== undefined) { // Clear previous timeout if any (e.g. rapid open/close) + clearTimeout(cancelTimeoutHandle); + } + cancelTimeoutHandle = window.setTimeout(() => { // window.setTimeout for NodeJS.Timeout type + // After yielding, if the promise hasn't been resolved by onChooseSuggestion + // and a suggestion wasn't flagged as chosen, then resolve as null (cancellation). + if (!isResolved) { + resolve(null); + } + // If suggestionChosen IS true here, it means onChooseSuggestion ran, + // set the flag, and it's responsible for resolving (or has already). + // If promiseResolved IS true, it's already handled. + }, 0); // Yield to the event loop. + }; + modal.open(); + + }); + + } onunload() { this.root.unmount(); diff --git a/manifest.json b/manifest.json index f7032fd..bdaf821 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "reactive-notes", "name": "Reactive Notes", - "version": "1.0.2", + "version": "1.0.3", "minAppVersion": "1.4.0", "description": "Transform your vault into a reactive computational environment. Create dynamic React components directly in your notes.", "author": "Elias Noesis", @@ -10,4 +10,4 @@ "fundingUrl": { "Buy Me A Coffee": "https://www.buymeacoffee.com/prodigist" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 24b7c0f..67d69f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "reactive-notes", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reactive-notes", - "version": "1.0.1", + "version": "1.0.3", "dependencies": { "@codemirror/language": "^6.10.6", "@emotion/css": "^11.13.5", diff --git a/package.json b/package.json index 05fcb7b..16d4777 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactive-notes", - "version": "1.0.2", + "version": "1.0.3", "description": "Transform your vault into a reactive computational environment with React components.", "main": "main.js", "scripts": { diff --git a/src/components/componentRegistry.tsx b/src/components/componentRegistry.tsx index a41d54e..feac808 100644 --- a/src/components/componentRegistry.tsx +++ b/src/components/componentRegistry.tsx @@ -15,7 +15,7 @@ import { Upload, Activity, AlertCircle, TrendingUp, TrendingDown, Home, BarChart2, BarChart3, Code, Settings, Moon, Sun, ChevronLeft, ChevronRight, Heart, ExternalLink, Play, Image as ImageIcon, Check, X, Plus, Minus, ArrowRightCircle, ArrowLeftCircle, ArrowUpCircle, ArrowDownCircle, ChevronUp, ChevronDown, ArrowRight, ArrowLeft, ArrowUp, ArrowDown, PlusCircle, MinusCircle, XCircle, CheckCircle, - Info, Search, Trash, Edit, Save, Share, Copy, Pause, Undo, Redo, ZoomIn, ZoomOut, PlayCircle, PauseCircle, + Info, Search, Trash, Edit, Save, Share, Copy, Pause, Undo, Redo, ZoomIn, ZoomOut, PlayCircle, PauseCircle, Clock, Calendar, } from 'lucide-react'; import * as LucideIcons from 'lucide-react'; import { @@ -99,7 +99,8 @@ RadialBarChart, RadialBar, ExternalLink, BarChart2, Play, ImageIcon, Check, X, Plus, Minus, ArrowRightCircle, ArrowLeftCircle, ArrowUpCircle, ArrowDownCircle, ChevronUp, ChevronDown, ArrowRight, ArrowLeft, ArrowUp, ArrowDown, PlusCircle, MinusCircle, XCircle, CheckCircle, - Info, Search, Trash, Edit, Save, Share, Copy, Pause, Undo, Redo, ZoomIn, ZoomOut, PlayCircle, PauseCircle, + Info, Search, Trash, Edit, Save, Share, Copy, Pause, Undo, Redo, ZoomIn, ZoomOut, PlayCircle, PauseCircle, Clock, Calendar, + LucideIcons, //data processing utilities dateFns: { format, addDays, differenceInDays, parseISO }, diff --git a/versions.json b/versions.json index 8a45d43..0e53289 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,4 @@ { - "1.0.2": "1.4.0" -} + "1.0.2": "1.4.0", + "1.0.3": "1.4.0" +} \ No newline at end of file