@fifthbell/brokaw 0.1.46 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fifthbell/brokaw",
3
3
  "description": "Server-side Handlebars renderer and templates for fifthbell pages.",
4
- "version": "0.1.46",
4
+ "version": "0.1.47",
5
5
  "license": "MIT",
6
6
  "author": "fifthbell",
7
7
  "repository": {
@@ -10,10 +10,12 @@
10
10
  </div>
11
11
  <div id='media-status' class='border border-slate-200 bg-white p-6 text-sm text-slate-600 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-300'>Loading photos.</div>
12
12
  <div id='media-grid' class='hidden grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'></div>
13
+ <nav id='media-pagination' aria-label='Pagination' class='mt-8 hidden items-center justify-center gap-6'></nav>
13
14
  </section>
14
15
  </main>
15
16
  <script>
16
17
  (function () {
18
+ var PAGE_SIZE = 48;
17
19
  var page = document.querySelector('[data-media-page]');
18
20
  if (!page) return;
19
21
 
@@ -21,7 +23,9 @@
21
23
  var meta = document.getElementById('media-meta');
22
24
  var status = document.getElementById('media-status');
23
25
  var grid = document.getElementById('media-grid');
26
+ var pagination = document.getElementById('media-pagination');
24
27
  var refresh = document.getElementById('media-refresh');
28
+ var loadedData = null;
25
29
 
26
30
  function text(value, fallback) {
27
31
  if (typeof value !== 'string') return fallback || '';
@@ -60,27 +64,104 @@
60
64
  status.textContent = message;
61
65
  status.classList.remove('hidden');
62
66
  grid.classList.add('hidden');
67
+ pagination.classList.add('hidden');
68
+ pagination.classList.remove('flex');
63
69
  }
64
70
 
65
71
  function imageUrl(item) {
66
72
  return text(item && (item.url || item.jpgUrl || item.originalUrl), '');
67
73
  }
68
74
 
75
+ function currentPageFromUrl() {
76
+ var value = new URL(window.location.href).searchParams.get('page') || '1';
77
+ var pageNumber = parseInt(value, 10);
78
+ return Number.isFinite(pageNumber) && pageNumber > 0 ? pageNumber : 1;
79
+ }
80
+
81
+ function pageHref(pageNumber) {
82
+ var url = new URL(window.location.href);
83
+ if (pageNumber <= 1) {
84
+ url.searchParams.delete('page');
85
+ } else {
86
+ url.searchParams.set('page', String(pageNumber));
87
+ }
88
+ return url.pathname + url.search + url.hash;
89
+ }
90
+
91
+ function paginationPages(currentPage, totalPages) {
92
+ var pages = [];
93
+ var lastAdded = 0;
94
+
95
+ function addPage(pageNumber) {
96
+ if (pageNumber < 1 || pageNumber > totalPages || pageNumber === lastAdded) return;
97
+ if (lastAdded && pageNumber > lastAdded + 1) pages.push({ ellipsis: true });
98
+ pages.push({ label: pageNumber, page: pageNumber, current: pageNumber === currentPage });
99
+ lastAdded = pageNumber;
100
+ }
101
+
102
+ addPage(1);
103
+ for (var pageNumber = currentPage - 2; pageNumber <= currentPage + 2; pageNumber += 1) {
104
+ addPage(pageNumber);
105
+ }
106
+ addPage(totalPages);
107
+
108
+ return pages;
109
+ }
110
+
111
+ function renderPagination(currentPage, totalPages) {
112
+ if (totalPages <= 1) {
113
+ pagination.innerHTML = '';
114
+ pagination.classList.add('hidden');
115
+ pagination.classList.remove('flex');
116
+ return;
117
+ }
118
+
119
+ var prev = currentPage > 1
120
+ ? '<a href="' + escapeHtml(pageHref(currentPage - 1)) + '" class="inline-flex items-center gap-1 text-sm font-medium text-slate-500 transition-colors hover:text-[#b21100] dark:text-slate-400 dark:hover:text-[#ff2e1a]" aria-label="Previous page"><svg viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4" aria-hidden="true"><path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd"/></svg>Prev</a>'
121
+ : '<span class="inline-flex items-center gap-1 text-sm font-medium text-slate-300 dark:text-slate-700" aria-disabled="true"><svg viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4" aria-hidden="true"><path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd"/></svg>Prev</span>';
122
+
123
+ var items = paginationPages(currentPage, totalPages)
124
+ .map(function (item) {
125
+ if (item.ellipsis) {
126
+ return '<li><span class="inline-flex h-9 w-9 items-center justify-center text-sm text-slate-400 dark:text-slate-500">...</span></li>';
127
+ }
128
+ if (item.current) {
129
+ return '<li><span aria-current="page" class="inline-flex h-9 w-9 items-center justify-center border-b-2 border-[#b21100] text-sm font-bold text-[#b21100] dark:border-[#ff2e1a] dark:text-[#ff2e1a]">' + item.label + '</span></li>';
130
+ }
131
+ return '<li><a href="' + escapeHtml(pageHref(item.page)) + '" class="inline-flex h-9 w-9 items-center justify-center text-sm font-medium text-slate-600 transition-colors hover:text-[#b21100] dark:text-slate-300 dark:hover:text-[#ff2e1a]">' + item.label + '</a></li>';
132
+ })
133
+ .join('');
134
+
135
+ var next = currentPage < totalPages
136
+ ? '<a href="' + escapeHtml(pageHref(currentPage + 1)) + '" class="inline-flex items-center gap-1 text-sm font-medium text-slate-500 transition-colors hover:text-[#b21100] dark:text-slate-400 dark:hover:text-[#ff2e1a]" aria-label="Next page">Next<svg viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4" aria-hidden="true"><path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 1 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/></svg></a>'
137
+ : '<span class="inline-flex items-center gap-1 text-sm font-medium text-slate-300 dark:text-slate-700" aria-disabled="true">Next<svg viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4" aria-hidden="true"><path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 1 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/></svg></span>';
138
+
139
+ pagination.innerHTML = prev + '<ol class="flex items-center gap-0.5">' + items + '</ol>' + next;
140
+ pagination.classList.remove('hidden');
141
+ pagination.classList.add('flex');
142
+ }
143
+
69
144
  function render(data) {
145
+ loadedData = data;
70
146
  var assignment = data.assignment || data;
71
147
  var photos = Array.isArray(data.photos) ? data.photos : Array.isArray(data.media) ? data.media : [];
72
148
  var name = text(assignment.name, 'Assignment photos');
73
149
  var generatedAt = formatDate(data.generatedAt);
150
+ var totalPages = Math.max(1, Math.ceil(photos.length / PAGE_SIZE));
151
+ var currentPage = Math.min(currentPageFromUrl(), totalPages);
152
+ var start = (currentPage - 1) * PAGE_SIZE;
153
+ var visiblePhotos = photos.slice(start, start + PAGE_SIZE);
74
154
 
75
155
  title.textContent = name;
76
- meta.textContent = photos.length + ' photo' + (photos.length === 1 ? '' : 's') + (generatedAt ? ' updated ' + generatedAt : '');
156
+ meta.textContent = photos.length + ' photo' + (photos.length === 1 ? '' : 's') + (totalPages > 1 ? ' - Page ' + currentPage + ' of ' + totalPages : '') + (generatedAt ? ' updated ' + generatedAt : '');
157
+ refresh.href = window.location.href;
77
158
 
78
159
  if (!photos.length) {
79
160
  setStatus('No photos have been published for this assignment yet.');
80
161
  return;
81
162
  }
82
163
 
83
- grid.innerHTML = photos
164
+ grid.innerHTML = visiblePhotos
84
165
  .map(function (item) {
85
166
  var url = imageUrl(item);
86
167
  var alt = text(item.alt || item.caption || item.originalFilename || item.filename, 'Assignment photo');
@@ -100,6 +181,7 @@
100
181
  status.classList.add('hidden');
101
182
  grid.classList.remove('hidden');
102
183
  grid.classList.add('grid');
184
+ renderPagination(currentPage, totalPages);
103
185
  }
104
186
 
105
187
  var assignmentId = assignmentIdFromPath();
@@ -109,6 +191,20 @@
109
191
  }
110
192
 
111
193
  refresh.href = window.location.href;
194
+ pagination.addEventListener('click', function (event) {
195
+ var target = event.target;
196
+ if (!(target instanceof Element)) return;
197
+ var anchor = target.closest('a[href]');
198
+ if (!(anchor instanceof HTMLAnchorElement)) return;
199
+ event.preventDefault();
200
+ window.history.pushState({}, '', anchor.getAttribute('href') || window.location.href);
201
+ render(loadedData);
202
+ });
203
+
204
+ window.addEventListener('popstate', function () {
205
+ if (loadedData) render(loadedData);
206
+ });
207
+
112
208
  fetch('https://cdn.fifthbell.com/contents/assignments/' + encodeURIComponent(assignmentId) + '.json', {
113
209
  cache: 'no-store'
114
210
  })