slidev-workspace 0.1.3 → 0.1.5
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/dist/cli.js +222 -37
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -10
- package/package.json +4 -5
- package/src/preview/App.vue +7 -0
- package/src/preview/assets/main.css +123 -0
- package/src/preview/components/SlideCard.vue +62 -0
- package/src/preview/components/SlideDeck.vue +88 -0
- package/src/preview/components/SlideDetail.vue +160 -0
- package/src/preview/components/ui/button/Button.vue +25 -0
- package/src/preview/components/ui/button/index.ts +32 -0
- package/src/preview/components/ui/card/Card.vue +22 -0
- package/src/preview/components/ui/card/CardAction.vue +17 -0
- package/src/preview/components/ui/card/CardContent.vue +17 -0
- package/src/preview/components/ui/card/CardDescription.vue +17 -0
- package/src/preview/components/ui/card/CardFooter.vue +17 -0
- package/src/preview/components/ui/card/CardHeader.vue +14 -0
- package/src/preview/components/ui/card/CardTitle.vue +17 -0
- package/src/preview/components/ui/card/index.ts +7 -0
- package/src/preview/components/ui/input/Input.vue +33 -0
- package/src/preview/components/ui/input/index.ts +1 -0
- package/src/preview/composables/useSlides.ts +62 -0
- package/src/preview/dist/assets/index-Cbywrv7-.css +2 -0
- package/src/preview/dist/assets/index-LGnZwLzS.js +24 -0
- package/src/preview/dist/index.html +13 -0
- package/src/preview/index.html +12 -0
- package/src/preview/lib/utils.ts +6 -0
- package/src/preview/main.ts +6 -0
- package/src/preview/utils/getSlides.ts +65 -0
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,209 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { build, createServer } from "vite";
|
|
5
|
+
import vue from "@vitejs/plugin-vue";
|
|
6
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, watch } from "fs";
|
|
8
|
+
import { basename, join as join$1, resolve as resolve$1 } from "path";
|
|
9
|
+
import { parse } from "yaml";
|
|
10
|
+
import "vue";
|
|
5
11
|
|
|
12
|
+
//#region src/scripts/config.ts
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
slidesDir: ["./slides"],
|
|
15
|
+
outputDir: "./dist",
|
|
16
|
+
baseUrl: "/",
|
|
17
|
+
exclude: ["node_modules", ".git"]
|
|
18
|
+
};
|
|
19
|
+
function loadConfig(workingDir) {
|
|
20
|
+
const configPaths = [
|
|
21
|
+
"slidev-workspace.config.js",
|
|
22
|
+
"slidev-workspace.config.ts",
|
|
23
|
+
"slidev-workspace.yml",
|
|
24
|
+
"slidev-workspace.yaml"
|
|
25
|
+
];
|
|
26
|
+
const projectRoot = workingDir || process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
|
|
27
|
+
for (const configPath of configPaths) {
|
|
28
|
+
const fullPath = join$1(projectRoot, configPath);
|
|
29
|
+
if (existsSync(fullPath)) try {
|
|
30
|
+
if (configPath.endsWith(".yml") || configPath.endsWith(".yaml")) {
|
|
31
|
+
const content = readFileSync(fullPath, "utf8");
|
|
32
|
+
const config = parse(content);
|
|
33
|
+
return {
|
|
34
|
+
...DEFAULT_CONFIG,
|
|
35
|
+
...config
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.warn(`Failed to load config from ${fullPath}:`, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return DEFAULT_CONFIG;
|
|
43
|
+
}
|
|
44
|
+
function resolveSlidesDirs(config, workingDir) {
|
|
45
|
+
const projectRoot = workingDir || process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
|
|
46
|
+
const resolvedDirs = (config.slidesDir || []).map((dir) => {
|
|
47
|
+
if (resolve$1(dir) === dir) return dir;
|
|
48
|
+
else return resolve$1(projectRoot, dir);
|
|
49
|
+
}).filter((dir) => {
|
|
50
|
+
const exists = existsSync(dir);
|
|
51
|
+
return exists;
|
|
52
|
+
});
|
|
53
|
+
return resolvedDirs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/scripts/getSlideFrontmatter.ts
|
|
58
|
+
function getSlideFrontmatterByPath(slideDir, slideName) {
|
|
59
|
+
try {
|
|
60
|
+
const fullPath = join$1(slideDir, slideName, "slides.md");
|
|
61
|
+
if (!existsSync(fullPath)) {
|
|
62
|
+
console.warn(`File not found: ${fullPath}`);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const content = readFileSync(fullPath, "utf8");
|
|
66
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
67
|
+
if (!frontmatterMatch) {
|
|
68
|
+
console.warn(`Frontmatter not found in ${fullPath}`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const frontmatterYaml = frontmatterMatch[1];
|
|
72
|
+
const frontmatter = parse(frontmatterYaml);
|
|
73
|
+
const sourceBasename = basename(slideDir);
|
|
74
|
+
const slideId = `${sourceBasename}/${slideName}`;
|
|
75
|
+
return {
|
|
76
|
+
id: slideId,
|
|
77
|
+
path: slideName,
|
|
78
|
+
fullPath,
|
|
79
|
+
sourceDir: slideDir,
|
|
80
|
+
frontmatter,
|
|
81
|
+
content: content.replace(frontmatterMatch[0], "")
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(`Error parsing frontmatter for ${slideName} in ${slideDir}:`, error);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function getAllSlidesFrontmatter() {
|
|
89
|
+
const config = loadConfig();
|
|
90
|
+
const slidesDirs = resolveSlidesDirs(config);
|
|
91
|
+
const slides = [];
|
|
92
|
+
for (const slidesDir of slidesDirs) {
|
|
93
|
+
if (!existsSync(slidesDir)) {
|
|
94
|
+
console.warn(`Slides directory not found: ${slidesDir}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const slideDirs = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => !(config.exclude || []).includes(dirent.name)).map((dirent) => dirent.name);
|
|
99
|
+
for (const slideDir of slideDirs) {
|
|
100
|
+
const slideInfo = getSlideFrontmatterByPath(slidesDir, slideDir);
|
|
101
|
+
if (slideInfo) slides.push(slideInfo);
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(`Error reading slides directory ${slidesDir}:`, error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return slides;
|
|
108
|
+
}
|
|
109
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
110
|
+
const slides = getAllSlidesFrontmatter();
|
|
111
|
+
console.log(JSON.stringify(slides, null, 2));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/scripts/vite-plugin-slides.ts
|
|
116
|
+
function slidesPlugin() {
|
|
117
|
+
return {
|
|
118
|
+
name: "vite-plugin-slides",
|
|
119
|
+
configureServer(server) {
|
|
120
|
+
const watchers = [];
|
|
121
|
+
const config = loadConfig();
|
|
122
|
+
const slidesDirs = resolveSlidesDirs(config);
|
|
123
|
+
slidesDirs.forEach((slidesDir) => {
|
|
124
|
+
const watcher = watch(slidesDir, { recursive: true }, (eventType, filename) => {
|
|
125
|
+
if (filename && filename.endsWith("slides.md")) try {
|
|
126
|
+
const slides = getAllSlidesFrontmatter();
|
|
127
|
+
server.ws.send({
|
|
128
|
+
type: "custom",
|
|
129
|
+
event: "slides-updated",
|
|
130
|
+
data: slides
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("❌ Error reading slides frontmatter:", error);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
watchers.push(watcher);
|
|
137
|
+
});
|
|
138
|
+
server.httpServer?.once("close", () => {
|
|
139
|
+
watchers.forEach((watcher) => watcher.close());
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
resolveId(id) {
|
|
143
|
+
if (id === "slidev:content") return id;
|
|
144
|
+
},
|
|
145
|
+
load(id) {
|
|
146
|
+
if (id === "slidev:content") try {
|
|
147
|
+
const slides = getAllSlidesFrontmatter();
|
|
148
|
+
return `export const slidesData = ${JSON.stringify(slides, null, 2)};
|
|
149
|
+
export default slidesData;`;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("Error loading slides data:", error);
|
|
152
|
+
return `export const slidesData = [];
|
|
153
|
+
export default slidesData;`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
6
160
|
//#region src/cli.ts
|
|
7
161
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
162
|
const __dirname = dirname(__filename);
|
|
9
163
|
const args = process.argv.slice(2);
|
|
10
164
|
const command = args[0];
|
|
11
165
|
const packageRoot = join(__dirname, "..");
|
|
166
|
+
function createViteConfig() {
|
|
167
|
+
const workspaceCwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
|
|
168
|
+
const config = loadConfig(workspaceCwd);
|
|
169
|
+
return {
|
|
170
|
+
root: resolve(packageRoot, "src/preview"),
|
|
171
|
+
plugins: [
|
|
172
|
+
vue(),
|
|
173
|
+
tailwindcss(),
|
|
174
|
+
slidesPlugin()
|
|
175
|
+
],
|
|
176
|
+
resolve: { alias: { "@": resolve(packageRoot, "src/preview") } },
|
|
177
|
+
build: { outDir: resolve(workspaceCwd, config.outputDir) },
|
|
178
|
+
server: {
|
|
179
|
+
port: 3e3,
|
|
180
|
+
open: true
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function runViteBuild() {
|
|
185
|
+
try {
|
|
186
|
+
console.log("📦 Building Slidev Workspace for production...");
|
|
187
|
+
const config = createViteConfig();
|
|
188
|
+
await build(config);
|
|
189
|
+
console.log("✅ Build completed successfully!");
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error("❌ Build failed:", error);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function runVitePreview() {
|
|
196
|
+
try {
|
|
197
|
+
console.log("🚀 Starting Slidev Workspace development server...");
|
|
198
|
+
const config = createViteConfig();
|
|
199
|
+
const server = await createServer(config);
|
|
200
|
+
await server.listen();
|
|
201
|
+
server.printUrls();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error("❌ Development server failed:", error);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
12
207
|
function showHelp() {
|
|
13
208
|
console.log(`
|
|
14
209
|
Slidev Workspace - A tool for managing multiple Slidev presentations
|
|
@@ -28,42 +223,32 @@ Examples:
|
|
|
28
223
|
For more information, visit: https://github.com/author/slidev-workspace
|
|
29
224
|
`);
|
|
30
225
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
console.log("📦 Building Slidev Workspace for production...");
|
|
53
|
-
process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
|
|
54
|
-
runCommand("pnpm", ["run", "slidev-workspace:build"]);
|
|
55
|
-
break;
|
|
56
|
-
case "help":
|
|
57
|
-
case "--help":
|
|
58
|
-
case "-h":
|
|
59
|
-
showHelp();
|
|
60
|
-
break;
|
|
61
|
-
default: if (!command) showHelp();
|
|
62
|
-
else {
|
|
63
|
-
console.error(`Unknown command: ${command}`);
|
|
64
|
-
console.error("Run \"slidev-workspace help\" for available commands.");
|
|
65
|
-
process.exit(1);
|
|
226
|
+
async function main() {
|
|
227
|
+
switch (command) {
|
|
228
|
+
case "preview":
|
|
229
|
+
process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
|
|
230
|
+
await runVitePreview();
|
|
231
|
+
break;
|
|
232
|
+
case "build":
|
|
233
|
+
process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
|
|
234
|
+
await runViteBuild();
|
|
235
|
+
break;
|
|
236
|
+
case "help":
|
|
237
|
+
case "--help":
|
|
238
|
+
case "-h":
|
|
239
|
+
showHelp();
|
|
240
|
+
break;
|
|
241
|
+
default: if (!command) showHelp();
|
|
242
|
+
else {
|
|
243
|
+
console.error(`Unknown command: ${command}`);
|
|
244
|
+
console.error("Run \"slidev-workspace help\" for available commands.");
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
66
247
|
}
|
|
67
248
|
}
|
|
249
|
+
main().catch((error) => {
|
|
250
|
+
console.error("❌ An error occurred:", error);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
});
|
|
68
253
|
|
|
69
254
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ interface SlideData$1 {
|
|
|
83
83
|
declare function useSlides(): {
|
|
84
84
|
slides: vue0.ComputedRef<SlideData$1[]>;
|
|
85
85
|
slidesCount: vue0.ComputedRef<number>;
|
|
86
|
+
loadSlidesData: () => Promise<void>;
|
|
86
87
|
};
|
|
87
88
|
//#endregion
|
|
88
89
|
export { type SlideData, type SlideFrontmatter, type SlideInfo, type SlidevWorkspaceConfig, getAllSlidesFrontmatter, getSlideFrontmatter, getSlideFrontmatterByPath, loadConfig, resolveSlidesDirs, slidesPlugin, useSlides };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, watch } from "fs";
|
|
2
2
|
import { basename, join, resolve } from "path";
|
|
3
3
|
import { parse } from "yaml";
|
|
4
|
-
import { computed } from "vue";
|
|
4
|
+
import { computed, ref } from "vue";
|
|
5
5
|
|
|
6
6
|
//#region src/scripts/config.ts
|
|
7
7
|
const DEFAULT_CONFIG = {
|
|
8
8
|
slidesDir: ["./slides"],
|
|
9
|
-
outputDir: "./
|
|
9
|
+
outputDir: "./dist",
|
|
10
10
|
baseUrl: "/",
|
|
11
11
|
exclude: ["node_modules", ".git"]
|
|
12
12
|
};
|
|
@@ -154,17 +154,23 @@ export default slidesData;`;
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
//#endregion
|
|
158
|
-
//#region slidev:content
|
|
159
|
-
const slidesData = [];
|
|
160
|
-
var slidev_content_default = slidesData;
|
|
161
|
-
|
|
162
157
|
//#endregion
|
|
163
158
|
//#region src/preview/composables/useSlides.ts
|
|
164
159
|
function useSlides() {
|
|
160
|
+
const slidesData = ref([]);
|
|
161
|
+
const loadSlidesData = async () => {
|
|
162
|
+
try {
|
|
163
|
+
const module = await import("slidev:content");
|
|
164
|
+
slidesData.value = module.default || module.slidesData || [];
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.warn("Failed to load slides data:", error);
|
|
167
|
+
slidesData.value = [];
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
loadSlidesData();
|
|
165
171
|
const slides = computed(() => {
|
|
166
|
-
if (!
|
|
167
|
-
return
|
|
172
|
+
if (!slidesData.value || slidesData.value.length === 0) return [];
|
|
173
|
+
return slidesData.value.map((slide) => ({
|
|
168
174
|
title: slide.frontmatter.title || slide.path,
|
|
169
175
|
url: slide.path,
|
|
170
176
|
description: slide.frontmatter.info || slide.frontmatter.seoMeta?.ogDescription || "No description available",
|
|
@@ -179,7 +185,8 @@ function useSlides() {
|
|
|
179
185
|
const slidesCount = computed(() => slides.value.length);
|
|
180
186
|
return {
|
|
181
187
|
slides,
|
|
182
|
-
slidesCount
|
|
188
|
+
slidesCount,
|
|
189
|
+
loadSlidesData
|
|
183
190
|
};
|
|
184
191
|
}
|
|
185
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidev-workspace",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A workspace tool for managing multiple Slidev presentations with API-based content management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"scripts",
|
|
22
|
+
"src/preview",
|
|
22
23
|
"vite.config.ts"
|
|
23
24
|
],
|
|
24
25
|
"main": "./dist/index.js",
|
|
@@ -42,14 +43,14 @@
|
|
|
42
43
|
"tailwind-merge": "^3.3.1",
|
|
43
44
|
"tw-animate-css": "^1.3.5",
|
|
44
45
|
"vite": "npm:rolldown-vite@latest",
|
|
45
|
-
"vue": "^3.5.17"
|
|
46
|
+
"vue": "^3.5.17",
|
|
47
|
+
"@tailwindcss/vite": "^4.1.11"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"tsdown": "^0.11.9",
|
|
49
51
|
"typescript": "^5.8.3",
|
|
50
52
|
"vitest": "^3.1.3",
|
|
51
53
|
"vue-tsc": "^3.0.3",
|
|
52
|
-
"@tailwindcss/vite": "^4.1.11",
|
|
53
54
|
"@tsconfig/node22": "^22.0.2",
|
|
54
55
|
"@types/node": "^22.15.32",
|
|
55
56
|
"@vue/eslint-config-prettier": "^10.2.0",
|
|
@@ -71,8 +72,6 @@
|
|
|
71
72
|
"build": "tsdown",
|
|
72
73
|
"dev": "pnpm run dev",
|
|
73
74
|
"dev:watch": "tsdown --watch",
|
|
74
|
-
"slidev-workspace:preview": "vite",
|
|
75
|
-
"slidev-workspace:build": "vite build --config vite.config.ts",
|
|
76
75
|
"test": "vitest",
|
|
77
76
|
"typecheck": "vue-tsc --noEmit",
|
|
78
77
|
"lint": "eslint . --fix",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
@theme inline {
|
|
7
|
+
--color-background: var(--background);
|
|
8
|
+
--color-foreground: var(--foreground);
|
|
9
|
+
--color-card: var(--card);
|
|
10
|
+
--color-card-foreground: var(--card-foreground);
|
|
11
|
+
--color-popover: var(--popover);
|
|
12
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
13
|
+
--color-primary: var(--primary);
|
|
14
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
15
|
+
--color-secondary: var(--secondary);
|
|
16
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
17
|
+
--color-muted: var(--muted);
|
|
18
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
19
|
+
--color-accent: var(--accent);
|
|
20
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
21
|
+
--color-destructive: var(--destructive);
|
|
22
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
23
|
+
--color-border: var(--border);
|
|
24
|
+
--color-input: var(--input);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--color-chart-1: var(--chart-1);
|
|
27
|
+
--color-chart-2: var(--chart-2);
|
|
28
|
+
--color-chart-3: var(--chart-3);
|
|
29
|
+
--color-chart-4: var(--chart-4);
|
|
30
|
+
--color-chart-5: var(--chart-5);
|
|
31
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
32
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
33
|
+
--radius-lg: var(--radius);
|
|
34
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
35
|
+
--color-sidebar: var(--sidebar);
|
|
36
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
37
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
38
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
39
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
40
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
41
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
42
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:root {
|
|
46
|
+
--background: oklch(1 0 0);
|
|
47
|
+
--foreground: oklch(0.145 0 0);
|
|
48
|
+
--card: oklch(1 0 0);
|
|
49
|
+
--card-foreground: oklch(0.145 0 0);
|
|
50
|
+
--popover: oklch(1 0 0);
|
|
51
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
52
|
+
--primary: oklch(0.205 0 0);
|
|
53
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
54
|
+
--secondary: oklch(0.97 0 0);
|
|
55
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
56
|
+
--muted: oklch(0.97 0 0);
|
|
57
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
58
|
+
--accent: oklch(0.97 0 0);
|
|
59
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
60
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
61
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
62
|
+
--border: oklch(0.922 0 0);
|
|
63
|
+
--input: oklch(0.922 0 0);
|
|
64
|
+
--ring: oklch(0.708 0 0);
|
|
65
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
66
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
67
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
68
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
69
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
70
|
+
--radius: 0.625rem;
|
|
71
|
+
--sidebar: oklch(0.985 0 0);
|
|
72
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
73
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
74
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
75
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
76
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
77
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
78
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.dark {
|
|
82
|
+
--background: oklch(0.145 0 0);
|
|
83
|
+
--foreground: oklch(0.985 0 0);
|
|
84
|
+
--card: oklch(0.145 0 0);
|
|
85
|
+
--card-foreground: oklch(0.985 0 0);
|
|
86
|
+
--popover: oklch(0.145 0 0);
|
|
87
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
88
|
+
--primary: oklch(0.985 0 0);
|
|
89
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
90
|
+
--secondary: oklch(0.269 0 0);
|
|
91
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
92
|
+
--muted: oklch(0.269 0 0);
|
|
93
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
94
|
+
--accent: oklch(0.269 0 0);
|
|
95
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
96
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
97
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
98
|
+
--border: oklch(0.269 0 0);
|
|
99
|
+
--input: oklch(0.269 0 0);
|
|
100
|
+
--ring: oklch(0.439 0 0);
|
|
101
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
102
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
103
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
104
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
105
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
106
|
+
--sidebar: oklch(0.205 0 0);
|
|
107
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
108
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
109
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
110
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
111
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
112
|
+
--sidebar-border: oklch(0.269 0 0);
|
|
113
|
+
--sidebar-ring: oklch(0.439 0 0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@layer base {
|
|
117
|
+
* {
|
|
118
|
+
@apply border-border outline-ring/50;
|
|
119
|
+
}
|
|
120
|
+
body {
|
|
121
|
+
@apply bg-background text-foreground;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Card class="group hover:shadow-lg transition-all duration-200 cursor-pointer" @click="$emit('click')">
|
|
3
|
+
<div class="relative overflow-hidden rounded-t-lg">
|
|
4
|
+
<img
|
|
5
|
+
:src="image || '/placeholder.svg'"
|
|
6
|
+
:alt="title"
|
|
7
|
+
class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-200"
|
|
8
|
+
/>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<CardHeader class="pb-2">
|
|
12
|
+
<CardTitle class="text-lg line-clamp-2 group-hover:text-primary transition-colors">
|
|
13
|
+
{{ title }}
|
|
14
|
+
</CardTitle>
|
|
15
|
+
</CardHeader>
|
|
16
|
+
|
|
17
|
+
<CardContent class="space-y-3">
|
|
18
|
+
<CardDescription class="line-clamp-3 text-sm h-[40px]">{{ description }}</CardDescription>
|
|
19
|
+
|
|
20
|
+
<div class="flex flex-wrap gap-1 mb-2">
|
|
21
|
+
<span v-if="theme" class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800">
|
|
22
|
+
{{ theme }}
|
|
23
|
+
</span>
|
|
24
|
+
<span v-if="sourceDir" class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
|
|
25
|
+
{{ sourceDir.split('/').pop() }}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="flex items-center justify-between text-xs text-muted-foreground pt-2 border-t">
|
|
30
|
+
<div class="flex items-center gap-1">
|
|
31
|
+
<User class="h-3 w-3" />
|
|
32
|
+
<span>{{ author }}</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="flex items-center gap-1">
|
|
35
|
+
<Calendar class="h-3 w-3" />
|
|
36
|
+
<span>{{ date }}</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</CardContent>
|
|
40
|
+
</Card>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
import { Calendar, User } from 'lucide-vue-next'
|
|
45
|
+
|
|
46
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
47
|
+
|
|
48
|
+
defineProps<{
|
|
49
|
+
title: string
|
|
50
|
+
image?: string
|
|
51
|
+
description?: string
|
|
52
|
+
url: string
|
|
53
|
+
author: string
|
|
54
|
+
date: string
|
|
55
|
+
theme?: string
|
|
56
|
+
sourceDir?: string
|
|
57
|
+
}>()
|
|
58
|
+
|
|
59
|
+
defineEmits<{
|
|
60
|
+
click: []
|
|
61
|
+
}>()
|
|
62
|
+
</script>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="min-h-screen bg-gradient-to-br from-gray-100 via-white to-gray-200 py-16 px-4"
|
|
4
|
+
>
|
|
5
|
+
<div class="max-w-5xl mx-auto">
|
|
6
|
+
<div class="mb-8">
|
|
7
|
+
<h1 class="text-3xl font-bold mb-2">Slide Deck</h1>
|
|
8
|
+
<p class="text-muted-foreground">
|
|
9
|
+
Browse all available slide decks and use the search function to
|
|
10
|
+
quickly find what you need.
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="space-y-4 mb-8">
|
|
15
|
+
<div class="relative">
|
|
16
|
+
<Input
|
|
17
|
+
placeholder="Search by title, description, or author..."
|
|
18
|
+
v-model="searchTerm"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="mb-6">
|
|
24
|
+
<p class="text-sm text-muted-foreground">
|
|
25
|
+
Found {{ filteredSlides.length }} of {{ slidesCount }} slides
|
|
26
|
+
<template v-if="searchTerm">
|
|
27
|
+
<span>
|
|
28
|
+
containing "
|
|
29
|
+
<span class="font-medium">{{ searchTerm }}</span>
|
|
30
|
+
"
|
|
31
|
+
</span>
|
|
32
|
+
</template>
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
|
|
37
|
+
<SlideCard
|
|
38
|
+
v-for="slide in filteredSlides"
|
|
39
|
+
:key="slide.title"
|
|
40
|
+
:title="slide.title"
|
|
41
|
+
:image="slide.image"
|
|
42
|
+
:description="slide.description"
|
|
43
|
+
:url="slide.url"
|
|
44
|
+
:author="slide.author"
|
|
45
|
+
:date="slide.date"
|
|
46
|
+
@click="() => openSlide(slide)"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Slide Detail Modal -->
|
|
52
|
+
<SlideDetail
|
|
53
|
+
v-if="selectedSlide"
|
|
54
|
+
:slide="selectedSlide"
|
|
55
|
+
@close="closeSlide"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import { ref, computed } from "vue";
|
|
62
|
+
import { useSlides } from "../composables/useSlides";
|
|
63
|
+
import { Input } from "../components/ui/input";
|
|
64
|
+
import SlideCard from "./SlideCard.vue";
|
|
65
|
+
import SlideDetail from "./SlideDetail.vue";
|
|
66
|
+
|
|
67
|
+
const searchTerm = ref("");
|
|
68
|
+
const { slides, slidesCount } = useSlides();
|
|
69
|
+
|
|
70
|
+
const filteredSlides = computed(() => {
|
|
71
|
+
if (!searchTerm.value) return slides.value;
|
|
72
|
+
return slides.value.filter((slide) =>
|
|
73
|
+
slide.title.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
|
|
74
|
+
slide.description.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
|
|
75
|
+
slide.author.toLowerCase().includes(searchTerm.value.toLowerCase())
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const selectedSlide = ref(null);
|
|
80
|
+
|
|
81
|
+
const openSlide = (slide: any) => {
|
|
82
|
+
selectedSlide.value = slide;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const closeSlide = () => {
|
|
86
|
+
selectedSlide.value = null;
|
|
87
|
+
};
|
|
88
|
+
</script>
|