Files
Brad Arant c1491b044b Add IMAP webmail tag, admin/opensource subpage tiers, store/cart, htmleditor, uploads
New __imap*/__smtpsend/__eval Jet tags give templates real IMAP
(list/message/flag/delete/move/search/folders/accounts) and SMTP-send
capability, backing the new /email webmail client (email.jet/
email.menu, assets/images/email.js + newemail.js) and its
mainpagetemplate.jet-based page shell. Most of this tag family's
source was already being built into the live site (Tag.cpp already
registered __imap/__imaplist/__imapmessage/__imapflag/__imapdelete/
__eval) but had never actually been committed; this also registers
and commits the remaining five (__imapmove/__imapfolders/
__imapsearch/__smtpsend/__imapaccounts).

admin.page/admin.menu gain a real subpage tier (admin-email,
admin-newemail, admin-invoices, admin-products, admin-users) replacing
several previously top-level pages, mirrored by a similar
opensource.page subpage tier (opensource-overview/asm1802/barantmail/
jet/serverframework/synthmax) - old top-level pages retired via git
rename (asm1802.page, jet.page, serverframework.page,
admin_invoices.page); barantmail.page is dropped outright, folded into
opensource-barantmail.subpage.

New storefront (store.page, store-createstore.subpage,
barantstore.page) and cart flow (cart.jet, updatecartqty.proc,
removecartitem.proc, editproduct.page/selectproduct.proc/
update_product.proc for inventory maintenance against the barant
database).

New document upload/download manager (documents.jet, documents_api.jet,
documents_upload.proc, documents_delete.proc,
assets/images/documents.js + documents-page.js) with a persistent
floating panel, and a rich-text page editor (htmleditor.jet/.page,
createhtmleditor.proc, savehtmleditor.proc, assets/images/htmleditor.js
+ its icon/logo assets).

SESSIONCTL.tag: login now redirects back to the originally-requested
page instead of always landing on /account ($[start:0] vs $[start]
variable-name mismatch).

main.css/menu.bar/invoices.menu/jet_overview.section/login.menu:
styling and navigation updates for the above; new site assets (fonts,
images, audio) referenced by them.

jetc/build-assets.sh, list_jet_files.sh, lowercase_jet.py,
strip_fragments.py: small dev tooling used while building the above.
2026-09-06 04:14:00 +00:00

679 lines
22 KiB
JavaScript

function b64encode(str) {
return btoa(unescape(encodeURIComponent(str)));
}
function submitNewLetter(form) {
var name = document.getElementById('filename_display').value;
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
alert('File name may only contain letters, numbers, underscores and hyphens.');
return false;
}
form.filename.value = name;
return true;
}
var mainpage;
var control;
var result;
var mousedownx;
var mousedowny;
var dragHint = "move";
var data;
var showGrid = false;
var snapToGrid = false;
var gridSize = 10;
var selected;
var firstSelected;
var itemparameters;
var grid;
var context;
var draggingHTML;
var window_dragging = false;
const undo = [];
var undoIndex = 0;
var lastRedo;
var mode = 'MOUSEUP';
var textobj;
var lastdragover;
var pendingDragover;
var dropIndicator;
var sequence = 0;
function showDropIndicator(target) {
if(!dropIndicator) {
dropIndicator = document.createElement('div');
dropIndicator.style.position = 'fixed';
dropIndicator.style.height = '4px';
dropIndicator.style.background = '#0088ff';
dropIndicator.style.zIndex = '99999';
dropIndicator.style.pointerEvents = 'none';
document.body.appendChild(dropIndicator);
}
let rect = target.getBoundingClientRect();
dropIndicator.style.left = rect.left + 'px';
dropIndicator.style.width = rect.width + 'px';
dropIndicator.style.top = (rect.top - 2) + 'px';
dropIndicator.style.display = 'block';
}
function hideDropIndicator() {
if(dropIndicator) {
dropIndicator.style.display = 'none';
}
}
function saveUndo() {
undo[undoIndex++] = mainpage.innerHTML;
}
function do_undo() {
if(undoIndex > 0) {
mainpage.innerHTML = undo[--undoIndex];
}
return true;
}
function do_redo() {
return true;
}
function init() {
mainpage = document.getElementById("mainpage");
data = document.getElementById("data");
itemparameters = document.getElementById("itemparameters");
var gridsize = document.getElementById("gridsize");
gridsize.value = gridSize;
grid = document.getElementById("grid");
context = grid.getContext("2d");
drawGrid();
buildToolbar();
document.designMode = 'off';
}
function getLayerMap(element) {
var map = '';
if(element.id != 'mainpage') {
var curelem = element;
while(curelem.id != 'mainpage') {
map = '<' + curelem.nodeName + '>' + map;
curelem = curelem.parentNode;
}
}
document.getElementById('map').innerHTML = map;
}
function jetEscape(text) {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function buildToolbar() {
let tbitems = [
{
"label": "&lt;div>",
"html": "<div id=\"newitem\" contenteditable=\"false\" style=\"width: 300px; height: 200px; background-color: #789;\"></div>"
},
{
"label": "&lt;input>",
"html": "<input id=\"newitem\" type=\"text\">"
},
{
"label": "&lt;button>",
"html": "<button id=\"newitem\" type=\"button\">Label</button>"
},
{
"label": "&lt;password>",
"html": "<input id=\"newitem\" type=\"password\">"
},
{
"label": "&lt;checkbox>",
"html": "<input id=\"newitem\" type=\"checkbox\">"
},
{
"label": "&lt;label>",
"html": "<label style=\"color: #000f;\" id=\"newitem\">Label text</label>"
},
{
"label": "&lt;p>",
"html": "<p style=\"color: #000f;\" id=\"newitem\">Type Here</p>"
},
{
"label": "&lt;hr>",
"html": "<hr id=\"newitem\">"
},
{
"label": "Jet: " + "$" + "[var]",
"html": "<code id=\"newitem\" contenteditable=\"true\" style=\"display:inline-block; background:#eef; border:1px dashed #99c; padding:4px; font-family:monospace; white-space:pre;\">" + jetEscape("$" + "[varname]") + "</code>"
},
{
"label": "Jet: &lt;set&gt;",
"html": "<code id=\"newitem\" contenteditable=\"true\" style=\"display:inline-block; background:#eef; border:1px dashed #99c; padding:4px; font-family:monospace; white-space:pre;\">" + jetEscape("<set name=\"varname\" value=\"\">") + "</code>"
},
{
"label": "Jet: &lt;if&gt;",
"html": "<code id=\"newitem\" contenteditable=\"true\" style=\"display:inline-block; background:#eef; border:1px dashed #99c; padding:4px; font-family:monospace; white-space:pre-wrap;\">" + jetEscape("<if value1=\"" + "$" + "[varname]\" value2=\"\" type=\"eq\">\ncontent\n</if>") + "</code>"
}
];
tbitems.forEach(doit);
function doit(currentValue, index) {
let index2 = index + 1;
let toolbar = document.getElementById('toolbar');
var itemHTML = '<button type="button" id="button' + index2;
itemHTML += '" style="width: 100%; height: 40px; border: 2px solid green;"';
itemHTML += '>' + currentValue.label + '</button>';
toolbar.insertAdjacentHTML("beforeend", itemHTML);
let id = 'button' + index2;
let button = document.getElementById(id);
button.html = currentValue.html;
button.addEventListener("mousedown",
(e) => {
e.preventDefault();
saveUndo();
draggingHTML = currentValue.html;
mainpage.insertAdjacentHTML('beforeend', draggingHTML);
let newEl = mainpage.lastElementChild;
newEl.style.position = 'absolute';
newEl.style.zIndex = '10000';
newEl.style.left = '0px';
newEl.style.top = '0px';
startNewItemDrag(newEl);
}
, false);
}
}
function startNewItemDrag(el) {
let rect = mainpage.getBoundingClientRect();
function ndMove(e) {
el.style.left = (e.clientX - rect.left - el.offsetWidth / 2) + 'px';
el.style.top = (e.clientY - rect.top - el.offsetHeight / 2) + 'px';
}
function ndUp(e) {
document.removeEventListener('mousemove', ndMove);
document.removeEventListener('mouseup', ndUp);
draggingHTML = null;
if((typeof selected !== 'undefined') && selected) {
selected.style.outline = '';
}
selected = el;
selected.style.outline = '2px solid #0088ff';
getLayerMap(selected);
displayParameters();
mode = "MOUSEUP";
}
document.addEventListener('mousemove', ndMove);
document.addEventListener('mouseup', ndUp);
}
function window_mousedown(win, e) {
mousedownx = e.offsetX;
mousedowny = e.offsetY;
if(e.offsetX >= (win.clientWidth - 3)) {
win.style.cursor = 'ew-resize';
window_dragging = true;
}
else {
win.style.cursor = 'default';
window_dragging = false;
}
}
function window_mousemove(win, e) {
}
function window_mouseup(win, e) {
}
function drawGrid() {
if(showGrid == true) {
context.clearRect(0,0,grid.width,grid.height);
context.lineWidth = 0.5;
context.strokeStyle = "#808080";
for(var ix = 0; ix < grid.width; ix += gridSize) {
context.beginPath();
context.moveTo(ix, 0);
context.lineTo(ix, grid.height);
context.stroke();
context.beginPath();
context.moveTo(0, ix);
context.lineTo(grid.width, ix);
context.stroke();
}
} else {
context.clearRect(0,0,grid.width,grid.height);
}
}
function resetDragHint(e) {
if(!selected) {
return;
}
if(selected.style.position == 'absolute') {
if((e.offsetX >= (parseFloat(selected.style.width) - 10)) && (e.offsetY >= (parseFloat(selected.style.height) - 10))) {
selected.style.cursor = "nwse-resize";
dragHint = "rightbottomresize";
} else if((e.offsetY < 10) && (e.offsetX >= (parseFloat(selected.style.width) - 10))) {
selected.style.cursor = "nesw-resize";
dragHint = "righttopresize";
} else if((e.offsetX <= 10) && (e.offsetY >= (parseFloat(selected.style.height) - 10))) {
selected.style.cursor = "nesw-resize";
dragHint = "leftbottomresize";
} else if((e.offsetX <= 10) && (e.offsetY <= 10)) {
selected.style.cursor = "nwse-resize";
dragHint = "lefttopresize";
} else if(e.offsetX >= (parseFloat(selected.style.width) - 10)) {
selected.style.cursor = "ew-resize";
dragHint = "rightresize";
} else if(e.offsetY >= (parseFloat(selected.style.height) - 10)) {
selected.style.cursor = "ns-resize";
dragHint = "bottomresize";
} else if(e.offsetX <= 10) {
selected.style.cursor = "ew-resize";
dragHint = "leftresize";
} else if(e.offsetY <= 10) {
selected.style.cursor = "ns-resize";
dragHint = "topresize";
} else {
selected.style.cursor = "move";
dragHint = "move";
}
} else {
if((e.offsetX >= (parseFloat(selected.style.width) - 10)) && (e.offsetY >= (parseFloat(selected.style.height) - 10))) {
selected.style.cursor = "nwse-resize";
dragHint = "rightbottomresize";
} else if(e.offsetX >= (parseFloat(selected.style.width) - 10)) {
selected.style.cursor = "ew-resize";
dragHint = "rightresize";
} else if(e.offsetY >= (parseFloat(selected.style.height) - 10)) {
selected.style.cursor = "ns-resize";
dragHint = "bottomresize";
} else {
selected.style.cursor = "move";
dragHint = "move";
}
}
}
function mousedown(e) {
let mouseabove = document.elementFromPoint(e.clientX, e.clientY);
if((typeof selected === 'undefined') || (selected != mouseabove)) {
mode = 'NEWSELECTION';
if((typeof selected !== 'undefined') && (selected.nodeName == "DIV")) {
document.designMode = 'off';
selected.contentEditable = false;
selected.blur();
selected.style.userSelect = 'none';
}
if((typeof selected !== 'undefined') && selected) {
selected.style.outline = '';
}
selected = mouseabove;
selected.style.outline = '2px solid #0088ff';
getLayerMap(selected);
} else {
if(mode != "TEXT") {
mode = "DOWNONSELECTED";
}
}
if(mode != 'TEXT') {
resetDragHint(e);
}
mousedownx = e.clientX;
mousedowny = e.clientY;
displayParameters();
return true;
}
function mouseup(e) {
if(mode == "TEXT") {
return true;
}
if(mode == "DOWNONSELECTED") {
if((selected.nodeName == "DIV") || (selected.nodeName == "CODE")) {
document.designMode = 'on';
selected.style.userSelect = 'auto';
selected.contentEditable = true;
selected.style.cursor = 'text';
selected.focus();
mode = "TEXT";
return true;
}
}
if(draggingHTML) {
draggingHTML = null;
}
if(selected && (mode == 'MOVED') && (selected.style.position == 'absolute')) {
selected.style.pointerEvents = 'none';
let dragover = document.elementFromPoint(e.clientX, e.clientY);
selected.style.pointerEvents = 'auto';
if(dragover.nodeName == 'DIV') {
if(dragover.id != selected.parentElement.id) {
dragover.appendChild(selected);
selected.style.left = ((selected.getBoundingClientRect().x + e.offsetX - mousedownx) - dragover.getBoundingClientRect().x) + 'px';
selected.style.top = ((selected.getBoundingClientRect().y + e.offsetY - mousedowny) - dragover.getBoundingClientRect().y) + 'px';
}
}
} else if(selected && (mode == 'MOVED') && pendingDragover) {
pendingDragover.prepend(selected);
selected.style.left = parseFloat(pendingDragover.style.left) + "px";
selected.style.top = parseFloat(pendingDragover.style.top) + "px";
}
pendingDragover = null;
hideDropIndicator();
mode = "MOUSEUP";
return true;
}
function mousemove(e) {
if(mode == "MOUSEUP") {
resetDragHint(e);
} else {
if(mode == "TEXT") {
return true;
}
if((selected.style.position == 'static') ||
(typeof selected.style.position === 'undefined') ||
(selected.style.position == '')) {
if(dragHint == "move") {
if(mode != "MOVED") {
mode = "MOVED";
}
selected.style.pointerEvents = 'none';
let dragover = document.elementFromPoint(e.clientX, e.clientY);
selected.style.pointerEvents = 'auto';
if(dragover.nodeName == 'DIV') {
pendingDragover = dragover;
showDropIndicator(dragover);
}
return true;
} else if(dragHint == "rightbottomresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.width = (parseFloat(selected.style.width) + e.movementX) + "px";
selected.style.height = (parseFloat(selected.style.height) + e.movementY) + "px";
} else if(dragHint == "rightresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.width = (parseFloat(selected.style.width) + e.movementX) + "px";
} else if(dragHint == "bottomresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.height = (parseFloat(selected.style.height) + e.movementY) + "px";
}
} else if(selected.style.position == 'absolute') {
if(dragHint == "move") {
if(mode != "MOVED") {
saveUndo();
mode = "MOVED";
}
selected.style.left = (parseFloat(selected.style.left) + e.movementX) + "px";
selected.style.top = (parseFloat(selected.style.top) + e.movementY) + "px";
} else if(dragHint == "rightbottomresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.width = (parseFloat(selected.style.width) + e.movementX) + "px";
selected.style.height = (parseFloat(selected.style.height) + e.movementY) + "px";
} else if(dragHint == "lefttopresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.left = (parseFloat(selected.style.left) + e.movementX) + "px";
selected.style.top = (parseFloat(selected.style.top) + e.movementY) + "px";
selected.style.width = (parseFloat(selected.style.width) - e.movementX) + "px";
selected.style.height = (parseFloat(selected.style.height) - e.movementY) + "px";
} else if(dragHint == "leftbottomresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.left = (parseFloat(selected.style.left) + e.movementX) + "px";
selected.style.width = (parseFloat(selected.style.width) - e.movementX) + "px";
selected.style.height = (parseFloat(selected.style.height) + e.movementY) + "px";
} else if(dragHint == "righttopresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.top = (parseFloat(selected.style.top) + e.movementY) + "px";
selected.style.height = (parseFloat(selected.style.height) - e.movementY) + "px";
selected.style.width = (parseFloat(selected.style.width) + e.movementX) + "px";
} else if(dragHint == "rightresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.width = (parseFloat(selected.style.width) + e.movementX) + "px";
} else if(dragHint == "bottomresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.height = (parseFloat(selected.style.height) + e.movementY) + "px";
} else if(dragHint == "leftresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.left = (parseFloat(selected.style.left) + e.movementX) + "px";
selected.style.width = (parseFloat(selected.style.width) - e.movementX) + "px";
} else if(dragHint == "topresize") {
if(mode != "RESIZED") {
saveUndo();
mode = "RESIZED";
}
selected.style.top = (parseFloat(selected.style.top) + e.movementY) + "px";
selected.style.height = (parseFloat(selected.style.height) - e.movementY) + "px";
}
displayParameters();
}
return true;
}
}
function heading(name) {
let properties = document.getElementById('properties');
properties.insertAdjacentHTML('beforeend', "<tr><td colspan=\"2\" style=\"background-color: #abc; padding: 4px; font-weight: bold;\">" + name + ":</td></tr>");
}
function setColorProperty(event) {
selected.style.setProperty(event.target.id, document.getElementById(event.target.id).value);
}
function getColorProperty(cssprop) {
let value = selected.style.getPropertyValue(cssprop);
if(value.substring(0, 4) == "rgb(") {
document.getElementById(cssprop).value = value;
}
else {
selected.style.removeProperty(cssprop);
}
}
function setSizeProperty(event) {
var value = document.getElementById(event.target.id);
var units = document.getElementById(event.target.id + "Units");
if(value.value == "") {
units.value = "";
} else if(units && units.value == "") {
units.value = "px";
}
if(units) {
selected.style.setProperty(event.target.id, value.value + units.value);
}
}
function getSizeProperty(cssprop) {
let size = selected.style.getPropertyValue(cssprop);
if(size) {
document.getElementById(cssprop).value = parseInt(size);
document.getElementById(cssprop + "Units").value = size.replace(/[0-9]/g, '');
}
}
function property(name, payload, cssprop, initfun, changefun) {
let properties = document.getElementById('properties');
properties.insertAdjacentHTML('beforeend', "<tr><td style=\"background-color: #cdf; padding: 3px 4px; vertical-align: middle; white-space: nowrap;\">" + name + ":</td><td style=\"background-color: #cdf; padding: 3px 4px; vertical-align: middle;\">" + payload + "</td></tr>");
if(initfun) {
initfun(cssprop);
}
if(cssprop) {
document.getElementById(cssprop).addEventListener('input', changefun);
let prop = document.getElementById(cssprop + "Units");
if(prop) {
prop.addEventListener('change', changefun);
}
}
}
function propsize(title, cssprop) {
property(title,
"<div style=\"display: flex; align-items: center; gap: 4px;\">" +
"<input type=\"number\" id=\"" + cssprop + "\" style=\"flex: 1 1 auto; min-width: 0; box-sizing: border-box;\">" +
"<select id=\"" + cssprop + "Units\" style=\"flex: 0 0 auto;\">" +
" <option value=\"\"></option>" +
" <option value=\"px\">px</option>" +
" <option value=\"%\">%</option>" +
"</select></div>",
cssprop,
getSizeProperty,
setSizeProperty);
}
function propcolor(title, cssprop) {
property(title,
"<input type=\"color\" id=\"" + cssprop + "\" style=\"height: 18px; width: 50px; margin: 0px; padding: 0px; border: none; width:85%;\">",
cssprop,
getColorProperty,
setColorProperty);
}
function displayParameters() {
if(!selected) {
itemparameters.innerHTML = "";
return;
}
itemparameters.innerHTML = "<div style=\"width: 300px; max-width: 300px;\"><table id=\"properties\" style=\"width: 300px; table-layout: fixed;\"><colgroup><col style=\"width: 35%;\"><col style=\"width: 65%;\"></colgroup></table></div>";
property("Type", selected.nodeName);
property("Id", selected.id);
property("Display",
"<select id=\"display\" style=\"width: 100%; float: right;\">" +
" <option value=\"inline\">Inline</option>" +
" <option value=\"block\">Block</option>" +
" <option value=\"inline-block\">Inline Block</option>" +
" <option value=\"none\">None</option>" +
"</select>",
"display",
() => { document.getElementById('display').value = selected.style.display; },
() => { selected.style.display = document.getElementById('display').value; }
);
if(selected.id != "mainpage") {
heading("Geometry");
property("Position",
"<select id=\"position\" style=\"width: 100%; float: right;\">" +
" <option value=\"absolute\">Absolute</option>" +
" <option value=\"static\">Static</option>" +
" <option value=\"relative\">Relative</option>" +
"</select>",
"position",
() => { document.getElementById('position').value = selected.style.position; },
() => { saveUndo();
selected.style.position = document.getElementById('position').value;
if(document.getElementById('position').value == 'absolute') {
selected.style.left = selected.getBoundingClientRect().x + 'px';
selected.style.top = selected.getBoundingClientRect().y + 'px';
}
displayParameters();
}
);
if(selected.style.position == 'absolute') {
propsize("Left", "left");
propsize("Top", "top");
}
propsize("Width", "width");
propsize("Height", "height");
}
if(selected.nodeName != 'IMG') {
heading("Font");
propcolor("Color", "color");
}
heading("Margins");
propsize("Left", "margin-left");
propsize("Top", "margin-top");
propsize("Right", "margin-right");
propsize("Bottom", "margin-bottom");
if(selected.id != "mainpage") {
heading("Border");
propsize("Width", "border-width");
propcolor("Color", "border-color");
}
heading("Background");
propcolor("Color", "background-color");
heading("Padding");
propsize("Left", "padding-left");
propsize("Top", "padding-top");
propsize("Right", "padding-right");
propsize("Bottom", "padding-bottom");
}
function newImage(filename, e) {
saveUndo();
let img = '<img id="newitem" src="/assets/' + filename + '" style="width: 150px;">';
mainpage.insertAdjacentHTML('beforeend', img);
}
function handleFiles(files) {
}
function b64encode(str) {
return btoa(unescape(encodeURIComponent(str)));
}
function save(form) {
form.html.value = b64encode(document.getElementById('mainpage').innerHTML);
form.exit.value = '';
form.submit();
}
function savenexit(form) {
form.html.value = b64encode(document.getElementById('mainpage').innerHTML);
form.exit.value = 'yes';
form.submit();
}
function exitnosave(form) {
form.html.value = '';
form.exit.value = 'yes';
form.submit();
}