Compare commits

..

34 Commits
dev ... master

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

View File

@ -2,13 +2,6 @@ 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
@ -17,12 +10,4 @@ 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

View File

@ -4,7 +4,6 @@ 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
@ -40,8 +39,6 @@ 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")

View File

@ -1,6 +1,6 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField, MultipleFileField, SelectField, DecimalField
from wtforms.validators import InputRequired, Length, Optional
from wtforms import StringField, SubmitField, TextAreaField, MultipleFileField, SelectField
from wtforms.validators import InputRequired, Length
import app.utils.validators as vd
from app.utils.antibot_field import AntibotField
@ -54,14 +54,6 @@ 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 lillustration',
validators=[Optional(), vd.attachment_exists])
submit = SubmitField('Créer le sujet')
@ -69,7 +61,11 @@ class AnonymousTopicCreationForm(TopicCreationForm, AnonymousCommentForm):
ab = AntibotField()
class TopicEditForm(TopicCreationForm):
class TopicEditForm(CommentEditForm):
title = StringField(
'Nom du sujet',
validators=[InputRequired(), Length(min=3, max=128)])
# List of forums is generated at runtime
forum = SelectField(
'Forum',

View File

@ -10,8 +10,3 @@ 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')

View File

@ -1,63 +1,13 @@
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):
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()])
date = DateField('Date', validators=[Optional()])
submit = SubmitField('Affiner la recherche')

View File

@ -1,7 +1,6 @@
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'
@ -30,17 +29,10 @@ 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)

View File

@ -261,7 +261,7 @@ class Member(User):
if comment.type != "comment":
return False
post = comment.thread.owner_post
return self.can_edit_post(post)
return self.can_edit_post(post) and (comment.author == post.author)
def can_lock_thread(self, post):
"""Whether this member can lock the thread associated with the post"""
@ -270,11 +270,6 @@ 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)
@ -341,7 +336,7 @@ class Member(User):
self.avatar_filename)
# Resize & convert image
im = Image.open(avatar)
im.thumbnail((128, 128), Image.Resampling.LANCZOS)
im.thumbnail((128, 128), Image.ANTIALIAS)
# Change avatar id
# TODO: verify concurrency behavior

View File

@ -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, redirect
from app.routes.posts import edit
from app.routes.programs import index, submit, program
from app.routes.api import markdown

View File

@ -43,8 +43,7 @@ def edit_account():
newsletter=form.newsletter.data,
theme=form.theme.data
)
if V5Config.USE_LDAP:
ldap.edit(old_username, current_user)
ldap.edit(old_username, current_user)
current_user.update(password=form.password.data or None)
db.session.merge(current_user)
db.session.commit()
@ -56,11 +55,7 @@ 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)

View File

@ -64,8 +64,7 @@ def adm_edit_account(user_id):
newsletter=form.newsletter.data,
xp=form.xp.data or None,
)
if V5Config.USE_LDAP:
ldap.edit(old_username, user)
ldap.edit(old_username, user)
user.update(password=form.password.data or None)
db.session.merge(user)
db.session.commit()
@ -76,9 +75,6 @@ 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:

View File

@ -5,10 +5,12 @@ 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_worker.js')
def v5shoutbox_worker_js():
return send_file('static/scripts/v5shoutbox_worker.js')
@app.route('/v5shoutbox.js')
def v5shoutbox_js():
return send_file('static/scripts/v5shoutbox.js')

View File

@ -54,7 +54,6 @@ 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()
@ -69,10 +68,6 @@ 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
@ -83,7 +78,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

View File

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

View File

@ -1,4 +1,4 @@
from app import app, db, V5Config
from app import app, db
from app.models.attachment import Attachment
from app.models.comment import Comment
from app.models.forum import Forum
@ -11,18 +11,17 @@ 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, MergePost
from app.forms.post import MovePost, SearchThread
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, and_
from datetime import timedelta
from sqlalchemy import text
@app.route('/post/editer/<int:postid>', methods=['GET','POST'])
@login_required
def edit_post(postid):
# FIXME: Maybe not safe
# TODO: Maybe not safe
referrer = urlparse(request.args.get('r', default = '/', type = str)).path
print(referrer)
@ -75,12 +74,6 @@ 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
@ -109,7 +102,6 @@ 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'])
@ -245,48 +237,4 @@ 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)
@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)
return redirect(request.referrer)

View File

@ -1,29 +0,0 @@
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)

View File

@ -1,116 +1,9 @@
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 import app
from app.forms.search import AdvancedSearchForm
from app.utils.render import render
from sqlalchemy import text, func, Date
from flask import request
from flask_sqlalchemy import Pagination
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)
@app.route('/rechercher')
def search():
form = AdvancedSearchForm()
return render('search.html', form=form)

View File

@ -6,7 +6,3 @@ 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'])

View File

@ -1,17 +0,0 @@
/* 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;
}

View File

@ -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;
}
}
}

View File

@ -158,7 +158,7 @@ input[type="submit"]:focus {
left: 50%;
padding: 8px;
position: absolute;
transform: translateY(-200%);
transform: translateY(-100%);
transition: transform 0.3s;
background: var(--links);
color: var(--warn-text);

View File

@ -1,60 +0,0 @@
.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;
}

View File

@ -1,7 +1,34 @@
#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 {
margin: 20px 5% 10px 5%;
background: #ffffff;
}
#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);
}

View File

@ -11,7 +11,6 @@
:root {
--background: #171a1c; /*22292c, 1c2124, 1E1E1E, 242424,*/
--background-alt: #171a1c;
--text: #eaeaea;
--text-light: #e2e2e2;
@ -78,7 +77,7 @@
header {
--background: #0d1215;
--text: #eaeaea;
--text: #000000;
--border: 1px solid #404040;
}

View File

@ -5,7 +5,6 @@
:root {
--background: #fff;
--background-alt: #eee;
--text: #030303;
--text-light: #b62727;

View File

@ -2,7 +2,6 @@
:root {
--background: #fff;
--background-alt: rgba(0, 0, 0, .1);
--text: #000;
--text-light: #111;
@ -31,7 +30,7 @@ table {
--border: #d8d8d8;
}
table tr:nth-child(odd) {
--background: var(--background-alt);
--background: rgba(0, 0, 0, .1);
}
table th {
--background: #eee;

View File

@ -1 +1 @@
../../../extra/v5shoutbox/style.css
../../../submodules/v5shoutbox/style.css

View File

@ -3,13 +3,6 @@
align-items: center;
width: 265px;
}
.profile.minimal {
width: 176px;
}
.profile.minimal .profile-avatar {
width: 64px;
height: 64px;
}
.profile-avatar {
width: 128px;
height: 128px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

View File

@ -1,74 +0,0 @@
.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;
}
}

View File

@ -8,15 +8,6 @@
width: 265px;
}
.profile.minimal {
width: 176px;
.profile-avatar {
width: 64px;
height: 64px;
}
}
.profile-avatar {
width: 128px;
height: 128px;

View File

@ -1,709 +0,0 @@
/* 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;
});
});

View File

@ -2,34 +2,42 @@
/* Locate the editor associated to an edition event.
event: Global event emitted by one of the editor buttons
Returns [the div.editor, the textarea] */
Returns [the div.editor, the button, the textarea] */
function editor_event_source(event)
{
let button = undefined;
let editor = undefined;
/* Grab the the parent editor block. The onclick event itself
/* Grab the button and the parent editor block. The onclick event itself
usually reports the SVG in the button as the source */
let node = event.current || event.srcElement;
let node = event.target || 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 (!editor) return;
if (!button || !editor) return;
const ta = editor.querySelector(".editor textarea");
return [editor, ta];
return [editor, button, 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)
{
textarea.value = textarea.value.substring(0, start)
+ contents
+ textarea.value.substring(end);
ta.value = ta.value.substring(0, start)
+ contents
+ ta.value.substring(end);
return [start, start + contents.length];
}
@ -38,7 +46,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, ta] = editor_event_source(event);
const [editor, button, ta] = editor_event_source(event);
ta.focus();
let indexStart = ta.selectionStart;
let indexEnd = ta.selectionEnd;
@ -59,14 +67,14 @@ function editor_insert_around(event, before="", after=null)
ta.selectionStart = ta.selectionEnd = start + before.length;
}
preview(editor);
preview();
}
/* Event handler that modifies each line within the selection through a
generic function. */
function editor_act_on_lines(event, fn)
{
const [editor, ta] = editor_event_source(event);
const [editor, button, ta] = editor_event_source(event);
ta.focus();
let indexStart = ta.selectionStart;
let indexEnd = ta.selectionEnd;
@ -94,28 +102,27 @@ function editor_act_on_lines(event, fn)
ta.selectionStart = start;
ta.selectionEnd = end;
preview(editor);
preview();
}
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
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];
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");
for(i = 0; i < media_type.length; i++) {
media_type[i].checked = false;
}
// Close all modal if requested
if (!close) { return }
const modals = editor.getElementsByClassName('modal');
const modals = document.getElementsByClassName('modal');
for (const i of modals) {i.style.display = 'none'};
}
@ -136,13 +143,12 @@ function editor_inline(event, type, enable_preview = true)
}
if (enable_preview) {
const [editor, ta] = editor_event_source(event);
preview(editor);
preview();
}
}
function editor_display_link_modal(event) {
const [editor, ta] = editor_event_source(event);
const [editor, button, ta] = editor_event_source(event);
let indexStart = ta.selectionStart;
let indexEnd = ta.selectionEnd;
let selection = ta.value.substring(indexStart, indexEnd);
@ -161,16 +167,16 @@ function editor_display_link_modal(event) {
function editor_insert_link(event, link_id, text_id, media = false)
{
const [editor, ta] = editor_event_source(event);
const [editor, button, ta] = editor_event_source(event);
ta.focus();
let indexStart = ta.selectionStart;
let indexEnd = ta.selectionEnd;
const link = editor.getElementsByClassName(link_id)[0].value;
const text = editor.getElementsByClassName(text_id)[0].value;
const link = document.getElementById(link_id).value;
const text = document.getElementById(text_id).value;
let media_type = "";
const media_selector = editor.getElementsByClassName("media-type")[0];
const media_selector = document.getElementsByName("media-type");
for(i = 0; i < media_selector.length; i++) {
if (media_selector[i].checked) {
media_type = `{type=${media_selector[i].value}}`;
@ -191,7 +197,7 @@ function editor_insert_link(event, link_id, text_id, media = false)
ta.selectionStart = ta.selectionEnd = start + 1;
}
preview(editor);
preview();
}
function editor_title(event, level, diff)
@ -287,8 +293,6 @@ 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"));
@ -297,24 +301,25 @@ function toggle_auto_preview() {
}
document.cookie = `auto-preview=${!auto_preview}; max-age=31536000; SameSite=Strict; Secure`
if (!auto_preview) {
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";
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";
} else {
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";
document.getElementById("toggle_preview").title = "Activer la prévisualisation";
document.getElementById("toggle_preview").innerHTML = ENABLE_PREVIEW_ICON;
document.getElementById("manual_preview").style = "display: block";
}
}
/* This request the server to get a complete render of the current text in the textarea */
function preview(editor, manual=false) {
function preview(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 = editor.querySelector(".editor_content_preview");
const ta = editor.querySelector("textarea");
const previewArea = document.querySelector("#editor_content_preview");
const textarea = document.querySelector(".editor textarea");
const payload = {text: ta.value};
const headers = new Headers();
@ -336,95 +341,86 @@ function preview(editor, manual=false) {
});
}
/* Wrapper for user-requested preview refresh */
function manual_preview(editor_id) {
const editor = document.getElementById("editor_" + editor_id);
preview(editor, manual = true);
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";
}
}
/* Add the event listener for the textarea hotkeys and auto-preview */
function editor_setup(editor_id) {
const editor = document.getElementById("editor_" + editor_id);
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();
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";
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;
}
}
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
// Set a timeout for refreshing the preview
if (previewTimeout != null) {
clearTimeout(previewTimeout);
previewTimeout = setTimeout(() => { preview(editor) }, 3000);
});
}
previewTimeout = setTimeout(preview, 3000);
});
editor.querySelector('emoji-picker').addEventListener('emoji-click', event => {
editor_clear_modals(event);
editor_insert_around(event, "", event.detail.unicode)
preview(editor);
});
}
document.querySelector('emoji-picker').addEventListener('emoji-click', event => {
editor_clear_modals(event);
editor_insert_around(event, "", event.detail.unicode)
preview();
});

View File

@ -1 +0,0 @@
../../../extra/emoji-picker-element/

View File

@ -1 +1 @@
../../../extra/v5shoutbox/v5shoutbox.js
../../../submodules/v5shoutbox/v5shoutbox.js

View File

@ -1 +0,0 @@
../../../extra/v5shoutbox/v5shoutbox_irc.js

View File

@ -1 +0,0 @@
../../../extra/v5shoutbox/v5shoutbox_ui.js

View File

@ -1 +0,0 @@
../../../extra/v5shoutbox/v5shoutbox_worker.js

View File

@ -1,5 +1,4 @@
{% extends "base/base.html" %}
{% import "widgets/editor.html" as widget_editor %}
{% set tabtitle = "Gestion du compte" %}
@ -69,11 +68,16 @@
{% endfor %}
</div>
<div>
{{ widget_editor.text_editor(form.signature) }}
{{ 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 %}
</div>
<div>
{{ widget_editor.text_editor(form.biography) }}
{% for error in form.signature.errors %}
{{ form.biography.label }}
<textarea id="{{ form.biography.name }}" name="{{ form.biography.name }}">{{ current_user.bio }}</textarea>
{% for error in form.biography.errors %}
<span class="msgerror">{{ error }}</span>
{% endfor %}
</div>

View File

@ -53,7 +53,6 @@
<span class="msgerror">{{ error }}</span>
{% endfor %}
</div>
{{ form.ab }}
<div>{{ form.submit(class_="bg-ok") }}</div>
</form>
</div>

View File

@ -1,5 +1,4 @@
{% extends "base/base.html" %}
{% import "widgets/editor.html" as widget_editor %}
{% set tabtitle = "Administration - Édition du compte de " + user.name %}
@ -82,10 +81,18 @@
{% endfor %}
</div>
<div>
{{ widget_editor.text_editor(form.signature) }}
{{ 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 %}
</div>
<div>
{{ widget_editor.text_editor(form.biography) }}
{{ 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 %}
</div>
<h2>Préférences</h2>

View File

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

View File

@ -20,9 +20,4 @@
<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>

View File

@ -3,4 +3,4 @@
{% endfor %}
{% for m in modules %}
<script type="module" src={{url_for('static', filename=m)}}></script>
{% endfor %}
{% endfor %}

View File

@ -1 +0,0 @@
../../extra/calcdb/calcdb-fr.html

View File

@ -1,141 +0,0 @@
{% 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 %}

View File

@ -4,7 +4,6 @@
<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>

View File

@ -48,22 +48,6 @@
{% 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 }}

View File

@ -75,22 +75,6 @@
{% 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>

View File

@ -15,20 +15,10 @@
<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, t) %}
{% call widget_thread.thread_leader(t.thread.top_comment) %}
<div class="info">
<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>
<div>Posté le {{ t.date_created | dyndate }}</div>
{{ widget_thread.post_actions(t) }}
</div>
{{ t.thread.top_comment.text | md }}

View File

@ -46,22 +46,69 @@
<div class="home-news">
<h1>Actualitées</h1>
<ul>
{% for n in last_news %}
<li>
<a href="{{ url_for('forum_topic', f=n.forum, page=(n,'fin'))}}"><img src="{{ n.thumbnail.url }}"/></a>
<a href="#"><img src="https://www.planet-casio.com/images/staff/tdm9_collision2.jpg"/></a>
<div>
<h3><a href="{{ url_for('forum_topic', f=n.forum, page=(n,'fin'))}}">{{ n.title }}</a></h3>
<p class="date"><i>Publié par <a href="{{ url_for('user_by_id', user_id=n.author.id) }}">{{ n.author.name }}</a>
le <time>{{ n.date_created | dyndate }}</time></i></p>
{{ (n.summary or "") | md }}
<h3><a href="#">Les collisions — partie 2</a></h3>
<p class="date"><i>Publié par <a href="#">Shadow15510</a> le <time>09/06/2023 09:10</time></i></p>
La deuxième partie du tutoriel du mercredi sur les collisions est maintenant disponible en vidéo.
</div>
</li>
{% endfor %}
<li><p><i><a href="/forum/actus/">Voir toutes les news</a></i></p></li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-thumb-results.png"/></a>
<div>
<h3><a href="#">Résultats du CPC #30 — Les profondeurs !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>08/06/2023 22:10</time></i></p>
Les programmes sont profonds et avec un peu de chance les tests aussi ! </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/tituya-thumb.png"/></a>
<div>
<h3><a href="#">Un second renardministrateur !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>07/06/2023 22:55</time></i></p>
Vive la renardocratie ! </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/massy2.jpg"/></a>
<div>
<h3><a href="#">Visite chez Casio France à Massy, musée inclus</a>
</h3>
<p class="date"><i>Publié par <a href="#">Critor</a> le <time>05/06/2023 11:51</time></i></p>
Compte-rendu de notre visite au sein même des locaux de Casio France à Massy en mai 2023, passage par leur musée privé inclus. </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-title.jpg"/></a>
<div>
<h3><a href="#">Le CPC#30 - Les Profondeurs ... C'est désormais terminé ... Bravo à tous les participants.</a>
</h3>
<p class="date"><i>Publié par <a href="#">Slyvtt</a> le <time>03/06/2023 21:36</time></i></p>
Aujourd'hui sonne la fin du CPC#30 avec à la clef 6 participations pour vous donner du fun. Graphs Monochromes et Couleurs sont à la fête avec des programmes en Basic et des Addins. </div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/tdm9_miniature.jpg"/></a>
<div>
<h3><a href="#">Les Tutos du Mercredi débarquent sur Youtube !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Shadow15510</a> le <time>29/05/2023 14:15</time></i></p>
</div>
</li>
<li>
<a href="#"><img src="https://www.planet-casio.com/images/staff/CPC30-image-thumb.jpg"/></a>
<div>
<h3><a href="#">Le CPC #30 - Les profondeurs !</a>
</h3>
<p class="date"><i>Publié par <a href="#">Lephenixnoir</a> le <time>27/05/2023 18:00</time></i></p>
Le concours CPC revient en force et c'est le moment de se plonger (métaphoriquement <i>et</i> littéralement !) dans le game design fin et la programmation sportive. Une semaine pour les programmer tous, et dans les profondeurs les li— oups ! </div>
</li>
<li><p><i><a href="https://www.planet-casio.com/Fr/forums/partie6-.html">Voir toutes les news</a></i></p></li>
</ul>
</div>
<div class="home-shoutbox" style="border: 1px solid #e0e0e0">
{% include "widgets/v5shoutbox.html" %}
<div class="home-shoutbox" style="border: 1px solid #737373; padding: 20px">
<div id="v5shoutbox">
Ici yaura la shoutbox (plus tard)
</div>
</div>
<div class="home-projects">
<h1>Projets du moment</h1>

View File

@ -1,37 +0,0 @@
{% 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 %}

View File

@ -17,7 +17,7 @@
<section>
{% if p.thread.top_comment %}
{% call widget_thread.thread_leader(p.thread.top_comment, p) %}
{% call widget_thread.thread_leader(p.thread.top_comment) %}
<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 %}

View File

@ -1,77 +1,21 @@
{% extends "base/base.html" %}
{% import "widgets/pagination.html" as widget_pagination with context %}
{% set tabtitle = "Recherche avancée" %}
{% block content %}
<section class="search-page">
<section class="form">
<h1>Recherche avancée</h1>
<form class="form" action="{{ url_for('search') }}" method="get">
{{ form.csrf_token }}
<div class="query">
<form action="" method="get">
<div>
{{ form.q.label }}
{{ form.q(value=request.args.get('q')) }}
{% for error in form.q.errors %}
<span class="msgerror">{{ error }}</span>
{% endfor %}
</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">
<div>
{{ form.date.label }}
{{ 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')}) }}
{{ form.date }}
</div>
<div>{{ form.submit(class_="bg-ok") }}</div>
</form>
</section>
{% endblock %}

View File

@ -14,7 +14,6 @@
<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>

View File

@ -1,8 +1,5 @@
{% macro text_editor(field, label=True, autofocus=false) %}
<div id="editor_{{ field.name}}" class="editor">
<!-- Field label if needed -->
{{ field.label if label }}
<div class="editor">
<!-- Buttons for the text editor -->
<div class="btn-group">
<!-- Underline, Bold, Italic, Strikethrough -->
@ -113,9 +110,9 @@
<div class="modal" style="display: none">
<div>
<label for="link-desc-input">Description</label>
<input type="text" class="link-desc-input" name="link-desc-input">
<input type="text" id="link-desc-input" name="link-desc-input">
<label for="link-link-input">Lien</label>
<input type="url" class="link-link-input" name="link-link-input">
<input type="url" id="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>
@ -131,17 +128,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" class="media-alt-input" name="media-alt-input">
<input type="text" id="media-alt-input" name="media-alt-input">
<label for="media-link-input">Lien vers le média</label>
<input type="url" class="media-link-input" name="media-link-input">
<input type="url" id="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" class="media-type media-type-video" name="media-type" value="video" title="Vidéo" />
<input type="radio" id="media-type-video" name="media-type" value="video" title="Vidéo" />
<label for="media-type-image" title="Image">Image</label>
<input type="radio" class="media-type media-type-image" name="media-type" value="image" title="Image" />
<input type="radio" id="media-type-image" name="media-type" value="image" title="Image" />
</fieldset>
</div>
<div>
@ -158,14 +155,14 @@
</svg>
</button>
-->
<div class="filler"></div>
<button class="manual_preview" type="button" onclick="manual_preview('{{ field.name }}')" style="display: none" title="Rafraichir la prévisualisation">
<div id="filler"></div>
<button id="manual_preview" type="button" onclick="preview(manual=true)" 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 class="toggle_preview" type="button" onclick="toggle_auto_preview()" title="Désactiver la prévisualisation">
<button id="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"/>
@ -175,21 +172,16 @@
<a href="#">Aide</a>
</div>
<!-- Field -->
<!-- Field & desc -->
{{ field.label if label }}
{{ field() }}
<!-- Comment preview -->
<div class="editor_content_preview"></div>
<div id="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 %}

View File

@ -13,7 +13,6 @@
{% 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" %}
@ -33,10 +32,6 @@
<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>
@ -103,20 +98,12 @@
leader: Posts's top comment (actual rendering is delegated to caller) #}
{% macro thread_leader(leader, thread) %}
{% macro thread_leader(leader) %}
<table class="thread topcomment">
{# Empty line to get normal background (instead of alternate one) #}
<tr></tr>
<tr id="{{ leader.id }}">
<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="author">{{ widget_user.profile(leader.author) }}</td>
<td class="message">{{ caller() }}</td>
</tr>
</table>

View File

@ -1,10 +1,9 @@
{% macro profile(user, minimal = False) %}
{% macro profile(user) %}
{% if user.type == "member" %}
<div class="profile{% if minimal %} minimal {% endif %}">
<div class="profile">
<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 %}
@ -17,7 +16,6 @@
{% else %}
<div class="profile-xp profile-xp-100"><div style='width: {{ user.level[0] - 100 }}%;'></div></div>
{% endif %}
{% endif %}
</div>
</div>
{% else %}

View File

@ -1 +1 @@
../../../extra/v5shoutbox/widget.html
../../../submodules/v5shoutbox/widget.html

View File

@ -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):

View File

@ -19,10 +19,6 @@ 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',
@ -32,7 +28,6 @@ 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',

View File

@ -56,13 +56,3 @@ 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('Lillustration doit être le numéro dune pièce-jointe')
return True

View File

@ -86,9 +86,6 @@ 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():

3
extra/.gitignore vendored
View File

@ -1,3 +0,0 @@
calcdb/
emoji-picker-element/
v5shoutbox/

View File

@ -1,34 +0,0 @@
"""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);")

View File

@ -1,36 +0,0 @@
"""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 ###

View File

@ -1,24 +0,0 @@
"""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