diff options
| -rw-r--r-- | .DS_Store | bin | 0 -> 6148 bytes | |||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | __pycache__/main.cpython-314.pyc | bin | 2884 -> 7838 bytes | |||
| -rw-r--r-- | favicon.ico | bin | 0 -> 15406 bytes | |||
| -rw-r--r-- | fonts/.DS_Store | bin | 0 -> 6148 bytes | |||
| -rw-r--r-- | fonts/Inter-Italic-VariableFont_opsz,wght.ttf | bin | 0 -> 904532 bytes | |||
| -rw-r--r-- | fonts/Inter-VariableFont_opsz,wght.ttf | bin | 0 -> 874708 bytes | |||
| -rw-r--r-- | fonts/JetBrainsMono-Italic-VariableFont_wght.ttf | bin | 0 -> 191988 bytes | |||
| -rw-r--r-- | fonts/JetBrainsMono-VariableFont_wght.ttf | bin | 0 -> 187860 bytes | |||
| -rw-r--r-- | icon.png | bin | 0 -> 118253 bytes | |||
| -rw-r--r-- | index.html | 14 | ||||
| -rw-r--r-- | main.py | 128 | ||||
| -rw-r--r-- | style.css | 183 |
13 files changed, 305 insertions, 21 deletions
diff --git a/.DS_Store b/.DS_Store Binary files differnew file mode 100644 index 0000000..0c32a4c --- /dev/null +++ b/.DS_Store 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 Binary files differindex 933130a..e6fb69a 100644 --- a/__pycache__/main.cpython-314.pyc +++ b/__pycache__/main.cpython-314.pyc diff --git a/favicon.ico b/favicon.ico Binary files differnew file mode 100644 index 0000000..c131a30 --- /dev/null +++ b/favicon.ico diff --git a/fonts/.DS_Store b/fonts/.DS_Store Binary files differnew file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/fonts/.DS_Store diff --git a/fonts/Inter-Italic-VariableFont_opsz,wght.ttf b/fonts/Inter-Italic-VariableFont_opsz,wght.ttf Binary files differnew file mode 100644 index 0000000..43ed4f5 --- /dev/null +++ b/fonts/Inter-Italic-VariableFont_opsz,wght.ttf diff --git a/fonts/Inter-VariableFont_opsz,wght.ttf b/fonts/Inter-VariableFont_opsz,wght.ttf Binary files differnew file mode 100644 index 0000000..e31b51e --- /dev/null +++ b/fonts/Inter-VariableFont_opsz,wght.ttf diff --git a/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf b/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf Binary files differnew file mode 100644 index 0000000..914e323 --- /dev/null +++ b/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf diff --git a/fonts/JetBrainsMono-VariableFont_wght.ttf b/fonts/JetBrainsMono-VariableFont_wght.ttf Binary files differnew file mode 100644 index 0000000..d73994a --- /dev/null +++ b/fonts/JetBrainsMono-VariableFont_wght.ttf diff --git a/icon.png b/icon.png Binary files differnew file mode 100644 index 0000000..a34719b --- /dev/null +++ b/icon.png @@ -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> @@ -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 @@ -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; + } +} |
