dumi 2.0.0-beta.3 → 2.0.0-beta.6

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.
Files changed (33) hide show
  1. package/dist/client/theme-api/DumiDemo.js +1 -1
  2. package/dist/client/theme-api/DumiPage.d.ts +4 -0
  3. package/dist/client/theme-api/DumiPage.js +39 -0
  4. package/dist/client/theme-api/index.d.ts +2 -1
  5. package/dist/client/theme-api/index.js +2 -1
  6. package/dist/client/theme-api/types.d.ts +14 -1
  7. package/dist/client/theme-api/useSidebarData.js +4 -2
  8. package/dist/features/assets.d.ts +12 -0
  9. package/dist/features/assets.js +67 -0
  10. package/dist/features/compile.js +5 -1
  11. package/dist/features/configPlugins/schema.js +1 -1
  12. package/dist/features/meta.d.ts +1 -0
  13. package/dist/features/meta.js +42 -19
  14. package/dist/features/parser.js +14 -9
  15. package/dist/features/routes.js +14 -4
  16. package/dist/features/tabs.d.ts +14 -0
  17. package/dist/features/tabs.js +128 -0
  18. package/dist/loaders/markdown/index.d.ts +2 -1
  19. package/dist/loaders/markdown/index.js +11 -11
  20. package/dist/loaders/markdown/transformer/rehypeDemo.js +9 -3
  21. package/dist/loaders/markdown/transformer/rehypeJsxify.js +1 -1
  22. package/dist/loaders/markdown/transformer/remarkMeta.js +7 -2
  23. package/dist/preset.js +3 -1
  24. package/dist/registerMethods.js +6 -1
  25. package/dist/types.d.ts +14 -2
  26. package/package.json +1 -1
  27. package/theme-default/locales/en-US.json +2 -1
  28. package/theme-default/locales/zh-CN.json +2 -1
  29. package/theme-default/slots/Content/index.less +1 -1
  30. package/theme-default/slots/ContentTabs/index.d.ts +11 -0
  31. package/theme-default/slots/ContentTabs/index.js +35 -0
  32. package/theme-default/slots/ContentTabs/index.less +57 -0
  33. package/theme-default/styles/variables.less +1 -0
@@ -14,6 +14,6 @@ export var DumiDemo = function DumiDemo(props) {
14
14
  return /*#__PURE__*/React.createElement(Previewer, _extends({
15
15
  asset: asset,
16
16
  demoUrl: // allow user override demoUrl by frontmatter
17
- props.previewerProps.demoUrl || "".concat(SP_ROUTE_PREFIX, "demos/").concat(props.demo.id)
17
+ props.previewerProps.demoUrl || "/".concat(SP_ROUTE_PREFIX, "demos/").concat(props.demo.id)
18
18
  }, props.previewerProps), /*#__PURE__*/createElement(component));
19
19
  };
@@ -0,0 +1,4 @@
1
+ import { type FC, type ReactNode } from 'react';
2
+ export declare const DumiPage: FC<{
3
+ children: ReactNode;
4
+ }>;
@@ -0,0 +1,39 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+
5
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
6
+
7
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
8
+
9
+ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
10
+
11
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
12
+
13
+ import { useRouteMeta, useSiteData } from 'dumi';
14
+ import ContentTabs from 'dumi/theme/slots/ContentTabs';
15
+ import React, { useEffect, useState } from 'react';
16
+ export var DumiPage = function DumiPage(props) {
17
+ var _useRouteMeta = useRouteMeta(),
18
+ tabs = _useRouteMeta.tabs;
19
+
20
+ var _useState = useState(),
21
+ _useState2 = _slicedToArray(_useState, 2),
22
+ tab = _useState2[0],
23
+ setTab = _useState2[1];
24
+
25
+ var _useSiteData = useSiteData(),
26
+ setLoading = _useSiteData.setLoading; // update loading status when page loaded
27
+
28
+
29
+ useEffect(function () {
30
+ setLoading(false);
31
+ }, []);
32
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ContentTabs, {
33
+ tabs: tabs,
34
+ tabKey: tab === null || tab === void 0 ? void 0 : tab.key,
35
+ onChange: function onChange(val) {
36
+ setTab(val);
37
+ }
38
+ }), tab ? /*#__PURE__*/React.createElement(tab.components.default) : props.children);
39
+ };
@@ -1,7 +1,8 @@
1
- export { useIntl } from 'react-intl';
1
+ export { createIntlCache, defineMessages, FormattedDate, FormattedDateParts, FormattedDisplayName, FormattedList, FormattedMessage, FormattedNumber, FormattedNumberParts, FormattedPlural, FormattedRelativeTime, FormattedTime, FormattedTimeParts, injectIntl, IntlContext, IntlProvider, RawIntlProvider, useIntl, } from 'react-intl';
2
2
  export { useSiteData } from './context';
3
3
  export { DumiDemo } from './DumiDemo';
4
4
  export { DumiDemoGrid } from './DumiDemoGrid';
5
+ export { DumiPage } from './DumiPage';
5
6
  export type { IPreviewerProps } from './types';
6
7
  export { useAtomAssets } from './useAtomAssets';
7
8
  export { useLocale } from './useLocale';
@@ -1,7 +1,8 @@
1
- export { useIntl } from 'react-intl';
1
+ export { createIntlCache, defineMessages, FormattedDate, FormattedDateParts, FormattedDisplayName, FormattedList, FormattedMessage, FormattedNumber, FormattedNumberParts, FormattedPlural, FormattedRelativeTime, FormattedTime, FormattedTimeParts, injectIntl, IntlContext, IntlProvider, RawIntlProvider, useIntl } from 'react-intl';
2
2
  export { useSiteData } from "./context";
3
3
  export { DumiDemo } from "./DumiDemo";
4
4
  export { DumiDemoGrid } from "./DumiDemoGrid";
5
+ export { DumiPage } from "./DumiPage";
5
6
  export { useAtomAssets } from "./useAtomAssets";
6
7
  export { useLocale } from "./useLocale";
7
8
  export { useNavData } from "./useNavData";
@@ -1,5 +1,5 @@
1
1
  import type { ExampleBlockAsset } from 'dumi-assets-types';
2
- import type { ReactNode } from 'react';
2
+ import type { ComponentType, ReactNode } from 'react';
3
3
  export interface IPreviewerProps {
4
4
  /**
5
5
  * title of current demo
@@ -68,6 +68,17 @@ export interface IRouteMeta {
68
68
  depth: number;
69
69
  title: string;
70
70
  }[];
71
+ tabs?: {
72
+ key: string;
73
+ components: {
74
+ default: ComponentType;
75
+ Extra: ComponentType;
76
+ Action: ComponentType;
77
+ };
78
+ frontmatter: Omit<IRouteMeta['frontmatter'], 'description' | 'keywords' | 'nav' | 'group'>;
79
+ toc: IRouteMeta['toc'];
80
+ [key: string]: any;
81
+ }[];
71
82
  [key: string]: any;
72
83
  }
73
84
  declare type IBasicLocale = {
@@ -88,6 +99,8 @@ export interface INavItem {
88
99
  interface ISidebarItem {
89
100
  title: string;
90
101
  link: string;
102
+ order: number;
103
+ frontmatter: IRouteMeta['frontmatter'];
91
104
  [key: string]: any;
92
105
  }
93
106
  export interface ISidebarGroup {
@@ -71,7 +71,8 @@ export var useFullSidebarData = function useFullSidebarData() {
71
71
  children: [].concat(_toConsumableArray(((_ret$parentPath$title2 = ret[parentPath][titleKey]) === null || _ret$parentPath$title2 === void 0 ? void 0 : _ret$parentPath$title2.children) || []), [{
72
72
  title: route.meta.frontmatter.title,
73
73
  link: "/".concat(route.path),
74
- order: route.meta.frontmatter.order || 0
74
+ order: route.meta.frontmatter.order || 0,
75
+ frontmatter: route.meta.frontmatter
75
76
  }])
76
77
  };
77
78
  }
@@ -125,7 +126,8 @@ export var useSidebarData = function useSidebarData() {
125
126
  // /a/b => /a
126
127
  // /en-US/a => /en-US/a
127
128
  // /en-US/a/b => /en-US/a
129
+ // /en-US/a/b/ => /en-US/a (also strip trailing /)
128
130
 
129
- var parentPath = clearPath ? pathname.replace(/(\/[^/]+)(\/[^/]+)$/, '$1') : pathname;
131
+ var parentPath = clearPath ? pathname.replace(/(\/[^/]+)(\/[^/]+\/?)$/, '$1') : pathname;
130
132
  return parentPath ? sidebar[parentPath] : [];
131
133
  };
@@ -0,0 +1,12 @@
1
+ import type { IApi } from "../types";
2
+ import type { ExampleAsset } from 'dumi-assets-types';
3
+ declare const examples: ExampleAsset[];
4
+ /**
5
+ * internal function to add example assets
6
+ */
7
+ export declare function addExampleAssets(data: typeof examples): void;
8
+ /**
9
+ * plugin for generate assets.json
10
+ */
11
+ declare const _default: (api: IApi) => void;
12
+ export default _default;
@@ -0,0 +1,67 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/features/assets.ts
23
+ var assets_exports = {};
24
+ __export(assets_exports, {
25
+ addExampleAssets: () => addExampleAssets,
26
+ default: () => assets_default
27
+ });
28
+ module.exports = __toCommonJS(assets_exports);
29
+ var import_fs = __toESM(require("fs"));
30
+ var import_path = __toESM(require("path"));
31
+ var import_plugin_utils = require("umi/plugin-utils");
32
+ var examples = [];
33
+ function addExampleAssets(data) {
34
+ examples.push(...data);
35
+ }
36
+ var assets_default = (api) => {
37
+ api.describe({
38
+ config: {
39
+ schema: (Joi) => Joi.object()
40
+ },
41
+ enableBy: ({ env }) => env === "production" && Boolean(api.args.assets)
42
+ });
43
+ api.onBuildComplete(async () => {
44
+ const { components } = await api.service.atomParser.parse();
45
+ const assets = await api.applyPlugins({
46
+ key: "modifyAssetsMetadata",
47
+ initialValue: {
48
+ name: api.config.themeConfig.title || api.pkg.name,
49
+ npmPackageName: api.pkg.name,
50
+ version: api.pkg.version,
51
+ description: api.pkg.description,
52
+ logo: api.config.themeConfig.logo,
53
+ homepage: api.pkg.homepage,
54
+ repository: api.pkg.repository,
55
+ assets: {
56
+ atoms: Object.values(components),
57
+ examples: import_plugin_utils.lodash.uniqBy(examples, "id")
58
+ }
59
+ }
60
+ });
61
+ import_fs.default.writeFileSync(import_path.default.join(api.cwd, "assets.json"), JSON.stringify(assets, null, 2), "utf-8");
62
+ });
63
+ };
64
+ // Annotate the CommonJS export names for ESM import in node:
65
+ 0 && (module.exports = {
66
+ addExampleAssets
67
+ });
@@ -26,6 +26,7 @@ __export(compile_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(compile_exports);
28
28
  var import_react = __toESM(require("../techStacks/react"));
29
+ var import_assets = require("./assets");
29
30
  var compile_default = (api) => {
30
31
  api.register({
31
32
  key: "registerTechStack",
@@ -52,7 +53,10 @@ var compile_default = (api) => {
52
53
  };
53
54
  memo.module.rule("dumi-md").type("javascript/auto").test(/\.md$/).oneOf("md-meta").resourceQuery(/meta$/).use("babel-loader").loader(babelInUmi.loader).options(babelInUmi.options).end().use("md-meta-loader").loader(loaderPath).options({
54
55
  ...loaderBaseOpts,
55
- mode: "meta"
56
+ mode: "meta",
57
+ onResolveDemos(demos) {
58
+ (0, import_assets.addExampleAssets)(demos.map(({ asset }) => asset));
59
+ }
56
60
  }).end().end().oneOf("md").use("babel-loader").loader(babelInUmi.loader).options(babelInUmi.options).end().use("md-loader").loader(loaderPath).options({
57
61
  ...loaderBaseOpts,
58
62
  builtins: api.service.themeData.builtins
@@ -28,7 +28,7 @@ function getUnifiedPluginSchema(Joi) {
28
28
  function getSchemas() {
29
29
  return {
30
30
  resolve: (Joi) => Joi.object({
31
- docDirs: Joi.array().items(Joi.string()).optional(),
31
+ docDirs: Joi.array().items(Joi.alternatives(Joi.string(), Joi.object({ dir: Joi.string(), type: Joi.string().optional() }))).optional(),
32
32
  entityDirs: Joi.array().items(Joi.object({ type: Joi.string(), dir: Joi.string() })).optional(),
33
33
  codeBlockMode: Joi.string().valid("active", "passive").optional(),
34
34
  entryFile: Joi.string().optional()
@@ -1,4 +1,5 @@
1
1
  import type { IApi } from "../types";
2
+ export declare const TABS_META_PATH = "dumi/meta/tabs.ts";
2
3
  export declare const ATOMS_META_PATH = "dumi/meta/atoms.ts";
3
4
  declare const _default: (api: IApi) => void;
4
5
  export default _default;
@@ -20,19 +20,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var meta_exports = {};
21
21
  __export(meta_exports, {
22
22
  ATOMS_META_PATH: () => ATOMS_META_PATH,
23
+ TABS_META_PATH: () => TABS_META_PATH,
23
24
  default: () => meta_default
24
25
  });
25
26
  module.exports = __toCommonJS(meta_exports);
26
27
  var import_plugin_utils = require("umi/plugin-utils");
28
+ var import_tabs = require("./tabs");
29
+ var TABS_META_PATH = "dumi/meta/tabs.ts";
27
30
  var ATOMS_META_PATH = "dumi/meta/atoms.ts";
28
31
  var meta_default = (api) => {
29
- const routeFiles = [];
32
+ const metaFiles = [];
30
33
  api.modifyRoutes((routes) => {
31
- routeFiles.length = 0;
34
+ metaFiles.length = 0;
32
35
  Object.values(routes).forEach((route) => {
33
- if (!route.isLayout && !/\*|:/.test(route.path)) {
34
- routeFiles.push({
35
- index: routeFiles.length,
36
+ if (!route.isLayout && !/\*|:/.test(route.path) && !(0, import_tabs.isTabRouteFile)(route.file)) {
37
+ metaFiles.push({
38
+ index: metaFiles.length,
36
39
  file: route.file,
37
40
  id: route.id
38
41
  });
@@ -40,7 +43,7 @@ var meta_default = (api) => {
40
43
  });
41
44
  return routes;
42
45
  });
43
- api.onGenerateFiles(() => {
46
+ api.onGenerateFiles(async () => {
44
47
  api.writeTmpFile({
45
48
  noPluginDir: true,
46
49
  path: ATOMS_META_PATH,
@@ -49,32 +52,51 @@ var meta_default = (api) => {
49
52
  api.writeTmpFile({
50
53
  noPluginDir: true,
51
54
  path: "dumi/meta/index.ts",
52
- content: import_plugin_utils.Mustache.render(`{{#routeFiles}}
55
+ content: import_plugin_utils.Mustache.render(`{{#metaFiles}}
53
56
  import { demos as d{{{index}}}, frontmatter as fm{{{index}}}, toc as toc{{{index}}} } from '{{{file}}}?type=meta';
54
- {{/routeFiles}}
57
+ {{/metaFiles}}
55
58
 
56
59
  export { components } from './atoms';
60
+ export { tabs } from './tabs';
57
61
 
58
62
  export const demos = {
59
- {{#routeFiles}}
63
+ {{#metaFiles}}
60
64
  ...d{{{index}}},
61
- {{/routeFiles}}
65
+ {{/metaFiles}}
62
66
  };
63
67
 
64
- export const routesMeta = {
65
- {{#routeFiles}}
66
- '{{{id}}}': { frontmatter: fm{{{index}}}, toc: toc{{{index}}} },
67
- {{/routeFiles}}
68
- }`, { routeFiles })
68
+ export const filesMeta = {
69
+ {{#metaFiles}}
70
+ '{{{id}}}': {
71
+ frontmatter: fm{{{index}}},
72
+ toc: toc{{{index}}},
73
+ {{#tabs}}
74
+ tabs: {{{tabs}}},
75
+ {{/tabs}}
76
+ },
77
+ {{/metaFiles}}
78
+ }`, {
79
+ metaFiles: await api.applyPlugins({
80
+ type: api.ApplyPluginsType.modify,
81
+ key: "dumi.modifyMetaFiles",
82
+ initialValue: metaFiles
83
+ })
84
+ })
69
85
  });
70
86
  api.writeTmpFile({
71
87
  noPluginDir: true,
72
88
  path: "dumi/meta/runtime.ts",
73
- content: `import { routesMeta } from '.';
89
+ content: `import { filesMeta, tabs } from '.';
74
90
  export const patchRoutes = ({ routes }) => {
75
91
  Object.values(routes).forEach((route) => {
76
- if (routesMeta[route.id]) {
77
- route.meta = { ...route.meta, ...routesMeta[route.id] };
92
+ if (filesMeta[route.id]) {
93
+ route.meta = { ...route.meta, ...filesMeta[route.id] };
94
+
95
+ // apply real tab data from id
96
+ route.meta.tabs = route.meta.tabs?.map(id => ({
97
+ ...tabs[id],
98
+ ...filesMeta[id],
99
+ }));
78
100
  }
79
101
  });
80
102
  }
@@ -85,5 +107,6 @@ export const patchRoutes = ({ routes }) => {
85
107
  };
86
108
  // Annotate the CommonJS export names for ESM import in node:
87
109
  0 && (module.exports = {
88
- ATOMS_META_PATH
110
+ ATOMS_META_PATH,
111
+ TABS_META_PATH
89
112
  });
@@ -25,7 +25,7 @@ __export(parser_exports, {
25
25
  default: () => parser_default
26
26
  });
27
27
  module.exports = __toCommonJS(parser_exports);
28
- var import_atom = __toESM(require("../assetParsers/atom"));
28
+ var import_assert = __toESM(require("assert"));
29
29
  var import_meta = require("./meta");
30
30
  var parser_default = (api) => {
31
31
  const writeAtomsMetaFile = (data) => {
@@ -35,17 +35,22 @@ var parser_default = (api) => {
35
35
  content: `export const components = ${JSON.stringify(data.components, null, 2)};`
36
36
  });
37
37
  };
38
- api.describe({ key: "apiParser", enableBy: api.EnableBy.config });
39
- api.modifyDefaultConfig((memo) => {
40
- var _a;
41
- if (!((_a = api.userConfig.resolve) == null ? void 0 : _a.entryFile)) {
42
- api.logger.error("`resolve.entryFile` must be configured when `apiParser` enable");
43
- process.exit(1);
38
+ api.describe({
39
+ key: "apiParser",
40
+ enableBy: api.EnableBy.config,
41
+ config: {
42
+ schema: (Joi) => Joi.object()
44
43
  }
44
+ });
45
+ api.modifyDefaultConfig((memo) => {
46
+ var _a, _b;
47
+ (0, import_assert.default)((_a = api.userConfig.resolve) == null ? void 0 : _a.entryFile, "`resolve.entryFile` must be configured when `apiParser` enable");
48
+ (0, import_assert.default)((_b = api.pkg.devDependencies) == null ? void 0 : _b["typescript"], "typescript must be installed when `apiParser` enable");
45
49
  return memo;
46
50
  });
47
- api.onStart(() => {
48
- api.service.atomParser = new import_atom.default({
51
+ api.onStart(async () => {
52
+ const { default: AtomAssetsParser } = await import("../assetParsers/atom");
53
+ api.service.atomParser = new AtomAssetsParser({
49
54
  entryFile: api.config.resolve.entryFile,
50
55
  resolveDir: api.cwd
51
56
  });
@@ -32,6 +32,9 @@ var import_path = __toESM(require("path"));
32
32
  var import_pluralize = require("pluralize");
33
33
  var import_plugin_utils = require("umi/plugin-utils");
34
34
  var CTX_LAYOUT_ID = "dumi-context-layout";
35
+ function normalizeDocDir(docDir) {
36
+ return typeof docDir === "object" ? docDir : { dir: docDir };
37
+ }
35
38
  function localizeUmiRoute(route, locales) {
36
39
  const locale = locales.find((locale2) => route.path.endsWith(`/${locale2.id}`) && import_path.default.parse(route.file).name.endsWith(`.${locale2.id}`));
37
40
  if (locale) {
@@ -44,9 +47,11 @@ function localizeUmiRoute(route, locales) {
44
47
  var routes_default = (api) => {
45
48
  var _a, _b, _c;
46
49
  const extraWatchPaths = [
47
- ...((_b = (_a = api.userConfig.resolve) == null ? void 0 : _a.entityDirs) == null ? void 0 : _b.map(({ dir }) => dir)) || ["docs"],
48
- ...((_c = api.userConfig.resolve) == null ? void 0 : _c.docDirs) || []
49
- ].map((dir) => import_path.default.join(api.cwd, dir, "**/*.md"));
50
+ ...((_a = api.userConfig.resolve) == null ? void 0 : _a.entityDirs) || [],
51
+ ...((_c = (_b = api.userConfig.resolve) == null ? void 0 : _b.docDirs) == null ? void 0 : _c.map(normalizeDocDir)) || [
52
+ { dir: "docs" }
53
+ ]
54
+ ].map(({ dir }) => import_path.default.join(api.cwd, dir, "**/*.md"));
50
55
  const pagesDir = import_path.default.join(api.cwd, ".dumi/pages");
51
56
  api.describe({ key: "dumi:routes" });
52
57
  api.addTmpGenerateWatcherPaths(() => extraWatchPaths);
@@ -110,7 +115,7 @@ var routes_default = (api) => {
110
115
  route.file = import_path.default.resolve(pagesDir, route.file);
111
116
  routes[route.id] = route;
112
117
  });
113
- docDirs.forEach((dir) => {
118
+ docDirs.map(normalizeDocDir).forEach(({ type, dir }) => {
114
119
  const base = import_path.default.join(api.cwd, dir);
115
120
  const dirRoutes = (0, import_core.getConventionRoutes)({
116
121
  base,
@@ -119,6 +124,11 @@ var routes_default = (api) => {
119
124
  Object.entries(dirRoutes).forEach(([key, route]) => {
120
125
  route.id = `${dir}/${key}`;
121
126
  route.parentId = docLayoutId;
127
+ if (type) {
128
+ const pluralType = (0, import_pluralize.plural)(type);
129
+ route.path = `${pluralType}/${route.path}`.replace(/\/+$/, "/");
130
+ route.absPath = `/${route.path}`;
131
+ }
122
132
  route.file = import_path.default.resolve(base, route.file);
123
133
  localizeUmiRoute(route, api.config.locales);
124
134
  routes[route.id] = route;
@@ -0,0 +1,14 @@
1
+ import type { IApi } from "../types";
2
+ export interface IContentTab {
3
+ key: string;
4
+ id?: string;
5
+ test?: RegExp;
6
+ component: string;
7
+ }
8
+ export declare function isTabRouteFile(file: string): boolean;
9
+ export declare function getTabKeyFromFile(file: string): string;
10
+ /**
11
+ * plugin for add conventional tab and plugin tab into page content
12
+ */
13
+ declare const _default: (api: IApi) => void;
14
+ export default _default;
@@ -0,0 +1,128 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/features/tabs.ts
23
+ var tabs_exports = {};
24
+ __export(tabs_exports, {
25
+ default: () => tabs_default,
26
+ getTabKeyFromFile: () => getTabKeyFromFile,
27
+ isTabRouteFile: () => isTabRouteFile
28
+ });
29
+ module.exports = __toCommonJS(tabs_exports);
30
+ var import_utils = require("@umijs/core/dist/route/utils");
31
+ var import_path = __toESM(require("path"));
32
+ var import_plugin_utils = require("umi/plugin-utils");
33
+ var import_meta = require("./meta");
34
+ function isTabRouteFile(file) {
35
+ return file.includes("$tab-");
36
+ }
37
+ function getTabKeyFromFile(file) {
38
+ return file.match(/\$tab-([^.]+)/)[1];
39
+ }
40
+ var tabs_default = (api) => {
41
+ let tabsFromPlugins;
42
+ const routesTabMapping = {};
43
+ const tabs = [];
44
+ api.describe({ key: void 0 });
45
+ api.modifyConfig(async (memo) => {
46
+ tabsFromPlugins = await api.applyPlugins({
47
+ key: "addContentTab"
48
+ });
49
+ tabsFromPlugins.forEach((tab) => {
50
+ tab.id ?? (tab.id = `plugin-tab${tab.test ? `-${tab.test}` : ""}-${tab.key}`);
51
+ });
52
+ return memo;
53
+ });
54
+ api.modifyRoutes((routes) => {
55
+ tabs.length = 0;
56
+ Object.values(routes).forEach((route) => {
57
+ if (isTabRouteFile(route.file)) {
58
+ delete routes[route.id];
59
+ const rtlFile = (0, import_plugin_utils.winPath)(import_path.default.relative(api.cwd, route.file));
60
+ const routeId = (0, import_utils.createRouteId)(rtlFile);
61
+ const tabKey = getTabKeyFromFile(rtlFile);
62
+ const parentFile = route.file.replace(/\$tab-[^.]+\./, "");
63
+ tabs.push({
64
+ index: tabs.length,
65
+ key: tabKey,
66
+ id: routeId,
67
+ file: route.file
68
+ });
69
+ routesTabMapping[parentFile] ?? (routesTabMapping[parentFile] = []);
70
+ routesTabMapping[parentFile].push(routeId);
71
+ } else {
72
+ tabsFromPlugins.forEach((tab) => {
73
+ var _a;
74
+ if (!tab.test || route.absPath.match(tab.test)) {
75
+ routesTabMapping[_a = route.file] ?? (routesTabMapping[_a] = []);
76
+ routesTabMapping[route.file].push(tab.id);
77
+ }
78
+ });
79
+ }
80
+ });
81
+ tabs.push(...tabsFromPlugins.map((tab) => ({
82
+ index: tabs.length,
83
+ key: tab.key,
84
+ id: tab.id,
85
+ file: tab.component
86
+ })));
87
+ return routes;
88
+ });
89
+ api.register({
90
+ key: "dumi.modifyMetaFiles",
91
+ fn: (metaFiles) => {
92
+ Object.values(metaFiles).forEach((metaFile) => {
93
+ if (routesTabMapping[metaFile.file]) {
94
+ metaFile.tabs = JSON.stringify(routesTabMapping[metaFile.file]);
95
+ }
96
+ });
97
+ tabs.forEach((tab) => {
98
+ metaFiles.push({
99
+ id: tab.id,
100
+ file: tab.file,
101
+ index: metaFiles.length
102
+ });
103
+ });
104
+ return metaFiles;
105
+ }
106
+ });
107
+ api.onGenerateFiles(() => {
108
+ api.writeTmpFile({
109
+ noPluginDir: true,
110
+ path: import_meta.TABS_META_PATH,
111
+ content: import_plugin_utils.Mustache.render(`{{#tabs}}
112
+ import * as tab{{{index}}} from '{{{file}}}';
113
+ {{/tabs}}
114
+
115
+ export const tabs = {
116
+ {{#tabs}}
117
+ '{{{id}}}': { key: '{{{key}}}', components: tab{{{index}}} },
118
+ {{/tabs}}
119
+ }
120
+ `, { tabs })
121
+ });
122
+ });
123
+ };
124
+ // Annotate the CommonJS export names for ESM import in node:
125
+ 0 && (module.exports = {
126
+ getTabKeyFromFile,
127
+ isTabRouteFile
128
+ });
@@ -1,11 +1,12 @@
1
1
  import type { IThemeLoadResult } from "../../features/theme/loader";
2
- import { type IMdTransformerOptions } from './transformer';
2
+ import { type IMdTransformerOptions, type IMdTransformerResult } from './transformer';
3
3
  interface IMdLoaderDefaultModeOptions extends Omit<IMdTransformerOptions, 'fileAbsPath'> {
4
4
  mode?: 'markdown';
5
5
  builtins: IThemeLoadResult['builtins'];
6
6
  }
7
7
  interface IMdLoaderDemosModeOptions extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
8
8
  mode: 'meta';
9
+ onResolveDemos?: (demos: IMdTransformerResult['meta']['demos']) => void;
9
10
  }
10
11
  export declare type IMdLoaderOptions = IMdLoaderDefaultModeOptions | IMdLoaderDemosModeOptions;
11
12
  export default function mdLoader(this: any, raw: string): void;
@@ -47,6 +47,9 @@ function mdLoader(raw) {
47
47
  if (opts.mode === "meta") {
48
48
  const { demos, frontmatter, toc, embeds = [] } = ret.meta;
49
49
  embeds.forEach((file) => this.addDependency(file));
50
+ if (demos && opts.onResolveDemos) {
51
+ opts.onResolveDemos(demos);
52
+ }
50
53
  cb(null, import_plugin_utils.Mustache.render(`import React from 'react';
51
54
 
52
55
  export const demos = {
@@ -64,27 +67,24 @@ export const toc = {{{toc}}}
64
67
  frontmatter: JSON.stringify(frontmatter),
65
68
  toc: JSON.stringify(toc),
66
69
  renderAsset: function renderAsset() {
70
+ let asset = this.asset;
67
71
  Object.keys(this.sources).forEach((file) => {
68
- this.asset.dependencies[file].value = `{{{require('!!raw-loader!${this.sources[file]}?raw').default}}}`;
72
+ asset = import_plugin_utils.lodash.cloneDeep(this.asset);
73
+ asset.dependencies[file].value = `{{{require('!!raw-loader!${this.sources[file]}?raw').default}}}`;
69
74
  });
70
- return JSON.stringify(this.asset, null, 2).replace(/"{{{|}}}"/g, "");
75
+ return JSON.stringify(asset, null, 2).replace(/"{{{|}}}"/g, "");
71
76
  }
72
77
  }));
73
78
  } else {
79
+ const isFragment = Boolean(this.resourcePath.includes("$tab-") || this.resourceQuery);
74
80
  cb(null, `${Object.values(opts.builtins).map((item) => `import ${item.specifier} from '${item.source}';`).join("\n")}
75
- import React, { useEffect } from 'react';
76
- import { useSiteData } from 'dumi';
81
+ import React from 'react';${isFragment ? "" : `
82
+ import { DumiPage } from 'dumi'`}
77
83
 
78
84
  // export named function for fastRefresh
79
85
  // ref: https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
80
86
  function DumiMarkdownContent() {
81
- const { setLoading } = useSiteData();
82
-
83
- useEffect(() => {
84
- setLoading(false);
85
- }, []);
86
-
87
- return ${ret.content};
87
+ return ${isFragment ? ret.content : `<DumiPage>${ret.content}</DumiPage>`};
88
88
  }
89
89
 
90
90
  export default DumiMarkdownContent;`);
@@ -153,14 +153,20 @@ function rehypeDemo(opts) {
153
153
  deferrers.push((0, import_block.default)(parseOpts).then(async ({ asset, sources, frontmatter }) => {
154
154
  var _a3;
155
155
  const { src, className, ...restAttrs } = codeNode.properties || {};
156
- if (restAttrs.title) {
157
- asset.title = String(restAttrs.title);
158
- }
156
+ const validAssetAttrs = [
157
+ "title",
158
+ "snapshot",
159
+ "keywords"
160
+ ];
159
161
  Object.keys(restAttrs).forEach((key) => {
160
162
  if (restAttrs[key] === "")
161
163
  restAttrs[key] = true;
162
164
  });
163
165
  const originalProps = Object.assign({}, frontmatter, restAttrs);
166
+ validAssetAttrs.forEach((key) => {
167
+ if (originalProps[key])
168
+ asset[key] = originalProps[key];
169
+ });
164
170
  Object.assign(previewerProps, await ((_a3 = techStack.generatePreviewerProps) == null ? void 0 : _a3.call(techStack, originalProps, {
165
171
  type: codeType,
166
172
  mdAbsPath: opts.fileAbsPath,
@@ -86,7 +86,7 @@ function rehypeJsxify() {
86
86
  delete copy.value;
87
87
  }
88
88
  });
89
- return toJs(esTree, { handlers: jsx }).value;
89
+ return toJs(esTree, { handlers: jsx }).value.trim().slice(0, -1);
90
90
  };
91
91
  }
92
92
  // Annotate the CommonJS export names for ESM import in node:
@@ -25,6 +25,7 @@ __export(remarkMeta_exports, {
25
25
  default: () => remarkMeta
26
26
  });
27
27
  module.exports = __toCommonJS(remarkMeta_exports);
28
+ var import_tabs = require("../../../features/tabs");
28
29
  var import_js_yaml = __toESM(require("js-yaml"));
29
30
  var import_path = __toESM(require("path"));
30
31
  var import_plugin_utils = require("umi/plugin-utils");
@@ -52,8 +53,12 @@ function remarkMeta(opts) {
52
53
  });
53
54
  },
54
55
  () => {
55
- const pathWithoutIndex = opts.fileAbsPath.replace(/(\/index([^/]+)?)?\.md$/, "");
56
- vFile.data.frontmatter.title = import_plugin_utils.lodash.startCase(import_path.default.basename(pathWithoutIndex));
56
+ if ((0, import_tabs.isTabRouteFile)(opts.fileAbsPath)) {
57
+ vFile.data.frontmatter.title = import_plugin_utils.lodash.startCase((0, import_tabs.getTabKeyFromFile)(opts.fileAbsPath));
58
+ } else {
59
+ const pathWithoutIndex = opts.fileAbsPath.replace(/(\/index([^/]+)?)?\.md$/, "");
60
+ vFile.data.frontmatter.title = import_plugin_utils.lodash.startCase(import_path.default.basename(pathWithoutIndex));
61
+ }
57
62
  }
58
63
  ];
59
64
  while (!vFile.data.frontmatter.title && titleReaders.length) {
package/dist/preset.js CHANGED
@@ -36,9 +36,11 @@ var preset_default = (api) => {
36
36
  require.resolve("./features/compile"),
37
37
  require.resolve("./features/routes"),
38
38
  require.resolve("./features/meta"),
39
+ require.resolve("./features/tabs"),
39
40
  require.resolve("./features/theme"),
40
41
  require.resolve("./features/locales"),
41
- require.resolve("./features/parser")
42
+ require.resolve("./features/parser"),
43
+ require.resolve("./features/assets")
42
44
  ]
43
45
  };
44
46
  };
@@ -24,7 +24,12 @@ __export(registerMethods_exports, {
24
24
  module.exports = __toCommonJS(registerMethods_exports);
25
25
  var registerMethods_default = (api) => {
26
26
  api.describe({ key: "dumi:registerMethods" });
27
- ["registerTechStack"].forEach((name) => {
27
+ [
28
+ "registerTechStack",
29
+ "addContentTab",
30
+ "modifyAssetsMetadata",
31
+ "modifyTheme"
32
+ ].forEach((name) => {
28
33
  api.registerMethod({ name });
29
34
  });
30
35
  };
package/dist/types.d.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import type AtomAssetsParser from "./assetParsers/atom";
2
2
  import type { IDumiDemoProps } from "./client/theme-api/DumiDemo";
3
3
  import type { ILocalesConfig, IThemeConfig } from "./client/theme-api/types";
4
+ import type { IContentTab } from "./features/tabs";
4
5
  import type { IThemeLoadResult } from "./features/theme/loader";
5
6
  import type { IModify } from '@umijs/core';
6
- import type { ExampleBlockAsset } from 'dumi-assets-types';
7
+ import type { AssetsPackage, ExampleBlockAsset } from 'dumi-assets-types';
7
8
  import type { Element } from 'hast';
8
9
  import type { IApi as IUmiApi } from 'umi';
9
10
  declare type IUmiConfig = IUmiApi['config'];
10
11
  export interface IDumiConfig extends IUmiConfig {
11
12
  resolve: {
12
- docDirs: string[];
13
+ docDirs: (string | {
14
+ type?: string;
15
+ dir: string;
16
+ })[];
13
17
  entityDirs: {
14
18
  type: string;
15
19
  dir: string;
@@ -74,5 +78,13 @@ export declare type IApi = IUmiApi & {
74
78
  * modify original theme data
75
79
  */
76
80
  modifyTheme: IModify<IThemeLoadResult, null>;
81
+ /**
82
+ * add content tab
83
+ */
84
+ addContentTab: (fn: () => IContentTab) => void;
85
+ /**
86
+ * modify assets metadata
87
+ */
88
+ modifyAssetsMetadata: IModify<AssetsPackage, null>;
77
89
  };
78
90
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dumi",
3
- "version": "2.0.0-beta.3",
3
+ "version": "2.0.0-beta.6",
4
4
  "description": "Framework for developing UI components",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -14,5 +14,6 @@
14
14
  "api.component.default": "Default",
15
15
  "api.component.required": "(required)",
16
16
  "api.component.loading": "Properties definition is resolving, wait a moment...",
17
- "api.component.not.found": "Properties definition not found for {id} component"
17
+ "api.component.not.found": "Properties definition not found for {id} component",
18
+ "content.tabs.default": "Doc"
18
19
  }
@@ -14,5 +14,6 @@
14
14
  "api.component.default": "默认值",
15
15
  "api.component.required": "(必选)",
16
16
  "api.component.loading": "属性定义正在解析中,稍等片刻...",
17
- "api.component.not.found": "未找到 {id} 组件的属性定义"
17
+ "api.component.not.found": "未找到 {id} 组件的属性定义",
18
+ "content.tabs.default": "文档"
18
19
  }
@@ -48,7 +48,7 @@
48
48
  max-width: 100%;
49
49
 
50
50
  &:not([data-no-sidebar]) {
51
- padding: 48px;
51
+ padding: @s-content-padding;
52
52
  background-color: #fff;
53
53
  border-top-left-radius: 10px;
54
54
  border-top-right-radius: 10px;
@@ -0,0 +1,11 @@
1
+ import { useRouteMeta } from 'dumi';
2
+ import { type FC } from 'react';
3
+ import './index.less';
4
+ declare type IContentTabs = ReturnType<typeof useRouteMeta>['tabs'];
5
+ export interface IContentTabsProps {
6
+ tabs: IContentTabs;
7
+ tabKey?: string;
8
+ onChange: (tab?: NonNullable<IContentTabs>[0]) => void;
9
+ }
10
+ declare const ContentTabs: FC<IContentTabsProps>;
11
+ export default ContentTabs;
@@ -0,0 +1,35 @@
1
+ import { useIntl } from 'dumi';
2
+ import React from 'react';
3
+ import "./index.less";
4
+
5
+ var ContentTabs = function ContentTabs(_ref) {
6
+ var tabs = _ref.tabs,
7
+ key = _ref.tabKey,
8
+ onChange = _ref.onChange;
9
+ var intl = useIntl(); // TODO: tab.Extra & tab.Action render
10
+
11
+ return Boolean(tabs === null || tabs === void 0 ? void 0 : tabs.length) ? /*#__PURE__*/React.createElement("ul", {
12
+ className: "dumi-default-content-tabs"
13
+ }, /*#__PURE__*/React.createElement("li", {
14
+ onClick: function onClick() {
15
+ return onChange();
16
+ },
17
+ "data-active": !key || undefined
18
+ }, /*#__PURE__*/React.createElement("button", {
19
+ type: "button"
20
+ }, intl.formatMessage({
21
+ id: 'content.tabs.default'
22
+ }))), tabs.map(function (tab) {
23
+ return /*#__PURE__*/React.createElement("li", {
24
+ key: tab.key,
25
+ onClick: function onClick() {
26
+ return onChange(tab);
27
+ },
28
+ "data-active": key === tab.key || undefined
29
+ }, /*#__PURE__*/React.createElement("button", {
30
+ type: "button"
31
+ }, tab.frontmatter.title));
32
+ })) : null;
33
+ };
34
+
35
+ export default ContentTabs;
@@ -0,0 +1,57 @@
1
+ @import (reference) '../../styles/variables.less';
2
+
3
+ .@{prefix}-content-tabs {
4
+ list-style-type: none;
5
+ display: flex;
6
+ align-items: center;
7
+ height: 60px;
8
+ margin: -@s-content-padding -@s-content-padding @s-content-padding -@s-content-padding;
9
+ padding: 0 @s-content-padding;
10
+ border-bottom: 1px solid @c-border-light;
11
+
12
+ [data-no-sidebar] & {
13
+ margin: 0 0 @s-content-padding;
14
+ padding: 0;
15
+ }
16
+
17
+ > li {
18
+ height: inherit;
19
+
20
+ > button {
21
+ padding: 0;
22
+ height: inherit;
23
+ color: @c-text-secondary;
24
+ font-size: 17px;
25
+ border: 0;
26
+ background: transparent;
27
+ cursor: pointer;
28
+ transition: all 0.2s;
29
+
30
+ &:hover {
31
+ color: @c-primary;
32
+ }
33
+ }
34
+
35
+ &:not(last-child) {
36
+ margin-right: 42px;
37
+ }
38
+
39
+ &[data-active] {
40
+ position: relative;
41
+
42
+ > button {
43
+ color: @c-text;
44
+ }
45
+
46
+ &::after {
47
+ content: '';
48
+ position: absolute;
49
+ left: 0;
50
+ right: 0;
51
+ bottom: -1px;
52
+ height: 1px;
53
+ background-color: @c-primary;
54
+ }
55
+ }
56
+ }
57
+ }
@@ -1,6 +1,7 @@
1
1
  @prefix: dumi-default;
2
2
 
3
3
  @s-content-width: 1392px;
4
+ @s-content-padding: 48px;
4
5
  @s-sidebar-width: 184px;
5
6
  @s-header-height: 76px;
6
7