gitoriaLog in with ident

components

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit3bc905c53bc905c5components #15 (mission 003) 2/2: docs (README Look, STATUS, LOG), reportmre3bc905c5/components/month-view/month-view.hl

7.3 KB

  1. // month-view.hl — THE MONTH VIEW: prev / next buttons on top, the month's name and the year between them, the
  2. // days below (weeks start on Monday; the days of the neighbouring months are dimmed; today is marked).
  3. // All Hybriel, no .js: the date arithmetic is written here (plain integers), so it runs the same on the server (first
  4. // render) and in the browser (paging). What only the browser knows — today in the user's time zone, the month and
  5. // weekday names in the user's language (`Date` and `Intl` are browser globals of View handlers) — is read the moment
  6. // the pointer, a finger or the keyboard reaches the month view; until then the server's UTC date and English names show.
  7. // The page reads `year` and `month` (1–12) as its own members. USE ONE PER PAGE: a composed component's
  8. // members are the page's state (COMPONENTS.md §12).
  9. import { serverToday } from './clock.hl'
  10. String today = serverToday()
  11. Number year = toNumber(today.slice(0, 4))
  12. Number month = toNumber(today.slice(5, 7))
  13. Hybrid monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
  14. Hybrid weekdayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  15. String title = monthNames[month - 1] + ' ' + year
  16. Hybrid weekdays = weekdayNames
  17. Hybrid days = build(year, month, today)
  18. Number seen = 0
  19. Number paged = 0
  20. String prevLabel = 'Previous month'
  21. String nextLabel = 'Next month'
  22. pad4(n) { return ('' + (10000 + n)).slice(1) }
  23. pad(n) { return n < 10 ? '0' + n : '' + n }
  24. leap(y) { return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 }
  25. daysIn(y, m) {
  26. if (m == 2) { return leap(y) ? 29 : 28 }
  27. if (m == 4 || m == 6 || m == 9 || m == 11) { return 30 }
  28. return 31
  29. }
  30. // 0 = Monday … 6 = Sunday (Sakamoto's method gives 0 = Sunday)
  31. weekdayOf(y, m, d) {
  32. t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
  33. yy = m < 3 ? y - 1 : y
  34. sun = (yy + (yy - yy % 4) / 4 - (yy - yy % 100) / 100 + (yy - yy % 400) / 400 + t[m - 1] + d) % 7
  35. return (sun + 6) % 7
  36. }
  37. // 42 days (six weeks of seven), so the view keeps its height from month to month
  38. build(y, m, todayText) {
  39. py = m == 1 ? y - 1 : y
  40. pm = m == 1 ? 12 : m - 1
  41. ny = m == 12 ? y + 1 : y
  42. nm = m == 12 ? 1 : m + 1
  43. lead = weekdayOf(y, m, 1)
  44. let n = daysIn(y, m)
  45. pn = daysIn(py, pm)
  46. out = []
  47. let day = 1 - lead
  48. let w = 0
  49. while (w < 6) {
  50. let i = 0
  51. while (i < 7) {
  52. let cy = y
  53. let cm = m
  54. let cd = day
  55. let cls = ''
  56. if (day < 1) { cy = py cm = pm cd = pn + day cls = 'outside' }
  57. if (day > n) { cy = ny cm = nm cd = day - n cls = 'outside' }
  58. let date = cy + '-' + pad(cm) + '-' + pad(cd)
  59. if (date == todayText) { cls = 'today' }
  60. out.push({ n = cd date = date cls = cls })
  61. day = day + 1
  62. i = i + 1
  63. }
  64. w = w + 1
  65. }
  66. return out
  67. }
  68. show(y, m) {
  69. year = y
  70. month = m
  71. title = monthNames[m - 1] + ' ' + y
  72. days = build(y, m, today)
  73. }
  74. // Look: black, white and the greys #555 #999 #ddd #f2f2f2 (the library's base, named in shared/base.hl; README "Look"). Every colour is a semantic token
  75. // with its base value as fallback, `var(--color-…, #000)`, so a bare copy looks right and an app that declares the tokens (layouts.worldapi.org's
  76. // theme) restyles it. No frames: a border or ring only where it means something (today). A component's browser module
  77. // cannot import tokens itself.
  78. Style {
  79. '@keyframes month-view-mount' { 'from' { opacity = '.999' } to { opacity = '1' } }
  80. 'month-view' { display = 'grid' gap = '.5rem' animation = 'month-view-mount 100ms infinite' }
  81. 'month-view header' { display = 'flex' alignItems = 'center' justifyContent = 'space-between' gap = '.5rem' }
  82. 'month-view h2' { margin = '0' fontSize = '1.1rem' color = 'var(--color-text-strong, #000)' textAlign = 'center' flex = '1' textTransform = 'capitalize' }
  83. 'month-view ol' { display = 'grid' gridTemplateColumns = 'repeat(7, minmax(0, 1fr))' gap = '.25rem' margin = '0' padding = '0' listStyle = 'none' }
  84. 'month-view button' { font = 'inherit' fontWeight = '600' padding = '.4rem .9rem' color = 'var(--color-text, #000)' background = 'transparent' border = '0' borderRadius = '.4rem' cursor = 'pointer' }
  85. 'month-view button:hover' { background = 'var(--color-surface, #f2f2f2)' }
  86. '#month-weekdays li' { padding = '.3rem 0' fontSize = '.8rem' fontWeight = '600' color = 'var(--color-text-muted, #555)' textAlign = 'center' }
  87. '#month-days li' { minHeight = '3rem' padding = '.3rem' border = '0' borderRadius = '.3rem' textAlign = 'right' background = 'var(--color-surface, #f2f2f2)' }
  88. '#month-days time' { color = 'var(--color-text, #000)' fontSize = '.95rem' }
  89. '#month-days li.outside' { background = 'var(--color-background, #fff)' }
  90. '#month-days li.outside time' { color = 'var(--color-text-muted, #555)' }
  91. '#month-days li.today' { outline = '2px solid var(--color-accent, #000)' outlineOffset = '-2px' }
  92. '#month-days li.today time' { color = 'var(--color-accent, #000)' fontWeight = '800' }
  93. '@media (min-width: 40rem)' {
  94. '#month-days li' { minHeight = '5rem' }
  95. }
  96. }
  97. View {
  98. monthView { on animationiteration(e) { emit monthMount(e) } on pointerover(e) { emit monthEnv(e) } on focusin(e) { emit monthEnv(e) } on touchstart(e) { emit monthEnv(e) }
  99. header {
  100. button { id = "month-prev" type = "button" class = "quiet" title = prevLabel "‹" on click(e) { emit monthPrev(e) } }
  101. h2 { id = "month-title" title }
  102. button { id = "month-next" type = "button" class = "quiet" title = nextLabel "›" on click(e) { emit monthNext(e) } }
  103. }
  104. ol { id = "month-weekdays" for (name of weekdays) { li { name } } }
  105. ol { id = "month-days" for (d of days) { li { class = d.cls time { datetime = d.date d.n } } } }
  106. input { id = "month-view-goto" type = "hidden" on change(e) { emit monthGoto(e.target.value) } }
  107. }
  108. }
  109. on monthPrev(e) {
  110. browserEnv()
  111. paged = 1
  112. show(month == 1 ? year - 1 : year, month == 1 ? 12 : month - 1)
  113. }
  114. on monthNext(e) {
  115. browserEnv()
  116. paged = 1
  117. show(month == 12 ? year + 1 : year, month == 12 ? 1 : month + 1)
  118. }
  119. // the mount hook (a 100 ms CSS animation fires once the page is live): the browser's today is read at load, not on first touch
  120. on monthMount(e) {
  121. e.target.style.animationName = 'none'
  122. browserEnv()
  123. }
  124. // the browser's today and language, once: today follows the user's time zone, the names their language
  125. on monthEnv(e) { browserEnv() }
  126. browserEnv() {
  127. if (seen == 1) { return null }
  128. seen = 1
  129. now = new Date()
  130. lang = navigator.language != null ? navigator.language : 'en'
  131. today = pad4(now.getFullYear()) + '-' + pad(now.getMonth() + 1) + '-' + pad(now.getDate())
  132. mf = Intl.DateTimeFormat(lang, { month = 'long' })
  133. df = Intl.DateTimeFormat(lang, { weekday = 'short' })
  134. ms = []
  135. ds = []
  136. let i = 0
  137. while (i < 12) { ms.push(mf.format(new Date(Date.UTC(2024, i, 15, 12)))) i = i + 1 }
  138. i = 0
  139. while (i < 7) { ds.push(df.format(new Date(Date.UTC(2024, 0, 1 + i, 12)))) i = i + 1 }
  140. monthNames = ms
  141. weekdayNames = ds
  142. weekdays = ds
  143. de = lang.slice(0, 2) == 'de'
  144. prevLabel = de ? 'Voriger Monat' : 'Previous month'
  145. nextLabel = de ? 'Nächster Monat' : 'Next month'
  146. // the month shown follows the corrected today only while the user has not paged yet
  147. if (paged == 0) { show(toNumber(today.slice(0, 4)), toNumber(today.slice(5, 7))) } else { show(year, month) }
  148. }
  149. // from a host script (the date picker): "YYYY-MM-DD" → show that date's month
  150. on monthGoto(text) {
  151. paged = 1
  152. show(toNumber(text.slice(0, 4)), toNumber(text.slice(5, 7)))
  153. }

Branches

Latest commits

  • 3bc905c5components #15 (mission 003) 2/2: docs (README Look, STATUS, LOG), reportmre
  • 5a98d77ecomponents #15 (mission 003) 1/2: black/white base look — greys for nuances, no WorldAPI fallbacks, no decorative frames; site + icon black/white; gate 230/0mre
  • 3f93d716components code order (mission 002) 4/4: docs, tests/letcount.py, tests/same-output*, reportmre
  • e747cfa0components code order (mission 002) 3/4: let only where reassigned (257 dropped, 0 never-reassigned left); gate 220/0, layouts gate 73/0, same outputmre
  • 172914a3components code order (mission 002) 2/4: lib/uploads.hl (demo upload storage), thin demo handlers, project.hl map; gate 220/0, same outputmre
  • d29f35b1components code order (mission 002) 1/4: styles.hl -> components/styles.hl; gate 220/0, same outputmre
  • 09945750components: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, http1 773de63e); gate 220/0mre
  • ede4d19acomponents: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gate 220/0mre
  • 2bb9a935components: Hybriel master 8efba065 (re-vendor round 069: #126 memory, #48 lambda copy)mre
  • e4f01768antcolony#40: mission references point to the moved missionsmre
  • d32b44e2antcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 0fb80b57components: Hybriel master ff51cf46 (re-vendor round)mre
  • 57408b6ecomponents#14: installable app (manifest, service worker, offline index), own iconmre
  • 317bd09fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • becbd59bcomponents#13: modal dialog (<dialog> based, focus wrap, backdrop/Esc/x close with reason, scroll lock, footer buttons)mre
  • 367dd19adeploy.sh: never send .git or .gitignore to Byrodinmre
  • 4b4dabe5State of 2026-09-27, before the move to gitoriamre