feat: implement core frontend infrastructure, notepad management API, and navigation components
This commit is contained in:
parent
1d80b47a3d
commit
62e8d38fbc
14 changed files with 769 additions and 1890 deletions
|
|
@ -6,6 +6,7 @@ from models.note import Note
|
||||||
from models.collaborator import Collaborator
|
from models.collaborator import Collaborator
|
||||||
from models.user import User
|
from models.user import User
|
||||||
from models.favorite import Favorite
|
from models.favorite import Favorite
|
||||||
|
from sqlalchemy import func
|
||||||
import csv
|
import csv
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
|
|
@ -68,13 +69,23 @@ def get_notepads():
|
||||||
query = query.filter_by(is_public=True)
|
query = query.filter_by(is_public=True)
|
||||||
|
|
||||||
pads = query.order_by(Notepad.name).all()
|
pads = query.order_by(Notepad.name).all()
|
||||||
|
|
||||||
|
# Optimize note count query (O(1) database hit instead of O(N))
|
||||||
|
note_counts = dict(db.session.query(Note.notepad_id, func.count(Note.id)).filter(Note.is_hidden == False).group_by(Note.notepad_id).all())
|
||||||
|
|
||||||
|
# Optimize write permissions check
|
||||||
|
collab_write_pad_ids = set()
|
||||||
|
if user_id:
|
||||||
|
collab_write_pad_ids = {c.notepad_id for c in Collaborator.query.filter_by(user_id=user_id, permission='write').all()}
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for pad in pads:
|
for pad in pads:
|
||||||
p_dict = pad.to_dict()
|
note_count = note_counts.get(pad.id, 0)
|
||||||
|
p_dict = pad.to_dict(note_count=note_count)
|
||||||
if user_id:
|
if user_id:
|
||||||
if pad.user_id == user_id:
|
if pad.user_id == user_id:
|
||||||
p_dict['share_token'] = get_share_token('notepad', pad.id)
|
p_dict['share_token'] = get_share_token('notepad', pad.id)
|
||||||
p_dict['can_write'] = check_pad_write_permission(pad, user_id)
|
p_dict['can_write'] = (pad.user_id == user_id) or (pad.id in collab_write_pad_ids)
|
||||||
else:
|
else:
|
||||||
p_dict['can_write'] = False
|
p_dict['can_write'] = False
|
||||||
results.append(p_dict)
|
results.append(p_dict)
|
||||||
|
|
@ -156,7 +167,6 @@ def get_note(note_id):
|
||||||
n_dict['share_token'] = get_share_token('note', note_id)
|
n_dict['share_token'] = get_share_token('note', note_id)
|
||||||
|
|
||||||
return jsonify(n_dict)
|
return jsonify(n_dict)
|
||||||
|
|
||||||
@api_bp.route('/notes/mine', methods=['GET'])
|
@api_bp.route('/notes/mine', methods=['GET'])
|
||||||
def get_my_all_notes():
|
def get_my_all_notes():
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
|
|
@ -164,10 +174,14 @@ def get_my_all_notes():
|
||||||
return jsonify({'error': 'Unauthorized'}), 401
|
return jsonify({'error': 'Unauthorized'}), 401
|
||||||
|
|
||||||
notes = Note.query.join(Notepad).filter(Notepad.user_id == user_id, Note.is_hidden == False).all()
|
notes = Note.query.join(Notepad).filter(Notepad.user_id == user_id, 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 = []
|
results = []
|
||||||
for note in notes:
|
for note in notes:
|
||||||
n_dict = note.to_dict()
|
n_dict = note.to_dict()
|
||||||
n_dict['notepad'] = note.notepad.to_dict()
|
n_dict['notepad'] = note.notepad.to_dict(note_count=note_counts.get(note.notepad_id, 0))
|
||||||
n_dict['owner_id'] = user_id
|
n_dict['owner_id'] = user_id
|
||||||
n_dict['is_public'] = note.notepad.is_public
|
n_dict['is_public'] = note.notepad.is_public
|
||||||
n_dict['share_token'] = get_share_token('note', note.id)
|
n_dict['share_token'] = get_share_token('note', note.id)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from flask import Blueprint, request, jsonify, session
|
from flask import Blueprint, request, jsonify, session
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_, func
|
||||||
from extensions import db
|
from extensions import db
|
||||||
from models.note import Note
|
from models.note import Note
|
||||||
from models.notepad import Notepad
|
from models.notepad import Notepad
|
||||||
from models.favorite import Favorite
|
from models.favorite import Favorite
|
||||||
|
from models.collaborator import Collaborator
|
||||||
from .api import check_pad_write_permission
|
from .api import check_pad_write_permission
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
@ -45,13 +46,19 @@ def search_notes():
|
||||||
favs = Favorite.query.filter_by(user_id=user_id).all()
|
favs = Favorite.query.filter_by(user_id=user_id).all()
|
||||||
fav_ids = {f.notepad_id for f in favs}
|
fav_ids = {f.notepad_id for f in favs}
|
||||||
|
|
||||||
|
# Eagerly fetch note counts and permissions to prevent N+1 queries
|
||||||
|
note_counts = dict(db.session.query(Note.notepad_id, func.count(Note.id)).filter(Note.is_hidden == False).group_by(Note.notepad_id).all())
|
||||||
|
collab_write_pad_ids = set()
|
||||||
|
if user_id:
|
||||||
|
collab_write_pad_ids = {c.notepad_id for c in Collaborator.query.filter_by(user_id=user_id, permission='write').all()}
|
||||||
|
|
||||||
# Construct response with extra info for frontend (owner_id, is_public)
|
# Construct response with extra info for frontend (owner_id, is_public)
|
||||||
output = []
|
output = []
|
||||||
for note, pad in results:
|
for note, pad in results:
|
||||||
n_dict = note.to_dict()
|
n_dict = note.to_dict()
|
||||||
p_dict = pad.to_dict()
|
p_dict = pad.to_dict(note_count=note_counts.get(pad.id, 0))
|
||||||
p_dict['is_favorite'] = pad.id in fav_ids
|
p_dict['is_favorite'] = pad.id in fav_ids
|
||||||
p_dict['can_write'] = check_pad_write_permission(pad, user_id)
|
p_dict['can_write'] = (pad.user_id == user_id) or (pad.id in collab_write_pad_ids)
|
||||||
|
|
||||||
n_dict['notepad'] = p_dict
|
n_dict['notepad'] = p_dict
|
||||||
n_dict['owner_id'] = pad.user_id # Keep for map marker compatibility
|
n_dict['owner_id'] = pad.user_id # Keep for map marker compatibility
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ class Notepad(db.Model):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, note_count=None):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'is_public': self.is_public,
|
'is_public': self.is_public,
|
||||||
'owner_id': self.user_id,
|
'owner_id': self.user_id,
|
||||||
'color': self.color,
|
'color': self.color,
|
||||||
'note_count': self.notes.filter_by(is_hidden=False).count()
|
'note_count': note_count if note_count is not None else self.notes.filter_by(is_hidden=False).count()
|
||||||
}
|
}
|
||||||
98
refactor.py
98
refactor.py
|
|
@ -1,98 +0,0 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
base_dir = r'z:\source\forgejo\spotnotes.app\static\assets\js'
|
|
||||||
index_path = os.path.join(base_dir, 'index.js')
|
|
||||||
|
|
||||||
with open(index_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Remove the blocks that were moved to map.js and sidebar.js
|
|
||||||
# initMap
|
|
||||||
content = re.sub(r'function initMap\(\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# initSidebar
|
|
||||||
content = re.sub(r'function initSidebar\(\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# renderMapMarkers
|
|
||||||
content = re.sub(r'function renderMapMarkers\(\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# stopTracking
|
|
||||||
content = re.sub(r'function stopTracking\(\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# updateTrackingUI
|
|
||||||
content = re.sub(r'function updateTrackingUI\(\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# renderNotepadList
|
|
||||||
content = re.sub(r'function renderNotepadList\(pads, containerId, isFavorite\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# setupPublicToggle
|
|
||||||
content = re.sub(r'function setupPublicToggle\(\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
# toggleSection
|
|
||||||
content = re.sub(r'function toggleSection\(event\).*?^}', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
||||||
|
|
||||||
|
|
||||||
# Now, wrap the whole thing or replace globals
|
|
||||||
# It's easier to rewrite index.js using a structured output or targeted regexes.
|
|
||||||
# Let's replace the variable declarations:
|
|
||||||
globals_to_replace = [
|
|
||||||
'map', 'activeNotes', 'userLocation', 'activePadName', 'activePadId',
|
|
||||||
'activePadIsPublic', 'activePadOwnerId', 'activePadCanWrite', 'currentUser',
|
|
||||||
'isTracking', 'userMarker', 'notifiedNoteIds', 'isLoggedIn', 'currentMarkers', 'topZIndex'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Remove the var declarations
|
|
||||||
for var in globals_to_replace:
|
|
||||||
content = re.sub(r'let\s+' + var + r'\s*=[^;]+;\n?', '', content)
|
|
||||||
|
|
||||||
# Prepend window.SpotNotes. to usages
|
|
||||||
for var in globals_to_replace:
|
|
||||||
# Use negative lookbehind/lookahead to avoid matching substring or properties if any
|
|
||||||
content = re.sub(r'(?<!\w|\.)' + var + r'(?!\w)', f'window.SpotNotes.{var}', content)
|
|
||||||
|
|
||||||
# Some function names
|
|
||||||
functions_to_namespace = [
|
|
||||||
'loadNotepads', 'loadNotepadsOfType', 'loadNotesForPad', 'loadSingleNote',
|
|
||||||
'loadAllMyNotes', 'toggleFavorite', 'shareNotepad', 'shareNote',
|
|
||||||
'deleteNotepad', 'deleteNote', 'reportNote', 'openShareSettingsModal',
|
|
||||||
'removeCollaborator', 'displaySearchResults', 'escapeHtml'
|
|
||||||
]
|
|
||||||
|
|
||||||
for func in functions_to_namespace:
|
|
||||||
# replace function definition
|
|
||||||
content = re.sub(r'async function ' + func + r'\(', f'window.SpotNotes.{func} = async function(', content)
|
|
||||||
content = re.sub(r'function ' + func + r'\(', f'window.SpotNotes.{func} = function(', content)
|
|
||||||
content = re.sub(r'window\.' + func + r'\s*=\s*async function\(', f'window.SpotNotes.{func} = async function(', content)
|
|
||||||
content = re.sub(r'window\.' + func + r'\s*=\s*function\(', f'window.SpotNotes.{func} = function(', content)
|
|
||||||
|
|
||||||
# replace usages
|
|
||||||
content = re.sub(r'(?<!\w|\.)' + func + r'\(', f'window.SpotNotes.{func}(', content)
|
|
||||||
# also replace usages as callbacks, e.g. .addEventListener('click', loadNotepads)
|
|
||||||
content = re.sub(r'(?<!\w|\.)' + func + r'(?!\w|\()', f'window.SpotNotes.{func}', content)
|
|
||||||
|
|
||||||
# Also fix the initMap / initSidebar / setupPublicToggle usages
|
|
||||||
for func in ['initMap', 'initSidebar', 'renderMapMarkers', 'stopTracking', 'updateTrackingUI', 'renderNotepadList', 'setupPublicToggle', 'toggleSection']:
|
|
||||||
content = re.sub(r'(?<!\w|\.)' + func + r'\(', f'window.SpotNotes.{func}(', content)
|
|
||||||
content = re.sub(r'(?<!\w|\.)' + func + r'(?!\w|\()', f'window.SpotNotes.{func}', content)
|
|
||||||
|
|
||||||
|
|
||||||
# Add initialization block for state
|
|
||||||
init_block = """window.SpotNotes = window.SpotNotes || {};
|
|
||||||
|
|
||||||
// Globals
|
|
||||||
window.SpotNotes.map = null;
|
|
||||||
window.SpotNotes.activeNotes = [];
|
|
||||||
window.SpotNotes.userLocation = null;
|
|
||||||
window.SpotNotes.activePadName = "";
|
|
||||||
window.SpotNotes.activePadId = null;
|
|
||||||
window.SpotNotes.activePadIsPublic = false;
|
|
||||||
window.SpotNotes.activePadOwnerId = null;
|
|
||||||
window.SpotNotes.activePadCanWrite = false;
|
|
||||||
window.SpotNotes.currentUser = null;
|
|
||||||
window.SpotNotes.isTracking = false;
|
|
||||||
window.SpotNotes.userMarker = null;
|
|
||||||
window.SpotNotes.notifiedNoteIds = new Set();
|
|
||||||
window.SpotNotes.isLoggedIn = false;
|
|
||||||
window.SpotNotes.currentMarkers = [];
|
|
||||||
window.SpotNotes.topZIndex = 1000000;
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(index_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(init_block + content)
|
|
||||||
|
|
||||||
print('Refactored index.js')
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
index_path = r'z:\source\forgejo\spotnotes.app\static\assets\js\index.js'
|
|
||||||
with open(index_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Add the filter input to the header
|
|
||||||
header_replace = """
|
|
||||||
<div style="display:flex; align-items:center;">
|
|
||||||
<button id="btn-back-pads" style="background:none; border:none; font-size:1.5rem; cursor:pointer; margin-right:10px;">←</button>
|
|
||||||
<h2 style="font-size:1.2rem; margin:0;">${window.SpotNotes.escapeHtml(currentName)}</h2>
|
|
||||||
<button onclick="window.SpotNotes.shareNotepad(${padId}, '${token || ''}')" style="border:none; background:none; cursor:pointer; margin-left:10px;" title="Share">🔗</button>
|
|
||||||
${manageShareBtn}
|
|
||||||
${exportBtn}
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 10px;">
|
|
||||||
<input type="text" id="filter-active-notes" placeholder="Filter notes..." style="width: 100%; padding: 5px; margin-bottom: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;">
|
|
||||||
</div>
|
|
||||||
${addNoteBtn}
|
|
||||||
"""
|
|
||||||
content = content.replace("""
|
|
||||||
<div style="display:flex; align-items:center;">
|
|
||||||
<button id="btn-back-pads" style="background:none; border:none; font-size:1.5rem; cursor:pointer; margin-right:10px;">←</button>
|
|
||||||
<h2 style="font-size:1.2rem; margin:0;">${window.SpotNotes.escapeHtml(currentName)}</h2>
|
|
||||||
<button onclick="window.SpotNotes.shareNotepad(${padId}, '${token || ''}')" style="border:none; background:none; cursor:pointer; margin-left:10px;" title="Share">🔗</button>
|
|
||||||
${manageShareBtn}
|
|
||||||
${exportBtn}
|
|
||||||
</div>
|
|
||||||
${addNoteBtn}
|
|
||||||
""", header_replace)
|
|
||||||
|
|
||||||
# Now, extract the rendering logic into a new function `window.SpotNotes.renderActiveNotesList`
|
|
||||||
old_render_block = """
|
|
||||||
// Render Sidebar List
|
|
||||||
sidebarBody.innerHTML = '';
|
|
||||||
if (notes.length === 0) {
|
|
||||||
sidebarBody.innerHTML = '<p style="padding:10px; color:#666;">No notes yet.</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
notes.forEach(note => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'sidebar-note-item';
|
|
||||||
|
|
||||||
let actionBtns = '';
|
|
||||||
if (window.SpotNotes.activePadCanWrite) {
|
|
||||||
const escapedTitle = window.SpotNotes.escapeHtml(note.title).replace(/'/g, "\\\\'").replace(/"/g, '"');
|
|
||||||
const escapedContent = window.SpotNotes.escapeHtml(note.content).replace(/'/g, "\\\\'").replace(/"/g, '"').replace(/\\n/g, '\\\\n');
|
|
||||||
actionBtns = `
|
|
||||||
<button onclick="event.stopPropagation(); openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')" style="font-size:0.7rem; color:#007bff; border:none; background:none; cursor:pointer; text-decoration:underline;">Edit</button>
|
|
||||||
<button onclick="event.stopPropagation(); window.SpotNotes.deleteNote(${note.id})" style="font-size:0.7rem; color:#dc3545; border:none; background:none; cursor:pointer; text-decoration:underline;">Delete</button>
|
|
||||||
`;
|
|
||||||
} else if (window.SpotNotes.activePadIsPublic) {
|
|
||||||
actionBtns = `<button onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})" style="font-size:0.7rem; color:orange; border:none; background:none; cursor:pointer; text-decoration:underline;">Report</button>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateStr = new Date(note.created_at).toLocaleString();
|
|
||||||
|
|
||||||
item.innerHTML = `
|
|
||||||
<div style="font-weight:bold;">${window.SpotNotes.escapeHtml(note.title)}</div>
|
|
||||||
<div style="font-size:0.8rem; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${window.SpotNotes.escapeHtml(note.content)}</div>
|
|
||||||
<div style="font-size:0.7rem; color:#888; margin-top:2px;">
|
|
||||||
${dateStr} <br> ${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}
|
|
||||||
</div>
|
|
||||||
<div style="text-align:right; margin-top:4px;">
|
|
||||||
<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" onclick="event.stopPropagation();" style="font-size:0.7rem; color:#28a745; text-decoration:underline; margin-right: 5px;">Directions</a>
|
|
||||||
${actionBtns}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
item.addEventListener('click', () => {
|
|
||||||
window.SpotNotes.stopTracking();
|
|
||||||
window.SpotNotes.map.setView([note.lat, note.lng], 16);
|
|
||||||
});
|
|
||||||
sidebarBody.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.SpotNotes.renderMapMarkers();
|
|
||||||
"""
|
|
||||||
|
|
||||||
new_render_call = """
|
|
||||||
// Render Sidebar List
|
|
||||||
window.SpotNotes.renderActiveNotesList();
|
|
||||||
"""
|
|
||||||
|
|
||||||
content = content.replace(old_render_block, new_render_call)
|
|
||||||
|
|
||||||
# Add event listener for the filter input inside loadNotesForPad
|
|
||||||
filter_listener = """
|
|
||||||
const filterInput = document.getElementById('filter-active-notes');
|
|
||||||
if (filterInput) {
|
|
||||||
filterInput.addEventListener('keyup', (e) => {
|
|
||||||
window.SpotNotes.renderActiveNotesList(e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
content = content.replace(new_render_call, new_render_call + filter_listener)
|
|
||||||
|
|
||||||
|
|
||||||
# Now append the function window.SpotNotes.renderActiveNotesList to the end of the file
|
|
||||||
render_function = """
|
|
||||||
window.SpotNotes.renderActiveNotesList = function(filterText = '') {
|
|
||||||
const sidebarBody = document.getElementById('sidebar-body');
|
|
||||||
if (!sidebarBody) return;
|
|
||||||
|
|
||||||
sidebarBody.innerHTML = '';
|
|
||||||
|
|
||||||
const filterLower = filterText.toLowerCase();
|
|
||||||
const filteredNotes = window.SpotNotes.activeNotes.filter(note => {
|
|
||||||
return note.title.toLowerCase().includes(filterLower) || note.content.toLowerCase().includes(filterLower);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filteredNotes.length === 0) {
|
|
||||||
sidebarBody.innerHTML = '<p style="padding:10px; color:#666;">No notes match.</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredNotes.forEach(note => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'sidebar-note-item';
|
|
||||||
|
|
||||||
let actionBtns = '';
|
|
||||||
if (window.SpotNotes.activePadCanWrite) {
|
|
||||||
const escapedTitle = window.SpotNotes.escapeHtml(note.title).replace(/'/g, "\\\\'").replace(/"/g, '"');
|
|
||||||
const escapedContent = window.SpotNotes.escapeHtml(note.content).replace(/'/g, "\\\\'").replace(/"/g, '"').replace(/\\n/g, '\\\\n');
|
|
||||||
actionBtns = `
|
|
||||||
<button onclick="event.stopPropagation(); openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')" style="font-size:0.7rem; color:#007bff; border:none; background:none; cursor:pointer; text-decoration:underline;">Edit</button>
|
|
||||||
<button onclick="event.stopPropagation(); window.SpotNotes.deleteNote(${note.id})" style="font-size:0.7rem; color:#dc3545; border:none; background:none; cursor:pointer; text-decoration:underline;">Delete</button>
|
|
||||||
`;
|
|
||||||
} else if (window.SpotNotes.activePadIsPublic) {
|
|
||||||
actionBtns = `<button onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})" style="font-size:0.7rem; color:orange; border:none; background:none; cursor:pointer; text-decoration:underline;">Report</button>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateStr = new Date(note.created_at).toLocaleString();
|
|
||||||
|
|
||||||
item.innerHTML = `
|
|
||||||
<div style="font-weight:bold;">${window.SpotNotes.escapeHtml(note.title)}</div>
|
|
||||||
<div style="font-size:0.8rem; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${window.SpotNotes.escapeHtml(note.content)}</div>
|
|
||||||
<div style="font-size:0.7rem; color:#888; margin-top:2px;">
|
|
||||||
${dateStr} <br> ${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}
|
|
||||||
</div>
|
|
||||||
<div style="text-align:right; margin-top:4px;">
|
|
||||||
<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" onclick="event.stopPropagation();" style="font-size:0.7rem; color:#28a745; text-decoration:underline; margin-right: 5px;">Directions</a>
|
|
||||||
${actionBtns}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
item.addEventListener('click', () => {
|
|
||||||
window.SpotNotes.stopTracking();
|
|
||||||
window.SpotNotes.map.setView([note.lat, note.lng], 16);
|
|
||||||
});
|
|
||||||
sidebarBody.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
// We do NOT call window.SpotNotes.renderMapMarkers() here, because we only want to filter the sidebar, not the map itself.
|
|
||||||
// Wait, the task says: "filtering the existing activeNotes array before re-rendering the sidebar list".
|
|
||||||
// It doesn't strictly say to filter the map markers, but maybe we should?
|
|
||||||
// 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).
|
|
||||||
};
|
|
||||||
"""
|
|
||||||
|
|
||||||
content += "\n" + render_function
|
|
||||||
|
|
||||||
with open(index_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print('Filtered active notes logic applied')
|
|
||||||
|
|
@ -1,46 +1,54 @@
|
||||||
/* Auth & Content Containers */
|
/* Auth Containers */
|
||||||
.auth-container, .content-container {
|
.auth-container, .content-container {
|
||||||
background: white;
|
background: var(--bg-card);
|
||||||
padding: 2rem;
|
padding: 2.5rem;
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-lg);
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow-lg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 20px;
|
margin: 40px auto;
|
||||||
|
transition: var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-container { max-width: 400px; }
|
.auth-container {
|
||||||
.content-container { max-width: 800px; margin-top: 20px; }
|
max-width: 440px;
|
||||||
|
}
|
||||||
/* Forms */
|
.content-container {
|
||||||
.form-group { margin-bottom: 1rem; }
|
max-width: 800px;
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input {
|
/* Auth Header */
|
||||||
width: 100%;
|
.auth-container h2 {
|
||||||
padding: 0.75rem;
|
font-size: 1.5rem;
|
||||||
border: 1px solid #ddd;
|
font-weight: 700;
|
||||||
border-radius: 4px;
|
margin-bottom: 24px;
|
||||||
font-size: 16px; /* Mobile friendly */
|
text-align: center;
|
||||||
min-height: 44px;
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.links { margin-top: 1rem; text-align: center; font-size: 0.9rem; }
|
/* Links */
|
||||||
.links a { color: #007bff; text-decoration: none; display: inline-block; padding: 10px; min-height: 44px; }
|
.links {
|
||||||
.links a:hover { text-decoration: underline; }
|
margin-top: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.links a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.links a:hover {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure buttons on content pages are white */
|
@media (max-width: 768px) {
|
||||||
.content-container .btn { color: white; }
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.auth-container, .content-container {
|
.auth-container, .content-container {
|
||||||
padding: 1.5rem;
|
padding: 2rem 1.5rem;
|
||||||
margin: 10px;
|
margin: 20px 12px;
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 24px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,47 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Color Palette */
|
||||||
|
--primary: #4f46e5; /* Modern Indigo */
|
||||||
|
--primary-hover: #4338ca;
|
||||||
|
--primary-light: #e0e7ff;
|
||||||
|
--secondary: #0ea5e9; /* Ocean sky blue */
|
||||||
|
--secondary-hover: #0284c7;
|
||||||
|
|
||||||
|
/* Backgrounds */
|
||||||
|
--bg-page: #f8fafc;
|
||||||
|
--bg-card: #ffffff;
|
||||||
|
--bg-glass: rgba(255, 255, 255, 0.85);
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border: #e2e8f0;
|
||||||
|
--border-glass: rgba(255, 255, 255, 0.4);
|
||||||
|
--border-focus: #a5b4fc;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text-main: #0f172a; /* Deep slate */
|
||||||
|
--text-muted: #64748b; /* Light slate */
|
||||||
|
--text-on-primary: #ffffff;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-glass: 0 8px 32px 0 rgba(31, 38, 135, 0.08);
|
||||||
|
|
||||||
|
/* Font */
|
||||||
|
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||||
|
|
||||||
|
/* Radii */
|
||||||
|
--radius-sm: 6px;
|
||||||
|
--radius-md: 10px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Reset and Base Styles */
|
/* Reset and Base Styles */
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -8,7 +52,9 @@
|
||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
font-family: var(--font-sans);
|
||||||
|
color: var(--text-main);
|
||||||
|
background-color: var(--bg-page);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,28 +63,122 @@ body.page-body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #f4f4f4;
|
background: radial-gradient(circle at 50% 50%, #f1f5f9 0%, #e2e8f0 100%);
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--text-muted);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generic Components */
|
/* Generic Components */
|
||||||
.btn {
|
.btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem 1.5rem;
|
||||||
color: white;
|
color: var(--text-on-primary);
|
||||||
border: none;
|
border: 1px solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: var(--radius-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-family: var(--font-sans);
|
||||||
transition: opacity 0.2s;
|
font-weight: 500;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: var(--transition);
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: #10b981; /* Emerald green */
|
||||||
|
}
|
||||||
|
.btn-success:hover {
|
||||||
|
background: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-main);
|
||||||
|
border-color: var(--border);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--border);
|
||||||
}
|
}
|
||||||
.btn:hover { opacity: 0.9; }
|
|
||||||
.btn-primary { background: #007bff; }
|
|
||||||
.btn-success { background: #28a745; }
|
|
||||||
.btn-secondary { background: #6c757d; margin-top: 10px; }
|
|
||||||
|
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
|
/* Global Forms & Input Elements styling */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"],
|
||||||
|
.form-group input[type="email"],
|
||||||
|
.form-group input[type="password"],
|
||||||
|
.form-group select,
|
||||||
|
.form-group textarea,
|
||||||
|
input.admin-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
background-color: var(--bg-page);
|
||||||
|
transition: var(--transition);
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"]:focus,
|
||||||
|
.form-group input[type="email"]:focus,
|
||||||
|
.form-group input[type="password"]:focus,
|
||||||
|
.form-group select:focus,
|
||||||
|
.form-group textarea:focus,
|
||||||
|
input.admin-search:focus {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--primary-light);
|
||||||
|
background-color: var(--bg-card);
|
||||||
|
}
|
||||||
|
|
@ -1,49 +1,110 @@
|
||||||
/* Modals */
|
/* Modals overlay */
|
||||||
.modal {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0; left: 0; width: 100%; height: 100%;
|
top: 0;
|
||||||
background: rgba(0,0,0,0.5);
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(15, 23, 42, 0.4);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
animation: modalFadeIn 0.2s ease-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal.hidden { display: none; }
|
.modal.hidden { display: none !important; }
|
||||||
|
|
||||||
|
/* Modal Content Card */
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: white;
|
background: var(--bg-card);
|
||||||
padding: 25px;
|
padding: 30px;
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-lg);
|
||||||
width: 90%;
|
width: 92%;
|
||||||
max-width: 400px;
|
max-width: 440px;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
box-shadow: var(--shadow-lg);
|
||||||
max-height: 90vh;
|
max-height: 85vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
animation: modalScaleUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
/* Close Button */
|
||||||
|
.close-modal {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 18px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-muted);
|
||||||
|
transition: var(--transition);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
.close-modal:hover {
|
||||||
|
color: var(--text-main);
|
||||||
|
background-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Color picker specific */
|
||||||
|
.form-group input[type="color"] {
|
||||||
|
width: 50px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes modalFadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalScaleUp {
|
||||||
|
from { transform: scale(0.96); opacity: 0; }
|
||||||
|
to { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Modals */
|
||||||
|
@media (max-width: 768px) {
|
||||||
.modal {
|
.modal {
|
||||||
align-items: flex-end; /* Align to bottom for better mobile reach or use full height */
|
align-items: flex-end; /* Anchor at the bottom on mobile */
|
||||||
}
|
}
|
||||||
.modal-content {
|
.modal-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
max-width: none;
|
max-width: none;
|
||||||
max-height: 100%;
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
border-radius: 0;
|
padding: 24px;
|
||||||
padding: 20px;
|
max-height: 80vh;
|
||||||
|
animation: modalSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
|
}
|
||||||
|
@keyframes modalSlideUp {
|
||||||
|
from { transform: translateY(100%); }
|
||||||
|
to { transform: translateY(0); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-modal {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px; right: 15px;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #aaa;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
.close-modal:hover { color: #333; }
|
|
||||||
|
|
@ -4,26 +4,37 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 60px;
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: var(--bg-glass);
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 15px;
|
padding: 0 24px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow-sm);
|
||||||
flex-shrink: 0
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
font-weight: bold;
|
font-weight: 700;
|
||||||
font-size: 1.2rem;
|
font-size: 1.25rem;
|
||||||
color: #333;
|
color: var(--text-main);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.brand img {
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.brand:hover img {
|
||||||
|
transform: scale(1.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
.nav-links {
|
||||||
|
|
@ -31,45 +42,94 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links a {
|
.nav-links a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #555;
|
color: var(--text-muted);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
transition: color 0.2s;
|
font-weight: 500;
|
||||||
|
transition: var(--transition);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
padding: 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links a:hover { color: #000; }
|
.nav-links a:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--primary);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: right;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
.nav-links a:hover::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
|
||||||
/* Search Bar in Navbar */
|
/* Search Bar in Navbar */
|
||||||
.nav-search {
|
.nav-search {
|
||||||
flex: 0 1 400px;
|
flex: 0 1 420px;
|
||||||
margin: 0 15px;
|
margin: 0 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.nav-search form {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.nav-search form:focus-within {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-search input {
|
.nav-search input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 10px;
|
padding: 10px 16px;
|
||||||
border: 1px solid #ccc;
|
border: none;
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 16px; /* Prevents zoom on iOS */
|
font-family: var(--font-sans);
|
||||||
min-height: 44px;
|
font-size: 0.95rem;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-main);
|
||||||
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
|
.nav-search input::placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-search button {
|
.nav-search button {
|
||||||
padding: 5px 12px;
|
padding: 0 16px;
|
||||||
border: 1px solid #ccc;
|
border: none;
|
||||||
border-left: none;
|
background: transparent;
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-height: 44px;
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
transition: var(--transition);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.nav-search button:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
background-color: var(--bg-page);
|
||||||
}
|
}
|
||||||
.nav-search button:hover { background: #e2e6ea; }
|
|
||||||
|
|
||||||
.nav-toggle {
|
.nav-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -77,37 +137,27 @@
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #333;
|
color: var(--text-main);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Navbar */
|
/* Mobile Navbar */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 768px) {
|
||||||
.navbar {
|
.navbar {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 10px;
|
padding: 12px 16px;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.brand {
|
.brand {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
.nav-toggle {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
.nav-links {
|
.nav-links {
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
order: 2;
|
order: 2;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
gap: 12px;
|
||||||
gap: 10px;
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.nav-links a {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
}
|
||||||
.nav-search {
|
.nav-search {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,54 @@
|
||||||
/* Right Collapsible Sidebar */
|
/* Right Collapsible Floating Sidebar */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50px; /* Below navbar */
|
top: 75px; /* Below navbar (60px) + margin */
|
||||||
right: 0;
|
right: 15px;
|
||||||
bottom: 0;
|
bottom: 15px;
|
||||||
width: 320px;
|
width: 340px;
|
||||||
background-color: white;
|
background-color: var(--bg-glass);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow-lg);
|
||||||
transition: transform 0.3s ease-in-out;
|
border: 1px solid var(--border-glass);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: visible; /* Let toggle stick out */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar.collapsed {
|
.sidebar.collapsed {
|
||||||
transform: translateX(320px);
|
transform: translateX(calc(100% + 20px));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toggle Button attached to sidebar */
|
/* Toggle Button attached to sidebar */
|
||||||
.sidebar-toggle {
|
.sidebar-toggle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -40px;
|
left: -40px;
|
||||||
top: 10px;
|
top: 20px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 48px;
|
||||||
background-color: white;
|
background-color: var(--bg-glass);
|
||||||
border: none;
|
backdrop-filter: blur(20px);
|
||||||
border-radius: 5px 0 0 5px;
|
-webkit-backdrop-filter: blur(20px);
|
||||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
border: 1px solid var(--border-glass);
|
||||||
|
border-right: none;
|
||||||
|
border-radius: var(--radius-md) 0 0 var(--radius-md);
|
||||||
|
box-shadow: -4px 4px 12px rgba(0,0,0,0.08);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: bold;
|
font-size: 1.1rem;
|
||||||
color: #555;
|
color: var(--text-muted);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 1.2rem;
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.sidebar-toggle:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
background-color: var(--bg-card);
|
||||||
|
width: 42px;
|
||||||
|
left: -42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-content {
|
.sidebar-content {
|
||||||
|
|
@ -47,138 +61,326 @@
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
padding: 15px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--border);
|
||||||
background: #fff;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.sidebar-header h2 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar-body {
|
#sidebar-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-new-pad {
|
.btn-new-pad {
|
||||||
background-color: #007bff;
|
background-color: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
width: 30px;
|
width: 32px;
|
||||||
height: 30px;
|
height: 32px;
|
||||||
border-radius: 50%;
|
border-radius: var(--radius-full);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
line-height: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
transition: var(--transition);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.btn-new-pad:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-center-location {
|
.btn-center-location {
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
margin: 15px;
|
margin: 10px 15px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #17a2b8;
|
background-color: var(--secondary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: var(--radius-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
transition: var(--transition);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.btn-center-location:hover {
|
||||||
|
background-color: var(--secondary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
.btn-center-location:hover { background-color: #138496; }
|
|
||||||
|
|
||||||
.btn-add-note {
|
.btn-add-note {
|
||||||
background-color: #28a745;
|
background-color: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 5px 10px;
|
padding: 8px 14px;
|
||||||
border-radius: 4px;
|
border-radius: var(--radius-md);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
.btn-add-note:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
.btn-add-note:hover { background-color: #218838; }
|
|
||||||
|
|
||||||
/* Sidebar Lists & Items */
|
/* Sidebar Lists & Items */
|
||||||
.list-section { border-bottom: 1px solid #f0f0f0; }
|
.list-section {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.list-section:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
.list-section-header,
|
.list-section-header,
|
||||||
.btn-toggle-section {
|
.btn-toggle-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 12px 15px;
|
padding: 14px 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: #555;
|
color: var(--text-muted);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 1px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
.list-section-header:hover,
|
|
||||||
.btn-toggle-section:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notepad-item, .sidebar-note-item {
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.notepad-item:hover, .sidebar-note-item:hover {
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Collaborator List in Share Modal */
|
|
||||||
.collaborator-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 0;
|
transition: var(--transition);
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
}
|
||||||
.collaborator-item:last-child {
|
.list-section-header:hover,
|
||||||
border-bottom: none;
|
.btn-toggle-section:hover {
|
||||||
}
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
.collaborator-item button {
|
color: var(--text-main);
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #dc3545;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Sidebar */
|
/* Notepad Cards inside list */
|
||||||
@media (max-width: 600px) {
|
.notepad-card {
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.03);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.notepad-card:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notepad-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notepad-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notepad-color-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notepad-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-icon {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: var(--transition);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.btn-action-icon:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
color: var(--text-main);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-delete:hover {
|
||||||
|
background-color: #fee2e2;
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
.btn-action-fav {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.btn-action-fav:hover {
|
||||||
|
color: #ec4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notepad-card-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-count {
|
||||||
|
background-color: var(--primary-light);
|
||||||
|
color: var(--primary);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
/* Note Cards in active pad view */
|
||||||
|
.note-card {
|
||||||
|
padding: 14px 20px;
|
||||||
|
margin: 8px 12px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.note-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border-color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-body {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-meta {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-link {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #000000 !important;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
.note-card-link:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-link.directions {
|
||||||
|
background-color: #d1fae5;
|
||||||
|
color: #065f46 !important;
|
||||||
|
}
|
||||||
|
.note-card-link.directions:hover {
|
||||||
|
background-color: #a7f3d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-link.share {
|
||||||
|
background-color: #6bbbf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-link.edit {
|
||||||
|
background-color: #cce5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-card-link.delete,
|
||||||
|
.note-card-link.report {
|
||||||
|
background-color: #ffcccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Sidebar Adaptation */
|
||||||
|
@media (max-width: 768px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 100%;
|
width: calc(100% - 24px);
|
||||||
max-width: none;
|
margin: 12px;
|
||||||
top: auto;
|
top: auto;
|
||||||
bottom: 0;
|
bottom: 12px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 60vh;
|
height: 45vh;
|
||||||
border-radius: 20px 20px 0 0;
|
border-radius: var(--radius-lg);
|
||||||
box-shadow: 0 -4px 15px rgba(0,0,0,0.15);
|
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
}
|
}
|
||||||
.sidebar.collapsed {
|
.sidebar.collapsed {
|
||||||
transform: translateY(100%);
|
transform: translateY(calc(100% + 20px));
|
||||||
}
|
}
|
||||||
.sidebar-toggle {
|
.sidebar-toggle {
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: -30px;
|
right: auto;
|
||||||
|
top: -40px;
|
||||||
|
width: 48px;
|
||||||
|
height: 40px;
|
||||||
|
border: 1px solid var(--border-glass);
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
||||||
transform: translateX(-50%) rotate(90deg);
|
transform: translateX(-50%) rotate(90deg);
|
||||||
width: 60px;
|
box-shadow: 0 -4px 10px rgba(0,0,0,0.05);
|
||||||
height: 30px;
|
}
|
||||||
border-radius: 5px 0 0 5px;
|
.sidebar-toggle:hover {
|
||||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
width: 48px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(90deg) scale(1.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,339 +0,0 @@
|
||||||
/* Reset and Base Styles */
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
overflow: hidden; /* Prevent scrollbars, map handles panning */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Map Container */
|
|
||||||
#map {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Map Crosshair */
|
|
||||||
#map-crosshair {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 900;
|
|
||||||
}
|
|
||||||
#map-crosshair::before, #map-crosshair::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(0,0,0,0.6);
|
|
||||||
}
|
|
||||||
#map-crosshair::before { top: 9px; left: 0; width: 20px; height: 2px; }
|
|
||||||
#map-crosshair::after { top: 0; left: 9px; width: 2px; height: 20px; }
|
|
||||||
|
|
||||||
/* Top Navigation Bar */
|
|
||||||
.navbar {
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 20px;
|
|
||||||
z-index: 1000; /* Above map */
|
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #333;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #555;
|
|
||||||
margin-left: 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links a:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search Bar in Navbar */
|
|
||||||
.nav-search {
|
|
||||||
flex: 1;
|
|
||||||
margin: 0 20px;
|
|
||||||
display: flex;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
.nav-search input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.nav-search button {
|
|
||||||
padding: 5px 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-left: none;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.nav-search button:hover {
|
|
||||||
background: #e2e6ea;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Right Collapsible Sidebar */
|
|
||||||
.sidebar {
|
|
||||||
position: absolute;
|
|
||||||
top: 60px; /* Below navbar */
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 300px;
|
|
||||||
background-color: white;
|
|
||||||
z-index: 1000;
|
|
||||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar.collapsed {
|
|
||||||
transform: translateX(300px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toggle Button attached to sidebar */
|
|
||||||
.sidebar-toggle {
|
|
||||||
position: absolute;
|
|
||||||
left: -40px;
|
|
||||||
top: 10px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background-color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px 0 0 5px;
|
|
||||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-content {
|
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-new-pad {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Center Location Button */
|
|
||||||
.btn-center-location {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
background-color: #17a2b8;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.btn-center-location:hover { background-color: #138496; }
|
|
||||||
|
|
||||||
/* Add Note Button (Themed) */
|
|
||||||
.btn-add-note {
|
|
||||||
background-color: #28a745;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.btn-add-note:hover { background-color: #218838; }
|
|
||||||
|
|
||||||
/* --- Auth & Content Page Styles --- */
|
|
||||||
|
|
||||||
/* Use this class on body for login/register/about pages */
|
|
||||||
body.page-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
background: #f4f4f4;
|
|
||||||
overflow: auto; /* Allow scrolling */
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-container, .content-container {
|
|
||||||
background: white;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-container {
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 20px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
.btn:hover { opacity: 0.9; }
|
|
||||||
.btn-primary { background: #007bff; }
|
|
||||||
.btn-success { background: #28a745; }
|
|
||||||
|
|
||||||
.links { margin-top: 1rem; text-align: center; font-size: 0.9rem; }
|
|
||||||
|
|
||||||
/* Ensure buttons on content pages are white */
|
|
||||||
.content-container .btn { color: white; }
|
|
||||||
|
|
||||||
/* --- Modals --- */
|
|
||||||
.modal {
|
|
||||||
position: fixed;
|
|
||||||
top: 0; left: 0; width: 100%; height: 100%;
|
|
||||||
background: rgba(0,0,0,0.5);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.hidden { display: none; }
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 400px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-modal {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px; right: 15px;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary { background: #6c757d; margin-top: 10px; }
|
|
||||||
|
|
||||||
/* Sidebar Note Items */
|
|
||||||
.sidebar-note-item {
|
|
||||||
padding: 10px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.sidebar-note-item:hover {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Map Note Card (Zoomed In) */
|
|
||||||
.map-note-card {
|
|
||||||
background: white;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
||||||
width: 200px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Sections */
|
|
||||||
.list-section {
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-section-header,
|
|
||||||
.btn-toggle-section {
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
background: #fff;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #555;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
margin: 0;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
.list-section-header:hover,
|
|
||||||
.btn-toggle-section:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notepad-item:hover {
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
@ -133,10 +133,12 @@ window.SpotNotes.loadNotepads = async function(isInitialLoad = false) {
|
||||||
// Reset Sidebar Header
|
// Reset Sidebar Header
|
||||||
const header = document.querySelector('.sidebar-header');
|
const header = document.querySelector('.sidebar-header');
|
||||||
if (header) {
|
if (header) {
|
||||||
let headerHTML = '<h2>Notepads</h2>';
|
let headerHTML = `<div style="display:flex; justify-content:space-between; align-items:center; width:100%;">
|
||||||
|
<h2>Notepads</h2>`;
|
||||||
if (window.SpotNotes.isLoggedIn) {
|
if (window.SpotNotes.isLoggedIn) {
|
||||||
headerHTML += '<button class="btn-new-pad" id="btn-create-pad" title="Create New Pad">+</button>';
|
headerHTML += '<button class="btn-new-pad" id="btn-create-pad" title="Create New Pad">+</button>';
|
||||||
}
|
}
|
||||||
|
headerHTML += '</div>';
|
||||||
header.innerHTML = headerHTML;
|
header.innerHTML = headerHTML;
|
||||||
|
|
||||||
if (window.SpotNotes.isLoggedIn) {
|
if (window.SpotNotes.isLoggedIn) {
|
||||||
|
|
@ -295,20 +297,28 @@ window.SpotNotes.loadNotesForPad = async function(padId, padName, isPublic, owne
|
||||||
const header = document.querySelector('.sidebar-header');
|
const header = document.querySelector('.sidebar-header');
|
||||||
if (header) {
|
if (header) {
|
||||||
const addNoteBtn = window.SpotNotes.activePadCanWrite ? `<button onclick="openCreateNoteModal(${padId})" class="btn-add-note">+ Add Note</button>` : '';
|
const addNoteBtn = window.SpotNotes.activePadCanWrite ? `<button onclick="openCreateNoteModal(${padId})" class="btn-add-note">+ Add Note</button>` : '';
|
||||||
const manageShareBtn = isOwner ? `<button onclick="window.SpotNotes.openShareSettingsModal(${padId}, '${window.SpotNotes.escapeHtml(currentName)}')" style="border:none; background:none; cursor:pointer; margin-left:5px;" title="Manage Sharing">⚙️</button>` : '';
|
const manageShareBtn = isOwner ? `<button class="btn-action-icon" onclick="window.SpotNotes.openShareSettingsModal(${padId}, '${window.SpotNotes.escapeHtml(currentName)}')" title="Manage Sharing">⚙️</button>` : '';
|
||||||
const exportBtn = `<button onclick="window.location.href='/api/notepads/${padId}/export${token ? '?token=' + token : ''}'" style="border:none; background:none; cursor:pointer; margin-left:5px;" title="Export to CSV">⬇️</button>`;
|
const exportBtn = `<button class="btn-action-icon" onclick="window.location.href='/api/notepads/${padId}/export${token ? '?token=' + token : ''}'" title="Export to CSV">⬇️</button>`;
|
||||||
|
const shareBtn = `<button class="btn-action-icon" onclick="window.SpotNotes.shareNotepad(${padId}, '${token || ''}')" title="Share Notepad">🔗</button>`;
|
||||||
|
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<div style="display:flex; align-items:center;">
|
<div class="sidebar-header-top" style="display:flex; align-items:center; justify-content:space-between; width:100%;">
|
||||||
<button id="btn-back-pads" style="background:none; border:none; font-size:1.5rem; cursor:pointer; margin-right:10px;">←</button>
|
<div style="display:flex; align-items:center; gap:8px; min-width:0;">
|
||||||
<h2 style="font-size:1.2rem; margin:0;">${window.SpotNotes.escapeHtml(currentName)}</h2>
|
<button id="btn-back-pads" class="btn-action-icon" style="font-size:1.25rem; margin-right:4px;">←</button>
|
||||||
<button onclick="window.SpotNotes.shareNotepad(${padId}, '${token || ''}')" style="border:none; background:none; cursor:pointer; margin-left:10px;" title="Share">🔗</button>
|
<h2 style="font-size:1.15rem; margin:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" title="${window.SpotNotes.escapeHtml(currentName)}">${window.SpotNotes.escapeHtml(currentName)}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="notepad-actions" style="display:flex; align-items:center; gap:4px; flex-shrink:0;">
|
||||||
|
${shareBtn}
|
||||||
${manageShareBtn}
|
${manageShareBtn}
|
||||||
${exportBtn}
|
${exportBtn}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 10px;">
|
|
||||||
<input type="text" id="filter-active-notes" placeholder="Filter notes..." style="width: 100%; padding: 5px; margin-bottom: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;">
|
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top: 12px; width:100%;">
|
||||||
|
<input type="text" id="filter-active-notes" placeholder="Filter notes..." style="width: 100%; padding: 8px 12px; box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius-md); outline:none; transition:var(--transition); font-family:var(--font-sans); font-size:0.9rem; background-color:var(--bg-page);">
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 10px; width:100%; display:flex; justify-content:space-between; align-items:center;">
|
||||||
${addNoteBtn}
|
${addNoteBtn}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
document.getElementById('btn-back-pads').addEventListener('click', window.SpotNotes.loadNotepads);
|
document.getElementById('btn-back-pads').addEventListener('click', window.SpotNotes.loadNotepads);
|
||||||
}
|
}
|
||||||
|
|
@ -526,25 +536,32 @@ window.SpotNotes.displaySearchResults = function(notes, location, query) {
|
||||||
|
|
||||||
entry.notes.forEach(note => {
|
entry.notes.forEach(note => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'sidebar-note-item';
|
item.className = 'note-card';
|
||||||
const dateStr = new Date(note.created_at).toLocaleString();
|
const dateStr = new Date(note.created_at).toLocaleString();
|
||||||
|
|
||||||
let actionBtn = ''; // Renaming to actionBtns for consistency
|
let actionBtn = '';
|
||||||
if (note.notepad && note.notepad.can_write) {
|
if (note.notepad && note.notepad.can_write) {
|
||||||
const escapedTitle = window.SpotNotes.escapeHtml(note.title).replace(/'/g, "\\'").replace(/"/g, '"');
|
const escapedTitle = window.SpotNotes.escapeHtml(note.title).replace(/'/g, "\\'").replace(/"/g, '"');
|
||||||
const escapedContent = window.SpotNotes.escapeHtml(note.content).replace(/'/g, "\\'").replace(/"/g, '"').replace(/\n/g, '\\n');
|
const escapedContent = window.SpotNotes.escapeHtml(note.content).replace(/'/g, "\\'").replace(/"/g, '"').replace(/\n/g, '\\n');
|
||||||
actionBtn = `<button onclick="event.stopPropagation(); openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')" style="font-size:0.7rem; color:#007bff; border:none; background:none; cursor:pointer; text-decoration:underline;">Edit</button> <button onclick="event.stopPropagation(); window.SpotNotes.deleteNote(${note.id})" style="font-size:0.7rem; color:#dc3545; border:none; background:none; cursor:pointer; text-decoration:underline;">Delete</button>`;
|
actionBtn = `<button class="note-card-link edit" onclick="event.stopPropagation(); openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')">Edit</button> <button class="note-card-link delete" onclick="event.stopPropagation(); window.SpotNotes.deleteNote(${note.id})">Delete</button>`;
|
||||||
} else if (note.is_public) {
|
} else if (note.is_public) {
|
||||||
actionBtn = `<button onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})" style="font-size:0.7rem; color:orange; border:none; background:none; cursor:pointer; text-decoration:underline;">Report</button>`;
|
actionBtn = `<button class="note-card-link report" onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})">Report</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shareToken = note.share_token || '';
|
||||||
|
const shareBtn = `<button class="note-card-link share" onclick="event.stopPropagation(); window.SpotNotes.shareNote(${note.id}, '${shareToken}')">Share</button>`;
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div style="font-weight:bold;">${window.SpotNotes.escapeHtml(note.title)}</div>
|
<div class="note-card-title">${window.SpotNotes.escapeHtml(note.title)}</div>
|
||||||
<div style="font-size:0.8rem; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${window.SpotNotes.escapeHtml(note.content)}</div>
|
<div class="note-card-body">${window.SpotNotes.escapeHtml(note.content)}</div>
|
||||||
<div style="font-size:0.7rem; color:#888; margin-top:2px;">
|
<div class="note-card-meta">
|
||||||
${dateStr} <br> ${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}
|
${dateStr} <br> ${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align:right; margin-top:4px;">${actionBtn}</div>
|
<div class="note-card-actions">
|
||||||
|
<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" onclick="event.stopPropagation();" class="note-card-link directions">Directions</a>
|
||||||
|
${shareBtn}
|
||||||
|
${actionBtn}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('click', () => {
|
||||||
window.SpotNotes.stopTracking();
|
window.SpotNotes.stopTracking();
|
||||||
|
|
@ -761,30 +778,34 @@ window.SpotNotes.renderActiveNotesList = function(filterText = '') {
|
||||||
|
|
||||||
filteredNotes.forEach(note => {
|
filteredNotes.forEach(note => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'sidebar-note-item';
|
item.className = 'note-card';
|
||||||
|
|
||||||
let actionBtns = '';
|
let actionBtns = '';
|
||||||
if (window.SpotNotes.activePadCanWrite) {
|
if (window.SpotNotes.activePadCanWrite) {
|
||||||
const escapedTitle = window.SpotNotes.escapeHtml(note.title).replace(/'/g, "\\'").replace(/"/g, '"');
|
const escapedTitle = window.SpotNotes.escapeHtml(note.title).replace(/'/g, "\\'").replace(/"/g, '"');
|
||||||
const escapedContent = window.SpotNotes.escapeHtml(note.content).replace(/'/g, "\\'").replace(/"/g, '"').replace(/\n/g, '\\n');
|
const escapedContent = window.SpotNotes.escapeHtml(note.content).replace(/'/g, "\\'").replace(/"/g, '"').replace(/\n/g, '\\n');
|
||||||
actionBtns = `
|
actionBtns = `
|
||||||
<button onclick="event.stopPropagation(); openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')" style="font-size:0.7rem; color:#007bff; border:none; background:none; cursor:pointer; text-decoration:underline;">Edit</button>
|
<button class="note-card-link edit" onclick="event.stopPropagation(); openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')">Edit</button>
|
||||||
<button onclick="event.stopPropagation(); window.SpotNotes.deleteNote(${note.id})" style="font-size:0.7rem; color:#dc3545; border:none; background:none; cursor:pointer; text-decoration:underline;">Delete</button>
|
<button class="note-card-link delete" onclick="event.stopPropagation(); window.SpotNotes.deleteNote(${note.id})">Delete</button>
|
||||||
`;
|
`;
|
||||||
} else if (window.SpotNotes.activePadIsPublic) {
|
} else if (window.SpotNotes.activePadIsPublic) {
|
||||||
actionBtns = `<button onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})" style="font-size:0.7rem; color:orange; border:none; background:none; cursor:pointer; text-decoration:underline;">Report</button>`;
|
actionBtns = `<button class="note-card-link report" onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})">Report</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shareToken = note.share_token || '';
|
||||||
|
const shareBtn = `<button class="note-card-link share" onclick="event.stopPropagation(); window.SpotNotes.shareNote(${note.id}, '${shareToken}')" title="Share Note">Share</button>`;
|
||||||
|
|
||||||
const dateStr = new Date(note.created_at).toLocaleString();
|
const dateStr = new Date(note.created_at).toLocaleString();
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div style="font-weight:bold;">${window.SpotNotes.escapeHtml(note.title)}</div>
|
<div class="note-card-title">${window.SpotNotes.escapeHtml(note.title)}</div>
|
||||||
<div style="font-size:0.8rem; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${window.SpotNotes.escapeHtml(note.content)}</div>
|
<div class="note-card-body">${window.SpotNotes.escapeHtml(note.content)}</div>
|
||||||
<div style="font-size:0.7rem; color:#888; margin-top:2px;">
|
<div class="note-card-meta">
|
||||||
${dateStr} <br> ${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}
|
${dateStr} <br> ${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align:right; margin-top:4px;">
|
<div class="note-card-actions">
|
||||||
<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" onclick="event.stopPropagation();" style="font-size:0.7rem; color:#28a745; text-decoration:underline; margin-right: 5px;">Directions</a>
|
<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" onclick="event.stopPropagation();" class="note-card-link directions">Directions</a>
|
||||||
|
${shareBtn}
|
||||||
${actionBtns}
|
${actionBtns}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -66,50 +66,51 @@ window.SpotNotes.renderNotepadList = function(pads, containerId, isFavorite) {
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
if (pads.length === 0) {
|
if (pads.length === 0) {
|
||||||
container.innerHTML = '<p style="padding:10px; color:#999; font-style:italic;">No notepads found.</p>';
|
container.innerHTML = '<p style="padding: 15px; color: var(--text-muted); font-style: italic; font-size: 0.9rem;">No notepads found.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pads.forEach(pad => {
|
pads.forEach(pad => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'notepad-item';
|
item.className = 'notepad-card';
|
||||||
item.style.padding = '10px';
|
|
||||||
item.style.borderBottom = '1px solid #eee';
|
|
||||||
item.style.cursor = 'pointer';
|
|
||||||
|
|
||||||
let icon = pad.is_public ? '🌍' : '🔒';
|
let icon = pad.is_public ? '🌍' : '🔒';
|
||||||
|
|
||||||
let favButton = '';
|
let favButton = '';
|
||||||
const editBtn = (window.SpotNotes.currentUser && pad.owner_id === window.SpotNotes.currentUser.id) ?
|
const editBtn = (window.SpotNotes.currentUser && pad.owner_id === window.SpotNotes.currentUser.id) ?
|
||||||
`<button onclick="event.stopPropagation(); openEditPadModal(${pad.id}, '${window.SpotNotes.escapeHtml(pad.name)}', ${pad.is_public}, '${pad.color || '#3388ff'}')" title="Edit Notepad" style="border:none; background:none; cursor:pointer; padding:0 5px;">✏️</button>`
|
`<button class="btn-action-icon" onclick="event.stopPropagation(); openEditPadModal(${pad.id}, '${window.SpotNotes.escapeHtml(pad.name)}', ${pad.is_public}, '${pad.color || '#3388ff'}')" title="Edit Notepad">✏️</button>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const isOwner = window.SpotNotes.currentUser && pad.owner_id === window.SpotNotes.currentUser.id;
|
const isOwner = window.SpotNotes.currentUser && pad.owner_id === window.SpotNotes.currentUser.id;
|
||||||
const manageShareBtn = isOwner ?
|
const manageShareBtn = isOwner ?
|
||||||
`<button onclick="event.stopPropagation(); openShareSettingsModal(${pad.id}, '${window.SpotNotes.escapeHtml(pad.name)}')" title="Manage Sharing" style="border:none; background:none; cursor:pointer; padding:0 5px;">⚙️</button>`
|
`<button class="btn-action-icon" onclick="event.stopPropagation(); openShareSettingsModal(${pad.id}, '${window.SpotNotes.escapeHtml(pad.name)}')" title="Manage Sharing">⚙️</button>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
if(window.SpotNotes.isLoggedIn){
|
if(window.SpotNotes.isLoggedIn){
|
||||||
const action = isFavorite ? 'unfavorite' : 'favorite';
|
const action = isFavorite ? 'unfavorite' : 'favorite';
|
||||||
const label = isFavorite ? '♥' : '♡';
|
const label = isFavorite ? '♥' : '♡';
|
||||||
favButton = `<button onclick="event.stopPropagation(); window.SpotNotes.toggleFavorite(${pad.id}, '${action}')" style="color:#dc3545; border:none; background:none; cursor:pointer; font-weight:bold; padding:0 5px;">${label}</button>`;
|
favButton = `<button class="btn-action-icon btn-action-fav" onclick="event.stopPropagation(); window.SpotNotes.toggleFavorite(${pad.id}, '${action}')" title="${action === 'favorite' ? 'Favorite' : 'Unfavorite'}">${label}</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteBtn = isOwner ?
|
const deleteBtn = isOwner ?
|
||||||
`<button onclick="event.stopPropagation(); window.SpotNotes.deleteNotepad(${pad.id})" style="color:#dc3545; border:none; background:none; cursor:pointer; font-weight:bold; padding:0 5px;">×</button>`
|
`<button class="btn-action-icon btn-action-delete" onclick="event.stopPropagation(); window.SpotNotes.deleteNotepad(${pad.id})" title="Delete Notepad">×</button>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const shareToken = pad.share_token || '';
|
const shareToken = pad.share_token || '';
|
||||||
const shareBtn = `<button onclick="event.stopPropagation(); window.SpotNotes.shareNotepad(${pad.id}, '${shareToken}')" title="Share Notepad" style="border:none; background:none; cursor:pointer; padding:0 5px;">🔗</button>`;
|
const shareBtn = `<button class="btn-action-icon" onclick="event.stopPropagation(); window.SpotNotes.shareNotepad(${pad.id}, '${shareToken}')" title="Share Notepad">🔗</button>`;
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
<div class="notepad-card-header">
|
||||||
<div style="font-weight:bold;">${icon} ${window.SpotNotes.escapeHtml(pad.name)}</div>
|
<div class="notepad-info">
|
||||||
<div style="display: flex; align-items: center;">
|
<span class="notepad-color-dot" style="background-color: ${pad.color || '#3388ff'}"></span>
|
||||||
${editBtn} ${manageShareBtn} ${shareBtn} ${favButton}
|
<span>${icon} ${window.SpotNotes.escapeHtml(pad.name)}</span>
|
||||||
${deleteBtn}
|
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:0.8rem; color:#666;">${pad.note_count || 0} notes</div>
|
<div class="notepad-actions">
|
||||||
|
${editBtn} ${manageShareBtn} ${shareBtn} ${favButton} ${deleteBtn}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="notepad-card-footer">
|
||||||
|
<span class="badge-count">${pad.note_count || 0} notes</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue