Content is user-generated and unverified.
<?php // ============================================================ // merk.php – Notizen mit Themen // Dateiformat topics.dat: ID$#$Name$#$deleted(0/1)\n // Dateiformat merk.dat: ID$#$TopicID$#$Text$#$deleted(0/1)[$#$deleted_at(unix)]\n // ============================================================ define('ITEMS_FILE', __DIR__ . '/merk.dat'); define('TOPICS_FILE', __DIR__ . '/topics.dat'); define('HISTORY_FILE', __DIR__ . '/items-history.dat'); // ── Hilfsfunktionen ───────────────────────────────────────── function generateId(): string { return str_pad((string)rand(1, 99999), 5, '0', STR_PAD_LEFT); } function readTopics(): array { if (!file_exists(TOPICS_FILE)) return []; $lines = file(TOPICS_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $topics = []; foreach ($lines as $line) { $parts = explode('$#$', $line); if (count($parts) === 3) { $topics[] = [ 'id' => $parts[0], 'name' => $parts[1], 'deleted' => $parts[2] === '1', ]; } } return $topics; } function writeTopics(array $topics): void { $lines = array_map(fn($t) => $t['id'] . '$#$' . $t['name'] . '$#$' . ($t['deleted'] ? '1' : '0'), $topics ); file_put_contents(TOPICS_FILE, implode("\n", $lines) . (count($lines) ? "\n" : '')); } function readItems(): array { if (!file_exists(ITEMS_FILE)) return []; $lines = file(ITEMS_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $items = []; foreach ($lines as $line) { $parts = explode('$#$', $line); if (count($parts) >= 4) { $items[] = [ 'id' => $parts[0], 'topic_id' => $parts[1], 'text' => $parts[2], 'deleted' => $parts[3] === '1', 'deleted_at' => isset($parts[4]) ? (int)$parts[4] : 0, ]; } } return $items; } function writeItems(array $items): void { $lines = array_map(fn($i) => $i['id'] . '$#$' . $i['topic_id'] . '$#$' . $i['text'] . '$#$' . ($i['deleted'] ? '1' : '0') . (!empty($i['deleted_at']) ? '$#$' . $i['deleted_at'] : ''), $items ); file_put_contents(ITEMS_FILE, implode("\n", $lines) . (count($lines) ? "\n" : '')); } // Historisierung: Eintrag in items-history.dat schreiben // Format: Datum$#$Typ(topic|item)$#$ID$#$TopicName$#$Text function writeHistory(string $type, string $id, string $topicName, string $text): void { $date = date('Y-m-d H:i:s'); $line = $date . '$#$' . $type . '$#$' . $id . '$#$' . $topicName . '$#$' . $text . "\n"; file_put_contents(HISTORY_FILE, $line, FILE_APPEND); } function autoDeleteEmptyTopics(): void { $topics = readTopics(); $items = readItems(); foreach ($topics as &$topic) { if ($topic['deleted']) continue; $activeItems = array_filter($items, fn($i) => $i['topic_id'] === $topic['id'] && !$i['deleted'] ); // Thema hat noch nie Items gehabt ODER alle gelöscht → soft-delete $anyItem = array_filter($items, fn($i) => $i['topic_id'] === $topic['id']); if (count($anyItem) > 0 && count($activeItems) === 0) { $topic['deleted'] = true; } } unset($topic); writeTopics($topics); } // ── Aktionen ──────────────────────────────────────────────── $action = $_POST['action'] ?? $_GET['action'] ?? ''; $topicId = $_POST['topic_id'] ?? $_GET['topic_id'] ?? ''; $view = $_GET['view'] ?? 'topics'; // topics | trash $redirect = 'merk.php'; switch ($action) { case 'add_topic': $name = trim($_POST['name'] ?? ''); if ($name !== '') { $topics = readTopics(); $topics[] = ['id' => generateId(), 'name' => htmlspecialchars($name, ENT_QUOTES), 'deleted' => false]; writeTopics($topics); } header("Location: $redirect"); exit; case 'delete_topic': // Nur löschen, wenn das Thema noch nie Items hatte (frisch angelegt) $topics = readTopics(); $items = readItems(); $anyItem = array_filter($items, fn($i) => $i['topic_id'] === $topicId); if (count($anyItem) === 0) { foreach ($topics as &$t) { if ($t['id'] === $topicId) { $t['deleted'] = true; break; } } unset($t); writeTopics($topics); } header("Location: $redirect"); exit; case 'restore_topic': $topics = readTopics(); foreach ($topics as &$t) { if ($t['id'] === $topicId) { $t['deleted'] = false; break; } } unset($t); // Items dieses Themas auch wiederherstellen $items = readItems(); foreach ($items as &$i) { if ($i['topic_id'] === $topicId) { $i['deleted'] = false; } } unset($i); writeItems($items); writeTopics($topics); header("Location: $redirect?view=trash"); exit; case 'purge_topic': // Thema + alle Items endgültig löschen → History schreiben $topics = readTopics(); $topicRec = array_values(array_filter($topics, fn($t) => $t['id'] === $topicId)); $tName = $topicRec[0]['name'] ?? $topicId; $topics = array_filter($topics, fn($t) => $t['id'] !== $topicId); writeTopics(array_values($topics)); $items = readItems(); $purged = array_filter($items, fn($i) => $i['topic_id'] === $topicId); foreach ($purged as $pi) { writeHistory('item', $pi['id'], $tName, $pi['text']); } writeHistory('topic', $topicId, $tName, '— Thema endgültig gelöscht —'); $items = array_filter($items, fn($i) => $i['topic_id'] !== $topicId); writeItems(array_values($items)); header("Location: $redirect?view=trash"); exit; case 'add_item': $text = trim($_POST['text'] ?? ''); if ($text !== '' && $topicId !== '') { $items = readItems(); $items[] = [ 'id' => generateId(), 'topic_id' => $topicId, 'text' => htmlspecialchars($text, ENT_QUOTES), 'deleted' => false, ]; writeItems($items); autoDeleteEmptyTopics(); } header("Location: $redirect?view=topic&topic_id=$topicId"); exit; case 'delete_item': $itemId = $_POST['item_id'] ?? $_GET['item_id'] ?? ''; $items = readItems(); foreach ($items as &$i) { if ($i['id'] === $itemId) { $i['deleted'] = true; $i['deleted_at'] = time(); break; } } unset($i); writeItems($items); autoDeleteEmptyTopics(); // Weiterleitung: zurück zum Thema oder Papierkorb $dest = ($view === 'trash') ? "?view=trash" : "?view=topic&topic_id=$topicId"; header("Location: $redirect$dest"); exit; case 'restore_item': $itemId = $_POST['item_id'] ?? $_GET['item_id'] ?? ''; $items = readItems(); foreach ($items as &$i) { if ($i['id'] === $itemId) { $i['deleted'] = false; $i['deleted_at'] = 0; break; } } unset($i); writeItems($items); header("Location: $redirect?view=trash"); exit; case 'purge_item': $itemId = $_POST['item_id'] ?? $_GET['item_id'] ?? ''; $items = readItems(); $purgedIt = array_values(array_filter($items, fn($i) => $i['id'] === $itemId)); if (!empty($purgedIt)) { $pi = $purgedIt[0]; $topics = readTopics(); $tRec = array_values(array_filter($topics, fn($t) => $t['id'] === $pi['topic_id'])); $tName = $tRec[0]['name'] ?? $pi['topic_id']; writeHistory('item', $pi['id'], $tName, $pi['text']); } $items = array_filter($items, fn($i) => $i['id'] !== $itemId); writeItems(array_values($items)); header("Location: $redirect?view=trash"); exit; } // ── Daten für Anzeige laden ────────────────────────────────── $allTopics = readTopics(); $allItems = readItems(); // Thema-Map für schnellen Zugriff $topicMap = []; foreach ($allTopics as $t) $topicMap[$t['id']] = $t; // Aktuelles Thema (Topic-View) $currentTopic = null; if ($view === 'topic' && $topicId !== '' && isset($topicMap[$topicId])) { $currentTopic = $topicMap[$topicId]; } // Linkify function linkify(string $text): string { return preg_replace( '/(https?:\/\/[^\s<>"\']+)/i', '<a href="$1" target="_blank" rel="noopener">$1</a>', htmlspecialchars_decode($text) ); } ?> <!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mein Gehirn</title> <style> :root { --bg: #f5f2eb; --card: #fffef9; --border: #d6cfc0; --accent: #3d6b4f; --accent2: #7a3d2e; --text: #2c2416; --muted: #8a7f70; --trash: #b84a2e; --shadow: 0 1px 4px rgba(0,0,0,.10); --radius: 6px; --font: 'Georgia', serif; --mono: 'Courier New', monospace; } * { box-sizing: border-box; margin: 0; padding: 0; } body { background: var(--bg); color: var(--text); font-family: var(--font); font-size: 16px; line-height: 1.6; min-height: 100vh; } /* ── Header ── */ header { background: var(--accent); color: #fff; padding: 12px 20px; display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 10; box-shadow: 0 2px 8px rgba(0,0,0,.18); } header h1 { font-size: 1.15rem; font-weight: normal; letter-spacing: .04em; flex: 1; } .header-badge { font-size: .75rem; background: rgba(255,255,255,.18); border-radius: 20px; padding: 2px 9px; font-family: var(--mono); } /* ── Nav-Tabs ── */ .tabs { display: flex; border-bottom: 2px solid var(--border); background: var(--card); padding: 0 16px; } .tabs a { display: inline-block; padding: 10px 18px; color: var(--muted); text-decoration: none; font-size: .9rem; border-bottom: 2px solid transparent; margin-bottom: -2px; transition: color .15s, border-color .15s; } .tabs a.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: bold; } .tabs a:hover:not(.active) { color: var(--text); } /* ── Layout ── */ .container { max-width: 620px; margin: 0 auto; padding: 20px 16px 60px; } /* ── Themen-Kacheln ── */ .topic-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 28px; } .topic-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; transition: box-shadow .15s, transform .1s; display: flex; flex-direction: column; } .topic-card:hover { box-shadow: 0 3px 10px rgba(0,0,0,.14); transform: translateY(-1px); } .topic-card a.topic-link { display: block; padding: 14px 14px 10px; text-decoration: none; color: var(--text); flex: 1; } .topic-name { font-size: 1rem; font-weight: bold; } .topic-count { font-size: .78rem; color: var(--text); margin-top: 3px; font-family: var(--mono); } .topic-card-footer { border-top: 1px solid var(--border); display: flex; justify-content: flex-end; padding: 4px 8px; } /* ── Topic-View Header ── */ .topic-header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; } .topic-header h2 { flex: 1; font-size: 1.25rem; } /* ── Items ── */ .item-list { list-style: none; margin-bottom: 24px; } .item-list li { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 10px 12px; margin-bottom: 8px; display: flex; align-items: flex-start; gap: 10px; transition: opacity .2s; } .item-list li.deleted { opacity: .55; } .item-text { flex: 1; word-break: break-word; } .item-text a { color: var(--accent); } /* ── Eingabefeld ── */ .add-form { display: flex; gap: 8px; margin-top: 8px; } .add-form input[type=text] { flex: 1; border: 1px solid var(--border); border-radius: var(--radius); padding: 9px 12px; font-family: var(--font); font-size: .95rem; background: var(--card); color: var(--text); outline: none; transition: border-color .15s; } .add-form input[type=text]:focus { border-color: var(--accent); } /* ── Buttons ── */ button, .btn { cursor: pointer; border: none; border-radius: var(--radius); font-family: var(--font); font-size: .85rem; padding: 7px 13px; transition: background .15s, color .15s; text-decoration: none; display: inline-block; } .btn-primary { background: var(--accent); color: #fff; } .btn-primary:hover { background: #2e5239; } .btn-back { background: #e8e2d8; color: var(--text); } .btn-back:hover { background: #d6cfc0; } .btn-danger { background: transparent; color: var(--trash); border: 1px solid #e0b8b0; } .btn-danger:hover { background: #fdecea; } .btn-restore { background: transparent; color: var(--accent); border: 1px solid #b0cebc; } .btn-restore:hover { background: #eaf3ee; } .btn-purge { background: transparent; color: #888; border: 1px solid #ccc; font-size: .8rem; } .btn-purge:hover { background: #f5f5f5; color: #555; } .btn-sm { padding: 4px 9px; font-size: 1rem; } /* ── Papierkorb ── */ .trash-section { margin-bottom: 28px; } .trash-section h3 { font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); border-bottom: 1px solid var(--border); padding-bottom: 5px; margin-bottom: 10px; } .trash-topic-block { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 10px; overflow: hidden; } .trash-topic-title { padding: 10px 14px; font-weight: bold; background: #f0ebe1; display: flex; align-items: center; gap: 8px; font-size: .95rem; } .trash-topic-title span { flex: 1; } .trash-item-row { padding: 7px 14px; border-top: 1px solid var(--border); display: flex; align-items: flex-start; gap: 8px; font-size: .9rem; } .trash-item-row .item-text { flex: 1; color: var(--muted); } .deleted-date { font-size: .75rem; color: var(--muted); font-family: var(--mono); white-space: nowrap; align-self: center; } /* ── Empty State ── */ .empty { text-align: center; color: var(--muted); padding: 40px 20px; font-size: .95rem; } .empty span { display: block; font-size: 2rem; margin-bottom: 8px; } /* ── Responsive ── */ @media (max-width: 420px) { .topic-grid { grid-template-columns: 1fr 1fr; } } </style> </head> <body> <header> <?php if ($view === 'topic' && $currentTopic): ?> <h1><?= htmlspecialchars_decode($currentTopic['name']) ?></h1> <a href="merk.php" class="btn btn-back btn-sm" style="padding:4px 10px;">Alle Themen</a> <?php else: ?> <h1>Mein Gehirn</h1> <?php endif; ?> <a href="merk.php?view=trash" class="header-badge" title="Papierkorb">🗑</a> </header> <?php if ($view !== 'topic'): ?> <nav class="tabs"> <a href="merk.php" class="<?= $view === 'topics' ? 'active' : '' ?>">Themen</a> <a href="merk.php?view=trash" class="<?= $view === 'trash' ? 'active' : '' ?>">Papierkorb</a> </nav> <?php endif; ?> <div class="container"> <?php // ════════════════════════════════════════════════════════════ // VIEW: THEMEN-ÜBERSICHT // ════════════════════════════════════════════════════════════ if ($view === 'topics'): $activeTopics = array_filter($allTopics, fn($t) => !$t['deleted']); ?> <?php if (empty($activeTopics)): ?> <div class="empty"><span>📋</span>Noch keine Themen. Lege unten eines an!</div> <?php else: ?> <div class="topic-grid"> <?php foreach ($activeTopics as $t): $cnt = count(array_filter($allItems, fn($i) => $i['topic_id'] === $t['id'] && !$i['deleted'])); ?> <div class="topic-card"> <a href="merk.php?view=topic&topic_id=<?= $t['id'] ?>" class="topic-link"> <div class="topic-name"><?= htmlspecialchars_decode($t['name']) ?></div> <div class="topic-count"><?= $cnt ?> Eintr<?= $cnt !== 1 ? '&#228;ge' : 'ag' ?></div> </a> <?php $anyItem = count(array_filter($allItems, fn($i) => $i['topic_id'] === $t['id'])); if ($anyItem === 0): ?> <div class="topic-card-footer"> <form method="post" style="display:inline"> <input type="hidden" name="action" value="delete_topic"> <input type="hidden" name="topic_id" value="<?= $t['id'] ?>"> <button type="submit" class="btn btn-danger btn-sm" title="Leeres Thema löschen">✕</button> </form> </div> <?php endif; ?> </div> <?php endforeach; ?> </div> <?php endif; ?> <form method="post" class="add-form"> <input type="hidden" name="action" value="add_topic"> <input type="text" name="name" placeholder="Neues Thema…" autocomplete="off" required> <button type="submit" class="btn btn-primary">+ Thema</button> </form> <?php // ════════════════════════════════════════════════════════════ // VIEW: EINZELNES THEMA // ════════════════════════════════════════════════════════════ elseif ($view === 'topic' && $currentTopic): $topicItems = array_filter($allItems, fn($i) => $i['topic_id'] === $topicId && !$i['deleted'] ); ?> <?php if (empty($topicItems)): ?> <div class="empty"><span>✏️</span>Noch keine Eintr&#228;ge in diesem Thema.</div> <?php else: ?> <ul class="item-list"> <?php foreach ($topicItems as $item): ?> <li> <span class="item-text"><?= linkify($item['text']) ?></span> <form method="post"> <input type="hidden" name="action" value="delete_item"> <input type="hidden" name="item_id" value="<?= $item['id'] ?>"> <input type="hidden" name="topic_id" value="<?= $topicId ?>"> <button type="submit" class="btn btn-danger btn-sm" title="Löschen">✕</button> </form> </li> <?php endforeach; ?> </ul> <?php endif; ?> <form method="post" class="add-form"> <input type="hidden" name="action" value="add_item"> <input type="hidden" name="topic_id" value="<?= $topicId ?>"> <input type="text" name="text" placeholder="Neuer Eintrag…" autocomplete="off" required> <button type="submit" class="btn btn-primary">+</button> </form> <?php // ════════════════════════════════════════════════════════════ // VIEW: PAPIERKORB // ════════════════════════════════════════════════════════════ elseif ($view === 'trash'): // Gelöschte Themen (inkl. ihrer Items) $deletedTopics = array_filter($allTopics, fn($t) => $t['deleted']); // Gelöschte Items aus AKTIVEN Themen $deletedItems = array_values(array_filter($allItems, fn($i) => $i['deleted'] && isset($topicMap[$i['topic_id']]) && !$topicMap[$i['topic_id']]['deleted'] )); usort($deletedItems, fn($a, $b) => $b['deleted_at'] - $a['deleted_at']); $hasAnything = count($deletedTopics) > 0 || count($deletedItems) > 0; ?> <?php if (!$hasAnything): ?> <div class="empty"><span>🗑</span>Papierkorb ist leer.</div> <?php else: ?> <?php if (!empty($deletedTopics)): ?> <div class="trash-section"> <h3>Gelöschte Themen</h3> <?php foreach ($deletedTopics as $t): $tItems = array_filter($allItems, fn($i) => $i['topic_id'] === $t['id']); ?> <div class="trash-topic-block"> <div class="trash-topic-title"> <span><?= htmlspecialchars_decode($t['name']) ?></span> <form method="post" style="display:inline"> <input type="hidden" name="action" value="restore_topic"> <input type="hidden" name="topic_id" value="<?= $t['id'] ?>"> <button type="submit" class="btn btn-restore btn-sm">↩ Wiederherstellen</button> </form> <form method="post" style="display:inline" onsubmit="return confirm('Endgültig löschen?')"> <input type="hidden" name="action" value="purge_topic"> <input type="hidden" name="topic_id" value="<?= $t['id'] ?>"> <button type="submit" class="btn btn-purge btn-sm">✕ Löschen</button> </form> </div> <?php foreach ($tItems as $item): ?> <div class="trash-item-row"> <span class="item-text"><?= linkify($item['text']) ?></span> </div> <?php endforeach; ?> <?php if (empty($tItems)): ?> <div class="trash-item-row"><span class="item-text" style="font-style:italic">Keine Eintr&#228;ge</span></div> <?php endif; ?> </div> <?php endforeach; ?> </div> <?php endif; ?> <?php if (!empty($deletedItems)): ?> <div class="trash-section"> <h3>Gelöschte Eintr&#228;ge (Themen noch aktiv)</h3> <?php // Gruppieren nach Thema $byTopic = []; foreach ($deletedItems as $item) { $byTopic[$item['topic_id']][] = $item; } foreach ($byTopic as $tid => $items): $tName = htmlspecialchars_decode($topicMap[$tid]['name'] ?? 'Unbekannt'); ?> <div class="trash-topic-block"> <div class="trash-topic-title"> <span>📌 <?= $tName ?></span> </div> <?php foreach ($items as $item): ?> <div class="trash-item-row"> <span class="item-text"><?= linkify($item['text']) ?></span> <?php if (!empty($item['deleted_at'])): ?> <span class="deleted-date" title="Gelöscht am <?= date('d.m.Y H:i', $item['deleted_at']) ?>"> <?= date('d.m.', $item['deleted_at']) ?> </span> <?php endif; ?> <form method="post" style="display:inline"> <input type="hidden" name="action" value="restore_item"> <input type="hidden" name="item_id" value="<?= $item['id'] ?>"> <button type="submit" class="btn btn-restore btn-sm">↩</button> </form> <form method="post" style="display:inline" onsubmit="return confirm('Endgültig löschen?')"> <input type="hidden" name="action" value="purge_item"> <input type="hidden" name="item_id" value="<?= $item['id'] ?>"> <button type="submit" class="btn btn-purge btn-sm">✕</button> </form> </div> <?php endforeach; ?> </div> <?php endforeach; ?> </div> <?php endif; ?> <?php endif; ?> <?php // ════════════════════════════════════════════════════════════ // Fallback: Topic nicht gefunden // ════════════════════════════════════════════════════════════ else: ?> <div class="empty"><span>🔍</span>Thema nicht gefunden. <a href="merk.php">Zurück</a></div> <?php endif; ?> </div><!-- .container --> </body> </html>
Content is user-generated and unverified.
    Merk.php - Simple Note Taking App with Topics | Claude