feat(frontend): panneau détail de carte, badges d'âge, limites WIP
- drawer de détail: corps markdown rendu (marked + DOMPurify
vendorés), commentaires, formulaire de commentaire vers Gitea;
clic distingué du drag par la distance du pointeur
- badges ⏳ N j au-delà du seuil d'âge (rouge au double)
- en-têtes de colonnes: n/limite, classe .over en rouge, toast
d'avertissement au dépôt dans une colonne saturée
- page activity.html, liens Activité dans dashboard et board
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Activité — Kanbante</title>
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📰</text></svg>">
|
||||||
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1><a href="/">📋 <span class="gradient-text">Kanbante</span></a> <span class="sep">/</span> 📰 Activité</h1>
|
||||||
|
<nav>
|
||||||
|
<a href="/focus" class="btn-sm">🎯 Focus</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="focus-main">
|
||||||
|
<p class="focus-intro">Les {{ events|length }} derniers événements du journal — déplacements, créations, fermetures et commentaires, tous projets confondus.</p>
|
||||||
|
{% if not events %}
|
||||||
|
<p class="focus-empty">Aucun événement pour le moment. Le journal se remplit dès que des cartes bougent.</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="activity-list">
|
||||||
|
{% for e in events %}
|
||||||
|
<div class="activity-item">
|
||||||
|
<span class="activity-icon">{{ event_icons.get(e.event_type, '•') }}</span>
|
||||||
|
<div class="activity-text">
|
||||||
|
<a href="/board/{{ e.repo_full_name }}">{{ e.repo_full_name }}</a>
|
||||||
|
<strong>#{{ e.issue_number }}</strong>
|
||||||
|
{% if e.event_type == 'created' %}
|
||||||
|
créée{% if e.to_column %} dans {{ column_labels.get(e.to_column, e.to_column) }}{% endif %}
|
||||||
|
{% elif e.event_type == 'moved' %}
|
||||||
|
déplacée{% if e.from_column %} de {{ column_labels.get(e.from_column, e.from_column) }}{% endif %} vers {{ column_labels.get(e.to_column, e.to_column) }}
|
||||||
|
{% elif e.event_type == 'closed' %}
|
||||||
|
fermée{% if e.source == 'gitea' %} (dans Gitea){% endif %}
|
||||||
|
{% elif e.event_type == 'reopened' %}
|
||||||
|
rouverte vers {{ column_labels.get(e.to_column, e.to_column) }}
|
||||||
|
{% elif e.event_type == 'commented' %}
|
||||||
|
commentée
|
||||||
|
{% endif %}
|
||||||
|
<span class="activity-date">{{ e.created_at[:16].replace('T', ' ') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
Kanbante v{{ version }} · journal d'événements
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
<script src="/static/vendor/htmx.min.js"></script>
|
<script src="/static/vendor/htmx.min.js"></script>
|
||||||
<script src="/static/vendor/sortable.min.js"></script>
|
<script src="/static/vendor/sortable.min.js"></script>
|
||||||
|
<script src="/static/vendor/marked.min.js"></script>
|
||||||
|
<script src="/static/vendor/purify.min.js"></script>
|
||||||
<script src="/static/js/board.js" defer></script>
|
<script src="/static/js/board.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/focus" class="btn-sm">🎯 Focus</a>
|
<a href="/focus" class="btn-sm">🎯 Focus</a>
|
||||||
|
<a href="/activity" class="btn-sm">📰 Activité</a>
|
||||||
{% if repo_info.html_url %}
|
{% if repo_info.html_url %}
|
||||||
<a href="{{ repo_info.html_url }}" target="_blank" class="btn-sm">🔗 Gitea</a>
|
<a href="{{ repo_info.html_url }}" target="_blank" class="btn-sm">🔗 Gitea</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -48,7 +51,9 @@
|
|||||||
data-column="{{ col }}">
|
data-column="{{ col }}">
|
||||||
<div class="column-header" style="border-top: 3px solid {{ column_colors[col] }}">
|
<div class="column-header" style="border-top: 3px solid {{ column_colors[col] }}">
|
||||||
<span class="column-title">{{ column_labels[col] }}</span>
|
<span class="column-title">{{ column_labels[col] }}</span>
|
||||||
<span class="column-count">{{ columns[col]|length }}</span>
|
{% set limit = wip_limits.get(col) %}
|
||||||
|
<span class="column-count{% if limit and columns[col]|length > limit %} over{% endif %}"
|
||||||
|
{% if limit %}data-limit="{{ limit }}" title="Limite WIP : {{ limit }}"{% endif %}>{% if limit %}{{ columns[col]|length }}/{{ limit }}{% else %}{{ columns[col]|length }}{% endif %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column-cards" data-column="{{ col }}">
|
<div class="column-cards" data-column="{{ col }}">
|
||||||
{% for issue in columns[col] %}
|
{% for issue in columns[col] %}
|
||||||
@@ -78,6 +83,11 @@
|
|||||||
<img src="{{ issue.assignee.avatar_url }}" class="avatar" alt="{{ issue.assignee.login }}">
|
<img src="{{ issue.assignee.avatar_url }}" class="avatar" alt="{{ issue.assignee.login }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="card-date">{{ issue.created_at[:10] }}</span>
|
<span class="card-date">{{ issue.created_at[:10] }}</span>
|
||||||
|
{% set age = ages.get(issue.number) %}
|
||||||
|
{% if age and age.days >= aging_days %}
|
||||||
|
<span class="age-badge{% if age.days >= aging_days * 2 %} stale{% endif %}"
|
||||||
|
title="Dans cette colonne depuis le {{ age.since[:10] }}">⏳ {{ age.days }} j</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -100,6 +110,28 @@
|
|||||||
</aside>
|
</aside>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- Card detail drawer: issue body, comments, comment form -->
|
||||||
|
<div id="detail-overlay" class="detail-overlay" hidden></div>
|
||||||
|
<aside id="card-detail" class="card-detail" hidden>
|
||||||
|
<div class="detail-head">
|
||||||
|
<span id="detail-number" class="card-id"></span>
|
||||||
|
<span id="detail-state" class="chip"></span>
|
||||||
|
<button id="detail-close" class="btn-sm" type="button">✕</button>
|
||||||
|
</div>
|
||||||
|
<h2 id="detail-title"></h2>
|
||||||
|
<div id="detail-meta" class="detail-meta"></div>
|
||||||
|
<div id="detail-labels" class="card-labels"></div>
|
||||||
|
<div id="detail-body" class="detail-body"></div>
|
||||||
|
<h3 class="detail-comments-title">💬 Commentaires</h3>
|
||||||
|
<div id="detail-comments"></div>
|
||||||
|
<form id="comment-form">
|
||||||
|
<textarea id="comment-body" rows="3" maxlength="10000"
|
||||||
|
placeholder="Écrire un commentaire (markdown)..."></textarea>
|
||||||
|
<button type="submit" class="btn-save">💬 Commenter dans Gitea</button>
|
||||||
|
</form>
|
||||||
|
<p class="detail-foot"><a id="detail-gitea-link" href="#" target="_blank">Ouvrir dans Gitea ↗</a></p>
|
||||||
|
</aside>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<span>{{ full_name }}</span>
|
<span>{{ full_name }}</span>
|
||||||
{% if repo_info.updated_at %}
|
{% if repo_info.updated_at %}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<h1><a href="/">📋 <span class="gradient-text">Kanbante</span></a></h1>
|
<h1><a href="/">📋 <span class="gradient-text">Kanbante</span></a></h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/focus" class="btn-sm">🎯 Focus</a>
|
<a href="/focus" class="btn-sm">🎯 Focus</a>
|
||||||
|
<a href="/activity" class="btn-sm">📰 Activité</a>
|
||||||
<span class="badge">{{ active_repos|length }} actifs</span>
|
<span class="badge">{{ active_repos|length }} actifs</span>
|
||||||
<span class="badge secondary">{{ archived_repos|length }} archivés</span>
|
<span class="badge secondary">{{ archived_repos|length }} archivés</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -382,6 +382,93 @@ footer {
|
|||||||
.error-box h2 { margin-bottom: 12px; }
|
.error-box h2 { margin-bottom: 12px; }
|
||||||
.error-box p { color: var(--text2); margin-bottom: 16px; line-height: 1.5; }
|
.error-box p { color: var(--text2); margin-bottom: 16px; line-height: 1.5; }
|
||||||
|
|
||||||
|
/* Aging badges + WIP over-limit */
|
||||||
|
.age-badge {
|
||||||
|
background: color-mix(in srgb, var(--yellow) 18%, transparent);
|
||||||
|
color: var(--yellow);
|
||||||
|
padding: 1px 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.age-badge.stale {
|
||||||
|
background: color-mix(in srgb, var(--red) 18%, transparent);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
.column-count.over {
|
||||||
|
background: color-mix(in srgb, var(--red) 20%, transparent);
|
||||||
|
color: var(--red);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card detail drawer */
|
||||||
|
.detail-overlay {
|
||||||
|
position: fixed; inset: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
.card-detail {
|
||||||
|
position: fixed; top: 0; right: 0;
|
||||||
|
width: min(480px, 94vw); height: 100vh;
|
||||||
|
background: var(--surface);
|
||||||
|
border-left: 1px solid var(--border);
|
||||||
|
z-index: 201;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.detail-head { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; }
|
||||||
|
.detail-head .btn-sm { margin-left: auto; }
|
||||||
|
.card-detail h2 { font-size: 1.15rem; line-height: 1.35; margin-bottom: 8px; }
|
||||||
|
.detail-meta { color: var(--text2); font-size: 0.85rem; margin-bottom: 10px; }
|
||||||
|
.detail-body {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
margin: 12px 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.detail-body p { margin-bottom: 10px; }
|
||||||
|
.detail-body pre { background: var(--surface2); padding: 10px; border-radius: 6px; overflow-x: auto; }
|
||||||
|
.detail-body code { font-size: 0.82rem; }
|
||||||
|
.detail-empty { color: var(--text2); font-style: italic; }
|
||||||
|
.detail-comments-title { font-size: 0.95rem; margin: 16px 0 10px; }
|
||||||
|
.comment {
|
||||||
|
background: var(--surface2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.comment-head { font-size: 0.75rem; color: var(--text2); margin-bottom: 6px; }
|
||||||
|
.comment-body { font-size: 0.88rem; line-height: 1.5; word-break: break-word; }
|
||||||
|
#comment-form textarea {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text);
|
||||||
|
padding: 10px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.detail-foot { margin-top: 14px; font-size: 0.85rem; }
|
||||||
|
|
||||||
|
/* Activity feed */
|
||||||
|
.activity-list { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
.activity-item {
|
||||||
|
display: flex; gap: 12px; align-items: baseline;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.activity-date { color: var(--text2); font-size: 0.78rem; margin-left: 8px; }
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.board-container { flex-direction: column; }
|
.board-container { flex-direction: column; }
|
||||||
|
|||||||
+139
-1
@@ -50,6 +50,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
updateCounts();
|
updateCounts();
|
||||||
|
const targetCol = evt.to.closest('.kanban-column');
|
||||||
|
const badge = targetCol?.querySelector('.column-count');
|
||||||
|
if (badge?.classList.contains('over')) {
|
||||||
|
showToast('⚠️ Limite WIP dépassée dans ' + evt.to.dataset.column.replace('_', ' ') + ' (' + badge.textContent + ')', true);
|
||||||
|
}
|
||||||
let msg = '✅ Déplacé vers ' + evt.to.dataset.column.replace('_', ' ');
|
let msg = '✅ Déplacé vers ' + evt.to.dataset.column.replace('_', ' ');
|
||||||
if (data.gitea_sync === 'closed') msg += ' · issue fermée dans Gitea';
|
if (data.gitea_sync === 'closed') msg += ' · issue fermée dans Gitea';
|
||||||
if (data.gitea_sync === 'reopened') msg += ' · issue rouverte dans Gitea';
|
if (data.gitea_sync === 'reopened') msg += ' · issue rouverte dans Gitea';
|
||||||
@@ -112,10 +117,143 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.querySelectorAll('.kanban-column').forEach(col => {
|
document.querySelectorAll('.kanban-column').forEach(col => {
|
||||||
const count = col.querySelectorAll('.kanban-card').length;
|
const count = col.querySelectorAll('.kanban-card').length;
|
||||||
const badge = col.querySelector('.column-count');
|
const badge = col.querySelector('.column-count');
|
||||||
if (badge) badge.textContent = count;
|
if (!badge) return;
|
||||||
|
const limit = parseInt(badge.dataset.limit || '0', 10);
|
||||||
|
if (limit > 0) {
|
||||||
|
badge.textContent = count + '/' + limit;
|
||||||
|
badge.classList.toggle('over', count > limit);
|
||||||
|
} else {
|
||||||
|
badge.textContent = count;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Card detail drawer ─────────────────────────────────
|
||||||
|
// A click opens the panel; a drag must not. Track pointer travel.
|
||||||
|
let downX = 0, downY = 0;
|
||||||
|
document.addEventListener('pointerdown', (e) => { downX = e.clientX; downY = e.clientY; });
|
||||||
|
document.querySelectorAll('.kanban-card').forEach(card => {
|
||||||
|
card.addEventListener('click', (e) => {
|
||||||
|
if (Math.hypot(e.clientX - downX, e.clientY - downY) > 6) return;
|
||||||
|
if (e.target.closest('a')) return;
|
||||||
|
openDetail(card.dataset.repo, card.dataset.issue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawer = document.getElementById('card-detail');
|
||||||
|
const overlay = document.getElementById('detail-overlay');
|
||||||
|
let currentIssue = null; // {repo, number}
|
||||||
|
|
||||||
|
function renderMarkdown(text) {
|
||||||
|
if (window.marked && window.DOMPurify) {
|
||||||
|
return DOMPurify.sanitize(marked.parse(text || ''));
|
||||||
|
}
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text || '';
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDetail() {
|
||||||
|
drawer.hidden = true;
|
||||||
|
overlay.hidden = true;
|
||||||
|
currentIssue = null;
|
||||||
|
}
|
||||||
|
document.getElementById('detail-close')?.addEventListener('click', closeDetail);
|
||||||
|
overlay?.addEventListener('click', closeDetail);
|
||||||
|
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDetail(); });
|
||||||
|
|
||||||
|
async function openDetail(repoFull, number) {
|
||||||
|
const [owner, repo] = repoFull.split('/', 2);
|
||||||
|
currentIssue = { repo: repoFull, number };
|
||||||
|
drawer.hidden = false;
|
||||||
|
overlay.hidden = false;
|
||||||
|
document.getElementById('detail-number').textContent = '#' + number;
|
||||||
|
document.getElementById('detail-title').textContent = 'Chargement...';
|
||||||
|
document.getElementById('detail-body').innerHTML = '';
|
||||||
|
document.getElementById('detail-comments').innerHTML = '';
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/issues/${owner}/${repo}/${number}`);
|
||||||
|
const data = await r.json().catch(() => ({}));
|
||||||
|
if (!r.ok || !data.ok) throw new Error(data.error || ('HTTP ' + r.status));
|
||||||
|
fillDetail(data.issue, data.comments);
|
||||||
|
} catch (err) {
|
||||||
|
document.getElementById('detail-title').textContent = 'Erreur de chargement';
|
||||||
|
showToast('❌ Détail indisponible : ' + err.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillDetail(issue, comments) {
|
||||||
|
document.getElementById('detail-title').textContent = issue.title;
|
||||||
|
const stateChip = document.getElementById('detail-state');
|
||||||
|
stateChip.textContent = issue.state === 'closed' ? '✅ Fermée' : '🟢 Ouverte';
|
||||||
|
const meta = [];
|
||||||
|
if (issue.milestone) meta.push('🎯 ' + issue.milestone);
|
||||||
|
if (issue.due_date) meta.push('📅 échéance ' + String(issue.due_date).slice(0, 10));
|
||||||
|
meta.push('créée le ' + String(issue.created_at).slice(0, 10));
|
||||||
|
for (const a of issue.assignees || []) {
|
||||||
|
meta.push('👤 ' + a.login);
|
||||||
|
}
|
||||||
|
document.getElementById('detail-meta').textContent = meta.join(' · ');
|
||||||
|
const labelsBox = document.getElementById('detail-labels');
|
||||||
|
labelsBox.innerHTML = '';
|
||||||
|
(issue.labels || []).forEach(l => {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'label-tag';
|
||||||
|
const color = /^[0-9a-fA-F]{6}$/.test(l.color || '') ? l.color : '888888';
|
||||||
|
span.style.background = '#' + color + '30';
|
||||||
|
span.style.color = '#' + color;
|
||||||
|
span.textContent = l.name;
|
||||||
|
labelsBox.appendChild(span);
|
||||||
|
});
|
||||||
|
document.getElementById('detail-body').innerHTML =
|
||||||
|
issue.body ? renderMarkdown(issue.body) : '<p class="detail-empty">Aucune description.</p>';
|
||||||
|
document.getElementById('detail-gitea-link').href = issue.html_url || '#';
|
||||||
|
const box = document.getElementById('detail-comments');
|
||||||
|
box.innerHTML = '';
|
||||||
|
(comments || []).forEach(c => box.appendChild(commentNode(c)));
|
||||||
|
if (!comments || comments.length === 0) {
|
||||||
|
box.innerHTML = '<p class="detail-empty">Aucun commentaire.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function commentNode(c) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'comment';
|
||||||
|
const head = document.createElement('div');
|
||||||
|
head.className = 'comment-head';
|
||||||
|
head.textContent = '👤 ' + (c.user?.login || '?') + ' · ' + String(c.created_at).slice(0, 16).replace('T', ' ');
|
||||||
|
const bodyEl = document.createElement('div');
|
||||||
|
bodyEl.className = 'comment-body';
|
||||||
|
bodyEl.innerHTML = renderMarkdown(c.body || '');
|
||||||
|
div.appendChild(head);
|
||||||
|
div.appendChild(bodyEl);
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('comment-form')?.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!currentIssue) return;
|
||||||
|
const textarea = document.getElementById('comment-body');
|
||||||
|
const body = textarea.value.trim();
|
||||||
|
if (!body) return;
|
||||||
|
const [owner, repo] = currentIssue.repo.split('/', 2);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('body', body);
|
||||||
|
formData.append('csrf_token', csrfToken);
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/issues/${owner}/${repo}/${currentIssue.number}/comments`, { method: 'POST', body: formData });
|
||||||
|
const data = await r.json().catch(() => ({}));
|
||||||
|
if (!r.ok || !data.ok) throw new Error(data.error || ('HTTP ' + r.status));
|
||||||
|
const box = document.getElementById('detail-comments');
|
||||||
|
if (box.querySelector('.detail-empty')) box.innerHTML = '';
|
||||||
|
box.appendChild(commentNode(data.comment));
|
||||||
|
textarea.value = '';
|
||||||
|
showToast('✅ Commentaire publié dans Gitea');
|
||||||
|
} catch (err) {
|
||||||
|
showToast('❌ Échec du commentaire : ' + err.message, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function showToast(msg, isError = false) {
|
function showToast(msg, isError = false) {
|
||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
toast.className = 'toast' + (isError ? ' error' : '');
|
toast.className = 'toast' + (isError ? ' error' : '');
|
||||||
|
|||||||
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+3
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user