@rspress/plugin-preview 1.44.0 → 1.45.0
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.js +3 -1
- package/package.json +4 -4
- package/static/global-components/ContainerFixedPerComp.tsx +73 -18
- package/static/global-components/DeviceFixedPerComp.tsx +106 -0
- package/static/global-components/common/mobile-operation.tsx +36 -8
- package/static/global-components/icons/Back.tsx +31 -0
- package/static/global-components/useIframeUrlPerComp.ts +39 -0
package/dist/index.js
CHANGED
@@ -608,7 +608,9 @@ var __webpack_exports__ = {};
|
|
608
608
|
(0, external_node_path_namespaceObject.join)(staticPath, 'global-components', 'ContainerFixedPerComp.tsx')
|
609
609
|
]
|
610
610
|
},
|
611
|
-
globalUIComponents: 'fixed-with-per-comp' === position
|
611
|
+
globalUIComponents: 'fixed-with-per-comp' === position ? [
|
612
|
+
(0, external_node_path_namespaceObject.join)(staticPath, 'global-components', 'DeviceFixedPerComp.tsx')
|
613
|
+
] : 'fixed' === position ? [
|
612
614
|
(0, external_node_path_namespaceObject.join)(staticPath, 'global-components', 'Device.tsx')
|
613
615
|
] : [],
|
614
616
|
globalStyles: (0, external_node_path_namespaceObject.join)(staticPath, 'global-styles', `${previewMode}.css`)
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@rspress/plugin-preview",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.45.0",
|
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": {
|
@@ -25,8 +25,8 @@
|
|
25
25
|
"@rsbuild/plugin-solid": "~1.0.5",
|
26
26
|
"lodash": "4.17.21",
|
27
27
|
"qrcode.react": "^3.2.0",
|
28
|
-
"@rspress/shared": "1.
|
29
|
-
"@rspress/theme-default": "1.
|
28
|
+
"@rspress/shared": "1.45.0",
|
29
|
+
"@rspress/theme-default": "1.45.0"
|
30
30
|
},
|
31
31
|
"devDependencies": {
|
32
32
|
"@rslib/core": "~0.6.9",
|
@@ -45,7 +45,7 @@
|
|
45
45
|
"unist-util-visit": "^4.1.2"
|
46
46
|
},
|
47
47
|
"peerDependencies": {
|
48
|
-
"@rspress/core": "^1.
|
48
|
+
"@rspress/core": "^1.45.0",
|
49
49
|
"react": ">=17.0.0",
|
50
50
|
"react-router-dom": "^6.8.1"
|
51
51
|
},
|
@@ -1,4 +1,7 @@
|
|
1
|
-
import { NoSSR, usePageData, withBase } from '@rspress/core/runtime';
|
1
|
+
import { NoSSR, useLang, usePageData, withBase } from '@rspress/core/runtime';
|
2
|
+
import { type MouseEvent, useCallback, useState } from 'react';
|
3
|
+
import IconCode from './icons/Code';
|
4
|
+
import { publishIframeUrl } from './useIframeUrlPerComp';
|
2
5
|
|
3
6
|
type ContainerProps = {
|
4
7
|
children: React.ReactNode[];
|
@@ -6,7 +9,7 @@ type ContainerProps = {
|
|
6
9
|
demoId: string;
|
7
10
|
};
|
8
11
|
|
9
|
-
const
|
12
|
+
const MobileContainerFixedPerComp: React.FC<ContainerProps> = props => {
|
10
13
|
const { children, demoId } = props;
|
11
14
|
const { page } = usePageData();
|
12
15
|
const url = `/~demo/${demoId}`;
|
@@ -22,24 +25,76 @@ const ContainerFixedPerComp: React.FC<ContainerProps> = props => {
|
|
22
25
|
return '';
|
23
26
|
};
|
24
27
|
|
28
|
+
const setIframeUrl = () => {
|
29
|
+
const url = getPageUrl();
|
30
|
+
const fixedIframe = document.querySelector('.rspress-fixed-iframe');
|
31
|
+
fixedIframe?.setAttribute('src', url);
|
32
|
+
publishIframeUrl(url);
|
33
|
+
};
|
34
|
+
|
35
|
+
return (
|
36
|
+
<div className="rspress-preview-code" onClick={setIframeUrl}>
|
37
|
+
{children}
|
38
|
+
</div>
|
39
|
+
);
|
40
|
+
};
|
41
|
+
|
42
|
+
const ContainerFixedPerComp = (props: ContainerProps) => {
|
43
|
+
const { children, isMobile } = props;
|
44
|
+
const [showCode, setShowCode] = useState(false);
|
45
|
+
const lang = useLang();
|
46
|
+
|
47
|
+
const toggleCode = useCallback(
|
48
|
+
(ev: MouseEvent<HTMLButtonElement>) => {
|
49
|
+
if (!showCode) {
|
50
|
+
ev.currentTarget.blur();
|
51
|
+
}
|
52
|
+
setShowCode(!showCode);
|
53
|
+
},
|
54
|
+
[showCode],
|
55
|
+
);
|
56
|
+
|
25
57
|
return (
|
26
|
-
|
27
|
-
|
28
|
-
<
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
58
|
+
<>
|
59
|
+
{isMobile === 'true' ? (
|
60
|
+
<MobileContainerFixedPerComp {...props} />
|
61
|
+
) : (
|
62
|
+
<NoSSR>
|
63
|
+
<div className="rspress-preview">
|
64
|
+
<div>
|
65
|
+
<div className="rspress-preview-card">
|
66
|
+
<div
|
67
|
+
style={{
|
68
|
+
overflow: 'auto',
|
69
|
+
flex: 'auto',
|
70
|
+
}}
|
71
|
+
>
|
72
|
+
{children?.[1]}
|
73
|
+
</div>
|
74
|
+
<div className="rspress-preview-operations web">
|
75
|
+
<button
|
76
|
+
onClick={toggleCode}
|
77
|
+
aria-label={lang === 'zh' ? '收起代码' : 'Collapse Code'}
|
78
|
+
className={showCode ? 'button-expanded' : ''}
|
79
|
+
>
|
80
|
+
<IconCode />
|
81
|
+
</button>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
<div
|
85
|
+
className={`${
|
86
|
+
showCode
|
87
|
+
? 'rspress-preview-code-show'
|
88
|
+
: 'rspress-preview-code-hide'
|
89
|
+
}`}
|
90
|
+
>
|
91
|
+
{children?.[0]}
|
92
|
+
</div>
|
93
|
+
</div>
|
39
94
|
</div>
|
40
|
-
</
|
41
|
-
|
42
|
-
|
95
|
+
</NoSSR>
|
96
|
+
)}
|
97
|
+
</>
|
43
98
|
);
|
44
99
|
};
|
45
100
|
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import {
|
2
|
+
NoSSR,
|
3
|
+
usePageData,
|
4
|
+
useWindowSize,
|
5
|
+
withBase,
|
6
|
+
} from '@rspress/core/runtime';
|
7
|
+
import { useCallback, useEffect, useState } from 'react';
|
8
|
+
// @ts-ignore
|
9
|
+
import { normalizeId } from '../../dist/utils';
|
10
|
+
import MobileOperation from './common/mobile-operation';
|
11
|
+
import { publishIframeUrl, useIframeUrlPerComp } from './useIframeUrlPerComp';
|
12
|
+
import './Device.scss';
|
13
|
+
|
14
|
+
export default () => {
|
15
|
+
const { page } = usePageData();
|
16
|
+
const pageName = `${normalizeId(page.pagePath)}`;
|
17
|
+
const demoId = `_${pageName}`;
|
18
|
+
const url = `~demo/${demoId}`;
|
19
|
+
const { haveDemos } = page;
|
20
|
+
|
21
|
+
const getPageUrl = (url: string) => {
|
22
|
+
if (page?.devPort) {
|
23
|
+
return `http://localhost:${page.devPort}/${demoId}`;
|
24
|
+
}
|
25
|
+
if (typeof window !== 'undefined') {
|
26
|
+
return `${window.location.origin}${withBase(url)}`;
|
27
|
+
}
|
28
|
+
// Do nothing in ssr
|
29
|
+
return '';
|
30
|
+
};
|
31
|
+
|
32
|
+
const initialUrl = getPageUrl(url);
|
33
|
+
|
34
|
+
const iframeUrl = useIframeUrlPerComp() ?? initialUrl;
|
35
|
+
|
36
|
+
const resetIframeUrl = useCallback(() => {
|
37
|
+
publishIframeUrl(initialUrl);
|
38
|
+
}, [initialUrl, url]);
|
39
|
+
|
40
|
+
useEffect(() => {
|
41
|
+
publishIframeUrl(initialUrl);
|
42
|
+
}, []);
|
43
|
+
|
44
|
+
const [asideWidth, setAsideWidth] = useState('0px');
|
45
|
+
const { width: innerWidth } = useWindowSize();
|
46
|
+
const [iframeKey, setIframeKey] = useState(0);
|
47
|
+
const refresh = useCallback(() => {
|
48
|
+
setIframeKey(Math.random());
|
49
|
+
}, []);
|
50
|
+
|
51
|
+
// get default value from root
|
52
|
+
// listen resize and re-render
|
53
|
+
useEffect(() => {
|
54
|
+
const root = document.querySelector(':root');
|
55
|
+
if (root) {
|
56
|
+
const defaultAsideWidth =
|
57
|
+
getComputedStyle(root).getPropertyValue('--rp-aside-width');
|
58
|
+
setAsideWidth(defaultAsideWidth);
|
59
|
+
}
|
60
|
+
}, []);
|
61
|
+
|
62
|
+
useEffect(() => {
|
63
|
+
const node = document.querySelector('.rspress-doc-container');
|
64
|
+
const { style } = document.documentElement;
|
65
|
+
if (haveDemos) {
|
66
|
+
if (innerWidth > 1280) {
|
67
|
+
node?.setAttribute(
|
68
|
+
'style',
|
69
|
+
'padding-right: calc(var(--rp-device-width) + var(--rp-preview-padding) * 2)',
|
70
|
+
);
|
71
|
+
} else if (innerWidth > 960) {
|
72
|
+
node?.setAttribute(
|
73
|
+
'style',
|
74
|
+
`padding-right: calc(${
|
75
|
+
innerWidth - 1280
|
76
|
+
}px + var(--rp-device-width) + var(--rp-preview-padding) * 2)`,
|
77
|
+
);
|
78
|
+
} else {
|
79
|
+
node?.removeAttribute('style');
|
80
|
+
}
|
81
|
+
style.setProperty('--rp-aside-width', '0px');
|
82
|
+
} else {
|
83
|
+
node?.removeAttribute('style');
|
84
|
+
style.setProperty('--rp-aside-width', asideWidth);
|
85
|
+
}
|
86
|
+
}, [haveDemos, asideWidth, innerWidth]);
|
87
|
+
|
88
|
+
return haveDemos ? (
|
89
|
+
<div className="rspress-fixed-device">
|
90
|
+
<NoSSR>
|
91
|
+
<iframe
|
92
|
+
// refresh when load the iframe, then remove NoSSR
|
93
|
+
src={iframeUrl}
|
94
|
+
className="rspress-fixed-iframe"
|
95
|
+
key={iframeKey}
|
96
|
+
></iframe>
|
97
|
+
</NoSSR>
|
98
|
+
<MobileOperation
|
99
|
+
url={iframeUrl}
|
100
|
+
className="rspress-fixed-operation"
|
101
|
+
refresh={refresh}
|
102
|
+
goBack={iframeUrl !== initialUrl ? resetIframeUrl : undefined}
|
103
|
+
/>
|
104
|
+
</div>
|
105
|
+
) : null;
|
106
|
+
};
|
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
useRef,
|
8
8
|
useState,
|
9
9
|
} from 'react';
|
10
|
+
import IconBack from '../icons/Back';
|
10
11
|
import IconLaunch from '../icons/Launch';
|
11
12
|
import IconQrcode from '../icons/Qrcode';
|
12
13
|
import IconRefresh from '../icons/Refresh';
|
@@ -15,21 +16,24 @@ import './index.scss';
|
|
15
16
|
const locales = {
|
16
17
|
zh: {
|
17
18
|
refresh: '刷新页面',
|
19
|
+
goBack: '返回',
|
18
20
|
open: '在新页面打开',
|
19
21
|
},
|
20
22
|
en: {
|
21
23
|
refresh: 'Refresh',
|
24
|
+
goBack: 'Go back',
|
22
25
|
open: 'Open in new page',
|
23
26
|
},
|
24
27
|
};
|
25
28
|
|
26
|
-
|
29
|
+
const MobileOperation = (props: {
|
27
30
|
url: string;
|
28
31
|
className?: string;
|
29
|
-
refresh
|
32
|
+
refresh?: () => void;
|
33
|
+
goBack?: () => void;
|
30
34
|
}) => {
|
35
|
+
const { url, className = '', refresh, goBack } = props;
|
31
36
|
const [showQRCode, setShowQRCode] = useState(false);
|
32
|
-
const { url, className = '', refresh } = props;
|
33
37
|
const lang = useLang();
|
34
38
|
const triggerRef = useRef(null);
|
35
39
|
const t = lang === 'zh' ? locales.zh : locales.en;
|
@@ -80,22 +84,46 @@ export default (props: {
|
|
80
84
|
|
81
85
|
return (
|
82
86
|
<div className={`rspress-preview-operations mobile ${className}`}>
|
83
|
-
|
84
|
-
<
|
85
|
-
|
87
|
+
{goBack && (
|
88
|
+
<button
|
89
|
+
onClick={goBack}
|
90
|
+
aria-label={t.goBack}
|
91
|
+
className="rspress-preview-operations-back"
|
92
|
+
>
|
93
|
+
<IconBack />
|
94
|
+
</button>
|
95
|
+
)}
|
96
|
+
{refresh && (
|
97
|
+
<button
|
98
|
+
onClick={refresh}
|
99
|
+
aria-label={t.refresh}
|
100
|
+
className="rspress-preview-operations-refresh"
|
101
|
+
>
|
102
|
+
<IconRefresh />
|
103
|
+
</button>
|
104
|
+
)}
|
86
105
|
<div className="relative" ref={triggerRef}>
|
87
106
|
{showQRCode && (
|
88
107
|
<div className="rspress-preview-qrcode">
|
89
108
|
<QRCodeSVG value={url} size={96} />
|
90
109
|
</div>
|
91
110
|
)}
|
92
|
-
<button
|
111
|
+
<button
|
112
|
+
onClick={toggleQRCode}
|
113
|
+
className="rspress-preview-operations-qrcode"
|
114
|
+
>
|
93
115
|
<IconQrcode />
|
94
116
|
</button>
|
95
117
|
</div>
|
96
|
-
<button
|
118
|
+
<button
|
119
|
+
onClick={openNewPage}
|
120
|
+
aria-label={t.open}
|
121
|
+
className="rspress-preview-operations-open"
|
122
|
+
>
|
97
123
|
<IconLaunch />
|
98
124
|
</button>
|
99
125
|
</div>
|
100
126
|
);
|
101
127
|
};
|
128
|
+
|
129
|
+
export default MobileOperation;
|
@@ -0,0 +1,31 @@
|
|
1
|
+
const Back = ({ color = 'currentColor', ...props }) => {
|
2
|
+
return (
|
3
|
+
<svg
|
4
|
+
width="1em"
|
5
|
+
height="1em"
|
6
|
+
viewBox="0 0 48 48"
|
7
|
+
fill="none"
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
9
|
+
stroke={color}
|
10
|
+
strokeWidth="4"
|
11
|
+
{...props}
|
12
|
+
>
|
13
|
+
<path
|
14
|
+
d="M12.9998 8L6 14L12.9998 21"
|
15
|
+
stroke="#333"
|
16
|
+
stroke-width="4"
|
17
|
+
stroke-linecap="round"
|
18
|
+
stroke-linejoin="round"
|
19
|
+
/>
|
20
|
+
<path
|
21
|
+
d="M6 14H28.9938C35.8768 14 41.7221 19.6204 41.9904 26.5C42.2739 33.7696 36.2671 40 28.9938 40H11.9984"
|
22
|
+
stroke="#333"
|
23
|
+
stroke-width="4"
|
24
|
+
stroke-linecap="round"
|
25
|
+
stroke-linejoin="round"
|
26
|
+
/>
|
27
|
+
</svg>
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
export default Back;
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
2
|
+
|
3
|
+
const useForceRefresh = () => {
|
4
|
+
const [_, setKey] = useState(0);
|
5
|
+
|
6
|
+
const forceRefresh = useCallback(() => {
|
7
|
+
return setKey(prevKey => prevKey + 1);
|
8
|
+
}, [setKey]);
|
9
|
+
|
10
|
+
return forceRefresh;
|
11
|
+
};
|
12
|
+
|
13
|
+
let iframeUrl: string | undefined;
|
14
|
+
|
15
|
+
const observer = new Map<string, () => void>();
|
16
|
+
|
17
|
+
const publishIframeUrl = (url: string) => {
|
18
|
+
iframeUrl = url;
|
19
|
+
for (const [, refresh] of observer) {
|
20
|
+
refresh();
|
21
|
+
}
|
22
|
+
};
|
23
|
+
|
24
|
+
const useIframeUrlPerComp = () => {
|
25
|
+
const forceRefresh = useForceRefresh();
|
26
|
+
|
27
|
+
useEffect(() => {
|
28
|
+
const id = Math.random().toString(36).slice(5);
|
29
|
+
observer.set(id, forceRefresh);
|
30
|
+
|
31
|
+
return () => {
|
32
|
+
observer.delete(id);
|
33
|
+
};
|
34
|
+
}, []);
|
35
|
+
|
36
|
+
return iframeUrl;
|
37
|
+
};
|
38
|
+
|
39
|
+
export { useIframeUrlPerComp, publishIframeUrl };
|