slidev-workspace 0.2.2 → 0.2.3

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 CHANGED
@@ -60,6 +60,7 @@ function resolveSlidesDirs(config, workingDir) {
60
60
  //#region src/scripts/getSlideFrontmatter.ts
61
61
  function getSlideFrontmatterByPath(slideDir, slideName) {
62
62
  try {
63
+ const config = loadConfig();
63
64
  const fullPath = join$1(slideDir, slideName, "slides.md");
64
65
  if (!existsSync$1(fullPath)) {
65
66
  console.warn(`File not found: ${fullPath}`);
@@ -81,7 +82,8 @@ function getSlideFrontmatterByPath(slideDir, slideName) {
81
82
  fullPath,
82
83
  sourceDir: slideDir,
83
84
  frontmatter,
84
- content: content.replace(frontmatterMatch[0], "")
85
+ content: content.replace(frontmatterMatch[0], ""),
86
+ baseUrl: config.baseUrl
85
87
  };
86
88
  } catch (error) {
87
89
  console.error(`Error parsing frontmatter for ${slideName} in ${slideDir}:`, error);
package/dist/index.d.ts CHANGED
@@ -27,6 +27,8 @@ interface SlideInfo {
27
27
  sourceDir: string;
28
28
  frontmatter: SlideFrontmatter;
29
29
  content: string;
30
+ /** The base URL of the slide, which is defined in slidev-workspace.yml */
31
+ baseUrl: string;
30
32
  }
31
33
  interface SlideData {
32
34
  title: string;
package/dist/index.js CHANGED
@@ -3,6 +3,33 @@ import { computed, ref } from "vue";
3
3
  //#region src/preview/constants/env.ts
4
4
  const IS_DEVELOPMENT = import.meta.env.MODE === "development";
5
5
 
6
+ //#endregion
7
+ //#region src/preview/lib/pathJoin.ts
8
+ /**
9
+ * Join multiple path segments into a single path
10
+ * - Removes duplicate slashes
11
+ * - Handles leading and trailing slashes properly
12
+ * - Works with both relative and absolute paths
13
+ *
14
+ * @example
15
+ * pathJoin('/base/', '/path/', 'file.jpg') // '/base/path/file.jpg'
16
+ * pathJoin('base', 'path', 'file.jpg') // 'base/path/file.jpg'
17
+ * pathJoin('/base/', 'path') // '/base/path'
18
+ */
19
+ function pathJoin(...segments) {
20
+ if (segments.length === 0) return "";
21
+ const filtered = segments.filter((segment) => segment !== "");
22
+ if (filtered.length === 0) return "";
23
+ const isAbsolute = filtered[0].startsWith("/");
24
+ const processed = filtered.map((segment) => {
25
+ return segment.replace(/^\/+|\/+$/g, "");
26
+ });
27
+ const nonEmpty = processed.filter((segment) => segment !== "");
28
+ if (nonEmpty.length === 0) return isAbsolute ? "/" : "";
29
+ const joined = nonEmpty.join("/");
30
+ return isAbsolute ? `/${joined}` : joined;
31
+ }
32
+
6
33
  //#endregion
7
34
  //#region src/preview/composables/useSlides.ts
8
35
  /**
@@ -18,19 +45,33 @@ function isUrl(str) {
18
45
  }
19
46
  }
20
47
  /**
21
- * Resolve background image path
22
- * If the background is not a URL, construct the full path using slide.path as base
48
+ * Resolve background image path.
49
+ * If the background is not a URL, construct the full path using slide.path as the base.
50
+ *
51
+ * Example (development mode):
52
+ * {
53
+ * background: "/bg1.jpg",
54
+ * slidePath: "/slides-presentation-1",
55
+ * baseUrl: "/slidev-workspace-starter/",
56
+ * domain: "http://localhost:3001" // domain of the current server
57
+ * }
58
+ * returns: "http://localhost:3001/slides-presentation-1/bg1.jpg"
59
+ *
60
+ * Example (production mode):
61
+ * {
62
+ * background: "/bg1.jpg",
63
+ * slidePath: "/slides-presentation-1",
64
+ * baseUrl: "/slidev-workspace-starter/",
65
+ * domain: "https://my-slides.com" // domain of the current server
66
+ * }
67
+ * returns: "https://my-slides.com/slidev-workspace-starter/slides-presentation-1/bg1.jpg"
23
68
  */
24
69
  function resolveBackgroundPath(params) {
25
- const { background, slidePath, devServerUrl } = params;
70
+ const { background, slidePath, domain, baseUrl } = params;
26
71
  if (!background) return "";
27
72
  if (isUrl(background)) return background;
28
73
  try {
29
- const domain = IS_DEVELOPMENT ? devServerUrl : window.location.origin;
30
- const basePath = slidePath.endsWith("/") ? slidePath : slidePath + "/";
31
- const relativeBg = background.startsWith("/") ? background.slice(1) : background;
32
- const resolvedUrl = new URL(basePath + relativeBg, domain);
33
- return resolvedUrl.href;
74
+ return IS_DEVELOPMENT ? new URL(pathJoin(slidePath, background), domain).href : new URL(pathJoin(baseUrl, slidePath, background), domain).href;
34
75
  } catch (error) {
35
76
  console.error("Failed to resolve background path:", error);
36
77
  return background;
@@ -60,7 +101,8 @@ function useSlides() {
60
101
  const imageUrl = background ? resolveBackgroundPath({
61
102
  background: slide.frontmatter.background,
62
103
  slidePath: slide.path,
63
- devServerUrl
104
+ baseUrl: slide.baseUrl,
105
+ domain: IS_DEVELOPMENT ? devServerUrl : window.location.origin
64
106
  }) : "https://cover.sli.dev";
65
107
  return {
66
108
  title: slide.frontmatter.title || slide.path,
@@ -51,6 +51,7 @@ function resolveSlidesDirs(config, workingDir) {
51
51
  //#region src/scripts/getSlideFrontmatter.ts
52
52
  function getSlideFrontmatterByPath(slideDir, slideName) {
53
53
  try {
54
+ const config = loadConfig();
54
55
  const fullPath = join(slideDir, slideName, "slides.md");
55
56
  if (!existsSync(fullPath)) {
56
57
  console.warn(`File not found: ${fullPath}`);
@@ -72,7 +73,8 @@ function getSlideFrontmatterByPath(slideDir, slideName) {
72
73
  fullPath,
73
74
  sourceDir: slideDir,
74
75
  frontmatter,
75
- content: content.replace(frontmatterMatch[0], "")
76
+ content: content.replace(frontmatterMatch[0], ""),
77
+ baseUrl: config.baseUrl
76
78
  };
77
79
  } catch (error) {
78
80
  console.error(`Error parsing frontmatter for ${slideName} in ${slideDir}:`, error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidev-workspace",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A workspace tool for managing multiple Slidev presentations with API-based content management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,6 +17,7 @@ const mockSlidesData: SlideInfo[] = [
17
17
  class: "text-center",
18
18
  },
19
19
  content: "# Slide content",
20
+ baseUrl: "/slidev-workspace-starter/",
20
21
  },
21
22
  {
22
23
  id: "slide-2",
@@ -32,6 +33,7 @@ const mockSlidesData: SlideInfo[] = [
32
33
  },
33
34
  },
34
35
  content: "# Another slide",
36
+ baseUrl: "/slidev-workspace-starter/",
35
37
  },
36
38
  {
37
39
  id: "slide-3",
@@ -40,6 +42,7 @@ const mockSlidesData: SlideInfo[] = [
40
42
  sourceDir: "/path/to/slides",
41
43
  frontmatter: {},
42
44
  content: "# Minimal slide",
45
+ baseUrl: "/slidev-workspace-starter/",
43
46
  },
44
47
  ];
45
48
 
@@ -201,7 +201,7 @@ describe("useSlides (Production Mode)", () => {
201
201
  const firstSlide = result.slides.value[0];
202
202
 
203
203
  expect(firstSlide.image).toBe(
204
- "https://my-slides.com/slides-presentation-1/bg1.jpg",
204
+ "https://my-slides.com/slidev-workspace-starter/slides-presentation-1/bg1.jpg",
205
205
  );
206
206
  });
207
207
 
@@ -1,6 +1,7 @@
1
1
  import { computed, ref } from "vue";
2
2
  import type { SlideData, SlideInfo } from "../../types/slide";
3
3
  import { IS_DEVELOPMENT } from "../constants/env";
4
+ import { pathJoin } from "../lib/pathJoin";
4
5
 
5
6
  /**
6
7
  * Check if a string is a valid URL
@@ -17,33 +18,47 @@ function isUrl(str: string | undefined): boolean {
17
18
  }
18
19
 
19
20
  /**
20
- * Resolve background image path
21
- * If the background is not a URL, construct the full path using slide.path as base
21
+ * Resolve background image path.
22
+ * If the background is not a URL, construct the full path using slide.path as the base.
23
+ *
24
+ * Example (development mode):
25
+ * {
26
+ * background: "/bg1.jpg",
27
+ * slidePath: "/slides-presentation-1",
28
+ * baseUrl: "/slidev-workspace-starter/",
29
+ * domain: "http://localhost:3001" // domain of the current server
30
+ * }
31
+ * returns: "http://localhost:3001/slides-presentation-1/bg1.jpg"
32
+ *
33
+ * Example (production mode):
34
+ * {
35
+ * background: "/bg1.jpg",
36
+ * slidePath: "/slides-presentation-1",
37
+ * baseUrl: "/slidev-workspace-starter/",
38
+ * domain: "https://my-slides.com" // domain of the current server
39
+ * }
40
+ * returns: "https://my-slides.com/slidev-workspace-starter/slides-presentation-1/bg1.jpg"
22
41
  */
23
42
  function resolveBackgroundPath(params: {
24
43
  background: string | undefined;
25
44
  slidePath: string;
26
- devServerUrl: string;
45
+ baseUrl: string;
46
+ domain: string;
27
47
  }): string {
28
- const { background, slidePath, devServerUrl } = params;
48
+ const { background, slidePath, domain, baseUrl } = params;
29
49
 
30
- if (!background) return "";
50
+ if (!background) {
51
+ return "";
52
+ }
31
53
 
32
54
  if (isUrl(background)) {
33
55
  return background;
34
56
  }
35
57
 
36
58
  try {
37
- const domain = IS_DEVELOPMENT ? devServerUrl : window.location.origin;
38
- // Ensure slidePath ends with / for proper path resolution
39
- const basePath = slidePath.endsWith("/") ? slidePath : slidePath + "/";
40
- // Remove leading / from background to treat it as relative
41
- const relativeBg = background.startsWith("/")
42
- ? background.slice(1)
43
- : background;
44
- const resolvedUrl = new URL(basePath + relativeBg, domain);
45
-
46
- return resolvedUrl.href;
59
+ return IS_DEVELOPMENT
60
+ ? new URL(pathJoin(slidePath, background), domain).href
61
+ : new URL(pathJoin(baseUrl, slidePath, background), domain).href;
47
62
  } catch (error) {
48
63
  console.error("Failed to resolve background path:", error);
49
64
  return background;
@@ -86,7 +101,8 @@ export function useSlides() {
86
101
  ? resolveBackgroundPath({
87
102
  background: slide.frontmatter.background,
88
103
  slidePath: slide.path,
89
- devServerUrl,
104
+ baseUrl: slide.baseUrl,
105
+ domain: IS_DEVELOPMENT ? devServerUrl : window.location.origin,
90
106
  })
91
107
  : "https://cover.sli.dev";
92
108
 
@@ -0,0 +1,120 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { pathJoin } from "./pathJoin";
3
+
4
+ describe("pathJoin", () => {
5
+ describe("basic functionality", () => {
6
+ it("should join simple path segments", () => {
7
+ expect(pathJoin("a", "b", "c")).toBe("a/b/c");
8
+ });
9
+
10
+ it("should handle single segment", () => {
11
+ expect(pathJoin("path")).toBe("path");
12
+ });
13
+
14
+ it("should return empty string for no arguments", () => {
15
+ expect(pathJoin()).toBe("");
16
+ });
17
+
18
+ it("should return empty string for all empty segments", () => {
19
+ expect(pathJoin("", "", "")).toBe("");
20
+ });
21
+ });
22
+
23
+ describe("leading slashes", () => {
24
+ it("should preserve leading slash for absolute paths", () => {
25
+ expect(pathJoin("/a", "b", "c")).toBe("/a/b/c");
26
+ });
27
+
28
+ it("should not add leading slash for relative paths", () => {
29
+ expect(pathJoin("a", "b", "c")).toBe("a/b/c");
30
+ });
31
+
32
+ it("should preserve leading slash with trailing slashes", () => {
33
+ expect(pathJoin("/a/", "b/", "c/")).toBe("/a/b/c");
34
+ });
35
+ });
36
+
37
+ describe("trailing slashes", () => {
38
+ it("should remove trailing slashes from segments", () => {
39
+ expect(pathJoin("a/", "b/", "c/")).toBe("a/b/c");
40
+ });
41
+
42
+ it("should handle segments with both leading and trailing slashes", () => {
43
+ expect(pathJoin("/a/", "/b/", "/c/")).toBe("/a/b/c");
44
+ });
45
+ });
46
+
47
+ describe("duplicate slashes", () => {
48
+ it("should remove duplicate slashes between segments", () => {
49
+ expect(pathJoin("a//", "//b", "c")).toBe("a/b/c");
50
+ });
51
+
52
+ it("should handle multiple consecutive slashes", () => {
53
+ expect(pathJoin("a///", "///b///", "///c")).toBe("a/b/c");
54
+ });
55
+
56
+ it("should handle absolute paths with multiple slashes", () => {
57
+ expect(pathJoin("///a", "b", "c")).toBe("/a/b/c");
58
+ });
59
+ });
60
+
61
+ describe("empty segments", () => {
62
+ it("should filter out empty segments", () => {
63
+ expect(pathJoin("a", "", "b", "", "c")).toBe("a/b/c");
64
+ });
65
+
66
+ it("should handle empty segments with slashes", () => {
67
+ expect(pathJoin("/a", "", "/b", "", "c")).toBe("/a/b/c");
68
+ });
69
+ });
70
+
71
+ describe("real-world use cases", () => {
72
+ it("should join base URL and relative path", () => {
73
+ expect(pathJoin("/base/", "/path/", "file.jpg")).toBe(
74
+ "/base/path/file.jpg",
75
+ );
76
+ });
77
+
78
+ it("should join without leading slash", () => {
79
+ expect(pathJoin("base", "path", "file.jpg")).toBe("base/path/file.jpg");
80
+ });
81
+
82
+ it("should join base and path segments", () => {
83
+ expect(pathJoin("/base/", "path")).toBe("/base/path");
84
+ });
85
+
86
+ it("should handle slidev-workspace paths", () => {
87
+ expect(
88
+ pathJoin(
89
+ "/slidev-workspace-starter",
90
+ "/slides-presentation-1/",
91
+ "bg1.jpg",
92
+ ),
93
+ ).toBe("/slidev-workspace-starter/slides-presentation-1/bg1.jpg");
94
+ });
95
+
96
+ it("should handle relative paths for slide backgrounds", () => {
97
+ expect(pathJoin("slides-presentation-1/", "bg1.jpg")).toBe(
98
+ "slides-presentation-1/bg1.jpg",
99
+ );
100
+ });
101
+ });
102
+
103
+ describe("edge cases", () => {
104
+ it("should handle only slashes", () => {
105
+ expect(pathJoin("/", "/", "/")).toBe("/");
106
+ });
107
+
108
+ it("should handle single slash", () => {
109
+ expect(pathJoin("/")).toBe("/");
110
+ });
111
+
112
+ it("should handle path with dots", () => {
113
+ expect(pathJoin("path", "to", "../file.jpg")).toBe("path/to/../file.jpg");
114
+ });
115
+
116
+ it("should handle file extensions", () => {
117
+ expect(pathJoin("/path", "file.name.ext")).toBe("/path/file.name.ext");
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Join multiple path segments into a single path
3
+ * - Removes duplicate slashes
4
+ * - Handles leading and trailing slashes properly
5
+ * - Works with both relative and absolute paths
6
+ *
7
+ * @example
8
+ * pathJoin('/base/', '/path/', 'file.jpg') // '/base/path/file.jpg'
9
+ * pathJoin('base', 'path', 'file.jpg') // 'base/path/file.jpg'
10
+ * pathJoin('/base/', 'path') // '/base/path'
11
+ */
12
+ export function pathJoin(...segments: string[]): string {
13
+ if (segments.length === 0) return "";
14
+
15
+ // Filter out empty segments
16
+ const filtered = segments.filter((segment) => segment !== "");
17
+ if (filtered.length === 0) return "";
18
+
19
+ // Check if path should start with /
20
+ const isAbsolute = filtered[0].startsWith("/");
21
+
22
+ // Process each segment: remove leading and trailing slashes
23
+ const processed = filtered.map((segment) => {
24
+ return segment.replace(/^\/+|\/+$/g, "");
25
+ });
26
+
27
+ // Filter out empty strings after processing
28
+ const nonEmpty = processed.filter((segment) => segment !== "");
29
+
30
+ if (nonEmpty.length === 0) {
31
+ return isAbsolute ? "/" : "";
32
+ }
33
+
34
+ // Join with single slash
35
+ const joined = nonEmpty.join("/");
36
+
37
+ // Add leading slash if original path was absolute
38
+ return isAbsolute ? `/${joined}` : joined;
39
+ }