Compare commits

...

34 Commits
dev ... master

Author SHA1 Message Date
Darks 21b863e0e2
Merge branch 'preprod' of gitea.planet-casio.com:devs/PCv5 2023-07-18 21:31:46 +02:00
Darks e92792c8d6
submodules: moved to PCv5-extra 2023-07-18 21:29:17 +02:00
Darks 20524d28c3
scripts: add modules to render helper 2023-07-15 20:36:44 +02:00
Eragon 0d9ca65238
config: Change default sender for mails 2023-07-06 15:35:59 +02:00
Darks 402e6699aa
fixed 'avancées de la v5' button 2023-07-04 21:45:35 +02:00
Darks 7e64a70eec Merge pull request 'Ajout de la page d’accueil en préprod' (#141) from landing_page into dev
Reviewed-on: https://gitea.planet-casio.com/devs/PCv5/pulls/141
2023-07-04 21:35:58 +02:00
Darks 920724718f
homepage: good enough for preview release 2023-07-04 21:30:16 +02:00
Lephenixnoir d6ff6eb77f Merge pull request 'post: unique delete button for guest posts' (#140) from IniKiwi/PCv5:guest-delete-button-2 into dev
Reviewed-on: https://gitea.planet-casio.com/devs/PCv5/pulls/140
2023-07-01 12:42:51 +02:00
IniKiwi c5df575af3
post: fix duplicate code 2023-07-01 12:39:46 +02:00
IniKiwi f93443310b
post: unique delete button for guest posts 2023-07-01 12:24:53 +02:00
Darks f6aefffc3c
homepage: still WIP, but better 2023-06-27 23:28:32 +02:00
Lephe c8f2d73bc2
shoutbox: add standalone shoutbox at /chat 2023-06-27 22:35:42 +02:00
Lephe d531106c78
meta: add shoutbox submodule 2023-06-27 22:08:27 +02:00
Eragon 43381bf493
homepage: Fix grid style 2023-06-25 01:57:39 +02:00
Darks 62341cf9d9
landing page: WIP 2023-06-23 23:41:46 +02:00
Eragon c88c993ee3
makefile: copy all scripts for emoji picker 2023-06-21 00:16:45 +02:00
Eragon ed8550f291
makefile: Mkdir folders for emoji JS 2023-06-21 00:04:38 +02:00
Eragon f163d15066
ldap: Update user informations in LDAP when edited from PCv5 2023-06-20 22:40:51 +02:00
Darks 14e81bdfb5
registration: fix link to CGU 2023-06-20 22:22:43 +02:00
Eragon 4231b3084e
member: Delete members from LDAP on account deletion 2023-06-20 20:08:41 +02:00
Darks 9902719328
Merge branch 'glados_say' of gitea.planet-casio.com:devs/PCv5 into dev 2023-06-20 19:39:49 +02:00
Darks 358a5fec9d
notifications: fixed notifications 2023-06-20 19:38:04 +02:00
Darks 8721a7be69 Merge pull request 'logging: add some logging for v5 events' (#136) from logging into dev
Reviewed-on: https://gitea.planet-casio.com/devs/PCv5/pulls/136
2023-06-20 19:11:41 +02:00
Darks 12483e70e4
logging: add some logging for v5 events 2023-06-13 23:32:58 +02:00
Darks fabbb130b6
Merge branch 'dev' of gitea.planet-casio.com:devs/PCv5 into glados_say 2023-06-12 20:09:02 +02:00
Darks 2530581095
glados: updated announces 2023-06-12 20:04:20 +02:00
Darks f06f14e814
templates: fix a template tabtitle 2023-06-12 19:21:26 +02:00
Darks 876cae2b69
glados: add some 'say' messages 2023-06-11 23:05:03 +02:00
Darks a5b2933727
Merge branch 'preprod' of gitea.planet-casio.com:devs/PCv5 2023-06-07 21:37:12 +02:00
Darks 9de0f9f823
Merge branch 'preprod' into 'master' 2022-04-26 23:49:11 +02:00
Eldeberen 41eaaa4c30
Merge branch 'preprod' on master 2021-02-23 00:15:29 +01:00
Darks ad1042865b
Merge branch 'master' of gitea.planet-casio.com:devs/PCv5 2020-07-23 20:25:41 +02:00
Darks 2dd7863e89
Rebase master from preprod 2020-07-23 20:25:00 +02:00
Darks e15005a427
Ajout des stats sur la durée de chargement 2019-08-20 18:04:10 +02:00
41 changed files with 474 additions and 274 deletions

3
.gitignore vendored
View File

@ -22,6 +22,9 @@ test.*
# Autosaves
*.dia~
## Logging files
*.log
## Deployment files

6
.gitmodules vendored
View File

@ -1,6 +0,0 @@
[submodule "submodules/emoji-picker-element"]
path = submodules/emoji-picker-element
url = https://gitea.planet-casio.com/devs/emoji-picker-element.git
[submodule "submodules/emoji-picker-element-data"]
path = submodules/emoji-picker-element-data
url = https://gitea.planet-casio.com/devs/emoji-picker-element-data.git

View File

@ -7,14 +7,6 @@ run: css
css: $(obj)
emoji: emoji-data
cd submodules/emoji-picker-element && npm install && npm run build
cp submodules/emoji-picker-element/i18n/*.js app/static/scripts/emoji-picker-element/
emoji-data:
cd submodules/emoji-picker-element-data/ && npm install && npm run build
cp submodules/emoji-picker-element-data/fr/emojibase/data.json app/static/scripts/emoji-picker-element/
app/static/css/%.css: app/static/less/%.less
$(CSSC) $< $@

View File

@ -9,6 +9,7 @@ from config import FlaskApplicationSettings, V5Config
app = Flask(__name__)
app.config.from_object(FlaskApplicationSettings)
app.v5logger = V5Config.v5logger()
# Check security of secret
if FlaskApplicationSettings.SECRET_KEY == "a-random-secret-key":

View File

@ -397,8 +397,7 @@ class Member(User):
Notify a user with a message.
An hyperlink can be added to redirect to the notification source
"""
return
n = Notification(self.id, message, href=href)
n = Notification(self, message, href=href)
db.session.add(n)
db.session.commit()

View File

@ -1,6 +1,6 @@
# Register routes here
from app.routes import index, search, users, tools, development
from app.routes import index, search, users, tools, development, chat
from app.routes.account import login, account, notification, polls
from app.routes.admin import index, groups, account, forums, \
attachments, config, members, polls, login_as

View File

@ -8,6 +8,7 @@ from app.models.trophy import Title
from app.utils.render import render
from app.utils.send_mail import send_validation_mail, send_reset_password_mail
from app.utils.priv_required import guest_only
from app.utils.glados import say, BOLD
import app.utils.ldap as ldap
import app.utils.validators as vd
from itsdangerous import URLSafeTimedSerializer
@ -30,6 +31,7 @@ def edit_account():
if form.submit.data:
if form.is_submitted() and form.validate(extra_validators=extra_vd):
old_username = current_user.norm
current_user.update(
avatar=form.avatar.data or None,
email=form.email.data or None,
@ -41,10 +43,13 @@ def edit_account():
newsletter=form.newsletter.data,
theme=form.theme.data
)
ldap.edit(old_username, current_user)
current_user.update(password=form.password.data or None)
db.session.merge(current_user)
db.session.commit()
current_user.update_trophies("on-profile-update")
flash('Modifications effectuées', 'ok')
app.v5logger.info(f"<{current_user.name}> has edited their account")
return redirect(request.url)
else:
flash('Erreur lors de la modification', 'error')
@ -62,6 +67,7 @@ def ask_reset_password():
m = Member.query.filter_by(email=form.email.data).first()
if m is not None:
send_reset_password_mail(m.name, m.email)
app.v5logger.info(f"<{m.name}> has asked a password reset token")
flash('Un email a été envoyé à l\'adresse renseignée', 'ok')
return redirect(url_for('login'))
elif request.method == "POST":
@ -87,6 +93,7 @@ def reset_password(token):
db.session.merge(m)
db.session.commit()
flash('Modifications effectuées', 'ok')
app.v5logger.info(f"<{m.name}> has reset their password")
return redirect(url_for('login'))
else:
flash('Erreur lors de la modification', 'error')
@ -102,6 +109,7 @@ def delete_account():
if del_form.submit.data:
if del_form.validate_on_submit():
name = current_user.name
if del_form.transfer.data:
guest = Guest(current_user.generate_guest_name())
db.session.add(guest)
@ -112,10 +120,14 @@ def delete_account():
current_user.delete_posts()
db.session.commit()
if (V5Config.USE_LDAP):
ldap.delete_member(current_user)
current_user.delete()
logout_user()
db.session.commit()
flash('Compte supprimé', 'ok')
app.v5logger.info(f"<{name}> has deleted their account ({'with' if del_form.transfer.data else 'without'} guest transfer)")
return redirect(url_for('index'))
else:
flash('Erreur lors de la suppression du compte', 'error')
@ -141,6 +153,7 @@ def register():
# Email validation message
send_validation_mail(member.name, member.email)
app.v5logger.info(f"<{member.name}> registered")
return redirect(url_for('validation') + "?email=" + form.email.data)
return render('account/register.html', title='Register',
@ -178,4 +191,8 @@ def activate_account(token):
db.session.commit()
flash("L'email a bien été confirmé", "ok")
app.v5logger.info(f"<{m.name}> has activated their account")
say(f"Un nouveau membre sest inscrit ! Il sagit de {BOLD}{m.name}{BOLD}.")
say(url_for('user', username=m.name, _external=True))
return redirect(url_for('login'))

View File

@ -49,6 +49,7 @@ def login():
login_user(member, remember=form.remember_me.data,
duration=datetime.timedelta(days=7))
member.update_trophies("on-login")
app.v5logger.info(f"<{member.name}> has logged in")
# Redirect safely (https://huit.re/open-redirect)
def is_safe_url(target):
@ -71,8 +72,10 @@ def login():
@login_required
@check_csrf
def logout():
name = current_user.name
logout_user()
flash('Déconnexion réussie', 'info')
app.v5logger.info(f"<{name}> has logged out")
if request.referrer:
return redirect(request.referrer)
return redirect(url_for('index'))

View File

@ -28,5 +28,6 @@ def account_polls():
db.session.commit()
flash(f"Le sondage {p.id} a été créé", "info")
app.v5logger.info(f"<{current_user.name}> has created the form #{p.id}")
return render("account/polls.html", polls=polls, form=form)

View File

@ -9,6 +9,7 @@ from app.forms.account import AdminUpdateAccountForm, AdminDeleteAccountForm, \
AdminAccountEditTrophyForm, AdminAccountEditGroupForm
from app.utils.render import render
from app.utils.notify import notify
from app.utils import ldap as ldap
from app import app, db
from config import V5Config
@ -50,12 +51,12 @@ def adm_edit_account(user_id):
# You cannot user vd.name_available because name will always be
# invalid! Maybe you can add another validator with arguments
raise Exception(f'{newname} is not available')
old_username = user.norm
user.update(
avatar=form.avatar.data or None,
name=form.username.data or None,
email=form.email.data or None,
email_confirmed=form.email_confirmed.data,
password=form.password.data or None,
birthday=form.birthday.data,
signature=form.signature.data,
title=form.title.data,
@ -63,11 +64,14 @@ def adm_edit_account(user_id):
newsletter=form.newsletter.data,
xp=form.xp.data or None,
)
ldap.edit(old_username, user)
user.update(password=form.password.data or None)
db.session.merge(user)
db.session.commit()
# TODO: send an email to member saying his account has been modified
user.notify(f"Vos informations personnelles ont été modifiées par {current_user.name}.")
flash('Modifications effectuées', 'ok')
app.v5logger.info(f"[admin] <{current_user.name}> has edited <{user.name}>'s data")
return redirect(request.url)
else:
flash('Erreur lors de la modification', 'error')
@ -85,6 +89,7 @@ def adm_edit_account(user_id):
db.session.merge(user)
db.session.commit()
flash('Modifications effectuées', 'ok')
app.v5logger.info(f"[admin] <{current_user.name}> has edited <{user.name}>'s trophies")
return redirect(request.url)
else:
flash("Erreur lors de la modification des trophées", 'error')
@ -102,6 +107,7 @@ def adm_edit_account(user_id):
db.session.merge(user)
db.session.commit()
flash('Modifications effectuées', 'ok')
app.v5logger.info(f"[admin] <{current_user.name}> has edited <{user.name}>'s groups")
return redirect(request.url)
else:
flash("Erreur lors de la modification des groupes", 'error')
@ -148,9 +154,13 @@ def adm_delete_account(user_id):
user.delete_posts()
db.session.commit()
if (V5Config.USE_LDAP):
ldap.delete_member(user)
user.delete()
db.session.commit()
flash('Compte supprimé', 'ok')
app.v5logger.info(f"[admin] <{current_user.name}> has deleted <{user.name}> account")
return redirect(url_for('adm'))
else:
flash('Erreur lors de la suppression du compte', 'error')

View File

@ -42,6 +42,7 @@ def adm_login_as():
# Create a safe token to flee when needed
s = Serializer(app.config["SECRET_KEY"])
vandal_token = s.dumps(current_user.id)
vandal_name = current_user.name
# Login and display some messages
login_user(user)
@ -51,9 +52,11 @@ def adm_login_as():
else:
flash(f"Connecté en tant que {user.name}")
app.v5logger.info(f"[admin] <{vandal_name}> has logged in as <{user.name}>")
# Return the response
resp = make_response(redirect(url_for('index')))
resp.set_cookie('vandale', vandal_token)
resp.set_cookie('vandale', vandal_token, path='/')
return resp
# Else return form
@ -76,13 +79,22 @@ def adm_logout_as():
abort(403)
user = Member.query.get(id)
# Send a notification to vandalized user
current_user.notify(f"{user.name} a accédé à ce compte à des fins de modération",
url_for('user', username=user.name))
# Switch back to admin
victim_name = current_user.name
logout_user()
login_user(user)
app.v5logger.info(f"[admin] <{user.name}> has logged out from <{victim_name}>'s account")
if request.referrer:
resp = make_response(redirect(request.referrer))
else:
resp = make_response(redirect(url_for('index')))
resp.set_cookie('vandale', '', expires=0)
resp.set_cookie('vandale', '', expires=0, path='/')
return resp

16
app/routes/chat.py Normal file
View File

@ -0,0 +1,16 @@
from app import app
from app.utils.render import render
from flask import send_file, url_for
@app.route('/chat')
def chat():
return render('chat.html',
styles=[
'+css/v5shoutbox.css'],
scripts=[
'-scripts/trigger_menu.js',
'-scripts/editor.js'])
@app.route('/v5shoutbox.js')
def v5shoutbox_js():
return send_file('static/scripts/v5shoutbox.js')

View File

@ -4,6 +4,7 @@ from flask import request, redirect, url_for, abort, flash
from app import app, db
from config import V5Config
from app.utils.render import render
from app.utils.glados import say, BOLD
from app.forms.forum import TopicCreationForm, AnonymousTopicCreationForm
from app.models.forum import Forum
from app.models.topic import Topic
@ -73,6 +74,11 @@ def forum_page(f, page=1):
current_user.update_trophies('new-post')
flash('Le sujet a bien été créé', 'ok')
app.v5logger.info(f"<{t.author.name}> has created the topic #{t.id}")
if f.is_default_accessible():
say(f"Nouveau topic de {author.name} : {BOLD}{t.title}{BOLD}")
say(url_for('forum_topic', f=f, page=(t, 1), _external=True))
return redirect(url_for('forum_topic', f=f, page=(t,1)))
# Paginate topic pages

View File

@ -5,6 +5,7 @@ from sqlalchemy import desc
from app import app, db
from config import V5Config
from app.utils.render import render
from app.utils.glados import say, BOLD
from app.forms.forum import CommentForm, AnonymousCommentForm
from app.models.thread import Thread
from app.models.comment import Comment
@ -54,6 +55,11 @@ def forum_topic(f, page):
current_user.update_trophies('new-post')
flash('Message envoyé', 'ok')
app.v5logger.info(f"<{c.author.name}> has posted a the comment #{c.id}")
if f.is_default_accessible():
say(f"Nouveau commentaire de {author.name} sur le topic : {BOLD}{t.title}{BOLD}")
say(url_for('forum_topic', f=f, page=(t, "fin"), _anchor=str(c.id), _external=True))
# Redirect to empty the form
return redirect(url_for('forum_topic', f=f, page=(t, "fin"),
_anchor=str(c.id)))

View File

@ -5,7 +5,7 @@ from app.utils.render import render
@app.route('/')
def index():
return render('index.html')
return render('index.html', styles=["+css/homepage.css"])
@app.errorhandler(404)

View File

@ -34,7 +34,8 @@ def poll_vote(poll_id):
db.session.add(answer)
db.session.commit()
flash('Le vote a été pris en compte', 'info')
flash('Le vote a été pris en compte', 'ok')
app.v5logger.info(f"<{current_user.name}> has voted on the poll #{poll.id}")
if request.referrer:
return redirect(request.referrer)

View File

@ -84,6 +84,10 @@ def edit_post(postid):
for a, file in attachments:
a.set_file(file)
flash('Modifications enregistrées', 'ok')
admin_msg = "[admin] " if current_user != p.author else ""
app.v5logger.info(f"{admin_msg}<{current_user.name}> has edited the post #{p.id}")
# Determine topic URL now, in case forum was changed
if isinstance(p, Topic):
return redirect(url_for('forum_topic', f=p.forum, page=(p,1)))
@ -111,6 +115,10 @@ def delete_post(postid):
if not current_user.can_delete_post(p):
abort(403)
# Is a penalty deletion
is_penalty = request.args.get('penalty') == 'True' \
and current_user.priv('delete.posts')
# Users who need to have their trophies updated
authors = set()
@ -126,16 +134,21 @@ def delete_post(postid):
authors.add(comment.author)
if isinstance(p.author, Member):
factor = 3 if request.args.get('penalty') == 'True' else 1
factor = 3 if is_penalty else 1
p.author.add_xp(xp * factor)
db.session.merge(p.author)
authors.add(p.author)
admin_msg = "[admin] " if current_user != p.author else ""
p.delete()
db.session.commit()
for author in authors:
author.update_trophies("new-post")
flash("Le contenu a été supprimé", 'ok')
penalty_msg = " (with penalty)" if is_penalty else ""
app.v5logger.info(f"{admin_msg}<{current_user.name}> has deleted the post #{p.id}{penalty_msg}")
return redirect(next_page)
@ -149,6 +162,9 @@ def set_post_topcomment(postid):
comment.thread.top_comment = comment
db.session.add(comment.thread)
db.session.commit()
flash("Le post a été défini comme nouvel en-tête", 'ok')
admin_msg = "[admin] " if current_user != comment.author else ""
app.v5logger.info(f"{admin_msg}<{current_user.name}> has set a new top comment on thread #{comment.thread.id}")
return redirect(request.referrer)
@ -191,8 +207,11 @@ def move_post(postid):
comment.thread = thread
db.session.add(comment)
db.session.commit()
flash("Le topic a été déplacé", 'ok')
admin_msg = "[admin] " if current_user != comment.author else ""
app.v5logger.info(f"{admin_msg}<{current_user.name}> has moved the comment #{comment.id} to thread #{thread.id}")
return redirect(url_for('forum_topic', f=t.forum, page=(t,1)))
return render('post/move_post.html', comment=comment,
search_form=search_form, move_form=move_form)
@ -212,8 +231,10 @@ def lock_thread(postid):
db.session.commit()
if post.thread.locked:
flash(f"Le thread a été verrouillé")
flash(f"Le thread a été verrouillé", 'ok')
app.v5logger.info(f"[admin] <{current_user.name}> has locked the thread #{post.thread.id}")
else:
flash(f"Le thread a été déverrouillé")
flash(f"Le thread a été déverrouillé", 'ok')
app.v5logger.info(f"[admin] <{current_user.name}> has unlocked the thread #{post.thread.id}")
return redirect(request.referrer)

View File

@ -4,6 +4,7 @@ from app.models.program import Program
from app.models.comment import Comment
from app.models.thread import Thread
from app.utils.render import render
from app.utils.glados import say, BOLD
from app.forms.forum import CommentForm, AnonymousCommentForm
from config import V5Config
@ -41,6 +42,10 @@ def program_view(page):
current_user.update_trophies('new-post')
flash('Message envoyé', 'ok')
app.v5logger.info(f"<{c.author.name}> has posted a the comment #{c.id}")
say(f"Nouveau commentaire de {author.name} sur le programme : {BOLD}{p.name}{BOLD}")
say(url_for('program_view', page=(p, "fin"), _anchor=str(c.id), _external=True))
# Redirect to empty the form
return redirect(url_for('program_view', page=(p, "fin"), _anchor=str(c.id)))

View File

@ -5,6 +5,7 @@ from app.models.comment import Comment
from app.models.tag import Tag
from app.models.attachment import Attachment
from app.utils.render import render
from app.utils.glados import say, BOLD
from app.forms.programs import ProgramCreationForm
from flask_login import login_required, current_user
@ -54,6 +55,10 @@ def program_submit():
current_user.update_trophies('new-program')
flash('Le programme a bien été soumis', 'ok')
return redirect(url_for('program_index'))
app.v5logger.info(f"<{p.author.name}> has submitted the program #{c.id}")
say(f"Nouveau programme de {current_user.name} : {BOLD}{p.name}{BOLD}")
say(url_for('program_view', page=(p, 1), _external=True))
return redirect(url_for('program_view', page=(p, 1)))
return render('/programs/submit.html', form=form)

View File

@ -163,6 +163,7 @@ input[type="submit"]:focus {
background: var(--links);
color: var(--warn-text);
border-radius: 1px;
overflow: hidden;
}
.skip-to-content-link:focus {
transform: translateY(0%);

View File

@ -1,135 +1,100 @@
.home-title {
margin: 20px 0;
padding: 10px 5%;
background: #bf1c11;
box-shadow: 0 2px 2px rgba(0,0,0,.3);
border-top: 10px solid #ab170c;
}
.home-title h1 {
margin-top: 0;
color: #ffffff;
border-color: #ffffff;
}
.home-title p {
margin-bottom: 0;
text-align: justify;
color: #ffffff;
}
.home-title a {
color: inherit;
text-decoration: underline;
}
.home-pinned-content > div {
display: flex;
justify-content: space-between;
}
.home-pinned-content h2 {
display: block;
margin: 5px 0;
font-size: 18px;
font-family: NotoSans;
font-weight: 200;
line-height: 20px;
}
.home-pinned-content a {
display: block;
}
.home-pinned-content a:hover img,
.home-pinned-content a:focus img {
filter: blur(3px);
}
.home-pinned-content a:hover div,
.home-pinned-content a:focus div {
padding: 200px 5% 10px 5%;
background-image: linear-gradient(180deg,transparent 0,rgba(0,0,0,.7)40px,rgba(0,0,0,.8));
}
.home-pinned-content img {
width: 100%;
filter: blur(0px);
}
.home-pinned-content article {
flex-grow: 1;
margin: 0 1px;
padding: 0;
position: relative;
max-width: 250px;
overflow: hidden;
}
.home-pinned-content article div {
position: absolute;
bottom: 0;
z-index: 3;
.home-pinned-content {
width: 90%;
margin: 0;
padding: 30px 5% 10px 5%;
color: #ffffff;
text-shadow: 1px 1px 0 rgba(0,0,0,.6);
background-image: linear-gradient(180deg,transparent 0,rgba(0,0,0,.7)40px,rgba(0,0,0,.8));
display: grid;
grid-template-areas: 'banner news''welcome news''shout news''projects projects';
grid-template-rows: auto auto minmax(200px,1fr)auto;
grid-template-columns: 4fr 3fr;
}
.home-articles {
.home-pinned-content > * {
margin: 10px 20px;
}
.home-pinned-content > * h1 {
font-size: 18px;
}
@media screen and (max-width:1449px) {
.home-pinned-content {
width: 97%;
}
}
@media screen and (max-width:1199px) {
.home-pinned-content {
width: 100%;
grid-template-areas: 'welcome''banner''news''shout''projects';
grid-template-rows: auto;
grid-template-columns: 1fr;
}
}
.home-banner {
grid-area: banner;
text-align: center;
}
.home-banner img {
max-width: 100%;
}
.home-welcome {
grid-area: welcome;
display: flex;
justify-content: space-between;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.home-articles > div {
.home-welcome h1 {
width: 100%;
margin-bottom: 0;
}
.home-welcome ul {
padding-left: 20px;
}
.home-welcome div {
flex-grow: 1;
max-width: 48%;
}
.home-articles h1 {
display: flex;
justify-content: space-between;
align-items: center;
}
.home-articles h1 a {
padding: 0;
font-family: NotoSans;
font-size: 16px;
font-weight: 400;
color: #234d5f;
}
.home-articles h1 a:hover,
.home-articles h1 a:focus {
padding-right: 10px;
}
.home-articles p {
.home-welcome h2 {
margin: 5px 0;
text-align: justify;
color: #808080;
}
.home-articles article {
padding: 10px;
margin: 10px 0;
.home-news {
grid-area: news;
}
.home-news ul {
padding: 0;
}
.home-news li {
display: flex;
flex-direction: row;
align-items: center;
background: #ffffff;
border: 1px solid rgba(0,0,0,.2);
flex-wrap: nowrap;
padding: 10px 0;
border-bottom: var(--hr-border);
}
.home-articles article > img {
float: left;
margin-right: 10px;
flex-shrink: 0;
.home-news li > a {
align-self: baseline;
}
.home-articles article > img.screeshot {
width: 128px;
height: 64px;
.home-news li img {
max-width: 100px;
max-height: 100px;
margin-right: 8px;
}
.home-articles article > div {
flex-shrink: 1;
}
.home-articles article h3 {
.home-news li h3 {
margin: 0;
color: #424242;
font-weight: normal;
font-size: 16px;
font-weight: bold;
font-family: Cantarell;
}
.home-articles article a:hover,
.home-articles article a:focus {
text-decoration: underline;
.home-news li .date {
margin: 4px 0 10px 0;
}
.home-articles .metadata {
margin: 0;
color: #22292c;
.home-news li div {
font-size: 13px;
line-height: 150%;
}
.home-articles .metadata a {
color: #22292c;
font-weight: 400;
font-style: italic;
@media screen and (max-width:499px) {
.home-news li {
flex-direction: column;
align-items: start;
}
}
.home-shoutbox {
grid-area: shout;
}
.home-projects {
grid-area: projects;
}

View File

@ -0,0 +1 @@
../../../submodules/v5shoutbox/style.css

View File

@ -161,6 +161,7 @@ button, .button, input[type="button"], input[type="submit"] {
background: var(--links);
color: var(--warn-text);
border-radius: 1px;
overflow: hidden;
&:focus {
transform: translateY(0%);

View File

@ -1,140 +1,131 @@
/*
home-title
*/
@import "vars";
.home-title {
margin: 20px 0; padding: 10px 5%;
background: #bf1c11; box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
border-top: 10px solid #ab170c;
.home-pinned-content {
width: 90%;
display: grid;
grid-template-areas:
'banner news'
'welcome news'
'shout news'
'projects projects';
grid-template-rows: auto auto minmax(200px, 1fr) auto;
grid-template-columns: 4fr 3fr;
h1 {
margin-top: 0;
color: #ffffff; border-color: #ffffff;
@media screen and (max-width: @normal) {
width: 97%;
}
p {
margin-bottom: 0; text-align: justify;
color: #ffffff;
@media screen and (max-width: @small) {
width: 100%;
grid-template-areas:
'welcome'
'banner'
'news'
'shout'
'projects';
grid-template-rows: auto;
grid-template-columns: 1fr;
}
a {
color: inherit; text-decoration: underline;
& > * {
//border: 1px solid red;
margin: 10px 20px;
h1 {
font-size: 18px;
}
}
}
.home-banner {
grid-area: banner;
text-align: center;
img {
max-width: 100%;
}
}
/*
pinned-content
*/
.home-welcome {
grid-area: welcome;
.home-pinned-content {
& > div {
display: flex; justify-content: space-between;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
h1 {
width: 100%;
margin-bottom: 0;
}
ul {
padding-left: 20px;
}
div {
flex-grow: 1;
}
h2 {
display: block; margin: 5px 0;
font-size: 18px; font-family: NotoSans; font-weight: 200;
line-height: 20px;
}
a {
display: block;
&:hover, &:focus {
img {
filter: blur(3px);
}
div {
padding: 200px 5% 10px 5%;
background-image: linear-gradient(180deg,transparent 0,rgba(0,0,0,.7) 40px,rgba(0,0,0,.8));
}
}
}
img {
width: 100%; filter: blur(0px);
}
article {
flex-grow: 1; margin: 0 1px; padding: 0;
position: relative;
max-width: 250px; overflow: hidden;
div {
position: absolute; bottom: 0; z-index: 3;
width: 90%; margin: 0;
padding: 30px 5% 10px 5%;
color: #ffffff; text-shadow: 1px 1px 0 rgba(0,0,0,.6);
background-image: linear-gradient(180deg,transparent 0,rgba(0,0,0,.7) 40px,rgba(0,0,0,.8));
}
margin: 5px 0;
}
}
.home-news {
grid-area: news;
/*
home-articles
*/
.home-articles {
display: flex; justify-content: space-between;
& > div {
flex-grow: 1; max-width: 48%;
ul {
padding: 0;
}
h1 {
display: flex; justify-content: space-between; align-items: center;
li {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
padding: 10px 0;
a {
padding: 0;
font-family: NotoSans; font-size: 16px;
font-weight: 400; color: /*#015078*/ /*#bf1c11*/ #234d5f;
border-bottom: var(--hr-border);
&:hover, &:focus {
padding-right: 10px;
}
}
}
p {
margin: 5px 0;
text-align: justify;
color: #808080;
}
article {
padding: 10px; margin: 10px 0; display: flex; align-items: center;
background: #ffffff; border: 1px solid rgba(0, 0, 0, .2);
& > img {
float: left; margin-right: 10px; flex-shrink: 0;
&.screeshot {
width: 128px; height: 64px;
}
@media screen and (max-width: @micro) {
flex-direction: column;
align-items: start;
}
& > div {
flex-shrink: 1;
& > a {
align-self: baseline;
}
img {
max-width: 100px;
max-height: 100px;
margin-right: 8px;
}
h3 {
margin: 0;
color: #424242; font-weight: normal;
font-size: 16px;
font-weight: bold;
font-family: Cantarell;
}
a:hover, a:focus {
text-decoration: underline;
.date {
margin: 4px 0 10px 0;
}
}
.metadata {
margin: 0;
color: #22292c;
a {
color: #22292c; font-weight: 400; font-style: italic;
div {
font-size: 13px;
line-height: 150%;
}
}
}
.home-shoutbox {
grid-area: shout;
}
.home-projects {
grid-area: projects;
}

View File

@ -0,0 +1 @@
../../../submodules/v5shoutbox/v5shoutbox.js

View File

@ -39,7 +39,7 @@
{% endfor %}
</div>
<div>
{{ form.guidelines.label }}
<label for="guidelines">J'accepte les <a href="#">CGU</a></label>
{{ form.guidelines() }}
{% for error in form.guidelines.errors %}
<span class="msgerror">{{ error }}</span>

View File

@ -14,8 +14,8 @@
{% include "base/flash.html" %}
<div id="main-content"></div>
{% block content %}
<div id="main-content"></div>
{% endblock %}
{% include "base/footer.html" %}

View File

@ -4,5 +4,11 @@
{% if current_user.is_authenticated and current_user.priv('misc.dev-infos') %}
<p>Page générée en {{ "%.3f" % g.request_time() }} secondes.</p>
{% endif %}
<<<<<<< HEAD
<<<<<<< HEAD
<p>Ceci est un environnement de test. Tout contenu peut être supprimé sans avertissement préalable.</p>
=======
>>>>>>> e15005a... Ajout des stats sur la durée de chargement
=======
>>>>>>> e15005a427f95829bbbad8f0d625ab9cb0c30e69
</footer>

View File

@ -9,7 +9,7 @@
</form>
<div id="spotlight">
<a href="/forum/actus/projets/2/fin/avancees-de-la-v5" class="button bg-error">Infos sur l'avancée de la v5</a>
<a href="/forum/projets/2/fin/avancees-de-la-v5" class="button bg-error">Infos sur l'avancée de la v5</a>
</div>
{% if current_user.is_authenticated and current_user.priv('misc.dev-infos') %}

View File

@ -1,4 +1,6 @@
{% for s in scripts %}
<script type="text/javascript" src={{url_for('static', filename=s)}}></script>
{% endfor %}
<script type="module" src={{url_for('static', filename='scripts/emoji-picker-element/index.js')}}></script>
{% for m in modules %}
<script type="module" src={{url_for('static', filename=m)}}></script>
{% endfor %}

10
app/templates/chat.html Normal file
View File

@ -0,0 +1,10 @@
{% set tabtitle = "Shoutbox" %}
<!DOCTYPE html>
<html lang="fr-FR">
{% include "base/head.html" with context %}
<body>
{% include "widgets/v5shoutbox.html" %}
{% include "base/scripts.html" %}
</body>
</html>

View File

@ -8,11 +8,114 @@
{% block content %}
<section class="home-pinned-content">
<div>
<div class="home-banner">
<img src="https://www.planet-casio.com/storage/staff/CPC30-banner.png" />
</div>
<div class="home-welcome">
<h1>Bienvenue sur Planète Casio !</h1>
<p>Planète Casio est la communauté française de référence pour toutes les calculatrices Casio.
Apprenez à utiliser votre machine, téléchargez et partagez des programmes, ou initiez-vous à l'informatique sur le forum.
Ou bien venez développer des jeux avec nous pour passer le temps !</p>
<div>
<h2>Les calculatrices</h2>
<ul>
<li>Tout sur sa Casio</li>
<li>Graph 25+E II</li>
<li>Graph 35+E II</li>
<li>Graph 90+E</li>
<li>Classpad 400+E</li>
<li>Comparer les calculatrices CASIO</li>
</ul>
</div>
<div>
<h2>La communauté</h2>
<ul>
<li>Sinscrire ou se connecter</li>
<li>Index du forum</li>
<li>Nos jeux outils et cours</li>
</ul>
</div>
<div>
<h2>Programmer</h2>
<ul>
<li>Apprendre à programmer</li>
<li>Articles et astuces</li>
</ul>
</div>
</div>
<div class="home-news">
<h1>Actualitées</h1>
<ul>
<li>Inscription : dans le menu "Compte" à gauche (les comptes seront ultimement reconnectés à la version originale du site)</li>
<li>Le forum est fonctionnel, les programmes arrivent sous peu.</li>
<li>Pour toute demande particulière, vous pouvez envoyer un email à <code>contact (at) planet-casio (dot) com</code>.</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/tdm9_collision2.jpg"/></a>
<div>
<h3><a href="#">Les collisions — partie 2</a></h3>
<p class="date"><i>Publié par <a href="#">Shadow15510</a> le <time>09/06/2023 09:10</time></i></p>
La deuxième partie du tutoriel du mercredi sur les collisions est maintenant disponible en vidéo.
</div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-thumb-results.png"/></a>
<div>
<h3><a href="#">Résultats du CPC #30 — Les profondeurs !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>08/06/2023 22:10</time></i></p>
Les programmes sont profonds et avec un peu de chance les tests aussi ! </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/tituya-thumb.png"/></a>
<div>
<h3><a href="#">Un second renardministrateur !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>07/06/2023 22:55</time></i></p>
Vive la renardocratie ! </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/massy2.jpg"/></a>
<div>
<h3><a href="#">Visite chez Casio France à Massy, musée inclus</a>
</h3>
<p class="date"><i>Publié par <a href="#">Critor</a> le <time>05/06/2023 11:51</time></i></p>
Compte-rendu de notre visite au sein même des locaux de Casio France à Massy en mai 2023, passage par leur musée privé inclus. </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-title.jpg"/></a>
<div>
<h3><a href="#">Le CPC#30 - Les Profondeurs ... C'est désormais terminé ... Bravo à tous les participants.</a>
</h3>
<p class="date"><i>Publié par <a href="#">Slyvtt</a> le <time>03/06/2023 21:36</time></i></p>
Aujourd'hui sonne la fin du CPC#30 avec à la clef 6 participations pour vous donner du fun. Graphs Monochromes et Couleurs sont à la fête avec des programmes en Basic et des Addins. </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/tdm9_miniature.jpg"/></a>
<div>
<h3><a href="#">Les Tutos du Mercredi débarquent sur Youtube !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Shadow15510</a> le <time>29/05/2023 14:15</time></i></p>
</div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-thumb.jpg"/></a>
<div>
<h3><a href="#">Le CPC #30 - Les profondeurs !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>27/05/2023 18:00</time></i></p>
Le concours CPC revient en force et c'est le moment de se plonger (métaphoriquement <i>et</i> littéralement !) dans le game design fin et la programmation sportive. Une semaine pour les programmer tous, et dans les profondeurs les li— oups ! </div>
</li>
<li><p><i><a href="https://www.planet-casio.com/Fr/forums/partie6-.html">Voir toutes les news</a></i></p></li>
</ul>
</div>
<div class="home-shoutbox" style="border: 1px solid #737373; padding: 20px">
<div id="v5shoutbox">
Ici yaura la shoutbox (plus tard)
</div>
</div>
<div class="home-projects">
<h1>Projets du moment</h1>
<ul>
<li>La v5, bien sûr</li>
<li>Chaos drop</li>
<li>Un easter egg</li>
</ul>
</div>
</section>

View File

@ -2,7 +2,7 @@
{% import "widgets/editor.html" as widget_editor %}
{% import "widgets/tag_selector.html" as widget_tag_selector with context %}
{% set tabtitle = f"Programmes - Soumettre un programme" %}
{% set tabtitle = "Programmes - Soumettre un programme" %}
{% block title %}
<a href="{{ url_for('program_index') }}">Programmes</a> » <h1>Soumettre un programme</h1>

View File

@ -32,7 +32,7 @@
<a href="{{ url_for('move_post', postid=post.id) }}">Déplacer</a>
{% endif %}
{% if can_punish %}
{% if can_punish and post.author.type == "member"%}
<a href="{{ url_for('delete_post', postid=post.id, penalty=False, csrf_token=csrf_token()) }}" onclick="return confirm('Le post sera supprimé.')">Supprimer{{ suffix }} (normal)</a>
<a href="{{ url_for('delete_post', postid=post.id, penalty=True, csrf_token=csrf_token()) }}" onclick="return confirm('Le post sera supprimé avec pénalité d\'XP.')">Supprimer{{ suffix }} (pénalité)</a>
{% elif can_delete %}

View File

@ -0,0 +1 @@
../../../submodules/v5shoutbox/widget.html

View File

@ -36,4 +36,4 @@ def say(msg, channels = ["#general"]):
def new_topic(topic):
""" Example wrapper for glados.say """
say(f"Le topic {BOLD}{topic.title}{BOLD} a été créé")
say(f"Nouveau topic de {topic.author.name}: {BOLD}{topic.title}{BOLD}")

View File

@ -16,18 +16,24 @@ def get_member(username):
return None
def edit(user, fields):
def edit(old_username, new_member):
""" Edit a user. Fields is {'name': ['value'], …} """
old_username = normalize(old_username)
conn = ldap.initialize("ldap://localhost")
# TODO: do this
# Connect as root
# conn.simple_bind_s(f'cn=ldap-root,{V5Config.LDAP_ENV}',
# V5Config.LDAP_PASSWORD)
# old_value = {"userPassword": ["my_old_password"]}
# new_value = {"userPassword": ["my_new_password"]}
conn.simple_bind_s(f'cn=ldap-root,{V5Config.LDAP_ROOT}',
V5Config.LDAP_PASSWORD)
# Create values fields
old_dn = f'cn={old_username},{V5Config.LDAP_ENV},{V5Config.LDAP_ROOT}'
new_dn = f'cn={new_member.norm}'
new_values = [
(ldap.MOD_REPLACE, 'sn', [new_member.norm.encode('utf-8')]),
(ldap.MOD_REPLACE, 'displayName', [new_member.name.encode('utf-8')]),
(ldap.MOD_REPLACE, 'mail', [new_member.email.encode('utf-8')]),
]
# modlist = modifyModlist(old_value, new_value)
# conn.modify_s(dn, modlist)
conn.modify_s(old_dn, new_values)
conn.rename_s(old_dn, new_dn)
def set_email(user, email):

View File

@ -1,7 +1,7 @@
from flask import render_template
from flask_login import current_user
def render(*args, styles=[], scripts=[], **kwargs):
def render(*args, styles=[], scripts=[], modules=[], **kwargs):
# Pour jouer sur les feuilles de style ou les scripts :
# render('page.html', styles=['-css/form.css', '+css/admin/forms.css'])
@ -29,6 +29,9 @@ def render(*args, styles=[], scripts=[], **kwargs):
'scripts/tag_selector.js',
'scripts/editor.js',
]
modules_ = [
'scripts/emoji-picker-element/index.js',
]
# Apply theme from user settings
theme = current_user.theme if current_user.is_authenticated else ''
@ -56,4 +59,10 @@ def render(*args, styles=[], scripts=[], **kwargs):
if s[0] == '+':
scripts_.append(s[1:])
return render_template(*args, **kwargs, styles=styles_, scripts=scripts_)
for m in modules:
if m[0] == '-':
modules_.remove(m[1:])
if m[0] == '+':
modules_.append(m[1:])
return render_template(*args, **kwargs, styles=styles_, scripts=scripts_, modules=modules_)

View File

@ -1,4 +1,5 @@
import os
import logging
from datetime import timedelta
try:
@ -34,7 +35,7 @@ class FlaskApplicationSettings(object):
+ LocalConfig.DB_NAME
SQLALCHEMY_TRACK_MODIFICATIONS = False
MAIL_DEFAULT_SENDER = "noreply@v5.planet-casio.com"
MAIL_DEFAULT_SENDER = "noreply-v5@planet-casio.com"
MAIL_SUPPRESS_SEND = None
# Only send cookies over HTTPS connections (use only if HTTPS is enabled)
@ -86,6 +87,18 @@ class DefaultConfig(object):
# Tab title prefix. Useful to dissociate local/dev/prod tabs
TABTITLE_PREFIX = ""
@staticmethod
def v5logger():
""" A fully configured logger for v5 activity logs """
logger = logging.getLogger('v5')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s')
handler = logging.FileHandler('v5_activity.log')
handler.setLevel(logging.DEBUG)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
class V5Config(LocalConfig, DefaultConfig):
"""

@ -1 +0,0 @@
Subproject commit 1b7ae3d2e3c609a4424f90249d8fea7b879d52e5

@ -1 +0,0 @@
Subproject commit f57420ce88b4ef69ecd6f4127c41146f53e709f7