odac 1.4.4 → 1.4.6

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/src/View.js CHANGED
@@ -103,6 +103,7 @@ class View {
103
103
  }
104
104
  }
105
105
  #part = {}
106
+ #refresh = new Set()
106
107
  #odac = null
107
108
 
108
109
  constructor(odac) {
@@ -148,10 +149,25 @@ class View {
148
149
  }
149
150
  }
150
151
 
151
- // Render requested elements
152
+ // Build current parts manifest (part name → view path)
153
+ const currentParts = {}
154
+ for (let key in this.#part) {
155
+ if (['all', 'skeleton'].includes(key)) continue
156
+ if (this.#part[key]) currentParts[key] = this.#part[key]
157
+ }
158
+
159
+ // Parse client's previous parts to detect unchanged regions
160
+ const clientParts = this.#odac.Request.clientParts || {}
161
+
162
+ // Render requested elements (skip unchanged parts)
152
163
  let title = null
153
164
  for (let element of this.#odac.Request.ajaxLoad) {
154
165
  if (this.#part[element]) {
166
+ // Skip rendering if the part view path has not changed and refresh is not forced
167
+ // Content is always re-rendered since its output depends on the current URL
168
+ if (element !== 'content' && !this.#refresh.has(element) && clientParts[element] && clientParts[element] === this.#part[element])
169
+ continue
170
+
155
171
  let viewPath = this.#part[element]
156
172
  if (viewPath.includes('.')) viewPath = viewPath.replace(/\./g, '/')
157
173
  if (await this.#exists(`./view/${element}/${viewPath}.html`)) {
@@ -202,6 +218,7 @@ class View {
202
218
 
203
219
  this.#odac.Request.end({
204
220
  output: output,
221
+ parts: currentParts,
205
222
  variables: variables,
206
223
  data: this.#odac.Request.sharedData,
207
224
  title: title,
@@ -239,6 +256,9 @@ class View {
239
256
  }
240
257
  }
241
258
  }
259
+
260
+ // Clean up unresolved skeleton placeholders
261
+ result = result.replace(/\{\{\s*[A-Z_]+\s*\}\}/g, '')
242
262
  }
243
263
 
244
264
  if (result) {
@@ -294,7 +314,7 @@ class View {
294
314
  }
295
315
 
296
316
  if (attrs.get) {
297
- return `{{ get('${attrs.get}') || '' }}`
317
+ return `{{ get('${attrs.get.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}') || '' }}`
298
318
  } else if (attrs.var) {
299
319
  if (attrs.raw) {
300
320
  return `{!! ${attrs.var} !!}`
@@ -323,7 +343,7 @@ class View {
323
343
  }
324
344
 
325
345
  if (attrs.get) {
326
- return `{{ get('${attrs.get}') || '' }}`
346
+ return `{{ get('${attrs.get.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}') || '' }}`
327
347
  } else if (attrs.var) {
328
348
  if (attrs.raw) {
329
349
  return `{!! ${attrs.var} !!}`
@@ -350,8 +370,9 @@ class View {
350
370
  return `%s${placeholderIndex++}`
351
371
  })
352
372
 
373
+ const escapedContent = processedContent.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
353
374
  const translationCall =
354
- placeholders.length > 0 ? `__('${processedContent}', ${placeholders.join(', ')})` : `__('${processedContent}')`
375
+ placeholders.length > 0 ? `__('${escapedContent}', ${placeholders.join(', ')})` : `__('${escapedContent}')`
355
376
 
356
377
  if (attrs.raw) {
357
378
  return `{!! ${translationCall} !!}`
@@ -359,7 +380,7 @@ class View {
359
380
  return `{{ ${translationCall} }}`
360
381
  }
361
382
  } else {
362
- return `{{ '${innerContent}' }}`
383
+ return `{{ '${innerContent.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' }}`
363
384
  }
364
385
  })
365
386
  if (before === content) break
@@ -523,8 +544,12 @@ class View {
523
544
 
524
545
  // - SET PARTS
525
546
  set(...args) {
526
- if (args.length === 1 && typeof args[0] === 'object') for (let key in args[0]) this.#part[key] = args[0][key]
527
- else if (args.length === 2) this.#part[args[0]] = args[1]
547
+ if (args.length === 1 && typeof args[0] === 'object') {
548
+ for (let key in args[0]) this.#part[key] = args[0][key]
549
+ } else if (args.length >= 2) {
550
+ this.#part[args[0]] = args[1]
551
+ if (args[2]?.refresh) this.#refresh.add(args[0])
552
+ }
528
553
 
529
554
  if (!this.#odac.Request.page) {
530
555
  this.#odac.Request.page = this.#part.content || this.#part.all || ''
@@ -541,12 +566,21 @@ class View {
541
566
  }
542
567
 
543
568
  #addNavigateAttribute(skeleton) {
544
- skeleton = skeleton.replace(/(<[^>]+>)(\s*\{\{\s*CONTENT\s*\}\})/, (match, openTag, content) => {
569
+ // Inject data-odac-navigate for placeholders already wrapped in an HTML tag
570
+ skeleton = skeleton.replace(/(<[^>]+>)(\s*\{\{\s*([A-Z_]+)\s*\}\})/g, (match, openTag, content, partName) => {
571
+ const attrName = partName.toLowerCase()
545
572
  if (openTag.includes('data-odac-navigate')) return match
546
- const tagWithAttr = openTag.slice(0, -1) + ' data-odac-navigate="content">'
573
+ const tagWithAttr = openTag.slice(0, -1) + ` data-odac-navigate="${attrName}">`
547
574
  return tagWithAttr + content
548
575
  })
549
576
 
577
+ // Auto-wrap unwrapped placeholders so AJAX navigation can target them
578
+ // Uses display:contents to avoid breaking flex/grid layouts
579
+ skeleton = skeleton.replace(/(?<!>)\s*(\{\{\s*([A-Z_]+)\s*\}\})/g, (match, placeholder, partName) => {
580
+ const attrName = partName.toLowerCase()
581
+ return `<div style="display:contents" data-odac-navigate="${attrName}">${placeholder}</div>`
582
+ })
583
+
550
584
  const skeletonName = this.#part.skeleton || 'main'
551
585
  const pageName = this.#odac.Request.page || ''
552
586
 
@@ -558,6 +592,17 @@ class View {
558
592
  if (!attrs.includes('data-odac-page')) {
559
593
  updates.push(`data-odac-page="${pageName}"`)
560
594
  }
595
+
596
+ // Embed current parts manifest for client-side diffing on first load
597
+ const partsMap = {}
598
+ for (let key in this.#part) {
599
+ if (['all', 'skeleton'].includes(key)) continue
600
+ if (this.#part[key]) partsMap[key] = this.#part[key]
601
+ }
602
+ if (!attrs.includes('data-odac-parts') && Object.keys(partsMap).length > 0) {
603
+ updates.push(`data-odac-parts='${JSON.stringify(partsMap)}'`)
604
+ }
605
+
561
606
  if (updates.length === 0) return match
562
607
  return `<html${attrs} ${updates.join(' ')}>`
563
608
  })
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * About Page Controller
3
3
  *
4
- * This controller renders the about page using Odac's skeleton-based view system.
5
- * Provides information about Odac and its key components.
4
+ * This controller renders the about page using ODAC's skeleton-based view system.
5
+ * Provides information about ODAC and its key components.
6
6
  *
7
7
  * For AJAX requests, only content is returned. For full page loads, skeleton + content.
8
8
  */
@@ -11,7 +11,7 @@ module.exports = function (Odac) {
11
11
  // Set variables for AJAX responses
12
12
  Odac.set(
13
13
  {
14
- pageTitle: 'About Odac',
14
+ pageTitle: 'About ODAC',
15
15
  version: '1.0.0'
16
16
  },
17
17
  true
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Home Page Controller
3
3
  *
4
- * This controller renders the home page using Odac's skeleton-based view system.
4
+ * This controller renders the home page using ODAC's skeleton-based view system.
5
5
  * The skeleton provides the layout (header, nav, footer) and the view provides the content.
6
6
  *
7
7
  * For AJAX requests (odac-link navigation), only the content is returned.
@@ -18,7 +18,7 @@ module.exports = function (Odac) {
18
18
  // Set variables that will be available in AJAX responses
19
19
  Odac.set(
20
20
  {
21
- welcomeMessage: 'Welcome to Odac!',
21
+ welcomeMessage: 'Welcome to ODAC!',
22
22
  timestamp: Date.now()
23
23
  },
24
24
  true
@@ -1,62 +1,21 @@
1
+ /* global Odac */
1
2
  /**
2
- * Odac Template - Client-Side Application
3
+ * ODAC Template - Client-Side Application
3
4
  *
4
5
  * This file demonstrates odac.js features including:
5
- * - AJAX page loading with odac.loader() for smooth navigation
6
+ * - AJAX page loading with Odac.loader() for smooth navigation
6
7
  * - History API integration
7
8
  * - Event delegation
8
9
  */
9
10
 
10
- odac.action({
11
- /**
12
- * AJAX Navigation
13
- * Enables smooth page transitions without full page reloads
14
- *
15
- * Minimal usage: navigate: 'main'
16
- * Medium usage: navigate: {update: 'main', on: callback}
17
- * Full usage: navigate: {links: 'a[href^="/"]', update: {...}, on: callback}
18
- */
19
- navigate: {
20
- update: 'main' // Update <main> element
21
- },
22
-
23
- /**
24
- * Custom functions
25
- * These become available as odac.fn.functionName()
26
- */
27
- function: {
28
- /**
29
- * Update active navigation state
30
- * Highlights the current page in the navigation menu
31
- */
32
- updateActiveNav: function (url) {
33
- // Remove active class from all navigation links
34
- const navLinks = document.querySelectorAll('nav a')
35
- navLinks.forEach(function (link) {
36
- link.classList.remove('active')
37
- })
38
-
39
- // Add active class to current page link
40
- const currentLink = document.querySelector(`nav a[href="${url}"]`)
41
- if (currentLink) {
42
- currentLink.classList.add('active')
43
- } else if (url === '/' || url === '') {
44
- // Handle home page
45
- const homeLink = document.querySelector('nav a[href="/"]')
46
- if (homeLink) {
47
- homeLink.classList.add('active')
48
- }
49
- }
50
- }
51
- },
52
-
11
+ Odac.action({
53
12
  /**
54
13
  * Initialize application on page load
55
14
  * This runs once when the page first loads
56
15
  */
57
16
  load: function () {
58
17
  // Set initial active navigation state
59
- odac.fn.updateActiveNav(window.location.pathname)
18
+ Odac.fn.updateActiveNav(window.location.pathname)
60
19
  },
61
20
 
62
21
  /**
@@ -76,15 +35,8 @@ odac.action({
76
35
  */
77
36
  about: function () {
78
37
  console.log('About page loaded')
79
- },
80
-
81
- /**
82
- * Docs page initialization
83
- */
84
- docs: function () {
85
- console.log('Docs page loaded')
86
38
  }
87
- }
39
+ },
88
40
 
89
41
  // Add your custom event handlers here
90
42
  // Example:
@@ -93,4 +45,36 @@ odac.action({
93
45
  // console.log('Button clicked')
94
46
  // }
95
47
  // }
48
+
49
+ /**
50
+ * Custom functions
51
+ * These become available as Odac.fn.functionName()
52
+ */
53
+ function: {
54
+ /**
55
+ * Update active navigation state
56
+ * Highlights the current page in the navigation menu
57
+ */
58
+ updateActiveNav: function (url) {
59
+ // Remove active class from all navigation links
60
+ const navLinks = document.querySelectorAll('nav a')
61
+ navLinks.forEach(function (link) {
62
+ link.classList.remove('active')
63
+ })
64
+
65
+ // Add active class to current page link
66
+ const currentLinks = document.querySelectorAll(`nav a[href="${url}"]`)
67
+ if (currentLinks.length > 0) {
68
+ currentLinks.forEach(function (link) {
69
+ link.classList.add('active')
70
+ })
71
+ } else if (url === '/' || url === '') {
72
+ // Handle home page
73
+ const homeLinks = document.querySelectorAll('nav a[href="/"]')
74
+ homeLinks.forEach(function (link) {
75
+ link.classList.add('active')
76
+ })
77
+ }
78
+ }
79
+ }
96
80
  })
@@ -3,16 +3,16 @@
3
3
  <head>
4
4
  {{ HEAD }}
5
5
  </head>
6
- <body>
7
- <header>
6
+ <body class="min-h-screen flex flex-col items-center">
7
+ <header class="sticky top-0 w-full z-50 glass h-16 flex items-center">
8
8
  {{ HEADER }}
9
9
  </header>
10
10
 
11
- <main>
11
+ <main class="flex-grow w-full max-w-5xl mx-auto px-6 py-12">
12
12
  {{ CONTENT }}
13
13
  </main>
14
14
 
15
- <footer>
15
+ <footer class="w-full border-t border-brand-200 py-12 mt-auto">
16
16
  {{ FOOTER }}
17
17
  </footer>
18
18
 
@@ -1,65 +1,69 @@
1
- <div class="container">
2
- <section class="page-hero">
3
- <h1>🍭 About This Template</h1>
4
- <p class="lead">This is a starter template for your Odac website. Customize it to build your own application.</p>
5
- </section>
1
+ <div class="space-y-24">
2
+ <!-- About Hero -->
3
+ <section class="text-center space-y-6">
4
+ <h1 class="text-4xl md:text-6xl font-bold tracking-tight text-brand-900 leading-tight">
5
+ About This <br/><span class="text-brand-900/40">Template</span>
6
+ </h1>
7
+ <p class="text-xl text-brand-900/50 max-w-2xl mx-auto font-medium leading-relaxed">
8
+ This is your starting point for building something incredible with ODAC. A modern, high-performance foundation.
9
+ </p>
10
+ </section>
6
11
 
7
- <section class="content-section">
8
- <h2>What You Can Build</h2>
9
- <p>
10
- With Odac, you can build any type of web application - from simple websites to complex web apps.
11
- This template provides a solid foundation with modern features and best practices already configured.
12
- </p>
13
- </section>
12
+ <!-- Template Features -->
13
+ <section class="space-y-12">
14
+ <div class="text-center space-y-2">
15
+ <h2 class="text-3xl font-bold tracking-tight text-brand-900">Template Features</h2>
16
+ <p class="text-brand-900/40">Everything you need, already configured.</p>
17
+ </div>
14
18
 
15
- <section class="content-section">
16
- <h2>Template Features</h2>
17
- <div class="component-grid">
18
- <div class="component-card">
19
- <h3>🎨 Modern UI</h3>
20
- <p>Dark theme with indigo colors, responsive design, and smooth animations.</p>
21
- </div>
22
-
23
- <div class="component-card">
24
- <h3>⚡ AJAX Navigation</h3>
25
- <p>Smooth page transitions without full reloads, already configured and working.</p>
26
- </div>
27
-
28
- <div class="component-card">
29
- <h3>🔒 Security</h3>
30
- <p>CSRF protection, secure sessions, and authentication ready to use.</p>
31
- </div>
32
-
33
- <div class="component-card">
34
- <h3>📱 Responsive</h3>
35
- <p>Mobile-first design that works perfectly on all devices.</p>
36
- </div>
37
- </div>
38
- </section>
19
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
20
+ <div class="p-8 bg-white rounded-[2rem] border border-brand-100 shadow-sm space-y-4">
21
+ <h3 class="text-xl font-bold text-brand-900">Modern UI</h3>
22
+ <p class="text-brand-900/60 leading-relaxed">Design with Apple-style aesthetics using Tailwind CSS. Responsive, minimal, and premium.</p>
23
+ </div>
24
+
25
+ <div class="p-8 bg-white rounded-[2rem] border border-brand-100 shadow-sm space-y-4">
26
+ <h3 class="text-xl font-bold text-brand-900">AJAX Navigation</h3>
27
+ <p class="text-brand-900/60 leading-relaxed">Smooth page transitions without full reloads, already configured and working out of the box.</p>
28
+ </div>
29
+
30
+ <div class="p-8 bg-white rounded-[2rem] border border-brand-100 shadow-sm space-y-4">
31
+ <h3 class="text-xl font-bold text-brand-900">Security</h3>
32
+ <p class="text-brand-900/60 leading-relaxed">CSRF protection, secure sessions, and authentication ready to use for your application.</p>
33
+ </div>
34
+
35
+ <div class="p-8 bg-white rounded-[2rem] border border-brand-100 shadow-sm space-y-4">
36
+ <h3 class="text-xl font-bold text-brand-900">Responsive</h3>
37
+ <p class="text-brand-900/60 leading-relaxed">Mobile-first design that works perfectly on all devices, from small phones to large desktop displays.</p>
38
+ </div>
39
+ </div>
40
+ </section>
39
41
 
40
- <section class="content-section">
41
- <h2>Next Steps</h2>
42
- <ul class="philosophy-list">
43
- <li><strong>Routes:</strong> Define URL patterns in <code>route/www.js</code> to map URLs to controllers</li>
44
- <li><strong>Controllers:</strong> Create page logic in <code>controller/</code> to handle requests and set data</li>
45
- <li><strong>Skeleton:</strong> Design your layout structure in <code>skeleton/</code> with placeholders</li>
46
- <li><strong>Views:</strong> Build page content in <code>view/</code> directory to fill skeleton sections</li>
47
- <li><strong>Assets:</strong> Customize styles and scripts in <code>public/</code> for your brand</li>
48
- </ul>
49
- </section>
42
+ <!-- Next Steps -->
43
+ <section class="bg-brand-900 rounded-[3rem] p-12 md:p-20 text-white space-y-12">
44
+ <h2 class="text-3xl font-bold tracking-tight text-center">Next Steps</h2>
45
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
46
+ <div class="space-y-2 border-l-2 border-white/10 pl-6">
47
+ <h4 class="font-bold">Routes</h4>
48
+ <p class="text-white/40 text-sm">Define URL patterns in <code>route/www.js</code></p>
49
+ </div>
50
+ <div class="space-y-2 border-l-2 border-white/10 pl-6">
51
+ <h4 class="font-bold">Controllers</h4>
52
+ <p class="text-white/40 text-sm">Create page logic in <code>controller/</code></p>
53
+ </div>
54
+ <div class="space-y-2 border-l-2 border-white/10 pl-6">
55
+ <h4 class="font-bold">Skeleton</h4>
56
+ <p class="text-white/40 text-sm">Design your layout structure in <code>skeleton/</code></p>
57
+ </div>
58
+ </div>
59
+ </section>
50
60
 
51
- <section class="content-section">
52
- <h2>Learn More</h2>
53
- <div class="links-grid">
54
- <a href="https://docs.odac.run" class="link-card" target="_blank" data-navigate="false">
55
- <h3>📚 Documentation</h3>
56
- <p>Complete guides, API reference, and tutorials</p>
57
- </a>
58
-
59
- <a href="https://odac.run" class="link-card" target="_blank" data-navigate="false">
60
- <h3>🌐 odac.run</h3>
61
- <p>Official website with examples and community</p>
62
- </a>
63
- </div>
64
- </section>
61
+ <!-- Learn More -->
62
+ <section class="text-center pb-24 space-y-8">
63
+ <h2 class="text-3xl font-bold text-brand-900 tracking-tight">Need help?</h2>
64
+ <div class="flex justify-center gap-4">
65
+ <a href="https://docs.odac.run" target="_blank" rel="noopener" class="px-8 py-3 bg-white border border-brand-100 rounded-full font-bold hover:bg-brand-50 transition-colors" data-navigate="false">Documentation</a>
66
+ <a href="https://odac.run" target="_blank" rel="noopener" class="px-8 py-3 bg-brand-900 text-white rounded-full font-bold hover:opacity-90 transition-opacity" data-navigate="false">Official Site</a>
67
+ </div>
68
+ </section>
65
69
  </div>