Add community section where public functions can be viewed

This commit is contained in:
Peter Stockings
2025-11-21 10:30:14 +11:00
parent 8eb9b7dceb
commit 213abbfe93
10 changed files with 365 additions and 10 deletions

View File

@@ -0,0 +1,70 @@
{% extends 'dashboard.html' %}
{% block page %}
<div class="flex flex-col space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Community Library</h1>
<div class="w-full max-w-sm">
<form class="relative" hx-get="{{ url_for('community.index') }}" hx-target="#container" hx-push-url="true">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none"
stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
<input type="search" name="q"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search functions..." value="{{ search_query }}">
</form>
</div>
</div>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{% for function in public_functions %}
<div
class="bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 hover:shadow-lg transition-shadow duration-200">
<div class="p-5">
<div class="flex justify-between items-start mb-2">
<h5 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white truncate"
title="{{ function.name }}">{{ function.name }}</h5>
<span
class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">{{
function.runtime }}</span>
</div>
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400 text-sm line-clamp-2 h-10">
{{ function.description or "No description provided." }}
</p>
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-4">
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 mr-1">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
{{ function.username }}
</span>
<span>{{ function.created_at.strftime('%Y-%m-%d') }}</span>
</div>
<a href="#" hx-get="{{ url_for('community.view', function_id=function.id) }}" hx-target="#container"
hx-push-url="true"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 w-full justify-center">
View Details
<svg aria-hidden="true" class="w-4 h-4 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</a>
</div>
</div>
{% else %}
<div class="col-span-full text-center py-10 text-gray-500 dark:text-gray-400">
<p class="text-lg">No public functions found.</p>
<p class="text-sm">Be the first to publish one!</p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,152 @@
{% extends 'dashboard.html' %}
{% block page %}
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Back Link -->
<div class="mb-6">
<a href="#" hx-get="{{ url_for('community.index') }}" hx-target="#container" hx-push-url="true"
class="inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 mr-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
Back to Community Library
</a>
</div>
<!-- Header Section -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-6">
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 mb-2">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white tracking-tight">{{ function.name }}</h1>
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300 border border-blue-200 dark:border-blue-800 uppercase tracking-wide">
{{ function.runtime }}
</span>
</div>
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-1.5">
<div class="p-1 rounded-full bg-gray-100 dark:bg-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="w-4 h-4 text-gray-600 dark:text-gray-300">
<path fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd" />
</svg>
</div>
<span class="font-medium text-gray-700 dark:text-gray-300">{{ function.username }}</span>
</div>
<span class="hidden sm:inline text-gray-300 dark:text-gray-600"></span>
<div class="flex items-center gap-1.5">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0h18M5.25 12h13.5h-13.5Zm0 3.75h13.5h-13.5Z" />
</svg>
<span>Published on {{ function.created_at.strftime('%B %d, %Y') }}</span>
</div>
</div>
{% if function.description %}
<div class="mt-4 prose prose-sm dark:prose-invert max-w-none text-gray-600 dark:text-gray-300">
<p class="whitespace-pre-wrap">{{ function.description }}</p>
</div>
{% endif %}
</div>
<div class="flex-shrink-0">
<button hx-post="{{ url_for('community.fork', function_id=function.id) }}" hx-target="#container"
class="inline-flex items-center justify-center px-5 py-2.5 text-sm font-medium text-white transition-all bg-green-600 border border-transparent rounded-lg shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 dark:focus:ring-offset-gray-900">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 h-5 mr-2 -ml-1">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
</svg>
Fork to My Library
</button>
</div>
</div>
</div>
<!-- Tabs & Content -->
<div
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
<div class="border-b border-gray-200 dark:border-gray-700">
<nav class="flex -mb-px" aria-label="Tabs">
<button onclick="switchTab('code')" id="tab-code"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm focus:outline-none transition-colors border-blue-500 text-blue-600 dark:text-blue-400">
Function Code
</button>
<button onclick="switchTab('env')" id="tab-env"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm focus:outline-none transition-colors border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300">
Environment Config
</button>
</nav>
</div>
<div class="p-0">
<div id="content-code" class="block">
<div id="code-editor" class="h-[500px] w-full"></div>
</div>
<div id="content-env" class="hidden">
<div id="env-editor" class="h-[500px] w-full"></div>
</div>
</div>
</div>
</div>
<script>
// Tab Switching Logic
window.switchTab = function (tabName) {
// Update Tab Styles
const tabs = ['code', 'env'];
tabs.forEach(t => {
const btn = document.getElementById(`tab-${t}`);
const content = document.getElementById(`content-${t}`);
if (t === tabName) {
btn.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300', 'dark:text-gray-400', 'dark:hover:text-gray-300');
btn.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
content.classList.remove('hidden');
content.classList.add('block');
} else {
btn.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300', 'dark:text-gray-400', 'dark:hover:text-gray-300');
btn.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
content.classList.remove('block');
content.classList.add('hidden');
}
});
// Resize editors to ensure they render correctly after visibility change
if (tabName === 'code') codeEditor.resize();
if (tabName === 'env') envEditor.resize();
}
// Initialize Ace Editors
var codeEditor = ace.edit("code-editor");
codeEditor.setTheme("ace/theme/github_dark");
codeEditor.session.setMode("ace/mode/{{ 'python' if function.runtime == 'python' else 'javascript' }}");
codeEditor.setValue({{ function.script_content | tojson | safe }}, -1);
codeEditor.setReadOnly(true);
codeEditor.setOptions({
fontSize: "14px",
showPrintMargin: false,
highlightActiveLine: false,
highlightGutterLine: false
});
var envEditor = ace.edit("env-editor");
envEditor.setTheme("ace/theme/github_dark");
envEditor.session.setMode("ace/mode/json");
envEditor.setValue({{ function.environment_info | safe }}, -1);
envEditor.setReadOnly(true);
envEditor.setOptions({
fontSize: "14px",
showPrintMargin: false,
highlightActiveLine: false,
highlightGutterLine: false
});
</script>
{% endblock %}

View File

@@ -63,6 +63,17 @@
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
Settings
</a><a
class="flex items-center gap-3 rounded-lg px-3 py-2 text-gray-500 transition-all hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-50 cursor-pointer"
data-id="18" href="{{ url_for('community.index') }}">
<svg data-slot="icon" data-darkreader-inline-stroke="" width="18" height="18" fill="none"
stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z">
</path>
</svg>
Community
</a></nav>
</div>

View File

@@ -25,6 +25,7 @@ history_url=url_for('http.history', function_id=function_id)) }}
name: '{{ name }}',
path: '{{ path }}',
functionId: {{ id }},
description: '{{ description }}',
jsValue: {{ script_content | tojson | safe }},
jsonValue: {{ environment_info | tojson | safe }},
isEdit: true,