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 +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +51 -9
- package/dist/plugin-slides.js +3 -1
- package/package.json +1 -1
- package/src/preview/composables/__mocks__/slidev-content.ts +3 -0
- package/src/preview/composables/useSlides.test.ts +1 -1
- package/src/preview/composables/useSlides.ts +32 -16
- package/src/preview/lib/pathJoin.test.ts +120 -0
- package/src/preview/lib/pathJoin.ts +39 -0
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
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,
|
|
70
|
+
const { background, slidePath, domain, baseUrl } = params;
|
|
26
71
|
if (!background) return "";
|
|
27
72
|
if (isUrl(background)) return background;
|
|
28
73
|
try {
|
|
29
|
-
|
|
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
|
-
|
|
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,
|
package/dist/plugin-slides.js
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
domain: string;
|
|
27
47
|
}): string {
|
|
28
|
-
const { background, slidePath,
|
|
48
|
+
const { background, slidePath, domain, baseUrl } = params;
|
|
29
49
|
|
|
30
|
-
if (!background)
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
+
}
|