Initial Commit

This commit is contained in:
2025-11-27 00:00:50 +00:00
commit b7e68a9057
43 changed files with 3445 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from flask import Blueprint
publish_bp = Blueprint("publish", __name__, template_folder="templates")

View File

@@ -0,0 +1,9 @@
from flask import render_template
from . import publish_bp
from core.auth import require_perms
@publish_bp.get("/")
@require_perms("publish.use")
def index():
return render_template("publish/index.html")

View File

@@ -0,0 +1,51 @@
{% extends 'core/base.html' %}
{% block title %}Publish Once — Portal{% endblock %}
{% block content %}
<section class="grid lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 card glass p-6">
<h1 class="text-xl font-semibold">Compose</h1>
<div class="mt-3 grid gap-3">
<input id="title" placeholder="Title" class="w-full">
<input id="hero" placeholder="Hero image URL (optional)" class="w-full">
<input id="cta" placeholder="Canonical URL" class="w-full">
<textarea id="body" rows="8" placeholder="Write your story…" class="w-full"></textarea>
<input id="tags" placeholder="Tags (comma)" class="w-full">
</div>
</div>
<aside class="space-y-4">
<div class="card glass p-4">
<h2 class="font-semibold">Blog FrontMatter</h2>
<button class="btn mt-2" onclick="copy('front')">Copy</button>
<pre id="front" class="mt-2 text-xs"></pre>
</div>
<div class="card glass p-4">
<h2 class="font-semibold">Facebook (Group/Page)</h2>
<button class="btn mt-2" onclick="copy('fb')">Copy</button>
<pre id="fb" class="mt-2 text-xs"></pre>
</div>
<div class="card glass p-4">
<h2 class="font-semibold">Instagram / TikTok</h2>
<button class="btn mt-2" onclick="copy('ig')">Copy</button>
<pre id="ig" class="mt-2 text-xs"></pre>
</div>
</aside>
</section>
<script>
function slugify(t){return (t||'').toLowerCase().trim().replace(/[^a-z0-9\s-]/g,'').replace(/\s+/g,'-').replace(/-+/g,'-')}
function yaml(s){return '"'+String(s||'').replaceAll('"','\\"')+'"'}
function utm(u,src){try{const x=new URL(u);x.searchParams.set('utm_source',src);x.searchParams.set('utm_medium','social');x.searchParams.set('utm_campaign','portal');return x+''}catch(e){return u}}
async function copy(id){const t=document.getElementById(id).innerText; await navigator.clipboard.writeText(t)}
function render(){
const title=t.value, body=bd.value, hero=h.value, cta=c.value, tags=tg.value
const ex=(body||'').replace(/\s+/g,' ').trim(); const short=ex.length<=160?ex:ex.slice(0,159)+'…'
const slug=slugify(title||'post')
front.textContent = `---\ntitle: ${yaml(title)}\nslug: ${slug}\nhero: ${yaml(hero)}\ntags: [${(tags||'').split(',').map(s=>s.trim()).filter(Boolean).map(x=>yaml(x)).join(', ')}]\nexcerpt: ${yaml(short)}\n---`
fb.textContent = `${title}\n\n${short}\n\nRead more → ${utm(cta,'facebook')}`
const hashtags=(tags||'').split(',').map(s=>s.trim()).filter(Boolean).map(x=>'#'+slugify(x)).slice(0,12).join(' ')
ig.textContent = `${title}\n\n${short}\n\n${hashtags}\n\nLink: ${utm(cta,'instagram')}`
}
const t=title, h=hero, c=cta, bd=body, tg=tags; [t,h,c,bd,tg].forEach(el=>el.addEventListener('input',render)); render()
</script>
{% endblock %}