@@ -31,7 +31,18 @@ function cacheSet(url, data) {
3131 try {
3232 sessionStorage . setItem ( cacheKey ( url ) , JSON . stringify ( { data, ts : Date . now ( ) } ) ) ;
3333 } catch {
34- // sessionStorage quota exceeded — silently skip caching.
34+ // sessionStorage quota exceeded; silently skip caching.
35+ }
36+ }
37+
38+ function decodeBase64Utf8 ( content ) {
39+ const binary = atob ( content . replace ( / \n / g, '' ) ) ;
40+ const bytes = Uint8Array . from ( binary , ( char ) => char . charCodeAt ( 0 ) ) ;
41+
42+ try {
43+ return new TextDecoder ( 'utf-8' ) . decode ( bytes ) ;
44+ } catch {
45+ return binary ;
3546 }
3647}
3748
@@ -67,7 +78,7 @@ export async function getManifestoFiles() {
6778 const url = `${ API_BASE } /repos/${ REPO_OWNER } /${ REPO_NAME } /contents/${ MANIFESTO_PATH } ` ;
6879 const files = await fetchJSON ( url ) ;
6980 return files
70- . filter ( f => f . type === 'file' && f . name . endsWith ( '.md' ) && f . name . toLowerCase ( ) !== 'readme.md' )
81+ . filter ( ( file ) => file . type === 'file' && file . name . endsWith ( '.md' ) && file . name . toLowerCase ( ) !== 'readme.md' )
7182 . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
7283}
7384
@@ -78,8 +89,7 @@ export async function getManifestoFiles() {
7889export async function getFileContent ( path ) {
7990 const url = `${ API_BASE } /repos/${ REPO_OWNER } /${ REPO_NAME } /contents/${ path } ` ;
8091 const file = await fetchJSON ( url ) ;
81- // GitHub API returns content as base64 with newlines.
82- return atob ( file . content . replace ( / \n / g, '' ) ) ;
92+ return decodeBase64Utf8 ( file . content ) ;
8393}
8494
8595// --- Pulls API ---------------------------------------------------------------
@@ -130,33 +140,32 @@ export async function getPipelineStatus() {
130140
131141/**
132142 * Extracts a human-readable title from a manifesto filename.
133- * e.g. "article_05_housing_and_homelessness.md" → "Article 05: Housing and Homelessness"
134- * "why_the_potato.md" → "Why the Potato?"
143+ * e.g. "article_05_housing_and_homelessness.md" -> "Article 05: Housing and Homelessness"
144+ * "why_the_potato.md" -> "Why the Potato?"
135145 */
136146export function filenameToTitle ( filename ) {
137147 const base = filename . replace ( / \. m d $ / , '' ) ;
138148
139- // Special case: why_the_potato
140149 if ( base === 'why_the_potato' ) return 'Why the Potato?' ;
141150
142- // article_NN_some_title pattern
143151 const match = base . match ( / ^ a r t i c l e _ ( \d + ) _ ( .+ ) $ / ) ;
144152 if ( match ) {
145153 const num = match [ 1 ] ;
146- const words = match [ 2 ] . split ( '_' ) . map ( w => w . charAt ( 0 ) . toUpperCase ( ) + w . slice ( 1 ) ) ;
154+ const words = match [ 2 ] . split ( '_' ) . map ( ( word ) => word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) ) ;
147155 return `Article ${ num } : ${ words . join ( ' ' ) } ` ;
148156 }
149157
150- // Fallback: capitalise words
151- return base . split ( '_' ) . map ( w => w . charAt ( 0 ) . toUpperCase ( ) + w . slice ( 1 ) ) . join ( ' ' ) ;
158+ return base . split ( '_' ) . map ( ( word ) => word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) ) . join ( ' ' ) ;
152159}
153160
154161/**
155162 * Formats an ISO date string to a readable date.
156163 */
157164export function formatDate ( iso ) {
158- if ( ! iso ) return '— ' ;
165+ if ( ! iso ) return '- ' ;
159166 return new Date ( iso ) . toLocaleDateString ( 'en-CA' , {
160- year : 'numeric' , month : 'long' , day : 'numeric'
167+ year : 'numeric' ,
168+ month : 'long' ,
169+ day : 'numeric'
161170 } ) ;
162171}
0 commit comments