This commit is contained in:
piecka 2025-03-21 11:19:20 +01:00
commit 42f1d1012f
18 changed files with 399 additions and 0 deletions

22
app/__init__.py Normal file
View file

@ -0,0 +1,22 @@
from flask import Flask
import os
from app.config import Config
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
# Ensure upload directory exists
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# Create empty URI map file if it doesn't exist
uri_map_path = os.path.join(app.config['UPLOAD_FOLDER'], 'uri_map.json')
if not os.path.exists(uri_map_path):
with open(uri_map_path, 'w') as f:
f.write('{}')
# Register blueprints
from app.routes.main import main_bp
app.register_blueprint(main_bp)
return app

Binary file not shown.

Binary file not shown.

7
app/config.py Normal file
View file

@ -0,0 +1,7 @@
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-please-change')
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'uploads')
DEBUG = True
MAX_CONTENT_LENGTH = 16 * 1024 * 1024

0
app/routes/__init__.py Normal file
View file

Binary file not shown.

Binary file not shown.

47
app/routes/main.py Normal file
View file

@ -0,0 +1,47 @@
from flask import Blueprint, render_template, request, current_app, redirect, url_for, abort, flash
import os
from app.utils.file_handler import save_file, read_markdown_file, get_file_by_uri, load_uri_map
main_bp = Blueprint('main', __name__)
@main_bp.route('/', methods=['GET'])
def index():
# List all available documents
uri_map = load_uri_map()
documents = [{'uri': uri, 'filename': info['filename']} for uri, info in uri_map.items()]
return render_template('index.html', documents=documents)
@main_bp.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No file selected')
return redirect(request.url)
if file and file.filename.endswith('.md'):
custom_uri = request.form.get('custom_uri', '').strip()
uri, filename = save_file(file, custom_uri if custom_uri else None)
return redirect(url_for('main.view_file', uri=uri))
else:
flash('Only markdown files are allowed')
return render_template('upload.html')
@main_bp.route('/view/<uri>')
def view_file(uri):
file_info = get_file_by_uri(uri)
if not file_info:
abort(404)
html_content = read_markdown_file(file_info['path'])
return render_template('viewer.html',
filename=file_info['filename'],
content=html_content,
uri=uri)

91
app/templates/index.html Normal file
View file

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<title>Markdown Viewer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/digitallytailored/classless@latest/classless.min.css">
<style>
.file-list {
margin: 2em 0;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75em;
border-bottom: 1px solid #eee;
}
.file-item:hover {
background-color: #f9f9f9;
}
.file-link {
font-weight: bold;
text-decoration: none;
}
.file-uri {
color: #666;
font-size: 0.9em;
}
.copy-button {
background: #f0f0f0;
border: 1px solid #ddd;
padding: 0.25em 0.5em;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
}
.empty-state {
text-align: center;
padding: 3em;
background-color: #f9f9f9;
border-radius: 4px;
margin: 2em 0;
}
</style>
</head>
<body>
<h1>Markdown Viewer</h1>
<div class="actions">
<a href="{{ url_for('main.upload_file') }}" class="button">Upload New Document</a>
</div>
{% if documents %}
<div class="file-list">
<h2>Available Documents</h2>
{% for doc in documents %}
<div class="file-item">
<div>
<a href="{{ url_for('main.view_file', uri=doc.uri) }}" class="file-link">
{{ doc.filename }}
</a>
<span class="file-uri">URI: {{ doc.uri }}</span>
</div>
<div>
<a href="{{ url_for('main.view_file', uri=doc.uri) }}" class="button">View</a>
<button class="copy-button" onclick="copyToClipboard('{{ url_for('main.view_file', uri=doc.uri, _external=True) }}')">
Copy Link
</button>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<h3>No documents available</h3>
<p>Upload a markdown file to get started!</p>
<a href="{{ url_for('main.upload_file') }}" class="button">Upload Now</a>
</div>
{% endif %}
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
alert('Link copied to clipboard!');
}, function() {
alert('Failed to copy link');
});
}
</script>
</body>
</html>

39
app/templates/upload.html Normal file
View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Upload Markdown File</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/digitallytailored/classless@latest/classless.min.css">
</head>
<body>
<h1>Upload Markdown File</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="messages">
{% for message in messages %}
<p class="alert">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="post" enctype="multipart/form-data">
<div>
<label for="file">Select a markdown file:</label>
<input type="file" name="file" id="file" accept=".md">
</div>
<div>
<label for="custom_uri">Custom URI (optional):</label>
<input type="text" name="custom_uri" id="custom_uri" placeholder="e.g., my-document">
<small>Leave blank for an auto-generated URI</small>
</div>
<div>
<input type="submit" value="Upload">
</div>
</form>
<a href="{{ url_for('main.index') }}">Back to Home</a>
</body>
</html>

69
app/templates/viewer.html Normal file
View file

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ filename }} - Markdown Viewer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/digitallytailored/classless@latest/classless.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--<link rel="stylesheet" href="github-markdown.css">-->
<style>
pre {
background-color: #f5f5f5;
padding: 1em;
border-radius: 4px;
overflow-x: auto;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
table, th, td {
border: 1px solid #ddd;
}
th, td {
padding: 0.5em;
text-align: left;
}
.document-info {
margin-bottom: 2em;
padding: 1em;
background-color: #f9f9f9;
border-radius: 4px;
}
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
} </style>
</head>
<body>
<div class="document-info">
<h1>{{ filename }}</h1>
<p>
<strong>Permanent Link:</strong>
<code>{{ url_for('main.view_file', uri=uri, _external=True) }}</code>
</p>
</div>
<div class="content">
{{ content|safe }}
</div>
<div class="actions">
<a href="{{ url_for('main.upload_file') }}">Upload Another Document</a> |
<a href="{{ url_for('main.index') }}">Home</a>
</div>
</body>
</html>

0
app/utils/__init__.py Normal file
View file

Binary file not shown.

Binary file not shown.

67
app/utils/file_handler.py Normal file
View file

@ -0,0 +1,67 @@
import os
import markdown
import secrets
from flask import current_app
from werkzeug.utils import secure_filename
import json
def get_uri_map_path():
"""Get the path to the URI mapping file"""
return os.path.join(current_app.config['UPLOAD_FOLDER'], 'uri_map.json')
def load_uri_map():
"""Load the URI mapping from file"""
map_path = get_uri_map_path()
if os.path.exists(map_path):
with open(map_path, 'r') as f:
return json.load(f)
return {}
def save_uri_map(uri_map):
"""Save the URI mapping to file"""
with open(get_uri_map_path(), 'w') as f:
json.dump(uri_map, f, indent=2)
def generate_unique_uri():
"""Generate a unique URI for a file"""
uri_map = load_uri_map()
while True:
uri = secrets.token_urlsafe(6) # Generate a short random string
if uri not in uri_map:
return uri
def save_file(file, custom_uri=None):
"""Save the uploaded file to the upload folder and assign a URI"""
# Secure the filename first
filename = secure_filename(file.filename)
# Generate or use custom URI
uri = custom_uri if custom_uri else generate_unique_uri()
# Save the file with its original name
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
# Update the URI map
uri_map = load_uri_map()
uri_map[uri] = {
'filename': filename,
'path': filepath
}
save_uri_map(uri_map)
return uri, filename
def get_file_by_uri(uri):
"""Get file info by URI"""
uri_map = load_uri_map()
if uri in uri_map:
return uri_map[uri]
return None
def read_markdown_file(filepath):
"""Read and convert markdown file to HTML"""
with open(filepath, 'r', encoding='utf-8') as f:
md_content = f.read()
html_content = markdown.markdown(md_content, extensions=['tables', 'fenced_code'])
return html_content

7
run.py Normal file
View file

@ -0,0 +1,7 @@
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)

44
uploads/docker.md Normal file
View file

@ -0,0 +1,44 @@
# Nice containers for beginners
#### **Quellen**
- [selfh.st](https://selfh.st/apps/) <- viele opensource projekte (einige davon dockerized)
## **Reverseproxies:**
> Hier wird zumindest einer benötigt, um SSL-Verschlüsselung und HTTPS auf den Websites zu aktivieren
> Caddy ist zwar an sich einfacher, weil es nur eine einzige config datei hat, aber zum lernen würde ich den
> nginxproxymanager empfehlen, da er eine kleine webui hat und man das konzept so gut lernen und verstehen kann
- [Nginx Reverseproxy-Manager](https://nginxproxymanager.com/guide/)
- [Caddy](https://hub.docker.com/_/caddy/)
## **Dashboards:**
> Pick whatever you like. Homepage wird in einer config datei (yml) konfiguriert. Daher würde ich Dashy oder Flame
> eher für den Anfang empfehlen
- [Dashy](https://github.com/Lissy93/dashy)
- [Flame](https://github.com/pawelmalak/flame)
- [Homepage](https://gethomepage.dev/)
## **Document-Management:**
> filebrowser und mydrive sind zwei sehr einfache file share services, die mit einer schönen kleinen weboberfläche kommen
> Nextcloud ist etwas mächtiger, zwar auch deutlich hübscher und mit Android/iOS app etc. Aber eben auch etwas komplexer. Ist aber machbar.
> PaperlessNGX ist ein Dokumentenspeichersystem, hauptsächlich für pdf dateien. Ich lasse mir dort täglich und regelmäßig aus meinen
> Mailpostfächern die .pdf dateien die als Rechnung deklariert sind automatisch in mein system einspielen. Automatisierungstechnisch ein Traum!
- [filebrowser](https://github.com/hurlenko/filebrowser-docker)
- [my-drive](https://github.com/subnub/myDrive)
- [nextcloud](https://hub.docker.com/_/nextcloud)
- [PaperlessNGX](https://docs.paperless-ngx.com/setup/#docker)
- [Immich](https://immich.app/docs/install/docker-compose) <- google photos clone, für zuhause! (Bessere Suchfunktion, Personenerkennung, Geodaten auf einer Map...)
## **Sonstige**
- [Bitwarden](https://github.com/dani-garcia/vaultwarden) <- das ist vaultwarden. Bitwarden + ohne zu Zahlen ^^
- [Excalidraw](https://hub.docker.com/r/excalidraw/excalidraw)
- [PDF-Tools](https://github.com/Stirling-Tools/Stirling-PDF)
- [IT-Tools](https://github.com/CorentinTh/it-tools)

6
uploads/uri_map.json Normal file
View file

@ -0,0 +1,6 @@
{
"docker": {
"filename": "docker.md",
"path": "/home/pika/projects/flask/markdownViewer/uploads/docker.md"
}
}