| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- import { nodes } from 'prosemirror-schema-basic'
- import type { Node, NodeSpec } from 'prosemirror-model'
- import { listItem as _listItem } from 'prosemirror-schema-list'
- interface Attr {
- [key: string]: number | string
- }
- const orderedList: NodeSpec = {
- attrs: {
- order: {
- default: 1,
- },
- listStyleType: {
- default: '',
- },
- fontsize: {
- default: '',
- },
- color: {
- default: '',
- },
- },
- content: 'list_item+',
- group: 'block',
- parseDOM: [
- {
- tag: 'ol',
- getAttrs: dom => {
- const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1
- const attr: Attr = { order: +order }
- const { listStyleType, fontSize, color } = (dom as HTMLElement).style
- if (listStyleType) attr['listStyleType'] = listStyleType
- if (fontSize) attr['fontsize'] = fontSize
- if (color) attr['color'] = color
- return attr
- }
- }
- ],
- toDOM: (node: Node) => {
- const { order, listStyleType, fontsize, color } = node.attrs
- let style = ''
- if (listStyleType) style += `list-style-type: ${listStyleType};`
- if (fontsize) style += `font-size: ${fontsize};`
- if (color) style += `color: ${color};`
- const attr: Attr = { style }
- if (order !== 1) attr['start'] = order
- return ['ol', attr, 0]
- },
- }
- const bulletList: NodeSpec = {
- attrs: {
- listStyleType: {
- default: '',
- },
- fontsize: {
- default: '',
- },
- color: {
- default: '',
- },
- },
- content: 'list_item+',
- group: 'block',
- parseDOM: [
- {
- tag: 'ul',
- getAttrs: dom => {
- const attr: Attr = {}
- const { listStyleType, fontSize, color } = (dom as HTMLElement).style
- if (listStyleType) attr['listStyleType'] = listStyleType
- if (fontSize) attr['fontsize'] = fontSize
- if (color) attr['color'] = color
- return attr
- }
- }
- ],
- toDOM: (node: Node) => {
- const { listStyleType, fontsize, color } = node.attrs
- let style = ''
- if (listStyleType) style += `list-style-type: ${listStyleType};`
- if (fontsize) style += `font-size: ${fontsize};`
- if (color) style += `color: ${color};`
- return ['ul', { style }, 0]
- },
- }
- /*
- const listItem: NodeSpec = {
- ..._listItem,
- content: 'paragraph block*',
- group: 'block',
- }
- */
- const listItem: NodeSpec = {
- attrs: {
- textAlign: { default: '' },
- textAlignLast: { default: '' },
- textIndent: { default: '' },
- marginTop: { default: '' },
- marginBottom: { default: '' },
- marginLeft: { default: '' },
- marginRight: { default: '' },
- lineHeight: { default: '' },
- paddingTop: { default: '' },
- paddingRight: { default: '' },
- paddingBottom: { default: '' },
- paddingLeft: { default: '' },
- whiteSpace: { default: 'normal' }, // 新增 white-space 属性
- },
- content: 'paragraph block*',
- group: 'block',
- parseDOM: [
- {
- tag: 'li',
- getAttrs(dom) {
- const el = dom as HTMLElement;
- const style = el.style;
- const textAlign = style.textAlign || '';
- const textAlignLast = style.textAlignLast || '';
- const textIndent = style.textIndent || '';
- const marginTop = style.marginTop || '';
- const marginBottom = style.marginBottom || '';
- const marginLeft = style.marginLeft || '';
- const marginRight = style.marginRight || '';
- const lineHeight = style.lineHeight || '';
- const paddingTop = style.paddingTop || '';
- const paddingRight = style.paddingRight || '';
- const paddingBottom = style.paddingBottom || '';
- const paddingLeft = style.paddingLeft || '';
- const whiteSpace = style.whiteSpace || 'normal'; // 读取 white-space
- return {
- textAlign,
- textAlignLast,
- textIndent,
- marginTop,
- marginBottom,
- marginLeft,
- marginRight,
- lineHeight,
- paddingTop,
- paddingRight,
- paddingBottom,
- paddingLeft,
- whiteSpace, // 返回 whiteSpace
- };
- },
- },
- ],
- toDOM(node) {
- const {
- textAlign,
- textAlignLast,
- textIndent,
- marginTop,
- marginBottom,
- marginLeft,
- marginRight,
- lineHeight,
- paddingTop,
- paddingRight,
- paddingBottom,
- paddingLeft,
- whiteSpace, // 获取 whiteSpace
- } = node.attrs;
- let style = '';
- if (textAlign && textAlign !== 'left') {
- style += `text-align: ${textAlign};`;
- }
- if (textAlignLast) {
- style += `text-align-last: ${textAlignLast};`;
- }
- if (textIndent) {
- style += `text-indent: (100% - ${textIndent});`;
- }
- if (marginTop) style += `margin-top: ${marginTop};`;
- if (marginBottom) style += `margin-bottom: ${marginBottom};`;
- if (marginLeft) {
- // 解析数值和单位
- const str = String(marginLeft).trim();
- const match = str.match(/^([+-]?\d*\.?\d+)(px|pt|em|rem|%|vw|vh)?$/i);
- if (match) {
- let num = parseFloat(match[1]);
- const unit = match[2] || 'px';
- const absNum = Math.abs(num); // 负数转正
- const val = absNum + unit;
- style += `margin-left: max(min(0px, 100% - ${val}), 0px);`;
- } else {
- style += `margin-left: ${marginLeft};`;
- }
- }
- if (marginRight) style += `margin-right: ${marginRight};`;
- if (lineHeight) {
- let finalValue;
- const str = String(lineHeight).trim();
- // 匹配纯数字(整数或小数,可带负号)
- if (/^-?\d+(\.\d+)?$/.test(str)) {
- finalValue = parseFloat(str) * 1.2;
- } else {
- // 带单位或其他非纯数字内容,直接使用原值
- finalValue = lineHeight;
- }
- style += `line-height: ${finalValue};`;
- }
- if (paddingTop) style += `padding-top: ${paddingTop};`;
- if (paddingRight) style += `padding-right: ${paddingRight};`;
- if (paddingBottom) style += `padding-bottom: ${paddingBottom};`;
- if (paddingLeft) style += `padding-left: ${paddingLeft};`;
- if (whiteSpace && whiteSpace !== 'normal') {
- style += `white-space: ${whiteSpace};`; // 添加 white-space
- }
- //const attrs: { style?: string } = {};
- //if (style) attrs.style = style;
- let isEmpty = false;
- const firstChild = node.content.firstChild;
- if (firstChild && firstChild.type.name === 'paragraph') {
- // 段落无任何子节点(包括 text 和 inline 节点)
- if (firstChild.content.size === 0) {
- isEmpty = true;
- }
- }
- // 如果整个 li 的内容长度为 0 也可以作为判断
- if (node.content.size === 0) isEmpty = true;
- const attrs: { style?: string; class?: string } = {};
- if (style) attrs.style = style;
- if (isEmpty) attrs.class = 'empty-li';
- return ['li', attrs, 0];
- },
- };
- const paragraph: NodeSpec = {
- whitespace: "pre", // 此属性控制 ProseMirror 内部空格处理,与 CSS white-space 无关,保留不变
- attrs: {
- textAlign: { default: '' },
- textAlignLast: { default: '' },
- indent: { default: 0 },
- textIndent: { default: 0 },
- marginTop: { default: '' },
- marginBottom: { default: '' },
- marginLeft: { default: '' },
- marginRight: { default: '' },
- lineHeight: { default: '' },
- paddingTop: { default: '' },
- paddingRight: { default: '' },
- paddingBottom: { default: '' },
- paddingLeft: { default: '' },
- whiteSpace: { default: 'normal' }, // 新增 white-space 属性
- },
- content: 'inline*',
- group: 'block',
- parseDOM: [
- {
- tag: 'p',
- getAttrs: dom => {
- const el = dom as HTMLElement;
- const style = el.style;
- const textAlign = style.textAlign || '';
- const textAlignLast = style.textAlignLast || '';
- const marginTop = style.marginTop || '';
- const marginBottom = style.marginBottom || '';
- const marginLeft = style.marginLeft || '';
- const marginRight = style.marginRight || '';
- const textIndent = style.textIndent || '';
- const lineHeight = style.lineHeight || '';
- const paddingTop = style.paddingTop || '';
- const paddingRight = style.paddingRight || '';
- const paddingBottom = style.paddingBottom || '';
- const paddingLeft = style.paddingLeft || '';
- const whiteSpace = style.whiteSpace || 'normal'; // 读取 white-space
- return {
- textAlign,
- textAlignLast,
- textIndent,
- marginTop,
- marginBottom,
- marginLeft,
- marginRight,
- lineHeight,
- paddingTop,
- paddingRight,
- paddingBottom,
- paddingLeft,
- whiteSpace, // 返回 whiteSpace
- };
- },
- },
- {
- tag: 'img',
- ignore: true,
- },
- {
- tag: 'pre',
- skip: true,
- },
- ],
- toDOM: (node: Node) => {
- const {
- textAlign,
- textAlignLast,
- textIndent,
- marginTop,
- marginBottom,
- marginLeft,
- marginRight,
- lineHeight,
- paddingTop,
- paddingRight,
- paddingBottom,
- paddingLeft,
- whiteSpace, // 获取 whiteSpace
- } = node.attrs;
- let style = '';
- if (textAlign && textAlign !== 'left') {
- style += `text-align: ${textAlign};`;
- }
- if (textAlignLast) {
- style += `text-align-last: ${textAlignLast};`;
- }
- if (textIndent) {
- style += `text-indent: (100% - ${textIndent});`;
- }
- if (marginTop) style += `margin-top: ${marginTop};`;
- if (marginBottom) style += `margin-bottom: ${marginBottom};`;
- /*
- if (marginLeft) {
- // 解析数值和单位
- const str = String(marginLeft).trim();
- const match = str.match(/^([+-]?\d*\.?\d+)(px|pt|em|rem|%|vw|vh)?$/i);
- if (match) {
- let num = parseFloat(match[1]);
- const unit = match[2] || 'px';
- const absNum = Math.abs(num); // 负数转正
- const val = absNum + unit;
- style += `margin-left: max(min(0px, 100% - ${val}), 0px);`;
- } else {
- style += `margin-left: ${marginLeft};`;
- }
- }
- */
- if (marginLeft) style += `margin-left: ${marginLeft};`;
- if (marginRight) style += `margin-right: ${marginRight};`;
- if (lineHeight) {
- let finalValue;
- const str = String(lineHeight).trim();
- // 匹配纯数字(整数或小数,可带负号)
- if (/^-?\d+(\.\d+)?$/.test(str)) {
- finalValue = parseFloat(str) * 1.2;
- } else {
- // 带单位或其他非纯数字内容,直接使用原值
- finalValue = lineHeight;
- }
- style += `line-height: ${finalValue};`;
- }
- if (paddingTop) style += `padding-top: ${paddingTop};`;
- if (paddingRight) style += `padding-right: ${paddingRight};`;
- if (paddingBottom) style += `padding-bottom: ${paddingBottom};`;
- if (paddingLeft) style += `padding-left: ${paddingLeft};`;
- if (whiteSpace && whiteSpace !== 'normal') {
- style += `white-space: ${whiteSpace};`; // 添加 white-space
- }
- const attrs: { style?: string; class?: string } = {};
- /*
- let isEmpty = false;
- const firstChild = node.content.firstChild;
- if (firstChild) {
- // 段落无任何子节点(包括 text 和 inline 节点)
- if (firstChild.content.size === 0) {
- isEmpty = true;
- }
- }
-
- // 如果整个 li 的内容长度为 0 也可以作为判断
- if (node.content.size === 0) isEmpty = true;
- if (style) attrs.style = style;
- //if (isEmpty) attrs.class = 'empty';
- */
- if (style) attrs.style = style;
- return ['p', attrs, 0];
- },
- };
- const hardBreak: NodeSpec = {
- inline: true, // 内联节点
- group: 'inline', // 属于 inline 组
- selectable: false, // 不可被光标单独选中
- parseDOM: [{ tag: 'br' }],
- toDOM() {
- return ['br'];
- },
- };
- const {
- doc,
- blockquote,
- text,
- } = nodes
- export default {
- doc,
- paragraph,
- blockquote,
- text,
- 'ordered_list': orderedList,
- 'bullet_list': bulletList,
- 'list_item': listItem,
- hard_break: hardBreak, // 新增
- }
|