25 Commits
1.1 ... main

Author SHA1 Message Date
Michachatz
b376311cff Maintain Updates 2025-11-24 16:35:43 +01:00
Michachatz
72c9ff6601 Maintain Updates 2025-11-24 16:32:37 +01:00
Michachatz
d8ba367b0d Update Fix Tranlations 2025-11-24 16:29:04 +01:00
Michachatz
a5766c510d Added the Official German Translation 2025-11-24 16:21:54 +01:00
Michachatz
b3e9fd9819 Update messages.po 2025-11-24 16:19:49 +01:00
Michachatz
7811396791 Delete .github/workflows directory 2025-11-23 15:42:19 +01:00
Michachatz
1ac918496d Create django.yml 2025-11-23 15:41:11 +01:00
Michachatz
803d3ca360 Update base.html 2025-11-23 15:30:07 +01:00
Michatec
c7b18d76ef API Events Moved 2025-11-23 13:02:56 +01:00
Michatec
1c05248829 Notifications Widget Added 2025-11-23 12:58:22 +01:00
Michachatz
3332a9ca7c Update README.md 2025-11-23 11:23:16 +01:00
Michatec
df8ee7703d Auto Adjust Tab Admin 2025-11-23 11:14:15 +01:00
Michatec
0e9024949b Fix comments Gravatar not shown 2025-11-23 10:53:37 +01:00
Michachatz
2cff51e779 Update README.md 2025-11-23 00:19:20 +01:00
af8b69989c Merge branch 'main' of https://github.com/Michatec/MiniFaceBook 2025-11-23 00:14:35 +01:00
1fd5cddd3c Fix Username not shown 2025-11-23 00:14:31 +01:00
Michachatz
1429e50b2b Update requirments.txt 2025-11-22 23:58:27 +01:00
2ef98ce897 - Added Gravatar Integration
- Realtime Notify & Notify API
- Some bugs fixed
2025-11-22 23:49:00 +01:00
858c98412f gitignore update 2025-11-08 21:24:13 +01:00
77e46d03c7 Merge branch 'main' of https://github.com/Michatec/MiniFaceBook 2025-11-08 21:21:44 +01:00
986a1a2a25 Secret Page removed. 2025-11-08 21:17:47 +01:00
Michachatz
0e03aa9c33 Update dependabot.yml 2025-09-28 15:28:21 +02:00
Michachatz
1b5976a190 Create dependabot.yml 2025-09-28 15:25:13 +02:00
Michachatz
0fd32dc2b8 Update main.py 2025-09-27 21:46:08 +02:00
Michachatz
1a94d52d61 Update requirments.txt 2025-09-27 21:44:42 +02:00
25 changed files with 853 additions and 986 deletions

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

1
.gitignore vendored
View File

@@ -8,7 +8,6 @@ migrations
*.log *.log
*.db *.db
*.env *.env
*.DS_Store
.vscode .vscode
routes/__pycache__ routes/__pycache__
tools tools

View File

@@ -7,10 +7,12 @@ MiniFacebook is a minimalist social network built with [Flask](https://flask.pal
- Share posts, images, videos, and documents - Share posts, images, videos, and documents
- Friend requests and friends list - Friend requests and friends list
- Activity notifications - Activity notifications
- Realtime notifications
- Shop for premium features (e.g., gold frames, extra uploads) - Shop for premium features (e.g., gold frames, extra uploads)
- Admin panel with user management - Admin panel with user management
- Multilingual (German/English) - Multilingual (German/English)
- Dark and light mode - Dark and light mode
- Gravatar Addon
- Discord login and linking - Discord login and linking
- Support ticket system - Support ticket system
- Password reset via email - Password reset via email
@@ -38,11 +40,11 @@ MiniFacebook is a minimalist social network built with [Flask](https://flask.pal
``` ```
4. **Optional:** 4. **Optional:**
Go to routes/example oauth.py Go to routes/example_oauth.py
Paste Your Client ID and Client Secret from the Discord Dev portal. Paste Your Client ID and Client Secret from the Discord Dev portal.
And rename it oauth.py And rename it to oauth.py
## Help to translate ## Help to translate

20
main.py
View File

@@ -15,7 +15,7 @@ from routes.user import user_bp
from routes.friends import friends_bp from routes.friends import friends_bp
from routes.notifications import noti_bp from routes.notifications import noti_bp
from routes.credits import credits_bp from routes.credits import credits_bp
from models import db, User, Reward, Event, UserShopItem, ShopItem, SHOPITEM_ID_PREMIUM, SHOPITEM_ID_GOLDRAHMEN, SHOPITEM_ID_EXTRA_TYPES, SHOPITEM_ID_EXTRA_UPLOAD from models import db, User, Reward, UserShopItem, ShopItem, SHOPITEM_ID_PREMIUM, SHOPITEM_ID_GOLDRAHMEN, SHOPITEM_ID_EXTRA_TYPES, SHOPITEM_ID_EXTRA_UPLOAD
try: try:
from routes.oauth import oauth from routes.oauth import oauth
except ImportError: except ImportError:
@@ -110,6 +110,7 @@ def needs_admin_setup():
def inject_discord_available(): def inject_discord_available():
try: try:
from routes.oauth import discord from routes.oauth import discord
return dict(discord=discord)
except ImportError: except ImportError:
return dict(discord=None) return dict(discord=None)
@@ -184,18 +185,6 @@ def setup():
return redirect(url_for('log.login')) return redirect(url_for('log.login'))
return render_template('setup.html') return render_template('setup.html')
@app.route('/api/events')
@login_required
def api_events():
if not current_user.is_admin:
abort(403)
events = db.session.query(Event).order_by(Event.timestamp.desc()).limit(20).all()
return jsonify([
{"timestamp": e.timestamp.strftime('%Y-%m-%d %H:%M'), "message": e.message}
for e in events
])
@app.route('/shop', methods=['GET', 'POST']) @app.route('/shop', methods=['GET', 'POST'])
@login_required @login_required
def shop(): def shop():
@@ -228,11 +217,6 @@ def not_found(error):
return redirect(url_for('post.feed')) return redirect(url_for('post.feed'))
return render_template('index.html'), 200 return render_template('index.html'), 200
@app.route('/secret')
@login_required
def secret():
return render_template('secret.html')
if __name__ == '__main__': if __name__ == '__main__':
try: try:
serve(app, host="0.0.0.0", port=80, threads=12) serve(app, host="0.0.0.0", port=80, threads=12)

View File

@@ -1,6 +1,7 @@
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin from flask_login import UserMixin
from datetime import datetime from datetime import datetime
from hashlib import md5
db = SQLAlchemy() db = SQLAlchemy()
@@ -39,6 +40,10 @@ class User(UserMixin, db.Model):
def reward_points(self): def reward_points(self):
return sum(r.points for r in self.rewards) return sum(r.points for r in self.rewards)
def avatar(self):
digest = md5(self.email.lower().encode('utf-8')).hexdigest()
return f'https://www.gravatar.com/avatar/{digest}?d=identicon&s=120'
class Friendship(db.Model): class Friendship(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
requester_id = db.Column(db.Integer, db.ForeignKey('user.id')) requester_id = db.Column(db.Integer, db.ForeignKey('user.id'))

View File

@@ -6,3 +6,4 @@ flask_babel
waitress waitress
authlib authlib
sqlalchemy sqlalchemy
requests

View File

@@ -113,7 +113,7 @@ def admin_delete_post(post_id):
db.session.add(notification) db.session.add(notification)
db.session.commit() db.session.commit()
flash(_('Post and associated files deleted.'), 'success') flash(_('Post and associated files deleted.'), 'success')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#posts')
@admin_bp.route('/delete_user/<int:user_id>', methods=['POST']) @admin_bp.route('/delete_user/<int:user_id>', methods=['POST'])
@login_required @login_required
@@ -121,7 +121,7 @@ def admin_delete_user(user_id):
user = db.session.get(User, user_id) user = db.session.get(User, user_id)
if user.is_owner: if user.is_owner:
flash(_('Cannot delete the owner account.'), 'danger') flash(_('Cannot delete the owner account.'), 'danger')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#users')
if user and not user.is_admin: if user and not user.is_admin:
event = Event(message=f"Admin {current_user.username} hat {user.username} gelöscht.") event = Event(message=f"Admin {current_user.username} hat {user.username} gelöscht.")
db.session.add(event) db.session.add(event)
@@ -158,7 +158,7 @@ def admin_delete_user(user_id):
flash(_('User deleted.'), 'success') flash(_('User deleted.'), 'success')
else: else:
flash(_('Cannot delete admin or user not found.'), 'danger') flash(_('Cannot delete admin or user not found.'), 'danger')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#users')
@admin_bp.route('/delete_pic/<int:user_id>', methods=['POST']) @admin_bp.route('/delete_pic/<int:user_id>', methods=['POST'])
@login_required @login_required
@@ -174,7 +174,7 @@ def admin_delete_pic(user_id):
user.profile_pic = "default.png" user.profile_pic = "default.png"
db.session.commit() db.session.commit()
flash(_(f'Profile picture of {user.username} deleted.'), 'success') flash(_(f'Profile picture of {user.username} deleted.'), 'success')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#users')
@admin_bp.route('/delete_all_notifications', methods=['POST']) @admin_bp.route('/delete_all_notifications', methods=['POST'])
@login_required @login_required
@@ -184,7 +184,7 @@ def admin_delete_all_notifications():
db.session.query(Notification).delete() db.session.query(Notification).delete()
db.session.commit() db.session.commit()
flash(_('All notifications have been deleted.'), 'success') flash(_('All notifications have been deleted.'), 'success')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#notifications')
@admin_bp.route('/delete_all_events', methods=['POST']) @admin_bp.route('/delete_all_events', methods=['POST'])
@login_required @login_required
@@ -194,7 +194,7 @@ def admin_delete_all_events():
db.session.query(Event).delete() db.session.query(Event).delete()
db.session.commit() db.session.commit()
flash(_('All events have been deleted.'), 'success') flash(_('All events have been deleted.'), 'success')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#events')
@admin_bp.route('/delete_upload/<int:upload_id>', methods=['POST']) @admin_bp.route('/delete_upload/<int:upload_id>', methods=['POST'])
@login_required @login_required
@@ -212,7 +212,7 @@ def admin_delete_upload(upload_id):
db.session.delete(upload) db.session.delete(upload)
db.session.commit() db.session.commit()
flash(_('Upload deleted.'), 'success') flash(_('Upload deleted.'), 'success')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#uploads')
@admin_bp.route('/delete_all_uploads', methods=['POST']) @admin_bp.route('/delete_all_uploads', methods=['POST'])
@login_required @login_required
@@ -230,7 +230,7 @@ def admin_delete_all_uploads():
except Exception: except Exception:
pass pass
flash(_('All uploads have been deleted.'), 'success') flash(_('All uploads have been deleted.'), 'success')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#uploads')
@admin_bp.route('/admin/points/<int:user_id>', methods=['POST']) @admin_bp.route('/admin/points/<int:user_id>', methods=['POST'])
@login_required @login_required
@@ -242,7 +242,7 @@ def admin_points(user_id):
points = int(request.form['points']) points = int(request.form['points'])
except: except:
flash(_('No Points entered!')) flash(_('No Points entered!'))
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#shop-orders')
cuser = db.session.get(User, current_user.id) cuser = db.session.get(User, current_user.id)
if not cuser.is_owner: if not cuser.is_owner:
abort(403) abort(403)
@@ -258,7 +258,7 @@ def admin_points(user_id):
flash(_('Points removed!'), 'success') flash(_('Points removed!'), 'success')
else: else:
flash(_("The user has not enough points to take!"), 'danger') flash(_("The user has not enough points to take!"), 'danger')
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#shop-orders')
@admin_bp.route('/make_admin/<int:user_id>', methods=['POST']) @admin_bp.route('/make_admin/<int:user_id>', methods=['POST'])
@login_required @login_required
@@ -270,7 +270,7 @@ def make_admin(user_id):
user.is_admin = True user.is_admin = True
db.session.commit() db.session.commit()
flash(_(f"{user.username} is now an admin."), "success") flash(_(f"{user.username} is now an admin."), "success")
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#users')
@admin_bp.route('/remove_admin/<int:user_id>', methods=['POST']) @admin_bp.route('/remove_admin/<int:user_id>', methods=['POST'])
@login_required @login_required
@@ -284,7 +284,7 @@ def remove_admin(user_id):
flash(_(f"Admin rights of {user.username} removed."), "info") flash(_(f"Admin rights of {user.username} removed."), "info")
else: else:
flash(_("Owner cannot be removed!"), "danger") flash(_("Owner cannot be removed!"), "danger")
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin')+'#users')
@admin_bp.route('/wipe_server', methods=['POST']) @admin_bp.route('/wipe_server', methods=['POST'])
@login_required @login_required

View File

@@ -1,6 +1,6 @@
from flask import Blueprint, redirect, url_for, flash, render_template from flask import Blueprint, jsonify, redirect, request, url_for, flash, render_template, abort
from flask_login import login_required, current_user from flask_login import login_required, current_user
from models import db, Notification from models import User, db, Notification, Event
from flask_babel import gettext as _ from flask_babel import gettext as _
from datetime import datetime, timedelta from datetime import datetime, timedelta
@@ -31,3 +31,32 @@ def notifications():
db.session.commit() db.session.commit()
notifications = db.session.query(Notification).filter_by(user_id=current_user.id).order_by(Notification.created_at.desc()).all() notifications = db.session.query(Notification).filter_by(user_id=current_user.id).order_by(Notification.created_at.desc()).all()
return render_template('notifications.html', notifications=notifications) return render_template('notifications.html', notifications=notifications)
@noti_bp.route('/api/notifications')
@login_required
def notifications_api():
expire_time = datetime.now() - timedelta(days=3)
db.session.query(Notification).filter(Notification.created_at < expire_time).delete()
db.session.commit()
notifications = db.session.query(Notification).filter_by(user_id=current_user.id).order_by(Notification.created_at.desc()).all()
return jsonify(
[
{
'name': User.query.get(n.user_id).username,
'data': n.message,
'created_at': n.created_at
} for n in notifications
]
)
@noti_bp.route('/api/events')
@login_required
def api_events():
if not current_user.is_admin:
abort(403)
events = db.session.query(Event).order_by(Event.timestamp.desc()).limit(20).all()
return jsonify([
{"timestamp": e.timestamp.strftime('%Y-%m-%d %H:%M'), "message": e.message}
for e in events
])

View File

@@ -102,3 +102,12 @@ def delete_account():
def users(): def users():
all_users = db.session.query(User).filter(User.id != current_user.id).all() all_users = db.session.query(User).filter(User.id != current_user.id).all()
return render_template('users.html', users=all_users) return render_template('users.html', users=all_users)
@user_bp.route('/use_gravatar', methods=['POST'])
@login_required
def gravatar():
avatar_url = current_user.avatar()
current_user.profile_pic = avatar_url
db.session.commit()
flash(_('Added Gravatar profile picture.'), 'success')
return redirect(url_for('profil.profile'))

View File

@@ -12,5 +12,8 @@ function reloadEvents() {
}); });
} }
setInterval(reloadEvents, 10000); $(function() {
window.onload = reloadEvents; setInterval(function() {
reloadEvents();
}, 1000);
});

View File

@@ -1,5 +0,0 @@
function reload_feed() {
setTimeout(function() {
window.location.reload();
}, 120000);
}

View File

@@ -51,13 +51,26 @@
<tbody> <tbody>
{% for user in users %} {% for user in users %}
<tr> <tr>
<td>{% if user.profile_pic and user.profile_pic != 'default.png' %}<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1">{% endif %} {{ user.username }}</td> <td>
{% if user.profile_pic and user.profile_pic != 'default.png' %}
{% if user.profile_pic.startswith('http') %}
<img src="{{ user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %}
{% endif %}
{{ user.username }}
</td>
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
<td>{% if user.is_admin %}<i class="bi bi-check-lg text-success"></i>{% endif %}</td> <td>{% if user.is_admin %}<i class="bi bi-check-lg text-success"></i>{% endif %}</td>
<td>{% if user.is_owner %}<i class="bi bi-star-fill text-warning"></i>{% endif %}</td> <td>{% if user.is_owner %}<i class="bi bi-star-fill text-warning"></i>{% endif %}</td>
<td> <td>
{% if user.profile_pic and user.profile_pic != 'default.png' %} {% if user.profile_pic and user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1"> {% if user.profile_pic.startswith('http') %}
<img src="{{ user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %}
<form action="{{ url_for('admin.admin_delete_pic', user_id=user.id) }}" method="post" style="display:inline;"> <form action="{{ url_for('admin.admin_delete_pic', user_id=user.id) }}" method="post" style="display:inline;">
<button class="btn btn-danger btn-sm" title="{{ _('Delete Picture') }}"><i class="bi bi-image"></i></button> <button class="btn btn-danger btn-sm" title="{{ _('Delete Picture') }}"><i class="bi bi-image"></i></button>
</form> </form>
@@ -103,10 +116,13 @@
<tr> <tr>
<td> <td>
{% if post.user.profile_pic and post.user.profile_pic != 'default.png' %} {% if post.user.profile_pic and post.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" alt="{{ post.user.username }}" class="rounded me-1" width="32">{{ post.user.username }} {% if post.user.profile_pic.startswith('http') %}
<img src="{{ post.user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ post.user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %} {% endif %}
{% endif %}
{{ post.user.username }}
</td> </td>
<td>{{ post.content|truncate(50) }}</td> <td>{{ post.content|truncate(50) }}</td>
<td> <td>
@@ -144,17 +160,23 @@
<tr> <tr>
<td> <td>
{% if f.requester.profile_pic and f.requester.profile_pic != 'default.png' %} {% if f.requester.profile_pic and f.requester.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ f.requester.profile_pic) }}" alt="{{ f.requester.username }}" class="rounded me-1" width="32">{{ f.requester.username }} {% if f.requester.profile_pic.startswith('http') %}
<img src="{{ f.requester.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ f.requester.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ f.requester.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %} {% endif %}
{% endif %}
{{ f.requester.username }}
</td> </td>
<td> <td>
{% if f.receiver.profile_pic and f.receiver.profile_pic != 'default.png' %} {% if f.receiver.profile_pic and f.receiver.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ f.receiver.profile_pic) }}" alt="{{ f.receiver.username }}" class="rounded me-1" width="32">{{ f.receiver.username }} {% if f.receiver.profile_pic.startswith('http') %}
<img src="{{ f.receiver.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ f.receiver.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ f.receiver.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %} {% endif %}
{% endif %}
{{ f.receiver.username }}
</td> </td>
<td> <td>
{% if f.status == 'accepted' %} {% if f.status == 'accepted' %}
@@ -189,10 +211,13 @@
<tr> <tr>
<td> <td>
{% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %} {% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32">{{ comment.user.username }} {% if comment.user.profile_pic.startswith('http') %}
<img src="{{ comment.user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ comment.user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %} {% endif %}
{% endif %}
{{ comment.user.username }}
</td> </td>
<td>{{ comment.post.id }}</td> <td>{{ comment.post.id }}</td>
<td>{{ comment.content|truncate(50) }}</td> <td>{{ comment.content|truncate(50) }}</td>
@@ -307,10 +332,13 @@
<tr> <tr>
<td> <td>
{% if usi.user.profile_pic and usi.user.profile_pic != 'default.png' %} {% if usi.user.profile_pic and usi.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ usi.user.profile_pic) }}" class="rounded me-2" width="32">{{ usi.user.username }} {% if usi.user.profile_pic.startswith('http') %}
<img src="{{ usi.user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ usi.user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ usi.user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %} {% endif %}
{% endif %}
{{ usi.user.username }}
</td> </td>
<td>{{ usi.item.name }}</td> <td>{{ usi.item.name }}</td>
</tr> </tr>
@@ -335,10 +363,13 @@
<tr> <tr>
<td> <td>
{% if user.profile_pic and user.profile_pic != 'default.png' %} {% if user.profile_pic and user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="rounded me-2" width="32">{{ user.username }} {% if user.profile_pic.startswith('http') %}
<img src="{{ user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %} {% endif %}
{% endif %}
{{ user.username }}
</td> </td>
<td>{{ user.reward_points() }}</td> <td>{{ user.reward_points() }}</td>
<td> <td>
@@ -358,7 +389,7 @@
</table> </table>
</div> </div>
</div> </div>
<script src="{{ url_for('static', filename='js/adstop.js') }}"></script> <script src="{{ url_for('static', filename='js/adtab.js') }}"></script>
<script> <script>
var triggerTabList = [].slice.call(document.querySelectorAll('#adminTab button')) var triggerTabList = [].slice.call(document.querySelectorAll('#adminTab button'))
triggerTabList.forEach(function (triggerEl) { triggerTabList.forEach(function (triggerEl) {

View File

@@ -8,6 +8,7 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename='js/theme.js') }}"></script> <script src="{{ url_for('static', filename='js/theme.js') }}"></script>
<script src="{{ url_for('static', filename='js/translate.js') }}"></script> <script src="{{ url_for('static', filename='js/translate.js') }}"></script>
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}"> <link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
@@ -43,7 +44,7 @@
<li class="nav-item"><a class="nav-link" href="{{ url_for('user.users') }}"><i class="bi bi-people me-1"></i>{{ _('Users') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('user.users') }}"><i class="bi bi-people me-1"></i>{{ _('Users') }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('friend.friends') }}"><i class="bi bi-person-check me-1"></i>{{ _('Friends') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('friend.friends') }}"><i class="bi bi-person-check me-1"></i>{{ _('Friends') }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('profil.my_posts') }}"><i class="bi bi-file-earmark-text me-1"></i>{{ _('My Posts') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('profil.my_posts') }}"><i class="bi bi-file-earmark-text me-1"></i>{{ _('My Posts') }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('notif.notifications') }}"><i class="bi bi-bell me-1"></i>{{ _('Notifications') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('notif.notifications') }}"><span class="badge bg-secondary" id="message_count">0</span><i class="bi bi-bell me-1"></i>{{ _('Notifications') }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('profil.profile') }}"><i class="bi bi-person-circle me-1"></i>{{ _('Profile') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('profil.profile') }}"><i class="bi bi-person-circle me-1"></i>{{ _('Profile') }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('log.logout') }}"><i class="bi bi-box-arrow-right me-1"></i>{{ _('Logout') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('log.logout') }}"><i class="bi bi-box-arrow-right me-1"></i>{{ _('Logout') }}</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('support.support') }}"><i class="bi bi-question-circle me-1"></i>{{ _('Support') }}</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('support.support') }}"><i class="bi bi-question-circle me-1"></i>{{ _('Support') }}</a></li>
@@ -80,12 +81,20 @@
{% endwith %} {% endwith %}
<div class="d-flex align-items-center mb-3"> <div class="d-flex align-items-center mb-3">
{% if user.profile_pic and user.profile_pic != 'default.png' %} {% if user.profile_pic and user.profile_pic != 'default.png' %}
{% if user.profile_pic.startswith('http') %}
{% if SHOPITEM_ID_GOLDRAHMEN in user.shop_items | map(attribute='item_id') | list %}
<img src="{{ user.profile_pic }}" class="profile-pic me-2" style="border-color: gold;" alt="Profile Picture">
{% else %}
<img src="{{ user.profile_pic }}" class="profile-pic me-2" alt="Profile Picture">
{% endif %}
{% else %}
{% if SHOPITEM_ID_GOLDRAHMEN in user.shop_items | map(attribute='item_id') | list %} {% if SHOPITEM_ID_GOLDRAHMEN in user.shop_items | map(attribute='item_id') | list %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;"> <img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
{% else %} {% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="profile-pic me-2"> <img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="profile-pic me-2">
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<h1>{{ _('Welcome, %(username)s!', username=user.username) }}</h1> <h1>{{ _('Welcome, %(username)s!', username=user.username) }}</h1>
{% endif %} {% endif %}
@@ -93,9 +102,15 @@
{% if user.is_admin %} {% if user.is_admin %}
<p class="text-success">{{ _('You are logged in as an admin.') }}</p> <p class="text-success">{{ _('You are logged in as an admin.') }}</p>
{% endif %} {% endif %}
{% if user.is_authenticated %}
<span class="badge bg-secondary" id="check_notify">{{ _('You dont have any notifications.') }}</span>
{% endif %}
{% if SHOPITEM_ID_PREMIUM in user.shop_items | map(attribute='item_id') | list %} {% if SHOPITEM_ID_PREMIUM in user.shop_items | map(attribute='item_id') | list %}
<span class="badge bg-warning text-dark"><i class="bi bi-star"></i> Premium</span> <span class="badge bg-warning text-dark"><i class="bi bi-star"></i> Premium</span>
{% endif %} {% endif %}
{% if user.is_authenticated %}
<hr>
{% endif %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<footer class="footer mt-auto py-3"> <footer class="footer mt-auto py-3">
@@ -109,5 +124,24 @@
</span> </span>
</div> </div>
</footer> </footer>
<script>
function set_message_count(n) {
$('#message_count').text(n);
$('#check_notify').text('{{ _("You have ") }}' + n + '{{ _(" new notification/s") }}');
}
{% if current_user.is_authenticated %}
$(function() {
setInterval(function() {
$.ajax("{{ url_for('notif.notifications_api') }}").done(
function(notifications) {
for (var i = 0; i < notifications.length; i++) {
set_message_count(i + 1);
}
}
);
}, 1000);
});
{% endif %}
</script>
</body> </body>
</html> </html>

View File

@@ -30,11 +30,19 @@
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-center mb-2"> <div class="d-flex align-items-center mb-2">
{% if post.user.profile_pic and post.user.profile_pic != 'default.png' %} {% if post.user.profile_pic and post.user.profile_pic != 'default.png' %}
{% if post.user.profile_pic.startswith('http') %}
{% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %}
<img src="{{ post.user.profile_pic }}" class="profile-pic me-2" style="border-color: gold;" alt="Profile Picture">
{% else %}
<img src="{{ post.user.profile_pic }}" class="profile-pic me-2" alt="Profile Picture">
{% endif %}
{% else %}
{% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %} {% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %}
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;"> <img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
{% else %} {% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2"> <img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2">
{% endif %} {% endif %}
{% endif %}
{% else %} {% else %}
<i class="bi bi-person-circle me-2"></i> <i class="bi bi-person-circle me-2"></i>
{% endif %} {% endif %}
@@ -97,9 +105,13 @@
{% for comment in post.comments %} {% for comment in post.comments %}
<div class="mt-1"> <div class="mt-1">
{% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %} {% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32"><b>{{ comment.user.username }}</b>: {% if comment.user.profile_pic.startswith('http') %}
<img src="{{ comment.user.profile_pic }}" class="rounded me-2" width="32" alt="Profile Picture"><b>{{ comment.user.username }}</b>:
{% else %} {% else %}
<i class="bi bi-person me-1"></i><b>{{ comment.user.username }}</b>: <img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32" alt="Profile Picture"><b>{{ comment.user.username }}</b>:
{% endif %}
{% else %}
<i class="bi bi-person-circle me-2"></i><b>{{ comment.user.username }}</b>:
{% endif %} {% endif %}
{{ comment.content }} {{ comment.content }}
{% if comment.user_id == current_user.id %} {% if comment.user_id == current_user.id %}
@@ -118,5 +130,4 @@
{{ _('No posts available.') }} {{ _('No posts available.') }}
</div> </div>
{% endif %} {% endif %}
<script src="{{ url_for('static', filename='js/feed.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -7,10 +7,23 @@
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
<span> <span>
{% if friend.profile_pic and friend.profile_pic != 'default.png' %} {% if friend.profile_pic and friend.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' + friend.profile_pic) }}" alt="{{ friend.username }}" class="profile-pic me-2">{{ friend.username }} {% if friend.profile_pic.startswith('http') %}
{% if SHOPITEM_ID_GOLDRAHMEN in friend.shop_items | map(attribute='item_id') | list %}
<img src="{{ friend.profile_pic }}" class="profile-pic me-2" style="border-color: gold;" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ friend.username }} <img src="{{ friend.profile_pic }}" class="profile-pic me-2" alt="Profile Picture">
{% endif %} {% endif %}
{% else %}
{% if SHOPITEM_ID_GOLDRAHMEN in friend.shop_items | map(attribute='item_id') | list %}
<img src="{{ url_for('static', filename='profile_pics/' ~ friend.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
{% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ friend.profile_pic) }}" class="profile-pic me-2">
{% endif %}
{% endif %}
{% else %}
<i class="bi bi-person-circle me-2"></i>
{% endif %}
{{ friend.username }}
</span> </span>
<form action="{{ url_for('friend.remove_friend', user_id=friend.id) }}" method="post" style="display:inline;"> <form action="{{ url_for('friend.remove_friend', user_id=friend.id) }}" method="post" style="display:inline;">
<button class="btn btn-warning btn-sm"><i class="bi bi-person-dash"></i> {{ _('Remove Friend') }}</button> <button class="btn btn-warning btn-sm"><i class="bi bi-person-dash"></i> {{ _('Remove Friend') }}</button>
@@ -26,10 +39,23 @@
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
<span> <span>
{% if req.requester.profile_pic and req.requester.profile_pic != 'default.png' %} {% if req.requester.profile_pic and req.requester.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' + req.requester.profile_pic) }}" alt="{{ req.requester.username }}" class="profile-pic me-2">{{ req.requester.username }} {% if req.requester.profile_pic.startswith('http') %}
{% if SHOPITEM_ID_GOLDRAHMEN in req.requester.shop_items | map(attribute='item_id') | list %}
<img src="{{ req.requester.profile_pic }}" class="profile-pic me-2" style="border-color: gold;" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ req.requester.username }} <img src="{{ req.requester.profile_pic }}" class="profile-pic me-2" alt="Profile Picture">
{% endif %} {% endif %}
{% else %}
{% if SHOPITEM_ID_GOLDRAHMEN in req.requester.shop_items | map(attribute='item_id') | list %}
<img src="{{ url_for('static', filename='profile_pics/' ~ req.requester.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
{% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ req.requester.profile_pic) }}" class="profile-pic me-2">
{% endif %}
{% endif %}
{% else %}
<i class="bi bi-person-circle me-2"></i>
{% endif %}
{{ req.requester.username }}
</span> </span>
<div> <div>
<form action="{{ url_for('friend.accept_friend', friendship_id=req.id) }}" method="post" style="display:inline;"> <form action="{{ url_for('friend.accept_friend', friendship_id=req.id) }}" method="post" style="display:inline;">

View File

@@ -7,13 +7,21 @@
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-center mb-2"> <div class="d-flex align-items-center mb-2">
{% if post.user.profile_pic and post.user.profile_pic != 'default.png' %} {% if post.user.profile_pic and post.user.profile_pic != 'default.png' %}
{% if post.user.profile_pic.startswith('http') %}
{% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %}
<img src="{{ post.user.profile_pic }}" class="profile-pic me-2" style="border-color: gold;" alt="Profile Picture">
{% else %}
<img src="{{ post.user.profile_pic }}" class="profile-pic me-2" alt="Profile Picture">
{% endif %}
{% else %}
{% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %} {% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %}
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;"> <img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
{% else %} {% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2"> <img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2">
{% endif %} {% endif %}
{% endif %}
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i> <i class="bi bi-person-circle me-2"></i>
{% endif %} {% endif %}
<strong>{{ post.user.username }}</strong> <strong>{{ post.user.username }}</strong>
</div> </div>
@@ -68,9 +76,13 @@
{% for comment in post.comments %} {% for comment in post.comments %}
<div class="mt-1"> <div class="mt-1">
{% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %} {% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32"><b>{{ comment.user.username }}</b>: {% if comment.user.profile_pic.startswith('http') %}
<img src="{{ comment.user.profile_pic }}" class="rounded me-2" width="32" alt="Profile Picture"><b>{{ comment.user.username }}</b>:
{% else %} {% else %}
<i class="bi bi-person me-1"></i><b>{{ comment.user.username }}</b>: <img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32" alt="Profile Picture"><b>{{ comment.user.username }}</b>:
{% endif %}
{% else %}
<i class="bi bi-person-circle me-2"></i><b>{{ comment.user.username }}</b>:
{% endif %} {% endif %}
{% if comment.user_id == current_user.id %} {% if comment.user_id == current_user.id %}
<form action="{{ url_for('post.delete_comment', comment_id=comment.id) }}" method="post" style="display:inline;"> <form action="{{ url_for('post.delete_comment', comment_id=comment.id) }}" method="post" style="display:inline;">

View File

@@ -3,10 +3,15 @@
{% block content %} {% block content %}
<p><i class="bi bi-envelope-at me-1"></i>{{ _('Email') }}: {{ user.email }}</p> <p><i class="bi bi-envelope-at me-1"></i>{{ _('Email') }}: {{ user.email }}</p>
{% if not user.profile_pic.startswith('http') %}
<form action="{{ url_for('user.upload_pic') }}" method="post" enctype="multipart/form-data" class="mb-3"> <form action="{{ url_for('user.upload_pic') }}" method="post" enctype="multipart/form-data" class="mb-3">
<input type="file" name="profile_pic" required> <input type="file" name="profile_pic" required>
<button class="btn btn-secondary btn-sm" type="submit"><i class="bi bi-upload"></i> {{ _('Upload Picture') }}</button> <button class="btn btn-secondary btn-sm" type="submit"><i class="bi bi-upload"></i> {{ _('Upload Picture') }}</button>
</form> </form>
<form action="{{ url_for('user.gravatar') }}" method="post" style="display:inline;">
<button class="btn btn-secondary btn-sm"><i class="bi bi-globe"></i> {{ _('Use Gravatar') }}</button>
</form>
{% endif %}
{% if user.profile_pic and user.profile_pic != 'default.png' %} {% if user.profile_pic and user.profile_pic != 'default.png' %}
<form action="{{ url_for('user.delete_pic') }}" method="post" style="display:inline;"> <form action="{{ url_for('user.delete_pic') }}" method="post" style="display:inline;">
<button class="btn btn-danger btn-sm"><i class="bi bi-trash"></i> {{ _('Delete Picture') }}</button> <button class="btn btn-danger btn-sm"><i class="bi bi-trash"></i> {{ _('Delete Picture') }}</button>

View File

@@ -18,9 +18,11 @@
<tr> <tr>
<td> <td>
{% if req.user.profile_pic and req.user.profile_pic != 'default.png' %} {% if req.user.profile_pic and req.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" class="rounded me-2" width="32">{{ req.user.username }} {% if req.user.profile_pic.startswith('http') %}
<img src="{{ req.user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ req.user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %}
{% endif %} {% endif %}
</td> </td>
<td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td> <td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td>
@@ -48,9 +50,11 @@
<tr> <tr>
<td> <td>
{% if req.user.profile_pic and req.user.profile_pic != 'default.png' %} {% if req.user.profile_pic and req.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" class="rounded me-2" width="32">{{ req.user.username }} {% if req.user.profile_pic.startswith('http') %}
<img src="{{ req.user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ req.user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %}
{% endif %} {% endif %}
</td> </td>
<td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td> <td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td>
@@ -73,9 +77,11 @@
<tr> <tr>
<td> <td>
{% if req.user.profile_pic and req.user.profile_pic != 'default.png' %} {% if req.user.profile_pic and req.user.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" class="rounded me-2" width="32">{{ req.user.username }} {% if req.user.profile_pic.startswith('http') %}
<img src="{{ req.user.profile_pic }}" width="32" class="rounded me-1" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ req.user.username }} <img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" width="32" class="rounded me-1" alt="Profile Picture">
{% endif %}
{% endif %} {% endif %}
</td> </td>
<td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td> <td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td>

View File

@@ -1,294 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ _('Secret') }}{% endblock %}
{% block content %}
<h1>Secret</h1>
<canvas width="320" height="640" id="game"></canvas>
<script>
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// generate a new tetromino sequence
// @see https://tetris.fandom.com/wiki/Random_Generator
function generateSequence() {
const sequence = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'];
while (sequence.length) {
const rand = getRandomInt(0, sequence.length - 1);
const name = sequence.splice(rand, 1)[0];
tetrominoSequence.push(name);
}
}
// get the next tetromino in the sequence
function getNextTetromino() {
if (tetrominoSequence.length === 0) {
generateSequence();
}
const name = tetrominoSequence.pop();
const matrix = tetrominos[name];
// I and O start centered, all others start in left-middle
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2);
// I starts on row 21 (-1), all others start on row 22 (-2)
const row = name === 'I' ? -1 : -2;
return {
name: name, // name of the piece (L, O, etc.)
matrix: matrix, // the current rotation matrix
row: row, // current row (starts offscreen)
col: col // current col
};
}
// rotate an NxN matrix 90deg
// @see https://codereview.stackexchange.com/a/186834
function rotate(matrix) {
const N = matrix.length - 1;
const result = matrix.map((row, i) =>
row.map((val, j) => matrix[N - j][i])
);
return result;
}
// check to see if the new matrix/row/col is valid
function isValidMove(matrix, cellRow, cellCol) {
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] && (
// outside the game bounds
cellCol + col < 0 ||
cellCol + col >= playfield[0].length ||
cellRow + row >= playfield.length ||
// collides with another piece
playfield[cellRow + row][cellCol + col])
) {
return false;
}
}
}
return true;
}
// place the tetromino on the playfield
function placeTetromino() {
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// game over if piece has any part offscreen
if (tetromino.row + row < 0) {
return showGameOver();
}
playfield[tetromino.row + row][tetromino.col + col] = tetromino.name;
}
}
}
// check for line clears starting from the bottom and working our way up
for (let row = playfield.length - 1; row >= 0; ) {
if (playfield[row].every(cell => !!cell)) {
// drop every row above this one
for (let r = row; r >= 0; r--) {
for (let c = 0; c < playfield[r].length; c++) {
playfield[r][c] = playfield[r-1][c];
}
}
}
else {
row--;
}
}
tetromino = getNextTetromino();
}
// show the game over screen
function showGameOver() {
cancelAnimationFrame(rAF);
gameOver = true;
context.fillStyle = 'black';
context.globalAlpha = 0.75;
context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
context.globalAlpha = 1;
context.fillStyle = 'white';
context.font = '36px monospace';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);
}
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
const grid = 32;
const tetrominoSequence = [];
// keep track of what is in every cell of the game using a 2d array
// tetris playfield is 10x20, with a few rows offscreen
const playfield = [];
// populate the empty state
for (let row = -2; row < 20; row++) {
playfield[row] = [];
for (let col = 0; col < 10; col++) {
playfield[row][col] = 0;
}
}
// how to draw each tetromino
// @see https://tetris.fandom.com/wiki/SRS
const tetrominos = {
'I': [
[0,0,0,0],
[1,1,1,1],
[0,0,0,0],
[0,0,0,0]
],
'J': [
[1,0,0],
[1,1,1],
[0,0,0],
],
'L': [
[0,0,1],
[1,1,1],
[0,0,0],
],
'O': [
[1,1],
[1,1],
],
'S': [
[0,1,1],
[1,1,0],
[0,0,0],
],
'Z': [
[1,1,0],
[0,1,1],
[0,0,0],
],
'T': [
[0,1,0],
[1,1,1],
[0,0,0],
]
};
// color of each tetromino
const colors = {
'I': 'cyan',
'O': 'yellow',
'T': 'purple',
'S': 'green',
'Z': 'red',
'J': 'blue',
'L': 'orange'
};
let count = 0;
let tetromino = getNextTetromino();
let rAF = null; // keep track of the animation frame so we can cancel it
let gameOver = false;
// game loop
function loop() {
rAF = requestAnimationFrame(loop);
context.clearRect(0,0,canvas.width,canvas.height);
// draw the playfield
for (let row = 0; row < 20; row++) {
for (let col = 0; col < 10; col++) {
if (playfield[row][col]) {
const name = playfield[row][col];
context.fillStyle = colors[name];
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect(col * grid, row * grid, grid-1, grid-1);
}
}
}
// draw the active tetromino
if (tetromino) {
// tetromino falls every 35 frames
if (++count > 35) {
tetromino.row++;
count = 0;
// place piece if it runs into anything
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
tetromino.row--;
placeTetromino();
}
}
context.fillStyle = colors[tetromino.name];
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid-1, grid-1);
}
}
}
}
}
// listen to keyboard events to move the active tetromino
document.addEventListener('keydown', function(e) {
if (gameOver) return;
// left and right arrow keys (move)
if (e.which === 37 || e.which === 39) {
const col = e.which === 37
? tetromino.col - 1
: tetromino.col + 1;
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
tetromino.col = col;
}
}
// up arrow key (rotate)
if (e.which === 38) {
const matrix = rotate(tetromino.matrix);
if (isValidMove(matrix, tetromino.row, tetromino.col)) {
tetromino.matrix = matrix;
}
}
// down arrow key (drop)
if(e.which === 40) {
const row = tetromino.row + 1;
if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
tetromino.row = row - 1;
placeTetromino();
return;
}
tetromino.row = row;
}
});
// start the game
rAF = requestAnimationFrame(loop);
</script>
{% endblock %}

View File

@@ -7,10 +7,23 @@
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
<span> <span>
{% if user_item.profile_pic and user_item.profile_pic != 'default.png' %} {% if user_item.profile_pic and user_item.profile_pic != 'default.png' %}
<img src="{{ url_for('static', filename='profile_pics/' + user_item.profile_pic) }}" alt="{{ user_item.username }}" class="profile-pic me-2">{{ user_item.username }} {% if user_item.profile_pic.startswith('http') %}
{% if SHOPITEM_ID_GOLDRAHMEN in user_item.shop_items | map(attribute='item_id') | list %}
<img src="{{ user_item.profile_pic }}" class="profile-pic me-2" style="border-color: gold;" alt="Profile Picture">
{% else %} {% else %}
<i class="bi bi-person-circle me-1"></i>{{ user_item.username }} <img src="{{ user_item.profile_pic }}" class="profile-pic me-2" alt="Profile Picture">
{% endif %} {% endif %}
{% else %}
{% if SHOPITEM_ID_GOLDRAHMEN in user_item.shop_items | map(attribute='item_id') | list %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user_item.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
{% else %}
<img src="{{ url_for('static', filename='profile_pics/' ~ user_item.profile_pic) }}" class="profile-pic me-2">
{% endif %}
{% endif %}
{% else %}
<i class="bi bi-person-circle me-2"></i>
{% endif %}
{{ user_item.username }}
</span> </span>
<div> <div>
{% if user_item.id != user.id %} {% if user_item.id != user.id %}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-09-27 20:09+0200\n" "POT-Creation-Date: 2025-11-22 23:43+0100\n"
"PO-Revision-Date: 2025-09-27 20:09+0200\n" "PO-Revision-Date: 2025-11-22 23:44+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n" "Language: en\n"
"Language-Team: en <LL@li.org>\n" "Language-Team: en <LL@li.org>\n"
@@ -18,43 +18,43 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n" "Generated-By: Babel 2.17.0\n"
#: main.py:34 #: main.py:37
msgid "Please log in to access this page." msgid "Please log in to access this page."
msgstr "" msgstr ""
#: main.py:117 routes/discord.py:44 routes/login.py:46 routes/profile.py:56 #: main.py:168 routes/discord.py:47 routes/login.py:46 routes/profile.py:56
msgid "Passwords do not match." msgid "Passwords do not match."
msgstr "" msgstr ""
#: main.py:119 routes/login.py:48 #: main.py:170 routes/login.py:48
msgid "Username already exists." msgid "Username already exists."
msgstr "" msgstr ""
#: main.py:121 routes/login.py:50 #: main.py:172 routes/login.py:50
msgid "E-Mail already exists." msgid "E-Mail already exists."
msgstr "" msgstr ""
#: main.py:123 routes/discord.py:41 routes/login.py:52 routes/profile.py:53 #: main.py:174 routes/discord.py:44 routes/login.py:52 routes/profile.py:53
msgid "Password must be at least 8 characters long." msgid "Password must be at least 8 characters long."
msgstr "" msgstr ""
#: main.py:125 routes/login.py:54 routes/profile.py:44 #: main.py:176 routes/login.py:54 routes/profile.py:44
msgid "Invalid email address." msgid "Invalid email address."
msgstr "" msgstr ""
#: main.py:127 routes/login.py:56 routes/profile.py:38 #: main.py:178 routes/login.py:56 routes/profile.py:38
msgid "Invalid username. Only alphanumeric characters are allowed." msgid "Invalid username. Only alphanumeric characters are allowed."
msgstr "" msgstr ""
#: main.py:133 #: main.py:184
msgid "Admin account created. You can now log in." msgid "Admin account created. You can now log in."
msgstr "" msgstr ""
#: main.py:159 #: main.py:210
msgid "Already purchased!" msgid "Already purchased!"
msgstr "" msgstr ""
#: main.py:167 #: main.py:218
msgid "Not enough points!" msgid "Not enough points!"
msgstr "" msgstr ""
@@ -126,27 +126,27 @@ msgstr ""
msgid "All Data has been deleted." msgid "All Data has been deleted."
msgstr "" msgstr ""
#: routes/discord.py:24 #: routes/discord.py:27
msgid "Logged in with Discord." msgid "Logged in with Discord."
msgstr "" msgstr ""
#: routes/discord.py:27 #: routes/discord.py:30
msgid "No account linked with this Discord. Please register." msgid "No account linked with this Discord. Please register."
msgstr "" msgstr ""
#: routes/discord.py:47 #: routes/discord.py:50
msgid "Username already exists. Please Report It." msgid "Username already exists. Please Report It."
msgstr "" msgstr ""
#: routes/discord.py:60 #: routes/discord.py:63
msgid "Account created and logged in with Discord." msgid "Account created and logged in with Discord."
msgstr "" msgstr ""
#: routes/discord.py:77 #: routes/discord.py:80
msgid "Discord account linked!" msgid "Discord account linked!"
msgstr "" msgstr ""
#: routes/discord.py:86 #: routes/discord.py:89
msgid "Discord account unlinked!" msgid "Discord account unlinked!"
msgstr "" msgstr ""
@@ -338,7 +338,11 @@ msgstr ""
msgid "Account and all your data deleted." msgid "Account and all your data deleted."
msgstr "" msgstr ""
#: templates/403.html:2 templates/base.html:106 templates/index.html:2 #: routes/user.py:112
msgid "Added Gravatar profile picture."
msgstr ""
#: templates/403.html:2 templates/base.html:115 templates/index.html:2
msgid "Home" msgid "Home"
msgstr "" msgstr ""
@@ -360,11 +364,11 @@ msgstr ""
msgid "Admin" msgid "Admin"
msgstr "" msgstr ""
#: templates/admin.html:4 templates/base.html:39 #: templates/admin.html:4 templates/base.html:40
msgid "Admin Panel" msgid "Admin Panel"
msgstr "" msgstr ""
#: templates/admin.html:8 templates/admin.html:39 templates/base.html:43 #: templates/admin.html:8 templates/admin.html:39 templates/base.html:44
#: templates/users.html:2 #: templates/users.html:2
msgid "Users" msgid "Users"
msgstr "" msgstr ""
@@ -373,19 +377,19 @@ msgstr ""
msgid "Posts" msgid "Posts"
msgstr "" msgstr ""
#: templates/admin.html:14 templates/admin.html:133 #: templates/admin.html:14 templates/admin.html:148
msgid "Friendships" msgid "Friendships"
msgstr "" msgstr ""
#: templates/admin.html:17 templates/admin.html:176 #: templates/admin.html:17 templates/admin.html:195
msgid "Comments" msgid "Comments"
msgstr "" msgstr ""
#: templates/admin.html:20 templates/admin.html:213 #: templates/admin.html:20 templates/admin.html:234
msgid "Uploads" msgid "Uploads"
msgstr "" msgstr ""
#: templates/admin.html:23 templates/admin.html:250 templates/base.html:46 #: templates/admin.html:23 templates/admin.html:271 templates/base.html:47
#: templates/notifications.html:2 templates/notifications.html:6 #: templates/notifications.html:2 templates/notifications.html:6
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
@@ -394,18 +398,18 @@ msgstr ""
msgid "Events" msgid "Events"
msgstr "" msgstr ""
#: templates/admin.html:29 templates/admin.html:297 #: templates/admin.html:29 templates/admin.html:318
msgid "Shop Orders" msgid "Shop Orders"
msgstr "" msgstr ""
#: templates/admin.html:32 templates/admin.html:324 #: templates/admin.html:32 templates/admin.html:347
msgid "Reward Points" msgid "Reward Points"
msgstr "" msgstr ""
#: templates/admin.html:43 templates/admin.html:94 templates/admin.html:180 #: templates/admin.html:43 templates/admin.html:107 templates/admin.html:199
#: templates/admin.html:221 templates/admin.html:258 templates/admin.html:301 #: templates/admin.html:242 templates/admin.html:279 templates/admin.html:322
#: templates/admin.html:328 templates/reset_requests.html:11 #: templates/admin.html:351 templates/reset_requests.html:11
#: templates/reset_requests.html:42 templates/reset_requests.html:67 #: templates/reset_requests.html:44 templates/reset_requests.html:71
msgid "User" msgid "User"
msgstr "" msgstr ""
@@ -422,152 +426,152 @@ msgstr ""
msgid "Profile Pic" msgid "Profile Pic"
msgstr "" msgstr ""
#: templates/admin.html:48 templates/admin.html:98 templates/admin.html:184 #: templates/admin.html:48 templates/admin.html:111 templates/admin.html:203
#: templates/admin.html:225 templates/admin.html:330 #: templates/admin.html:246 templates/admin.html:353
#: templates/reset_requests.html:13 #: templates/reset_requests.html:13
msgid "Actions" msgid "Actions"
msgstr "" msgstr ""
#: templates/admin.html:62 templates/profile.html:12 #: templates/admin.html:75 templates/profile.html:17
msgid "Delete Picture" msgid "Delete Picture"
msgstr "" msgstr ""
#: templates/admin.html:69 templates/users.html:34 #: templates/admin.html:82 templates/users.html:47
msgid "Make Admin" msgid "Make Admin"
msgstr "" msgstr ""
#: templates/admin.html:73 templates/users.html:38 #: templates/admin.html:86 templates/users.html:51
msgid "Remove Admin" msgid "Remove Admin"
msgstr "" msgstr ""
#: templates/admin.html:78 #: templates/admin.html:91
msgid "Delete User" msgid "Delete User"
msgstr "" msgstr ""
#: templates/admin.html:90 #: templates/admin.html:103
msgid "All Posts" msgid "All Posts"
msgstr "" msgstr ""
#: templates/admin.html:95 templates/admin.html:182 templates/edit_post.html:7 #: templates/admin.html:108 templates/admin.html:201 templates/edit_post.html:7
msgid "Content" msgid "Content"
msgstr "" msgstr ""
#: templates/admin.html:96 templates/edit_post.html:16 #: templates/admin.html:109 templates/edit_post.html:16
msgid "Visibility" msgid "Visibility"
msgstr "" msgstr ""
#: templates/admin.html:97 templates/admin.html:183 templates/admin.html:260 #: templates/admin.html:110 templates/admin.html:202 templates/admin.html:281
#: templates/support.html:26 #: templates/support.html:26
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#: templates/admin.html:114 templates/edit_post.html:18 templates/feed.html:18 #: templates/admin.html:129 templates/edit_post.html:18 templates/feed.html:18
#: templates/feed.html:86 templates/my_posts.html:61 #: templates/feed.html:94 templates/my_posts.html:69
msgid "Public" msgid "Public"
msgstr "" msgstr ""
#: templates/admin.html:116 templates/base.html:44 templates/friends.html:2 #: templates/admin.html:131 templates/base.html:45 templates/friends.html:2
msgid "Friends" msgid "Friends"
msgstr "" msgstr ""
#: templates/admin.html:122 #: templates/admin.html:137
msgid "Delete Post" msgid "Delete Post"
msgstr "" msgstr ""
#: templates/admin.html:137 #: templates/admin.html:152
msgid "User 1" msgid "User 1"
msgstr "" msgstr ""
#: templates/admin.html:138 #: templates/admin.html:153
msgid "User 2" msgid "User 2"
msgstr "" msgstr ""
#: templates/admin.html:139 templates/support.html:25 #: templates/admin.html:154 templates/support.html:25
#: templates/support_thread.html:5 #: templates/support_thread.html:5
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: templates/admin.html:161 #: templates/admin.html:180
msgid "Accepted" msgid "Accepted"
msgstr "" msgstr ""
#: templates/admin.html:163 #: templates/admin.html:182
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
#: templates/admin.html:165 #: templates/admin.html:184
msgid "Rejected" msgid "Rejected"
msgstr "" msgstr ""
#: templates/admin.html:181 templates/feed.html:21 #: templates/admin.html:200 templates/feed.html:21
msgid "Post" msgid "Post"
msgstr "" msgstr ""
#: templates/admin.html:202 #: templates/admin.html:223
msgid "Delete Comment" msgid "Delete Comment"
msgstr "" msgstr ""
#: templates/admin.html:215 #: templates/admin.html:236
msgid "Delete All Uploads" msgid "Delete All Uploads"
msgstr "" msgstr ""
#: templates/admin.html:222 #: templates/admin.html:243
msgid "Filename" msgid "Filename"
msgstr "" msgstr ""
#: templates/admin.html:223 #: templates/admin.html:244
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#: templates/admin.html:224 #: templates/admin.html:245
msgid "Uploaded" msgid "Uploaded"
msgstr "" msgstr ""
#: templates/admin.html:236 templates/feed.html:55 templates/my_posts.html:32 #: templates/admin.html:257 templates/feed.html:63 templates/my_posts.html:40
msgid "Download" msgid "Download"
msgstr "" msgstr ""
#: templates/admin.html:238 #: templates/admin.html:259
msgid "Delete Upload" msgid "Delete Upload"
msgstr "" msgstr ""
#: templates/admin.html:251 #: templates/admin.html:272
msgid "Are you sure you want to delete all notifications?" msgid "Are you sure you want to delete all notifications?"
msgstr "" msgstr ""
#: templates/admin.html:252 #: templates/admin.html:273
msgid "Delete All Notifications" msgid "Delete All Notifications"
msgstr "" msgstr ""
#: templates/admin.html:259 #: templates/admin.html:280
msgid "Message" msgid "Message"
msgstr "" msgstr ""
#: templates/admin.html:278 #: templates/admin.html:299
msgid "Recent Events" msgid "Recent Events"
msgstr "" msgstr ""
#: templates/admin.html:279 #: templates/admin.html:300
msgid "Are you sure you want to delete all events?" msgid "Are you sure you want to delete all events?"
msgstr "" msgstr ""
#: templates/admin.html:280 #: templates/admin.html:301
msgid "Delete All Events" msgid "Delete All Events"
msgstr "" msgstr ""
#: templates/admin.html:302 #: templates/admin.html:323
msgid "Item" msgid "Item"
msgstr "" msgstr ""
#: templates/admin.html:329 templates/shop.html:17 #: templates/admin.html:352 templates/shop.html:17
msgid "Points" msgid "Points"
msgstr "" msgstr ""
#: templates/admin.html:347 #: templates/admin.html:372
msgid "Add Points" msgid "Add Points"
msgstr "" msgstr ""
#: templates/admin.html:350 #: templates/admin.html:375
msgid "Remove Points" msgid "Remove Points"
msgstr "" msgstr ""
@@ -584,65 +588,65 @@ msgstr ""
msgid "New Password" msgid "New Password"
msgstr "" msgstr ""
#: templates/admin_set_password.html:10 templates/reset_requests.html:28 #: templates/admin_set_password.html:10 templates/reset_requests.html:30
msgid "Set Password" msgid "Set Password"
msgstr "" msgstr ""
#: templates/base.html:40 #: templates/base.html:41
msgid "Reset Requests" msgid "Reset Requests"
msgstr "" msgstr ""
#: templates/base.html:42 templates/base.html:51 templates/feed.html:2 #: templates/base.html:43 templates/base.html:52 templates/feed.html:2
msgid "Feed" msgid "Feed"
msgstr "" msgstr ""
#: templates/base.html:45 templates/my_posts.html:2 templates/my_posts.html:4 #: templates/base.html:46 templates/my_posts.html:2 templates/my_posts.html:4
msgid "My Posts" msgid "My Posts"
msgstr "" msgstr ""
#: templates/base.html:47 templates/profile.html:2 #: templates/base.html:48 templates/profile.html:2
msgid "Profile" msgid "Profile"
msgstr "" msgstr ""
#: templates/base.html:48 #: templates/base.html:49
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
#: templates/base.html:49 templates/support.html:2 templates/support.html:15 #: templates/base.html:50 templates/support.html:2 templates/support.html:15
msgid "Support" msgid "Support"
msgstr "" msgstr ""
#: templates/base.html:52 templates/index.html:16 templates/login.html:2 #: templates/base.html:53 templates/index.html:16 templates/login.html:2
#: templates/login.html:7 templates/login.html:18 templates/register.html:30 #: templates/login.html:7 templates/login.html:18 templates/register.html:30
#: templates/reset_password.html:17 #: templates/reset_password.html:17
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: templates/base.html:53 templates/index.html:17 templates/login.html:21 #: templates/base.html:54 templates/index.html:17 templates/login.html:21
#: templates/register.html:2 templates/register.html:7 #: templates/register.html:2 templates/register.html:7
#: templates/register.html:26 #: templates/register.html:26
msgid "Register" msgid "Register"
msgstr "" msgstr ""
#: templates/base.html:57 #: templates/base.html:58
msgid "Theme" msgid "Theme"
msgstr "" msgstr ""
#: templates/base.html:90 #: templates/base.html:99
#, python-format #, python-format
msgid "Welcome, %(username)s!" msgid "Welcome, %(username)s!"
msgstr "" msgstr ""
#: templates/base.html:94 #: templates/base.html:103
msgid "You are logged in as an admin." msgid "You are logged in as an admin."
msgstr "" msgstr ""
#: templates/base.html:107 templates/privacy_policy.html:2 #: templates/base.html:116 templates/privacy_policy.html:2
#: templates/privacy_policy.html:4 #: templates/privacy_policy.html:4
msgid "Privacy Policy" msgid "Privacy Policy"
msgstr "" msgstr ""
#: templates/base.html:108 templates/credits.html:2 templates/credits.html:4 #: templates/base.html:117 templates/credits.html:2 templates/credits.html:4
msgid "Credits" msgid "Credits"
msgstr "" msgstr ""
@@ -750,8 +754,8 @@ msgstr ""
msgid "Limit: 500" msgid "Limit: 500"
msgstr "" msgstr ""
#: templates/edit_post.html:19 templates/feed.html:19 templates/feed.html:84 #: templates/edit_post.html:19 templates/feed.html:19 templates/feed.html:92
#: templates/my_posts.html:59 #: templates/my_posts.html:67
msgid "Friends only" msgid "Friends only"
msgstr "" msgstr ""
@@ -776,7 +780,7 @@ msgid "No uploads found for this post."
msgstr "" msgstr ""
#: templates/edit_profile.html:2 templates/edit_profile.html:4 #: templates/edit_profile.html:2 templates/edit_profile.html:4
#: templates/profile.html:16 #: templates/profile.html:21
msgid "Edit Profile" msgid "Edit Profile"
msgstr "" msgstr ""
@@ -814,41 +818,41 @@ msgstr ""
msgid "to create a post." msgid "to create a post."
msgstr "" msgstr ""
#: templates/feed.html:61 templates/my_posts.html:38 #: templates/feed.html:69 templates/my_posts.html:46
msgid "Download Video" msgid "Download Video"
msgstr "" msgstr ""
#: templates/feed.html:64 templates/my_posts.html:41 #: templates/feed.html:72 templates/my_posts.html:49
msgid "Download Audio" msgid "Download Audio"
msgstr "" msgstr ""
#: templates/feed.html:70 templates/my_posts.html:47 #: templates/feed.html:78 templates/my_posts.html:55
msgid "Like" msgid "Like"
msgstr "" msgstr ""
#: templates/feed.html:73 templates/my_posts.html:50 #: templates/feed.html:81 templates/my_posts.html:58
msgid "Unlike" msgid "Unlike"
msgstr "" msgstr ""
#: templates/feed.html:77 templates/feed.html:107 templates/my_posts.html:53 #: templates/feed.html:85 templates/feed.html:115 templates/my_posts.html:61
#: templates/my_posts.html:77 templates/notifications.html:25 #: templates/my_posts.html:85 templates/notifications.html:25
#: templates/support_thread.html:35 #: templates/support_thread.html:35
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
#: templates/feed.html:80 templates/my_posts.html:56 #: templates/feed.html:88 templates/my_posts.html:64
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: templates/feed.html:91 templates/my_posts.html:66 #: templates/feed.html:99 templates/my_posts.html:74
msgid "Comment..." msgid "Comment..."
msgstr "" msgstr ""
#: templates/feed.html:95 #: templates/feed.html:103
msgid "Please login to comment." msgid "Please login to comment."
msgstr "" msgstr ""
#: templates/feed.html:118 templates/my_posts.html:88 #: templates/feed.html:126 templates/my_posts.html:96
msgid "No posts available." msgid "No posts available."
msgstr "" msgstr ""
@@ -856,27 +860,27 @@ msgstr ""
msgid "Your Friends" msgid "Your Friends"
msgstr "" msgstr ""
#: templates/friends.html:16 #: templates/friends.html:29
msgid "Remove Friend" msgid "Remove Friend"
msgstr "" msgstr ""
#: templates/friends.html:20 #: templates/friends.html:33
msgid "No friends yet." msgid "No friends yet."
msgstr "" msgstr ""
#: templates/friends.html:23 #: templates/friends.html:36
msgid "Friend Requests" msgid "Friend Requests"
msgstr "" msgstr ""
#: templates/friends.html:36 #: templates/friends.html:62
msgid "Accept" msgid "Accept"
msgstr "" msgstr ""
#: templates/friends.html:39 templates/reset_requests.html:30 #: templates/friends.html:65 templates/reset_requests.html:32
msgid "Reject" msgid "Reject"
msgstr "" msgstr ""
#: templates/friends.html:44 #: templates/friends.html:70
msgid "No new requests" msgid "No new requests"
msgstr "" msgstr ""
@@ -932,7 +936,7 @@ msgstr ""
msgid "Forgot password?" msgid "Forgot password?"
msgstr "" msgstr ""
#: templates/login.html:27 templates/register.html:34 #: templates/login.html:28 templates/register.html:35
msgid "Login with Discord" msgid "Login with Discord"
msgstr "" msgstr ""
@@ -999,27 +1003,31 @@ msgid ""
"contact us." "contact us."
msgstr "" msgstr ""
#: templates/profile.html:8 #: templates/profile.html:9
msgid "Upload Picture" msgid "Upload Picture"
msgstr "" msgstr ""
#: templates/profile.html:15 templates/shop.html:2 templates/shop.html:4 #: templates/profile.html:12
msgid "Use Gravatar"
msgstr ""
#: templates/profile.html:20 templates/shop.html:2 templates/shop.html:4
msgid "Shop" msgid "Shop"
msgstr "" msgstr ""
#: templates/profile.html:18 #: templates/profile.html:23
msgid "Delete Account" msgid "Delete Account"
msgstr "" msgstr ""
#: templates/profile.html:22 #: templates/profile.html:28
msgid "Link Discord Account" msgid "Link Discord Account"
msgstr "" msgstr ""
#: templates/profile.html:25 #: templates/profile.html:31
msgid "Discord Linked" msgid "Discord Linked"
msgstr "" msgstr ""
#: templates/profile.html:28 #: templates/profile.html:34
msgid "Unlink Discord" msgid "Unlink Discord"
msgstr "" msgstr ""
@@ -1043,27 +1051,23 @@ msgstr ""
msgid "Delete All" msgid "Delete All"
msgstr "" msgstr ""
#: templates/reset_requests.html:12 templates/reset_requests.html:43 #: templates/reset_requests.html:12 templates/reset_requests.html:45
#: templates/reset_requests.html:68 #: templates/reset_requests.html:72
msgid "Requested At" msgid "Requested At"
msgstr "" msgstr ""
#: templates/reset_requests.html:38 #: templates/reset_requests.html:40
msgid "Completed Requests" msgid "Completed Requests"
msgstr "" msgstr ""
#: templates/reset_requests.html:63 #: templates/reset_requests.html:67
msgid "Rejected Requests" msgid "Rejected Requests"
msgstr "" msgstr ""
#: templates/reset_requests.html:88 #: templates/reset_requests.html:94
msgid "No open reset requests." msgid "No open reset requests."
msgstr "" msgstr ""
#: templates/secret.html:2
msgid "Secret"
msgstr ""
#: templates/setup.html:2 #: templates/setup.html:2
msgid "Admin Setup" msgid "Admin Setup"
msgstr "" msgstr ""
@@ -1148,19 +1152,19 @@ msgstr ""
msgid "All Users" msgid "All Users"
msgstr "" msgstr ""
#: templates/users.html:20 #: templates/users.html:33
msgid "Request sent" msgid "Request sent"
msgstr "" msgstr ""
#: templates/users.html:22 #: templates/users.html:35
msgid "Request received" msgid "Request received"
msgstr "" msgstr ""
#: templates/users.html:24 #: templates/users.html:37
msgid "Friend" msgid "Friend"
msgstr "" msgstr ""
#: templates/users.html:27 #: templates/users.html:40
msgid "Add Friend" msgid "Add Friend"
msgstr "" msgstr ""