refactor: code improvie

This commit is contained in:
katherinehhh 2025-06-29 22:28:58 +08:00
parent ec7a5fb534
commit 5d927391c3
7 changed files with 374 additions and 1 deletions

View File

@ -0,0 +1,55 @@
/**
* 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.
*/
import { Input } from '@formily/antd-v5';
import React from 'react';
import { connect, mapProps, mapReadPretty } from '@formily/react';
import { EditableFieldModel } from '../EditableFieldModel';
import { useParseMarkdown } from './util';
import { useMarkdownStyles } from './style';
const MarkdownReadPretty = (props) => {
const markdownClass = useMarkdownStyles();
const { html = '' } = useParseMarkdown(props.value);
const value = (
<div
className={` ${markdownClass} nb-markdown nb-markdown-default nb-markdown-table`}
dangerouslySetInnerHTML={{ __html: html }}
/>
);
return value;
};
const Markdown: any = connect(
Input.TextArea,
mapProps((props: any, field) => {
return {
...props,
};
}),
mapReadPretty((props) => <MarkdownReadPretty {...props} />),
);
export class MarkdownEditableFieldModel extends EditableFieldModel {
static supportedFieldInterfaces = ['markdown'];
setComponentProps(componentProps) {
super.setComponentProps({
...componentProps,
autoSize: {
maxRows: 10,
minRows: 3,
},
});
}
get component() {
return [Markdown, {}];
}
}

View File

@ -0,0 +1,85 @@
/**
* 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.
*/
/* istanbul ignore file -- @preserve */
// 因为这里有 commonjs在 vitest 下会报错,所以忽略这个文件
import MarkdownIt from 'markdown-it';
import Mermaid from 'mermaid';
/**
* from https://github.com/agoose77/markdown-it-mermaid
*/
// Define interface to await readiness of import
export default function mermaidPlugin(md: MarkdownIt, options: any) {
// Setup Mermaid
Mermaid.initialize({
securityLevel: 'loose',
...options,
});
function getLangName(info: string): string {
return info.split(/\s+/g)[0];
}
// Store reference to original renderer.
const defaultFenceRenderer = md.renderer.rules.fence;
// Render custom code types as SVGs, letting the fence parser do all the heavy lifting.
function customFenceRenderer(tokens: any[], idx: number, options: any, env: any, slf: any) {
const token = tokens[idx];
const info = token.info.trim();
const langName = info ? getLangName(info) : '';
if (['mermaid', '{mermaid}'].indexOf(langName) === -1) {
if (defaultFenceRenderer !== undefined) {
return defaultFenceRenderer(tokens, idx, options, env, slf);
}
// Missing fence renderer!
return '';
}
let imageHTML = '';
const imageAttrs: string[][] = [];
// Create element to render into
const element = document.createElement('div');
document.body.appendChild(element);
// Render with Mermaid
try {
const container_id = 'mermaid-container';
Mermaid.mermaidAPI.render(
container_id,
token.content,
(html: string) => {
// We need to forcibly extract the max-width/height attributes to set on img tag
const svg = document.getElementById(container_id);
if (svg !== null) {
imageAttrs.push(['style', `max-width:${svg.style.maxWidth};max-height:${svg.style.maxHeight}`]);
}
// Store HTML
imageHTML = html;
},
element,
);
} catch (e) {
return `<div class="alert alert-danger">${e}</div>`;
} finally {
element.remove();
}
// Store encoded image data
imageAttrs.push(['src', `data:image/svg+xml,${encodeURIComponent(imageHTML)}`]);
return `<img ${slf.renderAttrs({ attrs: imageAttrs })}>`;
}
md.renderer.rules.fence = customFenceRenderer;
}

View File

@ -0,0 +1,24 @@
/**
* 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.
*/
import MarkdownIt from 'markdown-it';
import markdownItHighlightjs from 'markdown-it-highlightjs';
import mermaidPlugin from './markdown-it-plugins/mermaidPlugin';
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
breaks: true,
});
md.use(markdownItHighlightjs);
md.use(mermaidPlugin);
export default md;

View File

@ -0,0 +1,163 @@
/**
* 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.
*/
import { TinyColor } from '@ctrl/tinycolor';
import { css } from '@emotion/css';
import { theme } from 'antd';
import { useGlobalTheme } from '../../../../../global-theme';
export const useMarkdownStyles = () => {
const { token } = theme.useToken();
const { isDarkTheme } = useGlobalTheme();
const colorFillAlterSolid = new TinyColor(token.colorFillAlter)
.onBackground(token.colorBgContainer)
.toHexShortString();
const defaultStyle: any = {
lineHeight: 'inherit',
// default style of markdown
'&.nb-markdown-default': {
'pre code.hljs': { display: 'block', overflowX: 'auto', padding: '1em' },
'code.hljs': { padding: '3px 5px' },
':not(pre) code': {
padding: '2px 5px',
color: '#d56161',
background: token.colorFillQuaternary,
border: `1px solid ${token.colorBorder}`,
borderRadius: token.borderRadiusSM,
},
blockquote: {
borderLeft: '4px solid #ccc',
paddingLeft: '20px',
marginLeft: '0',
color: '#666',
fontStyle: 'italic',
},
img: { maxWidth: '100%' },
'.hljs': { background: '#f8f8f8', color: '#444' },
'.hljs-comment': { color: '#697070' },
'.hljs-punctuation,.hljs-tag': { color: '#444a' },
'.hljs-tag .hljs-attr,.hljs-tag .hljs-name': { color: '#444' },
'.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag': {
fontWeight: 700,
},
'.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type':
{
color: '#800',
},
'.hljs-section,.hljs-title': { color: '#800', fontWeight: 700 },
'.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable':
{
color: '#ab5656',
},
'.hljs-literal': { color: '#695' },
'.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code': {
color: '#397300',
},
'.hljs-meta': { color: '#1f7199' },
'.hljs-meta .hljs-string': { color: '#38a' },
'.hljs-emphasis': { fontStyle: 'italic' },
'.hljs-strong': { fontWeight: 700 },
},
// table style of markdown
'&.nb-markdown-table': {
table: {
borderCollapse: 'collapse',
width: '100%',
fontFamily: 'Arial, sans-serif',
marginBottom: '1.5rem',
},
'th, td': {
borderBottom: `1px solid ${token.colorBorderSecondary}`,
padding: `${token.paddingContentVertical}px ${token.paddingContentHorizontal}px`,
textAlign: 'left',
},
th: {
backgroundColor: colorFillAlterSolid,
fontWeight: 'bold',
color: token.colorText,
},
'tr:hover': { backgroundColor: token.colorFillTertiary },
'tr:last-child td': { borderBottom: 'none' },
'tr:first-child th': { borderTop: 'none' },
},
};
const darkStyle: any = {
// default style of markdown
'&.nb-markdown-default': {
'pre code.hljs': { display: 'block', overflowX: 'auto', padding: '1em' },
'code.hljs': { padding: '3px 5px' },
':not(pre) code': {
padding: '2px 5px',
color: '#d56161',
background: token.colorFillQuaternary,
border: `1px solid ${token.colorBorder}`,
borderRadius: token.borderRadiusSM,
},
'.hljs': { color: '#adbac7', background: '#22272e' },
'.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_':
{
color: '#f47067',
},
'.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_': {
color: '#dcbdfb',
},
'.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable':
{
color: '#6cb6ff',
},
'.hljs-meta .hljs-string,.hljs-regexp,.hljs-string': { color: '#96d0ff' },
'.hljs-built_in,.hljs-symbol': { color: '#f69d50' },
'.hljs-code,.hljs-comment,.hljs-formula': { color: '#768390' },
'.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag': {
color: '#8ddb8c',
},
'.hljs-subst': { color: '#adbac7' },
'.hljs-section': { color: '#316dca', fontWeight: 700 },
'.hljs-bullet': { color: '#eac55f' },
'.hljs-emphasis': { color: '#adbac7', fontStyle: 'italic' },
'.hljs-strong': { color: '#adbac7', fontWeight: 700 },
'.hljs-addition': { color: '#b4f1b4', backgroundColor: '#1b4721' },
'.hljs-deletion': { color: '#ffd8d3', backgroundColor: '#78191b' },
},
// table style of markdown
'&.nb-markdown-table': {
table: {
borderCollapse: 'collapse',
width: '100%',
fontFamily: 'Arial, sans-serif',
marginBottom: '1.5rem',
},
'th, td': {
borderBottom: `1px solid ${token.colorBorderSecondary}`,
padding: `${token.paddingContentVertical}px ${token.paddingContentHorizontal}px`,
textAlign: 'left',
},
th: {
backgroundColor: colorFillAlterSolid,
fontWeight: 'bold',
color: token.colorText,
},
'tr:hover': { backgroundColor: token.colorFillTertiary },
'tr:last-child td': { borderBottom: 'none' },
'tr:first-child th': { borderTop: 'none' },
},
};
return css({
lineHeight: token.lineHeight,
'& > *:last-child': { marginBottom: '0' },
'.ant-description-textarea, .ant-description-input': { lineHeight: token.lineHeight },
'.field-interface-datetime': { minWidth: '100px' },
...(isDarkTheme ? darkStyle : defaultStyle),
});
};

View File

@ -0,0 +1,45 @@
/**
* 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.
*/
import _ from 'lodash';
import { useEffect, useState } from 'react';
export const parseMarkdown = _.memoize(async (text: string) => {
if (!text) {
return text;
}
const m = await import('./md');
return m.default.render(text);
});
export function useParseMarkdown(text: string) {
const [html, setHtml] = useState<any>('');
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
parseMarkdown(text)
.then((r) => {
setHtml(r);
setLoading(false);
})
.catch((error) => console.log(error));
}, [text]);
return { html, loading };
}
export function convertToText(markdownText: string) {
const content = markdownText;
let temp = document.createElement('div');
temp.innerHTML = content;
const text = temp.innerText;
temp = null;
return text?.replace(/[\n\r]/g, '') || '';
}

View File

@ -11,7 +11,7 @@ import { Input } from '@formily/antd-v5';
import { EditableFieldModel } from './EditableFieldModel';
export class TextareaEditableFieldModel extends EditableFieldModel {
static supportedFieldInterfaces = ['textarea', 'markdown'];
static supportedFieldInterfaces = ['textarea'];
setComponentProps(componentProps) {
super.setComponentProps({

View File

@ -25,3 +25,4 @@ export * from './SelectEditableFieldModel';
export * from './SingleTextEditableFieldModel';
export * from './TextareaEditableFieldModel';
export * from './TimeEditableFieldModel';
export * from './MarkdownEditableFieldModel';