mirror of
https://github.com/prodigist/ReactiveNotes.git
synced 2026-07-22 12:30:26 +00:00
Added Vault file system access/Updated README
This commit is contained in:
parent
2e92684a5e
commit
41ae4b0cd7
10 changed files with 237 additions and 82 deletions
204
README.md
204
README.md
|
|
@ -20,13 +20,6 @@ Transform your Obsidian vault into a reactive computational environment. Reactiv
|
|||
|
||||
- Obsidian v1.4.0 or higher
|
||||
- React v18.2.0
|
||||
- Libraries:
|
||||
- Recharts v2.10.0
|
||||
- Lightweight Charts v4.1.0
|
||||
- d3-force v3.0.0
|
||||
- lucide-react v0.263.1
|
||||
- shadcn/ui (latest components)
|
||||
|
||||
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
|
@ -109,46 +102,31 @@ export default MyComponent;
|
|||
|
||||
|
||||
|
||||
## 📚 Built-in Libraries & Components
|
||||
## 📚 Available Libraries & Components
|
||||
|
||||
### React Core & Hooks
|
||||
```javascript
|
||||
import React, { useState, useEffect, useRef, useMemo } from react';
|
||||
```
|
||||
### UI Components from shadcn/ui
|
||||
```javascript
|
||||
import {
|
||||
Card, CardHeader, CardTitle, CardContent,
|
||||
Tabs, TabsList, TabsTrigger,
|
||||
Button, Dialog, Form
|
||||
} from '@/components/ui';
|
||||
```
|
||||
### Visualization Libraries
|
||||
```javascript
|
||||
//Include Responsive containers
|
||||
// Recharts for data visualization
|
||||
import {
|
||||
LineChart, BarChart, PieChart,
|
||||
Line, Bar, Pie,
|
||||
Tooltip, Legend
|
||||
} from 'recharts';
|
||||
ReactiveNotes provides access to popular libraries and components directly in your notes:
|
||||
### Core Libraries
|
||||
```jsx
|
||||
// React and hooks
|
||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||
|
||||
// Lightweight Charts for financial charts
|
||||
// Data visualization
|
||||
import { LineChart, BarChart, PieChart } from 'recharts';
|
||||
import { createChart } from 'lightweight-charts';
|
||||
|
||||
// D3-force for network graphs
|
||||
import {
|
||||
forceSimulation,
|
||||
forceLink
|
||||
} from 'd3-force';
|
||||
```
|
||||
### Icons
|
||||
```javascript
|
||||
import {
|
||||
Upload, Activity, AlertCircle,
|
||||
TrendingUp, TrendingDown
|
||||
} from 'lucide-react';
|
||||
// Data processing
|
||||
import Papa from 'papaparse'; // CSV parsing
|
||||
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
|
||||
```
|
||||
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.
|
||||
|
||||
## 💻 Core Features
|
||||
|
||||
### 1. Component Systems
|
||||
|
|
@ -161,21 +139,20 @@ import {
|
|||
- Real-time component updates
|
||||
- Error reporting
|
||||
|
||||
### 3. Canvas Manipulation
|
||||
- Direct canvas access for custom drawing
|
||||
- Real-time canvas updates
|
||||
- Image processing capabilities
|
||||
- Custom overlays and annotations
|
||||
- Interactive drawing tools
|
||||
### 2. Canvas Manipulation
|
||||
- HTML Canvas API support for custom drawing
|
||||
- THREE.js integration for 3D graphics
|
||||
- Lightweight Charts for financial visualization
|
||||
- D3 force layout integration for network graphs
|
||||
- Support for both 2D and 3D rendering contexts
|
||||
|
||||
|
||||
|
||||
### 5. Performance
|
||||
### 3. Performance
|
||||
- Efficient re-rendering
|
||||
- Memory leak prevention
|
||||
- Proper cleanup of resources in useEffect hooks
|
||||
- Lazy loading support
|
||||
|
||||
### 2. State Management
|
||||
### 4. State Management
|
||||
```javascript
|
||||
// Local state with React
|
||||
const [local, setLocal] = useState(0);
|
||||
|
|
@ -189,7 +166,7 @@ const [stored, setStored] = useStorage('key', defaultValue);
|
|||
- Cross-note state management
|
||||
|
||||
### Note-Specific Data Persistence
|
||||
- Each note maintains independent state through frontmatter
|
||||
- Each note maintains independent state through frontmatter storage under a key called react_data
|
||||
- Component states persist across sessions
|
||||
- Data automatically travels with notes when shared
|
||||
- Multiple instances of same component can have different states
|
||||
|
|
@ -210,19 +187,97 @@ const Counter = () => {
|
|||
return <button onClick={() => setCount2(count2 + 1)}>Count: {count2}</button>;
|
||||
};
|
||||
```
|
||||
#### Browser LocalStorage
|
||||
|
||||
For components that need to share state across notes or maintain state independent of specific notes, browser localStorage is available.
|
||||
|
||||
### 3. File System Integration
|
||||
```javascript
|
||||
// Read files with encoding
|
||||
const text = await window.fs.readFile('data.txt', {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
// Create a localStorage hook
|
||||
const useLocalStorage = (key, defaultValue) => {
|
||||
const [state, setState] = useState(() => {
|
||||
try {
|
||||
const storedValue = localStorage.getItem(key);
|
||||
return storedValue ? JSON.parse(storedValue) : defaultValue;
|
||||
} catch (error) {
|
||||
console.error(`Error reading localStorage key "${key}":`, error);
|
||||
return defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Read binary files
|
||||
const binary = await window.fs.readFile('data.xlsx');
|
||||
useEffect(() => {
|
||||
localStorage.setItem(key, JSON.stringify(state));
|
||||
}, [key, state]);
|
||||
|
||||
return [state, setState];
|
||||
};
|
||||
|
||||
// Use it in components
|
||||
const [value, setValue] = useLocalStorage('shared-key', defaultValue);
|
||||
//This key is shared across all notes
|
||||
```
|
||||
|
||||
### 4. CDN Library Support
|
||||
Key features:
|
||||
- Data persists across browser sessions
|
||||
- Shared state across all notes
|
||||
- Independent of note content
|
||||
- Approximately 5-10MB storage per domain
|
||||
- Persists until browser data is cleared
|
||||
|
||||
### 5. File Reading Utility
|
||||
|
||||
The plugin provides a flexible file reading utility through the `readFile` function that helps you interactively select and read files from your vault.
|
||||
```javascript
|
||||
const fileData = await readFile( path,extensions);
|
||||
```
|
||||
### Parameters
|
||||
- `path` (optional): Direct path to a specific file. If provided, skips the file selector. Default: `null`
|
||||
- `extensions` (optional): Array of file extensions to filter by. Default: `['txt', 'md', 'json', 'csv']`
|
||||
|
||||
### Return Value
|
||||
|
||||
Returns a Promise that resolves to an object containing:
|
||||
|
||||
- `path`: Full file path
|
||||
- `name`: File name without extension
|
||||
- `extension`: File extension
|
||||
- `content`: String content of the file
|
||||
|
||||
Returns `null` if the operation is canceled or fails, allowing you to handle this case gracefully.
|
||||
|
||||
```javascript
|
||||
// Interactive file selection with file type filtering
|
||||
const textFile = await readFile(null,['txt', 'md']);
|
||||
if (textFile) {
|
||||
console.log(`Selected: ${textFile.name}`);
|
||||
console.log(`Content: ${textFile.content}`);
|
||||
}
|
||||
|
||||
// Direct file access by path (no modal)
|
||||
const jsonFile = await readFile('path/to/config.json');
|
||||
if (jsonFile) {
|
||||
const config = JSON.parse(jsonFile.content);
|
||||
}
|
||||
|
||||
// Read CSV files
|
||||
const csvFile = await readFile(null,['csv']);
|
||||
if (csvFile) {
|
||||
// Process CSV content
|
||||
const lines = csvFile.content.split('\n');
|
||||
const headers = lines[0].split(',');
|
||||
// ...
|
||||
}
|
||||
|
||||
// Read binary files like Excel spreadsheets
|
||||
const excelFile = await readFile(null,['xlsx', 'xls']);
|
||||
if (excelFile) {
|
||||
// excelFile.content contains the file data
|
||||
// Process with SheetJS or other libraries
|
||||
}
|
||||
```
|
||||
This utility is particularly useful for components that need to process files from your vault, such as data visualizations, document analysis, and custom importers.
|
||||
|
||||
|
||||
### 6. CDN Library Support
|
||||
Import additional libraries from cdnjs:
|
||||
```javascript
|
||||
import Chart from 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js';
|
||||
|
|
@ -233,24 +288,18 @@ Requirements:
|
|||
- Imports at component top
|
||||
- Browser-compatible libraries only
|
||||
|
||||
### 5. Theme Integration
|
||||
### 7. Theme Integration
|
||||
```javascript
|
||||
const theme = getTheme(); // Returns 'dark' or 'light'
|
||||
const theme = getTheme(); // Returns 'dark' or 'light' correspodning to obsidian theme
|
||||
|
||||
const styles = {
|
||||
background: theme === 'dark' ? 'var(--background-primary)' : 'white',
|
||||
color: theme === 'dark' ? 'var(--text-normal)' : 'black'
|
||||
};
|
||||
// Or use 'dark:' within your components
|
||||
```
|
||||
|
||||
|
||||
### File System Access
|
||||
```javascript
|
||||
// Read files
|
||||
const csv = await window.fs.readFile('data.csv', { encoding: 'utf8' });
|
||||
const binary = await window.fs.readFile('data.xlsx');
|
||||
```
|
||||
|
||||
## 🎨 Component Examples
|
||||
|
||||
### Interactive Data Visualization
|
||||
|
|
@ -366,6 +415,17 @@ const NetworkGraph = () => {
|
|||
**Tabbed UI**
|
||||

|
||||
|
||||
**Progression Dashboard**
|
||||
|
||||
Knowledge progression dashboard using persistant storage and ability to make new notes/categories
|
||||

|
||||
|
||||
**Integrated use case with MCP**
|
||||
|
||||
MCP fetches data from youtube and uses Reactive Notes to display react component for visualising fetched data.
|
||||
|
||||

|
||||
|
||||
## 🛠️Development Guide
|
||||
### Component Structure
|
||||
```react
|
||||
|
|
@ -515,8 +575,8 @@ Best practices for optimal performance:
|
|||
1. **File System**
|
||||
```javascript
|
||||
// File operations
|
||||
await window.fs.readFile(path, options);
|
||||
const text = await window.fs.readFile(path, { encoding: 'utf8' });
|
||||
let file = await readFile(path, extensions);
|
||||
let content=file.content;
|
||||
```
|
||||
|
||||
2. **Theme Management**
|
||||
|
|
|
|||
BIN
assets/IntegratedUsage.gif
Normal file
BIN
assets/IntegratedUsage.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 MiB |
BIN
assets/ProgressionDashboard.gif
Normal file
BIN
assets/ProgressionDashboard.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 MiB |
BIN
assets/gamingObsidian.gif
Normal file
BIN
assets/gamingObsidian.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 MiB |
98
main.tsx
98
main.tsx
|
|
@ -1,4 +1,4 @@
|
|||
import { Plugin, MarkdownPostProcessorContext, MarkdownRenderChild, MarkdownRenderer, TFile, App, stringifyYaml, Notice } from 'obsidian';
|
||||
import { Plugin, MarkdownPostProcessorContext, MarkdownRenderChild, MarkdownRenderer, TFile, App, stringifyYaml, Notice, SuggestModal } from 'obsidian';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { transform } from '@babel/standalone';
|
||||
import * as React from 'react';
|
||||
|
|
@ -312,7 +312,100 @@ 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<TFile> {
|
||||
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();
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Execute the code and get the component
|
||||
const Component = await new Function(
|
||||
|
|
@ -392,6 +485,7 @@ export default class ReactNotesPlugin extends Plugin {
|
|||
);
|
||||
// Register a global Markdown postprocessor for HTML
|
||||
this.registerMarkdownPostProcessor((element, context) => {
|
||||
// Only process elements that are within ReactiveNotes components
|
||||
if (element.closest('.react-component-container')) {
|
||||
this.parseMarkdownInHtml(element);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "reactive-notes",
|
||||
"name": "Reactive Notes",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"minAppVersion": "1.4.0",
|
||||
"description": "Transform your vault into a reactive computational environment. Create dynamic React components directly in your notes.",
|
||||
"author": "Elias Noesis",
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "reactive-notes",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "reactive-notes",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "Transform your vault into a reactive computational environment with React components.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// src/config/componentRegistry.ts
|
||||
//Can be modified to add more components and libraries
|
||||
// src/config/componentRegistry.tsx
|
||||
import React from 'react';
|
||||
import {
|
||||
PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend,
|
||||
|
|
@ -29,8 +28,8 @@ import {
|
|||
import { format, addDays, differenceInDays, parseISO } from 'date-fns';
|
||||
import { parse, stringify } from 'csv/sync';
|
||||
import * as mathjs from 'mathjs';
|
||||
|
||||
|
||||
import * as XLSX from 'xlsx';
|
||||
import Papa from 'papaparse';
|
||||
|
||||
// Core React hooks wrapper
|
||||
// Type for React utilities (non-component exports)
|
||||
|
|
@ -106,6 +105,8 @@ RadialBarChart, RadialBar,
|
|||
dateFns: { format, addDays, differenceInDays, parseISO },
|
||||
csv: { parse, stringify },
|
||||
math: mathjs,
|
||||
XLSX,
|
||||
Papa,
|
||||
} as const;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"1.0.1": "1.4.0"
|
||||
"1.0.2": "1.4.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue