Initial commit

This commit is contained in:
Dendy 2024-02-24 06:46:16 +01:00
commit a5c94f728a
9 changed files with 367 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
.DS_Store
.idea
*.log
tmp/
.bak*
.cache/
# --- Python ---
__pycache__/
venv/
# --------------
# --- JavaScript ---
node_modules
bun.lockb
# ------------------
# --- Local env ---
data/
config.toml
# -----------------

4
app.py Executable file
View File

@ -0,0 +1,4 @@
from src import app
if __name__ == "__main__":
app.run()

3
debug.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
venv/bin/python -m flask run --debug -h 192.168.5.3

128
src/__init__.py Normal file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env python3
import traceback
import os
from flask import Flask, render_template, request
from pygments import formatters, highlight, lexers
import markdown
from src import (
# tango_controller,
utils,
config,
)
app = Flask(
__name__,
template_folder="../templates",
static_folder="../static",
)
# app.register_blueprint(tango_controller.bp)
# Edit
# Create file
def get_tree() -> dict:
data_path = config.get('data_path')
ret = {}
for root, _, files in os.walk(data_path):
current = ret
relpath = os.path.relpath(root, data_path)
breadcrumbs = relpath.split(os.path.sep)
if breadcrumbs == ['.']:
breadcrumbs = []
# Move the current to that folder and create
# the intermediate steps if they don't exist
for folder in breadcrumbs:
if folder not in current or current[folder] is None:
current[folder] = {}
current = current[folder]
print(breadcrumbs)
for file in files:
filename, _ = os.path.splitext(file)
current[filename] = None
return ret
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods=['POST', 'GET'])
def index(path):
internal_path = os.path.join(config.get('data_path'), path + '.md')
path = '/' + path
# Checks ###################################################
if '..' in path:
return 'Path cannot contain double dots, i.e. "..".'
# If the user sent input, save it ##########################
if path == '/':
return (
render_template(
"main.html",
path=path,
item_list=get_tree(),
markdown=markdown.markdown,
content='Welcome',
edit=False,
),
200,
)
raw_markdown = request.form.get('text', None)
if raw_markdown is not None:
with open(internal_path, 'w') as file:
file.write(str(raw_markdown))
# Get the document contents ################################
is_edit = 'edit' in request.args and path != '/'
if os.path.exists(internal_path):
with open(internal_path) as file:
raw_markdown = file.read()
elif is_edit:
raw_markdown = f'# {path}\n\nChangeme'
else:
return (
render_template(
"error.html",
code=404,
msg=f'The path "{path}" does not exist.',
),
404,
)
# Actual serving ###########################################
return (
render_template(
"main.html",
path=path,
item_list=get_tree(),
markdown=markdown.markdown,
content=raw_markdown,
edit=is_edit,
),
200,
)
@ app.errorhandler(Exception)
def internal_error(e):
tb_text = "".join(traceback.format_exc())
lexer = lexers.get_lexer_by_name("pytb", stripall=True)
formatter = formatters.get_formatter_by_name("terminal")
tb_colored = highlight(tb_text, lexer, formatter)
print(tb_colored)
return utils.ERR_500

8
src/config.py Normal file
View File

@ -0,0 +1,8 @@
import tomllib
with open("config.toml", "rb") as f:
config = tomllib.load(f)
def get(q: str):
return config.get(q, None)

100
static/style/main.css Normal file
View File

@ -0,0 +1,100 @@
html {
min-height: 100%;
}
body {
padding: 20px;
background-color: #191919;
color: white;
font-family: monospace;
margin-bottom: 100px;
}
h1, h2, h3, h4, h5, h6 {
color: orange;
}
a {
text-decoration: none;
color: orange;
}
a:hover {
color: #191919;
background-color: orange;
}
input {
border: 1px solid black;
background-color: white;
}
footer {
background-color: black;
position: fixed;
left: 0;
bottom: 0;
width: calc(100vw - 20px);
padding: 10px;
text-align: center;
overflow: hidden;
}
ul {
padding-left: 20px;
padding-bottom: 0px;
list-style-type: none;
}
main {
display: flex;
gap: 20px;
min-height: 80vh;
}
nav {
padding-right: 10px;
display: flex;
flex-direction: column;
border-right: 1px solid orange;
width: 200px;
}
article {
flex-grow: 1;
}
pre {
background-color: #000;
color: #DDD;
padding: 10px;
padding-left: 20px;
}
.editor-toolbar {
border-top: 0px solid #444;
border-left: 0px solid #444;
border-right: 0px solid #444;
border-radius: 0px;
}
.EasyMDEContainer .CodeMirror {
border: 1px solid #444;
}
.editor-toolbar .fa {
color: white;
}
.editor-toolbar button.active,
.editor-toolbar button:hover {
background-color: black;
}
.CodeMirror {
color: white;
background-color: black;
}
.CodeMirror-cursor {
border-left: 2px solid gray;
}

25
templates/base.html Normal file
View File

@ -0,0 +1,25 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}{% endblock %} - {{ config.get("name") }}</title>
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
{# <script src="{{ url_for('static', filename='lib/htmx.min.js') }}"></script> #}
<link
rel="stylesheet"
href="{{ url_for('static', filename='style/main.css') }}"
/>
<link rel="icon" href="{{ config.get("favicon") }}">
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

9
templates/error.html Normal file
View File

@ -0,0 +1,9 @@
{% extends 'base.html' %}
{% block title %}Error {{ code }}{% endblock %}
{% block body %}
<h1>Error {{ code }}</h1>
<p>{{ msg }}</p>
<a href="/">Return to Index</a>
{% endblock %}

68
templates/main.html Normal file
View File

@ -0,0 +1,68 @@
{% extends 'base.html' %}
{% block title %}{{ path }}{% endblock %}
{% macro render_tree(tree, root='/') %}
<ul>
{% for key, value in tree.items() %}
<li><a href="{{ root }}{{ key }}">{{ key }}</a>
{% if value is mapping %}
{{ render_tree(value, root + key + '/') }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
{% block body %}
<main>
<nav>
{{ render_tree(item_list) }}
</nav>
<article>
{% if not edit %}
{% autoescape false %}
{{ markdown(content, extensions=['extra']) }}
{% endautoescape %}
{% if path != '/' %}
<a href="{{ path }}?edit">Edit</a>
{% endif %}
{% else %}
<form method="POST" action="{{ path }}">
<textarea id="markdown-textarea" name="text">{{ content }}</textarea>
<input type="submit" value="Save"/>
<form>
<script>
const easyMDE = new EasyMDE({
element: document.getElementById('markdown-textarea'),
autosave: {
enabled: false,
uniqueId: "whatever-i-guess",
delay: 1000,
submit_delay: 5000,
timeFormat: {
locale: 'en-US',
format: {
//year: 'numeric',
//month: 'long',
//day: '2-digit',
hour: '2-digit',
minute: '2-digit',
},
},
text: "Autosaved: "
},
});
</script>
{% endif %}
</article>
</main>
{% set footer = config.get('foote') %}
{% if footer not in [None, False, ""] %}
<footer>{{ config.get('footer') }}</footer>
{% endif %}
{% endblock %}