feat: integrate Tagify for multi-tag support and add a new view-note modal across the application

This commit is contained in:
root
2026-03-29 16:25:45 +07:00
parent f4f842d658
commit 8dedb57692
8 changed files with 408 additions and 106 deletions

View File

@@ -122,8 +122,11 @@ $all_users = query("SELECT id, username FROM users WHERE id != ?", [$user_id])->
$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;
$lbls = explode(',', (string)($n['label_name'] ?? ''));
foreach($lbls as $lbl) {
$lbl = trim($lbl);
if($lbl !== '') $label_names[strtolower($lbl)] = $lbl;
}
}
natcasesort($label_names);
@@ -182,7 +185,8 @@ $note_colors = [
<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">
<label style="font-size:0.8rem; color:var(--text-light); margin-bottom:0.25rem; display:block;">Tags</label>
<input type="text" name="label_name" class="form-control tagify-input" id="new-note-label" placeholder="Add tags...">
</div>
</div>
<!-- WYSIWYG Toolbar -->
@@ -231,6 +235,36 @@ $note_colors = [
</div>
</div>
<!-- ── View Note Modal ────────────────────────────────────────────────────── -->
<div class="modal-overlay" id="view-note-modal">
<div class="modal-content note-modal-compact" style="max-width:760px; max-height:92vh; overflow-y:auto; padding:0; background:var(--bg);">
<div id="view-note-header" style="padding:1.5rem; border-bottom:1px solid var(--border); background:var(--surface); position:sticky; top:0; z-index:10;">
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:1rem;">
<div style="flex:1; min-width:0;">
<h2 id="view-note-title" style="margin:0 0 0.5rem 0; font-size:1.4rem; font-weight:800; line-height:1.3; color:var(--text); word-wrap:break-word;"></h2>
<div style="display:flex; align-items:center; gap:0.5rem; flex-wrap:wrap;">
<span id="view-note-label-container" style="display:none; align-items:center; gap:0.35rem; padding:3px 8px; border-radius:999px; background:rgba(var(--primary-rgb),0.12); color:var(--primary); font-size:0.75rem; font-weight:600;">
<i class="fa fa-tag" style="font-size:0.7rem;"></i> <span id="view-note-label"></span>
</span>
<span id="view-note-date" style="font-size:0.8rem; color:var(--text-light);"></span>
<span id="view-note-shared-badge" class="note-card-shared-badge" style="display:none;">Shared</span>
</div>
</div>
<div style="display:flex; align-items:center; gap:0.5rem; flex-shrink:0;">
<button class="btn btn-sm btn-secondary" id="view-note-edit-btn" onclick="transToEditNote()"><i class="fa fa-pencil"></i> Edit</button>
<button type="button" style="background:none;border:none;color:var(--text-light);cursor:pointer;font-size:1.6rem; line-height:1; padding:0 4px;" onclick="document.getElementById('view-note-modal').classList.remove('active')">×</button>
</div>
</div>
<div id="view-note-image-container" style="display:none; margin-top:1.25rem; border-radius:0.75rem; overflow:hidden; border:1px solid var(--border);">
<img id="view-note-image" src="" style="width:100%; max-height:450px; object-fit:cover; display:block;">
</div>
</div>
<div style="padding:1.5rem;">
<div id="view-note-content" class="markdown-body" style="font-size:1rem; line-height:1.7; color:var(--text);"></div>
</div>
</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;">
@@ -246,7 +280,8 @@ $note_colors = [
<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">
<label style="font-size:0.8rem; color:var(--text-light); margin-bottom:0.25rem; display:block;">Tags</label>
<input type="text" name="label_name" id="edit-note-label" class="form-control tagify-input" placeholder="Add tags...">
</div>
</div>
<div class="note-editor-toolbar">
@@ -374,7 +409,8 @@ $note_colors = [
data-image="<?= htmlspecialchars((string)($n['image_path'] ?? '')) ?>"
data-shares="<?= $shares_json ?>"
data-mine="<?= $n['is_mine'] ?>"
onclick="openNote(this)">
data-updated="<?= date('M j, Y, H:i', strtotime($n['updated_at'] ?: $n['created_at'])) ?>"
onclick="viewNote(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; ?>
@@ -382,8 +418,12 @@ $note_colors = [
<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 style="display:flex; flex-wrap:wrap; gap:0.35rem; margin-bottom:0.5rem;">
<?php foreach(explode(',', $n['label_name']) as $lbl): $lbl=trim($lbl); if(!$lbl) continue; ?>
<div style="display:inline-flex; align-items:center; gap:0.35rem; 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($lbl) ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="note-card-preview"><?= htmlspecialchars($preview) ?></div>
@@ -478,7 +518,7 @@ function openNote(card) {
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';
document.getElementById('edit-preview-btn').innerHTML = '👁 Preview';
// Color swatches
document.querySelectorAll('#edit-note-colors .color-swatch').forEach(s => {
@@ -518,6 +558,60 @@ function openNote(card) {
document.getElementById('edit-note-modal').classList.add('active');
}
let currentViewedCard = null;
function viewNote(card) {
currentViewedCard = card;
const title = card.dataset.title || 'Untitled Note';
const content = card.dataset.content || '';
const label = card.dataset.labelRaw || '';
const image = card.dataset.image;
const updatedAt = card.dataset.updated;
const isMine = card.dataset.mine === '1';
document.getElementById('view-note-title').textContent = title;
// Clear old tags
document.getElementById('view-note-label-container').innerHTML = '';
if(label) {
document.getElementById('view-note-label-container').style.display = 'flex';
const lbls = label.split(',');
lbls.forEach(l => {
const span = document.createElement('span');
span.style.cssText = 'display:inline-flex; align-items:center; gap:0.35rem; padding:3px 8px; border-radius:999px; background:rgba(var(--primary-rgb),0.12); color:var(--primary); font-size:0.75rem; font-weight:600;';
span.innerHTML = `<i class="fa fa-tag" style="font-size:0.7rem;"></i> ${l.trim()}`;
document.getElementById('view-note-label-container').appendChild(span);
});
} else {
document.getElementById('view-note-label-container').style.display = 'none';
}
document.getElementById('view-note-date').textContent = 'Updated ' + updatedAt;
document.getElementById('view-note-shared-badge').style.display = !isMine ? 'inline-block' : 'none';
if(image) {
document.getElementById('view-note-image-container').style.display = 'block';
document.getElementById('view-note-image').src = image;
} else {
document.getElementById('view-note-image-container').style.display = 'none';
}
document.getElementById('view-note-content').innerHTML = marked.parse(content);
document.getElementById('view-note-edit-btn').style.display = isMine ? 'inline-flex' : 'none';
document.getElementById('view-note-modal').classList.add('active');
}
function transToEditNote() {
if(currentViewedCard) {
document.getElementById('view-note-modal').classList.remove('active');
openNote(currentViewedCard);
}
}
function renderNoteShares(jsonShares) {
const list = document.getElementById('note-shares-list');
list.innerHTML = '';
@@ -542,9 +636,12 @@ function renderNoteShares(jsonShares) {
function removeNoteShare(shareId) {
if(!shareId) return;
if(!confirm('Unshare this note access?')) return;
document.getElementById('remove-note-share-id').value = String(shareId);
document.getElementById('remove-note-share-form').submit();
appConfirm('Unshare this note access?').then(y => {
if(y) {
document.getElementById('remove-note-share-id').value = String(shareId);
document.getElementById('remove-note-share-form').submit();
}
});
}
function filterNotes() {
@@ -553,17 +650,21 @@ function filterNotes() {
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;
// data-label is now comma separated
const cardLabels = (card.dataset.label || '').split(',').map(s=>s.trim());
const okText = !q || t.includes(q) || c.includes(q) || cardLabels.join(' ').includes(q);
const okLabel = !lbl || cardLabels.includes(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();
appConfirm('Delete this note permanently?').then(y => {
if(y) {
document.getElementById('delete-note-id').value = id;
document.getElementById('delete-note-form').submit();
}
});
}
// Fix note card colors in dark mode