veslx 0.1.29 → 0.1.31

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/bin/lib/build.ts CHANGED
@@ -86,6 +86,7 @@ export default async function buildApp(dir?: string) {
86
86
  ...fileConfig?.site,
87
87
  },
88
88
  slides: fileConfig?.slides,
89
+ posts: fileConfig?.posts,
89
90
  };
90
91
 
91
92
  const veslxRoot = new URL('../..', import.meta.url).pathname;
package/bin/lib/export.ts CHANGED
@@ -124,6 +124,7 @@ export default async function exportToPdf(
124
124
  ...fileConfig?.site,
125
125
  },
126
126
  slides: fileConfig?.slides,
127
+ posts: fileConfig?.posts,
127
128
  }
128
129
 
129
130
  const veslxRoot = new URL('../..', import.meta.url).pathname
package/bin/lib/serve.ts CHANGED
@@ -81,6 +81,7 @@ export default async function serve(dir?: string) {
81
81
  ...fileConfig?.site,
82
82
  },
83
83
  slides: fileConfig?.slides,
84
+ posts: fileConfig?.posts,
84
85
  };
85
86
 
86
87
  const veslxRoot = new URL('../..', import.meta.url).pathname;
@@ -38,15 +38,17 @@ function PostList() {
38
38
  return /* @__PURE__ */ jsx("div", { className: "py-24 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground font-mono text-sm tracking-wide", children: "no entries" }) });
39
39
  }
40
40
  const sortMode = ((_a = veslxConfig.posts) == null ? void 0 : _a.sort) ?? "alpha";
41
- if (sortMode === "date") {
41
+ if (sortMode === "date" || sortMode === "date-asc") {
42
+ const ascending = sortMode === "date-asc";
42
43
  posts = posts.sort((a, b) => {
43
44
  var _a2, _b;
44
45
  const dateA = (_a2 = getFrontmatter(a)) == null ? void 0 : _a2.date;
45
46
  const dateB = (_b = getFrontmatter(b)) == null ? void 0 : _b.date;
46
47
  if (!dateA && !dateB) return a.name.localeCompare(b.name);
47
- if (!dateA) return 1;
48
- if (!dateB) return -1;
49
- return new Date(dateB).getTime() - new Date(dateA).getTime();
48
+ if (!dateA) return -1;
49
+ if (!dateB) return 1;
50
+ const diff = new Date(dateA).getTime() - new Date(dateB).getTime();
51
+ return ascending ? diff : -diff;
50
52
  });
51
53
  } else {
52
54
  posts = posts.sort((a, b) => a.name.localeCompare(b.name));
@@ -1 +1 @@
1
- {"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return `/${post.file.path}`;\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return `/${post.readme.path}`;\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nexport function PostList() {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date') {\n // Sort by date descending (newest first), posts without dates go to the end\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return 1;\n if (!dateB) return -1;\n return new Date(dateB as string).getTime() - new Date(dateA as string).getTime();\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const linkPath = getLinkPath(post);\n const isSlides = linkPath.endsWith('SLIDES.mdx') || linkPath.endsWith('.slides.mdx');\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n linkPath={linkPath}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":";;;;;;;AAcA,SAAS,WAAW,MAAsB;AACxC,SAAO,KACJ,QAAQ,SAAS,EAAE,EACnB,QAAQ,MAAM,GAAG,EACjB,QAAQ,SAAS,CAAA,MAAK,EAAE,aAAa;AAC1C;AAGA,SAAS,YAAY,MAAyB;AAC5C,MAAI,KAAK,MAAM;AAEb,WAAO,IAAI,KAAK,KAAK,IAAI;AAAA,EAC3B,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAEtC,WAAO,IAAI,KAAK,OAAO,IAAI;AAAA,EAC7B,WAAW,KAAK,QAAQ;AAEtB,WAAO,IAAI,KAAK,OAAO,IAAI;AAAA,EAC7B,OAAO;AAEL,WAAO,IAAI,KAAK,IAAI;AAAA,EACtB;AACF;AAEO,SAAS,WAAW;;AACzB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAE5B,QAAM,EAAE,WAAoB,UAAU,aAAa,IAAI;AAEvD,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAQA,MAAI,CAAC,WAAW;AACd,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAEA,MAAI,QAAQ,uBAAuB,SAAS;AAE5C,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,UAAQ,mBAAmB,KAAK;AAEhC,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,QAAM,aAAW,iBAAY,UAAZ,mBAAmB,SAAQ;AAC5C,MAAI,aAAa,QAAQ;AAEvB,YAAQ,MAAM,KAAK,CAAC,GAAG,MAAM;;AAC3B,YAAM,SAAQA,MAAA,eAAe,CAAC,MAAhB,gBAAAA,IAAmB;AACjC,YAAM,SAAQ,oBAAe,CAAC,MAAhB,mBAAmB;AACjC,UAAI,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,cAAc,EAAE,IAAI;AACxD,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,IAAI,KAAK,KAAe,EAAE,QAAA,IAAY,IAAI,KAAK,KAAe,EAAE,QAAA;AAAA,IACzE,CAAC;AAAA,EACH,OAAO;AAEL,YAAQ,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EAC3D;AAEA,6BACG,OAAA,EAAI,WAAU,uBACZ,UAAA,MAAM,IAAI,CAAC,SAAS;AACnB,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,SAAS,2CAAa,UAAoB,WAAW,KAAK,IAAI;AACpE,UAAM,cAAc,2CAAa;AACjC,UAAM,QAAO,2CAAa,QAAO,IAAI,KAAK,YAAY,IAAc,IAAI;AACxE,UAAM,WAAW,YAAY,IAAI;AACjC,UAAM,WAAW,SAAS,SAAS,YAAY,KAAK,SAAS,SAAS,aAAa;AAEnF,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MALK,KAAK;AAAA,IAAA;AAAA,EAQhB,CAAC,EAAA,CACH;AAEJ;"}
1
+ {"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return `/${post.file.path}`;\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return `/${post.readme.path}`;\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nexport function PostList() {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date' || sortMode === 'date-asc') {\n const ascending = sortMode === 'date-asc';\n // Sort by date, posts without dates go to the top\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return -1;\n if (!dateB) return 1;\n const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();\n return ascending ? diff : -diff;\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const linkPath = getLinkPath(post);\n const isSlides = linkPath.endsWith('SLIDES.mdx') || linkPath.endsWith('.slides.mdx');\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n linkPath={linkPath}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":";;;;;;;AAcA,SAAS,WAAW,MAAsB;AACxC,SAAO,KACJ,QAAQ,SAAS,EAAE,EACnB,QAAQ,MAAM,GAAG,EACjB,QAAQ,SAAS,CAAA,MAAK,EAAE,aAAa;AAC1C;AAGA,SAAS,YAAY,MAAyB;AAC5C,MAAI,KAAK,MAAM;AAEb,WAAO,IAAI,KAAK,KAAK,IAAI;AAAA,EAC3B,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAEtC,WAAO,IAAI,KAAK,OAAO,IAAI;AAAA,EAC7B,WAAW,KAAK,QAAQ;AAEtB,WAAO,IAAI,KAAK,OAAO,IAAI;AAAA,EAC7B,OAAO;AAEL,WAAO,IAAI,KAAK,IAAI;AAAA,EACtB;AACF;AAEO,SAAS,WAAW;;AACzB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAE5B,QAAM,EAAE,WAAoB,UAAU,aAAa,IAAI;AAEvD,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAQA,MAAI,CAAC,WAAW;AACd,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAEA,MAAI,QAAQ,uBAAuB,SAAS;AAE5C,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,UAAQ,mBAAmB,KAAK;AAEhC,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,QAAM,aAAW,iBAAY,UAAZ,mBAAmB,SAAQ;AAC5C,MAAI,aAAa,UAAU,aAAa,YAAY;AAClD,UAAM,YAAY,aAAa;AAE/B,YAAQ,MAAM,KAAK,CAAC,GAAG,MAAM;;AAC3B,YAAM,SAAQA,MAAA,eAAe,CAAC,MAAhB,gBAAAA,IAAmB;AACjC,YAAM,SAAQ,oBAAe,CAAC,MAAhB,mBAAmB;AACjC,UAAI,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,cAAc,EAAE,IAAI;AACxD,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,CAAC,MAAO,QAAO;AACnB,YAAM,OAAO,IAAI,KAAK,KAAe,EAAE,QAAA,IAAY,IAAI,KAAK,KAAe,EAAE,QAAA;AAC7E,aAAO,YAAY,OAAO,CAAC;AAAA,IAC7B,CAAC;AAAA,EACH,OAAO;AAEL,YAAQ,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EAC3D;AAEA,6BACG,OAAA,EAAI,WAAU,uBACZ,UAAA,MAAM,IAAI,CAAC,SAAS;AACnB,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,SAAS,2CAAa,UAAoB,WAAW,KAAK,IAAI;AACpE,UAAM,cAAc,2CAAa;AACjC,UAAM,QAAO,2CAAa,QAAO,IAAI,KAAK,YAAY,IAAc,IAAI;AACxE,UAAM,WAAW,YAAY,IAAI;AACjC,UAAM,WAAW,SAAS,SAAS,YAAY,KAAK,SAAS,SAAS,aAAa;AAEnF,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MALK,KAAK;AAAA,IAAA;AAAA,EAQhB,CAAC,EAAA,CACH;AAEJ;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veslx",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -281,33 +281,15 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
281
281
  next()
282
282
  }
283
283
 
284
+ // Virtual module ID for the modified CSS
285
+ const VIRTUAL_CSS_MODULE = '\0veslx:index.css'
286
+
284
287
  return {
285
288
  name: 'content',
286
289
  enforce: 'pre',
287
290
 
288
- // Inject @source directive for Tailwind to scan content directory for classes
289
- transform(code, id) {
290
- // Only process CSS files containing the tailwindcss import
291
- if (!id.endsWith('.css')) return null
292
- if (!code.includes('@import "tailwindcss"')) return null
293
-
294
- // Calculate relative path from CSS file to content directory
295
- const cssDir = path.dirname(id)
296
- let relativeContentDir = path.relative(cssDir, dir)
297
- relativeContentDir = relativeContentDir.replace(/\\/g, '/') // Windows compatibility
298
-
299
- // Inject @source directive after the tailwindcss import
300
- const sourceDirective = `@source "${relativeContentDir}";`
301
- const modified = code.replace(
302
- /(@import\s+["']tailwindcss["'];?)/,
303
- `$1\n${sourceDirective}`
304
- )
305
-
306
- return { code: modified, map: null }
307
- },
308
-
309
291
  // Inject @content alias and fs.allow into Vite config
310
- config() {
292
+ config(config, { command }) {
311
293
  return {
312
294
  resolve: {
313
295
  alias: {
@@ -325,8 +307,18 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
325
307
  }
326
308
  },
327
309
 
328
- // Virtual modules for content MDX imports and site config
329
- resolveId(id) {
310
+ // Intercept CSS and virtual module imports
311
+ resolveId(id, importer) {
312
+ // Intercept index.css imported from main.tsx and redirect to our virtual module
313
+ // This allows us to inject @source directive for Tailwind to scan user content
314
+ if (id === './index.css' && importer?.endsWith('/src/main.tsx')) {
315
+ return VIRTUAL_CSS_MODULE
316
+ }
317
+ // Also catch the resolved path
318
+ if (id.endsWith('/src/index.css') && !id.startsWith('\0')) {
319
+ return VIRTUAL_CSS_MODULE
320
+ }
321
+ // Virtual modules for content
330
322
  if (id === VIRTUAL_MODULE_ID) {
331
323
  return RESOLVED_VIRTUAL_MODULE_ID
332
324
  }
@@ -336,6 +328,28 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
336
328
  },
337
329
 
338
330
  load(id) {
331
+ // Serve the modified CSS content with @source directive
332
+ // This enables Tailwind v4 to scan the user's content directory for classes
333
+ if (id === VIRTUAL_CSS_MODULE) {
334
+ // Read the original CSS
335
+ const veslxRoot = path.dirname(path.dirname(__dirname))
336
+ const cssPath = path.join(veslxRoot, 'src/index.css')
337
+ const cssContent = fs.readFileSync(cssPath, 'utf-8')
338
+
339
+ // Use absolute path for @source directive
340
+ const absoluteContentDir = dir.replace(/\\/g, '/')
341
+
342
+ // Inject @source directive after the tailwindcss import
343
+ const sourceDirective = `@source "${absoluteContentDir}";`
344
+ const modified = cssContent.replace(
345
+ /(@import\s+["']tailwindcss["'];?)/,
346
+ `$1\n${sourceDirective}`
347
+ )
348
+
349
+ return modified
350
+ }
351
+
352
+ // Virtual module for content
339
353
  if (id === RESOLVED_VIRTUAL_MODULE_ID) {
340
354
  // Extract frontmatter from MDX files at build time (avoids MDX hook issues)
341
355
  const frontmatterData = extractFrontmatters(dir);
@@ -3,7 +3,7 @@ export interface SlidesConfig {
3
3
  }
4
4
 
5
5
  export interface PostsConfig {
6
- sort?: 'date' | 'alpha';
6
+ sort?: 'date' | 'date-asc' | 'alpha';
7
7
  }
8
8
 
9
9
  export interface SiteConfig {
@@ -26,7 +26,7 @@ export interface ResolvedSlidesConfig {
26
26
  }
27
27
 
28
28
  export interface ResolvedPostsConfig {
29
- sort: 'date' | 'alpha';
29
+ sort: 'date' | 'date-asc' | 'alpha';
30
30
  }
31
31
 
32
32
  export interface ResolvedSiteConfig {
@@ -82,15 +82,17 @@ export function PostList() {
82
82
 
83
83
  // Sort based on config
84
84
  const sortMode = veslxConfig.posts?.sort ?? 'alpha';
85
- if (sortMode === 'date') {
86
- // Sort by date descending (newest first), posts without dates go to the end
85
+ if (sortMode === 'date' || sortMode === 'date-asc') {
86
+ const ascending = sortMode === 'date-asc';
87
+ // Sort by date, posts without dates go to the top
87
88
  posts = posts.sort((a, b) => {
88
89
  const dateA = getFrontmatter(a)?.date;
89
90
  const dateB = getFrontmatter(b)?.date;
90
91
  if (!dateA && !dateB) return a.name.localeCompare(b.name);
91
- if (!dateA) return 1;
92
- if (!dateB) return -1;
93
- return new Date(dateB as string).getTime() - new Date(dateA as string).getTime();
92
+ if (!dateA) return -1;
93
+ if (!dateB) return 1;
94
+ const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();
95
+ return ascending ? diff : -diff;
94
96
  });
95
97
  } else {
96
98
  // Alphanumeric sorting by name