exec("ALTER TABLE notes ADD COLUMN color TEXT DEFAULT NULL"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE notes ADD COLUMN image_path TEXT DEFAULT NULL"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE notes ADD COLUMN updated_at DATETIME DEFAULT NULL"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE notes ADD COLUMN label_name TEXT DEFAULT NULL"); } catch(Exception $e) {} try { $pdo->exec("CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, content TEXT, owner_id INTEGER, color TEXT DEFAULT NULL, image_path TEXT DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT NULL, FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE )"); } catch(Exception $e) {} try { $pdo->exec("CREATE TABLE IF NOT EXISTS note_shares ( id INTEGER PRIMARY KEY AUTOINCREMENT, note_id INTEGER, share_type TEXT, share_id INTEGER, FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE )"); } catch(Exception $e) {} // ── Handle POST ─────────────────────────────────────────────────────────────── if($_SERVER['REQUEST_METHOD'] === 'POST') { if(isset($_POST['create_note'])) { $image_path = null; if(isset($_FILES['note_image']) && $_FILES['note_image']['error'] === 0) { $allowed = ['image/jpeg','image/png','image/gif','image/webp']; if(in_array($_FILES['note_image']['type'], $allowed)) { $ext = pathinfo($_FILES['note_image']['name'], PATHINFO_EXTENSION); $fname = 'note_' . $user_id . '_' . time() . '.' . $ext; $dest = 'uploads/' . $fname; if(move_uploaded_file($_FILES['note_image']['tmp_name'], $dest)) $image_path = $dest; } } $stmt = $pdo->prepare("INSERT INTO notes (title, content, owner_id, color, image_path, label_name) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([$_POST['title'] ?? 'Untitled', $_POST['content'], $user_id, $_POST['color'] ?? null, $image_path, trim($_POST['label_name'] ?? '') ?: null]); // Note creation only notifies owner in UI, but we can trigger a WAHA self-alert if they have it enabled notify_user($user_id, "You created a new note: " . ($_POST['title'] ?? 'Untitled'), ['action'=>'Created', 'module'=>'Note', 'item'=>$_POST['title']??'Untitled']); } if(isset($_POST['update_note'])) { $n = query("SELECT owner_id, image_path FROM notes WHERE id=?", [$_POST['note_id']])->fetch(); if($n && $n['owner_id'] == $user_id) { $image_path = $n['image_path']; if(isset($_FILES['note_image']) && $_FILES['note_image']['error'] === 0) { $allowed = ['image/jpeg','image/png','image/gif','image/webp']; if(in_array($_FILES['note_image']['type'], $allowed)) { $ext = pathinfo($_FILES['note_image']['name'], PATHINFO_EXTENSION); $fname = 'note_' . $user_id . '_' . time() . '.' . $ext; $dest = 'uploads/' . $fname; if(move_uploaded_file($_FILES['note_image']['tmp_name'], $dest)) { if($image_path && file_exists($image_path)) @unlink($image_path); $image_path = $dest; } } } if(isset($_POST['remove_image']) && $_POST['remove_image']) { if($image_path && file_exists($image_path)) @unlink($image_path); $image_path = null; } $stmt = $pdo->prepare("UPDATE notes SET title=?, content=?, color=?, image_path=?, label_name=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND owner_id=?"); $stmt->execute([$_POST['title'] ?? 'Untitled', $_POST['content'], $_POST['color'] ?? null, $image_path, trim($_POST['label_name'] ?? '') ?: null, $_POST['note_id'], $user_id]); $ctx = ['action'=>'Updated', 'module'=>'Note', 'item'=>$_POST['title']??'Untitled']; // Notify owner notify_user($user_id, "Note '{$_POST['title']}' updated.", $ctx); // Notify anyone this note is shared with $shares = query("SELECT share_type, share_id FROM note_shares WHERE note_id=?", [$_POST['note_id']])->fetchAll(); foreach($shares as $sh) { if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared note '{$_POST['title']}' updated.", $ctx); else notify_group($sh['share_id'], "Shared group note '{$_POST['title']}' updated.", $ctx); } } } if(isset($_POST['delete_note'])) { $n = query("SELECT owner_id, image_path, title FROM notes WHERE id=?", [$_POST['note_id']])->fetch(); if($n && $n['owner_id'] == $user_id) { if($n['image_path'] && file_exists($n['image_path'])) @unlink($n['image_path']); $ctx = ['action'=>'Deleted', 'module'=>'Note', 'item'=>$n['title']??'Untitled']; // Notify anyone it was shared with before deleting $shares = query("SELECT share_type, share_id FROM note_shares WHERE note_id=?", [$_POST['note_id']])->fetchAll(); foreach($shares as $sh) { if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared note '{$n['title']}' has been deleted.", $ctx); else notify_group($sh['share_id'], "Shared group note '{$n['title']}' has been deleted.", $ctx); } query("DELETE FROM notes WHERE id=? AND owner_id=?", [$_POST['note_id'], $user_id]); notify_user($user_id, "Note '{$n['title']}' has been deleted.", $ctx); } } if(isset($_POST['share_note'])) { $n_id = $_POST['note_id']; $share = $_POST['share_target']; $parts = explode('_', $share, 2); if(count($parts)===2) { $stmt = $pdo->prepare("INSERT INTO note_shares (note_id, share_type, share_id) VALUES (?, ?, ?)"); $stmt->execute([$n_id, $parts[0], $parts[1]]); $n = query("SELECT title FROM notes WHERE id=?", [$n_id])->fetch(); $msg = "A note '{$n['title']}' has been shared with you."; $ctx = ['action'=>'Shared', 'module'=>'Note', 'item'=>$n['title']??'Untitled', 'user'=>$_SESSION['username']??'System']; if($parts[0] === 'user') notify_user($parts[1], $msg, $ctx); else notify_group($parts[1], $msg, $ctx); } } if(isset($_POST['remove_note_share'])) { $share_id = (int)$_POST['share_id']; $chk = query("SELECT ns.id FROM note_shares ns JOIN notes n ON ns.note_id=n.id WHERE ns.id=? AND n.owner_id=?", [$share_id, $user_id])->fetch(); if($chk) { $pdo->prepare("DELETE FROM note_shares WHERE id=?")->execute([$share_id]); } } } // ── Fetch Notes ─────────────────────────────────────────────────────────────── $user_groups = get_user_groups($user_id); $my_group_ids = array_column($user_groups, 'id'); $group_in = implode(',', array_fill(0, count($my_group_ids), '?')) ?: '0'; $sql = "SELECT n.*, (n.owner_id = ?) as is_mine, u.username as owner_name FROM notes n LEFT JOIN note_shares ns ON n.id = ns.note_id LEFT JOIN users u ON n.owner_id = u.id WHERE n.owner_id = ? OR (ns.share_type='user' AND ns.share_id = ?) OR (ns.share_type='group' AND ns.share_id IN ($group_in)) GROUP BY n.id ORDER BY n.updated_at DESC, n.created_at DESC"; $params = array_merge([$user_id, $user_id, $user_id], $my_group_ids); $stmt = $pdo->prepare($sql); $stmt->execute($params); $notes = $stmt->fetchAll(); $all_users = query("SELECT id, username FROM users WHERE id != ?", [$user_id])->fetchAll(); $all_groups = query("SELECT * FROM groups")->fetchAll(); $label_names = []; foreach($notes as $n) { $lbls = explode(',', (string)($n['label_name'] ?? '')); foreach($lbls as $lbl) { $lbl = trim($lbl); if($lbl !== '') $label_names[strtolower($lbl)] = $lbl; } } natcasesort($label_names); // Define note colors — Google Keep inspired $note_colors = [ '' => ['bg'=>'var(--surface)', 'label'=>'Default', 'hex'=>'#ffffff'], '#fff9c4' => ['bg'=>'#fff9c4', 'label'=>'Yellow', 'hex'=>'#fff9c4'], '#ccff90' => ['bg'=>'#ccff90', 'label'=>'Lime', 'hex'=>'#ccff90'], '#d4edda' => ['bg'=>'#d4edda', 'label'=>'Sage', 'hex'=>'#d4edda'], '#80cbc4' => ['bg'=>'#80cbc4', 'label'=>'Teal', 'hex'=>'#80cbc4'], '#b3e5fc' => ['bg'=>'#b3e5fc', 'label'=>'Sky', 'hex'=>'#b3e5fc'], '#cce5ff' => ['bg'=>'#cce5ff', 'label'=>'Blue', 'hex'=>'#cce5ff'], '#e2d9f3' => ['bg'=>'#e2d9f3', 'label'=>'Lavender', 'hex'=>'#e2d9f3'], '#f3e5f5' => ['bg'=>'#f3e5f5', 'label'=>'Orchid', 'hex'=>'#f3e5f5'], '#f8d7da' => ['bg'=>'#f8d7da', 'label'=>'Rose', 'hex'=>'#f8d7da'], '#ef9a9a' => ['bg'=>'#ef9a9a', 'label'=>'Red', 'hex'=>'#ef9a9a'], '#ffcc80' => ['bg'=>'#ffcc80', 'label'=>'Peach', 'hex'=>'#ffcc80'], '#fde8d8' => ['bg'=>'#fde8d8', 'label'=>'Orange', 'hex'=>'#fde8d8'], '#d7ccc8' => ['bg'=>'#d7ccc8', 'label'=>'Sand', 'hex'=>'#d7ccc8'], '#f5f5f5' => ['bg'=>'#f5f5f5', 'label'=>'Stone', 'hex'=>'#f5f5f5'], '#e8f5e9' => ['bg'=>'#e8f5e9', 'label'=>'Mint', 'hex'=>'#e8f5e9'], '#fff3e0' => ['bg'=>'#fff3e0', 'label'=>'Apricot', 'hex'=>'#fff3e0'], '#fce4ec' => ['bg'=>'#fce4ec', 'label'=>'Pink', 'hex'=>'#fce4ec'], '#ede7f6' => ['bg'=>'#ede7f6', 'label'=>'Violet', 'hex'=>'#ede7f6'], '#e0f7fa' => ['bg'=>'#e0f7fa', 'label'=>'Cyan', 'hex'=>'#e0f7fa'], '#fffde7' => ['bg'=>'#fffde7', 'label'=>'Butter', 'hex'=>'#fffde7'], ]; ?>
📝

No notes yet

Click New Note to start writing something.

120 ? '...' : ''); $note_shares = []; if($n['is_mine']) { $note_shares = query("SELECT ns.id, ns.share_type, ns.share_id, u.username, u.profile_pic as upic, g.name as gname, g.profile_pic as gpic FROM note_shares ns LEFT JOIN users u ON (ns.share_type='user' AND ns.share_id=u.id) LEFT JOIN groups g ON (ns.share_type='group' AND ns.share_id=g.id) WHERE ns.note_id=?", [$n['id']])->fetchAll(); } $shares_json = htmlspecialchars(json_encode($note_shares), ENT_QUOTES, 'UTF-8'); ?>
Note image