nodes.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import { nodes } from 'prosemirror-schema-basic'
  2. import type { Node, NodeSpec } from 'prosemirror-model'
  3. import { listItem as _listItem } from 'prosemirror-schema-list'
  4. interface Attr {
  5. [key: string]: number | string
  6. }
  7. const orderedList: NodeSpec = {
  8. attrs: {
  9. order: {
  10. default: 1,
  11. },
  12. listStyleType: {
  13. default: '',
  14. },
  15. fontsize: {
  16. default: '',
  17. },
  18. color: {
  19. default: '',
  20. },
  21. },
  22. content: 'list_item+',
  23. group: 'block',
  24. parseDOM: [
  25. {
  26. tag: 'ol',
  27. getAttrs: dom => {
  28. const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1
  29. const attr: Attr = { order: +order }
  30. const { listStyleType, fontSize, color } = (dom as HTMLElement).style
  31. if (listStyleType) attr['listStyleType'] = listStyleType
  32. if (fontSize) attr['fontsize'] = fontSize
  33. if (color) attr['color'] = color
  34. return attr
  35. }
  36. }
  37. ],
  38. toDOM: (node: Node) => {
  39. const { order, listStyleType, fontsize, color } = node.attrs
  40. let style = ''
  41. if (listStyleType) style += `list-style-type: ${listStyleType};`
  42. if (fontsize) style += `font-size: ${fontsize};`
  43. if (color) style += `color: ${color};`
  44. const attr: Attr = { style }
  45. if (order !== 1) attr['start'] = order
  46. return ['ol', attr, 0]
  47. },
  48. }
  49. const bulletList: NodeSpec = {
  50. attrs: {
  51. listStyleType: {
  52. default: '',
  53. },
  54. fontsize: {
  55. default: '',
  56. },
  57. color: {
  58. default: '',
  59. },
  60. },
  61. content: 'list_item+',
  62. group: 'block',
  63. parseDOM: [
  64. {
  65. tag: 'ul',
  66. getAttrs: dom => {
  67. const attr: Attr = {}
  68. const { listStyleType, fontSize, color } = (dom as HTMLElement).style
  69. if (listStyleType) attr['listStyleType'] = listStyleType
  70. if (fontSize) attr['fontsize'] = fontSize
  71. if (color) attr['color'] = color
  72. return attr
  73. }
  74. }
  75. ],
  76. toDOM: (node: Node) => {
  77. const { listStyleType, fontsize, color } = node.attrs
  78. let style = ''
  79. if (listStyleType) style += `list-style-type: ${listStyleType};`
  80. if (fontsize) style += `font-size: ${fontsize};`
  81. if (color) style += `color: ${color};`
  82. return ['ul', { style }, 0]
  83. },
  84. }
  85. /*
  86. const listItem: NodeSpec = {
  87. ..._listItem,
  88. content: 'paragraph block*',
  89. group: 'block',
  90. }
  91. */
  92. const listItem: NodeSpec = {
  93. attrs: {
  94. textAlign: { default: '' },
  95. textAlignLast: { default: '' },
  96. textIndent: { default: '' },
  97. marginTop: { default: '' },
  98. marginBottom: { default: '' },
  99. marginLeft: { default: '' },
  100. marginRight: { default: '' },
  101. lineHeight: { default: '' },
  102. paddingTop: { default: '' },
  103. paddingRight: { default: '' },
  104. paddingBottom: { default: '' },
  105. paddingLeft: { default: '' },
  106. whiteSpace: { default: 'normal' },
  107. writingMode: { default: '' },
  108. // 新增字体相关属性
  109. fontWeight: { default: '' },
  110. fontStyle: { default: '' },
  111. textDecoration: { default: '' },
  112. textDecorationLine: { default: '' },
  113. verticalAlign: { default: '' },
  114. letterSpacing: { default: '' },
  115. textShadow: { default: '' },
  116. },
  117. content: 'paragraph block*',
  118. group: 'block',
  119. parseDOM: [
  120. {
  121. tag: 'li',
  122. getAttrs(dom) {
  123. const el = dom as HTMLElement;
  124. const style = el.style;
  125. const textAlign = style.textAlign || '';
  126. const textAlignLast = style.textAlignLast || '';
  127. const textIndent = style.textIndent || '';
  128. const marginTop = style.marginTop || '';
  129. const marginBottom = style.marginBottom || '';
  130. const marginLeft = style.marginLeft || '';
  131. const marginRight = style.marginRight || '';
  132. const lineHeight = style.lineHeight || '';
  133. const paddingTop = style.paddingTop || '';
  134. const paddingRight = style.paddingRight || '';
  135. const paddingBottom = style.paddingBottom || '';
  136. const paddingLeft = style.paddingLeft || '';
  137. const whiteSpace = style.whiteSpace || 'normal';
  138. const writingMode = style.writingMode || '';
  139. // 读取新增属性
  140. const fontWeight = style.fontWeight || '';
  141. const fontStyle = style.fontStyle || '';
  142. const textDecoration = style.textDecoration || '';
  143. const textDecorationLine = style.textDecorationLine || '';
  144. const verticalAlign = style.verticalAlign || '';
  145. const letterSpacing = style.letterSpacing || '';
  146. const textShadow = style.textShadow || '';
  147. return {
  148. textAlign,
  149. textAlignLast,
  150. textIndent,
  151. marginTop,
  152. marginBottom,
  153. marginLeft,
  154. marginRight,
  155. lineHeight,
  156. paddingTop,
  157. paddingRight,
  158. paddingBottom,
  159. paddingLeft,
  160. whiteSpace,
  161. writingMode,
  162. fontWeight,
  163. fontStyle,
  164. textDecoration,
  165. textDecorationLine,
  166. verticalAlign,
  167. letterSpacing,
  168. textShadow,
  169. };
  170. },
  171. },
  172. ],
  173. toDOM(node) {
  174. const {
  175. textAlign,
  176. textAlignLast,
  177. textIndent,
  178. marginTop,
  179. marginBottom,
  180. marginLeft,
  181. marginRight,
  182. lineHeight,
  183. paddingTop,
  184. paddingRight,
  185. paddingBottom,
  186. paddingLeft,
  187. whiteSpace,
  188. writingMode,
  189. fontWeight,
  190. fontStyle,
  191. textDecoration,
  192. textDecorationLine,
  193. verticalAlign,
  194. letterSpacing,
  195. textShadow,
  196. } = node.attrs;
  197. let style = '';
  198. if (textAlign && textAlign !== 'left') {
  199. style += `text-align: ${textAlign};`;
  200. }
  201. if (textAlignLast) {
  202. style += `text-align-last: ${textAlignLast};`;
  203. }
  204. if (textIndent) {
  205. style += `text-indent: (100% - ${textIndent});`;
  206. }
  207. if (marginTop) style += `margin-top: ${marginTop};`;
  208. if (marginBottom) style += `margin-bottom: ${marginBottom};`;
  209. if (marginLeft) {
  210. const str = String(marginLeft).trim();
  211. const match = str.match(/^([+-]?\d*\.?\d+)(px|pt|em|rem|%|vw|vh)?$/i);
  212. if (match) {
  213. let num = parseFloat(match[1]);
  214. const unit = match[2] || 'px';
  215. const absNum = Math.abs(num);
  216. const val = absNum + unit;
  217. style += `margin-left: max(min(0px, 100% - ${val}), 0px);`;
  218. } else {
  219. style += `margin-left: ${marginLeft};`;
  220. }
  221. }
  222. if (marginRight) style += `margin-right: ${marginRight};`;
  223. if (lineHeight) {
  224. let finalValue;
  225. const str = String(lineHeight).trim();
  226. if (/^-?\d+(\.\d+)?$/.test(str)) {
  227. finalValue = parseFloat(str) * 1.2;
  228. } else {
  229. finalValue = lineHeight;
  230. }
  231. style += `line-height: ${finalValue};`;
  232. }
  233. else{
  234. style += `line-height: 1.2;`;
  235. }
  236. if (paddingTop) style += `padding-top: ${paddingTop};`;
  237. if (paddingRight) style += `padding-right: ${paddingRight};`;
  238. if (paddingBottom) style += `padding-bottom: ${paddingBottom};`;
  239. if (paddingLeft) style += `padding-left: ${paddingLeft};`;
  240. if (whiteSpace && whiteSpace !== 'normal') {
  241. style += `white-space: ${whiteSpace};`;
  242. }
  243. if (writingMode) {
  244. style += `writing-mode: ${writingMode};`;
  245. }
  246. // 新增字体相关样式输出
  247. if (fontWeight) style += `font-weight: ${fontWeight};`;
  248. if (fontStyle) style += `font-style: ${fontStyle};`;
  249. if (textDecoration) style += `text-decoration: ${textDecoration};`;
  250. if (textDecorationLine) style += `text-decoration-line: ${textDecorationLine};`;
  251. if (verticalAlign) style += `vertical-align: ${verticalAlign};`;
  252. if (letterSpacing) style += `letter-spacing: ${letterSpacing};`;
  253. if (textShadow) style += `text-shadow: ${textShadow};`;
  254. let isEmpty = false;
  255. const firstChild = node.content.firstChild;
  256. if (firstChild && firstChild.type.name === 'paragraph') {
  257. if (firstChild.content.size === 0) {
  258. isEmpty = true;
  259. }
  260. }
  261. if (node.content.size === 0) isEmpty = true;
  262. const attrs: { style?: string; class?: string } = {};
  263. if (style) attrs.style = style;
  264. if (isEmpty) attrs.class = 'empty-li';
  265. return ['li', attrs, 0];
  266. },
  267. };
  268. const paragraph: NodeSpec = {
  269. whitespace: "pre",
  270. attrs: {
  271. textAlign: { default: '' },
  272. textAlignLast: { default: '' },
  273. indent: { default: 0 },
  274. textIndent: { default: 0 },
  275. marginTop: { default: '' },
  276. marginBottom: { default: '' },
  277. marginLeft: { default: '' },
  278. marginRight: { default: '' },
  279. lineHeight: { default: '' },
  280. paddingTop: { default: '' },
  281. paddingRight: { default: '' },
  282. paddingBottom: { default: '' },
  283. paddingLeft: { default: '' },
  284. whiteSpace: { default: 'normal' },
  285. writingMode: { default: '' },
  286. // 新增字体相关属性
  287. fontWeight: { default: '' },
  288. fontStyle: { default: '' },
  289. textDecoration: { default: '' },
  290. textDecorationLine: { default: '' },
  291. verticalAlign: { default: '' },
  292. letterSpacing: { default: '' },
  293. textShadow: { default: '' },
  294. },
  295. content: 'inline*',
  296. group: 'block',
  297. parseDOM: [
  298. {
  299. tag: 'p',
  300. getAttrs: dom => {
  301. const el = dom as HTMLElement;
  302. const style = el.style;
  303. const textAlign = style.textAlign || '';
  304. const textAlignLast = style.textAlignLast || '';
  305. const marginTop = style.marginTop || '';
  306. const marginBottom = style.marginBottom || '';
  307. const marginLeft = style.marginLeft || '';
  308. const marginRight = style.marginRight || '';
  309. const textIndent = style.textIndent || '';
  310. const lineHeight = style.lineHeight || '';
  311. const paddingTop = style.paddingTop || '';
  312. const paddingRight = style.paddingRight || '';
  313. const paddingBottom = style.paddingBottom || '';
  314. const paddingLeft = style.paddingLeft || '';
  315. const whiteSpace = style.whiteSpace || 'normal';
  316. const writingMode = style.writingMode || '';
  317. // 读取新增属性
  318. const fontWeight = style.fontWeight || '';
  319. const fontStyle = style.fontStyle || '';
  320. const textDecoration = style.textDecoration || '';
  321. const textDecorationLine = style.textDecorationLine || '';
  322. const verticalAlign = style.verticalAlign || '';
  323. const letterSpacing = style.letterSpacing || '';
  324. const textShadow = style.textShadow || '';
  325. return {
  326. textAlign,
  327. textAlignLast,
  328. textIndent,
  329. marginTop,
  330. marginBottom,
  331. marginLeft,
  332. marginRight,
  333. lineHeight,
  334. paddingTop,
  335. paddingRight,
  336. paddingBottom,
  337. paddingLeft,
  338. whiteSpace,
  339. writingMode,
  340. fontWeight,
  341. fontStyle,
  342. textDecoration,
  343. textDecorationLine,
  344. verticalAlign,
  345. letterSpacing,
  346. textShadow,
  347. };
  348. },
  349. },
  350. {
  351. tag: 'img',
  352. ignore: true,
  353. },
  354. {
  355. tag: 'pre',
  356. skip: true,
  357. },
  358. ],
  359. toDOM: (node: Node) => {
  360. const {
  361. textAlign,
  362. textAlignLast,
  363. textIndent,
  364. marginTop,
  365. marginBottom,
  366. marginLeft,
  367. marginRight,
  368. lineHeight,
  369. paddingTop,
  370. paddingRight,
  371. paddingBottom,
  372. paddingLeft,
  373. whiteSpace,
  374. writingMode,
  375. fontWeight,
  376. fontStyle,
  377. textDecoration,
  378. textDecorationLine,
  379. verticalAlign,
  380. letterSpacing,
  381. textShadow,
  382. } = node.attrs;
  383. let style = '';
  384. if (textAlign && textAlign !== 'left') {
  385. style += `text-align: ${textAlign};`;
  386. }
  387. if (textAlignLast) {
  388. style += `text-align-last: ${textAlignLast};`;
  389. }
  390. if (textIndent) {
  391. style += `text-indent: (100% - ${textIndent});`;
  392. }
  393. if (marginTop) style += `margin-top: ${marginTop};`;
  394. if (marginBottom) style += `margin-bottom: ${marginBottom};`;
  395. if (marginLeft) style += `margin-left: ${marginLeft};`;
  396. if (marginRight) style += `margin-right: ${marginRight};`;
  397. if (lineHeight) {
  398. let finalValue;
  399. const str = String(lineHeight).trim();
  400. if (/^-?\d+(\.\d+)?$/.test(str)) {
  401. finalValue = parseFloat(str) * 1.2;
  402. } else {
  403. finalValue = lineHeight;
  404. }
  405. style += `line-height: ${finalValue};`;
  406. }
  407. else{
  408. style += `line-height: 1.2;`;
  409. }
  410. if (paddingTop) style += `padding-top: ${paddingTop};`;
  411. if (paddingRight) style += `padding-right: ${paddingRight};`;
  412. if (paddingBottom) style += `padding-bottom: ${paddingBottom};`;
  413. if (paddingLeft) style += `padding-left: ${paddingLeft};`;
  414. if (whiteSpace && whiteSpace !== 'normal') {
  415. style += `white-space: ${whiteSpace};`;
  416. }
  417. if (writingMode) {
  418. style += `writing-mode: ${writingMode};`;
  419. }
  420. // 新增字体相关样式输出
  421. if (fontWeight) style += `font-weight: ${fontWeight};`;
  422. if (fontStyle) style += `font-style: ${fontStyle};`;
  423. if (textDecoration) style += `text-decoration: ${textDecoration};`;
  424. if (textDecorationLine) style += `text-decoration-line: ${textDecorationLine};`;
  425. if (verticalAlign) style += `vertical-align: ${verticalAlign};`;
  426. if (letterSpacing) style += `letter-spacing: ${letterSpacing};`;
  427. if (textShadow) style += `text-shadow: ${textShadow};`;
  428. const attrs: { style?: string; class?: string } = {};
  429. if (style) attrs.style = style;
  430. return ['p', attrs, 0];
  431. },
  432. };
  433. const hardBreak: NodeSpec = {
  434. inline: true, // 内联节点
  435. group: 'inline', // 属于 inline 组
  436. selectable: false, // 不可被光标单独选中
  437. parseDOM: [{ tag: 'br' }],
  438. toDOM() {
  439. return ['br'];
  440. },
  441. };
  442. const {
  443. doc,
  444. blockquote,
  445. text,
  446. } = nodes
  447. export default {
  448. doc,
  449. paragraph,
  450. blockquote,
  451. text,
  452. 'ordered_list': orderedList,
  453. 'bullet_list': bulletList,
  454. 'list_item': listItem,
  455. hard_break: hardBreak, // 新增
  456. }