code refactoring.. addition of marker clumping and search box in the notes tab
This commit is contained in:
parent
46d1af6d41
commit
6c582b059c
13 changed files with 1892 additions and 486 deletions
44
GEMINI.md
44
GEMINI.md
|
|
@ -1,40 +1,10 @@
|
|||
# SpotNotes Future Implementation & Refactoring Plan
|
||||
|
||||
## 1. Refactoring `index.js` (Reducing Complexity)
|
||||
## 1. Feature: Mobile UX & Responsive Overhaul
|
||||
- **Status:** **[To Do]**
|
||||
- **Action Plan:**
|
||||
- `index.js` is currently a monolithic file (~1000 lines) handling global state, Leaflet map initialization, sidebar DOM manipulation, and raw API calls simultaneously.
|
||||
- **Extract Map Logic:** Create `static/assets/js/map.js` to encapsulate `initMap`, `renderMapMarkers`, geolocation tracking, and all Leaflet-specific code.
|
||||
- **Extract UI/Sidebar Logic:** Create `static/assets/js/sidebar.js` to handle `initSidebar`, `renderNotepadList`, HTML generation for note list items, and DOM toggling.
|
||||
- **Extract API & State Logic:** Keep the core data fetching (`loadNotepads`, `loadNotesForPad`, `loadAllMyNotes`) in `index.js` (or a dedicated `data.js`) to act as the central controller.
|
||||
- **Namespace Globals:** Group global variables (`activeNotes`, `activePadId`, `currentUser`, `map`, etc.) into a single `window.SpotNotes` state object to prevent scope pollution and make dependencies explicitly clear across the new script files.
|
||||
|
||||
## 2. Feature: "Get Directions" Button
|
||||
- **Status:** **[To Do]**
|
||||
- **Action Plan:**
|
||||
- **Low-Hanging Fruit:** Add a quick "Directions" or "Navigate" link/button to the map marker popups and sidebar note items.
|
||||
- Clicking the button will easily open a new tab mapping directly to the note's coordinates: `https://www.google.com/maps/dir/?api=1&destination={lat},{lng}`.
|
||||
- Extremely useful UX for a location-based app, requiring absolutely zero backend changes.
|
||||
|
||||
## 3. Feature: Map Marker Clustering
|
||||
- **Status:** **[To Do]**
|
||||
- **Action Plan:**
|
||||
- **Low-Hanging Fruit:** Integrate the official `Leaflet.markercluster` plugin.
|
||||
- Currently, viewing an "All My Notes" view with hundreds of notes in the same city creates a messy, overlapping visual experience and impacts browser performance.
|
||||
- Clustering groups nearby markers into a single circle with a count, automatically expanding when the user clicks or zooms in.
|
||||
- Implementation requires just adding the CDN links to `index.html` and wrapping the markers in a `L.markerClusterGroup()` inside the `renderMapMarkers` loop.
|
||||
|
||||
## 4. Feature: Sort & Filter Active Notes
|
||||
- **Status:** **[To Do]**
|
||||
- **Action Plan:**
|
||||
- **Low-Hanging Fruit:** Add a simple, fast text input filter floating above the note list in the sidebar whenever a notepad is actively viewed.
|
||||
- Allows users to instantly search for a specific note's title or content within a massive notepad *without* triggering a slow, global backend search.
|
||||
- Implementation is 100% frontend logic: listening to the input's `keyup` event and filtering the existing `activeNotes` array before re-rendering the sidebar list.
|
||||
|
||||
## 5. Feature: Custom Marker Colors per Notepad
|
||||
- **Status:** **[To Do]**
|
||||
- **Action Plan:**
|
||||
- Update the `Notepad` database model to include an optional `color` field (defaulting to the standard Leaflet blue).
|
||||
- Add a simple HTML `<input type="color">` selector to the Create/Edit Notepad modals.
|
||||
- Update the Leaflet marker icon logic to apply a CSS tint/filter (or use customized SVG icons) matching the notepad's selected color.
|
||||
- This allows users to visually distinguish between different notes (e.g., "Restaurants" vs "Birding Spots") instantly when looking at the map in mixed views like "All My Notes".
|
||||
- **Audit & Consistency:** Review every application state (Login, Register, Admin Dashboard, Map View) to ensure visual parity with the desktop experience while optimizing for vertical screen constraints.
|
||||
- **Compact Layout & Spacing:** Re-evaluate the spacing and layering of icons, buttons, and UI elements. Aim for a "small and tight" aesthetic that maximizes map visibility without cramping everything into an unreadable jumble.
|
||||
- **Sidebar to Bottom-Sheet:** On mobile devices, explore transitioning the right-side drawer into a "Bottom-Sheet" component (sliding up from the bottom) to provide a more ergonomic, thumb-reachable interface for note lists.
|
||||
- **Enhanced Touch Targets:** Standardize all interactive elements (buttons, icons, notepad list items) to a minimum 44x44px touch target to prevent accidental clicks and improve accessibility.
|
||||
- **Responsive Modals:** Refactor modal windows to occupy 100% of the viewport height/width on small screens, ensuring that form inputs and the "Add Note" textarea remain fully visible above the virtual keyboard.
|
||||
- **Mobile Navigation:** Consolidate the desktop navbar into a streamlined mobile header, utilizing a hamburger menu or action bar to keep the search functionality and account links accessible but unobtrusive.
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ def update_notepad(pad_id):
|
|||
data = request.get_json()
|
||||
notepad.name = data.get('name', notepad.name)
|
||||
notepad.is_public = data.get('is_public', notepad.is_public)
|
||||
notepad.color = data.get('color', notepad.color)
|
||||
db.session.commit()
|
||||
return jsonify(notepad.to_dict())
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class Notepad(db.Model):
|
|||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
is_public = db.Column(db.Boolean, default=False)
|
||||
color = db.Column(db.String(7), default="#3388ff")
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||
|
||||
|
|
|
|||
98
refactor.py
Normal file
98
refactor.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
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')
|
||||
164
refactor_filter.py
Normal file
164
refactor_filter.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
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')
|
||||
|
|
@ -21,12 +21,13 @@ function initModals() {
|
|||
e.preventDefault();
|
||||
const name = document.getElementById('pad-name').value;
|
||||
const isPublic = document.getElementById('pad-public').checked;
|
||||
const color = document.getElementById('pad-color').value;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/notepads', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, is_public: isPublic })
|
||||
body: JSON.stringify({ name, is_public: isPublic, color: color })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
|
|
@ -34,7 +35,7 @@ function initModals() {
|
|||
modalPad.classList.add('hidden');
|
||||
formPad.reset();
|
||||
// Transition to the new notepad
|
||||
if (typeof loadNotesForPad === 'function') loadNotesForPad(newPad.id, newPad.name, newPad.is_public, newPad.owner_id);
|
||||
if (typeof window.SpotNotes.loadNotesForPad === 'function') window.SpotNotes.loadNotesForPad(newPad.id, newPad.name, newPad.is_public, newPad.owner_id);
|
||||
} else {
|
||||
alert('Failed to create notepad');
|
||||
}
|
||||
|
|
@ -60,7 +61,7 @@ function initModals() {
|
|||
const content = document.getElementById('note-content').value;
|
||||
|
||||
// Use current map center
|
||||
const center = map.getCenter();
|
||||
const center = window.SpotNotes.map.getCenter();
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/notes', {
|
||||
|
|
@ -79,7 +80,7 @@ function initModals() {
|
|||
modalNote.classList.add('hidden');
|
||||
formNote.reset();
|
||||
// Refresh notes
|
||||
if (typeof loadNotesForPad === 'function') loadNotesForPad(activeNotepadId);
|
||||
if (typeof window.SpotNotes.loadNotesForPad === 'function') window.SpotNotes.loadNotesForPad(activeNotepadId);
|
||||
} else {
|
||||
alert('Failed to create note');
|
||||
}
|
||||
|
|
@ -98,10 +99,11 @@ function initModals() {
|
|||
}
|
||||
|
||||
// Helper to open edit notepad modal (called from index.js)
|
||||
window.openEditPadModal = function(id, currentName, currentPublic) {
|
||||
window.openEditPadModal = function(id, currentName, currentPublic, currentColor = '#3388ff') {
|
||||
document.getElementById('pad-id-edit').value = id;
|
||||
document.getElementById('pad-name-edit').value = currentName;
|
||||
document.getElementById('pad-public-edit').checked = currentPublic;
|
||||
document.getElementById('pad-color-edit').value = currentColor;
|
||||
document.getElementById('modal-edit-pad').classList.remove('hidden');
|
||||
};
|
||||
|
||||
|
|
@ -118,3 +120,5 @@ function openCreateNoteModal(notepadId) {
|
|||
activeNotepadId = notepadId;
|
||||
document.getElementById('modal-create-note').classList.remove('hidden');
|
||||
}
|
||||
en');
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1024
static/assets/js/index.js.bak
Normal file
1024
static/assets/js/index.js.bak
Normal file
File diff suppressed because it is too large
Load diff
178
static/assets/js/map.js
Normal file
178
static/assets/js/map.js
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
window.SpotNotes = window.SpotNotes || {};
|
||||
|
||||
window.SpotNotes.initMap = function() {
|
||||
// Default view (e.g., Oklahoma area if geolocation fails)
|
||||
window.SpotNotes.map = L.map('map').setView([35.6528, -97.4781], 10);
|
||||
|
||||
// Ask for location
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.watchPosition((position) => {
|
||||
const { latitude, longitude } = position.coords;
|
||||
window.SpotNotes.userLocation = { lat: latitude, lng: longitude };
|
||||
|
||||
const centerBtn = document.getElementById('btn-center-user');
|
||||
if (centerBtn) centerBtn.style.display = 'block';
|
||||
|
||||
if (window.SpotNotes.isTracking) {
|
||||
window.SpotNotes.map.setView([latitude, longitude]);
|
||||
} else if (!window.SpotNotes.userMarker && !new URLSearchParams(window.location.search).has('pad') && !new URLSearchParams(window.location.search).has('note')) {
|
||||
window.SpotNotes.map.setView([latitude, longitude], 13);
|
||||
console.log("Map centered on user location:", latitude, longitude);
|
||||
}
|
||||
|
||||
// Add User Location Dot
|
||||
if (window.SpotNotes.userMarker) {
|
||||
window.SpotNotes.userMarker.setLatLng([latitude, longitude]);
|
||||
} else {
|
||||
window.SpotNotes.userMarker = L.circleMarker([latitude, longitude], {
|
||||
radius: 8,
|
||||
fillColor: "#3388ff",
|
||||
color: "#fff",
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.8
|
||||
}).addTo(window.SpotNotes.map).bindPopup("You are here");
|
||||
}
|
||||
|
||||
// Location-Based Browser Alerts
|
||||
const userLatLng = L.latLng(latitude, longitude);
|
||||
if (window.SpotNotes.activeNotes && window.SpotNotes.activeNotes.length > 0) {
|
||||
window.SpotNotes.activeNotes.forEach(note => {
|
||||
const noteLatLng = L.latLng(note.lat, note.lng);
|
||||
if (userLatLng.distanceTo(noteLatLng) <= 50) {
|
||||
if (!window.SpotNotes.notifiedNoteIds.has(note.id)) {
|
||||
window.SpotNotes.notifiedNoteIds.add(note.id);
|
||||
alert(`You are near a note!\n\n${note.title}\n${note.content}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
console.warn("Geolocation error:", err);
|
||||
}, {
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
maximumAge: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Add OpenStreetMap tiles
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(window.SpotNotes.map);
|
||||
|
||||
// Listen for zoom changes to toggle note display
|
||||
window.SpotNotes.map.on('zoomend', () => {
|
||||
if (window.SpotNotes.activeNotes.length > 0) {
|
||||
window.SpotNotes.renderMapMarkers();
|
||||
}
|
||||
});
|
||||
|
||||
window.SpotNotes.map.on('dragstart', window.SpotNotes.stopTracking);
|
||||
window.SpotNotes.map.on('popupopen', window.SpotNotes.stopTracking);
|
||||
|
||||
// Initialize MarkerClusterGroup
|
||||
window.SpotNotes.markerClusterGroup = L.markerClusterGroup({
|
||||
disableClusteringAtZoom: 16
|
||||
});
|
||||
window.SpotNotes.map.addLayer(window.SpotNotes.markerClusterGroup);
|
||||
};
|
||||
|
||||
window.SpotNotes.renderMapMarkers = function() {
|
||||
// Clear existing
|
||||
if (window.SpotNotes.markerClusterGroup) {
|
||||
window.SpotNotes.markerClusterGroup.clearLayers();
|
||||
}
|
||||
|
||||
// Also remove from map directly just in case (fallback)
|
||||
window.SpotNotes.currentMarkers.forEach(marker => window.SpotNotes.map.removeLayer(marker));
|
||||
window.SpotNotes.currentMarkers = [];
|
||||
|
||||
const zoom = window.SpotNotes.map.getZoom();
|
||||
const showFull = zoom >= 14; // Zoom threshold
|
||||
|
||||
window.SpotNotes.activeNotes.forEach(note => {
|
||||
let marker;
|
||||
let actionBtns = '';
|
||||
|
||||
// Determine permissions: Use note-specific info if available (search results), else fall back to pad globals
|
||||
const isOwner = window.SpotNotes.currentUser && (note.owner_id ? note.owner_id === window.SpotNotes.currentUser.id : window.SpotNotes.activePadOwnerId === window.SpotNotes.currentUser.id);
|
||||
const isPublic = note.is_public !== undefined ? note.is_public : window.SpotNotes.activePadIsPublic;
|
||||
const canWrite = (note.notepad && note.notepad.can_write !== undefined) ? note.notepad.can_write : window.SpotNotes.activePadCanWrite;
|
||||
|
||||
if (canWrite) {
|
||||
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 = `<br><button onclick="openEditNoteModal(${note.id}, '${escapedTitle}', '${escapedContent}')" class="btn-note-edit">✏️ Edit</button><button onclick="window.SpotNotes.deleteNote(${note.id})" class="btn-note-delete">Delete</button>`;
|
||||
} else if (isPublic) {
|
||||
actionBtns = `<br><button onclick="window.SpotNotes.reportNote(${note.id})" class="btn-note-report">Report</button>`;
|
||||
}
|
||||
|
||||
const shareToken = note.share_token || '';
|
||||
const shareBtn = `<button onclick="window.SpotNotes.shareNote(${note.id}, '${shareToken}')" class="btn-note-share">🔗 Share</button>`;
|
||||
|
||||
const dateStr = new Date(note.created_at).toLocaleString();
|
||||
|
||||
const padColor = (note.notepad && note.notepad.color) ? note.notepad.color : '#3388ff';
|
||||
|
||||
const directionsBtn = `<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" class="btn-note-directions" style="display:inline-block; margin-top:5px; margin-right:5px; font-size:0.8rem; text-decoration:none;">🗺️ Directions</a>`;
|
||||
|
||||
if (showFull) {
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<div class="map-note-card" style="border-top: 4px solid ${padColor};">
|
||||
<div style="font-weight:bold; margin-bottom:4px;">${window.SpotNotes.escapeHtml(note.title)}</div>
|
||||
<div style="white-space: pre-wrap;">${window.SpotNotes.escapeHtml(note.content)}</div>
|
||||
<div style="font-size:0.7rem; color:#888; margin-top:4px;">${dateStr}</div>
|
||||
${directionsBtn} ${shareBtn} ${actionBtns}
|
||||
</div>`,
|
||||
iconSize: [200, 'auto'],
|
||||
iconAnchor: [100, 0]
|
||||
});
|
||||
marker = L.marker([note.lat, note.lng], { icon: icon });
|
||||
} else {
|
||||
const svgIcon = L.divIcon({
|
||||
className: 'custom-div-icon',
|
||||
html: `<div style="background-color: ${padColor}; width: 16px; height: 16px; border-radius: 50%; border: 2px solid white; box-shadow: 0 0 4px rgba(0,0,0,0.5);"></div>`,
|
||||
iconSize: [20, 20],
|
||||
iconAnchor: [10, 10],
|
||||
popupAnchor: [0, -10]
|
||||
});
|
||||
marker = L.marker([note.lat, note.lng], { icon: svgIcon });
|
||||
marker.bindPopup(`<b>${window.SpotNotes.escapeHtml(note.title)}</b><br><div style="white-space: pre-wrap;">${window.SpotNotes.escapeHtml(note.content)}</div><span style="font-size:0.7rem; color:#888;">${dateStr}</span><br>${directionsBtn} ${shareBtn} ${actionBtns}`);
|
||||
}
|
||||
|
||||
const bumpZIndex = function() {
|
||||
this.setZIndexOffset(window.SpotNotes.topZIndex++);
|
||||
};
|
||||
marker.on('click', bumpZIndex);
|
||||
marker.on('mousedown', bumpZIndex);
|
||||
|
||||
if (window.SpotNotes.markerClusterGroup) {
|
||||
window.SpotNotes.markerClusterGroup.addLayer(marker);
|
||||
} else {
|
||||
marker.addTo(window.SpotNotes.map);
|
||||
}
|
||||
|
||||
window.SpotNotes.currentMarkers.push(marker);
|
||||
});
|
||||
};
|
||||
|
||||
window.SpotNotes.stopTracking = function() {
|
||||
window.SpotNotes.isTracking = false;
|
||||
window.SpotNotes.updateTrackingUI();
|
||||
};
|
||||
|
||||
window.SpotNotes.updateTrackingUI = function() {
|
||||
const btn = document.getElementById('btn-center-user');
|
||||
if (btn) {
|
||||
if (window.SpotNotes.isTracking) {
|
||||
btn.textContent = "Tracking Location...";
|
||||
btn.style.backgroundColor = "#28a745";
|
||||
} else {
|
||||
btn.textContent = "Target My Location";
|
||||
btn.style.backgroundColor = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -25,8 +25,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
// If notes found, display them and stop (don't geocode)
|
||||
if (notes.length > 0) {
|
||||
if (window.displaySearchResults) {
|
||||
window.displaySearchResults(notes, null, query);
|
||||
if (window.SpotNotes && window.SpotNotes.displaySearchResults) {
|
||||
window.SpotNotes.displaySearchResults(notes, null, query);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -48,8 +48,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
|
||||
// 3. Update UI via index.js helper
|
||||
if (window.displaySearchResults) {
|
||||
window.displaySearchResults(notes, location, query);
|
||||
if (window.SpotNotes && window.SpotNotes.displaySearchResults) {
|
||||
window.SpotNotes.displaySearchResults(notes, location, query);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
127
static/assets/js/sidebar.js
Normal file
127
static/assets/js/sidebar.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
window.SpotNotes = window.SpotNotes || {};
|
||||
|
||||
window.SpotNotes.initSidebar = function() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const toggleBtn = document.getElementById('sidebar-toggle');
|
||||
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
sidebar.classList.toggle('collapsed');
|
||||
// Change arrow direction based on state
|
||||
if (sidebar.classList.contains('collapsed')) {
|
||||
const sections = document.querySelectorAll('.list-section');
|
||||
sections.forEach(section => {
|
||||
const content = section.querySelector('.list-section-content');
|
||||
if (content) content.style.display = 'none';
|
||||
});
|
||||
toggleBtn.innerHTML = '<'; // Point left (open)
|
||||
} else {
|
||||
toggleBtn.innerHTML = '>'; // Point right (close)
|
||||
}
|
||||
});
|
||||
|
||||
const centerBtn = document.getElementById('btn-center-user');
|
||||
if (centerBtn) {
|
||||
centerBtn.addEventListener('click', () => {
|
||||
window.SpotNotes.isTracking = true;
|
||||
window.SpotNotes.updateTrackingUI();
|
||||
if (window.SpotNotes.userLocation) {
|
||||
window.SpotNotes.map.setView([window.SpotNotes.userLocation.lat, window.SpotNotes.userLocation.lng], 16);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.SpotNotes.setupPublicToggle = function() {
|
||||
const btn = document.getElementById('btn-toggle-public');
|
||||
const list = document.getElementById('public-notepads-list');
|
||||
|
||||
if (!btn || !list) return;
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
if (list.style.display === 'none') {
|
||||
list.style.display = 'block';
|
||||
btn.innerHTML = 'Public Notepads ▾';
|
||||
|
||||
// Lazy load if empty
|
||||
if (list.innerHTML === '') {
|
||||
list.innerHTML = '<p style="padding:10px; color:#666;">Loading...</p>';
|
||||
try {
|
||||
const res = await fetch('/api/notepads?type=public');
|
||||
const pads = await res.json();
|
||||
window.SpotNotes.renderNotepadList(pads, 'public-notepads-list');
|
||||
} catch (err) {
|
||||
list.innerHTML = '<p style="color:red; padding:10px;">Error loading public pads.</p>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list.style.display = 'none';
|
||||
btn.innerHTML = 'Public Notepads ▸';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.SpotNotes.renderNotepadList = function(pads, containerId, isFavorite) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
|
||||
if (pads.length === 0) {
|
||||
container.innerHTML = '<p style="padding:10px; color:#999; font-style:italic;">No notepads found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
pads.forEach(pad => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'notepad-item';
|
||||
item.style.padding = '10px';
|
||||
item.style.borderBottom = '1px solid #eee';
|
||||
item.style.cursor = 'pointer';
|
||||
|
||||
let icon = pad.is_public ? '🌍' : '🔒';
|
||||
|
||||
let favButton = '';
|
||||
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>`
|
||||
: '';
|
||||
|
||||
const isOwner = window.SpotNotes.currentUser && pad.owner_id === window.SpotNotes.currentUser.id;
|
||||
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>`
|
||||
: '';
|
||||
|
||||
if(window.SpotNotes.isLoggedIn){
|
||||
const action = isFavorite ? 'unfavorite' : 'favorite';
|
||||
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>`;
|
||||
}
|
||||
|
||||
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>`
|
||||
: '';
|
||||
|
||||
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>`;
|
||||
|
||||
item.innerHTML = `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<div style="font-weight:bold;">${icon} ${window.SpotNotes.escapeHtml(pad.name)}</div>
|
||||
<div style="display: flex; align-items: center;">
|
||||
${editBtn} ${manageShareBtn} ${shareBtn} ${favButton}
|
||||
${deleteBtn}
|
||||
</div>
|
||||
<div style="font-size:0.8rem; color:#666;">${pad.note_count || 0} notes</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
item.addEventListener('click', () => window.SpotNotes.loadNotesForPad(pad.id, pad.name, pad.is_public, pad.owner_id, false, null, pad.can_write));
|
||||
container.appendChild(item);
|
||||
});
|
||||
};
|
||||
|
||||
window.SpotNotes.toggleSection = function(event) {
|
||||
const section = event.target.closest('.list-section');
|
||||
const content = section.querySelector('.list-section-content');
|
||||
const isCollapsed = content.style.display === 'none';
|
||||
content.style.display = isCollapsed ? 'block' : 'none';
|
||||
event.target.innerHTML = isCollapsed ? event.target.innerHTML.replace('▸', '▾') : event.target.innerHTML.replace('▾', '▸');
|
||||
};
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin=""/>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" />
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<link rel="stylesheet" href="/assets/css/base.css">
|
||||
|
|
@ -100,6 +102,10 @@
|
|||
<input type="checkbox" id="pad-public-edit" style="width:auto;"> Make Public
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Marker Color</label>
|
||||
<input type="color" id="pad-color-edit" value="#3388ff">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -177,7 +183,10 @@
|
|||
|
||||
<!-- Scripts -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
||||
<script src="/assets/js/nav.js"></script>
|
||||
<script src="/assets/js/map.js"></script>
|
||||
<script src="/assets/js/sidebar.js"></script>
|
||||
<script src="/assets/js/index.js"></script>
|
||||
<script src="/assets/js/actions.js"></script>
|
||||
<script src="/assets/js/search.js"></script>
|
||||
|
|
|
|||
51
update_map_colors.py
Normal file
51
update_map_colors.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
path = r'z:\source\forgejo\spotnotes.app\static\assets\js\map.js'
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
new_render = """ const shareToken = note.share_token || '';
|
||||
const shareBtn = `<button onclick="window.SpotNotes.shareNote(${note.id}, '${shareToken}')" class="btn-note-share">🔗 Share</button>`;
|
||||
|
||||
const dateStr = new Date(note.created_at).toLocaleString();
|
||||
|
||||
const padColor = (note.notepad && note.notepad.color) ? note.notepad.color : '#3388ff';
|
||||
|
||||
const directionsBtn = `<a href="https://www.google.com/maps/dir/?api=1&destination=${note.lat},${note.lng}" target="_blank" class="btn-note-directions" style="display:inline-block; margin-top:5px; margin-right:5px; font-size:0.8rem; text-decoration:none;">🗺️ Directions</a>`;
|
||||
|
||||
if (showFull) {
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<div class="map-note-card" style="border-top: 4px solid ${padColor};">
|
||||
<div style="font-weight:bold; margin-bottom:4px;">${window.SpotNotes.escapeHtml(note.title)}</div>
|
||||
<div style="white-space: pre-wrap;">${window.SpotNotes.escapeHtml(note.content)}</div>
|
||||
<div style="font-size:0.7rem; color:#888; margin-top:4px;">${dateStr}</div>
|
||||
${directionsBtn} ${shareBtn} ${actionBtns}
|
||||
</div>`,
|
||||
iconSize: [200, 'auto'],
|
||||
iconAnchor: [100, 0]
|
||||
});
|
||||
marker = L.marker([note.lat, note.lng], { icon: icon });
|
||||
} else {
|
||||
const svgIcon = L.divIcon({
|
||||
className: 'custom-div-icon',
|
||||
html: `<div style="background-color: ${padColor}; width: 16px; height: 16px; border-radius: 50%; border: 2px solid white; box-shadow: 0 0 4px rgba(0,0,0,0.5);"></div>`,
|
||||
iconSize: [20, 20],
|
||||
iconAnchor: [10, 10],
|
||||
popupAnchor: [0, -10]
|
||||
});
|
||||
marker = L.marker([note.lat, note.lng], { icon: svgIcon });
|
||||
marker.bindPopup(`<b>${window.SpotNotes.escapeHtml(note.title)}</b><br><div style="white-space: pre-wrap;">${window.SpotNotes.escapeHtml(note.content)}</div><span style="font-size:0.7rem; color:#888;">${dateStr}</span><br>${directionsBtn} ${shareBtn} ${actionBtns}`);
|
||||
}"""
|
||||
|
||||
# Extract old chunk to replace
|
||||
old_chunk_start = "const shareToken = note.share_token || '';"
|
||||
old_chunk_end = "marker.bindPopup(`<b>${window.SpotNotes.escapeHtml(note.title)}</b><br><div style=\"white-space: pre-wrap;\">${window.SpotNotes.escapeHtml(note.content)}</div><span style=\"font-size:0.7rem; color:#888;\">${dateStr}</span><br>${directionsBtn} ${shareBtn} ${actionBtns}`);\n }"
|
||||
|
||||
pattern = re.escape(old_chunk_start) + r'.*?' + re.escape(old_chunk_end)
|
||||
content = re.sub(pattern, new_render, content, flags=re.DOTALL)
|
||||
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print('Marker logic updated with colors.')
|
||||
Loading…
Reference in a new issue