initial template rendering
This commit is contained in:
parent
39d0a79bbb
commit
d0e556ec3c
1062
Cargo.lock
generated
1062
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -1,9 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mintee"
|
name = "mintee"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pulldown-cmark = "0.9.3"
|
git2 = "0.20.2"
|
||||||
syntect = "5.1.0"
|
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
||||||
tera = "1.19.1"
|
#serde_derive = { version = "1.0.219", default-features = false }
|
||||||
|
#inkjet = "0.11.1"
|
||||||
|
#markdown = "1.0.0"
|
||||||
|
tera = { version = "1.20.0", default-features = false, features = ["builtins"] }
|
||||||
|
127
assets/templates/repo/base.html
Normal file
127
assets/templates/repo/base.html
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2022 Sasha Koshka <sashakoshka@tebibyte.media>
|
||||||
|
Copyright (c) 2023,2025 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
This file is part of Mintee.
|
||||||
|
|
||||||
|
Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<title>{%block title %}{{ owner }}/{{ repo }} – {% endblock title %}Mintee</title>
|
||||||
|
|
||||||
|
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="res/style.css">
|
||||||
|
<link rel="stylesheet" href="res/martian.css">
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
@licstart The following is the entire license notice for the
|
||||||
|
JavaScript code in this page.
|
||||||
|
|
||||||
|
Copyright (c) 2022 Sasha Koshka <sashakoshka@tebibyte.media>
|
||||||
|
|
||||||
|
The JavaScript code in this page is free software: you can
|
||||||
|
redistribute it and/or modify it under the terms of the GNU
|
||||||
|
General Public License (GNU GPL) as published by the Free Software
|
||||||
|
Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version. The code is distributed WITHOUT ANY WARRANTY;
|
||||||
|
without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
||||||
|
|
||||||
|
As additional permission under GNU GPL version 3 section 7, you
|
||||||
|
may distribute non-source (e.g., minimized or compacted) forms of
|
||||||
|
that code without the copy of the GNU GPL normally required by
|
||||||
|
section 4, provided you include this license notice and a URL
|
||||||
|
through which recipients can access the Corresponding Source.
|
||||||
|
|
||||||
|
|
||||||
|
@licend The above is the entire license notice
|
||||||
|
for the JavaScript code in this page.
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body id="{% block page %}{% endblock page %}">
|
||||||
|
|
||||||
|
<div id=stickyWrap>
|
||||||
|
<nav>
|
||||||
|
<div class=buttonListWrap>
|
||||||
|
<ul class=buttonList>
|
||||||
|
<li><a href="/">{{ site }}</a></li>
|
||||||
|
<li><a class=dashboardButton href="/">
|
||||||
|
<span class=collapse>dashboard</span>
|
||||||
|
</a></li>
|
||||||
|
<li><a class=profileButton href="/{{ user }}">
|
||||||
|
<span class=collapse>profile</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li><a class=notificationsButton href="/notifications">
|
||||||
|
<span class=collapse>notifications</span>
|
||||||
|
{{ notif_count}}
|
||||||
|
</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=buttonListWrap>
|
||||||
|
<ul class=buttonList>
|
||||||
|
<li><a href="./">code</a></li>
|
||||||
|
<li><a href="./commits">commits</a></li>
|
||||||
|
<li><a href="./tags">tags</a></li>
|
||||||
|
<li><a href="./tickets">issues {{ ticket_count }}</a></li>
|
||||||
|
<li><a href="/">releases</a></li>
|
||||||
|
<li><a href="/">settings</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<span>
|
||||||
|
viewing
|
||||||
|
<span class=linkedPath>
|
||||||
|
<a href="/">{{ owner }}</a>/<a href="/">{{ repo }}</a>/<a href="/">{{ branch }}</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ul class=buttonList>
|
||||||
|
<li><a class=watchButton href="/">watch</a></li>
|
||||||
|
<li><a class=starButton href="/">star</a></li>
|
||||||
|
<li><a class=forkButton href="/">fork</a></li>
|
||||||
|
<li><a class=cloneButton href="/">clone</a></li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% block content %}{%endblock content %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let stickyWrap = document.querySelector("#stickyWrap")
|
||||||
|
document.addEventListener ("scroll", () => {
|
||||||
|
if (document.documentElement.scrollTop > 0) {
|
||||||
|
stickyWrap.className = "lifted"
|
||||||
|
} else {
|
||||||
|
stickyWrap.className = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="https://git.tebibyte.media/meta/mintee">Mintee</a>,
|
||||||
|
the fresh and tasty git frontend. page: #s. template: #s.
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
38
assets/templates/repo/code.html
Normal file
38
assets/templates/repo/code.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block page %}code{% endblock page %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id=contentWrap>
|
||||||
|
<aside>
|
||||||
|
<p class=sidebarLabel>
|
||||||
|
<a href="/"></a><a href="/">{{ directory }}</a>
|
||||||
|
</p>
|
||||||
|
<table class=files border=1>
|
||||||
|
<tbody>
|
||||||
|
{% block entries_list %}
|
||||||
|
{% for entry in entries %}
|
||||||
|
<tr>
|
||||||
|
<td><a class={{ entry.class }} href="/">{{ entry.path }}</a></td>
|
||||||
|
<td><a class=commit href="./commit/{{ entry.last_commit }}">
|
||||||
|
{{ entry.last_commit_short }}
|
||||||
|
</a></td>
|
||||||
|
<td><time datetime="{{ entry.last_commit_time }}">
|
||||||
|
{{ entry.last_commit_time | date(format="%Y-%m-%d %H:%M")}}
|
||||||
|
</time></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock entries %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</aside>
|
||||||
|
{% block readme %}
|
||||||
|
<main>
|
||||||
|
<p class=previewLabel>README.md</p>
|
||||||
|
<article class=preview>
|
||||||
|
{{ readme_content }}
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
{% endblock readme %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -1 +0,0 @@
|
|||||||
fn main() { }
|
|
164
src/render.rs
Normal file
164
src/render.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Sergey "Shnatsel" Davidoff <shnatsel@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
env::current_dir,
|
||||||
|
fs::metadata,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use git2::{ Commit, Repository, Sort };
|
||||||
|
use serde::Serialize;
|
||||||
|
use tera::{ Context, Tera };
|
||||||
|
|
||||||
|
trait ToContext {
|
||||||
|
fn to_context(self) -> Result<Context, git2::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Entry {
|
||||||
|
class: String,
|
||||||
|
committer: String,
|
||||||
|
last_commit: String,
|
||||||
|
last_commit_short: String,
|
||||||
|
last_commit_time: i64,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToContext for Repository {
|
||||||
|
fn to_context(self) -> Result<Context, git2::Error> {
|
||||||
|
let repo = self.commondir();
|
||||||
|
let _index = self.index()?;
|
||||||
|
let head = self.head()?;
|
||||||
|
let branch = if head.is_branch() {
|
||||||
|
head.shorthand().unwrap()
|
||||||
|
} else { "detached" };
|
||||||
|
let head_commit = head.peel_to_commit()?;
|
||||||
|
let entries = get_entries(&self)?;
|
||||||
|
let committer = head_commit.committer().name().unwrap().to_owned();
|
||||||
|
let _author = head_commit.author().name().unwrap().to_owned();
|
||||||
|
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
|
||||||
|
// stub until we have database
|
||||||
|
ctx.insert("user", "anon");
|
||||||
|
ctx.insert("site", "TiB.");
|
||||||
|
ctx.insert("notif_count", "");
|
||||||
|
ctx.insert("ticket_count", "(47)");
|
||||||
|
ctx.insert("owner", &committer);
|
||||||
|
ctx.insert("repo", repo);
|
||||||
|
ctx.insert("branch", &branch);
|
||||||
|
ctx.insert("directory", repo);
|
||||||
|
ctx.insert("entries", &entries);
|
||||||
|
ctx.insert("readme_content", "this is a readme");
|
||||||
|
|
||||||
|
Ok(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
fn new(commit: &Commit, path: String) -> Result<Self, git2::Error> {
|
||||||
|
let commit_id = commit.id();
|
||||||
|
let ft = metadata(&path).unwrap().file_type();
|
||||||
|
let class: String;
|
||||||
|
|
||||||
|
if ft.is_dir() {
|
||||||
|
class = "directory".to_owned();
|
||||||
|
} else {
|
||||||
|
class = "file".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Entry {
|
||||||
|
class,
|
||||||
|
committer: commit
|
||||||
|
.committer()
|
||||||
|
.name()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_owned(),
|
||||||
|
last_commit: commit_id.to_string(),
|
||||||
|
last_commit_short: commit.as_object().short_id()?.as_str().unwrap_or("").to_owned(),
|
||||||
|
last_commit_time: commit.time().seconds(),
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entries(repo: &Repository) -> Result<Vec<Entry>, git2::Error> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
let mut revwalk = repo.revwalk()?;
|
||||||
|
|
||||||
|
revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
|
||||||
|
revwalk.push_head()?;
|
||||||
|
|
||||||
|
for commit_id in revwalk {
|
||||||
|
let commit_id = commit_id?;
|
||||||
|
let commit = repo.find_commit(commit_id)?;
|
||||||
|
if commit.parent_count() <= 1 {
|
||||||
|
let tree = commit.tree()?;
|
||||||
|
let prev_tree = match commit.parent_count() {
|
||||||
|
1 => Some(commit.parent(0)?.tree()?),
|
||||||
|
0 => None,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let diff = repo.diff_tree_to_tree(prev_tree.as_ref(), Some(&tree), None)?;
|
||||||
|
for delta in diff.deltas() {
|
||||||
|
if let Some(file_path) = delta.new_file().path() {
|
||||||
|
entries.push(Entry::new(&commit, file_path.to_str().unwrap_or("Invalid UTF-8").to_owned())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_path(path: PathBuf) -> tera::Result<String> {
|
||||||
|
let tera = Tera::new("./assets/templates/repo/*")?;
|
||||||
|
let repo = Repository::discover(path).unwrap();
|
||||||
|
let context = repo.to_context().unwrap();
|
||||||
|
|
||||||
|
tera.render("code.html", &context)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user