Files
client/www/js/utilities.mjs
T
2026-04-17 16:52:51 -04:00

1811 lines
57 KiB
JavaScript

// ██ ██ ████████ ██ ██ ██ ████████ ██ ███████ ███████
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
// ██ ██ ██ ██ ██ ██ ██ ██ █████ ███████
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
// ██████ ██ ██ ███████ ██ ██ ██ ███████ ███████
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// CONSTANTS
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export const objMimeTypes = {
html: "text/html",
jpeg: "image/jpeg",
jpg: "image/jpeg",
png: "image/png",
gif: "image/gif",
webp: "image/webp",
svg: "image/svg+xml",
json: "application/json",
js: "text/javascript",
css: "text/css",
ico: "image/x-icon",
mp3: "audio/mp3",
mjs: "text/javascript",
};
export const arrImageExt = [".apng", ".avif", ".gif", ".jpg", ".jpeg", ".jfif", ".pjpeg", ".pjp", ".png", ".svg", ".webp"];
export const htmlCenteringWrapper = `<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>#title#</title>
<style>
.clsMain {
display: flex;
justify-content: center;
align-items: center;
}
</style>
</head>
<body>
<div class="clsMain">
#body#
</div>
</body>
</html>`;
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// TIME ROUTINES
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export function timeConverter(UNIX_timestamp) {
var a = new Date(UNIX_timestamp * 1000);
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var year = a.getFullYear();
var month = months[a.getMonth()];
var date = a.getDate();
var hour = pad(a.getHours(), 2);
var min = pad(a.getMinutes(), 2);
var sec = pad(a.getSeconds(), 2);
var time = date + " " + month + " " + year + " " + hour + ":" + min + "." + sec;
return time;
}
export function strTechDate(d) {
return (
("0" + d.getFullYear()).slice(-2) +
`.` +
("0" + (d.getMonth() + 1)).slice(-2) +
`.` +
("0" + d.getDate()).slice(-2) +
" " +
("0" + d.getHours()).slice(-2) +
`:` +
("0" + d.getMinutes()).slice(-2)
// ("0" + d.getSeconds()).slice(-2)
);
}
export function wait(ms) {
var start = Date.now();
now = start;
while (now - start < ms) {
now = Date.now();
}
}
export function PythonTimestamp() {
var ts = (new Date() / 1000).toFixed(0);
return Number(ts);
}
export function intTimestampSeconds() {
var ts = (new Date() / 1000).toFixed(0);
return Number(ts);
}
export function JSTimestamp() {
var ts = new Date().toFixed(0);
return Number(ts);
}
export function JSTimestampMidnight() {
var d = new Date();
var Day = d.getDate();
var Month = d.getMonth() + 1;
var Year = d.getFullYear();
var ToDay = new Date(Year + "-" + Month + "-" + Day);
// var ts = (ToDay / 1000).toFixed(0) // For seconds
// console.log('Midnight',Year,Month,Day)
return Number(ToDay);
}
export const DaysBetweenDates = (dteDate1, dteDate2) => {
let d1 = new Date(dteDate1);
let d2 = new Date(dteDate2);
if (d1 instanceof Date && d2 instanceof Date) {
return Math.floor((d2 - d1) / 86400000);
} else {
return `error`;
}
};
export const HoursBetweenDates = (dteDate1, dteDate2) => {
let d1 = new Date(dteDate1);
let d2 = new Date(dteDate2);
if (d1 instanceof Date && d2 instanceof Date) {
return Math.floor((d2 - d1) / 3600000);
} else {
return `error`;
}
};
export const strCountdown = (numSeconds) => {
var d = Math.floor(numSeconds / (3600 * 24));
var h = Math.floor((numSeconds % (3600 * 24)) / 3600);
var m = Math.floor((numSeconds % 3600) / 60);
var s = Math.floor(numSeconds % 60);
h = ("0" + h).slice(-2);
m = ("0" + m).slice(-2);
s = ("0" + s).slice(-2);
return `${d}:${h}:${m}:${s}`;
};
Date.prototype.addDays = function (h) {
this.setTime(this.getTime() + h * 24 * 60 * 60 * 1000);
return this;
};
Date.prototype.addHours = function (h) {
this.setTime(this.getTime() + h * 60 * 60 * 1000);
return this;
};
Date.prototype.addMinutes = function (h) {
this.setTime(this.getTime() + h * 60 * 1000);
return this;
};
Date.prototype.addSeconds = function (h) {
this.setTime(this.getTime() + h * 1000);
return this;
};
export const secHourAgo = () => {
const Now = Math.floor(Date.now() / 1000);
return Now - 60 * 60;
};
export const secDayAgo = () => {
const Now = Math.floor(Date.now() / 1000);
return Now - 60 * 60 * 24;
};
export const secWeekAgo = () => {
const Now = Math.floor(Date.now() / 1000);
return Now - Now - 60 * 60 * 24 * 7;
};
export const secMonthAgo = () => {
const Now = Math.floor(Date.now() / 1000);
return Now - 60 * 60 * 24 * 30;
};
export const secYearAgo = () => {
const Now = Math.floor(Date.now() / 1000);
return Now - 60 * 60 * 24 * 365;
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// MATH ROUTINES
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export const hash53 = (str, seed = 0) => {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
r1 = 4294967296 * (2097151 & h2) + (h1 >>> 0);
return r1.toString(16);
};
// PAD A NUMBER WITH LEADING ZEROES
export const pad = (num, size) => {
num = num.toString();
while (num.length < size) num = "0" + num;
return num;
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// FILE ROUTINES
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export const arrAllFilesInSubdirectories = async (dir) => {
// import { resolve } from 'path'
// import { readdir } from 'fs/promises'
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
dirents.map((dirent) => {
const res = resolve(dir, dirent.name);
return dirent.isDirectory() ? arrAllFilesInSubdirectories(res) : res;
})
);
return Array.prototype.concat(...files);
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// GEO LOCATION
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export function getFlagEmoji(countryCode) {
const codePoints = countryCode
.toUpperCase()
.split("")
.map((char) => 127397 + char.charCodeAt());
return String.fromCodePoint(...codePoints);
}
export const objCountryCode = {
AF: "Afghanistan",
AX: "Aland Islands",
AL: "Albania",
DZ: "Algeria",
AS: "American Samoa",
AD: "Andorra",
AO: "Angola",
AI: "Anguilla",
AQ: "Antarctica",
AG: "Antigua And Barbuda",
AR: "Argentina",
AM: "Armenia",
AW: "Aruba",
AU: "Australia",
AT: "Austria",
AZ: "Azerbaijan",
BS: "Bahamas",
BH: "Bahrain",
BD: "Bangladesh",
BB: "Barbados",
BY: "Belarus",
BE: "Belgium",
BZ: "Belize",
BJ: "Benin",
BM: "Bermuda",
BT: "Bhutan",
BO: "Bolivia",
BA: "Bosnia And Herzegovina",
BW: "Botswana",
BV: "Bouvet Island",
BR: "Brazil",
IO: "British Indian Ocean Territory",
BN: "Brunei Darussalam",
BG: "Bulgaria",
BF: "Burkina Faso",
BI: "Burundi",
KH: "Cambodia",
CM: "Cameroon",
CA: "Canada",
CV: "Cape Verde",
KY: "Cayman Islands",
CF: "Central African Republic",
TD: "Chad",
CL: "Chile",
CN: "China",
CX: "Christmas Island",
CC: "Cocos (Keeling) Islands",
CO: "Colombia",
KM: "Comoros",
CG: "Congo",
CD: "Congo, Democratic Republic",
CK: "Cook Islands",
CR: "Costa Rica",
CI: 'Cote D"Ivoire',
HR: "Croatia",
CU: "Cuba",
CY: "Cyprus",
CZ: "Czech Republic",
DK: "Denmark",
DJ: "Djibouti",
DM: "Dominica",
DO: "Dominican Republic",
EC: "Ecuador",
EG: "Egypt",
SV: "El Salvador",
GQ: "Equatorial Guinea",
ER: "Eritrea",
EE: "Estonia",
ET: "Ethiopia",
FK: "Falkland Islands (Malvinas)",
FO: "Faroe Islands",
FJ: "Fiji",
FI: "Finland",
FR: "France",
GF: "French Guiana",
PF: "French Polynesia",
TF: "French Southern Territories",
GA: "Gabon",
GM: "Gambia",
GE: "Georgia",
DE: "Germany",
GH: "Ghana",
GI: "Gibraltar",
GR: "Greece",
GL: "Greenland",
GD: "Grenada",
GP: "Guadeloupe",
GU: "Guam",
GT: "Guatemala",
GG: "Guernsey",
GN: "Guinea",
GW: "Guinea-Bissau",
GY: "Guyana",
HT: "Haiti",
HM: "Heard Island & Mcdonald Islands",
VA: "Holy See (Vatican City State)",
HN: "Honduras",
HK: "Hong Kong",
HU: "Hungary",
IS: "Iceland",
IN: "India",
ID: "Indonesia",
IR: "Iran, Islamic Republic Of",
IQ: "Iraq",
IE: "Ireland",
IM: "Isle Of Man",
IL: "Israel",
IT: "Italy",
JM: "Jamaica",
JP: "Japan",
JE: "Jersey",
JO: "Jordan",
KZ: "Kazakhstan",
KE: "Kenya",
KI: "Kiribati",
KR: "Korea",
KP: "North Korea",
KW: "Kuwait",
KG: "Kyrgyzstan",
LA: 'Lao People"s Democratic Republic',
LV: "Latvia",
LB: "Lebanon",
LS: "Lesotho",
LR: "Liberia",
LY: "Libyan Arab Jamahiriya",
LI: "Liechtenstein",
LT: "Lithuania",
LU: "Luxembourg",
MO: "Macao",
MK: "Macedonia",
MG: "Madagascar",
MW: "Malawi",
MY: "Malaysia",
MV: "Maldives",
ML: "Mali",
MT: "Malta",
MH: "Marshall Islands",
MQ: "Martinique",
MR: "Mauritania",
MU: "Mauritius",
YT: "Mayotte",
MX: "Mexico",
FM: "Micronesia, Federated States Of",
MD: "Moldova",
MC: "Monaco",
MN: "Mongolia",
ME: "Montenegro",
MS: "Montserrat",
MA: "Morocco",
MZ: "Mozambique",
MM: "Myanmar",
NA: "Namibia",
NR: "Nauru",
NP: "Nepal",
NL: "Netherlands",
AN: "Netherlands Antilles",
NC: "New Caledonia",
NZ: "New Zealand",
NI: "Nicaragua",
NE: "Niger",
NG: "Nigeria",
NU: "Niue",
NF: "Norfolk Island",
MP: "Northern Mariana Islands",
NO: "Norway",
OM: "Oman",
PK: "Pakistan",
PW: "Palau",
PS: "Palestinian Territory, Occupied",
PA: "Panama",
PG: "Papua New Guinea",
PY: "Paraguay",
PE: "Peru",
PH: "Philippines",
PN: "Pitcairn",
PL: "Poland",
PT: "Portugal",
PR: "Puerto Rico",
QA: "Qatar",
RE: "Reunion",
RO: "Romania",
RU: "Russian Federation",
RW: "Rwanda",
BL: "Saint Barthelemy",
SH: "Saint Helena",
KN: "Saint Kitts And Nevis",
LC: "Saint Lucia",
MF: "Saint Martin",
PM: "Saint Pierre And Miquelon",
VC: "Saint Vincent And Grenadines",
WS: "Samoa",
SM: "San Marino",
ST: "Sao Tome And Principe",
SA: "Saudi Arabia",
SN: "Senegal",
RS: "Serbia",
SC: "Seychelles",
SL: "Sierra Leone",
SG: "Singapore",
SK: "Slovakia",
SI: "Slovenia",
SB: "Solomon Islands",
SO: "Somalia",
ZA: "South Africa",
GS: "South Georgia And Sandwich Isl.",
ES: "Spain",
LK: "Sri Lanka",
SD: "Sudan",
SR: "Suriname",
SJ: "Svalbard And Jan Mayen",
SZ: "Swaziland",
SE: "Sweden",
CH: "Switzerland",
SY: "Syrian Arab Republic",
TW: "Taiwan",
TJ: "Tajikistan",
TZ: "Tanzania",
TH: "Thailand",
TL: "Timor-Leste",
TG: "Togo",
TK: "Tokelau",
TO: "Tonga",
TT: "Trinidad And Tobago",
TN: "Tunisia",
TR: "Turkey",
TM: "Turkmenistan",
TC: "Turks And Caicos Islands",
TV: "Tuvalu",
UG: "Uganda",
UA: "Ukraine",
AE: "United Arab Emirates",
GB: "United Kingdom",
US: "United States",
UM: "United States Outlying Islands",
UY: "Uruguay",
UZ: "Uzbekistan",
VU: "Vanuatu",
VE: "Venezuela",
VN: "Vietnam",
VG: "Virgin Islands, British",
VI: "Virgin Islands, U.S.",
WF: "Wallis And Futuna",
EH: "Western Sahara",
YE: "Yemen",
ZM: "Zambia",
ZW: "Zimbabwe",
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// HTML ROUTINES
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Shared inbound-text sanitization helpers (NFC + optional mojibake repair)
export const normalizeTextNfc = (value) => String(value || '').normalize('NFC');
export const repairMojibakeIfNeeded = (value) => {
const input = normalizeTextNfc(value);
if (!input) return input;
const suspiciousPattern = /(?:Ã.|Â.|â.|ðŸ|Ý|)/;
if (!suspiciousPattern.test(input)) return input;
try {
const bytes = Uint8Array.from(Array.from(input, (ch) => ch.charCodeAt(0) & 0xff));
const decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes).normalize('NFC');
const score = (text) => (text.match(/(?:Ã.|Â.|â.|ðŸ|Ý|)/g) || []).length;
return score(decoded) < score(input) ? decoded : input;
} catch (_) {
return input;
}
};
export const sanitizeInboundText = (value) => repairMojibakeIfNeeded(normalizeTextNfc(value));
// USED TO PROCESS SOCIAL POSTS FROM TEXT TO HTML
export const htmlFormatText = (txt) => {
txt = normalizeTextNfc(txt);
var rgxURL = /\bhttps?:\/\/[^\s<]+/gi;
var rgxImg = /(.png|.jpg|.jpeg|.gif|.png|.svg|.webp)\b/i;
var rgxNostr = /nostr:(npub1|nprofile1|note1|nevent1|naddr1)[a-z0-9]+/gi;
var rgxVideoExt = /\.(mp4|webm|ogg|mov|m4v)(?:$|[?#])/i;
console.log('[utilities.htmlFormatText] input preview:', String(txt || '').slice(0, 180));
// First pass: replace nostr: entities with renderable placeholders.
const nostrMatches = txt.match(rgxNostr) || [];
console.log('[utilities.htmlFormatText] nostr matches:', nostrMatches.length, nostrMatches);
for (const rawMatch of nostrMatches) {
const fullMatch = String(rawMatch);
const bech32 = fullMatch.replace(/^nostr:/i, '');
let decoded;
try {
decoded = window?.NostrTools?.nip19?.decode?.(bech32);
} catch (_) {
decoded = null;
}
const typeFromPrefix = bech32.startsWith('npub1')
? 'npub'
: bech32.startsWith('nprofile1')
? 'nprofile'
: bech32.startsWith('note1')
? 'note'
: bech32.startsWith('nevent1')
? 'nevent'
: bech32.startsWith('naddr1')
? 'naddr'
: null;
const resolvedType = decoded?.type || typeFromPrefix;
if (!resolvedType) {
console.log('[utilities.htmlFormatText] unresolved nostr entity, skipping:', bech32.slice(0, 24) + '…');
continue;
}
console.log('[utilities.htmlFormatText] replacing nostr entity:', {
bech32: bech32.slice(0, 24) + '…',
resolvedType,
decodeType: decoded?.type || null
});
if (resolvedType === 'nprofile') {
const pubkey = decoded?.data?.pubkey || null;
const shortLabel = pubkey ? (pubkey.slice(0, 8) + '…') : bech32.slice(0, 16) + '…';
const href = pubkey
? `post.html?npub=${pubkey}&profile=true&post=false`
: `nostr:${bech32}`;
const pubkeyAttr = pubkey ? ` data-pubkey="${pubkey}"` : '';
const replacement = `<a class="nostr-mention" data-entity="${bech32}"${pubkeyAttr} href="${href}" target="_blank" rel="noopener noreferrer"><span class="nostr-mention-name">@${shortLabel}</span></a>`;
txt = txt.replace(fullMatch, replacement);
continue;
}
if (resolvedType === 'npub') {
const pubkey = decoded?.data || null;
const shortLabel = pubkey ? (pubkey.slice(0, 8) + '…') : bech32.slice(0, 16) + '…';
const href = pubkey
? `post.html?npub=${pubkey}&profile=true&post=false`
: `post.html?npub=${bech32}&profile=true&post=false`;
const pubkeyAttr = pubkey ? ` data-pubkey="${pubkey}"` : '';
const replacement = `<a class="nostr-mention" data-entity="${bech32}"${pubkeyAttr} href="${href}" target="_blank" rel="noopener noreferrer"><span class="nostr-mention-name">@${shortLabel}</span></a>`;
txt = txt.replace(fullMatch, replacement);
continue;
}
if (resolvedType === 'note' || resolvedType === 'nevent') {
const eventId = resolvedType === 'note' ? decoded?.data : decoded?.data?.id;
const eventIdAttr = eventId ? ` data-event-id="${eventId}"` : '';
const replacement = `<div class="nostr-embed" data-entity="${bech32}"${eventIdAttr}><div class="nostr-embed-label">Referenced note</div><div class="nostr-embed-content">Loading…</div></div>`;
txt = txt.replace(fullMatch, replacement);
continue;
}
if (resolvedType === 'naddr') {
const replacement = `<div class="nostr-embed" data-entity="${bech32}"><div class="nostr-embed-label">Referenced content</div><div class="nostr-embed-content">Loading…</div></div>`;
txt = txt.replace(fullMatch, replacement);
}
}
// Second pass: regular URLs and image links on plain-text chunks only
// (avoid touching URLs inside already-generated HTML attributes).
const isVideoUrl = (candidate) => {
if (!candidate || typeof candidate !== 'string') return false;
if (rgxVideoExt.test(candidate)) return true;
try {
const parsed = new URL(candidate);
if (!/^https?:$/i.test(parsed.protocol)) return false;
const pathname = parsed.pathname || '';
return rgxVideoExt.test(pathname);
} catch (_) {
return false;
}
};
const replaceUrlsInTextChunk = (chunk) => {
const arrLinks = chunk.match(rgxURL) || [];
if (arrLinks.length > 0) {
console.log('[utilities.htmlFormatText] text-chunk url matches:', arrLinks);
}
for (const EachLink of arrLinks) {
if (rgxImg.test(EachLink)) {
chunk = chunk.replace(EachLink, `<img src="${EachLink}">`);
} else if (isVideoUrl(EachLink)) {
chunk = chunk.replace(
EachLink,
`<video class="post-video-embed" src="${EachLink}" controls playsinline preload="metadata"></video>`
);
} else {
chunk = chunk.replace(EachLink, `<a href="${EachLink}" target="_blank" rel="noopener noreferrer">${EachLink}</a>`);
}
}
return chunk;
};
const htmlAwareParts = txt.split(/(<[^>]+>)/g);
console.log('[utilities.htmlFormatText] html-aware part count:', htmlAwareParts.length);
txt = htmlAwareParts
.map((part) => (part.startsWith('<') ? part : replaceUrlsInTextChunk(part)))
.join('');
console.log('[utilities.htmlFormatText] output preview:', String(txt || '').slice(0, 220));
return txt;
};
export const arrImageLinks = async (htmlSearch) => {
// Return a list of image links from an HTML string
let setImg = new Set([]);
let linkStart = 0;
let linkEnd = 0;
let strStartTag = "src=";
let strEndTag = '"';
if (htmlSearch) {
let strHTML = htmlSearch.replace(/'/g, '"'); //replace all single q's w doubles
linkStart = strHTML.indexOf(strStartTag);
while (linkStart > 0) {
strHTML = strHTML.slice(linkStart + strStartTag.length + 1);
linkEnd = strHTML.indexOf(strEndTag);
setImg.add(strHTML.slice(0, linkEnd));
linkStart = strHTML.indexOf(strStartTag);
} onclick="ToggleFullScreen()"
}
return Array.from(setImg); //remove duplicates
};
export const RemoveTagContent = (strHTML, strStartTag, strEndTag) => {
while (strHTML.indexOf(strStartTag) > 0 && strHTML.indexOf(strEndTag) > strHTML.indexOf(strStartTag)) {
strHTML = strHTML.slice(0, strHTML.indexOf(strStartTag)) + strHTML.slice(strHTML.indexOf(strEndTag) + strEndTag.length);
}
return strHTML;
};
// GET THE QUERY STRING FROM THE URL OF A WEB PAGE AND RETURN IT IN AN OBJECT
export const objQueryStr = async () => {
let output = {};
if (window.location.search) {
var queryParams = window.location.search.substring(1);
var listQueries = queryParams.split("&");
for (var query in listQueries) {
if (listQueries.hasOwnProperty(query)) {
var queryPair = listQueries[query].split("=");
output[queryPair[0]] = decodeURIComponent(queryPair[1] || "");
}
}
}
return output;
};
export const CopyValToClipboard = async (val) => {
navigator.clipboard.writeText(val);
};
//SELECT ALL CONTENT WHEN EDITABLE DIV GETS FOCUS
export const SelAllContent = async (id) => {
var div = document.getElementById(id);
window.setTimeout(function () {
var sel, range;
if (window.getSelection && document.createRange) {
range = document.createRange();
range.selectNodeContents(div);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(div);
range.select();
}
}, 10);
};
export const ToggleFullScreen = async () => {
let IsFullScreen = false;
console.log(
window.outerHeight,
window.innerHeight,
window.screen.height,
window.innerWidth,
screen.width,
window.innerHeight / screen.height,
window.innerWidth / screen.width
);
if (window.innerHeight / screen.height > 0.9 && window.innerWidth / screen.width > 0.9) {
IsFullScreen = true;
}
if ((window.outerHeight == window.innerHeight) == screen.height) {
IsFullScreen = true;
}
console.log("Is full")
console.log(IsFullScreen)
if (IsFullScreen) {
document.exitFullscreen();
document.getElementById("divFullScreen").innerHTML = `<div id="divFullScreen" class="divHeaderButtons">
<svg class="svgHeaderButtons" viewBox="0 0 10 10">
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="5" y1="2" x2="5" y2="8" />
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="5" y1="2" x2="3" y2="4" />
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="5" y1="2" x2="7" y2="4" />
</svg>
</div>`;
} else {
let elem = document.documentElement;
elem.requestFullscreen();
document.getElementById("divFullScreen").innerHTML = `<div id="divFullScreen" class="divHeaderButtons">
<svg class="svgHeaderButtons" viewBox="0 0 10 10">
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="5" y1="2" x2="5" y2="8" />
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="7" y1="6" x2="5" y2="8" />
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="3" y1="6" x2="5" y2="8" />
</svg>
</div>`;
}
};
export const ToggleSpellCheck = async () => {
console.log("Spell check");
let SpellCheck = !divBody.spellcheck;
divBody.spellcheck = SpellCheck;
SpellCheck = !taNote.spellcheck;
taNote.spellcheck = SpellCheck;
if (SpellCheck) {
document.getElementById("svgCheckPath").setAttribute("stroke", "black");
} else {
document.getElementById("svgCheckPath").setAttribute("stroke", "lightgray");
}
};
export const ConvertStringToType = async (str) => {
if (typeof str == "undefined") {
str = "";
} else if (!isNaN(str)) {
str = parseFloat(str);
} else if (str == "true") {
str = true;
} else if (str == "false") {
str = false;
}
//CHECK FOR ARRAYS
else if (str.slice(0, 1) == "[" && str.slice(-1) == "]") {
str = JSON.parse(str);
}
//CHECK FOR OBJECTS
else if (str.slice(0, 1) == "{" && str.slice(-1) == "}") {
str = JSON.parse(str);
}
//REMOVE LEADING AND TRAILING QUOTES IF IT HAS THEM
else if (str.slice(0, 1) == '"' && str.slice(-1) == '"') {
str = str.slice(1, -1);
}
return str;
};
// MEASURE THE WIDTH OF SOME TEXT
// font = "30px Arial"
export const displayTextWidth = async (text, font) => {
let canvas = displayTextWidth.canvas || (displayTextWidth.canvas = document.createElement("canvas"));
let context = canvas.getContext("2d");
context.font = font;
let metrics = context.measureText(text);
return metrics.width;
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// DOM ELEMENT CREATION
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// CREATE AN SELECT ELEMENT CONTAINING THE FIELDS FROM THE GIVEN DATABASE
export const selDBFields = async (db = objDB, optFirst = "", IncludeSysFields = false) => {
//GET A SET OF ALL THE FIELD NAMES, CHECKING EVERY ROW.
let setFields = new Set([]);
for (let Each of objDB.rows) {
for (let KeyName of Object.keys(Each))
try {
setFields.add(KeyName);
} catch {}
}
let arrFld = Array.from(setFields);
arrFld.sort();
//REMOVE SYS FIELDS IF REQUESTED
if (!IncludeSysFields) {
var i = arrFld.length;
while (i--) {
if (arrFld[i].slice(0, 1) == `_`) {
arrFld.splice(i, 1);
}
}
}
//CREATE THE SELECT ELEMENT
let SelOut = document.createElement(`select`);
//IF THERE IS TO BE A SPECIAL FIRST 'TITLE' OPTION, WITH NO VALUE
if (optFirst != "") {
let opt = document.createElement(`option`);
opt.innerHTML = optFirst;
opt.value = "";
SelOut.appendChild(opt);
}
//ADD ALL THE FIELDS TO THE SELECT ELEMENT
for (Each of arrFld) {
let opt = document.createElement(`option`);
opt.value = Each;
opt.innerHTML = Each;
SelOut.appendChild(opt);
}
return SelOut;
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// OBJECT AND ARRAY ROUTINES
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// FOR AN ARRAY OF OBJECTS, GET THE VALUE OF THE OBJECT THAT HAS A
// PARTICULAR KEY
// Example
// arrObj =[{"id":"svg01"},{"cx":5},{"cy":5},{"r":4},{"stroke":"black"}]
// Key = "id"
// The function will return "svg01"
export const ValInArr = async (arrObj, Key) => {
console.log(arrObj);
for (let Ob of arrObj) {
if (Object.keys(Ob)[0] == Key) {
return Ob[Object.keys(Ob)[0]];
}
}
return "";
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// SORTING
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export const arrOfObjSort = async (arr, key, asc = true) => {
//CHECK IF THIS KEY IS NUMERIC OR A STRING. ALSO LOOK FOR UNDEFINED
let Numeric = true;
for (let Each of arr) {
if (typeof Each[key] != "undefined") {
if (isNaN(Each[key])) {
Numeric = false;
break;
}
}
}
if (Numeric && asc) {
arr.sort((a, b) => {
if (typeof a[key] == "undefined") {
return -1;
}
if (typeof b[key] == "undefined") {
return 1;
}
return a[key] - b[key];
});
} else if (Numeric && !asc) {
arr.sort((a, b) => {
if (typeof a[key] == "undefined") {
return 1;
}
if (typeof b[key] == "undefined") {
return -1;
}
return b[key] - a[key];
});
} else if (asc) {
arr.sort((a, b) => {
if (typeof a[key] == "undefined") {
return 1;
}
if (typeof b[key] == "undefined") {
return -1;
}
let fa = a[key].toLowerCase(),
fb = b[key].toLowerCase();
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
});
} else {
arr.sort((a, b) => {
if (typeof a[key] == "undefined") {
return -1;
}
if (typeof b[key] == "undefined") {
return 1;
}
let fa = a[key].toLowerCase(),
fb = b[key].toLowerCase();
if (fa > fb) {
return -1;
}
if (fa < fb) {
return 1;
}
return 0;
});
}
return arr;
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// UTILITIES
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// base-x encoding / decoding
// Copyright (c) 2018 base-x contributors
// Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp)
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.
export function basex(ALPHABET) {
if (ALPHABET.length >= 255) {
throw new TypeError("Alphabet too long");
}
var BASE_MAP = new Uint8Array(256);
for (var j = 0; j < BASE_MAP.length; j++) {
BASE_MAP[j] = 255;
}
for (var i = 0; i < ALPHABET.length; i++) {
var x = ALPHABET.charAt(i);
var xc = x.charCodeAt(0);
if (BASE_MAP[xc] !== 255) {
throw new TypeError(x + " is ambiguous");
}
BASE_MAP[xc] = i;
}
var BASE = ALPHABET.length;
var LEADER = ALPHABET.charAt(0);
var FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up
var iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up
function encode(source) {
if (source instanceof Uint8Array) {
} else if (ArrayBuffer.isView(source)) {
source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
} else if (Array.isArray(source)) {
source = Uint8Array.from(source);
}
if (!(source instanceof Uint8Array)) {
throw new TypeError("Expected Uint8Array");
}
if (source.length === 0) {
return "";
}
// Skip & count leading zeroes.
var zeroes = 0;
var length = 0;
var pbegin = 0;
var pend = source.length;
while (pbegin !== pend && source[pbegin] === 0) {
pbegin++;
zeroes++;
}
// Allocate enough space in big-endian base58 representation.
var size = ((pend - pbegin) * iFACTOR + 1) >>> 0;
var b58 = new Uint8Array(size);
// Process the bytes.
while (pbegin !== pend) {
var carry = source[pbegin];
// Apply "b58 = b58 * 256 + ch".
var i = 0;
for (var it1 = size - 1; (carry !== 0 || i < length) && it1 !== -1; it1--, i++) {
carry += (256 * b58[it1]) >>> 0;
b58[it1] = carry % BASE >>> 0;
carry = (carry / BASE) >>> 0;
}
if (carry !== 0) {
throw new Error("Non-zero carry");
}
length = i;
pbegin++;
}
// Skip leading zeroes in base58 result.
var it2 = size - length;
while (it2 !== size && b58[it2] === 0) {
it2++;
}
// Translate the result into a string.
var str = LEADER.repeat(zeroes);
for (; it2 < size; ++it2) {
str += ALPHABET.charAt(b58[it2]);
}
return str;
}
function decodeUnsafe(source) {
if (typeof source !== "string") {
throw new TypeError("Expected String");
}
if (source.length === 0) {
return new Uint8Array();
}
var psz = 0;
// Skip and count leading '1's.
var zeroes = 0;
var length = 0;
while (source[psz] === LEADER) {
zeroes++;
psz++;
}
// Allocate enough space in big-endian base256 representation.
var size = ((source.length - psz) * FACTOR + 1) >>> 0; // log(58) / log(256), rounded up.
var b256 = new Uint8Array(size);
// Process the characters.
while (source[psz]) {
// Decode character
var carry = BASE_MAP[source.charCodeAt(psz)];
// Invalid character
if (carry === 255) {
return;
}
var i = 0;
for (var it3 = size - 1; (carry !== 0 || i < length) && it3 !== -1; it3--, i++) {
carry += (BASE * b256[it3]) >>> 0;
b256[it3] = carry % 256 >>> 0;
carry = (carry / 256) >>> 0;
}
if (carry !== 0) {
throw new Error("Non-zero carry");
}
length = i;
psz++;
}
// Skip leading zeroes in b256.
var it4 = size - length;
while (it4 !== size && b256[it4] === 0) {
it4++;
}
var vch = new Uint8Array(zeroes + (size - it4));
var j = zeroes;
while (it4 !== size) {
vch[j++] = b256[it4++];
}
return vch;
}
function decode(string) {
var buffer = decodeUnsafe(string);
if (buffer) {
return buffer;
}
throw new Error("Non-base" + BASE + " character");
}
return {
encode: encode,
decodeUnsafe: decodeUnsafe,
decode: decode,
};
}
const BASE02 = `01`;
const BASE06 = `123456`;
const BASE08 = `01234567`;
const BASE10 = `0123456789`;
const BASE16 = `0123456789abcdef`;
const BASE58 = `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`;
// No 0, I, O, l
const BASE64 = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`;
export const bs02 = basex(BASE02);
export const bs06 = basex(BASE06);
export const bs08 = basex(BASE08);
export const bs10 = basex(BASE10);
export const bs16 = basex(BASE16);
export const bs58 = basex(BASE58);
export const bs64 = basex(BASE64);
// Examples
// decoded = bs58.decode('5Kd3')
// input must be a Uint8Array, Buffer, or an Array. It returns a string.
// encoded = bs16.encode(decoded))
export const BaseEncodeDecode = async (Val, Base, Enc = true) => {
//ENCODE
if (Enc) {
switch (Base) {
case `bs02`:
return bs02.encode(Val);
case `bs06`:
return bs06.encode(Val);
case `bs08`:
return bs08.encode(Val);
case `bs10`:
return bs10.encode(Val);
case `bs16`:
return bs16.encode(Val);
case `bs58`:
return bs58.encode(Val);
case `bs64`:
return bs64.encode(Val);
case `utf8`:
let Dec = new TextDecoder("utf-8");
return Dec.decode(Val);
}
}
//DECODE
else {
switch (Base) {
case `bs02`:
return bs02.decode(Val);
case `bs06`:
return bs06.decode(Val);
case `bs08`:
return bs08.decode(Val);
case `bs10`:
return bs10.decode(Val);
case `bs16`:
return bs16.decode(Val);
case `bs58`:
return bs58.decode(Val);
case `bs64`:
return bs64.decode(Val);
case `utf8`:
let Enc = new TextEncoder();
return Enc.encode(Val);
}
}
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// PAD JSON DB ROUTINES SERVER
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
export const objReadJSONFile = async (docPath) => {
const data = await fs.promises.readFile(docPath, "utf8");
return JSON.parse(data);
};
export const objWriteJSONFile = async (docPath, objData) => {
await fs.promises.writeFile(docPath, JSON.stringify(objData, null, 2));
};
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// SVG
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
{
/* <svg id="SVG-51" viewBox="0 0 10 10" _index="0" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="5" y1="2" x2="5" y2="8" />
<line stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="black" stroke-opacity="1" fill="black" fill-opacity="0" x1="2" y1="5" x2="8" y2="5" />
</svg> */
}
export const svgIcon = async (Type) => {
// DownArrow, UpArrow, DownCarrot, RightCarrot, X, Play, Pause, Plus,
// Hamburger, HamburgerV, Circle
let xmlns = "http://www.w3.org/2000/svg";
const svgEle = document.createElementNS(xmlns, "svg");
svgEle.setAttributeNS(null, "viewBox", "0 0 10 10");
svgEle.setAttributeNS(null, "stroke-width", 2);
svgEle.setAttributeNS(null, "stroke-linecap", `round`);
svgEle.setAttributeNS(null, "stroke-linejoin", `round`);
svgEle.setAttributeNS(null, "fill-opacity", 0);
if (Type == `Plus`) {
const svgLine1 = document.createElementNS(xmlns, "line");
svgLine1.setAttributeNS(null, "x1", 5);
svgLine1.setAttributeNS(null, "y1", 2);
svgLine1.setAttributeNS(null, "x2", 5);
svgLine1.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine1);
const svgLine2 = document.createElementNS(xmlns, "line");
svgLine2.setAttributeNS(null, "x1", 2);
svgLine2.setAttributeNS(null, "y1", 5);
svgLine2.setAttributeNS(null, "x2", 8);
svgLine2.setAttributeNS(null, "y2", 5);
svgEle.appendChild(svgLine2);
return svgEle;
}
if (Type == `X`) {
const svgLine1 = document.createElementNS(xmlns, "line");
svgLine1.setAttributeNS(null, "x1", 2);
svgLine1.setAttributeNS(null, "y1", 2);
svgLine1.setAttributeNS(null, "x2", 8);
svgLine1.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine1);
const svgLine2 = document.createElementNS(xmlns, "line");
svgLine2.setAttributeNS(null, "x1", 8);
svgLine2.setAttributeNS(null, "y1", 2);
svgLine2.setAttributeNS(null, "x2", 2);
svgLine2.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine2);
return svgEle;
}
if (Type == `Minus`) {
const svgLine2 = document.createElementNS(xmlns, "line");
svgLine2.setAttributeNS(null, "x1", 2);
svgLine2.setAttributeNS(null, "y1", 5);
svgLine2.setAttributeNS(null, "x2", 8);
svgLine2.setAttributeNS(null, "y2", 5);
svgEle.appendChild(svgLine2);
return svgEle;
}
if (Type == `Play`) {
const svgPolygon = document.createElementNS(xmlns, "polygon");
svgPolygon.setAttributeNS(null, "points", (points = "2,8 8,5 2,2 "));
svgEle.appendChild(svgPolygon);
return svgEle;
}
if (Type == `1`) {
const svgLine1 = document.createElementNS(xmlns, "line");
svgLine1.setAttributeNS(null, "x1", 5);
svgLine1.setAttributeNS(null, "y1", 2);
svgLine1.setAttributeNS(null, "x2", 5);
svgLine1.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine1);
return svgEle;
}
if (Type == `2`) {
const svgLine1 = document.createElementNS(xmlns, "line");
svgLine1.setAttributeNS(null, "x1", 3);
svgLine1.setAttributeNS(null, "y1", 2);
svgLine1.setAttributeNS(null, "x2", 3);
svgLine1.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine1);
const svgLine2 = document.createElementNS(xmlns, "line");
svgLine2.setAttributeNS(null, "x1", 7);
svgLine2.setAttributeNS(null, "y1", 2);
svgLine2.setAttributeNS(null, "x2", 7);
svgLine2.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine2);
return svgEle;
}
if (Type == `3`) {
const svgLine1 = document.createElementNS(xmlns, "line");
svgLine1.setAttributeNS(null, "x1", 2);
svgLine1.setAttributeNS(null, "y1", 2);
svgLine1.setAttributeNS(null, "x2", 2);
svgLine1.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine1);
const svgLine2 = document.createElementNS(xmlns, "line");
svgLine2.setAttributeNS(null, "x1", 5);
svgLine2.setAttributeNS(null, "y1", 2);
svgLine2.setAttributeNS(null, "x2", 5);
svgLine2.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine2);
const svgLine3 = document.createElementNS(xmlns, "line");
svgLine3.setAttributeNS(null, "x1", 8);
svgLine3.setAttributeNS(null, "y1", 2);
svgLine3.setAttributeNS(null, "x2", 8);
svgLine3.setAttributeNS(null, "y2", 8);
svgEle.appendChild(svgLine3);
return svgEle;
}
if (Type == `carrot_down`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", "M2,2 L5,7 L8,2");
svgEle.appendChild(svgPath);
return svgEle;
}
if (Type == `carrot_right`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", "M2,2 L8,5 L2,8");
svgEle.appendChild(svgPath);
return svgEle;
}
if (Type == `carrot_up`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", "M2,8 L5,2 L8,8");
svgEle.appendChild(svgPath);
return svgEle;
}
if (Type == `carrot_left`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", "M8,2 L2,5 L8,8");
svgEle.appendChild(svgPath);
return svgEle;
}
if (Type == `pencil`) {
const svgPolygon = document.createElementNS(xmlns, "polygon");
svgPolygon.setAttributeNS(null, "points", "2,8 2,6 6,2 8,4 4,8");
// svgPolygon.setAttributeNS(null, 'points', points="2,8 2,6 6,2 8,4 4,8")
svgEle.appendChild(svgPolygon);
return svgEle;
}
if (Type == `check`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", (points = "M2,6 L4,8 L8,2"));
svgEle.appendChild(svgPath);
return svgEle;
}
if (Type == `arrow_up_right`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", (points = "M2,8 L8,2 L5,2 L8,2 L8,5"));
svgEle.appendChild(svgPath);
return svgEle;
}
if (Type == `box`) {
const svgPath = document.createElementNS(xmlns, "path");
svgPath.setAttributeNS(null, "d", (points = "M2,2 L8,2 L8,8 L2,8 L2,2 L2,2 L8,2 L8,8 L2,8 L2,2"));
svgEle.appendChild(svgPath);
return svgEle;
}
};
export const htmlFromObjSVG = async (objSVG) => {
let htmlOut = `<svg `;
// console.log(objSVG.attributes)
for (let key in objSVG.attributes) {
htmlOut += `${key}="${objSVG.attributes[key]}" `;
}
htmlOut += xmlSVGTagHeader;
htmlOut += `> \n`;
for (let Ele of objSVG.elements) {
htmlOut += `<${Ele._element_type} `;
// console.log("Ele", Ele)
for (let key in Ele) {
if (key.slice(0, 1) != "_") {
htmlOut += `${key}="${Ele[key]}" `;
}
}
htmlOut += `/> \n`;
}
htmlOut += `</svg> `;
return htmlOut;
};
//////////////////////////////////////////////////////////////////////////////
// HASHTAGS
//////////////////////////////////////////////////////////////////////////////
// RETURN ALL THE USED HASHTAGS
export const objHashtags = async (db = DB) => {
let arrOut = [];
let objOut = {};
for (let Row of db.rows) {
arrOut = arrOut.concat(Row.hashtags);
}
arrOut = [...new Set(arrOut)];
arrOut.sort();
for (let H of arrOut) {
objOut[H] = { Enabled: false };
}
return objOut;
};
export const ToggleHashtags = async (hashtag, row, db = DB) => {
if (hashtag.slice(0, 1) != `#`) {
hashtag = `#${hashtag}`;
}
//REMOVE HASHTAG
if (db.rows[row].hashtags.includes(hashtag)) {
var i = db.rows[row].hashtags.length;
while (i--) {
if (db.rows[row].hashtags[i] == hashtag) {
db.rows[row].hashtags.splice(i, 1);
}
}
}
//ADD HASHTAG
else {
db.rows[row].hashtags.push(hashtag);
}
};
//////////////////////////////////////////////////////////////////////////////
// NOSTR
//////////////////////////////////////////////////////////////////////////////
export const objNostrEvents = [
{ 0: { name: "Metadata" } },
{ 1: { name: "Short Text Note" } },
{ 2: { name: "Recommend Relay" } },
{ 3: { name: "Contacts" } },
{ 4: { name: "Encrypted Direct Messages" } },
{ 5: { name: "Event Deletion" } },
{ 6: { name: "Repost" } },
{ 7: { name: "Reaction" } },
{ 8: { name: "Badge Award" } },
{ 16: { name: "Generic Repost" } },
{ 40: { name: "Channel Creation" } },
{ 41: { name: "Channel Metadata" } },
{ 42: { name: "Channel Message" } },
{ 43: { name: "Channel Hide Message" } },
{ 44: { name: "Channel Mute User" } },
{ 1063: { name: "File Metadata" } },
{ 1311: { name: "Live Chat Message" } },
{ 1984: { name: "Reporting" } },
{ 1985: { name: "Label" } },
{ 4550: { name: "Community Post Approval" } },
{ 9734: { name: "Zap Request" } },
{ 9735: { name: "Zap" } },
{ 10000: { name: "Mute List" } },
{ 10001: { name: "Pin List" } },
{ 10002: { name: "Relay List Metadata" } },
{ 13194: { name: "Wallet Info" } },
{ 22242: { name: "Client Authentication" } },
{ 23194: { name: "Wallet Request" } },
{ 23195: { name: "Wallet Response" } },
{ 24133: { name: "Nostr Connect" } },
{ 27235: { name: "HTTP Auth" } },
{ 30000: { name: "Categorized People List" } },
{ 30001: { name: "Categorized Bookmark List" } },
{ 30008: { name: "Profile Badges" } },
{ 30009: { name: "Badge Definition" } },
{ 30017: { name: "Create or update a stall" } },
{ 30018: { name: "Create or update a product" } },
{ 30023: { name: "Long-form Content" } },
{ 30024: { name: "Draft Long-form Content" } },
{ 30078: { name: "Application-specific Data" } },
{ 30311: { name: "Live Event" } },
{ 30402: { name: "Classified Listing" } },
{ 30403: { name: "Draft Classified Listing" } },
{ 31922: { name: "Date-Based Calendar Event" } },
{ 31923: { name: "Time-Based Calendar Event" } },
{ 31924: { name: "Calendar" } },
{ 31925: { name: "Calendar Event RSVP" } },
{ 31989: { name: "Handler recommendation" } },
{ 31990: { name: "Handler information" } },
{ 34550: { name: "Community Definition" } },
];
export const arrNostrEvents = [
[0, "Metadata"],
[1, "Short Text Note"],
[2, "Recommend Relay"],
[3, "Contacts"],
[4, "Encrypted Direct Messages"],
[5, "Event Deletion"],
[6, "Repost"],
[7, "Reaction"],
[8, "Badge Award"],
[16, "Generic Repost"],
[40, "Channel Creation"],
[41, "Channel Metadata"],
[42, "Channel Message"],
[43, "Channel Hide Message"],
[44, "Channel Mute User"],
[1063, "File Metadata"],
[1311, "Live Chat Message"],
[1984, "Reporting"],
[1985, "Label"],
[4550, "Community Post Approval"],
[9734, "Zap Request"],
[9735, "Zap"],
[10000, "Mute List"],
[10001, "Pin List"],
[10002, "Relay List Metadata"],
[13194, "Wallet Info"],
[22242, "Client Authentication"],
[23194, "Wallet Request"],
[23195, "Wallet Response"],
[24133, "Nostr Connect"],
[27235, "HTTP Auth"],
[30000, "Categorized People List"],
[30001, "Categorized Bookmark List"],
[30008, "Profile Badges"],
[30009, "Badge Definition"],
[30017, "Create or update a stall"],
[30018, "Create or update a product"],
[30023, "Long-form Content"],
[30024, "Draft Long-form Content"],
[30078, "Application-specific Data"],
[30311, "Live Event"],
[30402, "Classified Listing"],
[30403, "Draft Classified Listing"],
[31922, "Date-Based Calendar Event"],
[31923, "Time-Based Calendar Event"],
[31924, "Calendar"],
[31925, "Calendar Event RSVP"],
[31989, "Handler recommendation"],
[31990, "Handler information"],
[34550, "Community Definition"],
];
export const arrNostrStandardizedTags = [
["e", "event id (hex)", ["relay URL", " marker"]],
["p", "pubkey (hex)", ["relay URL", " petname"]],
["a", "coordinates to an event", ["relay URL"]],
["d", "identifier", []],
["alt", "Alt tag", []],
["g", "geohash", []],
["i", "identity", ["proof"]],
["k", "kind number (string)", []],
["l", "label, label namespace", ["annotations"]],
["L", "label namespace", []],
["r", "a reference (URL, etc)", ["petname"]],
["r", "relay url", ["marker"]],
["t", "hashtag", []],
["amount", "millisatoshis, stringified", []],
["bolt11", "bolt11 invoice", []],
["challenge", "challenge string", []],
["content-warning", "reason", []],
["delegation", "pubkey, conditions, delegation token", []],
["description", "badge description", []],
["description", "invoice description", []],
["emoji", "shortcode, image URL", []],
["expiration", "unix timestamp (string)", []],
["image", "image URL", ["dimensions in pixels"]],
["lnurl", "bech32 encoded lnurl", []],
["location", "location string", []],
["name", "badge name", []],
["nonce", "random", []],
["preimage", "hash of bolt11 invoice", []],
["price", "price", ["currency", " frequency"]],
["proxy", "external ID", ["protocol"]],
["published_at", "unix timestamp (string)", []],
["relay", "relay url", []],
["relays", "relay list", []],
["subject", "subject", []],
["summary", "article summary", []],
["thumb", "badge thumbnail", ["dimensions in pixels"]],
["title", "article title", []],
["zap", "pubkey (hex), relay URL", ["weight"]],
];
export const objNip11 = async (Relay) => {
let Addr = `https:${Relay.slice(4)}`;
try {
const myInit = {
method: "GET",
headers: { Accept: "application/nostr+json" },
mode: "cors",
// cache: "default",
signal: AbortSignal.timeout(5000),
};
let response = await fetch(Addr, myInit);
let objOut = await response.json();
objOut.timestamp = intTimestampSeconds();
return await objOut;
} catch {
let objOut = {};
objOut.timestamp = intTimestampSeconds();
return objOut;
}
};
export const objRelaysFromNostrWatch = async () => {
let Addr = `https://api.nostr.watch/v1/online`;
try {
const myInit = {
method: "GET",
headers: { Accept: "application/nostr+json" },
mode: "cors",
// cache: "default",
signal: AbortSignal.timeout(5000),
};
let response = await fetch(Addr, myInit);
let objOut = await response.json();
objOut.timestamp = intTimestampSeconds();
return objOut;
} catch {}
};
// FIND THE HIGHEST INTEGER IN THE _id FIELD
export const numMax_id = async (db = DB) => {
let MaxNum = 0;
for (let Row of db) {
if (!isNaN(Row._id)) {
if (Row._id > MaxNum) {
MaxNum = Row._id;
}
}
}
return Number(MaxNum);
};
//////////////////////////////////////////////////////////////////////////////
// COMPRESSION
//////////////////////////////////////////////////////////////////////////////
export const lzw_encode = async (s) => {
var dict = {};
var data = (s + '').split('');
var out = [];
var currChar;
var phrase = data[0];
var code = 256;
for (var i = 1; i < data.length; i++) {
currChar = data[i];
if (dict[phrase + currChar] != null) {
phrase += currChar;
} else {
out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
dict[phrase + currChar] = code;
code++;
phrase = currChar;
}
}
out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
for (var i = 0; i < out.length; i++) {
out[i] = String.fromCharCode(out[i]);
}
return out.join('');
}
export const lzw_decode = async (s) => {
var dict = {};
var data = (s + '').split('');
var currChar = data[0];
var oldPhrase = currChar;
var out = [currChar];
var code = 256;
var phrase;
for (var i = 1; i < data.length; i++) {
var currCode = data[i].charCodeAt(0);
if (currCode < 256) {
phrase = data[i];
} else {
phrase = dict[currCode] ? dict[currCode] : (oldPhrase + currChar);
}
out.push(phrase);
currChar = phrase.charAt(0);
dict[code] = oldPhrase + currChar;
code++;
oldPhrase = phrase;
}
return out.join('');
}
export const gzip_to_b64 = async (str) => {
// Use a browsers inbuilt CompressionStream to gzip
console.log("Uncompressed length", str.length)
const stream = new Blob([str], {
type: "application/json",
}).stream();
const compressedReadableStream = stream.pipeThrough(new CompressionStream("gzip"));
const compressedResponse = await new Response(compressedReadableStream);
const blob = await compressedResponse.blob();
const buffer = await blob.arrayBuffer();
console.log("Compressed length", buffer.byteLength)
// convert ArrayBuffer to base64 encoded string
// const compressedBase64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
const compressedBase64 = btoa(
new Uint8Array(buffer).reduce(function (data, byte) {
return data + String.fromCharCode(byte);
}, "")
);
console.log("Compressed length B64", compressedBase64.length)
return compressedBase64;
};
export const ungzip_from_b64 = async (b64) => {
// Ungzip from this routine: gzip_to_b64()
function b64decode(str) {
const binary_string = window.atob(str);
const len = binary_string.length;
const bytes = new Uint8Array(new ArrayBuffer(len));
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
}
// base64 encoding to Blob
const stream = new Blob([b64decode(b64)], {
type: "application/json",
}).stream();
const compressedReadableStream = stream.pipeThrough(new DecompressionStream("gzip"));
const resp = await new Response(compressedReadableStream);
const blob = await resp.blob();
const data = await blob.text();
return data;
};