Slim sxc/pages/__init__.py for federation, test, cart, blog
Move render functions, layouts, helpers, and utils from __init__.py to sub-modules (renders.py, layouts.py, helpers.py, utils.py). Update all bp route imports to point at sub-modules directly. Each __init__.py is now ≤20 lines of setup + registration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
177
blog/sxc/pages/renders.py
Normal file
177
blog/sxc/pages/renders.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""Blog editor panel rendering."""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
async def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
|
||||
"""Build the WYSIWYG editor panel HTML for new post/page creation."""
|
||||
import os
|
||||
from quart import url_for as qurl, current_app
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
from shared.sx.helpers import render_to_sx
|
||||
|
||||
csrf = generate_csrf_token()
|
||||
asset_url_fn = current_app.jinja_env.globals.get("asset_url", lambda p: "")
|
||||
editor_css = asset_url_fn("scripts/editor.css")
|
||||
editor_js = asset_url_fn("scripts/editor.js")
|
||||
sx_editor_js = asset_url_fn("scripts/sx-editor.js")
|
||||
|
||||
upload_image_url = qurl("blog.editor_api.upload_image")
|
||||
upload_media_url = qurl("blog.editor_api.upload_media")
|
||||
upload_file_url = qurl("blog.editor_api.upload_file")
|
||||
oembed_url = qurl("blog.editor_api.oembed_proxy")
|
||||
snippets_url = qurl("blog.editor_api.list_snippets")
|
||||
unsplash_key = os.environ.get("UNSPLASH_ACCESS_KEY", "")
|
||||
|
||||
title_placeholder = "Page title..." if is_page else "Post title..."
|
||||
create_label = "Create Page" if is_page else "Create Post"
|
||||
|
||||
parts: list[str] = []
|
||||
|
||||
if save_error:
|
||||
parts.append(await render_to_sx("blog-editor-error", error=str(save_error)))
|
||||
|
||||
parts.append(await render_to_sx("blog-editor-form",
|
||||
csrf=csrf, title_placeholder=title_placeholder,
|
||||
create_label=create_label,
|
||||
))
|
||||
|
||||
parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
|
||||
parts.append(await render_to_sx("sx-editor-styles"))
|
||||
|
||||
init_js = (
|
||||
"console.log('[EDITOR-DEBUG] init script running');\n"
|
||||
"(function() {\n"
|
||||
" console.log('[EDITOR-DEBUG] IIFE entered, mountEditor=', typeof window.mountEditor);\n"
|
||||
" function init() {\n"
|
||||
" var csrfToken = document.querySelector('input[name=\"csrf_token\"]').value;\n"
|
||||
f" var uploadUrl = '{upload_image_url}';\n"
|
||||
" var uploadUrls = {\n"
|
||||
" image: uploadUrl,\n"
|
||||
f" media: '{upload_media_url}',\n"
|
||||
f" file: '{upload_file_url}',\n"
|
||||
" };\n"
|
||||
"\n"
|
||||
" var fileInput = document.getElementById('feature-image-file');\n"
|
||||
" var addBtn = document.getElementById('feature-image-add-btn');\n"
|
||||
" var deleteBtn = document.getElementById('feature-image-delete-btn');\n"
|
||||
" var preview = document.getElementById('feature-image-preview');\n"
|
||||
" var emptyState = document.getElementById('feature-image-empty');\n"
|
||||
" var filledState = document.getElementById('feature-image-filled');\n"
|
||||
" var hiddenUrl = document.getElementById('feature-image-input');\n"
|
||||
" var hiddenCaption = document.getElementById('feature-image-caption-input');\n"
|
||||
" var captionInput = document.getElementById('feature-image-caption');\n"
|
||||
" var uploading = document.getElementById('feature-image-uploading');\n"
|
||||
"\n"
|
||||
" function showFilled(url) {\n"
|
||||
" preview.src = url;\n"
|
||||
" hiddenUrl.value = url;\n"
|
||||
" emptyState.classList.add('hidden');\n"
|
||||
" filledState.classList.remove('hidden');\n"
|
||||
" uploading.classList.add('hidden');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function showEmpty() {\n"
|
||||
" preview.src = '';\n"
|
||||
" hiddenUrl.value = '';\n"
|
||||
" hiddenCaption.value = '';\n"
|
||||
" captionInput.value = '';\n"
|
||||
" emptyState.classList.remove('hidden');\n"
|
||||
" filledState.classList.add('hidden');\n"
|
||||
" uploading.classList.add('hidden');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function uploadFile(file) {\n"
|
||||
" emptyState.classList.add('hidden');\n"
|
||||
" uploading.classList.remove('hidden');\n"
|
||||
" var fd = new FormData();\n"
|
||||
" fd.append('file', file);\n"
|
||||
" fetch(uploadUrl, {\n"
|
||||
" method: 'POST',\n"
|
||||
" body: fd,\n"
|
||||
" headers: { 'X-CSRFToken': csrfToken },\n"
|
||||
" })\n"
|
||||
" .then(function(r) {\n"
|
||||
" if (!r.ok) throw new Error('Upload failed (' + r.status + ')');\n"
|
||||
" return r.json();\n"
|
||||
" })\n"
|
||||
" .then(function(data) {\n"
|
||||
" var url = data.images && data.images[0] && data.images[0].url;\n"
|
||||
" if (url) showFilled(url);\n"
|
||||
" else { showEmpty(); alert('Upload succeeded but no image URL returned.'); }\n"
|
||||
" })\n"
|
||||
" .catch(function(e) {\n"
|
||||
" showEmpty();\n"
|
||||
" alert(e.message);\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" addBtn.addEventListener('click', function() { fileInput.click(); });\n"
|
||||
" preview.addEventListener('click', function() { fileInput.click(); });\n"
|
||||
" deleteBtn.addEventListener('click', function(e) {\n"
|
||||
" e.stopPropagation();\n"
|
||||
" showEmpty();\n"
|
||||
" });\n"
|
||||
" fileInput.addEventListener('change', function() {\n"
|
||||
" if (fileInput.files && fileInput.files[0]) {\n"
|
||||
" uploadFile(fileInput.files[0]);\n"
|
||||
" fileInput.value = '';\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
" captionInput.addEventListener('input', function() {\n"
|
||||
" hiddenCaption.value = captionInput.value;\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" var excerpt = document.querySelector('textarea[name=\"custom_excerpt\"]');\n"
|
||||
" function autoResize() {\n"
|
||||
" excerpt.style.height = 'auto';\n"
|
||||
" excerpt.style.height = excerpt.scrollHeight + 'px';\n"
|
||||
" }\n"
|
||||
" excerpt.addEventListener('input', autoResize);\n"
|
||||
" autoResize();\n"
|
||||
"\n"
|
||||
" window.mountEditor('lexical-editor', {\n"
|
||||
" initialJson: null,\n"
|
||||
" csrfToken: csrfToken,\n"
|
||||
" uploadUrls: uploadUrls,\n"
|
||||
f" oembedUrl: '{oembed_url}',\n"
|
||||
f" unsplashApiKey: '{unsplash_key}',\n"
|
||||
f" snippetsUrl: '{snippets_url}',\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" if (typeof SxEditor !== 'undefined') {\n"
|
||||
" SxEditor.mount('sx-editor', {\n"
|
||||
" initialSx: (document.getElementById('sx-content-input') || {}).value || null,\n"
|
||||
" csrfToken: csrfToken,\n"
|
||||
" uploadUrls: uploadUrls,\n"
|
||||
f" oembedUrl: '{oembed_url}',\n"
|
||||
" onChange: function(sx) {\n"
|
||||
" document.getElementById('sx-content-input').value = sx;\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" document.addEventListener('keydown', function(e) {\n"
|
||||
" if ((e.ctrlKey || e.metaKey) && e.key === 's') {\n"
|
||||
" e.preventDefault();\n"
|
||||
" document.getElementById('post-new-form').requestSubmit();\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (typeof window.mountEditor === 'function') {\n"
|
||||
" init();\n"
|
||||
" } else {\n"
|
||||
" var _t = setInterval(function() {\n"
|
||||
" if (typeof window.mountEditor === 'function') { clearInterval(_t); init(); }\n"
|
||||
" }, 50);\n"
|
||||
" }\n"
|
||||
"})();\n"
|
||||
)
|
||||
parts.append(await render_to_sx("blog-editor-scripts",
|
||||
js_src=editor_js,
|
||||
sx_editor_js_src=sx_editor_js,
|
||||
init_js=init_js))
|
||||
|
||||
from shared.sx.parser import SxExpr
|
||||
return await render_to_sx("blog-editor-panel",
|
||||
parts=SxExpr("(<> " + " ".join(parts) + ")")) if parts else ""
|
||||
Reference in New Issue
Block a user