@rspress/plugin-preview 0.0.0-next-20230927072732 → 0.0.0-next-20231108104528

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.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Converts a string to a valid variable name. If the string is already a valid variable name, returns the original string.
3
+ * @param str - The string to convert.
4
+ * @returns The converted string.
5
+ */
6
+ declare const toValidVarName: (str: string) => string;
7
+ declare const generateId: (pageName: string, index: number) => string;
8
+ /**
9
+ * remove .html extension and validate
10
+ * @param routePath id from pathname
11
+ * @returns normalized id
12
+ */
13
+ declare const normalizeId: (routePath: string) => string;
14
+ declare const injectDemoBlockImport: (str: string, path: string) => string;
15
+
16
+ export { generateId, injectDemoBlockImport, normalizeId, toValidVarName };
package/dist/utils.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/utils.ts
21
+ var utils_exports = {};
22
+ __export(utils_exports, {
23
+ generateId: () => generateId,
24
+ injectDemoBlockImport: () => injectDemoBlockImport,
25
+ normalizeId: () => normalizeId,
26
+ toValidVarName: () => toValidVarName
27
+ });
28
+ module.exports = __toCommonJS(utils_exports);
29
+ var toValidVarName = (str) => {
30
+ if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(str)) {
31
+ return str;
32
+ } else {
33
+ return str.replace(/[^0-9a-zA-Z_$]/g, "_").replace(/^([0-9])/, "_$1");
34
+ }
35
+ };
36
+ var generateId = (pageName, index) => {
37
+ return `_${toValidVarName(pageName)}_${index}`;
38
+ };
39
+ var normalizeId = (routePath) => {
40
+ const result = routePath.replace(/\.(.*)?$/, "");
41
+ return toValidVarName(result);
42
+ };
43
+ var injectDemoBlockImport = (str, path) => {
44
+ return `
45
+ import DemoBlock from '${path}';
46
+ ${str}
47
+ `;
48
+ };
49
+ // Annotate the CommonJS export names for ESM import in node:
50
+ 0 && (module.exports = {
51
+ generateId,
52
+ injectDemoBlockImport,
53
+ normalizeId,
54
+ toValidVarName
55
+ });
56
+
57
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKO,IAAM,iBAAiB,CAAC,QAAwB;AAErD,MAAI,6BAA6B,KAAK,GAAG,GAAG;AAC1C,WAAO;AAAA,EACT,OAAO;AAEL,WAAO,IAAI,QAAQ,mBAAmB,GAAG,EAAE,QAAQ,YAAY,KAAK;AAAA,EACtE;AACF;AAEO,IAAM,aAAa,CAAC,UAAkB,UAAkB;AAC7D,SAAO,IAAI,eAAe,QAAQ,CAAC,IAAI,KAAK;AAC9C;AAOO,IAAM,cAAc,CAAC,cAAsB;AAChD,QAAM,SAAS,UAAU,QAAQ,YAAY,EAAE;AAC/C,SAAO,eAAe,MAAM;AAC9B;AAEO,IAAM,wBAAwB,CAAC,KAAa,SAAyB;AAC1E,SAAO;AAAA,6BACoB,IAAI;AAAA,MAC3B,GAAG;AAAA;AAET;","names":[],"sources":["../src/utils.ts"],"sourcesContent":["/**\n * Converts a string to a valid variable name. If the string is already a valid variable name, returns the original string.\n * @param str - The string to convert.\n * @returns The converted string.\n */\nexport const toValidVarName = (str: string): string => {\n // Check if the string is a valid variable name\n if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(str)) {\n return str; // If it is a valid variable name, return the original string\n } else {\n // If it is not a valid variable name, convert it to a valid variable name\n return str.replace(/[^0-9a-zA-Z_$]/g, '_').replace(/^([0-9])/, '_$1');\n }\n};\n\nexport const generateId = (pageName: string, index: number) => {\n return `_${toValidVarName(pageName)}_${index}`;\n};\n\n/**\n * remove .html extension and validate\n * @param routePath id from pathname\n * @returns normalized id\n */\nexport const normalizeId = (routePath: string) => {\n const result = routePath.replace(/\\.(.*)?$/, '');\n return toValidVarName(result);\n};\n\nexport const injectDemoBlockImport = (str: string, path: string): string => {\n return `\n import DemoBlock from '${path}';\n ${str}\n `;\n};\n"]}
@@ -1,5 +1,4 @@
1
1
  // src/virtual-demo.tsx
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import { demos } from "virtual-meta";
4
3
  import { createElement } from "react";
5
4
  import { NoSSR, useLocation } from "@rspress/core/runtime";
@@ -51,33 +50,22 @@ var css_248z = ":root {\n --rp-iframe-bg: var(--rp-code-block-bg);\n}\n\nbody {
51
50
  style_inject_es_default(css_248z);
52
51
 
53
52
  // src/virtual-demo.tsx
53
+ import { jsx, jsxs } from "react/jsx-runtime";
54
54
  function Demo(props) {
55
55
  const { pathname } = useLocation();
56
- const id = pathname.split("/").pop();
57
- const normalizedId = normalizeId(id || "");
56
+ const pureDemoPath = pathname.split("/").filter(Boolean).slice(1)?.join("/");
57
+ const normalizedId = normalizeId(pureDemoPath || "");
58
58
  if (props.iframePosition === "fixed") {
59
59
  const renderDemos = demos.flat().filter((item) => new RegExp(`${normalizedId}_\\d+`).test(item.id));
60
- return renderDemos.length > 0 ? /* @__PURE__ */ _jsx(NoSSR, {
61
- children: /* @__PURE__ */ _jsxs("div", {
62
- className: "preview-container",
63
- children: [
64
- /* @__PURE__ */ _jsx("div", {
65
- className: "preview-nav",
66
- children: renderDemos[0].title
67
- }),
68
- renderDemos.map((renderDemo) => {
69
- return /* @__PURE__ */ _jsx("div", {
70
- children: /* @__PURE__ */ createElement(renderDemo.component)
71
- }, renderDemo.id);
72
- })
73
- ]
60
+ return renderDemos.length > 0 ? /* @__PURE__ */ jsx(NoSSR, { children: /* @__PURE__ */ jsxs("div", { className: "preview-container", children: [
61
+ /* @__PURE__ */ jsx("div", { className: "preview-nav", children: renderDemos[0].title }),
62
+ renderDemos.map((renderDemo) => {
63
+ return /* @__PURE__ */ jsx("div", { children: createElement(renderDemo.component) }, renderDemo.id);
74
64
  })
75
- }) : /* @__PURE__ */ _jsx(NotFoundLayout, {});
65
+ ] }) }) : /* @__PURE__ */ jsx(NotFoundLayout, {});
76
66
  } else {
77
67
  const component = demos.flat().find((item) => item.id === normalizedId)?.component;
78
- return component ? /* @__PURE__ */ _jsx(NoSSR, {
79
- children: /* @__PURE__ */ createElement(component)
80
- }) : /* @__PURE__ */ _jsx(NotFoundLayout, {});
68
+ return component ? /* @__PURE__ */ jsx(NoSSR, { children: createElement(component) }) : /* @__PURE__ */ jsx(NotFoundLayout, {});
81
69
  }
82
70
  }
83
71
  export {
@@ -14,6 +14,7 @@ module.exports = async function () {
14
14
  .map(item => {
15
15
  return `{
16
16
  "id": "${item.id}",
17
+ "title": "${item.title}",
17
18
  "component": Demo_${item.id}
18
19
  }`;
19
20
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rspress/plugin-preview",
3
- "version": "0.0.0-next-20230927072732",
3
+ "version": "0.0.0-next-20231108104528",
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": {
@@ -21,14 +21,18 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@mdx-js/mdx": "2.2.1",
24
- "@modern-js/utils": "2.35.1",
24
+ "@modern-js/utils": "2.39.2",
25
25
  "qrcode.react": "^3.1.0",
26
26
  "remark-gfm": "3.0.1",
27
- "@rspress/shared": "0.0.0-next-20230927072732"
27
+ "rspack-plugin-virtual-module": "0.1.12",
28
+ "codesandbox": "2.2.3",
29
+ "lodash": "4.17.21",
30
+ "@rspress/shared": "0.0.0-next-20231108104528"
28
31
  },
29
32
  "devDependencies": {
30
33
  "@types/mdast": "^3.0.10",
31
34
  "@types/node": "^18.11.17",
35
+ "@types/lodash": "4.14.200",
32
36
  "@types/react": "^18",
33
37
  "@types/react-dom": "^18",
34
38
  "mdast-util-mdxjs-esm": "^1.3.0",
@@ -36,7 +40,6 @@
36
40
  "react": "^18",
37
41
  "react-dom": "^18",
38
42
  "react-router-dom": "^6.8.1",
39
- "rspack-plugin-virtual-module": "0.1.11",
40
43
  "typescript": "^5",
41
44
  "unified": "^10.1.2",
42
45
  "unist-util-visit": "^4.1.1"
@@ -44,7 +47,7 @@
44
47
  "peerDependencies": {
45
48
  "react": ">=17",
46
49
  "react-router-dom": "^6.8.1",
47
- "@rspress/core": "0.0.0-next-20230927072732"
50
+ "@rspress/core": "0.0.0-next-20231108104528"
48
51
  },
49
52
  "files": [
50
53
  "dist",
@@ -11,43 +11,6 @@
11
11
  .rspress-preview {
12
12
  padding: 16px 0;
13
13
 
14
- &-operations {
15
- button {
16
- width: 28px;
17
- height: 28px;
18
- padding: 0;
19
- text-align: center;
20
- border-radius: 50%;
21
- border: 1px solid transparent;
22
- background-color: var(--rp-c-bg-soft);
23
- margin-left: 14px;
24
-
25
- &:hover {
26
- background-color: var(--rp-preview-button-hover-bg);
27
- }
28
- }
29
-
30
- svg {
31
- display: inline-block;
32
- vertical-align: -2px;
33
- }
34
-
35
- &.mobile {
36
- display: flex;
37
- justify-content: flex-end;
38
- width: 100%;
39
- padding: 6px;
40
- }
41
-
42
- &.web {
43
- display: flex;
44
- justify-content: center;
45
- position: absolute;
46
- top: calc(50% - 14px);
47
- right: 16px;
48
- }
49
- }
50
-
51
14
  &-wrapper {
52
15
  border: 1px solid #e6e6e6;
53
16
  border-radius: 8px;
@@ -58,6 +21,7 @@
58
21
  position: relative;
59
22
  border: 1px solid #e6e6e6;
60
23
  border-radius: 8px;
24
+ display: flex;
61
25
  }
62
26
 
63
27
  &-qrcode {
@@ -1,17 +1,23 @@
1
1
  import { useCallback, useState } from 'react';
2
2
  import { withBase, useLang, NoSSR } from '@rspress/core/runtime';
3
+ import { getParameters } from 'codesandbox/lib/api/define';
3
4
  import MobileOperation from './common/mobile-operation';
4
5
  import IconCode from './icons/Code';
6
+ import IconCodesandbox from './icons/Codesandbox';
5
7
  import './Container.scss';
6
8
 
7
9
  type ContainerProps = {
8
10
  children: React.ReactNode[];
9
11
  isMobile: 'true' | 'false';
12
+ enableCodesandbox: 'true' | 'false';
10
13
  url: string;
14
+ content: string;
15
+ packageName: string;
11
16
  };
12
17
 
13
18
  const Container: React.FC<ContainerProps> = props => {
14
- const { children, isMobile, url } = props;
19
+ const { children, isMobile, url, content, packageName, enableCodesandbox } =
20
+ props;
15
21
  const [showCode, setShowCode] = useState(false);
16
22
  const lang = useLang();
17
23
 
@@ -34,6 +40,54 @@ const Container: React.FC<ContainerProps> = props => {
34
40
  setIframeKey(Math.random());
35
41
  }, []);
36
42
 
43
+ const gotoCodeSandBox = () => {
44
+ const sandBoxConfig = {
45
+ files: {
46
+ 'package.json': {
47
+ isBinary: false,
48
+ content: JSON.stringify({
49
+ dependencies: {
50
+ react: '18',
51
+ 'react-dom': '18',
52
+ [packageName]: 'latest',
53
+ },
54
+ }),
55
+ },
56
+ [`demo.tsx`]: {
57
+ isBinary: false,
58
+ content,
59
+ },
60
+ [`index.tsx`]: {
61
+ isBinary: false,
62
+ content: [
63
+ `import React from 'react'`,
64
+ `import ReactDOM from 'react-dom'`,
65
+ `import Demo from './demo'`,
66
+ `ReactDOM.render(<Demo />, document.getElementById('root'))`,
67
+ ].join('\n'),
68
+ },
69
+ },
70
+ };
71
+
72
+ // to specific demo file
73
+ const query = `file=/demo.tsx`;
74
+ const form = document.createElement('form');
75
+ form.action = `https://codesandbox.io/api/v1/sandboxes/define?query=${encodeURIComponent(
76
+ query,
77
+ )}`;
78
+ form.target = '_blank';
79
+ form.method = 'POST';
80
+ form.style.display = 'none';
81
+ const field = document.createElement('input');
82
+ field.name = 'parameters';
83
+ field.type = 'hidden';
84
+ field.setAttribute('value', getParameters(sandBoxConfig));
85
+ form.appendChild(field);
86
+ document.body.appendChild(form);
87
+ form.submit();
88
+ document.body.removeChild(form);
89
+ };
90
+
37
91
  return (
38
92
  <NoSSR>
39
93
  <div className="rspress-preview">
@@ -42,7 +96,13 @@ const Container: React.FC<ContainerProps> = props => {
42
96
  <div className="rspress-preview-code">{children?.[0]}</div>
43
97
  <div className="rspress-preview-device">
44
98
  <iframe src={getPageUrl()} key={iframeKey}></iframe>
45
- <MobileOperation url={url} refresh={refresh} />
99
+ <MobileOperation
100
+ url={url}
101
+ refresh={refresh}
102
+ gotoCodeSandBox={
103
+ enableCodesandbox === 'true' ? gotoCodeSandBox : undefined
104
+ }
105
+ />
46
106
  </div>
47
107
  </div>
48
108
  ) : (
@@ -51,16 +111,28 @@ const Container: React.FC<ContainerProps> = props => {
51
111
  <div
52
112
  style={{
53
113
  overflow: 'auto',
54
- marginRight: '44px',
114
+ flex: 'auto',
55
115
  }}
56
116
  >
57
117
  {children?.[1]}
58
118
  </div>
59
119
  <div className="rspress-preview-operations web">
120
+ {enableCodesandbox === 'true' && (
121
+ <button
122
+ onClick={gotoCodeSandBox}
123
+ aria-label={
124
+ lang === 'zh'
125
+ ? '在 Codesandbox 打开'
126
+ : 'Open in Codesandbox'
127
+ }
128
+ >
129
+ <IconCodesandbox />
130
+ </button>
131
+ )}
60
132
  <button
61
133
  onClick={toggleCode}
62
- aria-label={lang === 'zh' ? '收起代码' : ''}
63
- className={showCode ? 'button-expanded' : 'Collapse Code'}
134
+ aria-label={lang === 'zh' ? '收起代码' : 'Collapse Code'}
135
+ className={showCode ? 'button-expanded' : ''}
64
136
  >
65
137
  <IconCode />
66
138
  </button>
@@ -1,6 +1,7 @@
1
1
  import { usePageData, withBase } from '@rspress/core/runtime';
2
2
  import { demos } from 'virtual-meta';
3
3
  import { useCallback, useEffect, useState } from 'react';
4
+ import { normalizeId } from '../../dist/utils';
4
5
  import MobileOperation from './common/mobile-operation';
5
6
  import './Device.scss';
6
7
 
@@ -12,22 +13,17 @@ export default () => {
12
13
  // Do nothing in ssr
13
14
  return '';
14
15
  };
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
16
  const { page } = usePageData();
24
- const pageName = getPageKey(page._relativePath);
17
+
18
+ const pageName = `_${normalizeId(page.pagePath)}`;
25
19
  const url = `~demo/${pageName}`;
26
20
  const haveDemos =
27
21
  demos.flat().filter(item => new RegExp(`${pageName}_\\d+`).test(item.id))
28
22
  .length > 0;
23
+ const initialInnerWidth =
24
+ typeof window !== 'undefined' ? window.innerWidth : 0;
29
25
  const [asideWidth, setAsideWidth] = useState('0px');
30
- const [innerWidth, setInnerWidth] = useState(window.innerWidth);
26
+ const [innerWidth, setInnerWidth] = useState(initialInnerWidth);
31
27
  const [iframeKey, setIframeKey] = useState(0);
32
28
  const refresh = useCallback(() => {
33
29
  setIframeKey(Math.random());
@@ -0,0 +1,37 @@
1
+ .rspress-preview {
2
+ &-operations {
3
+ button {
4
+ width: 28px;
5
+ height: 28px;
6
+ padding: 0;
7
+ text-align: center;
8
+ border-radius: 50%;
9
+ border: 1px solid transparent;
10
+ background-color: var(--rp-c-bg-soft);
11
+ margin-left: 14px;
12
+
13
+ &:hover {
14
+ background-color: var(--rp-preview-button-hover-bg);
15
+ }
16
+ }
17
+
18
+ svg {
19
+ display: inline-block;
20
+ vertical-align: -2px;
21
+ }
22
+
23
+ &.mobile {
24
+ display: flex;
25
+ justify-content: flex-end;
26
+ width: 100%;
27
+ padding: 6px;
28
+ }
29
+
30
+ &.web {
31
+ display: flex;
32
+ justify-content: center;
33
+ align-items: center;
34
+ flex: none;
35
+ }
36
+ }
37
+ }
@@ -4,15 +4,19 @@ import { withBase, useLang } from '@rspress/core/runtime';
4
4
  import IconLaunch from '../icons/Launch';
5
5
  import IconQrcode from '../icons/Qrcode';
6
6
  import IconRefresh from '../icons/Refresh';
7
+ import IconCodesandbox from '../icons/Codesandbox';
8
+ import './index.scss';
7
9
 
8
10
  const locales = {
9
11
  zh: {
10
12
  refresh: '刷新页面',
11
13
  open: '在新页面打开',
14
+ codesandbox: '在 Codesandbox 打开',
12
15
  },
13
16
  en: {
14
- refresh: 'refresh',
17
+ refresh: 'Refresh',
15
18
  open: 'Open in new page',
19
+ codesandbox: 'Open in Codesandbox',
16
20
  },
17
21
  };
18
22
 
@@ -20,9 +24,10 @@ export default (props: {
20
24
  url: string;
21
25
  className?: string;
22
26
  refresh: () => void;
27
+ gotoCodeSandBox?: () => void;
23
28
  }) => {
24
29
  const [showQRCode, setShowQRCode] = useState(false);
25
- const { url, className = '', refresh } = props;
30
+ const { url, className = '', refresh, gotoCodeSandBox } = props;
26
31
  const lang = useLang();
27
32
  const triggerRef = useRef(null);
28
33
  const t = lang === 'zh' ? locales.zh : locales.en;
@@ -85,6 +90,11 @@ export default (props: {
85
90
 
86
91
  return (
87
92
  <div className={`rspress-preview-operations mobile ${className}`}>
93
+ {gotoCodeSandBox && (
94
+ <button onClick={gotoCodeSandBox} aria-label={t.codesandbox}>
95
+ <IconCodesandbox />
96
+ </button>
97
+ )}
88
98
  <button onClick={refresh} aria-label={t.refresh}>
89
99
  <IconRefresh />
90
100
  </button>
@@ -0,0 +1,22 @@
1
+ const Codesandbox = ({ color = 'currentColor', ...props }) => (
2
+ <svg
3
+ width="1em"
4
+ height="1em"
5
+ viewBox="0 0 48 48"
6
+ fill="none"
7
+ stroke={color}
8
+ strokeWidth="4"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ {...props}
11
+ >
12
+ <path
13
+ d="M25.0016 1.5998L42.9016 11.8998C43.5016 12.2998 43.9016 12.8998 43.9016 13.5998V34.2998C43.9016 34.9998 43.5016 35.6998 42.9016 35.9998L25.0016 46.3998C24.4016 46.7998 23.6016 46.7998 23.0016 46.3998L5.10156 36.0998C4.50156 35.6998 4.10156 35.0998 4.10156 34.3998V13.6998C4.10156 12.9998 4.50156 12.2998 5.10156 11.9998L23.0016 1.5998C23.6016 1.1998 24.4016 1.1998 25.0016 1.5998ZM38.5016 13.9998L30.6016 9.49981L24.0016 13.9998L17.5016 9.9998L10.2016 14.2998L24.0016 22.9998L38.5016 13.9998ZM22.0016 40.3998V26.2998L8.00156 17.3998V25.2998L16.0016 30.7998V36.9998L22.0016 40.3998ZM26.0016 40.3998L32.0016 36.8998V31.6998L40.0016 26.1998V17.2998L26.0016 26.1998V40.3998Z"
14
+ fill="currentColor"
15
+ stroke="none"
16
+ stroke-width="none"
17
+ stroke-linecap="butt"
18
+ ></path>
19
+ </svg>
20
+ );
21
+
22
+ export default Codesandbox;
@@ -1,9 +1,3 @@
1
- :root {
2
- --rp-sidebar-width: 210px;
3
- --rp-aside-width: 192px;
4
- --rp-preview-padding: 32px;
5
- }
6
-
7
1
  :root .preview-code div[class*='language-'] {
8
2
  margin: 0;
9
3
  border-radius: 0;