summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.DS_Storebin0 -> 6148 bytes
-rw-r--r--.gitignore1
-rw-r--r--__pycache__/main.cpython-314.pycbin2884 -> 7838 bytes
-rw-r--r--favicon.icobin0 -> 15406 bytes
-rw-r--r--fonts/.DS_Storebin0 -> 6148 bytes
-rw-r--r--fonts/Inter-Italic-VariableFont_opsz,wght.ttfbin0 -> 904532 bytes
-rw-r--r--fonts/Inter-VariableFont_opsz,wght.ttfbin0 -> 874708 bytes
-rw-r--r--fonts/JetBrainsMono-Italic-VariableFont_wght.ttfbin0 -> 191988 bytes
-rw-r--r--fonts/JetBrainsMono-VariableFont_wght.ttfbin0 -> 187860 bytes
-rw-r--r--icon.pngbin0 -> 118253 bytes
-rw-r--r--index.html14
-rw-r--r--main.py128
-rw-r--r--style.css183
13 files changed, 305 insertions, 21 deletions
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..0c32a4c
--- /dev/null
+++ b/.DS_Store
Binary files differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..85de9cf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+src
diff --git a/__pycache__/main.cpython-314.pyc b/__pycache__/main.cpython-314.pyc
index 933130a..e6fb69a 100644
--- a/__pycache__/main.cpython-314.pyc
+++ b/__pycache__/main.cpython-314.pyc
Binary files differ
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..c131a30
--- /dev/null
+++ b/favicon.ico
Binary files differ
diff --git a/fonts/.DS_Store b/fonts/.DS_Store
new file mode 100644
index 0000000..5008ddf
--- /dev/null
+++ b/fonts/.DS_Store
Binary files differ
diff --git a/fonts/Inter-Italic-VariableFont_opsz,wght.ttf b/fonts/Inter-Italic-VariableFont_opsz,wght.ttf
new file mode 100644
index 0000000..43ed4f5
--- /dev/null
+++ b/fonts/Inter-Italic-VariableFont_opsz,wght.ttf
Binary files differ
diff --git a/fonts/Inter-VariableFont_opsz,wght.ttf b/fonts/Inter-VariableFont_opsz,wght.ttf
new file mode 100644
index 0000000..e31b51e
--- /dev/null
+++ b/fonts/Inter-VariableFont_opsz,wght.ttf
Binary files differ
diff --git a/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf b/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf
new file mode 100644
index 0000000..914e323
--- /dev/null
+++ b/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf
Binary files differ
diff --git a/fonts/JetBrainsMono-VariableFont_wght.ttf b/fonts/JetBrainsMono-VariableFont_wght.ttf
new file mode 100644
index 0000000..d73994a
--- /dev/null
+++ b/fonts/JetBrainsMono-VariableFont_wght.ttf
Binary files differ
diff --git a/icon.png b/icon.png
new file mode 100644
index 0000000..a34719b
--- /dev/null
+++ b/icon.png
Binary files differ
diff --git a/index.html b/index.html
index 8736890..ca1f5a3 100644
--- a/index.html
+++ b/index.html
@@ -3,17 +3,25 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <link rel="icon" type="image/x-icon" href="/src/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <link rel="stylesheet" href="/style.css">
+ <link rel="stylesheet" href="/src/style.css">
<title>altaf-files</title>
</head>
<body>
<div class="sidebar">
- <a href="/"><img class="sidebar-img" src="" alt=""></a>
+ <a href="/"><img class="sidebar-img" src="/src/icon.png" alt=""></a>
<span class="sidebar-text">altaf-files</span>
</div>
<div class="content">
- {{content}}
+ <section>
+ <div class="div-sizing" id="breadcrumb-container">
+ </div>
+ </section>
+ <section>
+ <div class="list-container" id="list-container">
+ </div>
+ </section>
</div>
</body>
</html>
diff --git a/main.py b/main.py
index dae6661..f9693b5 100644
--- a/main.py
+++ b/main.py
@@ -1,11 +1,14 @@
+# files.altafcreator.com is a READ-ONLY website of files I want to share.
+# This code is not safe for user-uploaded files.
+
import fastapi
from fastapi.middleware.cors import CORSMiddleware
import fastapi.staticfiles
import fastapi.responses
-from pydantic import BaseModel
from pathlib import Path
-import os
import html
+from bs4 import BeautifulSoup
+import datetime
print("Hello, world!")
@@ -29,36 +32,125 @@ app.add_middleware(
)
+HTML_FOLDER_TEMPLATE = ""
+with open("index.html") as f:
+ HTML_FOLDER_TEMPLATE = f.read()
+
+
+@app.middleware("http")
+async def force_trailing_slash(request: fastapi.Request, call_next):
+ path = request.url.path
+
+ if not path.endswith("/") and not Path(path).suffix:
+ new_url = str(request.url.replace(path=path + "/"))
+ return fastapi.responses.RedirectResponse(url=new_url, status_code=301)
+
+ response = await call_next(request)
+ return response
+
+
@app.get("/{path:path}")
def folder(path: str):
target_path = (FOLDER_PATH / path).resolve()
- if FOLDER_PATH not in target_path.parents and target_path != FOLDER_PATH:
+ if not target_path.is_relative_to(FOLDER_PATH):
return fastapi.responses.Response(status_code=403, content="Access denied.")
- is_file = False
-
if target_path.is_file():
- is_file = True
+# if target_path.suffix == ".md":
+# return fastapi.responses.Response(content=markdown(target_path), media_type="text/html", status_code=200)
return fastapi.responses.FileResponse(target_path)
elif not target_path.is_dir():
return fastapi.responses.Response(status_code=404)
- content = f"<p>/{path}</p><a href='..'>../</a><br>"
+ files = []
+ directories = []
for item in sorted(target_path.iterdir()):
- safe_child_path = html.escape(item.name)
-
- href_path = ""
- if path == "":
- href_path = f"/{safe_child_path}"
+ if (target_path / item.name).resolve().is_file():
+ files.append(item.name)
else:
- href_path = f"/{path.rstrip('/')}/{safe_child_path}"
+ directories.append(item.name)
+
+ soup = BeautifulSoup(HTML_FOLDER_TEMPLATE, "html.parser")
+
+ for d in directories:
+ add_item_html(d, path, True, soup)
+ for f in files:
+ add_item_html(f, path, False, soup)
+
+ local_path_list = path.split("/")
+
+ home_btn = soup.new_tag("a")
+ home_btn["class"] = "breadcrumb-button"
+ home_btn["href"] = "/"
+ home_btn.string = "🏠 altaf-files"
+ soup.select_one("#breadcrumb-container").append(home_btn)
+
+ for i in range(len(local_path_list)):
+ new_sep = soup.new_tag("span")
+ new_sep.string = "/"
+ soup.select_one("#breadcrumb-container").append(new_sep)
+
+ new_btn = soup.new_tag("a")
+ new_btn["class"] = "breadcrumb-button"
+ new_btn["href"] = "/" + "/".join(local_path_list[0:i + 1])
+ new_btn.string = local_path_list[i]
+ soup.select_one("#breadcrumb-container").append(new_btn)
+
+ return fastapi.responses.Response(content=str(soup), media_type="text/html", status_code=200)
+
+
+def markdown(absolute_path: str):
+ pass
+
+
+def add_item_html(child_path: str, current_path: str, is_directory: bool, soup: BeautifulSoup):
+ href_path = ""
+
+ if current_path == "":
+ href_path = f"/{child_path}"
+ else:
+ href_path = f"/{current_path.rstrip('/')}/{child_path}"
+
+ symbol = "📄"
+ if is_directory:
+ symbol = "📂"
+
+ absolute_path = Path(f"{str(FOLDER_PATH).rstrip('/')}/{current_path.rstrip('/')}/{child_path}").resolve()
+
+ if not absolute_path.exists():
+ return ""
+
+ size = "—"
+ if not is_directory:
+ size = format_size(absolute_path.stat().st_size)
+
+ date_modified = str(datetime.datetime.fromtimestamp(absolute_path.stat().st_mtime))[:10].replace("-", "/")
+
+ new_btn = soup.new_tag("a")
+ new_btn["href"] = href_path
+
+ new_div = soup.new_tag("div")
+ new_btn.append(new_div)
+
+ new_span_name = soup.new_tag("span")
+ new_span_name.string = f"{symbol} {child_path}"
+ new_div.append(new_span_name)
+ new_span_date = soup.new_tag("span")
+ new_span_date["class"] = "list-metadata list-date"
+ new_div.append(new_span_date)
+ new_span_date.string = date_modified
+ new_span_size = soup.new_tag("span")
+ new_span_size["class"] = "list-metadata"
+ new_span_size.string = size
+ new_div.append(new_span_size)
- filetype_string = "📂"
- if (target_path / safe_child_path).resolve().is_file():
- filetype_string = "📄"
+ soup.select_one("#list-container").append(new_btn)
- content += f"<a href='{href_path}'>{filetype_string} {safe_child_path}</a><br>"
- return fastapi.responses.Response(content=content, media_type="text/html", status_code=200)
+def format_size(byte_size):
+ for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB']:
+ if byte_size < 1024.0:
+ return f"{round(byte_size, 1)} {unit}"
+ byte_size /= 1024.0
diff --git a/style.css b/style.css
index e69de29..f809e08 100644
--- a/style.css
+++ b/style.css
@@ -0,0 +1,183 @@
+@font-face {
+ font-family: "JetBrains Mono";
+ src: url("/src/fonts/JetBrainsMono-VariableFont_wght.ttf") format("truetype");
+ font-weight: 100 800;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "JetBrains Mono";
+ src: url("/src/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf") format("truetype");
+ font-weight: 100 800;
+ font-style: italic;
+}
+
+@font-face {
+ font-family: "Inter";
+ src: url("/src/fonts/Inter-VariableFont_opsz,wght.ttf") format("truetype");
+ font-weight: 100 900;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Inter";
+ src: url("/src/fonts/Inter-Italic-VariableFont_opsz,wght.ttf") format("truetype");
+ font-weight: 100 900;
+ font-style: italic;
+}
+
+:root {
+ --sidebar-size: 72px;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: "Inter";
+}
+
+.sidebar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: var(--sidebar-size);
+ border-right: black solid 2px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.sidebar-text {
+ text-orientation: sideways;
+ writing-mode: vertical-lr;
+ font-size: xx-large;
+ font-family: "JetBrains Mono";
+ margin-top: 30px;
+}
+
+.sidebar > a {
+ width: 70%;
+}
+
+.sidebar-img {
+ width: 100%;
+ margin-top: 20px;
+}
+
+.content {
+ position: fixed;
+ top: 0;
+ left: calc(var(--sidebar-size) + 2px);
+ right: 0;
+ bottom: 0;
+ overflow-y: auto;
+}
+
+section:first-of-type {
+ border-top: none;
+}
+
+section {
+ border-top: 2px solid black;
+ display: flex;
+ justify-content: center;
+}
+
+.div-sizing {
+ max-width: 1200px;
+ width: 100%;
+ padding: 16px;
+}
+
+.list-container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.list-container > a {
+ display: flex;
+ justify-content: center;
+ padding: 8px;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+ text-decoration: none;
+ color: #1A1A1A;
+}
+
+.list-container > a:hover {
+ background-color: rgba(0, 0, 0, 0.025);
+}
+
+.list-container > a:active {
+ background-color: rgba(0, 0, 0, 0.05);
+}
+
+.list-container > a:focus > div {
+ border: 2px solid #00A37B;
+}
+
+.list-container > a > div {
+ max-width: 1200px;
+ width: 100%;
+ border: 2px solid transparent;
+ display: flex;
+ flex-direction: row;
+}
+
+.list-container > a > div > span {
+ flex: 1 1;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ text-wrap: nowrap;
+ min-width: 0;
+}
+
+.list-container > a > div > .list-metadata {
+ max-width: 12ch;
+ font-family: "JetBrains Mono";
+}
+
+.list-container > a > div > .list-date {
+ max-width: 16ch;
+}
+
+#breadcrumb-container {
+ color: rgba(0, 0, 0, 0.5);
+}
+
+#breadcrumb-container > span {
+ margin-inline: .5ch;
+}
+
+#directory-container {
+ display: flex;
+ flex-direction: column;
+ gap: .2rem;
+}
+
+#directory-container > a {
+ display: block;
+ width: 100%;
+ padding: 8px;
+ background-color: rgba(0, 0, 0, 0.05);
+ text-decoration: none;
+ color: black;
+}
+
+@media only screen and (max-width: 600px) {
+ :root {
+ --sidebar-size: 51px;
+ }
+
+ .sidebar-text {
+ font-size: larger;
+ margin-top: 20px;
+ }
+
+ .list-date {
+ display: none;
+ }
+}