/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import type { DataSourceDeps, Id, MComponent, MNode } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema'; export * from './dom'; dayjs.extend(utc); export const sleep = (ms: number): Promise => new Promise((resolve) => { const timer = setTimeout(() => { clearTimeout(timer); resolve(); }, ms); }); export const datetimeFormatter = ( v: string | Date, defaultValue = '-', format = 'YYYY-MM-DD HH:mm:ss', ): string | number => { if (v) { let time = null; if (['x', 'timestamp'].includes(format)) { time = dayjs(v).valueOf(); } else if ((typeof v === 'string' && v.includes('Z')) || v.constructor === Date) { // UTC字符串时间或Date对象格式化为北京时间 time = dayjs(v).utcOffset(8).format(format); } else { time = dayjs(v).format(format); } // 格式化为北京时间 if (time !== 'Invalid Date') { return time; } return defaultValue; } return defaultValue; }; // 驼峰转换横线 export const toLine = (name = '') => name.replace(/\B([A-Z])/g, '-$1').toLowerCase(); export const toHump = (name = ''): string => name.replace(/-(\w)/g, (all, letter) => letter.toUpperCase()); export const emptyFn = (): any => undefined; /** * 通过id获取组件在应用的子孙路径 * @param {number | string} id 组件id * @param {Array} data 要查找的根容器节点 * @return {Array} 组件在data中的子孙路径 */ export const getNodePath = (id: Id, data: MNode[] = []): MNode[] => { const path: MNode[] = []; const get = function (id: number | string, data: MNode[]): MNode | null { if (!Array.isArray(data)) { return null; } for (let i = 0, l = data.length; i < l; i++) { const item = data[i]; path.push(item); if (`${item.id}` === `${id}`) { return item; } if (item.items) { const node = get(id, item.items); if (node) { return node; } } path.pop(); } return null; }; get(id, data); return path; }; export const filterXSS = (str: string) => str.replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); export const getUrlParam = (param: string, url?: string) => { const u = url || location.href; const reg = new RegExp(`[?&#]${param}=([^&#]+)`, 'gi'); const matches = u.match(reg); let strArr; if (matches && matches.length > 0) { strArr = matches[matches.length - 1].split('='); if (strArr && strArr.length > 1) { // 过滤XSS字符 return filterXSS(strArr[1]); } return ''; } return ''; }; export const isPop = (node: MComponent | null): boolean => Boolean(node?.type?.toLowerCase().endsWith('pop')); export const isPage = (node?: MComponent | null): boolean => { if (!node) return false; return Boolean(node.type?.toLowerCase() === NodeType.PAGE); }; export const isNumber = (value: string) => /^(-?\d+)(\.\d+)?$/.test(value); export const getHost = (targetUrl: string) => targetUrl.match(/\/\/([^/]+)/)?.[1]; export const isSameDomain = (targetUrl = '', source = globalThis.location.host) => { const isHttpUrl = /^(http[s]?:)?\/\//.test(targetUrl); if (!isHttpUrl) return true; return getHost(targetUrl) === source; }; /** * 生成指定位数的GUID,无【-】格式 * @param digit 位数,默认值8 * @returns */ export const guid = (digit = 8): string => 'x'.repeat(digit).replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); export const getValueByKeyPath: any = (keys: string, value: Record) => { const path = keys.split('.'); const pathLength = path.length; return path.reduce((accumulator, currentValue: any, currentIndex: number) => { if (Object.prototype.toString.call(accumulator) === '[object Object]' || Array.isArray(accumulator)) { return accumulator[currentValue]; } if (pathLength - 1 === currentIndex) { return undefined; } return {}; }, value); }; export const getNodes = (ids: Id[], data: MNode[] = []): MNode[] => { const nodes: MNode[] = []; const get = function (ids: Id[], data: MNode[]) { if (!Array.isArray(data)) { return; } for (let i = 0, l = data.length; i < l; i++) { const item = data[i]; const index = ids.findIndex((id: Id) => `${id}` === `${item.id}`); if (index > -1) { ids.slice(index, 1); nodes.push(item); } if (item.items) { get(ids, item.items); } } }; get(ids, data); return nodes; }; export const getDepKeys = (dataSourceDeps: DataSourceDeps = {}, nodeId: Id) => Array.from( Object.values(dataSourceDeps).reduce((prev, cur) => { (cur[nodeId]?.keys || []).forEach((key) => prev.add(key)); return prev; }, new Set()), ); export const getDepNodeIds = (dataSourceDeps: DataSourceDeps = {}) => Array.from( Object.values(dataSourceDeps).reduce((prev, cur) => { Object.keys(cur).forEach((id) => { prev.add(id); }); return prev; }, new Set()), ); /** * 将新节点更新到data或者parentId对应的节点的子节点中 * @param newNode 新节点 * @param data 需要修改的数据 * @param parentId 父节点 id */ export const replaceChildNode = (newNode: MNode, data?: MNode[], parentId?: Id) => { const path = getNodePath(newNode.id, data); const node = path.pop(); let parent = path.pop(); if (parentId) { parent = getNodePath(parentId, data).pop(); } if (!node) throw new Error('未找到目标节点'); if (!parent) throw new Error('未找到父节点'); const index = parent.items?.findIndex((child: MNode) => child.id === node.id); parent.items.splice(index, 1, newNode); }; export const compiledNode = ( compile: (template: string) => string, node: MNode, dataSourceDeps: DataSourceDeps = {}, sourceId?: Id, ) => { let keys: Id[] = []; if (!sourceId) { keys = getDepKeys(dataSourceDeps, node.id); } else { const dep = dataSourceDeps[sourceId]; keys = dep?.[node.id].keys || []; } const keyPrefix = '__magic__'; keys.forEach((key) => { const keyPath = `${key}`.split('.'); const keyPathLength = keyPath.length; keyPath.reduce((accumulator, currentValue: any, currentIndex) => { if (keyPathLength - 1 === currentIndex) { if (typeof accumulator[`${keyPrefix}${currentValue}`] === 'undefined') { accumulator[`${keyPrefix}${currentValue}`] = accumulator[currentValue]; } try { accumulator[currentValue] = compile(accumulator[`${keyPrefix}${currentValue}`]); } catch (e) { console.error(e); accumulator[currentValue] = ''; } return accumulator; } if (Object.prototype.toString.call(accumulator) === '[object Object]' || Array.isArray(accumulator)) { return accumulator[currentValue]; } return {}; }, node); }); if (Array.isArray(node.items)) { node.items.forEach((item) => compiledNode(compile, item, dataSourceDeps)); } return node; };