Téléverser les fichiers vers "web/templates"

This commit is contained in:
RainbowYoshi 2026-01-31 21:53:01 +00:00
parent e2aed85f6d
commit 86d2ca7799
5 changed files with 494 additions and 0 deletions

View File

@ -0,0 +1,60 @@
{{define "file_browser"}}
<div class="file-browser">
<div class="readme-header" style="display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; gap: 1rem; align-items: center;">
<span id="file-name-display" style="font-weight: 600;">{{.CurrentPath}}</span>
<span style="color: var(--text-muted); font-weight: normal; font-size: 0.8rem;">{{len .FileContent}}
bytes</span>
</div>
<div>
<a href="/repo?name={{.Repository.Name}}&path={{.CurrentPath}}&type=edit" class="btn-primary"
style="padding: 0.2rem 0.6rem; font-size: 0.8rem; height: auto; width: auto; text-decoration: none; display: inline-block;">Edit</a>
</div>
</div>
<div id="markdown-view" class="readme-content" style="display: none;"></div>
<pre id="code-view" class="blob-content"
style="margin: 0; padding: 1rem; background: #0d1117; overflow: auto;"><code id="code-content">{{.FileContent}}</code></pre>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const path = "{{.CurrentPath}}";
const ext = path.split('.').pop().toLowerCase();
const content = document.getElementById('code-content').textContent;
if (ext === 'md') {
document.getElementById('code-view').style.display = 'none';
const container = document.getElementById('markdown-view');
container.style.display = 'block';
container.innerHTML = marked.parse(content);
// Register 'env' alias to 'ini' or 'bash' to avoid warnings
if (!hljs.getLanguage('env')) {
if (hljs.getLanguage('ini')) {
hljs.registerAlias('env', { languageName: 'ini' });
} else if (hljs.getLanguage('bash')) {
hljs.registerAlias('env', { languageName: 'bash' });
} else {
hljs.registerLanguage('env', function () { return { contains: [] }; });
}
}
container.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
container.querySelectorAll('img').forEach((img) => {
img.style.maxWidth = '100%';
img.style.height = 'auto';
img.style.display = 'inline-block';
img.style.verticalAlign = 'middle';
});
container.querySelectorAll('a > img').forEach((img) => {
img.parentElement.style.display = 'inline-block';
});
} else {
const codeBlock = document.getElementById('code-content');
codeBlock.classList.add('language-' + ext);
hljs.highlightElement(codeBlock);
}
});
</script>
{{end}}

View File

@ -0,0 +1,83 @@
{{define "code_editor"}}
<div class="file-browser">
<div class="readme-header">Editing {{.CurrentPath}}</div>
<form id="edit-form" action="/repo?name={{.Repository.Name}}&path={{.CurrentPath}}&action=save_file" method="POST" style="margin: 0;">
<input type="hidden" name="action" value="save_file">
<input type="hidden" name="content" id="hidden-content">
<div id="monaco-container" style="width: 100%; height: 60vh; border-bottom: 1px solid var(--border-color);"></div>
<textarea id="raw-content" style="display:none;">{{.FileContent}}</textarea>
<div style="padding: 1rem; border-top: 1px solid var(--border-color); background: var(--card-bg); display: flex; gap: 1rem; align-items: center;">
<input type="text" name="message" class="form-control" placeholder="Commit summary (optional)" style="flex: 1;">
<a href="/repo?name={{.Repository.Name}}&path={{.CurrentPath}}&type=blob" class="btn-primary" style="background: transparent; border: 1px solid var(--border-color); color: var(--text-primary); width: auto; text-decoration: none; padding: 0.5rem 1rem; display: inline-block; text-align: center;">Cancel</a>
<button type="submit" class="btn-primary" style="width: auto;">Commit changes</button>
</div>
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"></script>
<script>
window.MonacoEnvironment = {
getWorkerUrl: function (workerId, label) {
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
self.MonacoEnvironment = {
baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/'
};
importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/base/worker/workerMain.js');`
)}`;
}
};
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
require(['vs/editor/editor.main'], function() {
var editor = monaco.editor.create(document.getElementById('monaco-container'), {
value: document.getElementById('raw-content').value,
language: getLanguageFromPath('{{.CurrentPath}}'),
theme: 'vs-dark',
automaticLayout: true,
minimap: { enabled: true },
padding: { top: 16, bottom: 16 },
fontSize: 14,
scrollBeyondLastLine: false
});
document.getElementById('edit-form').addEventListener('submit', function() {
document.getElementById('hidden-content').value = editor.getValue();
});
});
function getLanguageFromPath(path) {
const ext = path.split('.').pop().toLowerCase();
const map = {
'js': 'javascript', 'mjs': 'javascript', 'jsx': 'javascript',
'ts': 'typescript', 'tsx': 'typescript',
'py': 'python',
'go': 'go',
'html': 'html', 'htm': 'html',
'css': 'css', 'scss': 'scss', 'less': 'less',
'json': 'json',
'md': 'markdown',
'php': 'php',
'sql': 'sql',
'java': 'java',
'c': 'c', 'h': 'c',
'cpp': 'cpp', 'hpp': 'cpp', 'cc': 'cpp',
'cs': 'csharp',
'rs': 'rust',
'xml': 'xml', 'svg': 'xml',
'yaml': 'yaml', 'yml': 'yaml',
'sh': 'shell', 'bash': 'shell', 'zsh': 'shell',
'bat': 'bat', 'cmd': 'bat',
'ps1': 'powershell',
'rb': 'ruby',
'lua': 'lua',
'r': 'r',
'ini': 'ini', 'toml': 'ini', 'conf': 'ini',
'dockerfile': 'dockerfile'
};
return map[ext] || 'plaintext';
}
</script>
{{end}}

View File

@ -0,0 +1,19 @@
{{define "commit_history"}}
<div class="file-browser">
<div class="readme-header">Commit History</div>
{{range .Commits}}
<div class="file-row">
<div class="file-icon" style="font-family: monospace; color: var(--accent-color);">
<a href="/repo?name={{$.Repository.Name}}&type=commit&hash={{.Hash}}" style="color: inherit; text-decoration: none;">{{.Hash}}</a>
</div>
<div class="file-name" style="font-weight: normal;">
<a href="/repo?name={{$.Repository.Name}}&type=commit&hash={{.Hash}}" style="color: inherit; text-decoration: none;">{{.Message}}</a>
</div>
<div class="file-meta">{{.Author}}</div>
<div class="file-meta">{{.Date}}</div>
</div>
{{else}}
<div style="padding: 1rem; text-align: center; color: var(--text-muted);">No commits found.</div>
{{end}}
</div>
{{end}}

View File

@ -0,0 +1,70 @@
{{define "commit_view"}}
<div style="display: flex; gap: 1.5rem; margin-top: 1rem;">
<div style="width: 250px; flex-shrink: 0;">
<div style="background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 6px; position: sticky; top: 7rem;">
<div class="readme-header" style="padding: 0.75rem 1rem; border-bottom: 1px solid var(--border-color); font-weight: 600; font-size: 0.8rem;">Files Changed</div>
<div style="padding: 0.5rem; max-height: calc(85vh - 9rem); overflow-y: auto;">
{{range .CommitDiffs}}
<a href="#diff-{{.Name}}" style="display: block; padding: 0.4rem 0.5rem; color: var(--text-primary); text-decoration: none; font-size: 0.9rem; border-radius: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{.Name}}
</a>
{{end}}
</div>
</div>
</div>
<div style="flex: 1; min-width: 0;">
{{range .CommitDiffs}}
<div id="diff-{{.Name}}" class="file-browser" style="margin-top: 0; margin-bottom: 2rem;">
<div class="readme-header" style="display: flex; justify-content: space-between; align-items: center; cursor: pointer; padding: 0.75rem 1rem; border-bottom: 1px solid var(--border-color); font-weight: 600; background: var(--card-bg);" onclick="toggleDiff(this)">
<span>{{.Name}}</span>
<span style="font-size: 0.8rem; color: var(--text-muted);">Toggle</span>
</div>
<div class="diff-container" style="position: relative;">
<pre class="blob-content diff-content" style="margin: 0; padding: 1rem; background: #0d1117; overflow: hidden; max-height: 250px; display: block; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px;"><code class="language-diff">{{.Content}}</code></pre>
<div class="diff-overlay" style="position: absolute; bottom: 0; left: 0; width: 100%; height: 60px; background: linear-gradient(to bottom, transparent, #0d1117); display: flex; align-items: flex-end; justify-content: center; padding-bottom: 15px; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px;">
<button onclick="expandDiff(this)" class="btn-primary" style="padding: 0.3rem 1rem; font-size: 0.8rem; width: auto; z-index: 10; box-shadow: 0 2px 4px rgba(0,0,0,0,0.5);">Show Full Diff</button>
</div>
</div>
</div>
{{else}}
<div style="padding: 2rem; text-align: center; color: var(--text-muted);">No changes found in this commit.</div>
{{end}}
</div>
</div>
<script>
function toggleDiff(header) {
const content = header.nextElementSibling;
if (content.style.display === 'none') {
content.style.display = 'block';
} else {
content.style.display = 'none';
}
}
function expandDiff(btn) {
const overlay = btn.parentElement;
const pre = overlay.previousElementSibling;
pre.style.maxHeight = 'none';
pre.style.overflow = 'auto';
overlay.style.display = 'none';
}
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll('code.language-diff').forEach((block) => {
hljs.highlightElement(block);
});
document.querySelectorAll('.diff-content').forEach(pre => {
if (pre.scrollHeight <= 260) {
pre.style.maxHeight = 'none';
const overlay = pre.nextElementSibling;
if (overlay) overlay.style.display = 'none';
}
});
});
</script>
{{end}}

View File

@ -0,0 +1,262 @@
{{define "contribution_graph_inner"}}
<div class="contrib-grid">
{{range .ContributionGraph}}
<div class="contrib-day {{.Color}}" data-count="{{.Count}}" data-date="{{.Date}}"></div>
{{end}}
</div>
<div id="activity-data" style="display:none;">
<div class="activity-header">Contribution activity</div>
<div class="activity-timeline">
{{range $index, $element := .ActivityFeed}}
<div class="activity-month-group {{if ge $index 2}}extra-activity{{end}}" {{if ge $index 2}}style="display:none;"{{end}}>
<div class="activity-month">{{.MonthName}}</div>
<div class="activity-entry">
<div class="timeline-icon">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo-push" style="fill: currentColor;">
<path d="M1 2.5A2.5 2.5 0 0 1 3.5 0h8.75a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0V1.5h-8A1 1 0 0 0 2.5 2.5v7a1 1 0 0 0 1 1h2.75a.75.75 0 0 1 0 1.5H3.5A2.5 2.5 0 0 1 1 9.5v-7Zm6.397 5.097c.328-.432.883-.432 1.21 0l3 3.957a.753.753 0 0 1 0 .95.748.748 0 0 1-1.056.002L8.25 10.126V15.25a.75.75 0 0 1-1.5 0V10.127l-2.298 2.38a.75.75 0 0 1-1.06-1.06l3.005-3.85Z"></path>
</svg>
</div>
<div class="activity-details">
<div class="activity-group-header">
<span>Created {{.TotalCommits}} commits in {{.RepoCount}} repositories</span>
</div>
<div class="activity-list">
{{range .Items}}
{{if $element.Date}}
<a href="/repo/{{.RepoName}}/commits?date={{$element.Date}}" class="activity-list-item">
{{else}}
<a href="/repo/{{.RepoName}}/commits?year={{$element.YearInt}}&month={{$element.MonthInt}}" class="activity-list-item">
{{end}}
<span class="activity-repo-name">{{.RepoName}}</span>
<div class="activity-stats">
<span style="font-size: 0.8rem; color: var(--text-muted);">{{.Commits}} commits</span>
</div>
</a>
{{end}}
</div>
</div>
</div>
</div>
{{else}}
<div class="activity-month">No activity recorded for this period.</div>
{{end}}
{{if gt (len .ActivityFeed) 2}}
<div class="activity-show-more">
<button id="btn-show-more" class="btn-activity-toggle">Show more activity</button>
</div>
{{end}}
</div>
</div>
{{end}}
{{define "contribution_graph"}}
<div class="contribution-graph-container" style="margin-bottom: 2rem;">
<div class="section-title">
<span id="contrib-title">Contributions in {{.SelectedYear}}</span>
</div>
<div class="calendar-graph">
<div class="graph-svg-container" id="graph-container">
<div style="height: 110px; display: flex; align-items: center; justify-content: center; color: var(--text-muted);">
<span>Loading...</span>
</div>
</div>
<div class="contrib-footer">
<span>Less</span>
<div class="contrib-legend">
<div class="contrib-day contrib-level-0"></div>
<div class="contrib-day contrib-level-1"></div>
<div class="contrib-day contrib-level-2"></div>
<div class="contrib-day contrib-level-3"></div>
<div class="contrib-day contrib-level-4"></div>
</div>
<span>More</span>
</div>
</div>
<div class="activity-layout">
<div class="activity-content" id="activity-content-target">
<div class="activity-header">Contribution activity</div>
<div class="activity-timeline">
<div class="activity-month">Loading activity...</div>
</div>
</div>
<div class="year-sidebar" id="year-list">
{{range .AvailableYears}}
<a href="#" class="year-btn {{if eq . $.SelectedYear}}active{{end}}" data-year="{{.}}">{{.}}</a>
{{end}}
</div>
</div>
</div>
<div id="graph-tooltip" class="graph-tooltip"></div>
<script>
(function() {
const tooltip = document.getElementById('graph-tooltip');
const graphContainer = document.getElementById('graph-container');
const titleSpan = document.getElementById('contrib-title');
const yearList = document.getElementById('year-list');
const activityTarget = document.getElementById('activity-content-target');
function bindTooltipEvents() {
const days = document.querySelectorAll('.contrib-day');
days.forEach(day => {
day.addEventListener('mouseenter', (e) => {
const count = e.target.getAttribute('data-count');
const date = e.target.getAttribute('data-date');
if (count !== null && date) {
const countNum = parseInt(count);
const countText = countNum === 0 ? "No contributions" : (countNum + " contribution" + (countNum > 1 ? "s" : ""));
tooltip.innerHTML = `<strong>${countText}</strong> on ${date}`;
tooltip.style.display = 'block';
}
});
day.addEventListener('mousemove', (e) => {
tooltip.style.left = (e.clientX - (tooltip.offsetWidth / 2)) + 'px';
tooltip.style.top = (e.clientY - 35) + 'px';
});
day.addEventListener('click', (e) => {
const date = e.target.getAttribute('data-date');
const count = e.target.getAttribute('data-count');
if (date) {
document.querySelectorAll('.contrib-day').forEach(d => d.style.opacity = '');
e.target.style.opacity = '0.5';
e.target.style.opacity = '0.5';
titleSpan.textContent = `Activity on ${date}`;
activityTarget.style.opacity = '0.5';
fetch(`/api/contribution-graph?date=${date}`)
.then(r => r.text())
.then(html => {
const temp = document.createElement('div');
temp.innerHTML = html;
const activityData = temp.querySelector('#activity-data');
if (activityData) {
activityTarget.innerHTML = activityData.innerHTML;
} else {
activityTarget.innerHTML = '<div class="activity-month">No activity.</div>';
}
activityTarget.style.opacity = '1';
bindShowMore();
setTimeout(() => e.target.style.opacity = '', 200);
});
}
});
day.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});
});
}
function bindShowMore() {
const btn = document.getElementById('btn-show-more');
if (btn) {
btn.addEventListener('click', function() {
const extra = document.querySelectorAll('.extra-activity');
const isExpanded = btn.getAttribute('data-expanded') === 'true';
if (isExpanded) {
extra.forEach(el => el.style.display = 'none');
btn.textContent = 'Show more activity';
btn.setAttribute('data-expanded', 'false');
} else {
extra.forEach(el => el.style.display = 'block');
btn.textContent = 'Show fewer activity';
btn.setAttribute('data-expanded', 'true');
}
});
}
}
function updateView(html) {
const temp = document.createElement('div');
temp.innerHTML = html;
const activityData = temp.querySelector('#activity-data');
if (activityData) {
activityTarget.innerHTML = activityData.innerHTML;
activityData.remove();
} else {
activityTarget.innerHTML = '<div class="activity-month">No activity data loaded.</div>';
}
graphContainer.innerHTML = temp.innerHTML;
graphContainer.style.opacity = '1';
activityTarget.style.opacity = '1';
bindTooltipEvents();
bindShowMore();
}
function bindYearClicks() {
document.querySelectorAll('.year-btn').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const year = this.getAttribute('data-year');
document.querySelectorAll('.year-btn').forEach(l => {
l.classList.remove('active');
});
this.classList.add('active');
titleSpan.textContent = `Contributions in ${year}`;
graphContainer.style.opacity = '0.5';
activityTarget.style.opacity = '0.5';
fetch(`/api/contribution-graph?year=${year}`)
.then(response => response.text())
.then(html => updateView(html))
.catch(err => {
console.error('Error fetching graph:', err);
graphContainer.style.opacity = '1';
});
});
});
}
bindTooltipEvents();
bindYearClicks();
if (yearList && yearList.children.length === 0) {
titleSpan.textContent = "Loading contributions...";
fetch('/api/contribution-graph-init')
.then(r => r.json())
.then(data => {
if (data.availableYears && data.availableYears.length > 0) {
data.availableYears.forEach(year => {
const a = document.createElement('a');
a.href = "#";
a.className = `year-btn ${year === data.selectedYear ? 'active' : ''}`;
a.setAttribute('data-year', year);
a.textContent = year;
yearList.appendChild(a);
});
titleSpan.textContent = `Contributions in ${data.selectedYear}`;
} else {
titleSpan.textContent = "No contribution history";
}
updateView(data.graphHTML);
bindYearClicks();
})
.catch(e => {
console.error("Failed to load graph init:", e);
titleSpan.textContent = "Error loading graph";
graphContainer.innerHTML = "<p>Failed to load.</p>";
});
}
})();
</script>
{{end}}