@rspress/runtime 0.0.0-nightly-20231026160444
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/LICENSE +21 -0
- package/dist/runtime/Content.d.ts +6 -0
- package/dist/runtime/Content.js +33 -0
- package/dist/runtime/NoSSR.d.ts +4 -0
- package/dist/runtime/NoSSR.js +17 -0
- package/dist/runtime/hooks.d.ts +39 -0
- package/dist/runtime/hooks.js +54 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +40 -0
- package/dist/runtime/tsconfig.json +10 -0
- package/dist/runtime/utils.d.ts +8 -0
- package/dist/runtime/utils.js +45 -0
- package/package.json +86 -0
- package/server.js +1 -0
- package/src/.eslintrc.cjs +9 -0
- package/src/runtime/Content.tsx +45 -0
- package/src/runtime/NoSSR.tsx +16 -0
- package/src/runtime/global.d.ts +20 -0
- package/src/runtime/hooks.ts +110 -0
- package/src/runtime/index.ts +22 -0
- package/src/runtime/tsconfig.json +10 -0
- package/src/runtime/utils.ts +46 -0
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023-present Bytedance, Inc. and its affiliates.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
2
|
+
import { Suspense, memo } from "react";
|
3
|
+
import { matchRoutes, useLocation } from "react-router-dom";
|
4
|
+
import siteData from "virtual-site-data";
|
5
|
+
import { normalizeRoutePath } from "./utils";
|
6
|
+
import { useViewTransition } from "./hooks";
|
7
|
+
const { routes } = process.env.__SSR__ ? require("virtual-routes-ssr") : require("virtual-routes");
|
8
|
+
function TransitionContentImpl(props) {
|
9
|
+
let element = props.el;
|
10
|
+
if (siteData?.themeConfig?.enableContentAnimation) {
|
11
|
+
element = useViewTransition(props.el);
|
12
|
+
}
|
13
|
+
return element;
|
14
|
+
}
|
15
|
+
const TransitionContent = memo(
|
16
|
+
TransitionContentImpl,
|
17
|
+
(prevProps, nextProps) => prevProps.el === nextProps.el
|
18
|
+
);
|
19
|
+
const Content = ({ fallback = /* @__PURE__ */ jsx(Fragment, {}) }) => {
|
20
|
+
const { pathname } = useLocation();
|
21
|
+
const matched = matchRoutes(routes, normalizeRoutePath(pathname));
|
22
|
+
if (!matched) {
|
23
|
+
return /* @__PURE__ */ jsx("div", {});
|
24
|
+
}
|
25
|
+
const routesElement = matched[0].route.element;
|
26
|
+
if (!process.env.__IS_REACT_18__ && process.env.__SSR__) {
|
27
|
+
return routesElement;
|
28
|
+
}
|
29
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback, children: /* @__PURE__ */ jsx(TransitionContent, { el: routesElement }) });
|
30
|
+
};
|
31
|
+
export {
|
32
|
+
Content
|
33
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
2
|
+
import { useEffect, useState } from "react";
|
3
|
+
function NoSSR(props) {
|
4
|
+
const { children } = props;
|
5
|
+
const [isMounted, setIsMounted] = useState(false);
|
6
|
+
useEffect(() => {
|
7
|
+
setIsMounted(true);
|
8
|
+
}, []);
|
9
|
+
if (!isMounted) {
|
10
|
+
return null;
|
11
|
+
} else {
|
12
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
13
|
+
}
|
14
|
+
}
|
15
|
+
export {
|
16
|
+
NoSSR
|
17
|
+
};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
/// <reference types="react" />
|
2
|
+
import { PageData } from '@rspress/shared';
|
3
|
+
declare global {
|
4
|
+
interface Window {
|
5
|
+
__MODERN_PAGE_DATA__: any;
|
6
|
+
}
|
7
|
+
}
|
8
|
+
interface IDataContext {
|
9
|
+
data: PageData;
|
10
|
+
setData?: (data: PageData) => void;
|
11
|
+
}
|
12
|
+
interface IThemeContext {
|
13
|
+
theme: 'light' | 'dark';
|
14
|
+
setTheme?: (theme: 'light' | 'dark') => void;
|
15
|
+
}
|
16
|
+
export declare const DataContext: import("react").Context<IDataContext>;
|
17
|
+
export declare const ThemeContext: import("react").Context<IThemeContext>;
|
18
|
+
export declare function usePageData(): PageData;
|
19
|
+
export declare function useLang(): string;
|
20
|
+
export declare function useVersion(): string;
|
21
|
+
export declare function useDark(): boolean;
|
22
|
+
export declare function useI18n<T = Record<string, Record<string, string>>>(): (key: keyof T) => any;
|
23
|
+
declare global {
|
24
|
+
interface Document {
|
25
|
+
startViewTransition: (callback: () => void) => void;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
/**
|
29
|
+
* There is a pitfall.
|
30
|
+
* I was working on the navigation between pages with hash. eg. from `guide/start` -> `config/repress#nav`
|
31
|
+
* I need a time to dispatch an event so that the sideEffect.ts would know that
|
32
|
+
* the dom is attached to the browser. Otherwise the scroll position and the
|
33
|
+
* animation would be incorrect. You can search for `RspressReloadContent` in this codebase
|
34
|
+
* to findout the logic that are consuming the event.
|
35
|
+
* The reason I didn't write it here is that I hope the logic of handling scroll and position
|
36
|
+
* could be in one place so that people wouldn't be confused.
|
37
|
+
*/
|
38
|
+
export declare function useViewTransition(dom: any): any;
|
39
|
+
export {};
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import { createContext, useContext, useLayoutEffect, useState } from "react";
|
2
|
+
import i18nTextData from "virtual-i18n-text";
|
3
|
+
import { flushSync } from "react-dom";
|
4
|
+
const DataContext = createContext({});
|
5
|
+
const ThemeContext = createContext({});
|
6
|
+
function usePageData() {
|
7
|
+
const ctx = useContext(DataContext);
|
8
|
+
return ctx.data;
|
9
|
+
}
|
10
|
+
function useLang() {
|
11
|
+
const ctx = useContext(DataContext);
|
12
|
+
return ctx.data.page.lang || "";
|
13
|
+
}
|
14
|
+
function useVersion() {
|
15
|
+
const ctx = useContext(DataContext);
|
16
|
+
return ctx.data.page.version || "";
|
17
|
+
}
|
18
|
+
function useDark() {
|
19
|
+
const ctx = useContext(ThemeContext);
|
20
|
+
return ctx.theme === "dark";
|
21
|
+
}
|
22
|
+
function useI18n() {
|
23
|
+
const lang = useLang();
|
24
|
+
return (key) => i18nTextData[key][lang];
|
25
|
+
}
|
26
|
+
function useViewTransition(dom) {
|
27
|
+
const [element, setElement] = useState(dom);
|
28
|
+
useLayoutEffect(() => {
|
29
|
+
if (document.startViewTransition && element !== dom) {
|
30
|
+
document.startViewTransition(() => {
|
31
|
+
flushSync(() => {
|
32
|
+
setElement(dom);
|
33
|
+
});
|
34
|
+
window.dispatchEvent(new Event("RspressReloadContent"));
|
35
|
+
});
|
36
|
+
} else {
|
37
|
+
flushSync(() => {
|
38
|
+
setElement(dom);
|
39
|
+
});
|
40
|
+
window.dispatchEvent(new Event("RspressReloadContent"));
|
41
|
+
}
|
42
|
+
}, [dom]);
|
43
|
+
return element;
|
44
|
+
}
|
45
|
+
export {
|
46
|
+
DataContext,
|
47
|
+
ThemeContext,
|
48
|
+
useDark,
|
49
|
+
useI18n,
|
50
|
+
useLang,
|
51
|
+
usePageData,
|
52
|
+
useVersion,
|
53
|
+
useViewTransition
|
54
|
+
};
|
@@ -0,0 +1,6 @@
|
|
1
|
+
export * from './hooks';
|
2
|
+
export * from './Content';
|
3
|
+
export { normalizeHrefInRuntime, normalizeImagePath, withBase, removeBase, addLeadingSlash, removeTrailingSlash, normalizeSlash, isProduction, normalizeRoutePath, isEqualPath } from './utils';
|
4
|
+
export { useLocation, useNavigate, matchRoutes, BrowserRouter } from 'react-router-dom';
|
5
|
+
export { Helmet } from 'react-helmet-async';
|
6
|
+
export { NoSSR } from './NoSSR';
|
@@ -0,0 +1,40 @@
|
|
1
|
+
export * from "./hooks";
|
2
|
+
export * from "./Content";
|
3
|
+
import {
|
4
|
+
normalizeHrefInRuntime,
|
5
|
+
normalizeImagePath,
|
6
|
+
withBase,
|
7
|
+
removeBase,
|
8
|
+
addLeadingSlash,
|
9
|
+
removeTrailingSlash,
|
10
|
+
normalizeSlash,
|
11
|
+
isProduction,
|
12
|
+
normalizeRoutePath,
|
13
|
+
isEqualPath
|
14
|
+
} from "./utils";
|
15
|
+
import {
|
16
|
+
useLocation,
|
17
|
+
useNavigate,
|
18
|
+
matchRoutes,
|
19
|
+
BrowserRouter
|
20
|
+
} from "react-router-dom";
|
21
|
+
import { Helmet } from "react-helmet-async";
|
22
|
+
import { NoSSR } from "./NoSSR";
|
23
|
+
export {
|
24
|
+
BrowserRouter,
|
25
|
+
Helmet,
|
26
|
+
NoSSR,
|
27
|
+
addLeadingSlash,
|
28
|
+
isEqualPath,
|
29
|
+
isProduction,
|
30
|
+
matchRoutes,
|
31
|
+
normalizeHrefInRuntime,
|
32
|
+
normalizeImagePath,
|
33
|
+
normalizeRoutePath,
|
34
|
+
normalizeSlash,
|
35
|
+
removeBase,
|
36
|
+
removeTrailingSlash,
|
37
|
+
useLocation,
|
38
|
+
useNavigate,
|
39
|
+
withBase
|
40
|
+
};
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { addLeadingSlash, removeTrailingSlash, normalizeSlash, isProduction } from '@rspress/shared';
|
2
|
+
export declare function normalizeRoutePath(routePath: string): string;
|
3
|
+
export declare function withBase(url?: string): string;
|
4
|
+
export declare function removeBase(url: string): string;
|
5
|
+
export declare function isEqualPath(a: string, b: string): boolean;
|
6
|
+
export declare function normalizeHrefInRuntime(a: string): string;
|
7
|
+
export declare function normalizeImagePath(imagePath: string): string;
|
8
|
+
export { addLeadingSlash, removeTrailingSlash, normalizeSlash, isProduction };
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import siteData from "virtual-site-data";
|
2
|
+
import {
|
3
|
+
addLeadingSlash,
|
4
|
+
removeTrailingSlash,
|
5
|
+
normalizeSlash,
|
6
|
+
isProduction,
|
7
|
+
normalizeHref,
|
8
|
+
withBase as rawWithBase,
|
9
|
+
removeBase as rawRemoveBase,
|
10
|
+
isExternalUrl
|
11
|
+
} from "@rspress/shared";
|
12
|
+
function normalizeRoutePath(routePath) {
|
13
|
+
return decodeURIComponent(routePath).replace(/\.html$/, "").replace(/\/index$/, "/");
|
14
|
+
}
|
15
|
+
function withBase(url = "/") {
|
16
|
+
return rawWithBase(url, siteData.base);
|
17
|
+
}
|
18
|
+
function removeBase(url) {
|
19
|
+
return rawRemoveBase(url, siteData.base);
|
20
|
+
}
|
21
|
+
function isEqualPath(a, b) {
|
22
|
+
return withBase(normalizeHrefInRuntime(a)) === withBase(normalizeHrefInRuntime(b));
|
23
|
+
}
|
24
|
+
function normalizeHrefInRuntime(a) {
|
25
|
+
const cleanUrls = Boolean(siteData?.route?.cleanUrls);
|
26
|
+
return normalizeHref(a, cleanUrls);
|
27
|
+
}
|
28
|
+
function normalizeImagePath(imagePath) {
|
29
|
+
if (isExternalUrl(imagePath)) {
|
30
|
+
return imagePath;
|
31
|
+
}
|
32
|
+
return withBase(imagePath);
|
33
|
+
}
|
34
|
+
export {
|
35
|
+
addLeadingSlash,
|
36
|
+
isEqualPath,
|
37
|
+
isProduction,
|
38
|
+
normalizeHrefInRuntime,
|
39
|
+
normalizeImagePath,
|
40
|
+
normalizeRoutePath,
|
41
|
+
normalizeSlash,
|
42
|
+
removeBase,
|
43
|
+
removeTrailingSlash,
|
44
|
+
withBase
|
45
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
{
|
2
|
+
"name": "@rspress/runtime",
|
3
|
+
"version": "0.0.0-nightly-20231026160444",
|
4
|
+
"description": "The Runtime of Rspress Documentation Framework",
|
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/rspress-runtime"
|
10
|
+
},
|
11
|
+
"license": "MIT",
|
12
|
+
"type": "module",
|
13
|
+
"jsnext:source": "./src/runtime/index.ts",
|
14
|
+
"types": "./dist/runtime/index.d.ts",
|
15
|
+
"main": "./dist/runtime/index.js",
|
16
|
+
"exports": {
|
17
|
+
".": {
|
18
|
+
"types": "./dist/runtime/index.d.ts",
|
19
|
+
"default": "./dist/runtime/index.js"
|
20
|
+
},
|
21
|
+
"./server": {
|
22
|
+
"default": "./server.js"
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"engines": {
|
26
|
+
"node": ">=14.17.6"
|
27
|
+
},
|
28
|
+
"eslintIgnore": [
|
29
|
+
"node_modules/",
|
30
|
+
"dist/"
|
31
|
+
],
|
32
|
+
"dependencies": {
|
33
|
+
"react": "^18.2.0",
|
34
|
+
"react-dom": "^18.2.0",
|
35
|
+
"react-helmet-async": "^1.3.0",
|
36
|
+
"react-router-dom": "^6.8.1",
|
37
|
+
"@rspress/shared": "0.0.0-nightly-20231026160444"
|
38
|
+
},
|
39
|
+
"devDependencies": {
|
40
|
+
"@modern-js/tsconfig": "2.38.0",
|
41
|
+
"@modern-js/types": "2.38.0",
|
42
|
+
"@types/jest": "^26.0.9",
|
43
|
+
"@types/react": "^18",
|
44
|
+
"@types/react-dom": "^18",
|
45
|
+
"husky": "^8",
|
46
|
+
"lint-staged": "~13.1.0",
|
47
|
+
"prettier": "^2.6.2",
|
48
|
+
"typescript": "^5"
|
49
|
+
},
|
50
|
+
"sideEffects": [
|
51
|
+
"*.css",
|
52
|
+
"*.less",
|
53
|
+
"*.sass",
|
54
|
+
"*.scss",
|
55
|
+
"**/virtual-global-styles.js",
|
56
|
+
"virtual-global-styles",
|
57
|
+
"./src/theme-default/styles/index.ts"
|
58
|
+
],
|
59
|
+
"files": [
|
60
|
+
"bin",
|
61
|
+
"dist",
|
62
|
+
"src",
|
63
|
+
"runtime.ts",
|
64
|
+
"server.js"
|
65
|
+
],
|
66
|
+
"publishConfig": {
|
67
|
+
"access": "public",
|
68
|
+
"provenance": true,
|
69
|
+
"registry": "https://registry.npmjs.org/"
|
70
|
+
},
|
71
|
+
"scripts": {
|
72
|
+
"dev": "modern build -w",
|
73
|
+
"build": "modern build",
|
74
|
+
"reset": "rimraf ./**/node_modules",
|
75
|
+
"lint": "modern lint",
|
76
|
+
"change": "modern change",
|
77
|
+
"bump": "modern bump",
|
78
|
+
"pre": "modern pre",
|
79
|
+
"change-status": "modern change-status",
|
80
|
+
"gen-release-note": "modern gen-release-note",
|
81
|
+
"release": "modern release",
|
82
|
+
"new": "modern new",
|
83
|
+
"test": "echo nothing",
|
84
|
+
"upgrade": "modern upgrade"
|
85
|
+
}
|
86
|
+
}
|
package/server.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { StaticRouter } from 'react-router-dom/server';
|
@@ -0,0 +1,45 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
3
|
+
import { ReactNode, Suspense, memo, ReactElement } from 'react';
|
4
|
+
import { matchRoutes, useLocation } from 'react-router-dom';
|
5
|
+
import siteData from 'virtual-site-data';
|
6
|
+
import { normalizeRoutePath } from './utils';
|
7
|
+
import { useViewTransition } from './hooks';
|
8
|
+
|
9
|
+
const { routes } = process.env.__SSR__
|
10
|
+
? (require('virtual-routes-ssr') as typeof import('virtual-routes-ssr'))
|
11
|
+
: (require('virtual-routes') as typeof import('virtual-routes'));
|
12
|
+
|
13
|
+
function TransitionContentImpl(props: { el: ReactElement }) {
|
14
|
+
let element = props.el;
|
15
|
+
if (siteData?.themeConfig?.enableContentAnimation) {
|
16
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
17
|
+
element = useViewTransition(props.el);
|
18
|
+
}
|
19
|
+
return element;
|
20
|
+
}
|
21
|
+
|
22
|
+
const TransitionContent = memo(
|
23
|
+
TransitionContentImpl,
|
24
|
+
(prevProps, nextProps) => prevProps.el === nextProps.el,
|
25
|
+
);
|
26
|
+
|
27
|
+
export const Content = ({ fallback = <></> }: { fallback?: ReactNode }) => {
|
28
|
+
const { pathname } = useLocation();
|
29
|
+
const matched = matchRoutes(routes, normalizeRoutePath(pathname));
|
30
|
+
if (!matched) {
|
31
|
+
return <div></div>;
|
32
|
+
}
|
33
|
+
const routesElement = matched[0].route.element;
|
34
|
+
|
35
|
+
// React 17 Suspense SSR is not supported
|
36
|
+
if (!process.env.__IS_REACT_18__ && process.env.__SSR__) {
|
37
|
+
return routesElement;
|
38
|
+
}
|
39
|
+
|
40
|
+
return (
|
41
|
+
<Suspense fallback={fallback}>
|
42
|
+
<TransitionContent el={routesElement} />
|
43
|
+
</Suspense>
|
44
|
+
);
|
45
|
+
};
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { useEffect, useState } from 'react';
|
2
|
+
|
3
|
+
export function NoSSR(props: { children: React.ReactNode }) {
|
4
|
+
const { children } = props;
|
5
|
+
const [isMounted, setIsMounted] = useState(false);
|
6
|
+
|
7
|
+
useEffect(() => {
|
8
|
+
setIsMounted(true);
|
9
|
+
}, []);
|
10
|
+
|
11
|
+
if (!isMounted) {
|
12
|
+
return null;
|
13
|
+
} else {
|
14
|
+
return <>{children}</>;
|
15
|
+
}
|
16
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
declare module 'virtual-routes' {
|
2
|
+
export { Route } from 'node/route/RouteService';
|
3
|
+
|
4
|
+
export const routes: Route[];
|
5
|
+
}
|
6
|
+
|
7
|
+
declare module 'virtual-routes-ssr' {
|
8
|
+
export { Route } from 'node/route/RouteService';
|
9
|
+
|
10
|
+
export const routes: Route[];
|
11
|
+
}
|
12
|
+
|
13
|
+
declare module 'virtual-site-data' {
|
14
|
+
import { SiteData, DefaultThemeConfig } from '@rspress/shared';
|
15
|
+
|
16
|
+
const data: SiteData<DefaultThemeConfig>;
|
17
|
+
export default data;
|
18
|
+
}
|
19
|
+
|
20
|
+
declare module 'virtual-i18n-text';
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import { createContext, useContext, useLayoutEffect, useState } from 'react';
|
2
|
+
import { PageData } from '@rspress/shared';
|
3
|
+
import i18nTextData from 'virtual-i18n-text';
|
4
|
+
import { flushSync } from 'react-dom';
|
5
|
+
|
6
|
+
// Type shim for window.__EDEN_PAGE_DATA__
|
7
|
+
declare global {
|
8
|
+
interface Window {
|
9
|
+
__MODERN_PAGE_DATA__: any;
|
10
|
+
}
|
11
|
+
}
|
12
|
+
interface IDataContext {
|
13
|
+
data: PageData;
|
14
|
+
setData?: (data: PageData) => void;
|
15
|
+
}
|
16
|
+
|
17
|
+
interface IThemeContext {
|
18
|
+
theme: 'light' | 'dark';
|
19
|
+
setTheme?: (theme: 'light' | 'dark') => void;
|
20
|
+
}
|
21
|
+
|
22
|
+
export const DataContext = createContext({} as IDataContext);
|
23
|
+
|
24
|
+
export const ThemeContext = createContext({} as IThemeContext);
|
25
|
+
|
26
|
+
export function usePageData() {
|
27
|
+
const ctx = useContext(DataContext);
|
28
|
+
return ctx.data;
|
29
|
+
}
|
30
|
+
|
31
|
+
export function useLang(): string {
|
32
|
+
const ctx = useContext(DataContext);
|
33
|
+
return ctx.data.page.lang || '';
|
34
|
+
}
|
35
|
+
|
36
|
+
export function useVersion(): string {
|
37
|
+
const ctx = useContext(DataContext);
|
38
|
+
return ctx.data.page.version || '';
|
39
|
+
}
|
40
|
+
|
41
|
+
export function useDark() {
|
42
|
+
const ctx = useContext(ThemeContext);
|
43
|
+
return ctx.theme === 'dark';
|
44
|
+
}
|
45
|
+
|
46
|
+
export function useI18n<T = Record<string, Record<string, string>>>() {
|
47
|
+
const lang = useLang();
|
48
|
+
|
49
|
+
return (key: keyof T) => i18nTextData[key][lang];
|
50
|
+
}
|
51
|
+
|
52
|
+
declare global {
|
53
|
+
interface Document {
|
54
|
+
startViewTransition: (callback: () => void) => void;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* There is a pitfall.
|
60
|
+
* I was working on the navigation between pages with hash. eg. from `guide/start` -> `config/repress#nav`
|
61
|
+
* I need a time to dispatch an event so that the sideEffect.ts would know that
|
62
|
+
* the dom is attached to the browser. Otherwise the scroll position and the
|
63
|
+
* animation would be incorrect. You can search for `RspressReloadContent` in this codebase
|
64
|
+
* to findout the logic that are consuming the event.
|
65
|
+
* The reason I didn't write it here is that I hope the logic of handling scroll and position
|
66
|
+
* could be in one place so that people wouldn't be confused.
|
67
|
+
*/
|
68
|
+
export function useViewTransition(dom) {
|
69
|
+
/**
|
70
|
+
* use a pesudo element to hold the actual JSX element so we can schedule the
|
71
|
+
* update later in sync
|
72
|
+
*/
|
73
|
+
const [element, setElement] = useState(dom);
|
74
|
+
|
75
|
+
useLayoutEffect(() => {
|
76
|
+
if (document.startViewTransition && element !== dom) {
|
77
|
+
/**
|
78
|
+
* the browser will take a screenshot here
|
79
|
+
*/
|
80
|
+
document.startViewTransition(() => {
|
81
|
+
/**
|
82
|
+
* react will batch all the updates in callback and flush it sync
|
83
|
+
*/
|
84
|
+
flushSync(() => {
|
85
|
+
setElement(dom);
|
86
|
+
});
|
87
|
+
/**
|
88
|
+
* react flushed the dom to browser
|
89
|
+
* and the browser will start the animation
|
90
|
+
*/
|
91
|
+
/**
|
92
|
+
* dispatchEvent for several logic
|
93
|
+
*/
|
94
|
+
window.dispatchEvent(new Event('RspressReloadContent'));
|
95
|
+
});
|
96
|
+
} else {
|
97
|
+
flushSync(() => {
|
98
|
+
setElement(dom);
|
99
|
+
});
|
100
|
+
/**
|
101
|
+
* dispatchEvent for several logic
|
102
|
+
*/
|
103
|
+
window.dispatchEvent(new Event('RspressReloadContent'));
|
104
|
+
}
|
105
|
+
}, [dom]);
|
106
|
+
/**
|
107
|
+
* take this element to the actual VDOM tree
|
108
|
+
*/
|
109
|
+
return element;
|
110
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
export * from './hooks';
|
2
|
+
export * from './Content';
|
3
|
+
export {
|
4
|
+
normalizeHrefInRuntime,
|
5
|
+
normalizeImagePath,
|
6
|
+
withBase,
|
7
|
+
removeBase,
|
8
|
+
addLeadingSlash,
|
9
|
+
removeTrailingSlash,
|
10
|
+
normalizeSlash,
|
11
|
+
isProduction,
|
12
|
+
normalizeRoutePath,
|
13
|
+
isEqualPath,
|
14
|
+
} from './utils';
|
15
|
+
export {
|
16
|
+
useLocation,
|
17
|
+
useNavigate,
|
18
|
+
matchRoutes,
|
19
|
+
BrowserRouter,
|
20
|
+
} from 'react-router-dom';
|
21
|
+
export { Helmet } from 'react-helmet-async';
|
22
|
+
export { NoSSR } from './NoSSR';
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import siteData from 'virtual-site-data';
|
2
|
+
import {
|
3
|
+
addLeadingSlash,
|
4
|
+
removeTrailingSlash,
|
5
|
+
normalizeSlash,
|
6
|
+
isProduction,
|
7
|
+
normalizeHref,
|
8
|
+
withBase as rawWithBase,
|
9
|
+
removeBase as rawRemoveBase,
|
10
|
+
isExternalUrl,
|
11
|
+
} from '@rspress/shared';
|
12
|
+
|
13
|
+
export function normalizeRoutePath(routePath: string) {
|
14
|
+
return decodeURIComponent(routePath)
|
15
|
+
.replace(/\.html$/, '')
|
16
|
+
.replace(/\/index$/, '/');
|
17
|
+
}
|
18
|
+
|
19
|
+
export function withBase(url = '/'): string {
|
20
|
+
return rawWithBase(url, siteData.base);
|
21
|
+
}
|
22
|
+
|
23
|
+
export function removeBase(url: string): string {
|
24
|
+
return rawRemoveBase(url, siteData.base);
|
25
|
+
}
|
26
|
+
|
27
|
+
export function isEqualPath(a: string, b: string) {
|
28
|
+
return (
|
29
|
+
withBase(normalizeHrefInRuntime(a)) === withBase(normalizeHrefInRuntime(b))
|
30
|
+
);
|
31
|
+
}
|
32
|
+
|
33
|
+
export function normalizeHrefInRuntime(a: string) {
|
34
|
+
const cleanUrls = Boolean(siteData?.route?.cleanUrls);
|
35
|
+
return normalizeHref(a, cleanUrls);
|
36
|
+
}
|
37
|
+
|
38
|
+
export function normalizeImagePath(imagePath: string) {
|
39
|
+
if (isExternalUrl(imagePath)) {
|
40
|
+
return imagePath;
|
41
|
+
}
|
42
|
+
|
43
|
+
return withBase(imagePath);
|
44
|
+
}
|
45
|
+
|
46
|
+
export { addLeadingSlash, removeTrailingSlash, normalizeSlash, isProduction };
|