Hi,
wenn jemand Webspace hat und schnell was ohne Datenbank kontinuierlich bloggen oder dokumentieren muss, kann gern mein Flatfile nutzen. Die ".html" entsprechend anpassen und einfach hochladen. Per FTP oder SSH eine txt-Datei "beitrag1.txt", "beitrag2.txt" etc. im selben Pfad erzeugen und loslegen. Lädt man die .html ohne Beitrag, wird erklärt, wie man Beträge erzeugt und formatiert.
Es ist html mit javascript.
Ich hab das "Ding" damals als Notizblog gebaut, weil ich mir nicht alles merken kann, wenn ich was nicht so häufig brauche. ![]()
Einfach als html-file speichern und aufrufen:
HTML
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOKU</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #e0e0e0;
background: #121212;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
}
/* Header */
header {
text-align: center;
margin-bottom: 50px;
}
h1 {
color: #64b5f6;
margin-bottom: 15px;
font-size: 2.5rem;
font-weight: 300;
}
.subtitle {
color: #90a4ae;
font-size: 1.1rem;
font-weight: 300;
}
/* Blog Posts */
.blog-post {
background: #1e1e1e;
border-radius: 12px;
padding: 30px;
margin-bottom: 35px;
border: 1px solid #2d2d2d;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.blog-post:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.post-image {
object-fit: cover;
margin: 0 auto 25px auto;
display: block;
}
.post-image-placeholder {
width: 100%;
max-width: 600px;
height: 300px;
background: linear-gradient(135deg, #2d2d2d 0%, #3d3d3d 100%);
border-radius: 8px;
margin: 0 auto 25px auto;
display: flex;
align-items: center;
justify-content: center;
color: #64b5f6;
font-size: 1.1rem;
border: 2px dashed #64b5f6;
opacity: 0.7;
}
.post-title {
color: #bb86fc;
margin-bottom: 20px;
font-size: 1.6rem;
font-weight: 400;
line-height: 1.3;
}
.post-date {
color: #64b5f6;
font-size: 0.9rem;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
}
.post-date::before {
content: "📅";
font-size: 0.8em;
}
.post-content {
line-height: 1.7;
color: #e0e0e0;
}
.post-content p {
margin-bottom: 20px;
white-space: pre-line;
}
/* Überschriften im Beitrag */
.post-content h2 {
color: #bb86fc;
font-size: 1.4rem;
font-weight: 600;
margin: 25px 0 15px 0;
padding-bottom: 8px;
border-bottom: 2px solid #64b5f6;
}
.post-content h3 {
color: #64b5f6;
font-size: 1.2rem;
font-weight: 600;
margin: 20px 0 12px 0;
}
/* Code Blocks */
.code-block {
background: #0d1117;
color: #e6edf3;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
overflow-x: auto;
font-family: 'Cascadia Code', 'Fira Code', 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.5;
white-space: pre;
border: 1px solid #30363d;
position: relative;
}
.code-block::before {
content: "{}";
position: absolute;
top: 10px;
right: 15px;
color: #64b5f6;
font-size: 0.8rem;
opacity: 0.7;
}
.code-block code {
display: block;
}
.inline-code {
background: #2d2d2d;
color: #bb86fc;
padding: 3px 8px;
border-radius: 4px;
font-family: 'Cascadia Code', 'Fira Code', 'Courier New', monospace;
font-size: 0.85em;
border: 1px solid #404040;
}
/* Status Messages */
.info {
background: #1a237e;
color: #8c9eff;
padding: 20px;
border-radius: 8px;
margin-bottom: 25px;
border-left: 4px solid #536dfe;
}
.error {
background: #4a0000;
color: #ff6e6e;
padding: 20px;
border-radius: 8px;
margin-bottom: 25px;
border-left: 4px solid #ff5252;
}
.success {
background: #1b4332;
color: #80ffb2;
padding: 20px;
border-radius: 8px;
margin-bottom: 25px;
border-left: 4px solid #4caf50;
}
/* Loading Animation */
.loading {
text-align: center;
padding: 40px;
color: #90a4ae;
}
.spinner {
border: 3px solid #2d2d2d;
border-left-color: #64b5f6;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Formatting Help */
.formatting-help {
background: #1a1a1a;
border-left: 4px solid #bb86fc;
padding: 25px;
margin: 25px 0;
border-radius: 0 8px 8px 0;
}
.formatting-help h4 {
margin-bottom: 15px;
color: #bb86fc;
font-weight: 500;
}
.formatting-help ul {
padding-left: 20px;
}
.formatting-help li {
margin-bottom: 8px;
color: #b0b0b0;
}
/* Footer */
footer {
text-align: center;
margin-top: 60px;
color: #757575;
font-size: 0.9rem;
}
/* Links */
a {
color: #64b5f6;
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: #bb86fc;
text-decoration: underline;
}
/* Blockquotes */
blockquote {
border-left: 4px solid #64b5f6;
padding-left: 20px;
margin: 20px 0;
color: #b0b0b0;inux Blog
font-style: italic;
}
/* Lists */
.post-content ul, .post-content ol {
margin: 15px 0;
padding-left: 30px;
}
.post-content li {
margin-bottom: 8px;
}
/* Responsive */
@media (max-width: 768px) {
body {
padding: 15px;
}
.blog-post {
padding: 20px;
margin-bottom: 25px;
}
.post-image {
height: 200px;
}
.post-image-placeholder {
height: 200px;
}
h1 {
font-size: 2rem;
}
.post-title {
font-size: 1.4rem;
}
.post-content h2 {
font-size: 1.3rem;
}
.post-content h3 {
font-size: 1.1rem;
}
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1e1e1e;
}
::-webkit-scrollbar-thumb {
background: #64b5f6;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #bb86fc;
}
</style>
</head>
<body>
<header>
<h1>MEIN PROJEKT</h1>
<p class="subtitle">DOKUMENTATION</p>
</header>
<main id="blogContent">
<div class="loading">
<div class="spinner"></div>
<p>Lade Blog-Beiträge...</p>
</div>
</main>
<footer>
<p>Fast & Flat by Nibbler</p>
</footer>
<script>
// Liste der Textdateien die als Blog-Beiträge geladen werden sollen
const blogFiles = [
'beitrag1.txt',
'beitrag2.txt',
'beitrag3.txt'
];
// Hauptfunktion zum Laden der Blog-Beiträge
async function loadBlogPosts() {
const blogContent = document.getElementById('blogContent');
try {
blogContent.innerHTML = '';
let loadedPosts = 0;
// Lade jeden Blog-Beitrag
for (const file of blogFiles) {
try {
const response = await fetch(file);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const text = await response.text();
const post = parseBlogPost(text, file);
blogContent.appendChild(await createPostElement(post));
loadedPosts++;
} catch (error) {
console.warn(`Konnte ${file} nicht laden:`, error.message);
// Keine Fehlermeldung anzeigen, einfach überspringen
}
}
// Wenn keine Beiträge geladen werden konnten
if (loadedPosts === 0) {
showExampleContent();
}
} catch (error) {
console.error('Fehler beim Laden der Blog-Beiträge:', error);
showExampleContent();
}
}
// Beispielinhalte anzeigen falls keine Dateien gefunden wurden
function showExampleContent() {
const blogContent = document.getElementById('blogContent');
const examplePosts = [
{
title: "Willkommen in Ihrem Dark Blog",
date: "01.01.2024",
image: "beispiel-bild.jpg",
content: `Dies ist ein Beispiel-Beitrag. Er wird angezeigt, weil keine Textdateien gefunden wurden.
## Überschrift Ebene 2
Dies ist eine Überschrift der Ebene 2. Sie wird größer und fett dargestellt.
### Überschrift Ebene 3
Dies ist eine kleinere Überschrift der Ebene 3.
So erstellen Sie Ihren ersten Beitrag:
1. Erstellen Sie eine Textdatei namens "beitrag1.txt"
2. Fügen Sie optional einen Bildpfad in der dritten Zeile hinzu
3. Platzieren Sie sie im selben Ordner wie diese HTML-Datei
4. Strukturieren Sie den Inhalt wie folgt:
\`\`\`
Titel Ihres Beitrags
TT.MM.JJJJ
bild.jpg
Ihr Blog-Inhalt hier...
## Überschrift Ebene 2
Verwenden Sie ## für Überschriften Ebene 2
### Überschrift Ebene 3
Verwenden Sie ### für Überschriften Ebene 3
\`\`\`
Viel Erfolg beim Bloggen! `,
filename: "beispiel.txt"
}
];
blogContent.innerHTML = '';
examplePosts.forEach(async post => {
blogContent.appendChild(await createPostElement(post));
});
// Formatierungshilfe anzeigen
const helpSection = document.createElement('div');
helpSection.className = 'formatting-help';
helpSection.innerHTML = `
<h4>📝 So erstellen Sie Blog-Beiträge:</h4>
<ul>
<li><strong>Dateinamen:</strong> beitrag1.txt, beitrag2.txt, etc.</li>
<li><strong>Zeile 1:</strong> Titel des Beitrags</li>
<li><strong>Zeile 2:</strong> Datum im Format TT.MM.JJJJ</li>
<li><strong>Zeile 3:</strong> Bilddatei (optional, z.B. bild.jpg)</li>
<li><strong>Ab Zeile 4:</strong> Ihr Inhalt mit Formatierung</li>
<li><strong>Überschriften:</strong> <code class="inline-code">## Überschrift</code> für Ebene 2, <code class="inline-code">### Überschrift</code> für Ebene 3</li>
<li><strong>Code-Blöcke:</strong> Umgeben mit \`\`\` am Anfang und Ende</li>
<li><strong>Inline-Code:</strong> Verwenden Sie \`einen Backtick\`</li>
</ul>
<p style="margin-top: 10px; color: #64b5f6;">
💡 Tipp: Laden Sie die Text- und Bilddateien per FTP in denselben Ordner wie diese HTML-Datei.
</p>
`;
blogContent.appendChild(helpSection);
}
// Funktion zum Parsen des Blog-Beitrags mit Bildunterstützung
function parseBlogPost(text, filename) {
const lines = text.split('\n');
let title = 'Ohne Titel';
let date = new Date().toLocaleDateString('de-DE');
let image = null;
let content = text;
if (lines.length >= 1) {
title = lines[0].replace(/^#\s*/, '').trim();
if (lines.length >= 2) {
const dateMatch = lines[1].match(/(\d{1,2}\.\d{1,2}\.\d{4})/);
if (dateMatch) {
date = dateMatch[0];
// Prüfe ob dritte Zeile ein Bildpfad ist
if (lines.length >= 3 && lines[2].trim() !== '') {
const potentialImage = lines[2].trim();
// Einfache Prüfung auf Bilddatei-Endungen
if (/(\.jpg|\.jpeg|\.png|\.gif|\.webp|\.svg)$/i.test(potentialImage)) {
image = potentialImage;
content = lines.slice(3).join('\n');
} else {
content = lines.slice(2).join('\n');
}
} else {
content = lines.slice(2).join('\n');
}
} else {
// Wenn zweite Zeile kein Datum, prüfe ob es ein Bildpfad ist
const potentialImage = lines[1].trim();
if (/(\.jpg|\.jpeg|\.png|\.gif|\.webp|\.svg)$/i.test(potentialImage)) {
image = potentialImage;
content = lines.slice(2).join('\n');
} else {
content = lines.slice(1).join('\n');
}
}
} else {
content = '';
}
}
return {
title,
date,
image,
content,
filename
};
}
// Funktion zum Überprüfen ob ein Bild existiert
async function checkImageExists(imageUrl) {
if (!imageUrl) return false;
try {
const response = await fetch(imageUrl, { method: 'HEAD' });
return response.ok;
} catch (error) {
return false;
}
}
// Funktion zum Erstellen eines Blog-Beitrag-Elements mit Bild
async function createPostElement(post) {
const postElement = document.createElement('article');
postElement.className = 'blog-post';
let imageHtml = '';
if (post.image) {
const imageExists = await checkImageExists(post.image);
if (imageExists) {
imageHtml = `<img src="${post.image}" alt="${post.title}" class="post-image" loading="lazy">`;
} else {
imageHtml = `
<div class="post-image-placeholder">
<div>
<div>📷 Bild nicht gefunden</div>
<small style="font-size: 0.8rem; margin-top: 5px; display: block;">${post.image}</small>
</div>
</div>
`;
}
} else {
imageHtml = `
<div class="post-image-placeholder">
<div>
<div>✨ Kein Bild</div>
<small style="font-size: 0.8rem; margin-top: 5px; display: block;">Optional: Bildpfad in Zeile 3</small>
</div>
</div>
`;
}
postElement.innerHTML = `
${imageHtml}
<h2 class="post-title">${escapeHtml(post.title)}</h2>
<div class="post-date">${post.date}</div>
<div class="post-content">${formatContent(post.content)}</div>
`;
return postElement;
}
// Verbesserte Funktion zum Formatieren des Inhalts mit Überschriften
function formatContent(text) {
if (!text.trim()) return '<p>Kein Inhalt verfügbar.</p>';
// Code-Blöcke temporär ersetzen
const codeBlocks = [];
let codeBlockIndex = 0;
let processedText = text.replace(/```([\s\S]*?)```/g, (match, code) => {
const placeholder = `___CODE_BLOCK_${codeBlockIndex}___`;
codeBlocks.push(code);
codeBlockIndex++;
return placeholder;
});
// Inline-Code ersetzen
const inlineCodes = [];
let inlineCodeIndex = 0;
processedText = processedText.replace(/`([^`]+)`/g, (match, code) => {
const placeholder = `___INLINE_CODE_${inlineCodeIndex}___`;
inlineCodes.push(code);
inlineCodeIndex++;
return placeholder;
});
// Überschriften ersetzen
processedText = processedText.replace(/^## (.*$)/gim, '<h2>$1</h2>');
processedText = processedText.replace(/^### (.*$)/gim, '<h3>$1</h3>');
// Absätze verarbeiten
const paragraphs = processedText.split(/\n\s*\n/);
let htmlContent = paragraphs.map(paragraph => {
if (paragraph.trim()) {
let paragraphHtml = paragraph.trim();
// Wenn es keine Überschrift ist, dann als normalen Absatz behandeln
if (!paragraphHtml.startsWith('<h2>') && !paragraphHtml.startsWith('<h3>')) {
paragraphHtml = escapeHtml(paragraphHtml);
paragraphHtml = paragraphHtml.replace(/\n/g, '<br>');
paragraphHtml = `<p>${paragraphHtml}</p>`;
}
// Code-Blöcke wieder einfügen
codeBlocks.forEach((code, index) => {
const placeholder = `___CODE_BLOCK_${index}___`;
if (paragraphHtml.includes(placeholder)) {
const codeHtml = `<div class="code-block"><code>${escapeHtml(code.trim())}</code></div>`;
paragraphHtml = paragraphHtml.replace(placeholder, codeHtml);
}
});
// Inline-Code wieder einfügen
inlineCodes.forEach((code, index) => {
const placeholder = `___INLINE_CODE_${index}___`;
if (paragraphHtml.includes(placeholder)) {
const inlineCodeHtml = `<code class="inline-code">${escapeHtml(code)}</code>`;
paragraphHtml = paragraphHtml.replace(placeholder, inlineCodeHtml);
}
});
return paragraphHtml;
}
return '';
}).join('');
return htmlContent;
}
// Hilfsfunktion zum Escapen von HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Starte das Laden der Blog-Beiträge
document.addEventListener('DOMContentLoaded', loadBlogPosts);
</script>
</body>
</html>
Display More