@aquera/nile-elements 1.9.7 → 1.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +6 -0
  2. package/dist/{index-249de90a.esm.js → index-289706c7.esm.js} +1 -1
  3. package/dist/{index-df196058.cjs.js → index-d7e6f1c6.cjs.js} +2 -2
  4. package/dist/index-d7e6f1c6.cjs.js.map +1 -0
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/index.js +1587 -1276
  8. package/dist/katex-44476ab4.esm.js +1 -0
  9. package/dist/katex-f4186f87.cjs.js +2 -0
  10. package/dist/katex-f4186f87.cjs.js.map +1 -0
  11. package/dist/nile-color-picker/index.cjs.js +1 -1
  12. package/dist/nile-color-picker/index.esm.js +1 -1
  13. package/dist/nile-color-picker/nile-color-picker.cjs.js +1 -1
  14. package/dist/nile-color-picker/nile-color-picker.cjs.js.map +1 -1
  15. package/dist/nile-color-picker/nile-color-picker.esm.js +1 -1
  16. package/dist/nile-select/virtual-scroll-helper.cjs.js +1 -1
  17. package/dist/nile-select/virtual-scroll-helper.cjs.js.map +1 -1
  18. package/dist/nile-select/virtual-scroll-helper.esm.js +52 -44
  19. package/dist/nile-virtual-select/renderer.cjs.js +1 -1
  20. package/dist/nile-virtual-select/renderer.cjs.js.map +1 -1
  21. package/dist/nile-virtual-select/renderer.esm.js +23 -22
  22. package/dist/nile-wysiwyg-editor/engine/commands.cjs.js +1 -1
  23. package/dist/nile-wysiwyg-editor/engine/commands.cjs.js.map +1 -1
  24. package/dist/nile-wysiwyg-editor/engine/commands.esm.js +1 -1
  25. package/dist/nile-wysiwyg-editor/engine/create-state.cjs.js +1 -1
  26. package/dist/nile-wysiwyg-editor/engine/create-state.cjs.js.map +1 -1
  27. package/dist/nile-wysiwyg-editor/engine/create-state.esm.js +1 -1
  28. package/dist/nile-wysiwyg-editor/engine/index.cjs.js +1 -1
  29. package/dist/nile-wysiwyg-editor/engine/index.esm.js +1 -1
  30. package/dist/nile-wysiwyg-editor/engine/inputrules.cjs.js +1 -1
  31. package/dist/nile-wysiwyg-editor/engine/keymap.cjs.js +1 -1
  32. package/dist/nile-wysiwyg-editor/engine/keymap.esm.js +1 -1
  33. package/dist/nile-wysiwyg-editor/engine/plugins/code-highlight.cjs.js +1 -1
  34. package/dist/nile-wysiwyg-editor/engine/plugins/code-highlight.cjs.js.map +1 -1
  35. package/dist/nile-wysiwyg-editor/engine/plugins/code-highlight.esm.js +1 -1
  36. package/dist/nile-wysiwyg-editor/engine/plugins/embed-view.cjs.js +1 -1
  37. package/dist/nile-wysiwyg-editor/engine/plugins/embed-view.cjs.js.map +1 -1
  38. package/dist/nile-wysiwyg-editor/engine/plugins/embed-view.esm.js +1 -1
  39. package/dist/nile-wysiwyg-editor/engine/plugins/embeds.cjs.js +1 -1
  40. package/dist/nile-wysiwyg-editor/engine/plugins/embeds.cjs.js.map +1 -1
  41. package/dist/nile-wysiwyg-editor/engine/plugins/embeds.esm.js +1 -1
  42. package/dist/nile-wysiwyg-editor/engine/plugins/math-view.cjs.js +1 -1
  43. package/dist/nile-wysiwyg-editor/engine/plugins/math-view.cjs.js.map +1 -1
  44. package/dist/nile-wysiwyg-editor/engine/plugins/math-view.esm.js +1 -1
  45. package/dist/nile-wysiwyg-editor/engine/plugins/style-block-view.cjs.js +2 -0
  46. package/dist/nile-wysiwyg-editor/engine/plugins/style-block-view.cjs.js.map +1 -0
  47. package/dist/nile-wysiwyg-editor/engine/plugins/style-block-view.esm.js +1 -0
  48. package/dist/nile-wysiwyg-editor/engine/plugins/tables.cjs.js +1 -1
  49. package/dist/nile-wysiwyg-editor/engine/plugins/tables.cjs.js.map +1 -1
  50. package/dist/nile-wysiwyg-editor/engine/plugins/tables.esm.js +1 -1
  51. package/dist/nile-wysiwyg-editor/engine/sanitize.cjs.js +1 -1
  52. package/dist/nile-wysiwyg-editor/engine/sanitize.cjs.js.map +1 -1
  53. package/dist/nile-wysiwyg-editor/engine/sanitize.esm.js +1 -1
  54. package/dist/nile-wysiwyg-editor/engine/schema.cjs.js +1 -1
  55. package/dist/nile-wysiwyg-editor/engine/schema.cjs.js.map +1 -1
  56. package/dist/nile-wysiwyg-editor/engine/schema.esm.js +1 -1
  57. package/dist/nile-wysiwyg-editor/engine/selection-state.cjs.js +1 -1
  58. package/dist/nile-wysiwyg-editor/engine/selection-state.cjs.js.map +1 -1
  59. package/dist/nile-wysiwyg-editor/engine/selection-state.esm.js +1 -1
  60. package/dist/nile-wysiwyg-editor/index.cjs.js +1 -1
  61. package/dist/nile-wysiwyg-editor/index.esm.js +1 -1
  62. package/dist/nile-wysiwyg-editor/math/katex-renderer.cjs.js +2 -0
  63. package/dist/nile-wysiwyg-editor/math/katex-renderer.cjs.js.map +1 -0
  64. package/dist/nile-wysiwyg-editor/math/katex-renderer.esm.js +1 -0
  65. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.cjs.js +1 -1
  66. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.cjs.js.map +1 -1
  67. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.css.cjs.js +1 -1
  68. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.css.cjs.js.map +1 -1
  69. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.css.esm.js +22 -3
  70. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.esm.js +7 -6
  71. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/index.cjs.js +1 -1
  72. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/index.esm.js +1 -1
  73. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.cjs.js +2 -0
  74. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.cjs.js.map +1 -0
  75. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.esm.js +1 -0
  76. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.cjs.js +1 -1
  77. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.cjs.js.map +1 -1
  78. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.cjs.js +1 -1
  79. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.cjs.js.map +1 -1
  80. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.esm.js +33 -54
  81. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.esm.js +83 -95
  82. package/dist/nile-wysiwyg-editor/types.cjs.js +1 -1
  83. package/dist/nile-wysiwyg-editor/types.cjs.js.map +1 -1
  84. package/dist/nile-wysiwyg-editor/types.esm.js +1 -1
  85. package/dist/nile-wysiwyg-editor/ui/bubble-item.cjs.js +2 -0
  86. package/dist/nile-wysiwyg-editor/ui/bubble-item.cjs.js.map +1 -0
  87. package/dist/nile-wysiwyg-editor/ui/bubble-item.esm.js +1 -0
  88. package/dist/nile-wysiwyg-editor/ui/bubble-menu.cjs.js +1 -1
  89. package/dist/nile-wysiwyg-editor/ui/bubble-menu.cjs.js.map +1 -1
  90. package/dist/nile-wysiwyg-editor/ui/bubble-menu.esm.js +23 -15
  91. package/dist/nile-wysiwyg-editor/ui/code-block-menu.cjs.js +1 -1
  92. package/dist/nile-wysiwyg-editor/ui/code-block-menu.cjs.js.map +1 -1
  93. package/dist/nile-wysiwyg-editor/ui/code-block-menu.esm.js +3 -3
  94. package/dist/nile-wysiwyg-editor/ui/embed-panel.cjs.js +1 -1
  95. package/dist/nile-wysiwyg-editor/ui/embed-panel.cjs.js.map +1 -1
  96. package/dist/nile-wysiwyg-editor/ui/embed-panel.esm.js +2 -2
  97. package/dist/nile-wysiwyg-editor/ui/math-panel.cjs.js +1 -1
  98. package/dist/nile-wysiwyg-editor/ui/math-panel.cjs.js.map +1 -1
  99. package/dist/nile-wysiwyg-editor/ui/math-panel.esm.js +0 -3
  100. package/dist/nile-wysiwyg-editor/ui/preview.cjs.js +2 -0
  101. package/dist/nile-wysiwyg-editor/ui/preview.cjs.js.map +1 -0
  102. package/dist/nile-wysiwyg-editor/ui/preview.esm.js +8 -0
  103. package/dist/nile-wysiwyg-editor/ui/source-view.cjs.js +1 -1
  104. package/dist/nile-wysiwyg-editor/ui/source-view.cjs.js.map +1 -1
  105. package/dist/nile-wysiwyg-editor/ui/source-view.esm.js +225 -24
  106. package/dist/nile-wysiwyg-editor/ui/suggest-list.cjs.js +1 -1
  107. package/dist/nile-wysiwyg-editor/ui/suggest-list.cjs.js.map +1 -1
  108. package/dist/nile-wysiwyg-editor/ui/suggest-list.esm.js +9 -9
  109. package/dist/nile-wysiwyg-editor/ui/table-grid-picker.cjs.js +1 -1
  110. package/dist/nile-wysiwyg-editor/ui/table-grid-picker.cjs.js.map +1 -1
  111. package/dist/nile-wysiwyg-editor/ui/table-grid-picker.esm.js +115 -13
  112. package/dist/nile-wysiwyg-editor/ui/table-menu.cjs.js +1 -1
  113. package/dist/nile-wysiwyg-editor/ui/table-menu.cjs.js.map +1 -1
  114. package/dist/nile-wysiwyg-editor/ui/table-menu.esm.js +13 -12
  115. package/dist/src/index.d.ts +5 -0
  116. package/dist/src/index.js +3 -0
  117. package/dist/src/index.js.map +1 -1
  118. package/dist/src/nile-color-picker/nile-color-picker.d.ts +1 -0
  119. package/dist/src/nile-color-picker/nile-color-picker.js +1 -0
  120. package/dist/src/nile-color-picker/nile-color-picker.js.map +1 -1
  121. package/dist/src/nile-select/virtual-scroll-helper.js +9 -1
  122. package/dist/src/nile-select/virtual-scroll-helper.js.map +1 -1
  123. package/dist/src/nile-virtual-select/renderer.js +1 -0
  124. package/dist/src/nile-virtual-select/renderer.js.map +1 -1
  125. package/dist/src/nile-wysiwyg-editor/engine/index.d.ts +1 -0
  126. package/dist/src/nile-wysiwyg-editor/engine/index.js +1 -0
  127. package/dist/src/nile-wysiwyg-editor/engine/index.js.map +1 -1
  128. package/dist/src/nile-wysiwyg-editor/engine/plugins/embed-view.js +3 -0
  129. package/dist/src/nile-wysiwyg-editor/engine/plugins/embed-view.js.map +1 -1
  130. package/dist/src/nile-wysiwyg-editor/engine/plugins/embeds.d.ts +19 -4
  131. package/dist/src/nile-wysiwyg-editor/engine/plugins/embeds.js +23 -18
  132. package/dist/src/nile-wysiwyg-editor/engine/plugins/embeds.js.map +1 -1
  133. package/dist/src/nile-wysiwyg-editor/engine/plugins/math-view.d.ts +8 -3
  134. package/dist/src/nile-wysiwyg-editor/engine/plugins/math-view.js +41 -4
  135. package/dist/src/nile-wysiwyg-editor/engine/plugins/math-view.js.map +1 -1
  136. package/dist/src/nile-wysiwyg-editor/engine/plugins/style-block-view.d.ts +17 -0
  137. package/dist/src/nile-wysiwyg-editor/engine/plugins/style-block-view.js +174 -0
  138. package/dist/src/nile-wysiwyg-editor/engine/plugins/style-block-view.js.map +1 -0
  139. package/dist/src/nile-wysiwyg-editor/engine/plugins/tables.js +33 -2
  140. package/dist/src/nile-wysiwyg-editor/engine/plugins/tables.js.map +1 -1
  141. package/dist/src/nile-wysiwyg-editor/engine/pm.test.js +49 -8
  142. package/dist/src/nile-wysiwyg-editor/engine/pm.test.js.map +1 -1
  143. package/dist/src/nile-wysiwyg-editor/engine/sanitize.d.ts +12 -0
  144. package/dist/src/nile-wysiwyg-editor/engine/sanitize.js +76 -8
  145. package/dist/src/nile-wysiwyg-editor/engine/sanitize.js.map +1 -1
  146. package/dist/src/nile-wysiwyg-editor/engine/schema.d.ts +2 -0
  147. package/dist/src/nile-wysiwyg-editor/engine/schema.js +154 -2
  148. package/dist/src/nile-wysiwyg-editor/engine/schema.js.map +1 -1
  149. package/dist/src/nile-wysiwyg-editor/index.d.ts +5 -0
  150. package/dist/src/nile-wysiwyg-editor/index.js +3 -0
  151. package/dist/src/nile-wysiwyg-editor/index.js.map +1 -1
  152. package/dist/src/nile-wysiwyg-editor/math/katex-renderer.d.ts +38 -0
  153. package/dist/src/nile-wysiwyg-editor/math/katex-renderer.js +40 -0
  154. package/dist/src/nile-wysiwyg-editor/math/katex-renderer.js.map +1 -0
  155. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.css.js +22 -3
  156. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.css.js.map +1 -1
  157. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.d.ts +36 -0
  158. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.js +125 -8
  159. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.js.map +1 -1
  160. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.test.js +5 -2
  161. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.test.js.map +1 -1
  162. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/index.d.ts +2 -0
  163. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/index.js +1 -0
  164. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/index.js.map +1 -1
  165. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.d.ts +55 -0
  166. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.js +79 -0
  167. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.js.map +1 -0
  168. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.js +33 -54
  169. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.js.map +1 -1
  170. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.d.ts +50 -2
  171. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.js +158 -102
  172. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.js.map +1 -1
  173. package/dist/src/nile-wysiwyg-editor/types.d.ts +4 -2
  174. package/dist/src/nile-wysiwyg-editor/types.js +2 -2
  175. package/dist/src/nile-wysiwyg-editor/types.js.map +1 -1
  176. package/dist/src/nile-wysiwyg-editor/ui/bubble-item.d.ts +48 -0
  177. package/dist/src/nile-wysiwyg-editor/ui/bubble-item.js +69 -0
  178. package/dist/src/nile-wysiwyg-editor/ui/bubble-item.js.map +1 -0
  179. package/dist/src/nile-wysiwyg-editor/ui/bubble-menu.d.ts +17 -2
  180. package/dist/src/nile-wysiwyg-editor/ui/bubble-menu.js +128 -35
  181. package/dist/src/nile-wysiwyg-editor/ui/bubble-menu.js.map +1 -1
  182. package/dist/src/nile-wysiwyg-editor/ui/embed-panel.d.ts +2 -1
  183. package/dist/src/nile-wysiwyg-editor/ui/embed-panel.js +4 -3
  184. package/dist/src/nile-wysiwyg-editor/ui/embed-panel.js.map +1 -1
  185. package/dist/src/nile-wysiwyg-editor/ui/math-panel.js +0 -3
  186. package/dist/src/nile-wysiwyg-editor/ui/math-panel.js.map +1 -1
  187. package/dist/src/nile-wysiwyg-editor/ui/preview.d.ts +20 -0
  188. package/dist/src/nile-wysiwyg-editor/ui/preview.js +44 -0
  189. package/dist/src/nile-wysiwyg-editor/ui/preview.js.map +1 -0
  190. package/dist/src/nile-wysiwyg-editor/ui/source-view.d.ts +35 -3
  191. package/dist/src/nile-wysiwyg-editor/ui/source-view.js +337 -24
  192. package/dist/src/nile-wysiwyg-editor/ui/source-view.js.map +1 -1
  193. package/dist/src/nile-wysiwyg-editor/ui/suggest-list.d.ts +1 -0
  194. package/dist/src/nile-wysiwyg-editor/ui/suggest-list.js +9 -0
  195. package/dist/src/nile-wysiwyg-editor/ui/suggest-list.js.map +1 -1
  196. package/dist/src/nile-wysiwyg-editor/ui/table-grid-picker.d.ts +13 -2
  197. package/dist/src/nile-wysiwyg-editor/ui/table-grid-picker.js +166 -10
  198. package/dist/src/nile-wysiwyg-editor/ui/table-grid-picker.js.map +1 -1
  199. package/dist/src/nile-wysiwyg-editor/ui/table-menu.d.ts +1 -0
  200. package/dist/src/nile-wysiwyg-editor/ui/table-menu.js +10 -8
  201. package/dist/src/nile-wysiwyg-editor/ui/table-menu.js.map +1 -1
  202. package/dist/src/version.js +1 -1
  203. package/dist/src/version.js.map +1 -1
  204. package/dist/tsconfig.tsbuildinfo +1 -1
  205. package/dist/xml-25c4108a.esm.js +1 -0
  206. package/dist/xml-90fd974e.cjs.js +2 -0
  207. package/dist/xml-90fd974e.cjs.js.map +1 -0
  208. package/package.json +2 -1
  209. package/rollup.config.js +1 -0
  210. package/src/index.ts +8 -0
  211. package/src/nile-color-picker/nile-color-picker.ts +1 -0
  212. package/src/nile-select/virtual-scroll-helper.ts +9 -1
  213. package/src/nile-virtual-select/renderer.ts +1 -1
  214. package/src/nile-wysiwyg-editor/engine/index.ts +1 -0
  215. package/src/nile-wysiwyg-editor/engine/plugins/embed-view.ts +3 -0
  216. package/src/nile-wysiwyg-editor/engine/plugins/embeds.ts +26 -20
  217. package/src/nile-wysiwyg-editor/engine/plugins/math-view.ts +45 -4
  218. package/src/nile-wysiwyg-editor/engine/plugins/style-block-view.ts +178 -0
  219. package/src/nile-wysiwyg-editor/engine/plugins/tables.ts +34 -2
  220. package/src/nile-wysiwyg-editor/engine/pm.test.ts +58 -8
  221. package/src/nile-wysiwyg-editor/engine/sanitize.ts +71 -8
  222. package/src/nile-wysiwyg-editor/engine/schema.ts +158 -7
  223. package/src/nile-wysiwyg-editor/index.ts +5 -0
  224. package/src/nile-wysiwyg-editor/math/katex-renderer.ts +59 -0
  225. package/src/nile-wysiwyg-editor/math/katex.d.ts +17 -0
  226. package/src/nile-wysiwyg-editor/nile-wysiwyg-editor.css.ts +22 -3
  227. package/src/nile-wysiwyg-editor/nile-wysiwyg-editor.test.ts +5 -2
  228. package/src/nile-wysiwyg-editor/nile-wysiwyg-editor.ts +142 -7
  229. package/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/index.ts +2 -0
  230. package/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar-item.ts +68 -0
  231. package/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.ts +33 -54
  232. package/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.ts +202 -108
  233. package/src/nile-wysiwyg-editor/types.ts +6 -4
  234. package/src/nile-wysiwyg-editor/ui/bubble-item.ts +60 -0
  235. package/src/nile-wysiwyg-editor/ui/bubble-menu.ts +147 -40
  236. package/src/nile-wysiwyg-editor/ui/embed-panel.ts +4 -3
  237. package/src/nile-wysiwyg-editor/ui/math-panel.ts +0 -3
  238. package/src/nile-wysiwyg-editor/ui/preview.ts +42 -0
  239. package/src/nile-wysiwyg-editor/ui/source-view.ts +339 -31
  240. package/src/nile-wysiwyg-editor/ui/suggest-list.ts +10 -0
  241. package/src/nile-wysiwyg-editor/ui/table-grid-picker.ts +160 -10
  242. package/src/nile-wysiwyg-editor/ui/table-menu.ts +10 -8
  243. package/vscode-html-custom-data.json +75 -6
  244. package/dist/index-df196058.cjs.js.map +0 -1
@@ -5,10 +5,10 @@
5
5
  * string ever touches a live DOM, and strips style declarations down to the
6
6
  * set the schema understands.
7
7
  */
8
- import { isAllowedEmbedSrc } from './plugins/embeds';
8
+ import { isAllowedEmbedSrc, EMBED_SANDBOX, EMBED_REFERRER_POLICY } from './plugins/embeds';
9
9
  /** Elements removed together with their content. */
10
10
  const DROP_WITH_CONTENT = new Set([
11
- 'script', 'style', 'frame', 'frameset', 'object', 'embed',
11
+ 'script', 'frame', 'frameset', 'object', 'embed',
12
12
  'applet', 'form', 'input', 'textarea', 'select', 'button', 'meta', 'link',
13
13
  'base', 'title', 'noscript', 'template', 'svg', 'math', 'canvas', 'audio',
14
14
  'video', 'dialog', 'slot',
@@ -39,9 +39,21 @@ const TAG_ATTRS = {
39
39
  th: new Set(['colspan', 'rowspan', 'data-colwidth']),
40
40
  col: new Set(['span', 'width']),
41
41
  div: new Set(['data-embed-provider', 'data-width', 'data-align']),
42
- iframe: new Set(['src', 'title', 'frameborder', 'allowfullscreen', 'allow']),
42
+ iframe: new Set([
43
+ 'src', 'title', 'frameborder', 'allowfullscreen', 'allow', 'sandbox', 'referrerpolicy',
44
+ ]),
43
45
  };
44
- const GLOBAL_ATTRS = new Set(['style', 'align']);
46
+ const GLOBAL_ATTRS = new Set(['style', 'align', 'class', 'id']);
47
+ /** Author class/id names starting with these are the editor's own; drop them so
48
+ * content can't impersonate or restyle the chrome. */
49
+ const RESERVED_NAME = /^(?:nile-wysiwyg|ProseMirror)/;
50
+ /** Filtered `class`: reserved-prefixed tokens removed. Empty → drop attribute. */
51
+ function cleanClassAttr(value) {
52
+ return value
53
+ .split(/\s+/)
54
+ .filter(token => token && !RESERVED_NAME.test(token))
55
+ .join(' ');
56
+ }
45
57
  /** Style declarations the schema can represent. Everything else is noise. */
46
58
  const ALLOWED_STYLES = new Set([
47
59
  'color', 'background-color', 'font-family', 'font-size', 'font-style',
@@ -89,6 +101,34 @@ function filterStyle(value, tag) {
89
101
  })
90
102
  .join('; ');
91
103
  }
104
+ /**
105
+ * Sanitizes the CSS inside a <style> block. Neutralizes the vectors that make
106
+ * a raw stylesheet dangerous while leaving ordinary rules intact:
107
+ * - `@import` (external fetch / data exfiltration) — removed,
108
+ * - `expression()`, `behavior:`, `-moz-binding:` (script execution) — broken,
109
+ * - `url(...)` pointing anywhere other than a safe data: image or a local
110
+ * `#fragment` — replaced with `url(#)`,
111
+ * - any `</style` sequence — stripped so the text can't break out of the
112
+ * element when the document is re-serialized.
113
+ * CSS comments are dropped first so none of the above can hide inside one.
114
+ */
115
+ export function sanitizeCss(css) {
116
+ return css
117
+ .replace(/<\/style/gi, '')
118
+ .replace(/\/\*[\s\S]*?\*\//g, '')
119
+ .replace(/@import[^;]*;?/gi, '')
120
+ .replace(/expression\s*\(/gi, 'blocked(')
121
+ .replace(/(behavior|-moz-binding)(\s*):/gi, 'blocked-prop$2:')
122
+ .replace(/url\(\s*(['"]?)([^)]*?)\1\s*\)/gi, (full, _quote, raw) => {
123
+ const url = String(raw).trim();
124
+ if (/^#/.test(url))
125
+ return full;
126
+ if (/^data:image\/(png|gif|jpe?g|webp|avif|bmp);/i.test(url))
127
+ return full;
128
+ return 'url(#)';
129
+ })
130
+ .trim();
131
+ }
92
132
  function sanitizeElement(el) {
93
133
  const tag = el.tagName.toLowerCase();
94
134
  const allowed = TAG_ATTRS[tag];
@@ -107,6 +147,17 @@ function sanitizeElement(el) {
107
147
  else
108
148
  el.removeAttribute('style');
109
149
  }
150
+ else if (name === 'class') {
151
+ const cleaned = cleanClassAttr(attr.value);
152
+ if (cleaned)
153
+ el.setAttribute('class', cleaned);
154
+ else
155
+ el.removeAttribute('class');
156
+ }
157
+ else if (name === 'id') {
158
+ if (RESERVED_NAME.test(attr.value))
159
+ el.removeAttribute('id');
160
+ }
110
161
  else if (name === 'href' || name === 'src') {
111
162
  // Raster data: URLs are allowed on images only.
112
163
  if (!isSafeUrl(attr.value, tag === 'img'))
@@ -127,9 +178,23 @@ function walk(node) {
127
178
  child.remove();
128
179
  continue;
129
180
  }
130
- // iframes are allowed ONLY for allowlisted embed providers.
131
- if (tag === 'iframe' && !isAllowedEmbedSrc(child.getAttribute('src') ?? '')) {
132
- child.remove();
181
+ // iframes are allowed for any https src, but always force-sandboxed so an
182
+ // untrusted embed can never navigate the top window or run unrestricted.
183
+ if (tag === 'iframe') {
184
+ if (!isAllowedEmbedSrc(child.getAttribute('src') ?? '')) {
185
+ child.remove();
186
+ continue;
187
+ }
188
+ child.setAttribute('sandbox', EMBED_SANDBOX);
189
+ child.setAttribute('referrerpolicy', EMBED_REFERRER_POLICY);
190
+ }
191
+ // <style> blocks are kept, but their CSS is sanitized and any attributes
192
+ // (media, nonce, …) dropped. Handled before the unwrap branch below so the
193
+ // raw CSS is never spilled into the document as visible text.
194
+ if (tag === 'style') {
195
+ child.textContent = sanitizeCss(child.textContent ?? '');
196
+ for (const attr of Array.from(child.attributes))
197
+ child.removeAttribute(attr.name);
133
198
  continue;
134
199
  }
135
200
  if (!ALLOWED_TAGS.has(tag)) {
@@ -148,7 +213,10 @@ function walk(node) {
148
213
  * DOMParser, so embedded scripts never execute and images never load.
149
214
  */
150
215
  export function sanitizeHtml(html) {
151
- const doc = new DOMParser().parseFromString(html, 'text/html');
216
+ // Wrap in an explicit <body> so metadata-like tags (notably <style>) stay in
217
+ // the body instead of being hoisted into <head> by the HTML parser — which
218
+ // would drop them before walk() ever sees them. Mirrors htmlToDoc's parse.
219
+ const doc = new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html');
152
220
  walk(doc.body);
153
221
  return doc.body.innerHTML;
154
222
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../../src/nile-wysiwyg-editor/engine/sanitize.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * HTML sanitization pre-pass. The ProseMirror schema is the real whitelist —\n * anything it can't parse is dropped — but this pass removes actively\n * dangerous content (scripts, event handlers, javascript: URLs) before the\n * string ever touches a live DOM, and strips style declarations down to the\n * set the schema understands.\n */\n\nimport { isAllowedEmbedSrc } from './plugins/embeds';\n\n/** Elements removed together with their content. */\nconst DROP_WITH_CONTENT = new Set([\n 'script', 'style', 'frame', 'frameset', 'object', 'embed',\n 'applet', 'form', 'input', 'textarea', 'select', 'button', 'meta', 'link',\n 'base', 'title', 'noscript', 'template', 'svg', 'math', 'canvas', 'audio',\n 'video', 'dialog', 'slot',\n]);\n\n/** Elements kept as-is (with filtered attributes). Extended in later phases. */\nconst ALLOWED_TAGS = new Set([\n 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'ul', 'ol', 'li',\n 'br', 'hr', 'a', 'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del',\n 'span', 'code', 'sub', 'sup', 'div', 'font',\n 'pre', 'figure', 'figcaption', 'img', 'iframe',\n 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'colgroup', 'col',\n]);\n\n/** Attributes allowed per tag (on top of GLOBAL_ATTRS). */\nconst TAG_ATTRS: Record<string, Set<string>> = {\n a: new Set(['href', 'title', 'target', 'rel', 'id', 'name', 'data-bookmark']),\n table: new Set(['role', 'data-layout']),\n ol: new Set(['start', 'type', 'data-list-style']),\n ul: new Set(['data-task-list']),\n figure: new Set(['data-align']),\n li: new Set(['data-task-item', 'data-checked']),\n span: new Set([\n 'data-mention-trigger', 'data-mention-key', 'data-mention-label',\n 'data-merge-field', 'data-merge-label', 'data-math', 'data-math-display',\n ]),\n pre: new Set(['data-language']),\n img: new Set(['src', 'alt', 'title', 'width', 'height']),\n td: new Set(['colspan', 'rowspan', 'data-colwidth']),\n th: new Set(['colspan', 'rowspan', 'data-colwidth']),\n col: new Set(['span', 'width']),\n div: new Set(['data-embed-provider', 'data-width', 'data-align']),\n iframe: new Set(['src', 'title', 'frameborder', 'allowfullscreen', 'allow']),\n};\n\nconst GLOBAL_ATTRS = new Set(['style', 'align']);\n\n/** Style declarations the schema can represent. Everything else is noise. */\nconst ALLOWED_STYLES = new Set([\n 'color', 'background-color', 'font-family', 'font-size', 'font-style',\n 'font-weight', 'text-align', 'text-decoration', 'text-decoration-line',\n 'vertical-align', 'margin-left', 'margin-inline-start', 'list-style-type',\n 'line-height',\n]);\n\nconst SAFE_URL_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'];\n\nexport function isSafeUrl(url: string, allowDataImage = false): boolean {\n const trimmed = url.trim();\n if (trimmed === '') return false;\n // Relative URLs and fragments have no protocol — safe.\n if (/^(#|\\/|\\.\\/|\\.\\.\\/)/.test(trimmed)) return true;\n const match = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);\n if (!match) return true;\n const protocol = `${match[1].toLowerCase()}:`;\n if (SAFE_URL_PROTOCOLS.includes(protocol)) return true;\n if (allowDataImage && protocol === 'data:') {\n // Raster images only — data:image/svg+xml can carry scripts.\n return /^data:image\\/(png|gif|jpe?g|webp|avif|bmp);/i.test(trimmed);\n }\n return false;\n}\n\n/** Size styles allowed only where they are part of the document model. */\nconst SIZED_TAGS: Record<string, Set<string>> = {\n img: new Set(['width', 'height']),\n figure: new Set(['width', 'margin-right']),\n div: new Set(['width', 'max-width']),\n};\n\nfunction filterStyle(value: string, tag: string): string {\n const extra = SIZED_TAGS[tag];\n return value\n .split(';')\n .map(decl => decl.trim())\n .filter(decl => {\n const name = decl.slice(0, decl.indexOf(':')).trim().toLowerCase();\n if (!ALLOWED_STYLES.has(name) && !(extra && extra.has(name))) return false;\n // Defense in depth: CSS url()/expression() smuggling.\n return !/url\\s*\\(|expression\\s*\\(/i.test(decl);\n })\n .join('; ');\n}\n\nfunction sanitizeElement(el: Element): void {\n const tag = el.tagName.toLowerCase();\n const allowed = TAG_ATTRS[tag];\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n const keep =\n (GLOBAL_ATTRS.has(name) || (allowed !== undefined && allowed.has(name))) &&\n !name.startsWith('on');\n if (!keep) {\n el.removeAttribute(attr.name);\n continue;\n }\n if (name === 'style') {\n const filtered = filterStyle(attr.value, tag);\n if (filtered) el.setAttribute('style', filtered);\n else el.removeAttribute('style');\n } else if (name === 'href' || name === 'src') {\n // Raster data: URLs are allowed on images only.\n if (!isSafeUrl(attr.value, tag === 'img')) el.removeAttribute(attr.name);\n } else if (name === 'target') {\n if (attr.value === '_blank') el.setAttribute('rel', 'noopener noreferrer');\n else el.removeAttribute('target');\n }\n }\n}\n\nfunction walk(node: Element): void {\n for (const child of Array.from(node.children)) {\n const tag = child.tagName.toLowerCase();\n if (DROP_WITH_CONTENT.has(tag)) {\n child.remove();\n continue;\n }\n // iframes are allowed ONLY for allowlisted embed providers.\n if (tag === 'iframe' && !isAllowedEmbedSrc(child.getAttribute('src') ?? '')) {\n child.remove();\n continue;\n }\n if (!ALLOWED_TAGS.has(tag)) {\n // Unknown wrapper (article, section, table for now, …): keep its\n // content, drop the element itself.\n walk(child);\n child.replaceWith(...Array.from(child.childNodes));\n continue;\n }\n sanitizeElement(child);\n walk(child);\n }\n}\n\n/**\n * Sanitizes an HTML string. Parsing happens in an inert document created by\n * DOMParser, so embedded scripts never execute and images never load.\n */\nexport function sanitizeHtml(html: string): string {\n const doc = new DOMParser().parseFromString(html, 'text/html');\n walk(doc.body);\n return doc.body.innerHTML;\n}\n"]}
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../../src/nile-wysiwyg-editor/engine/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE3F,oDAAoD;AACpD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO;IAChD,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM;IACzE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO;IACzE,OAAO,EAAE,QAAQ,EAAE,MAAM;CAC1B,CAAC,CAAC;AAEH,gFAAgF;AAChF,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;IAC3B,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IACvE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK;IACpE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;IAC3C,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ;IAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK;CACxE,CAAC,CAAC;AAEH,2DAA2D;AAC3D,MAAM,SAAS,GAAgC;IAC7C,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7E,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACvC,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACjD,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC/B,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAC/B,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IAC/C,IAAI,EAAE,IAAI,GAAG,CAAC;QACZ,sBAAsB,EAAE,kBAAkB,EAAE,oBAAoB;QAChE,kBAAkB,EAAE,kBAAkB,EAAE,WAAW,EAAE,mBAAmB;KACzE,CAAC;IACF,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;IAC/B,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACxD,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IACpD,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IACpD,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,qBAAqB,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IACjE,MAAM,EAAE,IAAI,GAAG,CAAC;QACd,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB;KACvF,CAAC;CACH,CAAC;AAEF,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;AAEhE;sDACsD;AACtD,MAAM,aAAa,GAAG,+BAA+B,CAAC;AAEtD,kFAAkF;AAClF,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK;SACT,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACpD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,6EAA6E;AAC7E,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY;IACrE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,sBAAsB;IACtE,gBAAgB,EAAE,aAAa,EAAE,qBAAqB,EAAE,iBAAiB;IACzE,aAAa;CACd,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1E,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,cAAc,GAAG,KAAK;IAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACjC,uDAAuD;IACvD,IAAI,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC;IAC9C,IAAI,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,cAAc,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC3C,6DAA6D;QAC7D,OAAO,8CAA8C,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,GAAgC;IAC9C,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACjC,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC1C,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;CACrC,CAAC;AAEF,SAAS,WAAW,CAAC,KAAa,EAAE,GAAW;IAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC9B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SACxB,MAAM,CAAC,IAAI,CAAC,EAAE;QACb,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACnE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3E,sDAAsD;QACtD,OAAO,CAAC,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,GAAG;SACP,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,mBAAmB,EAAE,UAAU,CAAC;SACxC,OAAO,CAAC,iCAAiC,EAAE,iBAAiB,CAAC;SAC7D,OAAO,CAAC,kCAAkC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;QACjE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,IAAI,8CAA8C,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1E,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;SACD,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,eAAe,CAAC,EAAW;IAClC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,IAAI,GACR,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YACxE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAC9C,IAAI,QAAQ;gBAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;;gBAC5C,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;aAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,OAAO;gBAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;;gBAC1C,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;aAAM,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACzB,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/D,CAAC;aAAM,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YAC7C,gDAAgD;YAChD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,KAAK,KAAK,CAAC;gBAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3E,CAAC;aAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;gBAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;;gBACtE,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,IAAI,CAAC,IAAa;IACzB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACxC,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,KAAK,CAAC,MAAM,EAAE,CAAC;YACf,SAAS;QACX,CAAC;QACD,0EAA0E;QAC1E,yEAAyE;QACzE,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACrB,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;gBACxD,KAAK,CAAC,MAAM,EAAE,CAAC;gBACf,SAAS;YACX,CAAC;YACD,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;YAC7C,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAC;QAC9D,CAAC;QACD,yEAAyE;QACzE,2EAA2E;QAC3E,8DAA8D;QAC9D,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YACzD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;gBAAE,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClF,SAAS;QACX,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,iEAAiE;YACjE,oCAAoC;YACpC,IAAI,CAAC,KAAK,CAAC,CAAC;YACZ,KAAK,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;YACnD,SAAS;QACX,CAAC;QACD,eAAe,CAAC,KAAK,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,6EAA6E;IAC7E,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC,eAAe,CAAC,SAAS,IAAI,SAAS,EAAE,WAAW,CAAC,CAAC;IACjF,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACf,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * HTML sanitization pre-pass. The ProseMirror schema is the real whitelist —\n * anything it can't parse is dropped — but this pass removes actively\n * dangerous content (scripts, event handlers, javascript: URLs) before the\n * string ever touches a live DOM, and strips style declarations down to the\n * set the schema understands.\n */\n\nimport { isAllowedEmbedSrc, EMBED_SANDBOX, EMBED_REFERRER_POLICY } from './plugins/embeds';\n\n/** Elements removed together with their content. */\nconst DROP_WITH_CONTENT = new Set([\n 'script', 'frame', 'frameset', 'object', 'embed',\n 'applet', 'form', 'input', 'textarea', 'select', 'button', 'meta', 'link',\n 'base', 'title', 'noscript', 'template', 'svg', 'math', 'canvas', 'audio',\n 'video', 'dialog', 'slot',\n]);\n\n/** Elements kept as-is (with filtered attributes). Extended in later phases. */\nconst ALLOWED_TAGS = new Set([\n 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'ul', 'ol', 'li',\n 'br', 'hr', 'a', 'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del',\n 'span', 'code', 'sub', 'sup', 'div', 'font',\n 'pre', 'figure', 'figcaption', 'img', 'iframe',\n 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'colgroup', 'col',\n]);\n\n/** Attributes allowed per tag (on top of GLOBAL_ATTRS). */\nconst TAG_ATTRS: Record<string, Set<string>> = {\n a: new Set(['href', 'title', 'target', 'rel', 'id', 'name', 'data-bookmark']),\n table: new Set(['role', 'data-layout']),\n ol: new Set(['start', 'type', 'data-list-style']),\n ul: new Set(['data-task-list']),\n figure: new Set(['data-align']),\n li: new Set(['data-task-item', 'data-checked']),\n span: new Set([\n 'data-mention-trigger', 'data-mention-key', 'data-mention-label',\n 'data-merge-field', 'data-merge-label', 'data-math', 'data-math-display',\n ]),\n pre: new Set(['data-language']),\n img: new Set(['src', 'alt', 'title', 'width', 'height']),\n td: new Set(['colspan', 'rowspan', 'data-colwidth']),\n th: new Set(['colspan', 'rowspan', 'data-colwidth']),\n col: new Set(['span', 'width']),\n div: new Set(['data-embed-provider', 'data-width', 'data-align']),\n iframe: new Set([\n 'src', 'title', 'frameborder', 'allowfullscreen', 'allow', 'sandbox', 'referrerpolicy',\n ]),\n};\n\nconst GLOBAL_ATTRS = new Set(['style', 'align', 'class', 'id']);\n\n/** Author class/id names starting with these are the editor's own; drop them so\n * content can't impersonate or restyle the chrome. */\nconst RESERVED_NAME = /^(?:nile-wysiwyg|ProseMirror)/;\n\n/** Filtered `class`: reserved-prefixed tokens removed. Empty → drop attribute. */\nfunction cleanClassAttr(value: string): string {\n return value\n .split(/\\s+/)\n .filter(token => token && !RESERVED_NAME.test(token))\n .join(' ');\n}\n\n/** Style declarations the schema can represent. Everything else is noise. */\nconst ALLOWED_STYLES = new Set([\n 'color', 'background-color', 'font-family', 'font-size', 'font-style',\n 'font-weight', 'text-align', 'text-decoration', 'text-decoration-line',\n 'vertical-align', 'margin-left', 'margin-inline-start', 'list-style-type',\n 'line-height',\n]);\n\nconst SAFE_URL_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'];\n\nexport function isSafeUrl(url: string, allowDataImage = false): boolean {\n const trimmed = url.trim();\n if (trimmed === '') return false;\n // Relative URLs and fragments have no protocol — safe.\n if (/^(#|\\/|\\.\\/|\\.\\.\\/)/.test(trimmed)) return true;\n const match = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);\n if (!match) return true;\n const protocol = `${match[1].toLowerCase()}:`;\n if (SAFE_URL_PROTOCOLS.includes(protocol)) return true;\n if (allowDataImage && protocol === 'data:') {\n // Raster images only — data:image/svg+xml can carry scripts.\n return /^data:image\\/(png|gif|jpe?g|webp|avif|bmp);/i.test(trimmed);\n }\n return false;\n}\n\n/** Size styles allowed only where they are part of the document model. */\nconst SIZED_TAGS: Record<string, Set<string>> = {\n img: new Set(['width', 'height']),\n figure: new Set(['width', 'margin-right']),\n div: new Set(['width', 'max-width']),\n};\n\nfunction filterStyle(value: string, tag: string): string {\n const extra = SIZED_TAGS[tag];\n return value\n .split(';')\n .map(decl => decl.trim())\n .filter(decl => {\n const name = decl.slice(0, decl.indexOf(':')).trim().toLowerCase();\n if (!ALLOWED_STYLES.has(name) && !(extra && extra.has(name))) return false;\n // Defense in depth: CSS url()/expression() smuggling.\n return !/url\\s*\\(|expression\\s*\\(/i.test(decl);\n })\n .join('; ');\n}\n\n/**\n * Sanitizes the CSS inside a <style> block. Neutralizes the vectors that make\n * a raw stylesheet dangerous while leaving ordinary rules intact:\n * - `@import` (external fetch / data exfiltration) — removed,\n * - `expression()`, `behavior:`, `-moz-binding:` (script execution) — broken,\n * - `url(...)` pointing anywhere other than a safe data: image or a local\n * `#fragment` — replaced with `url(#)`,\n * - any `</style` sequence — stripped so the text can't break out of the\n * element when the document is re-serialized.\n * CSS comments are dropped first so none of the above can hide inside one.\n */\nexport function sanitizeCss(css: string): string {\n return css\n .replace(/<\\/style/gi, '')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/@import[^;]*;?/gi, '')\n .replace(/expression\\s*\\(/gi, 'blocked(')\n .replace(/(behavior|-moz-binding)(\\s*):/gi, 'blocked-prop$2:')\n .replace(/url\\(\\s*(['\"]?)([^)]*?)\\1\\s*\\)/gi, (full, _quote, raw) => {\n const url = String(raw).trim();\n if (/^#/.test(url)) return full;\n if (/^data:image\\/(png|gif|jpe?g|webp|avif|bmp);/i.test(url)) return full;\n return 'url(#)';\n })\n .trim();\n}\n\nfunction sanitizeElement(el: Element): void {\n const tag = el.tagName.toLowerCase();\n const allowed = TAG_ATTRS[tag];\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n const keep =\n (GLOBAL_ATTRS.has(name) || (allowed !== undefined && allowed.has(name))) &&\n !name.startsWith('on');\n if (!keep) {\n el.removeAttribute(attr.name);\n continue;\n }\n if (name === 'style') {\n const filtered = filterStyle(attr.value, tag);\n if (filtered) el.setAttribute('style', filtered);\n else el.removeAttribute('style');\n } else if (name === 'class') {\n const cleaned = cleanClassAttr(attr.value);\n if (cleaned) el.setAttribute('class', cleaned);\n else el.removeAttribute('class');\n } else if (name === 'id') {\n if (RESERVED_NAME.test(attr.value)) el.removeAttribute('id');\n } else if (name === 'href' || name === 'src') {\n // Raster data: URLs are allowed on images only.\n if (!isSafeUrl(attr.value, tag === 'img')) el.removeAttribute(attr.name);\n } else if (name === 'target') {\n if (attr.value === '_blank') el.setAttribute('rel', 'noopener noreferrer');\n else el.removeAttribute('target');\n }\n }\n}\n\nfunction walk(node: Element): void {\n for (const child of Array.from(node.children)) {\n const tag = child.tagName.toLowerCase();\n if (DROP_WITH_CONTENT.has(tag)) {\n child.remove();\n continue;\n }\n // iframes are allowed for any https src, but always force-sandboxed so an\n // untrusted embed can never navigate the top window or run unrestricted.\n if (tag === 'iframe') {\n if (!isAllowedEmbedSrc(child.getAttribute('src') ?? '')) {\n child.remove();\n continue;\n }\n child.setAttribute('sandbox', EMBED_SANDBOX);\n child.setAttribute('referrerpolicy', EMBED_REFERRER_POLICY);\n }\n // <style> blocks are kept, but their CSS is sanitized and any attributes\n // (media, nonce, …) dropped. Handled before the unwrap branch below so the\n // raw CSS is never spilled into the document as visible text.\n if (tag === 'style') {\n child.textContent = sanitizeCss(child.textContent ?? '');\n for (const attr of Array.from(child.attributes)) child.removeAttribute(attr.name);\n continue;\n }\n if (!ALLOWED_TAGS.has(tag)) {\n // Unknown wrapper (article, section, table for now, …): keep its\n // content, drop the element itself.\n walk(child);\n child.replaceWith(...Array.from(child.childNodes));\n continue;\n }\n sanitizeElement(child);\n walk(child);\n }\n}\n\n/**\n * Sanitizes an HTML string. Parsing happens in an inert document created by\n * DOMParser, so embedded scripts never execute and images never load.\n */\nexport function sanitizeHtml(html: string): string {\n // Wrap in an explicit <body> so metadata-like tags (notably <style>) stay in\n // the body instead of being hoisted into <head> by the HTML parser — which\n // would drop them before walk() ever sees them. Mirrors htmlToDoc's parse.\n const doc = new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html');\n walk(doc.body);\n return doc.body.innerHTML;\n}\n"]}
@@ -3,6 +3,8 @@ export type Alignment = 'left' | 'center' | 'right' | 'justify';
3
3
  /** Indent depth in 40px steps, like CKEditor/Google Docs. */
4
4
  export declare const INDENT_STEP_PX = 40;
5
5
  export declare const MAX_INDENT = 8;
6
+ export declare function cleanClass(value: string | null): string | null;
7
+ export declare function cleanId(value: string | null): string | null;
6
8
  /** Bullet markers CSS understands; anything else falls back to default. */
7
9
  export declare const BULLET_STYLES: readonly ["disc", "circle", "square"];
8
10
  /** Numbering styles, keyed by the HTML `type` attribute letter. */
@@ -2,6 +2,7 @@ import { Schema } from 'prosemirror-model';
2
2
  import { addListNodes } from 'prosemirror-schema-list';
3
3
  import { tableNodes } from 'prosemirror-tables';
4
4
  import OrderedMap from 'orderedmap';
5
+ import { EMBED_SANDBOX, EMBED_REFERRER_POLICY } from './plugins/embeds';
5
6
  const ALIGN_VALUES = ['left', 'center', 'right', 'justify'];
6
7
  function getAlignFromDom(dom) {
7
8
  const value = dom.style.textAlign || dom.getAttribute('align') || '';
@@ -47,6 +48,95 @@ function blockStyle(attrs) {
47
48
  parts.push(`line-height: ${attrs.lineHeight}`);
48
49
  return parts.length ? parts.join('; ') : null;
49
50
  }
51
+ /* -------------------------------------------------------------------------
52
+ * Author class / id passthrough
53
+ *
54
+ * Content elements keep the `class` and `id` an author gave them, so class/id
55
+ * CSS (e.g. a scoped author <style> block) can target the document. Values that
56
+ * collide with the editor's own internal names are dropped so content can never
57
+ * impersonate or restyle the chrome; the editor's own classes (emitted in
58
+ * toDOM) are always preserved and merged with the author's.
59
+ * ------------------------------------------------------------------------- */
60
+ const RESERVED_NAME = /^(?:nile-wysiwyg|ProseMirror)/;
61
+ export function cleanClass(value) {
62
+ if (!value)
63
+ return null;
64
+ const kept = value.split(/\s+/).filter(token => token && !RESERVED_NAME.test(token));
65
+ return kept.length ? kept.join(' ') : null;
66
+ }
67
+ export function cleanId(value) {
68
+ return value && !RESERVED_NAME.test(value) ? value : null;
69
+ }
70
+ const HTML_ATTR_SPEC = { htmlClass: { default: null }, htmlId: { default: null } };
71
+ function readHtmlAttrs(dom) {
72
+ return {
73
+ htmlClass: cleanClass(dom.getAttribute('class')),
74
+ htmlId: cleanId(dom.getAttribute('id')),
75
+ };
76
+ }
77
+ /** Wraps a tag-based parse rule so it also captures author class/id. Rules
78
+ * keyed by `style` (whose getAttrs receives a value string, not an element)
79
+ * pass through untouched. */
80
+ function augmentParseRule(rule) {
81
+ if (!('tag' in rule) || 'style' in rule)
82
+ return rule;
83
+ const inner = rule.getAttrs;
84
+ return {
85
+ ...rule,
86
+ getAttrs(dom) {
87
+ const base = inner ? inner(dom) : null;
88
+ if (base === false)
89
+ return false;
90
+ return { ...(base ?? {}), ...readHtmlAttrs(dom) };
91
+ },
92
+ };
93
+ }
94
+ /** Wraps a toDOM so stored class/id are written back onto the element, merged
95
+ * with any class the spec emits itself. Handles the `[tag]`, `[tag, attrs]`,
96
+ * `[tag, 0]`, and `[tag, attrs, …children]` output shapes. */
97
+ function augmentToDOM(toDOM) {
98
+ if (!toDOM)
99
+ return toDOM;
100
+ return (node, ...rest) => {
101
+ const out = toDOM(node, ...rest);
102
+ const cls = node.attrs.htmlClass;
103
+ const id = node.attrs.htmlId;
104
+ if (!Array.isArray(out) || (!cls && !id))
105
+ return out;
106
+ const [tag, ...tail] = out;
107
+ let attrs = {};
108
+ let children = tail;
109
+ if (tail.length && tail[0] && typeof tail[0] === 'object' && !Array.isArray(tail[0])) {
110
+ attrs = { ...tail[0] };
111
+ children = tail.slice(1);
112
+ }
113
+ if (cls)
114
+ attrs.class = attrs.class ? `${attrs.class} ${cls}` : cls;
115
+ if (id)
116
+ attrs.id = id;
117
+ return [tag, attrs, ...children];
118
+ };
119
+ }
120
+ /** Adds `htmlClass`/`htmlId` attrs plus parse/serialize support to a spec. */
121
+ function withHtmlAttrs(spec) {
122
+ return {
123
+ ...spec,
124
+ attrs: { ...(spec.attrs ?? {}), ...HTML_ATTR_SPEC },
125
+ parseDOM: spec.parseDOM?.map(augmentParseRule),
126
+ toDOM: augmentToDOM(spec.toDOM),
127
+ };
128
+ }
129
+ /** Content nodes/marks that carry author class/id. Excludes structural leaves
130
+ * (doc, text), atoms with bespoke DOM (image, embeds, mentions, math, …) and
131
+ * style-only marks (their spans are covered by `html_span`). */
132
+ const HTML_ATTR_NODES = [
133
+ 'paragraph', 'heading', 'blockquote', 'code_block',
134
+ 'bullet_list', 'ordered_list', 'list_item',
135
+ 'table', 'table_row', 'table_cell', 'table_header',
136
+ ];
137
+ const HTML_ATTR_MARKS = [
138
+ 'link', 'bold', 'italic', 'underline', 'strike', 'code', 'subscript', 'superscript',
139
+ ];
50
140
  /**
51
141
  * Block nodes. `align` is modeled as an attribute on textblocks rather than a
52
142
  * wrapper node so alignment survives block-type changes (heading <-> paragraph).
@@ -360,6 +450,8 @@ const nodes = {
360
450
  frameborder: '0',
361
451
  allowfullscreen: 'true',
362
452
  allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
453
+ sandbox: EMBED_SANDBOX,
454
+ referrerpolicy: EMBED_REFERRER_POLICY,
363
455
  };
364
456
  if (title)
365
457
  iframeAttrs.title = title;
@@ -384,6 +476,28 @@ const nodes = {
384
476
  return ['div', wrapperAttrs, ['iframe', iframeAttrs]];
385
477
  },
386
478
  },
479
+ /**
480
+ * Author-supplied `<style>` block, entered via the HTML source view. The CSS
481
+ * is sanitized on the way in (see sanitizeCss) — `@import`, `expression()`
482
+ * and external `url()` are neutralized — then stored verbatim and serialized
483
+ * straight back to a `<style>` element. A node view renders it live in the
484
+ * editor with a small chip so the otherwise-invisible block stays selectable.
485
+ */
486
+ styleBlock: {
487
+ group: 'block',
488
+ atom: true,
489
+ selectable: true,
490
+ attrs: { css: { default: '' } },
491
+ parseDOM: [
492
+ {
493
+ tag: 'style',
494
+ getAttrs: (dom) => ({ css: dom.textContent ?? '' }),
495
+ },
496
+ ],
497
+ toDOM(node) {
498
+ return ['style', node.attrs.css];
499
+ },
500
+ },
387
501
  /**
388
502
  * Bookmark: a named in-text anchor (`<a id="…">`) for intra-document
389
503
  * navigation. Invisible in serialized output; the editor draws a flag
@@ -685,6 +799,31 @@ const marks = {
685
799
  return ['code', { class: 'nile-wysiwyg__inline-code' }, 0];
686
800
  },
687
801
  },
802
+ /** Carries an author `class`/`id` on an inline `<span>` that has no other
803
+ * meaning, so class/id CSS can target runs of text. Low priority so styled
804
+ * spans (color/font marks) and atom spans (mention/merge/math) win. */
805
+ html_span: {
806
+ attrs: { htmlClass: { default: null }, htmlId: { default: null } },
807
+ parseDOM: [
808
+ {
809
+ tag: 'span',
810
+ priority: 5,
811
+ getAttrs: (dom) => {
812
+ const htmlClass = cleanClass(dom.getAttribute('class'));
813
+ const htmlId = cleanId(dom.getAttribute('id'));
814
+ return htmlClass || htmlId ? { htmlClass, htmlId } : false;
815
+ },
816
+ },
817
+ ],
818
+ toDOM(mark) {
819
+ const attrs = {};
820
+ if (mark.attrs.htmlClass)
821
+ attrs.class = mark.attrs.htmlClass;
822
+ if (mark.attrs.htmlId)
823
+ attrs.id = mark.attrs.htmlId;
824
+ return ['span', attrs, 0];
825
+ },
826
+ },
688
827
  text_color: {
689
828
  attrs: { color: {} },
690
829
  parseDOM: [
@@ -811,12 +950,25 @@ const orderedListSpec = {
811
950
  };
812
951
  /** Builds the editor schema. List nodes are added via prosemirror-schema-list. */
813
952
  export function createSchema() {
814
- const nodeMap = addListNodes(OrderedMap.from(nodes), 'paragraph block*', 'block')
953
+ let nodeMap = addListNodes(OrderedMap.from(nodes), 'paragraph block*', 'block')
815
954
  .update('bullet_list', bulletListSpec)
816
955
  .update('ordered_list', orderedListSpec)
817
956
  .append(tableNodeSpecs)
818
957
  .update('table', tableSpec);
819
- return new Schema({ nodes: nodeMap, marks: OrderedMap.from(marks) });
958
+ // Thread author class/id through the content nodes and marks (applied last so
959
+ // it also wraps the list/table specs pulled in from external packages).
960
+ for (const name of HTML_ATTR_NODES) {
961
+ const spec = nodeMap.get(name);
962
+ if (spec)
963
+ nodeMap = nodeMap.update(name, withHtmlAttrs(spec));
964
+ }
965
+ let markMap = OrderedMap.from(marks);
966
+ for (const name of HTML_ATTR_MARKS) {
967
+ const spec = markMap.get(name);
968
+ if (spec)
969
+ markMap = markMap.update(name, withHtmlAttrs(spec));
970
+ }
971
+ return new Schema({ nodes: nodeMap, marks: markMap });
820
972
  }
821
973
  /** A shared default schema instance. */
822
974
  export const defaultSchema = createSchema();