diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml new file mode 100644 index 0000000..18fc071 --- /dev/null +++ b/.github/workflows/build-docker-image.yml @@ -0,0 +1,118 @@ +name: Build Docker Image + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - "main" + - "next" + pull_request: + branches: + - "**" +jobs: + build-and-push: + # Don't run just after creating repo from template + if: ${{ github.run_number != 1 && github.event.repository.name != 'plugin-pro-tpl' }} + runs-on: ubuntu-latest + services: + verdaccio: + image: verdaccio/verdaccio:latest + ports: + - 4873:4873 + steps: + - name: Get plugin name + id: plugin-name + run: | + PLUGIN_NAME=$(echo ${{ github.event.repository.name }} | sed "s/plugin-//") + echo "pluginName=$PLUGIN_NAME" >> $GITHUB_OUTPUT + - name: Checkout nocobase/nocobase + uses: actions/checkout@v4 + with: + repository: nocobase/nocobase + ref: main + fetch-depth: 0 + - name: git checkout ${{ github.event.pull_request.base.ref }} + run: git checkout ${{ github.event.pull_request.base.ref }} + continue-on-error: true + - name: git checkout ${{ github.head_ref || github.ref_name }} + run: git checkout ${{ github.head_ref || github.ref_name }} + continue-on-error: true + - name: Checkout plugin + uses: actions/checkout@v3 + with: + path: packages/pro-plugins/@nocobase/${{ github.event.repository.name }} + - name: rm .git + run: rm -rf packages/pro-plugins/@nocobase/${{ github.event.repository.name }}/.git && git config --global user.email "you@example.com" && git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + nocobase/nocobase + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - name: Login to Aliyun Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ secrets.ALI_DOCKER_REGISTRY }} + username: ${{ secrets.ALI_DOCKER_USERNAME }} + password: ${{ secrets.ALI_DOCKER_PASSWORD }} + - name: Set tags + id: set-tags + run: | + echo "::set-output name=tags::${{ secrets.ALI_DOCKER_REGISTRY }}/${{ steps.meta.outputs.tags }}-${{ steps.plugin-name.outputs.pluginName }}" + - name: IMAGE_TAG + env: + IMAGE_TAG: ${{ steps.meta.outputs.tags }} + run: | + echo $IMAGE_TAG + - name: Set variables + run: | + target_directory="./packages/pro-plugins/@nocobase" + subdirectories=$(find "$target_directory" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | tr '\n' ' ') + trimmed_variable=$(echo "$subdirectories" | xargs) + packageNames="@nocobase/${trimmed_variable// / @nocobase/}" + pluginNames="${trimmed_variable//plugin-/}" + BEFORE_PACK_NOCOBASE="yarn add $packageNames -W" + APPEND_PRESET_LOCAL_PLUGINS="${pluginNames// /,}" + echo "var1=$BEFORE_PACK_NOCOBASE" >> $GITHUB_OUTPUT + echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT + id: vars + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: Dockerfile + build-args: | + VERDACCIO_URL=http://localhost:4873/ + COMMIT_HASH=${GITHUB_SHA} + PLUGINS_DIRS=pro-plugins + BEFORE_PACK_NOCOBASE=${{ steps.vars.outputs.var1 }} + APPEND_PRESET_LOCAL_PLUGINS=${{ steps.vars.outputs.var2 }} + push: true + tags: ${{ steps.set-tags.outputs.tags }} + - name: Deploy NocoBase + env: + IMAGE_TAG: ${{ steps.meta.outputs.tags }} + run: | + echo $IMAGE_TAG + export APP_NAME=$(echo $IMAGE_TAG | cut -d ":" -f 2)-${{ steps.plugin-name.outputs.pluginName }} + echo $APP_NAME + curl --retry 2 --location --request POST "${{secrets.NOCOBASE_DEPLOY_HOST}}$APP_NAME" \ + --header 'Content-Type: application/json' \ + -d "{ + \"tag\": \"$APP_NAME\", + \"dialect\": \"postgres\" + }" diff --git a/.github/workflows/manual-build-docker-image.yml b/.github/workflows/manual-build-docker-image.yml new file mode 100644 index 0000000..b9a012b --- /dev/null +++ b/.github/workflows/manual-build-docker-image.yml @@ -0,0 +1,119 @@ +name: Build Docker Image + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + inputs: + pr_number: + description: "Please enter the pr number of nocobase/nocobase" + required: false +jobs: + build-and-push: + runs-on: ubuntu-latest + services: + verdaccio: + image: verdaccio/verdaccio:latest + ports: + - 4873:4873 + steps: + - name: Get plugin name + id: plugin-name + run: | + PLUGIN_NAME=$(echo ${{ github.event.repository.name }} | sed "s/plugin-//") + echo "pluginName=$PLUGIN_NAME" >> $GITHUB_OUTPUT + - name: Checkout nocobase/nocobase + uses: actions/checkout@v4 + with: + repository: nocobase/nocobase + ref: main + fetch-depth: 0 + - name: gh pr checkout {{ inputs.pr_number }} + run: gh pr checkout {{ inputs.pr_number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + - name: git checkout ${{ github.event.pull_request.base.ref }} + run: git checkout ${{ github.event.pull_request.base.ref }} + continue-on-error: true + - name: git checkout ${{ github.head_ref || github.ref_name }} + run: git checkout ${{ github.head_ref || github.ref_name }} + continue-on-error: true + - name: Checkout plugin + uses: actions/checkout@v3 + with: + path: packages/pro-plugins/@nocobase/${{ github.event.repository.name }} + - name: rm .git + run: rm -rf packages/pro-plugins/@nocobase/${{ github.event.repository.name }}/.git && git config --global user.email "you@example.com" && git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + nocobase/nocobase + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - name: Login to Aliyun Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ secrets.ALI_DOCKER_REGISTRY }} + username: ${{ secrets.ALI_DOCKER_USERNAME }} + password: ${{ secrets.ALI_DOCKER_PASSWORD }} + - name: Set tags + id: set-tags + run: | + echo "::set-output name=tags::${{ secrets.ALI_DOCKER_REGISTRY }}/${{ steps.meta.outputs.tags }}-${{ steps.plugin-name.outputs.pluginName }}" + - name: IMAGE_TAG + env: + IMAGE_TAG: ${{ steps.meta.outputs.tags }} + run: | + echo $IMAGE_TAG + - name: Set variables + run: | + target_directory="./packages/pro-plugins/@nocobase" + subdirectories=$(find "$target_directory" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | tr '\n' ' ') + trimmed_variable=$(echo "$subdirectories" | xargs) + packageNames="@nocobase/${trimmed_variable// / @nocobase/}" + pluginNames="${trimmed_variable//plugin-/}" + BEFORE_PACK_NOCOBASE="yarn add $packageNames -W" + APPEND_PRESET_LOCAL_PLUGINS="${pluginNames// /,}" + echo "var1=$BEFORE_PACK_NOCOBASE" >> $GITHUB_OUTPUT + echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT + id: vars + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: Dockerfile + build-args: | + VERDACCIO_URL=http://localhost:4873/ + COMMIT_HASH=${GITHUB_SHA} + PLUGINS_DIRS=pro-plugins + BEFORE_PACK_NOCOBASE=${{ steps.vars.outputs.var1 }} + APPEND_PRESET_LOCAL_PLUGINS=${{ steps.vars.outputs.var2 }} + push: true + tags: ${{ steps.set-tags.outputs.tags }} + - name: Deploy NocoBase + env: + IMAGE_TAG: ${{ steps.meta.outputs.tags }} + run: | + echo $IMAGE_TAG + export APP_NAME=$(echo $IMAGE_TAG | cut -d ":" -f 2)-${{ steps.plugin-name.outputs.pluginName }} + echo $APP_NAME + curl --retry 2 --location --request POST "${{secrets.NOCOBASE_DEPLOY_HOST}}$APP_NAME" \ + --header 'Content-Type: application/json' \ + -d "{ + \"tag\": \"$APP_NAME\", + \"dialect\": \"postgres\" + }" diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..65f5e87 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/README.md b/README.md new file mode 100644 index 0000000..de13153 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# @nocobase-sample/plugin-shared-forms diff --git a/client.d.ts b/client.d.ts new file mode 100644 index 0000000..6c459cb --- /dev/null +++ b/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/client.js b/client.js new file mode 100644 index 0000000..b6e3be7 --- /dev/null +++ b/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..af999d0 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "@nocobase/plugin-shared-forms", + "version": "1.3.0-alpha", + "main": "dist/server/index.js", + "dependencies": {}, + "peerDependencies": { + "@nocobase/client": "1.x", + "@nocobase/server": "1.x", + "@nocobase/test": "1.x" + } +} diff --git a/server.d.ts b/server.d.ts new file mode 100644 index 0000000..c41081d --- /dev/null +++ b/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/server.js b/server.js new file mode 100644 index 0000000..9728420 --- /dev/null +++ b/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/src/client/ConfigureLink.tsx b/src/client/ConfigureLink.tsx new file mode 100644 index 0000000..9a42c47 --- /dev/null +++ b/src/client/ConfigureLink.tsx @@ -0,0 +1,8 @@ +import { useFilterByTk } from '@nocobase/client'; +import React from 'react'; +import { Link } from 'react-router-dom'; + +export function ConfigureLink() { + const value = useFilterByTk(); + return Configure; +} diff --git a/src/client/PublicSharedForm.tsx b/src/client/PublicSharedForm.tsx new file mode 100644 index 0000000..aad749c --- /dev/null +++ b/src/client/PublicSharedForm.tsx @@ -0,0 +1,21 @@ +import { SchemaComponent, SchemaComponentProvider, useRequest } from '@nocobase/client'; +import { Spin } from 'antd'; +import React from 'react'; +import { useParams } from 'react-router'; + +export function PublicSharedForm() { + const params = useParams(); + const { data, loading } = useRequest({ + url: `sharedForms:getMeta/${params.name}`, + }); + if (loading) { + return ; + } + return ( +
+ + + +
+ ); +} diff --git a/src/client/SharedFormConfigure.tsx b/src/client/SharedFormConfigure.tsx new file mode 100644 index 0000000..37f8ec9 --- /dev/null +++ b/src/client/SharedFormConfigure.tsx @@ -0,0 +1,42 @@ +import { RemoteSchemaComponent } from '@nocobase/client'; +import { Breadcrumb, Button, Space } from 'antd'; +import React from 'react'; +import { useParams } from 'react-router'; +import { Link } from 'react-router-dom'; + +export function SharedFormConfigure() { + const params = useParams(); + return ( +
+
+ Shared forms, + }, + { + title: 'Test', + }, + ]} + /> + + + + + +
+
+ +
+
+ ); +} diff --git a/src/client/SharedFormTable.tsx b/src/client/SharedFormTable.tsx new file mode 100644 index 0000000..7e6a030 --- /dev/null +++ b/src/client/SharedFormTable.tsx @@ -0,0 +1,426 @@ +import { createForm } from '@formily/core'; +import { useForm } from '@formily/react'; +import { uid } from '@formily/shared'; +import { + ActionProps, + ExtendCollectionsProvider, + ISchema, + SchemaComponent, + useActionContext, + useAPIClient, + useCollection, + useCollectionRecordData, + useDataBlockRequest, + useDataBlockResource, +} from '@nocobase/client'; +import { App as AntdApp } from 'antd'; +import React, { useMemo } from 'react'; +import { ConfigureLink } from './ConfigureLink'; + +const sharedFormsCollection = { + name: 'sharedForms', + filterTargetKey: 'slug', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + title: 'Title', + required: true, + 'x-component': 'Input', + }, + }, + { + type: 'text', + name: 'description', + interface: 'textarea', + uiSchema: { + type: 'string', + title: 'Description', + 'x-component': 'Input.TextArea', + }, + }, + { + type: 'string', + name: 'dataSource', + interface: 'input', + uiSchema: { + type: 'string', + title: 'Data source', + required: true, + 'x-component': 'Input', + }, + }, + { + type: 'string', + name: 'collection', + interface: 'collection', + uiSchema: { + type: 'string', + title: 'Collection', + required: true, + 'x-component': 'CollectionSelect', + }, + }, + { + type: 'password', + name: 'password', + interface: 'password', + uiSchema: { + type: 'string', + title: 'Password', + 'x-component': 'Password', + }, + }, + ], +}; + +const initialSchema = (values) => { + return { + type: 'void', + name: uid(), + 'x-toolbar': 'BlockSchemaToolbar', + 'x-toolbar-props': { + draggable: false, + }, + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + collection: values.collection, + dataSource: values.dataSource || 'main', + }, + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + properties: { + a69vmspkv8h: { + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + }, + l9xfwp6cfh1: { + type: 'void', + 'x-component': 'ActionBar', + 'x-initializer': 'createForm:configureActions', + 'x-component-props': { + layout: 'one-column', + }, + }, + }, + }, + }, + }; +}; + +const useSubmitActionProps = () => { + const { setVisible } = useActionContext(); + const { message } = AntdApp.useApp(); + const form = useForm(); + const resource = useDataBlockResource(); + const { runAsync } = useDataBlockRequest(); + const collection = useCollection(); + const api = useAPIClient(); + return { + type: 'primary', + async onClick() { + await form.submit(); + const values = form.values; + if (values[collection.filterTargetKey]) { + await resource.update({ + values, + filterByTk: values[collection.filterTargetKey], + }); + } else { + const slug = uid(); + const schema = initialSchema(values); + schema['x-uid'] = slug; + await resource.create({ + values: { + ...values, + slug, + }, + }); + await api.resource('uiSchemas').insert({ values: schema }); + } + await runAsync(); + message.success('Saved successfully!'); + setVisible(false); + }, + }; +}; + +const useEditFormProps = () => { + const recordData = useCollectionRecordData(); + const form = useMemo( + () => + createForm({ + initialValues: recordData, + }), + [], + ); + + return { + form, + }; +}; + +function useDeleteActionProps(): ActionProps { + const { message } = AntdApp.useApp(); + const record = useCollectionRecordData(); + const resource = useDataBlockResource(); + const { runAsync } = useDataBlockRequest(); + const collection = useCollection(); + return { + confirm: { + title: 'Delete', + content: 'Are you sure you want to delete it?', + }, + async onClick() { + await resource.destroy({ + filterByTk: record[collection.filterTargetKey], + }); + await runAsync(); + message.success('Deleted!'); + }, + }; +} + +const schema: ISchema = { + type: 'void', + name: uid(), + 'x-component': 'CardItem', + 'x-decorator': 'TableBlockProvider', + 'x-decorator-props': { + collection: sharedFormsCollection.name, + action: 'list', + showIndex: true, + dragSort: false, + }, + properties: { + actions: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 20, + }, + }, + properties: { + add: { + type: 'void', + 'x-component': 'Action', + title: 'Add New', + 'x-align': 'right', + 'x-component-props': { + type: 'primary', + }, + properties: { + drawer: { + type: 'void', + 'x-component': 'Action.Drawer', + title: 'Add new', + properties: { + form: { + type: 'void', + 'x-component': 'FormV2', + properties: { + title: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + required: true, + }, + dataSource: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + default: 'main', + }, + collection: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + required: true, + }, + description: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + }, + password: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + }, + footer: { + type: 'void', + 'x-component': 'Action.Drawer.Footer', + properties: { + submit: { + title: 'Submit', + 'x-component': 'Action', + 'x-use-component-props': 'useSubmitActionProps', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + table: { + type: 'array', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: sharedFormsCollection.filterTargetKey, + rowSelection: { + type: 'checkbox', + }, + }, + properties: { + title: { + type: 'void', + title: 'Title', + 'x-component': 'TableV2.Column', + properties: { + title: { + type: 'string', + 'x-component': 'CollectionField', + 'x-pattern': 'readPretty', + }, + }, + }, + dataSource: { + type: 'void', + title: 'Data source', + 'x-component': 'TableV2.Column', + properties: { + dataSource: { + type: 'string', + 'x-component': 'CollectionField', + 'x-pattern': 'readPretty', + }, + }, + }, + collection: { + type: 'void', + title: 'Collection', + 'x-component': 'TableV2.Column', + properties: { + collection: { + type: 'string', + 'x-component': 'CollectionField', + 'x-pattern': 'readPretty', + }, + }, + }, + description: { + type: 'void', + title: 'Description', + 'x-component': 'TableV2.Column', + properties: { + description: { + type: 'string', + 'x-component': 'CollectionField', + 'x-pattern': 'readPretty', + }, + }, + }, + actions: { + type: 'void', + title: 'Actions', + 'x-component': 'TableV2.Column', + properties: { + actions: { + type: 'void', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + properties: { + configure: { + type: 'void', + title: 'Configure', + 'x-component': ConfigureLink, + // 'x-use-component-props': 'useDeleteActionProps', + }, + edit: { + type: 'void', + title: 'Edit', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'drawer', + icon: 'EditOutlined', + }, + properties: { + drawer: { + type: 'void', + title: 'Edit', + 'x-component': 'Action.Drawer', + properties: { + form: { + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useEditFormProps', + properties: { + title: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + required: true, + }, + description: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + required: true, + }, + footer: { + type: 'void', + 'x-component': 'Action.Drawer.Footer', + properties: { + submit: { + title: 'Submit', + 'x-component': 'Action', + 'x-use-component-props': 'useSubmitActionProps', + }, + }, + }, + }, + }, + }, + }, + }, + }, + delete: { + type: 'void', + title: 'Delete', + 'x-component': 'Action.Link', + 'x-use-component-props': 'useDeleteActionProps', + }, + }, + }, + }, + }, + }, + }, + }, +}; + +export const SharedFormTable = () => { + return ( + + + + ); +}; diff --git a/src/client/client.d.ts b/src/client/client.d.ts new file mode 100644 index 0000000..4e96f83 --- /dev/null +++ b/src/client/client.d.ts @@ -0,0 +1,249 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +// CSS modules +type CSSModuleClasses = { readonly [key: string]: string }; + +declare module '*.module.css' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.scss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sass' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.less' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.styl' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.stylus' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.pcss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sss' { + const classes: CSSModuleClasses; + export default classes; +} + +// CSS +declare module '*.css' { } +declare module '*.scss' { } +declare module '*.sass' { } +declare module '*.less' { } +declare module '*.styl' { } +declare module '*.stylus' { } +declare module '*.pcss' { } +declare module '*.sss' { } + +// Built-in asset types +// see `src/node/constants.ts` + +// images +declare module '*.apng' { + const src: string; + export default src; +} +declare module '*.png' { + const src: string; + export default src; +} +declare module '*.jpg' { + const src: string; + export default src; +} +declare module '*.jpeg' { + const src: string; + export default src; +} +declare module '*.jfif' { + const src: string; + export default src; +} +declare module '*.pjpeg' { + const src: string; + export default src; +} +declare module '*.pjp' { + const src: string; + export default src; +} +declare module '*.gif' { + const src: string; + export default src; +} +declare module '*.svg' { + const src: string; + export default src; +} +declare module '*.ico' { + const src: string; + export default src; +} +declare module '*.webp' { + const src: string; + export default src; +} +declare module '*.avif' { + const src: string; + export default src; +} + +// media +declare module '*.mp4' { + const src: string; + export default src; +} +declare module '*.webm' { + const src: string; + export default src; +} +declare module '*.ogg' { + const src: string; + export default src; +} +declare module '*.mp3' { + const src: string; + export default src; +} +declare module '*.wav' { + const src: string; + export default src; +} +declare module '*.flac' { + const src: string; + export default src; +} +declare module '*.aac' { + const src: string; + export default src; +} +declare module '*.opus' { + const src: string; + export default src; +} +declare module '*.mov' { + const src: string; + export default src; +} +declare module '*.m4a' { + const src: string; + export default src; +} +declare module '*.vtt' { + const src: string; + export default src; +} + +// fonts +declare module '*.woff' { + const src: string; + export default src; +} +declare module '*.woff2' { + const src: string; + export default src; +} +declare module '*.eot' { + const src: string; + export default src; +} +declare module '*.ttf' { + const src: string; + export default src; +} +declare module '*.otf' { + const src: string; + export default src; +} + +// other +declare module '*.webmanifest' { + const src: string; + export default src; +} +declare module '*.pdf' { + const src: string; + export default src; +} +declare module '*.txt' { + const src: string; + export default src; +} + +// wasm?init +declare module '*.wasm?init' { + const initWasm: (options?: WebAssembly.Imports) => Promise; + export default initWasm; +} + +// web worker +declare module '*?worker' { + const workerConstructor: { + new(options?: { name?: string }): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&inline' { + const workerConstructor: { + new(options?: { name?: string }): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&url' { + const src: string; + export default src; +} + +declare module '*?sharedworker' { + const sharedWorkerConstructor: { + new(options?: { name?: string }): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&inline' { + const sharedWorkerConstructor: { + new(options?: { name?: string }): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&url' { + const src: string; + export default src; +} + +declare module '*?raw' { + const src: string; + export default src; +} + +declare module '*?url' { + const src: string; + export default src; +} + +declare module '*?inline' { + const src: string; + export default src; +} diff --git a/src/client/index.tsx b/src/client/index.tsx new file mode 100644 index 0000000..5bb4b09 --- /dev/null +++ b/src/client/index.tsx @@ -0,0 +1,26 @@ +import { Plugin } from '@nocobase/client'; +import { PublicSharedForm } from './PublicSharedForm'; +import { SharedFormConfigure } from './SharedFormConfigure'; +import { SharedFormTable } from './SharedFormTable'; + +export class PluginSharedFormsClient extends Plugin { + async load() { + this.app.router.add('shared-forms', { + path: '/shared-forms/:name', + Component: PublicSharedForm, + }); + this.app.pluginSettingsManager.add('shared-forms', { + title: 'Shared forms', + icon: 'TableOutlined', + Component: SharedFormTable, + }); + this.app.pluginSettingsManager.add(`shared-forms/:name`, { + title: false, + pluginKey: 'shared-forms', + isTopLevel: false, + Component: SharedFormConfigure, + }); + } +} + +export default PluginSharedFormsClient; diff --git a/src/client/locale.ts b/src/client/locale.ts new file mode 100644 index 0000000..7e451b2 --- /dev/null +++ b/src/client/locale.ts @@ -0,0 +1,12 @@ +import { useApp } from '@nocobase/client'; +// @ts-ignore +import pkg from './../../package.json'; + +export function useT() { + const app = useApp(); + return (str: string) => app.i18n.t(str, { ns: [pkg.name, 'client'] }); +} + +export function tStr(key: string) { + return `{{t(${JSON.stringify(key)}, { ns: ['${pkg.name}', 'client'], nsMode: 'fallback' })}}`; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..c683267 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from './server'; +export { default } from './server'; +// diff --git a/src/server/collections/sharedForms.ts b/src/server/collections/sharedForms.ts new file mode 100644 index 0000000..5d0357c --- /dev/null +++ b/src/server/collections/sharedForms.ts @@ -0,0 +1,33 @@ +import { defineCollection } from '@nocobase/database'; + +export default defineCollection({ + name: 'sharedForms', + filterTargetKey: 'slug', + fields: [ + { + type: 'uid', + name: 'slug', + }, + { + type: 'string', + name: 'title', + }, + { + type: 'string', + name: 'dataSource', + }, + { + type: 'string', + name: 'collection', + }, + { + type: 'string', + name: 'description', + }, + { + type: 'password', + name: 'password', + hidden: true, + }, + ], +}); diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 0000000..b68aea5 --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/src/server/plugin.ts b/src/server/plugin.ts new file mode 100644 index 0000000..7d4abbe --- /dev/null +++ b/src/server/plugin.ts @@ -0,0 +1,43 @@ +import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage'; +import { Plugin } from '@nocobase/server'; + +export class PluginSharedFormsServer extends Plugin { + async afterAdd() {} + + async beforeLoad() {} + + async load() { + this.app.resourceManager.registerActionHandlers({ + 'sharedForms:getMeta': async (ctx, next) => { + const { filterByTk } = ctx.action.params; + const sharedForms = this.db.getRepository('sharedForms'); + const uiSchema = this.db.getRepository('uiSchemas'); + const instance = await sharedForms.findOne({ + filter: { + slug: filterByTk, + }, + }); + const schema = await uiSchema.getJsonSchema(filterByTk); + ctx.body = { + collections: [], + token: this.app.authManager.jwt.sign({ + // todo + }), + schema, + }; + await next(); + }, + }); + this.app.acl.allow('sharedForms', 'getMeta', 'public'); + } + + async install() {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default PluginSharedFormsServer;