diff --git a/packages/core/acl/package.json b/packages/core/acl/package.json index 5bd8c9a8b6..3e78860f63 100644 --- a/packages/core/acl/package.json +++ b/packages/core/acl/package.json @@ -8,7 +8,6 @@ "dependencies": { "@nocobase/resourcer": "0.9.2-alpha.4", "@nocobase/utils": "0.9.2-alpha.4", - "json-templates": "^4.2.0", "minimatch": "^5.1.1" }, "repository": { diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 369e1ead0d..a3ba8dc215 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -25,7 +25,6 @@ "file-saver": "^2.0.5", "i18next": "^22.4.9", "i18next-http-backend": "^2.1.1", - "json-templates": "^4.2.0", "markdown-it": "13.0.1", "markdown-it-highlightjs": "3.3.1", "mathjs": "^10.6.0", diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 86c1d1b7ec..dbf8a41e3a 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -1,6 +1,6 @@ import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react'; import { Modal, message } from 'antd'; -import parse from 'json-templates'; +import { parse } from '@nocobase/utils/client'; import { cloneDeep } from 'lodash'; import get from 'lodash/get'; import omit from 'lodash/omit'; diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index 5f36313d96..e7d36afea4 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -6,9 +6,11 @@ "license": "Apache-2.0", "dependencies": { "@hapi/topo": "^6.0.0", + "dedupe": "^3.0.2", "deepmerge": "^4.2.2", "flat-to-nested": "^1.1.1", - "graphlib": "^2.1.8" + "graphlib": "^2.1.8", + "object-path": "^0.11.8" }, "peerDependencies": { "moment": "2.x", diff --git a/packages/core/utils/src/client.ts b/packages/core/utils/src/client.ts index 8d5777efbf..8db461e455 100644 --- a/packages/core/utils/src/client.ts +++ b/packages/core/utils/src/client.ts @@ -9,3 +9,4 @@ export * from './parse-filter'; export * from './registry'; // export * from './toposort'; export * from './uid'; +export * from './json-templates'; diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index 31e50c8b1d..d60aec9f87 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -13,3 +13,4 @@ export * from './registry'; export * from './requireModule'; export * from './toposort'; export * from './uid'; +export * from './json-templates'; diff --git a/packages/core/utils/src/json-templates.ts b/packages/core/utils/src/json-templates.ts new file mode 100644 index 0000000000..0315462df6 --- /dev/null +++ b/packages/core/utils/src/json-templates.ts @@ -0,0 +1,148 @@ +// json-templates +// Simple templating within JSON structures. +// +// Created by Curran Kelleher and Chrostophe Serafin. +// Contributions from Paul Brewer and Javier Blanco Martinez. +import objectPath from 'object-path'; +import dedupe from 'dedupe'; + +// An enhanced version of `typeof` that handles arrays and dates as well. +function type(value) { + let valueType: string = typeof value; + if (Array.isArray(value)) { + valueType = 'array'; + } else if (value instanceof Date) { + valueType = 'date'; + } else if (value === null) { + valueType = 'null'; + } + + return valueType; +} + +// Constructs a parameter object from a match result. +// e.g. "['{{foo}}']" --> { key: "foo" } +// e.g. "['{{foo:bar}}']" --> { key: "foo", defaultValue: "bar" } +function Parameter(match) { + let param; + const matchValue = match.substr(2, match.length - 4).trim(); + const i = matchValue.indexOf(':'); + + if (i !== -1) { + param = { + key: matchValue.substr(0, i), + defaultValue: matchValue.substr(i + 1), + }; + } else { + param = { key: matchValue }; + } + + return param; +} + +// Constructs a template function with deduped `parameters` property. +function Template(fn, parameters) { + // Paul Brewer Dec 2017 add deduplication call, use only key property to eliminate + Object.assign(fn, { + parameters: dedupe(parameters, (item) => item.key), + }); + + return fn; +} + +// Parses the given template object. +// +// Returns a function `template(context)` that will "fill in" the template +// with the context object passed to it. +// +// The returned function has a `parameters` property, +// which is an array of parameter descriptor objects, +// each of which has a `key` property and possibly a `defaultValue` property. +export function parse(value) { + switch (type(value)) { + case 'string': + return parseString(value); + case 'object': + return parseObject(value); + case 'array': + return parseArray(value); + default: + return Template(function () { + return value; + }, []); + } +} + +// Parses leaf nodes of the template object that are strings. +// Also used for parsing keys that contain templates. +const parseString = (() => { + // This regular expression detects instances of the + // template parameter syntax such as {{foo}} or {{foo:someDefault}}. + const regex = /{{(\w|:|[\s-+.,@///()?=*_$])+}}/g; + + return (str) => { + let parameters = []; + let templateFn = (context) => str; + + const matches = str.match(regex); + if (matches) { + parameters = matches.map(Parameter); + templateFn = (context) => { + context = context || {}; + return matches.reduce((result, match, i) => { + const parameter = parameters[i]; + let value = objectPath.get(context, parameter.key); + if (value == null) { + value = parameter.defaultValue; + } + + if (typeof value === 'function') { + value = value(); + } + + if (typeof value === 'object') { + return value; + } + + // Accommodate numbers as values. + if (matches.length === 1 && str.startsWith('{{') && str.endsWith('}}')) { + return value; + } + + return result.replace(match, value == null ? '' : value); + }, str); + }; + } + + return Template(templateFn, parameters); + }; +})(); + +// Parses non-leaf-nodes in the template object that are objects. +function parseObject(object) { + const children = Object.keys(object).map((key) => ({ + keyTemplate: parseString(key), + valueTemplate: parse(object[key]), + })); + const templateParameters = children.reduce( + (parameters, child) => parameters.concat(child.valueTemplate.parameters, child.keyTemplate.parameters), + [], + ); + const templateFn = (context) => { + return children.reduce((newObject, child) => { + newObject[child.keyTemplate(context)] = child.valueTemplate(context); + return newObject; + }, {}); + }; + + return Template(templateFn, templateParameters); +} + +// Parses non-leaf-nodes in the template object that are arrays. +function parseArray(array) { + const templates = array.map(parse); + const templateParameters = templates.reduce((parameters, template) => parameters.concat(template.parameters), []); + const templateFn = (context) => templates.map((template) => template(context)); + + return Template(templateFn, templateParameters); +} diff --git a/packages/plugins/users/package.json b/packages/plugins/users/package.json index bcf97c8500..4f659d469e 100644 --- a/packages/plugins/users/package.json +++ b/packages/plugins/users/package.json @@ -11,7 +11,6 @@ "@nocobase/resourcer": "0.9.2-alpha.4", "@nocobase/server": "0.9.2-alpha.4", "@nocobase/utils": "0.9.2-alpha.4", - "json-templates": "^4.2.0", "jsonwebtoken": "^8.5.1" }, "devDependencies": { diff --git a/packages/plugins/users/src/server.ts b/packages/plugins/users/src/server.ts index 41ad971540..1fcf23b5ac 100644 --- a/packages/plugins/users/src/server.ts +++ b/packages/plugins/users/src/server.ts @@ -1,8 +1,7 @@ import { Collection, Op } from '@nocobase/database'; import { HandlerType } from '@nocobase/resourcer'; import { Plugin } from '@nocobase/server'; -import { Registry } from '@nocobase/utils'; -import parse from 'json-templates'; +import { Registry, parse } from '@nocobase/utils'; import { resolve } from 'path'; import { namespace } from './'; diff --git a/packages/plugins/workflow/package.json b/packages/plugins/workflow/package.json index d78becb248..721a6667f9 100644 --- a/packages/plugins/workflow/package.json +++ b/packages/plugins/workflow/package.json @@ -18,7 +18,6 @@ "axios": "^0.27.2", "classnames": "^2.3.1", "cron-parser": "4.4.0", - "json-templates": "^4.2.0", "lru-cache": "8.0.5", "moment": "^2.29.2", "react-js-cron": "^3.1.0" diff --git a/packages/plugins/workflow/src/client/nodes/calculation.tsx b/packages/plugins/workflow/src/client/nodes/calculation.tsx index 51b3e5bf7a..af12743a5b 100644 --- a/packages/plugins/workflow/src/client/nodes/calculation.tsx +++ b/packages/plugins/workflow/src/client/nodes/calculation.tsx @@ -2,8 +2,8 @@ import { css } from '@emotion/css'; import { FormItem, FormLayout } from '@formily/antd'; import { SchemaInitializer, SchemaInitializerItemOptions, Variable, useCollectionManager } from '@nocobase/client'; import { Evaluator, evaluators, getOptions } from '@nocobase/evaluators/client'; +import { parse } from '@nocobase/utils/client'; import { Radio } from 'antd'; -import parse from 'json-templates'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useFlowContext } from '../FlowContext'; diff --git a/packages/plugins/workflow/src/client/nodes/index.tsx b/packages/plugins/workflow/src/client/nodes/index.tsx index 272a0b2813..f4ebf46f69 100644 --- a/packages/plugins/workflow/src/client/nodes/index.tsx +++ b/packages/plugins/workflow/src/client/nodes/index.tsx @@ -4,9 +4,8 @@ import { css, cx } from '@emotion/css'; import { ISchema, useForm } from '@formily/react'; import { Button, message, Modal, Tag, Alert, Input } from 'antd'; import { useTranslation } from 'react-i18next'; -import parse from 'json-templates'; -import { Registry } from '@nocobase/utils/client'; +import { Registry, parse } from '@nocobase/utils/client'; import { ActionContext, SchemaComponent, diff --git a/packages/plugins/workflow/src/client/nodes/manual/WorkflowTodo.tsx b/packages/plugins/workflow/src/client/nodes/manual/WorkflowTodo.tsx index d78f0abbb7..67125d1371 100644 --- a/packages/plugins/workflow/src/client/nodes/manual/WorkflowTodo.tsx +++ b/packages/plugins/workflow/src/client/nodes/manual/WorkflowTodo.tsx @@ -1,7 +1,6 @@ import React, { useContext, createContext, useEffect, useState } from 'react'; import { observer, useForm, useField, useFieldSchema } from '@formily/react'; import { Tag } from 'antd'; -import parse from 'json-templates'; import { css } from '@emotion/css'; import moment from 'moment'; @@ -19,7 +18,7 @@ import { useRequest, useTableBlockContext, } from '@nocobase/client'; -import { uid } from '@nocobase/utils/client'; +import { uid, parse } from '@nocobase/utils/client'; import { JobStatusOptions, JobStatusOptionsMap, JOB_STATUS } from '../../constants'; import { NAMESPACE } from '../../locale'; diff --git a/packages/plugins/workflow/src/server/Processor.ts b/packages/plugins/workflow/src/server/Processor.ts index 5d30b53fa2..de2368e48e 100644 --- a/packages/plugins/workflow/src/server/Processor.ts +++ b/packages/plugins/workflow/src/server/Processor.ts @@ -1,7 +1,7 @@ import { Model } from '@nocobase/database'; import { appendArrayColumn } from '@nocobase/evaluators'; import { Logger } from '@nocobase/logger'; -import parse from 'json-templates'; +import { parse } from '@nocobase/utils'; import { Transaction, Transactionable } from 'sequelize'; import Plugin from '.'; import { EXECUTION_STATUS, JOB_STATUS } from './constants'; diff --git a/packages/plugins/workflow/src/server/instructions/calculation.ts b/packages/plugins/workflow/src/server/instructions/calculation.ts index cf6cd8b4a3..6e3523770e 100644 --- a/packages/plugins/workflow/src/server/instructions/calculation.ts +++ b/packages/plugins/workflow/src/server/instructions/calculation.ts @@ -1,5 +1,5 @@ import { Evaluator, evaluators } from '@nocobase/evaluators'; -import parse from 'json-templates'; +import { parse } from '@nocobase/utils'; import { Instruction } from '.'; import { Processor } from '..'; import { JOB_STATUS } from '../constants'; diff --git a/yarn.lock b/yarn.lock index 2af6c19c1e..ff6caa1ebe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15505,14 +15505,6 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json-templates@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/json-templates/-/json-templates-4.2.0.tgz#34fdd6fedbe6955e934d86812a89d30d1df87415" - integrity sha512-NX0r9SEI2bsz17DJpKhZPymvlw/vcMcRqjqRC0jl7pofLwcpaSsfjweatMFt9QXaUv+dR5EBzInBp8y5lzluIQ== - dependencies: - dedupe "^3.0.2" - object-path "^0.11.8" - json2module@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/json2module/-/json2module-0.0.3.tgz#00fb5f4a9b7adfc3f0647c29cb17bcd1979be9b2"