fixed Lua Runtime. Plugins now have priority for hooks and POST for routes if requested. also changed the CSS for main.css to color #1e1e1e for darkmode body
This commit is contained in:
530
lua/plugins/examples/demo/test.lua
Normal file
530
lua/plugins/examples/demo/test.lua
Normal file
@@ -0,0 +1,530 @@
|
||||
|
||||
log("Loading Comprehensive Example Plugin...")
|
||||
|
||||
-- ============================================================================
|
||||
-- PART 1: HOOK CHAINING DEMONSTRATION
|
||||
-- Multiple hooks that modify HTML content sequentially
|
||||
-- ============================================================================
|
||||
|
||||
-- Hook 1: Add custom CSS (Priority 10 - runs first)
|
||||
register_hook("post_render", function(filepath, html_content)
|
||||
log("Hook 1 (Priority 10): Adding custom CSS to " .. filepath)
|
||||
|
||||
local custom_css = [[
|
||||
<style>
|
||||
.plugin-banner {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
font-family: 'Arial', sans-serif;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.plugin-footer {
|
||||
background: #2d3748;
|
||||
color: #cbd5e0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.enhanced-code {
|
||||
border-left: 4px solid #667eea;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
]]
|
||||
|
||||
-- Insert CSS before closing head tag
|
||||
local modified = html_insert_before(html_content, "</head>", custom_css)
|
||||
return modified -- Pass to next hook
|
||||
end, 10)
|
||||
|
||||
-- Hook 2: Add banner (Priority 20 - runs second)
|
||||
register_hook("post_render", function(filepath, html_content)
|
||||
log("Hook 2 (Priority 20): Adding banner")
|
||||
|
||||
local banner = [[
|
||||
<div class="plugin-banner">
|
||||
<strong>🚀 Enhanced by Plugin System</strong> |
|
||||
<span style="opacity: 0.8;">Processing: ]] .. filepath .. [[</span>
|
||||
</div>
|
||||
]]
|
||||
|
||||
local modified = html_insert_after(html_content, "<body>", banner)
|
||||
return modified
|
||||
end, 20)
|
||||
|
||||
-- Hook 3: Enhance code blocks (Priority 30 - runs third)
|
||||
register_hook("post_render", function(filepath, html_content)
|
||||
log("Hook 3 (Priority 30): Enhancing code blocks")
|
||||
|
||||
-- Add class to all code tags
|
||||
local modified = html_add_class(html_content, "code", "enhanced-code")
|
||||
modified = html_add_attribute(modified, "pre", "data-enhanced", "true")
|
||||
|
||||
return modified
|
||||
end, 30)
|
||||
|
||||
-- Hook 4: Add footer (Priority 40 - runs last)
|
||||
register_hook("post_render", function(filepath, html_content)
|
||||
log("Hook 4 (Priority 40): Adding footer")
|
||||
|
||||
local footer = [[
|
||||
<div class="plugin-footer">
|
||||
<p>🔌 Powered by PyPost Plugin System</p>
|
||||
<p>Generated: ]] .. os.date("%Y-%m-%d %H:%M:%S") .. [[</p>
|
||||
<p style="font-size: 0.8em; opacity: 0.7;">
|
||||
Processed through 4 sequential hooks
|
||||
</p>
|
||||
</div>
|
||||
]]
|
||||
|
||||
local modified = html_insert_before(html_content, "</body>", footer)
|
||||
return modified
|
||||
end, 40)
|
||||
|
||||
-- ============================================================================
|
||||
-- PART 2: GET ROUTES
|
||||
-- Demonstrate various GET route patterns
|
||||
-- ============================================================================
|
||||
|
||||
-- Simple text response
|
||||
add_get_route("/plugin/hello", function(req)
|
||||
return "<h1>Hello from Plugin!</h1><p>This is a simple GET route.</p>"
|
||||
end, 50)
|
||||
|
||||
-- JSON API endpoint
|
||||
add_get_route("/plugin/api/info", function(req)
|
||||
local info = {
|
||||
plugin_name = "Comprehensive Example",
|
||||
version = "1.0.0",
|
||||
features = {"hooks", "routes", "file_ops"},
|
||||
html_files = #list_html_files(),
|
||||
markdown_files = #list_markdown_files(),
|
||||
timestamp = os.date("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
return 200,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json(info)
|
||||
end, 50)
|
||||
|
||||
-- File listing endpoint
|
||||
add_get_route("/plugin/files", function(req)
|
||||
local html_files = list_html_files()
|
||||
local md_files = list_markdown_files()
|
||||
|
||||
local html = [[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>File Browser</title>
|
||||
<style>
|
||||
body { font-family: Arial; max-width: 1000px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
|
||||
.file-list { background: white; padding: 20px; border-radius: 8px; margin: 20px 0; }
|
||||
.file-item { padding: 10px; border-bottom: 1px solid #eee; }
|
||||
.file-item:hover { background: #f9f9f9; }
|
||||
h2 { color: #667eea; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>📁 File Browser</h1>
|
||||
|
||||
<div class="file-list">
|
||||
<h2>HTML Files (]] .. #html_files .. [[)</h2>
|
||||
]]
|
||||
|
||||
for i, file in ipairs(html_files) do
|
||||
html = html .. ' <div class="file-item">📄 ' .. file .. '</div>\n'
|
||||
end
|
||||
|
||||
html = html .. [[
|
||||
</div>
|
||||
|
||||
<div class="file-list">
|
||||
<h2>Markdown Files (]] .. #md_files .. [[)</h2>
|
||||
]]
|
||||
|
||||
for i, file in ipairs(md_files) do
|
||||
html = html .. ' <div class="file-item">📝 ' .. file .. '</div>\n'
|
||||
end
|
||||
|
||||
html = html .. [[
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
]]
|
||||
|
||||
return html
|
||||
end, 50)
|
||||
|
||||
-- ============================================================================
|
||||
-- PART 3: POST ROUTES
|
||||
-- Handle form submissions and API requests
|
||||
-- ============================================================================
|
||||
|
||||
-- Contact form submission
|
||||
add_post_route("/plugin/api/contact", function(req)
|
||||
local data = req.data or {}
|
||||
|
||||
log("Contact form received:")
|
||||
log(" Name: " .. (data.name or "N/A"))
|
||||
log(" Email: " .. (data.email or "N/A"))
|
||||
log(" Message: " .. (data.message or "N/A"))
|
||||
|
||||
-- Validation
|
||||
if not data.name or data.name == "" then
|
||||
return 400,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json({success = false, error = "Name is required"})
|
||||
end
|
||||
|
||||
if not data.message or data.message == "" then
|
||||
return 400,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json({success = false, error = "Message is required"})
|
||||
end
|
||||
|
||||
-- Save to file (example)
|
||||
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
|
||||
local submission = string_join({
|
||||
"---",
|
||||
"Name: " .. data.name,
|
||||
"Email: " .. (data.email or "N/A"),
|
||||
"Time: " .. timestamp,
|
||||
"Message:",
|
||||
data.message,
|
||||
"---",
|
||||
""
|
||||
}, "\n")
|
||||
|
||||
-- Note: In production, you'd want better file handling
|
||||
-- write_file("submissions.txt", submission)
|
||||
|
||||
local response = {
|
||||
success = true,
|
||||
message = "Thank you for your submission!",
|
||||
received_at = timestamp
|
||||
}
|
||||
|
||||
return 200,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json(response)
|
||||
end, 50)
|
||||
|
||||
-- File upload/create endpoint
|
||||
add_post_route("/plugin/api/create-note", function(req)
|
||||
local data = req.data or {}
|
||||
local title = data.title or "Untitled"
|
||||
local content = data.content or ""
|
||||
|
||||
if content == "" then
|
||||
return 400,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json({success = false, error = "Content cannot be empty"})
|
||||
end
|
||||
|
||||
-- Create markdown file
|
||||
local filename = string_replace(title, " ", "-") .. ".md"
|
||||
filename = string.lower(filename)
|
||||
|
||||
local markdown_content = md_add_header("", 1, title)
|
||||
markdown_content = md_append_content(markdown_content, content)
|
||||
markdown_content = md_append_content(markdown_content,
|
||||
"\n---\n*Created by plugin at " .. os.date("%Y-%m-%d %H:%M:%S") .. "*")
|
||||
|
||||
-- Write the file
|
||||
local success = write_markdown(filename, markdown_content)
|
||||
|
||||
if success then
|
||||
log("Created new markdown file: " .. filename)
|
||||
return 200,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json({
|
||||
success = true,
|
||||
message = "Note created successfully",
|
||||
filename = filename
|
||||
})
|
||||
else
|
||||
return 500,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json({
|
||||
success = false,
|
||||
error = "Failed to create file"
|
||||
})
|
||||
end
|
||||
end, 50)
|
||||
|
||||
-- ============================================================================
|
||||
-- PART 4: DEMONSTRATION DASHBOARD
|
||||
-- A full-featured page showing all capabilities
|
||||
-- ============================================================================
|
||||
|
||||
add_get_route("/plugin/dashboard", function(req)
|
||||
local html_count = #list_html_files()
|
||||
local md_count = #list_markdown_files()
|
||||
|
||||
local html = [[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Plugin Dashboard</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f0f4f8; }
|
||||
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin: 20px 0; }
|
||||
.card { background: white; padding: 25px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
|
||||
.card h3 { color: #667eea; margin-bottom: 15px; }
|
||||
.stat { font-size: 3em; font-weight: bold; color: #667eea; margin: 10px 0; }
|
||||
.feature-list { list-style: none; padding: 0; }
|
||||
.feature-list li { padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
|
||||
.feature-list li:before { content: "✅ "; margin-right: 8px; }
|
||||
.button { background: #667eea; color: white; padding: 12px 24px; border: none; border-radius: 5px; cursor: pointer; text-decoration: none; display: inline-block; margin: 5px; }
|
||||
.button:hover { background: #5568d3; }
|
||||
.form-section { background: white; padding: 25px; border-radius: 10px; margin: 20px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
|
||||
input, textarea { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 5px; }
|
||||
.response { margin-top: 15px; padding: 15px; border-radius: 5px; display: none; }
|
||||
.success { background: #d4edda; color: #155724; display: block; }
|
||||
.error { background: #f8d7da; color: #721c24; display: block; }
|
||||
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🔌 PyPost Plugin Dashboard</h1>
|
||||
<p>Comprehensive Plugin System Demonstration</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Statistics Cards -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>📄 HTML Files</h3>
|
||||
<div class="stat">]] .. html_count .. [[</div>
|
||||
<p>Generated HTML documents</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📝 Markdown Files</h3>
|
||||
<div class="stat">]] .. md_count .. [[</div>
|
||||
<p>Source markdown files</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>⏰ Server Time</h3>
|
||||
<div class="stat" style="font-size: 1.5em;">]] .. os.date("%H:%M") .. [[</div>
|
||||
<p>]] .. os.date("%Y-%m-%d") .. [[</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features Section -->
|
||||
<div class="card">
|
||||
<h3>🚀 Plugin Features</h3>
|
||||
<ul class="feature-list">
|
||||
<li><strong>Hook Chaining:</strong> Multiple plugins modify content sequentially</li>
|
||||
<li><strong>Priority System:</strong> Control execution order (0-100)</li>
|
||||
<li><strong>GET Routes:</strong> Serve custom pages and APIs</li>
|
||||
<li><strong>POST Routes:</strong> Handle form submissions</li>
|
||||
<li><strong>HTML Manipulation:</strong> Find, replace, insert, wrap elements</li>
|
||||
<li><strong>Markdown Operations:</strong> Add headers, sections, list items</li>
|
||||
<li><strong>File I/O:</strong> Read, write, list files safely</li>
|
||||
<li><strong>JSON Support:</strong> Parse and stringify data</li>
|
||||
<li><strong>Hot Reloading:</strong> Plugins reload automatically on changes</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- API Endpoints -->
|
||||
<div class="card">
|
||||
<h3>🌐 Available API Endpoints</h3>
|
||||
<p><strong>GET</strong> <code>/plugin/hello</code> - Simple greeting</p>
|
||||
<p><strong>GET</strong> <code>/plugin/api/info</code> - JSON plugin info</p>
|
||||
<p><strong>GET</strong> <code>/plugin/files</code> - File browser</p>
|
||||
<p><strong>GET</strong> <code>/plugin/dashboard</code> - This page</p>
|
||||
<p><strong>POST</strong> <code>/plugin/api/contact</code> - Submit contact form</p>
|
||||
<p><strong>POST</strong> <code>/plugin/api/create-note</code> - Create markdown note</p>
|
||||
<br>
|
||||
<a href="/plugin/files" class="button">View Files</a>
|
||||
<a href="/plugin/api/info" class="button">API Info (JSON)</a>
|
||||
</div>
|
||||
|
||||
<!-- Contact Form Test -->
|
||||
<div class="form-section">
|
||||
<h3>📬 Test POST Route - Contact Form</h3>
|
||||
<form id="contactForm">
|
||||
<input type="text" name="name" placeholder="Your Name" required>
|
||||
<input type="email" name="email" placeholder="Your Email">
|
||||
<textarea name="message" rows="4" placeholder="Your Message" required></textarea>
|
||||
<button type="submit" class="button">Submit</button>
|
||||
</form>
|
||||
<div id="contactResponse" class="response"></div>
|
||||
</div>
|
||||
|
||||
<!-- Create Note Form -->
|
||||
<div class="form-section">
|
||||
<h3>📝 Test POST Route - Create Markdown Note</h3>
|
||||
<form id="noteForm">
|
||||
<input type="text" name="title" placeholder="Note Title" required>
|
||||
<textarea name="content" rows="6" placeholder="Note content in markdown..." required></textarea>
|
||||
<button type="submit" class="button">Create Note</button>
|
||||
</form>
|
||||
<div id="noteResponse" class="response"></div>
|
||||
</div>
|
||||
|
||||
<!-- Hook Chain Visualization -->
|
||||
<div class="card">
|
||||
<h3>🔗 Hook Chain Processing Order</h3>
|
||||
<p style="margin-bottom: 15px;">When a markdown file is rendered, these hooks process the HTML sequentially:</p>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 15px; background: #f9f9f9; border-radius: 5px; margin: 10px 0;">
|
||||
<span>1️⃣ Add CSS (Priority 10)</span>
|
||||
<span>→</span>
|
||||
<span>2️⃣ Add Banner (Priority 20)</span>
|
||||
<span>→</span>
|
||||
<span>3️⃣ Enhance Code (Priority 30)</span>
|
||||
<span>→</span>
|
||||
<span>4️⃣ Add Footer (Priority 40)</span>
|
||||
</div>
|
||||
<p style="margin-top: 15px; font-size: 0.9em; color: #666;">
|
||||
Each hook receives the output from the previous hook, ensuring no modifications are lost.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Code Example -->
|
||||
<div class="card">
|
||||
<h3>💻 Example Plugin Code</h3>
|
||||
<pre style="background: #2d3748; color: #e2e8f0; padding: 20px; border-radius: 5px; overflow-x: auto;"><code>-- Register a hook with priority
|
||||
register_hook("post_render", function(filepath, html)
|
||||
log("Processing: " .. filepath)
|
||||
local modified = html_insert_after(html, "<body>",
|
||||
"<div class='banner'>Hello!</div>")
|
||||
return modified -- Chain to next hook
|
||||
end, 20)
|
||||
|
||||
-- Register a POST route
|
||||
add_post_route("/api/submit", function(req)
|
||||
local data = req.data
|
||||
log("Received: " .. data.name)
|
||||
|
||||
return 200,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json({success = true})
|
||||
end, 50)</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Contact form handler
|
||||
document.getElementById('contactForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const data = Object.fromEntries(formData);
|
||||
const responseDiv = document.getElementById('contactResponse');
|
||||
|
||||
try {
|
||||
const response = await fetch('/plugin/api/contact', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
responseDiv.className = 'response success';
|
||||
responseDiv.innerHTML = '<strong>✅ Success!</strong><br>' + result.message;
|
||||
e.target.reset();
|
||||
} else {
|
||||
responseDiv.className = 'response error';
|
||||
responseDiv.innerHTML = '<strong>❌ Error:</strong><br>' + result.error;
|
||||
}
|
||||
} catch (err) {
|
||||
responseDiv.className = 'response error';
|
||||
responseDiv.innerHTML = '<strong>❌ Error:</strong><br>' + err.message;
|
||||
}
|
||||
});
|
||||
|
||||
// Note form handler
|
||||
document.getElementById('noteForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const data = Object.fromEntries(formData);
|
||||
const responseDiv = document.getElementById('noteResponse');
|
||||
|
||||
try {
|
||||
const response = await fetch('/plugin/api/create-note', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
responseDiv.className = 'response success';
|
||||
responseDiv.innerHTML = '<strong>✅ Success!</strong><br>' +
|
||||
result.message + '<br>File: <code>' + result.filename + '</code>';
|
||||
e.target.reset();
|
||||
} else {
|
||||
responseDiv.className = 'response error';
|
||||
responseDiv.innerHTML = '<strong>❌ Error:</strong><br>' + result.error;
|
||||
}
|
||||
} catch (err) {
|
||||
responseDiv.className = 'response error';
|
||||
responseDiv.innerHTML = '<strong>❌ Error:</strong><br>' + err.message;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
]]
|
||||
|
||||
return html
|
||||
end, 50)
|
||||
|
||||
-- ============================================================================
|
||||
-- PART 5: UTILITY DEMONSTRATIONS
|
||||
-- Show string and file utilities
|
||||
-- ============================================================================
|
||||
|
||||
-- String manipulation API
|
||||
add_get_route("/plugin/api/string-demo", function(req)
|
||||
local text = "Hello, World! This is a test."
|
||||
|
||||
local demo = {
|
||||
original = text,
|
||||
split = string_split(text, " "),
|
||||
replaced = string_replace(text, "World", "PyPost"),
|
||||
uppercase = string.upper(text),
|
||||
lowercase = string.lower(text),
|
||||
match = string_match(text, "w+"),
|
||||
all_words = string_match_all(text, "%w+")
|
||||
}
|
||||
|
||||
return 200,
|
||||
{["Content-Type"] = "application/json"},
|
||||
table_to_json(demo)
|
||||
end, 50)
|
||||
|
||||
-- ============================================================================
|
||||
-- INITIALIZATION LOG
|
||||
-- ============================================================================
|
||||
|
||||
log("========================================")
|
||||
log("Comprehensive Plugin Loaded Successfully!")
|
||||
log("========================================")
|
||||
log("Registered Hooks:")
|
||||
log(" - post_render (4 hooks with priorities 10, 20, 30, 40)")
|
||||
log("Registered GET Routes:")
|
||||
log(" - /plugin/hello")
|
||||
log(" - /plugin/api/info")
|
||||
log(" - /plugin/files")
|
||||
log(" - /plugin/dashboard")
|
||||
log(" - /plugin/api/string-demo")
|
||||
log("Registered POST Routes:")
|
||||
log(" - /plugin/api/contact")
|
||||
log(" - /plugin/api/create-note")
|
||||
log("========================================")
|
||||
log("Visit /plugin/dashboard to see all features!")
|
||||
log("========================================")
|
||||
Reference in New Issue
Block a user