diff --git a/packages/app/client/src/plugins/iframe-block.ts b/packages/app/client/src/plugins/iframe-block.ts new file mode 100644 index 000000000..efd6abd53 --- /dev/null +++ b/packages/app/client/src/plugins/iframe-block.ts @@ -0,0 +1 @@ +export { default } from '@nocobase/plugin-iframe-block/client'; \ No newline at end of file diff --git a/packages/core/client/src/schema-component/common/iframe/Iframe.Designer.tsx b/packages/core/client/src/schema-component/common/iframe/Iframe.Designer.tsx deleted file mode 100644 index 0b4505cea..000000000 --- a/packages/core/client/src/schema-component/common/iframe/Iframe.Designer.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { ISchema, useField, useFieldSchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; -import { useDesignable } from '../../hooks'; - -export const IframeDesigner = () => { - const field = useField(); - const fieldSchema = useFieldSchema(); - const { t } = useTranslation(); - const { dn } = useDesignable(); - - return ( - - { - field.componentProps.url = url; - field.componentProps.height = height; - const componentProps = fieldSchema['x-component-props'] || {}; - componentProps['url'] = url; - componentProps['height'] = height; - fieldSchema['x-component-props'] = componentProps; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - 'x-component-props': componentProps, - }, - }); - }} - /> - - - - ); -}; diff --git a/packages/core/client/src/schema-component/common/index.ts b/packages/core/client/src/schema-component/common/index.ts index ba61b9f1c..3f18bf971 100644 --- a/packages/core/client/src/schema-component/common/index.ts +++ b/packages/core/client/src/schema-component/common/index.ts @@ -1,4 +1,2 @@ export * from './dnd-context'; -export * from './iframe'; export * from './sortable-item'; - diff --git a/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx index d00f42fea..c7f3b0b46 100644 --- a/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx @@ -164,12 +164,6 @@ export const BlockInitializers = { title: '{{t("Markdown")}}', component: 'MarkdownBlockInitializer', }, - { - key: 'iframe', - type: 'item', - title: '{{t("Iframe")}}', - component: 'IframeBlockInitializer', - }, { key: 'auditLogs', type: 'item', diff --git a/packages/core/client/src/schema-initializer/items/index.tsx b/packages/core/client/src/schema-initializer/items/index.tsx index ecef69bc1..42eb9fe50 100644 --- a/packages/core/client/src/schema-initializer/items/index.tsx +++ b/packages/core/client/src/schema-initializer/items/index.tsx @@ -17,7 +17,6 @@ export * from './DetailsBlockInitializer'; export * from './FilterActionInitializer'; export * from './FormBlockInitializer'; export * from './G2PlotInitializer'; -export * from './IframeBlockInitializer'; export * from './InitializerWithSwitch'; export * from './KanbanBlockInitializer'; export * from './MarkdownBlockInitializer'; @@ -38,4 +37,3 @@ export * from './TableSelectorInitializer'; export * from './UpdateActionInitializer'; export * from './UpdateSubmitActionInitializer'; export * from './ViewActionInitializer'; - diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 5a2b2abe1..8d55f28a5 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -22,7 +22,7 @@ import { useAPIClient, useCollection, useCompile, - useDesignable, + useDesignable } from '..'; import { useSchemaTemplateManager } from '../schema-templates'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; @@ -574,7 +574,18 @@ SchemaSettings.ActionModalItem = React.memo((props: any) => { }); SchemaSettings.ModalItem = (props) => { - const { hidden, title, components, scope, effects, schema, onSubmit, initialValues, ...others } = props; + const { + hidden, + title, + components, + scope, + effects, + schema, + onSubmit, + asyncGetInitialValues, + initialValues, + ...others + } = props; const options = useContext(SchemaOptionsContext); const cm = useContext(CollectionManagerContext); if (hidden) { @@ -583,7 +594,8 @@ SchemaSettings.ModalItem = (props) => { return ( { + onClick={async () => { + const values = asyncGetInitialValues ? await asyncGetInitialValues() : initialValues; FormDialog(schema.title || title, () => { return ( @@ -596,7 +608,7 @@ SchemaSettings.ModalItem = (props) => { ); }) .open({ - initialValues, + initialValues: values, effects, }) .then((values) => { diff --git a/packages/core/server/src/helper.ts b/packages/core/server/src/helper.ts index d866c99f4..a2534aa6e 100644 --- a/packages/core/server/src/helper.ts +++ b/packages/core/server/src/helper.ts @@ -56,7 +56,8 @@ export function registerMiddlewares(app: Application, options: ApplicationOption app.use(async (ctx, next) => { ctx.getBearerToken = () => { - return ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); + const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); + return token || ctx.query.token; }; await next(); }); diff --git a/packages/plugins/iframe-block/client.d.ts b/packages/plugins/iframe-block/client.d.ts new file mode 100644 index 000000000..765db9222 --- /dev/null +++ b/packages/plugins/iframe-block/client.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +export * from './lib/client'; +export { default } from './lib/client'; + diff --git a/packages/plugins/iframe-block/client.js b/packages/plugins/iframe-block/client.js new file mode 100644 index 000000000..238820257 --- /dev/null +++ b/packages/plugins/iframe-block/client.js @@ -0,0 +1,30 @@ +"use strict"; + +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +var _index = _interopRequireWildcard(require("./lib/client")); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = {}; +Object.defineProperty(exports, "default", { + enumerable: true, + get: function get() { + return _index.default; + } +}); + +Object.keys(_index).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _index[key]; + } + }); +}); diff --git a/packages/plugins/iframe-block/package.json b/packages/plugins/iframe-block/package.json new file mode 100644 index 000000000..47ceed275 --- /dev/null +++ b/packages/plugins/iframe-block/package.json @@ -0,0 +1,9 @@ +{ + "name": "@nocobase/plugin-iframe-block", + "version": "0.8.0-alpha.13", + "main": "lib/server/index.js", + "devDependencies": { + "@nocobase/server": "0.8.0-alpha.13", + "@nocobase/test": "0.8.0-alpha.13" + } +} diff --git a/packages/plugins/iframe-block/server.d.ts b/packages/plugins/iframe-block/server.d.ts new file mode 100644 index 000000000..e70edb928 --- /dev/null +++ b/packages/plugins/iframe-block/server.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +export * from './lib/server'; +export { default } from './lib/server'; + diff --git a/packages/plugins/iframe-block/server.js b/packages/plugins/iframe-block/server.js new file mode 100644 index 000000000..d06a7eb92 --- /dev/null +++ b/packages/plugins/iframe-block/server.js @@ -0,0 +1,30 @@ +"use strict"; + +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +var _index = _interopRequireWildcard(require("./lib/server")); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = {}; +Object.defineProperty(exports, "default", { + enumerable: true, + get: function get() { + return _index.default; + } +}); + +Object.keys(_index).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _index[key]; + } + }); +}); diff --git a/packages/plugins/iframe-block/src/client/Iframe.Designer.tsx b/packages/plugins/iframe-block/src/client/Iframe.Designer.tsx new file mode 100644 index 000000000..7fc384c08 --- /dev/null +++ b/packages/plugins/iframe-block/src/client/Iframe.Designer.tsx @@ -0,0 +1,131 @@ +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import { uid } from '@formily/shared'; +import { GeneralSchemaDesigner, SchemaSettings, useAPIClient, useDesignable } from '@nocobase/client'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export const IframeDesigner = () => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + const api = useAPIClient(); + const { mode, url, htmlId, height = '60vh' } = fieldSchema['x-component-props'] || {}; + + const saveHtml = async (html: string) => { + const options = { + values: { html }, + }; + if (htmlId) { + const { data } = await api.resource('iframeHtml').update?.({ ...options, filterByTk: htmlId }); + return data?.data?.[0] || { id: htmlId }; + } else { + const { data } = await api.resource('iframeHtml').create?.(options); + return data?.data; + } + }; + + const submitHandler = async ({ mode, url, html, height }) => { + const componentProps = fieldSchema['x-component-props'] || {}; + componentProps['mode'] = mode; + componentProps['height'] = height; + componentProps['url'] = url; + if (mode === 'html') { + const data = await saveHtml(html); + componentProps['htmlId'] = data.id; + } + fieldSchema['x-component-props'] = componentProps; + field.componentProps = { ...componentProps }; + field.data = { v: uid() }; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-component-props': componentProps, + }, + }); + }; + + return ( + + { + const values = { + mode, + url, + height, + }; + if (htmlId) { + const { data } = await api.resource('iframeHtml').get?.({ filterByTk: htmlId }); + values['html'] = data?.data?.html || ''; + } + return values; + }} + schema={ + { + type: 'object', + title: t('Edit iframe'), + properties: { + mode: { + title: '{{t("Mode")}}', + 'x-component': 'Radio.Group', + 'x-decorator': 'FormItem', + required: true, + default: 'url', + enum: [ + { value: 'url', label: t('URL') }, + { value: 'html', label: t('html') }, + ], + }, + url: { + title: t('URL'), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + required: true, + 'x-reactions': { + dependencies: ['mode'], + fulfill: { + state: { + hidden: '{{$deps[0] === "html"}}', + }, + }, + }, + }, + html: { + title: t('html'), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input.TextArea', + required: true, + 'x-reactions': { + dependencies: ['mode'], + fulfill: { + state: { + hidden: '{{$deps[0] === "url"}}', + }, + }, + }, + }, + height: { + title: t('Height'), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + required: true, + }, + }, + } as ISchema + } + onSubmit={submitHandler} + /> + + + + ); +}; diff --git a/packages/core/client/src/schema-component/common/iframe/index.tsx b/packages/plugins/iframe-block/src/client/Iframe.tsx similarity index 57% rename from packages/core/client/src/schema-component/common/iframe/index.tsx rename to packages/plugins/iframe-block/src/client/Iframe.tsx index b434d123f..f43424956 100644 --- a/packages/core/client/src/schema-component/common/iframe/index.tsx +++ b/packages/plugins/iframe-block/src/client/Iframe.tsx @@ -1,4 +1,5 @@ -import { observer } from '@formily/react'; +import { observer, useField } from '@formily/react'; +import { useAPIClient } from '@nocobase/client'; import { Card } from 'antd'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -6,7 +7,7 @@ import RIframe from 'react-iframe'; import type { IIframe } from 'react-iframe/types'; import { IframeDesigner } from './Iframe.Designer'; -function isNumeric(str: string) { +function isNumeric(str: string | undefined) { if (typeof str !== 'string') return false; // we only process strings! return ( !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... @@ -14,15 +15,27 @@ function isNumeric(str: string) { ); // ...and ensure strings of whitespace fail } -export const Iframe: any = observer((props: IIframe) => { - const { url, height, ...others } = props; +export const Iframe: any = observer((props: IIframe & { html?: string; htmlId?: number; mode: string }) => { + const { url, htmlId, mode = 'url', height, html, ...others } = props; + const { t } = useTranslation(); - if (!url) { + const api = useAPIClient(); + const field = useField(); + + const src = React.useMemo(() => { + if (mode === 'html') { + return `/api/iframeHtml:getHtml/${htmlId}?token=${api.auth.getToken()}&v=${field.data?.v || ''}`; + } + return url; + }, [url, mode, htmlId, field.data?.v]); + + if ((mode === 'url' && !url) || (mode === 'html' && !htmlId)) { return {t('Please fill in the iframe URL')}; } + return ( { const { insert } = props; const { t } = useTranslation(); diff --git a/packages/plugins/iframe-block/src/client/IframeBlockInitializerProvider.tsx b/packages/plugins/iframe-block/src/client/IframeBlockInitializerProvider.tsx new file mode 100644 index 000000000..13e0fdfd4 --- /dev/null +++ b/packages/plugins/iframe-block/src/client/IframeBlockInitializerProvider.tsx @@ -0,0 +1,18 @@ +import { SchemaInitializerContext } from '@nocobase/client'; +import { useContext } from 'react'; + +export const IframeBlockInitializerProvider = (props: any) => { + const initializes = useContext(SchemaInitializerContext); + const mediaItem = initializes.BlockInitializers.items.find((item) => item.key === 'media'); + const hasIframeBlockInitializer = mediaItem.children.some( + (initialize) => initialize.component === 'IframeBlockInitializer', + ); + !hasIframeBlockInitializer && + mediaItem.children.push({ + key: 'iframe', + type: 'item', + title: '{{t("Iframe")}}', + component: 'IframeBlockInitializer', + }); + return props.children; +}; diff --git a/packages/plugins/iframe-block/src/client/IframeBlockPlugin.tsx b/packages/plugins/iframe-block/src/client/IframeBlockPlugin.tsx new file mode 100644 index 000000000..8d25bf798 --- /dev/null +++ b/packages/plugins/iframe-block/src/client/IframeBlockPlugin.tsx @@ -0,0 +1,13 @@ +import { SchemaComponentOptions } from '@nocobase/client'; +import React from 'react'; +import { Iframe } from './Iframe'; +import { IframeBlockInitializer } from './IframeBlockInitializer'; +import { IframeBlockInitializerProvider } from './IframeBlockInitializerProvider'; + +export const IframeBlockPlugin = (props: any) => { + return ( + + {props.children} + + ); +}; diff --git a/packages/plugins/iframe-block/src/client/index.ts b/packages/plugins/iframe-block/src/client/index.ts new file mode 100644 index 000000000..41ae44e4d --- /dev/null +++ b/packages/plugins/iframe-block/src/client/index.ts @@ -0,0 +1 @@ +export { IframeBlockPlugin as default } from './IframeBlockPlugin'; diff --git a/packages/plugins/iframe-block/src/index.ts b/packages/plugins/iframe-block/src/index.ts new file mode 100644 index 000000000..7ddad5814 --- /dev/null +++ b/packages/plugins/iframe-block/src/index.ts @@ -0,0 +1 @@ +export { default } from './server'; diff --git a/packages/plugins/iframe-block/src/server/actions/index.ts b/packages/plugins/iframe-block/src/server/actions/index.ts new file mode 100644 index 000000000..622bb9111 --- /dev/null +++ b/packages/plugins/iframe-block/src/server/actions/index.ts @@ -0,0 +1,17 @@ +import { Context, Next } from '@nocobase/actions'; +import { Repository } from '@nocobase/database'; + +export async function getHtml(ctx: Context, next: Next) { + let { filterByTk } = ctx.action.params; + const { resourceName } = ctx.action; + const repository = ctx.db.getRepository(resourceName) as Repository; + const model = await repository.findById(filterByTk); + ctx.body = model.get('html'); + ctx.withoutDataWrapping = true; + + ctx.set({ + 'Content-Type': 'text/html; charset=UTF-8', + }); + + await next(); +} diff --git a/packages/plugins/iframe-block/src/server/collections/.gitkeep b/packages/plugins/iframe-block/src/server/collections/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/iframe-block/src/server/collections/iframe-html.ts b/packages/plugins/iframe-block/src/server/collections/iframe-html.ts new file mode 100644 index 000000000..0fca8db1a --- /dev/null +++ b/packages/plugins/iframe-block/src/server/collections/iframe-html.ts @@ -0,0 +1,18 @@ +import { CollectionOptions } from '@nocobase/database'; + +export default { + name: 'iframeHtml', + createdBy: true, + updatedBy: true, + fields: [ + { + type: 'uid', + name: 'id', + primaryKey: true, + }, + { + type: 'text', + name: 'html', + }, + ], +} as CollectionOptions; diff --git a/packages/plugins/iframe-block/src/server/index.ts b/packages/plugins/iframe-block/src/server/index.ts new file mode 100644 index 000000000..b68aea57f --- /dev/null +++ b/packages/plugins/iframe-block/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugins/iframe-block/src/server/plugin.ts b/packages/plugins/iframe-block/src/server/plugin.ts new file mode 100644 index 000000000..866ec25d2 --- /dev/null +++ b/packages/plugins/iframe-block/src/server/plugin.ts @@ -0,0 +1,30 @@ +import { InstallOptions, Plugin } from '@nocobase/server'; +import path from 'path'; +import { getHtml } from './actions'; + +export class IframeBlockPlugin extends Plugin { + afterAdd() {} + + beforeLoad() {} + + async load() { + await this.db.import({ + directory: path.resolve(__dirname, 'collections'), + }); + this.app.acl.allow('iframeHtml', ['get', 'create', 'update', 'destroy'], 'allowConfigure'); + this.app.acl.allow('iframeHtml', 'getHtml', 'loggedIn'); + this.app.actions({ + 'iframeHtml:getHtml': getHtml, + }); + } + + async install(options?: InstallOptions) {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default IframeBlockPlugin; diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index a424a5588..ed88cf6be 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -31,6 +31,7 @@ "@nocobase/plugin-verification": "0.8.0-alpha.13", "@nocobase/plugin-workflow": "0.8.0-alpha.13", "@nocobase/plugin-map": "0.8.0-alpha.13", + "@nocobase/plugin-iframe-block": "0.8.0-alpha.13", "@nocobase/server": "0.8.0-alpha.13" }, "repository": { diff --git a/packages/presets/nocobase/src/index.ts b/packages/presets/nocobase/src/index.ts index d1c326d25..e70c60cc0 100644 --- a/packages/presets/nocobase/src/index.ts +++ b/packages/presets/nocobase/src/index.ts @@ -23,6 +23,7 @@ export class PresetNocoBase extends Plugin { 'export', 'import', 'audit-logs', + 'iframe-block', ].concat(plugins), ); }