ident
All repositories: gitoria
4.6 KB
// lib/notify-helpers.hl — what lib/notify.hl looks up: the checks of a kind name, a text and a URL, the time// stamp, the two orders (newest first, by name), on/off words. No table access. Statics only.import { timestamp } from 'hl:time'import { countOf, hasControl, urlChars } from './util.hl'static maxName = 60static maxText = 2000static maxUrl = 2000static bool = (v) => { return v == true }static onOff = (b) => { return b ? 'on' : 'off' }// "YYYY-MM-DD HH:MM UTC" (hl:time knows no zones, hybriel #11)static stamp = (ms) => {if (ms == null) { return '' }t = timestamp(ms)return t.slice(0, 10) + ' ' + t.slice(11, 16) + ' UTC'}// NEWEST FIRST by `created` (a tie by the id); insertion sort by hand (no list sort(), hybriel #6)static idAfter = (a, b) => {let i = 0while (i < a.length && i < b.length) {let x = a.charCodeAt(i)let y = b.charCodeAt(i)if (x != y) { return x > y }i = i + 1}return a.length > b.length}static newerThan = (a, b) => {ca = a.created == null ? 0 : a.createdcb = b.created == null ? 0 : b.createdif (ca != cb) { return ca > cb }return idAfter(a.id, b.id)}static newestFirst = (list) => {out = []if (countOf(list) == 0) { return out }for (x of list) { out.push(x) }let i = 1while (i < out.length) {let cur = out[i]let j = i - 1while (j >= 0 && newerThan(cur, out[j])) {out[j + 1] = out[j]j = j - 1}out[j + 1] = curi = i + 1}return out}// kinds by name, A-Z (char codes)static nameBefore = (a, b) => {x = a.name.toLowerCase()y = b.name.toLowerCase()let i = 0while (i < x.length && i < y.length) {if (x.charCodeAt(i) != y.charCodeAt(i)) { return x.charCodeAt(i) < y.charCodeAt(i) }i = i + 1}return x.length < y.length}static byName = (list) => {out = []if (countOf(list) == 0) { return out }for (x of list) { out.push(x) }let i = 1while (i < out.length) {let cur = out[i]let j = i - 1while (j >= 0 && nameBefore(cur, out[j])) {out[j + 1] = out[j]j = j - 1}out[j + 1] = curi = i + 1}return out}// ---- validation -------------------------------------------------------------------------// A KIND NAME / NOTIFICATION NAME: trimmed, 1-60 chars, one line. Answers { name } or { error }.static checkKindName = (v, what) => {if (v == null || hlTypeName(v) != 'String') { return { error = 'field ' + what + ' must be a string' } }t = v.trim()if (t == '') { return { error = 'field ' + what + ' is empty' } }if (t.length > maxName) { return { error = 'field ' + what + ' is longer than ' + maxName + ' characters' } }if (hasControl(t)) { return { error = 'field ' + what + ' contains a control character' } }return { name = t }}// THE TEXT: 1-2000 chars (Markdown by the worldapi convention; shown as plain text with// its line breaks); tab, line feed and carriage return are the only control charactersstatic checkText = (v) => {if (v == null || hlTypeName(v) != 'String') { return { error = 'field text must be a string' } }t = v.trim()if (t == '') { return { error = 'field text is empty' } }if (t.length > maxText) { return { error = 'field text is longer than ' + maxText + ' characters' } }let i = 0while (i < t.length) {let c = t.charCodeAt(i)if ((c < 32 && c != 9 && c != 10 && c != 13) || c == 127) { return { error = 'field text contains a control character' } }i = i + 1}return { text = t }}// AN ICON / ACTION LINK: an absolute http(s) URL, printable URL characters only, ≤ 2000static checkUrl = (v, what) => {if (v == null || hlTypeName(v) != 'String') { return { error = 'field ' + what + ' must be a string' } }t = v.trim()if (t.length > maxUrl) { return { error = 'field ' + what + ' is longer than ' + maxUrl + ' characters' } }let i = 0while (i < t.length) {if (!urlChars.includes(t[i])) { return { error = 'field ' + what + ' contains a character that is not allowed in a URL' } }i = i + 1}lower = t.toLowerCase()scheme = lower.slice(0, 7) == 'http://' ? 7 : (lower.slice(0, 8) == 'https://' ? 8 : 0)if (scheme == 0) { return { error = 'field ' + what + ' must be an http:// or https:// URL' } }rest = t.slice(scheme, t.length)if (rest == '' || rest[0] == '/' || rest[0] == '?') { return { error = 'field ' + what + ' has no host' } }return { url = t }}// a user's switch over the app's preset: 'on' / 'off', or '' = the presetstatic switchValue = (userValue, preset) => {if (userValue == 'on') { return true }if (userValue == 'off') { return false }return preset}static kindRowOut = (k) => { return { name = k.name push = bool(k.push) email = bool(k.email) } }static unreadCount = (rows) => {let n = 0for (r of rows) { if (r.unread) { n = n + 1 } }return n}
Branches
- mainmain branch
Latest commits
- fe183516ident mission 009 (3/4): let only where reassigned — 293 never-reassigned lets are plain declarations; kept: reassigned, loop bodies, names of a file member, a name declared twice in one function; same outputmre
- d2e7f91bident mission 009 (2/4): one lib file per topic (login, accounts, identities, apps, invites, selector, notify + helpers, util), function routes as thin wrappers in lib/api.hl, project.hl = the map; same outputmre
- 91017164ident mission 009 (1/4): file moves — the root .hl files into lib/ (api.hl → lib/api-helpers.hl), styles.hl → components/styles.hl; imports adjusted, no other changemre
- f8bdcbc2ident: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); all gates greenmre
- ff78726cident: Hybriel master 190aa11d (fc838894 GC correctness, #127, #126 closure scopes); gates all greenmre
- a3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmre
- 98226b41antcolony#40: mission references point to the moved missionsmre
- ff805b9aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
- 51a7bcdfident: Hybriel master 73267707 (#122); /code uses the new page() signature; pending address passed as parameter; once-checksmre
- 836f644fident#24: installable app (manifest, service worker, data-free offline /start), own iconmre
- 8bebbbf2deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
- cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
- 81b15b7bState of 2026-09-27, before the move to gitoriamre