openwrite 0.14.0__tar.gz → 0.15.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.
- {openwrite-0.14.0/openwrite.egg-info → openwrite-0.15.0}/PKG-INFO +5 -3
- {openwrite-0.14.0 → openwrite-0.15.0}/README.md +4 -2
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/__init__.py +11 -11
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/gemini_multi.py +21 -1
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/auth.py +1 -1
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/blog.py +53 -58
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/dashboard.py +76 -5
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/main.py +29 -6
- openwrite-0.15.0/openwrite/static/favicon.png +0 -0
- openwrite-0.15.0/openwrite/static/feed/.gitkeep +0 -0
- openwrite-0.15.0/openwrite/static/feed/74.xml +12 -0
- openwrite-0.15.0/openwrite/static/feed/a.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/anim3sh1t.xml +12 -0
- openwrite-0.15.0/openwrite/static/feed/b.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/corrode.xml +4 -0
- openwrite-0.15.0/openwrite/static/feed/dirtysanchez.xml +6 -0
- openwrite-0.15.0/openwrite/static/feed/eikerammen.xml +165 -0
- openwrite-0.15.0/openwrite/static/feed/fedi.xml +9 -0
- openwrite-0.15.0/openwrite/static/feed/friskt.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/gordi.xml +6 -0
- openwrite-0.15.0/openwrite/static/feed/igilq.xml +226 -0
- openwrite-0.15.0/openwrite/static/feed/io.xml +50 -0
- openwrite-0.15.0/openwrite/static/feed/isyuricunha.xml +4 -0
- openwrite-0.15.0/openwrite/static/feed/kenny.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/m0bi.xml +6 -0
- openwrite-0.15.0/openwrite/static/feed/main.xml +236 -0
- openwrite-0.15.0/openwrite/static/feed/mywritese.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/nay-thoughs.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/nintendoflesz.xml +7 -0
- openwrite-0.15.0/openwrite/static/feed/phronima.xml +68 -0
- openwrite-0.15.0/openwrite/static/feed/ram.xml +75 -0
- openwrite-0.15.0/openwrite/static/feed/samah.xml +2 -0
- openwrite-0.15.0/openwrite/static/feed/themes-test.xml +18 -0
- openwrite-0.15.0/openwrite/static/feed/totylkoja.xml +78 -0
- openwrite-0.15.0/openwrite/static/feed/vegafjord.xml +48 -0
- openwrite-0.15.0/openwrite/static/feed/vijayvaradharaj.xml +2 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/style.css +5 -1
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/blog.html +3 -1
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/dashboard.html +1 -1
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/edit.html +6 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/footer.html +1 -1
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/new_page.html +1 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/new_post.html +54 -2
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/post.html +9 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/stats.html +26 -0
- openwrite-0.15.0/openwrite/utils/create_db.py +28 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/utils/helpers.py +3 -2
- openwrite-0.15.0/openwrite/utils/migrations.py +136 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/utils/models.py +26 -1
- openwrite-0.15.0/openwrite/utils/tags.py +242 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/utils/worker.py +4 -3
- {openwrite-0.14.0 → openwrite-0.15.0/openwrite.egg-info}/PKG-INFO +5 -3
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite.egg-info/SOURCES.txt +30 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/pyproject.toml +1 -1
- openwrite-0.14.0/openwrite/utils/create_db.py +0 -15
- {openwrite-0.14.0 → openwrite-0.15.0}/LICENSE +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/MANIFEST.in +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/cli.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/db/__init__.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/db/base.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/gemini_single.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/admin.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/federation.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/routes/upload.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/Merriweather.ttf +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/avatar.png +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/emoji.json +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/favicon.ico +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/logo.png +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/chart.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/highlight.min.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/nativeEmoji.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/openwrite.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/papaparse.min.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/showdown.min.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/showdown.min.js.map +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/site.compat.min.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/script/site.min.js +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/blog_design.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/default.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/hl.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/nativeEmoji.min.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/cyberpunk.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/light.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/obscura.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/paper.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/stickynote.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/terminal.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/warmnight.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/static/style/themes/windows98.css +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/admin.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/blog_footer.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/blog_header.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/blog_preview.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/changepw.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/create.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/discover.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/header.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/import.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/index.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/instances.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/login.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/page_preview.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/preview.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/register.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/settings.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/templates/translations.html +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/utils/db.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/utils/i18n.json +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite/utils/valkey_client.py +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite.egg-info/dependency_links.txt +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite.egg-info/entry_points.txt +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite.egg-info/requires.txt +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/openwrite.egg-info/top_level.txt +0 -0
- {openwrite-0.14.0 → openwrite-0.15.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openwrite
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
5
5
|
Version 3, 19 November 2007
|
|
6
6
|
|
|
@@ -689,13 +689,13 @@ Requires-Dist: redis
|
|
|
689
689
|
Dynamic: license-file
|
|
690
690
|
|
|
691
691
|

|
|
692
|
+
|
|
692
693
|
openwrite is a minimalist blogging platform built for writing freely, hosting independently, and publishing without noise.
|
|
693
694
|
|
|
694
695
|

|
|
695
696
|

|
|
696
697
|
[](LICENSE)
|
|
697
698
|

|
|
698
|
-

|
|
699
699
|
|
|
700
700
|
---
|
|
701
701
|
|
|
@@ -715,7 +715,7 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
|
|
|
715
715
|
- Lightweight
|
|
716
716
|
- No tracking, only data collected is anonymized(hashed) IP for post view counting and likes
|
|
717
717
|
- Custom CSS per blog
|
|
718
|
-
- Federation using ActivityPub protocol
|
|
718
|
+
- Federation using ActivityPub protocol
|
|
719
719
|
- Likes system
|
|
720
720
|
- Posts importing from xml/csv
|
|
721
721
|
- Blog themes
|
|
@@ -724,6 +724,8 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
|
|
|
724
724
|
- Multilanguage (currently English and Polish)
|
|
725
725
|
- Custom favicon per blog
|
|
726
726
|
- Subpages per blog
|
|
727
|
+
- Valkey support
|
|
728
|
+
- Post tags, filtering
|
|
727
729
|
|
|
728
730
|
|
|
729
731
|
## Installation
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|

|
|
2
|
+
|
|
2
3
|
openwrite is a minimalist blogging platform built for writing freely, hosting independently, and publishing without noise.
|
|
3
4
|
|
|
4
5
|

|
|
5
6
|

|
|
6
7
|
[](LICENSE)
|
|
7
8
|

|
|
8
|
-

|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -25,7 +25,7 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
|
|
|
25
25
|
- Lightweight
|
|
26
26
|
- No tracking, only data collected is anonymized(hashed) IP for post view counting and likes
|
|
27
27
|
- Custom CSS per blog
|
|
28
|
-
- Federation using ActivityPub protocol
|
|
28
|
+
- Federation using ActivityPub protocol
|
|
29
29
|
- Likes system
|
|
30
30
|
- Posts importing from xml/csv
|
|
31
31
|
- Blog themes
|
|
@@ -34,6 +34,8 @@ openwrite is a minimalist blogging platform built for writing freely, hosting in
|
|
|
34
34
|
- Multilanguage (currently English and Polish)
|
|
35
35
|
- Custom favicon per blog
|
|
36
36
|
- Subpages per blog
|
|
37
|
+
- Valkey support
|
|
38
|
+
- Post tags, filtering
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
## Installation
|
|
@@ -25,17 +25,21 @@ def create_app(test_config=None):
|
|
|
25
25
|
db_path = test_config.get("DB_PATH", "data.db")
|
|
26
26
|
load_dotenv(test_config.get("env"), override=True)
|
|
27
27
|
|
|
28
|
+
# Always run database migrations to ensure schema is up to date
|
|
29
|
+
from .utils.create_db import init_db
|
|
30
|
+
init_db(db_type, db_path)
|
|
31
|
+
|
|
32
|
+
# Initialize database engine and session
|
|
33
|
+
from .utils.models import User, Blog, Post, Settings, Home, Page
|
|
34
|
+
from .utils.db import init_engine, SessionLocal
|
|
35
|
+
init_engine(db_type, db_path)
|
|
36
|
+
from .utils.db import SessionLocal
|
|
37
|
+
|
|
28
38
|
if not os.path.exists(".initialized") or test_config is not None:
|
|
29
39
|
mode = os.getenv("MODE")
|
|
30
40
|
domain = os.getenv("DOMAIN")
|
|
31
41
|
f_abs_path = os.path.abspath(__file__)
|
|
32
42
|
file_cwd = "/".join(f_abs_path.split("/")[:-1])
|
|
33
|
-
from .utils.create_db import init_db
|
|
34
|
-
init_db(db_type, db_path)
|
|
35
|
-
from .utils.models import User, Blog, Post, Settings, Home, Page
|
|
36
|
-
from .utils.db import init_engine, SessionLocal
|
|
37
|
-
init_engine(db_type, db_path)
|
|
38
|
-
from .utils.db import SessionLocal
|
|
39
43
|
admin_password = os.urandom(16).hex()
|
|
40
44
|
hashed = bcrypt.hashpw("openwrite".encode("utf-8"), bcrypt.gensalt())
|
|
41
45
|
admin_user = User(username="admin", email="", password_hash=hashed.decode("utf-8"), verified=1, admin=1)
|
|
@@ -171,14 +175,10 @@ def create_app(test_config=None):
|
|
|
171
175
|
from .routes.main import main_bp
|
|
172
176
|
from .routes.upload import upload_bp
|
|
173
177
|
from .routes.federation import federation_bp
|
|
174
|
-
from .utils.db import init_engine, SessionLocal
|
|
175
178
|
from flask_cors import CORS
|
|
176
179
|
|
|
177
180
|
CORS(app)
|
|
178
181
|
|
|
179
|
-
init_engine(db_type, db_path)
|
|
180
|
-
from .utils.db import SessionLocal
|
|
181
|
-
|
|
182
182
|
app.register_blueprint(auth_bp)
|
|
183
183
|
app.register_blueprint(dashboard_bp)
|
|
184
184
|
app.register_blueprint(blog_bp)
|
|
@@ -212,7 +212,7 @@ def create_app(test_config=None):
|
|
|
212
212
|
|
|
213
213
|
trans = g.db.query(Home).filter(Home.type == "translation", Home.language == lang).all()
|
|
214
214
|
|
|
215
|
-
g.version = "0.
|
|
215
|
+
g.version = "0.15.0"
|
|
216
216
|
g.mode = os.getenv("MODE")
|
|
217
217
|
f_abs_path = os.path.abspath(__file__)
|
|
218
218
|
g.mainpath = "/".join(f_abs_path.split("/")[:-1])
|
|
@@ -22,8 +22,10 @@ def root(req):
|
|
|
22
22
|
global session
|
|
23
23
|
resp = f"```\n{openwrite_logo}\n```"
|
|
24
24
|
resp += "\nopenwrite.io is a minimalist blogging platform built for writing freely, hosting independently, and publishing without noise. To create your own blog, visit:\n"
|
|
25
|
-
resp += "=> https://openwrite.io openwrite.io\n"
|
|
25
|
+
resp += "=> https://openwrite.io https://openwrite.io\n"
|
|
26
26
|
resp += "In here you can read posts published.\n"
|
|
27
|
+
resp += "If you want to list all blogs (that did not disable indexing) find them in the link below:\n"
|
|
28
|
+
resp += "=> /blogs All Blogs list"
|
|
27
29
|
resp += "\n## 🗞️ Latest posts:\n"
|
|
28
30
|
try:
|
|
29
31
|
posts = (session.query(Post).filter(Post.feed == "1", Post.isdraft == "0").order_by(Post.id.desc()).limit(10).all())
|
|
@@ -36,6 +38,20 @@ def root(req):
|
|
|
36
38
|
finally:
|
|
37
39
|
session.close()
|
|
38
40
|
|
|
41
|
+
def all_blogs(req):
|
|
42
|
+
global session
|
|
43
|
+
blogs = session.query(Blog).filter_by(index='on').order_by(Blog.title).all()
|
|
44
|
+
resp = "Here you will find a list of all blogs in openwrite that did not disable their indexing:\n"
|
|
45
|
+
resp += "\n"
|
|
46
|
+
for b in blogs:
|
|
47
|
+
posts = session.query(Post).filter_by(blog=b.id).count()
|
|
48
|
+
last = session.query(Post).filter_by(blog=b.id).order_by(Post.date.desc()).first()
|
|
49
|
+
resp += f"=> /b/{b.name} {b.title} \n"
|
|
50
|
+
resp += f"Posts: {posts}\n"
|
|
51
|
+
resp += f"Last posted: {last.date if last is not None else 'never'}\n\n"
|
|
52
|
+
|
|
53
|
+
return resp
|
|
54
|
+
|
|
39
55
|
def blog_index(req):
|
|
40
56
|
global session
|
|
41
57
|
path = req.path
|
|
@@ -134,3 +150,7 @@ def init(capsule):
|
|
|
134
150
|
capsule.add("/b/*",
|
|
135
151
|
blog_index,
|
|
136
152
|
protocol='gemini')
|
|
153
|
+
|
|
154
|
+
capsule.add("/blogs",
|
|
155
|
+
all_blogs,
|
|
156
|
+
protocol='gemini')
|
|
@@ -19,7 +19,7 @@ def register():
|
|
|
19
19
|
return render_template('register.html')
|
|
20
20
|
|
|
21
21
|
form_username = request.form.get('username')
|
|
22
|
-
if not re.match(r"^[a-zA-Z0-9](?:[a-zA-Z0-9_-]{1,28}[a-zA-Z0-9])
|
|
22
|
+
if not re.match(r"^[a-zA-Z0-9](?:[a-zA-Z0-9_-]{1,28}[a-zA-Z0-9])+$", form_username):
|
|
23
23
|
return render_template('register.html', error=g.trans['wrong_username'])
|
|
24
24
|
form_password = request.form.get('password')
|
|
25
25
|
form_password2 = request.form.get('password2')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
from flask import Blueprint, render_template, redirect, request, g, Response, abort
|
|
1
|
+
from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file
|
|
2
2
|
from openwrite.utils.models import Blog, Post, User, View, Like, Page
|
|
3
|
+
from openwrite.utils.tags import get_post_tags, parse_tags_from_posts_syntax, get_posts_by_tags
|
|
3
4
|
from openwrite.utils.helpers import gen_link, sanitize_html, anonymize, get_ip
|
|
4
5
|
from openwrite.utils.valkey_client import valkey_client
|
|
5
6
|
from feedgen.feed import FeedGenerator
|
|
@@ -12,6 +13,26 @@ from datetime import timezone, datetime
|
|
|
12
13
|
|
|
13
14
|
blog_bp = Blueprint("blog", __name__)
|
|
14
15
|
|
|
16
|
+
def process_posts_content(page, blog_id, blog_url, domain):
|
|
17
|
+
posts_matches = re.findall(r'{posts[^}]*}', page.content_raw)
|
|
18
|
+
if not posts_matches:
|
|
19
|
+
return [], page.content_html
|
|
20
|
+
|
|
21
|
+
posts_syntax = posts_matches[0]
|
|
22
|
+
tag_names = parse_tags_from_posts_syntax(posts_syntax)
|
|
23
|
+
posts = get_posts_by_tags(g.db, blog_id, tag_names)
|
|
24
|
+
|
|
25
|
+
posts_html = ""
|
|
26
|
+
for post in posts:
|
|
27
|
+
post_url = f"{blog_url}/{post.link}"
|
|
28
|
+
posts_html += f'<div class="postlist_item"><i class="datetime">{post.date}</i> - <a href="{post_url}">{post.title}</a></div>'
|
|
29
|
+
|
|
30
|
+
processed_content = page.content_html
|
|
31
|
+
for match in posts_matches:
|
|
32
|
+
processed_content = processed_content.replace(match, posts_html)
|
|
33
|
+
|
|
34
|
+
return posts, processed_content
|
|
35
|
+
|
|
15
36
|
def create_activity_object(post, blog, url, full_content=True):
|
|
16
37
|
"""Create ActivityPub object (Article or Note) based on post content length"""
|
|
17
38
|
dt = datetime.strptime(str(post.date), "%Y-%m-%d %H:%M:%S")
|
|
@@ -139,11 +160,9 @@ def show_blog(blog):
|
|
|
139
160
|
pages = g.db.query(Page).filter_by(blog=blog_obj.id).all()
|
|
140
161
|
blog_obj.url = f"/b/{blog_obj.name}"
|
|
141
162
|
homepage = g.db.query(Page).filter(Page.blog == blog_obj.id, Page.url == "").first()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return render_template("blog.html", blog=blog_obj, page=homepage, pages=pages)
|
|
163
|
+
|
|
164
|
+
posts, processed_content = process_posts_content(homepage, blog_obj.id, blog_obj.url, g.main_domain)
|
|
165
|
+
return render_template("blog.html", blog=blog_obj, page=homepage, posts=posts, pages=pages, processed_content=processed_content)
|
|
147
166
|
|
|
148
167
|
@blog_bp.route("/", subdomain="<blog>")
|
|
149
168
|
def show_subblog(blog):
|
|
@@ -167,11 +186,9 @@ def show_subblog(blog):
|
|
|
167
186
|
blog_obj.url = f"https://{blog_obj.name}.{os.getenv('DOMAIN')}"
|
|
168
187
|
pages = g.db.query(Page).filter_by(blog=blog_obj.id).all()
|
|
169
188
|
homepage = g.db.query(Page).filter(Page.blog == blog_obj.id, Page.url == "").first()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return render_template("blog.html", blog=blog_obj, page=homepage, pages=pages)
|
|
189
|
+
|
|
190
|
+
posts, processed_content = process_posts_content(homepage, blog_obj.id, blog_obj.url, g.main_domain)
|
|
191
|
+
return render_template("blog.html", blog=blog_obj, page=homepage, posts=posts, pages=pages, processed_content=processed_content)
|
|
175
192
|
|
|
176
193
|
@blog_bp.route("/b/<blog>/<post>")
|
|
177
194
|
def show_post(blog, post):
|
|
@@ -182,7 +199,7 @@ def show_post(blog, post):
|
|
|
182
199
|
return redirect("/")
|
|
183
200
|
|
|
184
201
|
if post == "rss":
|
|
185
|
-
return
|
|
202
|
+
return serve_rss(blog_obj)
|
|
186
203
|
|
|
187
204
|
if blog_obj.access == "domain":
|
|
188
205
|
return redirect(f"https://{blog_obj.name}.{os.getenv('DOMAIN')}/{post}")
|
|
@@ -196,11 +213,8 @@ def show_post(blog, post):
|
|
|
196
213
|
if not page:
|
|
197
214
|
return redirect("/")
|
|
198
215
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return render_template("blog.html", blog=blog_obj, page=page, posts=posts, pages=pages)
|
|
202
|
-
|
|
203
|
-
return render_template("blog.html", blog=blog_obj, page=page, pages=pages)
|
|
216
|
+
posts, processed_content = process_posts_content(page, blog_obj.id, blog_obj.url, g.main_domain)
|
|
217
|
+
return render_template("blog.html", blog=blog_obj, page=page, posts=posts, pages=pages, processed_content=processed_content)
|
|
204
218
|
|
|
205
219
|
if is_activitypub_request():
|
|
206
220
|
url = f"https://{g.main_domain}/b/{blog}"
|
|
@@ -240,8 +254,10 @@ def show_post(blog, post):
|
|
|
240
254
|
one_post.likes = likes
|
|
241
255
|
one_post.liked = liked
|
|
242
256
|
|
|
257
|
+
post_tags = get_post_tags(g.db, one_post)
|
|
258
|
+
|
|
243
259
|
user = g.db.query(User).filter_by(id=g.user) if g.user else None
|
|
244
|
-
return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages)
|
|
260
|
+
return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags)
|
|
245
261
|
|
|
246
262
|
@blog_bp.route("/<post>", subdomain="<blog>")
|
|
247
263
|
def show_subpost(blog, post):
|
|
@@ -252,7 +268,7 @@ def show_subpost(blog, post):
|
|
|
252
268
|
return redirect(f"https://{os.getenv('DOMAIN')}/")
|
|
253
269
|
|
|
254
270
|
if post == "rss":
|
|
255
|
-
return
|
|
271
|
+
return serve_rss(blog_obj)
|
|
256
272
|
|
|
257
273
|
if blog_obj.access == "path":
|
|
258
274
|
return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog_obj.name}/{post}")
|
|
@@ -267,11 +283,8 @@ def show_subpost(blog, post):
|
|
|
267
283
|
if not page:
|
|
268
284
|
return redirect("/")
|
|
269
285
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return render_template("blog.html", blog=blog_obj, page=page, posts=posts, pages=pages)
|
|
273
|
-
|
|
274
|
-
return render_template("blog.html", blog=blog_obj, page=page, pages=pages)
|
|
286
|
+
posts, processed_content = process_posts_content(page, blog_obj.id, blog_obj.url, g.main_domain)
|
|
287
|
+
return render_template("blog.html", blog=blog_obj, page=page, posts=posts, pages=pages, processed_content=processed_content)
|
|
275
288
|
|
|
276
289
|
if is_activitypub_request():
|
|
277
290
|
url = f"https://{blog}.{g.main_domain}"
|
|
@@ -310,9 +323,11 @@ def show_subpost(blog, post):
|
|
|
310
323
|
|
|
311
324
|
one_post.likes = likes
|
|
312
325
|
one_post.liked = liked
|
|
326
|
+
|
|
327
|
+
post_tags = get_post_tags(g.db, one_post)
|
|
313
328
|
|
|
314
329
|
user = g.db.query(User).filter_by(id=g.user) if g.user else None
|
|
315
|
-
return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages)
|
|
330
|
+
return render_template("post.html", blog=blog_obj, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags)
|
|
316
331
|
|
|
317
332
|
@blog_bp.route("/.well-known/webfinger")
|
|
318
333
|
def webfinger():
|
|
@@ -382,12 +397,8 @@ def single_showpost(post):
|
|
|
382
397
|
if not page:
|
|
383
398
|
return redirect("/")
|
|
384
399
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages)
|
|
389
|
-
|
|
390
|
-
return render_template("blog.html", blog=blog, page=page, pages=pages)
|
|
400
|
+
posts, processed_content = process_posts_content(page, blog.id, blog.url, g.main_domain)
|
|
401
|
+
return render_template("blog.html", blog=blog, page=page, posts=posts, pages=pages, processed_content=processed_content)
|
|
391
402
|
|
|
392
403
|
if is_activitypub_request():
|
|
393
404
|
url = f"https://{g.main_domain}"
|
|
@@ -426,8 +437,10 @@ def single_showpost(post):
|
|
|
426
437
|
one_post.likes = likes
|
|
427
438
|
one_post.liked = liked
|
|
428
439
|
|
|
440
|
+
post_tags = get_post_tags(g.db, one_post)
|
|
441
|
+
|
|
429
442
|
user = g.db.query(User).filter_by(id=g.user) if g.user else None
|
|
430
|
-
return render_template("post.html", blog=blog, post=one_post, user=user, views=v, likes=likes, pages=pages)
|
|
443
|
+
return render_template("post.html", blog=blog, post=one_post, user=user, views=v, likes=likes, pages=pages, post_tags=post_tags)
|
|
431
444
|
|
|
432
445
|
@blog_bp.route("/rss")
|
|
433
446
|
def single_rss():
|
|
@@ -435,7 +448,7 @@ def single_rss():
|
|
|
435
448
|
return redirect("/")
|
|
436
449
|
|
|
437
450
|
blog = g.db.query(Blog).first()
|
|
438
|
-
return
|
|
451
|
+
return serve_rss(blog)
|
|
439
452
|
|
|
440
453
|
@blog_bp.route("/like", methods=["POST"])
|
|
441
454
|
def like():
|
|
@@ -483,29 +496,11 @@ def like():
|
|
|
483
496
|
status = 201
|
|
484
497
|
|
|
485
498
|
return resp, status
|
|
486
|
-
|
|
487
499
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
post_base_url = blog_url
|
|
496
|
-
else:
|
|
497
|
-
blog_url = f"https://{g.main_domain}/b/{blog.name}"
|
|
498
|
-
post_base_url = blog_url
|
|
499
|
-
|
|
500
|
-
fg.link(href=blog_url, rel="alternate")
|
|
501
|
-
fg.description(blog.description_html)
|
|
502
|
-
|
|
503
|
-
for p in posts:
|
|
504
|
-
soup = BeautifulSoup(p.content_html, "html.parser")
|
|
505
|
-
fe = fg.add_entry()
|
|
506
|
-
fe.title(p.title)
|
|
507
|
-
fe.link(href=f"{post_base_url}/{p.link}")
|
|
508
|
-
fe.description(soup.get_text())
|
|
509
|
-
fe.published(p.date.replace(tzinfo=timezone.utc))
|
|
510
|
-
|
|
511
|
-
return Response(fg.rss_str(pretty=True).decode("utf-8"), mimetype="application/rss+xml")
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def serve_rss(blog):
|
|
503
|
+
"""
|
|
504
|
+
Here we just serve static RSS files :)
|
|
505
|
+
"""
|
|
506
|
+
return send_file(f"{g.mainpath}/static/feed/{blog.name}.xml")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from flask import Blueprint, render_template, redirect, request, g, abort, Response
|
|
2
|
-
from openwrite.utils.models import Blog, Post, User, View, Like, Page
|
|
2
|
+
from openwrite.utils.models import Blog, Post, User, View, Like, Page, Settings, Tag, PostTag
|
|
3
3
|
from openwrite.utils.helpers import sanitize_html, gen_link, safe_css, send_activity, is_html, get_themes
|
|
4
|
+
from openwrite.utils.tags import parse_tags_from_input, update_post_tags, get_post_tags
|
|
4
5
|
from openwrite.routes.blog import create_activity_object, create_person_object
|
|
5
6
|
from openwrite.utils.valkey_client import valkey_client
|
|
6
7
|
import requests
|
|
@@ -17,6 +18,7 @@ from user_agents import parse
|
|
|
17
18
|
import re
|
|
18
19
|
import csv
|
|
19
20
|
from io import StringIO
|
|
21
|
+
from feedgen.feed import FeedGenerator
|
|
20
22
|
|
|
21
23
|
dashboard_bp = Blueprint("dashboard", __name__)
|
|
22
24
|
|
|
@@ -277,6 +279,14 @@ def new_post(name):
|
|
|
277
279
|
isdraft=request.form.get('draft', '0')
|
|
278
280
|
)
|
|
279
281
|
g.db.add(post)
|
|
282
|
+
g.db.flush() # Get the post ID
|
|
283
|
+
|
|
284
|
+
# Handle tags
|
|
285
|
+
tags_input = request.form.get('tags', '').strip()
|
|
286
|
+
if tags_input:
|
|
287
|
+
tag_names = parse_tags_from_input(tags_input)
|
|
288
|
+
update_post_tags(g.db, post, tag_names)
|
|
289
|
+
|
|
280
290
|
g.db.commit()
|
|
281
291
|
|
|
282
292
|
if blog.access == "domain":
|
|
@@ -308,7 +318,8 @@ def new_post(name):
|
|
|
308
318
|
valkey_client.queue_federation_activity(activity, blog.id, blog.priv_key, blog_url, f"{actor}/inbox")
|
|
309
319
|
else:
|
|
310
320
|
send_activity(activity, blog.priv_key, blog_url, f"{actor}/inbox")
|
|
311
|
-
|
|
321
|
+
|
|
322
|
+
generate_rss(blog.name)
|
|
312
323
|
return redirect(f"/dashboard/edit/{blog.name}")
|
|
313
324
|
|
|
314
325
|
@dashboard_bp.route("/dashboard/preview", methods=['POST'])
|
|
@@ -342,11 +353,18 @@ def blog_preview():
|
|
|
342
353
|
abort(404)
|
|
343
354
|
|
|
344
355
|
homepage = g.db.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
|
|
356
|
+
|
|
357
|
+
# Process content to handle {posts} patterns with tags
|
|
358
|
+
content = homepage.content_html
|
|
359
|
+
if '{posts}' in content:
|
|
360
|
+
# Replace all {posts} patterns (including {posts:#tags}) with placeholder
|
|
361
|
+
content = re.sub(r'{posts[^}]*}', '{posts}', content)
|
|
362
|
+
|
|
345
363
|
data = {
|
|
346
364
|
'title': request.form.get('title'),
|
|
347
365
|
'css': request.form.get('css'),
|
|
348
366
|
'icon': request.form.get('icon'),
|
|
349
|
-
'content':
|
|
367
|
+
'content': content,
|
|
350
368
|
'theme': request.form.get('theme')
|
|
351
369
|
}
|
|
352
370
|
|
|
@@ -364,7 +382,9 @@ def edit_post(blog, post):
|
|
|
364
382
|
|
|
365
383
|
e_post = g.db.query(Post).filter_by(link=post).first()
|
|
366
384
|
if request.method == "GET":
|
|
367
|
-
|
|
385
|
+
# Get existing tags for the post
|
|
386
|
+
existing_tags = get_post_tags(g.db, e_post) if e_post else []
|
|
387
|
+
return render_template("new_post.html", blog=blog_obj, post=e_post, existing_tags=existing_tags)
|
|
368
388
|
|
|
369
389
|
p = g.db.query(Post).filter_by(link=post, blog=blog_obj.id).first()
|
|
370
390
|
if not p:
|
|
@@ -394,6 +414,12 @@ def edit_post(blog, post):
|
|
|
394
414
|
else:
|
|
395
415
|
p.updated = now
|
|
396
416
|
p.isdraft = request.form.get("draft", "0")
|
|
417
|
+
|
|
418
|
+
# Handle tags
|
|
419
|
+
tags_input = request.form.get('tags', '').strip()
|
|
420
|
+
tag_names = parse_tags_from_input(tags_input)
|
|
421
|
+
update_post_tags(g.db, p, tag_names)
|
|
422
|
+
|
|
397
423
|
g.db.commit()
|
|
398
424
|
|
|
399
425
|
if blog_obj.access == "domain":
|
|
@@ -425,6 +451,7 @@ def edit_post(blog, post):
|
|
|
425
451
|
else:
|
|
426
452
|
send_activity(update_activity, blog_obj.priv_key, blog_url, f"{actor}/inbox")
|
|
427
453
|
|
|
454
|
+
generate_rss(blog)
|
|
428
455
|
return redirect(f"/dashboard/edit/{blog}")
|
|
429
456
|
|
|
430
457
|
@dashboard_bp.route("/dashboard/edit/<blog>/<post>/delete")
|
|
@@ -475,6 +502,7 @@ def delete_post(blog, post):
|
|
|
475
502
|
g.db.delete(p)
|
|
476
503
|
g.db.commit()
|
|
477
504
|
|
|
505
|
+
generate_rss(blog)
|
|
478
506
|
return redirect(f"/dashboard/edit/{blog_obj.name}")
|
|
479
507
|
|
|
480
508
|
|
|
@@ -535,6 +563,7 @@ def migrate():
|
|
|
535
563
|
g.db.add(new_post)
|
|
536
564
|
g.db.commit()
|
|
537
565
|
|
|
566
|
+
generate_rss(blog.name)
|
|
538
567
|
return "ok", 200
|
|
539
568
|
|
|
540
569
|
@dashboard_bp.route("/dashboard/stats/<blog>")
|
|
@@ -675,9 +704,15 @@ def page_preview():
|
|
|
675
704
|
if g.user is None:
|
|
676
705
|
return redirect("/")
|
|
677
706
|
|
|
707
|
+
# Process content to handle {posts} patterns with tags
|
|
708
|
+
content = sanitize_html(request.form.get('content'))
|
|
709
|
+
if '{posts}' in content:
|
|
710
|
+
# Replace all {posts} patterns (including {posts:#tags}) with placeholder
|
|
711
|
+
content = re.sub(r'{posts[^}]*}', '{posts}', content)
|
|
712
|
+
|
|
678
713
|
data = {
|
|
679
714
|
'name': request.form.get('name'),
|
|
680
|
-
'content':
|
|
715
|
+
'content': content,
|
|
681
716
|
'blog_title': request.form.get('blog_title'),
|
|
682
717
|
'blog_name': request.form.get('blog_name'),
|
|
683
718
|
'theme': request.form.get('theme')
|
|
@@ -744,3 +779,39 @@ def export_posts(blog):
|
|
|
744
779
|
'Content-Disposition': f'attachment; filename={blog}_posts.csv'
|
|
745
780
|
}
|
|
746
781
|
)
|
|
782
|
+
|
|
783
|
+
def generate_rss(blog):
|
|
784
|
+
"""
|
|
785
|
+
Function to generate new static RSS feeds
|
|
786
|
+
"""
|
|
787
|
+
b = g.db.query(Blog).filter_by(name=blog).first()
|
|
788
|
+
fg = FeedGenerator()
|
|
789
|
+
fg.ttl(5)
|
|
790
|
+
fg.logo("https://openwrite.io/static/logo.png")
|
|
791
|
+
fg.image(url="https://openwrite.io/static/favicon.png")
|
|
792
|
+
fg.title(b.title)
|
|
793
|
+
|
|
794
|
+
if b.access == "domain":
|
|
795
|
+
blog_url = f"https://{b.name}.{g.main_domain}"
|
|
796
|
+
post_base_url = blog_url
|
|
797
|
+
else:
|
|
798
|
+
blog_url = f"https://{g.main_domain}/b/{b.name}"
|
|
799
|
+
post_base_url = blog_url
|
|
800
|
+
|
|
801
|
+
fg.link(href=blog_url, rel="alternate")
|
|
802
|
+
soup = BeautifulSoup(b.description_html, "html.parser")
|
|
803
|
+
fg.description(soup.get_text())
|
|
804
|
+
|
|
805
|
+
posts = g.db.query(Post).filter(Post.blog == b.id, Post.isdraft == "0").limit(10).all()
|
|
806
|
+
for p in posts:
|
|
807
|
+
soup = BeautifulSoup(p.content_html, "html.parser")
|
|
808
|
+
content = soup.get_text()
|
|
809
|
+
fe = fg.add_entry()
|
|
810
|
+
fe.title(p.title)
|
|
811
|
+
fe.link(href=f"{post_base_url}/{p.link}")
|
|
812
|
+
fe.description(" ".join(content.split(" ")[:30]) + "...", True)
|
|
813
|
+
fe.content(content=p.content_html, type="CDATA")
|
|
814
|
+
fe.published(p.date.replace(tzinfo=timezone.utc))
|
|
815
|
+
|
|
816
|
+
rssfeed = fg.rss_str(pretty=True)
|
|
817
|
+
fg.rss_file(f"{g.mainpath}/static/feed/{b.name}.xml")
|
|
@@ -3,7 +3,9 @@ import os
|
|
|
3
3
|
import json
|
|
4
4
|
import datetime
|
|
5
5
|
import requests
|
|
6
|
-
|
|
6
|
+
import re
|
|
7
|
+
from openwrite.utils.models import Blog, Post, User, View, Like, Page, Settings, Home
|
|
8
|
+
from openwrite.utils.tags import parse_tags_from_posts_syntax, get_posts_by_tags
|
|
7
9
|
from openwrite.routes.blog import create_person_object, is_activitypub_request
|
|
8
10
|
import time
|
|
9
11
|
from openwrite import start_time
|
|
@@ -24,7 +26,6 @@ def index():
|
|
|
24
26
|
elif g.mode == "single":
|
|
25
27
|
blog = g.db.query(Blog).filter_by(name="default").first()
|
|
26
28
|
|
|
27
|
-
# Check for ActivityPub request
|
|
28
29
|
if is_activitypub_request():
|
|
29
30
|
blog_url = f"https://{g.main_domain}"
|
|
30
31
|
person = create_person_object(blog, blog_url)
|
|
@@ -36,11 +37,33 @@ def index():
|
|
|
36
37
|
blog.url = f"http://{g.main_domain}"
|
|
37
38
|
page = g.db.query(Page).filter_by(blog=blog.id, url="").first()
|
|
38
39
|
pages = g.db.query(Page).filter_by(blog=blog.id).all()
|
|
40
|
+
|
|
39
41
|
if '{posts}' in page.content_raw:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
posts_matches = re.findall(r'{posts[^}]*}', page.content_raw)
|
|
43
|
+
if posts_matches:
|
|
44
|
+
posts_syntax = posts_matches[0]
|
|
45
|
+
tag_names = parse_tags_from_posts_syntax(posts_syntax)
|
|
46
|
+
posts = get_posts_by_tags(g.db, blog.id, tag_names)
|
|
47
|
+
|
|
48
|
+
posts_html = ""
|
|
49
|
+
for post in posts:
|
|
50
|
+
if g.mode == "multi":
|
|
51
|
+
post_url = f"{blog.url}/{post.link}"
|
|
52
|
+
else:
|
|
53
|
+
post_url = f"http://{g.main_domain}/p/{post.link}"
|
|
54
|
+
posts_html += f'<div class="postlist_item"><i class="datetime">{post.date}</i> - <a href="{post_url}">{post.title}</a></div>'
|
|
55
|
+
|
|
56
|
+
processed_content = page.content_html
|
|
57
|
+
for match in posts_matches:
|
|
58
|
+
processed_content = processed_content.replace(match, posts_html)
|
|
59
|
+
else:
|
|
60
|
+
posts = g.db.query(Post).filter_by(blog=blog.id, isdraft='0').order_by(desc(Post.id)).all()
|
|
61
|
+
processed_content = page.content_html
|
|
62
|
+
|
|
63
|
+
return render_template('blog.html', blog=blog, page=page, posts=posts, pages=pages, processed_content=processed_content)
|
|
64
|
+
|
|
65
|
+
posts = g.db.query(Post).filter_by(blog=blog.id, isdraft='0').all()
|
|
66
|
+
return render_template('blog.html', blog=blog, posts=posts, page=page, pages=pages, processed_content=page.content_html)
|
|
44
67
|
|
|
45
68
|
@main_bp.route("/set-lang/<lang_code>")
|
|
46
69
|
def set_lang(lang_code):
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>Kierunkowy74</title><link>https://74.openwrite.io</link><description>x</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>Kierunkowy74</title><link>https://74.openwrite.io</link></image><lastBuildDate>Sat, 13 Sep 2025 16:34:24 +0000</lastBuildDate><ttl>5</ttl><item><title>Pierwsze wrażenia na openwrite</title><link>https://74.openwrite.io/pierwsze-wrazenia-na-openwrite</link><description>Ponieważ cały FTdL jest teraz nieczynny (a w związku z tym również i polskie WriteFreely) nastąpiła pora sprawdzić świeżo powstałe openwrite.
|
|
3
|
+
Muszę zmartwić tych, którzy chcieliby zaobserwować blog przez np....</description><content:encoded><![CDATA[<p><del>Ponieważ cały FTdL jest teraz nieczynny (a w związku z tym również i polskie WriteFreely)</del> nastąpiła pora sprawdzić świeżo powstałe openwrite. </p>
|
|
4
|
+
<p><del>Muszę zmartwić tych, którzy chcieliby zaobserwować blog przez np. Mastodona, bo <strong>federacji na tę chwilę jeszcze nie ma</strong></del> federacja jest, zaobserwujesz mnie z np. Mastodona jako @74@openwrite.io . Tak, są <em>lajki</em> - ale <del>skoro openwrite nie jest jeszcze fedi, to</del> ten wpis można zalajkować… z ulicy.</p>
|
|
5
|
+
<p>Różnic z WriteFreely jest więcej. openwrite nie jest tak minimalistyczny jak jego rywal i wiele rzecz pozwala po prostu wyklikać. <img src="https://openwrite.b-cdn.net/cf1167fc770ab887cdd2f26a4466ed5c.png" alt="Interfejs openwrite"></p>
|
|
6
|
+
<p>Są przyciski do wstawiania formatowania Markdown. Można normalnie zamieścić obrazek, który będzie znajdował się na openwrite. (czyli nie musisz jak na WriteFreely korzystać z serwisów zewnętrznych do podlinkowania obrazka) Jest możliwość by dołączyć informacje o autorze (nazwa użytkownika). Jest podgląd.</p>
|
|
7
|
+
<p>Można sobie wyklikać motyw lub zrobić własny CSS. Są dostępne statystyki, które są bardziej rozbudowane niż na WriteFreely. Można zobaczyć sobie liczbę wyświetleń na wykresie, w tym według przeglądarki i systemu operacyjnego.</p>
|
|
8
|
+
<h3>Czego brakuje?</h3>
|
|
9
|
+
<p>Na pewno federacji, chociaż (jak wynika z opisu na GitHubie) powinna być. Szkiców i autozapisu. Wpisów publikowanych według harmonogramu (ale wypadałoby wspomnieć, że na WriteFreely takie wpisy nie federują i ślamazarny rozwój WF nie daje wielkich nadziei na zmianę). W końcu zaś biuletynów, tudzież subskrypcji mailem.</p>
|
|
10
|
+
<p>A, i przy okazji zauważyłem jedną usterkę. Dodanie przyciskiem np. <strong>pogrubienia</strong>, <em>kursywy</em> lub <u>przekreślenia</u> dodaje znaczniki początkowy i końcowy, ale umieszcza kursor po nich, a nie pomiędzy nimi. Edytor Markdown w np. PieFedzie robi to lepiej. (edycja: druga - da się dodać wpis bez tytułu. W Odkrywaj wpis będzie bez tytułu i nie będzie dało się weń kliknąć xd)</p>
|
|
11
|
+
<p>Serwis na pewno jest ciekawy. No i chyba tyle.</p>
|
|
12
|
+
<h1>Czy ten wpis jest widoczny na Gemini?</h1>]]></content:encoded><pubDate>Fri, 18 Jul 2025 11:07:52 +0000</pubDate></item></channel></rss>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>A</title><link>https://openwrite.io/b/a</link><description>Hello there! 👋You can edit your blog description in dashboard</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>A</title><link>https://openwrite.io/b/a</link></image><lastBuildDate>Sat, 13 Sep 2025 16:34:24 +0000</lastBuildDate><ttl>5</ttl></channel></rss>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>4nim3 typ3 sh1ft</title><link>https://anim3sh1t.openwrite.io</link><description>x</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>4nim3 typ3 sh1ft</title><link>https://anim3sh1t.openwrite.io</link></image><lastBuildDate>Sat, 13 Sep 2025 16:34:24 +0000</lastBuildDate><ttl>5</ttl><item><title>An1m3 sH1t</title><link>https://anim3sh1t.openwrite.io/an1m3-sh1t</link><description>
|
|
3
|
+
M3ga like click this all links:
|
|
4
|
+
https://ptoszek.pl/
|
|
5
|
+
https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
6
|
+
https://sekurwaposzukaj.pl/
|
|
7
|
+
<33 ...</description><content:encoded><![CDATA[<p><img src="https://openwrite.b-cdn.net/01a494ce9056e43757ca5161ba8a496c.png" alt="alt text">
|
|
8
|
+
M3ga like click this all links:
|
|
9
|
+
https://ptoszek.pl/
|
|
10
|
+
https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
11
|
+
https://sekurwaposzukaj.pl/
|
|
12
|
+
<33 </p>]]></content:encoded><pubDate>Thu, 24 Jul 2025 13:24:35 +0000</pubDate></item></channel></rss>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>B</title><link>https://openwrite.io/b/b</link><description>Hello there! 👋You can edit your blog description in dashboard</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><image><url>https://openwrite.io/static/favicon.png</url><title>B</title><link>https://openwrite.io/b/b</link></image><lastBuildDate>Sat, 13 Sep 2025 16:34:24 +0000</lastBuildDate><ttl>5</ttl></channel></rss>
|