openwrite 0.13.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.
Files changed (81) hide show
  1. {openwrite-0.13.0/openwrite.egg-info → openwrite-0.13.1}/PKG-INFO +1 -1
  2. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/admin.py +24 -9
  3. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/dashboard.py +71 -29
  4. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/main.py +1 -1
  5. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/style.css +6 -0
  6. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/create.html +7 -1
  7. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/dashboard.html +5 -2
  8. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/discover.html +1 -1
  9. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/edit.html +9 -2
  10. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/footer.html +1 -0
  11. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/header.html +0 -1
  12. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/new_page.html +7 -1
  13. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/new_post.html +46 -11
  14. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/post.html +7 -3
  15. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/settings.html +7 -1
  16. openwrite-0.13.1/openwrite/utils/i18n.json +1 -0
  17. {openwrite-0.13.0 → openwrite-0.13.1/openwrite.egg-info}/PKG-INFO +1 -1
  18. {openwrite-0.13.0 → openwrite-0.13.1}/pyproject.toml +1 -1
  19. openwrite-0.13.0/openwrite/utils/i18n.json +0 -1
  20. {openwrite-0.13.0 → openwrite-0.13.1}/LICENSE +0 -0
  21. {openwrite-0.13.0 → openwrite-0.13.1}/MANIFEST.in +0 -0
  22. {openwrite-0.13.0 → openwrite-0.13.1}/README.md +0 -0
  23. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/__init__.py +0 -0
  24. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/cli.py +0 -0
  25. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/db/__init__.py +0 -0
  26. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/db/base.py +0 -0
  27. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/gemini_multi.py +0 -0
  28. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/gemini_single.py +0 -0
  29. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/auth.py +0 -0
  30. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/blog.py +0 -0
  31. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/federation.py +0 -0
  32. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/routes/upload.py +0 -0
  33. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
  34. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/Merriweather.ttf +0 -0
  35. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/avatar.png +0 -0
  36. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/emoji.json +0 -0
  37. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/favicon.ico +0 -0
  38. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/logo.png +0 -0
  39. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/chart.js +0 -0
  40. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/highlight.min.js +0 -0
  41. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/nativeEmoji.js +0 -0
  42. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/openwrite.js +0 -0
  43. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/papaparse.min.js +0 -0
  44. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/showdown.min.js +0 -0
  45. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/showdown.min.js.map +0 -0
  46. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/site.compat.min.js +0 -0
  47. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/script/site.min.js +0 -0
  48. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/hl.css +0 -0
  49. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/nativeEmoji.min.css +0 -0
  50. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/cyberpunk.css +0 -0
  51. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/light.css +0 -0
  52. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/obscura.css +0 -0
  53. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/stickynote.css +0 -0
  54. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/terminal.css +0 -0
  55. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/warmnight.css +0 -0
  56. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/static/style/themes/windows98.css +0 -0
  57. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/admin.html +0 -0
  58. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/blog.html +0 -0
  59. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/blog_footer.html +0 -0
  60. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/blog_header.html +0 -0
  61. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/blog_preview.html +0 -0
  62. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/changepw.html +0 -0
  63. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/import.html +0 -0
  64. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/index.html +0 -0
  65. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/instances.html +0 -0
  66. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/login.html +0 -0
  67. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/page_preview.html +0 -0
  68. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/preview.html +0 -0
  69. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/register.html +0 -0
  70. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/stats.html +0 -0
  71. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/templates/translations.html +0 -0
  72. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/utils/create_db.py +0 -0
  73. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/utils/db.py +0 -0
  74. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/utils/helpers.py +0 -0
  75. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite/utils/models.py +0 -0
  76. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite.egg-info/SOURCES.txt +0 -0
  77. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite.egg-info/dependency_links.txt +0 -0
  78. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite.egg-info/entry_points.txt +0 -0
  79. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite.egg-info/requires.txt +0 -0
  80. {openwrite-0.13.0 → openwrite-0.13.1}/openwrite.egg-info/top_level.txt +0 -0
  81. {openwrite-0.13.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.13.0
3
+ Version: 0.13.1
4
4
  License: GNU AFFERO GENERAL PUBLIC LICENSE
5
5
  Version 3, 19 November 2007
6
6
 
@@ -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=g.trans['wrong_username'], add="1", captcha="0")
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=g.trans['password_dont_match'], add="1", captcha="0")
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=g.trans["user_exists"], add="1", captcha="0")
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=g.trans["error"], add="1", captcha="0")
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="Wrong file!")
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="Wrong extension!")
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="File is not image!")
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")
@@ -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="Blog limit reached!")
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="Title too long! Max 30 characters.")
49
+ return render_template("create.html", error="title-too-long")
48
50
  if len(form_url) > 30:
49
- return render_template("create.html", error="URL too long! Max 30 characters.")
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="This URL already exists!")
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="Wrong access!")
59
+ return render_template("create.html", error="wrong-access")
58
60
 
59
61
  key = rsa.generate_private_key(
60
62
  public_exponent=65537,
@@ -107,7 +109,7 @@ def create_blog():
107
109
 
108
110
  return redirect("/dashboard")
109
111
  except Exception:
110
- return render_template("create.html", error=g.trans['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
- return redirect("/dashboard")
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
- return redirect("/dashboard")
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="Title too long! Max 30 characters.")
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="Wrong theme!")
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
- return redirect("/dashboard")
173
+ abort(403)
172
174
 
173
175
  if request.method == "GET":
174
176
  return render_template("new_post.html", blog=blog)
@@ -176,11 +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="Title too long! Max 120 characters.")
181
+ return render_template("new_post.html", blog=blog, error="title-too-long")
180
182
 
181
183
  if len(title) < 1:
182
- return render_template("new_post.html", blog=blog, error="Title cannot be empty!")
183
-
184
+ return render_template("new_post.html", blog=blog, error="title-empty")
185
+
184
186
  link = gen_link(title)
185
187
  if link == "rss":
186
188
  link = "p_rss"
@@ -272,7 +274,7 @@ def blog_preview():
272
274
  blog_name = request.referrer.split("/")[-1]
273
275
  blog = g.db.query(Blog).filter_by(name=blog_name).first()
274
276
  if not blog:
275
- return redirect("/")
277
+ abort(404)
276
278
 
277
279
  homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
278
280
  data = {
@@ -293,7 +295,7 @@ def edit_post(blog, post):
293
295
 
294
296
  blog_obj = g.db.query(Blog).filter_by(name=blog).first()
295
297
  if blog_obj is None or blog_obj.owner != g.user:
296
- return redirect("/dashboard")
298
+ abort(403)
297
299
 
298
300
  e_post = g.db.query(Post).filter_by(link=post).first()
299
301
  if request.method == "GET":
@@ -301,11 +303,11 @@ def edit_post(blog, post):
301
303
 
302
304
  p = g.db.query(Post).filter_by(link=post, blog=blog_obj.id).first()
303
305
  if not p:
304
- return redirect("/dashboard")
306
+ abort(404)
305
307
 
306
308
  title = request.form.get("title")
307
309
  if len(title) > 120:
308
- return render_template("new_post.html", blog=blog_obj, error="Title too long! Max 120 characters.")
310
+ return render_template("new_post.html", blog=blog_obj, error="title-too-long")
309
311
  link = gen_link(title)
310
312
  if link == "rss":
311
313
  link = "p_rss"
@@ -334,7 +336,7 @@ def delete_post(blog, post):
334
336
 
335
337
  blog_obj = g.db.query(Blog).filter_by(name=blog).first()
336
338
  if blog_obj is None or blog_obj.owner != g.user:
337
- return redirect("/dashboard")
339
+ abort(403)
338
340
 
339
341
  p = g.db.query(Post).filter_by(link=post, blog=blog_obj.id).first()
340
342
  if p:
@@ -358,14 +360,14 @@ def changepw():
358
360
  user = g.db.query(User).filter_by(id=g.user).first()
359
361
  if user and bcrypt.checkpw(old_pw.encode('utf-8'), user.password_hash.encode('utf-8')):
360
362
  if new_pass != new_pass2:
361
- return render_template("changepw.html", error=g.trans['passwords_dont_match'])
363
+ return render_template("changepw.html", error='passwords_dont_match')
362
364
  hashed = bcrypt.hashpw(new_pass.encode('utf-8'), bcrypt.gensalt())
363
365
  user.password_hash = hashed
364
366
  return redirect("/dashboard")
365
367
 
366
368
  else:
367
- return render_template("changepw.html", error=g.trans['invalid_password'])
368
-
369
+ return render_template("changepw.html", error='invalid_password')
370
+
369
371
 
370
372
  @dashboard_bp.route("/dashboard/import", methods=['GET', 'POST'])
371
373
  def migrate():
@@ -382,7 +384,7 @@ def migrate():
382
384
 
383
385
  blog = g.db.query(Blog).filter_by(name=blog_d).first()
384
386
  if blog.owner != g.user:
385
- return redirect("/dashboard")
387
+ abort(403)
386
388
 
387
389
  for post in posts:
388
390
  title = post['title']
@@ -409,7 +411,7 @@ def stats(blog):
409
411
 
410
412
  blog = g.db.query(Blog).filter_by(name=blog).first()
411
413
  if blog.owner != g.user:
412
- return redirect("/dashboard")
414
+ abort(403)
413
415
 
414
416
  posts = g.db.query(Post).filter_by(blog=blog.id).all()
415
417
 
@@ -464,9 +466,9 @@ def new_page(blog):
464
466
  content_html = request.form.get("content")
465
467
  show = request.form.get("show")
466
468
  if len(name) > 120:
467
- return render_template("new_page.html", blog=blog, error="Name too long! Max 120 characters")
469
+ return render_template("new_page.html", blog=blog, error="name-too-long")
468
470
  if not re.match(r"^\/[a-zA-Z0-9\-\_]+$", url):
469
- return render_template("new_page.html", blog=blog, error="Invalid URL!")
471
+ return render_template("new_page.html", blog=blog, error="invalid-url")
470
472
 
471
473
  url = url[1:]
472
474
  if url == "rss":
@@ -511,10 +513,10 @@ def edit_page(page):
511
513
  show = request.form.get("show")
512
514
  url = request.form.get("url")
513
515
  if len(name) > 120:
514
- return render_template("new_page.html", blog=blog, page=page, home=home, error="Name too long!")
516
+ return render_template("new_page.html", blog=blog, page=page, home=home, error="name-too-long")
515
517
  if home == False:
516
518
  if not re.match(r"^\/[a-zA-Z0-9\-\_]+$", url):
517
- return render_template("new_page.html", blog=blog, error="Invalid URL!")
519
+ return render_template("new_page.html", blog=blog, error="invalid-url")
518
520
 
519
521
  url = url[1:]
520
522
  if url == "rss":
@@ -569,3 +571,43 @@ def page_delete(page):
569
571
  g.db.commit()
570
572
 
571
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
+ )
@@ -96,7 +96,7 @@ def show_instance():
96
96
  blog_count = g.db.query(Blog).count()
97
97
  user_count = g.db.query(User).count()
98
98
  return {
99
- "version": os.getenv("VERSION"),
99
+ "version": "0.13.1",
100
100
  "blogs": blog_count,
101
101
  "users": user_count,
102
102
  "uptime": int(time.time() - int(start_time)),
@@ -389,6 +389,10 @@ i.post-date {
389
389
  font-size: 15px;
390
390
  }
391
391
 
392
+ i.post-date.updated {
393
+ opacity: 0.5;
394
+ }
395
+
392
396
  article.post-content {
393
397
  display: flex;
394
398
  flex-direction: column;
@@ -535,6 +539,8 @@ div.postlist_item {
535
539
  justify-content: left;
536
540
  } .to-center {
537
541
  justify-content: center;
542
+ } .space-between {
543
+ justify-content: space-between;
538
544
  }
539
545
 
540
546
  .post-content#posts {
@@ -2,7 +2,13 @@
2
2
  <h1 class="centered">create a new blog</h1>
3
3
  <article class="main-content">
4
4
  {% if error %}
5
- <div class="error">{{ error }}</div>
5
+ <div class="error">{{ _(error) }}</div>
6
+ {% endif %}
7
+ {% if request.args.get('error') %}
8
+ <div class="error">{{ _(request.args.get('error')) }}</div>
9
+ {% endif %}
10
+ {% if message %}
11
+ <div class="message">{{ _(message) }}</div>
6
12
  {% endif %}
7
13
 
8
14
  <form method="post" action="/dashboard/create">
@@ -1,9 +1,12 @@
1
1
  {% include 'header.html' %}
2
2
  {% if error %}
3
- <div class="error">{{ error }}</div>
3
+ <div class="error">{{ _(error) }}</div>
4
+ {% endif %}
5
+ {% if request.args.get('error') %}
6
+ <div class="error">{{ _(request.args.get('error')) }}</div>
4
7
  {% endif %}
5
8
  {% if message %}
6
- <div class="message">{{ message }}</div>
9
+ <div class="message">{{ _(message) }}</div>
7
10
  {% endif %}
8
11
  <h1 class="centered">{% if g.mode == "single" %}{{ _('your_blog') }}{% else %}{{ _('your_blogs') }}{% endif %}</h1>
9
12
  {% if g.mode == "multi" %}
@@ -4,7 +4,7 @@
4
4
  <div style="display: flex; flex-direction: column; gap: 1rem">
5
5
  {% for p in posts %}
6
6
  <div class="discover_post">
7
- <i class="datetime">{{ p.date }}</i>{{ p.blogname }}: <a class="action" href="{{ p.url }}" target="_blank">{{ p.title }}</a> {{ p.likes }} ♡
7
+ <i class="datetime">{{ p.date }}</i>{{ p.blogname }}: <a class="action" href="{{ p.url }}" target="_blank">{{ p.title }}</a> {{ p.likes }} ♡ {% if g.isadmin == 1 %}<a href="/admin/remove_from_feed/{{ p.id }}" class="action" onclick="return confirm('Remove post {{ p.title }} from discover?')">Remove from discover</a>{% endif %}
8
8
  </div>
9
9
  {% endfor %}
10
10
  </div>
@@ -1,6 +1,12 @@
1
1
  {% include 'header.html' %}
2
2
  {% if error %}
3
- <div class="error">{{ error }}</div>
3
+ <div class="error">{{ _(error) }}</div>
4
+ {% endif %}
5
+ {% if request.args.get('error') %}
6
+ <div class="error">{{ _(request.args.get('error')) }}</div>
7
+ {% endif %}
8
+ {% if message %}
9
+ <div class="message">{{ _(message) }}</div>
4
10
  {% endif %}
5
11
  <div><a href="/dashboard" class="action">Dashboard</a> > {{ blog.title }}</div>
6
12
  <h1 class="centered">
@@ -55,7 +61,7 @@ blog: {{ blog.name }}
55
61
  <tbody>
56
62
  {% for page in pages %}
57
63
  <tr>
58
- <td style="text-align: center; padding: 0.5rem;">/{{ page.url }}</td><td style="text-align: center">{{ page.name }}</td><td style="text-align: center;">{% if page.show == "1" %}{{ _('yes') }}{% else %}{{ _('no') }}{% endif %}</td><td style="display: flex; gap: 1rem; padding: 0.5rem; justify-content: space-between;">
64
+ <td style="text-align: center; padding: 0.5rem;">/{{ page.url }}</td><td style="text-align: center"><a href="{% if blog.access == "path" %}https://{{ g.main_domain }}/b/{{ blog.name }}/{% else %}https://{{ blog.name }}.{{ g.main_domain }}/{% endif %}{{ page.url }}" class="action" target="_blank">{{ page.name }}</a></td><td style="text-align: center;">{% if page.show == "1" %}{{ _('yes') }}{% else %}{{ _('no') }}{% endif %}</td><td style="display: flex; gap: 1rem; padding: 0.5rem; justify-content: space-between;">
59
65
  <a class="action" href="/dashboard/page/{{ page.id }}/edit">{{ _('edit') }}</a>
60
66
  {% if page.url != "" %}<a class="action" href="/dashboard/page/{{ page.id }}/delete" onclick="return confirm('{{ _('are-you-sure-page') }} {{ page.name }}?\n{{ _('this_action_cannot_be_reverted') }}.')">{{ _('delete') }}</a>{% endif %}
61
67
  </td>
@@ -80,6 +86,7 @@ blog: {{ blog.name }}
80
86
  </table>
81
87
  <div class="buttons-list">
82
88
  <a href="/dashboard/post/{{ blog.name }}" class="btn purple">{{ _('new_post') }}</a>
89
+ <a href="/dashboard/export/{{ blog.name }}" class="btn purple">{{ _('export-posts') }}</a>
83
90
  <a href="/dashboard/delete/{{ blog.name }}" class="btn red" onclick="return confirm('{{ _('are_you_sure_blog') }} {{ blog.name }}?\n{{ _('this_action_cannot_be_reverted') }}.')">{{ _('delete_blog') }}</a>
84
91
  </div>
85
92
  </div>
@@ -4,6 +4,7 @@
4
4
  <a href="https://hub.docker.com/r/openwrite/openwrite">Dockerhub</a>
5
5
  <a href="https://ko-fi.com/openwrite">{{ _('buy_coffee') }}</a>
6
6
  <a href="https://main.openwrite.io/contact">{{ _('contact') }}</a>
7
+ <a href="https://main.openwrite.io/terms-of-service">{{ _('terms-of-service') }}</a>
7
8
  <p>{{ _('language') }}: </p>
8
9
  <select name="lang" onchange="location = '/set-lang/' + this.value">
9
10
  {% for code in available_languages.keys() %}
@@ -44,7 +44,6 @@
44
44
  <a href="/dashboard">Dashboard</a>
45
45
  {% endif %}
46
46
  <a href="/discover">{{ _('discover') }}</a>
47
- <a href="/instances">{{ _('instances') }}</a>
48
47
  <a href="https://main.openwrite.io/hosting">{{ _('hosting') }}</a>
49
48
  {% if g.user %}
50
49
  <a href="/logout">{{ _('logout') }}</a>
@@ -1,6 +1,12 @@
1
1
  {% include 'header.html' %}
2
2
  {% if error %}
3
- <div class="error">{{ error }}</div>
3
+ <div class="error">{{ _(error) }}</div>
4
+ {% endif %}
5
+ {% if request.args.get('error') %}
6
+ <div class="error">{{ _(request.args.get('error')) }}</div>
7
+ {% endif %}
8
+ {% if message %}
9
+ <div class="message">{{ _(message) }}</div>
4
10
  {% endif %}
5
11
  <div><a href="/dashboard" class="action">Dashboard</a> > <a href="/dashboard/edit/{{ blog.name }}" class="action">{{ blog.title }}</a> > {% if page %}{{ page.name }}{% else %}{{ _('new-page') }}{% endif %}</div>
6
12
  <div class="container-row-fluid">
@@ -1,6 +1,12 @@
1
1
  {% include 'header.html' %}
2
2
  {% if error %}
3
- <div class="error">{{ error }}</div>
3
+ <div class="error">{{ _(error) }}</div>
4
+ {% endif %}
5
+ {% if request.args.get('error') %}
6
+ <div class="error">{{ _(request.args.get('error')) }}</div>
7
+ {% endif %}
8
+ {% if message %}
9
+ <div class="message">{{ _(message) }}</div>
4
10
  {% endif %}
5
11
  <div><a href="/dashboard" class="action">Dashboard</a> > <a href="/dashboard/edit/{{ blog.name }}" class="action">{{ blog.title }}</a> > {% if post %}{{ post.title }}{% else %}{{ _('new_post') }}{% endif %}</div>
6
12
  <div class="container-row-fluid">
@@ -11,15 +17,16 @@
11
17
  <form method="post" action="{% if post %}/dashboard/edit/{{ blog.name }}/{{ post.link }}{% else %}/dashboard/post/{{ blog.name }}{% endif %}">
12
18
  <input type="text" name="title" placeholder="{{ _('title') }}" style="max-width: 100%; height: 50px; font-size: 25px; margin-bottom: 1rem;" value="{% if post %}{{ post.title }}{% endif %}">
13
19
  <div style="display: flex; flex-direction: row; gap: 5px; align-items: center;">
14
- <a href="#" class="small btn empty" data-action="h1">H1</a>
15
- <a href="#" class="small btn empty" data-action="h2">H2</a>
16
- <a href="#" class="small btn empty" data-action="h3">H3</a>
17
- <a href="#" class="small btn empty" data-action="bold"><b>B</b></a>
18
- <a href="#" class="small btn empty" data-action="italic"><i>I</i></a>
19
- <a href="#" class="small btn empty" data-action="underline"><u>U</u></a>
20
- <a href="#" class="small btn empty" data-action="strikethrough"><del>C</del></a>
21
- <a href="#" class="small btn empty" data-action="quote">Q</a>
22
- <a href="https://main.openwrite.io/markdown-syntax" style="text-decoration: none; color: #eeeeee; margin-left: auto" target="_blank">Markdown syntax</a>
20
+ <a href="#" class="small btn empty" data-action="h1" title="Heading 1">H1</a>
21
+ <a href="#" class="small btn empty" data-action="h2" title="Heading 2">H2</a>
22
+ <a href="#" class="small btn empty" data-action="h3" title="Heading 3">H3</a>
23
+ <a href="#" class="small btn empty" data-action="bold" title="Bold text"><b>B</b></a>
24
+ <a href="#" class="small btn empty" data-action="italic" title="Italic text"><i>I</i></a>
25
+ <a href="#" class="small btn empty" data-action="underline" title="Underlined text"><u>U</u></a>
26
+ <a href="#" class="small btn empty" data-action="strikethrough" title="Strikethrough text"><del>C</del></a>
27
+ <a href="#" class="small btn empty" data-action="quote" title="Quote/Blockquote">Q</a>
28
+ <a href="#" class="small btn empty" data-action="link" title="Insert link">🔗</a>
29
+ <a href="https://main.openwrite.io/markdown-syntax" style="text-decoration: none; color: #eeeeee; margin-left: auto" target="_blank" title="View Markdown syntax guide">Markdown syntax</a>
23
30
  </div>
24
31
  <textarea autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" name="content" class="post_content" placeholder="{{ _('content') }}">{% if post %}{{ post.content_raw }}{% endif %}</textarea>
25
32
  <div style="display: flex; justify-content: space-between">
@@ -40,6 +47,7 @@
40
47
  <input type="submit" value="{{ _('save-as-draft') }}" name="draft" id="draft" class="btn empty">
41
48
  {% endif %}
42
49
  <input type="submit" value="{% if post %}{% if post.isdraft == "0" %}{{ _('save') }}{% else %}{{ _('save-draft') }}{% endif %}{% else %}{{ _('create') }}{% endif %}" class="btn purple" id="{% if post %}{% if post.isdraft == "0" %}submit{% else %}draft{% endif %}{% else %}submit{% endif %}">
50
+ {% if post %}{% if post.isdraft == "0" %}<input type="hidden" id="draft">{% endif %}{% endif %}
43
51
  {% if post %}
44
52
  {% if post.isdraft == "1" %}
45
53
  <input type="submit" value="{{ _('publish') }}" class="btn purple" id="submit">
@@ -52,10 +60,10 @@
52
60
  var converter = new showdown.Converter();
53
61
  converter.setOption('emoji', true);
54
62
  converter.setOption('noHeaderId', true);
55
- converter.setOption('openLinksInNewWindow', true);
56
63
  converter.setOption('tables', true);
57
64
  converter.setOption('tasklists', true);
58
65
  converter.setOption('strikethrough', true);
66
+ converter.setOption('simplifiedAutoLink', true);
59
67
 
60
68
  document.getElementById('preview').addEventListener('click', function(e) {
61
69
  e.preventDefault();
@@ -219,34 +227,61 @@ document.querySelectorAll('[data-action]').forEach(btn => {
219
227
  const selected = editor.value.slice(start, end);
220
228
 
221
229
  let modified = selected;
230
+ let cursorOffset = 0;
231
+
222
232
  switch (action) {
223
233
  case "bold":
224
234
  modified = `**${selected}**`;
235
+ cursorOffset = selected ? 0 : 2;
225
236
  break;
226
237
  case "italic":
227
238
  modified = `*${selected}*`;
239
+ cursorOffset = selected ? 0 : 1;
228
240
  break;
229
241
  case "underline":
230
242
  modified = `<u>${selected}</u>`;
243
+ cursorOffset = selected ? 0 : 3;
231
244
  break;
232
245
  case "strikethrough":
233
246
  modified = `~~${selected}~~`;
247
+ cursorOffset = selected ? 0 : 2;
234
248
  break;
235
249
  case "quote":
236
250
  modified = selected.split("\n").map(line => `> ${line}`).join("\n");
251
+ cursorOffset = selected ? 0 : 2;
237
252
  break;
238
253
  case "h1":
239
254
  modified = `# ${selected}`;
255
+ cursorOffset = selected ? 0 : 2;
240
256
  break;
241
257
  case "h2":
242
258
  modified = `## ${selected}`;
259
+ cursorOffset = selected ? 0 : 3;
243
260
  break;
244
261
  case "h3":
245
262
  modified = `### ${selected}`;
263
+ cursorOffset = selected ? 0 : 4;
264
+ break;
265
+ case "link":
266
+ const url = prompt("Enter the URL:");
267
+ if (url) {
268
+ if (selected) {
269
+ modified = `[${selected}](${url})`;
270
+ cursorOffset = 0;
271
+ } else {
272
+ modified = `[link description](${url})`;
273
+ cursorOffset = 1;
274
+ }
275
+ }
246
276
  break;
247
277
  }
248
278
 
249
279
  editor.setRangeText(modified, start, end, "end");
280
+
281
+ if (!selected && cursorOffset > 0) {
282
+ editor.selectionStart = editor.selectionEnd = start + cursorOffset;
283
+ }
284
+
250
285
  editor.focus();
251
286
  });
252
287
  });
@@ -4,9 +4,13 @@
4
4
  <article class="post-content">
5
5
  {{ post.content_html | safe }}
6
6
  </article>
7
- <div class="flex row small-gap to-right">
8
- <span>{{ post.likes }}</span>
9
- <svg id="like" width="20px" height="20px" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--twemoji" preserveAspectRatio="xMidYMid meet"><path style="{% if post.liked > 0 %}fill: #DD2E44;{% else %}fill: none{% endif %}" stroke="#DD2E44" stroke-width="2" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868c-3.308 0-6.227 1.633-8.018 4.129c-1.791-2.496-4.71-4.129-8.017-4.129c-5.45 0-9.868 4.417-9.868 9.868c0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959c.17-.721.268-1.469.268-2.242z"></path></svg>
7
+ <div class="flex row small-gap space-between">
8
+ <i class="post-date updated">
9
+ {% if post.updated not in (None, "", "null", "NULL") %}
10
+ {{ _('last-updated') }}: <span class="datetime">{{ post.updated }}</span>
11
+ {% endif %}
12
+ </i>
13
+ <span>{{ post.likes }} <svg id="like" width="20px" height="20px" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--twemoji" preserveAspectRatio="xMidYMid meet"><path style="{% if post.liked > 0 %}fill: #DD2E44;{% else %}fill: none{% endif %}" stroke="#DD2E44" stroke-width="2" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868c-3.308 0-6.227 1.633-8.018 4.129c-1.791-2.496-4.71-4.129-8.017-4.129c-5.45 0-9.868 4.417-9.868 9.868c0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959c.17-.721.268-1.469.268-2.242z"></path></svg></span>
10
14
  </div>
11
15
 
12
16
 
@@ -1,6 +1,12 @@
1
1
  {% include 'header.html' %}
2
2
  {% if error %}
3
- <div class="error">{{ error }}</div>
3
+ <div class="error">{{ _(error) }}</div>
4
+ {% endif %}
5
+ {% if request.args.get('error') %}
6
+ <div class="error">{{ _(request.args.get('error')) }}</div>
7
+ {% endif %}
8
+ {% if message %}
9
+ <div class="message">{{ _(message) }}</div>
4
10
  {% endif %}
5
11
  <h1 class="centered">{{ _('settings') }}</h1>
6
12
  <form method="post" action="/admin/settings" enctype="multipart/form-data">
@@ -0,0 +1 @@
1
+ {"pl": {"lang_name": "🇵🇱 Polski", "instances": "Instancje", "hosting": "Hosting", "register": "Rejestracja", "login": "Zaloguj się", "register_here": "Zarejestruj się tutaj", "self_host": "Samodzielny hosting", "username": "Nazwa użytkownika", "password": "Hasło", "email": "E-mail", "not_required": "nie wymagany, ale zalecany do 2FA i odzyskiwania konta", "register_on_this_instance": "zarejestruj się na tej instancji", "or": "lub", "language": "Język", "buy_coffee": "Kup mi kawkę", "user_exists": "Użytkownik o takiej nazwie istnieje. Wybierz inną.", "error": "Wystąpił nieznany błąd. Spróbuj ponownie później.", "registered": "Zarejestrowano pomyslnie! Możesz się zalogować.", "invalid_credentials": "Login lub hasło nieprawidłowe!", "logout": "Wyloguj", "your_blogs": "twoje blogi", "title": "Tytuł", "edit": "Edytuj", "new_post": "Nowy wpis", "delete_blog": "Usuń bloga", "create_new_blog": "Utwórz nowego bloga", "description": "Opis", "save": "Zapisz", "upload_image": "Prześlij obraz", "date": "Data", "actions": "Akcje", "choose_blog_title": "Wybierz tytuł bloga", "choose_blog_url": "Wybierz adres URL bloga", "access_blog_how": "Jaki chcesz link do bloga", "domain_name": "Nazwa domeny", "path": "Ścieżka", "create": "Utwórz", "index_blog": "Indeksuj bloga w wyszukiwarkach", "content": "Treść", "preview": "Podgląd", "post": "Opublikuj", "include_author": "Dołącz informacje o autorze (nazwa użytkownika) we wpisie", "new_post_on": "nowy wpis na blogu", "posts": "Wpisy", "are_you_sure_blog": "Czy na pewno chcesz usunąć bloga", "this_action_cannot_be_reverted": "Tej operacji nie można cofnąć", "are_you_sure_user": "Czy na pewno chcesz usunąć użytkownika", "are_you_sure_post": "Czy na pewno chcesz usunąć wpis", "delete": "Usuń", "editing_post": "edycja wpisu", "views": "Wyświetlenia", "settings": "Ustawienia", "mfa": "Weryfikacja dwuetapowa", "verify": "Zweryfikuj", "delete_account": "Usuń konto", "instances_text": "Tutaj znajdziesz wszystkie znane nam instancje openwrite.io wraz z ich właściwościami", "name": "Nazwa", "blogs_running": "Uruchomione blogi", "users": "Użytkownicy", "media_upload": "Przesyłanie mediów?", "password2": "Powtórz hasło", "passwords_dont_match": "Hasła się nie zgadzają!", "include_in_feed": "Wyświetlać wpis w sekcji \"Odkrywaj\"", "discover": "Odkrywaj", "discover_text": "Tutaj znajdziesz wszystkie najnowsze posty z tej instancji openwrite.", "yes": "Tak", "no": "Nie", "change_password": "Zmień hasło", "migrate_posts": "Importuj posty", "wrong_username": "Nieprawidłowa nazwa!", "invalid_captcha": "Captcha nieprawidłowa!", "uploads_disabled": "Przesyłanie plików wyłączone", "invalid_filename": "Nieprawidłowa nazwa pliku", "unsupported_filetype": "Nieprawidłowy typ pliku", "no_file": "Brak pliku", "not_image": "Przesyłany plik nie jest obrazem", "upload_failed": "Niepowodzenie w przesyłaniu", "current_password": "Aktualne hasło", "new_password": "Nowe hasło", "choose_vendor": "Wybierz dostawcę obecnego bloga", "select_posts": "Wybierz wpisy do migracji", "migrate_info": "Dane są używane wyłącznie do migracji wpisów i nie są zapisywane.", "invalid_password": "Nieprawidłowe hasło!", "choose_format": "Wybierz format danych", "title_column": "Nazwa kolumny z tytułem", "content_column": "Nazwa kolumny z treścią", "date_column": "Nazwa kolumny z datą", "content_format": "Format treści wpisu", "step": "Krok", "upload_file": "Wybierz plik", "send": "Wyślij", "import_info": "W tym miejscu możesz zaimportować wpisy z innej platformy. Przyjmowane są pliki z Wordpress(XML) oraz CSV. <a class=\"link\" href=\"https://main.openwrite.io/posts-migration\">Tutaj</a> dowiesz się jak przygotować dane do importu.", "import_success1": "Import wpisów zakończony sukcesem! Przejdź do", "import_success2": "dashboardu bloga", "import_success3": "żeby zobaczyć swoje posty.", "import_failed": "Wystąpił błąd! Sprawdź przesyłane dane.", "select_blog_to_import": "Wybierz blog, do którego trafią wpisy", "remove_admin": "Zabierz prawa administratora", "add_admin": "Mianuj administratorem", "are_you_sure_make_admin": "Czy na pewno chcesz nadać prawa administratora dla", "are_you_sure_remove_admin": "Czy na pewno zabrać uprawnienia administratora ", "add_user": "Dodaj użytkownika", "select_theme": "Wybierz motyw", "default": "Domyślny", "see_themes": "Zobacz motywy", "custom_css": "Własny CSS", "css_unsafe_info": "Niebezpieczne wartości jak url(), @keyframes, @import, expression, javascript i animation zostaną usunięte.", "your_blog": "twój blog", "statistics": "Statystyki", "contact": "Kontakt", "pages": "Strony", "new-page": "Nowa strona", "show-in-header": "Pokaż w nagłówku bloga", "are-you-sure-page": "Czy na pewno chcesz usunąć stronę", "new-page-on": "Nowa strona na blogu", "hint-posts": "Wpisz {posts} gdziekolwiek chcesz żeby pokazała się twoja lista wpisów!", "and": "i", "icon": "Ikona", "save-as-draft": "Zapisz jako szkic", "draft": "Szkic", "save-draft": "Zapisz szkic", "publish": "Opublikuj", "export-posts": "Eksportuj wpisy", "blog-limit-reached": "Osiągnięto limit blogów!", "title-too-long": "Tytuł za długi!", "url-too-long": "URL za długi! Max 30 znaków.", "url-exists": "Taki URL już istnieje, wybierz inny.", "wrong-access": "Niepoprawny link!", "wrong-theme": "Niepoprawny motyw!", "title-empty": "Tytuł nie może być pusty!", "name-too-long": "Nazwa za długa!", "invalid-url": "Nieprawidłowy URL!", "terms-of-use": "Regulamin", "terms-of-service": "Regulamin", "last-updated": "Ostatnio edytowane"}, "en": {"lang_name": "🇬🇧 English", "instances": "Instances", "hosting": "Hosting", "register": "Register", "login": "Login", "register_here": "Register Here", "self_host": "Self Host", "username": "Username", "password": "Password", "email": "Email", "not_required": "not required by recommended for 2FA and recovery", "register_on_this_instance": "register on this instance", "or": "or", "language": "Language", "buy_coffee": "Buy me a coffee", "user_exists": "User already exists. Choose different name.", "error": "Unknown error occured. Try again later.", "registered": "Register successful! You can login now.", "invalid_credentials": "Invalid credentials!", "logout": "Log out", "your_blogs": "your blogs", "title": "Title", "edit": "Edit", "new_post": "New post", "delete_blog": "Delete blog", "create_new_blog": "Create new blog", "description": "Description", "save": "Save", "upload_image": "Upload image", "date": "Date", "actions": "Actions", "choose_blog_title": "Choose blog title", "choose_blog_url": "Choose blog URL", "access_blog_how": "How would you like to access your blog", "domain_name": "Domain name", "path": "Path", "create": "Create", "index_blog": "Index your blog in search engines", "content": "content", "preview": "Preview", "post": "Post", "include_author": "Include author information(username) in post", "new_post_on": "new post on blog", "posts": "Posts", "are_you_sure_blog": "Are you sure to delete blog", "this_action_cannot_be_reverted": "This action cannot be reverted", "are_you_sure_user": "Are you sure to delete user", "are_you_sure_post": "Are you sure to delete post", "delete": "Delete", "editing_post": "editing post", "views": "Views", "settings": "Settings", "mfa": "Multi-factor authentication", "verify": "Verify", "delete_account": "Delete account", "instances_text": "Here you will find all known openwrite instances along with their properties", "name": "Name", "blogs_running": "Blogs running", "users": "Users", "media_upload": "Upload media?", "password2": "Retype password", "passwords_dont_match": "Passwords don't match!", "include_in_feed": "Include post in \"Discover\" section", "discover": "Discover", "discover_text": "Here you can find most recent posts from this instance.", "yes": "Yes", "no": "No", "change_password": "Change Password", "migrate_posts": "Import posts", "wrong_username": "Wrong username!", "invalid_captcha": "Invalid captcha!", "uploads_disabled": "Uploads disabled", "invalid_filename": "Invalid filename", "unsupported_filetype": "Unsupported file type", "no_file": "No file", "not_image": "Uploaded file is not a valid image", "upload_failed": "Upload failed", "current_password": "Current password", "new_password": "New password", "choose_vendor": "Choose blog vendor", "select_posts": "Select posts to migrate", "migrate_info": "Data is used only for migration and not saved in any place.", "invalid_password": "Invalid password!", "choose_format": "Choose data format", "title_column": "Title column name", "content_column": "Content column name", "date_column": "Date column name", "content_format": "Content format", "step": "Step", "upload_file": "Upload file", "send": "Send", "import_info": "In here you can import posts from another platform. Files can be exported from Wordpress(XML) or in general CSV format. <a class=\"link\" href=\"https://main.openwrite.io/posts-migration\">Here</a> you can find information about how to prepare data to import.", "import_success1": "Import successful! Go to", "import_success2": "blog dashboard", "import_success3": "to see your posts.", "import_failed": "Error occured! Check your data format.", "select_blog_to_import": "Select blog to import data to", "remove_admin": "Remove admin", "add_admin": "Make admin", "are_you_sure_make_admin": "Are you sure to add admin rights to", "are_you_sure_remove_admin": "Are you sure to remove admin rights from", "add_user": "Add user", "select_theme": "Select theme", "default": "Default", "see_themes": "See themes", "custom_css": "Custom CSS", "css_unsafe_info": "Unsafe values like url(), @keyframes, @import, expression, javascript and animation will be removed.", "your_blog": "your blog", "statistics": "Statistics", "contact": "Contact", "pages": "Pages", "new-page": "New page", "show-in-header": "Show in blog header", "are-you-sure-page": "Are you sure to delete page", "new-page-on": "New page on blog", "hint-posts": "Add {posts} wherever you want to print your post list!", "and": "and", "icon": "Icon", "save-as-draft": "Save as draft", "draft": "Draft", "save-draft": "Save draft", "publish": "Publish", "export-posts": "Export posts", "blog-limit-reached": "Blog limit reached!", "title-too-long": "Title too long!", "url-too-long": "URL too long! Max 30 characters.", "url-exists": "This URL already exists. Choose different one.", "wrong-access": "Wrong access!", "wrong-theme": "Wrong theme!", "title-empty": "Title cannot be empty!", "name-too-long": "Name too long!", "invalid-url": "Invalid URL!", "terms-of-use": "Terms of Service", "terms-of-service": "Terms of Service", "last-updated": "Last updated"}}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwrite
3
- Version: 0.13.0
3
+ Version: 0.13.1
4
4
  License: GNU AFFERO GENERAL PUBLIC LICENSE
5
5
  Version 3, 19 November 2007
6
6
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openwrite"
7
- version = "0.13.0"
7
+ version = "0.13.1"
8
8
  license = { file = "LICENSE" }
9
9
  dependencies = [
10
10
  "flask",
@@ -1 +0,0 @@
1
- {"pl": {"lang_name": "🇵🇱 Polski", "instances": "Instancje", "hosting": "Hosting", "register": "Rejestracja", "login": "Zaloguj się", "register_here": "Zarejestruj się tutaj", "self_host": "Samodzielny hosting", "username": "Nazwa użytkownika", "password": "Hasło", "email": "E-mail", "not_required": "nie wymagany, ale zalecany do 2FA i odzyskiwania konta", "register_on_this_instance": "zarejestruj się na tej instancji", "or": "lub", "language": "Język", "buy_coffee": "Kup mi kawkę", "user_exists": "Użytkownik o takiej nazwie istnieje. Wybierz inną.", "error": "Wystąpił nieznany błąd. Spróbuj ponownie później.", "registered": "Zarejestrowano pomyslnie! Możesz się zalogować.", "invalid_credentials": "Login lub hasło nieprawidłowe!", "logout": "Wyloguj", "your_blogs": "twoje blogi", "title": "Tytuł", "edit": "Edytuj", "new_post": "Nowy wpis", "delete_blog": "Usuń bloga", "create_new_blog": "Utwórz nowego bloga", "description": "Opis", "save": "Zapisz", "upload_image": "Prześlij obraz", "date": "Data", "actions": "Akcje", "choose_blog_title": "Wybierz tytuł bloga", "choose_blog_url": "Wybierz adres URL bloga", "access_blog_how": "Jaki chcesz link do bloga", "domain_name": "Nazwa domeny", "path": "Ścieżka", "create": "Utwórz", "index_blog": "Indeksuj bloga w wyszukiwarkach", "content": "Treść", "preview": "Podgląd", "post": "Opublikuj", "include_author": "Dołącz informacje o autorze (nazwa użytkownika) we wpisie", "new_post_on": "nowy wpis na blogu", "posts": "Wpisy", "are_you_sure_blog": "Czy na pewno chcesz usunąć bloga", "this_action_cannot_be_reverted": "Tej operacji nie można cofnąć", "are_you_sure_user": "Czy na pewno chcesz usunąć użytkownika", "are_you_sure_post": "Czy na pewno chcesz usunąć wpis", "delete": "Usuń", "editing_post": "edycja wpisu", "views": "Wyświetlenia", "settings": "Ustawienia", "mfa": "Weryfikacja dwuetapowa", "verify": "Zweryfikuj", "delete_account": "Usuń konto", "instances_text": "Tutaj znajdziesz wszystkie znane nam instancje openwrite.io wraz z ich właściwościami", "name": "Nazwa", "blogs_running": "Uruchomione blogi", "users": "Użytkownicy", "media_upload": "Przesyłanie mediów?", "password2": "Powtórz hasło", "passwords_dont_match": "Hasła się nie zgadzają!", "include_in_feed": "Wyświetlać wpis w sekcji \"Odkrywaj\"", "discover": "Odkrywaj", "discover_text": "Tutaj znajdziesz wszystkie najnowsze posty z tej instancji openwrite.", "yes": "Tak", "no": "Nie", "change_password": "Zmień hasło", "migrate_posts": "Importuj posty", "wrong_username": "Nieprawidłowa nazwa!", "invalid_captcha": "Captcha nieprawidłowa!", "uploads_disabled": "Przesyłanie plików wyłączone", "invalid_filename": "Nieprawidłowa nazwa pliku", "unsupported_filetype": "Nieprawidłowy typ pliku", "no_file": "Brak pliku", "not_image": "Przesyłany plik nie jest obrazem", "upload_failed": "Niepowodzenie w przesyłaniu", "current_password": "Aktualne hasło", "new_password": "Nowe hasło", "choose_vendor": "Wybierz dostawcę obecnego bloga", "select_posts": "Wybierz wpisy do migracji", "migrate_info": "Dane są używane wyłącznie do migracji wpisów i nie są zapisywane.", "invalid_password": "Nieprawidłowe hasło!", "choose_format": "Wybierz format danych", "title_column": "Nazwa kolumny z tytułem", "content_column": "Nazwa kolumny z treścią", "date_column": "Nazwa kolumny z datą", "content_format": "Format treści wpisu", "step": "Krok", "upload_file": "Wybierz plik", "send": "Wyślij", "import_info": "W tym miejscu możesz zaimportować wpisy z innej platformy. Przyjmowane są pliki z Wordpress(XML) oraz CSV. <a class=\"link\" href=\"https://main.openwrite.io/posts-migration\">Tutaj</a> dowiesz się jak przygotować dane do importu.", "import_success1": "Import wpisów zakończony sukcesem! Przejdź do", "import_success2": "dashboardu bloga", "import_success3": "żeby zobaczyć swoje posty.", "import_failed": "Wystąpił błąd! Sprawdź przesyłane dane.", "select_blog_to_import": "Wybierz blog, do którego trafią wpisy", "remove_admin": "Zabierz prawa administratora", "add_admin": "Mianuj administratorem", "are_you_sure_make_admin": "Czy na pewno chcesz nadać prawa administratora dla", "are_you_sure_remove_admin": "Czy na pewno zabrać uprawnienia administratora ", "add_user": "Dodaj użytkownika", "select_theme": "Wybierz motyw", "default": "Domyślny", "see_themes": "Zobacz motywy", "custom_css": "Własny CSS", "css_unsafe_info": "Niebezpieczne wartości jak url(), @keyframes, @import, expression, javascript i animation zostaną usunięte.", "your_blog": "twój blog", "statistics": "Statystyki", "contact": "Kontakt", "pages": "Strony", "new-page": "Nowa strona", "show-in-header": "Pokaż w nagłówku bloga", "are-you-sure-page": "Czy na pewno chcesz usunąć stronę", "new-page-on": "Nowa strona na blogu", "hint-posts": "Wpisz {posts} gdziekolwiek chcesz żeby pokazała się twoja lista wpisów!", "and": "i", "icon": "Ikona", "save-as-draft": "Zapisz jako szkic", "draft": "Szkic", "save-draft": "Zapisz szkic", "publish": "Opublikuj"}, "en": {"lang_name": "🇬🇧 English", "instances": "Instances", "hosting": "Hosting", "register": "Register", "login": "Login", "register_here": "Register Here", "self_host": "Self Host", "username": "Username", "password": "Password", "email": "Email", "not_required": "not required by recommended for 2FA and recovery", "register_on_this_instance": "register on this instance", "or": "or", "language": "Language", "buy_coffee": "Buy me a coffee", "user_exists": "User already exists. Choose different name.", "error": "Unknown error occured. Try again later.", "registered": "Register successful! You can login now.", "invalid_credentials": "Invalid credentials!", "logout": "Log out", "your_blogs": "your blogs", "title": "Title", "edit": "Edit", "new_post": "New post", "delete_blog": "Delete blog", "create_new_blog": "Create new blog", "description": "Description", "save": "Save", "upload_image": "Upload image", "date": "Date", "actions": "Actions", "choose_blog_title": "Choose blog title", "choose_blog_url": "Choose blog URL", "access_blog_how": "How would you like to access your blog", "domain_name": "Domain name", "path": "Path", "create": "Create", "index_blog": "Index your blog in search engines", "content": "content", "preview": "Preview", "post": "Post", "include_author": "Include author information(username) in post", "new_post_on": "new post on blog", "posts": "Posts", "are_you_sure_blog": "Are you sure to delete blog", "this_action_cannot_be_reverted": "This action cannot be reverted", "are_you_sure_user": "Are you sure to delete user", "are_you_sure_post": "Are you sure to delete post", "delete": "Delete", "editing_post": "editing post", "views": "Views", "settings": "Settings", "mfa": "Multi-factor authentication", "verify": "Verify", "delete_account": "Delete account", "instances_text": "Here you will find all known openwrite instances along with their properties", "name": "Name", "blogs_running": "Blogs running", "users": "Users", "media_upload": "Upload media?", "password2": "Retype password", "passwords_dont_match": "Passwords don't match!", "include_in_feed": "Include post in \"Discover\" section", "discover": "Discover", "discover_text": "Here you can find most recent posts from this instance.", "yes": "Yes", "no": "No", "change_password": "Change Password", "migrate_posts": "Import posts", "wrong_username": "Wrong username!", "invalid_captcha": "Invalid captcha!", "uploads_disabled": "Uploads disabled", "invalid_filename": "Invalid filename", "unsupported_filetype": "Unsupported file type", "no_file": "No file", "not_image": "Uploaded file is not a valid image", "upload_failed": "Upload failed", "current_password": "Current password", "new_password": "New password", "choose_vendor": "Choose blog vendor", "select_posts": "Select posts to migrate", "migrate_info": "Data is used only for migration and not saved in any place.", "invalid_password": "Invalid password!", "choose_format": "Choose data format", "title_column": "Title column name", "content_column": "Content column name", "date_column": "Date column name", "content_format": "Content format", "step": "Step", "upload_file": "Upload file", "send": "Send", "import_info": "In here you can import posts from another platform. Files can be exported from Wordpress(XML) or in general CSV format. <a class=\"link\" href=\"https://main.openwrite.io/posts-migration\">Here</a> you can find information about how to prepare data to import.", "import_success1": "Import successful! Go to", "import_success2": "blog dashboard", "import_success3": "to see your posts.", "import_failed": "Error occured! Check your data format.", "select_blog_to_import": "Select blog to import data to", "remove_admin": "Remove admin", "add_admin": "Make admin", "are_you_sure_make_admin": "Are you sure to add admin rights to", "are_you_sure_remove_admin": "Are you sure to remove admin rights from", "add_user": "Add user", "select_theme": "Select theme", "default": "Default", "see_themes": "See themes", "custom_css": "Custom CSS", "css_unsafe_info": "Unsafe values like url(), @keyframes, @import, expression, javascript and animation will be removed.", "your_blog": "your blog", "statistics": "Statistics", "contact": "Contact", "pages": "Pages", "new-page": "New page", "show-in-header": "Show in blog header", "are-you-sure-page": "Are you sure to delete page", "new-page-on": "New page on blog", "hint-posts": "Add {posts} wherever you want to print your post list!", "and": "and", "icon": "Icon", "save-as-draft": "Save as draft", "draft": "Draft", "save-draft": "Save draft", "publish": "Publish"}}
File without changes
File without changes
File without changes
File without changes
File without changes