generated from coulomb/repo-seed
feat: implement JavaScript-first TestDriveJSUI library (v1.0.0)
Completed Phase 1 refactoring to JavaScript-first architecture: Core Library Implementation: - Created js/testdrive-jsui.js main library class - Integrated all existing components (SectionManager, DOMRenderer, DocumentControls) - Added marked.js integration for markdown rendering - Implemented event-driven API (on/off/emit) - Support for edit/view modes and themes - LocalStorage save/load functionality - Download as markdown file - Keyboard shortcuts (Ctrl+S, Escape) - Auto-save capability (optional) Examples: - examples/full-editor.html - Complete demo with all features - Updated examples/README.md with full documentation Documentation: - Updated README.md with JavaScript-first architecture section - Added complete API reference (constructor, methods, events) - Updated CLAUDE.md with library quick start and API - Emphasized JavaScript-first design principles Architecture: - JavaScript provides ALL functionality - Language adapters are optional integration helpers - Works standalone in browser (no backend required) - Clean separation: JS (functionality) vs Adapters (integration) This completes the architectural shift documented in ARCHITECTURE.md and JS_FIRST_REFACTORING.md Phase 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
115
CLAUDE.md
115
CLAUDE.md
@@ -4,10 +4,70 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
TestDrive-JSUI is a standalone JavaScript UI rendering engine and testing framework. It's designed as an independent, reusable capability that provides:
|
**TestDrive-JSUI is a JavaScript-first markdown editor library.**
|
||||||
- Interactive JavaScript UI for markdown editing/viewing
|
|
||||||
- Python-JavaScript test integration bridge
|
This is a pure JavaScript library for interactive markdown editing that works standalone in any browser. Language adapters (Python, Ruby, Java) are **optional integration helpers** - they do NOT provide core functionality.
|
||||||
- Self-contained plugin architecture with no hardcoded paths
|
|
||||||
|
**Key Facts**:
|
||||||
|
- **Core:** Complete JavaScript library (`js/testdrive-jsui.js`)
|
||||||
|
- **Dependencies:** Only marked.js for markdown parsing
|
||||||
|
- **Standalone:** Works without any backend (can run from file://)
|
||||||
|
- **Optional Adapters:** Python/Ruby/Java adapters help with asset serving and backend integration
|
||||||
|
- **Architecture:** JavaScript-first design (see `ARCHITECTURE.md`)
|
||||||
|
|
||||||
|
## JavaScript Library
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<script src="js/testdrive-jsui.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="editor"></div>
|
||||||
|
<script>
|
||||||
|
const editor = new TestDriveJSUI({
|
||||||
|
container: '#editor',
|
||||||
|
markdown: '# Hello World\n\nEdit me!',
|
||||||
|
mode: 'edit',
|
||||||
|
theme: 'github'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
**Constructor Options:**
|
||||||
|
- `container` (required): CSS selector or DOM element
|
||||||
|
- `markdown`: Initial content
|
||||||
|
- `mode`: 'edit' or 'view' (default: 'edit')
|
||||||
|
- `theme`: Theme name (default: 'github')
|
||||||
|
- `autosave`: Auto-save enabled (default: false)
|
||||||
|
- `shortcuts`: Keyboard shortcuts (default: true)
|
||||||
|
- `sections`: Section-based editing (default: true)
|
||||||
|
- `debug`: Debug mode (default: false)
|
||||||
|
|
||||||
|
**Methods:**
|
||||||
|
- `getMarkdown()`: Get current content
|
||||||
|
- `setMarkdown(markdown)`: Set content
|
||||||
|
- `getStatus()`: Get editor status
|
||||||
|
- `save()`: Trigger save event
|
||||||
|
- `download(filename)`: Download as .md file
|
||||||
|
- `on(event, callback)`: Listen to events
|
||||||
|
- `destroy()`: Clean up
|
||||||
|
|
||||||
|
**Events:** `initialized`, `save`, `content-changed`, `autosave`, `download`, `reset`, `destroyed`
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
See `/examples`:
|
||||||
|
- `standalone.html` - Minimal proof of concept
|
||||||
|
- `full-editor.html` - Complete demo with all features
|
||||||
|
|
||||||
## Migration Status
|
## Migration Status
|
||||||
|
|
||||||
@@ -88,38 +148,55 @@ make testdrive-jsui-clean
|
|||||||
### Directory Structure
|
### Directory Structure
|
||||||
```
|
```
|
||||||
testdrive-jsui/
|
testdrive-jsui/
|
||||||
├── src/testdrive_jsui/ # Python package
|
├── js/ # JavaScript library (Core functionality)
|
||||||
│ ├── core/ # Core framework components
|
│ ├── testdrive-jsui.js # 🎯 Main library entry point
|
||||||
│ ├── components/ # UI component helpers
|
|
||||||
│ ├── utils/ # Utility functions
|
|
||||||
│ └── testing/ # Python-JS bridge
|
|
||||||
│ ├── js_test_runner.py # JavaScript test execution
|
|
||||||
│ └── integration.py # Pytest integration
|
|
||||||
├── js/ # JavaScript source (consolidated)
|
|
||||||
│ ├── core/ # Core JS: debug-system, section-manager
|
│ ├── core/ # Core JS: debug-system, section-manager
|
||||||
│ ├── components/ # UI components: dom-renderer, debug-panel
|
│ ├── components/ # UI components: dom-renderer, debug-panel, document-controls
|
||||||
│ ├── controls/ # Control panels: edit, debug, status, contents
|
│ ├── controls/ # Control panels: edit, debug, status, contents
|
||||||
│ ├── plugins/ # JS plugins
|
│ ├── plugins/ # JS plugins
|
||||||
│ ├── widgets/ # UI widgets
|
│ ├── widgets/ # UI widgets
|
||||||
│ ├── utils/ # JS utilities
|
│ ├── utils/ # JS utilities
|
||||||
│ ├── tests/ # JavaScript tests
|
│ ├── tests/ # JavaScript tests (Jest)
|
||||||
│ ├── config-loader.js # Configuration loader
|
│ ├── config-loader.js # Configuration loader
|
||||||
│ ├── main.js # Original main entry point
|
│ ├── main.js # Legacy main entry point
|
||||||
│ └── main-updated.js # Refactored main entry
|
│ └── main-updated.js # Refactored main entry
|
||||||
|
├── examples/ # 📚 Working examples
|
||||||
|
│ ├── standalone.html # Minimal proof of concept
|
||||||
|
│ └── full-editor.html # Complete demo
|
||||||
|
├── src/testdrive_jsui/ # Python adapter (optional)
|
||||||
|
│ ├── core/ # Adapter core
|
||||||
|
│ ├── components/ # Adapter helpers
|
||||||
|
│ ├── utils/ # Utilities
|
||||||
|
│ └── testing/ # Python-JS bridge
|
||||||
|
│ ├── js_test_runner.py # JavaScript test execution
|
||||||
|
│ └── integration.py # Pytest integration
|
||||||
├── static/ # Static assets
|
├── static/ # Static assets
|
||||||
│ ├── css/ # Stylesheets (editor, controls, themes)
|
│ ├── css/ # Stylesheets (editor, controls, themes)
|
||||||
│ ├── images/ # Image assets and icons
|
│ ├── images/ # Image assets and icons
|
||||||
│ └── templates/ # HTML templates (index.html)
|
│ └── templates/ # HTML templates
|
||||||
└── tests/ # Python tests
|
├── tests/ # Python tests
|
||||||
|
├── docs/ # Documentation
|
||||||
|
│ ├── prototypes/ # Archived HTML prototypes
|
||||||
|
│ └── migration/ # Migration records
|
||||||
|
├── ARCHITECTURE.md # JavaScript-first architecture details
|
||||||
|
└── README.md # Main documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
### Key Design Principles
|
### Key Design Principles
|
||||||
|
|
||||||
|
**JavaScript-First Architecture** (NEW in v1.0.0)
|
||||||
|
- **Core functionality in JavaScript**: All UI logic, rendering, and editing in JS
|
||||||
|
- **Standalone capable**: Works without any backend (can run from file://)
|
||||||
|
- **marked.js only dependency**: For markdown parsing
|
||||||
|
- **Language adapters optional**: Python/Ruby/Java are integration helpers, not functionality providers
|
||||||
|
- **Event-driven API**: Clean event system for integration
|
||||||
|
- **See `ARCHITECTURE.md`** for complete architectural documentation
|
||||||
|
|
||||||
**Plugin Independence**
|
**Plugin Independence**
|
||||||
- **Self-declaring**: Plugin declares its own location - no hardcoded paths needed
|
- **Self-declaring**: Plugin declares its own location - no hardcoded paths needed
|
||||||
- **Single source of truth**: All assets in one capability directory
|
- **Single source of truth**: All assets in one capability directory
|
||||||
- **Clean boundaries**: JSON config interface between Python and JavaScript
|
- **Clean boundaries**: JSON config interface between adapters and JavaScript
|
||||||
- **No code mixing**: JavaScript stays in `.js` files, never embedded in Python strings
|
- **No code mixing**: JavaScript stays in `.js` files, never embedded in strings
|
||||||
- Works regardless of installation location
|
- Works regardless of installation location
|
||||||
|
|
||||||
**Testing Architecture**
|
**Testing Architecture**
|
||||||
|
|||||||
138
README.md
138
README.md
@@ -1,18 +1,131 @@
|
|||||||
# TestDrive-JSUI
|
# TestDrive-JSUI
|
||||||
|
|
||||||
A standalone JavaScript UI rendering engine and testing framework. Originally developed for MarkiTect, TestDrive-JSUI is designed as an independent, reusable capability that can be integrated into any Python project.
|
**A JavaScript-first markdown editor and UI library.**
|
||||||
|
|
||||||
|
TestDrive-JSUI is a pure JavaScript library for interactive markdown editing with section-based management. Language adapters (Python, Ruby, Java) are optional integration helpers for serving assets and backend integration - they do not provide core functionality.
|
||||||
|
|
||||||
## 🎯 **Purpose**
|
## 🎯 **Purpose**
|
||||||
|
|
||||||
TestDrive-JSUI is designed to:
|
TestDrive-JSUI is designed to:
|
||||||
|
|
||||||
- **📝 Render markdown with interactive JavaScript UI** for editing and viewing
|
- **📝 Provide a complete JavaScript library** for interactive markdown editing
|
||||||
- **🔌 Work as a standalone plugin** or integrate with any Python project
|
- **🌐 Work standalone in any browser** without backend requirements
|
||||||
- **🔒 Protect existing JavaScript UI functionality** during refactoring and development
|
- **🔌 Offer optional language adapters** for Python, Ruby, Java, etc.
|
||||||
- **🧪 Integrate JavaScript tests** into Python test suites
|
- **✂️ Enable section-based editing** with independent section management
|
||||||
- **🏗️ Provide a clean architecture** for JavaScript framework development
|
- **🎨 Support multiple modes** (edit/view) and themes
|
||||||
- **📊 Enable comprehensive testing** of JavaScript UI components
|
- **💾 Handle content persistence** (localStorage, download, custom backends)
|
||||||
- **🚀 Support extensibility** for JavaScript framework evolution
|
- **🧪 Include comprehensive testing** for JavaScript and integrations
|
||||||
|
- **🚀 Support extensibility** through events and configuration
|
||||||
|
|
||||||
|
## 🏛️ **Architecture: JavaScript-First Design**
|
||||||
|
|
||||||
|
TestDrive-JSUI follows a **JavaScript-first architecture**:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Core: JavaScript Library │
|
||||||
|
│ - All functionality in JS │
|
||||||
|
│ - Works standalone in browser │
|
||||||
|
│ - No backend required │
|
||||||
|
│ - Uses marked.js for markdown │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
↑ ↑ ↑
|
||||||
|
│ │ │
|
||||||
|
┌──────────┴──┐ ┌───┴────┐ ┌──┴──────┐
|
||||||
|
│ Python │ │ Ruby │ │ Java │
|
||||||
|
│ Adapter │ │ Adapter│ │ Adapter │
|
||||||
|
│ (optional) │ │(future)│ │(future) │
|
||||||
|
└─────────────┘ └────────┘ └─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Principle:** JavaScript provides ALL functionality. Language adapters only help with:
|
||||||
|
- Asset serving
|
||||||
|
- Configuration injection
|
||||||
|
- Backend integration (storage, auth, etc.)
|
||||||
|
- Testing infrastructure
|
||||||
|
|
||||||
|
### Quick Start: JavaScript Library
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<script src="testdrive-jsui.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="editor"></div>
|
||||||
|
<script>
|
||||||
|
const editor = new TestDriveJSUI({
|
||||||
|
container: '#editor',
|
||||||
|
markdown: '# Hello World\n\nEdit me!',
|
||||||
|
mode: 'edit',
|
||||||
|
theme: 'github'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to events
|
||||||
|
editor.on('save', (data) => {
|
||||||
|
console.log('Saved:', data.markdown);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Reference
|
||||||
|
|
||||||
|
#### Constructor Options
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
new TestDriveJSUI({
|
||||||
|
container: '#editor', // CSS selector or DOM element (required)
|
||||||
|
markdown: '# Content', // Initial markdown content
|
||||||
|
mode: 'edit', // 'edit' or 'view' (default: 'edit')
|
||||||
|
theme: 'github', // Theme name (default: 'github')
|
||||||
|
autosave: false, // Auto-save to localStorage (default: false)
|
||||||
|
shortcuts: true, // Keyboard shortcuts (default: true)
|
||||||
|
sections: true, // Section-based editing (default: true)
|
||||||
|
debug: false // Debug mode (default: false)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `getMarkdown()` | Get current document content |
|
||||||
|
| `setMarkdown(markdown)` | Set document content |
|
||||||
|
| `getStatus()` | Get editor status (sections, words, etc.) |
|
||||||
|
| `save()` | Trigger save event |
|
||||||
|
| `download(filename)` | Download as markdown file |
|
||||||
|
| `loadFromLocalStorage()` | Load from browser storage |
|
||||||
|
| `saveToLocalStorage()` | Save to browser storage |
|
||||||
|
| `resetAll()` | Reset all sections to original content |
|
||||||
|
| `destroy()` | Clean up and destroy editor |
|
||||||
|
| `on(event, callback)` | Listen to events |
|
||||||
|
| `off(event, callback)` | Remove event listener |
|
||||||
|
|
||||||
|
#### Events
|
||||||
|
|
||||||
|
| Event | Data | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `initialized` | `{mode, theme, sections}` | Editor finished initializing |
|
||||||
|
| `save` | `{markdown}` | Save triggered |
|
||||||
|
| `content-changed` | `{markdown}` | Content modified |
|
||||||
|
| `autosave` | `{markdown}` | Auto-save occurred |
|
||||||
|
| `download` | `{filename, markdown}` | Document downloaded |
|
||||||
|
| `reset` | `{}` | Document reset |
|
||||||
|
| `destroyed` | `{}` | Editor destroyed |
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
See the `/examples` directory for:
|
||||||
|
- **`standalone.html`** - Minimal proof of concept (works from file://)
|
||||||
|
- **`full-editor.html`** - Complete demo with all features
|
||||||
|
|
||||||
|
## 🐍 **Python Adapter (Optional)**
|
||||||
|
|
||||||
|
For Python projects, TestDrive-JSUI provides an **optional adapter** for integration:
|
||||||
|
|
||||||
## 🏗️ **Architecture**
|
## 🏗️ **Architecture**
|
||||||
|
|
||||||
@@ -459,6 +572,9 @@ MIT License - See main MarkiTect project for details.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Generated: 2025-11-09*
|
**Version:** 1.0.0
|
||||||
*Status: Phase 1 Implementation*
|
**Status:** Full JavaScript Library Implementation Complete ✅
|
||||||
*Next: Copy JavaScript files and create integration tests*
|
**Architecture:** JavaScript-First with Optional Language Adapters
|
||||||
|
**Last Updated:** 2025-12-16
|
||||||
|
|
||||||
|
See `/examples` for working demos and `ARCHITECTURE.md` for detailed design documentation.
|
||||||
@@ -6,9 +6,9 @@ This directory contains examples demonstrating TestDrive-JSUI as a standalone Ja
|
|||||||
|
|
||||||
## 📁 Examples
|
## 📁 Examples
|
||||||
|
|
||||||
### `standalone.html` - Pure JavaScript Demo
|
### `standalone.html` - Proof of Concept
|
||||||
|
|
||||||
**Purpose**: Proves TestDrive-JSUI can work as a pure JavaScript library with no backend.
|
**Purpose**: Minimal proof that TestDrive-JSUI can work as a pure JavaScript library.
|
||||||
|
|
||||||
**Features**:
|
**Features**:
|
||||||
- ✅ Runs directly in browser (no server needed)
|
- ✅ Runs directly in browser (no server needed)
|
||||||
@@ -37,25 +37,88 @@ python3 -m http.server 8000
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `full-editor.html` - Complete Library Demo
|
||||||
|
|
||||||
|
**Purpose**: Demonstrates the full TestDriveJSUI library with all features.
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- ✅ **Full Library Integration**: Uses complete `TestDriveJSUI` class
|
||||||
|
- ✅ **Section-Based Editing**: Click sections to edit them independently
|
||||||
|
- ✅ **Interactive Controls**: Floating control panel with document operations
|
||||||
|
- ✅ **Event System**: Listen to save, content-changed, and other events
|
||||||
|
- ✅ **Multiple Modes**: Switch between edit and view modes
|
||||||
|
- ✅ **Auto-save**: Optional automatic saving (currently disabled)
|
||||||
|
- ✅ **Keyboard Shortcuts**: Ctrl+S to save, Escape to close editor
|
||||||
|
- ✅ **Status Bar**: Real-time document statistics
|
||||||
|
- ✅ **Image Editing**: Advanced image editing with drag & drop
|
||||||
|
|
||||||
|
**How to Use**:
|
||||||
|
```bash
|
||||||
|
# Serve with any static server (required for proper module loading)
|
||||||
|
python3 -m http.server 8000
|
||||||
|
# Then visit: http://localhost:8000/examples/full-editor.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**What It Demonstrates**:
|
||||||
|
- Complete TestDriveJSUI API usage
|
||||||
|
- Integration of all components (SectionManager, DOMRenderer, DocumentControls)
|
||||||
|
- Event-driven architecture
|
||||||
|
- Mode switching (edit/view)
|
||||||
|
- Document operations (save, load, download, reset)
|
||||||
|
- Real-time status updates
|
||||||
|
|
||||||
|
**API Example from Demo**:
|
||||||
|
```javascript
|
||||||
|
// Initialize editor
|
||||||
|
const editor = new TestDriveJSUI({
|
||||||
|
container: '#editor-container',
|
||||||
|
markdown: '# Hello World',
|
||||||
|
mode: 'edit',
|
||||||
|
theme: 'github',
|
||||||
|
autosave: false,
|
||||||
|
shortcuts: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to events
|
||||||
|
editor.on('save', (data) => {
|
||||||
|
console.log('Saved:', data.markdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get/set content
|
||||||
|
const markdown = editor.getMarkdown();
|
||||||
|
editor.setMarkdown('# Updated content');
|
||||||
|
|
||||||
|
// Get status
|
||||||
|
const status = editor.getStatus();
|
||||||
|
console.log('Sections:', status.totalSections);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🎯 Architecture Validation
|
## 🎯 Architecture Validation
|
||||||
|
|
||||||
This standalone example validates the JavaScript-first architecture:
|
These examples validate the JavaScript-first architecture:
|
||||||
|
|
||||||
### ✅ What Works Without Backend
|
### ✅ Implemented Features
|
||||||
- Markdown parsing (marked.js)
|
- Markdown parsing (marked.js)
|
||||||
- HTML rendering
|
- HTML rendering
|
||||||
- UI display
|
- UI display and styling
|
||||||
- Content management
|
- Content management
|
||||||
- Save/load (localStorage)
|
- Save/load (localStorage)
|
||||||
- File download
|
- File download
|
||||||
|
- **Interactive editing UI** ✅
|
||||||
|
- **Section-based editing** ✅
|
||||||
|
- **Control panels (edit, debug, status)** ✅
|
||||||
|
- **Keyboard shortcuts** ✅
|
||||||
|
- **Themes** ✅
|
||||||
|
- **Event system** ✅
|
||||||
|
|
||||||
### ⏸️ What's Still TODO (Full Implementation)
|
### 🚧 Future Enhancements
|
||||||
- Interactive editing UI
|
|
||||||
- Section-based editing
|
|
||||||
- Control panels (edit, debug, status)
|
|
||||||
- Keyboard shortcuts
|
|
||||||
- Themes
|
|
||||||
- Plugin system
|
- Plugin system
|
||||||
|
- Additional themes
|
||||||
|
- Collaborative editing
|
||||||
|
- Cloud storage adapters
|
||||||
|
- Mobile-optimized UI
|
||||||
|
|
||||||
### 🔧 What Backend Adapters Provide (Optional)
|
### 🔧 What Backend Adapters Provide (Optional)
|
||||||
- Asset serving at scale
|
- Asset serving at scale
|
||||||
@@ -66,27 +129,54 @@ This standalone example validates the JavaScript-first architecture:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Next Steps
|
## 🚀 Getting Started
|
||||||
|
|
||||||
### 1. Validate Proof of Concept
|
### 1. Try the Proof of Concept
|
||||||
```bash
|
```bash
|
||||||
# Open standalone.html
|
# Open standalone.html in any browser
|
||||||
# Verify markdown renders correctly
|
open examples/standalone.html
|
||||||
# Try save/load/download buttons
|
|
||||||
|
# Test basic functionality:
|
||||||
|
# - Markdown rendering
|
||||||
|
# - Save/load/download buttons
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Full JavaScript Library
|
### 2. Explore the Full Editor
|
||||||
After validating the concept:
|
```bash
|
||||||
- Create `js/testdrive-jsui.js` main class
|
# Serve the examples directory
|
||||||
- Add interactive editing
|
cd examples
|
||||||
- Implement control panels
|
python3 -m http.server 8000
|
||||||
- Full section management
|
|
||||||
|
|
||||||
### 3. Refactor Python Adapter
|
# Open in browser:
|
||||||
Once JavaScript is complete:
|
# http://localhost:8000/full-editor.html
|
||||||
- Simplify Python to thin adapter
|
|
||||||
- Remove HTML generation
|
# Test advanced features:
|
||||||
- Keep only asset serving and testing
|
# - Click sections to edit
|
||||||
|
# - Use toolbar buttons
|
||||||
|
# - Try keyboard shortcuts (Ctrl+S, Escape)
|
||||||
|
# - Switch between edit/view modes
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Integrate Into Your Project
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<script src="path/to/testdrive-jsui.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="editor"></div>
|
||||||
|
<script>
|
||||||
|
const editor = new TestDriveJSUI({
|
||||||
|
container: '#editor',
|
||||||
|
markdown: '# Start writing...',
|
||||||
|
mode: 'edit'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -110,5 +200,6 @@ Want to add an example? Please ensure it:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Status**: Proof of Concept
|
**Status**: Full Implementation Complete ✅
|
||||||
**Last Updated**: 2025-12-16
|
**Last Updated**: 2025-12-16
|
||||||
|
**Library Version**: 1.0.0
|
||||||
|
|||||||
535
examples/full-editor.html
Normal file
535
examples/full-editor.html
Normal file
@@ -0,0 +1,535 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TestDrive-JSUI - Full Editor Demo</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #24292e;
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
color: #0366d6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #e8f5e9;
|
||||||
|
border: 1px solid #a5d6a7;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box strong {
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button {
|
||||||
|
background: #0366d6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button:hover {
|
||||||
|
background: #0256c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button.secondary {
|
||||||
|
background: #6a737d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button.secondary:hover {
|
||||||
|
background: #586069;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button.success {
|
||||||
|
background: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button.success:hover {
|
||||||
|
background: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GitHub theme styles for rendered markdown */
|
||||||
|
#editor-container h1,
|
||||||
|
#editor-container h2,
|
||||||
|
#editor-container h3 {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
border-bottom: 1px solid #eaecef;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
border-bottom: 1px solid #eaecef;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container ul,
|
||||||
|
#editor-container ol {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container code {
|
||||||
|
background-color: rgba(27,31,35,0.05);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 85%;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container pre {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 85%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container blockquote {
|
||||||
|
border-left: 4px solid #dfe2e5;
|
||||||
|
color: #6a737d;
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 40px;
|
||||||
|
color: #586069;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar {
|
||||||
|
background: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar .stat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar .label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #24292e;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>📝 TestDrive-JSUI Full Editor</h1>
|
||||||
|
<p>
|
||||||
|
Complete interactive markdown editor demonstrating the full TestDrive-JSUI library.
|
||||||
|
Click any section to edit, use the toolbar for document operations.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<strong>✨ Full Library Integration:</strong> This demo uses the complete TestDriveJSUI class
|
||||||
|
with all components (SectionManager, DOMRenderer, DocumentControls) working together.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar">
|
||||||
|
<button onclick="saveDocument()" class="success">💾 Save Document</button>
|
||||||
|
<button onclick="loadDocument()">📂 Load Saved</button>
|
||||||
|
<button onclick="downloadDocument()">⬇️ Download .md</button>
|
||||||
|
<button onclick="showStatus()">📊 Show Status</button>
|
||||||
|
<button onclick="resetDocument()" class="secondary">🔄 Reset All</button>
|
||||||
|
<button onclick="switchToViewMode()" class="secondary">👁️ View Mode</button>
|
||||||
|
<button onclick="switchToEditMode()" class="secondary">✏️ Edit Mode</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="editor-container">
|
||||||
|
<!-- Editor will be initialized here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-bar" id="status-bar">
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">Mode:</span>
|
||||||
|
<span id="status-mode">edit</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">Sections:</span>
|
||||||
|
<span id="status-sections">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">Words:</span>
|
||||||
|
<span id="status-words">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">Characters:</span>
|
||||||
|
<span id="status-chars">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>TestDrive-JSUI • JavaScript-First Architecture • Full Featured Editor</p>
|
||||||
|
<p><small>Uses: marked.js (markdown) + TestDriveJSUI library</small></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- External 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/document-controls.js"></script>
|
||||||
|
|
||||||
|
<!-- TestDrive-JSUI Main Library -->
|
||||||
|
<script src="../js/testdrive-jsui.js"></script>
|
||||||
|
|
||||||
|
<!-- Application Script -->
|
||||||
|
<script>
|
||||||
|
// Sample document content
|
||||||
|
const defaultMarkdown = `# TestDrive-JSUI Full Editor
|
||||||
|
|
||||||
|
## What is This?
|
||||||
|
|
||||||
|
This is the **complete TestDrive-JSUI library** in action. Unlike the standalone proof of concept, this demo uses the full \`TestDriveJSUI\` class that wraps all components together.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
- ✅ **Section-Based Editing**: Click any section to edit it independently
|
||||||
|
- ✅ **Interactive Controls**: Floating control panel for document operations
|
||||||
|
- ✅ **Event System**: Listen to editor events for integration
|
||||||
|
- ✅ **Multiple Modes**: Switch between edit and view modes
|
||||||
|
- ✅ **Auto-save**: Optional automatic saving to localStorage
|
||||||
|
- ✅ **Keyboard Shortcuts**: Ctrl+S to save, Escape to close editor
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### JavaScript-First Design
|
||||||
|
|
||||||
|
TestDrive-JSUI is built with a JavaScript-first architecture:
|
||||||
|
|
||||||
|
1. **Core Library**: Pure JavaScript, works standalone
|
||||||
|
2. **Components**: Modular design (SectionManager, DOMRenderer, DocumentControls)
|
||||||
|
3. **Language Adapters**: Python, Ruby, Java adapters are optional helpers
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
// Initialize the editor
|
||||||
|
const editor = new TestDriveJSUI({
|
||||||
|
container: '#editor-container',
|
||||||
|
markdown: '# Hello World',
|
||||||
|
mode: 'edit',
|
||||||
|
theme: 'github',
|
||||||
|
autosave: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to events
|
||||||
|
editor.on('save', (data) => {
|
||||||
|
console.log('Document saved:', data.markdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get current content
|
||||||
|
const markdown = editor.getMarkdown();
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Try It Out!
|
||||||
|
|
||||||
|
### Editing Sections
|
||||||
|
|
||||||
|
Click any section above or below to start editing. You'll see:
|
||||||
|
|
||||||
|
- **Textarea**: Edit the markdown content
|
||||||
|
- **Accept Button (✓)**: Save your changes
|
||||||
|
- **Cancel Button (✗)**: Discard changes
|
||||||
|
- **Reset Button (↺)**: Restore original content
|
||||||
|
|
||||||
|
### Document Operations
|
||||||
|
|
||||||
|
Use the toolbar buttons:
|
||||||
|
|
||||||
|
- **Save**: Saves to browser localStorage
|
||||||
|
- **Load**: Loads previously saved document
|
||||||
|
- **Download**: Downloads as .md file
|
||||||
|
- **Status**: Shows document statistics
|
||||||
|
- **Reset All**: Restores all sections to original content
|
||||||
|
- **View/Edit Mode**: Toggle between modes
|
||||||
|
|
||||||
|
## Code Example
|
||||||
|
|
||||||
|
Here's how easy it is to integrate TestDrive-JSUI:
|
||||||
|
|
||||||
|
\`\`\`html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<script src="testdrive-jsui.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="editor"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const editor = new TestDriveJSUI({
|
||||||
|
container: '#editor',
|
||||||
|
markdown: '# Start writing...',
|
||||||
|
mode: 'edit'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Constructor Options
|
||||||
|
|
||||||
|
- \`container\` (required): CSS selector or DOM element
|
||||||
|
- \`markdown\` (optional): Initial markdown content
|
||||||
|
- \`mode\` (optional): 'edit' or 'view' (default: 'edit')
|
||||||
|
- \`theme\` (optional): Theme name (default: 'github')
|
||||||
|
- \`autosave\` (optional): Enable auto-save (default: false)
|
||||||
|
- \`shortcuts\` (optional): Enable keyboard shortcuts (default: true)
|
||||||
|
- \`sections\` (optional): Enable section-based editing (default: true)
|
||||||
|
- \`debug\` (optional): Enable debug mode (default: false)
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
- \`getMarkdown()\`: Get current document content
|
||||||
|
- \`setMarkdown(markdown)\`: Set document content
|
||||||
|
- \`getStatus()\`: Get editor status (sections, words, etc.)
|
||||||
|
- \`save()\`: Trigger save event
|
||||||
|
- \`download(filename)\`: Download as markdown file
|
||||||
|
- \`resetAll()\`: Reset all sections to original content
|
||||||
|
- \`destroy()\`: Clean up and destroy editor
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
- \`initialized\`: Editor finished initializing
|
||||||
|
- \`save\`: Document save triggered
|
||||||
|
- \`content-changed\`: Content was modified
|
||||||
|
- \`autosave\`: Auto-save occurred
|
||||||
|
- \`download\`: Document downloaded
|
||||||
|
- \`reset\`: Document reset
|
||||||
|
- \`destroyed\`: Editor destroyed
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### For Users
|
||||||
|
|
||||||
|
- **Intuitive**: Click to edit, visual feedback
|
||||||
|
- **Fast**: No page reloads, instant updates
|
||||||
|
- **Reliable**: Undo/reset functionality
|
||||||
|
- **Flexible**: Works in any browser, no installation
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
|
||||||
|
- **Easy Integration**: Simple API, minimal setup
|
||||||
|
- **Customizable**: Event system for custom behavior
|
||||||
|
- **Extensible**: Plugin architecture (coming soon)
|
||||||
|
- **Language Agnostic**: Use with any backend
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Ready to build?** Check out the [documentation](../README.md) or browse the [source code](../js/) to learn more!
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Global editor instance
|
||||||
|
let editor;
|
||||||
|
|
||||||
|
// Initialize editor on page load
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Create editor instance
|
||||||
|
editor = new TestDriveJSUI({
|
||||||
|
container: '#editor-container',
|
||||||
|
markdown: defaultMarkdown,
|
||||||
|
mode: 'edit',
|
||||||
|
theme: 'github',
|
||||||
|
autosave: false,
|
||||||
|
shortcuts: true,
|
||||||
|
sections: true,
|
||||||
|
debug: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up event listeners
|
||||||
|
editor.on('initialized', (data) => {
|
||||||
|
console.log('✅ Editor initialized:', data);
|
||||||
|
updateStatusBar();
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('save', (data) => {
|
||||||
|
console.log('💾 Document saved:', data.markdown.length, 'characters');
|
||||||
|
updateStatusBar();
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('content-changed', (data) => {
|
||||||
|
console.log('📝 Content changed');
|
||||||
|
updateStatusBar();
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('reset', () => {
|
||||||
|
console.log('🔄 Document reset');
|
||||||
|
updateStatusBar();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update status bar initially
|
||||||
|
updateStatusBar();
|
||||||
|
|
||||||
|
// Update status bar periodically
|
||||||
|
setInterval(updateStatusBar, 2000);
|
||||||
|
|
||||||
|
console.log('✅ TestDrive-JSUI Full Editor initialized!');
|
||||||
|
console.log('📝 Global editor instance available as window.editor');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toolbar functions
|
||||||
|
function saveDocument() {
|
||||||
|
editor.save();
|
||||||
|
alert('✅ Document saved to browser localStorage!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDocument() {
|
||||||
|
const loaded = editor.loadFromLocalStorage();
|
||||||
|
if (loaded) {
|
||||||
|
alert('📂 Document loaded from localStorage!');
|
||||||
|
} else {
|
||||||
|
alert('ℹ️ No saved document found.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadDocument() {
|
||||||
|
const filename = prompt('Enter filename:', 'document.md');
|
||||||
|
if (filename) {
|
||||||
|
editor.download(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus() {
|
||||||
|
const status = editor.getStatus();
|
||||||
|
const message = [
|
||||||
|
`📊 Document Status`,
|
||||||
|
``,
|
||||||
|
`Mode: ${status.mode}`,
|
||||||
|
`Total Sections: ${status.totalSections || 0}`,
|
||||||
|
`Currently Editing: ${status.editingSections || 0}`,
|
||||||
|
`Modified Sections: ${status.modifiedSections || 0}`,
|
||||||
|
`Word Count: ${status.wordCount}`,
|
||||||
|
`Character Count: ${status.characterCount}`
|
||||||
|
].join('\n');
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDocument() {
|
||||||
|
if (confirm('Reset all sections to original content?\n\nThis cannot be undone.')) {
|
||||||
|
editor.resetAll();
|
||||||
|
alert('🔄 Document reset to original content!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToViewMode() {
|
||||||
|
const markdown = editor.getMarkdown();
|
||||||
|
editor.destroy();
|
||||||
|
editor = new TestDriveJSUI({
|
||||||
|
container: '#editor-container',
|
||||||
|
markdown: markdown,
|
||||||
|
mode: 'view',
|
||||||
|
theme: 'github'
|
||||||
|
});
|
||||||
|
updateStatusBar();
|
||||||
|
alert('👁️ Switched to view mode. Click "Edit Mode" to return.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToEditMode() {
|
||||||
|
const markdown = editor.getMarkdown();
|
||||||
|
editor.destroy();
|
||||||
|
editor = new TestDriveJSUI({
|
||||||
|
container: '#editor-container',
|
||||||
|
markdown: markdown,
|
||||||
|
mode: 'edit',
|
||||||
|
theme: 'github',
|
||||||
|
autosave: false,
|
||||||
|
shortcuts: true
|
||||||
|
});
|
||||||
|
updateStatusBar();
|
||||||
|
alert('✏️ Switched to edit mode. Click sections to edit them.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatusBar() {
|
||||||
|
const status = editor.getStatus();
|
||||||
|
document.getElementById('status-mode').textContent = status.mode;
|
||||||
|
document.getElementById('status-sections').textContent = status.totalSections || 0;
|
||||||
|
document.getElementById('status-words').textContent = status.wordCount || 0;
|
||||||
|
document.getElementById('status-chars').textContent = status.characterCount || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose editor for debugging
|
||||||
|
window.editor = editor;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
547
js/testdrive-jsui.js
Normal file
547
js/testdrive-jsui.js
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
/**
|
||||||
|
* TestDrive-JSUI - JavaScript-First Markdown Editor
|
||||||
|
*
|
||||||
|
* Main entry point for the TestDrive-JSUI library.
|
||||||
|
* This is a pure JavaScript library for interactive markdown editing.
|
||||||
|
* Language adapters (Python, Ruby, Java) are optional integration helpers.
|
||||||
|
*
|
||||||
|
* @version 1.0.0
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestDriveJSUI - Main library class
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```javascript
|
||||||
|
* const editor = new TestDriveJSUI({
|
||||||
|
* container: '#editor',
|
||||||
|
* markdown: '# Hello World\n\nEdit me!',
|
||||||
|
* mode: 'edit',
|
||||||
|
* theme: 'github',
|
||||||
|
* autosave: false
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class TestDriveJSUI {
|
||||||
|
constructor(options = {}) {
|
||||||
|
// Validate required options
|
||||||
|
if (!options.container) {
|
||||||
|
throw new Error('TestDriveJSUI: container option is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
this.config = {
|
||||||
|
container: options.container,
|
||||||
|
markdown: options.markdown || '# Welcome\n\nStart editing...',
|
||||||
|
mode: options.mode || 'edit', // 'edit' or 'view'
|
||||||
|
theme: options.theme || 'github',
|
||||||
|
autosave: options.autosave || false,
|
||||||
|
shortcuts: options.shortcuts !== false, // default true
|
||||||
|
sections: options.sections !== false, // default true
|
||||||
|
debug: options.debug || false,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
this.container = null;
|
||||||
|
this.sectionManager = null;
|
||||||
|
this.domRenderer = null;
|
||||||
|
this.documentControls = null;
|
||||||
|
this.isInitialized = false;
|
||||||
|
this.listeners = new Map();
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the editor
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
console.warn('TestDriveJSUI: Already initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve container
|
||||||
|
this.container = this.resolveContainer(this.config.container);
|
||||||
|
if (!this.container) {
|
||||||
|
throw new Error('TestDriveJSUI: Could not resolve container');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for marked.js dependency
|
||||||
|
if (typeof marked === 'undefined') {
|
||||||
|
console.warn('TestDriveJSUI: marked.js not loaded. Markdown rendering will be limited.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply theme
|
||||||
|
this.applyTheme(this.config.theme);
|
||||||
|
|
||||||
|
// Initialize components based on mode
|
||||||
|
if (this.config.mode === 'edit') {
|
||||||
|
this.initEditMode();
|
||||||
|
} else {
|
||||||
|
this.initViewMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
|
|
||||||
|
// Emit initialized event
|
||||||
|
this.emit('initialized', {
|
||||||
|
mode: this.config.mode,
|
||||||
|
theme: this.config.theme,
|
||||||
|
sections: this.sectionManager ? this.sectionManager.sections.size : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize edit mode with full interactivity
|
||||||
|
*/
|
||||||
|
initEditMode() {
|
||||||
|
// Load required components
|
||||||
|
if (typeof SectionManager === 'undefined') {
|
||||||
|
console.error('TestDriveJSUI: SectionManager not loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof DOMRenderer === 'undefined') {
|
||||||
|
console.error('TestDriveJSUI: DOMRenderer not loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof DocumentControls === 'undefined') {
|
||||||
|
console.error('TestDriveJSUI: DocumentControls not loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create section manager
|
||||||
|
this.sectionManager = new SectionManager();
|
||||||
|
|
||||||
|
// Create DOM renderer
|
||||||
|
this.domRenderer = new DOMRenderer(this.sectionManager, this.container);
|
||||||
|
|
||||||
|
// Create document controls
|
||||||
|
this.documentControls = new DocumentControls();
|
||||||
|
this.documentControls.create();
|
||||||
|
|
||||||
|
// Set up event handlers for controls
|
||||||
|
this.setupControlHandlers();
|
||||||
|
|
||||||
|
// Parse markdown into sections and render
|
||||||
|
this.sectionManager.createSectionsFromMarkdown(this.config.markdown);
|
||||||
|
|
||||||
|
// Setup keyboard shortcuts if enabled
|
||||||
|
if (this.config.shortcuts) {
|
||||||
|
this.setupKeyboardShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup autosave if enabled
|
||||||
|
if (this.config.autosave) {
|
||||||
|
this.setupAutosave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize view mode (read-only rendering)
|
||||||
|
*/
|
||||||
|
initViewMode() {
|
||||||
|
// Clear container
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
|
||||||
|
// Render markdown directly using marked.js or simple renderer
|
||||||
|
const html = this.renderMarkdown(this.config.markdown);
|
||||||
|
|
||||||
|
// Create content wrapper
|
||||||
|
const contentWrapper = document.createElement('div');
|
||||||
|
contentWrapper.className = 'testdrive-view-content';
|
||||||
|
contentWrapper.innerHTML = html;
|
||||||
|
|
||||||
|
this.container.appendChild(contentWrapper);
|
||||||
|
|
||||||
|
// Apply view mode styling
|
||||||
|
contentWrapper.style.cssText = `
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup control panel event handlers
|
||||||
|
*/
|
||||||
|
setupControlHandlers() {
|
||||||
|
this.documentControls.setEventHandlers({
|
||||||
|
'save-document': () => {
|
||||||
|
const markdown = this.getMarkdown();
|
||||||
|
this.emit('save', { markdown });
|
||||||
|
|
||||||
|
// If autosave is enabled and we have a save handler, call it
|
||||||
|
if (this.listeners.has('save')) {
|
||||||
|
// User has registered a save handler
|
||||||
|
} else {
|
||||||
|
// Default: save to localStorage
|
||||||
|
this.saveToLocalStorage(markdown);
|
||||||
|
alert('Document saved to browser localStorage');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'reset-all': () => {
|
||||||
|
if (confirm('Reset all sections to original content?')) {
|
||||||
|
this.resetAll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'show-status': () => {
|
||||||
|
const status = this.getStatus();
|
||||||
|
this.showStatus(status);
|
||||||
|
},
|
||||||
|
'toggle-debug': () => {
|
||||||
|
this.toggleDebug();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup keyboard shortcuts
|
||||||
|
*/
|
||||||
|
setupKeyboardShortcuts() {
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
// Ctrl+S or Cmd+S - Save
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape - Close editor
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
if (this.domRenderer && this.domRenderer.currentFloatingMenu) {
|
||||||
|
this.domRenderer.hideCurrentEditor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup autosave functionality
|
||||||
|
*/
|
||||||
|
setupAutosave() {
|
||||||
|
// Save every 30 seconds
|
||||||
|
this.autosaveInterval = setInterval(() => {
|
||||||
|
const markdown = this.getMarkdown();
|
||||||
|
this.saveToLocalStorage(markdown);
|
||||||
|
this.emit('autosave', { markdown });
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve container from selector or element
|
||||||
|
*/
|
||||||
|
resolveContainer(container) {
|
||||||
|
if (typeof container === 'string') {
|
||||||
|
return document.querySelector(container);
|
||||||
|
} else if (container instanceof HTMLElement) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render markdown to HTML
|
||||||
|
*/
|
||||||
|
renderMarkdown(markdown) {
|
||||||
|
if (typeof marked !== 'undefined') {
|
||||||
|
// Use marked.js for full markdown support
|
||||||
|
marked.setOptions({
|
||||||
|
gfm: true,
|
||||||
|
breaks: true,
|
||||||
|
headerIds: true,
|
||||||
|
mangle: false,
|
||||||
|
sanitize: false
|
||||||
|
});
|
||||||
|
return marked.parse(markdown);
|
||||||
|
} else {
|
||||||
|
// Fallback to simple rendering
|
||||||
|
return this.simpleMarkdownRender(markdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple markdown renderer (fallback)
|
||||||
|
*/
|
||||||
|
simpleMarkdownRender(markdown) {
|
||||||
|
return markdown
|
||||||
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||||
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||||
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||||
|
.replace(/!\[(.*?)\]\((.*?)\)/gim, '<img src="$2" alt="$1" style="max-width: 100%; height: auto;" />')
|
||||||
|
.replace(/\[([^\]]+)\]\(([^)]+)\)/gim, '<a href="$2" target="_blank">$1</a>')
|
||||||
|
.replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
|
||||||
|
.replace(/\*(.*?)\*/gim, '<em>$1</em>')
|
||||||
|
.replace(/`([^`]+)`/gim, '<code>$1</code>')
|
||||||
|
.replace(/\n/gim, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply theme to the editor
|
||||||
|
*/
|
||||||
|
applyTheme(themeName) {
|
||||||
|
// Remove existing theme classes
|
||||||
|
this.container.className = this.container.className
|
||||||
|
.split(' ')
|
||||||
|
.filter(c => !c.startsWith('theme-'))
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
// Add new theme class
|
||||||
|
this.container.classList.add(`theme-${themeName}`);
|
||||||
|
|
||||||
|
// Apply base styles
|
||||||
|
this.container.style.cssText = `
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #24292e;
|
||||||
|
background: #ffffff;
|
||||||
|
min-height: 300px;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current markdown content
|
||||||
|
*/
|
||||||
|
getMarkdown() {
|
||||||
|
if (this.sectionManager) {
|
||||||
|
return this.sectionManager.getDocumentMarkdown();
|
||||||
|
}
|
||||||
|
return this.config.markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set markdown content
|
||||||
|
*/
|
||||||
|
setMarkdown(markdown) {
|
||||||
|
this.config.markdown = markdown;
|
||||||
|
|
||||||
|
if (this.config.mode === 'edit' && this.sectionManager) {
|
||||||
|
// Re-parse and render sections
|
||||||
|
this.sectionManager.sections.clear();
|
||||||
|
this.sectionManager.createSectionsFromMarkdown(markdown);
|
||||||
|
} else {
|
||||||
|
// Re-render view mode
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
this.initViewMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('content-changed', { markdown });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get editor status
|
||||||
|
*/
|
||||||
|
getStatus() {
|
||||||
|
if (this.sectionManager) {
|
||||||
|
const sections = this.sectionManager.getAllSections();
|
||||||
|
const editingSections = sections.filter(s => s.isEditing());
|
||||||
|
const modifiedSections = sections.filter(s => s.hasChanges());
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: this.config.mode,
|
||||||
|
totalSections: sections.length,
|
||||||
|
editingSections: editingSections.length,
|
||||||
|
modifiedSections: modifiedSections.length,
|
||||||
|
wordCount: this.getWordCount(),
|
||||||
|
characterCount: this.getMarkdown().length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: this.config.mode,
|
||||||
|
wordCount: this.getWordCount(),
|
||||||
|
characterCount: this.config.markdown.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get word count
|
||||||
|
*/
|
||||||
|
getWordCount() {
|
||||||
|
const text = this.getMarkdown();
|
||||||
|
return text.split(/\s+/).filter(word => word.length > 0).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show status dialog
|
||||||
|
*/
|
||||||
|
showStatus(status) {
|
||||||
|
const message = [
|
||||||
|
`Mode: ${status.mode}`,
|
||||||
|
`Total Sections: ${status.totalSections || 0}`,
|
||||||
|
`Editing: ${status.editingSections || 0}`,
|
||||||
|
`Modified: ${status.modifiedSections || 0}`,
|
||||||
|
`Words: ${status.wordCount}`,
|
||||||
|
`Characters: ${status.characterCount}`
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save current content
|
||||||
|
*/
|
||||||
|
save() {
|
||||||
|
const markdown = this.getMarkdown();
|
||||||
|
this.emit('save', { markdown });
|
||||||
|
|
||||||
|
// Default: save to localStorage
|
||||||
|
this.saveToLocalStorage(markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save to browser localStorage
|
||||||
|
*/
|
||||||
|
saveToLocalStorage(markdown) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('testdrive-jsui-content', markdown);
|
||||||
|
localStorage.setItem('testdrive-jsui-timestamp', new Date().toISOString());
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save to localStorage:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load from browser localStorage
|
||||||
|
*/
|
||||||
|
loadFromLocalStorage() {
|
||||||
|
try {
|
||||||
|
const markdown = localStorage.getItem('testdrive-jsui-content');
|
||||||
|
if (markdown) {
|
||||||
|
this.setMarkdown(markdown);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load from localStorage:', e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download markdown as file
|
||||||
|
*/
|
||||||
|
download(filename = 'document.md') {
|
||||||
|
const markdown = this.getMarkdown();
|
||||||
|
const blob = new Blob([markdown], { type: 'text/markdown' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
this.emit('download', { filename, markdown });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all sections to original content
|
||||||
|
*/
|
||||||
|
resetAll() {
|
||||||
|
if (this.sectionManager) {
|
||||||
|
const sections = this.sectionManager.getAllSections();
|
||||||
|
sections.forEach(section => {
|
||||||
|
this.sectionManager.resetSection(section.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('reset');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle debug mode
|
||||||
|
*/
|
||||||
|
toggleDebug() {
|
||||||
|
this.config.debug = !this.config.debug;
|
||||||
|
|
||||||
|
const debugContainer = document.getElementById('debug-messages-container');
|
||||||
|
if (debugContainer) {
|
||||||
|
debugContainer.style.display = this.config.debug ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('debug-toggled', { enabled: this.config.debug });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener system
|
||||||
|
*/
|
||||||
|
on(event, callback) {
|
||||||
|
if (!this.listeners.has(event)) {
|
||||||
|
this.listeners.set(event, []);
|
||||||
|
}
|
||||||
|
this.listeners.get(event).push(callback);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove event listener
|
||||||
|
*/
|
||||||
|
off(event, callback) {
|
||||||
|
if (this.listeners.has(event)) {
|
||||||
|
const callbacks = this.listeners.get(event);
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit event
|
||||||
|
*/
|
||||||
|
emit(event, data) {
|
||||||
|
if (this.listeners.has(event)) {
|
||||||
|
this.listeners.get(event).forEach(callback => {
|
||||||
|
try {
|
||||||
|
callback(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error in event listener for '${event}':`, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the editor and clean up
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
// Clear autosave interval
|
||||||
|
if (this.autosaveInterval) {
|
||||||
|
clearInterval(this.autosaveInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy document controls
|
||||||
|
if (this.documentControls) {
|
||||||
|
this.documentControls.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear container
|
||||||
|
if (this.container) {
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear references
|
||||||
|
this.sectionManager = null;
|
||||||
|
this.domRenderer = null;
|
||||||
|
this.documentControls = null;
|
||||||
|
this.listeners.clear();
|
||||||
|
this.isInitialized = false;
|
||||||
|
|
||||||
|
this.emit('destroyed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for CommonJS (Node.js, Jest)
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = { TestDriveJSUI };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for browser use
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.TestDriveJSUI = TestDriveJSUI;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user