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