Add global music player, invoicing/payment system, account subpages, mail account admin, and the qualityiron.vegas Jet 2.0 port
Jet interpreter (Tag.cpp, __call.cpp, __imap*.cpp/h, __smtpsend.cpp/h, __imapemptyfolder.cpp/h - new): assorted parser/tag fixes found across this work - a stray literal-tag-in-comment and backslash-quote parser corruption class, a dangling-pointer fix, a __call.cpp fd leak, IMAP folder-management tags, multi-account/per-host IMAP support, and a smtpsend Message-ID fix. barant.com site (jetc/barant-site/): - A site-wide persistent music player (header band, playlist/queue, stereo FFT EQ, session-persisted state) fed by new library/ ianslibrary/music song-list subpages. - /account split into info/phones/invoices subpages; a full invoicing and Square payment system (create/edit/copy/pay/email invoice); admin invoice management and mail-account management subpages. - /services split into overview/hosting/siptrunking/documents subpages, retiring the old standalone pages. - SIPPHONE.tag: a Jet 2.0 WebRTC softphone tag. - main.css/spa.js/email.js: the SPA loading indicator, the music player band styling, and assorted fixes accumulated alongside this work. qualityiron-site/: the qualityiron.vegas job/contractor/customer/ document management app, ported from its original Jet 1.4 codebase to Jet 2.0 as barant.coms APIServers second multi-tenant site. wcwc/wcwc.jet: the WCWC site Jet source (wcwc.jtc build output not included). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
905fbafa5a
commit
b8f7793773
406 files changed
+15803
-587
No files matched your search
@@ -43,6 +43,13 @@
|
||||
pageNext:
|
||||
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
|
||||
'<path d="M9 18l6-6-6-6"/>' +
|
||||
'</svg>',
|
||||
emptyFolder:
|
||||
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
|
||||
'<path d="M4 7h16"/>' +
|
||||
'<path d="M9 7V4h6v3"/>' +
|
||||
'<path d="M6 7l1 13a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-13"/>' +
|
||||
'<path d="M9.5 12.5l5 5M14.5 12.5l-5 5"/>' +
|
||||
'</svg>'
|
||||
};
|
||||
|
||||
@@ -109,6 +116,15 @@
|
||||
var links = document.querySelectorAll('.email-folder-link');
|
||||
for (var i = 0; i < links.length; i++)
|
||||
links[i].classList.toggle('email-folder-selected', links[i].getAttribute('data-folder') === currentFolder);
|
||||
// Only Spam/Trash ever offer this - emptying Inbox (or any future
|
||||
// folder) as a single destructive click isn't something any real
|
||||
// mail client exposes, and the server enforces the exact same
|
||||
// Spam/Trash-only allowlist independently (see email.jet) - this is
|
||||
// just keeping the button from ever being visible somewhere it
|
||||
// would 400 anyway, not the actual safety boundary.
|
||||
var emptyBtn = document.getElementById('email-empty-folder-btn');
|
||||
if (emptyBtn)
|
||||
emptyBtn.hidden = (currentFolder !== 'Spam' && currentFolder !== 'Trash');
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
@@ -158,6 +174,7 @@
|
||||
'<button type="button" id="email-search-btn" class="icon-btn" title="Search">' + ICONS.search + '</button>' +
|
||||
'</span>' +
|
||||
'<button type="button" id="email-compose-btn" class="icon-btn" title="New Mail">' + ICONS.compose + '</button>' +
|
||||
'<button type="button" id="email-empty-folder-btn" class="icon-btn" title="Permanently empty this folder" hidden>' + ICONS.emptyFolder + '</button>' +
|
||||
'<button type="button" id="email-refresh-btn" class="icon-btn" title="Refresh">' + ICONS.refresh + '</button>' +
|
||||
'</span>' +
|
||||
'</div>' +
|
||||
@@ -218,6 +235,7 @@
|
||||
document.getElementById('email-refresh-btn').addEventListener('click', function() {
|
||||
loadMessageList();
|
||||
});
|
||||
document.getElementById('email-empty-folder-btn').addEventListener('click', emptyCurrentFolder);
|
||||
|
||||
// Matches sender, subject, or body content in whatever folder is
|
||||
// currently open (see __imap.cpp's searchMessages()) - an empty
|
||||
@@ -444,6 +462,38 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Permanently deletes every message in the CURRENT folder (only ever
|
||||
// reachable while viewing Spam or Trash - see syncActiveFolder()'s own
|
||||
// button-visibility gate, and email.jet's independent server-side
|
||||
// allowlist, which is the real enforcement). Unlike trashMessage()
|
||||
// there's no undo, no "move somewhere" - a real, human-readable confirm()
|
||||
// is the whole safety net here, matching how Gmail/Outlook both gate
|
||||
// "Empty Trash now" behind exactly one extra click and nothing fancier.
|
||||
function emptyCurrentFolder() {
|
||||
if (currentFolder !== 'Spam' && currentFolder !== 'Trash')
|
||||
return;
|
||||
if (!window.confirm('Permanently delete every message in ' + currentFolder + '? This cannot be undone.'))
|
||||
return;
|
||||
var btn = document.getElementById('email-empty-folder-btn');
|
||||
if (btn) btn.disabled = true;
|
||||
post('/email/emptyfolder', { folder: currentFolder })
|
||||
.then(function(res) {
|
||||
if (btn) btn.disabled = false;
|
||||
if (!res.ok) {
|
||||
window.alert(res.error || 'Could not empty the folder.');
|
||||
return;
|
||||
}
|
||||
currentUid = null;
|
||||
document.getElementById('email-reading-pane').innerHTML =
|
||||
'<div id="email-empty-state">Select a message to read it.</div>';
|
||||
loadMessageList();
|
||||
})
|
||||
.catch(function() {
|
||||
if (btn) btn.disabled = false;
|
||||
window.alert('Empty-folder request failed.');
|
||||
});
|
||||
}
|
||||
|
||||
// Same fade-out/fetch-concurrently/fade-in shape as openMessage() and
|
||||
// spa.js's own navigate() - every list refresh (folder switch,
|
||||
// refresh button) gets the same smooth transition instead of a flash
|
||||
@@ -488,8 +538,10 @@
|
||||
if (!skipFade)
|
||||
list.classList.remove('email-fading');
|
||||
})
|
||||
.catch(function() {
|
||||
list.innerHTML = '<div id="email-empty-state">Could not load messages.</div>';
|
||||
.catch(function(err) {
|
||||
console.error('loadMessageList failed:', err);
|
||||
list.innerHTML = '<div id="email-empty-state">Could not load messages' +
|
||||
(err && err.message ? ': ' + escapeHtml(err.message) : '.') + '</div>';
|
||||
if (!skipFade)
|
||||
list.classList.remove('email-fading');
|
||||
});
|
||||
@@ -506,8 +558,10 @@
|
||||
// cosmetic problem, not worth surfacing as a user-visible failure.
|
||||
function loadFolderCounts() {
|
||||
post('/email/folders', {}).then(function(res) {
|
||||
if (res.error)
|
||||
if (res.error) {
|
||||
console.error('loadFolderCounts server error:', res.error);
|
||||
return;
|
||||
}
|
||||
var folders = ['Inbox', 'Spam', 'Trash'];
|
||||
for (var i = 0; i < folders.length; i++) {
|
||||
var f = folders[i];
|
||||
@@ -515,7 +569,9 @@
|
||||
if (span && res[f])
|
||||
span.textContent = res[f].messages > 0 ? (res[f].unseen + '/' + res[f].messages) : '';
|
||||
}
|
||||
}).catch(function() {});
|
||||
}).catch(function(err) {
|
||||
console.error('loadFolderCounts failed:', err);
|
||||
});
|
||||
}
|
||||
|
||||
function loadAccounts() {
|
||||
@@ -756,8 +812,10 @@
|
||||
}
|
||||
pane.classList.remove('email-fading');
|
||||
})
|
||||
.catch(function() {
|
||||
pane.innerHTML = '<div id="email-empty-state">Could not load message.</div>';
|
||||
.catch(function(err) {
|
||||
console.error('openMessage failed:', err);
|
||||
pane.innerHTML = '<div id="email-empty-state">Could not load message' +
|
||||
(err && err.message ? ': ' + escapeHtml(err.message) : '.') + '</div>';
|
||||
pane.classList.remove('email-fading');
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user