openwrite 0.12.0__tar.gz → 0.13.1__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.12.0 → openwrite-0.13.1}/MANIFEST.in +3 -0
- {openwrite-0.12.0/openwrite.egg-info → openwrite-0.13.1}/PKG-INFO +2 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/README.md +1 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/__init__.py +1 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/gemini_multi.py +33 -12
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/gemini_single.py +37 -12
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/admin.py +24 -9
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/blog.py +12 -9
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/dashboard.py +81 -31
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/federation.py +176 -24
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/main.py +5 -3
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/style.css +6 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/create.html +7 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/dashboard.html +5 -2
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/discover.html +1 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/edit.html +13 -6
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/footer.html +1 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/header.html +0 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/new_page.html +7 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/new_post.html +97 -14
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/post.html +7 -3
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/settings.html +7 -1
- openwrite-0.13.1/openwrite/utils/i18n.json +1 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/utils/models.py +1 -0
- {openwrite-0.12.0 → openwrite-0.13.1/openwrite.egg-info}/PKG-INFO +2 -1
- {openwrite-0.12.0 → openwrite-0.13.1}/pyproject.toml +4 -1
- openwrite-0.12.0/openwrite/utils/i18n.json +0 -227
- {openwrite-0.12.0 → openwrite-0.13.1}/LICENSE +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/cli.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/db/__init__.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/db/base.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/auth.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/routes/upload.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/Merriweather.ttf +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/avatar.png +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/emoji.json +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/favicon.ico +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/logo.png +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/chart.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/highlight.min.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/nativeEmoji.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/openwrite.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/papaparse.min.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/showdown.min.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/showdown.min.js.map +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/site.compat.min.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/script/site.min.js +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/hl.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/nativeEmoji.min.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/cyberpunk.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/light.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/obscura.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/stickynote.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/terminal.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/warmnight.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/static/style/themes/windows98.css +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/admin.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/blog.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/blog_footer.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/blog_header.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/blog_preview.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/changepw.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/import.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/index.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/instances.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/login.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/page_preview.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/preview.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/register.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/stats.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/templates/translations.html +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/utils/create_db.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/utils/db.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite/utils/helpers.py +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite.egg-info/SOURCES.txt +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite.egg-info/dependency_links.txt +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite.egg-info/entry_points.txt +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite.egg-info/requires.txt +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/openwrite.egg-info/top_level.txt +0 -0
- {openwrite-0.12.0 → openwrite-0.13.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openwrite
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.1
|
|
4
4
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
5
5
|
Version 3, 19 November 2007
|
|
6
6
|
|
|
@@ -704,6 +704,7 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
|
|
|
704
704
|
- Single-blog mode
|
|
705
705
|
- Supports sqlite and mysql databases
|
|
706
706
|
- Upload images to local storage or bunny cdn
|
|
707
|
+
- Drafts
|
|
707
708
|
- Simple markdown editor in posting
|
|
708
709
|
- Discover section
|
|
709
710
|
- Privacy:
|
|
@@ -15,6 +15,7 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
|
|
|
15
15
|
- Single-blog mode
|
|
16
16
|
- Supports sqlite and mysql databases
|
|
17
17
|
- Upload images to local storage or bunny cdn
|
|
18
|
+
- Drafts
|
|
18
19
|
- Simple markdown editor in posting
|
|
19
20
|
- Discover section
|
|
20
21
|
- Privacy:
|
|
@@ -85,7 +85,7 @@ def create_app(test_config=None):
|
|
|
85
85
|
url="",
|
|
86
86
|
content_raw=f"\n\n# Hello there! 👋\n\nYou can edit your blog home page in [dashboard](https://{domain}/dashboard/page/1/edit)\n\n---\n### Posts\n\n{{posts}}",
|
|
87
87
|
content_html=f"<p><img src=\"https://openwrite.b-cdn.net/hello.jpg\" width=\"500\" height=\"258\"></p><h1>Hello there! 👋</h1><p>You can edit your blog home page in <a href=\"https://{domain}/dashboard/page/1/edit\">dashboard</a></p>\n\n<hr>\n<h3>Posts\n\n{{posts}}",
|
|
88
|
-
show="
|
|
88
|
+
show="0"
|
|
89
89
|
)
|
|
90
90
|
SessionLocal.add(new_page)
|
|
91
91
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import os, subprocess
|
|
2
2
|
from dotenv import load_dotenv
|
|
3
3
|
from openwrite.utils.db import init_engine, SessionLocal
|
|
4
|
-
from openwrite.utils.models import Blog, Post, User, View
|
|
4
|
+
from openwrite.utils.models import Blog, Post, User, View, Page
|
|
5
5
|
from openwrite.utils.helpers import anonymize
|
|
6
6
|
from md2gemini import md2gemini
|
|
7
7
|
from datetime import datetime, timezone
|
|
8
|
+
import re
|
|
8
9
|
session = None
|
|
9
10
|
|
|
10
11
|
openwrite_logo = """
|
|
@@ -25,7 +26,7 @@ def root(req):
|
|
|
25
26
|
resp += "In here you can read posts published.\n"
|
|
26
27
|
resp += "\n## 🗞️ Latest posts:\n"
|
|
27
28
|
try:
|
|
28
|
-
posts = (session.query(Post).
|
|
29
|
+
posts = (session.query(Post).filter(Post.feed == "1", Post.isdraft == "0").order_by(Post.id.desc()).limit(10).all())
|
|
29
30
|
for p in posts:
|
|
30
31
|
blog = session.query(Blog).filter_by(id=p.blog).first()
|
|
31
32
|
resp += f"\n{p.date} - 📓 {blog.title}\n"
|
|
@@ -45,15 +46,24 @@ def blog_index(req):
|
|
|
45
46
|
if not blog:
|
|
46
47
|
return "not found"
|
|
47
48
|
posts = (session.query(Post)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
.filter_by(blog=blog.id, isdraft="0")
|
|
50
|
+
.order_by(Post.id.desc())
|
|
51
|
+
.all())
|
|
52
|
+
pages = (session.query(Page)
|
|
53
|
+
.filter(Page.blog == blog.id, Page.show == "1").all())
|
|
54
|
+
homepage = session.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
51
55
|
body = f"# 📓 {blog.title}\n\n"
|
|
52
|
-
|
|
53
|
-
body += "\n--------------------------------------------------\n"
|
|
54
|
-
body += "### Posts:\n\n"
|
|
56
|
+
posts_data = ""
|
|
55
57
|
for post in posts:
|
|
56
|
-
|
|
58
|
+
posts_data += f"=> /b/{blogname}/{post.link} {post.title}\n\n"
|
|
59
|
+
pages_data = ""
|
|
60
|
+
for page in pages:
|
|
61
|
+
pages_data += f"=> /b/{blogname}/{page.url} {page.name}\n\n"
|
|
62
|
+
content = pages_data + "\n"
|
|
63
|
+
content += homepage.content_raw.replace('{posts}', f"\n{posts_data}")
|
|
64
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
65
|
+
body += md2gemini(content)
|
|
66
|
+
|
|
57
67
|
return body
|
|
58
68
|
finally:
|
|
59
69
|
session.close()
|
|
@@ -67,10 +77,19 @@ def blog_index(req):
|
|
|
67
77
|
return "not found"
|
|
68
78
|
|
|
69
79
|
post = (session.query(Post)
|
|
70
|
-
.filter_by(blog=blog.id, link=slug)
|
|
80
|
+
.filter_by(blog=blog.id, link=slug, isdraft="0")
|
|
71
81
|
.first())
|
|
72
82
|
if not post:
|
|
73
|
-
|
|
83
|
+
page = (session.query(Page)
|
|
84
|
+
.filter_by(blog=blog.id, url=slug)
|
|
85
|
+
.first())
|
|
86
|
+
if page:
|
|
87
|
+
content = f"=> /b/{blog.name} 📓 {blog.title}\n\n"
|
|
88
|
+
content += page.content_raw
|
|
89
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
90
|
+
return md2gemini(content)
|
|
91
|
+
else:
|
|
92
|
+
return "not found"
|
|
74
93
|
user = session.query(User).filter_by(id=blog.owner).first()
|
|
75
94
|
|
|
76
95
|
now = datetime.now(timezone.utc)
|
|
@@ -88,7 +107,9 @@ def blog_index(req):
|
|
|
88
107
|
if post.author != "0":
|
|
89
108
|
gemtext += f"by {post.authorname}"
|
|
90
109
|
gemtext += "\n--------------------------------------------------\n"
|
|
91
|
-
|
|
110
|
+
content = post.content_raw
|
|
111
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
112
|
+
gemtext += f"\n\n{md2gemini(content)}\n\n"
|
|
92
113
|
return gemtext
|
|
93
114
|
|
|
94
115
|
finally:
|
|
@@ -11,17 +11,28 @@ def blog_index(req):
|
|
|
11
11
|
global session
|
|
12
12
|
try:
|
|
13
13
|
blog = session.query(Blog).filter_by(name="default").first()
|
|
14
|
-
|
|
14
|
+
if not blog:
|
|
15
|
+
return "not found"
|
|
16
|
+
posts = (session.query(Post)
|
|
15
17
|
.filter_by(blog=blog.id)
|
|
16
18
|
.order_by(Post.id.desc())
|
|
17
19
|
.all())
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
pages = (session.query(Page)
|
|
21
|
+
.filter(Page.blog == blog.id, Page.show == "1").all())
|
|
22
|
+
homepage = session.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
23
|
+
body = f"# 📓 {blog.title}\n\n"
|
|
24
|
+
posts_data = ""
|
|
25
|
+
for post in posts:
|
|
26
|
+
posts_data += f"=> /b/{blogname}/{post.link} {post.title}\n\n"
|
|
27
|
+
pages_data = ""
|
|
28
|
+
for page in pages:
|
|
29
|
+
pages_data += f"=> /b/{blogname}/{page.url} {page.name}\n\n"
|
|
30
|
+
content = pages_data + "\n"
|
|
31
|
+
content += homepage.content_raw.replace('{posts}', f"\n{posts_data}")
|
|
32
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
33
|
+
body += md2gemini(content)
|
|
34
|
+
|
|
35
|
+
return body
|
|
25
36
|
finally:
|
|
26
37
|
session.close()
|
|
27
38
|
|
|
@@ -33,11 +44,23 @@ def blog_post(req):
|
|
|
33
44
|
slug = path.split("/")[2]
|
|
34
45
|
try:
|
|
35
46
|
blog = session.query(Blog).filter_by(name="default").first()
|
|
47
|
+
if not blog:
|
|
48
|
+
return "not found"
|
|
49
|
+
|
|
36
50
|
post = (session.query(Post)
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
.filter_by(blog=blog.id, link=slug, isdraft="0")
|
|
52
|
+
.first())
|
|
39
53
|
if not post:
|
|
40
|
-
|
|
54
|
+
page = (session.query(Page)
|
|
55
|
+
.filter_by(blog=blog.id, url=slug)
|
|
56
|
+
.first())
|
|
57
|
+
if page:
|
|
58
|
+
content = f"=> /b/{blog.name} 📓 {blog.title}\n\n"
|
|
59
|
+
content += page.content_raw
|
|
60
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
61
|
+
return md2gemini(content)
|
|
62
|
+
else:
|
|
63
|
+
return "not found"
|
|
41
64
|
user = session.query(User).filter_by(id=blog.owner).first()
|
|
42
65
|
|
|
43
66
|
now = datetime.now(timezone.utc)
|
|
@@ -55,7 +78,9 @@ def blog_post(req):
|
|
|
55
78
|
if post.author != "0":
|
|
56
79
|
gemtext += f"by {post.authorname}"
|
|
57
80
|
gemtext += "\n--------------------------------------------------\n"
|
|
58
|
-
|
|
81
|
+
content = post.content_raw
|
|
82
|
+
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', content)
|
|
83
|
+
gemtext += f"\n\n{md2gemini(content)}\n\n"
|
|
59
84
|
return gemtext
|
|
60
85
|
|
|
61
86
|
finally:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from flask import Blueprint, render_template, redirect, g, request, make_response
|
|
2
2
|
import json
|
|
3
|
-
from openwrite.utils.models import Blog, User, Settings, Home
|
|
3
|
+
from openwrite.utils.models import Blog, User, Settings, Home, Post
|
|
4
4
|
import re
|
|
5
5
|
import bcrypt
|
|
6
6
|
from werkzeug.utils import secure_filename
|
|
@@ -116,16 +116,16 @@ def admin_add_user():
|
|
|
116
116
|
|
|
117
117
|
form_username = request.form.get('username')
|
|
118
118
|
if not re.match(r"^[a-zA-Z0-9](?:[a-zA-Z0-9_-]{1,28}[a-zA-Z0-9])?$", form_username):
|
|
119
|
-
return render_template('register.html', error=
|
|
119
|
+
return render_template('register.html', error='wrong_username', add="1", captcha="0")
|
|
120
120
|
form_password = request.form.get('password')
|
|
121
121
|
form_password2 = request.form.get('password2')
|
|
122
122
|
|
|
123
123
|
if form_password != form_password2:
|
|
124
|
-
return render_template("register.html", error=
|
|
124
|
+
return render_template("register.html", error='password_dont_match', add="1", captcha="0")
|
|
125
125
|
|
|
126
126
|
user = g.db.query(User).filter_by(username=form_username).first()
|
|
127
127
|
if user:
|
|
128
|
-
return render_template('register.html', error=
|
|
128
|
+
return render_template('register.html', error="user_exists", add="1", captcha="0")
|
|
129
129
|
|
|
130
130
|
try:
|
|
131
131
|
hashed = bcrypt.hashpw(form_password.encode('utf-8'), bcrypt.gensalt())
|
|
@@ -136,7 +136,7 @@ def admin_add_user():
|
|
|
136
136
|
except Exception as e:
|
|
137
137
|
print(e)
|
|
138
138
|
g.db.rollback()
|
|
139
|
-
return render_template('register.html', error=
|
|
139
|
+
return render_template('register.html', error='error', add="1", captcha="0")
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
@admin_bp.route("/admin/settings", methods=['GET', 'POST'])
|
|
@@ -166,17 +166,17 @@ def admin_settings():
|
|
|
166
166
|
file = request.files['logo']
|
|
167
167
|
filename = secure_filename(file.filename)
|
|
168
168
|
if "." not in filename:
|
|
169
|
-
return render_template("settings.html", settings=settings_dict, hometext=hometext, error=
|
|
169
|
+
return render_template("settings.html", settings=settings_dict, hometext=hometext, error='invalid_filename')
|
|
170
170
|
|
|
171
171
|
extension = filename.rsplit('.', 1)[1].lower()
|
|
172
172
|
if extension not in ALLOWED_EXTENSIONS:
|
|
173
|
-
return render_template("settings.html", settings=settings_dict, hometext=hometext, error=
|
|
173
|
+
return render_template("settings.html", settings=settings_dict, hometext=hometext, error='unsupported_filetype')
|
|
174
174
|
|
|
175
175
|
try:
|
|
176
176
|
img = Image.open(file.stream)
|
|
177
177
|
img.verify()
|
|
178
178
|
except Exception:
|
|
179
|
-
return render_template("settings.html", settings=settings_dict, hometext=hometext, error="
|
|
179
|
+
return render_template("settings.html", settings=settings_dict, hometext=hometext, error="not_image")
|
|
180
180
|
|
|
181
181
|
file.stream.seek(0)
|
|
182
182
|
|
|
@@ -245,4 +245,19 @@ def download_translations():
|
|
|
245
245
|
response = make_response(json.dumps(dict(trans_dict), ensure_ascii=False))
|
|
246
246
|
response.headers["Content-Disposition"] = "attachment; filename=i18n.json"
|
|
247
247
|
response.headers["Content-Type"] = "application/json"
|
|
248
|
-
return response
|
|
248
|
+
return response
|
|
249
|
+
|
|
250
|
+
@admin_bp.route("/admin/remove_from_feed/<int:post_id>")
|
|
251
|
+
def remove_from_feed(post_id):
|
|
252
|
+
"""
|
|
253
|
+
Remove a post from the discover feed.
|
|
254
|
+
"""
|
|
255
|
+
if g.user is None or g.isadmin == 0 or g.mode == "single":
|
|
256
|
+
return redirect("/")
|
|
257
|
+
|
|
258
|
+
post = g.db.query(Post).filter_by(id=post_id).first()
|
|
259
|
+
if post:
|
|
260
|
+
post.feed = "0"
|
|
261
|
+
g.db.commit()
|
|
262
|
+
|
|
263
|
+
return redirect("/discover")
|
|
@@ -24,7 +24,7 @@ def show_blog(blog):
|
|
|
24
24
|
blog.url = f"/b/{blog.name}"
|
|
25
25
|
homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
26
26
|
if "{posts}" in homepage.content_raw:
|
|
27
|
-
posts = g.db.query(Post).
|
|
27
|
+
posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
|
|
28
28
|
return render_template("blog.html", blog=blog, page=homepage, posts=posts, pages=pages)
|
|
29
29
|
|
|
30
30
|
return render_template("blog.html", blog=blog, page=homepage, pages=pages)
|
|
@@ -40,10 +40,11 @@ def show_subblog(blog):
|
|
|
40
40
|
if blog.access == "path":
|
|
41
41
|
return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog.name}")
|
|
42
42
|
|
|
43
|
+
blog.url = f"https://{blog.name}.{os.getenv('DOMAIN')}"
|
|
43
44
|
pages = g.db.query(Page).filter_by(blog=blog.id).all()
|
|
44
45
|
homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
45
46
|
if "{posts}" in homepage.content_raw:
|
|
46
|
-
posts = g.db.query(Post).
|
|
47
|
+
posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
|
|
47
48
|
return render_template("blog.html", blog=blog, page=homepage, posts=posts, pages=pages)
|
|
48
49
|
|
|
49
50
|
return render_template("blog.html", blog=blog, page=homepage, pages=pages)
|
|
@@ -64,14 +65,14 @@ def show_post(blog, post):
|
|
|
64
65
|
|
|
65
66
|
blog.url = f"/b/{blog.name}"
|
|
66
67
|
pages = g.db.query(Page).filter_by(blog=blog.id).all()
|
|
67
|
-
one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post).first()
|
|
68
|
+
one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post, Post.isdraft == "0").first()
|
|
68
69
|
if not one_post:
|
|
69
70
|
page = g.db.query(Page).filter(Page.blog == blog.id, Page.url == post).first()
|
|
70
71
|
if not page:
|
|
71
72
|
return redirect("/")
|
|
72
73
|
|
|
73
74
|
if "{posts}" in page.content_raw:
|
|
74
|
-
posts = g.db.query(Post).
|
|
75
|
+
posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
|
|
75
76
|
|
|
76
77
|
return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
|
|
77
78
|
|
|
@@ -112,8 +113,10 @@ def show_subpost(blog, post):
|
|
|
112
113
|
if blog.access == "path":
|
|
113
114
|
return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog.name}/{post}")
|
|
114
115
|
|
|
116
|
+
blog.url = f"https://{blog.name}.{os.getenv('DOMAIN')}"
|
|
117
|
+
|
|
115
118
|
pages = g.db.query(Page).filter_by(blog=blog.id).all()
|
|
116
|
-
one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post).first()
|
|
119
|
+
one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post, Post.isdraft == "0").first()
|
|
117
120
|
|
|
118
121
|
if not one_post:
|
|
119
122
|
page = g.db.query(Page).filter(Page.blog == blog.id, Page.url == post).first()
|
|
@@ -121,7 +124,7 @@ def show_subpost(blog, post):
|
|
|
121
124
|
return redirect("/")
|
|
122
125
|
|
|
123
126
|
if "{posts}" in page.content_raw:
|
|
124
|
-
posts = g.db.query(Post).
|
|
127
|
+
posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
|
|
125
128
|
|
|
126
129
|
return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
|
|
127
130
|
|
|
@@ -155,7 +158,7 @@ def single_showpost(post):
|
|
|
155
158
|
|
|
156
159
|
blog = g.db.query(Blog).filter_by(id=1).first()
|
|
157
160
|
blog.url = f"http://{g.main_domain}"
|
|
158
|
-
one_post = g.db.query(Post).filter(Post.blog == 1, Post.link == post).first()
|
|
161
|
+
one_post = g.db.query(Post).filter(Post.blog == 1, Post.link == post, Post.isdraft == "0").first()
|
|
159
162
|
|
|
160
163
|
pages = g.db.query(Page).filter_by(blog=1).all()
|
|
161
164
|
|
|
@@ -165,7 +168,7 @@ def single_showpost(post):
|
|
|
165
168
|
return redirect("/")
|
|
166
169
|
|
|
167
170
|
if "{posts}" in page.content_raw:
|
|
168
|
-
posts = g.db.query(Post).
|
|
171
|
+
posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
|
|
169
172
|
|
|
170
173
|
return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
|
|
171
174
|
|
|
@@ -230,7 +233,7 @@ def like():
|
|
|
230
233
|
|
|
231
234
|
|
|
232
235
|
def _generate_rss(blog):
|
|
233
|
-
posts = g.db.query(Post).
|
|
236
|
+
posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").all()
|
|
234
237
|
fg = FeedGenerator()
|
|
235
238
|
fg.title(blog.title)
|
|
236
239
|
fg.link(href=f"https://{os.getenv('DOMAIN')}/b/{blog.name}", rel="alternate")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from flask import Blueprint, render_template, redirect, request, g, abort
|
|
1
|
+
from flask import Blueprint, render_template, redirect, request, g, abort, Response
|
|
2
2
|
from openwrite.utils.models import Blog, Post, User, View, Page
|
|
3
3
|
from openwrite.utils.helpers import sanitize_html, gen_link, safe_css, send_activity, is_html, get_themes
|
|
4
4
|
import requests
|
|
@@ -13,6 +13,8 @@ import markdown
|
|
|
13
13
|
from bs4 import BeautifulSoup
|
|
14
14
|
from user_agents import parse
|
|
15
15
|
import re
|
|
16
|
+
import csv
|
|
17
|
+
from io import StringIO
|
|
16
18
|
|
|
17
19
|
dashboard_bp = Blueprint("dashboard", __name__)
|
|
18
20
|
|
|
@@ -36,7 +38,7 @@ def create_blog():
|
|
|
36
38
|
if int(g.blog_limit) > 0:
|
|
37
39
|
count = g.db.query(Blog).filter_by(owner=g.user).count()
|
|
38
40
|
if count >= int(g.blog_limit):
|
|
39
|
-
return render_template("create.html", error="
|
|
41
|
+
return render_template("create.html", error="blog-limit-reached")
|
|
40
42
|
|
|
41
43
|
if request.method == "GET":
|
|
42
44
|
return render_template("create.html")
|
|
@@ -44,17 +46,17 @@ def create_blog():
|
|
|
44
46
|
form_name = request.form.get("name")
|
|
45
47
|
form_url = gen_link(request.form.get("url"))
|
|
46
48
|
if len(form_name) > 30:
|
|
47
|
-
return render_template("create.html", error="
|
|
49
|
+
return render_template("create.html", error="title-too-long")
|
|
48
50
|
if len(form_url) > 30:
|
|
49
|
-
return render_template("create.html", error="
|
|
51
|
+
return render_template("create.html", error="url-too-long")
|
|
50
52
|
blog = g.db.query(Blog).filter_by(name=form_url).first()
|
|
51
53
|
if blog:
|
|
52
|
-
return render_template("create.html", error="
|
|
54
|
+
return render_template("create.html", error="url-exists")
|
|
53
55
|
|
|
54
56
|
form_index = request.form.get("index") or "off"
|
|
55
57
|
form_access = request.form.get("access")
|
|
56
58
|
if form_access not in ("path", "domain"):
|
|
57
|
-
return render_template("create.html", error="
|
|
59
|
+
return render_template("create.html", error="wrong-access")
|
|
58
60
|
|
|
59
61
|
key = rsa.generate_private_key(
|
|
60
62
|
public_exponent=65537,
|
|
@@ -99,15 +101,15 @@ def create_blog():
|
|
|
99
101
|
name="Home",
|
|
100
102
|
url="",
|
|
101
103
|
content_raw=f"\n\n# Hello there! 👋\n\nYou can edit your blog home page in [dashboard](https://{g.main_domain}/dashboard/edit/{form_url})\n\n---\n### Posts\n\n{{posts}}",
|
|
102
|
-
content_html=f"<p><img src=\"https://openwrite.b-cdn.net/hello.jpg\" width=\"500\" height=\"258\"></p><h1>Hello there! 👋</h1><p>You can edit your blog home page in <a href=\"https://{g.main_domain}/dashboard/edit/{form_url}\">dashboard</a></p>\n\n<hr>\n<h3>Posts
|
|
103
|
-
show="
|
|
104
|
+
content_html=f"<p><img src=\"https://openwrite.b-cdn.net/hello.jpg\" width=\"500\" height=\"258\"></p><h1>Hello there! 👋</h1><p>You can edit your blog home page in <a href=\"https://{g.main_domain}/dashboard/edit/{form_url}\">dashboard</a></p>\n\n<hr>\n<h3>Posts</h3>\n\n{{posts}}",
|
|
105
|
+
show="0"
|
|
104
106
|
)
|
|
105
107
|
g.db.add(new_page)
|
|
106
108
|
g.db.commit()
|
|
107
109
|
|
|
108
110
|
return redirect("/dashboard")
|
|
109
111
|
except Exception:
|
|
110
|
-
return render_template("create.html", error=
|
|
112
|
+
return render_template("create.html", error="error")
|
|
111
113
|
|
|
112
114
|
@dashboard_bp.route("/dashboard/delete/<name>")
|
|
113
115
|
def delete_blog(name):
|
|
@@ -118,7 +120,7 @@ def delete_blog(name):
|
|
|
118
120
|
|
|
119
121
|
blog = g.db.query(Blog).filter_by(name=name).first()
|
|
120
122
|
if blog is None or blog.owner != g.user:
|
|
121
|
-
|
|
123
|
+
abort(403)
|
|
122
124
|
|
|
123
125
|
g.db.delete(blog)
|
|
124
126
|
g.db.commit()
|
|
@@ -131,7 +133,7 @@ def edit_blog(name):
|
|
|
131
133
|
|
|
132
134
|
blog = g.db.query(Blog).filter_by(name=name).first()
|
|
133
135
|
if blog is None or blog.owner != g.user:
|
|
134
|
-
|
|
136
|
+
abort(403)
|
|
135
137
|
|
|
136
138
|
posts = g.db.query(Post).filter_by(blog=blog.id).all()
|
|
137
139
|
for p in posts:
|
|
@@ -148,14 +150,14 @@ def edit_blog(name):
|
|
|
148
150
|
|
|
149
151
|
now = datetime.now(timezone.utc).replace(microsecond=0)
|
|
150
152
|
if len(request.form.get("title")) > 30:
|
|
151
|
-
return render_template("edit.html", blog=blog, posts=posts, themes=themes, pages=pages, error="
|
|
153
|
+
return render_template("edit.html", blog=blog, posts=posts, themes=themes, pages=pages, error="title-too-long")
|
|
152
154
|
blog.css = safe_css(request.form.get("css"))
|
|
153
155
|
blog.updated = now
|
|
154
156
|
blog.title = request.form.get("title")
|
|
155
157
|
blog.favicon = request.form.get("icon")[:10]
|
|
156
158
|
selected_theme = request.form.get("theme")
|
|
157
159
|
if selected_theme not in themes:
|
|
158
|
-
return render_template("edit.html", blog=blog, posts=posts, themes=themes, pages=pages, error="
|
|
160
|
+
return render_template("edit.html", blog=blog, posts=posts, themes=themes, pages=pages, error="wrong-theme")
|
|
159
161
|
blog.theme = selected_theme
|
|
160
162
|
g.db.commit()
|
|
161
163
|
|
|
@@ -168,7 +170,7 @@ def new_post(name):
|
|
|
168
170
|
|
|
169
171
|
blog = g.db.query(Blog).filter_by(name=name).first()
|
|
170
172
|
if blog is None or blog.owner != g.user:
|
|
171
|
-
|
|
173
|
+
abort(403)
|
|
172
174
|
|
|
173
175
|
if request.method == "GET":
|
|
174
176
|
return render_template("new_post.html", blog=blog)
|
|
@@ -176,8 +178,11 @@ def new_post(name):
|
|
|
176
178
|
u = g.db.query(User).filter_by(id=g.user).first()
|
|
177
179
|
title = request.form.get('title')
|
|
178
180
|
if len(title) > 120:
|
|
179
|
-
return render_template("new_post.html", blog=blog, error="
|
|
180
|
-
|
|
181
|
+
return render_template("new_post.html", blog=blog, error="title-too-long")
|
|
182
|
+
|
|
183
|
+
if len(title) < 1:
|
|
184
|
+
return render_template("new_post.html", blog=blog, error="title-empty")
|
|
185
|
+
|
|
181
186
|
link = gen_link(title)
|
|
182
187
|
if link == "rss":
|
|
183
188
|
link = "p_rss"
|
|
@@ -198,7 +203,8 @@ def new_post(name):
|
|
|
198
203
|
author=request.form.get('author'),
|
|
199
204
|
link=link,
|
|
200
205
|
date=now,
|
|
201
|
-
feed=request.form.get('feed')
|
|
206
|
+
feed=request.form.get('feed'),
|
|
207
|
+
isdraft=request.form.get('draft', '0')
|
|
202
208
|
)
|
|
203
209
|
g.db.add(post)
|
|
204
210
|
g.db.commit()
|
|
@@ -268,7 +274,7 @@ def blog_preview():
|
|
|
268
274
|
blog_name = request.referrer.split("/")[-1]
|
|
269
275
|
blog = g.db.query(Blog).filter_by(name=blog_name).first()
|
|
270
276
|
if not blog:
|
|
271
|
-
|
|
277
|
+
abort(404)
|
|
272
278
|
|
|
273
279
|
homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
274
280
|
data = {
|
|
@@ -289,7 +295,7 @@ def edit_post(blog, post):
|
|
|
289
295
|
|
|
290
296
|
blog_obj = g.db.query(Blog).filter_by(name=blog).first()
|
|
291
297
|
if blog_obj is None or blog_obj.owner != g.user:
|
|
292
|
-
|
|
298
|
+
abort(403)
|
|
293
299
|
|
|
294
300
|
e_post = g.db.query(Post).filter_by(link=post).first()
|
|
295
301
|
if request.method == "GET":
|
|
@@ -297,11 +303,11 @@ def edit_post(blog, post):
|
|
|
297
303
|
|
|
298
304
|
p = g.db.query(Post).filter_by(link=post, blog=blog_obj.id).first()
|
|
299
305
|
if not p:
|
|
300
|
-
|
|
306
|
+
abort(404)
|
|
301
307
|
|
|
302
308
|
title = request.form.get("title")
|
|
303
309
|
if len(title) > 120:
|
|
304
|
-
return render_template("new_post.html", blog=blog_obj, error="
|
|
310
|
+
return render_template("new_post.html", blog=blog_obj, error="title-too-long")
|
|
305
311
|
link = gen_link(title)
|
|
306
312
|
if link == "rss":
|
|
307
313
|
link = "p_rss"
|
|
@@ -319,6 +325,7 @@ def edit_post(blog, post):
|
|
|
319
325
|
p.feed = request.form.get("feed")
|
|
320
326
|
p.link = link
|
|
321
327
|
p.updated = now
|
|
328
|
+
p.isdraft = request.form.get("draft", "0")
|
|
322
329
|
g.db.commit()
|
|
323
330
|
return redirect(f"/dashboard/edit/{blog_obj.name}")
|
|
324
331
|
|
|
@@ -329,7 +336,7 @@ def delete_post(blog, post):
|
|
|
329
336
|
|
|
330
337
|
blog_obj = g.db.query(Blog).filter_by(name=blog).first()
|
|
331
338
|
if blog_obj is None or blog_obj.owner != g.user:
|
|
332
|
-
|
|
339
|
+
abort(403)
|
|
333
340
|
|
|
334
341
|
p = g.db.query(Post).filter_by(link=post, blog=blog_obj.id).first()
|
|
335
342
|
if p:
|
|
@@ -353,14 +360,14 @@ def changepw():
|
|
|
353
360
|
user = g.db.query(User).filter_by(id=g.user).first()
|
|
354
361
|
if user and bcrypt.checkpw(old_pw.encode('utf-8'), user.password_hash.encode('utf-8')):
|
|
355
362
|
if new_pass != new_pass2:
|
|
356
|
-
return render_template("changepw.html", error=
|
|
363
|
+
return render_template("changepw.html", error='passwords_dont_match')
|
|
357
364
|
hashed = bcrypt.hashpw(new_pass.encode('utf-8'), bcrypt.gensalt())
|
|
358
365
|
user.password_hash = hashed
|
|
359
366
|
return redirect("/dashboard")
|
|
360
367
|
|
|
361
368
|
else:
|
|
362
|
-
return render_template("changepw.html", error=
|
|
363
|
-
|
|
369
|
+
return render_template("changepw.html", error='invalid_password')
|
|
370
|
+
|
|
364
371
|
|
|
365
372
|
@dashboard_bp.route("/dashboard/import", methods=['GET', 'POST'])
|
|
366
373
|
def migrate():
|
|
@@ -377,7 +384,7 @@ def migrate():
|
|
|
377
384
|
|
|
378
385
|
blog = g.db.query(Blog).filter_by(name=blog_d).first()
|
|
379
386
|
if blog.owner != g.user:
|
|
380
|
-
|
|
387
|
+
abort(403)
|
|
381
388
|
|
|
382
389
|
for post in posts:
|
|
383
390
|
title = post['title']
|
|
@@ -404,7 +411,7 @@ def stats(blog):
|
|
|
404
411
|
|
|
405
412
|
blog = g.db.query(Blog).filter_by(name=blog).first()
|
|
406
413
|
if blog.owner != g.user:
|
|
407
|
-
|
|
414
|
+
abort(403)
|
|
408
415
|
|
|
409
416
|
posts = g.db.query(Post).filter_by(blog=blog.id).all()
|
|
410
417
|
|
|
@@ -459,9 +466,9 @@ def new_page(blog):
|
|
|
459
466
|
content_html = request.form.get("content")
|
|
460
467
|
show = request.form.get("show")
|
|
461
468
|
if len(name) > 120:
|
|
462
|
-
return render_template("new_page.html", blog=blog, error="
|
|
469
|
+
return render_template("new_page.html", blog=blog, error="name-too-long")
|
|
463
470
|
if not re.match(r"^\/[a-zA-Z0-9\-\_]+$", url):
|
|
464
|
-
return render_template("new_page.html", blog=blog, error="
|
|
471
|
+
return render_template("new_page.html", blog=blog, error="invalid-url")
|
|
465
472
|
|
|
466
473
|
url = url[1:]
|
|
467
474
|
if url == "rss":
|
|
@@ -506,10 +513,10 @@ def edit_page(page):
|
|
|
506
513
|
show = request.form.get("show")
|
|
507
514
|
url = request.form.get("url")
|
|
508
515
|
if len(name) > 120:
|
|
509
|
-
return render_template("new_page.html", blog=blog, page=page, home=home, error="
|
|
516
|
+
return render_template("new_page.html", blog=blog, page=page, home=home, error="name-too-long")
|
|
510
517
|
if home == False:
|
|
511
518
|
if not re.match(r"^\/[a-zA-Z0-9\-\_]+$", url):
|
|
512
|
-
return render_template("new_page.html", blog=blog, error="
|
|
519
|
+
return render_template("new_page.html", blog=blog, error="invalid-url")
|
|
513
520
|
|
|
514
521
|
url = url[1:]
|
|
515
522
|
if url == "rss":
|
|
@@ -557,7 +564,50 @@ def page_delete(page):
|
|
|
557
564
|
if blog.owner != g.user:
|
|
558
565
|
return abort(403)
|
|
559
566
|
|
|
567
|
+
if page.url == "":
|
|
568
|
+
return redirect("/dashboard")
|
|
569
|
+
|
|
560
570
|
g.db.delete(page)
|
|
561
571
|
g.db.commit()
|
|
562
572
|
|
|
563
573
|
return redirect(f"/dashboard/edit/{blog.name}")
|
|
574
|
+
|
|
575
|
+
@dashboard_bp.route("/dashboard/export/<blog>")
|
|
576
|
+
def export_posts(blog):
|
|
577
|
+
if g.user is None:
|
|
578
|
+
return redirect("/login")
|
|
579
|
+
|
|
580
|
+
blog_obj = g.db.query(Blog).filter_by(name=blog).first()
|
|
581
|
+
if blog_obj is None or blog_obj.owner != g.user:
|
|
582
|
+
return redirect("/dashboard")
|
|
583
|
+
|
|
584
|
+
posts = g.db.query(Post).filter_by(blog=blog_obj.id).all()
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
output = StringIO()
|
|
588
|
+
writer = csv.writer(output)
|
|
589
|
+
|
|
590
|
+
writer.writerow(['Title', 'Content Raw', 'Content HTML', 'Author', 'Link', 'Date', 'Updated', 'Feed', 'Is Draft'])
|
|
591
|
+
|
|
592
|
+
for post in posts:
|
|
593
|
+
writer.writerow([
|
|
594
|
+
post.title,
|
|
595
|
+
post.content_raw,
|
|
596
|
+
post.content_html,
|
|
597
|
+
post.author,
|
|
598
|
+
post.link,
|
|
599
|
+
post.date.isoformat() if post.date else '',
|
|
600
|
+
post.updated.isoformat() if post.updated else '',
|
|
601
|
+
post.feed,
|
|
602
|
+
post.isdraft
|
|
603
|
+
])
|
|
604
|
+
|
|
605
|
+
output.seek(0)
|
|
606
|
+
|
|
607
|
+
return Response(
|
|
608
|
+
output.getvalue(),
|
|
609
|
+
mimetype='text/csv',
|
|
610
|
+
headers={
|
|
611
|
+
'Content-Disposition': f'attachment; filename={blog}_posts.csv'
|
|
612
|
+
}
|
|
613
|
+
)
|