mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
refactor: code improvie
This commit is contained in:
parent
ec7a5fb534
commit
5d927391c3
@ -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, {}];
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
@ -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),
|
||||
});
|
||||
};
|
@ -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, '') || '';
|
||||
}
|
@ -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({
|
||||
|
@ -25,3 +25,4 @@ export * from './SelectEditableFieldModel';
|
||||
export * from './SingleTextEditableFieldModel';
|
||||
export * from './TextareaEditableFieldModel';
|
||||
export * from './TimeEditableFieldModel';
|
||||
export * from './MarkdownEditableFieldModel';
|
||||
|
Loading…
x
Reference in New Issue
Block a user