openwrite 0.15.0__tar.gz → 0.16.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.
Files changed (123) hide show
  1. {openwrite-0.15.0/openwrite.egg-info → openwrite-0.16.1}/PKG-INFO +3 -1
  2. {openwrite-0.15.0 → openwrite-0.16.1}/README.md +2 -0
  3. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/__init__.py +1 -0
  4. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/cli.py +1 -1
  5. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/auth.py +7 -2
  6. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/blog.py +116 -5
  7. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/dashboard.py +7 -4
  8. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/blog_design.css +74 -0
  9. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/default.css +55 -0
  10. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/themes/cyberpunk.css +69 -0
  11. openwrite-0.16.1/openwrite/static/style/themes/light.css +111 -0
  12. openwrite-0.16.1/openwrite/static/style/themes/obscura.css +82 -0
  13. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/themes/paper.css +73 -0
  14. openwrite-0.16.1/openwrite/static/style/themes/sepia.css +344 -0
  15. openwrite-0.16.1/openwrite/static/style/themes/stickynote.css +117 -0
  16. openwrite-0.16.1/openwrite/static/style/themes/synthwave.css +389 -0
  17. openwrite-0.16.1/openwrite/static/style/themes/terminal.css +126 -0
  18. openwrite-0.16.1/openwrite/static/style/themes/warmnight.css +141 -0
  19. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/themes/windows98.css +81 -0
  20. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/new_post.html +130 -4
  21. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/post.html +28 -0
  22. openwrite-0.16.1/openwrite/utils/i18n.json +1 -0
  23. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/migrations.py +3 -3
  24. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/models.py +33 -0
  25. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/worker.py +4 -0
  26. {openwrite-0.15.0 → openwrite-0.16.1/openwrite.egg-info}/PKG-INFO +3 -1
  27. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite.egg-info/SOURCES.txt +2 -0
  28. {openwrite-0.15.0 → openwrite-0.16.1}/pyproject.toml +1 -1
  29. openwrite-0.15.0/openwrite/static/style/themes/light.css +0 -58
  30. openwrite-0.15.0/openwrite/static/style/themes/obscura.css +0 -27
  31. openwrite-0.15.0/openwrite/static/style/themes/stickynote.css +0 -60
  32. openwrite-0.15.0/openwrite/static/style/themes/terminal.css +0 -46
  33. openwrite-0.15.0/openwrite/static/style/themes/warmnight.css +0 -79
  34. openwrite-0.15.0/openwrite/utils/i18n.json +0 -1
  35. {openwrite-0.15.0 → openwrite-0.16.1}/LICENSE +0 -0
  36. {openwrite-0.15.0 → openwrite-0.16.1}/MANIFEST.in +0 -0
  37. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/db/__init__.py +0 -0
  38. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/db/base.py +0 -0
  39. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/gemini_multi.py +0 -0
  40. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/gemini_single.py +0 -0
  41. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/admin.py +0 -0
  42. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/federation.py +0 -0
  43. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/main.py +0 -0
  44. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/routes/upload.py +0 -0
  45. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
  46. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/Merriweather.ttf +0 -0
  47. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/avatar.png +0 -0
  48. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/emoji.json +0 -0
  49. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/favicon.ico +0 -0
  50. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/favicon.png +0 -0
  51. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/.gitkeep +0 -0
  52. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/74.xml +0 -0
  53. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/a.xml +0 -0
  54. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/anim3sh1t.xml +0 -0
  55. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/b.xml +0 -0
  56. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/corrode.xml +0 -0
  57. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/dirtysanchez.xml +0 -0
  58. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/eikerammen.xml +0 -0
  59. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/fedi.xml +0 -0
  60. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/friskt.xml +0 -0
  61. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/gordi.xml +0 -0
  62. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/igilq.xml +0 -0
  63. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/io.xml +0 -0
  64. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/isyuricunha.xml +0 -0
  65. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/kenny.xml +0 -0
  66. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/m0bi.xml +0 -0
  67. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/main.xml +0 -0
  68. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/mywritese.xml +0 -0
  69. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/nay-thoughs.xml +0 -0
  70. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/nintendoflesz.xml +0 -0
  71. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/phronima.xml +0 -0
  72. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/ram.xml +0 -0
  73. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/samah.xml +0 -0
  74. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/themes-test.xml +0 -0
  75. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/totylkoja.xml +0 -0
  76. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/vegafjord.xml +0 -0
  77. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/feed/vijayvaradharaj.xml +0 -0
  78. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/logo.png +0 -0
  79. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/chart.js +0 -0
  80. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/highlight.min.js +0 -0
  81. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/nativeEmoji.js +0 -0
  82. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/openwrite.js +0 -0
  83. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/papaparse.min.js +0 -0
  84. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/showdown.min.js +0 -0
  85. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/showdown.min.js.map +0 -0
  86. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/site.compat.min.js +0 -0
  87. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/script/site.min.js +0 -0
  88. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/hl.css +0 -0
  89. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/nativeEmoji.min.css +0 -0
  90. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/static/style/style.css +0 -0
  91. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/admin.html +0 -0
  92. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/blog.html +0 -0
  93. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/blog_footer.html +0 -0
  94. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/blog_header.html +0 -0
  95. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/blog_preview.html +0 -0
  96. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/changepw.html +0 -0
  97. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/create.html +0 -0
  98. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/dashboard.html +0 -0
  99. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/discover.html +0 -0
  100. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/edit.html +0 -0
  101. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/footer.html +0 -0
  102. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/header.html +0 -0
  103. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/import.html +0 -0
  104. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/index.html +0 -0
  105. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/instances.html +0 -0
  106. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/login.html +0 -0
  107. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/new_page.html +0 -0
  108. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/page_preview.html +0 -0
  109. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/preview.html +0 -0
  110. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/register.html +0 -0
  111. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/settings.html +0 -0
  112. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/stats.html +0 -0
  113. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/templates/translations.html +0 -0
  114. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/create_db.py +0 -0
  115. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/db.py +0 -0
  116. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/helpers.py +0 -0
  117. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/tags.py +0 -0
  118. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite/utils/valkey_client.py +0 -0
  119. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite.egg-info/dependency_links.txt +0 -0
  120. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite.egg-info/entry_points.txt +0 -0
  121. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite.egg-info/requires.txt +0 -0
  122. {openwrite-0.15.0 → openwrite-0.16.1}/openwrite.egg-info/top_level.txt +0 -0
  123. {openwrite-0.15.0 → openwrite-0.16.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwrite
3
- Version: 0.15.0
3
+ Version: 0.16.1
4
4
  License: GNU AFFERO GENERAL PUBLIC LICENSE
5
5
  Version 3, 19 November 2007
6
6
 
@@ -726,6 +726,8 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
726
726
  - Subpages per blog
727
727
  - Valkey support
728
728
  - Post tags, filtering
729
+ - Optional comments per post
730
+ - Auto save during typing
729
731
 
730
732
 
731
733
  ## Installation
@@ -36,6 +36,8 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
36
36
  - Subpages per blog
37
37
  - Valkey support
38
38
  - Post tags, filtering
39
+ - Optional comments per post
40
+ - Auto save during typing
39
41
 
40
42
 
41
43
  ## Installation
@@ -167,6 +167,7 @@ def create_app(test_config=None):
167
167
  app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
168
168
  app.secret_key = os.getenv("SECRET_KEY")
169
169
  app.config['SERVER_NAME'] = os.getenv("DOMAIN")
170
+ app.config['SESSION_COOKIE_DOMAIN'] = '.' + os.getenv("DOMAIN", "").split(':')[0]
170
171
 
171
172
  from .routes.auth import auth_bp
172
173
  from .routes.dashboard import dashboard_bp
@@ -152,8 +152,8 @@ def init():
152
152
  if captcha_enabled:
153
153
  f.write(f"FRIENDLY_CAPTCHA_SITEKEY={captcha_sitekey}\n")
154
154
  f.write(f"FRIENDLY_CAPTCHA_APIKEY={captcha_apikey}\n")
155
+ f.write(f"VALKEY_ENABLED={'yes' if valkey_enabled else 'no'}\n")
155
156
  if valkey_enabled:
156
- f.write(f"VALKEY_ENABLED={'yes' if valkey_enabled else 'no'}\n")
157
157
  f.write(f"VALKEY_HOST={valkey_host}\n")
158
158
  f.write(f"VALKEY_PORT={valkey_port}\n")
159
159
  f.write(f"VALKEY_PASSWORD={valkey_password}\n")
@@ -1,4 +1,4 @@
1
- from flask import Blueprint, render_template, redirect, request, session, g
1
+ from flask import Blueprint, render_template, redirect, request, session, g, current_app
2
2
  from openwrite.utils.models import User
3
3
  import bcrypt
4
4
  import re
@@ -74,5 +74,10 @@ def login():
74
74
  @auth_bp.route("/logout")
75
75
  def logout():
76
76
  session.clear()
77
- return redirect("/")
77
+ response = redirect("/")
78
+ cookie_name = current_app.config.get('SESSION_COOKIE_NAME', 'session')
79
+ cookie_domain = current_app.config.get('SESSION_COOKIE_DOMAIN')
80
+ response.delete_cookie(cookie_name, path='/', domain=cookie_domain)
81
+ response.delete_cookie(cookie_name, path='/')
82
+ return response
78
83
 
@@ -1,5 +1,5 @@
1
- from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file
2
- from openwrite.utils.models import Blog, Post, User, View, Like, Page
1
+ from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file, jsonify
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
5
5
  from openwrite.utils.valkey_client import valkey_client
@@ -255,9 +255,10 @@ def show_post(blog, post):
255
255
  one_post.liked = liked
256
256
 
257
257
  post_tags = get_post_tags(g.db, one_post)
258
+ comments = g.db.query(Comment).filter_by(blog_id=blog_obj.id, post_id=one_post.id).order_by(Comment.created_at.asc()).all() if one_post.comments_allowed == "1" else []
258
259
 
259
260
  user = g.db.query(User).filter_by(id=g.user) if g.user else None
260
- return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags)
261
+ return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags, comments=comments)
261
262
 
262
263
  @blog_bp.route("/<post>", subdomain="<blog>")
263
264
  def show_subpost(blog, post):
@@ -325,9 +326,10 @@ def show_subpost(blog, post):
325
326
  one_post.liked = liked
326
327
 
327
328
  post_tags = get_post_tags(g.db, one_post)
329
+ comments = g.db.query(Comment).filter_by(blog_id=blog_obj.id, post_id=one_post.id).order_by(Comment.created_at.asc()).all() if one_post.comments_allowed == "1" else []
328
330
 
329
331
  user = g.db.query(User).filter_by(id=g.user) if g.user else None
330
- return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags)
332
+ return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags, comments=comments)
331
333
 
332
334
  @blog_bp.route("/.well-known/webfinger")
333
335
  def webfinger():
@@ -438,9 +440,10 @@ def single_showpost(post):
438
440
  one_post.liked = liked
439
441
 
440
442
  post_tags = get_post_tags(g.db, one_post)
443
+ comments = g.db.query(Comment).filter_by(blog_id=blog.id, post_id=one_post.id).order_by(Comment.created_at.asc()).all() if one_post.comments_allowed == "1" else []
441
444
 
442
445
  user = g.db.query(User).filter_by(id=g.user) if g.user else None
443
- return render_template("post.html", blog=blog, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags)
446
+ return render_template("post.html", blog=blog, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags, comments=comments)
444
447
 
445
448
  @blog_bp.route("/rss")
446
449
  def single_rss():
@@ -504,3 +507,111 @@ def serve_rss(blog):
504
507
  Here we just serve static RSS files :)
505
508
  """
506
509
  return send_file(f"{g.mainpath}/static/feed/{blog.name}.xml")
510
+
511
+
512
+ @blog_bp.route("/comment", methods=['POST'])
513
+ @blog_bp.route("/comment", subdomain="<blog>", methods=['POST'])
514
+ def add_comment(blog=None):
515
+ blog_id = request.form.get('blog_id', type=int)
516
+ post_id = request.form.get('post_id', type=int)
517
+ author_name = request.form.get('author_name', '').strip()[:50]
518
+ content = request.form.get('content', '').strip()[:1000]
519
+ redirect_to = request.referrer or '/'
520
+
521
+ if not blog_id or not post_id:
522
+ abort(400)
523
+
524
+ post = g.db.query(Post).filter_by(id=post_id, blog=blog_id).first()
525
+ if not post or post.comments_allowed != '1':
526
+ abort(403)
527
+
528
+ if not author_name or not content:
529
+ return redirect(redirect_to)
530
+
531
+ now = datetime.now(timezone.utc).replace(microsecond=0)
532
+ comment = Comment(blog_id=blog_id, post_id=post_id, author_name=author_name, content=content, created_at=now)
533
+ g.db.add(comment)
534
+ g.db.commit()
535
+ return redirect(redirect_to)
536
+
537
+
538
+ @blog_bp.route("/comment/<int:comment_id>/delete", methods=['POST'])
539
+ @blog_bp.route("/comment/<int:comment_id>/delete", subdomain="<blog>", methods=['POST'])
540
+ def delete_comment(comment_id, blog=None):
541
+ if g.user is None:
542
+ abort(403)
543
+
544
+ comment = g.db.query(Comment).filter_by(id=comment_id).first()
545
+ if not comment:
546
+ abort(404)
547
+
548
+ blog = g.db.query(Blog).filter_by(id=comment.blog_id).first()
549
+ if not blog or blog.owner != g.user:
550
+ abort(403)
551
+
552
+ redirect_to = request.referrer or '/'
553
+ g.db.delete(comment)
554
+ g.db.commit()
555
+ return redirect(redirect_to)
556
+
557
+
558
+ @blog_bp.route("/dashboard/autosave/<blog_name>", methods=['POST'])
559
+ def autosave_post(blog_name):
560
+ if g.user is None:
561
+ abort(403)
562
+
563
+ blog = g.db.query(Blog).filter_by(name=blog_name).first()
564
+ if blog is None or blog.owner != g.user:
565
+ abort(403)
566
+
567
+ data = request.get_json()
568
+ if not data:
569
+ abort(400)
570
+
571
+ title = data.get('title', '')[:128]
572
+ content_raw = data.get('content_raw', '')
573
+ post_id = data.get('post_id')
574
+ session_key = data.get('session_key') if post_id is None else None
575
+
576
+ now = datetime.now(timezone.utc).replace(microsecond=0)
577
+ draft = PostDraft(post_id=post_id, blog_id=blog.id, session_key=session_key, title=title, content_raw=content_raw, saved_at=now)
578
+ g.db.add(draft)
579
+ g.db.flush()
580
+
581
+ if post_id is not None:
582
+ existing = g.db.query(PostDraft).filter_by(post_id=post_id, blog_id=blog.id).order_by(PostDraft.saved_at.desc()).all()
583
+ else:
584
+ existing = g.db.query(PostDraft).filter_by(session_key=session_key, blog_id=blog.id).order_by(PostDraft.saved_at.desc()).all()
585
+
586
+ for old in existing[20:]:
587
+ g.db.delete(old)
588
+
589
+ g.db.commit()
590
+ return {'status': 'ok'}, 200
591
+
592
+
593
+ @blog_bp.route("/dashboard/autosaves/<blog_name>", methods=['GET'])
594
+ def get_autosaves(blog_name):
595
+ if g.user is None:
596
+ abort(403)
597
+
598
+ blog = g.db.query(Blog).filter_by(name=blog_name).first()
599
+ if blog is None or blog.owner != g.user:
600
+ abort(403)
601
+
602
+ post_id = request.args.get('post_id', type=int)
603
+ session_key = request.args.get('session_key')
604
+
605
+ if post_id is not None:
606
+ drafts = g.db.query(PostDraft).filter_by(post_id=post_id, blog_id=blog.id).order_by(PostDraft.saved_at.desc()).limit(20).all()
607
+ elif session_key:
608
+ drafts = g.db.query(PostDraft).filter_by(session_key=session_key, blog_id=blog.id).order_by(PostDraft.saved_at.desc()).limit(20).all()
609
+ else:
610
+ drafts = []
611
+
612
+ return jsonify([{
613
+ 'id': d.id,
614
+ 'title': d.title,
615
+ 'content_raw': d.content_raw,
616
+ 'saved_at': d.saved_at.strftime('%Y-%m-%dT%H:%M:%SZ')
617
+ } for d in drafts])
@@ -250,10 +250,10 @@ def new_post(name):
250
250
  u = g.db.query(User).filter_by(id=g.user).first()
251
251
  title = request.form.get('title')
252
252
  if len(title) > 120:
253
- return render_template("new_post.html", blog=blog, error="title-too-long")
253
+ return render_template("new_post.html", blog=blog, error="title-too-long", form_data=request.form)
254
254
 
255
255
  if len(title) < 1:
256
- return render_template("new_post.html", blog=blog, error="title-empty")
256
+ return render_template("new_post.html", blog=blog, error="title-empty", form_data=request.form)
257
257
 
258
258
  link = gen_link(title)
259
259
  if link == "rss":
@@ -276,7 +276,8 @@ def new_post(name):
276
276
  link=link,
277
277
  date=now,
278
278
  feed=request.form.get('feed'),
279
- isdraft=request.form.get('draft', '0')
279
+ isdraft=request.form.get('draft', '0'),
280
+ comments_allowed=request.form.get('comments_allowed', '0')
280
281
  )
281
282
  g.db.add(post)
282
283
  g.db.flush() # Get the post ID
@@ -392,7 +393,8 @@ def edit_post(blog, post):
392
393
 
393
394
  title = request.form.get("title")
394
395
  if len(title) > 120:
395
- return render_template("new_post.html", blog=blog_obj, error="title-too-long")
396
+ existing_tags = get_post_tags(g.db, p) if p else []
397
+ return render_template("new_post.html", blog=blog_obj, post=p, existing_tags=existing_tags, error="title-too-long", form_data=request.form)
396
398
  link = gen_link(title)
397
399
  if link == "rss":
398
400
  link = "p_rss"
@@ -408,6 +410,7 @@ def edit_post(blog, post):
408
410
  p.content_html = sanitize_html(request.form.get("content"))
409
411
  p.author = request.form.get("author")
410
412
  p.feed = request.form.get("feed")
413
+ p.comments_allowed = request.form.get("comments_allowed", "0")
411
414
  p.link = link
412
415
  if request.form.get("publish") == "1":
413
416
  p.date = now
@@ -256,6 +256,80 @@ i.caption {
256
256
  cursor: pointer;
257
257
  }
258
258
 
259
+ .post-comments {
260
+ margin-top: 2rem;
261
+ padding-top: 1rem;
262
+ border-top: 1px solid #333;
263
+ }
264
+
265
+ .post-comments h3 {
266
+ margin-bottom: 1rem;
267
+ }
268
+
269
+ .comment-item {
270
+ margin: 1rem 0;
271
+ padding-bottom: 1rem;
272
+ border-bottom: 1px solid #222;
273
+ }
274
+
275
+ .comment-meta {
276
+ font-size: 0.85em;
277
+ color: #999;
278
+ margin-bottom: 0.3rem;
279
+ }
280
+
281
+ .comment-content {
282
+ white-space: pre-wrap;
283
+ }
284
+
285
+ .comment-empty {
286
+ color: #999;
287
+ font-size: 0.9em;
288
+ }
289
+
290
+ .comment-form {
291
+ margin-top: 1.5rem;
292
+ }
293
+
294
+ .comment-form input[type="text"],
295
+ .comment-form textarea {
296
+ display: block;
297
+ width: 100%;
298
+ box-sizing: border-box;
299
+ margin-bottom: 0.5rem;
300
+ padding: 0.4rem;
301
+ }
302
+
303
+ .comment-form textarea {
304
+ resize: vertical;
305
+ }
306
+
307
+ .comment-form input[type="submit"] {
308
+ padding: 0.4rem 1rem;
309
+ cursor: pointer;
310
+ }
311
+
312
+ .comment-delete-form {
313
+ display: inline;
314
+ margin-left: 0.5rem;
315
+ }
316
+
317
+ .comment-delete {
318
+ background: none;
319
+ border: none;
320
+ cursor: pointer;
321
+ padding: 0;
322
+ font-size: 1em;
323
+ line-height: 1;
324
+ opacity: 0.4;
325
+ color: inherit;
326
+ vertical-align: middle;
327
+ }
328
+
329
+ .comment-delete:hover {
330
+ opacity: 1;
331
+ }
332
+
259
333
  @media only screen and (max-width: 850px) {
260
334
  .container {
261
335
  width: 80% !important;
@@ -133,6 +133,61 @@ a.action:hover {
133
133
  text-decoration: underline;
134
134
  }
135
135
 
136
+ .post-comments {
137
+ border-top: 1px solid #3333333b;
138
+ }
139
+
140
+ .comment-item {
141
+ border-bottom: 1px solid #3333333b;
142
+ }
143
+
144
+ .comment-meta {
145
+ color: #757575;
146
+ }
147
+
148
+ .comment-empty {
149
+ color: #757575;
150
+ }
151
+
152
+ .comment-form input[type="text"],
153
+ .comment-form textarea {
154
+ background: #282c34;
155
+ border: 1px solid #3333333b;
156
+ color: #e6e6e6;
157
+ border-radius: 4px;
158
+ font-family: "IBMPlexSans";
159
+ }
160
+
161
+ .comment-form input[type="text"]:focus,
162
+ .comment-form textarea:focus {
163
+ outline: none;
164
+ border-color: #af9cfb;
165
+ }
166
+
167
+ .comment-form input[type="submit"] {
168
+ background: #795EB5;
169
+ border: none;
170
+ color: #fff;
171
+ border-radius: 4px;
172
+ font-family: "IBMPlexSans";
173
+ }
174
+
175
+ .comment-form input[type="submit"]:hover {
176
+ background: #8f72cc;
177
+ }
178
+
179
+ .comment-form input[type="text"]::placeholder,
180
+ .comment-form textarea::placeholder {
181
+ color: #555;
182
+ }
183
+
184
+ .comment-form {
185
+ background: #1a1a20;
186
+ border: 1px solid #3333333b;
187
+ border-radius: 4px;
188
+ padding: 1rem;
189
+ }
190
+
136
191
  @media only screen and (max-width: 850px) {
137
192
  .content h1 {
138
193
  font-size: 30px !important;
@@ -384,6 +384,75 @@ p code {
384
384
  text-shadow: 0 0 5px rgba(255, 0, 255, 0.5);
385
385
  }
386
386
 
387
+ .post-comments {
388
+ border-top: 1px solid rgba(0, 255, 255, 0.3);
389
+ }
390
+
391
+ .post-comments h3 {
392
+ color: #ff00ff;
393
+ text-shadow: 0 0 10px rgba(255, 0, 255, 0.3);
394
+ border-bottom: 2px solid rgba(255, 0, 255, 0.3);
395
+ padding-bottom: 0.5rem;
396
+ }
397
+
398
+ .comment-item {
399
+ border-bottom: 1px solid rgba(0, 255, 255, 0.1);
400
+ }
401
+
402
+ .comment-meta {
403
+ color: #888;
404
+ }
405
+
406
+ .comment-empty {
407
+ color: #888;
408
+ }
409
+
410
+ .comment-form input[type="text"],
411
+ .comment-form textarea {
412
+ background: rgba(16, 16, 32, 0.8);
413
+ border: 1px solid rgba(0, 255, 255, 0.3);
414
+ color: #e0e0e0;
415
+ border-radius: 4px;
416
+ font-family: 'Courier New', monospace;
417
+ }
418
+
419
+ .comment-form input[type="text"]:focus,
420
+ .comment-form textarea:focus {
421
+ outline: none;
422
+ border-color: #00ffff;
423
+ box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
424
+ }
425
+
426
+ .comment-form input[type="submit"] {
427
+ background: transparent;
428
+ border: 1px solid #00ffff;
429
+ color: #00ffff;
430
+ border-radius: 20px;
431
+ font-size: 0.8rem;
432
+ letter-spacing: 1px;
433
+ text-transform: uppercase;
434
+ transition: all 0.3s ease;
435
+ }
436
+
437
+ .comment-form input[type="submit"]:hover {
438
+ background: rgba(0, 255, 255, 0.1);
439
+ box-shadow: 0 0 15px rgba(0, 255, 255, 0.3);
440
+ }
441
+
442
+ .comment-form input[type="text"]::placeholder,
443
+ .comment-form textarea::placeholder {
444
+ color: rgba(0, 255, 255, 0.35);
445
+ font-style: italic;
446
+ }
447
+
448
+ .comment-form {
449
+ border: 1px solid rgba(255, 0, 255, 0.2);
450
+ border-radius: 8px;
451
+ padding: 1.2rem;
452
+ background: rgba(255, 0, 255, 0.02);
453
+ box-shadow: inset 0 0 20px rgba(255, 0, 255, 0.03);
454
+ }
455
+
387
456
  .postlist_item i.datetime {
388
457
  color: #888;
389
458
  font-size: 0.9rem;
@@ -0,0 +1,111 @@
1
+ @font-face {
2
+ font-family: 'IBMPlexSans';
3
+ src: url('/static/IBMPlexSans-Regular.ttf') format('truetype');
4
+ }
5
+
6
+ @font-face {
7
+ font-family: 'Merriweather';
8
+ src: url('/static/Merriweather.ttf') format('truetype');
9
+ }
10
+
11
+
12
+ body {
13
+ background: #efefef;
14
+ color: #464646;
15
+ }
16
+
17
+ article.post-content {
18
+ color: #3c3c3c;
19
+ }
20
+
21
+ article.post-content a {
22
+ color: #6834ab;
23
+ }
24
+
25
+ h1.blog-name {
26
+ font-family: "Merriweather";
27
+ }
28
+
29
+ h1.blog-name a {
30
+ color: #6834ab;
31
+ }
32
+
33
+ .header {
34
+ border-bottom: 1px solid #3333333b;
35
+ font-family: "IBMPlexSans";
36
+ }
37
+
38
+ .header #menu a {
39
+ color: #6834ab;
40
+ font-weight: bold;
41
+ }
42
+
43
+ .header #menu a:hover {
44
+ text-decoration: underline;
45
+ color: #6834ab;
46
+ }
47
+
48
+ i.post-date {
49
+ color: #505050;
50
+ }
51
+
52
+ svg {
53
+ fill: #6834ab;
54
+ }
55
+
56
+ pre code.hljs {
57
+ background: #fff;
58
+ }
59
+
60
+ .post-comments {
61
+ border-top: 1px solid #3333333b;
62
+ }
63
+
64
+ .comment-item {
65
+ border-bottom: 1px solid #3333331a;
66
+ }
67
+
68
+ .comment-meta {
69
+ color: #888;
70
+ }
71
+
72
+ .comment-empty {
73
+ color: #888;
74
+ }
75
+
76
+ .comment-form input[type="text"],
77
+ .comment-form textarea {
78
+ background: #fff;
79
+ border: 1px solid #ccc;
80
+ color: #464646;
81
+ border-radius: 4px;
82
+ }
83
+
84
+ .comment-form input[type="text"]:focus,
85
+ .comment-form textarea:focus {
86
+ outline: none;
87
+ border-color: #6834ab;
88
+ }
89
+
90
+ .comment-form input[type="submit"] {
91
+ background: #6834ab;
92
+ border: none;
93
+ color: #fff;
94
+ border-radius: 4px;
95
+ }
96
+
97
+ .comment-form input[type="submit"]:hover {
98
+ background: #7a3fc8;
99
+ }
100
+
101
+ .comment-form input[type="text"]::placeholder,
102
+ .comment-form textarea::placeholder {
103
+ color: #aaa;
104
+ }
105
+
106
+ .comment-form {
107
+ background: #f5f5f5;
108
+ border: 1px solid #d0d0d0;
109
+ border-radius: 4px;
110
+ padding: 1rem;
111
+ }
@@ -0,0 +1,82 @@
1
+ body {
2
+ background: #11111a;
3
+ }
4
+
5
+ h1.blog-name {
6
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
7
+ }
8
+
9
+ h1.blog-name a, article.blog-content h3, article.post-content a {
10
+ color: #e3bc5e;
11
+ }
12
+
13
+ div#menu a svg {
14
+ fill: #e3bc5e;
15
+ }
16
+
17
+ article.post-content {
18
+ color: #bbbaba;
19
+ }
20
+
21
+ h1.post-title {
22
+ color: #fff;
23
+ }
24
+
25
+ i.post-date {
26
+ color: #737373;
27
+ }
28
+
29
+ .post-comments {
30
+ border-top: 1px solid rgba(227, 188, 94, 0.3);
31
+ }
32
+
33
+ .post-comments h3 {
34
+ color: #e3bc5e;
35
+ }
36
+
37
+ .comment-item {
38
+ border-bottom: 1px solid rgba(227, 188, 94, 0.15);
39
+ }
40
+
41
+ .comment-meta {
42
+ color: #737373;
43
+ }
44
+
45
+ .comment-empty {
46
+ color: #737373;
47
+ }
48
+
49
+ .comment-form input[type="text"],
50
+ .comment-form textarea {
51
+ background: #1a1a25;
52
+ border: 1px solid rgba(227, 188, 94, 0.3);
53
+ color: #bbbaba;
54
+ border-radius: 2px;
55
+ }
56
+
57
+ .comment-form input[type="text"]:focus,
58
+ .comment-form textarea:focus {
59
+ outline: none;
60
+ border-color: #e3bc5e;
61
+ }
62
+
63
+ .comment-form input[type="submit"] {
64
+ background: transparent;
65
+ border: 1px solid #e3bc5e;
66
+ color: #e3bc5e;
67
+ }
68
+
69
+ .comment-form input[type="submit"]:hover {
70
+ background: rgba(227, 188, 94, 0.1);
71
+ }
72
+
73
+ .comment-form input[type="text"]::placeholder,
74
+ .comment-form textarea::placeholder {
75
+ color: rgba(227, 188, 94, 0.3);
76
+ }
77
+
78
+ .comment-form {
79
+ background: rgba(227, 188, 94, 0.03);
80
+ border: 1px solid rgba(227, 188, 94, 0.15);
81
+ padding: 1rem;
82
+ }