| |
| import os |
| from supabase import create_client |
| from dotenv import load_dotenv |
|
|
| load_dotenv() |
|
|
| SUPABASE_URL = os.environ["SUPABASE_URL"] |
| SUPABASE_SERVICE_ROLE = os.environ["SUPABASE_SERVICE_ROLE"] |
|
|
| if not SUPABASE_URL or not SUPABASE_SERVICE_ROLE: |
| raise RuntimeError("Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE") |
|
|
| supabase = create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE) |
|
|
| from upload_weblink_to_supabase import extract_paragraphs |
|
|
| |
| VIEW_TEMPLATE = """ |
| <!DOCTYPE html> |
| <html lang="de"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Hochschulgesetz NRW – Paragraph Viewer</title> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| margin: 0; |
| padding: 0; |
| display: flex; |
| } |
| /* ----------- SIDEBAR ------------- */ |
| #sidebar { |
| width: 280px; |
| height: 100vh; |
| overflow-y: auto; |
| background: #f5f5f5; |
| border-right: 1px solid #ccc; |
| padding: 15px; |
| position: sticky; |
| top: 0; |
| } |
| #sidebar h2 { |
| margin-top: 0; |
| } |
| #searchBox { |
| width: 100%; |
| padding: 8px; |
| font-size: 15px; |
| margin-bottom: 10px; |
| border: 1px solid #aaa; |
| border-radius: 5px; |
| } |
| .sidebar-link { |
| display: block; |
| padding: 6px 8px; |
| margin-bottom: 4px; |
| text-decoration: none; |
| color: #003366; |
| border-radius: 4px; |
| } |
| .sidebar-link:hover { |
| background: #e0e7ff; |
| color: #001d4d; |
| } |
| /* ----------- CONTENT ------------- */ |
| #content { |
| flex: 1; |
| padding: 25px; |
| max-width: 900px; |
| } |
| /* Absatz block */ |
| .para { |
| padding: 20px 0; |
| border-bottom: 1px solid #ddd; |
| } |
| .para h2 { |
| color: #003366; |
| margin-bottom: 10px; |
| } |
| /* ----------- Fußnoten ------------- */ |
| .fn-block { |
| background: #fafafa; |
| border-left: 4px solid #999; |
| padding: 12px; |
| margin-top: 10px; |
| margin-bottom: 25px; |
| } |
| .fn-toggle { |
| cursor: pointer; |
| font-weight: bold; |
| color: #003366; |
| margin-bottom: 5px; |
| } |
| .fn-content { |
| display: none; |
| padding-left: 10px; |
| } |
| .fn-title { |
| font-weight: bold; |
| margin-bottom: 6px; |
| } |
| .fn-item { |
| margin-bottom: 8px; |
| } |
| /* ----------- Highlight beim Öffnen ------------- */ |
| .highlight { |
| animation: flash 2s ease-in-out; |
| background: #fff8c6 !important; |
| } |
| @keyframes flash { |
| 0% { background: #fff8c6; } |
| 100% { background: transparent; } |
| } |
| /* Keyword highlight */ |
| .keyword { |
| background: yellow; |
| padding: 2px 3px; |
| border-radius: 3px; |
| } |
| /* Back to top button */ |
| #topBtn { |
| position: fixed; |
| bottom: 25px; |
| right: 25px; |
| background: #003366; |
| color: white; |
| border-radius: 8px; |
| padding: 10px 14px; |
| cursor: pointer; |
| font-size: 16px; |
| display: none; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="sidebar"> |
| <h2>Inhaltsverzeichnis</h2> |
| <input type="text" id="searchBox" placeholder="Suchen nach § …"> |
| <!-- SIDEBAR_LINKS --> |
| </div> |
| <div id="content"> |
| <h1>Hochschulgesetz NRW – Paragraph Viewer</h1> |
| <!-- PARAGRAPH_CONTENT --> |
| </div> |
| <div id="topBtn" onclick="scrollToTop()">⬆️ Top</div> |
| <script> |
| // ------ TỰ ĐỘNG HIGHLIGHT Absatz khi có #anchor HIGHLIGHT ABSATZ & SCROLL ------ |
| window.onload = function() { |
| const anchor = window.location.hash.substring(1); |
| const params = new URLSearchParams(window.location.search); |
| const keywords = params.get("k"); |
| if (anchor) { |
| const el = document.getElementById(anchor); |
| if (el) { |
| el.classList.add("highlight"); |
| el.scrollIntoView({ behavior: "smooth", block: "center" }); |
| } |
| } |
| /* KEYWORD HIGHLIGHT */ |
| if (keywords) { |
| const words = keywords.split("%20"); |
| highlightKeywords(words); |
| } |
| }; |
| /* --- KEYWORD HIGHLIGHT FUNCTION --- */ |
| function highlightKeywords(words) { |
| const container = document.getElementById("content"); |
| let html = container.innerHTML; |
| words.forEach(word => { |
| if (word.length < 2) return; |
| const regex = new RegExp(`(${decodeURIComponent(word)})`, "gi"); |
| html = html.replace(regex, `<span class="keyword">$1</span>`); |
| }); |
| container.innerHTML = html; |
| } |
| /* --- SEARCH IN SIDEBAR --- */ |
| document.getElementById("searchBox").addEventListener("input", function() { |
| const q = this.value.toLowerCase(); |
| document.querySelectorAll(".sidebar-link").forEach(link => { |
| const txt = link.innerText.toLowerCase(); |
| link.style.display = txt.includes(q) ? "block" : "none"; |
| }); |
| }); |
| /* --- COLLAPSIBLE FUSSNOTEN --- */ |
| document.addEventListener("click", function(e) { |
| if (e.target.classList.contains("fn-toggle")) { |
| const content = e.target.nextElementSibling; |
| content.style.display = content.style.display === "block" ? "none" : "block"; |
| } |
| }); |
| /* --- BACK TO TOP BUTTON --- */ |
| window.onscroll = function() { |
| document.getElementById("topBtn").style.display = |
| window.scrollY > 300 ? "block" : "none"; |
| }; |
| function scrollToTop() { |
| window.scrollTo({ top: 0, behavior: 'smooth' }); |
| } |
| </script> |
| </body> |
| </html> |
| """ |
|
|
| |
| |
| |
|
|
| def build_html_from_db(): |
| """ |
| Liest alle Paragraphen aus hg_nrw und baut daraus HTML. |
| """ |
| print(">>> Lade Paragraphen aus Supabase (hg_nrw) …") |
| paras = extract_paragraphs() |
| |
| res = supabase.table("hg_nrw").select("*").order("order_index").execute() |
| rows = res.data or [] |
|
|
| sidebar_links = "" |
| content_html = "" |
|
|
| for p in paras: |
| pid = p["abs_id"] |
| title = p["title"] |
| body = p["content"] |
|
|
| |
| sidebar_links += f'<a class="sidebar-link" href="#{pid}">{title}</a>\n' |
|
|
| |
| lines = body.split("\n") |
| main_text = [] |
| fn_text = [] |
| in_fn = False |
|
|
| for line in lines: |
| if line.startswith("Fn "): |
| in_fn = True |
| if in_fn: |
| fn_text.append(line) |
| else: |
| main_text.append(line) |
|
|
| footnotes_html = "" |
| if fn_text: |
| footnotes_html += '<div class="fn-block">' |
| footnotes_html += '<div class="fn-title">Fußnoten:</div>' |
| for fn in fn_text: |
| footnotes_html += f'<div class="fn-item">{fn}</div>' |
| footnotes_html += "</div>" |
|
|
| |
| content_html += f""" |
| <div class="para" id="{pid}"> |
| <h2>{title}</h2> |
| <div>{'<br>'.join(main_text)}</div> |
| {footnotes_html} |
| </div> |
| """ |
|
|
| html = VIEW_TEMPLATE.replace("<!-- SIDEBAR_LINKS -->", sidebar_links) |
| html = html.replace("<!-- PARAGRAPH_CONTENT -->", content_html) |
|
|
| return html |
|
|
| |
| |
| |
|
|
| def upload_html(): |
| html = build_html_from_db() |
|
|
| supabase.storage.from_("hg_viewer").update( |
| "hg_clean.html", |
| html.encode("utf-8"), |
| { |
| "content-type": "text/html", |
| "x-upsert": "true" |
| } |
| ) |
|
|
| print("✔ hg_clean.html uploaded!") |
|
|
| if __name__ == "__main__": |
| upload_html() |
|
|