openwrite 0.11.0__tar.gz → 0.13.0__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.
Files changed (83) hide show
  1. {openwrite-0.11.0 → openwrite-0.13.0}/MANIFEST.in +3 -0
  2. {openwrite-0.11.0/openwrite.egg-info → openwrite-0.13.0}/PKG-INFO +5 -1
  3. {openwrite-0.11.0 → openwrite-0.13.0}/README.md +4 -0
  4. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/__init__.py +14 -3
  5. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/gemini_multi.py +33 -12
  6. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/gemini_single.py +37 -12
  7. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/admin.py +49 -1
  8. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/blog.py +62 -15
  9. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/dashboard.py +175 -13
  10. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/federation.py +176 -24
  11. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/main.py +11 -3
  12. openwrite-0.13.0/openwrite/static/emoji.json +15627 -0
  13. openwrite-0.13.0/openwrite/static/script/nativeEmoji.js +389 -0
  14. openwrite-0.13.0/openwrite/static/style/nativeEmoji.min.css +1 -0
  15. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/style.css +27 -2
  16. openwrite-0.13.0/openwrite/static/style/themes/cyberpunk.css +391 -0
  17. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/themes/light.css +11 -1
  18. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/themes/stickynote.css +11 -1
  19. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/themes/terminal.css +9 -0
  20. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/themes/warmnight.css +5 -0
  21. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/themes/windows98.css +58 -3
  22. openwrite-0.13.0/openwrite/templates/blog.html +18 -0
  23. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/blog_header.html +16 -1
  24. openwrite-0.13.0/openwrite/templates/blog_preview.html +57 -0
  25. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/dashboard.html +1 -1
  26. openwrite-0.13.0/openwrite/templates/discover.html +11 -0
  27. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/edit.html +40 -54
  28. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/header.html +4 -0
  29. openwrite-0.13.0/openwrite/templates/new_page.html +218 -0
  30. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/new_post.html +51 -3
  31. openwrite-0.11.0/openwrite/templates/blog_preview.html → openwrite-0.13.0/openwrite/templates/page_preview.html +15 -11
  32. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/translations.html +1 -0
  33. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/utils/create_db.py +1 -1
  34. openwrite-0.13.0/openwrite/utils/i18n.json +1 -0
  35. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/utils/models.py +16 -0
  36. {openwrite-0.11.0 → openwrite-0.13.0/openwrite.egg-info}/PKG-INFO +5 -1
  37. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite.egg-info/SOURCES.txt +6 -0
  38. {openwrite-0.11.0 → openwrite-0.13.0}/pyproject.toml +4 -1
  39. openwrite-0.11.0/openwrite/templates/blog.html +0 -16
  40. openwrite-0.11.0/openwrite/templates/discover.html +0 -11
  41. openwrite-0.11.0/openwrite/utils/i18n.json +0 -227
  42. {openwrite-0.11.0 → openwrite-0.13.0}/LICENSE +0 -0
  43. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/cli.py +0 -0
  44. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/db/__init__.py +0 -0
  45. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/db/base.py +0 -0
  46. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/auth.py +0 -0
  47. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/routes/upload.py +0 -0
  48. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
  49. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/Merriweather.ttf +0 -0
  50. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/avatar.png +0 -0
  51. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/favicon.ico +0 -0
  52. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/logo.png +0 -0
  53. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/chart.js +0 -0
  54. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/highlight.min.js +0 -0
  55. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/openwrite.js +0 -0
  56. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/papaparse.min.js +0 -0
  57. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/showdown.min.js +0 -0
  58. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/showdown.min.js.map +0 -0
  59. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/site.compat.min.js +0 -0
  60. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/script/site.min.js +0 -0
  61. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/hl.css +0 -0
  62. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/static/style/themes/obscura.css +0 -0
  63. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/admin.html +0 -0
  64. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/blog_footer.html +0 -0
  65. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/changepw.html +0 -0
  66. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/create.html +0 -0
  67. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/footer.html +0 -0
  68. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/import.html +0 -0
  69. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/index.html +0 -0
  70. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/instances.html +0 -0
  71. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/login.html +0 -0
  72. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/post.html +0 -0
  73. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/preview.html +0 -0
  74. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/register.html +0 -0
  75. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/settings.html +0 -0
  76. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/templates/stats.html +0 -0
  77. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/utils/db.py +0 -0
  78. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite/utils/helpers.py +0 -0
  79. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite.egg-info/dependency_links.txt +0 -0
  80. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite.egg-info/entry_points.txt +0 -0
  81. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite.egg-info/requires.txt +0 -0
  82. {openwrite-0.11.0 → openwrite-0.13.0}/openwrite.egg-info/top_level.txt +0 -0
  83. {openwrite-0.11.0 → openwrite-0.13.0}/setup.cfg +0 -0
@@ -1,3 +1,6 @@
1
1
  include openwrite/utils/i18n.json
2
2
  recursive-include openwrite/templates *
3
3
  recursive-include openwrite/static *
4
+ exclude logs/*
5
+ exclude gemini/*
6
+ exclude uploads/*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwrite
3
- Version: 0.11.0
3
+ Version: 0.13.0
4
4
  License: GNU AFFERO GENERAL PUBLIC LICENSE
5
5
  Version 3, 19 November 2007
6
6
 
@@ -690,6 +690,7 @@ Dynamic: license-file
690
690
  ![logo](https://openwrite.io/static/logo.png)
691
691
  openwrite is a minimalist blogging platform built for writing freely, hosting independently, and publishing without noise.
692
692
 
693
+ ![PyPI - Downloads](https://img.shields.io/pypi/dw/openwrite)
693
694
  ![PyPI - Version](https://img.shields.io/pypi/v/openwrite)
694
695
  [![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue.svg)](LICENSE)
695
696
  ![pythonver](https://img.shields.io/badge/python%20version-3.11-blue)
@@ -703,6 +704,7 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
703
704
  - Single-blog mode
704
705
  - Supports sqlite and mysql databases
705
706
  - Upload images to local storage or bunny cdn
707
+ - Drafts
706
708
  - Simple markdown editor in posting
707
709
  - Discover section
708
710
  - Privacy:
@@ -719,6 +721,8 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
719
721
  - Gemini protocol
720
722
  - Statistics per post
721
723
  - Multilanguage (currently English and Polish)
724
+ - Custom favicon per blog
725
+ - Subpages per blog
722
726
 
723
727
 
724
728
  ## Installation
@@ -1,6 +1,7 @@
1
1
  ![logo](https://openwrite.io/static/logo.png)
2
2
  openwrite is a minimalist blogging platform built for writing freely, hosting independently, and publishing without noise.
3
3
 
4
+ ![PyPI - Downloads](https://img.shields.io/pypi/dw/openwrite)
4
5
  ![PyPI - Version](https://img.shields.io/pypi/v/openwrite)
5
6
  [![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue.svg)](LICENSE)
6
7
  ![pythonver](https://img.shields.io/badge/python%20version-3.11-blue)
@@ -14,6 +15,7 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
14
15
  - Single-blog mode
15
16
  - Supports sqlite and mysql databases
16
17
  - Upload images to local storage or bunny cdn
18
+ - Drafts
17
19
  - Simple markdown editor in posting
18
20
  - Discover section
19
21
  - Privacy:
@@ -30,6 +32,8 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
30
32
  - Gemini protocol
31
33
  - Statistics per post
32
34
  - Multilanguage (currently English and Polish)
35
+ - Custom favicon per blog
36
+ - Subpages per blog
33
37
 
34
38
 
35
39
  ## Installation
@@ -32,7 +32,7 @@ def create_app(test_config=None):
32
32
  file_cwd = "/".join(f_abs_path.split("/")[:-1])
33
33
  from .utils.create_db import init_db
34
34
  init_db(db_type, db_path)
35
- from .utils.models import User, Blog, Post, Settings, Home
35
+ from .utils.models import User, Blog, Post, Settings, Home, Page
36
36
  from .utils.db import init_engine, SessionLocal
37
37
  init_engine(db_type, db_path)
38
38
  from .utils.db import SessionLocal
@@ -68,15 +68,26 @@ def create_app(test_config=None):
68
68
  title=domain,
69
69
  index="on",
70
70
  access="domain",
71
- description_raw=f"![hello](https://openwrite.b-cdn.net/hello.jpg =500x258)\n\n# Hello there! 👋\n\nYou can edit your blog description in [dashboard](http://{domain}/dashboard/edit/default)",
72
- description_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 description in <a href=\"http://{domain}/dashboard/edit/default\">dashboard</a></p>",
73
71
  css="",
72
+ description_raw="x",
73
+ description_html="x",
74
74
  pub_key=public_pem,
75
75
  priv_key=private_pem,
76
76
  theme="default",
77
+ favicon="",
77
78
  created=now
78
79
  )
79
80
  SessionLocal.add(new_blog)
81
+
82
+ new_page = Page(
83
+ blog=1,
84
+ name="Home",
85
+ url="",
86
+ content_raw=f"![hello](https://openwrite.b-cdn.net/hello.jpg =500x258)\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
+ 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="0"
89
+ )
90
+ SessionLocal.add(new_page)
80
91
 
81
92
  hometext_pl = """
82
93
  <h1 class="centered"><span style="color: #f2d2c8">cicha</span> przestrzeń na <span style="color: #5a36bf">głośne</span> myśli.</h1>
@@ -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).filter_by(feed="1").order_by(Post.id.desc()).limit(10).all())
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
- .filter_by(blog=blog.id)
49
- .order_by(Post.id.desc())
50
- .all())
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
- body += md2gemini(blog.description_raw.replace("#", ""))
53
- body += "\n--------------------------------------------------\n"
54
- body += "### Posts:\n\n"
56
+ posts_data = ""
55
57
  for post in posts:
56
- body += f"=> /b/{blogname}/{post.link} {post.title}\n"
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'![\1](\2)', 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
- return "not found"
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'![\1](\2)', 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
- gemtext += f"\n\n{md2gemini(post.content_raw)}"
110
+ content = post.content_raw
111
+ content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'![\1](\2)', 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
- posts = (session.query(Post)
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
- body = f"# 📓 {blog.title}\n\n"
19
- body += md2gemini(blog.description_raw.replace("#", ""))
20
- body += "\n--------------------------------------------------\n"
21
- body += "### Posts:\n\n"
22
- for post in posts:
23
- body += f"=> /p/{post.link} {post.title}\n"
24
- return body
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'![\1](\2)', 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
- .filter_by(blog=blog.id, link=slug)
38
- .first())
51
+ .filter_by(blog=blog.id, link=slug, isdraft="0")
52
+ .first())
39
53
  if not post:
40
- return "not found"
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'![\1](\2)', 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
- gemtext += f"\n\n{md2gemini(post.content_raw)}"
81
+ content = post.content_raw
82
+ content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'![\1](\2)', content)
83
+ gemtext += f"\n\n{md2gemini(content)}\n\n"
59
84
  return gemtext
60
85
 
61
86
  finally:
@@ -1,4 +1,5 @@
1
- from flask import Blueprint, render_template, redirect, g, request
1
+ from flask import Blueprint, render_template, redirect, g, request, make_response
2
+ import json
2
3
  from openwrite.utils.models import Blog, User, Settings, Home
3
4
  import re
4
5
  import bcrypt
@@ -13,6 +14,9 @@ ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "webp"}
13
14
 
14
15
  @admin_bp.route("/admin")
15
16
  def admin():
17
+ """
18
+ Admin dashboard to manage blogs and users.
19
+ """
16
20
  if g.user is None or g.isadmin == 0 or g.mode == "single":
17
21
  return redirect("/")
18
22
 
@@ -26,6 +30,9 @@ def admin():
26
30
 
27
31
  @admin_bp.route("/admin/delete_blog/<blog>")
28
32
  def admin_delete_blog(blog):
33
+ """
34
+ Delete a blog by its name.
35
+ """
29
36
  if g.user is None or g.isadmin == 0 or g.mode == "single":
30
37
  return redirect("/")
31
38
 
@@ -38,6 +45,9 @@ def admin_delete_blog(blog):
38
45
 
39
46
  @admin_bp.route("/admin/delete_user/<username>")
40
47
  def admin_delete_user(username):
48
+ """
49
+ Delete a user and all their blogs.
50
+ """
41
51
  if g.user is None or g.isadmin == 0 or g.mode == "single":
42
52
  return redirect("/")
43
53
 
@@ -59,6 +69,9 @@ def admin_delete_user(username):
59
69
 
60
70
  @admin_bp.route("/admin/make_admin/<username>")
61
71
  def admin_make_admin(username):
72
+ """
73
+ Grant admin privileges to a user.
74
+ """
62
75
  if g.user is None or g.isadmin == 0 or g.mode == "single":
63
76
  return redirect("/")
64
77
 
@@ -73,6 +86,9 @@ def admin_make_admin(username):
73
86
 
74
87
  @admin_bp.route("/admin/remove_admin/<username>")
75
88
  def admin_remove_admin(username):
89
+ """
90
+ Remove admin privileges from a user.
91
+ """
76
92
  if g.user is None or g.isadmin == 0 or g.mode == "single":
77
93
  return redirect("/")
78
94
 
@@ -90,6 +106,9 @@ def admin_remove_admin(username):
90
106
 
91
107
  @admin_bp.route("/admin/add_user", methods=['GET', 'POST'])
92
108
  def admin_add_user():
109
+ """
110
+ Add a new user to the system.
111
+ """
93
112
  if g.user is None or g.isadmin == 0 or g.mode == "single":
94
113
  return redirect("/")
95
114
  if request.method == "GET":
@@ -122,6 +141,9 @@ def admin_add_user():
122
141
 
123
142
  @admin_bp.route("/admin/settings", methods=['GET', 'POST'])
124
143
  def admin_settings():
144
+ """
145
+ Manage application settings.
146
+ """
125
147
  if g.user is None or g.isadmin == 0 or g.mode == "single":
126
148
  return redirect("/")
127
149
 
@@ -174,6 +196,14 @@ def admin_settings():
174
196
 
175
197
  @admin_bp.route("/admin/translations", methods=['GET', 'POST'])
176
198
  def translations():
199
+ """
200
+ Manage translations for the application.
201
+ Allows viewing, editing, and saving translations.
202
+ (I can't believe I didn't secured it earlier lol)
203
+ """
204
+ if g.user is None or g.isadmin == 0 or g.mode == "single":
205
+ return redirect("/")
206
+
177
207
  if request.method == "GET":
178
208
  trans = g.db.query(Home).filter_by(type="translation").all()
179
209
  grouped = defaultdict(dict)
@@ -198,3 +228,21 @@ def translations():
198
228
 
199
229
  g.db.commit()
200
230
  return redirect("/admin/translations")
231
+
232
+ @admin_bp.route("/admin/download_translations")
233
+ def download_translations():
234
+ """
235
+ Download all translations as a json file.
236
+ """
237
+ if g.user is None or g.isadmin == 0 or g.mode == "single":
238
+ return redirect("/")
239
+
240
+ translations = g.db.query(Home).filter_by(type="translation").all()
241
+ trans_dict = defaultdict(dict)
242
+ for t in translations:
243
+ trans_dict[t.language][t.name] = t.content
244
+
245
+ response = make_response(json.dumps(dict(trans_dict), ensure_ascii=False))
246
+ response.headers["Content-Disposition"] = "attachment; filename=i18n.json"
247
+ response.headers["Content-Type"] = "application/json"
248
+ return response
@@ -1,5 +1,5 @@
1
1
  from flask import Blueprint, render_template, redirect, request, g, Response, abort
2
- from openwrite.utils.models import Blog, Post, User, View, Like
2
+ from openwrite.utils.models import Blog, Post, User, View, Like, Page
3
3
  from openwrite.utils.helpers import gen_link, sanitize_html, anonymize, get_ip
4
4
  from feedgen.feed import FeedGenerator
5
5
  from sqlalchemy import desc
@@ -20,10 +20,14 @@ def show_blog(blog):
20
20
  if blog.access == "domain":
21
21
  return redirect(f"https://{blog.name}.{os.getenv('DOMAIN')}/")
22
22
 
23
- posts = g.db.query(Post).filter_by(blog=blog.id).order_by(desc(Post.id)).all()
23
+ pages = g.db.query(Page).filter_by(blog=blog.id).all()
24
24
  blog.url = f"/b/{blog.name}"
25
+ homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
26
+ if "{posts}" in homepage.content_raw:
27
+ posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
28
+ return render_template("blog.html", blog=blog, page=homepage, posts=posts, pages=pages)
25
29
 
26
- return render_template("blog.html", blog=blog, posts=posts)
30
+ return render_template("blog.html", blog=blog, page=homepage, pages=pages)
27
31
 
28
32
  @blog_bp.route("/", subdomain="<blog>")
29
33
  def show_subblog(blog):
@@ -36,9 +40,14 @@ def show_subblog(blog):
36
40
  if blog.access == "path":
37
41
  return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog.name}")
38
42
 
39
- posts = g.db.query(Post).filter_by(blog=blog.id).order_by(desc(Post.id)).all()
43
+ blog.url = f"https://{blog.name}.{os.getenv('DOMAIN')}"
44
+ pages = g.db.query(Page).filter_by(blog=blog.id).all()
45
+ homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
46
+ if "{posts}" in homepage.content_raw:
47
+ posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
48
+ return render_template("blog.html", blog=blog, page=homepage, posts=posts, pages=pages)
40
49
 
41
- return render_template("blog.html", blog=blog, posts=posts)
50
+ return render_template("blog.html", blog=blog, page=homepage, pages=pages)
42
51
 
43
52
  @blog_bp.route("/b/<blog>/<post>")
44
53
  def show_post(blog, post):
@@ -54,9 +63,21 @@ def show_post(blog, post):
54
63
  if blog.access == "domain":
55
64
  return redirect(f"https://{blog.name}.{os.getenv('DOMAIN')}/{post}")
56
65
 
57
- one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post).first()
66
+ blog.url = f"/b/{blog.name}"
67
+ pages = g.db.query(Page).filter_by(blog=blog.id).all()
68
+ one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post, Post.isdraft == "0").first()
58
69
  if not one_post:
59
- return redirect("/")
70
+ page = g.db.query(Page).filter(Page.blog == blog.id, Page.url == post).first()
71
+ if not page:
72
+ return redirect("/")
73
+
74
+ if "{posts}" in page.content_raw:
75
+ posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
76
+
77
+ return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
78
+
79
+ return render_template("blog.html", blog=blog, page=page, pages=pages)
80
+
60
81
 
61
82
  post_author = g.db.query(User).filter_by(id=blog.owner).first()
62
83
  one_post.authorname = post_author.username
@@ -76,7 +97,7 @@ def show_post(blog, post):
76
97
  one_post.liked = liked
77
98
 
78
99
  user = g.db.query(User).filter_by(id=g.user) if g.user else None
79
- return render_template("post.html", blog=blog, post=one_post, user=user, views=v, likes=likes)
100
+ return render_template("post.html", blog=blog, post=one_post, user=user, views=v, likes=likes, pages=pages)
80
101
 
81
102
  @blog_bp.route("/<post>", subdomain="<blog>")
82
103
  def show_subpost(blog, post):
@@ -92,9 +113,22 @@ def show_subpost(blog, post):
92
113
  if blog.access == "path":
93
114
  return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog.name}/{post}")
94
115
 
95
- one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post).first()
116
+ blog.url = f"https://{blog.name}.{os.getenv('DOMAIN')}"
117
+
118
+ pages = g.db.query(Page).filter_by(blog=blog.id).all()
119
+ one_post = g.db.query(Post).filter(Post.blog == blog.id, Post.link == post, Post.isdraft == "0").first()
120
+
96
121
  if not one_post:
97
- return redirect("/")
122
+ page = g.db.query(Page).filter(Page.blog == blog.id, Page.url == post).first()
123
+ if not page:
124
+ return redirect("/")
125
+
126
+ if "{posts}" in page.content_raw:
127
+ posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
128
+
129
+ return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
130
+
131
+ return render_template("blog.html", blog=blog, page=page, pages=pages)
98
132
 
99
133
  post_author = g.db.query(User).filter_by(id=blog.owner).first()
100
134
  one_post.authorname = post_author.username
@@ -115,7 +149,7 @@ def show_subpost(blog, post):
115
149
  one_post.liked = liked
116
150
 
117
151
  user = g.db.query(User).filter_by(id=g.user) if g.user else None
118
- return render_template("post.html", blog=blog, post=one_post, user=user, views=v)
152
+ return render_template("post.html", blog=blog, post=one_post, user=user, views=v, pages=pages)
119
153
 
120
154
  @blog_bp.route("/p/<post>")
121
155
  def single_showpost(post):
@@ -123,9 +157,22 @@ def single_showpost(post):
123
157
  return redirect("/")
124
158
 
125
159
  blog = g.db.query(Blog).filter_by(id=1).first()
126
- one_post = g.db.query(Post).filter(Post.blog == 1, Post.link == post).first()
160
+ blog.url = f"http://{g.main_domain}"
161
+ one_post = g.db.query(Post).filter(Post.blog == 1, Post.link == post, Post.isdraft == "0").first()
162
+
163
+ pages = g.db.query(Page).filter_by(blog=1).all()
164
+
127
165
  if not one_post:
128
- return redirect("/")
166
+ page = g.db.query(Page).filter(Page.blog == blog.id, Page.url == post).first()
167
+ if not page:
168
+ return redirect("/")
169
+
170
+ if "{posts}" in page.content_raw:
171
+ posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").order_by(desc(Post.id)).all()
172
+
173
+ return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
174
+
175
+ return render_template("blog.html", blog=blog, page=page, pages=pages)
129
176
 
130
177
  post_author = g.db.query(User).filter_by(id=1).first()
131
178
  one_post.authorname = post_author.username
@@ -145,7 +192,7 @@ def single_showpost(post):
145
192
  one_post.liked = liked
146
193
 
147
194
  user = g.db.query(User).filter_by(id=g.user) if g.user else None
148
- return render_template("post.html", blog=blog, post=one_post, user=user, views=v)
195
+ return render_template("post.html", blog=blog, post=one_post, user=user, views=v, pages=pages)
149
196
 
150
197
  @blog_bp.route("/rss")
151
198
  def single_rss():
@@ -186,7 +233,7 @@ def like():
186
233
 
187
234
 
188
235
  def _generate_rss(blog):
189
- posts = g.db.query(Post).filter_by(blog=blog.id).all()
236
+ posts = g.db.query(Post).filter(Post.blog == blog.id, Post.isdraft == "0").all()
190
237
  fg = FeedGenerator()
191
238
  fg.title(blog.title)
192
239
  fg.link(href=f"https://{os.getenv('DOMAIN')}/b/{blog.name}", rel="alternate")