Initial commit
This commit is contained in:
commit
a5c94f728a
|
@ -0,0 +1,22 @@
|
|||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
.bak*
|
||||
.cache/
|
||||
|
||||
# --- Python ---
|
||||
__pycache__/
|
||||
venv/
|
||||
# --------------
|
||||
|
||||
# --- JavaScript ---
|
||||
node_modules
|
||||
bun.lockb
|
||||
# ------------------
|
||||
|
||||
# --- Local env ---
|
||||
data/
|
||||
config.toml
|
||||
# -----------------
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from src import app
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
venv/bin/python -m flask run --debug -h 192.168.5.3
|
|
@ -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
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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 %}
|
Loading…
Reference in New Issue