Merge branch 'dev' into new_editor
This commit is contained in:
commit
fae28982f2
|
@ -10,7 +10,7 @@ class Post(db.Model):
|
|||
# Unique Post ID for the whole site
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
# Post type (polymorphic discriminator)
|
||||
type = db.Column(db.String(20))
|
||||
type = db.Column(db.String(20), index=True)
|
||||
|
||||
# Creation and edition date
|
||||
date_created = db.Column(db.DateTime)
|
||||
|
|
|
@ -3,11 +3,17 @@ from flask_login import UserMixin
|
|||
from sqlalchemy import func as SQLfunc
|
||||
from os.path import isfile
|
||||
from PIL import Image
|
||||
|
||||
from app import app, db
|
||||
from app.models.priv import SpecialPrivilege, Group, GroupMember, \
|
||||
GroupPrivilege
|
||||
from app.models.trophy import Trophy, TrophyMember, Title
|
||||
from app.models.notification import Notification
|
||||
from app.models.post import Post
|
||||
from app.models.comment import Comment
|
||||
from app.models.topic import Topic
|
||||
from app.models.program import Program
|
||||
|
||||
import app.utils.unicode_names as unicode_names
|
||||
import app.utils.ldap as ldap
|
||||
from app.utils.unicode_names import normalize
|
||||
|
@ -113,9 +119,15 @@ class Member(User):
|
|||
# Relations
|
||||
trophies = db.relationship('Trophy', secondary=TrophyMember,
|
||||
back_populates='owners')
|
||||
topics = db.relationship('Topic')
|
||||
programs = db.relationship('Program')
|
||||
comments = db.relationship('Comment')
|
||||
|
||||
# Access to polymorphic posts
|
||||
# TODO: Check that the query uses the double index on Post.{author_id,type}
|
||||
def comments(self):
|
||||
return db.session.query(Comment).filter(Post.author_id==self.id).all()
|
||||
def topics(self):
|
||||
return db.session.query(Topic).filter(Post.author_id==self.id).all()
|
||||
def programs(self):
|
||||
return db.session.query(Program).filter(Post.author_id==self.id).all()
|
||||
|
||||
# Other fields populated automatically through relations:
|
||||
# <notifications> List of unseen notifications (of type Notification)
|
||||
|
@ -129,7 +141,7 @@ class Member(User):
|
|||
self.email_confirmed = not V5Config.ENABLE_EMAIL_CONFIRMATION
|
||||
if not V5Config.USE_LDAP:
|
||||
self.set_password(password)
|
||||
# Workflow with LDAP enabled is User → Postgresql → LDAP → set password
|
||||
# Workflow with LDAP enabled is User → PostgreSQL → LDAP → set password
|
||||
self.xp = 0
|
||||
|
||||
self.theme = 'default_theme'
|
||||
|
@ -149,23 +161,23 @@ class Member(User):
|
|||
Transfers all the posts to another user. This is generally used to
|
||||
transfer ownership to a newly-created Guest before deleting an account.
|
||||
"""
|
||||
for t in self.topics:
|
||||
for t in self.topics():
|
||||
t.author = other
|
||||
db.session.add(t)
|
||||
for p in self.programs:
|
||||
for p in self.programs():
|
||||
p.author = other
|
||||
db.session.add(p)
|
||||
for c in self.comments:
|
||||
for c in self.comments():
|
||||
c.author = other
|
||||
db.session.add(c)
|
||||
|
||||
def delete_posts(self):
|
||||
"""Deletes the user's posts."""
|
||||
for t in self.topics:
|
||||
for t in self.topics():
|
||||
t.delete()
|
||||
for p in self.programs:
|
||||
for p in self.programs():
|
||||
p.delete()
|
||||
for c in self.comments:
|
||||
for c in self.comments():
|
||||
c.delete()
|
||||
|
||||
def delete(self):
|
||||
|
@ -450,7 +462,7 @@ class Member(User):
|
|||
progress(levels, post_count)
|
||||
|
||||
if context in ["new-program", None]:
|
||||
program_count = len(self.programs)
|
||||
program_count = len(self.programs())
|
||||
|
||||
levels = {
|
||||
5: "Programmeur du dimanche",
|
||||
|
|
|
@ -128,9 +128,9 @@ def adm_delete_account(user_id):
|
|||
|
||||
# TODO: Number of comments by *other* members which will be deleted
|
||||
stats = {
|
||||
'comments': len(user.comments),
|
||||
'topics': len(user.topics),
|
||||
'programs': len(user.programs),
|
||||
'comments': len(user.comments()),
|
||||
'topics': len(user.topics()),
|
||||
'programs': len(user.programs()),
|
||||
'groups': len(user.groups),
|
||||
'privs': len(user.special_privileges()),
|
||||
}
|
||||
|
|
|
@ -139,3 +139,16 @@ div.editor-toolbar, div.CodeMirror {
|
|||
--separator: #404040;
|
||||
--text-disabled: #262c2f;
|
||||
}
|
||||
|
||||
.dl-button {
|
||||
--link: #149641;
|
||||
--link-text: #ffffff;
|
||||
--link-active: #0f7331;
|
||||
--meta: rgba(255, 255, 255, .15);
|
||||
--meta-text: #ffffff;
|
||||
}
|
||||
|
||||
.gallery, .gallery-js {
|
||||
--border: rgba(255, 255, 255, 0.8);
|
||||
--selected: rgba(255, 0, 0, 1.0);
|
||||
}
|
||||
|
|
|
@ -121,6 +121,19 @@ footer {
|
|||
--icons: #000;
|
||||
}
|
||||
|
||||
.dl-button {
|
||||
--link: #149641;
|
||||
--link-text: #ffffff;
|
||||
--link-active: #0f7331;
|
||||
--meta: rgba(0, 0, 0, .15);
|
||||
--meta-text: #000000;
|
||||
}
|
||||
|
||||
.gallery, .gallery-js {
|
||||
--border: rgba(0, 0, 0, 0.5);
|
||||
--selected: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
/* Extra style on top of the Pygments style */
|
||||
table.codehilitetable td.linenos {
|
||||
color: #888;
|
||||
|
|
|
@ -86,6 +86,9 @@
|
|||
height: 64px;
|
||||
}
|
||||
}
|
||||
hr.signature {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.trophies {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -125,6 +128,83 @@
|
|||
.trophy span {
|
||||
font-size: 80%;
|
||||
}
|
||||
hr.signature {
|
||||
opacity: 0.2;
|
||||
.dl-button {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin: 3px 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.dl-button a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 15px;
|
||||
font-size: 110%;
|
||||
background: var(--link);
|
||||
color: var(--link-text);
|
||||
}
|
||||
.dl-button a:hover,
|
||||
.dl-button a:focus,
|
||||
.dl-button a:active {
|
||||
background: var(--link-active);
|
||||
text-decoration: none;
|
||||
}
|
||||
.dl-button span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 8px;
|
||||
background: var(--meta);
|
||||
color: var(--meta-text);
|
||||
font-size: 90%;
|
||||
}
|
||||
.gallery {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
}
|
||||
.gallery * {
|
||||
margin: 3px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.gallery-js {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
height: 180px;
|
||||
}
|
||||
.gallery-js img,
|
||||
.gallery-js video {
|
||||
height: 100%;
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
}
|
||||
.gallery-js img:not(:first-child),
|
||||
.gallery-js video:not(:first-child) {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.gallery-js img.selected,
|
||||
.gallery-js video.selected {
|
||||
box-shadow: 0 0 7.5px var(--selected);
|
||||
}
|
||||
@media screen and (max-width:1199px) {
|
||||
.gallery-js {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width:499px) {
|
||||
.gallery-js {
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
.gallery-spot {
|
||||
justify-content: center;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.gallery-spot * {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -109,6 +109,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
hr.signature {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
|
||||
/* Trophies */
|
||||
.trophies {
|
||||
display: flex;
|
||||
|
@ -157,6 +162,80 @@
|
|||
}
|
||||
}
|
||||
|
||||
hr.signature {
|
||||
opacity: 0.2;
|
||||
|
||||
/* Download button */
|
||||
.dl-button {
|
||||
display: inline-flex; flex-direction: row; align-items: stretch;
|
||||
|
||||
border-radius: 5px; overflow: hidden;
|
||||
margin: 3px 5px; vertical-align: middle;
|
||||
|
||||
a {
|
||||
display:flex; align-items:center;
|
||||
padding: 5px 15px;
|
||||
|
||||
font-size: 110%;
|
||||
background: var(--link); color: var(--link-text);
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
background: var(--link-active);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex; align-items:center;
|
||||
padding: 5px 8px;
|
||||
background: var(--meta); color: var(--meta-text);
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gallery without Javascript */
|
||||
.gallery {
|
||||
display: flex; flex-wrap: wrap;
|
||||
justify-content: center; margin: auto;
|
||||
|
||||
* {
|
||||
margin: 3px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
/* Gallery with Javascript */
|
||||
.gallery-js {
|
||||
@padding: 15px;
|
||||
display: flex; overflow-x: auto; overflow-y: hidden;
|
||||
margin: auto; padding: @padding;
|
||||
height: 150px + 2 * @padding;
|
||||
|
||||
@media screen and (max-width: @small) {
|
||||
height: 120px + 2 * @padding;
|
||||
}
|
||||
@media screen and (max-width: @micro) {
|
||||
height: 100px + 2 * @padding;
|
||||
}
|
||||
|
||||
img, video {
|
||||
height: 100%;
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer; //box-sizing: content-box;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: @padding;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
box-shadow: 0 0 @padding/2 var(--selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery-spot {
|
||||
justify-content: center;
|
||||
margin: 10px auto;
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
document.querySelectorAll(".gallery").forEach(item => {
|
||||
// Switch to gallery-js stylesheet
|
||||
item.className = "gallery-js";
|
||||
|
||||
// Create the spotlight container
|
||||
let spot = document.createElement('div');
|
||||
spot.className = "gallery-spot";
|
||||
spot.style.display = "none";
|
||||
spot.appendChild(item.firstElementChild.cloneNode(true));
|
||||
item.after(spot);
|
||||
|
||||
// Add some logic
|
||||
// item.addEventListener("click", function(e) {
|
||||
// console.log(e.target);
|
||||
// console.log(e.currentTarget);
|
||||
// // Select the clicked media
|
||||
// Array.from(item.children).forEach(child => {
|
||||
// child.classList.remove('selected');
|
||||
// });
|
||||
// e.target.classList.add('selected');
|
||||
//
|
||||
// // Display the current
|
||||
// e.currentTarget.nextElementSibling.querySelector('div').innerHTML = e.target.outerHTML;
|
||||
// });
|
||||
});
|
||||
|
||||
document.querySelectorAll(".gallery-js > *").forEach(item => {
|
||||
item.addEventListener("click", function(e) {
|
||||
console.log(e.target);
|
||||
// Manage selected media
|
||||
if(e.target.classList.contains('selected')) {
|
||||
e.target.classList.remove('selected');
|
||||
} else {
|
||||
e.target.classList.add('selected');
|
||||
}
|
||||
Array.from(e.target.parentElement.children).forEach(el => {
|
||||
if(el != e.target) el.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Change content of spotlight
|
||||
let spot = e.target.parentElement.nextElementSibling;
|
||||
spot.replaceChild(e.target.cloneNode(true), spot.firstElementChild);
|
||||
// Open spotlight media in a new tab
|
||||
spot.firstElementChild.addEventListener("click", function(e) {
|
||||
window.open(spot.firstElementChild.src, "_blank");
|
||||
});
|
||||
|
||||
// Display the spotlight
|
||||
if(e.target.classList.contains('selected')) {
|
||||
spot.style.display = "flex";
|
||||
} else {
|
||||
spot.style.display = "none";
|
||||
}
|
||||
});
|
||||
});
|
|
@ -69,5 +69,5 @@ let keyboard_trigger = function(event) {
|
|||
}
|
||||
}
|
||||
|
||||
document.onclick = mouse_trigger;
|
||||
document.onkeypress = keyboard_trigger;
|
||||
document.addEventListener("click", mouse_trigger);
|
||||
document.addEventListener("keydown", keyboard_trigger);
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
<th>Forum</th>
|
||||
<th>Création</th>
|
||||
</tr>
|
||||
{% for t in member.topics %}
|
||||
{% for t in member.topics() %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('forum_topic', f=t.forum, page=(t, 1)) }}">{{ t.title }}</a></td>
|
||||
<td><a href="{{ url_for('forum_page', f=t.forum) }}">{{ t.forum.name }}</a></td>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<ul>
|
||||
<li>{{ stats.comments }} commentaire{{ stats.comments | pluralize }}</li>
|
||||
<li>{{ stats.topics }} topic{{ stats.topics | pluralize }}</li>
|
||||
<li>{{ stats.programs }} topic{{ stats.programs | pluralize }}</li>
|
||||
<li>{{ stats.programs }} programme{{ stats.programs | pluralize }}</li>
|
||||
</ul>
|
||||
<p>Les propriétés suivantes seront supprimées :</p>
|
||||
<ul>
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
</svg>
|
||||
Actualités
|
||||
</h2>
|
||||
<a href='/forum/news'>Toutes les nouveautés</a>
|
||||
<a href='/forum/actus'>Toutes les nouveautés</a>
|
||||
|
||||
<hr>
|
||||
|
||||
<a href='/forum/news/calc'>Nouveautés Casio</a>
|
||||
<a href='/forum/news/projects'>Projets communutaires</a>
|
||||
<a href='/forum/news/events'>Événements de Planète Casio</a>
|
||||
<a href='/forum/news/other'>Autres nouveautés</a>
|
||||
<a href='/forum/actus/calc'>Nouveautés Casio</a>
|
||||
<a href='/forum/actus/projets'>Projets communutaires</a>
|
||||
<a href='/forum/actus/evenements'>Événements de Planète Casio</a>
|
||||
<a href='/forum/actus/autres'>Autres nouveautés</a>
|
||||
|
||||
<hr>
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
{% extends "base/base.html" %}
|
||||
{% import "widgets/editor.html" as widget_editor %}
|
||||
{% import "widgets/thread.html" as widget_thread with context %}
|
||||
{% import "widgets/user.html" as widget_user %}
|
||||
{% import "widgets/pagination.html" as widget_pagination with context %}
|
||||
{% import "widgets/attachments.html" as widget_attachments %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
<h1>Programme {{ program.name }}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<div style="display:flex;flex-wrap:wrap;align-items:center;">
|
||||
<div>
|
||||
{{ widget_user.profile(program.author) }}
|
||||
</div>
|
||||
<div style="padding:30px;">
|
||||
<div style="font-size:115%;font-style:italic;margin-bottom:15px;">
|
||||
{{ program.title }}
|
||||
</div>
|
||||
</div>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae
|
||||
feugiat ante. Pellentesque luctus lorem tincidunt vestibulum condimentum.
|
||||
Nullam sed tempus sem. Phasellus quis diam vitae sapien luctus consequat
|
||||
ac eget lacus. Sed rutrum condimentum sagittis. Nullam erat nibh, euismod
|
||||
ac metus at, consequat tincidunt ipsum. Fusce sagittis iaculis orci sedporta.
|
||||
Etiam bibendum purus et ipsum pellentesque, quis sodales libero congue. Nunc
|
||||
lectus quam, cursus non dictum nec, volutpat eget felis. Praesent sollicitudin
|
||||
massa erat, nec venenatis lorem lacinia et. Etiam ullamcorper neque quis
|
||||
nisi sodales vulputate. Integer scelerisque luctus arcu, ut elementum justo
|
||||
auctor a. Praesent sit amet libero risus.</p>
|
||||
|
||||
<div class="gallery">
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_3_manoir.png" alt="">
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_7_stats.png" alt="">
|
||||
<video>
|
||||
<source src="https://linx.breizh.pm/selif/xgh9k9sq.mp4">
|
||||
</video>
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_3_manoir.png" alt="">
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_7_stats.png" alt="">
|
||||
<video>
|
||||
<source src="https://www.planet-casio.com/storage/staff/IDK_5_intrigue.mp4">
|
||||
</video>
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_3_manoir.png" alt="">
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_7_stats.png" alt="">
|
||||
</div>
|
||||
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae
|
||||
feugiat ante. Pellentesque luctus lorem tincidunt vestibulum condimentum.
|
||||
Nullam sed tempus sem. Phasellus quis diam vitae sapien luctus consequat
|
||||
ac eget lacus. Sed rutrum condimentum sagittis. Nullam erat nibh, euismod
|
||||
ac metus at, consequat tincidunt ipsum. Fusce sagittis iaculis orci sedporta.
|
||||
Etiam bibendum purus et ipsum pellentesque, quis sodales libero congue. Nunc
|
||||
lectus quam, cursus non dictum nec, volutpat eget felis. Praesent sollicitudin
|
||||
massa erat, nec venenatis lorem lacinia et. Etiam ullamcorper neque quis
|
||||
nisi sodales vulputate. Integer scelerisque luctus arcu, ut elementum justo
|
||||
auctor a. Praesent sit amet libero risus.</p>
|
||||
|
||||
<div class="gallery">
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_3_manoir.png" alt="">
|
||||
<img src="https://www.planet-casio.com/storage/staff/IDK_7_stats.png" alt="">
|
||||
<video>
|
||||
<source src="https://linx.breizh.pm/selif/xgh9k9sq.mp4">
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae
|
||||
feugiat ante. Pellentesque luctus lorem tincidunt vestibulum condimentum.
|
||||
Nullam sed tempus sem. Phasellus quis diam vitae sapien luctus consequat
|
||||
ac eget lacus. Sed rutrum condimentum sagittis. Nullam erat nibh, euismod
|
||||
ac metus at, consequat tincidunt ipsum. Fusce sagittis iaculis orci sedporta.
|
||||
Etiam bibendum purus et ipsum pellentesque, quis sodales libero congue. Nunc
|
||||
lectus quam, cursus non dictum nec, volutpat eget felis. Praesent sollicitudin
|
||||
massa erat, nec venenatis lorem lacinia et. Etiam ullamcorper neque quis
|
||||
nisi sodales vulputate. Integer scelerisque luctus arcu, ut elementum justo
|
||||
auctor a. Praesent sit amet libero risus.</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,8 @@
|
|||
{% macro download_button(file) %}
|
||||
<span class="dl-button">
|
||||
<a href="{{ file.url }}">{{ file.name }}</a>
|
||||
<span>{{ file.size | humanize(unit='o') }}</span>
|
||||
</span>
|
||||
{% endmacro %}
|
||||
|
||||
{{ download_button(file) if file }}
|
|
@ -12,6 +12,7 @@ from app.utils.markdown_extensions.hardbreaks import HardBreakExtension
|
|||
from app.utils.markdown_extensions.escape_html import EscapeHtmlExtension
|
||||
from app.utils.markdown_extensions.linkify import LinkifyExtension
|
||||
from app.utils.markdown_extensions.media import MediaExtension
|
||||
from app.utils.markdown_extensions.gallery import GalleryExtension
|
||||
|
||||
|
||||
@app.template_filter('md')
|
||||
|
@ -35,6 +36,7 @@ def md(text):
|
|||
TocExtension(baselevel=2),
|
||||
PCLinkExtension(),
|
||||
MediaExtension(),
|
||||
GalleryExtension(),
|
||||
]
|
||||
|
||||
html = markdown(text, options=options, extensions=extensions)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
from markdown.extensions import Extension
|
||||
from markdown.treeprocessors import Treeprocessor
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
class GalleryTreeprocessor(Treeprocessor):
|
||||
def run(self, doc):
|
||||
for parent in doc.findall(".//ul/.."):
|
||||
for idx, ul in enumerate(parent):
|
||||
if ul.tag != "ul" or len(ul) == 0:
|
||||
continue
|
||||
has_gallery = False
|
||||
|
||||
# Option 1: In raw text in the <li>
|
||||
if ul[-1].text and ul[-1].text.endswith("{gallery}"):
|
||||
has_gallery = True
|
||||
ul[-1].text = ul[-1].text[:-9]
|
||||
# Option 2: After the last child (in its tail) \
|
||||
if len(ul[-1]) and ul[-1][-1].tail and \
|
||||
ul[-1][-1].tail.endswith("{gallery}"):
|
||||
has_gallery = True
|
||||
ul[-1][-1].tail = ul[-1][-1].tail[:-9]
|
||||
|
||||
if has_gallery:
|
||||
el = etree.Element("div")
|
||||
el.set('class', 'gallery')
|
||||
parent.remove(ul)
|
||||
for li in ul:
|
||||
# Filter out items that are not single medias
|
||||
if len(li) == 1 and li[0].tag in ["img", "video"]:
|
||||
el.append(li[0])
|
||||
|
||||
parent.insert(idx, el)
|
||||
|
||||
class GalleryExtension(Extension):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
md.treeprocessors.register(GalleryTreeprocessor(md), 'gallery', 8)
|
||||
md.registerExtension(self)
|
|
@ -16,9 +16,11 @@ from markdown.inlinepatterns import InlineProcessor
|
|||
import xml.etree.ElementTree as etree
|
||||
from flask import url_for, render_template
|
||||
from app.utils.unicode_names import normalize
|
||||
from app.utils.filters.humanize import humanize
|
||||
from app.models.poll import Poll
|
||||
from app.models.topic import Topic
|
||||
from app.models.user import Member
|
||||
from app.models.attachment import Attachment
|
||||
|
||||
|
||||
class PCLinkExtension(Extension):
|
||||
|
@ -34,7 +36,7 @@ class PCLinkExtension(Extension):
|
|||
self.md = md
|
||||
|
||||
# append to end of inline patterns
|
||||
PCLINK_RE = r'\[\[([a-z]+): ?(\w+)\]\]'
|
||||
PCLINK_RE = r'<([a-z]+): ?(\w+)>'
|
||||
pclinkPattern = PCLinksInlineProcessor(PCLINK_RE, self.getConfigs())
|
||||
pclinkPattern.md = md
|
||||
md.inlinePatterns.register(pclinkPattern, 'pclink', 135)
|
||||
|
@ -48,6 +50,7 @@ class PCLinksInlineProcessor(InlineProcessor):
|
|||
'membre': handleUser, 'user': handleUser, 'u': handleUser,
|
||||
'sondage': handlePoll, 'poll': handlePoll,
|
||||
'topic': handleTopic, 't': handleTopic,
|
||||
'fichier': handleFile, 'file': handleFile, 'f': handleFile,
|
||||
}
|
||||
|
||||
def handleMatch(self, m, data):
|
||||
|
@ -70,12 +73,10 @@ class PCLinksInlineProcessor(InlineProcessor):
|
|||
# - either an xml.etree.ElementTree
|
||||
|
||||
def handlePoll(content_id, context):
|
||||
if not context.startswith("[[") or not context.endswith("]]"):
|
||||
return "[Sondage invalide]"
|
||||
try:
|
||||
id = int(content_id)
|
||||
except ValueError:
|
||||
return "[ID du sondage invalide]"
|
||||
return "[ID de sondage invalide]"
|
||||
|
||||
poll = Poll.query.get(content_id)
|
||||
|
||||
|
@ -90,7 +91,7 @@ def handleTopic(content_id, context):
|
|||
try:
|
||||
id = int(content_id)
|
||||
except ValueError:
|
||||
return "[ID du topic invalide]"
|
||||
return "[ID de topic invalide]"
|
||||
|
||||
topic = Topic.query.get(content_id)
|
||||
|
||||
|
@ -121,3 +122,18 @@ def handleUser(content_id, context):
|
|||
a.set('class', 'profile-link')
|
||||
|
||||
return a
|
||||
|
||||
def handleFile(content_id, context):
|
||||
try:
|
||||
content_id = int(content_id)
|
||||
except ValueError:
|
||||
return "[ID de fichier invalide]"
|
||||
|
||||
file = Attachment.query.get(content_id)
|
||||
|
||||
if file is None:
|
||||
return "[Fichier non trouvé]"
|
||||
|
||||
html = render_template('widgets/download_button.html', file=file)
|
||||
html = html.replace('\n', '') # Needed to avoid lots of <br> due to etree
|
||||
return etree.fromstring(html)
|
||||
|
|
|
@ -23,6 +23,7 @@ def render(*args, styles=[], scripts=[], **kwargs):
|
|||
'scripts/pc-utils.js',
|
||||
'scripts/smartphone_patch.js',
|
||||
'scripts/editor.js',
|
||||
'scripts/gallery.js',
|
||||
'scripts/filter.js'
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
"""Add an index on Post.type
|
||||
|
||||
Revision ID: d2227d2479e2
|
||||
Revises: bcfdb271b88d
|
||||
Create Date: 2022-04-25 16:44:51.241965
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd2227d2479e2'
|
||||
down_revision = 'bcfdb271b88d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_index(op.f('ix_post_type'), 'post', ['type'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_post_type'), table_name='post')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue