partirdezero/templates/courses/partials/_course_toc.html

261 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&amp;autopause=0&amp;player_id=0&amp;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 %}
{{ lesson.content|safe }}
<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>