diff --git a/docs/cores/index.md b/docs/cores/index.md
index f8b8278360..2924f8fa61 100644
--- a/docs/cores/index.md
+++ b/docs/cores/index.md
@@ -11,25 +11,92 @@ nav:
NocoBase 核心主要围绕三点:
-- 数据的存储 —— 结构和关系
-- 数据的行为 —— 操作和事件
-- 数据的形态 —— 页面和区块
+- 数据的结构
+- 数据的行为
+- 数据的形态
-由此构建了
+由此抽象了三类配置协议
-- @nocobase/database:提供灵活且强大的数据库构造器
-- @nocobase/resourcer:为数据提供资源操作方法
-- @nocobase/blocks:为数据提供的 UI 模块
+- Collection:用于描述数据的结构和关系
+- Resourcer:用于描述数据资源和操作方法
+- UI Schema:用于描述用户界面(组件树结构)
-更近一步
+## Collection
-- @nocobase/database 和 @nocobase/resourcer 组装成了 @nocobase/server,也就是最核心的 NocoBase
-- @nocobase/blocks 是前端最重要部分,提供了现代化的 Block-styled Editor/Renderer
+基于 Sequelize ModelOptions
-
+```ts
+{
+ name: 'posts',
+ fields: [
+ {type: 'string', name: 'title'},
+ {type: 'text', name: 'content'},
+ ],
+}
+```
-## 数据的存储 —— 结构和关系
+## Resourcer
-## 数据的行为 —— 操作和事件
+基于资源(resource)和操作方法(action)设计,将 REST 和 RPC 思想融合起来
-## 数据的形态 —— 页面和区块
\ No newline at end of file
+```ts
+{
+ name: 'posts',
+ actions: {
+ list: {
+ filter: {}, // 过滤
+ fields: [], // 输出哪些字段
+ sort: '-created_at', // 排序
+ page: 1,
+ perPage: 20,
+ // ...
+ },
+ get: {
+ filter: {},
+ fields: [],
+ // ...
+ },
+ create: {
+ fields: [],
+ values: {},
+ // ...
+ },
+ update: {
+ fields: [],
+ values: {},
+ // ...
+ },
+ destroy: {
+ filter: {},
+ // ...
+ },
+ },
+}
+```
+
+## UI Schema
+
+基于 Formily Schema 2.0
+
+```ts
+{
+ type: 'object',
+ // 'x-component': 'Form',
+ properties: {
+ title: {
+ type: 'string',
+ title: '标题',
+ 'x-component': 'Input',
+ },
+ content: {
+ type: 'string',
+ title: '标题',
+ 'x-component': 'Input.TextArea',
+ },
+ },
+}
+```
+
+更进一步,构建了整个 NocoBase 架构:
+
+
\ No newline at end of file
diff --git a/docs/cores/packages/blocks.md b/docs/cores/packages/blocks.md
deleted file mode 100644
index 8e91ae2755..0000000000
--- a/docs/cores/packages/blocks.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-title: '@nocobase/blocks'
-order: 6
----
-
-# @nocobase/blocks 未实现
-
-
-
-因为前端代码的打包还有许多细节问题,核心代码暂时也都放在 @nocobase/app 里了。
-
-v0.4 版本 components 分为了 Action、Field、View 三部分,下一把版本打算都整合到 Block 里。
-
-
-
-在 NocoBase 中,区块是所有 HTML 元素片段(包括 React/Vue 等框架的自定义元素)的总称。区块可以任意组合或嵌套,为了方便使用,内置了常用的一些区块,大致分类有:
-
-- Database - 数据
-- Fields - 字段
-- Buttons - 按钮
-- Media - 多媒体
-- Design - 设计
-- 自定义区块
-
-## Database - 数据
-
-数据类型的区块需要绑定数据源。
-
-### table
-### form
-### descriptions
-### calendar
-### kanban
-
-## Fields - 字段
-
-字段是一种特殊的子区块,只用于数据类型区块中,有两种形态:静态纯展示和动态可输入。
-
-### boolean
-### checkbox
-### cascader
-### time
-### markdown
-### string
-### icon
-### textarea
-### number
-### remoteSelect
-### drawerSelect
-### colorSelect
-### subTable
-### icon
-
-## Buttons - 按钮
-
-### create
-### update
-### destroy
-### filter
-### print
-### export
-### button
-
-## Media - 多媒体
-
-### markdown
-
-## Design - 设计
-
-### grid
-### drawer
-### page
-
-## 自定义区块
diff --git a/docs/cores/packages/client.md b/docs/cores/packages/client.md
deleted file mode 100644
index e5fe3b26e7..0000000000
--- a/docs/cores/packages/client.md
+++ /dev/null
@@ -1,256 +0,0 @@
----
-title: '@nocobase/client'
-order: 6
----
-
-# @nocobase/client 未实现
-
-## 介绍
-
-提供适配 Ant Design 组件的 NocoBase 客户端。
-
-
-
-@nocobase/client 可以用于任意 React 框架中,不过还存在许多难点和细节未解决。
-
-
-
-## Loaders
-
-### TemplateLoader 待完善
-
-- routes:路由表,大杂烩。类型包括:layout、page、redirect、url、menuGroup,**页面就是 type=page 的 route**
-- templates:模板
-- pathname:URL 路径
-
-为了适应无代码需求,提供了一种 URL 和 Template 映射规则,如以下例子:
-
-```ts
-const routes = [
- {
- type: 'layout',
- name: 'auth', // 因为 /login、/register 没有统一的前缀,所以这里没有配置 path
- template: 'AuthLayout', // 用于处理 login、register 等页面的布局
- children: [
- {
- type: 'page',
- name: 'login',
- path: '/login',
- template: 'BlockLoader', // 用于解析 blocks 配置参数
- blocks: [],
- },
- {
- type: 'page',
- name: 'register',
- path: '/register',
- template: 'BlockLoader',
- blocks: [],
- }
- ]
- },
- {
- type: 'layout',
- name: 'admin',
- path: '/admin', // /admin 下的任意 uri 都转到这里
- template: 'AdminLoader',
- redirect: '/admin/welcome',
- // admin layout 提供了 top/left 布局的菜单,菜单由 children 组成
- // 通过解析 /admin/:name,找到对应子页面
- // 「菜单和页面配置」就是这部分的内容
- children: [
- {
- type: 'page',
- name: 'welcome',
- template: 'BlockLoader',
- title: '欢迎',
- blocks: [],
- },
- {
- type: 'url',
- url: 'https://www.nocobase.com/',
- title: 'xxx',
- },
- {
- type: 'menuGroup',
- title: 'xxx',
- },
- ],
- },
- {
- // 配置跳转
- type: 'redirect',
- path: '/',
- redirect: '/admin',
- },
-];
-```
-
-
-
-
-
-### BlockLoader 未实现
-
-区块驱动器。
-
-### AdminLoader 待完善
-
-一种 top/left 菜单结构的 Admin 布局。菜单由其对应的 children 组成,通过 `/admin/:name` 映射到对应子页面,「菜单和页面配置」就是这部分的内容。
-
-### ShareLoader 未实现
-
-分享模块,细节待补充
-
-## Blocks
-
-将页面内部的各个块元素进行提炼,抽象了 block(区块)的概念。
-
-### Grid - 布局
-
-```ts
-{
- type: 'grid',
- span: 12,
- blocks: [
- {
- col: 1,
- order: 1,
- },
- {
- col: 2,
- order: 1,
- },
- {
- col: 1,
- order: 2,
- },
- ],
-}
-```
-
-### Descriptions - 详情
-
-```ts
-{
- type: 'descriptions',
- fields: [],
- actions: [],
-}
-```
-
-### Form - 表单
-
-```ts
-{
- type: 'form',
- fields: [],
- // 表单提交反馈信息,细节待定
- returnType,
- redirect,
- message,
-}
-```
-
-### Table - 表单
-
-```ts
-{
- type: 'table',
- defaultPerPage: 20,
- draggable: false,
- filter: {},
- sort: [],
- detailsOpenMode: 'drawer',
- actions: [],
- fields: [],
- details: [],
- labelField,
-}
-```
-
-### Calendar - 日历
-
-```ts
-{
- type: 'calendar',
- filter: {},
- detailsOpenMode: 'drawer',
- actions: [],
- details: [],
- labelField,
-}
-```
-
-### Kanban - 看板
-
-```ts
-{
- type: 'kanban',
- groupField,
- labelField,
- fields,
- filter,
- actions,
- detailsOpenMode,
- details,
-}
-```
-
-### Markdown
-
-```ts
-{
- type: 'markdown',
- content: '',
-}
-```
-
-## Actions
-
-操作按钮
-
-### create - 新增
-
-### update - 编辑
-
-### destroy - 删除
-
-### filter - 筛选
-
-### print - 打印
-
-### export - 导出
-
-## Fields
-
-字段控件
-
-### boolean
-### cascader
-### checkbox
-### checkboxes
-### colorSelect
-### date
-### drawerSelect
-### filter
-### icon
-### markdown
-### number
-### password
-### percent
-### radio
-### remoteSelect
-### string
-### select
-### subTable
-### textarea
-### time
-### upload
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 17445d8012..5c37d8d24a 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -13,7 +13,7 @@ NocoBase 是一个开源免费的无代码、低代码开发平台。 无论是
## 架构
-
+
### 微内核
@@ -41,11 +41,3 @@ NocoBase 采用微内核架构,框架只保留核心的概念,具体各类
- 直接写在代码里,多用于处理动态配置
- 保存在文件里,多用于系统表配置或纯开发配置
- 保存在数据表里,多用于业务表配置
-
-## 核心点在哪里?
-
-NocoBase 本质上是对数据的信息化处理,包括三点核心:
-
-- 数据的存储 —— 结构和关系
-- 数据的行为 —— 方法和事件
-- 数据的形态 —— 页面和区块
diff --git a/nodemon.json b/nodemon.json
new file mode 100644
index 0000000000..51695bfc20
--- /dev/null
+++ b/nodemon.json
@@ -0,0 +1,6 @@
+{
+ "watch": ["packages/", ".env"],
+ "ignore": ["packages/app"],
+ "ext": "ts",
+ "exec": "ts-node -r dotenv/config ./packages/api/src/index.ts"
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index a52ebf86ac..f57b61f0ec 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"scripts": {
"bootstrap": "lerna bootstrap",
"clean": "lerna clean",
+ "start-server": "nodemon",
"start-docs": "dumi dev",
"build-docs": "dumi build",
"build2": "lerna run build",
@@ -44,6 +45,7 @@
"koa-bodyparser": "^4.3.0",
"lerna": "^3.22.0",
"mockjs": "^1.1.0",
+ "nodemon": "^2.0.12",
"pg": "^8.6.0",
"pg-hstore": "^2.3.3",
"prettier": "^2.3.0",
diff --git a/packages/api/package.json b/packages/api/package.json
index 4fbaa19193..8cd3a05ee9 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -19,7 +19,7 @@
"@nocobase/plugin-file-manager": "^0.4.0-alpha.7",
"@nocobase/plugin-pages": "^0.4.0-alpha.7",
"@nocobase/plugin-permissions": "^0.4.0-alpha.7",
- "@nocobase/plugin-routes": "^0.4.0-alpha.7",
+ "@nocobase/plugin-ui-router": "^0.4.0-alpha.7",
"@nocobase/plugin-ui-schema": "^0.4.0-alpha.7",
"@nocobase/plugin-users": "^0.4.0-alpha.7",
"@nocobase/server": "^0.4.0-alpha.7",
diff --git a/packages/api/src/app.ts b/packages/api/src/app.ts
index 53adaaa339..dedd4e86f4 100644
--- a/packages/api/src/app.ts
+++ b/packages/api/src/app.ts
@@ -39,7 +39,7 @@ const api = Api.create({
const plugins = [
'@nocobase/plugin-collections',
- '@nocobase/plugin-routes',
+ '@nocobase/plugin-ui-router',
'@nocobase/plugin-ui-schema',
// '@nocobase/plugin-action-logs',
// '@nocobase/plugin-pages',
diff --git a/packages/api/src/migrations/init.ts b/packages/api/src/migrations/init.ts
index 6b28fe18fb..64e596464f 100644
--- a/packages/api/src/migrations/init.ts
+++ b/packages/api/src/migrations/init.ts
@@ -27,25 +27,27 @@ import * as uiSchema from './ui-schema';
exact: true,
},
{
+ type: 'route',
path: '/admin/:name(.+)?',
component: 'AdminLayout',
title: `后台`,
uiSchema: uiSchema.menu,
},
{
+ type: 'route',
component: 'AuthLayout',
children: [
{
- name: 'login',
+ type: 'route',
path: '/login',
- component: 'DefaultPage',
+ component: 'RouteSchemaRenderer',
title: `登录`,
uiSchema: uiSchema.login,
},
{
- name: 'register',
+ type: 'route',
path: '/register',
- component: 'DefaultPage',
+ component: 'RouteSchemaRenderer',
title: `注册`,
uiSchema: uiSchema.register,
},
diff --git a/packages/api/src/migrations/ui-schema/login.ts b/packages/api/src/migrations/ui-schema/login.ts
index d06e4c780d..b69843ac97 100644
--- a/packages/api/src/migrations/ui-schema/login.ts
+++ b/packages/api/src/migrations/ui-schema/login.ts
@@ -2,7 +2,6 @@ import { ISchema } from '@formily/react';
export const login: ISchema = {
key: 'dtf9j0b8p9u',
- name: 'dtf9j0b8p9u',
type: 'object',
properties: {
email: {
diff --git a/packages/api/src/migrations/ui-schema/register.ts b/packages/api/src/migrations/ui-schema/register.ts
index a129f5e3a3..993d56a2fa 100644
--- a/packages/api/src/migrations/ui-schema/register.ts
+++ b/packages/api/src/migrations/ui-schema/register.ts
@@ -1,6 +1,5 @@
export const register = {
key: '46qlxqam3xk',
- name: '46qlxqam3xk',
type: 'object',
properties: {
username: {
diff --git a/packages/client/src/components/RouteSwitch/__tests__/RouteSwitch.test.tsx b/packages/client/src/components/RouteSwitch/__tests__/RouteSwitch.test.tsx
deleted file mode 100644
index 5e1384ddc9..0000000000
--- a/packages/client/src/components/RouteSwitch/__tests__/RouteSwitch.test.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
-import { MemoryRouter as Router, useParams, Link } from 'react-router-dom';
-import { RouteSwitch } from '../';
-
-const components = {
- Index: () => index
,
- Home: ({ children }) => (
-
-
Home
- {children}
-
- ),
- Blog: ({ children }) => (
-
-
Blog
- {children}
-
- ),
- BlogPost: () => {
- let { slug } = useParams();
- return Now showing post {slug}
;
- },
- Login: () => login
,
- Register: () => register
,
-};
-
-const routes = [
- {
- type: 'redirect',
- from: '/blog/123',
- to: '/blog/1234',
- },
- // {
- // component: 'Home',
- // routes: [
- // {
- // path: '/blog/123555',
- // component: () => /blog/123555
,
- // },
- // ],
- // },
- {
- path: '/blog',
- component: 'Blog',
- routes: [
- {
- path: '/blog/:slug',
- // exact: true,
- component: 'BlogPost',
- },
- ],
- },
- {
- component: 'Home',
- routes: [
- // {
- // path: '/blog/123555',
- // component: () => /blog/123555
,
- // },
- {
- path: '/login',
- component: 'Login',
- },
- {
- path: '/register',
- component: 'Register',
- },
- {
- path: '/',
- // exact: true,
- component: 'Index',
- },
- ],
- },
-];
-
-it('route component', () => {
- const t = renderer
- .create(
-
- test
,
- },
- ]}
- />
- ,
- )
- .toJSON();
- expect(t).toMatchSnapshot();
-});
-
-it('pathname=/', () => {
- const t = renderer
- .create(
-
-
- ,
- )
- .toJSON();
- expect(t).toMatchSnapshot();
-});
-
-it('pathname=/login', () => {
- const t = renderer
- .create(
-
-
- ,
- )
- .toJSON();
- expect(t).toMatchSnapshot();
-});
-
-it('pathname=/blog/123', () => {
- const t = renderer
- .create(
-
-
- ,
- )
- .toJSON();
- expect(t).toMatchSnapshot();
-});
diff --git a/packages/client/src/components/RouteSwitch/__tests__/__snapshots__/RouteSwitch.test.tsx.snap b/packages/client/src/components/RouteSwitch/__tests__/__snapshots__/RouteSwitch.test.tsx.snap
deleted file mode 100644
index 46917a102b..0000000000
--- a/packages/client/src/components/RouteSwitch/__tests__/__snapshots__/RouteSwitch.test.tsx.snap
+++ /dev/null
@@ -1,41 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`pathname=/ 1`] = `
-
-`;
-
-exports[`pathname=/blog/123 1`] = `
-
-
- Blog
-
-
- Now showing post
- 1234
-
-
-`;
-
-exports[`pathname=/login 1`] = `
-
-`;
-
-exports[`route component 1`] = `
-
- test
-
-`;
diff --git a/packages/client/src/components/RouteSwitch/demos/demo1.tsx b/packages/client/src/components/RouteSwitch/demos/demo1.tsx
deleted file mode 100644
index 7b996c0c3c..0000000000
--- a/packages/client/src/components/RouteSwitch/demos/demo1.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import {
- Link,
- useLocation,
- useRouteMatch,
- MemoryRouter as Router,
-} from 'react-router-dom';
-import {
- RouteSwitch,
- AuthLayout,
- AdminLayout,
- PageTemplate,
-} from '@nocobase/client';
-
-const routes = [
- {
- path: '/admin/:slug(.+)?',
- component: 'AdminLayout',
- },
- {
- component: 'AuthLayout',
- routes: [
- {
- name: 'login',
- path: '/login',
- component: 'PageTemplate',
- title: '登录',
- },
- {
- name: 'register',
- path: '/register',
- component: 'PageTemplate',
- title: '注册',
- },
- ],
- },
- {
- type: 'redirect',
- from: '/',
- to: '/admin',
- exact: true,
- },
-];
-
-const components = {
- AuthLayout,
- AdminLayout,
- PageTemplate,
-};
-
-function App() {
- const location = useLocation();
- return (
-
-
{location.pathname}
-
- - path=/login
- - path=/register
- - path=/
- - path=/admin/welcome
-
-
-
- );
-}
-
-export default () => {
- return (
-
-
-
- );
-};
diff --git a/packages/client/src/components/RouteSwitch/index.md b/packages/client/src/components/RouteSwitch/index.md
deleted file mode 100644
index c8f6e86862..0000000000
--- a/packages/client/src/components/RouteSwitch/index.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: RouteSwitch - 路由转换器
-nav:
- title: 组件
- path: /client
-group:
- order: 3
- title: 其他
- path: /client/others
----
-
-# RouteSwitch - 路由转换器
-
-## 代码演示
-
-
diff --git a/packages/client/src/components/RouteSwitch/index.tsx b/packages/client/src/components/RouteSwitch/index.tsx
deleted file mode 100644
index 81807d69ea..0000000000
--- a/packages/client/src/components/RouteSwitch/index.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { createContext, useContext } from 'react';
-import { Switch, Route as ReactRoute, Redirect } from 'react-router-dom';
-
-export const RouteComponentContext = createContext({});
-
-export interface RouteSwitchProps {
- routes?: any[];
- components?: any;
-}
-
-export function RouteSwitch(props: RouteSwitchProps) {
- const { routes } = props;
- if (!Array.isArray(routes)) {
- return null;
- }
- const components = props.components || useContext(RouteComponentContext);
- return (
-
-
- {routes.map((route, key) => {
- if (route.type === 'redirect') {
- return (
-
- );
- }
- if (route.component) {
- let path = route.path;
- if (!path && Array.isArray(route.routes)) {
- path = route.routes.map((r) => r.path);
- }
- return (
- {
- const Component =
- typeof route.component === 'string'
- ? components[route.component]
- : route.component;
- if (!Component) {
- return null;
- }
- return (
-
-
-
- );
- }}
- />
- );
- }
- })}
-
-
- );
-}
-
-export default RouteSwitch;
diff --git a/packages/client/src/components/admin-layout/index.md b/packages/client/src/components/admin-layout/index.md
new file mode 100644
index 0000000000..276c9f236e
--- /dev/null
+++ b/packages/client/src/components/admin-layout/index.md
@@ -0,0 +1,14 @@
+---
+title: AdminLayout - 后台布局
+nav:
+ title: 组件
+ path: /client
+group:
+ order: 2
+ title: Templates
+ path: /client/templates
+---
+
+# AdminLayout - 后台布局
+
+内置的后台布局模板,提供了基础的菜单和路由切换。
diff --git a/packages/client/src/components/admin-layout/index.tsx b/packages/client/src/components/admin-layout/index.tsx
new file mode 100644
index 0000000000..4fe3e23423
--- /dev/null
+++ b/packages/client/src/components/admin-layout/index.tsx
@@ -0,0 +1,92 @@
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import {
+ Button,
+ Spin,
+ Layout,
+ PageHeader,
+ Modal,
+ Menu,
+ Collapse,
+ Dropdown,
+} from 'antd';
+import isEmpty from 'lodash/isEmpty';
+import {
+ Link,
+ useLocation,
+ useRouteMatch,
+ useHistory,
+ Redirect,
+} from 'react-router-dom';
+import { SchemaRenderer } from '../../schemas';
+import { useRequest } from 'ahooks';
+import './style.less';
+
+function LayoutWithMenu({ schema }) {
+ const match = useRouteMatch();
+ const location = useLocation();
+ const sideMenuRef = useRef();
+ const [activeKey, setActiveKey] = useState(match.params.name);
+ const onSelect = (info) => {
+ console.log('LayoutWithMenu', schema);
+ setActiveKey(info.key);
+ };
+ console.log({ match });
+ return (
+
+
+
+
+
+
+
+ {activeKey && }
+
+
+
+ );
+}
+
+function Content({ activeKey }) {
+ const { data = {}, loading } = useRequest(
+ `ui_schemas:getTree/${activeKey}?filter[parentId]=${activeKey}`,
+ {
+ refreshDeps: [activeKey],
+ formatResult: (result) => result?.data,
+ },
+ );
+
+ if (loading) {
+ return ;
+ }
+
+ return ;
+}
+
+export function AdminLayout({ route }: any) {
+ const { data = {}, loading } = useRequest(
+ `ui_schemas:getTree/${route.uiSchemaKey}`,
+ {
+ refreshDeps: [route],
+ formatResult: (result) => result?.data,
+ },
+ );
+
+ if (loading) {
+ return ;
+ }
+
+ return ;
+}
+
+export default AdminLayout;
diff --git a/packages/client/src/components/admin-layout/style.less b/packages/client/src/components/admin-layout/style.less
new file mode 100644
index 0000000000..0134008839
--- /dev/null
+++ b/packages/client/src/components/admin-layout/style.less
@@ -0,0 +1,49 @@
+// .fields-collapse {
+// border: 1px solid #f0f0f0;
+// border-bottom: 0;
+// > .ant-collapse-item {
+// border-bottom: 1px solid #f0f0f0;
+// }
+// .ant-collapse-content {
+// &.ant-collapse-content-active {
+// border-top: 1px solid #f0f0f0;
+// }
+// }
+// > .ant-collapse-item > .ant-collapse-content {
+// background: #fff;
+// }
+// > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box {
+// padding: 16px;
+// padding-bottom: 1px;
+// }
+// }
+
+.ant-collapse {
+ border: 1px solid #d9d9d9 !important;
+ border-bottom: 0 !important;
+ > .ant-collapse-item {
+ border-bottom: 1px solid #d9d9d9 !important;
+ }
+ .ant-collapse-content {
+ border-top: 1px solid #d9d9d9 !important;
+ }
+ .ant-collapse-content > .ant-collapse-content-box {
+ padding: 24px !important;
+ padding-bottom: 0 !important;
+ }
+}
+
+.database-sider {
+ background: #fafafa;
+ padding-top: 16px;
+ .ant-menu {
+ background: #fafafa;
+ border-right: 0;
+ }
+ .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
+ background: #fff;
+ &::after {
+ display: none;
+ }
+ }
+}
diff --git a/packages/client/src/components/auth-layout/index.md b/packages/client/src/components/auth-layout/index.md
new file mode 100644
index 0000000000..7cc91617fe
--- /dev/null
+++ b/packages/client/src/components/auth-layout/index.md
@@ -0,0 +1,14 @@
+---
+title: AuthLayout - 登录布局
+nav:
+ title: 组件
+ path: /client
+group:
+ order: 2
+ title: Templates
+ path: /client/templates
+---
+
+# AuthLayout - 登录布局
+
+内置的登录、注册页布局
\ No newline at end of file
diff --git a/packages/client/src/components/auth-layout/index.tsx b/packages/client/src/components/auth-layout/index.tsx
new file mode 100644
index 0000000000..578364a91d
--- /dev/null
+++ b/packages/client/src/components/auth-layout/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { useLocation, useHistory } from 'react-router-dom';
+
+export function AuthLayout({ children, route }: any) {
+ const location = useLocation();
+ const history = useHistory();
+ return (
+
+
NocoBase
+ {children}
+
+ );
+}
+
+export default AuthLayout;
diff --git a/packages/client/src/components/route-schema-renderer/index.tsx b/packages/client/src/components/route-schema-renderer/index.tsx
new file mode 100644
index 0000000000..9b40888e12
--- /dev/null
+++ b/packages/client/src/components/route-schema-renderer/index.tsx
@@ -0,0 +1,25 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { Spin } from 'antd';
+import { Helmet } from 'react-helmet';
+import { useRequest } from 'ahooks';
+import { SchemaRenderer } from '../../schemas';
+
+export function RouteSchemaRenderer({ route }) {
+ const { data = {}, loading } = useRequest(
+ `ui_schemas:getTree/${route.uiSchemaKey}`,
+ {
+ refreshDeps: [route],
+ formatResult: (result) => result?.data,
+ },
+ );
+ if (loading) {
+ return ;
+ }
+ return (
+
+
+
+ );
+}
+
+export default RouteSchemaRenderer;
diff --git a/packages/client/src/components/router-config/demos/demo1.tsx b/packages/client/src/components/router-config/demos/demo1.tsx
new file mode 100644
index 0000000000..b50a55b773
--- /dev/null
+++ b/packages/client/src/components/router-config/demos/demo1.tsx
@@ -0,0 +1,49 @@
+import React, { useMemo } from 'react';
+import {
+ Link,
+ useLocation,
+ useRouteMatch,
+ MemoryRouter as Router,
+} from 'react-router-dom';
+import { createRouteSwitch, RouteRedirectProps } from '..';
+
+const RouteSwitch = createRouteSwitch({
+ components: {
+ Home: (props) => {
+ console.log({ props });
+ return Home {props.children}
+ },
+ Login: () => Login
,
+ },
+});
+
+export default () => {
+ const routes: Array = [
+ {
+ type: 'route',
+ path: '/login',
+ exact: true,
+ component: 'Login',
+ },
+ {
+ type: 'route',
+ path: '/home',
+ component: 'Home',
+ routes: [
+ {
+ type: 'route',
+ path: '/home/123',
+ exact: true,
+ component: 'Login',
+ },
+ ],
+ },
+ ];
+ return (
+
+
+
+
+
+ );
+};
diff --git a/packages/client/src/components/router-config/demos/demo2.tsx b/packages/client/src/components/router-config/demos/demo2.tsx
new file mode 100644
index 0000000000..8d79e1f137
--- /dev/null
+++ b/packages/client/src/components/router-config/demos/demo2.tsx
@@ -0,0 +1,65 @@
+import React, { useMemo } from 'react';
+import {
+ Link,
+ useLocation,
+ useRouteMatch,
+ MemoryRouter as Router,
+} from 'react-router-dom';
+import { createRouteSwitch, RouteRedirectProps } from '..';
+import { AdminLayout } from '../../admin-layout';
+import { AuthLayout } from '../../auth-layout';
+import { RouteSchemaRenderer } from '../../route-schema-renderer';
+
+const RouteSwitch = createRouteSwitch({
+ components: {
+ AdminLayout,
+ AuthLayout,
+ RouteSchemaRenderer,
+ },
+});
+
+const routes: Array = [
+ {
+ type: 'redirect',
+ from: '/',
+ to: '/admin',
+ exact: true,
+ },
+ {
+ type: 'route',
+ path: '/admin/:name(.+)?',
+ component: 'AdminLayout',
+ title: `后台`,
+ uiSchemaKey: 'qqzzjakwkwl',
+ },
+ {
+ type: 'route',
+ component: 'AuthLayout',
+ children: [
+ {
+ type: 'route',
+ path: '/login',
+ component: 'RouteSchemaRenderer',
+ title: `登录`,
+ uiSchemaKey: 'dtf9j0b8p9u',
+ },
+ {
+ type: 'route',
+ path: '/register',
+ component: 'RouteSchemaRenderer',
+ title: `注册`,
+ uiSchemaKey: '46qlxqam3xk',
+ },
+ ],
+ },
+];
+
+export default () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/packages/client/src/components/router-config/index.md b/packages/client/src/components/router-config/index.md
new file mode 100644
index 0000000000..7154b54467
--- /dev/null
+++ b/packages/client/src/components/router-config/index.md
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/client/src/components/router-config/index.tsx b/packages/client/src/components/router-config/index.tsx
new file mode 100644
index 0000000000..98830b7606
--- /dev/null
+++ b/packages/client/src/components/router-config/index.tsx
@@ -0,0 +1,105 @@
+import { get } from 'lodash';
+import React from 'react';
+import { useContext } from 'react';
+import { createContext } from 'react';
+import { Switch, Route, Redirect } from 'react-router';
+
+export interface RedirectProps {
+ type: 'redirect';
+ to: any;
+ path?: string;
+ exact?: boolean;
+ strict?: boolean;
+ push?: boolean;
+ from?: string;
+ [key: string]: any;
+}
+
+export interface RouteProps {
+ type: 'route';
+ path?: string | string[];
+ exact?: boolean;
+ strict?: boolean;
+ sensitive?: boolean;
+ component?: any;
+ routes?: RouteProps[],
+ [key: string]: any;
+}
+
+export type RouteRedirectProps = RedirectProps | RouteProps;
+
+export interface RouteSwitchOptions {
+ routes?: RouteRedirectProps[];
+ components?: any;
+}
+
+export interface RouteSwitchProps {
+ routes?: RouteRedirectProps[];
+ components?: any;
+}
+
+export const RouteComponentsContext = createContext(null);
+
+export function useComponent(route: RouteProps) {
+ const components = useContext(RouteComponentsContext);
+ if (typeof route.component === 'string') {
+ const component = get(components, route.component);
+ return component;
+ }
+ return route.component || (() => null);
+}
+
+export function createRouteSwitch(options: RouteSwitchOptions) {
+ function ComponentRenderer(props) {
+ const Component = useComponent(props.route);
+ return (
+
+
+
+ );
+ }
+
+ function RouteSwitch(props: RouteSwitchProps) {
+ const { routes = [] } = props;
+ if (!routes.length) {
+ return null;
+ }
+ return (
+
+
+ {routes.map((route) => {
+ if (route.type == 'redirect') {
+ return (
+
+ );
+ }
+ if (!route.path && Array.isArray(route.routes)) {
+ route.path = route.routes.map((r) => r.path) as any;
+ }
+ return (
+ {
+ return ;
+ }}
+ />
+ );
+ })}
+
+
+ );
+ }
+ return RouteSwitch;
+}
diff --git a/packages/client/src/demos/api/routes-getAccessible.ts b/packages/client/src/demos/api/routes-getAccessible.ts
index ac4f51b425..9fd1bb7f7f 100644
--- a/packages/client/src/demos/api/routes-getAccessible.ts
+++ b/packages/client/src/demos/api/routes-getAccessible.ts
@@ -1,12 +1,4 @@
-import Mock from 'mockjs';
-
export default [
- {
- type: 'redirect',
- from: '/admin',
- to: '/admin/item2',
- exact: true,
- },
{
type: 'redirect',
from: '/',
@@ -14,28 +6,30 @@ export default [
exact: true,
},
{
+ type: 'route',
path: '/admin/:name(.+)?',
component: 'AdminLayout',
- title: `后台 - ${Mock.mock('@string')}`,
- schemaName: 'menu',
+ title: `后台`,
+ uiSchemaKey: 'qqzzjakwkwl',
},
{
+ type: 'route',
component: 'AuthLayout',
- routes: [
+ children: [
{
- name: 'login',
+ type: 'route',
path: '/login',
- component: 'DefaultPage',
- title: `登录 - ${Mock.mock('@string')}`,
- schemaName: 'login',
+ component: 'RouteSchemaRenderer',
+ title: `登录`,
+ uiSchemaKey: 'dtf9j0b8p9u',
},
{
- name: 'register',
+ type: 'route',
path: '/register',
- component: 'DefaultPage',
- title: `注册 - ${Mock.mock('@string')}`,
- schemaName: 'register',
+ component: 'RouteSchemaRenderer',
+ title: `注册`,
+ uiSchemaKey: '46qlxqam3xk',
},
],
},
-]
+];
diff --git a/packages/client/src/demos/demo1.tsx b/packages/client/src/demos/demo1.tsx
index 0439e58573..71abaa3b4c 100644
--- a/packages/client/src/demos/demo1.tsx
+++ b/packages/client/src/demos/demo1.tsx
@@ -1,43 +1,51 @@
-import React, { useEffect } from 'react';
+import { useRequest } from 'ahooks';
import { Spin } from 'antd';
+import React, { useMemo } from 'react';
import {
- RouteSwitch,
- useGlobalAction,
- AdminLayout,
- AuthLayout,
- DefaultPage,
-} from '@nocobase/client';
-import {
- Link,
- useHistory,
- useLocation,
- useRouteMatch,
MemoryRouter as Router,
} from 'react-router-dom';
-import { UseRequestProvider } from 'ahooks';
-import { request } from './api';
-
-const templates = {
+import {
+ createRouteSwitch,
+ RouteRedirectProps,
AdminLayout,
AuthLayout,
- DefaultPage,
-};
+ RouteSchemaRenderer,
+} from '../';
+import { UseRequestProvider } from 'ahooks';
+import { extend } from 'umi-request';
+
+const request = extend({
+ prefix: 'http://localhost:23003/api/',
+ timeout: 1000,
+});
+
+const RouteSwitch = createRouteSwitch({
+ components: {
+ AdminLayout,
+ AuthLayout,
+ RouteSchemaRenderer,
+ },
+});
+
+const App = () => {
+ const { data, loading } = useRequest('routes:getAccessible', {
+ formatResult: (result) => result?.data,
+ });
-function App() {
- const { data, loading } = useGlobalAction('routes:getAccessible');
if (loading) {
- return ;
+ return
}
+
return (
-
+
);
-}
+};
-export default function IndexPage() {
+export default () => {
return (
{
@@ -231,6 +234,9 @@ export function useDesignable(path?: any) {
if (!property.name) {
property.name = uid();
}
+ if (target['key']) {
+ property['parentKey'] = target['key'];
+ }
target.addProperty(property.name, property);
// BUG: 空 properties 时,addProperty 无反应。
const tmp = { name: uid() };
@@ -251,6 +257,9 @@ export function useDesignable(path?: any) {
if (!property.name) {
property.name = uid();
}
+ if (target['parentKey']) {
+ property['parentKey'] = target['parentKey'];
+ }
addPropertyAfter(target, property);
refresh();
return target.parent.properties[property.name];
@@ -267,6 +276,9 @@ export function useDesignable(path?: any) {
if (!property.name) {
property.name = uid();
}
+ if (target['parentKey']) {
+ property['parentKey'] = target['parentKey'];
+ }
addPropertyBefore(target, property);
refresh();
return target.parent.properties[property.name];
diff --git a/packages/client/src/schemas/menu/index.md b/packages/client/src/schemas/menu/index.md
index 0f60866266..30bc108d86 100644
--- a/packages/client/src/schemas/menu/index.md
+++ b/packages/client/src/schemas/menu/index.md
@@ -412,6 +412,68 @@ export default () => {
}
```
+### 设计器模式 - 菜单项为空时
+
+```tsx
+import React, { useRef, useState } from 'react';
+import { SchemaRenderer } from '../';
+import { MenuContainerContext } from './';
+import { Layout } from 'antd';
+
+export default () => {
+ const sideMenuRef = useRef();
+
+ const [activeKey, setActiveKey] = useState('item3');
+
+ const onSelect = (info) => {
+ setActiveKey(info.key);
+ console.log({ info })
+ }
+
+ const schema = {
+ type: 'object',
+ properties: {
+ menu1: {
+ type: 'void',
+ 'x-component': 'Menu',
+ 'x-designable-bar': 'Menu.DesignableBar',
+ 'x-component-props': {
+ defaultSelectedKeys: [activeKey],
+ mode: 'mix',
+ theme: 'dark',
+ sideMenuRef: '{{ sideMenuRef }}',
+ onSelect: '{{ onSelect }}',
+ },
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {activeKey}
+
+
+
+
+ )
+}
+```
+
### Menu.Action
diff --git a/packages/client/src/schemas/menu/index.tsx b/packages/client/src/schemas/menu/index.tsx
index 937620fb41..f5b301c86e 100644
--- a/packages/client/src/schemas/menu/index.tsx
+++ b/packages/client/src/schemas/menu/index.tsx
@@ -94,22 +94,30 @@ function useDesignableBar() {
}
export const Menu: MenuType = observer((props: any) => {
- const { sideMenuRef, onSelect, mode, defaultSelectedKeys, ...others } = props;
+ const {
+ children,
+ sideMenuRef,
+ onSelect,
+ mode,
+ defaultSelectedKeys,
+ ...others
+ } = props;
let defaultSelectedKey = defaultSelectedKeys ? defaultSelectedKeys[0] : null;
- const schema = useFieldSchema();
- const { schema: designableSchema, refresh } = useDesignable();
+ // const schema = useFieldSchema();
+ const { schema, schema: designableSchema, refresh } = useDesignable();
const designableBar = schema['x-designable-bar'];
const history = useHistory();
const renderSideMenu = (selectedKey) => {
- if (!selectedKey) {
- return;
- }
if ((mode as any) !== 'mix') {
return;
}
if (!sideMenuRef || !sideMenuRef.current) {
return;
}
+ if (!selectedKey || !schema.properties) {
+ sideMenuRef.current.style.display = 'none';
+ return;
+ }
const subSchema = schema.properties[selectedKey];
if (!subSchema) {
sideMenuRef.current.style.display = 'none';
@@ -160,6 +168,7 @@ export const Menu: MenuType = observer((props: any) => {
...newProps,
[`${uid()}-add-new`]: {
type: 'void',
+ parentKey: subSchema['key'],
'x-component': 'Menu.AddNew',
},
},
@@ -173,6 +182,8 @@ export const Menu: MenuType = observer((props: any) => {
console.log({ defaultSelectedKey }, schema.properties);
renderSideMenu(defaultSelectedKey);
});
+ const isEmpty = !Object.keys(designableSchema.properties || {}).length;
+ console.log({ designableSchema })
return (
{
onSelect && onSelect(info);
}}
mode={(mode as any) === 'mix' ? 'horizontal' : mode}
- />
+ >
+ {!isEmpty ? (
+
+ ) : (
+ {
+ Object.keys(subSchema.properties).forEach((name) => {
+ if (name === 'add-new') {
+ return;
+ }
+ designableSchema.addProperty(
+ name,
+ subSchema.properties[name].toJSON(),
+ );
+ });
+ refresh();
+ }}
+ schema={{
+ type: 'object',
+ properties: {
+ 'add-new': {
+ parentKey: schema['key'],
+ type: 'void',
+ 'x-component': 'Menu.AddNew',
+ },
+ },
+ }}
+ />
+ )}
+
);
});
const AddNewAction = () => {
- const { insertBefore } = useDesignable();
+ const fieldSchema = useFieldSchema();
+ const { schema, insertBefore } = useDesignable();
+ console.log('AddNewAction', schema, fieldSchema);
return (
{
{
- insertBefore({
+ const s = insertBefore({
type: 'void',
title: uid(),
+ key: uid(),
'x-component': 'Menu.Item',
});
+ console.log('s.s.s.s.s.s', s)
}}
style={{ minWidth: 150 }}
>
@@ -375,7 +419,7 @@ Menu.DesignableBar = (props) => {
const field = useField();
const fieldSchema = useFieldSchema();
const [visible, setVisible] = useState(false);
- const { schema, remove, refresh } = useDesignable();
+ const { schema, remove, refresh, insertAfter } = useDesignable();
return (
{
>
修改标题
+
{
+ const s = insertAfter({
+ type: 'void',
+ key: uid(),
+ title: uid(),
+ 'x-component': 'Menu.SubMenu',
+ });
+ }}>插入
{
Modal.confirm({
diff --git a/packages/plugin-routes/src/models/route.ts b/packages/plugin-routes/src/models/route.ts
deleted file mode 100644
index 443913eb71..0000000000
--- a/packages/plugin-routes/src/models/route.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import _ from 'lodash';
-import { Model } from '@nocobase/database';
-
-export class Route extends Model {
-
-}
diff --git a/packages/plugin-routes/.npmignore b/packages/plugin-ui-router/.npmignore
similarity index 100%
rename from packages/plugin-routes/.npmignore
rename to packages/plugin-ui-router/.npmignore
diff --git a/packages/plugin-routes/package.json b/packages/plugin-ui-router/package.json
similarity index 78%
rename from packages/plugin-routes/package.json
rename to packages/plugin-ui-router/package.json
index 6fc081878c..0813e6cf55 100644
--- a/packages/plugin-routes/package.json
+++ b/packages/plugin-ui-router/package.json
@@ -1,5 +1,5 @@
{
- "name": "@nocobase/plugin-routes",
+ "name": "@nocobase/plugin-ui-router",
"version": "0.4.0-alpha.7",
"main": "lib/index.js",
"license": "MIT",
@@ -7,7 +7,8 @@
"@nocobase/database": "^0.4.0-alpha.7",
"@nocobase/resourcer": "^0.4.0-alpha.7",
"@nocobase/server": "^0.4.0-alpha.7",
- "deepmerge": "^4.2.2"
+ "deepmerge": "^4.2.2",
+ "flat-to-nested": "^1.1.1"
},
"devDependencies": {
"@nocobase/actions": "^0.4.0-alpha.7"
diff --git a/packages/plugin-routes/src/__tests__/index.ts b/packages/plugin-ui-router/src/__tests__/index.ts
similarity index 100%
rename from packages/plugin-routes/src/__tests__/index.ts
rename to packages/plugin-ui-router/src/__tests__/index.ts
diff --git a/packages/plugin-routes/src/__tests__/routes.test.ts b/packages/plugin-ui-router/src/__tests__/routes.test.ts
similarity index 78%
rename from packages/plugin-routes/src/__tests__/routes.test.ts
rename to packages/plugin-ui-router/src/__tests__/routes.test.ts
index 6b46d3fec0..789f026643 100644
--- a/packages/plugin-routes/src/__tests__/routes.test.ts
+++ b/packages/plugin-ui-router/src/__tests__/routes.test.ts
@@ -15,20 +15,20 @@ describe('routes', () => {
afterEach(() => app.database.close());
- it.only('create route', async () => {
- const Route = db.getModel('routes');
- const item = {
- path: '/admin/:name(.+)?',
- component: 'AdminLayout',
- title: `后台`,
- uiSchema: {
- name: 'menu',
- },
- };
- console.log(Route.associations);
- const route = await Route.create(item);
- await route.updateAssociations(item);
- });
+ // it.only('create route', async () => {
+ // const Route = db.getModel('routes');
+ // const item = {
+ // path: '/admin/:name(.+)?',
+ // component: 'AdminLayout',
+ // title: `后台`,
+ // uiSchema: {
+ // name: 'menu',
+ // },
+ // };
+ // console.log(Route.associations);
+ // const route = await Route.create(item);
+ // await route.updateAssociations(item);
+ // });
it('create route', async () => {
const Route = db.getModel('routes');
diff --git a/packages/plugin-ui-router/src/actions/getAccessible.ts b/packages/plugin-ui-router/src/actions/getAccessible.ts
new file mode 100644
index 0000000000..49d9201ab8
--- /dev/null
+++ b/packages/plugin-ui-router/src/actions/getAccessible.ts
@@ -0,0 +1,18 @@
+import { Model, ModelCtor } from '@nocobase/database';
+import { actions } from '@nocobase/actions';
+import FlatToNested from 'flat-to-nested';
+
+const flatToNested = new FlatToNested({
+ id: 'key',
+ parent: 'parentKey',
+ children: 'routes',
+});
+
+export default async (ctx: actions.Context, next: actions.Next) => {
+ const { resourceKey } = ctx.action.params;
+ const Route = ctx.db.getModel('routes');
+ const routes = await Route.findAll();
+ const data = flatToNested.convert(routes.map(route => route.toProps()));
+ ctx.body = data.routes;
+ await next();
+}
diff --git a/packages/plugin-routes/src/collections/routes.ts b/packages/plugin-ui-router/src/collections/routes.ts
similarity index 97%
rename from packages/plugin-routes/src/collections/routes.ts
rename to packages/plugin-ui-router/src/collections/routes.ts
index 66ade6d26c..3d01f95298 100644
--- a/packages/plugin-routes/src/collections/routes.ts
+++ b/packages/plugin-ui-router/src/collections/routes.ts
@@ -3,6 +3,7 @@ import { TableOptions } from '@nocobase/database';
export default {
name: 'routes',
title: '路由表',
+ model: 'Route',
fields: [
{
type: 'uid',
diff --git a/packages/plugin-routes/src/models/index.ts b/packages/plugin-ui-router/src/models/index.ts
similarity index 100%
rename from packages/plugin-routes/src/models/index.ts
rename to packages/plugin-ui-router/src/models/index.ts
diff --git a/packages/plugin-ui-router/src/models/route.ts b/packages/plugin-ui-router/src/models/route.ts
new file mode 100644
index 0000000000..cd8d72102f
--- /dev/null
+++ b/packages/plugin-ui-router/src/models/route.ts
@@ -0,0 +1,33 @@
+import _ from 'lodash';
+import { Model } from '@nocobase/database';
+
+export class Route extends Model {
+ static async create(value?: any, options?: any): Promise {
+ // console.log({ value });
+ const attributes = this.toAttributes(value);
+ // @ts-ignore
+ const model: Model = await super.create(attributes, options);
+ return model;
+ }
+
+ static toAttributes(value = {}): any {
+ const data = _.cloneDeep(value);
+ const keys = [
+ ...Object.keys(this.rawAttributes),
+ ...Object.keys(this.associations),
+ ];
+ const attrs = _.pick(data, keys);
+ const options = _.omit(data, keys);
+ return { ...attrs, options };
+ }
+
+ toProps() {
+ const json = this.toJSON();
+ const data: any = _.omit(json, ['options', 'created_at', 'updated_at', 'ui_schema_key']);
+ const options = json['options'] || {};
+ if (json['ui_schema_key']) {
+ data.uiSchemaKey = json['ui_schema_key'];
+ }
+ return { ...data, ...options }
+ }
+}
diff --git a/packages/plugin-routes/src/server.ts b/packages/plugin-ui-router/src/server.ts
similarity index 73%
rename from packages/plugin-routes/src/server.ts
rename to packages/plugin-ui-router/src/server.ts
index 0a1bdeee64..2c9a3496f2 100644
--- a/packages/plugin-routes/src/server.ts
+++ b/packages/plugin-ui-router/src/server.ts
@@ -2,6 +2,7 @@ import path from 'path';
import { Application } from '@nocobase/server';
import { registerModels } from '@nocobase/database';
import * as models from './models';
+import getAccessible from './actions/getAccessible';
export default async function (this: Application, options = {}) {
const database = this.database;
@@ -10,4 +11,6 @@ export default async function (this: Application, options = {}) {
database.import({
directory: path.resolve(__dirname, 'collections'),
});
+
+ this.resourcer.registerActionHandler('routes:getAccessible', getAccessible);
}
diff --git a/packages/plugin-routes/src/utils.ts b/packages/plugin-ui-router/src/utils.ts
similarity index 100%
rename from packages/plugin-routes/src/utils.ts
rename to packages/plugin-ui-router/src/utils.ts
diff --git a/packages/plugin-ui-schema/src/actions/getTree.ts b/packages/plugin-ui-schema/src/actions/getTree.ts
new file mode 100644
index 0000000000..92683baaf5
--- /dev/null
+++ b/packages/plugin-ui-schema/src/actions/getTree.ts
@@ -0,0 +1,15 @@
+import { Model, ModelCtor } from '@nocobase/database';
+import { actions } from '@nocobase/actions';
+
+export default async (ctx: actions.Context, next: actions.Next) => {
+ const { resourceKey } = ctx.action.params;
+ const UISchema = ctx.db.getModel('ui_schemas');
+ const schema = await UISchema.findByPk(resourceKey);
+ const property = schema.toProperty();
+ const properties = await schema.getProperties();
+ if (Object.keys(properties).length) {
+ property.properties = properties;
+ }
+ ctx.body = property;
+ await next();
+}
diff --git a/packages/plugin-ui-schema/src/server.ts b/packages/plugin-ui-schema/src/server.ts
index 0146073ccf..8318f9ff43 100644
--- a/packages/plugin-ui-schema/src/server.ts
+++ b/packages/plugin-ui-schema/src/server.ts
@@ -2,6 +2,7 @@ import path from 'path';
import { Application } from '@nocobase/server';
import { registerModels, Table } from '@nocobase/database';
import * as models from './models';
+import getTree from './actions/getTree';
export default async function (this: Application, options = {}) {
const database = this.database;
@@ -11,9 +12,11 @@ export default async function (this: Application, options = {}) {
directory: path.resolve(__dirname, 'collections'),
});
- database.getModel('ui_schemas').beforeCreate((model) => {
- if (!model.get('name')) {
- model.set('name', model.get('key'));
- }
- });
+ // database.getModel('ui_schemas').beforeCreate((model) => {
+ // if (!model.get('name')) {
+ // model.set('name', model.get('key'));
+ // }
+ // });
+
+ this.resourcer.registerActionHandler('ui_schemas:getTree', getTree);
}
diff --git a/packages/server/src/middleware.ts b/packages/server/src/middleware.ts
index 038722fa4c..d088b81f94 100644
--- a/packages/server/src/middleware.ts
+++ b/packages/server/src/middleware.ts
@@ -1,5 +1,5 @@
import compose from 'koa-compose';
-import { pathToRegexp } from 'path-to-regexp';
+import pathToRegexp from 'path-to-regexp';
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, parseQuery, ResourcerContext, ResourceType } from '@nocobase/resourcer';
import Database, { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
diff --git a/packages/server/src/middlewares/db-resource-router.ts b/packages/server/src/middlewares/db-resource-router.ts
index 1256998f4e..7628940762 100644
--- a/packages/server/src/middlewares/db-resource-router.ts
+++ b/packages/server/src/middlewares/db-resource-router.ts
@@ -1,5 +1,5 @@
import compose from 'koa-compose';
-import { pathToRegexp } from 'path-to-regexp';
+import pathToRegexp from 'path-to-regexp';
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, parseQuery, ResourcerContext, ResourceType } from '@nocobase/resourcer';
import Database, { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
diff --git a/yarn.lock b/yarn.lock
index a8a79366d8..97ca74b588 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5278,6 +5278,20 @@ boxen@^3.0.0:
type-fest "^0.3.0"
widest-line "^2.0.0"
+boxen@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
+ integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==
+ dependencies:
+ ansi-align "^3.0.0"
+ camelcase "^5.3.1"
+ chalk "^3.0.0"
+ cli-boxes "^2.2.0"
+ string-width "^4.1.0"
+ term-size "^2.1.0"
+ type-fest "^0.8.1"
+ widest-line "^3.1.0"
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -5702,6 +5716,14 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
+chalk@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+ integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.1"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
@@ -5803,7 +5825,7 @@ chokidar@^2.0.4:
optionalDependencies:
fsevents "^1.2.7"
-chokidar@^3.0.2:
+chokidar@^3.0.2, chokidar@^3.2.2:
version "3.5.2"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
@@ -6187,6 +6209,18 @@ configstore@^4.0.0:
write-file-atomic "^2.0.0"
xdg-basedir "^3.0.0"
+configstore@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
+ integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
+ dependencies:
+ dot-prop "^5.2.0"
+ graceful-fs "^4.1.2"
+ make-dir "^3.0.0"
+ unique-string "^2.0.0"
+ write-file-atomic "^3.0.0"
+ xdg-basedir "^4.0.0"
+
connected-react-router@6.5.2:
version "6.5.2"
resolved "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.5.2.tgz#422af70f86cb276681e20ab4295cf27dd9b6c7e3"
@@ -6534,6 +6568,11 @@ crypto-random-string@^1.0.0:
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
+crypto-random-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
+ integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
+
crypto-random-string@^3.3.0, crypto-random-string@^3.3.1:
version "3.3.1"
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.3.1.tgz#13cee94cac8001e4842501608ef779e0ed08f82d"
@@ -6878,7 +6917,7 @@ debug@3.1.0, debug@~3.1.0:
dependencies:
ms "2.0.0"
-debug@3.X, debug@^3.1.0, debug@^3.2.7:
+debug@3.X, debug@^3.1.0, debug@^3.2.6, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@@ -7679,6 +7718,11 @@ escalade@^3.1.1:
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+escape-goat@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
+ integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
+
escape-html@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -8370,6 +8414,11 @@ flat-cache@^3.0.4:
flatted "^3.1.0"
rimraf "^3.0.2"
+flat-to-nested@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/flat-to-nested/-/flat-to-nested-1.1.1.tgz#ec183cd9a72f6bfbf8ca21acb0fc8235e509f581"
+ integrity sha512-Sym5oik6BO9JnsDEjv9Q9hPTCexG2ttk0UiM2mgLEiCiiUOQr8acBd33r8ixnoSGR0HAxPoP8WtLAL5oV46IhQ==
+
flatted@^3.1.0:
version "3.1.1"
resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
@@ -8811,6 +8860,13 @@ global-dirs@^0.1.0:
dependencies:
ini "^1.3.4"
+global-dirs@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d"
+ integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==
+ dependencies:
+ ini "1.3.7"
+
global@^4.3.2:
version "4.4.0"
resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
@@ -9480,6 +9536,11 @@ iferr@^0.1.5:
resolved "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
+ignore-by-default@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+ integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=
+
ignore-walk@^3.0.1:
version "3.0.4"
resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335"
@@ -9647,6 +9708,11 @@ inherits@2.0.3:
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+ini@1.3.7:
+ version "1.3.7"
+ resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
+ integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
+
ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
version "1.3.8"
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
@@ -9998,6 +10064,14 @@ is-installed-globally@^0.1.0:
global-dirs "^0.1.0"
is-path-inside "^1.0.0"
+is-installed-globally@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
+ integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
+ dependencies:
+ global-dirs "^2.0.1"
+ is-path-inside "^3.0.1"
+
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@@ -10026,6 +10100,11 @@ is-npm@^3.0.0:
resolved "https://registry.npmjs.org/is-npm/-/is-npm-3.0.0.tgz#ec9147bfb629c43f494cf67936a961edec7e8053"
integrity sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA==
+is-npm@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
+ integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
+
is-number-object@^1.0.4:
version "1.0.5"
resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
@@ -10060,6 +10139,11 @@ is-path-inside@^1.0.0:
dependencies:
path-is-inside "^1.0.1"
+is-path-inside@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
@@ -12889,6 +12973,22 @@ node-schedule@^2.0.0:
long-timeout "0.1.1"
sorted-array-functions "^1.3.0"
+nodemon@^2.0.12:
+ version "2.0.12"
+ resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5"
+ integrity sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==
+ dependencies:
+ chokidar "^3.2.2"
+ debug "^3.2.6"
+ ignore-by-default "^1.0.1"
+ minimatch "^3.0.4"
+ pstree.remy "^1.1.7"
+ semver "^5.7.1"
+ supports-color "^5.5.0"
+ touch "^3.1.0"
+ undefsafe "^2.0.3"
+ update-notifier "^4.1.0"
+
nopt@^4.0.1:
version "4.0.3"
resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
@@ -12904,6 +13004,13 @@ nopt@^5.0.0:
dependencies:
abbrev "1"
+nopt@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+ integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
+ dependencies:
+ abbrev "1"
+
normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -14812,6 +14919,11 @@ psl@^1.1.28, psl@^1.1.33:
resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
+pstree.remy@^1.1.7:
+ version "1.1.8"
+ resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
+ integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
+
public-encrypt@^4.0.0:
version "4.0.3"
resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
@@ -14864,6 +14976,13 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+pupa@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
+ integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+ dependencies:
+ escape-goat "^2.0.0"
+
q@^1.1.2, q@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -16771,6 +16890,13 @@ semver-diff@^2.0.0:
dependencies:
semver "^5.0.3"
+semver-diff@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
+ integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
+ dependencies:
+ semver "^6.3.0"
+
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -17385,7 +17511,7 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
-string-width@^4.1.0, string-width@^4.2.0:
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
version "4.2.2"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
@@ -17626,7 +17752,7 @@ supports-color@^3.2.3:
dependencies:
has-flag "^1.0.0"
-supports-color@^5.3.0, supports-color@^5.4.0:
+supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -17755,6 +17881,11 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
+term-size@^2.1.0:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
+ integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
+
terminal-link@^2.0.0:
version "2.1.1"
resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@@ -18024,6 +18155,13 @@ toposort-class@^1.0.1:
resolved "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988"
integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=
+touch@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
+ integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
+ dependencies:
+ nopt "~1.0.10"
+
tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -18408,6 +18546,13 @@ unc-path-regex@^0.1.2:
resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
+undefsafe@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"
+ integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==
+ dependencies:
+ debug "^2.2.0"
+
underscore@^1.13.1:
version "1.13.1"
resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
@@ -18503,6 +18648,13 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
+unique-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
+ integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
+ dependencies:
+ crypto-random-string "^2.0.0"
+
unist-builder@^2.0.0:
version "2.0.3"
resolved "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436"
@@ -18619,6 +18771,25 @@ update-notifier@3.0.0:
semver-diff "^2.0.0"
xdg-basedir "^3.0.0"
+update-notifier@^4.1.0:
+ version "4.1.3"
+ resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3"
+ integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==
+ dependencies:
+ boxen "^4.2.0"
+ chalk "^3.0.0"
+ configstore "^5.0.1"
+ has-yarn "^2.1.0"
+ import-lazy "^2.1.0"
+ is-ci "^2.0.0"
+ is-installed-globally "^0.3.1"
+ is-npm "^4.0.0"
+ is-yarn-global "^0.3.0"
+ latest-version "^5.0.0"
+ pupa "^2.0.1"
+ semver-diff "^3.1.1"
+ xdg-basedir "^4.0.0"
+
upper-case@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a"
@@ -19084,6 +19255,13 @@ widest-line@^2.0.0:
dependencies:
string-width "^2.1.1"
+widest-line@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
+ integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+ dependencies:
+ string-width "^4.0.0"
+
wildcard@^1.1.0:
version "1.1.2"
resolved "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz#a7020453084d8cd2efe70ba9d3696263de1710a5"
@@ -19237,6 +19415,11 @@ xdg-basedir@^3.0.0:
resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
+xdg-basedir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
+ integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
+
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"