Compare commits
67 Commits
Author | SHA1 | Date |
---|---|---|
Eragon | 5813de1208 | |
Eragon | 08054175bf | |
Eragon | 2685ffcbc7 | |
Eragon | f9c4d05121 | |
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 |
15
Makefile
|
@ -2,6 +2,13 @@ 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
|
||||
|
||||
|
@ -10,4 +17,12 @@ css: $(obj)
|
|||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,7 +6,7 @@ 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
|
||||
|
||||
|
|
|
@ -43,7 +43,8 @@ def edit_account():
|
|||
newsletter=form.newsletter.data,
|
||||
theme=form.theme.data
|
||||
)
|
||||
ldap.edit(old_username, current_user)
|
||||
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()
|
||||
|
@ -55,7 +56,11 @@ def edit_account():
|
|||
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)
|
||||
|
||||
|
|
|
@ -64,7 +64,8 @@ def adm_edit_account(user_id):
|
|||
newsletter=form.newsletter.data,
|
||||
xp=form.xp.data or None,
|
||||
)
|
||||
ldap.edit(old_username, user)
|
||||
if V5Config.USE_LDAP:
|
||||
ldap.edit(old_username, user)
|
||||
user.update(password=form.password.data or None)
|
||||
db.session.merge(user)
|
||||
db.session.commit()
|
||||
|
@ -75,6 +76,9 @@ def adm_edit_account(user_id):
|
|||
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:
|
||||
|
|
|
@ -5,12 +5,10 @@ from flask import send_file, url_for
|
|||
@app.route('/chat')
|
||||
def chat():
|
||||
return render('chat.html',
|
||||
styles=[
|
||||
'+css/v5shoutbox.css'],
|
||||
scripts=[
|
||||
'-scripts/trigger_menu.js',
|
||||
'-scripts/editor.js'])
|
||||
|
||||
@app.route('/v5shoutbox.js')
|
||||
def v5shoutbox_js():
|
||||
return send_file('static/scripts/v5shoutbox.js')
|
||||
@app.route('/v5shoutbox_worker.js')
|
||||
def v5shoutbox_worker_js():
|
||||
return send_file('static/scripts/v5shoutbox_worker.js')
|
||||
|
|
|
@ -54,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()
|
||||
|
||||
|
@ -68,6 +69,10 @@ 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
|
||||
|
@ -78,7 +83,7 @@ def forum_page(f, page=1):
|
|||
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,7 +5,12 @@ from app.utils.render import render
|
|||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render('index.html', styles=["+css/homepage.css"])
|
||||
return render('index.html',
|
||||
styles=["+css/homepage.css"],
|
||||
scripts=[
|
||||
"+scripts/v5shoutbox_ui.js",
|
||||
"+scripts/v5shoutbox.js"
|
||||
])
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
|
|
|
@ -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
|
||||
|
@ -102,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'])
|
||||
|
@ -237,4 +245,48 @@ def lock_thread(postid):
|
|||
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)
|
|
@ -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,7 +158,7 @@ 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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1 +1 @@
|
|||
../../../submodules/v5shoutbox/style.css
|
||||
../../../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 |
|
@ -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/
|
|
@ -1 +1 @@
|
|||
../../../submodules/v5shoutbox/v5shoutbox.js
|
||||
../../../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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -4,11 +4,5 @@
|
|||
{% if current_user.is_authenticated and current_user.priv('misc.dev-infos') %}
|
||||
<p>Page générée en {{ "%.3f" % g.request_time() }} secondes.</p>
|
||||
{% endif %}
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<p>Ceci est un environnement de test. Tout contenu peut être supprimé sans avertissement préalable.</p>
|
||||
=======
|
||||
>>>>>>> e15005a... Ajout des stats sur la durée de chargement
|
||||
=======
|
||||
>>>>>>> e15005a427f95829bbbad8f0d625ab9cb0c30e69
|
||||
</footer>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
{% endfor %}
|
||||
{% for m in modules %}
|
||||
<script type="module" src={{url_for('static', filename=m)}}></script>
|
||||
{% endfor %}
|
||||
{% 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 %}
|
|
@ -4,6 +4,7 @@
|
|||
<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>
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -46,69 +46,22 @@
|
|||
<div class="home-news">
|
||||
<h1>Actualitées</h1>
|
||||
<ul>
|
||||
{% for n in last_news %}
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/tdm9_collision2.jpg"/></a>
|
||||
<a href="{{ url_for('forum_topic', f=n.forum, page=(n,'fin'))}}"><img src="{{ n.thumbnail.url }}"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Les collisions — partie 2</a></h3>
|
||||
<p class="date"><i>Publié par <a href="#">Shadow15510</a> le <time>09/06/2023 09:10</time></i></p>
|
||||
La deuxième partie du tutoriel du mercredi sur les collisions est maintenant disponible en vidéo.
|
||||
<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>
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-thumb-results.png"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Résultats du CPC #30 — Les profondeurs !</a>
|
||||
</h3>
|
||||
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>08/06/2023 22:10</time></i></p>
|
||||
Les programmes sont profonds et avec un peu de chance les tests aussi ! </div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/tituya-thumb.png"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Un second renardministrateur !</a>
|
||||
</h3>
|
||||
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>07/06/2023 22:55</time></i></p>
|
||||
Vive la renardocratie ! </div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/massy2.jpg"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Visite chez Casio France à Massy, musée inclus</a>
|
||||
</h3>
|
||||
<p class="date"><i>Publié par <a href="#">Critor</a> le <time>05/06/2023 11:51</time></i></p>
|
||||
Compte-rendu de notre visite au sein même des locaux de Casio France à Massy en mai 2023, passage par leur musée privé inclus. </div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-title.jpg"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Le CPC#30 - Les Profondeurs ... C'est désormais terminé ... Bravo à tous les participants.</a>
|
||||
</h3>
|
||||
<p class="date"><i>Publié par <a href="#">Slyvtt</a> le <time>03/06/2023 21:36</time></i></p>
|
||||
Aujourd'hui sonne la fin du CPC#30 avec à la clef 6 participations pour vous donner du fun. Graphs Monochromes et Couleurs sont à la fête avec des programmes en Basic et des Addins. </div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/tdm9_miniature.jpg"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Les Tutos du Mercredi débarquent sur Youtube !</a>
|
||||
</h3>
|
||||
<p class="date"><i>Publié par <a href="#">Shadow15510</a> le <time>29/05/2023 14:15</time></i></p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-thumb.jpg"/></a>
|
||||
<div>
|
||||
<h3><a href="#">Le CPC #30 - Les profondeurs !</a>
|
||||
</h3>
|
||||
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>27/05/2023 18:00</time></i></p>
|
||||
Le concours CPC revient en force et c'est le moment de se plonger (métaphoriquement <i>et</i> littéralement !) dans le game design fin et la programmation sportive. Une semaine pour les programmer tous, et dans les profondeurs les li— oups ! </div>
|
||||
</li>
|
||||
<li><p><i><a href="https://www.planet-casio.com/Fr/forums/partie6-.html">Voir toutes les news</a></i></p></li>
|
||||
{% 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 #737373; padding: 20px">
|
||||
<div id="v5shoutbox">
|
||||
Ici y’aura la shoutbox (plus tard)
|
||||
</div>
|
||||
<div class="home-shoutbox" style="border: 1px solid #e0e0e0">
|
||||
{% include "widgets/v5shoutbox.html" %}
|
||||
</div>
|
||||
<div class="home-projects">
|
||||
<h1>Projets du moment</h1>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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,6 +33,10 @@
|
|||
<a href="{{ url_for('move_post', postid=post.id) }}">Déplacer</a>
|
||||
{% endif %}
|
||||
|
||||
{% 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>
|
||||
|
@ -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 %}
|
||||
|
|
|
@ -1 +1 @@
|
|||
../../../submodules/v5shoutbox/widget.html
|
||||
../../../extra/v5shoutbox/widget.html
|
|
@ -3,7 +3,7 @@ from wtforms.validators import Optional, ValidationError
|
|||
|
||||
def antibot_validator(form, field):
|
||||
if field.data:
|
||||
raise ValidationError('Bas les pattes!')
|
||||
raise ValidationError('Bas les pattes !')
|
||||
return True
|
||||
|
||||
class AntibotField(EmailField):
|
||||
|
|
|
@ -19,6 +19,10 @@ def render(*args, styles=[], scripts=[], modules=[], **kwargs):
|
|||
'css/debugger.css',
|
||||
'css/programs.css',
|
||||
'css/editor.css',
|
||||
'css/search.css',
|
||||
# The order of these two is important; shoutbox.css overrides
|
||||
'css/v5shoutbox.css',
|
||||
'css/shoutbox.css',
|
||||
]
|
||||
scripts_ = [
|
||||
'scripts/trigger_menu.js',
|
||||
|
@ -28,6 +32,7 @@ def render(*args, styles=[], scripts=[], modules=[], **kwargs):
|
|||
'scripts/filter.js',
|
||||
'scripts/tag_selector.js',
|
||||
'scripts/editor.js',
|
||||
'scripts/v5shoutbox_irc.js',
|
||||
]
|
||||
modules_ = [
|
||||
'scripts/emoji-picker-element/index.js',
|
||||
|
|
|
@ -56,3 +56,13 @@ def own_title(form, title):
|
|||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def attachment_exists(form, number):
|
||||
try:
|
||||
n = int(number.data)
|
||||
if n <= 0 or n > len(form.attachments.data):
|
||||
raise Exception()
|
||||
except Exception as e:
|
||||
raise ValidationError('L’illustration doit être le numéro d’une pièce-jointe')
|
||||
|
||||
return True
|
||||
|
|
|
@ -86,6 +86,9 @@ class DefaultConfig(object):
|
|||
ENABLE_FLASK_DEBUG_TOOLBAR = False
|
||||
# Tab title prefix. Useful to dissociate local/dev/prod tabs
|
||||
TABTITLE_PREFIX = ""
|
||||
# Posts which are separated by more than this value (in seconds)
|
||||
# can't be merged together (I thought that 10 minutes was a good default)
|
||||
MERGE_AGE_THRESHOLD = 600
|
||||
|
||||
@staticmethod
|
||||
def v5logger():
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
calcdb/
|
||||
emoji-picker-element/
|
||||
v5shoutbox/
|
|
@ -0,0 +1,34 @@
|
|||
"""Search functions
|
||||
|
||||
Revision ID: a803745f7840
|
||||
Revises: 5ffc4e562ed8
|
||||
Create Date: 2023-06-27 23:10:06.088917
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a803745f7840'
|
||||
down_revision = '5ffc4e562ed8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("""CREATE FUNCTION websearch_to_tsquery_multilang(text) RETURNS tsquery AS $$
|
||||
SELECT websearch_to_tsquery('french', $1) ||
|
||||
websearch_to_tsquery('english', $1) ||
|
||||
websearch_to_tsquery('simple', $1)
|
||||
$$ LANGUAGE sql IMMUTABLE;""")
|
||||
op.execute("""CREATE FUNCTION to_tsvector_multilang(text) RETURNS tsvector AS $$
|
||||
SELECT to_tsvector('french', $1) ||
|
||||
to_tsvector('english', $1) ||
|
||||
to_tsvector('simple', $1)
|
||||
$$ LANGUAGE sql IMMUTABLE;""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("DROP FUNCTION websearch_to_tsquery_multilang(text);")
|
||||
op.execute("DROP FUNCTION to_tsvector_multilang(text);")
|
|
@ -0,0 +1,36 @@
|
|||
"""Topics: add summary and thumbnail
|
||||
|
||||
Revision ID: a8f539a93bd5
|
||||
Revises: 5ffc4e562ed8
|
||||
Create Date: 2023-07-25 21:44:55.782425
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a8f539a93bd5'
|
||||
down_revision = '5ffc4e562ed8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('topic', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('summary', sa.UnicodeText(), nullable=True))
|
||||
batch_op.add_column(sa.Column('thumbnail_id', postgresql.UUID(as_uuid=True), nullable=True))
|
||||
batch_op.create_foreign_key(None, 'attachment', ['thumbnail_id'], ['id'])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('topic', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.drop_column('thumbnail_id')
|
||||
batch_op.drop_column('summary')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,24 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: dc99ddd17cf3
|
||||
Revises: a803745f7840, a8f539a93bd5
|
||||
Create Date: 2023-08-08 19:46:55.441606
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'dc99ddd17cf3'
|
||||
down_revision = ('a803745f7840', 'a8f539a93bd5')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|