@rspress/plugin-preview 2.0.0-rc.0 → 2.0.0-rc.2
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/index.d.ts +7 -24
- package/dist/index.js +273 -273
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +1 -5
- package/package.json +9 -12
- package/static/global-components/{Device.css → FixedDevice.css} +31 -23
- package/static/global-components/FixedDevice.tsx +68 -0
- package/static/global-components/Preview.css +133 -0
- package/static/global-components/Preview.tsx +130 -0
- package/static/global-components/common/PreviewOperations.css +61 -0
- package/static/global-components/common/{mobile-operation.tsx → PreviewOperations.tsx} +20 -7
- package/static/{global-styles → iframe}/entry.css +3 -4
- package/static/iframe/entry.js +25 -0
- package/static/global-components/Container.tsx +0 -92
- package/static/global-components/DemoBlock.css +0 -23
- package/static/global-components/DemoBlock.tsx +0 -16
- package/static/global-components/Device.tsx +0 -76
- package/static/global-components/common/index.css +0 -103
- package/static/global-styles/iframe.css +0 -31
- package/static/global-styles/internal.css +0 -5
package/dist/utils.js
CHANGED
|
@@ -7,10 +7,6 @@ const normalizeId = (routePath)=>{
|
|
|
7
7
|
const result = routePath.replace(/\.(.*)?$/, '');
|
|
8
8
|
return toValidVarName(result);
|
|
9
9
|
};
|
|
10
|
-
const injectDemoBlockImport = (str, path)=>`
|
|
11
|
-
import DemoBlock from ${JSON.stringify(path)};
|
|
12
|
-
${str}
|
|
13
|
-
`;
|
|
14
10
|
const getLangFileExt = (lang)=>{
|
|
15
11
|
switch(lang){
|
|
16
12
|
case 'jsx':
|
|
@@ -22,4 +18,4 @@ const getLangFileExt = (lang)=>{
|
|
|
22
18
|
return lang;
|
|
23
19
|
}
|
|
24
20
|
};
|
|
25
|
-
export { generateId, getLangFileExt,
|
|
21
|
+
export { generateId, getLangFileExt, normalizeId, toValidVarName };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rspress/plugin-preview",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.2",
|
|
4
4
|
"description": "A plugin for rspress to preview the code block in markdown/mdx file.",
|
|
5
5
|
"bugs": "https://github.com/web-infra-dev/rspress/issues",
|
|
6
6
|
"repository": {
|
|
@@ -23,32 +23,29 @@
|
|
|
23
23
|
"static"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@rsbuild/core": "~1.6.
|
|
26
|
+
"@rsbuild/core": "~1.6.13",
|
|
27
27
|
"@rsbuild/plugin-babel": "~1.0.6",
|
|
28
28
|
"@rsbuild/plugin-react": "~1.4.2",
|
|
29
|
-
"@rsbuild/plugin-solid": "~1.0.6",
|
|
30
|
-
"lodash": "4.17.21",
|
|
31
29
|
"qrcode.react": "^4.2.0"
|
|
32
30
|
},
|
|
33
31
|
"devDependencies": {
|
|
34
|
-
"@rslib/core": "0.
|
|
35
|
-
"@types/lodash": "^4.17.20",
|
|
32
|
+
"@rslib/core": "0.18.3",
|
|
36
33
|
"@types/mdast": "^4.0.4",
|
|
37
34
|
"@types/node": "^22.8.1",
|
|
38
|
-
"@types/react": "^19.2.
|
|
39
|
-
"@types/react-dom": "^19.2.
|
|
35
|
+
"@types/react": "^19.2.7",
|
|
36
|
+
"@types/react-dom": "^19.2.3",
|
|
40
37
|
"mdast-util-mdx-jsx": "^3.2.0",
|
|
41
38
|
"mdast-util-mdxjs-esm": "^2.0.1",
|
|
42
|
-
"react": "^19.2.
|
|
43
|
-
"react-dom": "^19.2.
|
|
44
|
-
"react-router-dom": "^
|
|
39
|
+
"react": "^19.2.1",
|
|
40
|
+
"react-dom": "^19.2.1",
|
|
41
|
+
"react-router-dom": "^7.10.1",
|
|
45
42
|
"rsbuild-plugin-publint": "^0.3.3",
|
|
46
43
|
"typescript": "^5.8.2",
|
|
47
44
|
"unified": "^11.0.5",
|
|
48
45
|
"unist-util-visit": "^5.0.0"
|
|
49
46
|
},
|
|
50
47
|
"peerDependencies": {
|
|
51
|
-
"@rspress/core": "^2.0.0-rc.
|
|
48
|
+
"@rspress/core": "^2.0.0-rc.2",
|
|
52
49
|
"react": ">=18.0.0",
|
|
53
50
|
"react-router-dom": "^6.8.1"
|
|
54
51
|
},
|
|
@@ -1,48 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
/* FixedDevice Component Styles (BEM) */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--rp-preview-padding: 32px;
|
|
5
|
+
|
|
6
|
+
--rp-device-width: 360px;
|
|
7
|
+
--rp-device-height: 640px;
|
|
8
|
+
--rp-device-border-radius: 20px;
|
|
9
|
+
--rp-device-border: 1px solid #e5e6e8;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Block: rp-fixed-device */
|
|
13
|
+
.rp-fixed-device {
|
|
2
14
|
display: none;
|
|
3
15
|
position: fixed;
|
|
4
|
-
top: calc(
|
|
16
|
+
top: calc(
|
|
17
|
+
var(--rp-nav-height) + var(--rp-sidebar-menu-height)+
|
|
18
|
+
var(--rp-preview-padding) + var(--rp-banner-height, 0px)
|
|
19
|
+
);
|
|
20
|
+
right: max(
|
|
21
|
+
calc(
|
|
22
|
+
var(--rp-outline-margin-right) + var(--rp-outline-width) -
|
|
23
|
+
var(--rp-device-width)
|
|
24
|
+
),
|
|
25
|
+
0px
|
|
26
|
+
);
|
|
5
27
|
overflow: hidden;
|
|
28
|
+
z-index: 100;
|
|
6
29
|
}
|
|
7
30
|
|
|
8
|
-
|
|
31
|
+
/* Element: __iframe */
|
|
32
|
+
.rp-fixed-device__iframe {
|
|
9
33
|
height: var(--rp-device-height);
|
|
10
34
|
max-height: calc(
|
|
11
35
|
100vh - var(--rp-preview-padding) * 2 - var(--rp-nav-height)
|
|
12
36
|
);
|
|
13
|
-
width:
|
|
37
|
+
width: var(--rp-device-width);
|
|
14
38
|
pointer-events: auto;
|
|
15
39
|
border-radius: var(--rp-device-border-radius) var(--rp-device-border-radius) 0
|
|
16
40
|
0;
|
|
17
41
|
border: var(--rp-device-border);
|
|
18
42
|
}
|
|
19
43
|
|
|
20
|
-
|
|
44
|
+
/* Element: __operations */
|
|
45
|
+
.rp-fixed-device__operations {
|
|
21
46
|
border: var(--rp-device-border);
|
|
22
47
|
border-top: 0;
|
|
23
48
|
border-radius: 0 0 var(--rp-device-border-radius)
|
|
24
49
|
var(--rp-device-border-radius);
|
|
25
50
|
}
|
|
26
51
|
|
|
27
|
-
:root {
|
|
28
|
-
--rp-device-width: 360px;
|
|
29
|
-
--rp-device-height: 640px;
|
|
30
|
-
--rp-device-border-radius: 20px;
|
|
31
|
-
--rp-device-border: 1px solid #e5e6e8;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
52
|
@media (min-width: 960px) {
|
|
35
|
-
.fixed-device {
|
|
36
|
-
display: inline;
|
|
37
|
-
left: calc(1280px - var(--rp-device-width) - var(--rp-preview-padding));
|
|
38
|
-
right: auto;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@media (min-width: 1280px) {
|
|
43
|
-
.fixed-device {
|
|
53
|
+
.rp-fixed-device {
|
|
44
54
|
display: inline;
|
|
45
|
-
right: var(--rp-preview-padding);
|
|
46
|
-
left: auto;
|
|
47
55
|
}
|
|
48
56
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { NoSSR, useDark, usePage, withBase } from '@rspress/core/runtime';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
// @ts-expect-error
|
|
4
|
+
import { normalizeId } from '../../dist/utils';
|
|
5
|
+
import MobileOperation from './common/PreviewOperations';
|
|
6
|
+
import './FixedDevice.css';
|
|
7
|
+
|
|
8
|
+
export default () => {
|
|
9
|
+
const { page } = usePage();
|
|
10
|
+
const pageName = `${normalizeId(page.pagePath)}`;
|
|
11
|
+
const demoId = `_${pageName}`;
|
|
12
|
+
const url = `~demo/${demoId}`;
|
|
13
|
+
const { haveIframeFixedDemos } = page;
|
|
14
|
+
|
|
15
|
+
const getPageUrl = (url: string) => {
|
|
16
|
+
if (page?.devPort) {
|
|
17
|
+
return `http://localhost:${page.devPort}/${demoId}`;
|
|
18
|
+
}
|
|
19
|
+
return withBase(url);
|
|
20
|
+
};
|
|
21
|
+
const [iframeKey, setIframeKey] = useState(0);
|
|
22
|
+
const dark = useDark();
|
|
23
|
+
const refresh = useCallback(() => {
|
|
24
|
+
setIframeKey(Math.random());
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
31
|
+
{
|
|
32
|
+
type: 'theme-change',
|
|
33
|
+
dark,
|
|
34
|
+
},
|
|
35
|
+
'*',
|
|
36
|
+
);
|
|
37
|
+
}, [dark]);
|
|
38
|
+
|
|
39
|
+
return haveIframeFixedDemos ? (
|
|
40
|
+
<div className="rp-fixed-device">
|
|
41
|
+
{/* hide the outline */}
|
|
42
|
+
<style>{`@media (min-width: 1280px) {
|
|
43
|
+
.rp-doc-layout__outline {
|
|
44
|
+
display: none;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
@media (min-width: 960px) and (max-width: 1280px) {
|
|
48
|
+
.rp-doc-layout__doc-container {
|
|
49
|
+
padding-right: calc(var(--rp-device-width) + var(--rp-preview-padding));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`}</style>
|
|
53
|
+
<NoSSR>
|
|
54
|
+
<iframe
|
|
55
|
+
src={getPageUrl(url)}
|
|
56
|
+
className="rp-fixed-device__iframe"
|
|
57
|
+
key={iframeKey}
|
|
58
|
+
ref={iframeRef}
|
|
59
|
+
/>
|
|
60
|
+
</NoSSR>
|
|
61
|
+
<MobileOperation
|
|
62
|
+
url={getPageUrl(url)}
|
|
63
|
+
className="rp-fixed-device__operations"
|
|
64
|
+
refresh={refresh}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
) : null;
|
|
68
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* Preview Component Styles (BEM) */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--rp-preview-button-hover-bg: #e5e6eb;
|
|
5
|
+
--rp-preview-button-bg: #e5e6eb;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.dark {
|
|
9
|
+
--rp-preview-button-hover-bg: #c5c5c5;
|
|
10
|
+
--rp-preview-button-bg: #c5c5c5;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Block: rp-preview */
|
|
14
|
+
.rp-preview {
|
|
15
|
+
margin: 16px 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* ==========================================================================
|
|
19
|
+
Preview--internal (internal mode)
|
|
20
|
+
========================================================================== */
|
|
21
|
+
|
|
22
|
+
.rp-preview--internal .rp-codeblock {
|
|
23
|
+
margin: 0;
|
|
24
|
+
border-radius: 0 0 var(--rp-radius) var(--rp-radius);
|
|
25
|
+
border-top: 0px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.rp-preview--internal__card {
|
|
29
|
+
padding: 16px;
|
|
30
|
+
position: relative;
|
|
31
|
+
border: 1px solid var(--rp-container-details-border);
|
|
32
|
+
border-radius: var(--rp-radius);
|
|
33
|
+
display: flex;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.rp-preview--internal__card__content {
|
|
37
|
+
overflow: auto;
|
|
38
|
+
flex: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Code collapse/expand animation using CSS Grid */
|
|
42
|
+
.rp-preview--internal--show-code .rp-preview--internal__card {
|
|
43
|
+
border-radius: var(--rp-radius) var(--rp-radius) 0 0;
|
|
44
|
+
transition: border-radius 0.2s ease-out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.rp-preview--internal__code-wrapper {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-rows: 0fr;
|
|
50
|
+
transition:
|
|
51
|
+
grid-template-rows 0.2s ease-out,
|
|
52
|
+
opacity 0.2s ease-out;
|
|
53
|
+
opacity: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rp-preview--internal__code-wrapper--visible {
|
|
57
|
+
grid-template-rows: 1fr;
|
|
58
|
+
opacity: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.rp-preview--internal__code {
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.rp-preview-operations__button--expanded {
|
|
66
|
+
background: var(--rp-c-bg-mute);
|
|
67
|
+
box-shadow: inset 1px 1px 0 1px var(--rp-c-divider-light);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ==========================================================================
|
|
71
|
+
Preview--iframe-follow (iframe-follow mode)
|
|
72
|
+
========================================================================== */
|
|
73
|
+
|
|
74
|
+
.rp-preview--iframe-follow {
|
|
75
|
+
border: 1px solid var(--rp-container-details-border);
|
|
76
|
+
border-radius: var(--rp-radius);
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (min-width: 960px) {
|
|
82
|
+
.rp-preview--iframe-follow {
|
|
83
|
+
flex-direction: row;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.rp-preview--iframe-follow__code {
|
|
88
|
+
position: relative;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
flex: 1 1 auto;
|
|
91
|
+
min-height: 300px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (min-width: 960px) {
|
|
95
|
+
.rp-preview--iframe-follow__code {
|
|
96
|
+
max-height: 700px;
|
|
97
|
+
min-height: auto;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.rp-preview--iframe-follow__code .rp-codeblock {
|
|
102
|
+
border: none;
|
|
103
|
+
margin: 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.rp-preview--iframe-follow__code .rp-codeblock,
|
|
107
|
+
.rp-preview--iframe-follow__code .rp-codeblock__content,
|
|
108
|
+
.rp-preview--iframe-follow__code
|
|
109
|
+
.rp-codeblock__content
|
|
110
|
+
.rp-codeblock__content__scroll-container {
|
|
111
|
+
height: 100%;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.rp-preview--iframe-follow__device {
|
|
115
|
+
position: relative;
|
|
116
|
+
flex: 1 1 auto;
|
|
117
|
+
border-top: 1px solid var(--rp-container-details-border);
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (min-width: 960px) {
|
|
123
|
+
.rp-preview--iframe-follow__device {
|
|
124
|
+
border-top: none;
|
|
125
|
+
border-left: 1px solid var(--rp-container-details-border);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.rp-preview--iframe-follow__device iframe {
|
|
130
|
+
border-bottom: 1px solid var(--rp-container-details-border);
|
|
131
|
+
height: 100%;
|
|
132
|
+
flex: 1 1 auto;
|
|
133
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { NoSSR, useDark, usePageData, withBase } from '@rspress/core/runtime';
|
|
2
|
+
import {
|
|
3
|
+
type MouseEvent,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import MobileOperation from './common/PreviewOperations';
|
|
10
|
+
import IconCode from './icons/Code';
|
|
11
|
+
import './Preview.css';
|
|
12
|
+
|
|
13
|
+
type PreviewProps = {
|
|
14
|
+
children: React.ReactNode[];
|
|
15
|
+
previewMode: 'internal' | 'iframe-follow';
|
|
16
|
+
demoId: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface BasePreviewProps {
|
|
20
|
+
children: React.ReactNode[];
|
|
21
|
+
getPageUrl: () => string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const PreviewIframeFollow: React.FC<BasePreviewProps> = ({
|
|
25
|
+
children,
|
|
26
|
+
getPageUrl,
|
|
27
|
+
}) => {
|
|
28
|
+
const [iframeKey, setIframeKey] = useState(0);
|
|
29
|
+
const refresh = useCallback(() => {
|
|
30
|
+
setIframeKey(Math.random());
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
34
|
+
const dark = useDark();
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
37
|
+
{
|
|
38
|
+
type: 'theme-change',
|
|
39
|
+
dark,
|
|
40
|
+
},
|
|
41
|
+
'*',
|
|
42
|
+
);
|
|
43
|
+
}, [dark]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="rp-preview rp-not-doc rp-preview--iframe-follow">
|
|
47
|
+
<div className="rp-preview--iframe-follow__code">{children?.[0]}</div>
|
|
48
|
+
<div className="rp-preview--iframe-follow__device">
|
|
49
|
+
<iframe
|
|
50
|
+
className="rp-preview--iframe-follow__device__iframe"
|
|
51
|
+
src={getPageUrl()}
|
|
52
|
+
key={iframeKey}
|
|
53
|
+
ref={iframeRef}
|
|
54
|
+
/>
|
|
55
|
+
<MobileOperation url={getPageUrl()} refresh={refresh} />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const PreviewInternal: React.FC<{ children: React.ReactNode[] }> = ({
|
|
62
|
+
children,
|
|
63
|
+
}) => {
|
|
64
|
+
const [showCode, setShowCode] = useState(false);
|
|
65
|
+
|
|
66
|
+
const toggleCode = useCallback(
|
|
67
|
+
(ev: MouseEvent<HTMLButtonElement>) => {
|
|
68
|
+
if (!showCode) {
|
|
69
|
+
ev.currentTarget.blur();
|
|
70
|
+
}
|
|
71
|
+
setShowCode(!showCode);
|
|
72
|
+
},
|
|
73
|
+
[showCode],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
className={`rp-preview rp-not-doc rp-preview--internal ${showCode ? 'rp-preview--internal--show-code' : ''}`}
|
|
79
|
+
>
|
|
80
|
+
<div className="rp-preview--internal__card">
|
|
81
|
+
<div className="rp-preview--internal__card__content">
|
|
82
|
+
{children?.[1]}
|
|
83
|
+
</div>
|
|
84
|
+
<div className="rp-preview-operations rp-preview-operations--web">
|
|
85
|
+
<button
|
|
86
|
+
onClick={toggleCode}
|
|
87
|
+
aria-label="Collapse Code"
|
|
88
|
+
className={`rp-preview-operations__button ${showCode ? 'rp-preview-operations__button--expanded' : ''}`}
|
|
89
|
+
>
|
|
90
|
+
<IconCode />
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div
|
|
95
|
+
className={`rp-preview--internal__code-wrapper ${
|
|
96
|
+
showCode ? 'rp-preview--internal__code-wrapper--visible' : ''
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
<div className="rp-preview--internal__code">{children?.[0]}</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const Preview: React.FC<PreviewProps> = props => {
|
|
106
|
+
const { children, previewMode, demoId } = props;
|
|
107
|
+
const { page } = usePageData();
|
|
108
|
+
|
|
109
|
+
const getPageUrl = useCallback(() => {
|
|
110
|
+
const url = `/~demo/${demoId}`;
|
|
111
|
+
if (page?.devPort) {
|
|
112
|
+
return `http://localhost:${page.devPort}/${demoId}`;
|
|
113
|
+
}
|
|
114
|
+
return withBase(url);
|
|
115
|
+
}, [page?.devPort, demoId]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<NoSSR>
|
|
119
|
+
{previewMode === 'iframe-follow' ? (
|
|
120
|
+
<PreviewIframeFollow getPageUrl={getPageUrl}>
|
|
121
|
+
{children}
|
|
122
|
+
</PreviewIframeFollow>
|
|
123
|
+
) : (
|
|
124
|
+
<PreviewInternal>{children}</PreviewInternal>
|
|
125
|
+
)}
|
|
126
|
+
</NoSSR>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default Preview;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* PreviewOperations Component Styles (BEM) */
|
|
2
|
+
|
|
3
|
+
/* Block: rp-preview-operations */
|
|
4
|
+
.rp-preview-operations {
|
|
5
|
+
display: flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Modifier: --mobile */
|
|
9
|
+
.rp-preview-operations--mobile {
|
|
10
|
+
justify-content: flex-end;
|
|
11
|
+
width: 100%;
|
|
12
|
+
padding: 6px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Modifier: --web */
|
|
16
|
+
.rp-preview-operations--web {
|
|
17
|
+
justify-content: center;
|
|
18
|
+
align-items: center;
|
|
19
|
+
flex: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Element: __button */
|
|
23
|
+
.rp-preview-operations__button {
|
|
24
|
+
width: 28px;
|
|
25
|
+
height: 28px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
padding: 0;
|
|
28
|
+
text-align: center;
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
border: 1px solid transparent;
|
|
31
|
+
background-color: var(--rp-c-bg-soft);
|
|
32
|
+
margin-left: 14px;
|
|
33
|
+
|
|
34
|
+
transition: background-color 0.2s ease-out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.rp-preview-operations__button:hover {
|
|
38
|
+
background-color: var(--rp-preview-button-hover-bg);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.rp-preview-operations__button svg {
|
|
42
|
+
display: inline-block;
|
|
43
|
+
vertical-align: -2px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Element: __qrcode */
|
|
47
|
+
.rp-preview-operations__qrcode {
|
|
48
|
+
position: relative;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Element: __qrcode-popup */
|
|
52
|
+
.rp-preview-operations__qrcode-popup {
|
|
53
|
+
background-color: #fff;
|
|
54
|
+
width: 120px;
|
|
55
|
+
height: 120px;
|
|
56
|
+
position: absolute;
|
|
57
|
+
top: -132px;
|
|
58
|
+
right: -46px;
|
|
59
|
+
padding: 12px;
|
|
60
|
+
z-index: 999;
|
|
61
|
+
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import IconLaunch from '../icons/Launch';
|
|
11
11
|
import IconQrcode from '../icons/Qrcode';
|
|
12
12
|
import IconRefresh from '../icons/Refresh';
|
|
13
|
-
import './
|
|
13
|
+
import './PreviewOperations.css';
|
|
14
14
|
|
|
15
15
|
const locales = {
|
|
16
16
|
zh: {
|
|
@@ -79,21 +79,34 @@ export default (props: {
|
|
|
79
79
|
}, [showQRCode]);
|
|
80
80
|
|
|
81
81
|
return (
|
|
82
|
-
<div
|
|
83
|
-
|
|
82
|
+
<div
|
|
83
|
+
className={`rp-preview-operations rp-preview-operations--mobile ${className}`}
|
|
84
|
+
>
|
|
85
|
+
<button
|
|
86
|
+
className="rp-preview-operations__button"
|
|
87
|
+
onClick={refresh}
|
|
88
|
+
aria-label={t.refresh}
|
|
89
|
+
>
|
|
84
90
|
<IconRefresh />
|
|
85
91
|
</button>
|
|
86
|
-
<div className="
|
|
92
|
+
<div className="rp-preview-operations__qrcode" ref={triggerRef}>
|
|
87
93
|
{showQRCode && (
|
|
88
|
-
<div className="
|
|
94
|
+
<div className="rp-preview-operations__qrcode-popup">
|
|
89
95
|
<QRCodeSVG value={url} size={96} />
|
|
90
96
|
</div>
|
|
91
97
|
)}
|
|
92
|
-
<button
|
|
98
|
+
<button
|
|
99
|
+
className="rp-preview-operations__button"
|
|
100
|
+
onClick={toggleQRCode}
|
|
101
|
+
>
|
|
93
102
|
<IconQrcode />
|
|
94
103
|
</button>
|
|
95
104
|
</div>
|
|
96
|
-
<button
|
|
105
|
+
<button
|
|
106
|
+
className="rp-preview-operations__button"
|
|
107
|
+
onClick={openNewPage}
|
|
108
|
+
aria-label={t.open}
|
|
109
|
+
>
|
|
97
110
|
<IconLaunch />
|
|
98
111
|
</button>
|
|
99
112
|
</div>
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
--rp-iframe-nav-bg: #ffffff;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
/* .dark {
|
|
6
|
+
.dark {
|
|
8
7
|
--rp-iframe-bg: #242424;
|
|
9
8
|
--rp-iframe-nav-bg: #191d24;
|
|
10
|
-
}
|
|
9
|
+
}
|
|
11
10
|
|
|
12
11
|
body {
|
|
13
12
|
background-color: var(--rp-iframe-bg) !important;
|
|
@@ -26,7 +25,7 @@ body {
|
|
|
26
25
|
}
|
|
27
26
|
/* #endregion copied from preflight.css */
|
|
28
27
|
|
|
29
|
-
.preview-nav {
|
|
28
|
+
.rp-preview-nav {
|
|
30
29
|
position: relative;
|
|
31
30
|
display: flex;
|
|
32
31
|
align-items: center;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const storageKey = 'rspress-plugin-preview-theme-appearance';
|
|
2
|
+
|
|
3
|
+
function setDocumentTheme(isDark) {
|
|
4
|
+
if (isDark) {
|
|
5
|
+
document.documentElement.classList.add('dark');
|
|
6
|
+
document.documentElement.style.colorScheme = 'dark';
|
|
7
|
+
} else {
|
|
8
|
+
document.documentElement.classList.remove('dark');
|
|
9
|
+
document.documentElement.style.colorScheme = 'light';
|
|
10
|
+
}
|
|
11
|
+
localStorage.setItem(
|
|
12
|
+
'rspress-plugin-preview-theme-appearance',
|
|
13
|
+
isDark ? 'dark' : 'light',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const saved = localStorage.getItem(storageKey) || 'light';
|
|
18
|
+
setDocumentTheme(saved === 'dark');
|
|
19
|
+
|
|
20
|
+
window.addEventListener('message', event => {
|
|
21
|
+
if (event.data.type === 'theme-change') {
|
|
22
|
+
const isDark = event.data.dark;
|
|
23
|
+
setDocumentTheme(isDark);
|
|
24
|
+
}
|
|
25
|
+
});
|