264 lines
23 KiB
HTML
264 lines
23 KiB
HTML
{% load comment_format %}
|
|
<nav class="courseToc" aria-label="Sommaire du cours">
|
|
{% regroup lessons by module as modules %}
|
|
<ol class="tocModules">
|
|
{% for group in modules %}
|
|
<li class="tocModule">
|
|
<div class="tocModuleHeader">
|
|
<span class="tocModuleIndex">{{ forloop.counter }}</span>
|
|
<span class="tocModuleTitle">{{ group.grouper.name }}</span>
|
|
</div>
|
|
<ol class="tocLessons">
|
|
{% for item in group.list %}
|
|
<li class="tocLesson{% if lesson and lesson.id == item.id %} current{% endif %}">
|
|
<a class="tocLink {% if item.is_premium and user.profile.is_premium == False %}premium{% endif %}" href="{% url 'courses:lesson_detail' course.slug item.module.slug item.slug %}">
|
|
{{ item.name }} {% if item.is_premium %}<span class="premium-tag">PREMIUM</span>{% endif %}
|
|
{% if lesson and lesson.id == item.id %}<span class="tocCurrentTag">(cours actuel)</span>{% endif %}
|
|
</a>
|
|
{% if lesson and lesson.id == item.id %}
|
|
<div class="lessonInline">
|
|
<div class="lesson">
|
|
{% if lesson.video_id %}
|
|
{% if not lesson.is_premium %}
|
|
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/{{ lesson.video_id }}?badge=0&autopause=0&player_id=0&app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share" referrerpolicy="strict-origin-when-cross-origin" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="1"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
|
|
<div class="videoNav" style="display:flex; justify-content:space-between; gap:12px; margin:12px 0 20px 0;">
|
|
<div>
|
|
{% if prev_lesson %}
|
|
<a class="btn btn-secondary" href="{% url 'courses:lesson_detail' course.slug prev_lesson.module.slug prev_lesson.slug %}">
|
|
← Vidéo précédente
|
|
</a>
|
|
{% else %}
|
|
<span class="btn btn-secondary" aria-disabled="true" style="opacity:.5; pointer-events:none;">← Vidéo précédente</span>
|
|
{% endif %}
|
|
</div>
|
|
<div>
|
|
{% if next_lesson %}
|
|
<a class="btn btn-primary" href="{% url 'courses:lesson_detail' course.slug next_lesson.module.slug next_lesson.slug %}">
|
|
Vidéo suivante →
|
|
</a>
|
|
{% else %}
|
|
<span class="btn btn-primary" aria-disabled="true" style="opacity:.5; pointer-events:none;">Vidéo suivante →</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
{% if not user.profile.is_premium %}
|
|
<div class="alert premium-lock" role="note" aria-live="polite">
|
|
<div style="display:flex; gap:16px; align-items:flex-start;">
|
|
<i class="fa-solid fa-lock" aria-hidden="true" style="font-size:20px; margin-top:2px; color: var(--muted);"></i>
|
|
<div>
|
|
<h4 style="margin:0 0 8px 0;">Contenu réservé aux membres Premium</h4>
|
|
{% if user.is_authenticated %}
|
|
<p class="muted" style="margin:0 0 12px 0;">Devenez membre Premium pour accéder à la vidéo de ce cours.</p>
|
|
<div class="actions">
|
|
<a class="btn btn-primary" href="{% url 'home:premium' item.id %}">Découvrir Premium</a>
|
|
</div>
|
|
{% else %}
|
|
<p class="muted" style="margin:0 0 12px 0;">Connectez-vous ou devenez membre Premium pour accéder à la vidéo.</p>
|
|
<div class="actions" style="display:flex; gap:8px; flex-wrap:wrap;">
|
|
<a class="btn btn-secondary" href="{% url 'login' %}?next={% url 'courses:lesson_detail' course.slug module.slug lesson.slug %}">Se connecter</a>
|
|
<a class="btn btn-primary" href="{% url 'courses:list' %}">Découvrir Premium</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endif %}
|
|
<div class="content-lesson">
|
|
<div class="lessonTitle">Ce que l'on voit durant ce cours : </div>
|
|
{{ lesson.content|comment_markdown }}
|
|
</div>
|
|
<h3 id="comments">Commentaires</h3>
|
|
<div class="lessonComments">
|
|
{% if comments %}
|
|
<ol class="commentsList">
|
|
{% for c in comments %}
|
|
<li class="commentItem" id="comment-{{ c.id }}">
|
|
<div class="commentHeader">
|
|
<span class="commentAuthor">{{ c.user.username }}</span>
|
|
<time class="commentDate" datetime="{{ c.created_at|date:'c' }}">{{ c.created_at|date:'d/m/Y H:i' }}</time>
|
|
</div>
|
|
<div class="commentContent">{{ c.content|comment_markdown }}</div>
|
|
|
|
{# Replies #}
|
|
{% if c.replies.all %}
|
|
{% with replies=c.replies.all %}
|
|
{% if replies %}
|
|
<ol class="commentsList" style="margin-top: var(--space-4); margin-left: var(--space-6);">
|
|
{% for r in replies %}
|
|
<li class="commentItem" id="comment-{{ r.id }}">
|
|
<div class="commentHeader">
|
|
<span class="commentAuthor">{{ r.user.username }}</span>
|
|
<time class="commentDate" datetime="{{ r.created_at|date:'c' }}">{{ r.created_at|date:'d/m/Y H:i' }}</time>
|
|
</div>
|
|
<div class="commentContent">{{ r.content|comment_markdown }}</div>
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% endif %}
|
|
|
|
{# Reply toggle + form #}
|
|
{% if user.is_authenticated %}
|
|
<button type="button" class="btn btnReply" aria-expanded="false" style="margin-top: var(--space-3);">Répondre</button>
|
|
<div class="commentFormBlock" style="display:none; margin-top: var(--space-3);">
|
|
<form method="post" action="{% url 'courses:lesson_detail' course.slug module.slug lesson.slug %}#comments" class="commentForm">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="parent" value="{{ c.id }}" />
|
|
<div class="commentToolbar" role="toolbar" aria-label="Outils de mise en forme des réponses">
|
|
<button class="btnTool" type="button" data-wrap="**|**" title="Gras (Ctrl/Cmd+B)"><i class="fa-solid fa-bold"></i></button>
|
|
<button class="btnTool" type="button" data-wrap="*|*" title="Italique (Ctrl/Cmd+I)"><i class="fa-solid fa-italic"></i></button>
|
|
<button class="btnTool" type="button" data-wrap="`|`" title="Code inline"><i class="fa-solid fa-code"></i></button>
|
|
<button class="btnTool" type="button" data-insert="[texte](https://exemple.com)" title="Lien"><i class="fa-solid fa-link"></i></button>
|
|
<button class="btnTool" type="button" data-prefix="- " title="Liste à puces"><i class="fa-solid fa-list-ul"></i></button>
|
|
<button class="btnTool" type="button" data-fence="```
|
|
|\n```" title="Bloc de code"><i class="fa-solid fa-square-code"></i></button>
|
|
</div>
|
|
<div class="commentField">
|
|
<textarea name="content" rows="2" placeholder="Répondre à ce commentaire…"></textarea>
|
|
</div>
|
|
<div class="formActions">
|
|
<button type="submit" class="btn">Publier</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{% else %}
|
|
<p>
|
|
<a href="{% url 'login' %}?next={% url 'courses:lesson_detail' course.slug module.slug lesson.slug %}#comments">Se connecter pour répondre</a>
|
|
</p>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
{% else %}
|
|
<p class="noComments">Aucun commentaire pour le moment.</p>
|
|
{% endif %}
|
|
|
|
<div class="commentFormBlock">
|
|
{% if user.is_authenticated %}
|
|
<form method="post" action="{% url 'courses:lesson_detail' course.slug module.slug lesson.slug %}#comments" class="commentForm">
|
|
{% csrf_token %}
|
|
<div class="commentToolbar" role="toolbar" aria-label="Outils de mise en forme des commentaires">
|
|
<button class="btnTool" type="button" data-wrap="**|**" title="Gras (Ctrl/Cmd+B)"><i class="fa-solid fa-bold"></i></button>
|
|
<button class="btnTool" type="button" data-wrap="*|*" title="Italique (Ctrl/Cmd+I)"><i class="fa-solid fa-italic"></i></button>
|
|
<button class="btnTool" type="button" data-wrap="`|`" title="Code inline"><i class="fa-solid fa-code"></i></button>
|
|
<button class="btnTool" type="button" data-insert="[texte](https://exemple.com)" title="Lien"><i class="fa-solid fa-link"></i></button>
|
|
<button class="btnTool" type="button" data-prefix="- " title="Liste à puces"><i class="fa-solid fa-list-ul"></i></button>
|
|
<button class="btnTool" type="button" data-fence="```\n|\n```" title="Bloc de code"><i class="fa-solid fa-square-code"></i></button>
|
|
</div>
|
|
<div class="commentField">
|
|
{{ comment_form.content }}
|
|
</div>
|
|
<p class="commentHelp">
|
|
Astuce: vous pouvez utiliser une mise en forme simple — **gras**, *italique*, `code`, listes (- item) et blocs de code avec ```.
|
|
</p>
|
|
<div class="formActions">
|
|
<button type="submit" class="btn">Publier</button>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<p>Vous devez être connecté pour publier un commentaire.
|
|
<a href="{% url 'login' %}?next={% url 'courses:lesson_detail' course.slug module.slug lesson.slug %}#comments">Se connecter</a>
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
<script>
|
|
(function(){
|
|
const getTextareaFromButton = (btn) => {
|
|
const form = btn.closest('.commentForm');
|
|
if (!form) return document.getElementById('id_content');
|
|
return form.querySelector('textarea') || document.getElementById('id_content');
|
|
};
|
|
const wrap = (area, before, after) => {
|
|
if (!area) return;
|
|
const start = area.selectionStart || 0;
|
|
const end = area.selectionEnd || 0;
|
|
const sel = area.value.substring(start, end) || '';
|
|
const bef = area.value.substring(0, start);
|
|
const aft = area.value.substring(end);
|
|
area.value = bef + before + sel + (after ?? '') + aft;
|
|
const pos = (bef + before + sel).length;
|
|
area.focus();
|
|
area.setSelectionRange(pos, pos);
|
|
};
|
|
const addPrefix = (area, prefix) => {
|
|
if (!area) return;
|
|
const start = area.selectionStart || 0;
|
|
const end = area.selectionEnd || 0;
|
|
const text = area.value;
|
|
const sel = text.substring(start, end) || '';
|
|
const lines = sel.split('\n');
|
|
const out = lines.map(l => l ? (prefix + l) : l).join('\n');
|
|
const bef = text.substring(0, start);
|
|
const aft = text.substring(end);
|
|
area.value = bef + out + aft;
|
|
const pos = (bef + out).length;
|
|
area.focus();
|
|
area.setSelectionRange(pos, pos);
|
|
};
|
|
document.addEventListener('click', function(e){
|
|
const b = e.target.closest('.btnTool');
|
|
if (b) {
|
|
e.preventDefault();
|
|
const area = getTextareaFromButton(b);
|
|
const wrapSpec = b.getAttribute('data-wrap');
|
|
const insertSpec = b.getAttribute('data-insert');
|
|
const prefix = b.getAttribute('data-prefix');
|
|
const fence = b.getAttribute('data-fence');
|
|
if (wrapSpec) {
|
|
const [before, after] = wrapSpec.split('|');
|
|
wrap(area, before, after);
|
|
} else if (insertSpec) {
|
|
wrap(area, insertSpec, '');
|
|
} else if (prefix) {
|
|
addPrefix(area, prefix);
|
|
} else if (fence) {
|
|
const [before, after] = fence.split('|');
|
|
wrap(area, before, after);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Toggle reply form when clicking "Répondre"
|
|
const replyBtn = e.target.closest('.btnReply');
|
|
if (replyBtn) {
|
|
e.preventDefault();
|
|
const formBlock = replyBtn.nextElementSibling;
|
|
if (!formBlock || !formBlock.classList.contains('commentFormBlock')) return;
|
|
const isHidden = formBlock.style.display === 'none' || getComputedStyle(formBlock).display === 'none';
|
|
formBlock.style.display = isHidden ? '' : 'none';
|
|
replyBtn.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
|
if (isHidden) {
|
|
const ta = formBlock.querySelector('textarea');
|
|
if (ta) ta.focus();
|
|
}
|
|
}
|
|
});
|
|
// Keyboard shortcuts for any textarea within comment forms
|
|
document.addEventListener('keydown', function(e){
|
|
const target = e.target;
|
|
if (!(target instanceof HTMLElement)) return;
|
|
if (!target.closest('.commentForm') || target.tagName !== 'TEXTAREA') return;
|
|
if ((e.metaKey || e.ctrlKey) && (e.key === 'b' || e.key === 'i')) {
|
|
e.preventDefault();
|
|
const isBold = e.key === 'b';
|
|
const spec = isBold ? ['**','**'] : ['*','*'];
|
|
wrap(target, spec[0], spec[1]);
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
</div> <!-- /.lessonComments -->
|
|
</div> <!-- /.lesson -->
|
|
</div> <!-- /.lessonInline -->
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</nav>
|