gitoriaLog in with ident

ident

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitd2e7f91bd2e7f91bident 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 outputmred2e7f91b/components/appsettings.hl

8.1 KB

  1. // components/appsettings.hl — `/inbox/<connection id>`: THE PER-APP PAGE (piece 4 of 6,
  2. // ticket ident#6; CONCEPT.md "Per-app page": "for each app and identity the user has a page
  3. // showing when they registered in that app and all its notifications (by name), each with
  4. // push and email switches, and on top one override for all").
  5. // A connection = one identity in one app (lib/apps.hl connectionsTable); its `created` is
  6. // "when they registered". The switches show the EFFECTIVE setting (the app's preset
  7. // unless the user switched it; lib/notify.hl effectiveOf). THE OVERRIDE: when it is on, its
  8. // push / email switches count for every notification of the app and the per-kind
  9. // switches are locked. Push is impossible for a name the app has not registered.
  10. // Every flip is saved at once (a face) and the server's new state is shown.
  11. //
  12. // Faces are named `inbox…` (a face is found by its EVENT NAME across all files).
  13. parent './main.hl'
  14. import { accountOfSession } from '../lib/accounts.hl'
  15. import { settingsPage, setSwitch, setOverride } from '../lib/notify.hl'
  16. import { disconnectConnection } from '../lib/apps.hl'
  17. import { siteName } from '../project.hl'
  18. session = null
  19. cid = null
  20. me = accountOfSession(session)
  21. signedIn = me != null
  22. signedOut = me == null
  23. page = me != null && cid != null ? settingsPage(me.id, cid) : null
  24. found = page != null
  25. notFound = me != null && page == null
  26. meEmail = me != null ? me.email : ''
  27. appName = page != null ? page.app : ''
  28. identityName = page != null ? page.identity : ''
  29. since = page != null ? page.since : ''
  30. overrideActive = page != null ? page.overrideActive : 'off'
  31. overridePush = page != null ? page.overridePush : 'off'
  32. overrideEmail = page != null ? page.overrideEmail : 'off'
  33. overrideOn = overrideActive == 'on'
  34. overrideActiveChecked = page != null ? page.overrideActiveChecked : 'false'
  35. overridePushChecked = page != null ? page.overridePushChecked : 'false'
  36. overrideEmailChecked = page != null ? page.overrideEmailChecked : 'false'
  37. kinds = page != null ? page.kinds : []
  38. hasKinds = kinds.length > 0
  39. noKinds = kinds.length == 0
  40. __title = siteName + (page != null ? ' | ' + page.app + ' notifications' : ' | notifications')
  41. message = ''
  42. notice = ''
  43. View {
  44. identCard { class = "wide"
  45. if (signedOut) {
  46. h1 { "Notification settings" }
  47. p { id = "signinfirst" class = "lead" "Sign in to ident first: " a { href = "/" "sign in" } "." }
  48. }
  49. if (notFound) {
  50. h1 { "Notification settings" }
  51. p { id = "notfound" class = "message" "This app connection does not exist or is not yours." }
  52. p { a { href = "/inbox" "Back to the inbox" } }
  53. }
  54. if (found) {
  55. accountBar {
  56. userEmail { id = "meemail" meEmail }
  57. a { id = "toinbox" href = "/inbox" "Inbox" }
  58. }
  59. section { id = "connhead"
  60. h1 { id = "connapp" appName }
  61. p { class = "lead" "Identity " strong { id = "connidentity" identityName } " — registered in this app since " strong { id = "connsince" since } "." }
  62. button { id = "disconnect" type = "button" class = "quiet small danger" "Sign out of this app" on click(e) { emit doDisconnect(e) } }
  63. }
  64. section { id = "overridesection"
  65. h2 { "All notifications" }
  66. p { class = "lead" "One setting for every notification of this app. While it is on, the switches below are locked." }
  67. switchRow {
  68. switchLabel { "Override all" }
  69. button { id = "ovactive" type = "button" class = "switch" role = "switch" "aria-checked" = overrideActiveChecked overrideActive on click(e) { emit flipOverride('active') } }
  70. }
  71. switchRow {
  72. switchLabel { "Push" }
  73. button { id = "ovpush" type = "button" class = "switch" role = "switch" "aria-checked" = overridePushChecked overridePush on click(e) { emit flipOverride('push') } }
  74. }
  75. switchRow {
  76. switchLabel { "Email" }
  77. button { id = "ovemail" type = "button" class = "switch" role = "switch" "aria-checked" = overrideEmailChecked overrideEmail on click(e) { emit flipOverride('email') } }
  78. }
  79. }
  80. section { id = "kindssection"
  81. h2 { "Notifications" }
  82. if (noKinds) { p { id = "nokinds" class = "lead" "This app has not registered or sent any notifications yet." } }
  83. ul { id = "kinds"
  84. for (k of kinds) {
  85. li { class = "kind"
  86. kindMain {
  87. kindName { k.name }
  88. kindPreset { k.preset }
  89. }
  90. kindSwitches {
  91. switchRow {
  92. switchLabel { "Push" }
  93. 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') } } }
  94. if (k.lockedPush) { button { type = "button" class = "switch push locked" role = "switch" "aria-checked" = k.pushChecked "aria-disabled" = "true" value = k.name k.push } }
  95. if (k.noPush) { switchOff { class = "push" "—" } }
  96. }
  97. switchRow {
  98. switchLabel { "Email" }
  99. 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') } } }
  100. if (k.lockedEmail) { button { type = "button" class = "switch email locked" role = "switch" "aria-checked" = k.emailChecked "aria-disabled" = "true" value = k.name k.email } }
  101. }
  102. }
  103. }
  104. }
  105. }
  106. }
  107. }
  108. p { id = "notice" class = "notice" notice }
  109. p { id = "message" class = "message" message }
  110. }
  111. }
  112. show = (p) => {
  113. overrideActive = p.overrideActive
  114. overridePush = p.overridePush
  115. overrideEmail = p.overrideEmail
  116. overrideOn = p.overrideActive == 'on'
  117. overrideActiveChecked = p.overrideActiveChecked
  118. overridePushChecked = p.overridePushChecked
  119. overrideEmailChecked = p.overrideEmailChecked
  120. kinds = p.kinds
  121. hasKinds = p.kinds.length > 0
  122. noKinds = p.kinds.length == 0
  123. return null
  124. }
  125. answer = (r) => {
  126. if (r == null) {
  127. message = 'the server did not answer — try again'
  128. return null
  129. }
  130. if (r.error != null) {
  131. message = r.error
  132. return null
  133. }
  134. show(r.page)
  135. notice = 'Saved.'
  136. return null
  137. }
  138. on flipOverride(field) {
  139. message = ''
  140. notice = ''
  141. let cur = field == 'active' ? overrideActive : (field == 'push' ? overridePush : overrideEmail)
  142. answer(emit server inboxOverride('' + cid, field, cur != 'on'))
  143. }
  144. kindRow = (name) => {
  145. for (k of kinds) { if (k.name == name) { return k } }
  146. return null
  147. }
  148. on flipKind(name, channel) {
  149. message = ''
  150. notice = ''
  151. let k = kindRow(name)
  152. if (k == null) { return null }
  153. let cur = channel == 'push' ? k.push : k.email
  154. answer(emit server inboxSwitch('' + cid, name, channel, cur != 'on'))
  155. }
  156. // FORGETS this connection: the app gets a fresh, unrelated id on a later login
  157. on doDisconnect(e) {
  158. message = ''
  159. notice = ''
  160. if (!confirm('Sign out of ' + appName + '? It will no longer recognize this identity.')) { return null }
  161. let r = emit server inboxDisconnect('' + cid)
  162. if (r == null) {
  163. message = 'the server did not answer — try again'
  164. return null
  165. }
  166. if (r.error != null) {
  167. message = r.error
  168. return null
  169. }
  170. found = false
  171. notFound = true
  172. notice = 'Signed out of ' + appName + '.'
  173. }
  174. // ---- the faces ------------------------------------------------------------------------
  175. // The LAST parameter is the session; accountOfSession only accepts the framework's own
  176. // Session (hl:web never takes it from the peer, hybriel#16/#31).
  177. on server inboxSwitch(cid, kind, channel, value, session) {
  178. let me = accountOfSession(session)
  179. if (me == null) { return { error = 'you are not signed in' } }
  180. if (cid == null || hlTypeName(cid) != 'String') { return { error = 'field connection must be a string' } }
  181. return setSwitch(me.id, cid, kind, channel, value)
  182. }
  183. on server inboxOverride(cid, field, value, session) {
  184. let me = accountOfSession(session)
  185. if (me == null) { return { error = 'you are not signed in' } }
  186. if (cid == null || hlTypeName(cid) != 'String') { return { error = 'field connection must be a string' } }
  187. return setOverride(me.id, cid, field, value)
  188. }
  189. on server inboxDisconnect(cid, session) {
  190. let me = accountOfSession(session)
  191. if (me == null) { return { error = 'you are not signed in' } }
  192. if (cid == null || hlTypeName(cid) != 'String') { return { error = 'field connection must be a string' } }
  193. return disconnectConnection(me.id, cid)
  194. }

Branches

Latest commits

  • 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