openwrite 0.17.1__tar.gz → 0.17.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openwrite-0.17.1/openwrite.egg-info → openwrite-0.17.2}/PKG-INFO +1 -1
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/gemini_single.py +26 -25
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/blog.py +10 -5
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/dashboard.py +9 -4
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/upload.py +2 -7
- openwrite-0.17.2/openwrite/static/feed/default.xml +2 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/feed/testx.xml +1 -1
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/post_preview.js +66 -6
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/new_post.html +1 -1
- {openwrite-0.17.1 → openwrite-0.17.2/openwrite.egg-info}/PKG-INFO +1 -1
- {openwrite-0.17.1 → openwrite-0.17.2}/pyproject.toml +1 -1
- openwrite-0.17.1/openwrite/static/feed/default.xml +0 -2
- {openwrite-0.17.1 → openwrite-0.17.2}/LICENSE +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/MANIFEST.in +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/README.md +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/__init__.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/cli.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/db/__init__.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/db/base.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/gemini_multi.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/admin.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/auth.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/federation.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/routes/main.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/Merriweather.ttf +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/avatar.png +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/emoji.json +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/favicon.ico +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/favicon.png +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/feed/.gitkeep +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/feed/test.xml +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/logo.png +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/chart.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/highlight.min.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/nativeEmoji.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/openwrite.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/papaparse.min.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/showdown.min.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/showdown.min.js.map +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/site.compat.min.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/script/site.min.js +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/blog_design.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/default.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/hl.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/nativeEmoji.min.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/style.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/cyberpunk.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/light.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/obscura.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/paper.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/sepia.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/stickynote.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/synthwave.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/terminal.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/warmnight.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/themes/windows98.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/static/style/toc.css +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/admin.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/blog.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/blog_footer.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/blog_header.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/blog_preview.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/changepw.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/create.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/dashboard.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/discover.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/edit.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/footer.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/header.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/import.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/index.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/instances.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/login.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/new_page.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/page_preview.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/post.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/preview.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/register.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/settings.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/stats.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/templates/translations.html +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/activitypub.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/create_db.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/db.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/helpers.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/i18n.json +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/migrations.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/models.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/tags.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/toc.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/valkey_client.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite/utils/worker.py +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite.egg-info/SOURCES.txt +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite.egg-info/dependency_links.txt +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite.egg-info/entry_points.txt +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite.egg-info/requires.txt +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/openwrite.egg-info/top_level.txt +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/setup.cfg +0 -0
- {openwrite-0.17.1 → openwrite-0.17.2}/tests/test_federation.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os, subprocess
|
|
2
|
+
import re
|
|
2
3
|
from dotenv import load_dotenv
|
|
3
4
|
from openwrite.utils.db import init_engine, SessionLocal
|
|
4
|
-
from openwrite.utils.models import Blog, Post, User, View
|
|
5
|
+
from openwrite.utils.models import Blog, Post, User, View, Page
|
|
5
6
|
from openwrite.utils.helpers import anonymize
|
|
6
7
|
from md2gemini import md2gemini
|
|
7
8
|
from datetime import datetime, timezone
|
|
@@ -11,28 +12,28 @@ def blog_index(req):
|
|
|
11
12
|
global session
|
|
12
13
|
try:
|
|
13
14
|
blog = session.query(Blog).filter_by(name="default").first()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
if not blog:
|
|
16
|
+
return "not found"
|
|
17
|
+
posts = (session.query(Post)
|
|
18
|
+
.filter_by(blog=blog.id, isdraft="0")
|
|
19
|
+
.order_by(Post.id.desc())
|
|
20
|
+
.all())
|
|
21
|
+
pages = (session.query(Page)
|
|
22
|
+
.filter(Page.blog == blog.id, Page.show == "1").all())
|
|
23
|
+
homepage = session.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
24
|
+
body = f"# 📓 {blog.title}\n\n"
|
|
25
|
+
posts_data = ""
|
|
26
|
+
for post in posts:
|
|
27
|
+
posts_data += f"=> /p/{post.link} {post.title}\n\n"
|
|
28
|
+
pages_data = ""
|
|
29
|
+
for page in pages:
|
|
30
|
+
pages_data += f"=> /p/{page.url} {page.name}\n\n"
|
|
31
|
+
content = pages_data + "\n"
|
|
32
|
+
content += homepage.content_raw.replace('{posts}', f"\n{posts_data}") if homepage else posts_data
|
|
33
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
34
|
+
body += md2gemini(content)
|
|
35
|
+
|
|
36
|
+
return body
|
|
36
37
|
finally:
|
|
37
38
|
session.close()
|
|
38
39
|
|
|
@@ -55,7 +56,7 @@ def blog_post(req):
|
|
|
55
56
|
.filter_by(blog=blog.id, url=slug)
|
|
56
57
|
.first())
|
|
57
58
|
if page:
|
|
58
|
-
content = f"=> /
|
|
59
|
+
content = f"=> / 📓 {blog.title}\n\n"
|
|
59
60
|
content += page.content_raw
|
|
60
61
|
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
61
62
|
return md2gemini(content)
|
|
@@ -72,7 +73,7 @@ def blog_post(req):
|
|
|
72
73
|
session.commit()
|
|
73
74
|
|
|
74
75
|
post.authorname = user.username
|
|
75
|
-
gemtext = f"=> /
|
|
76
|
+
gemtext = f"=> / 📓 {blog.title}\n"
|
|
76
77
|
gemtext += f"# 🧾 {post.title}\n\n"
|
|
77
78
|
gemtext += f"{post.date} "
|
|
78
79
|
if post.author != "0":
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file, jsonify
|
|
1
|
+
from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file, jsonify, current_app
|
|
2
2
|
from openwrite.utils.models import Blog, Post, User, View, Like, Page, PostDraft, Comment
|
|
3
3
|
from openwrite.utils.tags import get_post_tags, parse_tags_from_posts_syntax, get_posts_by_tags
|
|
4
4
|
from openwrite.utils.helpers import gen_link, sanitize_html, anonymize, get_ip
|
|
@@ -457,10 +457,15 @@ def like():
|
|
|
457
457
|
|
|
458
458
|
|
|
459
459
|
def serve_rss(blog):
|
|
460
|
-
"""
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
"""Generate a missing feed, then serve the cached file as usual."""
|
|
461
|
+
if blog is None:
|
|
462
|
+
abort(404)
|
|
463
|
+
directory = current_app.config.get('RSS_DIRECTORY', f'{g.mainpath}/static/feed')
|
|
464
|
+
path = os.path.join(directory, f'{blog.name}.xml')
|
|
465
|
+
if not os.path.isfile(path):
|
|
466
|
+
from openwrite.routes.dashboard import generate_rss
|
|
467
|
+
generate_rss(blog.name)
|
|
468
|
+
return send_file(path, mimetype='application/rss+xml')
|
|
464
469
|
|
|
465
470
|
|
|
466
471
|
@blog_bp.route("/comment", methods=['POST'])
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from flask import Blueprint, render_template, redirect, request, g, abort, Response
|
|
1
|
+
from flask import Blueprint, render_template, redirect, request, g, abort, Response, current_app
|
|
2
2
|
from openwrite.utils.models import Blog, Post, User, View, Like, Page, Settings, Tag, PostTag
|
|
3
3
|
from openwrite.utils.helpers import sanitize_html, gen_link, safe_css, is_html, get_themes
|
|
4
4
|
from openwrite.utils.tags import parse_tags_from_input, update_post_tags, get_post_tags
|
|
@@ -14,6 +14,7 @@ from zoneinfo import ZoneInfo
|
|
|
14
14
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
15
15
|
from cryptography.hazmat.primitives import serialization
|
|
16
16
|
import json
|
|
17
|
+
import os
|
|
17
18
|
import bcrypt
|
|
18
19
|
import markdown
|
|
19
20
|
from bs4 import BeautifulSoup
|
|
@@ -672,7 +673,10 @@ def generate_rss(blog):
|
|
|
672
673
|
fg.image(url="https://openwrite.io/static/favicon.png")
|
|
673
674
|
fg.title(b.title)
|
|
674
675
|
|
|
675
|
-
if
|
|
676
|
+
if g.mode == "single":
|
|
677
|
+
blog_url = f"https://{g.main_domain}"
|
|
678
|
+
post_base_url = f"{blog_url}/p"
|
|
679
|
+
elif b.access == "domain":
|
|
676
680
|
blog_url = f"https://{b.name}.{g.main_domain}"
|
|
677
681
|
post_base_url = blog_url
|
|
678
682
|
else:
|
|
@@ -694,5 +698,6 @@ def generate_rss(blog):
|
|
|
694
698
|
fe.content(content=p.content_html, type="CDATA")
|
|
695
699
|
fe.published(p.date.replace(tzinfo=timezone.utc))
|
|
696
700
|
|
|
697
|
-
|
|
698
|
-
|
|
701
|
+
directory = current_app.config.get('RSS_DIRECTORY', f'{g.mainpath}/static/feed')
|
|
702
|
+
os.makedirs(directory, exist_ok=True)
|
|
703
|
+
fg.rss_file(os.path.join(directory, f'{b.name}.xml'))
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from flask import Blueprint, request, jsonify, g, send_from_directory, abort
|
|
2
|
-
from openwrite.utils.models import User
|
|
3
2
|
from werkzeug.utils import secure_filename
|
|
4
3
|
from PIL import Image
|
|
5
4
|
import os
|
|
6
|
-
import
|
|
5
|
+
from uuid import uuid4
|
|
7
6
|
import requests
|
|
8
7
|
|
|
9
8
|
upload_bp = Blueprint("upload", __name__)
|
|
@@ -43,11 +42,7 @@ def upload_image():
|
|
|
43
42
|
return jsonify({"error": g.trans['not_image']}), 400
|
|
44
43
|
|
|
45
44
|
file.stream.seek(0)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
m = hashlib.md5()
|
|
49
|
-
m.update(filename.encode() + user.username.encode())
|
|
50
|
-
filename = f"{m.hexdigest()}.{extension}"
|
|
45
|
+
filename = f"{uuid4().hex}.{extension}"
|
|
51
46
|
|
|
52
47
|
if STORAGE_BACKEND == "bunny":
|
|
53
48
|
url = f"https://storage.bunnycdn.com/{BUNNY_ZONE}/{filename}"
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>singletest.open.write</title><link>https://singletest.open.write</link><description>x</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>singletest.open.write</title><link>https://singletest.open.write</link></image><lastBuildDate>Sat, 07 Nov 2026 22:26:48 +0000</lastBuildDate><ttl>5</ttl><item><title>this is a test</title><link>https://singletest.open.write/p/this-is-a-test</link><description>test...</description><content:encoded><![CDATA[<p>test</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 22:26:48 +0000</pubDate></item><item><title>test</title><link>https://singletest.open.write/p/test-2</link><description>test2...</description><content:encoded><![CDATA[<p>test2</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 22:26:48 +0000</pubDate></item><item><title>test</title><link>https://singletest.open.write/p/test</link><description>test...</description><content:encoded><![CDATA[<p>test</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 22:26:48 +0000</pubDate></item></channel></rss>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
-
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>testx</title><link>https://multitest.open.write/b/testx</link><description>x</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>testx</title><link>https://multitest.open.write/b/testx</link></image><lastBuildDate>Sat, 07 Nov 2026
|
|
2
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>testx</title><link>https://multitest.open.write/b/testx</link><description>x</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>testx</title><link>https://multitest.open.write/b/testx</link></image><lastBuildDate>Sat, 07 Nov 2026 22:26:48 +0000</lastBuildDate><ttl>5</ttl><item><title>this is a test</title><link>https://multitest.open.write/b/testx/this-is-a-test</link><description>test2...</description><content:encoded><![CDATA[<p>test2</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 22:26:48 +0000</pubDate></item></channel></rss>
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
placeholder.textContent = frame.dataset.placeholder;
|
|
11
11
|
placeholder.className = 'placeholder';
|
|
12
12
|
let timer;
|
|
13
|
+
let previewPort;
|
|
14
|
+
let latestHtml = '';
|
|
15
|
+
|
|
16
|
+
function sendPreview() {
|
|
17
|
+
if (previewPort) previewPort.postMessage(latestHtml);
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
function withToc(html) {
|
|
15
21
|
// The template stays detached: authored HTML is never inserted into the editor.
|
|
@@ -60,16 +66,31 @@
|
|
|
60
66
|
if (!toggle.checked) return;
|
|
61
67
|
let html = editor.value.trim() ? converter.makeHtml(editor.value) : placeholder.outerHTML;
|
|
62
68
|
if (tocToggle.checked) html = withToc(html);
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
latestHtml = html;
|
|
70
|
+
if (frame.hasAttribute('srcdoc')) {
|
|
71
|
+
sendPreview();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Load the frame once. Only our nonce-protected updater may run; authored
|
|
75
|
+
// scripts stay blocked and the frame has no same-origin permissions.
|
|
76
|
+
const nonce = Array.from(crypto.getRandomValues(new Uint8Array(16)), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
77
|
+
frame.addEventListener('load', () => {
|
|
78
|
+
// Connect only the initial, empty document. A port stays with that
|
|
79
|
+
// document, so later iframe navigation cannot receive draft content.
|
|
80
|
+
const channel = new MessageChannel();
|
|
81
|
+
previewPort = channel.port1;
|
|
82
|
+
frame.contentWindow.postMessage('openwrite-preview-connect', '*', [channel.port2]);
|
|
83
|
+
sendPreview();
|
|
84
|
+
}, {once: true});
|
|
65
85
|
frame.srcdoc = `<!doctype html><html><head><meta charset="utf-8">
|
|
66
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src http: https: data:; media-src http: https:; base-uri 'none'; form-action 'none'">
|
|
86
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; img-src http: https: data:; media-src http: https:; base-uri 'none'; form-action 'none'">
|
|
67
87
|
<style>
|
|
68
88
|
* { box-sizing: border-box; }
|
|
89
|
+
html { overflow-anchor: none; scroll-behavior: auto; }
|
|
69
90
|
body { margin: 0; padding: 0 1rem 1rem; color: #e9d4ce;
|
|
70
91
|
font: 16px/1.6 system-ui, sans-serif; overflow-wrap: anywhere; }
|
|
71
92
|
h1, h2, h3, h4, h5, h6 { line-height: 1.3; margin: 1em 0 .5em; }
|
|
72
|
-
|
|
93
|
+
#preview-content > :first-child { margin-top: 0; }
|
|
73
94
|
p, ul, ol, pre, blockquote, table { margin: 0 0 1em; }
|
|
74
95
|
a { color: #b8a0eb; }
|
|
75
96
|
img, video { max-width: 100%; height: auto; }
|
|
@@ -84,7 +105,47 @@
|
|
|
84
105
|
.post-toc ul { margin: .35rem 0 0; padding-left: 1.25rem; }
|
|
85
106
|
.post-toc li { margin: .15rem 0; }
|
|
86
107
|
[id^="post-section-"] { scroll-margin-top: 1rem; }
|
|
87
|
-
</style></head><body
|
|
108
|
+
</style></head><body><div id="preview-content"></div>
|
|
109
|
+
<script nonce="${nonce}">
|
|
110
|
+
(() => {
|
|
111
|
+
const content = document.getElementById('preview-content');
|
|
112
|
+
window.addEventListener('message', event => {
|
|
113
|
+
if (event.source !== parent || event.data !== 'openwrite-preview-connect' || !event.ports[0]) return;
|
|
114
|
+
event.ports[0].onmessage = ({data}) => {
|
|
115
|
+
if (typeof data !== 'string') return;
|
|
116
|
+
const x = window.scrollX, y = window.scrollY;
|
|
117
|
+
const template = document.createElement('template');
|
|
118
|
+
template.innerHTML = data;
|
|
119
|
+
template.content.querySelectorAll('script, meta, base, link, iframe, object, embed').forEach(node => node.remove());
|
|
120
|
+
// Keep unchanged blocks (especially loaded images) in place.
|
|
121
|
+
const oldNodes = [...content.childNodes];
|
|
122
|
+
const newNodes = [...template.content.childNodes];
|
|
123
|
+
let start = 0, oldEnd = oldNodes.length, newEnd = newNodes.length;
|
|
124
|
+
while (start < oldEnd && start < newEnd && oldNodes[start].isEqualNode(newNodes[start])) start++;
|
|
125
|
+
while (oldEnd > start && newEnd > start && oldNodes[oldEnd - 1].isEqualNode(newNodes[newEnd - 1])) { oldEnd--; newEnd--; }
|
|
126
|
+
const fragment = document.createDocumentFragment();
|
|
127
|
+
for (let i = start; i < newEnd; i++) fragment.append(newNodes[i]);
|
|
128
|
+
const range = document.createRange();
|
|
129
|
+
range.setStart(content, start);
|
|
130
|
+
range.setEnd(content, oldEnd);
|
|
131
|
+
range.deleteContents();
|
|
132
|
+
range.insertNode(fragment);
|
|
133
|
+
window.scrollTo({left: x, top: y, behavior: 'instant'});
|
|
134
|
+
};
|
|
135
|
+
}, {once: true});
|
|
136
|
+
// Keep TOC links inside this document; other links must not replace it.
|
|
137
|
+
document.addEventListener('click', event => {
|
|
138
|
+
const link = event.target.closest('a');
|
|
139
|
+
if (!link) return;
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
const href = link.getAttribute('href') || '';
|
|
142
|
+
if (href.startsWith('about:srcdoc#') || href.startsWith('#')) {
|
|
143
|
+
const id = href.slice(href.indexOf('#') + 1);
|
|
144
|
+
document.getElementById(id)?.scrollIntoView();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
})();
|
|
148
|
+
</script></body></html>`;
|
|
88
149
|
}
|
|
89
150
|
|
|
90
151
|
toggle.addEventListener('change', () => {
|
|
@@ -92,7 +153,6 @@
|
|
|
92
153
|
panel.hidden = !toggle.checked;
|
|
93
154
|
layout.classList.toggle('has-preview', toggle.checked);
|
|
94
155
|
if (toggle.checked) render();
|
|
95
|
-
else frame.removeAttribute('srcdoc');
|
|
96
156
|
});
|
|
97
157
|
|
|
98
158
|
editor.addEventListener('input', () => {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<div class="post-editor" id="post-editor">
|
|
47
47
|
<textarea autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" name="content" class="post_content" placeholder="{{ _('content') }}">{% if form_data %}{{ form_data.get('content_raw', '') }}{% elif post %}{{ post.content_raw }}{% endif %}</textarea>
|
|
48
48
|
<section class="live-preview-panel" id="live-preview-panel" aria-label="{{ live_preview_label }}" hidden>
|
|
49
|
-
<iframe id="live-preview-frame" title="{{ live_preview_label }}" sandbox="" referrerpolicy="no-referrer" data-placeholder="{{ preview_hint }}"></iframe>
|
|
49
|
+
<iframe id="live-preview-frame" title="{{ live_preview_label }}" sandbox="allow-scripts" referrerpolicy="no-referrer" data-placeholder="{{ preview_hint }}"></iframe>
|
|
50
50
|
</section>
|
|
51
51
|
</div>
|
|
52
52
|
<input type="hidden" name="content_raw" id="content_raw" value="{% if post %}{{ post.content_raw }}{% endif %}">
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
-
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>singletest.open.write</title><link>https://default.singletest.open.write</link><description>x</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>singletest.open.write</title><link>https://default.singletest.open.write</link></image><lastBuildDate>Sat, 07 Nov 2026 21:25:56 +0000</lastBuildDate><ttl>5</ttl><item><title>this is a test</title><link>https://default.singletest.open.write/this-is-a-test</link><description>test...</description><content:encoded><![CDATA[<p>test</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 21:25:56 +0000</pubDate></item><item><title>test</title><link>https://default.singletest.open.write/test-2</link><description>test2...</description><content:encoded><![CDATA[<p>test2</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 21:25:56 +0000</pubDate></item><item><title>test</title><link>https://default.singletest.open.write/test</link><description>test...</description><content:encoded><![CDATA[<p>test</p>]]></content:encoded><pubDate>Sat, 07 Nov 2026 21:25:56 +0000</pubDate></item></channel></rss>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|