batman
This commit is contained in:
commit
42f1d1012f
18 changed files with 399 additions and 0 deletions
22
app/__init__.py
Normal file
22
app/__init__.py
Normal 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
|
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/config.cpython-312.pyc
Normal file
BIN
app/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
7
app/config.py
Normal file
7
app/config.py
Normal 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
0
app/routes/__init__.py
Normal file
BIN
app/routes/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/routes/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/main.cpython-312.pyc
Normal file
BIN
app/routes/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
47
app/routes/main.py
Normal file
47
app/routes/main.py
Normal 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
91
app/templates/index.html
Normal 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
39
app/templates/upload.html
Normal 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
69
app/templates/viewer.html
Normal 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
0
app/utils/__init__.py
Normal file
BIN
app/utils/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/utils/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/utils/__pycache__/file_handler.cpython-312.pyc
Normal file
BIN
app/utils/__pycache__/file_handler.cpython-312.pyc
Normal file
Binary file not shown.
67
app/utils/file_handler.py
Normal file
67
app/utils/file_handler.py
Normal 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
7
run.py
Normal 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
44
uploads/docker.md
Normal 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
6
uploads/uri_map.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"docker": {
|
||||||
|
"filename": "docker.md",
|
||||||
|
"path": "/home/pika/projects/flask/markdownViewer/uploads/docker.md"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue