2026-06-02 03:49:38 +00:00
|
|
|
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;
|
2026-10-06 19:24:25 +00:00
|
|
|
window.SpotNotes.activePadEncryptedKey = null;
|
2026-06-02 03:49:38 +00:00
|
|
|
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;
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.initMap();
|
|
|
|
|
window.SpotNotes.initSidebar();
|
|
|
|
|
window.SpotNotes.loadNotepads(true);
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// --- Add Collaborator Modal Handler ---
|
2026-05-12 23:58:18 +00:00
|
|
|
const form = document.getElementById('form-add-collaborator');
|
|
|
|
|
if (form) {
|
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const padId = document.getElementById('share-pad-id').value;
|
2026-10-06 19:24:25 +00:00
|
|
|
const email = document.getElementById('collaborator-email').value.trim();
|
2026-05-12 23:58:18 +00:00
|
|
|
const permission = document.getElementById('collaborator-permission').value;
|
|
|
|
|
|
|
|
|
|
try {
|
2026-10-06 19:24:25 +00:00
|
|
|
let encryptedKey = null;
|
|
|
|
|
|
|
|
|
|
// If private notepad, encrypt symmetric key with collaborator's public key
|
|
|
|
|
if (!window.SpotNotes.activePadIsPublic) {
|
|
|
|
|
const lookupRes = await fetch(`/api/users/lookup?email=${encodeURIComponent(email)}`);
|
|
|
|
|
if (!lookupRes.ok) {
|
|
|
|
|
const errData = await lookupRes.json();
|
|
|
|
|
alert(errData.error || `User with email "${email}" was not found. Collaborator must be a registered user.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const collaboratorUser = await lookupRes.json();
|
|
|
|
|
if (!collaboratorUser.public_key) {
|
|
|
|
|
alert(`User "${email}" does not have an active public key registered.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(padId);
|
|
|
|
|
if (!padKey && window.SpotNotes.activePadEncryptedKey) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(padId, window.SpotNotes.activePadEncryptedKey);
|
|
|
|
|
}
|
|
|
|
|
if (!padKey) {
|
|
|
|
|
alert('Could not access encryption key for this notepad.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
encryptedKey = await window.SpotCrypto.wrapPadKeyForCollaborator(padKey, collaboratorUser.public_key);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
const res = await fetch(`/api/notepads/${padId}/collaborators`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
2026-10-06 19:24:25 +00:00
|
|
|
body: JSON.stringify({ email, permission, encrypted_key: encryptedKey })
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
form.reset();
|
|
|
|
|
const padName = document.getElementById('share-pad-name').textContent;
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.openShareSettingsModal(padId, padName);
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
|
|
|
|
const err = await res.json();
|
|
|
|
|
alert(`Failed to add collaborator: ${err.error}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
2026-10-06 19:24:25 +00:00
|
|
|
alert('An error occurred adding collaborator.');
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// --- Edit Notepad Modal Handler ---
|
2026-05-12 23:58:18 +00:00
|
|
|
const formEditPad = document.getElementById('form-edit-pad');
|
|
|
|
|
if (formEditPad) {
|
|
|
|
|
formEditPad.addEventListener('submit', async (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const padId = document.getElementById('pad-id-edit').value;
|
|
|
|
|
const name = document.getElementById('pad-name-edit').value;
|
|
|
|
|
const isPublic = document.getElementById('pad-public-edit').checked;
|
2026-06-02 03:49:38 +00:00
|
|
|
const color = document.getElementById('pad-color-edit').value;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
2026-06-02 03:49:38 +00:00
|
|
|
body: JSON.stringify({ name, is_public: isPublic, color })
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
document.getElementById('modal-edit-pad').classList.add('hidden');
|
2026-10-06 19:24:25 +00:00
|
|
|
window.SpotNotes.loadNotepads();
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
|
|
|
|
alert('Failed to update notepad.');
|
|
|
|
|
}
|
|
|
|
|
} catch (err) { console.error(err); }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// --- Edit Note Modal Handler ---
|
2026-05-12 23:58:18 +00:00
|
|
|
const formEditNote = document.getElementById('form-edit-note');
|
|
|
|
|
if (formEditNote) {
|
|
|
|
|
formEditNote.addEventListener('submit', async (e) => {
|
|
|
|
|
e.preventDefault();
|
2026-10-06 19:24:25 +00:00
|
|
|
const noteId = parseInt(document.getElementById('note-id-edit').value, 10);
|
|
|
|
|
const title = document.getElementById('note-title-edit').value.trim();
|
2026-05-12 23:58:18 +00:00
|
|
|
const content = document.getElementById('note-content-edit').value;
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
const existingNote = window.SpotNotes.activeNotes.find(n => n.id === noteId);
|
|
|
|
|
const isEncrypted = existingNote ? existingNote.is_encrypted : !window.SpotNotes.activePadIsPublic;
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
try {
|
2026-10-06 19:24:25 +00:00
|
|
|
let bodyPayload = {};
|
|
|
|
|
if (isEncrypted) {
|
|
|
|
|
const targetPadId = (existingNote && existingNote.notepad_id) || window.SpotNotes.activePadId;
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(targetPadId);
|
|
|
|
|
if (!padKey && window.SpotNotes.activePadEncryptedKey) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(targetPadId, window.SpotNotes.activePadEncryptedKey);
|
|
|
|
|
}
|
|
|
|
|
if (!padKey) {
|
|
|
|
|
alert('Encryption key not available to edit this private note.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const encPayload = await window.SpotCrypto.encryptNote({
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
lat: existingNote ? existingNote.lat : 0,
|
|
|
|
|
lng: existingNote ? existingNote.lng : 0
|
|
|
|
|
}, padKey);
|
|
|
|
|
bodyPayload = { title: "🔒 Encrypted Note", content: encPayload };
|
|
|
|
|
} else {
|
|
|
|
|
bodyPayload = { title, content };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
const res = await fetch(`/api/notes/${noteId}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
2026-10-06 19:24:25 +00:00
|
|
|
body: JSON.stringify(bodyPayload)
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
2026-10-06 19:24:25 +00:00
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
if (res.ok) {
|
|
|
|
|
document.getElementById('modal-edit-note').classList.add('hidden');
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.activePadId) {
|
|
|
|
|
window.SpotNotes.loadNotesForPad(window.SpotNotes.activePadId);
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotepads();
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
alert('Failed to update note.');
|
|
|
|
|
}
|
|
|
|
|
} catch (err) { console.error(err); }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotepads = async function(isInitialLoad = false) {
|
2026-05-12 23:58:18 +00:00
|
|
|
// Reset state
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activeNotes = [];
|
|
|
|
|
window.SpotNotes.currentMarkers.forEach(marker => window.SpotNotes.map.removeLayer(marker));
|
|
|
|
|
window.SpotNotes.currentMarkers = [];
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
// Check auth first
|
|
|
|
|
try {
|
|
|
|
|
const authRes = await fetch('/auth/me');
|
|
|
|
|
const authData = await authRes.json();
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.isLoggedIn = authData.authenticated;
|
|
|
|
|
window.SpotNotes.currentUser = authData.user || null;
|
2026-10-06 19:24:25 +00:00
|
|
|
|
|
|
|
|
// Try restoring user's private key from session storage
|
|
|
|
|
if (window.SpotNotes.isLoggedIn && window.SpotCrypto) {
|
|
|
|
|
await window.SpotCrypto.restoreUserPrivateKeyFromSession();
|
|
|
|
|
}
|
2026-05-12 23:58:18 +00:00
|
|
|
} catch (e) { console.error(e); }
|
|
|
|
|
|
|
|
|
|
// Reset Sidebar Header
|
|
|
|
|
const header = document.querySelector('.sidebar-header');
|
|
|
|
|
if (header) {
|
2026-06-23 19:23:45 +00:00
|
|
|
let headerHTML = `<div style="display:flex; justify-content:space-between; align-items:center; width:100%;">
|
|
|
|
|
<h2>Notepads</h2>`;
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.isLoggedIn) {
|
2026-05-12 23:58:18 +00:00
|
|
|
headerHTML += '<button class="btn-new-pad" id="btn-create-pad" title="Create New Pad">+</button>';
|
|
|
|
|
}
|
2026-06-23 19:23:45 +00:00
|
|
|
headerHTML += '</div>';
|
2026-05-12 23:58:18 +00:00
|
|
|
header.innerHTML = headerHTML;
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.isLoggedIn) {
|
2026-05-12 23:58:18 +00:00
|
|
|
document.getElementById('btn-create-pad').addEventListener('click', () => {
|
|
|
|
|
document.getElementById('modal-create-pad').classList.remove('hidden');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sidebarBody = document.getElementById('sidebar-body');
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.isLoggedIn) {
|
2026-05-12 23:58:18 +00:00
|
|
|
sidebarBody.innerHTML = `
|
|
|
|
|
<div class="list-section" id="favorite-notepads-section">
|
|
|
|
|
<button class="list-section-header">Favorite Notepads ▾</button>
|
|
|
|
|
<div class="list-section-content" id="favorite-notepads-list">
|
|
|
|
|
<p style="padding:10px; color:#666;">Loading...</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-10-06 19:24:25 +00:00
|
|
|
<div class="list-section" id="my-notepads-section">
|
2026-05-12 23:58:18 +00:00
|
|
|
<button class="list-section-header">My Notepads ▾</button>
|
|
|
|
|
<div class="list-section-content" id="my-notepads-list"><p style="padding:10px; color:#666;">Loading...</p></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="list-section" id="shared-notepads-section">
|
|
|
|
|
<button class="list-section-header">Shared With Me ▾</button>
|
|
|
|
|
<div class="list-section-content" id="shared-notepads-list"><p style="padding:10px; color:#666;">Loading...</p></div>
|
|
|
|
|
</div>
|
2026-10-06 19:24:25 +00:00
|
|
|
<div class="list-section" id="public-notepads-section">
|
2026-05-12 23:58:18 +00:00
|
|
|
<button id="btn-toggle-public" class="btn-toggle-section">Public Notepads ▸</button>
|
|
|
|
|
<div id="public-notepads-list" style="display:none;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
const listSection = document.querySelectorAll('.list-section-header');
|
2026-10-06 19:24:25 +00:00
|
|
|
listSection.forEach(h => {
|
|
|
|
|
h.addEventListener('click', window.SpotNotes.toggleSection);
|
|
|
|
|
});
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.setupPublicToggle();
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
try {
|
2026-06-02 03:49:38 +00:00
|
|
|
await window.SpotNotes.loadNotepadsOfType('favorites', 'favorite-notepads-list');
|
|
|
|
|
await window.SpotNotes.loadNotepadsOfType('shared', 'shared-notepads-list');
|
2026-05-12 23:58:18 +00:00
|
|
|
const res = await fetch('/api/notepads?type=mine');
|
|
|
|
|
const notepads = await res.json();
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderNotepadList(notepads, 'my-notepads-list');
|
2026-05-12 23:58:18 +00:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to load notepads", err);
|
|
|
|
|
document.getElementById('my-notepads-list').innerHTML = '<p style="color:red; padding:10px;">Error loading data.</p>';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Logged out: Only Public Notepads
|
|
|
|
|
sidebarBody.innerHTML = `
|
|
|
|
|
<div id="public-notepads-section">
|
|
|
|
|
<h3 class="list-section-header">Public Notepads</h3>
|
|
|
|
|
<div id="public-notepads-list"><p style="padding:10px; color:#666;">Loading...</p></div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch('/api/notepads?type=public');
|
|
|
|
|
const pads = await res.json();
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderNotepadList(pads, 'public-notepads-list');
|
2026-05-12 23:58:18 +00:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Initial / Default Load Logic
|
2026-06-23 19:35:17 +00:00
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
if (isInitialLoad === true && params.has('pad')) {
|
|
|
|
|
const token = params.get('token');
|
|
|
|
|
window.SpotNotes.loadNotesForPad(params.get('pad'), undefined, undefined, undefined, true, token);
|
|
|
|
|
} else if (isInitialLoad === true && params.has('note')) {
|
|
|
|
|
const token = params.get('token');
|
|
|
|
|
window.SpotNotes.loadSingleNote(params.get('note'), token);
|
|
|
|
|
} else {
|
|
|
|
|
await window.SpotNotes.loadDefaultNotes();
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.toggleFavorite = async function(padId, action) {
|
2026-05-12 23:58:18 +00:00
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}/${action}`, { method: 'POST' });
|
|
|
|
|
if (res.ok) {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotepadsOfType('favorites', 'favorite-notepads-list');
|
|
|
|
|
window.SpotNotes.loadNotepadsOfType('mine', 'my-notepads-list');
|
|
|
|
|
window.SpotNotes.loadNotepadsOfType('public', 'public-notepads-list');
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
|
|
|
|
alert(`Failed to ${action} notepad`);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotepadsOfType = async function(type, containerId) {
|
2026-05-12 23:58:18 +00:00
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads?type=${type}`);
|
|
|
|
|
const notepads = await res.json();
|
|
|
|
|
const isFavorite = type === 'favorites';
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderNotepadList(notepads, containerId, isFavorite);
|
2026-05-12 23:58:18 +00:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error(`Failed to load ${type} notepads`, err);
|
|
|
|
|
document.getElementById(containerId).innerHTML = '<p style="color:red; padding:10px;">Error loading data.</p>';
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.SpotNotes.loadNotesForPad = async function(padId, padName, isPublic, ownerId, centerOnFirst = false, token = null, canWrite = undefined, encryptedKey = null) {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activePadId = padId;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// If metadata is missing, fetch it
|
|
|
|
|
if (padName === undefined || encryptedKey === undefined) {
|
2026-05-12 23:58:18 +00:00
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}?token=${token || ''}`);
|
|
|
|
|
const pad = await res.json();
|
2026-10-06 19:24:25 +00:00
|
|
|
padName = pad.name;
|
|
|
|
|
isPublic = pad.is_public;
|
|
|
|
|
ownerId = pad.owner_id;
|
|
|
|
|
window.SpotNotes.activePadCanWrite = pad.can_write;
|
|
|
|
|
if (pad.encrypted_key) encryptedKey = pad.encrypted_key;
|
|
|
|
|
} catch(e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
window.SpotNotes.activePadCanWrite = false;
|
|
|
|
|
}
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
if (padName !== undefined) window.SpotNotes.activePadName = padName;
|
|
|
|
|
if (isPublic !== undefined) window.SpotNotes.activePadIsPublic = isPublic;
|
|
|
|
|
if (ownerId !== undefined) window.SpotNotes.activePadOwnerId = ownerId;
|
2026-10-06 19:24:25 +00:00
|
|
|
if (encryptedKey !== null) window.SpotNotes.activePadEncryptedKey = encryptedKey;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
if (canWrite !== undefined) {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activePadCanWrite = canWrite;
|
2026-05-12 23:58:18 +00:00
|
|
|
} else if (padName !== undefined) {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activePadCanWrite = (window.SpotNotes.currentUser && ownerId === window.SpotNotes.currentUser.id);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Check if URL hash has #key=... for link-shared private notes/pads
|
|
|
|
|
if (window.location.hash.includes('key=')) {
|
|
|
|
|
const hashParams = new URLSearchParams(window.location.hash.replace(/^#/, ''));
|
|
|
|
|
const linkKey = hashParams.get('key');
|
|
|
|
|
if (linkKey && window.SpotCrypto) {
|
|
|
|
|
await window.SpotCrypto.importPadKeyFromLink(padId, linkKey);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If private and encrypted_key present, unwrap with user's private key
|
|
|
|
|
if (!isPublic && window.SpotNotes.activePadEncryptedKey && window.SpotCrypto) {
|
|
|
|
|
await window.SpotCrypto.unwrapNotepadKey(padId, window.SpotNotes.activePadEncryptedKey);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
const currentName = window.SpotNotes.activePadName;
|
|
|
|
|
const isOwner = window.SpotNotes.currentUser && window.SpotNotes.activePadOwnerId === window.SpotNotes.currentUser.id;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
// Clear existing markers
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.currentMarkers.forEach(marker => window.SpotNotes.map.removeLayer(marker));
|
|
|
|
|
window.SpotNotes.currentMarkers = [];
|
|
|
|
|
window.SpotNotes.activeNotes = [];
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
// Update Sidebar Header
|
|
|
|
|
const header = document.querySelector('.sidebar-header');
|
|
|
|
|
if (header) {
|
2026-06-02 03:49:38 +00:00
|
|
|
const addNoteBtn = window.SpotNotes.activePadCanWrite ? `<button onclick="openCreateNoteModal(${padId})" class="btn-add-note">+ Add Note</button>` : '';
|
2026-06-23 19:23:45 +00:00
|
|
|
const manageShareBtn = isOwner ? `<button class="btn-action-icon" onclick="window.SpotNotes.openShareSettingsModal(${padId}, '${window.SpotNotes.escapeHtml(currentName)}')" title="Manage Sharing">⚙️</button>` : '';
|
2026-10-06 19:24:25 +00:00
|
|
|
const exportBtn = `<button class="btn-action-icon" onclick="window.SpotNotes.exportCurrentPadCsv(${padId}, '${window.SpotNotes.escapeHtml(currentName)}')" title="Export to CSV">⬇️</button>`;
|
2026-06-23 19:23:45 +00:00
|
|
|
const shareBtn = `<button class="btn-action-icon" onclick="window.SpotNotes.shareNotepad(${padId}, '${token || ''}')" title="Share Notepad">🔗</button>`;
|
2026-10-06 19:24:25 +00:00
|
|
|
const encryptionBadge = !isPublic ? '<span title="End-to-End Encrypted at Rest" style="font-size:0.85rem; margin-left:4px;">🔒</span>' : '<span title="Public Unencrypted" style="font-size:0.85rem; margin-left:4px;">🌍</span>';
|
2026-06-23 19:23:45 +00:00
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
header.innerHTML = `
|
2026-06-23 19:23:45 +00:00
|
|
|
<div class="sidebar-header-top" style="display:flex; align-items:center; justify-content:space-between; width:100%;">
|
|
|
|
|
<div style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
|
|
|
<button id="btn-back-pads" class="btn-action-icon" style="font-size:1.25rem; margin-right:4px;">←</button>
|
2026-10-06 19:24:25 +00:00
|
|
|
<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)} ${encryptionBadge}</h2>
|
2026-06-23 19:23:45 +00:00
|
|
|
</div>
|
|
|
|
|
<div class="notepad-actions" style="display:flex; align-items:center; gap:4px; flex-shrink:0;">
|
|
|
|
|
${shareBtn}
|
|
|
|
|
${manageShareBtn}
|
|
|
|
|
${exportBtn}
|
|
|
|
|
</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);">
|
2026-05-12 23:58:18 +00:00
|
|
|
</div>
|
2026-06-23 19:23:45 +00:00
|
|
|
<div style="margin-top: 10px; width:100%; display:flex; justify-content:space-between; align-items:center;">
|
|
|
|
|
${addNoteBtn}
|
2026-06-02 03:49:38 +00:00
|
|
|
</div>
|
2026-05-12 23:58:18 +00:00
|
|
|
`;
|
2026-06-02 03:49:38 +00:00
|
|
|
document.getElementById('btn-back-pads').addEventListener('click', window.SpotNotes.loadNotepads);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sidebarBody = document.getElementById('sidebar-body');
|
|
|
|
|
sidebarBody.innerHTML = '<p style="padding:10px;">Loading notes...</p>';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}/notes?token=${token || ''}`);
|
|
|
|
|
const notes = await res.json();
|
2026-10-06 19:24:25 +00:00
|
|
|
|
|
|
|
|
let decryptedNotes = notes;
|
|
|
|
|
if (!isPublic && window.SpotCrypto) {
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(padId);
|
|
|
|
|
if (!padKey && window.SpotNotes.activePadEncryptedKey) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(padId, window.SpotNotes.activePadEncryptedKey);
|
|
|
|
|
}
|
|
|
|
|
decryptedNotes = await window.SpotCrypto.decryptNotes(notes, padKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.SpotNotes.activeNotes = decryptedNotes;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Render Sidebar List & Markers
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderActiveNotesList();
|
|
|
|
|
window.SpotNotes.renderMapMarkers();
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
const filterInput = document.getElementById('filter-active-notes');
|
|
|
|
|
if (filterInput) {
|
|
|
|
|
filterInput.addEventListener('keyup', (e) => {
|
|
|
|
|
window.SpotNotes.renderActiveNotesList(e.target.value);
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
2026-06-02 03:49:38 +00:00
|
|
|
}
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
if (centerOnFirst && decryptedNotes.length > 0 && decryptedNotes[0].lat && decryptedNotes[0].lng) {
|
|
|
|
|
window.SpotNotes.map.setView([decryptedNotes[0].lat, decryptedNotes[0].lng], 13);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
sidebarBody.innerHTML = '<p style="color:red; padding:10px;">Error loading notes.</p>';
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadSingleNote = async function(noteId, token = null) {
|
2026-05-12 23:58:18 +00:00
|
|
|
// Reset state
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activeNotes = [];
|
|
|
|
|
window.SpotNotes.currentMarkers.forEach(marker => window.SpotNotes.map.removeLayer(marker));
|
|
|
|
|
window.SpotNotes.currentMarkers = [];
|
|
|
|
|
window.SpotNotes.activePadId = null;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
const sidebarBody = document.getElementById('sidebar-body');
|
|
|
|
|
sidebarBody.innerHTML = '<p style="padding:10px;">Loading note...</p>';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notes/${noteId}?token=${token || ''}`);
|
|
|
|
|
if (!res.ok) throw new Error("Note not found or private");
|
2026-10-06 19:24:25 +00:00
|
|
|
let note = await res.json();
|
|
|
|
|
|
|
|
|
|
// Check hash #key=... for decryption
|
|
|
|
|
if (note.is_encrypted && window.SpotCrypto) {
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(note.notepad_id);
|
|
|
|
|
if (!padKey && window.location.hash.includes('key=')) {
|
|
|
|
|
const hashParams = new URLSearchParams(window.location.hash.replace(/^#/, ''));
|
|
|
|
|
const linkKey = hashParams.get('key');
|
|
|
|
|
if (linkKey) {
|
|
|
|
|
padKey = await window.SpotCrypto.importPadKeyFromLink(note.notepad_id, linkKey);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!padKey && note.notepad && note.notepad.encrypted_key) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(note.notepad_id, note.notepad.encrypted_key);
|
|
|
|
|
}
|
|
|
|
|
note = await window.SpotCrypto.decryptNote(note, padKey);
|
|
|
|
|
}
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activeNotes = [note];
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
// Update Header
|
|
|
|
|
const header = document.querySelector('.sidebar-header');
|
|
|
|
|
if (header) {
|
|
|
|
|
header.innerHTML = `
|
|
|
|
|
<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>
|
2026-10-06 19:24:25 +00:00
|
|
|
<h2 style="font-size:1.2rem; margin:0;">Shared Note ${note.is_encrypted ? '🔒' : '🌍'}</h2>
|
2026-05-12 23:58:18 +00:00
|
|
|
</div>
|
|
|
|
|
`;
|
2026-06-02 03:49:38 +00:00
|
|
|
document.getElementById('btn-back-pads').addEventListener('click', window.SpotNotes.loadNotepads);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sidebarBody.innerHTML = '';
|
|
|
|
|
const noteInfoDiv = document.createElement('div');
|
|
|
|
|
noteInfoDiv.style.padding = '10px';
|
|
|
|
|
|
|
|
|
|
const p = document.createElement('p');
|
|
|
|
|
p.appendChild(document.createTextNode('Viewing single note from '));
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
if (note.notepad && note.notepad.is_public) {
|
2026-05-12 23:58:18 +00:00
|
|
|
const link = document.createElement('a');
|
|
|
|
|
link.href = "#";
|
|
|
|
|
link.textContent = note.notepad.name;
|
|
|
|
|
link.style.fontWeight = 'bold';
|
|
|
|
|
link.onclick = (e) => {
|
|
|
|
|
e.preventDefault();
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotesForPad(note.notepad.id, note.notepad.name, true, note.owner_id, false, null, note.notepad.can_write);
|
2026-05-12 23:58:18 +00:00
|
|
|
};
|
|
|
|
|
p.appendChild(link);
|
|
|
|
|
} else {
|
|
|
|
|
const strong = document.createElement('strong');
|
2026-10-06 19:24:25 +00:00
|
|
|
strong.textContent = (note.notepad && note.notepad.name) || 'Private Notepad';
|
2026-05-12 23:58:18 +00:00
|
|
|
p.appendChild(strong);
|
|
|
|
|
}
|
|
|
|
|
p.appendChild(document.createTextNode('.'));
|
|
|
|
|
noteInfoDiv.appendChild(p);
|
|
|
|
|
sidebarBody.appendChild(noteInfoDiv);
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderMapMarkers();
|
2026-10-06 19:24:25 +00:00
|
|
|
if (note.lat && note.lng) {
|
|
|
|
|
window.SpotNotes.map.setView([note.lat, note.lng], 16);
|
|
|
|
|
}
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
if (window.innerWidth <= 768) {
|
2026-05-12 23:58:18 +00:00
|
|
|
const sidebar = document.getElementById('sidebar');
|
|
|
|
|
const toggleBtn = document.getElementById('sidebar-toggle');
|
|
|
|
|
if (sidebar && !sidebar.classList.contains('collapsed')) {
|
|
|
|
|
sidebar.classList.add('collapsed');
|
|
|
|
|
if (toggleBtn) toggleBtn.innerHTML = '<';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
sidebarBody.innerHTML = '<p style="color:red; padding:10px;">Failed to load note. It may be private or deleted.</p>';
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.displaySearchResults = function(notes, location, query) {
|
|
|
|
|
window.SpotNotes.activePadId = null;
|
|
|
|
|
window.SpotNotes.activePadName = `Search: ${query}`;
|
2026-10-06 19:24:25 +00:00
|
|
|
window.SpotNotes.activePadIsPublic = false;
|
|
|
|
|
window.SpotNotes.activePadOwnerId = null;
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.activeNotes = notes;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
const header = document.querySelector('.sidebar-header');
|
|
|
|
|
if (header) {
|
|
|
|
|
header.innerHTML = `
|
|
|
|
|
<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>
|
2026-06-02 03:49:38 +00:00
|
|
|
<h2 style="font-size:1.2rem; margin:0;">Results: ${window.SpotNotes.escapeHtml(query)}</h2>
|
2026-05-12 23:58:18 +00:00
|
|
|
</div>
|
|
|
|
|
`;
|
2026-06-02 03:49:38 +00:00
|
|
|
document.getElementById('btn-back-pads').addEventListener('click', window.SpotNotes.loadNotepads);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sidebarBody = document.getElementById('sidebar-body');
|
|
|
|
|
sidebarBody.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
if (notes.length === 0 && !location) {
|
|
|
|
|
sidebarBody.innerHTML = '<p style="padding:10px; color:#666;">No results found.</p>';
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderMapMarkers();
|
2026-05-12 23:58:18 +00:00
|
|
|
return;
|
|
|
|
|
} else if (notes.length === 0 && location) {
|
2026-10-06 19:24:25 +00:00
|
|
|
sidebarBody.innerHTML = '<p style="padding:10px; color:#666;">Location found on map.</p>';
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderMapMarkers();
|
|
|
|
|
window.SpotNotes.map.setView([location.lat, location.lng], 13);
|
2026-05-12 23:58:18 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sections = {
|
2026-10-06 19:24:25 +00:00
|
|
|
favorites: {},
|
2026-05-12 23:58:18 +00:00
|
|
|
mine: {},
|
|
|
|
|
public: {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
notes.forEach(note => {
|
2026-10-06 19:24:25 +00:00
|
|
|
const pad = note.notepad || { id: note.notepad_id, name: 'Notes', is_public: !note.is_encrypted };
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
const addToSection = (secKey) => {
|
|
|
|
|
if (!sections[secKey][pad.id]) {
|
|
|
|
|
sections[secKey][pad.id] = { pad: pad, notes: [] };
|
|
|
|
|
}
|
|
|
|
|
sections[secKey][pad.id].notes.push(note);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (pad.is_favorite) addToSection('favorites');
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.currentUser && pad.owner_id === window.SpotNotes.currentUser.id) addToSection('mine');
|
2026-05-12 23:58:18 +00:00
|
|
|
if (pad.is_public) addToSection('public');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const renderSection = (title, dataMap, isOpen) => {
|
|
|
|
|
const sectionDiv = document.createElement('div');
|
|
|
|
|
sectionDiv.className = 'list-section';
|
|
|
|
|
|
|
|
|
|
const arrow = isOpen ? '▾' : '▸';
|
|
|
|
|
const display = isOpen ? 'block' : 'none';
|
|
|
|
|
|
|
|
|
|
const headerBtn = document.createElement('button');
|
|
|
|
|
headerBtn.className = 'list-section-header';
|
|
|
|
|
headerBtn.innerHTML = `${title} ${arrow}`;
|
2026-06-02 03:49:38 +00:00
|
|
|
headerBtn.onclick = window.SpotNotes.toggleSection;
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
const contentDiv = document.createElement('div');
|
|
|
|
|
contentDiv.className = 'list-section-content';
|
|
|
|
|
contentDiv.style.display = display;
|
|
|
|
|
|
|
|
|
|
const padIds = Object.keys(dataMap);
|
|
|
|
|
if (padIds.length === 0) {
|
|
|
|
|
contentDiv.innerHTML = '<p style="padding:10px; color:#999; font-style:italic; font-size:0.8rem;">No matching notes.</p>';
|
|
|
|
|
} else {
|
|
|
|
|
padIds.forEach(padId => {
|
|
|
|
|
const entry = dataMap[padId];
|
|
|
|
|
const pad = entry.pad;
|
|
|
|
|
|
|
|
|
|
const padItem = document.createElement('div');
|
|
|
|
|
padItem.className = 'notepad-item';
|
2026-10-06 19:24:25 +00:00
|
|
|
padItem.style.backgroundColor = '#e9ecef';
|
2026-05-12 23:58:18 +00:00
|
|
|
padItem.style.cursor = 'default';
|
|
|
|
|
|
|
|
|
|
let icon = pad.is_public ? '🌍' : '🔒';
|
|
|
|
|
let favButton = '';
|
2026-10-06 19:24:25 +00:00
|
|
|
if (window.SpotNotes.isLoggedIn) {
|
2026-05-12 23:58:18 +00:00
|
|
|
const isFavorite = pad.is_favorite;
|
|
|
|
|
const action = isFavorite ? 'unfavorite' : 'favorite';
|
|
|
|
|
const label = isFavorite ? '♥' : '♡';
|
2026-06-02 03:49:38 +00:00
|
|
|
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>`;
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
padItem.innerHTML = `
|
|
|
|
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
2026-06-02 03:49:38 +00:00
|
|
|
<div style="font-weight:bold;">${icon} ${window.SpotNotes.escapeHtml(pad.name)}</div>
|
2026-05-12 23:58:18 +00:00
|
|
|
<div style="display: flex; align-items: center;">
|
|
|
|
|
${favButton}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
contentDiv.appendChild(padItem);
|
|
|
|
|
|
|
|
|
|
entry.notes.forEach(note => {
|
|
|
|
|
const item = document.createElement('div');
|
2026-06-23 19:23:45 +00:00
|
|
|
item.className = 'note-card';
|
2026-05-12 23:58:18 +00:00
|
|
|
const dateStr = new Date(note.created_at).toLocaleString();
|
|
|
|
|
|
2026-06-23 19:23:45 +00:00
|
|
|
let actionBtn = '';
|
2026-05-12 23:58:18 +00:00
|
|
|
if (note.notepad && note.notepad.can_write) {
|
2026-06-02 03:49:38 +00:00
|
|
|
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');
|
2026-06-23 19:23:45 +00:00
|
|
|
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>`;
|
2026-05-12 23:58:18 +00:00
|
|
|
} else if (note.is_public) {
|
2026-06-23 19:23:45 +00:00
|
|
|
actionBtn = `<button class="note-card-link report" onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})">Report</button>`;
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:23:45 +00:00
|
|
|
const shareToken = note.share_token || '';
|
|
|
|
|
const shareBtn = `<button class="note-card-link share" onclick="event.stopPropagation(); window.SpotNotes.shareNote(${note.id}, '${shareToken}')">Share</button>`;
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
item.innerHTML = `
|
2026-06-23 19:23:45 +00:00
|
|
|
<div class="note-card-title">${window.SpotNotes.escapeHtml(note.title)}</div>
|
|
|
|
|
<div class="note-card-body">${window.SpotNotes.escapeHtml(note.content)}</div>
|
|
|
|
|
<div class="note-card-meta">
|
2026-10-06 19:24:25 +00:00
|
|
|
${dateStr} <br> ${note.lat ? note.lat.toFixed(5) : ''}, ${note.lng ? note.lng.toFixed(5) : ''}
|
2026-05-12 23:58:18 +00:00
|
|
|
</div>
|
2026-06-23 19:23:45 +00:00
|
|
|
<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>
|
2026-05-12 23:58:18 +00:00
|
|
|
`;
|
|
|
|
|
item.addEventListener('click', () => {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.stopTracking();
|
2026-10-06 19:24:25 +00:00
|
|
|
if (note.lat && note.lng) window.SpotNotes.map.setView([note.lat, note.lng], 16);
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
|
|
|
|
contentDiv.appendChild(item);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sectionDiv.appendChild(headerBtn);
|
|
|
|
|
sectionDiv.appendChild(contentDiv);
|
|
|
|
|
sidebarBody.appendChild(sectionDiv);
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.isLoggedIn) {
|
2026-05-12 23:58:18 +00:00
|
|
|
if (Object.keys(sections.favorites).length > 0) renderSection('Favorite Notepads', sections.favorites, true);
|
|
|
|
|
if (Object.keys(sections.mine).length > 0) renderSection('My Notepads', sections.mine, true);
|
|
|
|
|
if (Object.keys(sections.public).length > 0) renderSection('Public Notepads', sections.public, true);
|
|
|
|
|
} else {
|
|
|
|
|
if (Object.keys(sections.public).length > 0) renderSection('Public Notepads', sections.public, true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderMapMarkers();
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
if (notes.length > 0 && notes[0].lat && notes[0].lng) {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.map.setView([notes[0].lat, notes[0].lng], 15);
|
2026-05-12 23:58:18 +00:00
|
|
|
} else if (location) {
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.map.setView([location.lat, location.lng], 13);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.reportNote = async function(noteId) {
|
2026-05-12 23:58:18 +00:00
|
|
|
if (!confirm("Report this note? It will be hidden from public view.")) return;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notes/${noteId}/report`, { method: 'POST' });
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
alert("Note reported and hidden.");
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotesForPad(window.SpotNotes.activePadId, window.SpotNotes.activePadName, window.SpotNotes.activePadIsPublic);
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Error reporting note:", err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.deleteNotepad = async function(padId) {
|
2026-05-12 23:58:18 +00:00
|
|
|
if (!confirm("Are you sure you want to delete this notepad? All notes inside will be lost.")) return;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}`, { method: 'DELETE' });
|
|
|
|
|
if (res.ok) {
|
2026-10-06 19:24:25 +00:00
|
|
|
window.SpotNotes.loadNotepads();
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
|
|
|
|
alert("Failed to delete notepad.");
|
|
|
|
|
}
|
|
|
|
|
} catch (err) { console.error(err); }
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.openShareSettingsModal = async function(padId, padName) {
|
2026-05-12 23:58:18 +00:00
|
|
|
const modal = document.getElementById('modal-share-settings');
|
|
|
|
|
document.getElementById('share-pad-name').textContent = padName;
|
|
|
|
|
document.getElementById('share-pad-id').value = padId;
|
|
|
|
|
|
|
|
|
|
const listDiv = document.getElementById('collaborators-list');
|
|
|
|
|
listDiv.innerHTML = '<p>Loading...</p>';
|
|
|
|
|
modal.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}/collaborators`);
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.json();
|
|
|
|
|
throw new Error(err.error || 'Failed to load collaborators');
|
|
|
|
|
}
|
|
|
|
|
const collaborators = await res.json();
|
|
|
|
|
|
|
|
|
|
listDiv.innerHTML = '';
|
|
|
|
|
if (collaborators.length === 0) {
|
|
|
|
|
listDiv.innerHTML = '<p style="color:#888; font-style:italic;">No collaborators yet.</p>';
|
|
|
|
|
} else {
|
|
|
|
|
collaborators.forEach(collab => {
|
|
|
|
|
const item = document.createElement('div');
|
|
|
|
|
item.className = 'collaborator-item';
|
|
|
|
|
item.innerHTML = `
|
2026-06-02 03:49:38 +00:00
|
|
|
<span>${window.SpotNotes.escapeHtml(collab.email)} (${collab.permission})</span>
|
|
|
|
|
<button onclick="window.SpotNotes.removeCollaborator(${padId}, ${collab.user_id})">Remove</button>
|
2026-05-12 23:58:18 +00:00
|
|
|
`;
|
|
|
|
|
listDiv.appendChild(item);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
listDiv.innerHTML = `<p style="color:red;">${err.message}</p>`;
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.removeCollaborator = async function(padId, userId) {
|
2026-05-12 23:58:18 +00:00
|
|
|
if (!confirm("Are you sure you want to remove this collaborator?")) return;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notepads/${padId}/collaborators/${userId}`, { method: 'DELETE' });
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
const padName = document.getElementById('share-pad-name').textContent;
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.openShareSettingsModal(padId, padName);
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
|
|
|
|
const err = await res.json();
|
|
|
|
|
alert(`Failed to remove collaborator: ${err.error}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
alert('An error occurred.');
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadAllMyNotes = async function() {
|
|
|
|
|
window.SpotNotes.activePadId = null;
|
|
|
|
|
window.SpotNotes.activePadName = "All My Notes";
|
|
|
|
|
window.SpotNotes.activePadIsPublic = false;
|
|
|
|
|
window.SpotNotes.activePadCanWrite = true;
|
|
|
|
|
if (window.SpotNotes.currentUser) {
|
|
|
|
|
window.SpotNotes.activePadOwnerId = window.SpotNotes.currentUser.id;
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.currentMarkers.forEach(marker => window.SpotNotes.map.removeLayer(marker));
|
|
|
|
|
window.SpotNotes.currentMarkers = [];
|
|
|
|
|
window.SpotNotes.activeNotes = [];
|
2026-05-12 23:58:18 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch('/api/notes/mine');
|
|
|
|
|
if (!res.ok) throw new Error("Failed to load");
|
|
|
|
|
const notes = await res.json();
|
2026-10-06 19:24:25 +00:00
|
|
|
|
|
|
|
|
// Decrypt any encrypted notes
|
|
|
|
|
const decryptedNotes = await Promise.all(notes.map(async (note) => {
|
|
|
|
|
if (note.is_encrypted && window.SpotCrypto) {
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(note.notepad_id);
|
|
|
|
|
if (!padKey && note.notepad && note.notepad.encrypted_key) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(note.notepad_id, note.notepad.encrypted_key);
|
|
|
|
|
}
|
|
|
|
|
return await window.SpotCrypto.decryptNote(note, padKey);
|
|
|
|
|
}
|
|
|
|
|
return note;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
window.SpotNotes.activeNotes = decryptedNotes;
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.renderMapMarkers();
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
const validNotes = decryptedNotes.filter(n => n.lat && n.lng);
|
|
|
|
|
if (validNotes.length > 0) {
|
|
|
|
|
const bounds = L.latLngBounds(validNotes.map(n => [n.lat, n.lng]));
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15 });
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Error loading all my notes:", err);
|
|
|
|
|
}
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Zero-knowledge link sharing for notepads
|
|
|
|
|
window.SpotNotes.shareNotepad = async function(padId, token) {
|
2026-05-12 23:58:18 +00:00
|
|
|
let url = `${window.location.origin}/?pad=${padId}`;
|
|
|
|
|
if (token) url += `&token=${token}`;
|
2026-10-06 19:24:25 +00:00
|
|
|
|
|
|
|
|
if (!window.SpotNotes.activePadIsPublic && window.SpotCrypto) {
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(padId);
|
|
|
|
|
if (!padKey && window.SpotNotes.activePadEncryptedKey) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(padId, window.SpotNotes.activePadEncryptedKey);
|
|
|
|
|
}
|
|
|
|
|
if (padKey) {
|
|
|
|
|
const keyB64 = await window.SpotCrypto.exportPadKeyForLink(padKey);
|
|
|
|
|
url += `#key=${keyB64}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
navigator.clipboard.writeText(url).then(() => {
|
2026-10-06 19:24:25 +00:00
|
|
|
alert("Notepad link copied to clipboard! (Includes decryption key fragment for private notepads)");
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Zero-knowledge link sharing for individual notes
|
|
|
|
|
window.SpotNotes.shareNote = async function(noteId, token) {
|
2026-05-12 23:58:18 +00:00
|
|
|
let url = `${window.location.origin}/?note=${noteId}`;
|
|
|
|
|
if (token) url += `&token=${token}`;
|
2026-10-06 19:24:25 +00:00
|
|
|
|
|
|
|
|
const note = window.SpotNotes.activeNotes.find(n => n.id === noteId);
|
|
|
|
|
if (note && note.is_encrypted && window.SpotCrypto) {
|
|
|
|
|
const padId = note.notepad_id || window.SpotNotes.activePadId;
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(padId);
|
|
|
|
|
if (!padKey && window.SpotNotes.activePadEncryptedKey) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(padId, window.SpotNotes.activePadEncryptedKey);
|
|
|
|
|
}
|
|
|
|
|
if (padKey) {
|
|
|
|
|
const keyB64 = await window.SpotCrypto.exportPadKeyForLink(padKey);
|
|
|
|
|
url += `#key=${keyB64}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:58:18 +00:00
|
|
|
navigator.clipboard.writeText(url).then(() => {
|
2026-10-06 19:24:25 +00:00
|
|
|
alert("Note link copied to clipboard! (Includes decryption key fragment for private notes)");
|
2026-05-12 23:58:18 +00:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Export decrypted notes client-side for private pads, or server export for public pads
|
|
|
|
|
window.SpotNotes.exportCurrentPadCsv = function(padId, padName) {
|
|
|
|
|
if (window.SpotNotes.activePadIsPublic) {
|
|
|
|
|
const token = new URLSearchParams(window.location.search).get('token') || '';
|
|
|
|
|
window.location.href = `/api/notepads/${padId}/export${token ? '?token=' + token : ''}`;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
// Client-side export for decrypted private notes
|
|
|
|
|
const rows = [['Note ID', 'Title', 'Content', 'Latitude', 'Longitude', 'Created At']];
|
|
|
|
|
window.SpotNotes.activeNotes.forEach(note => {
|
|
|
|
|
rows.push([
|
|
|
|
|
note.id,
|
|
|
|
|
note.title,
|
|
|
|
|
note.content,
|
|
|
|
|
note.lat,
|
|
|
|
|
note.lng,
|
|
|
|
|
note.created_at
|
|
|
|
|
]);
|
|
|
|
|
});
|
2026-05-12 23:58:18 +00:00
|
|
|
|
2026-10-06 19:24:25 +00:00
|
|
|
const csvContent = "data:text/csv;charset=utf-8," + rows.map(r => r.map(val => `"${String(val ?? '').replace(/"/g, '""')}"`).join(",")).join("\n");
|
|
|
|
|
const encodedUri = encodeURI(csvContent);
|
|
|
|
|
const link = document.createElement("a");
|
|
|
|
|
link.setAttribute("href", encodedUri);
|
|
|
|
|
link.setAttribute("download", `notepad_${padId}_decrypted_export.csv`);
|
|
|
|
|
document.body.appendChild(link);
|
|
|
|
|
link.click();
|
|
|
|
|
document.body.removeChild(link);
|
|
|
|
|
};
|
2026-06-02 03:49:38 +00:00
|
|
|
|
|
|
|
|
window.SpotNotes.escapeHtml = function(text) {
|
2026-05-12 23:58:18 +00:00
|
|
|
if (text === null || text === undefined) return '';
|
|
|
|
|
return String(text)
|
|
|
|
|
.replace(/&/g, "&")
|
|
|
|
|
.replace(/</g, "<")
|
|
|
|
|
.replace(/>/g, ">")
|
|
|
|
|
.replace(/"/g, """)
|
|
|
|
|
.replace(/'/g, "'");
|
2026-10-06 19:24:25 +00:00
|
|
|
};
|
2026-06-02 03:49:38 +00:00
|
|
|
|
|
|
|
|
window.SpotNotes.deleteNote = async function(noteId) {
|
2026-05-12 23:58:18 +00:00
|
|
|
if (!confirm("Delete this note?")) return;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/notes/${noteId}`, { method: 'DELETE' });
|
|
|
|
|
if (res.ok) {
|
2026-06-02 03:49:38 +00:00
|
|
|
if (window.SpotNotes.activePadId) {
|
|
|
|
|
window.SpotNotes.loadNotesForPad(window.SpotNotes.activePadId);
|
2026-05-12 23:58:18 +00:00
|
|
|
} else {
|
|
|
|
|
alert("Note deleted. Returning to notepad list.");
|
2026-06-02 03:49:38 +00:00
|
|
|
window.SpotNotes.loadNotepads();
|
2026-05-12 23:58:18 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
alert("Failed to delete note.");
|
|
|
|
|
}
|
|
|
|
|
} catch (err) { console.error(err); }
|
2026-06-02 03:49:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 => {
|
2026-10-06 19:24:25 +00:00
|
|
|
return (note.title || '').toLowerCase().includes(filterLower) || (note.content || '').toLowerCase().includes(filterLower);
|
2026-06-02 03:49:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (filteredNotes.length === 0) {
|
|
|
|
|
sidebarBody.innerHTML = '<p style="padding:10px; color:#666;">No notes match.</p>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filteredNotes.forEach(note => {
|
|
|
|
|
const item = document.createElement('div');
|
2026-06-23 19:23:45 +00:00
|
|
|
item.className = 'note-card';
|
2026-06-02 03:49:38 +00:00
|
|
|
|
|
|
|
|
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 = `
|
2026-06-23 19:23:45 +00:00
|
|
|
<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>
|
2026-06-02 03:49:38 +00:00
|
|
|
`;
|
|
|
|
|
} else if (window.SpotNotes.activePadIsPublic) {
|
2026-06-23 19:23:45 +00:00
|
|
|
actionBtns = `<button class="note-card-link report" onclick="event.stopPropagation(); window.SpotNotes.reportNote(${note.id})">Report</button>`;
|
2026-06-02 03:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:23:45 +00:00
|
|
|
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>`;
|
|
|
|
|
|
2026-06-02 03:49:38 +00:00
|
|
|
const dateStr = new Date(note.created_at).toLocaleString();
|
2026-10-06 19:24:25 +00:00
|
|
|
const coordStr = (note.lat !== undefined && note.lat !== 0) ? `${note.lat.toFixed(5)}, ${note.lng.toFixed(5)}` : '';
|
|
|
|
|
const lockIcon = note.is_encrypted ? '🔒 ' : '';
|
2026-06-02 03:49:38 +00:00
|
|
|
|
|
|
|
|
item.innerHTML = `
|
2026-10-06 19:24:25 +00:00
|
|
|
<div class="note-card-title">${lockIcon}${window.SpotNotes.escapeHtml(note.title)}</div>
|
2026-06-23 19:23:45 +00:00
|
|
|
<div class="note-card-body">${window.SpotNotes.escapeHtml(note.content)}</div>
|
|
|
|
|
<div class="note-card-meta">
|
2026-10-06 19:24:25 +00:00
|
|
|
${dateStr} ${coordStr ? '<br>' + coordStr : ''}
|
2026-06-02 03:49:38 +00:00
|
|
|
</div>
|
2026-06-23 19:23:45 +00:00
|
|
|
<div class="note-card-actions">
|
2026-10-06 19:24:25 +00:00
|
|
|
${(note.lat && note.lng) ? `<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>` : ''}
|
2026-06-23 19:23:45 +00:00
|
|
|
${shareBtn}
|
|
|
|
|
${actionBtns}
|
|
|
|
|
</div>
|
2026-06-02 03:49:38 +00:00
|
|
|
`;
|
|
|
|
|
item.addEventListener('click', () => {
|
|
|
|
|
window.SpotNotes.stopTracking();
|
2026-10-06 19:24:25 +00:00
|
|
|
if (note.lat && note.lng) {
|
|
|
|
|
window.SpotNotes.map.setView([note.lat, note.lng], 16);
|
|
|
|
|
}
|
2026-06-02 03:49:38 +00:00
|
|
|
});
|
|
|
|
|
sidebarBody.appendChild(item);
|
|
|
|
|
});
|
|
|
|
|
};
|
2026-06-23 19:35:17 +00:00
|
|
|
|
|
|
|
|
window.SpotNotes.loadDefaultNotes = async function() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch('/api/notes/default');
|
|
|
|
|
if (!res.ok) throw new Error("Failed to load");
|
|
|
|
|
const notes = await res.json();
|
2026-10-06 19:24:25 +00:00
|
|
|
|
|
|
|
|
// Decrypt any encrypted notes if user has access
|
|
|
|
|
const decryptedNotes = await Promise.all(notes.map(async (note) => {
|
|
|
|
|
if (note.is_encrypted && window.SpotCrypto) {
|
|
|
|
|
let padKey = window.SpotCrypto.getCachedPadKey(note.notepad_id);
|
|
|
|
|
if (!padKey && note.notepad && note.notepad.encrypted_key) {
|
|
|
|
|
padKey = await window.SpotCrypto.unwrapNotepadKey(note.notepad_id, note.notepad.encrypted_key);
|
|
|
|
|
}
|
|
|
|
|
return await window.SpotCrypto.decryptNote(note, padKey);
|
|
|
|
|
}
|
|
|
|
|
return note;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
window.SpotNotes.activeNotes = decryptedNotes;
|
2026-06-23 19:35:17 +00:00
|
|
|
window.SpotNotes.renderMapMarkers();
|
|
|
|
|
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
2026-10-06 19:24:25 +00:00
|
|
|
const validCoordsNotes = decryptedNotes.filter(n => n.lat && n.lng);
|
|
|
|
|
if (validCoordsNotes.length > 0 && !params.has('pad') && !params.has('note')) {
|
|
|
|
|
const bounds = L.latLngBounds(validCoordsNotes.map(n => [n.lat, n.lng]));
|
2026-06-23 19:35:17 +00:00
|
|
|
window.SpotNotes.map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15 });
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Error loading default notes:", err);
|
|
|
|
|
}
|
|
|
|
|
};
|