Yacine Jernite commited on
Commit
1238bb5
·
1 Parent(s): af4f87f

single_area_pagesd

Browse files
js/components/Card.js CHANGED
@@ -33,7 +33,7 @@ export function createCard(type, data, options = {}) {
33
  * Renders an artifact card (for carousels)
34
  */
35
  function renderArtifactCard(artifact, options = {}) {
36
- const { index = 0 } = options;
37
 
38
  // Handle both old and new field names
39
  const title = artifact.title;
@@ -64,7 +64,8 @@ function renderArtifactCard(artifact, options = {}) {
64
  // Render tags
65
  const { areaTagsHtml, topicTagsHtml } = renderTagSet(areaTags, subAreaTags);
66
 
67
- const cardId = `artifact-card-${index}`;
 
68
 
69
  return `
70
  <div class="flex-none w-80 h-60 border border-gray-300 rounded-lg overflow-hidden bg-white/95 shadow-sm relative group hover:shadow-lg transition-shadow duration-200">
 
33
  * Renders an artifact card (for carousels)
34
  */
35
  function renderArtifactCard(artifact, options = {}) {
36
+ const { index = 0, containerId = 'default' } = options;
37
 
38
  // Handle both old and new field names
39
  const title = artifact.title;
 
64
  // Render tags
65
  const { areaTagsHtml, topicTagsHtml } = renderTagSet(areaTags, subAreaTags);
66
 
67
+ // Generate unique card ID by including container ID to avoid duplicates across multiple carousels
68
+ const cardId = `artifact-card-${containerId}-${index}`;
69
 
70
  return `
71
  <div class="flex-none w-80 h-60 border border-gray-300 rounded-lg overflow-hidden bg-white/95 shadow-sm relative group hover:shadow-lg transition-shadow duration-200">
js/components/Carousel.js CHANGED
@@ -6,7 +6,7 @@ export function createArtifactCarousel(artifacts, containerId) {
6
  if (!container) return;
7
 
8
  const cardsHtml = artifacts.map((artifact, index) =>
9
- renderArtifactCard(artifact, { index })
10
  ).join('');
11
 
12
  container.innerHTML = `
 
6
  if (!container) return;
7
 
8
  const cardsHtml = artifacts.map((artifact, index) =>
9
+ renderArtifactCard(artifact, { index, containerId })
10
  ).join('');
11
 
12
  container.innerHTML = `
js/components/PageNavigation.js CHANGED
@@ -56,12 +56,12 @@ export function renderAreaNavigation(areaId, areaTitle, topics, currentTopicId =
56
  const items = [
57
  {
58
  text: 'Overview',
59
- href: `/${areaId}`,
60
- active: currentTopicId === null
61
  },
62
  ...topics.map(topic => ({
63
  text: topic.navName,
64
- href: `/${areaId}/${topic.id}`,
65
  active: topic.id === currentTopicId
66
  }))
67
  ];
 
56
  const items = [
57
  {
58
  text: 'Overview',
59
+ href: `/${areaId}#overview`,
60
+ active: currentTopicId === null || currentTopicId === 'overview'
61
  },
62
  ...topics.map(topic => ({
63
  text: topic.navName,
64
+ href: `/${areaId}#${topic.id}`,
65
  active: topic.id === currentTopicId
66
  }))
67
  ];
js/pages/AreaPage.js CHANGED
@@ -16,24 +16,11 @@ export function renderAreaPage(areaId, topicId = null) {
16
  };
17
  }
18
 
19
- // If no topic specified, show overview of the area
20
- if (!topicId) {
21
- return renderAreaOverview(area, areaId);
22
- }
23
-
24
- // Show specific topic
25
- const topic = area.topics[topicId];
26
- if (!topic) {
27
- return {
28
- content: `<div class="bg-white/90 backdrop-blur-md rounded-lg shadow-sm p-8 mx-4"><h1 class="text-2xl font-bold text-red-600">Topic not found</h1></div>`,
29
- init: () => {}
30
- };
31
- }
32
-
33
- return renderTopicView(area, areaId, topic, topicId);
34
  }
35
 
36
- function renderAreaOverview(area, areaId) {
37
  // Get topics as array
38
  const topics = Object.entries(area.topics).map(([key, value]) => ({
39
  id: key,
@@ -47,8 +34,9 @@ function renderAreaOverview(area, areaId) {
47
  <!-- Top spacing -->
48
  <div class="mb-8"></div>
49
 
50
- ${renderAreaNavigation(areaId, area.navTitle, topics, null)}
51
 
 
52
  ${renderContentSection('overview', `
53
  <h1 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">${area.title}</h1>
54
 
@@ -92,7 +80,7 @@ function renderAreaOverview(area, areaId) {
92
  const titleAttr = shortDesc ? ` title="${shortDesc}"` : '';
93
 
94
  return `
95
- <a href="/${areaId}/${topic.id}" class="inline-block px-3 py-1.5 text-sm font-semibold ${bgColor} ${textColor} ${borderColor} border rounded hover:opacity-90 transition-opacity"${titleAttr}>
96
  ${topic.navName}
97
  </a>
98
  `;
@@ -108,89 +96,52 @@ function renderAreaOverview(area, areaId) {
108
  </div>
109
  </div>
110
  `)}
 
 
 
111
  `;
112
 
113
  return {
114
  content,
115
  init: () => {
116
  initializeAreaArtifactsCarousel(areaId);
 
 
 
 
117
  }
118
  };
119
  }
120
 
121
- function renderTopicView(area, areaId, topic, topicId) {
 
122
  const topicName = topic.navName || topic.name;
123
  const topicDescription = topic.description?.paragraphs
124
  ? topic.description.paragraphs.map(p => `<p class="text-gray-900 leading-relaxed mb-4">${p}</p>`).join('')
125
  : (topic.description?.short || topic.description || '');
126
 
127
- // Get all topics for navigation
128
- const allTopics = Object.entries(area.topics).map(([key, value]) => ({
129
- id: key,
130
- navName: typeof value === 'string' ? value : (value.navName || value.name)
131
- }));
 
 
132
 
133
- const content = `
134
- <!-- Top spacing -->
135
- <div class="mb-8"></div>
136
 
137
- ${renderAreaNavigation(areaId, area.navTitle, allTopics, topicId)}
138
-
139
- ${renderContentSection(topicId, `
140
- <!-- Breadcrumb -->
141
- <div class="mb-4 text-sm text-gray-600">
142
- <a href="/${areaId}" class="hover:text-blue-600 transition-colors">${area.navTitle}</a>
143
- <span class="mx-2">›</span>
144
- <span class="text-gray-900 font-medium">${topicName}</span>
145
- </div>
146
-
147
- <h1 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">${topic.name}</h1>
148
-
149
- <!-- Topic Description -->
150
- ${topicDescription ? `
151
- <div class="mb-8 text-gray-900">
152
- ${topicDescription}
153
- </div>
154
- ` : ''}
155
-
156
- <h3 class="text-xl md:text-2xl font-bold text-gray-900 mb-6">Related Research & Resources</h3>
157
- <div id="${topicId}-artifacts-carousel" class="overflow-x-auto -mx-2">
158
- <!-- Carousel will be inserted here -->
159
- </div>
160
- `)}
161
- `;
162
 
163
- return {
164
- content,
165
- init: () => {
166
- // Initialize artifact carousel for this topic
167
- setTimeout(async () => {
168
- try {
169
- const allArtifacts = window.allArtifacts || [];
170
-
171
- // Filter artifacts by this specific topic
172
- const filteredArtifacts = allArtifacts.filter(artifact =>
173
- artifact.topics && artifact.topics.includes(topicId)
174
- );
175
-
176
- // Transform field names to match expected format
177
- const transformedArtifacts = filteredArtifacts.map(artifact => ({
178
- ...artifact,
179
- areaTags: artifact.areas,
180
- subAreaTags: artifact.topics,
181
- sourceUrl: artifact.url
182
- }));
183
-
184
- const carouselId = `${topicId}-artifacts-carousel`;
185
- createArtifactCarousel(transformedArtifacts.length > 0 ? transformedArtifacts : getFeaturedArtifacts(), carouselId);
186
- } catch (error) {
187
- console.error(`Error creating carousel for ${topicId}:`, error);
188
- const carouselId = `${topicId}-artifacts-carousel`;
189
- createArtifactCarousel(getFeaturedArtifacts(), carouselId);
190
- }
191
- }, 50);
192
- }
193
- };
194
  }
195
 
196
  // Helper function to initialize area artifacts carousel
@@ -222,3 +173,32 @@ function initializeAreaArtifactsCarousel(areaId) {
222
  }
223
  }, 50);
224
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  };
17
  }
18
 
19
+ // Always render full page with all topics (single scrollable page)
20
+ return renderFullAreaPage(area, areaId, topicId);
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
+ function renderFullAreaPage(area, areaId, initialTopicId = null) {
24
  // Get topics as array
25
  const topics = Object.entries(area.topics).map(([key, value]) => ({
26
  id: key,
 
34
  <!-- Top spacing -->
35
  <div class="mb-8"></div>
36
 
37
+ ${renderAreaNavigation(areaId, area.navTitle, topics, initialTopicId)}
38
 
39
+ <!-- Overview Section -->
40
  ${renderContentSection('overview', `
41
  <h1 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">${area.title}</h1>
42
 
 
80
  const titleAttr = shortDesc ? ` title="${shortDesc}"` : '';
81
 
82
  return `
83
+ <a href="/${areaId}#${topic.id}" class="inline-block px-3 py-1.5 text-sm font-semibold ${bgColor} ${textColor} ${borderColor} border rounded hover:opacity-90 transition-opacity"${titleAttr}>
84
  ${topic.navName}
85
  </a>
86
  `;
 
96
  </div>
97
  </div>
98
  `)}
99
+
100
+ <!-- Topic Sections (each as a separate content section) -->
101
+ ${topics.map(topic => renderTopicSection(area, areaId, topic)).join('')}
102
  `;
103
 
104
  return {
105
  content,
106
  init: () => {
107
  initializeAreaArtifactsCarousel(areaId);
108
+ // Initialize artifact carousels for all topics
109
+ topics.forEach(topic => {
110
+ initializeTopicArtifactsCarousel(areaId, topic.id);
111
+ });
112
  }
113
  };
114
  }
115
 
116
+ function renderTopicSection(area, areaId, topic) {
117
+ const topicId = topic.id;
118
  const topicName = topic.navName || topic.name;
119
  const topicDescription = topic.description?.paragraphs
120
  ? topic.description.paragraphs.map(p => `<p class="text-gray-900 leading-relaxed mb-4">${p}</p>`).join('')
121
  : (topic.description?.short || topic.description || '');
122
 
123
+ return renderContentSection(topicId, `
124
+ <!-- Breadcrumb -->
125
+ <div class="mb-4 text-sm text-gray-600">
126
+ <a href="/${areaId}#overview" class="hover:text-blue-600 transition-colors">${area.navTitle}</a>
127
+ <span class="mx-2">›</span>
128
+ <span class="text-gray-900 font-medium">${topicName}</span>
129
+ </div>
130
 
131
+ <h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-6">${topic.name}</h2>
 
 
132
 
133
+ <!-- Topic Description -->
134
+ ${topicDescription ? `
135
+ <div class="mb-8 text-gray-900">
136
+ ${topicDescription}
137
+ </div>
138
+ ` : ''}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
+ <h3 class="text-xl md:text-2xl font-bold text-gray-900 mb-6">Related Research & Resources</h3>
141
+ <div id="${topicId}-artifacts-carousel" class="overflow-x-auto -mx-2">
142
+ <!-- Carousel will be inserted here -->
143
+ </div>
144
+ `);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
 
147
  // Helper function to initialize area artifacts carousel
 
173
  }
174
  }, 50);
175
  }
176
+
177
+ // Helper function to initialize topic artifacts carousel
178
+ function initializeTopicArtifactsCarousel(areaId, topicId) {
179
+ setTimeout(() => {
180
+ try {
181
+ const allArtifacts = window.allArtifacts || [];
182
+
183
+ // Filter artifacts by this specific topic
184
+ const filteredArtifacts = allArtifacts.filter(artifact =>
185
+ artifact.topics && artifact.topics.includes(topicId)
186
+ );
187
+
188
+ // Transform field names to match expected format
189
+ const transformedArtifacts = filteredArtifacts.map(artifact => ({
190
+ ...artifact,
191
+ areaTags: artifact.areas,
192
+ subAreaTags: artifact.topics,
193
+ sourceUrl: artifact.url
194
+ }));
195
+
196
+ const carouselId = `${topicId}-artifacts-carousel`;
197
+ createArtifactCarousel(transformedArtifacts.length > 0 ? transformedArtifacts : getFeaturedArtifacts(), carouselId);
198
+ } catch (error) {
199
+ console.error(`Error creating carousel for ${topicId}:`, error);
200
+ const carouselId = `${topicId}-artifacts-carousel`;
201
+ createArtifactCarousel(getFeaturedArtifacts(), carouselId);
202
+ }
203
+ }, 50);
204
+ }
js/utils/router.js CHANGED
@@ -29,8 +29,10 @@ class Router {
29
  }
30
 
31
  init() {
32
- // Handle initial page load
33
- this.loadPage(window.location.pathname);
 
 
34
 
35
  // Handle browser back/forward
36
  window.addEventListener('popstate', (e) => {
@@ -52,7 +54,6 @@ class Router {
52
  });
53
 
54
  // Handle initial hash if present
55
- const initialHash = window.location.hash.substring(1);
56
  if (initialHash) {
57
  setTimeout(() => scrollToSection(initialHash), 200);
58
  }
@@ -61,7 +62,7 @@ class Router {
61
  this.initializeNavigation();
62
  }
63
 
64
- async loadPage(path) {
65
  // Parse path to support /area/topic format
66
  const pathParts = path.split('/').filter(p => p);
67
  let route, area, topic;
@@ -74,12 +75,18 @@ class Router {
74
  route = this.routes[path] || 'home';
75
  if (this.areas.includes(pathParts[0])) {
76
  area = pathParts[0];
 
77
  }
78
  } else if (pathParts.length === 2 && this.areas.includes(pathParts[0])) {
79
- // Two segments: /efficiency/environment
80
  area = pathParts[0];
81
  topic = pathParts[1];
82
- route = area;
 
 
 
 
 
83
  } else {
84
  // Unknown route, default to home
85
  route = 'home';
@@ -363,8 +370,13 @@ class Router {
363
  window.history.pushState({}, '', fullUrl);
364
  }
365
 
366
- // Load page and then handle hash
367
- this.loadPage(path).then(() => {
 
 
 
 
 
368
  if (hash) {
369
  setTimeout(() => scrollToSection(hash), 100);
370
  } else {
 
29
  }
30
 
31
  init() {
32
+ // Handle initial page load with hash support
33
+ const initialPath = window.location.pathname;
34
+ const initialHash = window.location.hash.substring(1);
35
+ this.loadPage(initialPath, initialHash);
36
 
37
  // Handle browser back/forward
38
  window.addEventListener('popstate', (e) => {
 
54
  });
55
 
56
  // Handle initial hash if present
 
57
  if (initialHash) {
58
  setTimeout(() => scrollToSection(initialHash), 200);
59
  }
 
62
  this.initializeNavigation();
63
  }
64
 
65
+ async loadPage(path, topicFromHash = null) {
66
  // Parse path to support /area/topic format
67
  const pathParts = path.split('/').filter(p => p);
68
  let route, area, topic;
 
75
  route = this.routes[path] || 'home';
76
  if (this.areas.includes(pathParts[0])) {
77
  area = pathParts[0];
78
+ topic = topicFromHash; // Use topic from hash for area pages
79
  }
80
  } else if (pathParts.length === 2 && this.areas.includes(pathParts[0])) {
81
+ // Two segments: /sustainability/climate - redirect to hash-based navigation
82
  area = pathParts[0];
83
  topic = pathParts[1];
84
+
85
+ // Redirect to new hash-based format
86
+ const newUrl = `/${area}#${topic}`;
87
+ window.history.replaceState({}, '', newUrl);
88
+ this.navigateToUrl(newUrl, false);
89
+ return;
90
  } else {
91
  // Unknown route, default to home
92
  route = 'home';
 
370
  window.history.pushState({}, '', fullUrl);
371
  }
372
 
373
+ // Extract topic from hash for area pages
374
+ const pathParts = path.split('/').filter(p => p);
375
+ const isAreaPage = pathParts.length === 1 && this.areas.includes(pathParts[0]);
376
+ const topicFromHash = isAreaPage && hash ? hash : null;
377
+
378
+ // Load page with topic from hash
379
+ this.loadPage(path, topicFromHash).then(() => {
380
  if (hash) {
381
  setTimeout(() => scrollToSection(hash), 100);
382
  } else {