@eox/pages-theme-eox 1.9.0 → 2.0.0-alpha.10

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.
Files changed (53) hide show
  1. package/.gitlab-ci.yml +4 -1
  2. package/README.md +88 -11
  3. package/cypress/support/mocks/authors.data.js +14 -0
  4. package/cypress/support/mocks/helpers.js +65 -4
  5. package/cypress/support/mocks/vitepress.js +8 -1
  6. package/cypress.config.js +14 -0
  7. package/package.json +3 -1
  8. package/src/Layout.vue +69 -1
  9. package/src/authors.data.js +14 -0
  10. package/src/components/BlogList.vue +655 -0
  11. package/src/components/BlogPost.vue +472 -0
  12. package/src/components/CTASection.vue +18 -4
  13. package/src/components/FeatureCard/Index.vue +462 -31
  14. package/src/components/FeatureCard/Poster.vue +25 -3
  15. package/src/components/FeatureSection.cy.js +16 -0
  16. package/src/components/FeatureSection.vue +3 -8
  17. package/src/components/FeaturesGallery.vue +34 -2
  18. package/src/components/Footer.cy.js +52 -2
  19. package/src/components/Footer.vue +17 -5
  20. package/src/components/HeroSection.cy.js +20 -0
  21. package/src/components/HeroSection.vue +140 -25
  22. package/src/components/ImageCarousel.vue +11 -2
  23. package/src/components/ImageElement.vue +16 -4
  24. package/src/components/ImageGallery.vue +29 -4
  25. package/src/components/LogoSection.vue +26 -3
  26. package/src/components/MobileNavDropdown.vue +13 -3
  27. package/src/components/NavBar.cy.js +15 -0
  28. package/src/components/NavBar.vue +149 -21
  29. package/src/components/NavDropdown.vue +12 -2
  30. package/src/components/OpenPositions.vue +292 -0
  31. package/src/components/SectionHeader.cy.js +54 -0
  32. package/src/components/SectionHeader.vue +115 -0
  33. package/src/components/TagList.vue +311 -0
  34. package/src/components/TeamPage.vue +347 -0
  35. package/src/components/TestimonialsSection.cy.js +76 -0
  36. package/src/components/TestimonialsSection.vue +368 -0
  37. package/src/components/TextSection.cy.js +115 -0
  38. package/src/components/TextSection.vue +396 -0
  39. package/src/helpers.js +182 -37
  40. package/src/helpers.test.js +181 -0
  41. package/src/index.js +16 -0
  42. package/src/pages/author/[author].md +5 -0
  43. package/src/pages/author/[author].paths.js +60 -0
  44. package/src/pages/blog.md +5 -0
  45. package/src/pages/tag/[tag].md +5 -0
  46. package/src/pages/tag/[tag].paths.js +49 -0
  47. package/src/pages/tag.md +5 -0
  48. package/src/pages/team.md +5 -0
  49. package/src/posts.data.js +134 -0
  50. package/src/style.css +389 -8
  51. package/src/tagUtils.js +148 -0
  52. package/src/team.data.js +1 -1
  53. package/src/vitepressConfig.mjs +101 -1
package/src/style.css CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  :root {
4
4
  --vp-nav-height: 58px;
5
+ --vp-nav-logo-height: 28px;
5
6
  }
6
7
  :root:has(.top-nav.at-top) {
7
8
  --vp-nav-height: 90px;
@@ -49,6 +50,11 @@ body {
49
50
  transition: padding-top 0.3s ease-in-out;
50
51
  }
51
52
 
53
+ main {
54
+ padding: 0 !important;
55
+ margin: 0 !important;
56
+ }
57
+
52
58
  *:has(> nav.top:not(.s, .m, .l)) {
53
59
  padding-block-start: 0;
54
60
  }
@@ -86,6 +92,91 @@ body {
86
92
  padding-right: 0;
87
93
  }
88
94
 
95
+ /* Ensure full-width layout sections touch and stack seamlessly without gaps */
96
+ .VPHome .full-width:not(.home-hero-section) {
97
+ margin-block-start: 0 !important;
98
+ margin-block-end: 0 !important;
99
+ }
100
+
101
+ .layout-blog .cta-section {
102
+ margin-block-end: 0 !important;
103
+ }
104
+
105
+ /* When a section follows a title (title plus section combination), eliminate start padding */
106
+ :is(
107
+ h1,
108
+ h2,
109
+ h3,
110
+ h4,
111
+ h5,
112
+ h6,
113
+ header,
114
+ .section-header-group,
115
+ [class*="section-title"]
116
+ )
117
+ + section,
118
+ :is(
119
+ h1,
120
+ h2,
121
+ h3,
122
+ h4,
123
+ h5,
124
+ h6,
125
+ header,
126
+ .section-header-group,
127
+ [class*="section-title"]
128
+ )
129
+ + :not(section, hr)
130
+ + section,
131
+ :is(
132
+ h1,
133
+ h2,
134
+ h3,
135
+ h4,
136
+ h5,
137
+ h6,
138
+ header,
139
+ .section-header-group,
140
+ [class*="section-title"]
141
+ )
142
+ + :not(section, hr)
143
+ + :not(section, hr)
144
+ + section,
145
+ :is(
146
+ h1,
147
+ h2,
148
+ h3,
149
+ h4,
150
+ h5,
151
+ h6,
152
+ header,
153
+ .section-header-group,
154
+ [class*="section-title"]
155
+ )
156
+ + :not(section, hr)
157
+ + :not(section, hr)
158
+ + :not(section, hr)
159
+ + section,
160
+ :is(
161
+ h1,
162
+ h2,
163
+ h3,
164
+ h4,
165
+ h5,
166
+ h6,
167
+ header,
168
+ .section-header-group,
169
+ [class*="section-title"]
170
+ )
171
+ + :not(section, hr)
172
+ + :not(section, hr)
173
+ + :not(section, hr)
174
+ + :not(section, hr)
175
+ + section {
176
+ padding-block-start: 0 !important;
177
+ padding-top: 0 !important;
178
+ }
179
+
89
180
  .VPHome .vp-doc a {
90
181
  font-weight: 400;
91
182
  color: inherit;
@@ -143,7 +234,8 @@ footer {
143
234
  margin-top: 0 !important;
144
235
  position: relative;
145
236
  z-index: var(--vp-z-index-footer);
146
- background: var(--surface-container-low);
237
+ background: var(--surface-container-lowest);
238
+ border-top: 1px solid var(--vp-c-divider, rgba(0, 0, 0, 0.08));
147
239
  }
148
240
 
149
241
  /**/
@@ -168,14 +260,24 @@ button.text:hover,
168
260
  }
169
261
  }
170
262
 
171
- .primary-gradient-bg {
263
+ .hero-gradient-bg {
264
+ /*Fallback*/
265
+ background: #000c14;
266
+ background: linear-gradient(
267
+ 180deg,
268
+ #000c14 0%,
269
+ var(--primary, #004170) 100%
270
+ ) !important;
271
+ }
272
+
273
+ .primary-gradient-bg,
274
+ .dark-section-gradient-bg {
172
275
  /*Fallback*/
173
- background: var(--primary);
174
- background: radial-gradient(
175
- 74.48% 130.95% at 50% -12.27%,
176
- hsl(from var(--primary) h s calc(l + 7)) 0%,
177
- var(--primary) 45%,
178
- hsl(from var(--primary) h s calc(l - 20)) 100%
276
+ background: #000c14;
277
+ background: linear-gradient(
278
+ 180deg,
279
+ var(--primary, #004170) 0%,
280
+ #000c14 100%
179
281
  ) !important;
180
282
  }
181
283
 
@@ -345,3 +447,282 @@ eox-feedback-button {
345
447
  z-index: calc(var(--vp-z-index-sidebar) + 2);
346
448
  }
347
449
  }
450
+
451
+ /* ==========================================================================
452
+ Simple Pages (layout: page) styling improvements
453
+ ========================================================================== */
454
+
455
+ .layout-page {
456
+ background-color: var(--surface) !important;
457
+ }
458
+
459
+ .layout-page .VPPage {
460
+ max-width: 1200px;
461
+ margin-inline: auto;
462
+ padding-block-start: 66px; /* Exactly 156px from top of viewport (90px navbar + 66px padding) */
463
+ padding-block-end: 96px;
464
+ padding-inline: 24px;
465
+ }
466
+
467
+ /* Remove double inner padding on custom layout elements to ensure absolute alignment */
468
+ .layout-page .VPPage .holder {
469
+ padding-inline: 0 !important;
470
+ }
471
+
472
+ /* Constrain standard reading elements to 1000px max-width, while letting full-width custom page components span 1200px */
473
+ .layout-page
474
+ .VPPage
475
+ > div
476
+ > div
477
+ > :is(p, ul, ol, blockquote, h1, h2, h3, h4, h5, h6, pre, table) {
478
+ max-width: 1000px;
479
+ margin-inline-start: 0;
480
+ margin-inline-end: auto;
481
+ width: 100%;
482
+ }
483
+
484
+ @media (max-width: 960px) {
485
+ .layout-page .VPPage {
486
+ padding-inline: 24px;
487
+ padding-block-start: 48px;
488
+ padding-block-end: 64px;
489
+ }
490
+ }
491
+
492
+ @media (max-width: 640px) {
493
+ .layout-page .VPPage {
494
+ padding-inline: 16px;
495
+ padding-block-start: 36px;
496
+ padding-block-end: 48px;
497
+ }
498
+ }
499
+
500
+ /* Typography on simple pages inside main page content */
501
+ .layout-page .VPPage h1 {
502
+ font-size: 2rem; /* Exactly 32px */
503
+ line-height: 1.25;
504
+ margin-block-start: 0;
505
+ margin-block-end: 1.5rem;
506
+ color: var(--on-surface);
507
+ font-weight: 700;
508
+ }
509
+
510
+ @media (max-width: 640px) {
511
+ .layout-page .VPPage h1 {
512
+ font-size: 1.75rem; /* 28px on mobile */
513
+ }
514
+ }
515
+
516
+ .layout-page .VPPage h2 {
517
+ font-size: 1.5rem; /* Exactly 24px */
518
+ line-height: 1.35;
519
+ margin-block-start: 3rem;
520
+ margin-block-end: 1.125rem;
521
+ color: var(--on-surface);
522
+ border-top: none;
523
+ }
524
+
525
+ @media (max-width: 640px) {
526
+ .layout-page .VPPage h2 {
527
+ font-size: 1.375rem; /* 22px on mobile */
528
+ }
529
+ }
530
+
531
+ .layout-page .VPPage h3 {
532
+ font-size: clamp(1.2rem, 3.5vw, 1.45rem);
533
+ line-height: 1.3;
534
+ margin-block-start: 2.25rem;
535
+ margin-block-end: 0.875rem;
536
+ color: var(--on-surface);
537
+ }
538
+
539
+ .layout-page .VPPage h4 {
540
+ font-size: 1.15rem;
541
+ line-height: 1.4;
542
+ margin-block-start: 1.75rem;
543
+ margin-block-end: 0.75rem;
544
+ color: var(--on-surface);
545
+ }
546
+
547
+ .layout-page .VPPage p {
548
+ font-size: 1rem; /* Exactly 16px */
549
+ line-height: 1.65;
550
+ margin-block-start: 0;
551
+ margin-block-end: 1.5rem;
552
+ color: var(--on-surface);
553
+ }
554
+
555
+ /* Lists */
556
+ .layout-page .VPPage ul,
557
+ .layout-page .VPPage ol {
558
+ margin-block-start: 0;
559
+ margin-block-end: 1.5rem;
560
+ padding-inline-start: 1.5rem;
561
+ }
562
+
563
+ .layout-page .VPPage li {
564
+ margin-block-end: 0.5rem;
565
+ line-height: 1.6;
566
+ font-size: 1rem; /* Exactly 16px */
567
+ color: var(--on-surface);
568
+ }
569
+
570
+ /* Inline links readability fix */
571
+ .layout-page .VPPage a:not(.button) {
572
+ color: var(--primary);
573
+ text-decoration: underline;
574
+ text-underline-offset: 3px;
575
+ text-decoration-thickness: 1px;
576
+ transition:
577
+ color 0.2s ease,
578
+ text-decoration-color 0.2s ease;
579
+ }
580
+
581
+ .layout-page .VPPage a:not(.button):hover {
582
+ opacity: 0.85;
583
+ text-decoration-thickness: 2px;
584
+ }
585
+
586
+ /* Scroll Anchors, Blockquotes & Code */
587
+ .layout-page .VPPage blockquote {
588
+ border-inline-start: 4px solid var(--primary);
589
+ padding-inline-start: 1.25rem;
590
+ margin-inline: 0;
591
+ margin-block: 2rem;
592
+ font-style: italic;
593
+ color: var(--on-surface-variant);
594
+ }
595
+
596
+ .layout-page .VPPage sup,
597
+ .layout-page .VPPage sub {
598
+ font-size: 0.8rem;
599
+ line-height: 1;
600
+ }
601
+
602
+ .layout-page .VPPage pre {
603
+ margin-block: 1.5rem;
604
+ padding: 1rem;
605
+ border-radius: 8px;
606
+ background-color: var(--surface-container-low);
607
+ overflow-x: auto;
608
+ }
609
+
610
+ /* Local Search styles overrides */
611
+ .VPLocalSearchBox .search-actions button {
612
+ height: 36px !important;
613
+ width: 36px !important;
614
+ min-width: 0 !important;
615
+ max-width: none !important;
616
+ border-radius: 50% !important;
617
+ padding: 0 !important;
618
+ display: inline-flex !important;
619
+ align-items: center !important;
620
+ justify-content: center !important;
621
+ border: none !important;
622
+ background: transparent !important;
623
+ box-shadow: none !important;
624
+ color: var(--vp-c-text-2) !important;
625
+ transition:
626
+ background-color 0.15s,
627
+ color 0.15s !important;
628
+ }
629
+
630
+ .VPLocalSearchBox .search-actions button:not([disabled]):hover {
631
+ background-color: var(--vp-c-default-soft) !important;
632
+ color: var(--vp-c-brand-1) !important;
633
+ }
634
+
635
+ .VPLocalSearchBox .search-actions button:disabled {
636
+ opacity: 0.37 !important;
637
+ background: transparent !important;
638
+ color: var(--vp-c-text-3) !important;
639
+ }
640
+
641
+ .VPLocalSearchBox .results {
642
+ list-style: none !important;
643
+ padding-left: 0 !important;
644
+ margin: 0 !important;
645
+ }
646
+
647
+ .VPLocalSearchBox .results li {
648
+ list-style: none !important;
649
+ margin: 0 !important;
650
+ }
651
+
652
+ /* Screen Reader Only Utility */
653
+ .sr-only {
654
+ position: absolute !important;
655
+ width: 1px !important;
656
+ height: 1px !important;
657
+ padding: 0 !important;
658
+ margin: -1px !important;
659
+ overflow: hidden !important;
660
+ clip: rect(0, 0, 0, 0) !important;
661
+ white-space: nowrap !important;
662
+ border-width: 0 !important;
663
+ }
664
+
665
+ /* ==========================================================================
666
+ Links in text hover underline
667
+ ========================================================================== */
668
+
669
+ a.link {
670
+ text-decoration: none;
671
+ }
672
+
673
+ :is(p, li, blockquote, figcaption, td, th, dd, dt, .card-body-text)
674
+ a:not(
675
+ .button,
676
+ [role="button"],
677
+ .chip,
678
+ .badge,
679
+ .circle,
680
+ .header-anchor,
681
+ .card-cover-link,
682
+ .grid-link,
683
+ .sidebar-tag,
684
+ .post-tag,
685
+ .clickable-chip,
686
+ .tag-link,
687
+ .social-link-btn,
688
+ .custom-cta-button,
689
+ :has(> img:only-child),
690
+ :has(> svg:only-child)
691
+ ):hover,
692
+ a.link:hover,
693
+ .vp-doc
694
+ a:not(
695
+ .button,
696
+ [role="button"],
697
+ .header-anchor,
698
+ .card-cover-link,
699
+ .grid-link,
700
+ .sidebar-tag,
701
+ .post-tag,
702
+ .clickable-chip,
703
+ .tag-link,
704
+ .social-link-btn,
705
+ .custom-cta-button,
706
+ :has(> img:only-child),
707
+ :has(> svg:only-child)
708
+ ):hover {
709
+ text-decoration: underline;
710
+ text-underline-offset: 2px;
711
+ }
712
+
713
+ /* Ensure navigation bars, dropdown menus, sidebars, and outline links do not get hover underline unless explicitly marked .link */
714
+ :is(
715
+ nav,
716
+ menu,
717
+ dialog,
718
+ .top-nav,
719
+ .social-links,
720
+ .VPSidebar,
721
+ .VPDocAsideOutline,
722
+ .VPLocalSearchBox
723
+ )
724
+ a:not(.link):hover,
725
+ menu a:hover,
726
+ menu li a:hover {
727
+ text-decoration: none !important;
728
+ }
@@ -0,0 +1,148 @@
1
+ export const tagThemes = [
2
+ // Managed cloud workspace (Category 0)
3
+ [
4
+ "eoxhub",
5
+ "eurodatacube",
6
+ "cloud-processing",
7
+ "mundi",
8
+ "jupyter",
9
+ "vre",
10
+ "processing",
11
+ ],
12
+ // Data management (Category 1)
13
+ [
14
+ "data-access",
15
+ "catalog",
16
+ "eo-data",
17
+ "open-search",
18
+ "ard",
19
+ "eoxserver",
20
+ "django",
21
+ "sxcat",
22
+ "data-cubes",
23
+ "opensearch",
24
+ ],
25
+ // Data visualization (Category 2)
26
+ [
27
+ "3d",
28
+ "animation",
29
+ "cesium",
30
+ "globe",
31
+ "ndvi",
32
+ "plotty",
33
+ "graphly",
34
+ "geotiff-js",
35
+ "client",
36
+ "visualization",
37
+ "cog",
38
+ "geotiff",
39
+ "openlayers",
40
+ "vue-js",
41
+ "eoxc",
42
+ "dashboard",
43
+ "javascript",
44
+ "leaflet",
45
+ "d3",
46
+ "webgl",
47
+ "video",
48
+ "data visualization",
49
+ ],
50
+ // Data products (Category 3)
51
+ [
52
+ "eoxcloudless",
53
+ "eoxmaps",
54
+ "s2cloudless",
55
+ "maps",
56
+ "sentinel-2",
57
+ "labels",
58
+ "cloudless-mosaic",
59
+ "voronoi-diagram",
60
+ "openstreetmap",
61
+ "mapchete",
62
+ ],
63
+ ];
64
+
65
+ export const tagColors = [
66
+ "rgb(255, 94, 94)", // Managed cloud workspace
67
+ "rgb(0, 177, 176)", // Data management
68
+ "rgb(219, 173, 66)", // Data visualization
69
+ "rgb(218, 71, 228)", // Data products
70
+ ];
71
+
72
+ export const tagCategories = [
73
+ "Managed cloud workspace",
74
+ "Data management",
75
+ "Data visualization",
76
+ "Data products",
77
+ ];
78
+
79
+ /**
80
+ * Get color for a given tag
81
+ * @param {string} tag
82
+ * @returns {string} color hex/rgb
83
+ */
84
+ export function getTagColor(tag) {
85
+ if (!tag) return "#e0e0e0";
86
+ const cleanTag = tag.toLowerCase().trim();
87
+ for (let i = 0; i < tagThemes.length; i++) {
88
+ if (tagThemes[i].includes(cleanTag)) {
89
+ return tagColors[i];
90
+ }
91
+ }
92
+ return "#e0e0e0";
93
+ }
94
+
95
+ /**
96
+ * Get style object for a given tag
97
+ * @param {string} tag
98
+ * @returns {object} style object
99
+ */
100
+ export function getTagStyle(tag) {
101
+ const color = getTagColor(tag);
102
+ if (color.startsWith("rgb(255, 94, 94)")) {
103
+ return {
104
+ backgroundColor: "rgba(255, 94, 94, 0.15)",
105
+ border: "1px solid rgb(255, 94, 94)",
106
+ color: "#d32f2f",
107
+ };
108
+ } else if (color.startsWith("rgb(0, 177, 176)")) {
109
+ return {
110
+ backgroundColor: "rgba(0, 177, 176, 0.15)",
111
+ border: "1px solid rgb(0, 177, 176)",
112
+ color: "#00796b",
113
+ };
114
+ } else if (color.startsWith("rgb(219, 173, 66)")) {
115
+ return {
116
+ backgroundColor: "rgba(219, 173, 66, 0.15)",
117
+ border: "1px solid rgb(219, 173, 66)",
118
+ color: "#f57c00",
119
+ };
120
+ } else if (color.startsWith("rgb(218, 71, 228)")) {
121
+ return {
122
+ backgroundColor: "rgba(218, 71, 228, 0.15)",
123
+ border: "1px solid rgb(218, 71, 228)",
124
+ color: "#c2185b",
125
+ };
126
+ }
127
+ return {
128
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
129
+ border: "1px solid rgba(0, 0, 0, 0.1)",
130
+ color: "#475569",
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Get category name for a given tag
136
+ * @param {string} tag
137
+ * @returns {string} category name
138
+ */
139
+ export function getTagCategory(tag) {
140
+ if (!tag) return "Other";
141
+ const cleanTag = tag.toLowerCase().trim();
142
+ for (let i = 0; i < tagThemes.length; i++) {
143
+ if (tagThemes[i].includes(cleanTag)) {
144
+ return tagCategories[i];
145
+ }
146
+ }
147
+ return "Other";
148
+ }
package/src/team.data.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createContentLoader } from "vitepress";
2
2
 
3
- export default createContentLoader("team/*.md", {
3
+ export default createContentLoader("_team/*.md", {
4
4
  transform(rawData) {
5
5
  return rawData
6
6
  .filter((page) => {
@@ -1,3 +1,90 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function copyThemePages() {
5
+ try {
6
+ const themeSrcDir = path.dirname(new URL(import.meta.url).pathname);
7
+ const pages = [
8
+ { src: "blog.md", dest: "blog.md" },
9
+ { src: "tag.md", dest: "tag.md" },
10
+ { src: "tag/[tag].md", dest: "tag/[tag].md" },
11
+ {
12
+ ...(() => {
13
+ // Let's resolve the actual pages dir inside the theme package
14
+ // This is robust on any npm installation structure
15
+ return {};
16
+ })(),
17
+ },
18
+ ];
19
+
20
+ // Better yet, just copy our built-in custom pages directly relative to this module
21
+ const filesToCopy = [
22
+ "blog.md",
23
+ "team.md",
24
+ "tag.md",
25
+ "tag/[tag].md",
26
+ "tag/[tag].paths.js",
27
+ "author/[author].md",
28
+ "author/[author].paths.js",
29
+ ];
30
+
31
+ const targetDir = process.cwd();
32
+ const pagesSrcDir = path.join(themeSrcDir, "pages");
33
+
34
+ for (const file of filesToCopy) {
35
+ const src = path.join(pagesSrcDir, file);
36
+ const dest = path.join(targetDir, file);
37
+ if (fs.existsSync(src)) {
38
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
39
+
40
+ let shouldCopy = !fs.existsSync(dest);
41
+ if (!shouldCopy) {
42
+ const srcContent = fs.readFileSync(src, "utf8");
43
+ const destContent = fs.readFileSync(dest, "utf8");
44
+ if (srcContent !== destContent) {
45
+ shouldCopy = true;
46
+ }
47
+ }
48
+ if (shouldCopy) {
49
+ fs.writeFileSync(dest, fs.readFileSync(src));
50
+ }
51
+ }
52
+ }
53
+ } catch (err) {
54
+ console.error("Failed to copy theme pages:", err);
55
+ }
56
+ }
57
+
58
+ function getBlogRewrites() {
59
+ const rewrites = {};
60
+ const postsDir = path.resolve(process.cwd(), "_posts");
61
+ if (!fs.existsSync(postsDir)) return rewrites;
62
+
63
+ const files = fs.readdirSync(postsDir).filter((f) => f.endsWith(".md"));
64
+ for (const file of files) {
65
+ const filePath = path.join(postsDir, file);
66
+ const content = fs.readFileSync(filePath, "utf8");
67
+ const dateMatch = content.match(/date:\s*["']?(\d{4})-(\d{2})-\d{2}["']?/);
68
+ if (dateMatch) {
69
+ const [_, year, month] = dateMatch;
70
+ const baseName = file.replace(/\.md$/, "");
71
+ const slug = baseName
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9]+/g, "-")
74
+ .replace(/^-+|-+$/g, "");
75
+ rewrites[`_posts/${file}`] = `${year}/${month}/${slug}.md`;
76
+ } else {
77
+ const baseName = file.replace(/\.md$/, "");
78
+ const slug = baseName
79
+ .toLowerCase()
80
+ .replace(/[^a-z0-9]+/g, "-")
81
+ .replace(/^-+|-+$/g, "");
82
+ rewrites[`_posts/${file}`] = `blog/${slug}.md`;
83
+ }
84
+ }
85
+ return rewrites;
86
+ }
87
+
1
88
  /**
2
89
  * Base configuration for VitePress
3
90
  * Documentation: see https://vitepress.dev/reference/site-config
@@ -5,9 +92,18 @@
5
92
  * @returns
6
93
  */
7
94
  export const generate = (brandConfig) => {
95
+ copyThemePages();
8
96
  const config = {
9
97
  appearance: false,
10
98
  cleanUrls: true,
99
+ markdown: {
100
+ headers: {
101
+ level: [2, 3, 4, 5, 6],
102
+ },
103
+ },
104
+ rewrites: {
105
+ ...getBlogRewrites(),
106
+ },
11
107
  description: brandConfig.meta?.description,
12
108
  head: [
13
109
  ["link", { rel: "icon", href: brandConfig.meta?.favicon }],
@@ -93,6 +189,7 @@ export const generate = (brandConfig) => {
93
189
  ],
94
190
  srcExclude: ["README.md"],
95
191
  themeConfig: {
192
+ outline: "deep",
96
193
  siteTitle: false,
97
194
  theme: {
98
195
  primaryColor: brandConfig.theme?.primary_color,
@@ -115,7 +212,7 @@ export const generate = (brandConfig) => {
115
212
  ></span>`,
116
213
  copyright:
117
214
  brandConfig.theme?.footer?.copyright ||
118
- 'Copyright © 2014-present <a href="https://eox.at" target="_blank">EOX IT Services GmbH</a>',
215
+ 'Copyright © 2008-present <a href="https://eox.at" target="_blank">EOX IT Services GmbH</a>',
119
216
  },
120
217
  },
121
218
  title: brandConfig.meta?.title,
@@ -123,6 +220,9 @@ export const generate = (brandConfig) => {
123
220
  ssr: {
124
221
  noExternal: ["@eox/pages-theme-eox"],
125
222
  },
223
+ optimizeDeps: {
224
+ exclude: ["@eox/pages-theme-eox"],
225
+ },
126
226
  },
127
227
  vue: {
128
228
  template: {