ident
All repositories: gitoria
8.1 KB
// components/appsettings.hl — `/inbox/<connection id>`: THE PER-APP PAGE (piece 4 of 6,// ticket ident#6; CONCEPT.md "Per-app page": "for each app and identity the user has a page// showing when they registered in that app and all its notifications (by name), each with// push and email switches, and on top one override for all").// A connection = one identity in one app (apps.hl connectionsTable); its `created` is// "when they registered". The switches show the EFFECTIVE setting (the app's preset// unless the user switched it; notify.hl effectiveOf). THE OVERRIDE: when it is on, its// push / email switches count for every notification of the app and the per-kind// switches are locked. Push is impossible for a name the app has not registered.// Every flip is saved at once (a face) and the server's new state is shown.//// Faces are named `inbox…` (a face is found by its EVENT NAME across all files).parent './main.hl'import { accountOfSession } from '../lib/store.hl'import { settingsPage, setSwitch, setOverride } from '../lib/notify.hl'import { disconnectConnection } from '../lib/apps.hl'import { siteName } from '../project.hl'session = nullcid = nullme = accountOfSession(session)signedIn = me != nullsignedOut = me == nullpage = me != null && cid != null ? settingsPage(me.id, cid) : nullfound = page != nullnotFound = me != null && page == nullmeEmail = me != null ? me.email : ''appName = page != null ? page.app : ''identityName = page != null ? page.identity : ''since = page != null ? page.since : ''overrideActive = page != null ? page.overrideActive : 'off'overridePush = page != null ? page.overridePush : 'off'overrideEmail = page != null ? page.overrideEmail : 'off'overrideOn = overrideActive == 'on'overrideActiveChecked = page != null ? page.overrideActiveChecked : 'false'overridePushChecked = page != null ? page.overridePushChecked : 'false'overrideEmailChecked = page != null ? page.overrideEmailChecked : 'false'kinds = page != null ? page.kinds : []hasKinds = kinds.length > 0noKinds = kinds.length == 0__title = siteName + (page != null ? ' | ' + page.app + ' notifications' : ' | notifications')message = ''notice = ''View {identCard { class = "wide"if (signedOut) {h1 { "Notification settings" }p { id = "signinfirst" class = "lead" "Sign in to ident first: " a { href = "/" "sign in" } "." }}if (notFound) {h1 { "Notification settings" }p { id = "notfound" class = "message" "This app connection does not exist or is not yours." }p { a { href = "/inbox" "Back to the inbox" } }}if (found) {accountBar {userEmail { id = "meemail" meEmail }a { id = "toinbox" href = "/inbox" "Inbox" }}section { id = "connhead"h1 { id = "connapp" appName }p { class = "lead" "Identity " strong { id = "connidentity" identityName } " — registered in this app since " strong { id = "connsince" since } "." }button { id = "disconnect" type = "button" class = "quiet small danger" "Sign out of this app" on click(e) { emit doDisconnect(e) } }}section { id = "overridesection"h2 { "All notifications" }p { class = "lead" "One setting for every notification of this app. While it is on, the switches below are locked." }switchRow {switchLabel { "Override all" }button { id = "ovactive" type = "button" class = "switch" role = "switch" "aria-checked" = overrideActiveChecked overrideActive on click(e) { emit flipOverride('active') } }}switchRow {switchLabel { "Push" }button { id = "ovpush" type = "button" class = "switch" role = "switch" "aria-checked" = overridePushChecked overridePush on click(e) { emit flipOverride('push') } }}switchRow {switchLabel { "Email" }button { id = "ovemail" type = "button" class = "switch" role = "switch" "aria-checked" = overrideEmailChecked overrideEmail on click(e) { emit flipOverride('email') } }}}section { id = "kindssection"h2 { "Notifications" }if (noKinds) { p { id = "nokinds" class = "lead" "This app has not registered or sent any notifications yet." } }ul { id = "kinds"for (k of kinds) {li { class = "kind"kindMain {kindName { k.name }kindPreset { k.preset }}kindSwitches {switchRow {switchLabel { "Push" }if (k.canPush) { button { type = "button" class = "switch push" role = "switch" "aria-checked" = k.pushChecked value = k.name k.push on click(e) { emit flipKind(e.target.value, 'push') } } }if (k.lockedPush) { button { type = "button" class = "switch push locked" role = "switch" "aria-checked" = k.pushChecked "aria-disabled" = "true" value = k.name k.push } }if (k.noPush) { switchOff { class = "push" "—" } }}switchRow {switchLabel { "Email" }if (k.canEmail) { button { type = "button" class = "switch email" role = "switch" "aria-checked" = k.emailChecked value = k.name k.email on click(e) { emit flipKind(e.target.value, 'email') } } }if (k.lockedEmail) { button { type = "button" class = "switch email locked" role = "switch" "aria-checked" = k.emailChecked "aria-disabled" = "true" value = k.name k.email } }}}}}}}}p { id = "notice" class = "notice" notice }p { id = "message" class = "message" message }}}show = (p) => {overrideActive = p.overrideActiveoverridePush = p.overridePushoverrideEmail = p.overrideEmailoverrideOn = p.overrideActive == 'on'overrideActiveChecked = p.overrideActiveCheckedoverridePushChecked = p.overridePushCheckedoverrideEmailChecked = p.overrideEmailCheckedkinds = p.kindshasKinds = p.kinds.length > 0noKinds = p.kinds.length == 0return null}answer = (r) => {if (r == null) {message = 'the server did not answer — try again'return null}if (r.error != null) {message = r.errorreturn null}show(r.page)notice = 'Saved.'return null}on flipOverride(field) {message = ''notice = ''let cur = field == 'active' ? overrideActive : (field == 'push' ? overridePush : overrideEmail)answer(emit server inboxOverride('' + cid, field, cur != 'on'))}kindRow = (name) => {for (k of kinds) { if (k.name == name) { return k } }return null}on flipKind(name, channel) {message = ''notice = ''let k = kindRow(name)if (k == null) { return null }let cur = channel == 'push' ? k.push : k.emailanswer(emit server inboxSwitch('' + cid, name, channel, cur != 'on'))}// FORGETS this connection: the app gets a fresh, unrelated id on a later loginon doDisconnect(e) {message = ''notice = ''if (!confirm('Sign out of ' + appName + '? It will no longer recognize this identity.')) { return null }let r = emit server inboxDisconnect('' + cid)if (r == null) {message = 'the server did not answer — try again'return null}if (r.error != null) {message = r.errorreturn null}found = falsenotFound = truenotice = 'Signed out of ' + appName + '.'}// ---- the faces ------------------------------------------------------------------------// The LAST parameter is the session; accountOfSession only accepts the framework's own// Session (hl:web never takes it from the peer, hybriel#16/#31).on server inboxSwitch(cid, kind, channel, value, session) {let me = accountOfSession(session)if (me == null) { return { error = 'you are not signed in' } }if (cid == null || hlTypeName(cid) != 'String') { return { error = 'field connection must be a string' } }return setSwitch(me.id, cid, kind, channel, value)}on server inboxOverride(cid, field, value, session) {let me = accountOfSession(session)if (me == null) { return { error = 'you are not signed in' } }if (cid == null || hlTypeName(cid) != 'String') { return { error = 'field connection must be a string' } }return setOverride(me.id, cid, field, value)}on server inboxDisconnect(cid, session) {let me = accountOfSession(session)if (me == null) { return { error = 'you are not signed in' } }if (cid == null || hlTypeName(cid) != 'String') { return { error = 'field connection must be a string' } }return disconnectConnection(me.id, cid)}
Branches
- mainmain branch
Latest commits
- 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