@rspress/plugin-preview 0.0.0-next-20230816082004

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/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@rspress/plugin-preview",
3
+ "version": "0.0.0-next-20230816082004",
4
+ "description": "A plugin for rspress to preview the code block in markdown/mdx file.",
5
+ "bugs": "https://github.com/web-infra-dev/rspress/issues",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/web-infra-dev/rspress",
9
+ "directory": "packages/plugin-preview"
10
+ },
11
+ "license": "MIT",
12
+ "jsnext:source": "./src/index.ts",
13
+ "types": "./dist/index.d.ts",
14
+ "main": "./dist/index.js",
15
+ "engines": {
16
+ "node": ">=14.17.6"
17
+ },
18
+ "eslintIgnore": [
19
+ "node_modules/",
20
+ "dist/"
21
+ ],
22
+ "dependencies": {
23
+ "@mdx-js/mdx": "2.2.1",
24
+ "@modern-js/utils": "2.30.0",
25
+ "qrcode.react": "^3.1.0",
26
+ "remark-gfm": "3.0.1",
27
+ "@rspress/shared": "0.0.0-next-20230816082004"
28
+ },
29
+ "devDependencies": {
30
+ "@types/mdast": "^3.0.10",
31
+ "@types/node": "^18.11.17",
32
+ "@types/react": "^18",
33
+ "@types/react-dom": "^18",
34
+ "mdast-util-mdxjs-esm": "^1.3.0",
35
+ "prettier": "^2.6.2",
36
+ "react": "^18",
37
+ "react-dom": "^18",
38
+ "react-router-dom": "^6.8.1",
39
+ "rspack-plugin-virtual-module": "0.1.8",
40
+ "typescript": "^5",
41
+ "unified": "^10.1.2",
42
+ "unist-util-visit": "^4.1.1"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=17",
46
+ "react-router-dom": "^6.8.1",
47
+ "@rspress/core": "0.0.0-next-20230816082004"
48
+ },
49
+ "files": [
50
+ "dist",
51
+ "static",
52
+ "mdx-meta-loader.cjs"
53
+ ],
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true,
57
+ "registry": "https://registry.npmjs.org/"
58
+ },
59
+ "scripts": {
60
+ "dev": "modern build -w",
61
+ "build": "modern build",
62
+ "reset": "rimraf ./**/node_modules",
63
+ "lint": "modern lint",
64
+ "change": "modern change",
65
+ "bump": "modern bump",
66
+ "pre": "modern pre",
67
+ "change-status": "modern change-status",
68
+ "gen-release-note": "modern gen-release-note",
69
+ "release": "modern release",
70
+ "new": "modern new",
71
+ "test": "vitest run --passWithNoTests",
72
+ "upgrade": "modern upgrade"
73
+ }
74
+ }
@@ -0,0 +1,105 @@
1
+ :root {
2
+ --modern-preview-button-hover-bg: #e5e6eb;
3
+ --modern-preview-button-bg: #e5e6eb
4
+
5
+ }
6
+
7
+ .dark {
8
+ --modern-preview-button-hover-bg: #c5c5c5;
9
+ --modern-preview-button-bg: #c5c5c5;
10
+ }
11
+
12
+ .modern-preview {
13
+ padding: 16px 0;
14
+
15
+ &-operations {
16
+ button {
17
+ width: 28px;
18
+ height: 28px;
19
+ padding: 0;
20
+ text-align: center;
21
+ border-radius: 50%;
22
+ border: 1px solid transparent;
23
+ background-color: var(--modern-c-bg-soft);
24
+ margin-left: 14px;
25
+
26
+ &:hover {
27
+ background-color: var(--modern-preview-button-hover-bg);
28
+ }
29
+ }
30
+
31
+ svg {
32
+ display: inline-block;
33
+ vertical-align: -2px;
34
+ }
35
+
36
+ &.mobile {
37
+ display: flex;
38
+ justify-content: flex-end;
39
+ width: 100%;
40
+ padding: 6px;
41
+ }
42
+
43
+ &.web {
44
+ display: flex;
45
+ justify-content: center;
46
+ position: absolute;
47
+ top: calc(50% - 14px);
48
+ right: 16px;
49
+ }
50
+ }
51
+
52
+ &-wrapper {
53
+ border: 1px solid #e6e6e6;
54
+ border-radius: 8px;
55
+ }
56
+
57
+ &-card {
58
+ padding: 16px;
59
+ position: relative;
60
+ border: 1px solid #e6e6e6;
61
+ border-radius: 8px;
62
+ }
63
+
64
+ &-qrcode {
65
+ background-color: #fff;
66
+ width: 120px;
67
+ height: 120px;
68
+ position: absolute;
69
+ top: -132px;
70
+ right: -46px;
71
+ padding: 12px;
72
+ }
73
+
74
+ &-code {
75
+ position: relative;
76
+ overflow: hidden;
77
+ flex: 1 1;
78
+ max-height: 700px;
79
+ }
80
+
81
+ &-code-hide {
82
+ position: relative;
83
+ overflow: hidden;
84
+ display: none;
85
+ }
86
+
87
+ &-code-show {
88
+ margin-top: 16px;
89
+ display: block;
90
+ }
91
+
92
+ &-device {
93
+ position: relative;
94
+ flex: 0 0;
95
+ border-left: 1px solid #e6e6e6;
96
+ display: flex;
97
+ flex-direction: column;
98
+ iframe {
99
+ border-bottom: 1px solid #e6e6e6;
100
+ width: 360px;
101
+ height: 100%;
102
+ flex: auto;
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,85 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { withBase, useLang, NoSSR } from '@rspress/core/runtime';
3
+ import MobileOperation from './common/mobile-operation';
4
+ import IconCode from './svg/code.svg';
5
+ import './Container.scss';
6
+
7
+ type ContainerProps = {
8
+ children: React.ReactNode[];
9
+ isMobile: 'true' | 'false';
10
+ url: string;
11
+ };
12
+
13
+ const Container: React.FC<ContainerProps> = props => {
14
+ const { children, isMobile, url } = props;
15
+ const [showCode, setShowCode] = useState(false);
16
+ const lang = useLang();
17
+
18
+ const getPageUrl = () => {
19
+ if (typeof window !== 'undefined') {
20
+ return `${window.location.origin}${withBase(url)}`;
21
+ }
22
+ // Do nothing in ssr
23
+ return '';
24
+ };
25
+ const toggleCode = (e: any) => {
26
+ if (!showCode) {
27
+ e.target.blur();
28
+ }
29
+ setShowCode(!showCode);
30
+ };
31
+
32
+ const [iframeKey, setIframeKey] = useState(0);
33
+ const refresh = useCallback(() => {
34
+ setIframeKey(Math.random());
35
+ }, []);
36
+
37
+ return (
38
+ <NoSSR>
39
+ <div className="modern-preview">
40
+ {isMobile === 'true' ? (
41
+ <div className="modern-preview-wrapper flex">
42
+ <div className="modern-preview-code">{children?.[0]}</div>
43
+ <div className="modern-preview-device">
44
+ <iframe src={getPageUrl()} key={iframeKey}></iframe>
45
+ <MobileOperation url={url} refresh={refresh} />
46
+ </div>
47
+ </div>
48
+ ) : (
49
+ <div>
50
+ <div className="modern-preview-card">
51
+ <div
52
+ style={{
53
+ overflow: 'auto',
54
+ marginRight: '44px',
55
+ }}
56
+ >
57
+ {children?.[1]}
58
+ </div>
59
+ <div className="modern-preview-operations web">
60
+ <button
61
+ onClick={toggleCode}
62
+ aria-label={lang === 'zh' ? '收起代码' : ''}
63
+ className={showCode ? 'button-expanded' : 'Collapse Code'}
64
+ >
65
+ <IconCode />
66
+ </button>
67
+ </div>
68
+ </div>
69
+ <div
70
+ className={`${
71
+ showCode
72
+ ? 'modern-preview-code-show'
73
+ : 'modern-preview-code-hide'
74
+ }`}
75
+ >
76
+ {children?.[0]}
77
+ </div>
78
+ </div>
79
+ )}
80
+ </div>
81
+ </NoSSR>
82
+ );
83
+ };
84
+
85
+ export default Container;
@@ -0,0 +1,21 @@
1
+ :root {
2
+ --modern-demo-block-bg: #f7f8fA;
3
+ }
4
+
5
+ .dark {
6
+ --modern-demo-block-bg: #1a1a1a
7
+ }
8
+
9
+ .modern-demo-block {
10
+ padding-bottom: 12px;
11
+ background-color: var(--modern-demo-block-bg);
12
+ &-title {
13
+ padding: 12px 12px 8px;
14
+ color: #697b8c;
15
+ font-size: 14px;
16
+ }
17
+ &-main {
18
+ border-right: none;
19
+ border-left: none;
20
+ }
21
+ }
@@ -0,0 +1,16 @@
1
+ import './DemoBlock.scss';
2
+
3
+ interface Props {
4
+ title: string;
5
+ children?: React.ReactNode;
6
+ }
7
+
8
+ export default (props: Props) => {
9
+ const { title, children } = props;
10
+ return (
11
+ <div className="modern-demo-block">
12
+ <div className="modern-demo-block-title">{title}</div>
13
+ <div className="modern-demo-block-main">{children}</div>
14
+ </div>
15
+ );
16
+ };
@@ -0,0 +1,46 @@
1
+ .fixed-device {
2
+ display: none;
3
+ position: fixed;
4
+ top: calc(var(--modern-nav-height) + var(--modern-preview-padding));
5
+ overflow: hidden;
6
+ }
7
+
8
+ .fixed-iframe {
9
+ height: var(--modern-device-height);
10
+ max-height: calc(100vh - var(--modern-preview-padding) * 2 - var(--modern-nav-height));
11
+ width: 360px;
12
+ pointer-events: auto;
13
+ border-radius: var(--modern-device-border-radius) var(--modern-device-border-radius) 0 0;
14
+ border: var(--modern-device-border);
15
+ }
16
+
17
+ .fixed-operation {
18
+ border: var(--modern-device-border);
19
+ border-top: 0;
20
+ border-radius: 0 0 var(--modern-device-border-radius) var(--modern-device-border-radius);
21
+ }
22
+
23
+ :root {
24
+ --modern-device-width: 360px;
25
+ --modern-device-height: 640px;
26
+ --modern-device-border-radius: 20px;
27
+ --modern-device-border: 1px solid #e5e6e8;
28
+ }
29
+
30
+
31
+ @media (min-width: 960px){
32
+ .fixed-device {
33
+ display: inline;
34
+ left: calc(1280px - var(--modern-device-width) - var(--modern-preview-padding));
35
+ right: auto;
36
+ }
37
+ }
38
+
39
+ @media (min-width: 1280px){
40
+ .fixed-device {
41
+ display: inline;
42
+ right: var(--modern-preview-padding);
43
+ left: auto;
44
+ }
45
+ }
46
+
@@ -0,0 +1,93 @@
1
+ import { usePageData, withBase } from '@rspress/core/runtime';
2
+ import { demos } from 'virtual-meta';
3
+ import { useCallback, useEffect, useState } from 'react';
4
+ import MobileOperation from './common/mobile-operation';
5
+ import './Device.scss';
6
+
7
+ export default () => {
8
+ const getPageUrl = (url: string) => {
9
+ if (typeof window !== 'undefined') {
10
+ return window.location.origin + withBase(url);
11
+ }
12
+ // Do nothing in ssr
13
+ return '';
14
+ };
15
+ const removeLeadingSlash = (url: string) => {
16
+ return url.charAt(0) === '/' ? url.slice(1) : url;
17
+ };
18
+
19
+ const getPageKey = (route: string) => {
20
+ const cleanRoute = removeLeadingSlash(route);
21
+ return cleanRoute.replace(/\//g, '_').replace(/\.[^.]+$/, '') || 'index';
22
+ };
23
+ const { page } = usePageData();
24
+ const pageName = getPageKey(page._relativePath);
25
+ const url = `~demo/${pageName}`;
26
+ const haveDemos =
27
+ demos.flat().filter(item => new RegExp(`${pageName}_\\d+`).test(item.id))
28
+ .length > 0;
29
+ const [asideWidth, setAsideWidth] = useState('0px');
30
+ const [innerWidth, setInnerWidth] = useState(window.innerWidth);
31
+ const [iframeKey, setIframeKey] = useState(0);
32
+ const refresh = useCallback(() => {
33
+ setIframeKey(Math.random());
34
+ }, []);
35
+
36
+ // get default value from root
37
+ // listen resize and re-render
38
+ useEffect(() => {
39
+ const root = document.querySelector(':root');
40
+ if (root) {
41
+ const defaultAsideWidth = getComputedStyle(root).getPropertyValue(
42
+ '--modern-aside-width',
43
+ );
44
+ setAsideWidth(defaultAsideWidth);
45
+ }
46
+ const handleResize = () => {
47
+ setInnerWidth(window.innerWidth);
48
+ };
49
+ window.addEventListener('resize', handleResize);
50
+ return () => window.removeEventListener('resize', handleResize);
51
+ }, []);
52
+
53
+ useEffect(() => {
54
+ const node = document.querySelector('.modern-doc-container');
55
+ const { style } = document.documentElement;
56
+ if (haveDemos) {
57
+ if (innerWidth > 1280) {
58
+ node?.setAttribute(
59
+ 'style',
60
+ 'padding-right: calc(var(--modern-device-width) + var(--modern-preview-padding) * 2)',
61
+ );
62
+ } else if (innerWidth > 960) {
63
+ node?.setAttribute(
64
+ 'style',
65
+ `padding-right: calc(${
66
+ innerWidth - 1280
67
+ }px + var(--modern-device-width) + var(--modern-preview-padding) * 2)`,
68
+ );
69
+ } else {
70
+ node?.removeAttribute('style');
71
+ }
72
+ style.setProperty('--modern-aside-width', '0');
73
+ } else {
74
+ node?.removeAttribute('style');
75
+ style.setProperty('--modern-aside-width', asideWidth);
76
+ }
77
+ }, [haveDemos, asideWidth, innerWidth]);
78
+
79
+ return haveDemos ? (
80
+ <div className="fixed-device">
81
+ <iframe
82
+ src={getPageUrl(url)}
83
+ className="fixed-iframe"
84
+ key={iframeKey}
85
+ ></iframe>
86
+ <MobileOperation
87
+ url={url}
88
+ className="fixed-operation"
89
+ refresh={refresh}
90
+ />
91
+ </div>
92
+ ) : null;
93
+ };
@@ -0,0 +1,106 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { QRCodeSVG } from 'qrcode.react';
3
+ import { withBase, useLang } from '@rspress/core/runtime';
4
+ import IconLaunch from '../svg/launch.svg';
5
+ import IconQrcode from '../svg/qrcode.svg';
6
+ import IconRefresh from '../svg/refresh.svg';
7
+
8
+ const locales = {
9
+ zh: {
10
+ refresh: '刷新页面',
11
+ open: '在新页面打开',
12
+ },
13
+ en: {
14
+ refresh: 'refresh',
15
+ open: 'Open in new page',
16
+ },
17
+ };
18
+
19
+ export default (props: {
20
+ url: string;
21
+ className?: string;
22
+ refresh: () => void;
23
+ }) => {
24
+ const [showQRCode, setShowQRCode] = useState(false);
25
+ const { url, className = '', refresh } = props;
26
+ const lang = useLang();
27
+ const triggerRef = useRef(null);
28
+ const t = lang === 'zh' ? locales.zh : locales.en;
29
+
30
+ const getPageUrl = () => {
31
+ if (typeof window !== 'undefined') {
32
+ return window.location.origin + withBase(url);
33
+ }
34
+ // Do nothing in ssr
35
+ return '';
36
+ };
37
+ const toggleQRCode = (e: any) => {
38
+ if (!showQRCode) {
39
+ e.target.blur();
40
+ }
41
+ setShowQRCode(!showQRCode);
42
+ };
43
+ const openNewPage = () => {
44
+ window.open(getPageUrl());
45
+ };
46
+
47
+ const contains = function (root: HTMLElement | null, ele: any) {
48
+ if (!root) {
49
+ return false;
50
+ }
51
+ if (root.contains) {
52
+ return root.contains(ele);
53
+ }
54
+ let node = ele;
55
+ while (node) {
56
+ if (node === root) {
57
+ return true;
58
+ }
59
+ node = node.parentNode;
60
+ }
61
+ return false;
62
+ };
63
+
64
+ const onClickOutside = useCallback(
65
+ (e: MouseEvent) => {
66
+ console.log(
67
+ !contains(triggerRef.current, e.target),
68
+ triggerRef.current,
69
+ e.target,
70
+ );
71
+ if (!contains(triggerRef.current, e.target)) {
72
+ setShowQRCode(false);
73
+ }
74
+ },
75
+ [triggerRef],
76
+ );
77
+
78
+ useEffect(() => {
79
+ if (showQRCode) {
80
+ document.addEventListener('mousedown', onClickOutside, false);
81
+ } else {
82
+ document.removeEventListener('mousedown', onClickOutside, false);
83
+ }
84
+ }, [showQRCode]);
85
+
86
+ return (
87
+ <div className={`modern-preview-operations mobile ${className}`}>
88
+ <button onClick={refresh} aria-label={t.refresh}>
89
+ <IconRefresh />
90
+ </button>
91
+ <div className="relative" ref={triggerRef}>
92
+ {showQRCode && (
93
+ <div className="modern-preview-qrcode">
94
+ <QRCodeSVG value={getPageUrl()} size={96} />
95
+ </div>
96
+ )}
97
+ <button onClick={toggleQRCode}>
98
+ <IconQrcode />
99
+ </button>
100
+ </div>
101
+ <button onClick={openNewPage} aria-label={t.open}>
102
+ <IconLaunch />
103
+ </button>
104
+ </div>
105
+ );
106
+ };
@@ -0,0 +1,3 @@
1
+ <svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="4">
2
+ <path d="M16.7336 12.6863L5.41992 24L16.7336 35.3137M31.2551 12.6863L42.5688 24L31.2551 35.3137M27.1999 6.28003L20.9486 41.7331" stroke-linecap="butt"></path>
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" stroke-width="4" stroke="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M41 25.9998V39.9998C41 40.552 40.5523 40.9998 40 40.9998H8C7.44772 40.9998 7 40.552 7 39.9998V7.99976C7 7.44747 7.44772 6.99976 8 6.99976H22" />
3
+ <path d="M19.822 28.1776L39.899 8.09961" />
4
+ <path d="M40.9998 20L41.0002 7H28.0008" />
5
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="4">
2
+ <path d="M24 30V34M24 37V43M43 24H37M34 24H30M7 7H24V24H7V7ZM7 32H16V41H7V32ZM32 32H41V41H32V32ZM32 7H41V16H32V7ZM14 14H17V17H14V14Z" stroke-linecap="butt"></path>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="4">
2
+ <path d="M38.837 18C36.4634 12.1363 30.7148 8 24 8C15.1634 8 8 15.1634 8 24C8 32.8366 15.1634 40 24 40C31.4554 40 37.7198 34.9009 39.4959 28M40 8V18H30" stroke-linecap="butt"></path>
3
+ </svg>
@@ -0,0 +1,35 @@
1
+ :root .modern-preview div[class*='language-'] {
2
+ margin: 0;
3
+ border-radius: 0;
4
+ height: 100%;
5
+ }
6
+
7
+ :root {
8
+ --modern-preview-padding: 32px
9
+ }
10
+
11
+ @media (min-width: 1280px) {
12
+ .modern-doc-layout {
13
+ width: calc(100% - var(--modern-preview-padding));
14
+ margin-left: var(--modern-preview-padding);
15
+ }
16
+
17
+ .modern-sidebar {
18
+ margin-left: var(--modern-preview-padding);
19
+ }
20
+
21
+ .modern-doc-nav-container {
22
+ max-width: 100%;
23
+ margin-left: var(--modern-preview-padding);
24
+ }
25
+
26
+ .modern-doc-container {
27
+ padding-right: var(--modern-preview-padding);
28
+ padding-left: var(--modern-preview-padding);
29
+ }
30
+
31
+ .modern-doc {
32
+ width: 100%;
33
+ padding-right: var(--modern-preview-padding);
34
+ }
35
+ }
@@ -0,0 +1,11 @@
1
+ :root {
2
+ --modern-sidebar-width: 210px;
3
+ --modern-aside-width: 192px;
4
+ --modern-preview-padding: 32px
5
+ }
6
+
7
+ :root .preview-code div[class*='language-'] {
8
+ margin: 0;
9
+ border-radius: 0;
10
+ height: 100%
11
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types='@modern-js/module-tools/types' />
2
+
3
+ declare module 'virtual-meta' {
4
+ const demos: any[];
5
+ export { demos };
6
+ }