...
This commit is contained in:
		@@ -6,6 +6,20 @@ use crate::models::User;
 | 
			
		||||
pub struct HomeController;
 | 
			
		||||
 | 
			
		||||
impl HomeController {
 | 
			
		||||
    /// Handles the markdown editor page route
 | 
			
		||||
    pub async fn editor(tmpl: web::Data<Tera>) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "editor");
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("editor.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the home page route
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
 | 
			
		||||
            .route("/about", web::get().to(HomeController::about))
 | 
			
		||||
            .route("/contact", web::get().to(HomeController::contact))
 | 
			
		||||
            .route("/contact", web::post().to(HomeController::submit_contact))
 | 
			
		||||
            .route("/editor", web::get().to(HomeController::editor))
 | 
			
		||||
            
 | 
			
		||||
            // Auth routes
 | 
			
		||||
            .route("/login", web::get().to(AuthController::login_page))
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,9 @@
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'tickets' %}active{% endif %}" href="/tickets">Support Tickets</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'editor' %}active{% endif %}" href="/editor">Markdown Editor</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
                <ul class="navbar-nav ms-auto">
 | 
			
		||||
                    {% if user and user.id %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										312
									
								
								actix_mvc_app/src/views/editor.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								actix_mvc_app/src/views/editor.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,312 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Markdown Editor{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_css %}
 | 
			
		||||
<link rel="preconnect" href="https://fonts.googleapis.com">
 | 
			
		||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 | 
			
		||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Merriweather:ital,wght@0,400;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
 | 
			
		||||
<style>
 | 
			
		||||
    :root {
 | 
			
		||||
        --editor-bg: #f8f9fa;
 | 
			
		||||
        --preview-bg: #ffffff;
 | 
			
		||||
        --border-color: #e9ecef;
 | 
			
		||||
        --text-color: #212529;
 | 
			
		||||
        --font-mono: 'JetBrains Mono', monospace;
 | 
			
		||||
        --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
 | 
			
		||||
        --font-serif: 'Merriweather', Georgia, serif;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .editor-container {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        height: calc(100vh - 250px);
 | 
			
		||||
        min-height: 500px;
 | 
			
		||||
        border: 1px solid var(--border-color);
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        margin-bottom: 20px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .editor-pane, .preview-pane {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        padding: 20px;
 | 
			
		||||
        overflow-y: auto;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .editor-pane {
 | 
			
		||||
        background-color: var(--editor-bg);
 | 
			
		||||
        border-right: 1px solid var(--border-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .preview-pane {
 | 
			
		||||
        background-color: var(--preview-bg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #editor {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        border: none;
 | 
			
		||||
        background-color: var(--editor-bg);
 | 
			
		||||
        font-family: var(--font-mono);
 | 
			
		||||
        font-size: 15px;
 | 
			
		||||
        line-height: 1.6;
 | 
			
		||||
        resize: none;
 | 
			
		||||
        outline: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview {
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Typography for the preview pane */
 | 
			
		||||
    #preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 {
 | 
			
		||||
        margin-top: 1.5em;
 | 
			
		||||
        margin-bottom: 0.75em;
 | 
			
		||||
        font-family: var(--font-sans);
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
        line-height: 1.2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview h1 {
 | 
			
		||||
        font-size: 2.25rem;
 | 
			
		||||
        border-bottom: 1px solid var(--border-color);
 | 
			
		||||
        padding-bottom: 0.3em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview h2 {
 | 
			
		||||
        font-size: 1.75rem;
 | 
			
		||||
        border-bottom: 1px solid var(--border-color);
 | 
			
		||||
        padding-bottom: 0.3em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview h3 {
 | 
			
		||||
        font-size: 1.5rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview h4 {
 | 
			
		||||
        font-size: 1.25rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview p {
 | 
			
		||||
        margin-bottom: 1.25em;
 | 
			
		||||
        font-family: var(--font-serif);
 | 
			
		||||
        font-size: 1.05rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview a {
 | 
			
		||||
        color: #0366d6;
 | 
			
		||||
        text-decoration: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview a:hover {
 | 
			
		||||
        text-decoration: underline;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview code {
 | 
			
		||||
        font-family: var(--font-mono);
 | 
			
		||||
        background-color: rgba(27, 31, 35, 0.05);
 | 
			
		||||
        padding: 0.2em 0.4em;
 | 
			
		||||
        border-radius: 3px;
 | 
			
		||||
        font-size: 0.9em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview pre {
 | 
			
		||||
        background-color: #f6f8fa;
 | 
			
		||||
        border-radius: 3px;
 | 
			
		||||
        padding: 16px;
 | 
			
		||||
        overflow: auto;
 | 
			
		||||
        margin-bottom: 1.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview pre code {
 | 
			
		||||
        background-color: transparent;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        font-size: 0.9em;
 | 
			
		||||
        line-height: 1.5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview blockquote {
 | 
			
		||||
        padding: 0 1em;
 | 
			
		||||
        color: #6a737d;
 | 
			
		||||
        border-left: 0.25em solid #dfe2e5;
 | 
			
		||||
        margin-bottom: 1.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview ul, #preview ol {
 | 
			
		||||
        padding-left: 2em;
 | 
			
		||||
        margin-bottom: 1.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview li {
 | 
			
		||||
        margin-bottom: 0.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview table {
 | 
			
		||||
        border-collapse: collapse;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        margin-bottom: 1.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview table th, #preview table td {
 | 
			
		||||
        padding: 8px 12px;
 | 
			
		||||
        border: 1px solid #dfe2e5;
 | 
			
		||||
        text-align: left;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview table th {
 | 
			
		||||
        background-color: #f6f8fa;
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview table tr:nth-child(even) {
 | 
			
		||||
        background-color: #f8f9fa;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #preview img {
 | 
			
		||||
        max-width: 100%;
 | 
			
		||||
        margin-bottom: 1.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .editor-header {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .editor-actions {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        gap: 10px;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="editor-header">
 | 
			
		||||
    <h1>Markdown Editor</h1>
 | 
			
		||||
    <div class="editor-actions">
 | 
			
		||||
        <button class="btn btn-outline-secondary" id="copy-html">Copy HTML</button>
 | 
			
		||||
        <button class="btn btn-outline-secondary" id="copy-markdown">Copy Markdown</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="editor-container">
 | 
			
		||||
    <div class="editor-pane">
 | 
			
		||||
        <textarea id="editor" spellcheck="false" placeholder="Type your markdown here..."></textarea>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="preview-pane">
 | 
			
		||||
        <div id="preview"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
 | 
			
		||||
<script>
 | 
			
		||||
    // Set up marked.js options
 | 
			
		||||
    marked.setOptions({
 | 
			
		||||
        breaks: true,
 | 
			
		||||
        gfm: true,
 | 
			
		||||
        headerIds: true,
 | 
			
		||||
        highlight: function(code, lang) {
 | 
			
		||||
            return code;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const editor = document.getElementById('editor');
 | 
			
		||||
    const preview = document.getElementById('preview');
 | 
			
		||||
    const copyHtmlBtn = document.getElementById('copy-html');
 | 
			
		||||
    const copyMarkdownBtn = document.getElementById('copy-markdown');
 | 
			
		||||
 | 
			
		||||
    // Example markdown content
 | 
			
		||||
    const exampleMarkdown = `# Markdown Editor
 | 
			
		||||
 | 
			
		||||
## Introduction
 | 
			
		||||
 | 
			
		||||
This is a real-time markdown editor with a split-screen layout. Type markdown on the left, and see the rendered output on the right.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- **Real-time rendering**: See your changes instantly
 | 
			
		||||
- **Split-screen layout**: Edit and preview side by side
 | 
			
		||||
- **Beautiful typography**: Using high-quality fonts for readability
 | 
			
		||||
- **Copy functionality**: Easily copy your markdown or HTML
 | 
			
		||||
 | 
			
		||||
## Code Example
 | 
			
		||||
 | 
			
		||||
\`\`\`javascript
 | 
			
		||||
function renderMarkdown() {
 | 
			
		||||
    const markdown = editor.value;
 | 
			
		||||
    const html = marked.parse(markdown);
 | 
			
		||||
    preview.innerHTML = html;
 | 
			
		||||
}
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## Table Example
 | 
			
		||||
 | 
			
		||||
| Feature | Description |
 | 
			
		||||
|---------|-------------|
 | 
			
		||||
| Real-time Preview | See changes as you type |
 | 
			
		||||
| Syntax Highlighting | Makes code easier to read |
 | 
			
		||||
| Tables | Format data in rows and columns |
 | 
			
		||||
| Lists | Create ordered and unordered lists |
 | 
			
		||||
 | 
			
		||||
## Blockquote Example
 | 
			
		||||
 | 
			
		||||
> The best way to predict the future is to invent it.
 | 
			
		||||
> 
 | 
			
		||||
> — Alan Kay
 | 
			
		||||
 | 
			
		||||
## Task List
 | 
			
		||||
 | 
			
		||||
- [x] Create markdown editor
 | 
			
		||||
- [x] Add real-time preview
 | 
			
		||||
- [x] Style with nice typography
 | 
			
		||||
- [ ] Add more features
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
*Made with ❤️ using Actix Web, Tera templates, and JavaScript*`;
 | 
			
		||||
 | 
			
		||||
    // Set initial content
 | 
			
		||||
    editor.value = exampleMarkdown;
 | 
			
		||||
 | 
			
		||||
    // Function to render markdown
 | 
			
		||||
    function renderMarkdown() {
 | 
			
		||||
        const markdown = editor.value;
 | 
			
		||||
        const html = marked.parse(markdown);
 | 
			
		||||
        preview.innerHTML = html;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initial render
 | 
			
		||||
    renderMarkdown();
 | 
			
		||||
 | 
			
		||||
    // Real-time rendering
 | 
			
		||||
    editor.addEventListener('input', renderMarkdown);
 | 
			
		||||
 | 
			
		||||
    // Copy HTML functionality
 | 
			
		||||
    copyHtmlBtn.addEventListener('click', function() {
 | 
			
		||||
        const html = preview.innerHTML;
 | 
			
		||||
        navigator.clipboard.writeText(html)
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                alert('HTML copied to clipboard!');
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => {
 | 
			
		||||
                console.error('Failed to copy HTML: ', err);
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Copy Markdown functionality
 | 
			
		||||
    copyMarkdownBtn.addEventListener('click', function() {
 | 
			
		||||
        const markdown = editor.value;
 | 
			
		||||
        navigator.clipboard.writeText(markdown)
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                alert('Markdown copied to clipboard!');
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => {
 | 
			
		||||
                console.error('Failed to copy Markdown: ', err);
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user