generated from coulomb/repo-seed
docs: establish JavaScript-first architecture and refactoring plan
Clarified that testdrive-jsui is a JavaScript library with optional backend adapters, not a Python package with JavaScript assets. Added ARCHITECTURE.md: - Core principle: JavaScript provides all functionality - Python/Ruby/Java are integration adapters only - Clear layer separation: JS library vs language adapters - Distribution models: CDN, npm, with adapters - Anti-patterns to avoid - Success metrics Added JS_FIRST_REFACTORING.md: - Phase 1: Create standalone JavaScript bundle - Phase 2: Refactor Python as thin adapter - Phase 3: Build and distribution - Concrete implementation steps with code examples - Timeline: 2-4 days of focused work Key Changes in Approach: - JavaScript does ALL rendering (using marked.js) - Backend adapters only serve assets and pass config - No HTML generation in Python/Ruby/Java - Library works completely standalone in browser This establishes foundation for true language-agnostic design. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
411
ARCHITECTURE.md
Normal file
411
ARCHITECTURE.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# TestDrive-JSUI Architecture
|
||||
|
||||
**Last Updated**: 2025-12-16
|
||||
**Status**: Refactoring to JavaScript-first architecture
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Core Principle
|
||||
|
||||
**TestDrive-JSUI is a JavaScript library, not a Python package.**
|
||||
|
||||
The JavaScript code provides all functionality. Python, Ruby, Java, or other language bindings are **integration adapters** that help serve, configure, and test the JavaScript library - they do not provide core functionality.
|
||||
|
||||
---
|
||||
|
||||
## 📐 Architectural Layers
|
||||
|
||||
### Layer 1: Core JavaScript Library (Functionality)
|
||||
|
||||
**Location**: `/js/`
|
||||
**Purpose**: Complete standalone JavaScript UI framework
|
||||
**Dependencies**: Only `marked.js` (markdown parsing)
|
||||
**Language**: Pure JavaScript (ES6+)
|
||||
|
||||
```
|
||||
js/
|
||||
├── testdrive-jsui.js # Main library bundle (future)
|
||||
├── core/ # Core systems
|
||||
│ ├── debug-system.js # Debug infrastructure
|
||||
│ └── section-manager.js # Document section management
|
||||
├── components/ # UI components
|
||||
│ ├── dom-renderer.js # DOM rendering engine
|
||||
│ ├── debug-panel.js # Debug UI
|
||||
│ └── document-controls.js # Document control UI
|
||||
├── controls/ # Interactive control panels
|
||||
│ ├── control-base.js # Base control class
|
||||
│ ├── edit-control.js # Edit mode controls
|
||||
│ ├── debug-control.js # Debug controls
|
||||
│ ├── status-control.js # Status indicator
|
||||
│ └── contents-control.js # Table of contents
|
||||
├── widgets/ # Reusable UI widgets
|
||||
├── plugins/ # Extension system
|
||||
└── utils/ # Shared utilities
|
||||
```
|
||||
|
||||
**Key Characteristics**:
|
||||
- ✅ Works standalone in any browser
|
||||
- ✅ No backend required (can load from file://)
|
||||
- ✅ Self-contained markdown rendering
|
||||
- ✅ All UI logic in JavaScript
|
||||
- ✅ Configuration via JSON
|
||||
|
||||
**Standalone Usage**:
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="testdrive-jsui.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
|
||||
<!-- TestDrive-JSUI -->
|
||||
<script src="testdrive-jsui.js"></script>
|
||||
|
||||
<script>
|
||||
// Initialize with configuration
|
||||
const ui = new TestDriveJSUI({
|
||||
container: '#content',
|
||||
mode: 'edit',
|
||||
markdown: '# Hello World\n\nEdit me!',
|
||||
theme: 'github'
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Layer 2: Language Adapters (Integration)
|
||||
|
||||
**Purpose**: Integration helpers for different backend languages
|
||||
**Function**: Serve assets, provide testing, backend integration
|
||||
**NOT**: Core functionality implementation
|
||||
|
||||
#### Python Adapter
|
||||
|
||||
**Location**: `/src/testdrive_jsui/` (optional, for Python projects)
|
||||
**Purpose**: Python project integration
|
||||
|
||||
```python
|
||||
# Python is just a helper to serve/test the JS library
|
||||
from testdrive_jsui import TestDriveJSUIAdapter
|
||||
|
||||
adapter = TestDriveJSUIAdapter()
|
||||
|
||||
# Adapter helps with:
|
||||
# 1. Asset serving
|
||||
assets = adapter.get_asset_urls()
|
||||
|
||||
# 2. HTML template generation
|
||||
html = adapter.generate_html(
|
||||
markdown="# Content",
|
||||
mode="edit",
|
||||
config={...}
|
||||
)
|
||||
|
||||
# 3. Testing JavaScript
|
||||
test_result = adapter.run_js_tests()
|
||||
```
|
||||
|
||||
**Python Adapter Responsibilities**:
|
||||
- ✅ Asset path resolution
|
||||
- ✅ HTML template generation
|
||||
- ✅ Configuration serialization (Python dict → JSON)
|
||||
- ✅ JavaScript test runner integration (pytest ↔ Jest)
|
||||
- ✅ Development server (optional)
|
||||
- ❌ **NOT** markdown rendering (JS does this)
|
||||
- ❌ **NOT** UI logic (JS does this)
|
||||
- ❌ **NOT** content transformation (JS does this)
|
||||
|
||||
#### Ruby Adapter (Future)
|
||||
|
||||
```ruby
|
||||
# Similar concept for Ruby/Rails projects
|
||||
adapter = TestDriveJSUI::Adapter.new
|
||||
|
||||
# Generate view helper
|
||||
<%= testdrive_jsui_editor(
|
||||
markdown: @document.content,
|
||||
mode: :edit
|
||||
) %>
|
||||
```
|
||||
|
||||
#### Java Adapter (Future)
|
||||
|
||||
```java
|
||||
// Similar concept for Java/Spring projects
|
||||
TestDriveJSUIAdapter adapter = new TestDriveJSUIAdapter();
|
||||
String html = adapter.generateHtml(markdown, "edit", config);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Data Flow
|
||||
|
||||
### Standalone Mode (No Backend)
|
||||
|
||||
```
|
||||
1. Browser loads HTML
|
||||
2. HTML includes testdrive-jsui.js
|
||||
3. JS initializes with config
|
||||
4. JS fetches/parses markdown → marked.js
|
||||
5. JS renders UI → DOM
|
||||
6. User interacts → JS handles everything
|
||||
7. Save via JS (localStorage, download, etc.)
|
||||
```
|
||||
|
||||
### With Backend Adapter (Python/Ruby/Java)
|
||||
|
||||
```
|
||||
1. Backend serves HTML template
|
||||
2. Backend injects config JSON
|
||||
3. Backend includes JS/CSS assets
|
||||
4. Browser runs JavaScript (same as standalone)
|
||||
5. JS may call backend APIs for save/load
|
||||
```
|
||||
|
||||
**Key Point**: Backend is optional and only for convenience!
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Current vs Target Architecture
|
||||
|
||||
### Current (Hybrid - Needs Refactoring)
|
||||
|
||||
```
|
||||
❌ Python generates HTML structure
|
||||
❌ Python does markdown fallback rendering
|
||||
❌ JavaScript enhances the Python-generated HTML
|
||||
❌ Tight coupling between Python and JavaScript
|
||||
```
|
||||
|
||||
### Target (Clean Separation)
|
||||
|
||||
```
|
||||
✅ JavaScript is the complete UI library
|
||||
✅ JavaScript handles all rendering (markdown → HTML → DOM)
|
||||
✅ Python/Ruby/Java are thin adapters
|
||||
✅ Zero coupling - JS works without any backend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Distribution Models
|
||||
|
||||
### Model 1: CDN (Simplest)
|
||||
|
||||
```html
|
||||
<!-- Use directly from CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/testdrive-jsui@1.0.0/dist/testdrive-jsui.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/testdrive-jsui@1.0.0/dist/testdrive-jsui.min.css">
|
||||
```
|
||||
|
||||
### Model 2: npm Package
|
||||
|
||||
```bash
|
||||
npm install testdrive-jsui
|
||||
```
|
||||
|
||||
```javascript
|
||||
import TestDriveJSUI from 'testdrive-jsui';
|
||||
```
|
||||
|
||||
### Model 3: With Language Adapter
|
||||
|
||||
```bash
|
||||
# Python projects
|
||||
pip install testdrive-jsui-python
|
||||
|
||||
# Ruby projects
|
||||
gem install testdrive-jsui
|
||||
|
||||
# Java projects
|
||||
maven: com.testdrive:testdrive-jsui-java
|
||||
```
|
||||
|
||||
**Important**: Language adapters are separate packages that bundle/reference the core JS library.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Architecture
|
||||
|
||||
### JavaScript Tests (Core)
|
||||
|
||||
**Framework**: Jest with jsdom
|
||||
**Location**: `/js/tests/`
|
||||
**What**: Tests the JavaScript library itself
|
||||
|
||||
```javascript
|
||||
describe('TestDriveJSUI', () => {
|
||||
test('renders markdown to HTML', () => {
|
||||
const ui = new TestDriveJSUI({...});
|
||||
expect(ui.render('# Hello')).toContain('<h1>');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Python Integration Tests (Adapter)
|
||||
|
||||
**Framework**: pytest
|
||||
**Location**: `/tests/` (in Python adapter)
|
||||
**What**: Tests Python ↔ JavaScript integration
|
||||
|
||||
```python
|
||||
def test_python_adapter_runs_js_tests():
|
||||
adapter = TestDriveJSUIAdapter()
|
||||
result = adapter.run_js_tests()
|
||||
assert result.success
|
||||
```
|
||||
|
||||
**Key**: Python tests verify the adapter works, not the JS functionality!
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration System
|
||||
|
||||
### JavaScript Configuration (Core)
|
||||
|
||||
```javascript
|
||||
{
|
||||
// Core JS library config
|
||||
"mode": "edit", // 'edit' | 'view'
|
||||
"theme": "github", // CSS theme
|
||||
"markdown": "# Content", // Initial content
|
||||
"autosave": false, // Auto-save behavior
|
||||
"shortcuts": true, // Keyboard shortcuts
|
||||
"sections": true, // Section management
|
||||
"debug": false // Debug mode
|
||||
}
|
||||
```
|
||||
|
||||
### Adapter Configuration (Language-specific)
|
||||
|
||||
```python
|
||||
# Python adapter config (separate from JS)
|
||||
{
|
||||
"asset_base_url": "/static", # Where to serve JS/CSS
|
||||
"development_mode": True, # Dev vs production
|
||||
"template_path": "...", # HTML template location
|
||||
}
|
||||
```
|
||||
|
||||
**Separation**: JS config controls the library, adapter config controls integration.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Migration Path
|
||||
|
||||
### Phase 1: Improve Current Structure ✅ (Starting)
|
||||
|
||||
1. ✅ Document architecture (this file)
|
||||
2. ⏸️ Rename Python code to clearly show it's an adapter
|
||||
3. ⏸️ Ensure JS can work standalone
|
||||
4. ⏸️ Separate JS config from adapter config
|
||||
|
||||
### Phase 2: Pure JavaScript Rendering
|
||||
|
||||
1. ⏸️ Move all markdown rendering to JavaScript (use marked.js)
|
||||
2. ⏸️ Remove Python HTML generation
|
||||
3. ⏸️ Make Python adapter purely serve assets + config
|
||||
4. ⏸️ Create standalone HTML example
|
||||
|
||||
### Phase 3: Proper Distribution
|
||||
|
||||
1. ⏸️ Bundle JavaScript library (`testdrive-jsui.js`)
|
||||
2. ⏸️ Publish to npm
|
||||
3. ⏸️ Publish to CDN
|
||||
4. ⏸️ Separate Python adapter package
|
||||
|
||||
### Phase 4: Additional Adapters
|
||||
|
||||
1. ⏸️ Ruby adapter
|
||||
2. ⏸️ Java adapter
|
||||
3. ⏸️ PHP adapter (if needed)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── javascript/ # Core JS library docs
|
||||
│ ├── getting-started.md # Standalone usage
|
||||
│ ├── api-reference.md # JavaScript API
|
||||
│ ├── configuration.md # JS config options
|
||||
│ └── examples/ # Pure JS examples
|
||||
├── adapters/ # Language adapter docs
|
||||
│ ├── python/
|
||||
│ │ ├── installation.md
|
||||
│ │ ├── django-integration.md
|
||||
│ │ └── flask-integration.md
|
||||
│ ├── ruby/
|
||||
│ │ └── rails-integration.md
|
||||
│ └── java/
|
||||
│ └── spring-integration.md
|
||||
└── architecture/
|
||||
└── ARCHITECTURE.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Principles
|
||||
|
||||
1. **JavaScript First**: All functionality in JavaScript
|
||||
2. **Backend Optional**: Works without any backend
|
||||
3. **Adapters Not Implementations**: Language bindings help integrate, don't implement
|
||||
4. **Clear Boundaries**: JS does UI, backend does serving/storage
|
||||
5. **Language Agnostic**: Core library works with any backend
|
||||
6. **Zero Lock-in**: Use testdrive-jsui without any specific backend framework
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Anti-Patterns to Avoid
|
||||
|
||||
❌ **Don't**: Implement UI logic in Python/Ruby/Java
|
||||
✅ **Do**: Implement UI logic in JavaScript
|
||||
|
||||
❌ **Don't**: Render markdown in backend
|
||||
✅ **Do**: Render markdown in JavaScript (marked.js)
|
||||
|
||||
❌ **Don't**: Generate HTML structure in backend
|
||||
✅ **Do**: Generate HTML structure in JavaScript
|
||||
|
||||
❌ **Don't**: Make JavaScript depend on backend APIs
|
||||
✅ **Do**: Make JavaScript work standalone, optionally call APIs
|
||||
|
||||
❌ **Don't**: Create language-specific forks
|
||||
✅ **Do**: Create thin adapters around single JS library
|
||||
|
||||
---
|
||||
|
||||
## 📈 Success Metrics
|
||||
|
||||
- [ ] Can use testdrive-jsui with pure HTML file (no backend)
|
||||
- [ ] Can use testdrive-jsui with Python/Flask
|
||||
- [ ] Can use testdrive-jsui with Ruby/Rails
|
||||
- [ ] Can use testdrive-jsui with Java/Spring
|
||||
- [ ] JavaScript library < 100KB minified
|
||||
- [ ] Zero runtime dependencies except marked.js
|
||||
- [ ] Works in all modern browsers
|
||||
- [ ] Published to npm and CDN
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
When contributing:
|
||||
- Core functionality → JavaScript
|
||||
- Integration helpers → Language adapters
|
||||
- Keep adapters thin and similar across languages
|
||||
- Test JavaScript with Jest
|
||||
- Test adapters with language-native tools (pytest, rspec, junit)
|
||||
|
||||
---
|
||||
|
||||
**Remember**: TestDrive-JSUI is a JavaScript library with optional backend adapters, not a backend framework with JavaScript assets!
|
||||
709
JS_FIRST_REFACTORING.md
Normal file
709
JS_FIRST_REFACTORING.md
Normal file
@@ -0,0 +1,709 @@
|
||||
# JavaScript-First Refactoring Plan
|
||||
|
||||
**Goal**: Transform testdrive-jsui from a Python package with JS assets into a **pure JavaScript library** with optional backend adapters.
|
||||
|
||||
**Date**: 2025-12-16
|
||||
**Status**: Planning
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Vision
|
||||
|
||||
### Before (Current - Hybrid)
|
||||
```
|
||||
Python (TestDriveJSUIEngine)
|
||||
↓ generates HTML template
|
||||
↓ does fallback markdown rendering
|
||||
↓ injects configuration
|
||||
Browser receives: HTML with embedded JS config
|
||||
JavaScript: Enhances the pre-rendered HTML
|
||||
```
|
||||
|
||||
### After (Target - JS-First)
|
||||
```
|
||||
JavaScript Library (testdrive-jsui.js)
|
||||
↓ Standalone, works in any browser
|
||||
↓ Renders markdown using marked.js
|
||||
↓ Creates full UI
|
||||
↓ Handles all interactions
|
||||
|
||||
Optional: Python/Ruby/Java Adapter
|
||||
↓ Just serves JS/CSS files
|
||||
↓ Passes config as JSON
|
||||
↓ Provides testing helpers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Phase 1: Create Standalone JavaScript Bundle
|
||||
|
||||
### Goal
|
||||
Create a single `testdrive-jsui.js` file that works completely standalone in a browser.
|
||||
|
||||
### Tasks
|
||||
|
||||
#### 1.1: Create Main Library Entry Point
|
||||
|
||||
**Create**: `js/testdrive-jsui.js`
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* TestDrive-JSUI - Standalone JavaScript Markdown Editor
|
||||
*
|
||||
* A complete markdown editing and viewing UI that runs entirely in the browser.
|
||||
* No backend required!
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @requires marked.js (for markdown parsing)
|
||||
*/
|
||||
|
||||
class TestDriveJSUI {
|
||||
/**
|
||||
* Initialize TestDrive-JSUI
|
||||
*
|
||||
* @param {Object} config - Configuration options
|
||||
* @param {string|HTMLElement} config.container - Container element or selector
|
||||
* @param {string} config.mode - 'edit' or 'view'
|
||||
* @param {string} config.markdown - Initial markdown content
|
||||
* @param {string} config.theme - Theme name (default: 'github')
|
||||
* @param {boolean} config.autosave - Enable autosave (default: false)
|
||||
* @param {Object} config.callbacks - Event callbacks
|
||||
*/
|
||||
constructor(config) {
|
||||
this.config = this._validateConfig(config);
|
||||
this.container = this._resolveContainer(config.container);
|
||||
|
||||
// Initialize core systems
|
||||
this.debugSystem = new MarkitectDebugSystem();
|
||||
this.sectionManager = new SectionManager();
|
||||
this.domRenderer = new DOMRenderer(this.sectionManager, this.container);
|
||||
|
||||
// Initialize controls based on mode
|
||||
this._initializeControls();
|
||||
|
||||
// Load initial content
|
||||
if (config.markdown) {
|
||||
this.setContent(config.markdown);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set markdown content
|
||||
* @param {string} markdown - Markdown text
|
||||
*/
|
||||
setContent(markdown) {
|
||||
// Parse markdown to sections
|
||||
const sections = this.sectionManager.createSectionsFromMarkdown(markdown);
|
||||
|
||||
// Render to DOM
|
||||
this.domRenderer.renderAllSections(sections);
|
||||
|
||||
// Emit event
|
||||
this._emit('contentLoaded', { markdown, sections });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current markdown content
|
||||
* @returns {string} Current markdown
|
||||
*/
|
||||
getContent() {
|
||||
return this.sectionManager.getAllSectionsAsMarkdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch between edit and view modes
|
||||
* @param {string} mode - 'edit' or 'view'
|
||||
*/
|
||||
setMode(mode) {
|
||||
if (!['edit', 'view'].includes(mode)) {
|
||||
throw new Error(`Invalid mode: ${mode}`);
|
||||
}
|
||||
|
||||
this.config.mode = mode;
|
||||
this._reinitializeControls();
|
||||
this._emit('modeChanged', { mode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current content (triggers callback)
|
||||
*/
|
||||
save() {
|
||||
const markdown = this.getContent();
|
||||
|
||||
if (this.config.callbacks?.onSave) {
|
||||
this.config.callbacks.onSave(markdown);
|
||||
} else if (this.config.autosave) {
|
||||
// Default: save to localStorage
|
||||
localStorage.setItem('testdrive-jsui-content', markdown);
|
||||
}
|
||||
|
||||
this._emit('saved', { markdown });
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the editor and clean up
|
||||
*/
|
||||
destroy() {
|
||||
// Clean up controls
|
||||
this.controls?.forEach(control => control?.destroy?.());
|
||||
|
||||
// Clear container
|
||||
this.container.innerHTML = '';
|
||||
|
||||
this._emit('destroyed');
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
_validateConfig(config) {
|
||||
const defaults = {
|
||||
mode: 'edit',
|
||||
theme: 'github',
|
||||
autosave: false,
|
||||
shortcuts: true,
|
||||
sections: true,
|
||||
debug: false,
|
||||
callbacks: {}
|
||||
};
|
||||
|
||||
return { ...defaults, ...config };
|
||||
}
|
||||
|
||||
_resolveContainer(selector) {
|
||||
if (typeof selector === 'string') {
|
||||
const element = document.querySelector(selector);
|
||||
if (!element) {
|
||||
throw new Error(`Container not found: ${selector}`);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
_initializeControls() {
|
||||
this.controls = [];
|
||||
|
||||
if (this.config.mode === 'edit') {
|
||||
this.controls.push(
|
||||
new EditControl(this),
|
||||
new StatusControl(this),
|
||||
new ContentsControl(this)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.debug) {
|
||||
this.controls.push(new DebugControl(this));
|
||||
}
|
||||
|
||||
// Initialize all controls
|
||||
this.controls.forEach(control => control.initialize());
|
||||
}
|
||||
|
||||
_reinitializeControls() {
|
||||
// Destroy old controls
|
||||
this.controls?.forEach(control => control?.destroy?.());
|
||||
|
||||
// Create new controls for current mode
|
||||
this._initializeControls();
|
||||
}
|
||||
|
||||
_emit(event, data) {
|
||||
const callback = this.config.callbacks?.[`on${event.charAt(0).toUpperCase()}${event.slice(1)}`];
|
||||
if (callback) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
// Also emit as custom event
|
||||
this.container.dispatchEvent(new CustomEvent(`testdrive:${event}`, { detail: data }));
|
||||
}
|
||||
}
|
||||
|
||||
// Expose globally
|
||||
if (typeof window !== 'undefined') {
|
||||
window.TestDriveJSUI = TestDriveJSUI;
|
||||
}
|
||||
|
||||
// ES module export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = TestDriveJSUI;
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2: Create Standalone HTML Example
|
||||
|
||||
**Create**: `examples/standalone.html`
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TestDrive-JSUI Standalone Demo</title>
|
||||
|
||||
<!-- TestDrive-JSUI Styles -->
|
||||
<link rel="stylesheet" href="../static/css/editor.css">
|
||||
<link rel="stylesheet" href="../static/css/controls.css">
|
||||
<link rel="stylesheet" href="../static/css/themes/github.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="editor-container"></div>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
|
||||
<!-- TestDrive-JSUI Core Components -->
|
||||
<script src="../js/core/debug-system.js"></script>
|
||||
<script src="../js/core/section-manager.js"></script>
|
||||
<script src="../js/components/dom-renderer.js"></script>
|
||||
<script src="../js/components/debug-panel.js"></script>
|
||||
<script src="../js/controls/control-base.js"></script>
|
||||
<script src="../js/controls/edit-control.js"></script>
|
||||
<script src="../js/controls/debug-control.js"></script>
|
||||
<script src="../js/controls/status-control.js"></script>
|
||||
<script src="../js/controls/contents-control.js"></script>
|
||||
|
||||
<!-- Main library -->
|
||||
<script src="../js/testdrive-jsui.js"></script>
|
||||
|
||||
<!-- Initialize -->
|
||||
<script>
|
||||
// Example markdown content
|
||||
const sampleMarkdown = `# TestDrive-JSUI Standalone Demo
|
||||
|
||||
## Welcome!
|
||||
|
||||
This is a **completely standalone** markdown editor running entirely in your browser. No backend required!
|
||||
|
||||
### Features
|
||||
|
||||
- ✅ Real-time markdown rendering
|
||||
- ✅ Section-based editing
|
||||
- ✅ Interactive controls
|
||||
- ✅ Keyboard shortcuts
|
||||
- ✅ Debug mode
|
||||
|
||||
### Try It Out
|
||||
|
||||
1. Click the edit button on any section
|
||||
2. Modify the markdown
|
||||
3. Save your changes
|
||||
|
||||
## Code Example
|
||||
|
||||
\`\`\`javascript
|
||||
const editor = new TestDriveJSUI({
|
||||
container: '#editor-container',
|
||||
mode: 'edit',
|
||||
markdown: '# Hello World'
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
That's it! No backend needed.`;
|
||||
|
||||
// Initialize TestDrive-JSUI
|
||||
const editor = new TestDriveJSUI({
|
||||
container: '#editor-container',
|
||||
mode: 'edit',
|
||||
markdown: sampleMarkdown,
|
||||
theme: 'github',
|
||||
autosave: true,
|
||||
debug: true,
|
||||
callbacks: {
|
||||
onSave: (markdown) => {
|
||||
console.log('Content saved:', markdown);
|
||||
alert('Content saved to localStorage!');
|
||||
},
|
||||
onContentLoaded: (data) => {
|
||||
console.log('Content loaded:', data);
|
||||
},
|
||||
onModeChanged: (data) => {
|
||||
console.log('Mode changed to:', data.mode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for debugging
|
||||
window.editor = editor;
|
||||
|
||||
console.log('✅ TestDrive-JSUI initialized!');
|
||||
console.log('Try: editor.getContent(), editor.setMode("view"), editor.save()');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 1.3: Update Components to Work Standalone
|
||||
|
||||
**Tasks**:
|
||||
- Ensure all JS components work without Python-generated HTML
|
||||
- Remove any assumptions about server-side rendering
|
||||
- Make all components initialize from JavaScript
|
||||
|
||||
#### 1.4: Add Markdown Rendering Helper
|
||||
|
||||
**Update**: `js/utils/markdown-helper.js` (create if needed)
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Markdown rendering utilities
|
||||
* Uses marked.js for parsing
|
||||
*/
|
||||
const MarkdownHelper = {
|
||||
/**
|
||||
* Render markdown to HTML
|
||||
* @param {string} markdown - Markdown text
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
render(markdown) {
|
||||
if (!window.marked) {
|
||||
throw new Error('marked.js is required but not loaded');
|
||||
}
|
||||
|
||||
// Configure marked
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
headerIds: true,
|
||||
mangle: false
|
||||
});
|
||||
|
||||
return marked.parse(markdown);
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract sections from markdown
|
||||
* @param {string} markdown - Markdown text
|
||||
* @returns {Array} Array of section objects
|
||||
*/
|
||||
extractSections(markdown) {
|
||||
// Split on headers
|
||||
const sections = [];
|
||||
const lines = markdown.split('\n');
|
||||
let currentSection = { level: 0, title: '', content: '' };
|
||||
|
||||
for (const line of lines) {
|
||||
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
||||
|
||||
if (headerMatch) {
|
||||
// Save previous section
|
||||
if (currentSection.content) {
|
||||
sections.push(currentSection);
|
||||
}
|
||||
|
||||
// Start new section
|
||||
currentSection = {
|
||||
level: headerMatch[1].length,
|
||||
title: headerMatch[2],
|
||||
content: line + '\n'
|
||||
};
|
||||
} else {
|
||||
currentSection.content += line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Add final section
|
||||
if (currentSection.content) {
|
||||
sections.push(currentSection);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.MarkdownHelper = MarkdownHelper;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Phase 2: Refactor Python as Thin Adapter
|
||||
|
||||
### Goal
|
||||
Transform Python code from "rendering engine" to "integration adapter".
|
||||
|
||||
### Tasks
|
||||
|
||||
#### 2.1: Rename Python Package Structure
|
||||
|
||||
```
|
||||
src/testdrive_jsui/ # OLD NAME
|
||||
↓
|
||||
src/testdrive_jsui_python/ # NEW NAME (makes it clear it's an adapter)
|
||||
```
|
||||
|
||||
Or keep name but rename classes to show they're adapters:
|
||||
|
||||
```python
|
||||
# Old (implies it does rendering)
|
||||
class TestDriveJSUIEngine
|
||||
|
||||
# New (clear it's just an adapter)
|
||||
class TestDriveJSUIAdapter
|
||||
class PythonIntegrationAdapter
|
||||
```
|
||||
|
||||
#### 2.2: Simplify Python Adapter
|
||||
|
||||
**New**: `src/testdrive_jsui/adapter.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
Python Integration Adapter for TestDrive-JSUI
|
||||
|
||||
This is NOT the rendering engine - it's a helper for Python projects.
|
||||
The JavaScript library does all the actual work.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
import json
|
||||
|
||||
class TestDriveJSUIAdapter:
|
||||
"""
|
||||
Python integration adapter for TestDrive-JSUI JavaScript library.
|
||||
|
||||
Purpose:
|
||||
- Serve JS/CSS assets
|
||||
- Generate HTML templates with config
|
||||
- Provide testing helpers
|
||||
|
||||
Does NOT:
|
||||
- Render markdown (JS does this)
|
||||
- Implement UI (JS does this)
|
||||
- Transform content (JS does this)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.js_library_path = self._find_js_library()
|
||||
|
||||
def _find_js_library(self) -> Path:
|
||||
"""Locate the JavaScript library files."""
|
||||
# Look in capability directory
|
||||
current = Path(__file__).parent
|
||||
while current.parent != current:
|
||||
js_dir = current / "js"
|
||||
if js_dir.exists() and (js_dir / "testdrive-jsui.js").exists():
|
||||
return js_dir
|
||||
current = current.parent
|
||||
raise FileNotFoundError("testdrive-jsui.js not found")
|
||||
|
||||
def get_asset_urls(self, base_url: str = "/static") -> Dict[str, list]:
|
||||
"""
|
||||
Get URLs for JS/CSS assets.
|
||||
|
||||
Helper for web frameworks to include assets in templates.
|
||||
"""
|
||||
return {
|
||||
"css": [
|
||||
f"{base_url}/testdrive-jsui/css/editor.css",
|
||||
f"{base_url}/testdrive-jsui/css/controls.css",
|
||||
f"{base_url}/testdrive-jsui/css/themes/github.css",
|
||||
],
|
||||
"js": [
|
||||
"https://cdn.jsdelivr.net/npm/marked/marked.min.js",
|
||||
f"{base_url}/testdrive-jsui/js/testdrive-jsui.js",
|
||||
]
|
||||
}
|
||||
|
||||
def generate_html(self,
|
||||
markdown: str,
|
||||
mode: str = "edit",
|
||||
config: Optional[Dict] = None) -> str:
|
||||
"""
|
||||
Generate HTML that loads and initializes the JS library.
|
||||
|
||||
This is just a convenience helper - you can write your own template!
|
||||
"""
|
||||
config = config or {}
|
||||
config.update({
|
||||
"markdown": markdown,
|
||||
"mode": mode
|
||||
})
|
||||
|
||||
assets = self.get_asset_urls()
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>TestDrive-JSUI</title>
|
||||
{''.join(f'<link rel="stylesheet" href="{url}">' for url in assets['css'])}
|
||||
</head>
|
||||
<body>
|
||||
<div id="editor"></div>
|
||||
|
||||
{''.join(f'<script src="{url}"></script>' for url in assets['js'])}
|
||||
|
||||
<script>
|
||||
const editor = new TestDriveJSUI({json.dumps(config, indent=2)});
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
def run_js_tests(self):
|
||||
"""Run JavaScript tests (existing functionality - keep this!)."""
|
||||
from .testing import JavaScriptTestRunner
|
||||
runner = JavaScriptTestRunner(self.js_library_path.parent)
|
||||
return runner.run_js_tests()
|
||||
```
|
||||
|
||||
#### 2.3: Update Documentation
|
||||
|
||||
**Update**: README.md to show Python as optional
|
||||
|
||||
```markdown
|
||||
## Installation
|
||||
|
||||
### Option 1: Use JavaScript Directly (No Backend)
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/testdrive-jsui@1.0.0"></script>
|
||||
<script>
|
||||
const editor = new TestDriveJSUI({ ... });
|
||||
</script>
|
||||
```
|
||||
|
||||
### Option 2: With Python Adapter (for Django/Flask)
|
||||
|
||||
```bash
|
||||
pip install testdrive-jsui-python
|
||||
```
|
||||
|
||||
```python
|
||||
from testdrive_jsui import TestDriveJSUIAdapter
|
||||
adapter = TestDriveJSUIAdapter()
|
||||
html = adapter.generate_html(markdown="# Hello")
|
||||
```
|
||||
|
||||
### Option 3: With Ruby Adapter (for Rails) - Coming Soon
|
||||
|
||||
### Option 4: With Java Adapter (for Spring) - Coming Soon
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Phase 3: Build and Distribution
|
||||
|
||||
### Goal
|
||||
Package JavaScript library for easy distribution.
|
||||
|
||||
### Tasks
|
||||
|
||||
#### 3.1: Create Build Script
|
||||
|
||||
**Create**: `js/build.js`
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Build script for testdrive-jsui
|
||||
* Combines all modules into single distributable file
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Files to concatenate in order
|
||||
const files = [
|
||||
'utils/markdown-helper.js',
|
||||
'core/debug-system.js',
|
||||
'core/section-manager.js',
|
||||
'components/dom-renderer.js',
|
||||
'components/debug-panel.js',
|
||||
'controls/control-base.js',
|
||||
'controls/edit-control.js',
|
||||
'controls/debug-control.js',
|
||||
'controls/status-control.js',
|
||||
'controls/contents-control.js',
|
||||
'testdrive-jsui.js' // Main class last
|
||||
];
|
||||
|
||||
// Read and concatenate
|
||||
let bundle = '// TestDrive-JSUI v1.0.0 - MIT License\n';
|
||||
bundle += '(function() {\n';
|
||||
|
||||
for (const file of files) {
|
||||
const content = fs.readFileSync(path.join(__dirname, file), 'utf8');
|
||||
bundle += '\n// === ' + file + ' ===\n';
|
||||
bundle += content + '\n';
|
||||
}
|
||||
|
||||
bundle += '})();\n';
|
||||
|
||||
// Write bundle
|
||||
fs.writeFileSync(path.join(__dirname, 'dist', 'testdrive-jsui.js'), bundle);
|
||||
|
||||
console.log('✅ Built: dist/testdrive-jsui.js');
|
||||
```
|
||||
|
||||
#### 3.2: Create package.json for npm
|
||||
|
||||
**Update**: `package.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "testdrive-jsui",
|
||||
"version": "1.0.0",
|
||||
"description": "Standalone JavaScript markdown editor with interactive UI",
|
||||
"main": "js/dist/testdrive-jsui.js",
|
||||
"types": "js/testdrive-jsui.d.ts",
|
||||
"files": [
|
||||
"js/dist/",
|
||||
"static/css/",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "node js/build.js",
|
||||
"test": "jest",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"markdown", "editor", "javascript", "standalone", "ui"
|
||||
],
|
||||
"dependencies": {
|
||||
"marked": "^4.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Criteria
|
||||
|
||||
### Phase 1 Complete When:
|
||||
- [ ] Can open `standalone.html` in browser (no server needed)
|
||||
- [ ] Can edit markdown completely in JavaScript
|
||||
- [ ] All UI controls work without Python
|
||||
- [ ] marked.js renders markdown in browser
|
||||
|
||||
### Phase 2 Complete When:
|
||||
- [ ] Python adapter is < 200 lines of code
|
||||
- [ ] Python doesn't render markdown
|
||||
- [ ] Python doesn't generate HTML structure
|
||||
- [ ] Can use testdrive-jsui with Flask/Django via adapter
|
||||
|
||||
### Phase 3 Complete When:
|
||||
- [ ] Single `testdrive-jsui.js` file works everywhere
|
||||
- [ ] Published to npm
|
||||
- [ ] Available via CDN
|
||||
- [ ] < 100KB minified + gzipped
|
||||
|
||||
---
|
||||
|
||||
## 📅 Timeline Estimate
|
||||
|
||||
- **Phase 1**: 1-2 days (core JavaScript refactoring)
|
||||
- **Phase 2**: 1 day (simplify Python adapter)
|
||||
- **Phase 3**: 0.5 day (build and distribution)
|
||||
|
||||
**Total**: 2-4 days of focused work
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Priority Order
|
||||
|
||||
1. **Phase 1.2**: Create standalone.html example (proves concept)
|
||||
2. **Phase 1.4**: Markdown rendering in JS
|
||||
3. **Phase 1.1**: Main library class
|
||||
4. **Phase 2.2**: Simplify Python adapter
|
||||
5. **Phase 3**: Build and distribution
|
||||
|
||||
Start with #1 to validate the approach!
|
||||
Reference in New Issue
Block a user