gitoriaLog in with ident

gitoria

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Branchmain4f47843egate: ticket links use the tickets short URL (/<slug>/<n>, tickets#25)mremain/components/tickets.hl

5.2 KB

  1. // components/tickets.hl — THE TICKETS of a repo (gitoria#10, gitoria#16): `/tickets` on <slug>.<domain>, its own
  2. // route, rendered on the SERVER with its list: the repo comes from the request's host, the tickets from
  3. // tickets.worldapi.org (lib/tickets.hl — hl:fetch answers at once, so the list is in the first HTML and in every
  4. // hl:web navigation). The repo must be connected to a tickets project first (gitoria#18, /settings). A named user
  5. // opens a ticket AS THEMSELVES (the repo's key + their ident id, tickets.hl); a ticket opened here reaches every open
  6. // tickets page of the repo (push `ticketOpened`).
  7. parent './main.hl'
  8. import { siteName } from '../project.hl'
  9. import RepoHead from './repohead.hl'
  10. import { slugOfHost } from '../lib/util.hl'
  11. import { infoOfSession, userOfSession } from '../lib/users.hl'
  12. import { repoAt } from '../lib/repos.hl'
  13. import { pageTitle } from '../lib/repos-helpers.hl'
  14. import { loadTickets, openTicket } from '../lib/tickets.hl'
  15. host = null
  16. session = null
  17. slug = slugOfHost(host)
  18. repo = repoAt(slug)
  19. hasRepo = repo != null
  20. me = infoOfSession(session)
  21. canCreate = repo != null && repo.ticketsOn && me != null && me.named
  22. needsLogin = repo != null && repo.ticketsOn && me == null
  23. notConnected = repo != null && repo.ticketsOff
  24. loaded = repo != null && repo.ticketsOn ? loadTickets(slug) : null
  25. ticketsErrorShown = loaded != null && loaded.error != null
  26. ticketsError = loaded != null && loaded.error != null ? loaded.error : ''
  27. tickets = loaded != null && loaded.tickets != null ? loaded.tickets : []
  28. ticketsTruncated = loaded != null && loaded.truncated == true
  29. ticketsHref = loaded != null && loaded.url != null ? loaded.url : ''
  30. ticketsShown = tickets.length > 0
  31. ticketsNone = loaded != null && loaded.error == null && tickets.length == 0
  32. ticketError = ''
  33. ticketNotice = ''
  34. hasTicketNotice = false
  35. __title = pageTitle(siteName, slug, 'Tickets')
  36. View {
  37. repoPage {
  38. RepoHead { }
  39. if (hasRepo) {
  40. repoTickets { id = "tickets"
  41. if (notConnected) { p { id = "ticketsnotconnected" class = "empty" "This repository is not connected to a tickets project. The owner connects it under " a { id = "tosettings" href = "/settings" "Settings" } "." } }
  42. if (ticketsErrorShown) { p { id = "ticketserror" class = "message" ticketsError } }
  43. if (canCreate) {
  44. details { id = "newticket"
  45. summary { "New ticket" }
  46. form { id = "newticketform"
  47. on submit(e) {
  48. e.preventDefault()
  49. emit openIt(e)
  50. }
  51. label { "Subject"
  52. input { id = "ticketsubject" name = "subject" required = "required" maxlength = "200" autocomplete = "off" }
  53. }
  54. label { "Text (Markdown, optional)"
  55. textarea { id = "ticketsummary" name = "summary" rows = "5" }
  56. }
  57. button { id = "ticketsave" type = "submit" "Open ticket" }
  58. p { id = "newticketerror" class = "message" ticketError }
  59. }
  60. }
  61. }
  62. if (needsLogin) { p { id = "ticketsloginhint" class = "muted" "Log in with ident (top right) to open a ticket." } }
  63. if (hasTicketNotice) { p { id = "ticketnotice" class = "notice" ticketNotice } }
  64. if (ticketsNone) { p { id = "noticketsyet" class = "empty" "No ticket yet in this project." } }
  65. if (ticketsShown) {
  66. ol { id = "ticketlist"
  67. for (t of tickets) {
  68. li {
  69. a { class = "number" href = t.url "#" t.number }
  70. a { class = "subject" href = t.url t.subject }
  71. span { class = "state" t.state }
  72. time { t.updated }
  73. }
  74. }
  75. }
  76. p { class = "muted" "Open a ticket to read and answer it in " a { id = "ticketsall" href = ticketsHref "tickets.worldapi.org" } "." }
  77. if (ticketsTruncated) { p { id = "ticketstruncated" class = "muted" "Only the latest tickets are listed here." } }
  78. }
  79. }
  80. }
  81. }
  82. }
  83. on openIt(e) {
  84. f = e.target.elements
  85. r = emit server gitoriaOpenTicket(slug, f.subject.value, f.summary.value)
  86. if (r == null || r.error != null) {
  87. ticketError = r != null ? r.error : 'could not open the ticket'
  88. return null
  89. }
  90. ticketError = ''
  91. f.subject.value = ''
  92. f.summary.value = ''
  93. ticketNotice = 'Opened ticket #' + r.ticket.number + '.'
  94. hasTicketNotice = true
  95. addTicket(r.ticket)
  96. }
  97. addTicket = (row) => {
  98. next = [row]
  99. for (x of tickets) { if (x.number != row.number) { next.push(x) } }
  100. tickets = next
  101. ticketsShown = true
  102. ticketsNone = false
  103. }
  104. // a ticket opened here by anyone: every open tickets page of that repo follows
  105. on client ticketOpened(slugOf, row) {
  106. if (!hasRepo || slugOf != slug) { return null }
  107. addTicket(row)
  108. }
  109. // this session's tabs follow a login / name / logout (the shell's faces push them)
  110. on client gitoriaSignedIn(tag, info) {
  111. canCreate = repo != null && repo.ticketsOn && info.named
  112. needsLogin = false
  113. }
  114. on client gitoriaSignedOut(tag) {
  115. canCreate = false
  116. needsLogin = repo != null && repo.ticketsOn
  117. }
  118. on server gitoriaOpenTicket(slugOf, subject, summary, session) {
  119. if (repoAt(slugOf) == null) { return { error = 'no such repository' } }
  120. u = userOfSession(session)
  121. if (u == null) { return { error = 'log in with ident (top right) to open a ticket' } }
  122. if (u.name == '') { return { error = 'choose a display name first (top of the page)' } }
  123. r = openTicket(slugOf, u.identity, subject, summary)
  124. if (r.error != null) { return { error = r.error } }
  125. emit client ticketOpened(slugOf, r.ticket)
  126. return r
  127. }

Branches

  • mainmain branch

Latest commits

  • 4f47843egate: ticket links use the tickets short URL (/<slug>/<n>, tickets#25)mre
  • 86605446mission 002 (code order) 4/4: README file map + import order + 'Same output' test + gate run with a tickets HEAD copy, STATUS (entry, lessons), LOG, report; tests/realdata-baseline.mjs + realdata-compare.py (a cleanup answers the same on live data: pages, modules, API, git over HTTPS and SSH, faces), tests/letcount.pymre
  • cc7bf7bamission 002 (code order) 3/4: let only where a variable is reassigned or re-bound in a loop body (289 lets → plain declarations; 213 left: 125 reassigned, 88 loop-bound; no member/import/param clash). gates 200/0, 46/0, 44/0; real-data reads + writes identical (browser modules: var → const only)mre
  • 090a20c6mission 002 (code order) 2/4: one lib/ file per topic — git.hl split into git (calls, branches, init, temp folder) / homepage / code / pulls / releases (+ git-helpers: paths, ids, |||PR/|||RL markers); repos-helpers, tickets-helpers, transport-helpers; util.hl = localtime + env, storage dir, addresses, lists, text checks, one newest-first sort (was 3 copies); the function routes out of project.hl into lib/api.hl (thin; plumbing in api-helpers.hl), sshgate.hl folded into api.hl + sshkeys.hl keyLine + repos.hl mayPush; 'Make main' and the merge answer out of the faces (code.hl makeMain, pulls.hl pullsView), one login helper (users.hl userOfLoginCode); project.hl is the map. Session-writing routes get &req + &server.sessions. gates 200/0, 46/0, 44/0; real-data identical except /login/failed now shows the parked reason for a browser that already had a session (the old copy-of-req lost it)mre
  • 110c2799mission 002 (code order) 1/4: .hl files out of the root — lib/ (api, git, localtime, markdown, repos, sshgate, sshkeys, tickets, tokens, transport, users), components/styles.hl; jsoncheck.hl removed (imported nowhere); import paths only. gates 200/0, 46/0, 44/0; real-data reads + writes identicalmre
  • fdfb4b1bgitoria: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); gates 200/0, 46/0, 44/0mre
  • 5b46ac84antcolony#40: LOG.md — missions 069/072 are antcolony missions (report paths on Byrodin)mre
  • 5602ff41gitoria: Hybriel master 190aa11d (fc838894 GC correctness, #127 mountKids by reference, #126, #48) — tracker README flat; gates 200/0, 46/0, 44/0mre
  • e85eaf01gitoria: 069 round 2 — hybriel 1a096ad3 not adopted (Markdown SSR still grows); browser gate waits for the server-side logout before restartmre
  • 09ce4f3fgitoria: mission 069 re-vendor hybriel 8efba065 stopped (big SSR pages grow + slow down); lambda audit clean; old vendor keptmre
  • 3dc43108antcolony#40: mission references point to the moved missionsmre
  • 8d9450fdantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 205d5fe4gitoria: Hybriel master ff51cf46; ssh keys/tokens no double rows (session sync); gates follow #20mre
  • 9b27cb26gitoria#21: installable app (manifest, service worker, offline start page), own iconmre
  • 68dcb603deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • e2deed6dgitoria#20: "Add code" only on the Code page of an empty repository, no collapsiblemre
  • 8bb97ffddeploy.sh: never send .git or .gitignore to Byrodinmre
  • fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mre
  • 4a2d7125initial commitmre