webspresso 0.0.44 → 0.0.45

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,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -125,6 +125,7 @@ const MenuItem = {
125
125
  : 'text-gray-700 hover:bg-gray-100',
126
126
  onclick: (e) => {
127
127
  e.preventDefault();
128
+ sidebarOpen = false;
128
129
  m.route.set(item.path);
129
130
  },
130
131
  }, [
@@ -182,6 +183,9 @@ const MenuGroup = {
182
183
  },
183
184
  };
184
185
 
186
+ // Global sidebar state (shared between Sidebar, Layout, and MobileHeader)
187
+ var sidebarOpen = false;
188
+
185
189
  // Sidebar Component
186
190
  const Sidebar = {
187
191
  oninit(vnode) {
@@ -207,53 +211,79 @@ const Sidebar = {
207
211
  const { menu, settings, loading } = vnode.state;
208
212
  const currentPath = m.route.get() || '/';
209
213
 
210
- return m('aside.w-64.bg-white.border-r.border-gray-200.flex.flex-col.h-screen.fixed.left-0.top-0', [
211
- // Logo/Title
212
- m('div.h-16.flex.items-center.px-4.border-b.border-gray-200', [
213
- m('a.flex.items-center.gap-2.text-lg.font-bold.text-gray-900', {
214
- href: '/',
215
- onclick: (e) => { e.preventDefault(); m.route.set('/'); },
216
- }, [
217
- m('div.w-8.h-8.bg-blue-600.rounded-lg.flex.items-center.justify-center', [
218
- m('span.text-white.font-bold', 'A'),
214
+ return [
215
+ // Backdrop overlay (mobile only)
216
+ sidebarOpen && m('div.fixed.inset-0.bg-black.bg-opacity-50.z-30.lg:hidden', {
217
+ onclick: () => { sidebarOpen = false; },
218
+ }),
219
+
220
+ m('aside.w-64.bg-white.border-r.border-gray-200.flex.flex-col.h-screen.fixed.left-0.top-0.z-40.transition-transform.duration-200', {
221
+ class: sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
222
+ }, [
223
+ // Logo/Title
224
+ m('div.h-16.flex.items-center.justify-between.px-4.border-b.border-gray-200', [
225
+ m('a.flex.items-center.gap-2.text-lg.font-bold.text-gray-900', {
226
+ href: '/',
227
+ onclick: (e) => { e.preventDefault(); sidebarOpen = false; m.route.set('/'); },
228
+ }, [
229
+ m('div.w-8.h-8.bg-blue-600.rounded-lg.flex.items-center.justify-center', [
230
+ m('span.text-white.font-bold', 'A'),
231
+ ]),
232
+ m('span', settings?.title || 'Admin'),
219
233
  ]),
220
- m('span', settings?.title || 'Admin'),
234
+ // Close button (mobile only)
235
+ m('button.p-1.text-gray-400.hover:text-gray-600.lg:hidden', {
236
+ onclick: () => { sidebarOpen = false; },
237
+ }, m(Icon, { name: 'x', class: 'w-5 h-5' })),
221
238
  ]),
222
- ]),
223
239
 
224
- // Menu
225
- m('nav.flex-1.overflow-y-auto.p-4.space-y-2', [
226
- loading
227
- ? m('div.flex.justify-center.py-4', m(Spinner))
228
- : menu.map(item =>
229
- item.items
230
- ? m(MenuGroup, { key: item.id, group: item, currentPath })
231
- : m(MenuItem, { key: item.id, item, active: item.path === currentPath })
232
- ),
233
- ]),
240
+ // Menu
241
+ m('nav.flex-1.overflow-y-auto.p-4.space-y-2', [
242
+ loading
243
+ ? m('div.flex.justify-center.py-4', m(Spinner))
244
+ : menu.map(item =>
245
+ item.items
246
+ ? m(MenuGroup, { key: item.id, group: item, currentPath })
247
+ : m(MenuItem, { key: item.id, item, active: item.path === currentPath })
248
+ ),
249
+ ]),
234
250
 
235
- // User section
236
- state.user && m('div.p-4.border-t.border-gray-200', [
237
- m('div.flex.items-center.gap-3', [
238
- m('div.w-8.h-8.bg-gray-200.rounded-full.flex.items-center.justify-center', [
239
- m('span.text-sm.font-medium.text-gray-600',
240
- (state.user.name || state.user.email || 'A').charAt(0).toUpperCase()
241
- ),
251
+ // User section
252
+ state.user && m('div.p-4.border-t.border-gray-200', [
253
+ m('div.flex.items-center.gap-3', [
254
+ m('div.w-8.h-8.bg-gray-200.rounded-full.flex.items-center.justify-center', [
255
+ m('span.text-sm.font-medium.text-gray-600',
256
+ (state.user.name || state.user.email || 'A').charAt(0).toUpperCase()
257
+ ),
258
+ ]),
259
+ m('div.flex-1.min-w-0', [
260
+ m('p.text-sm.font-medium.text-gray-900.truncate', state.user.name || 'Admin'),
261
+ m('p.text-xs.text-gray-500.truncate', state.user.email),
262
+ ]),
263
+ m('button.p-1.text-gray-400.hover:text-gray-600', {
264
+ title: 'Logout',
265
+ onclick: async () => {
266
+ await api.post('/auth/logout');
267
+ state.user = null;
268
+ sidebarOpen = false;
269
+ m.route.set('/login');
270
+ },
271
+ }, m(Icon, { name: 'logout', class: 'w-5 h-5' })),
242
272
  ]),
243
- m('div.flex-1.min-w-0', [
244
- m('p.text-sm.font-medium.text-gray-900.truncate', state.user.name || 'Admin'),
245
- m('p.text-xs.text-gray-500.truncate', state.user.email),
246
- ]),
247
- m('button.p-1.text-gray-400.hover:text-gray-600', {
248
- title: 'Logout',
249
- onclick: async () => {
250
- await api.post('/auth/logout');
251
- state.user = null;
252
- m.route.set('/login');
253
- },
254
- }, m(Icon, { name: 'logout', class: 'w-5 h-5' })),
255
273
  ]),
256
274
  ]),
275
+ ];
276
+ },
277
+ };
278
+
279
+ // Mobile Header with hamburger button
280
+ const MobileHeader = {
281
+ view(vnode) {
282
+ return m('div.lg:hidden.fixed.top-0.left-0.right-0.h-14.bg-white.border-b.border-gray-200.flex.items-center.px-4.z-20', [
283
+ m('button.p-2.-ml-1.text-gray-600.hover:text-gray-900.rounded-lg.hover:bg-gray-100', {
284
+ onclick: () => { sidebarOpen = true; },
285
+ }, m(Icon, { name: 'menu', class: 'w-6 h-6' })),
286
+ m('span.ml-3.text-lg.font-semibold.text-gray-900', 'Admin'),
257
287
  ]);
258
288
  },
259
289
  };
@@ -262,8 +292,9 @@ const Sidebar = {
262
292
  const Layout = {
263
293
  view(vnode) {
264
294
  return m('div.min-h-screen.bg-gray-50', [
295
+ m(MobileHeader),
265
296
  m(Sidebar),
266
- m('main.ml-64.p-6', vnode.children),
297
+ m('main.lg:ml-64.p-6.pt-20.lg:pt-6', vnode.children),
267
298
  ]);
268
299
  },
269
300
  };
@@ -326,11 +326,11 @@ var AnalyticsPage = {
326
326
  // Top Pages + Recent Activity row
327
327
  m('div.grid.grid-cols-1.lg:grid-cols-2.gap-4.mb-6', [
328
328
  // Top Pages
329
- m('div.bg-white.rounded-lg.shadow', [
330
- m('div.px-5.py-4.border-b.border-gray-100', [
329
+ m('div.bg-white.rounded-lg.shadow.flex.flex-col', { style: 'max-height:480px' }, [
330
+ m('div.px-5.py-4.border-b.border-gray-100.shrink-0', [
331
331
  m('h3.text-sm.font-semibold.text-gray-900', 'Top Pages'),
332
332
  ]),
333
- m('div.divide-y.divide-gray-50', [
333
+ m('div.divide-y.divide-gray-50.overflow-y-auto.flex-1', [
334
334
  s.topPages.length === 0
335
335
  ? m('p.text-gray-400.text-sm.text-center.py-6', 'No page views yet')
336
336
  : (function() {
@@ -356,12 +356,12 @@ var AnalyticsPage = {
356
356
  ]),
357
357
 
358
358
  // Recent Activity
359
- m('div.bg-white.rounded-lg.shadow', [
360
- m('div.px-5.py-4.border-b.border-gray-100.flex.items-center.justify-between', [
359
+ m('div.bg-white.rounded-lg.shadow.flex.flex-col', { style: 'max-height:480px' }, [
360
+ m('div.px-5.py-4.border-b.border-gray-100.flex.items-center.justify-between.shrink-0', [
361
361
  m('h3.text-sm.font-semibold.text-gray-900', 'Recent Activity'),
362
362
  m('span.text-xs.text-gray-400', 'Live'),
363
363
  ]),
364
- m('div.divide-y.divide-gray-50', [
364
+ m('div.divide-y.divide-gray-50.overflow-y-auto.flex-1', [
365
365
  s.recent.length === 0
366
366
  ? m('p.text-gray-400.text-sm.text-center.py-6', 'No recent activity')
367
367
  : s.recent.slice(0, 10).map(function(item) {
@@ -35,6 +35,10 @@ function siteAnalyticsPlugin(options = {}) {
35
35
  description: 'Self-hosted page view analytics with admin dashboard',
36
36
  dependencies: { 'admin-panel': '*' },
37
37
 
38
+ csp: {
39
+ scriptSrc: ['https://cdn.jsdelivr.net'],
40
+ },
41
+
38
42
  register(ctx) {
39
43
  const knex = db.knex || db;
40
44