Files
Task-Management/views/notes.php

531 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// ── DB migrations ─────────────────────────────────────────────────────────────
try { $pdo->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]);
}
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]);
}
}
if(isset($_POST['delete_note'])) {
$n = query("SELECT owner_id, image_path 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']);
query("DELETE FROM notes WHERE id=? AND owner_id=?", [$_POST['note_id'], $user_id]);
}
}
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.";
if($parts[0] === 'user') notify_user($parts[1], $msg);
else notify_group($parts[1], $msg);
}
}
}
// ── 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) {
$lbl = trim((string)($n['label_name'] ?? ''));
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'],
];
?>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; gap:0.75rem; flex-wrap:wrap;">
<button class="btn" onclick="document.getElementById('new-note-modal').classList.add('active')">
<i class="fa fa-plus"></i> New Note
</button>
<div style="display:flex; align-items:center; gap:0.5rem; flex-wrap:wrap;">
<input type="text" id="notes-search-input" class="form-control" placeholder="Search notes..." style="width:200px; padding:0.45rem 0.6rem; font-size:0.84rem;" oninput="filterNotes()">
<select id="notes-label-filter" class="form-control" style="width:180px; padding:0.45rem 0.6rem; font-size:0.84rem;" onchange="filterNotes()">
<option value="">All labels</option>
<?php foreach($label_names as $lbl): ?>
<option value="<?= htmlspecialchars(strtolower($lbl)) ?>"><?= htmlspecialchars($lbl) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- ── New Note Modal ─────────────────────────────────────────────────────── -->
<div class="modal-overlay" id="new-note-modal">
<div class="modal-content note-modal-compact" style="max-width:760px; max-height:92vh; overflow-y:auto;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
<h3 style="font-size:1rem; font-weight:700;">New Note</h3>
<button type="button" style="background:none;border:none;color:var(--text-light);cursor:pointer;font-size:1.2rem;" onclick="document.getElementById('new-note-modal').classList.remove('active')">×</button>
</div>
<form method="POST" enctype="multipart/form-data" id="new-note-form">
<input type="hidden" name="create_note" value="1">
<div class="note-modal-top-fields" style="display:grid; grid-template-columns:2fr 1fr; gap:0.75rem;">
<div class="form-group">
<input type="text" name="title" class="form-control" placeholder="Title..." id="new-note-title" style="font-weight:600; font-size:1rem;">
</div>
<div class="form-group">
<input type="text" name="label_name" class="form-control" id="new-note-label" placeholder="Tag/Label name">
</div>
</div>
<!-- WYSIWYG Toolbar -->
<div class="note-editor-toolbar" id="new-note-toolbar">
<button type="button" class="note-toolbar-btn" onclick="formatNote('new-note-content','bold')" title="Bold"><b>B</b></button>
<button type="button" class="note-toolbar-btn" onclick="formatNote('new-note-content','italic')" title="Italic"><i>I</i></button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','# ')" title="H1">H1</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','## ')" title="H2">H2</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','### ')" title="H3">H3</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','- ')" title="Bullet">• List</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','1. ')" title="Numbered">1. List</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','- [ ] ')" title="Todo">☑ Todo</button>
<button type="button" class="note-toolbar-btn" onclick="wrapMarkdown('new-note-content','`','`')" title="Code"><code>Code</code></button>
<button type="button" class="note-toolbar-btn" onclick="wrapMarkdown('new-note-content','```\n','\n```')" title="Code Block">{ } Block</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','> ')" title="Quote">❝ Quote</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('new-note-content','---\n')" title="Divider">─ Line</button>
<button type="button" class="note-toolbar-btn" id="new-preview-btn" onclick="togglePreview('new-note-content','new-note-preview','new-preview-btn')">👁 Preview</button>
</div>
<div class="form-group">
<textarea name="content" id="new-note-content" class="note-content-area" placeholder="Write your note... (Markdown supported)" rows="8"></textarea>
<div id="new-note-preview" class="markdown-body" style="display:none; min-height:160px; border:1px solid var(--border); border-radius:0.5rem; padding:0.75rem; background:var(--bg); font-size:0.9rem;"></div>
</div>
<!-- Color picker -->
<div class="form-group">
<label style="font-size:0.8rem; color:var(--text-light);">Note Color</label>
<div class="note-color-palette" id="new-note-colors">
<?php foreach($note_colors as $hex => $info): ?>
<div class="color-swatch <?= !$hex ? 'selected' : '' ?>"
style="background:<?= $hex ?: '#fff' ?>; border:2px solid var(--border);"
onclick="selectColor(this,'new-note-color-input','<?= $hex ?>')"
title="<?= $info['label'] ?>"></div>
<?php endforeach; ?>
</div>
<input type="hidden" name="color" id="new-note-color-input" value="">
</div>
<!-- Image upload -->
<div class="form-group">
<label style="font-size:0.8rem; color:var(--text-light);">Attach Image (optional)</label>
<input type="file" name="note_image" accept="image/*" class="form-control">
</div>
<div style="display:flex; gap:0.5rem; justify-content:flex-end; margin-top:1rem;">
<button type="button" class="btn btn-secondary" onclick="document.getElementById('new-note-modal').classList.remove('active')">Cancel</button>
<button type="submit" class="btn"><i class="fa fa-save"></i> Save Note</button>
</div>
</form>
</div>
</div>
<!-- ── Edit Note Modal ────────────────────────────────────────────────────── -->
<div class="modal-overlay" id="edit-note-modal">
<div class="modal-content note-modal-compact" style="max-width:760px; max-height:90vh; overflow-y:auto;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
<h3 style="margin:0;">Edit Note</h3>
<button type="button" style="background:none;border:none;color:var(--text-light);cursor:pointer;font-size:1.2rem;" onclick="document.getElementById('edit-note-modal').classList.remove('active')">×</button>
</div>
<form method="POST" enctype="multipart/form-data" id="edit-note-form">
<input type="hidden" name="update_note" value="1">
<input type="hidden" name="note_id" id="edit-note-id">
<div class="note-modal-top-fields" style="display:grid; grid-template-columns:2fr 1fr; gap:0.75rem;">
<div class="form-group">
<input type="text" name="title" id="edit-note-title" class="form-control" placeholder="Title..." style="font-weight:600; font-size:1rem;">
</div>
<div class="form-group">
<input type="text" name="label_name" id="edit-note-label" class="form-control" placeholder="Tag/Label name">
</div>
</div>
<div class="note-editor-toolbar">
<button type="button" class="note-toolbar-btn" onclick="formatNote('edit-note-content','bold')"><b>B</b></button>
<button type="button" class="note-toolbar-btn" onclick="formatNote('edit-note-content','italic')"><i>I</i></button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','# ')">H1</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','## ')">H2</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','### ')">H3</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','- ')">• List</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','1. ')">1. List</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','- [ ] ')">☑ Todo</button>
<button type="button" class="note-toolbar-btn" onclick="wrapMarkdown('edit-note-content','`','`')"><code>Code</code></button>
<button type="button" class="note-toolbar-btn" onclick="wrapMarkdown('edit-note-content','```\n','\n```')">{ } Block</button>
<button type="button" class="note-toolbar-btn" onclick="insertMarkdown('edit-note-content','> ')">❝ Quote</button>
<button type="button" class="note-toolbar-btn" id="edit-preview-btn" onclick="togglePreview('edit-note-content','edit-note-preview','edit-preview-btn')">👁 Preview</button>
</div>
<div class="form-group">
<textarea name="content" id="edit-note-content" class="note-content-area" rows="8"></textarea>
<div id="edit-note-preview" class="markdown-body" style="display:none; min-height:160px; border:1px solid var(--border); border-radius:0.5rem; padding:0.75rem; background:var(--bg); font-size:0.9rem;"></div>
</div>
<div class="form-group">
<label style="font-size:0.8rem; color:var(--text-light);">Note Color</label>
<div class="note-color-palette" id="edit-note-colors">
<?php foreach($note_colors as $hex => $info): ?>
<div class="color-swatch"
style="background:<?= $hex ?: '#fff' ?>; border:2px solid var(--border);"
onclick="selectColor(this,'edit-note-color-input','<?= $hex ?>')"
title="<?= $info['label'] ?>"></div>
<?php endforeach; ?>
</div>
<input type="hidden" name="color" id="edit-note-color-input" value="">
</div>
<div id="edit-current-image" style="display:none; margin-bottom:0.75rem;">
<label style="font-size:0.8rem; color:var(--text-light);">Current Image:</label>
<div style="display:flex; align-items:center; gap:0.5rem; margin-top:0.25rem;">
<img id="edit-current-img-preview" src="" alt="Current" style="height:60px; border-radius:0.5rem; object-fit:cover;">
<label style="display:flex; align-items:center; gap:0.25rem; font-size:0.8rem; cursor:pointer;">
<input type="checkbox" name="remove_image" value="1"> Remove image
</label>
</div>
</div>
<div class="form-group">
<label style="font-size:0.8rem; color:var(--text-light);">Replace Image (optional)</label>
<input type="file" name="note_image" accept="image/*" class="form-control">
</div>
<!-- Share section (only for owner) -->
<div id="edit-share-section" style="border-top:1px solid var(--border); padding-top:0.75rem; margin-top:0.5rem;">
<label style="font-size:0.8rem; color:var(--text-light); margin-bottom:0.4rem; display:block;">Share this note</label>
<div style="display:flex; gap:0.5rem; flex-wrap:wrap;">
<select name="share_target" id="edit-share-target" class="form-control" style="flex:1; min-width:150px; padding:0.4rem;">
<?php foreach($all_users as $u): ?>
<option value="user_<?= $u['id'] ?>">👤 <?= htmlspecialchars($u['username']) ?></option>
<?php endforeach; ?>
<?php foreach($all_groups as $g): ?>
<option value="group_<?= $g['id'] ?>">👥 <?= htmlspecialchars($g['name']) ?></option>
<?php endforeach; ?>
</select>
<button type="submit" name="share_note" value="1" class="btn btn-sm" onclick="this.form.action=''; document.querySelector('[name=update_note]').disabled=true; this.form.onsubmit=null;" formaction="">
<i class="fa fa-share-nodes"></i> Share
</button>
</div>
</div>
<div style="display:flex; gap:0.5rem; justify-content:space-between; margin-top:1rem; flex-wrap:wrap;">
<button type="button" class="btn btn-danger btn-sm" onclick="deleteNote(document.getElementById('edit-note-id').value)"><i class="fa fa-trash"></i> Delete</button>
<div style="display:flex; gap:0.5rem;">
<button type="button" class="btn btn-secondary" onclick="document.getElementById('edit-note-modal').classList.remove('active')">Cancel</button>
<button type="submit" class="btn"><i class="fa fa-save"></i> Save Changes</button>
</div>
</div>
</form>
</div>
</div>
<!-- Delete Note Form -->
<form method="POST" id="delete-note-form" style="display:none;">
<input type="hidden" name="delete_note" value="1">
<input type="hidden" name="note_id" id="delete-note-id">
</form>
<!-- Share Note Form (separate POST for share_note) -->
<form method="POST" id="share-note-standalone-form" style="display:none;">
<input type="hidden" name="share_note" value="1">
<input type="hidden" name="note_id" id="share-note-standalone-id">
<input type="hidden" name="share_target" id="share-note-standalone-target">
</form>
<!-- Markdown Libs -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- ── Notes Grid ─────────────────────────────────────────────────────────── -->
<?php if(empty($notes)): ?>
<div style="text-align:center; padding:4rem 2rem; color:var(--text-light);">
<div style="font-size:3rem; margin-bottom:1rem;">📝</div>
<h3 style="margin-bottom:0.5rem;">No notes yet</h3>
<p>Click <strong>New Note</strong> to start writing something.</p>
</div>
<?php else: ?>
<div class="notes-grid">
<?php foreach($notes as $n):
$bg = $n['color'] ?: 'var(--surface)';
$preview = strip_tags(isset($n['content']) ? (function_exists('marked') ? $n['content'] : $n['content']) : '');
$preview = mb_substr($preview, 0, 120) . (mb_strlen($preview) > 120 ? '...' : '');
?>
<div class="note-card note-filter-item"
style="background:<?= htmlspecialchars($bg) ?>;"
data-id="<?= $n['id'] ?>"
data-title="<?= htmlspecialchars(addslashes((string)$n['title'])) ?>"
data-content="<?= htmlspecialchars(addslashes((string)$n['content'])) ?>"
data-label="<?= htmlspecialchars(strtolower(trim((string)($n['label_name'] ?? '')))) ?>"
data-label-raw="<?= htmlspecialchars(trim((string)($n['label_name'] ?? ''))) ?>"
data-color="<?= htmlspecialchars((string)$n['color']) ?>"
data-image="<?= htmlspecialchars((string)($n['image_path'] ?? '')) ?>"
data-mine="<?= $n['is_mine'] ?>"
onclick="openNote(this)">
<?php if($n['image_path'] && file_exists($n['image_path'])): ?>
<img src="<?= htmlspecialchars($n['image_path']) ?>" alt="Note image" class="note-card-img">
<?php endif; ?>
<?php if($n['title']): ?>
<div class="note-card-title"><?= htmlspecialchars($n['title']) ?></div>
<?php endif; ?>
<?php if(!empty($n['label_name'])): ?>
<div style="display:inline-flex; align-items:center; gap:0.35rem; width:max-content; max-width:100%; padding:3px 8px; border-radius:999px; background:rgba(var(--primary-rgb),0.12); color:var(--primary); font-size:0.7rem; font-weight:600;">
<i class="fa fa-tag" style="font-size:0.65rem;"></i> <?= htmlspecialchars($n['label_name']) ?>
</div>
<?php endif; ?>
<div class="note-card-preview"><?= htmlspecialchars($preview) ?></div>
<div class="note-card-footer">
<div style="display:flex; align-items:center; gap:0.4rem;">
<?php if($n['color']): ?><div class="note-color-dot" style="background:<?= htmlspecialchars($n['color']) ?>;"></div><?php endif; ?>
<span style="font-size:0.7rem; color:var(--text-light);"><?= date('M j', strtotime($n['updated_at'] ?: $n['created_at'])) ?></span>
<?php if(!$n['is_mine']): ?>
<span class="note-card-shared-badge">Shared</span>
<?php endif; ?>
</div>
<div class="note-card-actions" onclick="event.stopPropagation()">
<?php if($n['is_mine']): ?>
<button class="btn btn-sm btn-danger" style="padding:3px 7px;" onclick="deleteNote(<?= $n['id'] ?>)" title="Delete"><i class="fa fa-trash"></i></button>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<script>
// ── WYSIWYG Markdown Tools ────────────────────────────────────────────────────
function formatNote(textareaId, cmd) {
const ta = document.getElementById(textareaId);
const start = ta.selectionStart, end = ta.selectionEnd;
const sel = ta.value.substring(start, end);
let wrap = cmd === 'bold' ? '**' : '_';
const replacement = `${wrap}${sel || 'text'}${wrap}`;
ta.value = ta.value.substring(0, start) + replacement + ta.value.substring(end);
ta.focus();
ta.selectionStart = start + wrap.length;
ta.selectionEnd = start + wrap.length + (sel || 'text').length;
}
function insertMarkdown(textareaId, prefix) {
const ta = document.getElementById(textareaId);
const start = ta.selectionStart;
// Go to start of line
const before = ta.value.substring(0, start);
const lineStart = before.lastIndexOf('\n') + 1;
ta.value = ta.value.substring(0, lineStart) + prefix + ta.value.substring(lineStart);
ta.focus();
const newPos = lineStart + prefix.length + (start - lineStart);
ta.selectionStart = ta.selectionEnd = newPos;
}
function wrapMarkdown(textareaId, before, after) {
const ta = document.getElementById(textareaId);
const start = ta.selectionStart, end = ta.selectionEnd;
const sel = ta.value.substring(start, end);
ta.value = ta.value.substring(0, start) + before + (sel || 'code') + after + ta.value.substring(end);
ta.focus();
}
function togglePreview(contentId, previewId, btnId) {
const content = document.getElementById(contentId);
const preview = document.getElementById(previewId);
const btn = document.getElementById(btnId);
if(preview.style.display === 'none') {
preview.innerHTML = marked.parse(content.value || '');
preview.style.display = 'block';
content.style.display = 'none';
btn.textContent = '✏️ Edit';
} else {
preview.style.display = 'none';
content.style.display = 'block';
btn.textContent = '👁 Preview';
}
}
function selectColor(el, inputId, color) {
el.closest('.note-color-palette').querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
el.classList.add('selected');
document.getElementById(inputId).value = color;
}
function openNote(card) {
const id = card.dataset.id;
const title = card.dataset.title;
const content = card.dataset.content;
const color = card.dataset.color;
const label = card.dataset.labelRaw || '';
const image = card.dataset.image;
const isMine = card.dataset.mine === '1';
document.getElementById('edit-note-id').value = id;
document.getElementById('edit-note-title').value = title;
document.getElementById('edit-note-content').value = content;
document.getElementById('edit-note-label').value = label;
document.getElementById('edit-note-color-input').value = color;
document.getElementById('edit-note-preview').style.display = 'none';
document.getElementById('edit-note-content').style.display = 'block';
document.getElementById('edit-preview-btn').textContent = '👁 Preview';
// Color swatches
document.querySelectorAll('#edit-note-colors .color-swatch').forEach(s => {
s.classList.remove('selected');
const swatchColor = s.getAttribute('onclick').match(/'([^']*)'/g);
if(swatchColor && swatchColor[1] && swatchColor[1].replace(/'/g,'') === color) s.classList.add('selected');
if(!color && s.title === 'Default') s.classList.add('selected');
});
// Image preview
const imgBlock = document.getElementById('edit-current-image');
if(image) {
imgBlock.style.display = 'block';
document.getElementById('edit-current-img-preview').src = image;
} else {
imgBlock.style.display = 'none';
}
// Share section
document.getElementById('edit-share-section').style.display = isMine ? 'block' : 'none';
// Wire up share button
const shareBtn = document.querySelector('#edit-note-form button[name="share_note"]');
if(shareBtn) {
shareBtn.onclick = (e) => {
e.preventDefault();
const target = document.getElementById('edit-share-target').value;
document.getElementById('share-note-standalone-id').value = id;
document.getElementById('share-note-standalone-target').value = target;
document.getElementById('share-note-standalone-form').submit();
};
}
document.getElementById('edit-note-modal').classList.add('active');
}
function filterNotes() {
const q = (document.getElementById('notes-search-input')?.value || '').toLowerCase().trim();
const lbl = (document.getElementById('notes-label-filter')?.value || '').trim();
document.querySelectorAll('.note-filter-item').forEach(card => {
const t = (card.dataset.title || '').toLowerCase();
const c = (card.dataset.content || '').toLowerCase();
const cl = (card.dataset.label || '').toLowerCase();
const okText = !q || t.includes(q) || c.includes(q) || cl.includes(q);
const okLabel = !lbl || cl === lbl;
card.style.display = (okText && okLabel) ? '' : 'none';
});
}
function deleteNote(id) {
if(!confirm('Delete this note permanently?')) return;
document.getElementById('delete-note-id').value = id;
document.getElementById('delete-note-form').submit();
}
// Fix note card colors in dark mode
function applyDarkNoteColors() {
const isDark = document.body.classList.contains('dark-mode');
document.querySelectorAll('.note-card').forEach(card => {
const origColor = card.dataset.color;
if(!origColor && isDark) {
card.style.background = 'var(--surface)';
}
});
}
const observer = new MutationObserver(applyDarkNoteColors);
observer.observe(document.body, {attributes: true, attributeFilter: ['class']});
applyDarkNoteColors();
</script>