Compare commits
69 Commits
Author | SHA1 | Date |
---|---|---|
Eragon | c051f754a1 | |
Eragon | 853193cbd8 | |
Eragon | 2801f8c1cd | |
Eragon | 92a5e4f05a | |
Darks | 333892aa54 | |
Darks | 87090111ae | |
Eragon | f7e91c4793 | |
Eragon | 1fa5dd8085 | |
Eragon | 5283d45c36 | |
Eragon | e41bfaddff | |
Eragon | 0b524f19b7 | |
Eragon | 6af47872cb | |
Eragon | cf21b4ee56 | |
Eragon | 39ec4c3c4a | |
Lephe | cddd4baa8a | |
Eragon | 47c3b82e1a | |
Eragon | 4a23aee7d9 | |
Eragon | 0c3fa843d8 | |
Eragon | 9c49671b73 | |
Eragon | 4ba7c10786 | |
Eragon | fee66d22bd | |
Eragon | 2d1695e1ee | |
Eragon | 25f63b4648 | |
Eragon | f194baae00 | |
Eragon | 7c098b2209 | |
Eragon | f02d70a7e5 | |
Eragon | 0e31abbdfd | |
Eragon | 21b73eef19 | |
Eragon | da037f677e | |
Eragon | a039d1b500 | |
Eragon | 8960ca22cd | |
Eragon | 8937bc902e | |
Eragon | e52b3ebe41 | |
Eragon | 7e28531106 | |
Eragon | 621cd40659 | |
Eragon | 2d7271723f | |
Eragon | 17f67682b6 | |
Eragon | 3a875253b4 | |
Eragon | b94f4c5944 | |
Eragon | d39067e586 | |
Eragon | bb5534c0ee | |
Eragon | 4f6586e3f6 | |
Lephe | 40a5d54c49 | |
Lephe | 13c1b30ad6 | |
Lephe | 4e80932588 | |
Darks | af61b21fc8 | |
Darks | c68b9b2048 | |
Darks | 2b4f3f34b0 | |
Darks | 4797433dcd | |
Eragon | 69dcff26ea | |
Darks | d3c790db2d | |
Darks | 5b1ebfbf7d | |
Darks | 14f7dbc34f | |
Eragon | cc725eb92f | |
Darks | 586c045604 | |
IniKiwi | 3d22fc5e03 | |
IniKiwi | 5369da453b | |
Lephe | 6a34a42081 | |
Lephe | c679f65406 | |
Eragon | a2ce27eee3 | |
Eragon | 643ff995d4 | |
Eragon | 912380ddf1 | |
Darks | 45e34718f5 | |
Eragon | a582053ba6 | |
Darks | 50a2ec69c2 | |
Darks | 6b74b6fea6 | |
Darks | d05942b660 | |
Darks | 8d90f640b6 | |
Darks | 4df78eb0c3 |
|
@ -22,6 +22,9 @@ test.*
|
|||
# Autosaves
|
||||
*.dia~
|
||||
|
||||
## Logging files
|
||||
*.log
|
||||
|
||||
|
||||
## Deployment files
|
||||
|
||||
|
|
|
@ -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
|
23
Makefile
|
@ -2,20 +2,27 @@ CSSC := lesscpy
|
|||
src := $(wildcard app/static/less/*.less)
|
||||
obj := $(src:app/static/less/%.less=app/static/css/%.css)
|
||||
|
||||
# PCv5-extra
|
||||
TAR := tar
|
||||
extra-uri := https://gitea.planet-casio.com/devs/PCv5-extra/releases/download/v20240308/PCv5-extra.tar.zstd
|
||||
extra-path := PCv5-extra.tar.zstd
|
||||
# wget or curl or something else ?
|
||||
DOWNLOADER := wget -O $(extra-path) $(extra-uri)
|
||||
|
||||
run: css
|
||||
@flask run
|
||||
|
||||
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) $< $@
|
||||
|
||||
clean-extra:
|
||||
rm -rf extra/*
|
||||
|
||||
get-extra: clean-extra
|
||||
$(DOWNLOADER)
|
||||
$(TAR) -xf $(extra-path)
|
||||
rm $(extra-path)
|
||||
|
||||
.PHONY: css run
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -4,6 +4,7 @@ from wtforms.fields.datetime import DateField
|
|||
from wtforms.fields.simple import EmailField
|
||||
from wtforms.validators import InputRequired, Optional, Email, EqualTo
|
||||
from flask_wtf.file import FileField # Cuz' wtforms' FileField is shitty
|
||||
from app.utils.antibot_field import AntibotField
|
||||
import app.utils.validators as vd
|
||||
|
||||
|
||||
|
@ -39,6 +40,8 @@ class RegistrationForm(FlaskForm):
|
|||
newsletter = BooleanField(
|
||||
'Inscription à la newsletter',
|
||||
description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.')
|
||||
|
||||
ab = AntibotField()
|
||||
|
||||
submit = SubmitField("S'inscrire")
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, TextAreaField, MultipleFileField, SelectField
|
||||
from wtforms.validators import InputRequired, Length
|
||||
from wtforms import StringField, SubmitField, TextAreaField, MultipleFileField, SelectField, DecimalField
|
||||
from wtforms.validators import InputRequired, Length, Optional
|
||||
import app.utils.validators as vd
|
||||
from app.utils.antibot_field import AntibotField
|
||||
|
||||
|
@ -54,6 +54,14 @@ class TopicCreationForm(CommentForm):
|
|||
'Nom du sujet',
|
||||
validators=[InputRequired(), Length(min=3, max=128)])
|
||||
|
||||
summary = TextAreaField(
|
||||
'Résumé',
|
||||
validators=[Optional(), Length(min=3, max=128)])
|
||||
|
||||
thumbnail = DecimalField(
|
||||
'N° de l’illustration',
|
||||
validators=[Optional(), vd.attachment_exists])
|
||||
|
||||
submit = SubmitField('Créer le sujet')
|
||||
|
||||
|
||||
|
@ -61,11 +69,7 @@ class AnonymousTopicCreationForm(TopicCreationForm, AnonymousCommentForm):
|
|||
ab = AntibotField()
|
||||
|
||||
|
||||
class TopicEditForm(CommentEditForm):
|
||||
title = StringField(
|
||||
'Nom du sujet',
|
||||
validators=[InputRequired(), Length(min=3, max=128)])
|
||||
|
||||
class TopicEditForm(TopicCreationForm):
|
||||
# List of forums is generated at runtime
|
||||
forum = SelectField(
|
||||
'Forum',
|
||||
|
|
|
@ -10,3 +10,8 @@ class MovePost(FlaskForm):
|
|||
class SearchThread(FlaskForm):
|
||||
name = StringField("Nom d'un topic, programme, …")
|
||||
search = SubmitField('Rechercher')
|
||||
|
||||
class MergePost(FlaskForm):
|
||||
# List of posts is generated at runtime
|
||||
post = SelectField('Fusionner avec', coerce=int, validators=[])
|
||||
submit = SubmitField('Fusionner')
|
||||
|
|
|
@ -1,13 +1,63 @@
|
|||
from flask_login import current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField
|
||||
from wtforms.fields.datetime import DateField
|
||||
from wtforms.validators import InputRequired, Optional
|
||||
from wtforms.fields import StringField, SubmitField, SelectField, SelectMultipleField, DateField
|
||||
from app.models.forum import Forum
|
||||
|
||||
|
||||
# TODO: compléter le formulaire de recherche avancée
|
||||
class SearchForm(FlaskForm):
|
||||
class Meta:
|
||||
csrf = False
|
||||
q = StringField('Rechercher', validators=[InputRequired()])
|
||||
|
||||
|
||||
class AdvancedSearchForm(SearchForm):
|
||||
date = DateField('Date', validators=[Optional()])
|
||||
def generate_choices():
|
||||
choices = {'Forum': [
|
||||
'Tous',
|
||||
'Actualités',
|
||||
'Aide et questions',
|
||||
'Forum des projets',
|
||||
'Vie communautaire',
|
||||
], 'Programmes': [
|
||||
'Tous',
|
||||
'Jeux',
|
||||
'Utilitaires',
|
||||
'Logiciels'
|
||||
], 'Utilisateurs': [
|
||||
'Tous'
|
||||
], 'Tutoriels': [
|
||||
'Tous',
|
||||
'Basic',
|
||||
'C/C++',
|
||||
'Arduino',
|
||||
'Python'
|
||||
], 'Sprites': [
|
||||
'Tous',
|
||||
'Personnages',
|
||||
'Environnements',
|
||||
'Objets',
|
||||
'Interfaces'
|
||||
]}
|
||||
# Forum reserved for admins and moderators
|
||||
f = Forum.query.filter_by(url='/admin').first()
|
||||
if (current_user.is_authenticated and current_user.can_access_forum(f)):
|
||||
choices['Forum'].append('Administration')
|
||||
# Forum reserved to members of CreativeCalc
|
||||
f = Forum.query.filter_by(url='/creativecalc').first()
|
||||
if (current_user.is_authenticated and current_user.can_access_forum(f)):
|
||||
choices['Forum'].append('CreativeCalc')
|
||||
return choices
|
||||
|
||||
sortBy = SelectField('Trier',
|
||||
choices={'Pertinence': ['Pertinence'],
|
||||
'Date': ['Date croissante',
|
||||
'Date décroissante'],
|
||||
'Ordre Alphabétique': [
|
||||
'Alphabétique croissant',
|
||||
'Alphabétique décroissant',]},
|
||||
validators=[Optional()])
|
||||
date = DateField('Date de publication', validators=[Optional()])
|
||||
scope = SelectMultipleField('', choices=generate_choices, validators=[Optional()])
|
||||
submit = SubmitField('Affiner la recherche')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from app import db
|
||||
from app.models.post import Post
|
||||
from sqlalchemy.orm import backref
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
class Topic(Post):
|
||||
__tablename__ = 'topic'
|
||||
|
@ -29,10 +30,17 @@ class Topic(Post):
|
|||
backref=backref('topics', lazy='dynamic'), foreign_keys=forum_id)
|
||||
|
||||
# Associated thread
|
||||
thread_id = db.Column(db.Integer,db.ForeignKey('thread.id'),nullable=False)
|
||||
thread_id = db.Column(db.Integer, db.ForeignKey('thread.id'), nullable=False)
|
||||
thread = db.relationship('Thread', foreign_keys=thread_id,
|
||||
back_populates='owner_topic')
|
||||
|
||||
# Summary
|
||||
summary = db.Column(db.UnicodeText)
|
||||
|
||||
# ID of thumbnail
|
||||
thumbnail_id = db.Column(UUID(as_uuid=True), db.ForeignKey('attachment.id'), nullable=True)
|
||||
thumbnail = db.relationship('Attachment', foreign_keys=thumbnail_id, lazy='joined')
|
||||
|
||||
# Number of views in the forum
|
||||
views = db.Column(db.Integer)
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ class Member(User):
|
|||
if comment.type != "comment":
|
||||
return False
|
||||
post = comment.thread.owner_post
|
||||
return self.can_edit_post(post) and (comment.author == post.author)
|
||||
return self.can_edit_post(post)
|
||||
|
||||
def can_lock_thread(self, post):
|
||||
"""Whether this member can lock the thread associated with the post"""
|
||||
|
@ -270,6 +270,11 @@ class Member(User):
|
|||
return False
|
||||
return self.priv("lock.threads")
|
||||
|
||||
def can_merge_post(self, post):
|
||||
"""Whether this member can merge the post"""
|
||||
# NOTE: Might need more check than this
|
||||
return self.can_edit_post(post)
|
||||
|
||||
def can_access_file(self, file):
|
||||
"""Whether this member can access the file."""
|
||||
return self.can_access_post(file.comment)
|
||||
|
@ -336,7 +341,7 @@ class Member(User):
|
|||
self.avatar_filename)
|
||||
# Resize & convert image
|
||||
im = Image.open(avatar)
|
||||
im.thumbnail((128, 128), Image.ANTIALIAS)
|
||||
im.thumbnail((128, 128), Image.Resampling.LANCZOS)
|
||||
|
||||
# Change avatar id
|
||||
# TODO: verify concurrency behavior
|
||||
|
@ -397,8 +402,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()
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# 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
|
||||
from app.routes.forum import index, topic
|
||||
from app.routes.polls import vote, delete
|
||||
from app.routes.posts import edit
|
||||
from app.routes.posts import edit, redirect
|
||||
from app.routes.programs import index, submit, program
|
||||
from app.routes.api import markdown
|
||||
|
||||
|
|
|
@ -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,16 +43,24 @@ def edit_account():
|
|||
newsletter=form.newsletter.data,
|
||||
theme=form.theme.data
|
||||
)
|
||||
if V5Config.USE_LDAP:
|
||||
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')
|
||||
else:
|
||||
form.theme.data = current_user.theme or 'default_theme'
|
||||
form.signature.data = current_user.signature
|
||||
form.biography.data = current_user.bio
|
||||
|
||||
form.signature.data = current_user.signature
|
||||
form.biography.data = current_user.bio
|
||||
return render('account/account.html', scripts=["+scripts/entropy.js"],
|
||||
form=form)
|
||||
|
||||
|
@ -62,6 +72,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 +98,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 +114,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 +125,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 +158,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 +196,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 s’est inscrit ! Il s’agit de {BOLD}{m.name}{BOLD}.")
|
||||
say(url_for('user', username=m.name, _external=True))
|
||||
|
||||
return redirect(url_for('login'))
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,14 +64,21 @@ def adm_edit_account(user_id):
|
|||
newsletter=form.newsletter.data,
|
||||
xp=form.xp.data or None,
|
||||
)
|
||||
if V5Config.USE_LDAP:
|
||||
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')
|
||||
else:
|
||||
form.signature.data = user.signature
|
||||
form.biography.data = user.bio
|
||||
|
||||
# Trophies
|
||||
if trophy_form.submit.data:
|
||||
|
@ -85,6 +93,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 +111,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 +158,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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
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',
|
||||
scripts=[
|
||||
'-scripts/trigger_menu.js',
|
||||
'-scripts/editor.js'])
|
||||
|
||||
@app.route('/v5shoutbox_worker.js')
|
||||
def v5shoutbox_worker_js():
|
||||
return send_file('static/scripts/v5shoutbox_worker.js')
|
|
@ -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
|
||||
|
@ -53,6 +54,7 @@ def forum_page(f, page=1):
|
|||
db.session.merge(th)
|
||||
|
||||
t = Topic(f, author, form.title.data, th)
|
||||
t.summary = form.summary.data
|
||||
db.session.add(t)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -67,12 +69,21 @@ def forum_page(f, page=1):
|
|||
for a, file in attachments:
|
||||
a.set_file(file)
|
||||
|
||||
# If there's a thumbnail, set it
|
||||
if form.thumbnail.data:
|
||||
t.thumbnail = c.attachments[int(form.thumbnail.data)-1]
|
||||
|
||||
# Update member's xp and trophies
|
||||
if current_user.is_authenticated:
|
||||
current_user.add_xp(2) # 2 points for a topic
|
||||
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
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -5,7 +5,12 @@ from app.utils.render import render
|
|||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render('index.html')
|
||||
return render('index.html',
|
||||
styles=["+css/homepage.css"],
|
||||
scripts=[
|
||||
"+scripts/v5shoutbox_ui.js",
|
||||
"+scripts/v5shoutbox.js"
|
||||
])
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from app import app, db
|
||||
from app import app, db, V5Config
|
||||
from app.models.attachment import Attachment
|
||||
from app.models.comment import Comment
|
||||
from app.models.forum import Forum
|
||||
|
@ -11,17 +11,18 @@ from app.utils.render import render
|
|||
from app.utils.check_csrf import check_csrf
|
||||
from app.utils.priv_required import priv_required
|
||||
from app.forms.forum import CommentEditForm, AnonymousCommentEditForm, TopicEditForm
|
||||
from app.forms.post import MovePost, SearchThread
|
||||
from app.forms.post import MovePost, SearchThread, MergePost
|
||||
from wtforms import BooleanField
|
||||
from urllib.parse import urlparse
|
||||
from flask import redirect, url_for, abort, request, flash
|
||||
from flask_login import login_required, current_user
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy import text, and_
|
||||
from datetime import timedelta
|
||||
|
||||
@app.route('/post/editer/<int:postid>', methods=['GET','POST'])
|
||||
@login_required
|
||||
def edit_post(postid):
|
||||
# TODO: Maybe not safe
|
||||
# FIXME: Maybe not safe
|
||||
referrer = urlparse(request.args.get('r', default = '/', type = str)).path
|
||||
print(referrer)
|
||||
|
||||
|
@ -74,6 +75,12 @@ def edit_post(postid):
|
|||
|
||||
if isinstance(p, Topic):
|
||||
p.title = form.title.data
|
||||
p.summary = form.summary.data
|
||||
# If there's a thumbnail, set it
|
||||
if form.thumbnail.data:
|
||||
p.thumbnail = comment.attachments[int(form.thumbnail.data)-1]
|
||||
else:
|
||||
p.thumbnail = None
|
||||
f = Forum.query.filter_by(url=form.forum.data).first_or_404()
|
||||
if current_user.can_post_in_forum(f):
|
||||
p.forum = f
|
||||
|
@ -84,6 +91,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)))
|
||||
|
@ -98,6 +109,7 @@ def edit_post(postid):
|
|||
form.message.data = p.thread.top_comment.text
|
||||
form.title.data = p.title
|
||||
form.forum.data = p.forum.url
|
||||
form.summary.data = p.summary
|
||||
return render('forum/edit_topic.html', t=p, form=form)
|
||||
|
||||
@app.route('/post/supprimer/<int:postid>', methods=['GET','POST'])
|
||||
|
@ -111,6 +123,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 +142,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 +170,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 +215,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 +239,54 @@ 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)
|
||||
return redirect(request.referrer)
|
||||
|
||||
@app.route('/post/fusionner/<int:postid>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def merge_post(postid):
|
||||
comment = Comment.query.get_or_404(postid)
|
||||
|
||||
# Get the posts from the same user in the same topic that are separated
|
||||
# by less than V5Config.MERGE_AGE_THRESHOLD
|
||||
compatible_comments = Comment.query.filter(and_(
|
||||
Comment.thread_id == comment.thread_id,
|
||||
and_(
|
||||
Post.author_id == comment.author_id,
|
||||
and_(
|
||||
Post.date_created > comment.date_created,
|
||||
Post.date_created <= comment.date_created + timedelta(0, V5Config.MERGE_AGE_THRESHOLD)
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
merge_form = MergePost()
|
||||
merge_form.post.choices = [(t.id, f"{t.text[:30]}[…]") for t in list(compatible_comments)]
|
||||
|
||||
if merge_form.validate_on_submit():
|
||||
merge_comment = Comment.query.filter_by(id=merge_form.post.data).first_or_404()
|
||||
|
||||
comment.text += f"\n\n---\n\n{merge_comment.text}"
|
||||
|
||||
# Change the modification date only if the other post is more recent
|
||||
if merge_comment.date_created > comment.date_modified:
|
||||
comment.date_modified = merge_comment.date_created
|
||||
|
||||
if merge_comment.is_top_comment:
|
||||
comment.thread.set_top_comment(comment)
|
||||
|
||||
db.session.add(comment)
|
||||
merge_comment.delete()
|
||||
db.session.commit()
|
||||
|
||||
if isinstance(comment.thread.owner_post, Topic):
|
||||
return redirect(url_for('forum_topic', f=comment.thread.owner_post.forum, page=[comment.thread.owner_post, -1]))
|
||||
elif isinstance(comment.thread.owner_post, Program):
|
||||
return redirect(url_for('program_view', page=[comment.thread.owner_post, -1]))
|
||||
|
||||
return render('post/merge_post.html', comment=comment, merge_form=merge_form)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
from app import app
|
||||
from app.models.comment import Comment
|
||||
from app.models.thread import Thread
|
||||
from app.models.program import Program
|
||||
from flask import redirect, url_for
|
||||
|
||||
|
||||
@app.route('/post/<int:postid>', methods=['GET', 'POST'])
|
||||
def redirect_post(postid):
|
||||
c = Comment.query.get_or_404(postid)
|
||||
|
||||
owner = c.thread.owner_post
|
||||
|
||||
# Get the comments for the thread
|
||||
comments = Comment.query.where(
|
||||
Comment.thread_id == c.thread.id,
|
||||
Comment.date_created <= c.date_created
|
||||
).order_by(
|
||||
Comment.date_created.asc()
|
||||
).paginate(per_page=Thread.COMMENTS_PER_PAGE, error_out=False)
|
||||
|
||||
if owner.type == 'topic':
|
||||
# Is a topic
|
||||
url = url_for('forum_topic', f=owner.forum, page=(owner, comments.pages), _anchor=str(c.id))
|
||||
else:
|
||||
# Is a program
|
||||
url = url_for('program_view', page=(owner, comments.pages), _anchor=str(c.id))
|
||||
|
||||
return redirect(url, 301)
|
|
@ -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,9 @@ def program_view(page):
|
|||
current_user.update_trophies('new-post')
|
||||
|
||||
flash('Message envoyé', 'ok')
|
||||
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)))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,9 +1,116 @@
|
|||
from app import app
|
||||
from app.forms.search import AdvancedSearchForm
|
||||
from app import app, db
|
||||
from app.forms.search import AdvancedSearchForm, SearchForm
|
||||
from app.models.post import Post
|
||||
from app.models.comment import Comment
|
||||
from app.models.topic import Topic
|
||||
from app.models.forum import Forum
|
||||
from app.models.program import Program
|
||||
from app.utils.render import render
|
||||
from sqlalchemy import text, func, Date
|
||||
from flask import request
|
||||
from flask_sqlalchemy import Pagination
|
||||
|
||||
|
||||
@app.route('/rechercher')
|
||||
def search():
|
||||
form = AdvancedSearchForm()
|
||||
return render('search.html', form=form)
|
||||
SEARCH_RESULTS_PER_PAGE = 20
|
||||
|
||||
def paginate(data, page, per_page):
|
||||
# Based on page and per_page info, calculate start and end index of items to keep
|
||||
start_index = (page - 1) * per_page
|
||||
end_index = start_index + per_page
|
||||
|
||||
# Get the paginated list of items
|
||||
items = data[start_index:end_index]
|
||||
|
||||
# Create Pagination object
|
||||
return Pagination(None, page, per_page, len(data), items)
|
||||
|
||||
def websearch_to_tsquery_multilang(search):
|
||||
return func.websearch_to_tsquery('french', search).op('||')(func.websearch_to_tsquery('english', search))
|
||||
|
||||
def to_tsvector_multilang(text):
|
||||
return func.to_tsvector('french', text).op('||')(func.to_tsvector('english', text))
|
||||
|
||||
|
||||
@app.route('/rechercher/')
|
||||
@app.route('/rechercher/<int:page>/')
|
||||
def search(page=1):
|
||||
form = AdvancedSearchForm(request.args)
|
||||
results = list()
|
||||
if form.validate():
|
||||
tsquery = websearch_to_tsquery_multilang(form.q.data)
|
||||
# Topics are sorted first in results
|
||||
topic_query = db.session.query(Topic).where(
|
||||
to_tsvector_multilang(Topic.title).bool_op('@@')(tsquery)
|
||||
).group_by(
|
||||
Topic.id,
|
||||
Post.id
|
||||
)
|
||||
# Programms follow directly in the results
|
||||
program_query = db.session.query(Program).where(
|
||||
to_tsvector_multilang(Program.name).bool_op('@@')(tsquery)
|
||||
).group_by(
|
||||
Program.id,
|
||||
Post.id
|
||||
)
|
||||
# Comments are less important than topics and programs
|
||||
comment_query = db.session.query(Comment).where(
|
||||
to_tsvector_multilang(Comment.text).bool_op('@@')(tsquery)
|
||||
).group_by(
|
||||
Comment.id,
|
||||
Post.id
|
||||
)
|
||||
if (form.date.data):
|
||||
topic_query = topic_query.where(
|
||||
Topic.date_created.cast(Date) == form.date.data
|
||||
)
|
||||
program_query = program_query.where(
|
||||
Program.date_created.cast(Date) == form.date.data
|
||||
)
|
||||
comment_query = comment_query.where(
|
||||
Comment.date_created.cast(Date) == form.date.data
|
||||
)
|
||||
if (form.sortBy.data == "Date croissante"):
|
||||
topic_query = topic_query.order_by(
|
||||
Topic.date_created.asc()
|
||||
)
|
||||
program_query = program_query.order_by(
|
||||
Program.date_created.asc()
|
||||
)
|
||||
comment_query = comment_query.order_by(
|
||||
Post.date_created.asc()
|
||||
)
|
||||
elif (form.sortBy.data == "Date décroissante"):
|
||||
topic_query = topic_query.order_by(
|
||||
Topic.date_created.desc()
|
||||
)
|
||||
program_query = program_query.order_by(
|
||||
Program.date_created.desc()
|
||||
)
|
||||
comment_query = comment_query.order_by(
|
||||
Post.date_created.desc()
|
||||
)
|
||||
elif (form.sortBy.data == "Alphabétique croissant"):
|
||||
topic_query = topic_query.order_by(
|
||||
Topic.title.asc()
|
||||
)
|
||||
program_query = program_query.order_by(
|
||||
Program.name.asc()
|
||||
)
|
||||
comment_query = comment_query.order_by(
|
||||
Comment.text.asc()
|
||||
)
|
||||
elif (form.sortBy.data == "Alphabétique décroissant"):
|
||||
topic_query = topic_query.order_by(
|
||||
Topic.title.desc()
|
||||
)
|
||||
program_query = program_query.order_by(
|
||||
Program.name.desc()
|
||||
)
|
||||
comment_query = comment_query.order_by(
|
||||
Comment.text.desc()
|
||||
)
|
||||
|
||||
results = list(topic_query) + list(program_query) + list(comment_query)
|
||||
|
||||
results = paginate(results, page, SEARCH_RESULTS_PER_PAGE)
|
||||
return render('search.html', form=form, results=results)
|
||||
|
|
|
@ -6,3 +6,7 @@ from app.utils.render import render
|
|||
@app.route('/outils')
|
||||
def tools():
|
||||
return render('tools.html')
|
||||
|
||||
@app.route('/outils/comparateur')
|
||||
def calc_comparator():
|
||||
return render('calcdb.html', scripts=['+scripts/calcdb_info.js'], styles=['+css/calcdb.css'])
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* Controlling checkboxes */
|
||||
|
||||
#calcdb-checkboxes {
|
||||
display: grid;
|
||||
grid: repeat(5,auto) / repeat(5, 1fr);
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
#calcdb-checkboxes div {
|
||||
display: block;
|
||||
}
|
||||
#calcdb-checkboxes div > input {
|
||||
margin: 0 0 4px 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
#calcdb-checkboxes div > span {
|
||||
vertical-align: middle;
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.editor .btn-group #filler {
|
||||
.editor .btn-group .filler {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.editor .btn-group button {
|
||||
|
@ -85,4 +85,4 @@
|
|||
transform: translateX(-50%);
|
||||
top: 50vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,11 +158,12 @@ input[type="submit"]:focus {
|
|||
left: 50%;
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
transform: translateY(-100%);
|
||||
transform: translateY(-200%);
|
||||
transition: transform 0.3s;
|
||||
background: var(--links);
|
||||
color: var(--warn-text);
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.skip-to-content-link:focus {
|
||||
transform: translateY(0%);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
.search-page > form {
|
||||
display: grid;
|
||||
grid-template-areas: 'search search submit''date sort scope''results results scope';
|
||||
grid-template-rows: 40% 40% 20%;
|
||||
grid-template-rows: 5em 5em 100%;
|
||||
}
|
||||
.search-page > form input,
|
||||
.search-page > form select {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
}
|
||||
.search-page > form label {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.search-page > form div.query {
|
||||
grid-area: search;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.search-page > form div.query label {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.search-page > form div.submit {
|
||||
grid-area: submit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 1em;
|
||||
}
|
||||
.search-page > form div.submit input#submit {
|
||||
width: fit-content;
|
||||
}
|
||||
.search-page > form div.date {
|
||||
grid-area: date;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.search-page > form div.date input#date {
|
||||
width: 80%;
|
||||
}
|
||||
.search-page > form div.sort {
|
||||
grid-area: sort;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 2em;
|
||||
}
|
||||
.search-page > form div.scope {
|
||||
grid-area: scope;
|
||||
width: 80%;
|
||||
margin-left: 1em;
|
||||
}
|
||||
.search-page > form div.scope select {
|
||||
width: 100%;
|
||||
height: 31rem;
|
||||
overflow: auto;
|
||||
}
|
||||
.search-page > form div.search-results {
|
||||
grid-area: results;
|
||||
width: 100%;
|
||||
min-height: 50vh;
|
||||
}
|
|
@ -1,34 +1,7 @@
|
|||
#shoutbox {
|
||||
margin: 20px 5% 10px 5%;
|
||||
background: #ffffff;
|
||||
#v5shoutbox {
|
||||
--shoutbox-color: var(--text);
|
||||
--shoutbox-border-color: var(--border, #d8d8d8);
|
||||
--shoutbox-header-bg: var(--background-alt);
|
||||
--shoutbox-link-color: var(--links);
|
||||
height: 240px;
|
||||
}
|
||||
#shoutbox > div {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 125px;
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
#shoutbox > div > div {
|
||||
padding: 2px 10px;
|
||||
border-bottom: 1px solid rgba(0,0,0,.3);
|
||||
font-size: 11px;
|
||||
}
|
||||
#shoutbox > div > div:hover {
|
||||
background: var(--background);
|
||||
}
|
||||
#shoutbox > div > div:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
#shoutbox > input {
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
border-radius: 0 0 5px 5px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
#shoutbox > input:focus {
|
||||
border-color: var(--border-focus);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075), 0 0 8px rgba(161,34,34,0.6);
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
:root {
|
||||
--background: #171a1c; /*22292c, 1c2124, 1E1E1E, 242424,*/
|
||||
--background-alt: #171a1c;
|
||||
--text: #eaeaea;
|
||||
--text-light: #e2e2e2;
|
||||
|
||||
|
@ -77,7 +78,7 @@
|
|||
|
||||
header {
|
||||
--background: #0d1215;
|
||||
--text: #000000;
|
||||
--text: #eaeaea;
|
||||
--border: 1px solid #404040;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
:root {
|
||||
--background: #fff;
|
||||
--background-alt: #eee;
|
||||
--text: #030303;
|
||||
--text-light: #b62727;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
:root {
|
||||
--background: #fff;
|
||||
--background-alt: rgba(0, 0, 0, .1);
|
||||
--text: #000;
|
||||
--text-light: #111;
|
||||
|
||||
|
@ -30,7 +31,7 @@ table {
|
|||
--border: #d8d8d8;
|
||||
}
|
||||
table tr:nth-child(odd) {
|
||||
--background: rgba(0, 0, 0, .1);
|
||||
--background: var(--background-alt);
|
||||
}
|
||||
table th {
|
||||
--background: #eee;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../../extra/v5shoutbox/style.css
|
|
@ -3,6 +3,13 @@
|
|||
align-items: center;
|
||||
width: 265px;
|
||||
}
|
||||
.profile.minimal {
|
||||
width: 176px;
|
||||
}
|
||||
.profile.minimal .profile-avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
.profile-avatar {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
|
|
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 335 B |
After Width: | Height: | Size: 335 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 322 B |
After Width: | Height: | Size: 322 B |
After Width: | Height: | Size: 369 B |
After Width: | Height: | Size: 363 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 363 B |
After Width: | Height: | Size: 334 B |
After Width: | Height: | Size: 401 B |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 341 B |
After Width: | Height: | Size: 421 B |
After Width: | Height: | Size: 391 B |
After Width: | Height: | Size: 391 B |
After Width: | Height: | Size: 391 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 411 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 408 B |
|
@ -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%);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
.search-page > form {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'search search submit'
|
||||
'date sort scope'
|
||||
'results results scope';
|
||||
grid-template-rows: 40% 40% 20%;
|
||||
grid-template-rows: 5em 5em 100%;
|
||||
|
||||
input, select {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
& div.query {
|
||||
grid-area: search;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& label {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
& div.submit {
|
||||
grid-area: submit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 1em;
|
||||
|
||||
& input#submit {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
& div.date {
|
||||
grid-area: date;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& input#date {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
& div.sort {
|
||||
grid-area: sort;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
& div.scope {
|
||||
grid-area: scope;
|
||||
width: 80%;
|
||||
margin-left: 1em;
|
||||
|
||||
& select {
|
||||
width: 100%;
|
||||
height: 31rem;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
& div.search-results {
|
||||
grid-area: results;
|
||||
width: 100%;
|
||||
min-height: 50vh;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,15 @@
|
|||
width: 265px;
|
||||
}
|
||||
|
||||
.profile.minimal {
|
||||
width: 176px;
|
||||
|
||||
.profile-avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
|
|
|
@ -0,0 +1,709 @@
|
|||
/* Currently-active calculators */
|
||||
const default_set = [ "g25+e2", "g35+e2", "g90+e", "cp400+e" ];
|
||||
|
||||
/* Hide or show a calculator, by controlling checkbox */
|
||||
function toggle_calc(checkbox) {
|
||||
const selector = "#calcdb .calc-" + checkbox.dataset.calc;
|
||||
document.querySelectorAll(selector).forEach(td => {
|
||||
if(checkbox.checked)
|
||||
td.style.display = "table-cell";
|
||||
else
|
||||
td.style.display = "none";
|
||||
});
|
||||
update_permalink();
|
||||
}
|
||||
|
||||
/* Select a fixed set set */
|
||||
function select_set(set) {
|
||||
document.querySelectorAll("#calcdb-checkboxes input").forEach(box => {
|
||||
box.checked = set.includes(box.dataset.name) || set.includes(box.dataset.calc);
|
||||
box.dispatchEvent(new Event("change"));
|
||||
});
|
||||
update_permalink();
|
||||
}
|
||||
/* Select all calculators */
|
||||
function select_all() {
|
||||
document.querySelectorAll("#calcdb-checkboxes input").forEach(box => {
|
||||
box.checked = true;
|
||||
box.dispatchEvent(new Event("change"));
|
||||
});
|
||||
update_permalink();
|
||||
}
|
||||
|
||||
/* Udpate permalink and page URL*/
|
||||
function update_permalink() {
|
||||
var search = [];
|
||||
document.querySelectorAll("#calcdb-checkboxes input").forEach(box => {
|
||||
if(box.checked) search.push(box.dataset.name);
|
||||
});
|
||||
|
||||
var loc = window.location.href.split("?")[0] + "?" + search.join(",");
|
||||
history.pushState({}, "Comparateur Planète Casio", loc);
|
||||
document.getElementById("calcdb-permalink").href = loc;
|
||||
}
|
||||
|
||||
/* The browser might load the page with some boxes pre-checked because of
|
||||
forms being cached, so upate to make sure the display is consistent */
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const query = window.location.search;
|
||||
document.querySelectorAll("#calcdb-checkboxes input").forEach(toggle_calc);
|
||||
|
||||
/* Also read the query string for a potential fixed set */
|
||||
if(!query) return;
|
||||
var set = [];
|
||||
query.substr(1).split(",").forEach(name => {
|
||||
if(name == "current") set = set.concat(default_set);
|
||||
else set.push(name);
|
||||
});
|
||||
select_set(set);
|
||||
}, false);
|
||||
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
|
||||
selectbl = new Array;
|
||||
selectbl[0] = "sel";
|
||||
selectbl[1] = "sel1";
|
||||
selectbl[2] = "sel2";
|
||||
selectbl[3] = "sel3";
|
||||
selectbl[4] = "sel4";
|
||||
selectbl[5] = "sel5";
|
||||
selectbl[6] = "sel6";
|
||||
selectbl[7] = "selform1";
|
||||
selectbl[8] = "selform2";
|
||||
selectbl[9] = "selform3";
|
||||
|
||||
document.onmousemove = position;
|
||||
|
||||
function hideselects(){
|
||||
for(i=0;i<selectbl.length;i++){
|
||||
if(document.getElementById(selectbl[i])){
|
||||
document.getElementById(selectbl[i]).style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showselects(){
|
||||
for(i=0;i<selectbl.length;i++){
|
||||
if(document.getElementById(selectbl[i])){
|
||||
document.getElementById(selectbl[i]).style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function position(e)
|
||||
{
|
||||
x = e.pageX
|
||||
y = e.pageY
|
||||
}
|
||||
|
||||
function showinfos(txt)
|
||||
{
|
||||
hideselects();
|
||||
position;
|
||||
|
||||
info = document.getElementById('infobulle').style;
|
||||
|
||||
txt = '<table class=\"infobulle\"><tr><td class=\"infobulletitle\">Aide :</td></tr><tr><td class=\"infobulle\">'+txt+'</td></tr></table>';
|
||||
document.getElementById('infobulle').innerHTML = txt;
|
||||
|
||||
info.top = (y+10)+"px";
|
||||
info.left = (x+10)+"px";
|
||||
if(screen.availWidth <= 1024 && x > 850)
|
||||
info.left = x-80;
|
||||
|
||||
info.visibility = 'visible';
|
||||
}
|
||||
|
||||
|
||||
function hideinfos()
|
||||
{
|
||||
document.getElementById('infobulle').style.visibility = 'hidden';
|
||||
showselects();
|
||||
}
|
||||
|
||||
function openimg(name,url)
|
||||
{
|
||||
linkimg = window.open(url,name,'resizable=yes,toolbar=no,scrollbar=no,width=50,height=50,top=0,left=0');
|
||||
}
|
||||
|
||||
function toggleSpoiler(spoiler, action) {
|
||||
var elements = spoiler.getElementsByTagName('div');
|
||||
|
||||
switch(action) {
|
||||
case 'open':
|
||||
elements[0].className = 'title off';
|
||||
elements[1].className = 'title on';
|
||||
elements[2].className = 'on';
|
||||
break;
|
||||
|
||||
case 'close':
|
||||
elements[0].className = 'title on';
|
||||
elements[1].className = 'title off';
|
||||
elements[2].className = 'off';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
|
||||
//Ajout d'un lien sur les images qui sont reduites par min-width
|
||||
var virtualImg = new Image();
|
||||
$(".news img, .topic img").one('load', function()
|
||||
{
|
||||
virtualImg.src = $(this).attr('src')
|
||||
|
||||
|
||||
|
||||
if($(this).attr('resized')!="true" && $(this).parent().get(0).tagName !="A" && virtualImg.width > $(this).css('max-width').slice(0,-2))
|
||||
$(this).wrap('<a href="'+ $(this).attr('src') +'"></a>');
|
||||
}).each(function() {
|
||||
if(this.complete) $(this).load();
|
||||
});
|
||||
|
||||
//formulaire de notation des programmes
|
||||
//vars
|
||||
var noteSelectionnee = -1
|
||||
var LiensNote = $('.PrgmNote').find('div.boutonsNote').children('a')
|
||||
|
||||
//events
|
||||
LiensNote.click(function() {
|
||||
noteSelectionneUpdate($(this).parent().parent(), $(this).attr('note'), true)
|
||||
return false;
|
||||
})
|
||||
LiensNote.mouseover(function() {
|
||||
noteSelectionneUpdate($(this).parent().parent(), $(this).attr('note'))
|
||||
})
|
||||
LiensNote.mouseout(function() {
|
||||
noteSelectionneUpdate($(this).parent().parent())
|
||||
})
|
||||
$('.PrgmNote').children("a.retourNote").click(function() {
|
||||
|
||||
|
||||
|
||||
if($('.PrgmNote').children("div").css('display') == 'block')
|
||||
$('.PrgmNote').children("div").css('display','none');
|
||||
else
|
||||
$('.PrgmNote').children("div").css('display','block');
|
||||
|
||||
return false;
|
||||
})
|
||||
//fonctions
|
||||
noteSelectionneUpdate = function(PrgmNoteDiv, note, clique)
|
||||
{
|
||||
if(note >= 0)
|
||||
{
|
||||
PrgmNoteDiv.find(".note").html(note + '/10')
|
||||
if(clique){
|
||||
PrgmNoteDiv.find(".note").css('font-weight','bold')
|
||||
PrgmNoteDiv.find(".note").css('font-style','normal')
|
||||
noteSelectionnee = note
|
||||
PrgmNoteDiv.find("select").children('option[selected=selected]').removeAttr('selected')
|
||||
PrgmNoteDiv.find("select").children('option[value=' + note + ']').attr('selected','selected')
|
||||
}
|
||||
else
|
||||
{
|
||||
PrgmNoteDiv.find(".note").css('font-style','italic')
|
||||
PrgmNoteDiv.find(".note").css('font-weight','normal')
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(noteSelectionnee < 0)
|
||||
{
|
||||
PrgmNoteDiv.find(".note").html('')
|
||||
}
|
||||
else
|
||||
{
|
||||
PrgmNoteDiv.find(".note").css('font-weight','bold')
|
||||
PrgmNoteDiv.find(".note").css('font-style','normal')
|
||||
PrgmNoteDiv.find(".note").html(noteSelectionnee + '/10')
|
||||
}
|
||||
}
|
||||
}
|
||||
//init
|
||||
$('.PrgmNote').find("select").css('display','none')
|
||||
$('.PrgmNote').find(".boutonsNote").css('display','block')
|
||||
$('.PrgmNote').children("div").css('display','none')
|
||||
noteSelectionneUpdate($('.PrgmNote'), $('.PrgmNote').find("option[selected]").attr('value'), true)
|
||||
|
||||
//addthis retardateur
|
||||
var isTimeFinish = false
|
||||
var isOut = true;
|
||||
function timeFinish(that)
|
||||
{
|
||||
isTimeFinish = true
|
||||
that.mouseover()
|
||||
}
|
||||
|
||||
$('.addthis_button').children('img').mouseenter(function() {
|
||||
if(isTimeFinish)
|
||||
{
|
||||
clearTimeout(timeoutID);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
timeoutID = setTimeout(function(that){timeFinish(that);}, 200, $(this));
|
||||
return false;
|
||||
}
|
||||
})
|
||||
$('.addthis_button').children('img').mouseleave(function() {
|
||||
isTimeFinish = false;
|
||||
clearTimeout(timeoutID);
|
||||
return true;
|
||||
})
|
||||
//antibot JS pour invité
|
||||
$(document).ready(function(){
|
||||
$('.messageForm').css('display','block');
|
||||
$('.messageForm').children('form').append('<input type="hidden" name="jsActive" value="Oui"/>');
|
||||
})
|
||||
//Editeur - namespace:ed
|
||||
//vars
|
||||
var ed_boutons = $('.editeur').find('.boutons').find('img,td,span');
|
||||
var ed_textarea = $('.editeur').find('textarea');
|
||||
var ed_pins = $('.editeur').find('.pinhidden,.pinshow');
|
||||
var ed_cross = $('.editeur').find('.cross');
|
||||
var ed_inputs = $('.editeur').find('.AskBoxInputText');
|
||||
var ed_smileys = $('.editeur').find('.ABsmiley').find('td');
|
||||
//speciale IE
|
||||
$(".editeur").data('isfocused',false);
|
||||
$(".editeur").data('selecStart',0);
|
||||
$(".editeur").data('selecEnd',0);
|
||||
$(".editeur").data('isPinned',false);
|
||||
$(".editeur").data('tag','');
|
||||
//events
|
||||
$('.editeur').find('.AskBoxBottom').children('img').click(function(){
|
||||
ed_addtag($(this).closest(".editeur"));
|
||||
});
|
||||
$('.editeur').find('.ABprofil').click(function(){
|
||||
var formname = $(this).closest('.editeur').data('formname');
|
||||
fenetre=window.open('/Fr/compte/liste_des_membres.php?id=ed_'+formname+'_lienMembre','fenetre','resizable=yes,toolbar=no,scrollbars=yes,width=280,height=450,top=0,left=0')
|
||||
});
|
||||
|
||||
ed_cross.click(function()//fermeture de toutes les askbox si on clique ailleur
|
||||
{
|
||||
close_AskBox($(this).closest(".editeur"),false);
|
||||
});
|
||||
|
||||
$('.editeur').find('.edSelect').change(function(){
|
||||
|
||||
var tag = $(this).children('option:checked').val();
|
||||
if(tag.length > 0)
|
||||
add_text($(this).closest('.editeur').find('textarea'),'['+tag+']', '[/'+tag+']', '')
|
||||
$(this).children('option').removeAttr('selected');
|
||||
$(this).children('option[value=]').attr('selected','selected');
|
||||
});
|
||||
|
||||
ed_pins.click(function(){
|
||||
var editeur = $(this).closest(".editeur");
|
||||
if(editeur.data('isPinned')==false)
|
||||
{
|
||||
editeur.data('isPinned',true);
|
||||
$(this).css('display','none');
|
||||
$(this).parent().children('.pinshow').css('display','block');
|
||||
}
|
||||
else
|
||||
{
|
||||
editeur.data('isPinned',false);
|
||||
$(this).css('display','none');
|
||||
$(this).parent().children('.pinhidden').css('display','block');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ed_boutons.click(function(){
|
||||
if($(this).data('type')=='text')
|
||||
{
|
||||
add_text($(this).closest(".editeur").find('textarea'), $(this).data('avant'),$(this).data('apres'),$(this).data('valeur'));
|
||||
}
|
||||
else if($(this).data('type')=='askbox')
|
||||
{
|
||||
show_AskBox($(this).closest(".editeur"), $(this).data('name'))
|
||||
}
|
||||
});
|
||||
|
||||
ed_smileys.click(function(){
|
||||
if($(this).data('type')=='text')
|
||||
{
|
||||
add_text($(this).closest(".editeur").find('textarea'), $(this).data('avant'),$(this).data('apres'),$(this).data('valeur'));
|
||||
}
|
||||
else if($(this).data('type')=='askbox')
|
||||
{
|
||||
show_AskBox($(this).closest(".editeur"), $(this).data('name'))
|
||||
}
|
||||
close_AskBox($(this).closest(".editeur"));
|
||||
});
|
||||
|
||||
ed_inputs.keypress(function(e) {
|
||||
if(e.which == 13) {
|
||||
ed_addtag($(this).closest(".editeur"));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ed_textarea.focus(function(){
|
||||
$(this).closest(".editeur").data('isfocused',true);
|
||||
});
|
||||
ed_textarea.blur(function(){
|
||||
$(this).closest(".editeur").data('isfocused',false);
|
||||
});
|
||||
//Askbox fonctions
|
||||
//affiche une askbox
|
||||
function show_AskBox(editeur, AskBoxName)
|
||||
{
|
||||
close_AskBox(editeur, false,false);
|
||||
var AskBox = editeur.children('.AskBox');
|
||||
var actualAskBox = AskBox.children('.AskBoxText.AB'+AskBoxName);
|
||||
var textarea = editeur.find('textarea')[0];
|
||||
var title = "";
|
||||
editeur.data('tag',AskBoxName);
|
||||
//Get selected text
|
||||
var selectedText = '';
|
||||
if(typeof document.selection != 'undefined')//IE support
|
||||
{
|
||||
var range = document.selection.createRange();
|
||||
selectedText=range.text;
|
||||
}
|
||||
else if(typeof textarea.selectionStart != 'undefined')//Firefox, GC support
|
||||
{
|
||||
selectedText=textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
|
||||
}
|
||||
//Open the askbox
|
||||
switch(AskBoxName)
|
||||
{
|
||||
case 'url' :
|
||||
var title = "Ajouter un lien";
|
||||
var focusOn = null;
|
||||
if(selectedText.substring(0,7)=="http://" || selectedText.substring(0,8)=="https://" || selectedText.substring(0,6)=="ftp://")
|
||||
{
|
||||
actualAskBox.find('.AskBoxInputText[name=ed-urlLien]').val(selectedText);
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-urlNom]');
|
||||
}
|
||||
else
|
||||
{
|
||||
actualAskBox.find('.AskBoxInputText[name=ed-urlNom]').val(selectedText);
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-urlLien]').val("http://");
|
||||
}
|
||||
break;
|
||||
case 'img' :
|
||||
title = "Ajouter une image";
|
||||
actualAskBox.find('.AskBoxInputRadio[name=ed-imgType][value=img]').prop('checked', true);
|
||||
actualAskBox.find('.AskBoxInputRadio[name=ed-imgAlign][value=center]').prop('checked', true);
|
||||
if(selectedText.substring(0,7)=="http://" || selectedText.substring(0,8)=="https://" || selectedText.substring(0,6)=="ftp://")
|
||||
{
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-imgAdresse]').val(selectedText);
|
||||
}
|
||||
else
|
||||
{
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-imgAdresse]').val("http://");
|
||||
}
|
||||
break;
|
||||
case 'video' :
|
||||
title = "Ajouter une vidéo";
|
||||
actualAskBox.find('.AskBoxInputRadio[name=ed-videoType][value=video]').prop('checked', true);
|
||||
if(selectedText.substring(0,7)=="http://" || selectedText.substring(0,8)=="https://")
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-videoAdresse]').val(selectedText);
|
||||
else
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-videoAdresse]').val("http://");
|
||||
break;
|
||||
case 'profil' :
|
||||
title = "Ajouter un lien vers un profil";
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-profilPseudo]').val(selectedText);
|
||||
break;
|
||||
case 'quote' :
|
||||
title = "Citer";
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-quoteAuteur]');
|
||||
break;
|
||||
case 'spoiler' :
|
||||
title = "Ajouter un spoiler";
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-spoilerOpen]');
|
||||
break;
|
||||
case 'progress' :
|
||||
title = "Ajouter une barre de progression";
|
||||
focusOn = actualAskBox.find('.AskBoxInputText[name=ed-progressTitle]').val(selectedText);
|
||||
break;
|
||||
case 'smiley' :
|
||||
title = "Plus de smileys !";
|
||||
AskBox.children('.AskBoxBottom').css('display','none');
|
||||
break;
|
||||
}
|
||||
AskBox.children('.AskBoxTop').children('span').html(title);
|
||||
// AskBox.css('display','block');
|
||||
editeur.children('.AskBox').show('fast');
|
||||
actualAskBox.css('display','block');
|
||||
if(focusOn!=null)focusOn.focus();
|
||||
}
|
||||
// masque tous les askbox(en verifiant s'ils sont pinned si verif=true)
|
||||
function close_AskBox(editeur, verif, anim)
|
||||
{ if(anim != false)anim = true;
|
||||
if(editeur.data('isPinned')==false || verif==false)
|
||||
{
|
||||
editeur.children('.AskBox').children('.AskBoxBottom').css('display','block');
|
||||
if(anim)
|
||||
editeur.children('.AskBox').hide('fast');
|
||||
else
|
||||
editeur.children('.AskBox').css('display','non');
|
||||
|
||||
editeur.children('.AskBox').children('.AskBoxText').css('display','none');
|
||||
editeur.data('tag','');
|
||||
editeur.data('isPinned',false);
|
||||
editeur.find('.pinshow').css('display','none');
|
||||
editeur.find('.pinhidden').css('display','block');
|
||||
}
|
||||
}
|
||||
//Validation des askbox
|
||||
function ed_addtag(editeur)
|
||||
{
|
||||
var tag = editeur.data('tag');
|
||||
var valeur='';
|
||||
var valeurSup='';
|
||||
// var selectionVide=true;
|
||||
var cursorAtEnd=false;
|
||||
|
||||
switch(tag)
|
||||
{
|
||||
case 'url':
|
||||
valeur=editeur.find('.AskBoxInputText[name=ed-urlNom]').val();
|
||||
valeurSup=editeur.find('.AskBoxInputText[name=ed-urlLien]').val();
|
||||
if(valeur==""){valeur=valeurSup;cursorAtEnd=false;}else cursorAtEnd=true;
|
||||
valeurSup="="+valeurSup;
|
||||
break;
|
||||
case 'img' :
|
||||
//adimg
|
||||
if(editeur.find('.AskBoxInputRadio[name=ed-imgType]:checked').val() == "adimg")
|
||||
tag = "adimg";
|
||||
//get value
|
||||
valeur=editeur.find('.AskBoxInputText[name=ed-imgAdresse]').val();
|
||||
//prepare valeurSup
|
||||
valeurSup = "=";
|
||||
var premierArg= true;
|
||||
var largeur = parseInt(editeur.find('.AskBoxInputText[name=ed-imgWidth]').val());
|
||||
if(largeur > 0)//if int
|
||||
{
|
||||
valeurSup += largeur;
|
||||
premierArg = false;
|
||||
}
|
||||
var hauteur = parseInt(editeur.find('.AskBoxInputText[name=ed-imgHeight]').val());
|
||||
if(hauteur > 0)//if int
|
||||
{
|
||||
valeurSup += "x" + hauteur;
|
||||
premierArg = false;
|
||||
}
|
||||
var align = editeur.find('.AskBoxInputRadio[name=ed-imgAlign]:checked').val()
|
||||
if(align.length >0)
|
||||
{
|
||||
if(premierArg==false){valeurSup += "|"}
|
||||
valeurSup += align;
|
||||
premierArg = false;
|
||||
}
|
||||
if(premierArg){valeurSup = ""}
|
||||
cursorAtEnd=true;
|
||||
break;
|
||||
case 'video' :
|
||||
valeur=editeur.find('.AskBoxInputText[name=ed-videoAdresse]').val();
|
||||
if(editeur.find('.AskBoxInputRadio[name=ed-videoType]:checked').val() == "video tiny")
|
||||
tag = "video mini";
|
||||
cursorAtEnd=true;
|
||||
break;
|
||||
case 'profil' :
|
||||
valeur = editeur.find('.AskBoxInputText[name=ed-profilPseudo]').val();
|
||||
if(valeur.length > 0) cursorAtEnd=true;
|
||||
break;
|
||||
case 'quote' :
|
||||
valeurSup = editeur.find('.AskBoxInputText[name=ed-quoteAuteur]').val();
|
||||
if(valeurSup!="")valeurSup="="+valeurSup;
|
||||
break;
|
||||
case 'spoiler':
|
||||
valeurSup = "=" + editeur.find('.AskBoxInputText[name=ed-spoilerOpen]').val() + "|" + editeur.find('.AskBoxInputText[name=ed-spoilerClose]').val();
|
||||
if(valeurSup == '=Cliquer pour dérouler|Cliquer pour enrouler')
|
||||
valeurSup = "";
|
||||
break;
|
||||
case 'progress':
|
||||
valeur= editeur.find('.AskBoxInputText[name=ed-progressTitle]').val();
|
||||
if(valeur.length <=0)
|
||||
{
|
||||
alert('Le titre de la barre de progression est obligatoire.');
|
||||
editeur.find('.AskBoxInputText[name=ed-progressTitle]').focus();
|
||||
return;
|
||||
}
|
||||
valeurSup= parseInt(editeur.find('.AskBoxInputText[name=ed-progressPourcent]').val());
|
||||
if(!valeurSup>=1 && !valeurSup<=100)
|
||||
{
|
||||
alert('Le pourcentage de la barre de progression est obligatoire et doit être compris entre 1 et 100');
|
||||
editeur.find('.AskBoxInputText[name=ed-progressPourcent]').focus();
|
||||
return;
|
||||
}
|
||||
valeurSup="="+valeurSup;
|
||||
cursorAtEnd=true;
|
||||
break;
|
||||
}
|
||||
//fermeture des askbox
|
||||
close_AskBox(editeur);
|
||||
//Envoi sur le textarea
|
||||
if(cursorAtEnd){add_text(editeur.find('textarea'), '[' + tag + valeurSup + ']'+ valeur +'[/' + tag + ']')}
|
||||
else{add_text(editeur.find('textarea'), '[' + tag + valeurSup + ']','[/' + tag + ']',valeur)}
|
||||
}
|
||||
//IE compatibility
|
||||
document.onmouseup = updateIECursorPosition;
|
||||
document.onkeydown = updateIECursorPosition;
|
||||
function updateIECursorPosition()//IE - detection de la position du curseur pour IE qu'il l'oublie quand on clique autrepart que sur le textarea..
|
||||
{
|
||||
$('.editeur').each(function(){
|
||||
var thisTxtarea = $(this).find('textarea');
|
||||
if($(this).data('isfocused') && typeof(thisTxtarea[0].createTextRange) == 'function')
|
||||
{
|
||||
var range = thisTxtarea[0].createTextRange();
|
||||
range.moveToBookmark(document.selection.createRange().getBookmark());
|
||||
range.moveEnd('character', thisTxtarea[0].value.length);
|
||||
$(".editeur").data('selecStart',(thisTxtarea[0].value.length - range.text.length))
|
||||
|
||||
var range = thisTxtarea[0].createTextRange();
|
||||
range.moveToBookmark(document.selection.createRange().getBookmark());
|
||||
range.moveStart('character', - thisTxtarea[0].value.length);
|
||||
$(".editeur").data('selecEnd',range.text.length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//general textarea functions
|
||||
function add_text(textarea, before, after, valeur)
|
||||
{
|
||||
selecStart = textarea.data('selecStart');
|
||||
selecEnd = textarea.data('selecEnd');
|
||||
if(before==null){before='';}
|
||||
if(after==null){after='';}
|
||||
if(valeur==null){valeur='';}
|
||||
if(typeof document.selection != 'undefined')//IE support
|
||||
{
|
||||
var insText;
|
||||
textarea.focus();
|
||||
if(selecStart!=null && selecEnd!=null)
|
||||
{ var range = document.selection.createRange();
|
||||
if (textarea[0].setSelectionRange)
|
||||
textarea[0].setSelectionRange(selecStart, selecEnd);
|
||||
else if (document.selection) {
|
||||
var range = textarea[0].createTextRange();
|
||||
range.moveStart('character', selecStart);
|
||||
range.moveEnd('character', - textarea[0].value.length + selecEnd);
|
||||
range.select();
|
||||
}
|
||||
}
|
||||
|
||||
textarea.focus();
|
||||
range = document.selection.createRange();
|
||||
if(valeur!=''){insText=valeur;}else{insText = range.text;}
|
||||
if(after=='' && valeur=='')
|
||||
{
|
||||
range.text = before;
|
||||
range.select();
|
||||
}
|
||||
else
|
||||
{
|
||||
range.text = before + insText + after ;
|
||||
range.moveStart('character', -after.length -insText.length);
|
||||
range.moveEnd('character', -after.length);
|
||||
range.select();
|
||||
}
|
||||
|
||||
}
|
||||
else if(typeof textarea[0].selectionStart != 'undefined')//Firefox, GC support
|
||||
{
|
||||
var start = textarea[0].selectionStart;
|
||||
var end = textarea[0].selectionEnd;
|
||||
var insText;
|
||||
if(valeur!='')
|
||||
insText=valeur;
|
||||
else
|
||||
insText = textarea.val().substring(start, end);
|
||||
|
||||
if(after=='' && valeur=='')
|
||||
{
|
||||
textarea.val(textarea.val().substr(0, start) + before + textarea.val().substr(end));
|
||||
textarea[0].selectionStart = start + before.length;
|
||||
textarea[0].selectionEnd = start + before.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
textarea.val(textarea.val().substr(0, start) + before + insText + after + textarea.val().substr(end));
|
||||
textarea[0].selectionStart = start + before.length;
|
||||
textarea[0].selectionEnd = start + before.length + insText.length;
|
||||
}
|
||||
|
||||
textarea.focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
textarea.val(before + valeur + after);
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Preview button on textarea
|
||||
$('.pctextarea_preview').on('shown.bs.modal', function () {
|
||||
const message = $(this).parent().find('.editeurTextareaDiv > textarea').val();
|
||||
const body = $(this).find('.modal-body');
|
||||
$.post('/Fr/forums/preview.php', {message}, function(data, status) {
|
||||
body.html(data.preview);
|
||||
})
|
||||
.fail(function() {
|
||||
body.html('<p class="align-center">Erreur pendant la récupération de la prévisualisation.</p>');
|
||||
});
|
||||
})
|
||||
|
||||
//fonction citer dans le forum
|
||||
$('.lien_citation').click(function(){
|
||||
var auteur = $(this).data('membre');
|
||||
var id_message = $(this).data('id');
|
||||
var textarea = $('textarea[name=message]');
|
||||
$.ajax({type: "POST",
|
||||
url: '/Fr/forums/quote.php',
|
||||
data: {id : id_message}
|
||||
}).done(function( msg )
|
||||
{
|
||||
add_text(textarea,'[quote=' + auteur + ']' + msg + '[/quote]','','');
|
||||
});
|
||||
});
|
||||
|
||||
//bouton d'affichage des stats
|
||||
$('#stats').click(function(){
|
||||
window.open($(this).attr('href'),'connectes','resizable=yes,toolbar=no,scrollbars=yes,width=550,height=300,top=0,left=0')
|
||||
return false;
|
||||
});
|
||||
//Lien de control de la fenetre principale depuis un popoup
|
||||
$('.popuplink').click(function(){
|
||||
window.opener.location.href=$(this).data('link');return false;
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Confirmation modal
|
||||
*
|
||||
* Add the 'need-confirm' class to a link to show a prompt asking for confirmation.
|
||||
* 'data-confirm-text' attribute can be used to customize the confirm message
|
||||
*/
|
||||
$('a.need-confirm').click(function(){
|
||||
const modal = $('#confirm-modal');
|
||||
|
||||
// Get text from 'data-confirm-text' arg
|
||||
let text = 'Êtes-vous sûr de vouloir continuer ?';
|
||||
console.log($(this).data())
|
||||
if ($(this).data('confirm-text')) {
|
||||
text = $(this).data('confirm-text');
|
||||
}
|
||||
modal.find('.confirm-modal-text').first().html(text)
|
||||
|
||||
// Set "Yes" link to original href of $(this)
|
||||
modal.find('.confirm-modal-yes').first().attr('href', $(this).attr('href'))
|
||||
|
||||
// Configure and open modal
|
||||
$('#confirm-modal').modal('show');
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -2,42 +2,34 @@
|
|||
|
||||
/* Locate the editor associated to an edition event.
|
||||
event: Global event emitted by one of the editor buttons
|
||||
Returns [the div.editor, the button, the textarea] */
|
||||
Returns [the div.editor, the textarea] */
|
||||
function editor_event_source(event)
|
||||
{
|
||||
let button = undefined;
|
||||
let editor = undefined;
|
||||
|
||||
/* Grab the button and the parent editor block. The onclick event itself
|
||||
/* Grab the the parent editor block. The onclick event itself
|
||||
usually reports the SVG in the button as the source */
|
||||
let node = event.target || event.srcElement;
|
||||
let node = event.current || event.srcElement;
|
||||
while (node != document.body) {
|
||||
if (node.tagName == "BUTTON" && !button) {
|
||||
button = node;
|
||||
}
|
||||
if (node.classList.contains("editor") && !editor) {
|
||||
editor = node;
|
||||
// Hack to use keybinds
|
||||
if (!button) {
|
||||
button = node.firstElementChild.firstElementChild
|
||||
}
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
if (!button || !editor) return;
|
||||
if (!editor) return;
|
||||
|
||||
const ta = editor.querySelector(".editor textarea");
|
||||
return [editor, button, ta];
|
||||
return [editor, ta];
|
||||
}
|
||||
|
||||
/* Replace the range [start:end) with the new contents, and returns the new
|
||||
interval [start:end) (ie. the range where the contents are now located). */
|
||||
function editor_replace_range(textarea, start, end, contents)
|
||||
{
|
||||
ta.value = ta.value.substring(0, start)
|
||||
+ contents
|
||||
+ ta.value.substring(end);
|
||||
textarea.value = textarea.value.substring(0, start)
|
||||
+ contents
|
||||
+ textarea.value.substring(end);
|
||||
|
||||
return [start, start + contents.length];
|
||||
}
|
||||
|
@ -46,7 +38,7 @@ function editor_replace_range(textarea, start, end, contents)
|
|||
after token is the same as before if not specified */
|
||||
function editor_insert_around(event, before="", after=null)
|
||||
{
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
ta.focus();
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
|
@ -67,14 +59,14 @@ function editor_insert_around(event, before="", after=null)
|
|||
ta.selectionStart = ta.selectionEnd = start + before.length;
|
||||
}
|
||||
|
||||
preview();
|
||||
preview(editor);
|
||||
}
|
||||
|
||||
/* Event handler that modifies each line within the selection through a
|
||||
generic function. */
|
||||
function editor_act_on_lines(event, fn)
|
||||
{
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
ta.focus();
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
|
@ -102,27 +94,28 @@ function editor_act_on_lines(event, fn)
|
|||
ta.selectionStart = start;
|
||||
ta.selectionEnd = end;
|
||||
|
||||
preview();
|
||||
preview(editor);
|
||||
}
|
||||
|
||||
function editor_clear_modals(event, close = true)
|
||||
{
|
||||
// Stop the propagation of the event
|
||||
event.stopPropagation()
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
|
||||
// Reset all modal inputs
|
||||
document.getElementById('media-alt-input').value = '';
|
||||
document.getElementById('media-link-input').value = '';
|
||||
document.getElementById('link-desc-input').value = '';
|
||||
document.getElementById('link-link-input').value = '';
|
||||
const media_type = document.getElementsByName("media-type");
|
||||
editor.getElementsByClassName('media-alt-input')[0].value = '';
|
||||
editor.getElementsByClassName('media-link-input')[0].value = '';
|
||||
editor.getElementsByClassName('link-desc-input')[0].value = '';
|
||||
editor.getElementsByClassName('link-link-input')[0].value = '';
|
||||
const media_type = editor.getElementsByClassName("media-type")[0];
|
||||
for(i = 0; i < media_type.length; i++) {
|
||||
media_type[i].checked = false;
|
||||
}
|
||||
|
||||
// Close all modal if requested
|
||||
if (!close) { return }
|
||||
const modals = document.getElementsByClassName('modal');
|
||||
const modals = editor.getElementsByClassName('modal');
|
||||
for (const i of modals) {i.style.display = 'none'};
|
||||
}
|
||||
|
||||
|
@ -143,12 +136,13 @@ function editor_inline(event, type, enable_preview = true)
|
|||
}
|
||||
|
||||
if (enable_preview) {
|
||||
preview();
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
preview(editor);
|
||||
}
|
||||
}
|
||||
|
||||
function editor_display_link_modal(event) {
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
let selection = ta.value.substring(indexStart, indexEnd);
|
||||
|
@ -167,16 +161,16 @@ function editor_display_link_modal(event) {
|
|||
|
||||
function editor_insert_link(event, link_id, text_id, media = false)
|
||||
{
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
ta.focus();
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
|
||||
const link = document.getElementById(link_id).value;
|
||||
const text = document.getElementById(text_id).value;
|
||||
const link = editor.getElementsByClassName(link_id)[0].value;
|
||||
const text = editor.getElementsByClassName(text_id)[0].value;
|
||||
let media_type = "";
|
||||
|
||||
const media_selector = document.getElementsByName("media-type");
|
||||
const media_selector = editor.getElementsByClassName("media-type")[0];
|
||||
for(i = 0; i < media_selector.length; i++) {
|
||||
if (media_selector[i].checked) {
|
||||
media_type = `{type=${media_selector[i].value}}`;
|
||||
|
@ -197,7 +191,7 @@ function editor_insert_link(event, link_id, text_id, media = false)
|
|||
ta.selectionStart = ta.selectionEnd = start + 1;
|
||||
}
|
||||
|
||||
preview();
|
||||
preview(editor);
|
||||
}
|
||||
|
||||
function editor_title(event, level, diff)
|
||||
|
@ -293,6 +287,8 @@ const DISABLE_PREVIEW_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16"
|
|||
const ENABLE_PREVIEW_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"><path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/><path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/></svg>';
|
||||
|
||||
function toggle_auto_preview() {
|
||||
const [editor, ta] = editor_event_source(event);
|
||||
|
||||
let auto_preview;
|
||||
if (document.cookie.split(";").some((item) => item.trim().startsWith("auto-preview="))) {
|
||||
auto_preview = document.cookie.split(";").some((item) => item.includes("auto-preview=true"));
|
||||
|
@ -301,25 +297,24 @@ function toggle_auto_preview() {
|
|||
}
|
||||
document.cookie = `auto-preview=${!auto_preview}; max-age=31536000; SameSite=Strict; Secure`
|
||||
if (!auto_preview) {
|
||||
document.getElementById("toggle_preview").title = "Désactiver la prévisualisation";
|
||||
document.getElementById("toggle_preview").innerHTML = DISABLE_PREVIEW_ICON;
|
||||
document.getElementById("manual_preview").style = "display: none";
|
||||
editor.getElementsByClassName("toggle_preview")[0].title = "Désactiver la prévisualisation";
|
||||
editor.getElementsByClassName("toggle_preview")[0].innerHTML = DISABLE_PREVIEW_ICON;
|
||||
editor.getElementsByClassName("manual_preview")[0].style = "display: none";
|
||||
} else {
|
||||
document.getElementById("toggle_preview").title = "Activer la prévisualisation";
|
||||
document.getElementById("toggle_preview").innerHTML = ENABLE_PREVIEW_ICON;
|
||||
document.getElementById("manual_preview").style = "display: block";
|
||||
editor.getElementsByClassName("toggle_preview")[0].title = "Activer la prévisualisation";
|
||||
editor.getElementsByClassName("toggle_preview")[0].innerHTML = ENABLE_PREVIEW_ICON;
|
||||
editor.getElementsByClassName("manual_preview")[0].style = "display: block";
|
||||
}
|
||||
}
|
||||
|
||||
/* This request the server to get a complete render of the current text in the textarea */
|
||||
function preview(manual=false) {
|
||||
function preview(editor, manual=false) {
|
||||
// If auto-preview is disabled and the preview is not manually requested by the user
|
||||
if (document.cookie.split(";").some((item) => item.includes("auto-preview=false")) && !manual) {
|
||||
return;
|
||||
}
|
||||
const previewArea = document.querySelector("#editor_content_preview");
|
||||
|
||||
const textarea = document.querySelector(".editor textarea");
|
||||
const previewArea = editor.querySelector(".editor_content_preview");
|
||||
const ta = editor.querySelector("textarea");
|
||||
const payload = {text: ta.value};
|
||||
|
||||
const headers = new Headers();
|
||||
|
@ -341,86 +336,95 @@ function preview(manual=false) {
|
|||
});
|
||||
}
|
||||
|
||||
if (document.cookie.split(";").some((item) => item.trim().startsWith("auto-preview="))) {
|
||||
if (document.cookie.split(";").some((item) => item.includes("auto-preview=false"))) {
|
||||
document.getElementById("toggle_preview").title = "Activer la prévisualisation";
|
||||
document.getElementById("toggle_preview").innerHTML = ENABLE_PREVIEW_ICON;
|
||||
document.getElementById("manual_preview").style = "display: block";
|
||||
}
|
||||
/* Wrapper for user-requested preview refresh */
|
||||
function manual_preview(editor_id) {
|
||||
const editor = document.getElementById("editor_" + editor_id);
|
||||
preview(editor, manual = true);
|
||||
}
|
||||
|
||||
let previewTimeout = null;
|
||||
let ta = document.querySelector(".editor textarea");
|
||||
ta.addEventListener('keydown', function(e) {
|
||||
// Tab insert some spaces
|
||||
let keyCode = e.keyCode || e.which;
|
||||
if (keyCode == 9) {
|
||||
// TODO Add one tab to selected text without replacing it
|
||||
e.preventDefault();
|
||||
/* Add the event listener for the textarea hotkeys and auto-preview */
|
||||
function editor_setup(editor_id) {
|
||||
const editor = document.getElementById("editor_" + editor_id);
|
||||
|
||||
let start = e.target.selectionStart;
|
||||
let end = e.target.selectionEnd;
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
e.target.value = e.target.value.substring(0, start) + "\t" + e.target.value.substring(end);
|
||||
e.target.selectionEnd = start + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keybindings for buttons. The default action of the keybinding is prevented.
|
||||
* Ctrl+B adds bold
|
||||
* Ctrl+I adds italic
|
||||
* Ctrl+U adds underline
|
||||
* Ctrl+S adds strikethrough
|
||||
* Ctrl+H adds Header +1
|
||||
* Ctrl+Enter send the form
|
||||
*/
|
||||
if (e.ctrlKey) {
|
||||
switch (keyCode) {
|
||||
case 13:
|
||||
let t = e.target;
|
||||
while(! (t instanceof HTMLFormElement)) {
|
||||
t = t.parentNode;
|
||||
}
|
||||
try {
|
||||
t.submit();
|
||||
} catch(exception) {
|
||||
t.submit.click();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 66: // B
|
||||
editor_inline(e, "bold", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 72: // H
|
||||
editor_title(e, 0, +1);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 73: // I
|
||||
editor_inline(e, "italic", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 83: // S
|
||||
editor_inline(e, "strike", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 85: // U
|
||||
editor_inline(e, "underline", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
if (document.cookie.split(";").some((item) => item.trim().startsWith("auto-preview="))) {
|
||||
if (document.cookie.split(";").some((item) => item.includes("auto-preview=false"))) {
|
||||
editor.getElementsByClassName("toggle_preview")[0].title = "Activer la prévisualisation";
|
||||
editor.getElementsByClassName("toggle_preview")[0].innerHTML = ENABLE_PREVIEW_ICON;
|
||||
editor.getElementsByClassName("manual_preview")[0].style = "display: block";
|
||||
}
|
||||
}
|
||||
|
||||
// Set a timeout for refreshing the preview
|
||||
if (previewTimeout != null) {
|
||||
let previewTimeout = null;
|
||||
let ta = editor.querySelector(".editor textarea");
|
||||
ta.addEventListener('keydown', function(e) {
|
||||
// Tab insert some spaces
|
||||
let keyCode = e.keyCode || e.which;
|
||||
if (keyCode == 9) {
|
||||
// TODO Add one tab to selected text without replacing it
|
||||
e.preventDefault();
|
||||
|
||||
let start = e.target.selectionStart;
|
||||
let end = e.target.selectionEnd;
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
e.target.value = e.target.value.substring(0, start) + "\t" + e.target.value.substring(end);
|
||||
e.target.selectionEnd = start + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keybindings for buttons. The default action of the keybinding is prevented.
|
||||
* Ctrl+B adds bold
|
||||
* Ctrl+I adds italic
|
||||
* Ctrl+U adds underline
|
||||
* Ctrl+S adds strikethrough
|
||||
* Ctrl+H adds Header +1
|
||||
* Ctrl+Enter send the form
|
||||
*/
|
||||
if (e.ctrlKey) {
|
||||
switch (keyCode) {
|
||||
case 13:
|
||||
let t = e.target;
|
||||
while(! (t instanceof HTMLFormElement)) {
|
||||
t = t.parentNode;
|
||||
}
|
||||
try {
|
||||
t.submit();
|
||||
} catch(exception) {
|
||||
t.submit.click();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 66: // B
|
||||
editor_inline(e, "bold", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 72: // H
|
||||
editor_title(e, 0, +1);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 73: // I
|
||||
editor_inline(e, "italic", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 83: // S
|
||||
editor_inline(e, "strike", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 85: // U
|
||||
editor_inline(e, "underline", false);
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set a timeout for refreshing the preview
|
||||
clearTimeout(previewTimeout);
|
||||
}
|
||||
previewTimeout = setTimeout(preview, 3000);
|
||||
});
|
||||
previewTimeout = setTimeout(() => { preview(editor) }, 3000);
|
||||
});
|
||||
|
||||
document.querySelector('emoji-picker').addEventListener('emoji-click', event => {
|
||||
editor_clear_modals(event);
|
||||
editor_insert_around(event, "", event.detail.unicode)
|
||||
|
||||
preview();
|
||||
});
|
||||
editor.querySelector('emoji-picker').addEventListener('emoji-click', event => {
|
||||
editor_clear_modals(event);
|
||||
editor_insert_around(event, "", event.detail.unicode)
|
||||
|
||||
preview(editor);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../../extra/emoji-picker-element/
|
|
@ -0,0 +1 @@
|
|||
../../../extra/v5shoutbox/v5shoutbox.js
|
|
@ -0,0 +1 @@
|
|||
../../../extra/v5shoutbox/v5shoutbox_irc.js
|
|
@ -0,0 +1 @@
|
|||
../../../extra/v5shoutbox/v5shoutbox_ui.js
|
|
@ -0,0 +1 @@
|
|||
../../../extra/v5shoutbox/v5shoutbox_worker.js
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base/base.html" %}
|
||||
{% import "widgets/editor.html" as widget_editor %}
|
||||
|
||||
{% set tabtitle = "Gestion du compte" %}
|
||||
|
||||
|
@ -68,16 +69,11 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
{{ form.signature.label }}
|
||||
<textarea id="{{ form.signature.name }}" name="{{ form.signature.name }}">{{ current_user.signature }}</textarea>
|
||||
{% for error in form.signature.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
{{ widget_editor.text_editor(form.signature) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form.biography.label }}
|
||||
<textarea id="{{ form.biography.name }}" name="{{ form.biography.name }}">{{ current_user.bio }}</textarea>
|
||||
{% for error in form.biography.errors %}
|
||||
{{ widget_editor.text_editor(form.biography) }}
|
||||
{% for error in form.signature.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
|
@ -53,6 +53,7 @@
|
|||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ form.ab }}
|
||||
<div>{{ form.submit(class_="bg-ok") }}</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base/base.html" %}
|
||||
{% import "widgets/editor.html" as widget_editor %}
|
||||
|
||||
{% set tabtitle = "Administration - Édition du compte de " + user.name %}
|
||||
|
||||
|
@ -81,18 +82,10 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
{{ form.signature.label }}
|
||||
<textarea id="{{ form.signature.name }}" name="{{ form.signature.name }}">{{ user.signature }}</textarea>
|
||||
{% for error in form.signature.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
{{ widget_editor.text_editor(form.signature) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form.biography.label }}
|
||||
<textarea id="{{ form.biography.name }}" name="{{ form.biography.name }}">{{ user.bio }}</textarea>
|
||||
{% for error in form.biography.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
{{ widget_editor.text_editor(form.biography) }}
|
||||
</div>
|
||||
|
||||
<h2>Préférences</h2>
|
||||
|
|
|
@ -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" %}
|
||||
|
|
|
@ -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') %}
|
||||
|
|
|
@ -20,4 +20,9 @@
|
|||
<path fill="#ffffff" d="M19,8L15,12H18A6,6 0 0,1 12,18C11,18 10.03,17.75 9.2,17.3L7.74,18.76C8.97,19.54 10.43,20 12,20A8,8 0 0,0 20,12H23M6,12A6,6 0 0,1 12,6C13,6 13.97,6.25 14.8,6.7L16.26,5.24C15.03,4.46 13.57,4 12,4A8,8 0 0,0 4,12H1L5,16L9,12"></path>
|
||||
</svg>SH4 Compatibility Tool
|
||||
</a>
|
||||
<a href="{{ url_for('calc_comparator') }}">
|
||||
<svg viewBox="0 0 24 24" fill="none">
|
||||
<path d="M5 9H19M15 18V15M9 18H9.01M12 18H12.01M12 15H12.01M9 15H9.01M15 12H15.01M12 12H12.01M9 12H9.01M8.2 21H15.8C16.9201 21 17.4802 21 17.908 20.782C18.2843 20.5903 18.5903 20.2843 18.782 19.908C19 19.4802 19 18.9201 19 17.8V6.2C19 5.0799 19 4.51984 18.782 4.09202C18.5903 3.71569 18.2843 3.40973 17.908 3.21799C17.4802 3 16.9201 3 15.8 3H8.2C7.0799 3 6.51984 3 6.09202 3.21799C5.71569 3.40973 5.40973 3.71569 5.21799 4.09202C5 4.51984 5 5.07989 5 6.2V17.8C5 18.9201 5 19.4802 5.21799 19.908C5.40973 20.2843 5.71569 20.5903 6.09202 20.782C6.51984 21 7.07989 21 8.2 21Z" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>Comparateur de calculatrices
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../extra/calcdb/calcdb-fr.html
|
|
@ -0,0 +1,141 @@
|
|||
{% extends "base/base.html" %}
|
||||
|
||||
{% set tabtitle = "Comparateur des calculatrices CASIO" %}
|
||||
|
||||
{% block title %}
|
||||
<h1>Comparateur des calculatrices CASIO</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<p>
|
||||
Cette page recense la majorité des calculatrices CASIO distribuées en
|
||||
France depuis 2005. Le tableau comparatif contient tous les détails
|
||||
logiciels et matériels des modèles, ainsi que de nombreux liens vers
|
||||
les ressources associées.
|
||||
</p>
|
||||
|
||||
<h4>Sélection des modèles</h4>
|
||||
<p>
|
||||
<button onclick="select_set(default_set)">Afficher les modèles actuels</button>
|
||||
<button onclick="select_all()">Tout afficher</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<a id="calcdb-permalink" href="https://www.planet-casio.com/Fr/infos/comparateur?g25+e2,g35+e2,g90+e,cp400+e">Lien permanent vers cette configuration</a>
|
||||
</div>
|
||||
|
||||
<div id="calcdb-checkboxes">
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g25ppro" data-name="g25+pro">
|
||||
<img src="/static/icons/calc/g25+pro.png">
|
||||
<span>Graph 25+ Pro</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g25pe" data-name="g25+e">
|
||||
<img src="/static/icons/calc/g25+e.png">
|
||||
<span>Graph 25+E</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" checked="" data-calc="g25pe2" data-name="g25+e2">
|
||||
<img src="/static/icons/calc/g25+e2.png">
|
||||
<span>Graph 25+E II</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g35p" data-name="g35+">
|
||||
<img src="/static/icons/calc/g35+.png">
|
||||
<span>Graph 35+</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g35pusb3" data-name="g35+usb3">
|
||||
<img src="/static/icons/calc/g35+usb3.png">
|
||||
<span>Graph 35+ USB (SH3)</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g35pusb4" data-name="g35+usb4">
|
||||
<img src="/static/icons/calc/g35+usb4.png">
|
||||
<span>Graph 35+ USB (SH4)</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g35pe" data-name="g35+e">
|
||||
<img src="/static/icons/calc/g35+e.png">
|
||||
<span>Graph 35+E</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" checked="" data-calc="g35pe2" data-name="g35+e2">
|
||||
<img src="/static/icons/calc/g35+e2.png">
|
||||
<span>Graph 35+E II</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g75" data-name="g75">
|
||||
<img src="/static/icons/calc/g75.png">
|
||||
<span>Graph 75</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g75p" data-name="g75+">
|
||||
<img src="/static/icons/calc/g75+.png">
|
||||
<span>Graph 75+</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g75pe" data-name="g75+e">
|
||||
<img src="/static/icons/calc/g75+e.png">
|
||||
<span>Graph 75+E</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g85" data-name="g85">
|
||||
<img src="/static/icons/calc/g85.png">
|
||||
<span>Graph 85</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g85sd" data-name="g85sd">
|
||||
<img src="/static/icons/calc/g85sd.png">
|
||||
<span>Graph 85 SD</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="g95" data-name="g95">
|
||||
<img src="/static/icons/calc/g95.png">
|
||||
<span>Graph 95 (SD)</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="cg20" data-name="cg20">
|
||||
<img src="/static/icons/calc/cg20.png">
|
||||
<span>Prizm</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" checked="" data-calc="g90pe" data-name="g90+e">
|
||||
<img src="/static/icons/calc/g90+e.png">
|
||||
<span>Graph 90+E</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="cp300" data-name="cp300">
|
||||
<img src="/static/icons/calc/cp300.png">
|
||||
<span>Classpad 300</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="cp330" data-name="cp330">
|
||||
<img src="/static/icons/calc/cp330.png">
|
||||
<span>Classpad 330</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="cp330p" data-name="cp330+">
|
||||
<img src="/static/icons/calc/cp330+.png">
|
||||
<span>Classpad 330+</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" unchecked="" data-calc="cp400" data-name="cp400">
|
||||
<img src="/static/icons/calc/cp400.png">
|
||||
<span>Classpad 400</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" onchange="toggle_calc(this)" checked="" data-calc="cp400pe" data-name="cp400+e">
|
||||
<img src="/static/icons/calc/cp400+e.png">
|
||||
<span>Classpad 400+E</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Tableau comparatif</h4>
|
||||
<div id="calcdb">
|
||||
{% include "calcdb-fr.html" %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% set tabtitle = "Shoutbox" %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr-FR">
|
||||
{% include "base/head.html" with context %}
|
||||
<body>
|
||||
<div id="v5shoutbox-fullscreen"></div>
|
||||
{% include "widgets/v5shoutbox.html" %}
|
||||
{% include "base/scripts.html" %}
|
||||
</body>
|
||||
</html>
|
|
@ -48,6 +48,22 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.summary.label }}
|
||||
{{ form.summary }}
|
||||
{% for error in form.summary.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.thumbnail.label }}
|
||||
{{ form.thumbnail }}
|
||||
{% for error in form.thumbnail.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if form.pseudo %}
|
||||
<div>
|
||||
{{ form.pseudo.label }}
|
||||
|
|
|
@ -75,6 +75,22 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.summary.label }}
|
||||
{{ form.summary }}
|
||||
{% for error in form.summary.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.thumbnail.label }}
|
||||
{{ form.thumbnail }}
|
||||
{% for error in form.thumbnail.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ widget_editor.text_editor(form.message) }}
|
||||
|
||||
<div>
|
||||
|
|
|
@ -15,10 +15,20 @@
|
|||
<section>
|
||||
<h1>{{ t.title }}</h1>
|
||||
|
||||
{% if t.summary %}
|
||||
<div>{{ t.summary | md }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if t.thread.top_comment %}
|
||||
{% call widget_thread.thread_leader(t.thread.top_comment) %}
|
||||
{% call widget_thread.thread_leader(t.thread.top_comment, t) %}
|
||||
<div class="info">
|
||||
<div>Posté le {{ t.date_created | dyndate }}</div>
|
||||
<div>
|
||||
Créé le {{ t.date_created | dyndate }}
|
||||
<!-- We check if the formatted date is not the same, because the date might differ slightly between the two even if it's the same -->
|
||||
{% if t.thread.top_comment.date_created | dyndate != t.date_created | dyndate %}
|
||||
(Posté le {{ t.thread.top_comment.date_created | dyndate }})
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ widget_thread.post_actions(t) }}
|
||||
</div>
|
||||
{{ t.thread.top_comment.text | md }}
|
||||
|
|
|
@ -8,11 +8,67 @@
|
|||
|
||||
{% 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>S’inscrire 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>
|
||||
{% for n in last_news %}
|
||||
<li>
|
||||
<a href="{{ url_for('forum_topic', f=n.forum, page=(n,'fin'))}}"><img src="{{ n.thumbnail.url }}"/></a>
|
||||
<div>
|
||||
<h3><a href="{{ url_for('forum_topic', f=n.forum, page=(n,'fin'))}}">{{ n.title }}</a></h3>
|
||||
<p class="date"><i>Publié par <a href="{{ url_for('user_by_id', user_id=n.author.id) }}">{{ n.author.name }}</a>
|
||||
le <time>{{ n.date_created | dyndate }}</time></i></p>
|
||||
{{ (n.summary or "") | md }}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li><p><i><a href="/forum/actus/">Voir toutes les news</a></i></p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="home-shoutbox" style="border: 1px solid #e0e0e0">
|
||||
{% include "widgets/v5shoutbox.html" %}
|
||||
</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>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
{% extends "base/base.html" %}
|
||||
{% import "widgets/editor.html" as widget_editor %}
|
||||
{% import "widgets/user.html" as widget_user %}
|
||||
|
||||
{% set tabtitle = "Déplacer un commentaire" %}
|
||||
|
||||
{% block title %}
|
||||
<a href='/forum'>Forum de Planète Casio</a> » Fusion de commentaire</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<h1>Fusionner deux commentaires</h1>
|
||||
|
||||
<table class="thread comment">
|
||||
<tr>
|
||||
<td class="author">{{ widget_user.profile(comment.author) }}</td>
|
||||
<td><div>{{ comment.text | md }}</div></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<form action="" method="post">
|
||||
<h3></h3>
|
||||
{{ merge_form.hidden_tag() }}
|
||||
|
||||
<div>
|
||||
{{ merge_form.post.label }}
|
||||
{{ merge_form.post }}
|
||||
{% for error in merge_form.post.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div>{{ merge_form.submit(class_='bg-ok') }}</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<section>
|
||||
{% if p.thread.top_comment %}
|
||||
{% call widget_thread.thread_leader(p.thread.top_comment) %}
|
||||
{% call widget_thread.thread_leader(p.thread.top_comment, p) %}
|
||||
<div class="info">
|
||||
{{ widget_thread.post_actions(p) }}
|
||||
</div>
|
||||
|
@ -55,11 +55,11 @@
|
|||
{% endcall %}
|
||||
{% endif %}
|
||||
|
||||
{{ widget_pagination.paginate(comments, 'program_view', p) }}
|
||||
{{ widget_pagination.paginate(comments, 'program_view', p, {}) }}
|
||||
|
||||
{{ widget_thread.thread(comments.items, p.thread.top_comment) }}
|
||||
|
||||
{{ widget_pagination.paginate(comments, 'program_view', p) }}
|
||||
{{ widget_pagination.paginate(comments, 'program_view', p, {}) }}
|
||||
|
||||
|
||||
{% if p.thread.locked %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,21 +1,77 @@
|
|||
{% extends "base/base.html" %}
|
||||
{% import "widgets/pagination.html" as widget_pagination with context %}
|
||||
|
||||
{% set tabtitle = "Recherche avancée" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="form">
|
||||
<section class="search-page">
|
||||
<h1>Recherche avancée</h1>
|
||||
|
||||
<form action="" method="get">
|
||||
<div>
|
||||
<form class="form" action="{{ url_for('search') }}" method="get">
|
||||
{{ form.csrf_token }}
|
||||
<div class="query">
|
||||
{{ form.q.label }}
|
||||
{{ form.q(value=request.args.get('q')) }}
|
||||
{% for error in form.q.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="submit">
|
||||
{{ form.submit(class_="bg-ok") }}
|
||||
{% for error in form.submit.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="date">
|
||||
{{ form.date.label }}
|
||||
{{ form.date }}
|
||||
{{ form.date(value=request.args.get('date')) }}
|
||||
{% for error in form.date.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="sort">
|
||||
{{ form.sortBy.label }}
|
||||
{{ form.sortBy(value=request.args.get('sortBy')) }}
|
||||
{% for error in form.sortBy.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="scope">
|
||||
{{ form.scope.label }}
|
||||
{{ form.scope(value=request.args.get('scope')) }}
|
||||
{% for error in form.scope.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="search-results">
|
||||
{{ widget_pagination.paginate(results, 'search', None, {
|
||||
'q': request.args.get('q'),
|
||||
'date': request.args.get('date'),
|
||||
'sortBy': request.args.get('sortBy')}) }}
|
||||
{% for i in results.items %}
|
||||
<div>
|
||||
{{ i.id }} {{ i.title }}<br>
|
||||
{% if i.forum %}
|
||||
<a href="{{ url_for('forum_topic', f=i.forum, page=(i , 'fin')) }}">{{ i.title }}</a>
|
||||
{% elif i.thread %}
|
||||
{% if i.thread.owner_program %}
|
||||
{% if i.thread.owner_program[0].id == i.id %}
|
||||
<a href="{{ url_for('program_view', page=(i.thread.owner_program[0], '')) }}">{{ i.thread.owner_program[0].name }}</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('redirect_post', postid=i.id) }}">{{ i.thread.owner_program[0].name }}</a>
|
||||
{% endif %}
|
||||
{% elif i.thread.owner_topic %}
|
||||
<a href="{{ url_for('redirect_post', postid=i.id) }}">{{ i.thread.owner_topic[0].title }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{{ i.headline }}<br>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ widget_pagination.paginate(results, 'search', None, {
|
||||
'q': request.args.get('q'),
|
||||
'date': request.args.get('date'),
|
||||
'sortBy': request.args.get('sortBy')}) }}
|
||||
</div>
|
||||
<div>{{ form.submit(class_="bg-ok") }}</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<li><a href="https://gitea.planet-casio.com">Gitea</a> (forge Git)</li>
|
||||
<li><a href="https://wiki.planet-casio.com">Wiki</a> (wiki répétoriant tout un tas de trucs)</li>
|
||||
<li><a href="https://bible.planet-casio.com">Bible</a> (la bible du programmeur Casio bas niveau)</li>
|
||||
<li><a href="{{ url_for('calc_comparator') }}">Comparateur de calculatrices</a> (Un comparateur des calculatrices CASIO)</li>
|
||||
</ul>
|
||||
</p>
|
||||
</section>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{% macro text_editor(field, label=True, autofocus=false) %}
|
||||
<div class="editor">
|
||||
<div id="editor_{{ field.name}}" class="editor">
|
||||
<!-- Field label if needed -->
|
||||
{{ field.label if label }}
|
||||
|
||||
<!-- Buttons for the text editor -->
|
||||
<div class="btn-group">
|
||||
<!-- Underline, Bold, Italic, Strikethrough -->
|
||||
|
@ -110,9 +113,9 @@
|
|||
<div class="modal" style="display: none">
|
||||
<div>
|
||||
<label for="link-desc-input">Description</label>
|
||||
<input type="text" id="link-desc-input" name="link-desc-input">
|
||||
<input type="text" class="link-desc-input" name="link-desc-input">
|
||||
<label for="link-link-input">Lien</label>
|
||||
<input type="url" id="link-link-input" name="link-link-input">
|
||||
<input type="url" class="link-link-input" name="link-link-input">
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="button bg-ok" onclick="editor_insert_link(event, 'link-link-input', 'link-desc-input')">Valider</a>
|
||||
|
@ -128,17 +131,17 @@
|
|||
<div class="modal" style="display: none">
|
||||
<div>
|
||||
<label for="media-alt-input">Texte alternatif (sera affiché en cas d'erreur de chargement)</label>
|
||||
<input type="text" id="media-alt-input" name="media-alt-input">
|
||||
<input type="text" class="media-alt-input" name="media-alt-input">
|
||||
<label for="media-link-input">Lien vers le média</label>
|
||||
<input type="url" id="media-link-input" name="media-link-input">
|
||||
<input type="url" class="media-link-input" name="media-link-input">
|
||||
<fieldset title="Optionel mais peut être important si le type de média n'est pas correctement détecté">
|
||||
<legend>
|
||||
Type de média
|
||||
</legend>
|
||||
<label for="media-type-video" title="Vidéo">Vidéo</label>
|
||||
<input type="radio" id="media-type-video" name="media-type" value="video" title="Vidéo" />
|
||||
<input type="radio" class="media-type media-type-video" name="media-type" value="video" title="Vidéo" />
|
||||
<label for="media-type-image" title="Image">Image</label>
|
||||
<input type="radio" id="media-type-image" name="media-type" value="image" title="Image" />
|
||||
<input type="radio" class="media-type media-type-image" name="media-type" value="image" title="Image" />
|
||||
</fieldset>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -155,14 +158,14 @@
|
|||
</svg>
|
||||
</button>
|
||||
-->
|
||||
<div id="filler"></div>
|
||||
<button id="manual_preview" type="button" onclick="preview(manual=true)" style="display: none" title="Rafraichir la prévisualisation">
|
||||
<div class="filler"></div>
|
||||
<button class="manual_preview" type="button" onclick="manual_preview('{{ field.name }}')" style="display: none" title="Rafraichir la prévisualisation">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="toggle_preview" type="button" onclick="toggle_auto_preview()" title="Désactiver la prévisualisation">
|
||||
<button class="toggle_preview" type="button" onclick="toggle_auto_preview()" title="Désactiver la prévisualisation">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye-slash" viewBox="0 0 16 16">
|
||||
<path d="M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7.028 7.028 0 0 0-2.79.588l.77.771A5.944 5.944 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.134 13.134 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755-.165.165-.337.328-.517.486l.708.709z"/>
|
||||
<path d="M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829l.822.822zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829z"/>
|
||||
|
@ -172,16 +175,21 @@
|
|||
<a href="#">Aide</a>
|
||||
</div>
|
||||
|
||||
<!-- Field & desc -->
|
||||
{{ field.label if label }}
|
||||
<!-- Field -->
|
||||
{{ field() }}
|
||||
|
||||
<!-- Comment preview -->
|
||||
<div id="editor_content_preview"></div>
|
||||
<div class="editor_content_preview"></div>
|
||||
|
||||
<!-- Display errors -->
|
||||
{% for error in field.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.addEventListener("load", function(){
|
||||
editor_setup("{{ field.name }}");
|
||||
});
|
||||
</script>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
{% set can_topcomm = auth and current_user.can_set_topcomment(post) %}
|
||||
{% set can_move = auth and current_user.can_edit_post(post) and post.type == "comment" %}
|
||||
{% set can_lock = auth and current_user.can_lock_thread(post) %}
|
||||
{% set can_merge = auth and current_user.can_merge_post(post) %}
|
||||
|
||||
{% if post.type == "topic" %}
|
||||
{% set suffix = " le sujet" %}
|
||||
|
@ -32,7 +33,11 @@
|
|||
<a href="{{ url_for('move_post', postid=post.id) }}">Déplacer</a>
|
||||
{% endif %}
|
||||
|
||||
{% if can_punish %}
|
||||
{% if can_merge %}
|
||||
<a href="{{ url_for('merge_post', postid=post.id) }}">Fusioner</a>
|
||||
{% endif %}
|
||||
|
||||
{% 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 %}
|
||||
|
@ -98,12 +103,20 @@
|
|||
|
||||
leader: Posts's top comment (actual rendering is delegated to caller) #}
|
||||
|
||||
{% macro thread_leader(leader) %}
|
||||
{% macro thread_leader(leader, thread) %}
|
||||
<table class="thread topcomment">
|
||||
{# Empty line to get normal background (instead of alternate one) #}
|
||||
<tr></tr>
|
||||
<tr id="{{ leader.id }}">
|
||||
<td class="author">{{ widget_user.profile(leader.author) }}</td>
|
||||
<td class="author">
|
||||
{% if thread.author_id != leader.author_id %}
|
||||
<em>Auteur de la page:</em>
|
||||
{{ widget_user.profile(thread.author, minimal = True) }}
|
||||
<hr/>
|
||||
<em>Auteur du post principal:</em>
|
||||
{% endif %}
|
||||
{{ widget_user.profile(leader.author) }}
|
||||
</td>
|
||||
<td class="message">{{ caller() }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{% macro profile(user) %}
|
||||
{% macro profile(user, minimal = False) %}
|
||||
{% if user.type == "member" %}
|
||||
<div class="profile">
|
||||
<div class="profile{% if minimal %} minimal {% endif %}">
|
||||
<img class="profile-avatar" src="{{ user.avatar_url }}" alt="Avatar de {{ user.name }}">
|
||||
<div>
|
||||
<div class="profile-name"><a href="{{ url_for('user', username=user.name) }}">{{ user.name }}</a></div>
|
||||
{% if minimal == False %}
|
||||
{% if user.title %}
|
||||
<div class="profile-title" style="{{ user.title.css }}">{{ user.title.name }}</div>
|
||||
{% else %}
|
||||
|
@ -16,6 +17,7 @@
|
|||
{% else %}
|
||||
<div class="profile-xp profile-xp-100"><div style='width: {{ user.level[0] - 100 }}%;'></div></div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
|
|