From 2e3694d2f7a35c510252de64023aa0241571c0f8 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Tue, 23 Jun 2026 14:35:17 -0500 Subject: [PATCH] feat: implement collapsable sidebar UI, API blueprints, and core map/sidebar interaction scripts --- blueprints/api.py | 28 ++++++++++++++++++++ static/assets/css/base.css | 26 ++++++++++++++++++ static/assets/css/map.css | 40 +++++++++++++++++++++++++--- static/assets/css/sidebar.css | 39 +++++++++++++++++++++++++++ static/assets/js/index.js | 50 ++++++++++++++++++++--------------- static/assets/js/map.js | 29 +++++++++++++++----- 6 files changed, 181 insertions(+), 31 deletions(-) diff --git a/blueprints/api.py b/blueprints/api.py index b327446..8d4f11f 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -188,6 +188,34 @@ def get_my_all_notes(): results.append(n_dict) return jsonify(results) +@api_bp.route('/notes/default', methods=['GET']) +def get_default_notes(): + user_id = session.get('user_id') + if user_id: + # Get owned notepads + favorite notepads + owned_pad_ids = [p.id for p in Notepad.query.filter_by(user_id=user_id).all()] + fav_pad_ids = [f.notepad_id for f in Favorite.query.filter_by(user_id=user_id).all()] + all_visible_pad_ids = list(set(owned_pad_ids + fav_pad_ids)) + + notes = Note.query.filter(Note.notepad_id.in_(all_visible_pad_ids), Note.is_hidden == False).all() + else: + # Logged out: All public notes + notes = Note.query.join(Notepad).filter(Notepad.is_public == True, Note.is_hidden == False).all() + + # Eagerly load note counts to avoid N+1 queries when calling pad.to_dict() + note_counts = dict(db.session.query(Note.notepad_id, func.count(Note.id)).filter(Note.is_hidden == False).group_by(Note.notepad_id).all()) + + results = [] + for note in notes: + n_dict = note.to_dict() + n_dict['notepad'] = note.notepad.to_dict(note_count=note_counts.get(note.notepad_id, 0)) + n_dict['owner_id'] = note.notepad.user_id + n_dict['is_public'] = note.notepad.is_public + if user_id and note.notepad.user_id == user_id: + n_dict['share_token'] = get_share_token('note', note.id) + results.append(n_dict) + return jsonify(results) + @api_bp.route('/notes', methods=['POST']) def create_note(): user_id = session.get('user_id') diff --git a/static/assets/css/base.css b/static/assets/css/base.css index eb06dc7..4bad794 100644 --- a/static/assets/css/base.css +++ b/static/assets/css/base.css @@ -181,4 +181,30 @@ input.admin-search:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light); background-color: var(--bg-card); +} + +/* Mobile Forms & Input Elements styling adjustments */ +@media (max-width: 768px) { + .btn { + padding: 0.6rem 1.2rem; + font-size: 0.9rem; + min-height: 40px; + } + .form-group { + margin-bottom: 1rem; + } + .form-group label { + margin-bottom: 0.35rem; + font-size: 0.8rem; + } + .form-group input[type="text"], + .form-group input[type="email"], + .form-group input[type="password"], + .form-group select, + .form-group textarea, + input.admin-search { + padding: 8px 12px; + font-size: 0.9rem; + min-height: 38px; + } } \ No newline at end of file diff --git a/static/assets/css/map.css b/static/assets/css/map.css index 952e4aa..5d75f0e 100644 --- a/static/assets/css/map.css +++ b/static/assets/css/map.css @@ -29,13 +29,11 @@ /* Map Note Card (Zoomed In) */ .map-note-card { - background: white; - padding: 8px; + background: transparent; border-radius: 4px; - box-shadow: 0 2px 8px rgba(0,0,0,0.3); + box-shadow: var(--shadow-md); width: 200px; font-size: 0.85rem; - border: 1px solid #ddd; white-space: normal; user-select: none; } @@ -101,4 +99,38 @@ .custom-div-icon { background: transparent; border: none; +} + +/* Custom Leaflet Popup Overrides */ +.leaflet-popup-content-wrapper { + padding: 0 !important; + overflow: hidden; + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + background: var(--bg-card); + border: 1px solid var(--border); +} +.leaflet-popup-content { + margin: 0 !important; + width: 240px !important; + font-family: var(--font-sans); +} + +.popup-note-card { + display: flex; + flex-direction: column; +} +.popup-note-header { + font-family: var(--font-sans); + text-shadow: 0 1px 2px rgba(0,0,0,0.15); +} +.popup-note-body { + padding: 12px 14px; + font-size: 0.85rem; + color: var(--text-main); + background-color: var(--bg-card); +} +.popup-note-body .note-card-link { + margin-top: 4px; + margin-right: 4px; } \ No newline at end of file diff --git a/static/assets/css/sidebar.css b/static/assets/css/sidebar.css index 3d6830b..44f3ebc 100644 --- a/static/assets/css/sidebar.css +++ b/static/assets/css/sidebar.css @@ -383,4 +383,43 @@ left: 50%; transform: translateX(-50%) rotate(90deg) scale(1.05); } + .sidebar-header { + padding: 12px 16px; + gap: 6px; + } + .sidebar-header h2 { + font-size: 1.1rem; + } + .notepad-card { + padding: 10px 16px; + gap: 4px; + } + .notepad-info { + font-size: 0.85rem; + } + .notepad-card-footer { + padding-left: 18px; + } + .note-card { + padding: 10px 14px; + margin: 6px 10px; + gap: 4px; + } + .note-card-title { + font-size: 0.85rem; + } + .note-card-body { + font-size: 0.8rem; + } + .note-card-meta { + font-size: 0.7rem; + } + .note-card-actions { + margin-top: 4px; + gap: 6px; + } + .note-card-link { + font-size: 0.7rem; + padding: 3px 6px; + } } \ No newline at end of file diff --git a/static/assets/js/index.js b/static/assets/js/index.js index a8f0597..48a0fca 100644 --- a/static/assets/js/index.js +++ b/static/assets/js/index.js @@ -205,27 +205,16 @@ window.SpotNotes.loadNotepads = async function(isInitialLoad = false) { } } - // Initial Load Logic (Check for URL params first, else default behavior based on auth) - if (isInitialLoad === true) { - const params = new URLSearchParams(window.location.search); - if (params.has('pad')) { - const token = params.get('token'); - window.SpotNotes.loadNotesForPad(params.get('pad'), undefined, undefined, undefined, true, token); - } else if (params.has('note')) { - const token = params.get('token'); - window.SpotNotes.loadSingleNote(params.get('note'), token); - } else if (window.SpotNotes.isLoggedIn) { - window.SpotNotes.loadAllMyNotes(); // Default action for logged in users - } else { - try { - const res = await fetch('/api/notepads?type=public'); - const pads = await res.json(); - const globalPad = pads.find(p => p.name === "Global Public Notes"); - if (globalPad) { - window.SpotNotes.loadNotesForPad(globalPad.id, globalPad.name, true, globalPad.owner_id); - } - } catch (err) {} - } + // Initial / Default Load Logic: when no notepad is selected, show default notes on map + const params = new URLSearchParams(window.location.search); + if (isInitialLoad === true && params.has('pad')) { + const token = params.get('token'); + window.SpotNotes.loadNotesForPad(params.get('pad'), undefined, undefined, undefined, true, token); + } else if (isInitialLoad === true && params.has('note')) { + const token = params.get('token'); + window.SpotNotes.loadSingleNote(params.get('note'), token); + } else { + await window.SpotNotes.loadDefaultNotes(); } } @@ -822,3 +811,22 @@ window.SpotNotes.renderActiveNotesList = function(filterText = '') { // Usually a sidebar filter only filters the list if it says "Add a simple, fast text input filter floating above the note list in the sidebar... filtering the existing activeNotes array before re-rendering the sidebar list". // We will leave the map markers as they are to show all active notes. Or wait, if we only render the list, the map shows everything. Let's just leave the map markers as is (calling renderMapMarkers once on load). }; + +window.SpotNotes.loadDefaultNotes = async function() { + try { + const res = await fetch('/api/notes/default'); + if (!res.ok) throw new Error("Failed to load"); + const notes = await res.json(); + window.SpotNotes.activeNotes = notes; + window.SpotNotes.renderMapMarkers(); + + // Also auto-center map bounds cleanly if we have notes and no pad/note query params + const params = new URLSearchParams(window.location.search); + if (notes.length > 0 && !params.has('pad') && !params.has('note')) { + const bounds = L.latLngBounds(notes.map(n => [n.lat, n.lng])); + window.SpotNotes.map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15 }); + } + } catch (err) { + console.error("Error loading default notes:", err); + } +}; diff --git a/static/assets/js/map.js b/static/assets/js/map.js index f804591..8db5f7c 100644 --- a/static/assets/js/map.js +++ b/static/assets/js/map.js @@ -121,11 +121,15 @@ window.SpotNotes.renderMapMarkers = function() { if (showFull) { const icon = L.divIcon({ className: '', - html: `
-
${window.SpotNotes.escapeHtml(note.title)}
-
${window.SpotNotes.escapeHtml(note.content)}
-
${dateStr}
- ${directionsBtn} ${shareBtn} ${actionBtns} + html: `
+ +
+
${window.SpotNotes.escapeHtml(note.content)}
+
${dateStr}
+
${directionsBtn} ${shareBtn} ${actionBtns}
+
`, iconSize: [200, 'auto'], iconAnchor: [100, 0] @@ -142,7 +146,20 @@ window.SpotNotes.renderMapMarkers = function() { popupAnchor: [0, -36] }); marker = L.marker([note.lat, note.lng], { icon: svgIcon }); - marker.bindPopup(`${window.SpotNotes.escapeHtml(note.title)}
${window.SpotNotes.escapeHtml(note.content)}
${dateStr}
${directionsBtn} ${shareBtn} ${actionBtns}`); + + const popupHtml = ` + + `; + marker.bindPopup(popupHtml, { padding: [0, 0] }); } const bumpZIndex = function() {