Pārlūkot izejas kodu

feat: ide site (#1860)

* feat: ide site

* fix: style fix

* fix: index bug

---------

Co-authored-by: 冯祥皓 <[email protected]>
hao 2 gadi atpakaļ
vecāks
revīzija
d7d7c580a7

+ 2 - 0
gatsby-node.js

@@ -99,6 +99,7 @@ exports.onCreateWebpackConfig = ({ stage, rules, loaders, plugins, actions }) =>
             alias: {
                 'semi-site-header': process.env.SEMI_SITE_HEADER || '@douyinfe/semi-site-header',
                 'semi-site-banner': process.env.SEMI_SITE_BANNER || '@douyinfe/semi-site-banner',
+                'univers-webview': process.env.SEMI_SITE_UNIVERS_WEBVIEW || resolve('packages/semi-ui'),
                 '@douyinfe/semi-ui': resolve('packages/semi-ui'),
                 '@douyinfe/semi-foundation': resolve('packages/semi-foundation'),
                 '@douyinfe/semi-icons': resolve('packages/semi-icons/src/'),
@@ -170,6 +171,7 @@ exports.onCreateWebpackConfig = ({ stage, rules, loaders, plugins, actions }) =>
             "DSM_URL": JSON.stringify(process.env['DSM_URL']),
             'process.env.SEMI_SITE_HEADER': JSON.stringify(process.env.SEMI_SITE_HEADER),
             'process.env.SEMI_SITE_BANNER': JSON.stringify(process.env.SEMI_SITE_BANNER),
+            "process.env.SEMI_SITE_UNIVERS_WEBVIEW": JSON.stringify(process.env.SEMI_SITE_UNIVERS_WEBVIEW),
             'process.env.D2C_URL': JSON.stringify(process.env.D2C_URL),
             "ASSET_PREFIX":JSON.stringify((process.env['CDN_OUTER_CN'] || process.env['CDN_INNER_CN']) ? `https://${(process.env['CDN_OUTER_CN'] || process.env['CDN_INNER_CN'])}/${process.env['CDN_PATH_PREFIX']}`: ""),
         })],

+ 25 - 18
src/components/layout.js

@@ -13,7 +13,7 @@ import '../styles/layout.scss';
 import 'typeface-inter';
 import 'typeface-inconsolata';
 
-import React, { useEffect, useState, useCallback } from 'react';
+import React, { useEffect, useState, useCallback, useRef } from 'react';
 import PropTypes from 'prop-types';
 import { useStaticQuery, graphql } from 'gatsby';
 import { IntlProvider } from 'react-intl';
@@ -32,6 +32,7 @@ import SideNav from './side-nav';
 import Footer from './Footer';
 import { itemsArr } from 'utils/category';
 import { getLocale, _t } from 'utils/locale';
+import { ISIDE, useIde } from './useIde';
 
 
 const insertScript = scriptText => {
@@ -43,6 +44,9 @@ const insertScript = scriptText => {
 
 const AppLayout = ({ type, location, children }) => {
     const [showBanner, setShowBanner] = useState(false);
+    const wrapperRef = useRef(null);
+    
+    useIde({ wrapperRef });
 
     // ----------------START insert static code to document-------------------------
     useEffect(() => {
@@ -111,7 +115,8 @@ const AppLayout = ({ type, location, children }) => {
     // -----------------------------------------------------------------------------
 
     const showSideNav =
-        location.pathname.replace(/(zh\-CN\/?|en\-US\/?)/, '') !== '/' && !/(showcase|resources|customers|contribute|teams)/g.test(location.pathname);
+        location.pathname.replace(/(zh\-CN\/?|en\-US\/?)/, '') !== '/' && !/(showcase|resources|customers|contribute|teams)/g.test(location.pathname) && !ISIDE;
+    
     const data = useStaticQuery(graphql`
         query {
             allMdx(
@@ -176,28 +181,30 @@ const AppLayout = ({ type, location, children }) => {
         <>
             <IntlProvider locale={locale} messages={messages}>
                 <LocaleProvider locale={semiLocaleSource}>
-                    <div style={{ position: 'fixed', width: '100%', top: 0, zIndex: 999 }}>
-                        <div className="skip-to-content">
-                            <div>{locale === "zh-CN" ? '跳转到:' : 'skip to:'}</div>
-                            <ol>
-                                {
-                                    showSideNav ? (<li><a className="skip-to-content-link" href='#side-nav'>{locale === "zh-CN" ? '跳转到侧边导航' : 'skip to navigation'}</a></li>):null
-                                }
-                                <li><a className="skip-to-content-link" href='#main-content'>{locale === "zh-CN" ? '跳转到主内容' : 'skip to main content'}</a></li>
-                                <li><a className="skip-to-content-link" href='#footer'>{locale === "zh-CN" ? '跳转到页脚' : 'skip to footer'}</a></li>
-                            </ol>
-                        </div>
-                        <SemiSiteBanner ref={bannerRef} type="black" style={{ height: 32 }} icon={null} />
-                        {/* ssr, can't use location directly, get location from layout and pass to children */}
-                        <Header style={headerStyle} location={location} localeCode={locale} />
-                    </div>
+                    {
+                        !ISIDE && (<div style={{ position: 'fixed', width: '100%', top: 0, zIndex: 999 }}>
+                            <div className="skip-to-content">
+                                <div>{locale === "zh-CN" ? '跳转到:' : 'skip to:'}</div>
+                                <ol>
+                                    {
+                                        showSideNav ? (<li><a className="skip-to-content-link" href='#side-nav'>{locale === "zh-CN" ? '跳转到侧边导航' : 'skip to navigation'}</a></li>) : null
+                                    }
+                                    <li><a className="skip-to-content-link" href='#main-content'>{locale === "zh-CN" ? '跳转到主内容' : 'skip to main content'}</a></li>
+                                    <li><a className="skip-to-content-link" href='#footer'>{locale === "zh-CN" ? '跳转到页脚' : 'skip to footer'}</a></li>
+                                </ol>
+                            </div>
+                            <SemiSiteBanner ref={bannerRef} type="black" style={{ height: 32 }} icon={null} />
+                            {/* ssr, can't use location directly, get location from layout and pass to children */}
+                            <Header style={headerStyle} location={location} localeCode={locale} />
+                        </div>)
+                    }
                     {showSideNav ? (
                         <>
                             <SideNav hasBanner={showBanner} type={type} style={sideNavStyle} location={location} edges={data.allMdx.edges} itemsArr={itemsArr} />
                             {/* {footer} */}
                         </>
                     ) : null}
-                    <div className="content-area" style={contentAeraStyle} id="main-content">
+                    <div className="content-area" style={contentAeraStyle} id="main-content" ref={wrapperRef}>
                         {children}
                         {!/showcase|teams/.test(location.pathname) && <Footer />}
                     </div>

+ 83 - 0
src/components/useIde.js

@@ -0,0 +1,83 @@
+import React, { useEffect, useRef } from 'react';
+import { Modal } from '@douyinfe/semi-ui';
+import '../styles/ide.scss';
+
+let runtime = null;
+let autoImportComponent = () => void 0;
+const ISSSR = typeof window === 'undefined';
+export const ISIDE = !ISSSR && window.location.search?.includes('env=ide');
+
+if (ISIDE) {
+    const { callNative, createProxy, Runtime } = require('univers-webview');
+    runtime = Runtime?.init({ isInIframe: false });
+    autoImportComponent = createProxy('ImportCode', callNative)?.autoImportComponent;
+}
+
+const formatCode = (code, demoIndex) => {
+    const codeArr = code.split('\n');
+    const bodyArr = [];
+    codeArr.forEach((str) => {
+        if (!str.startsWith('export default')) {
+            if (str.startsWith('() =>')) {
+                str = str.replace('() =>', 'const Demo = () =>');
+            }
+            bodyArr.push(str);
+        }
+    });
+    return {
+        codeString: bodyArr.join('\n'),
+        snippetString: '',
+    };
+};
+
+
+/**
+ * IDE plug-in related functions
+ */
+export const useIde = (props) => {
+    const timer = useRef(null);
+    const { wrapperRef } = props;
+
+    const addSnippetsBtn = (demoIndex) => {
+        const btnElm = document.createElement('div');
+        const svgStr =
+            '<svg fill="none" stroke="currentColor" stroke-width="4" viewBox="0 0 48 48" aria-hidden="true" focusable="false" class="arco-icon arco-icon-code"><path d="M43 22c0-7.732-6.492-14-14.5-14S14 14.268 14 22v.055A9.001 9.001 0 0 0 15 40h13m16.142-5.929-7.07 7.071L30 34.072M37.07 26v15"></path></svg>';
+        btnElm.innerHTML = svgStr;
+        btnElm.setAttribute('class', 'action-ide');
+        btnElm.onclick = () => {
+            const codeElem = wrapperRef?.current?.querySelectorAll('.gatsby-live-code-ide');
+            const index = demoIndex + 1;
+            if (codeElem?.length && codeElem[index]) {
+                Modal.confirm({
+                    title: '插入 Demo 代码',
+                    content: '确定在编辑器中插入当前 Demo 代码吗?',
+                    onOk: () => {
+                        const { codeString } = formatCode(codeElem[index]?.value || '');
+                        autoImportComponent({ codeString, snippetString: '' });
+                        const lineNum = codeString?.split('\n')?.length || 0;
+                        runtime?.require('tea').behave('importCode-semi', { num: lineNum });
+                    }
+                });
+            }
+        };
+        return btnElm;
+    };
+
+    const setSandBoxVisible = () => {
+        const actions = wrapperRef?.current?.querySelectorAll('.gatsby-live-code div[class^=index-module_actions__]');
+        actions?.forEach((action, index) => {
+            const snippetsBtn = addSnippetsBtn(index);
+            action?.appendChild(snippetsBtn);
+        });
+    };
+
+    useEffect(() => {
+        timer.current = setTimeout(() => {
+            ISIDE && setSandBoxVisible();
+        }, 1000);
+
+        return () => {
+            clearTimeout(timer.current);
+        };
+    });
+};

+ 14 - 0
src/styles/ide.scss

@@ -0,0 +1,14 @@
+.action-ide {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    margin-left: 4px;
+    font-size: 16px;
+    line-height: 20px;
+    vertical-align: middle;
+    cursor: pointer;
+}
+
+.gatsby-live-code-ide {
+    display: none;
+}

+ 3 - 1
src/templates/postTemplate.js

@@ -52,6 +52,7 @@ import ImageBox from 'components/ImageBox';
 import './toUEDUtils/toUED.scss';
 import { debounce } from 'lodash';
 import StickyHeaderTable from '../demos/StickyHeaderTable';
+import { ISIDE } from 'components/useIde';
 
 const Text = ({ lang, letterSpacing, size, lineHeight, text }) => {
     letterSpacing = letterSpacing || 'auto';
@@ -207,7 +208,8 @@ const code = ({ ...props }) => {
         };
     });
     return (
-        <div className={'gatsby-live-code'}>
+        <div className={'gatsby-live-code'} >
+            {ISIDE && <textarea {...{ value: ref.current.newProps.children }} className='gatsby-live-code-ide'/>}
             <Component {...ref.current.newProps} />
         </div>
     );