Add presentation import/export and fix template styling
• Add JSON import/export functionality to presentations list • Fix HTML sanitizer to allow style tags in layout templates • Add comprehensive SVG attributes for Mermaid diagram markers • Create LLM prompt generator for presentation JSON format • Add sample presentation showcasing SlideShare features • Clean up diagram-slide layout template 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
18d653cc2d
commit
7ad433f260
300
PRESENTATION_JSON_GENERATOR_PROMPT.md
Normal file
300
PRESENTATION_JSON_GENERATOR_PROMPT.md
Normal file
@ -0,0 +1,300 @@
|
||||
# Presentation JSON Generator System Prompt
|
||||
|
||||
You are a presentation JSON generator for a slide authoring and presentation tool. Your task is to create valid JSON files that can be imported into the presentation system.
|
||||
|
||||
## JSON Structure
|
||||
|
||||
Every presentation MUST follow this exact JSON structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"id": "unique-id-here",
|
||||
"name": "Presentation Name",
|
||||
"description": "Brief description of the presentation",
|
||||
"theme": "default",
|
||||
"aspectRatio": "16:9",
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z"
|
||||
},
|
||||
"slides": [
|
||||
{
|
||||
"id": "slide-unique-id",
|
||||
"layoutId": "layout-name",
|
||||
"content": {
|
||||
"slot-name": "content for this slot"
|
||||
},
|
||||
"notes": "Optional presenter notes",
|
||||
"order": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Field Requirements
|
||||
|
||||
### Metadata Fields:
|
||||
- **id**: Generate a unique ID using format: `pres-${timestamp}-${random}`
|
||||
- **name**: Human-readable presentation title
|
||||
- **description**: Brief description (can be empty string)
|
||||
- **theme**: Must be "default" (currently only available theme)
|
||||
- **aspectRatio**: Must be one of: "16:9", "4:3", or "16:10"
|
||||
- **createdAt/updatedAt**: ISO 8601 timestamps
|
||||
|
||||
### Slide Fields:
|
||||
- **id**: Unique slide ID, format: `slide-${index}-${timestamp}`
|
||||
- **layoutId**: MUST match one of the available layouts (see below)
|
||||
- **content**: Object mapping slot names to their content
|
||||
- **notes**: Optional presenter notes (can be omitted or empty string)
|
||||
- **order**: Sequential number starting from 0
|
||||
|
||||
## Available Layouts and Their Slots
|
||||
|
||||
### 1. title-slide
|
||||
**Purpose**: Opening slide with main title
|
||||
**Slots**:
|
||||
- `title`: Main presentation title (required)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "title-slide",
|
||||
"content": {
|
||||
"title": "Introduction to Machine Learning"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. content-slide
|
||||
**Purpose**: Standard content slide with title and body text
|
||||
**Slots**:
|
||||
- `title`: Slide title (required)
|
||||
- `content`: Main content area (multiline)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "content-slide",
|
||||
"content": {
|
||||
"title": "What is Machine Learning?",
|
||||
"content": "Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 2-content-blocks
|
||||
**Purpose**: Side-by-side content comparison
|
||||
**Slots**:
|
||||
- `title`: Slide title (required)
|
||||
- `content1`: First content block (required)
|
||||
- `content2`: Second content block (required)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "2-content-blocks",
|
||||
"content": {
|
||||
"title": "Supervised vs Unsupervised Learning",
|
||||
"content1": "Supervised Learning:\n• Uses labeled data\n• Predicts outcomes\n• Examples: Classification, Regression",
|
||||
"content2": "Unsupervised Learning:\n• Uses unlabeled data\n• Finds patterns\n• Examples: Clustering, Dimensionality Reduction"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. image-slide
|
||||
**Purpose**: Display an image with title
|
||||
**Slots**:
|
||||
- `title`: Slide title (required)
|
||||
- `image`: Image data URL or path
|
||||
- `image-alt`: Alt text for accessibility (hidden field)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "image-slide",
|
||||
"content": {
|
||||
"title": "Neural Network Architecture",
|
||||
"image": "data:image/png;base64,iVBORw0KG...",
|
||||
"image-alt": "Diagram showing layers of a neural network"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. code-slide
|
||||
**Purpose**: Display code with syntax highlighting
|
||||
**Slots**:
|
||||
- `title`: Code example title (required)
|
||||
- `code`: JavaScript code (multiline)
|
||||
- `notes`: Optional explanation
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "code-slide",
|
||||
"content": {
|
||||
"title": "Simple Neural Network in JavaScript",
|
||||
"code": "class NeuralNetwork {\n constructor(inputNodes, hiddenNodes, outputNodes) {\n this.inputNodes = inputNodes;\n this.hiddenNodes = hiddenNodes;\n this.outputNodes = outputNodes;\n }\n\n train(inputs, targets) {\n // Training logic here\n }\n}",
|
||||
"notes": "This example shows a basic neural network class structure"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. markdown-slide
|
||||
**Purpose**: Rich text content with markdown formatting
|
||||
**Slots**:
|
||||
- `title`: Slide title (required)
|
||||
- `content`: Markdown content (multiline)
|
||||
|
||||
Supported Markdown:
|
||||
- Headers: `#`, `##`, `###`
|
||||
- Bold: `**text**`
|
||||
- Italic: `*text*`
|
||||
- Lists: `-` or `1.`
|
||||
- Code: `` `inline` `` or code blocks
|
||||
- Links: `[text](url)`
|
||||
- Blockquotes: `> quote`
|
||||
- Tables: GitHub-flavored markdown
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Key Concepts",
|
||||
"content": "## Machine Learning Types\n\n### Supervised Learning\n- **Classification**: Predicting categories\n- **Regression**: Predicting continuous values\n\n### Unsupervised Learning\n- **Clustering**: Grouping similar data\n- **Dimensionality Reduction**: Simplifying complex data\n\n> \"The goal is to turn data into information, and information into insight.\" - Carly Fiorina"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. diagram-slide
|
||||
**Purpose**: Display Mermaid diagrams
|
||||
**Slots**:
|
||||
- `title`: Diagram title (required)
|
||||
- `diagram`: Mermaid diagram syntax (multiline)
|
||||
- `content2`: Additional content (required)
|
||||
- `notes`: Optional explanation
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"layoutId": "diagram-slide",
|
||||
"content": {
|
||||
"title": "ML Pipeline",
|
||||
"diagram": "graph LR\n A[Data Collection] --> B[Data Preprocessing]\n B --> C[Feature Engineering]\n C --> D[Model Training]\n D --> E[Model Evaluation]\n E --> F[Deployment]",
|
||||
"content2": "Each step in the pipeline is crucial for model success",
|
||||
"notes": "This diagram shows the typical machine learning workflow"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Content Guidelines
|
||||
|
||||
### Text Content:
|
||||
- Use `\n` for line breaks within multiline content
|
||||
- Escape quotes with backslash: `\"`
|
||||
- Keep titles concise (under 60 characters)
|
||||
- Use bullet points with `•` or `-`
|
||||
|
||||
### Markdown Content:
|
||||
- Use proper markdown syntax
|
||||
- Headers should start at `##` (title is already `#`)
|
||||
- Use backticks for code: `` `code` ``
|
||||
- Tables use pipe separators: `| Col1 | Col2 |`
|
||||
|
||||
### Code Content:
|
||||
- Properly escape special characters
|
||||
- Use `\n` for line breaks
|
||||
- Maintain proper indentation with spaces
|
||||
- Currently optimized for JavaScript syntax
|
||||
|
||||
### Diagram Content:
|
||||
- Use valid Mermaid syntax
|
||||
- Common types: graph, flowchart, sequenceDiagram
|
||||
- Direction: LR (left-right), TD (top-down), etc.
|
||||
|
||||
## Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"id": "pres-1704067200000-abc123",
|
||||
"name": "Introduction to Machine Learning",
|
||||
"description": "A comprehensive overview of ML concepts and applications",
|
||||
"theme": "default",
|
||||
"aspectRatio": "16:9",
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z"
|
||||
},
|
||||
"slides": [
|
||||
{
|
||||
"id": "slide-0-1704067200000",
|
||||
"layoutId": "title-slide",
|
||||
"content": {
|
||||
"title": "Introduction to Machine Learning"
|
||||
},
|
||||
"order": 0
|
||||
},
|
||||
{
|
||||
"id": "slide-1-1704067200001",
|
||||
"layoutId": "content-slide",
|
||||
"content": {
|
||||
"title": "What is Machine Learning?",
|
||||
"content": "Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.\n\nKey characteristics:\n• Data-driven approach\n• Pattern recognition\n• Predictive capabilities\n• Continuous improvement"
|
||||
},
|
||||
"notes": "Emphasize the difference between traditional programming and ML",
|
||||
"order": 1
|
||||
},
|
||||
{
|
||||
"id": "slide-2-1704067200002",
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Types of Machine Learning",
|
||||
"content": "## Three Main Categories\n\n### 1. Supervised Learning\n**Training with labeled data**\n- Classification: Spam detection, image recognition\n- Regression: Price prediction, weather forecasting\n\n### 2. Unsupervised Learning\n**Finding patterns in unlabeled data**\n- Clustering: Customer segmentation\n- Dimensionality reduction: PCA, t-SNE\n\n### 3. Reinforcement Learning\n**Learning through interaction**\n- Game playing: Chess, Go\n- Robotics: Navigation, manipulation"
|
||||
},
|
||||
"order": 2
|
||||
},
|
||||
{
|
||||
"id": "slide-3-1704067200003",
|
||||
"layoutId": "code-slide",
|
||||
"content": {
|
||||
"title": "Simple Linear Regression",
|
||||
"code": "// Simple linear regression in JavaScript\nfunction linearRegression(data) {\n const n = data.length;\n let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;\n \n for (let point of data) {\n sumX += point.x;\n sumY += point.y;\n sumXY += point.x * point.y;\n sumX2 += point.x * point.x;\n }\n \n const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);\n const intercept = (sumY - slope * sumX) / n;\n \n return { slope, intercept };\n}",
|
||||
"notes": "This implements the least squares method for finding the best-fit line"
|
||||
},
|
||||
"order": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
1. **Layout IDs must exactly match**: Use only the 7 layouts listed above
|
||||
2. **Required slots cannot be empty**: Each layout has required slots marked
|
||||
3. **Use proper JSON escaping**: Escape quotes, backslashes, and special characters
|
||||
4. **Maintain slide order**: Order field should be sequential starting from 0
|
||||
5. **Generate unique IDs**: Each slide and presentation needs unique identifiers
|
||||
6. **Theme must be "default"**: Currently the only supported theme
|
||||
7. **Valid aspect ratios only**: Must be "16:9", "4:3", or "16:10"
|
||||
8. **Multiline content**: Use `\n` for line breaks in content fields
|
||||
|
||||
## File Naming
|
||||
|
||||
When saving the JSON file, use this format:
|
||||
- Replace spaces with `%20` or `-`
|
||||
- Replace special characters with URL encoding
|
||||
- Add `.json` extension
|
||||
- Example: `"My Presentation!"` becomes `My%20Presentation%21.json` or `my-presentation.json`
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before generating, verify:
|
||||
- [ ] All required metadata fields are present
|
||||
- [ ] Theme is "default"
|
||||
- [ ] Aspect ratio is valid ("16:9", "4:3", or "16:10")
|
||||
- [ ] Each slide has unique ID
|
||||
- [ ] Layout IDs match available layouts exactly
|
||||
- [ ] Required slots for each layout are filled
|
||||
- [ ] Content is properly escaped for JSON
|
||||
- [ ] Order fields are sequential from 0
|
||||
- [ ] Timestamps are valid ISO 8601 format
|
159
onlinePresentation.json
Normal file
159
onlinePresentation.json
Normal file
@ -0,0 +1,159 @@
|
||||
{
|
||||
"metadata": {
|
||||
"id": "pres-1736712000000-slideshare",
|
||||
"name": "SlideShare: Modern Web-Based Presentation Tool",
|
||||
"description": "An overview of the SlideShare presentation authoring and delivery platform",
|
||||
"theme": "default",
|
||||
"aspectRatio": "16:9",
|
||||
"createdAt": "2025-01-12T18:00:00.000Z",
|
||||
"updatedAt": "2025-01-12T18:00:00.000Z"
|
||||
},
|
||||
"slides": [
|
||||
{
|
||||
"id": "slide-0-1736712000000",
|
||||
"layoutId": "title-slide",
|
||||
"content": {
|
||||
"title": "SlideShare: Modern Presentation Authoring"
|
||||
},
|
||||
"order": 0
|
||||
},
|
||||
{
|
||||
"id": "slide-1-1736712000001",
|
||||
"layoutId": "content-slide",
|
||||
"content": {
|
||||
"title": "What is SlideShare?",
|
||||
"content": "A powerful, browser-based presentation tool that works entirely offline.\n\n• No backend server required\n• All data stored locally in IndexedDB\n• Built with React and modern web technologies\n• Theme-based design system\n• Export and share presentations as JSON"
|
||||
},
|
||||
"notes": "Emphasize the offline-first approach and zero server dependency",
|
||||
"order": 1
|
||||
},
|
||||
{
|
||||
"id": "slide-2-1736712000002",
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Key Features",
|
||||
"content": "## Core Capabilities\n\n### 📝 **Intuitive Slide Authoring**\n- Visual slide editor with live preview\n- Multiple layout options per theme\n- Drag-and-drop content placement\n- Rich text and markdown support\n\n### 🎨 **Flexible Theming System**\n- Pre-built professional themes\n- Customizable layouts with HTML/CSS\n- Consistent design across presentations\n- Theme switching without content loss\n\n### 💾 **Local-First Architecture**\n- Works completely offline\n- IndexedDB for persistent storage\n- Export/import JSON presentations\n- No cloud dependency or subscription"
|
||||
},
|
||||
"order": 2
|
||||
},
|
||||
{
|
||||
"id": "slide-3-1736712000003",
|
||||
"layoutId": "2-content-blocks",
|
||||
"content": {
|
||||
"title": "Traditional vs SlideShare",
|
||||
"content1": "**Traditional Tools:**\n\n• Require installation\n• Platform-specific\n• Subscription-based\n• Cloud dependency\n• Limited customization\n• Proprietary formats",
|
||||
"content2": "**SlideShare:**\n\n• Runs in any browser\n• Cross-platform\n• Free and open\n• Works offline\n• Fully customizable\n• Open JSON format"
|
||||
},
|
||||
"notes": "Highlight the advantages of web-based, local-first approach",
|
||||
"order": 3
|
||||
},
|
||||
{
|
||||
"id": "slide-4-1736712000004",
|
||||
"layoutId": "diagram-slide",
|
||||
"content": {
|
||||
"title": "Architecture Overview",
|
||||
"diagram": "graph TD\n A[Browser] --> B[React Application]\n B --> C[Presentation Editor]\n B --> D[Theme Engine]\n B --> E[Storage Layer]\n C --> F[Slide Components]\n D --> G[Layout Templates]\n D --> H[CSS Styles]\n E --> I[IndexedDB]\n E --> J[JSON Export/Import]",
|
||||
"content2": "Clean separation of concerns with modular architecture",
|
||||
"notes": "The architecture emphasizes modularity and browser-native technologies"
|
||||
},
|
||||
"order": 4
|
||||
},
|
||||
{
|
||||
"id": "slide-5-1736712000005",
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Theme System Design",
|
||||
"content": "## Powerful & Flexible\n\n### **Layout Templates**\n- Simple HTML with Handlebars syntax\n- Semantic slot-based content areas\n- Multiple layouts per theme\n\n### **Styling with CSS**\n- CSS custom properties for theming\n- Responsive design built-in\n- Print-friendly styles included\n\n### **Content Slots**\n```html\n<div class=\"slot\" \n data-slot=\"title\" \n data-placeholder=\"Enter title\">\n {{title}}\n</div>\n```\n\n> Designers can create themes using only HTML and CSS - no JavaScript required!"
|
||||
},
|
||||
"order": 5
|
||||
},
|
||||
{
|
||||
"id": "slide-6-1736712000006",
|
||||
"layoutId": "code-slide",
|
||||
"content": {
|
||||
"title": "Simple Theme Creation",
|
||||
"code": "<!-- layout-quote.html -->\n<div class=\"slide layout-quote\">\n <blockquote class=\"slot quote-text\" \n data-slot=\"quote\"\n data-placeholder=\"Enter quote...\">\n {{quote}}\n </blockquote>\n \n <cite class=\"slot author-text\"\n data-slot=\"author\"\n data-placeholder=\"Author name\">\n — {{author}}\n </cite>\n</div>\n\n/* style.css */\n.layout-quote {\n justify-content: center;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n}\n\n.layout-quote blockquote {\n font-size: 2.5rem;\n font-style: italic;\n color: white;\n}",
|
||||
"notes": "Creating a new layout is as simple as HTML and CSS"
|
||||
},
|
||||
"order": 6
|
||||
},
|
||||
{
|
||||
"id": "slide-7-1736712000007",
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Why It's Valuable",
|
||||
"content": "## 🚀 **For Individuals**\n- **Zero Cost**: No subscriptions or licenses\n- **Privacy First**: Your data stays on your device\n- **Work Anywhere**: No internet required after initial load\n- **Full Control**: Export and own your content\n\n## 🏢 **For Organizations**\n- **Self-Hosted**: Deploy on internal networks\n- **Customizable**: Brand-aligned themes\n- **Secure**: No data leaves the organization\n- **Standardized**: Consistent presentation format\n\n## 👩💻 **For Developers**\n- **Open Source**: Extend and customize freely\n- **Modern Stack**: React, TypeScript, Vite\n- **Clean Architecture**: Easy to understand and modify\n- **Theme API**: Create custom themes with HTML/CSS"
|
||||
},
|
||||
"order": 7
|
||||
},
|
||||
{
|
||||
"id": "slide-8-1736712000008",
|
||||
"layoutId": "content-slide",
|
||||
"content": {
|
||||
"title": "Technical Excellence",
|
||||
"content": "Built with modern best practices:\n\n• React 19 with latest patterns\n• TypeScript for type safety\n• Vite for fast development\n• IndexedDB for robust storage\n• Semantic HTML for accessibility\n• CSS custom properties for theming\n• Responsive design principles\n• Clean, maintainable codebase"
|
||||
},
|
||||
"notes": "Emphasize the modern, professional development approach",
|
||||
"order": 8
|
||||
},
|
||||
{
|
||||
"id": "slide-9-1736712000009",
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Content Types Supported",
|
||||
"content": "## Rich Content Options\n\n### 📝 **Text & Typography**\n- Plain text with formatting\n- Markdown with full syntax support\n- Custom fonts per theme\n\n### 🖼️ **Visual Elements**\n- Images with drag-and-drop upload\n- Mermaid diagrams for flowcharts\n- Code blocks with syntax highlighting\n\n### 🎯 **Layout Varieties**\n- Title slides\n- Content slides\n- Two-column layouts\n- Image showcases\n- Code demonstrations\n- Diagram presentations\n- Markdown-rich content"
|
||||
},
|
||||
"order": 9
|
||||
},
|
||||
{
|
||||
"id": "slide-10-1736712000010",
|
||||
"layoutId": "2-content-blocks",
|
||||
"content": {
|
||||
"title": "Export & Sharing",
|
||||
"content1": "**Export Options:**\n\n• JSON format for data portability\n• URL-encoded filenames\n• Complete presentation structure\n• Theme information included\n• Easy backup and restore",
|
||||
"content2": "**Sharing Features:**\n\n• Send JSON files directly\n• Import into any instance\n• Version control friendly\n• Collaborative workflows\n• Future: PDF export planned"
|
||||
},
|
||||
"order": 10
|
||||
},
|
||||
{
|
||||
"id": "slide-11-1736712000011",
|
||||
"layoutId": "diagram-slide",
|
||||
"content": {
|
||||
"title": "Development Workflow",
|
||||
"diagram": "graph LR\n A[Create Theme] --> B[Design Layouts]\n B --> C[Author Content]\n C --> D[Preview Live]\n D --> E{Happy?}\n E -->|No| C\n E -->|Yes| F[Present]\n F --> G[Export/Share]\n \n style A fill:#667eea\n style F fill:#764ba2\n style G fill:#f093fb",
|
||||
"content2": "Streamlined workflow from creation to presentation",
|
||||
"notes": "The workflow is designed to be intuitive and efficient"
|
||||
},
|
||||
"order": 11
|
||||
},
|
||||
{
|
||||
"id": "slide-12-1736712000012",
|
||||
"layoutId": "content-slide",
|
||||
"content": {
|
||||
"title": "Future Roadmap",
|
||||
"content": "Planned enhancements:\n\n• Additional built-in themes\n• Theme marketplace/gallery\n• PDF export functionality\n• Presenter mode with notes view\n• Animation and transitions\n• Real-time collaboration\n• Plugin system for extensions\n• Mobile app for presenting"
|
||||
},
|
||||
"notes": "The project has an ambitious roadmap while maintaining simplicity",
|
||||
"order": 12
|
||||
},
|
||||
{
|
||||
"id": "slide-13-1736712000013",
|
||||
"layoutId": "markdown-slide",
|
||||
"content": {
|
||||
"title": "Get Started Today",
|
||||
"content": "## 🎯 **Quick Start**\n\n### **For Users:**\n1. Open SlideShare in your browser\n2. Create a new presentation\n3. Choose a theme\n4. Add slides and content\n5. Present or export\n\n### **For Developers:**\n```bash\n# Clone the repository\ngit clone [repo-url]\n\n# Install dependencies\nnpm install\n\n# Start development\nnpm run dev\n```\n\n### **For Designers:**\n- Create custom themes with HTML/CSS\n- No programming knowledge required\n- Use the built-in theme as a template"
|
||||
},
|
||||
"order": 13
|
||||
},
|
||||
{
|
||||
"id": "slide-14-1736712000014",
|
||||
"layoutId": "content-slide",
|
||||
"content": {
|
||||
"title": "Summary",
|
||||
"content": "SlideShare revolutionizes presentations:\n\n✅ Works entirely offline\n✅ No server or subscription required\n✅ Professional themes and layouts\n✅ Export and own your data\n✅ Open source and extensible\n✅ Modern web technologies\n✅ Privacy-focused design\n\nThe future of presentations is local-first, open, and free."
|
||||
},
|
||||
"notes": "Reinforce the key value propositions",
|
||||
"order": 14
|
||||
}
|
||||
]
|
||||
}
|
@ -15,5 +15,5 @@
|
||||
"hasMasterSlide": true
|
||||
}
|
||||
},
|
||||
"generated": "2025-08-22T11:35:39.977Z"
|
||||
"generated": "2025-09-12T13:05:24.316Z"
|
||||
}
|
@ -12,18 +12,5 @@
|
||||
data-type="diagram"
|
||||
data-placeholder="Enter Mermaid diagram syntax here..."
|
||||
data-multiline="true">{{diagram}}</div>
|
||||
|
||||
<div class="slot content-slot" data-slot="content2" data-placeholder="Second content block" data-required>
|
||||
<span class="fade-in-slow">
|
||||
{{content2}}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="slot notes-content"
|
||||
data-slot="notes"
|
||||
data-type="text"
|
||||
data-placeholder="Optional diagram explanation..."
|
||||
data-multiline="true">
|
||||
{{notes}}
|
||||
</div>
|
||||
|
||||
</div>
|
@ -1,4 +1,15 @@
|
||||
<div class="fade-in slide layout-image-slide">
|
||||
<style>
|
||||
.layout-image-slide .image-container {
|
||||
padding-top: 5%;
|
||||
}
|
||||
.image-container img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="I slide layout-image-slide">
|
||||
<h1 class="slot title-slot" data-slot="title" data-placeholder="Slide Title" data-required>
|
||||
{{title}}
|
||||
</h1>
|
||||
|
@ -331,6 +331,7 @@
|
||||
|
||||
/* Mermaid diagram styling */
|
||||
.mermaid-container {
|
||||
width: 100%;
|
||||
margin: 2rem 0;
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
@ -339,6 +340,10 @@
|
||||
text-align: center;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.layout-diagram-slide > .content-slot {
|
||||
max-height: 50%;
|
||||
border: 1px solid var(--theme-secondary);
|
||||
}
|
||||
|
||||
.mermaid-diagram {
|
||||
max-width: 100%;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { Presentation } from '../../types/presentation.ts';
|
||||
import { getAllPresentations, deletePresentation } from '../../utils/presentationStorage.ts';
|
||||
import { getAllPresentations, deletePresentation, importPresentation } from '../../utils/presentationStorage.ts';
|
||||
import { loggers } from '../../utils/logger.ts';
|
||||
import { ConfirmDialog } from '../ui/ConfirmDialog.tsx';
|
||||
import { AlertDialog } from '../ui/AlertDialog.tsx';
|
||||
@ -27,6 +27,8 @@ export const PresentationsList: React.FC = () => {
|
||||
message: string;
|
||||
type?: 'error' | 'success';
|
||||
}>({ isOpen: false, message: '' });
|
||||
|
||||
const [importing, setImporting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadPresentations();
|
||||
@ -79,6 +81,76 @@ export const PresentationsList: React.FC = () => {
|
||||
};
|
||||
|
||||
|
||||
const handleImportPresentation = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.name.endsWith('.json')) {
|
||||
setAlertDialog({
|
||||
isOpen: true,
|
||||
message: 'Please select a valid JSON file.',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setImporting(true);
|
||||
|
||||
try {
|
||||
const text = await file.text();
|
||||
let jsonData;
|
||||
|
||||
try {
|
||||
jsonData = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
setAlertDialog({
|
||||
isOpen: true,
|
||||
message: 'Invalid JSON format. Please check the file.',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const importedPresentation = await importPresentation(jsonData);
|
||||
await loadPresentations();
|
||||
|
||||
setAlertDialog({
|
||||
isOpen: true,
|
||||
message: `Successfully imported "${importedPresentation.metadata.name}"`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
loggers.presentation.error('Failed to import presentation', error instanceof Error ? error : new Error(String(error)));
|
||||
setAlertDialog({
|
||||
isOpen: true,
|
||||
message: error instanceof Error ? error.message : 'Failed to import presentation. Please check the file format.',
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
setImporting(false);
|
||||
// Reset the input
|
||||
event.target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadPresentation = (presentation: Presentation) => {
|
||||
const filename = presentation.metadata.name
|
||||
.replace(/[^a-zA-Z0-9]/g, (char) => encodeURIComponent(char))
|
||||
.toLowerCase() + '.json';
|
||||
|
||||
const dataStr = JSON.stringify(presentation, null, 2);
|
||||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(dataBlob);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
@ -123,6 +195,23 @@ export const PresentationsList: React.FC = () => {
|
||||
<h1>My Presentations</h1>
|
||||
<p>Manage and organize your presentation library</p>
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
<input
|
||||
type="file"
|
||||
id="import-presentation"
|
||||
accept=".json"
|
||||
onChange={handleImportPresentation}
|
||||
style={{ display: 'none' }}
|
||||
disabled={importing}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => document.getElementById('import-presentation')?.click()}
|
||||
disabled={importing}
|
||||
>
|
||||
{importing ? 'Importing...' : 'Import JSON'}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="list-content">
|
||||
@ -162,6 +251,14 @@ export const PresentationsList: React.FC = () => {
|
||||
>
|
||||
✏️
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="action-icon"
|
||||
onClick={() => handleDownloadPresentation(presentation)}
|
||||
title="Download as JSON"
|
||||
>
|
||||
↧
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="action-icon delete"
|
||||
@ -234,6 +331,21 @@ export const PresentationsList: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="footer-actions">
|
||||
<input
|
||||
type="file"
|
||||
id="import-presentation-footer"
|
||||
accept=".json"
|
||||
onChange={handleImportPresentation}
|
||||
style={{ display: 'none' }}
|
||||
disabled={importing}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => document.getElementById('import-presentation-footer')?.click()}
|
||||
disabled={importing}
|
||||
>
|
||||
{importing ? 'Importing...' : 'Import'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => navigate('/presentations/new')}
|
||||
|
@ -50,17 +50,26 @@ const DEFAULT_SLIDE_CONFIG: Required<SanitizeConfig> = {
|
||||
'blockquote', 'cite',
|
||||
// Code elements
|
||||
'pre', 'code',
|
||||
// Style element for template-specific styles
|
||||
'style',
|
||||
// SVG elements for diagrams
|
||||
'svg', 'g', 'path', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon',
|
||||
'text', 'tspan', 'defs', 'marker', 'pattern', 'foreignObject'
|
||||
'text', 'tspan', 'defs', 'marker', 'pattern', 'foreignObject', 'use', 'symbol',
|
||||
'linearGradient', 'radialGradient', 'stop', 'clipPath', 'mask'
|
||||
],
|
||||
allowedAttributes: [
|
||||
'class', 'id', 'style', 'data-*',
|
||||
'class', 'id', 'style', 'data-*', 'type',
|
||||
// SVG attributes
|
||||
'xmlns', 'viewBox', 'width', 'height', 'd', 'x', 'y', 'x1', 'y1', 'x2', 'y2',
|
||||
'cx', 'cy', 'r', 'rx', 'ry', 'fill', 'stroke', 'stroke-width', 'stroke-dasharray',
|
||||
'transform', 'points', 'font-family', 'font-size', 'text-anchor', 'dominant-baseline',
|
||||
'markerWidth', 'markerHeight', 'orient', 'refX', 'refY'
|
||||
// Marker attributes for arrow heads
|
||||
'markerWidth', 'markerHeight', 'orient', 'refX', 'refY', 'markerUnits',
|
||||
'marker-start', 'marker-end', 'marker-mid',
|
||||
// Additional SVG attributes needed by Mermaid
|
||||
'preserveAspectRatio', 'xmlns:xlink', 'href', 'xlink:href', 'path',
|
||||
'opacity', 'fill-opacity', 'stroke-opacity', 'stroke-linejoin', 'stroke-linecap',
|
||||
'stroke-miterlimit', 'patternUnits', 'patternTransform'
|
||||
]
|
||||
};
|
||||
|
||||
@ -86,16 +95,19 @@ export function sanitizeHtml(html: string, config: SanitizeConfig = {}): string
|
||||
FORBID_ATTR: string[];
|
||||
KEEP_CONTENT: boolean;
|
||||
ALLOW_DATA_ATTR: boolean;
|
||||
FORCE_BODY: boolean;
|
||||
} = {
|
||||
ALLOWED_TAGS: finalConfig.allowedTags,
|
||||
ALLOWED_ATTR: finalConfig.allowedAttributes,
|
||||
// Remove any scripts or dangerous content
|
||||
FORBID_TAGS: ['script', 'object', 'embed', 'base', 'link', 'meta', 'style'],
|
||||
FORBID_TAGS: ['script', 'object', 'embed', 'base', 'link', 'meta'],
|
||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur'],
|
||||
// Keep content structure but remove dangerous elements
|
||||
KEEP_CONTENT: true,
|
||||
// Allow data attributes for slots and styling
|
||||
ALLOW_DATA_ATTR: true
|
||||
ALLOW_DATA_ATTR: true,
|
||||
// Force style tags to be preserved
|
||||
FORCE_BODY: true
|
||||
};
|
||||
|
||||
// Add image support if requested
|
||||
|
@ -211,4 +211,71 @@ export const getPresentationsByTheme = async (theme: string): Promise<Presentati
|
||||
export const getPresentationMetadata = async (): Promise<PresentationMetadata[]> => {
|
||||
const presentations = await getAllPresentations();
|
||||
return presentations.map(p => p.metadata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate imported presentation structure
|
||||
*/
|
||||
const validatePresentation = (data: any): data is Presentation => {
|
||||
// Check required metadata fields
|
||||
if (!data.metadata || typeof data.metadata !== 'object') return false;
|
||||
if (!data.metadata.id || typeof data.metadata.id !== 'string') return false;
|
||||
if (!data.metadata.name || typeof data.metadata.name !== 'string') return false;
|
||||
if (!data.metadata.theme || typeof data.metadata.theme !== 'string') return false;
|
||||
if (!data.metadata.aspectRatio || !['16:9', '4:3', '16:10'].includes(data.metadata.aspectRatio)) return false;
|
||||
|
||||
// Check slides array
|
||||
if (!Array.isArray(data.slides)) return false;
|
||||
|
||||
// Validate each slide
|
||||
for (const slide of data.slides) {
|
||||
if (!slide.id || typeof slide.id !== 'string') return false;
|
||||
if (!slide.layoutId || typeof slide.layoutId !== 'string') return false;
|
||||
if (!slide.content || typeof slide.content !== 'object') return false;
|
||||
if (typeof slide.order !== 'number') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Import a presentation from JSON
|
||||
*/
|
||||
export const importPresentation = async (jsonData: any): Promise<Presentation> => {
|
||||
// Validate the structure
|
||||
if (!validatePresentation(jsonData)) {
|
||||
throw new Error('Invalid presentation format. Please check the JSON structure.');
|
||||
}
|
||||
|
||||
const db = await initializeDB();
|
||||
|
||||
// Generate a new ID to avoid conflicts
|
||||
const importedPresentation: Presentation = {
|
||||
metadata: {
|
||||
...jsonData.metadata,
|
||||
id: generatePresentationId(),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
slides: jsonData.slides
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction([PRESENTATIONS_STORE], 'readwrite');
|
||||
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
||||
|
||||
const request = store.add(importedPresentation);
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('Failed to import presentation'));
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(importedPresentation);
|
||||
};
|
||||
|
||||
transaction.onerror = () => {
|
||||
reject(new Error('Transaction failed while importing presentation'));
|
||||
};
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue
Block a user