@khanacademy/perseus-editor 1.1.1 → 2.0.0

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 (312) hide show
  1. package/.babelrc.js +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/CHANGELOG.md +14 -0
  4. package/dist/all-editors.d.ts +2 -0
  5. package/dist/all-editors.js.flow +8 -0
  6. package/dist/all-widgets.d.ts +22 -0
  7. package/dist/all-widgets.js.flow +34 -0
  8. package/dist/article-editor.d.ts +59 -0
  9. package/dist/article-editor.js.flow +72 -0
  10. package/dist/components/blur-input.d.ts +19 -0
  11. package/dist/components/blur-input.js.flow +25 -0
  12. package/dist/components/checkbox.d.ts +20 -0
  13. package/dist/components/checkbox.js.flow +26 -0
  14. package/dist/components/device-framer.d.ts +15 -0
  15. package/dist/components/device-framer.js.flow +20 -0
  16. package/dist/components/drag-target.d.ts +2 -0
  17. package/dist/components/drag-target.js.flow +8 -0
  18. package/dist/components/dropdown-option.d.ts +35 -0
  19. package/dist/components/dropdown-option.js.flow +49 -0
  20. package/dist/components/form-wrapped-text-field.d.ts +48 -0
  21. package/dist/components/form-wrapped-text-field.js.flow +48 -0
  22. package/dist/components/graph-settings.d.ts +2 -0
  23. package/dist/components/graph-settings.js.flow +8 -0
  24. package/dist/components/hover-behavior.d.ts +85 -0
  25. package/dist/components/hover-behavior.js.flow +48 -0
  26. package/dist/components/json-editor.d.ts +2 -0
  27. package/dist/components/json-editor.js.flow +8 -0
  28. package/dist/components/link.d.ts +41 -0
  29. package/dist/components/link.js.flow +50 -0
  30. package/dist/components/simple-button.d.ts +16 -0
  31. package/dist/components/simple-button.js.flow +18 -0
  32. package/dist/components/sortable.d.ts +2 -0
  33. package/dist/components/sortable.js.flow +8 -0
  34. package/dist/components/util.d.ts +1 -0
  35. package/dist/components/util.js.flow +9 -0
  36. package/dist/components/viewport-resizer.d.ts +16 -0
  37. package/dist/components/viewport-resizer.js.flow +22 -0
  38. package/dist/diffs/__stories__/perseus-diff-wrapper.d.ts +5 -0
  39. package/dist/diffs/__stories__/perseus-diff-wrapper.js.flow +11 -0
  40. package/dist/diffs/article-diff.d.ts +50 -0
  41. package/dist/diffs/article-diff.js.flow +71 -0
  42. package/dist/diffs/item-diff.d.ts +23 -0
  43. package/dist/diffs/item-diff.js.flow +41 -0
  44. package/dist/diffs/renderer-diff.d.ts +22 -0
  45. package/dist/diffs/renderer-diff.js.flow +25 -0
  46. package/dist/diffs/split-diff.d.ts +2 -0
  47. package/dist/diffs/split-diff.js.flow +8 -0
  48. package/dist/diffs/string-array-diff.d.ts +2 -0
  49. package/dist/diffs/string-array-diff.js.flow +11 -0
  50. package/dist/diffs/structured-item-diff.d.ts +25 -0
  51. package/dist/diffs/structured-item-diff.js.flow +37 -0
  52. package/dist/diffs/tags-diff.d.ts +21 -0
  53. package/dist/diffs/tags-diff.js.flow +24 -0
  54. package/dist/diffs/text-diff.d.ts +15 -0
  55. package/dist/diffs/text-diff.js.flow +21 -0
  56. package/dist/diffs/widget-diff-performer.d.ts +2 -0
  57. package/dist/diffs/widget-diff-performer.js.flow +8 -0
  58. package/dist/diffs/widget-diff.d.ts +21 -0
  59. package/dist/diffs/widget-diff.js.flow +31 -0
  60. package/dist/editor-page.d.ts +57 -0
  61. package/dist/editor-page.js.flow +73 -0
  62. package/dist/editor.d.ts +82 -0
  63. package/dist/editor.js.flow +98 -0
  64. package/dist/es/index.js +1428 -2587
  65. package/dist/es/index.js.map +1 -1
  66. package/dist/hint-editor.d.ts +64 -0
  67. package/dist/hint-editor.js.flow +87 -0
  68. package/dist/i18n.d.ts +7 -0
  69. package/dist/i18n.js.flow +13 -0
  70. package/dist/iframe-content-renderer.d.ts +22 -0
  71. package/dist/iframe-content-renderer.js.flow +28 -0
  72. package/dist/index.d.ts +17 -2
  73. package/dist/index.js +1492 -2548
  74. package/dist/index.js.flow +23 -2
  75. package/dist/index.js.map +1 -1
  76. package/dist/item-editor.d.ts +36 -0
  77. package/dist/item-editor.js.flow +46 -0
  78. package/dist/item-extras-editor.d.ts +17 -0
  79. package/dist/item-extras-editor.js.flow +23 -0
  80. package/dist/katex-error-view.d.ts +16 -0
  81. package/dist/katex-error-view.js.flow +24 -0
  82. package/dist/multirenderer-editor.d.ts +32 -0
  83. package/dist/multirenderer-editor.js.flow +46 -0
  84. package/dist/section-control-button.d.ts +12 -0
  85. package/dist/section-control-button.js.flow +20 -0
  86. package/dist/stateful-article-editor.d.ts +10 -0
  87. package/dist/stateful-article-editor.js.flow +16 -0
  88. package/dist/stateful-editor-page.d.ts +2 -0
  89. package/dist/stateful-editor-page.js.flow +8 -0
  90. package/dist/testing-widgets.d.ts +22 -0
  91. package/dist/testing-widgets.js.flow +34 -0
  92. package/dist/util/register-all-widgets-and-editors-for-testing.d.ts +1 -0
  93. package/dist/util/register-all-widgets-and-editors-for-testing.js.flow +7 -0
  94. package/dist/widgets/categorizer-editor.d.ts +47 -0
  95. package/dist/widgets/categorizer-editor.js.flow +61 -0
  96. package/dist/widgets/cs-program-editor.d.ts +21 -0
  97. package/dist/widgets/cs-program-editor.js.flow +25 -0
  98. package/dist/widgets/definition-editor.d.ts +17 -0
  99. package/dist/widgets/definition-editor.js.flow +23 -0
  100. package/dist/widgets/dropdown-editor.d.ts +23 -0
  101. package/dist/widgets/dropdown-editor.js.flow +37 -0
  102. package/dist/widgets/example-graphie-widget-editor.d.ts +18 -0
  103. package/dist/widgets/example-graphie-widget-editor.js.flow +25 -0
  104. package/dist/widgets/example-graphie-widget.d.ts +87 -0
  105. package/dist/widgets/example-graphie-widget.js.flow +101 -0
  106. package/dist/widgets/example-widget-editor.d.ts +19 -0
  107. package/dist/widgets/example-widget-editor.js.flow +26 -0
  108. package/dist/widgets/example-widget.d.ts +71 -0
  109. package/dist/widgets/example-widget.js.flow +86 -0
  110. package/dist/widgets/explanation-editor.d.ts +21 -0
  111. package/dist/widgets/explanation-editor.js.flow +27 -0
  112. package/dist/widgets/expression-editor.d.ts +33 -0
  113. package/dist/widgets/expression-editor.js.flow +41 -0
  114. package/dist/widgets/graded-group-editor.d.ts +53 -0
  115. package/dist/widgets/graded-group-editor.js.flow +67 -0
  116. package/dist/widgets/graded-group-set-editor.d.ts +51 -0
  117. package/dist/widgets/graded-group-set-editor.js.flow +65 -0
  118. package/dist/widgets/grapher-editor.d.ts +14 -0
  119. package/dist/widgets/grapher-editor.js.flow +21 -0
  120. package/dist/widgets/group-editor.d.ts +49 -0
  121. package/dist/widgets/group-editor.js.flow +63 -0
  122. package/dist/widgets/iframe-editor.d.ts +18 -0
  123. package/dist/widgets/iframe-editor.js.flow +24 -0
  124. package/dist/widgets/image-editor.d.ts +2 -0
  125. package/dist/widgets/image-editor.js.flow +8 -0
  126. package/dist/widgets/input-number-editor.d.ts +47 -0
  127. package/dist/widgets/input-number-editor.js.flow +77 -0
  128. package/dist/widgets/interaction/constraint-editor.d.ts +26 -0
  129. package/dist/widgets/interaction/constraint-editor.js.flow +35 -0
  130. package/dist/widgets/interaction-editor.d.ts +28 -0
  131. package/dist/widgets/interaction-editor.js.flow +36 -0
  132. package/dist/widgets/interactive-graph-editor.d.ts +2 -0
  133. package/dist/widgets/interactive-graph-editor.js.flow +8 -0
  134. package/dist/widgets/label-image/answer-choices.d.ts +13 -0
  135. package/dist/widgets/label-image/answer-choices.js.flow +16 -0
  136. package/dist/widgets/label-image/behavior.d.ts +14 -0
  137. package/dist/widgets/label-image/behavior.js.flow +17 -0
  138. package/dist/widgets/label-image/marker.d.ts +30 -0
  139. package/dist/widgets/label-image/marker.js.flow +34 -0
  140. package/dist/widgets/label-image/question-markers.d.ts +22 -0
  141. package/dist/widgets/label-image/question-markers.js.flow +25 -0
  142. package/dist/widgets/label-image/select-image.d.ts +10 -0
  143. package/dist/widgets/label-image/select-image.js.flow +13 -0
  144. package/dist/widgets/label-image-editor.d.ts +45 -0
  145. package/dist/widgets/label-image-editor.js.flow +44 -0
  146. package/dist/widgets/lights-puzzle-editor.d.ts +24 -0
  147. package/dist/widgets/lights-puzzle-editor.js.flow +34 -0
  148. package/dist/widgets/matcher-editor.d.ts +19 -0
  149. package/dist/widgets/matcher-editor.js.flow +28 -0
  150. package/dist/widgets/matrix-editor.d.ts +20 -0
  151. package/dist/widgets/matrix-editor.js.flow +26 -0
  152. package/dist/widgets/measurer-editor.d.ts +32 -0
  153. package/dist/widgets/measurer-editor.js.flow +42 -0
  154. package/dist/widgets/molecule-editor.d.ts +17 -0
  155. package/dist/widgets/molecule-editor.js.flow +23 -0
  156. package/dist/widgets/number-line-editor.d.ts +36 -0
  157. package/dist/widgets/number-line-editor.js.flow +45 -0
  158. package/dist/widgets/numeric-input-editor.d.ts +2 -0
  159. package/dist/widgets/numeric-input-editor.js.flow +8 -0
  160. package/dist/widgets/orderer-editor.d.ts +20 -0
  161. package/dist/widgets/orderer-editor.js.flow +30 -0
  162. package/dist/widgets/passage-editor.d.ts +18 -0
  163. package/dist/widgets/passage-editor.js.flow +24 -0
  164. package/dist/widgets/passage-ref-editor.d.ts +11 -0
  165. package/dist/widgets/passage-ref-editor.js.flow +17 -0
  166. package/dist/widgets/passage-ref-target-editor.d.ts +16 -0
  167. package/dist/widgets/passage-ref-target-editor.js.flow +22 -0
  168. package/dist/widgets/plotter-editor.d.ts +45 -0
  169. package/dist/widgets/plotter-editor.js.flow +53 -0
  170. package/dist/widgets/radio/editor.d.ts +64 -0
  171. package/dist/widgets/radio/editor.js.flow +80 -0
  172. package/dist/widgets/reaction-diagram-editor.d.ts +20 -0
  173. package/dist/widgets/reaction-diagram-editor.js.flow +26 -0
  174. package/dist/widgets/sequence-editor.d.ts +56 -0
  175. package/dist/widgets/sequence-editor.js.flow +72 -0
  176. package/dist/widgets/simple-markdown-tester-editor.d.ts +14 -0
  177. package/dist/widgets/simple-markdown-tester-editor.js.flow +23 -0
  178. package/dist/widgets/simple-markdown-tester.d.ts +52 -0
  179. package/dist/widgets/simple-markdown-tester.js.flow +61 -0
  180. package/dist/widgets/simulator-editor.d.ts +20 -0
  181. package/dist/widgets/simulator-editor.js.flow +26 -0
  182. package/dist/widgets/sorter-editor.d.ts +16 -0
  183. package/dist/widgets/sorter-editor.js.flow +22 -0
  184. package/dist/widgets/table-editor.d.ts +18 -0
  185. package/dist/widgets/table-editor.js.flow +24 -0
  186. package/dist/widgets/transformer-editor.d.ts +11 -0
  187. package/dist/widgets/transformer-editor.js.flow +17 -0
  188. package/dist/widgets/unit-editor.d.ts +33 -0
  189. package/dist/widgets/unit-editor.js.flow +41 -0
  190. package/dist/widgets/video-editor.d.ts +19 -0
  191. package/dist/widgets/video-editor.js.flow +25 -0
  192. package/package.json +6 -5
  193. package/src/__stories__/{editor.stories.js → editor.stories.tsx} +4 -5
  194. package/src/__stories__/{section-control-button.stories.jsx → section-control-button.stories.tsx} +10 -11
  195. package/src/__testdata__/{input-number_testdata.js → input-number.testdata.ts} +2 -3
  196. package/src/__tests__/{editor_test.jsx → editor.test.tsx} +4 -7
  197. package/src/__tests__/{i18n_test.jsx → i18n.test.ts} +5 -5
  198. package/src/__tests__/{traversal_test.jsx → traversal.test.ts} +9 -10
  199. package/src/{all-editors_test.js → all-editors.test.ts} +1 -2
  200. package/src/all-editors.ts +83 -0
  201. package/src/all-widgets.ts +7 -0
  202. package/src/{article-editor.jsx → article-editor.tsx} +52 -42
  203. package/src/components/__stories__/{blur-input.stories.jsx → blur-input.stories.tsx} +2 -4
  204. package/src/components/{blur-input.jsx → blur-input.tsx} +12 -13
  205. package/src/components/{checkbox.jsx → checkbox.tsx} +18 -19
  206. package/src/components/{device-framer.jsx → device-framer.tsx} +4 -5
  207. package/src/components/{drag-target.jsx → drag-target.tsx} +0 -1
  208. package/src/components/{dropdown-option.jsx → dropdown-option.tsx} +40 -40
  209. package/src/components/{form-wrapped-text-field.jsx → form-wrapped-text-field.tsx} +47 -53
  210. package/src/components/{graph-settings.jsx → graph-settings.tsx} +4 -8
  211. package/src/components/{hover-behavior.jsx → hover-behavior.ts} +32 -33
  212. package/src/components/{json-editor.jsx → json-editor.tsx} +4 -3
  213. package/src/components/{link.jsx → link.ts} +33 -33
  214. package/src/components/{simple-button.jsx → simple-button.tsx} +3 -4
  215. package/src/components/{sortable.jsx → sortable.tsx} +3 -3
  216. package/src/components/{util.js → util.ts} +1 -2
  217. package/src/components/{viewport-resizer.jsx → viewport-resizer.tsx} +5 -6
  218. package/src/diffs/__stories__/{perseus-diff-wrapper.jsx → perseus-diff-wrapper.tsx} +3 -2
  219. package/src/diffs/__stories__/{structured-item-diff.stories.jsx → structured-item-diff.stories.tsx} +27 -22
  220. package/src/diffs/__stories__/{tags-diff.stories.jsx → tags-diff.stories.tsx} +12 -14
  221. package/src/diffs/__stories__/{text-diff.stories.jsx → text-diff.stories.tsx} +12 -14
  222. package/src/diffs/__tests__/{split-diff_test.js → split-diff.test.ts} +1 -2
  223. package/src/diffs/__tests__/{string-array-diff_test.js → string-array-diff.test.ts} +1 -2
  224. package/src/diffs/__tests__/{structured-item-diff_test.js → structured-item-diff.test.ts} +15 -14
  225. package/src/diffs/__tests__/{widget-diff-performer_test.js → widget-diff-performer.test.ts} +1 -2
  226. package/src/diffs/{article-diff.jsx → article-diff.tsx} +5 -6
  227. package/src/diffs/{item-diff.jsx → item-diff.tsx} +4 -5
  228. package/src/diffs/{renderer-diff.jsx → renderer-diff.tsx} +22 -28
  229. package/src/diffs/{split-diff.jsx → split-diff.ts} +4 -3
  230. package/src/diffs/{string-array-diff.jsx → string-array-diff.ts} +8 -9
  231. package/src/diffs/{structured-item-diff.jsx → structured-item-diff.tsx} +32 -27
  232. package/src/diffs/{tags-diff.jsx → tags-diff.tsx} +15 -16
  233. package/src/diffs/{text-diff.jsx → text-diff.tsx} +13 -13
  234. package/src/diffs/{widget-diff-performer.jsx → widget-diff-performer.ts} +3 -8
  235. package/src/diffs/{widget-diff.jsx → widget-diff.tsx} +15 -14
  236. package/src/{editor-page.jsx → editor-page.tsx} +52 -60
  237. package/src/{editor.jsx → editor.tsx} +315 -277
  238. package/src/{hint-editor.jsx → hint-editor.tsx} +105 -79
  239. package/src/{i18n.jsx → i18n.ts} +9 -6
  240. package/src/{iframe-content-renderer.jsx → iframe-content-renderer.tsx} +23 -17
  241. package/src/{index.js → index.ts} +15 -16
  242. package/src/{item-editor.jsx → item-editor.tsx} +35 -30
  243. package/src/{item-extras-editor.jsx → item-extras-editor.tsx} +3 -4
  244. package/src/{katex-error-view.jsx → katex-error-view.tsx} +4 -5
  245. package/src/{multirenderer-editor.jsx → multirenderer-editor.tsx} +132 -127
  246. package/src/{section-control-button.jsx → section-control-button.tsx} +10 -9
  247. package/src/{stateful-article-editor.jsx → stateful-article-editor.tsx} +2 -5
  248. package/src/{stateful-editor-page.jsx → stateful-editor-page.tsx} +1 -3
  249. package/src/{testing-widgets.js → testing-widgets.ts} +5 -6
  250. package/src/util/{register-all-widgets-and-editors-for-testing_test.js → register-all-widgets-and-editors-for-testing.test.ts} +1 -2
  251. package/src/util/{register-all-widgets-and-editors-for-testing.js → register-all-widgets-and-editors-for-testing.ts} +2 -3
  252. package/src/widgets/__stories__/{label-image-editor.stories.jsx → label-image-editor.stories.tsx} +22 -18
  253. package/src/widgets/__stories__/{radio-editor.stories.jsx → radio-editor.stories.tsx} +15 -10
  254. package/src/widgets/{categorizer-editor.jsx → categorizer-editor.tsx} +8 -6
  255. package/src/widgets/{cs-program-editor.jsx → cs-program-editor.tsx} +17 -17
  256. package/src/widgets/{definition-editor.jsx → definition-editor.tsx} +9 -8
  257. package/src/widgets/{dropdown-editor.jsx → dropdown-editor.tsx} +27 -19
  258. package/src/widgets/{example-graphie-widget-editor.jsx → example-graphie-widget-editor.tsx} +8 -8
  259. package/src/widgets/{example-graphie-widget.jsx → example-graphie-widget.tsx} +14 -17
  260. package/src/widgets/{example-widget-editor.jsx → example-widget-editor.tsx} +10 -7
  261. package/src/widgets/{example-widget.jsx → example-widget.tsx} +14 -12
  262. package/src/widgets/{explanation-editor.jsx → explanation-editor.tsx} +12 -10
  263. package/src/widgets/{expression-editor.jsx → expression-editor.tsx} +36 -26
  264. package/src/widgets/{graded-group-editor.jsx → graded-group-editor.tsx} +18 -10
  265. package/src/widgets/{graded-group-set-editor.jsx → graded-group-set-editor.tsx} +15 -12
  266. package/src/widgets/{grapher-editor.jsx → grapher-editor.tsx} +10 -9
  267. package/src/widgets/{group-editor.jsx → group-editor.tsx} +11 -9
  268. package/src/widgets/{iframe-editor.jsx → iframe-editor.tsx} +16 -13
  269. package/src/widgets/{image-editor.jsx → image-editor.tsx} +4 -7
  270. package/src/widgets/{input-number-editor.jsx → input-number-editor.tsx} +61 -47
  271. package/src/widgets/interaction/{constraint-editor.jsx → constraint-editor.tsx} +21 -24
  272. package/src/widgets/{interaction-editor.jsx → interaction-editor.tsx} +160 -61
  273. package/src/widgets/{interactive-graph-editor.jsx → interactive-graph-editor.tsx} +13 -10
  274. package/src/widgets/label-image/__stories__/{answer-choices.stories.jsx → answer-choices.stories.tsx} +22 -17
  275. package/src/widgets/label-image/__stories__/{behavior.stories.jsx → behavior.stories.tsx} +9 -10
  276. package/src/widgets/label-image/__stories__/{marker.stories.jsx → marker.stories.tsx} +11 -12
  277. package/src/widgets/label-image/__stories__/{question-markers.stories.jsx → question-markers.stories.tsx} +17 -16
  278. package/src/widgets/label-image/__stories__/{select-image.stories.jsx → select-image.stories.tsx} +12 -13
  279. package/src/widgets/label-image/{answer-choices.jsx → answer-choices.tsx} +18 -18
  280. package/src/widgets/label-image/{behavior.jsx → behavior.tsx} +9 -11
  281. package/src/widgets/label-image/{marker.jsx → marker.tsx} +17 -19
  282. package/src/widgets/label-image/{question-markers.jsx → question-markers.tsx} +16 -21
  283. package/src/widgets/label-image/{select-image.jsx → select-image.tsx} +5 -7
  284. package/src/widgets/{label-image-editor.jsx → label-image-editor.tsx} +38 -44
  285. package/src/widgets/{lights-puzzle-editor.jsx → lights-puzzle-editor.tsx} +34 -24
  286. package/src/widgets/{matcher-editor.jsx → matcher-editor.tsx} +9 -7
  287. package/src/widgets/{matrix-editor.jsx → matrix-editor.tsx} +10 -9
  288. package/src/widgets/{measurer-editor.jsx → measurer-editor.tsx} +21 -14
  289. package/src/widgets/{molecule-editor.jsx → molecule-editor.tsx} +9 -8
  290. package/src/widgets/{number-line-editor.jsx → number-line-editor.tsx} +19 -17
  291. package/src/widgets/{numeric-input-editor.jsx → numeric-input-editor.tsx} +10 -10
  292. package/src/widgets/{orderer-editor.jsx → orderer-editor.tsx} +17 -12
  293. package/src/widgets/{passage-editor.jsx → passage-editor.tsx} +9 -7
  294. package/src/widgets/{passage-ref-editor.jsx → passage-ref-editor.tsx} +8 -6
  295. package/src/widgets/{passage-ref-target-editor.jsx → passage-ref-target-editor.tsx} +9 -7
  296. package/src/widgets/{plotter-editor.jsx → plotter-editor.tsx} +34 -33
  297. package/src/widgets/radio/{editor.jsx → editor.tsx} +22 -21
  298. package/src/widgets/{reaction-diagram-editor.jsx → reaction-diagram-editor.tsx} +14 -10
  299. package/src/widgets/{sequence-editor.jsx → sequence-editor.tsx} +13 -13
  300. package/src/widgets/{simple-markdown-tester-editor.jsx → simple-markdown-tester-editor.tsx} +11 -9
  301. package/src/widgets/{simple-markdown-tester.jsx → simple-markdown-tester.tsx} +8 -7
  302. package/src/widgets/{simulator-editor.jsx → simulator-editor.tsx} +10 -8
  303. package/src/widgets/{sorter-editor.jsx → sorter-editor.tsx} +8 -6
  304. package/src/widgets/{table-editor.jsx → table-editor.tsx} +11 -10
  305. package/src/widgets/{transformer-editor.jsx → transformer-editor.tsx} +37 -35
  306. package/src/widgets/{unit-editor.jsx → unit-editor.tsx} +31 -25
  307. package/src/widgets/{video-editor.jsx → video-editor.tsx} +8 -9
  308. package/tsconfig.json +22 -0
  309. package/tsconfig.tsbuildinfo +1 -0
  310. package/src/all-editors.js +0 -85
  311. package/src/all-widgets.js +0 -10
  312. /package/dist/{strings.js → strings} +0 -0
@@ -1,4 +1,3 @@
1
- // @flow
2
1
  import {
3
2
  components,
4
3
  icons,
@@ -13,15 +12,15 @@ import {
13
12
  WIDGET_PROP_DENYLIST,
14
13
  } from "@khanacademy/perseus";
15
14
  import $ from "jquery";
16
- // eslint-disable-next-line import/no-unresolved
17
- import typeof katex from "katex";
15
+ // eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies
16
+ import katex from "katex";
18
17
  import * as React from "react";
19
18
  import * as ReactDOM from "react-dom";
20
19
  import _ from "underscore";
21
20
 
22
- import DragTarget from "./components/drag-target.jsx";
23
- import KatexErrorView from "./katex-error-view.jsx";
24
- import SectionControlButton from "./section-control-button.jsx";
21
+ import DragTarget from "./components/drag-target";
22
+ import KatexErrorView from "./katex-error-view";
23
+ import SectionControlButton from "./section-control-button";
25
24
 
26
25
  import type {Alignment, ChangeHandler, WidgetInfo} from "@khanacademy/perseus";
27
26
 
@@ -42,14 +41,14 @@ const ENDS_WITH_A_PARAGRAPH = /(?:\n{2,}|^\n*)$/;
42
41
  const TRAILING_NEWLINES = /(\n*)$/;
43
42
  const LEADING_NEWLINES = /^(\n*)/;
44
43
 
45
- const commafyInteger = (n) => {
44
+ const commafyInteger = (n: number) => {
46
45
  let str = n.toString();
47
46
  if (str.length >= 5) {
48
47
  str = str.replace(/(\d)(?=(\d{3})+$)/g, "$1{,}");
49
48
  }
50
49
  return str;
51
50
  };
52
- const makeEndWithAParagraphIfNecessary = (content) => {
51
+ const makeEndWithAParagraphIfNecessary = (content: string) => {
53
52
  if (!ENDS_WITH_A_PARAGRAPH.test(content)) {
54
53
  const match = TRAILING_NEWLINES.exec(content);
55
54
  if (match) {
@@ -68,16 +67,16 @@ const makeStartWithAParagraphAlways = (content) => {
68
67
  return "\n\n".slice(0, 2 - newlines.length) + content;
69
68
  };
70
69
 
71
- type WidgetSelectProps = {|
72
- onChange?: (widgetType: string) => mixed,
73
- |};
70
+ type WidgetSelectProps = {
71
+ onChange?: (widgetType: string) => unknown;
72
+ };
74
73
 
75
74
  class WidgetSelect extends React.Component<WidgetSelectProps> {
76
75
  shouldComponentUpdate() {
77
76
  return false;
78
77
  }
79
78
 
80
- handleChange = (e: SyntheticEvent<HTMLSelectElement>) => {
79
+ handleChange = (e: React.SyntheticEvent<HTMLSelectElement>) => {
81
80
  const widgetType = e.currentTarget.value;
82
81
  if (widgetType === "") {
83
82
  // TODO(alpert): Not sure if change will trigger here
@@ -89,7 +88,7 @@ class WidgetSelect extends React.Component<WidgetSelectProps> {
89
88
  }
90
89
  };
91
90
 
92
- render(): React.Node {
91
+ render(): React.ReactNode {
93
92
  const widgets = Widgets.getPublicWidgets();
94
93
  const orderedWidgetNames = _.sortBy(_.keys(widgets), (name) => {
95
94
  return widgets[name].displayName;
@@ -111,29 +110,30 @@ class WidgetSelect extends React.Component<WidgetSelectProps> {
111
110
  }
112
111
  }
113
112
 
114
- type WidgetEditorProps = {|
113
+ type WidgetEditorProps = {
115
114
  // Unserialized props
116
- id: string,
115
+ id: string;
117
116
  onChange: (
118
117
  widgetInfo: WidgetInfo,
119
- cb?: () => mixed,
118
+ cb?: () => unknown,
120
119
  silent?: boolean,
121
- ) => mixed,
122
- onRemove: () => mixed,
123
- apiOptions: any,
124
-
125
- ...WidgetInfo,
126
- |};
127
-
128
- type WidgetEditorState = {|
129
- showWidget: boolean,
130
- widgetInfo: WidgetInfo,
131
- |};
120
+ ) => unknown;
121
+ onRemove: () => unknown;
122
+ apiOptions: any;
123
+ } & WidgetInfo;
124
+
125
+ type WidgetEditorState = {
126
+ showWidget: boolean;
127
+ widgetInfo: WidgetInfo;
128
+ };
132
129
 
133
- const _upgradeWidgetInfo = (props: WidgetEditorProps): WidgetInfo => {
130
+ const _upgradeWidgetInfo: React.FC<WidgetEditorProps> = (
131
+ props,
132
+ ): React.ReactElement => {
134
133
  // We can't call serialize here because this.refs.widget
135
134
  // doesn't exist before this component is mounted.
136
135
  const filteredProps = _.omit(props, WIDGET_PROP_DENYLIST);
136
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'PerseusWidget' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>>'. | TS2345 - Argument of type 'Partial<{ id: string; onChange: (widgetInfo: PerseusWidget, cb?: (() => unknown) | undefined, silent?: boolean | undefined) => unknown; onRemove: () => unknown; apiOptions: any; } & CategorizerWidget & { ...; }> | ... 38 more ... | Partial<...>' is not assignable to parameter of type 'PerseusWidget'.
137
137
  return Widgets.upgradeWidgetInfoToLatestVersion(filteredProps);
138
138
  };
139
139
 
@@ -144,36 +144,39 @@ const _upgradeWidgetInfo = (props: WidgetEditorProps): WidgetInfo => {
144
144
  // eslint-disable-next-line react/no-unsafe
145
145
  class WidgetEditor extends React.Component<
146
146
  WidgetEditorProps,
147
- WidgetEditorState,
147
+ WidgetEditorState
148
148
  > {
149
149
  constructor(props: WidgetEditorProps) {
150
150
  super(props);
151
151
  this.state = {
152
152
  showWidget: false,
153
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'ReactElement<any, any> | null' is not assignable to type 'PerseusWidget'.
153
154
  widgetInfo: _upgradeWidgetInfo(props),
154
155
  };
155
156
  }
156
157
 
157
158
  UNSAFE_componentWillReceiveProps(nextProps: WidgetEditorProps) {
159
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'ReactElement<any, any> | null' is not assignable to type 'PerseusWidget'.
158
160
  this.setState({widgetInfo: _upgradeWidgetInfo(nextProps)});
159
161
  }
160
162
 
161
- _toggleWidget = (e: SyntheticEvent<>) => {
163
+ _toggleWidget = (e: React.SyntheticEvent) => {
162
164
  e.preventDefault();
163
165
  this.setState({showWidget: !this.state.showWidget});
164
166
  };
165
167
 
166
168
  _handleWidgetChange = (
167
169
  newProps: WidgetEditorProps,
168
- cb: () => mixed,
170
+ cb: () => unknown,
169
171
  silent: boolean,
170
172
  ) => {
171
- const newWidgetInfo = ((Object.assign(
173
+ const newWidgetInfo = Object.assign(
172
174
  {},
173
175
  this.state.widgetInfo,
174
- ): any): WidgetInfo);
176
+ ) as WidgetInfo;
175
177
  newWidgetInfo.options = Object.assign(
176
178
  // eslint-disable-next-line react/no-string-refs
179
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
177
180
  this.refs.widget.serialize(),
178
181
  newProps,
179
182
  );
@@ -182,24 +185,25 @@ class WidgetEditor extends React.Component<
182
185
 
183
186
  _toggleStatic = (e: Event) => {
184
187
  e.preventDefault();
185
- const newWidgetInfo = ((Object.assign({}, this.state.widgetInfo, {
188
+ const newWidgetInfo = Object.assign({}, this.state.widgetInfo, {
186
189
  static: !this.state.widgetInfo.static,
187
- }): any): WidgetInfo);
190
+ }) as WidgetInfo;
188
191
  this.props.onChange(newWidgetInfo);
189
192
  };
190
193
 
191
- _handleAlignmentChange = (e: SyntheticEvent<HTMLSelectElement>) => {
192
- const newAlignment = ((e.currentTarget.value: any): Alignment);
193
- const newWidgetInfo = ((Object.assign(
194
+ _handleAlignmentChange = (e: React.SyntheticEvent<HTMLSelectElement>) => {
195
+ const newAlignment = e.currentTarget.value as Alignment;
196
+ const newWidgetInfo = Object.assign(
194
197
  {},
195
198
  this.state.widgetInfo,
196
- ): any): WidgetInfo);
199
+ ) as WidgetInfo;
197
200
  newWidgetInfo.alignment = newAlignment;
198
201
  this.props.onChange(newWidgetInfo);
199
202
  };
200
203
 
201
204
  getSaveWarnings = () => {
202
205
  // eslint-disable-next-line react/no-string-refs
206
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
203
207
  const issuesFunc = this.refs.widget.getSaveWarnings;
204
208
  return issuesFunc ? issuesFunc() : [];
205
209
  };
@@ -215,16 +219,17 @@ class WidgetEditor extends React.Component<
215
219
  static: widgetInfo.static,
216
220
  graded: widgetInfo.graded,
217
221
  // eslint-disable-next-line react/no-string-refs
222
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
218
223
  options: this.refs.widget.serialize(),
219
224
  version: widgetInfo.version,
220
225
  };
221
226
  };
222
227
 
223
- render(): React.Node {
228
+ render(): React.ReactNode {
224
229
  const widgetInfo = this.state.widgetInfo;
225
230
 
226
231
  const Ed = Widgets.getEditor(widgetInfo.type);
227
- let supportedAlignments: $ReadOnlyArray<Alignment>;
232
+ let supportedAlignments: ReadonlyArray<Alignment>;
228
233
  if (this.props.apiOptions.showAlignmentOptions) {
229
234
  supportedAlignments = Widgets.getSupportedAlignments(
230
235
  widgetInfo.type,
@@ -270,6 +275,7 @@ class WidgetEditor extends React.Component<
270
275
  {supportsStaticMode && (
271
276
  <input
272
277
  type="button"
278
+ // @ts-expect-error [FEI-5003] - TS2322 - Type '(e: Event) => void' is not assignable to type 'MouseEventHandler<HTMLInputElement>'.
273
279
  onClick={this._toggleStatic}
274
280
  className="simple-button--small"
275
281
  value={
@@ -340,8 +346,9 @@ const IMAGE_REGEX = /!\[[^\]]*\]\(([^\s)]+)[^)]*\)/g;
340
346
  * Note: Returns an array of the capture objects, whereas String::match
341
347
  * ignores captures. If you don't need captures, use String::match
342
348
  */
343
- const allMatches = function (regex, str) {
344
- const result = [];
349
+ const allMatches = function (regex: RegExp, str: string) {
350
+ // @ts-expect-error [FEI-5003] - TS2702 - 'RegExp' only refers to a type, but is being used as a namespace here.
351
+ const result: Array<any | RegExp.matchResult> = [];
345
352
  // eslint-disable-next-line no-constant-condition
346
353
  while (true) {
347
354
  const match = regex.exec(str);
@@ -357,52 +364,59 @@ const allMatches = function (regex, str) {
357
364
  * Return an array of URLs of all the images in the given renderer
358
365
  * markdown.
359
366
  */
360
- const imageUrlsFromContent = function (content) {
367
+ const imageUrlsFromContent = function (content: string) {
361
368
  return _.map(allMatches(IMAGE_REGEX, content), (capture) => capture[1]);
362
369
  };
363
370
 
364
- type EditorProps = $ReadOnly<{|
365
- apiOptions: any,
366
- className?: string,
367
- content: string,
368
- replace?: any,
369
- placeholder: string,
370
- widgets: {|[name: string]: WidgetInfo|},
371
- images: any,
372
- disabled: boolean,
373
- widgetEnabled: boolean,
374
- immutableWidgets: boolean,
375
- showWordCount: boolean,
376
- warnNoPrompt: boolean,
377
- warnNoWidgets: boolean,
378
- imageUploader?: (file: string, callback: (url: string) => mixed) => mixed,
379
- onChange: ChangeHandler,
380
- |}>;
381
-
382
- type DeafultProps = {|
383
- content: string,
384
- disabled: boolean,
385
- images: {...},
386
- immutableWidgets: boolean,
387
- placeholder: string,
388
- showWordCount: boolean,
389
- warnNoPrompt: boolean,
390
- warnNoWidgets: boolean,
391
- widgetEnabled: boolean,
392
- widgets: {|[name: string]: WidgetInfo|},
393
- |};
394
-
395
- type EditorState = {|
396
- showKatexErrors: boolean,
397
- textAreaValue: string,
398
- katex?: katex,
399
- |};
371
+ type EditorProps = Readonly<{
372
+ apiOptions: any;
373
+ className?: string;
374
+ content: string;
375
+ replace?: any;
376
+ placeholder: string;
377
+ widgets: {
378
+ [name: string]: WidgetInfo;
379
+ };
380
+ images: any;
381
+ disabled: boolean;
382
+ widgetEnabled: boolean;
383
+ immutableWidgets: boolean;
384
+ showWordCount: boolean;
385
+ warnNoPrompt: boolean;
386
+ warnNoWidgets: boolean;
387
+ imageUploader?: (
388
+ file: string,
389
+ callback: (url: string) => unknown,
390
+ ) => unknown;
391
+ onChange: ChangeHandler;
392
+ }>;
393
+
394
+ type DeafultProps = {
395
+ content: string;
396
+ disabled: boolean;
397
+ images: Record<any, any>;
398
+ immutableWidgets: boolean;
399
+ placeholder: string;
400
+ showWordCount: boolean;
401
+ warnNoPrompt: boolean;
402
+ warnNoWidgets: boolean;
403
+ widgetEnabled: boolean;
404
+ widgets: {
405
+ [name: string]: WidgetInfo;
406
+ };
407
+ };
408
+
409
+ type EditorState = {
410
+ showKatexErrors: boolean;
411
+ textAreaValue: string;
412
+ katex?: katex;
413
+ };
400
414
 
401
415
  // eslint-disable-next-line react/no-unsafe
402
416
  class Editor extends React.Component<EditorProps, EditorState> {
403
- lastUserValue: ?string;
404
- deferredChange: ?any;
405
- widgetIds: ?any;
417
+ lastUserValue: string | null | undefined;
418
+ deferredChange: any | null | undefined;
419
+ widgetIds: any | null | undefined;
406
420
 
407
421
  static defaultProps: DeafultProps = {
408
422
  content: "",
@@ -433,7 +447,9 @@ class Editor extends React.Component<EditorProps, EditorState> {
433
447
  // setState
434
448
  this._sizeImages(this.props);
435
449
  // eslint-disable-next-line react/no-string-refs
450
+ // @ts-expect-error [FEI-5003] - TS2769 - No overload matches this call.
436
451
  $(ReactDOM.findDOMNode(this.refs.textarea))
452
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'on' does not exist on type 'JQueryStatic'.
437
453
  .on("copy cut", this._maybeCopyWidgets)
438
454
  .on("paste", this._maybePasteWidgets);
439
455
 
@@ -471,13 +487,13 @@ class Editor extends React.Component<EditorProps, EditorState> {
471
487
  * textarea should be refined with an instanceof check to
472
488
  * HTMLTextAreaElement so that these props are available.
473
489
  */
474
- // $FlowFixMe[prop-missing]
490
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'focus' does not exist on type 'Element | Text'.
475
491
  textarea.focus();
476
- // $FlowFixMe[prop-missing]
492
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'value' does not exist on type 'Element | Text'.
477
493
  textarea.value = this.lastUserValue;
478
- // $FlowFixMe[prop-missing]
494
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'selectionStart' does not exist on type 'Element | Text'.
479
495
  textarea.selectionStart = 0;
480
- // $FlowFixMe[prop-missing]
496
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'select' does not exist on type 'Element | Text'.
481
497
  textarea.select(0, prevProps.content.length);
482
498
  if (
483
499
  document.execCommand(
@@ -488,7 +504,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
488
504
  ) {
489
505
  // This command is not implemented. Fall back to setting `value`
490
506
  // directly.
491
- // $FlowFixMe[prop-missing]
507
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'value' does not exist on type 'Element | Text'.
492
508
  textarea.value = this.props.content;
493
509
  }
494
510
  this.lastUserValue = null;
@@ -507,38 +523,38 @@ class Editor extends React.Component<EditorProps, EditorState> {
507
523
  clearTimeout(this.deferredChange);
508
524
  }
509
525
 
510
- getWidgetEditor: (id: string, type: string) => void | React.Node = (
511
- id: string,
512
- type: string,
513
- ) => {
514
- if (!Widgets.getEditor(type)) {
515
- return;
516
- }
517
- return (
518
- <WidgetEditor
519
- key={id}
520
- ref={id}
521
- id={id}
522
- type={type}
523
- // eslint-disable-next-line react/jsx-no-bind
524
- onChange={this._handleWidgetEditorChange.bind(this, id)}
525
- // eslint-disable-next-line react/jsx-no-bind
526
- onRemove={this._handleWidgetEditorRemove.bind(this, id)}
527
- apiOptions={this.props.apiOptions}
528
- {...this.props.widgets[id]}
529
- />
530
- );
531
- };
526
+ getWidgetEditor: (id: string, type: string) => undefined | React.ReactNode =
527
+ (id: string, type: string) => {
528
+ if (!Widgets.getEditor(type)) {
529
+ return;
530
+ }
531
+ return (
532
+ <WidgetEditor
533
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
534
+ key={id}
535
+ ref={id}
536
+ id={id}
537
+ // @ts-expect-error [FEI-5003] - TS2783 - 'type' is specified more than once, so this usage will be overwritten.
538
+ type={type}
539
+ // eslint-disable-next-line react/jsx-no-bind
540
+ onChange={this._handleWidgetEditorChange.bind(this, id)}
541
+ // eslint-disable-next-line react/jsx-no-bind
542
+ onRemove={this._handleWidgetEditorRemove.bind(this, id)}
543
+ apiOptions={this.props.apiOptions}
544
+ {...this.props.widgets[id]}
545
+ />
546
+ );
547
+ };
532
548
 
533
549
  _handleWidgetEditorChange: (
534
550
  id: string,
535
551
  newWidgetInfo: WidgetInfo,
536
- cb?: () => mixed,
552
+ cb?: () => unknown,
537
553
  silent?: boolean,
538
554
  ) => void = (
539
555
  id: string,
540
556
  newWidgetInfo: WidgetInfo,
541
- cb?: () => mixed,
557
+ cb?: () => unknown,
542
558
  silent?: boolean,
543
559
  ) => {
544
560
  const widgets = Object.assign({}, this.props.widgets);
@@ -550,6 +566,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
550
566
  // eslint-disable-next-line react/no-string-refs
551
567
  const textarea = this.refs.textarea;
552
568
  const re = new RegExp(widgetRegExp.replace("{id}", id), "gm");
569
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'value' does not exist on type 'ReactInstance'.
553
570
  this.props.onChange({content: textarea.value.replace(re, "")});
554
571
  };
555
572
 
@@ -589,6 +606,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
589
606
  {
590
607
  images: _.clone(images),
591
608
  },
609
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'null' is not assignable to parameter of type '(() => unknown) | undefined'.
592
610
  null, // callback
593
611
  true, // silent
594
612
  );
@@ -664,11 +682,13 @@ class Editor extends React.Component<EditorProps, EditorState> {
664
682
  this.props.onChange({content: content});
665
683
  })
666
684
  .each((fileAndSentinel) => {
685
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'. | TS2345 - Argument of type 'File' is not assignable to parameter of type 'string'.
667
686
  imageUploader(fileAndSentinel.file, (url) => {
668
687
  // See componentDidUpdate() for how this flag is used
669
688
  this.lastUserValue = origContent;
670
689
  this.props.onChange({
671
690
  content: this.state.textAreaValue.replace(
691
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'.
672
692
  fileAndSentinel.sentinel,
673
693
  url,
674
694
  ),
@@ -677,8 +697,8 @@ class Editor extends React.Component<EditorProps, EditorState> {
677
697
  });
678
698
  };
679
699
 
680
- handleChange: (e: SyntheticEvent<HTMLTextAreaElement>) => void = (
681
- e: SyntheticEvent<HTMLTextAreaElement>,
700
+ handleChange: (e: React.SyntheticEvent<HTMLTextAreaElement>) => void = (
701
+ e: React.SyntheticEvent<HTMLTextAreaElement>,
682
702
  ) => {
683
703
  // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
684
704
  // eslint-disable-next-line no-restricted-syntax
@@ -693,8 +713,8 @@ class Editor extends React.Component<EditorProps, EditorState> {
693
713
  }, this.props.apiOptions.editorChangeDelay);
694
714
  };
695
715
 
696
- _handleKeyDown: (e: SyntheticKeyboardEvent<HTMLTextAreaElement>) => void = (
697
- e: SyntheticKeyboardEvent<HTMLTextAreaElement>,
716
+ _handleKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void = (
717
+ e: React.KeyboardEvent<HTMLTextAreaElement>,
698
718
  ) => {
699
719
  e.stopPropagation();
700
720
  // Tab-completion of widgets. For example, to insert an image:
@@ -704,7 +724,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
704
724
  const textarea = ReactDOM.findDOMNode(this.refs.textarea);
705
725
 
706
726
  // findDOMNode can also return Text, but we know it's an element.
707
- // $FlowFixMe[incompatible-call]
727
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'Element | Text | null' is not assignable to parameter of type 'HTMLTextAreaElement'.
708
728
  const word = Util.textarea.getWordBeforeCursor(textarea);
709
729
  const matches = word.string.toLowerCase().match(shortcutRegexp);
710
730
 
@@ -730,165 +750,169 @@ class Editor extends React.Component<EditorProps, EditorState> {
730
750
  }
731
751
  };
732
752
 
733
- _maybeCopyWidgets: (e: SyntheticEvent<HTMLTextAreaElement>) => void = (
734
- e: SyntheticEvent<HTMLTextAreaElement>,
735
- ) => {
736
- // If there are widgets being cut/copied, put the widget JSON in
737
- // localStorage.perseusLastCopiedWidgets to allow copy-pasting of
738
- // widgets between Editors. Also store the text to be pasted in
739
- // localStorage.perseusLastCopiedText since we want to know if the user
740
- // is actually pasting something originally from Perseus later.
741
- const textarea = e.currentTarget;
742
- const selectedText = textarea.value.substring(
743
- textarea.selectionStart,
744
- textarea.selectionEnd,
745
- );
753
+ _maybeCopyWidgets: (e: React.SyntheticEvent<HTMLTextAreaElement>) => void =
754
+ (e: React.SyntheticEvent<HTMLTextAreaElement>) => {
755
+ // If there are widgets being cut/copied, put the widget JSON in
756
+ // localStorage.perseusLastCopiedWidgets to allow copy-pasting of
757
+ // widgets between Editors. Also store the text to be pasted in
758
+ // localStorage.perseusLastCopiedText since we want to know if the user
759
+ // is actually pasting something originally from Perseus later.
760
+ const textarea = e.currentTarget;
761
+ const selectedText = textarea.value.substring(
762
+ textarea.selectionStart,
763
+ textarea.selectionEnd,
764
+ );
746
765
 
747
- const widgetNames = _.map(
748
- selectedText.match(rWidgetSplit),
749
- (syntax) => {
750
- // $FlowFixMe[incompatible-use] exec could return null.
751
- return Util.rWidgetParts.exec(syntax)[1];
752
- },
753
- );
766
+ const widgetNames = _.map(
767
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'RegExpMatchArray | null' is not assignable to parameter of type 'Collection<any>'.
768
+ selectedText.match(rWidgetSplit),
769
+ (syntax) => {
770
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'.
771
+ return Util.rWidgetParts.exec(syntax)[1];
772
+ },
773
+ );
754
774
 
755
- const widgetData = _.pick(this.serialize().widgets, widgetNames);
775
+ const widgetData = _.pick(this.serialize().widgets, widgetNames);
756
776
 
757
- // $FlowIgnore[prop-missing] localStorage can be used as an object
758
- localStorage.perseusLastCopiedText = selectedText;
759
- // $FlowIgnore[prop-missing] localStorage can be used as an object
760
- localStorage.perseusLastCopiedWidgets = JSON.stringify(widgetData);
777
+ localStorage.perseusLastCopiedText = selectedText;
778
+ localStorage.perseusLastCopiedWidgets = JSON.stringify(widgetData);
761
779
 
762
- Log.log(`Widgets copied: ${localStorage.perseusLastCopiedWidgets}`);
763
- };
780
+ Log.log(`Widgets copied: ${localStorage.perseusLastCopiedWidgets}`);
781
+ };
764
782
 
765
- _maybePasteWidgets: (e: SyntheticEvent<HTMLTextAreaElement>) => void = (
766
- e: SyntheticEvent<HTMLTextAreaElement>,
767
- ) => {
768
- // Use the data from localStorage to paste any widgets we copied
769
- // before. Avoid name conflicts by renumbering pasted widgets so that
770
- // their numbers are always higher than the highest numbered widget of
771
- // their type.
772
- // TODO(sam): Fix widget numbering in the widget editor titles
773
-
774
- // $FlowIgnore[prop-missing] localStorage can be used as an object
775
- const widgetJSON = localStorage.perseusLastCopiedWidgets;
776
- // $FlowIgnore[prop-missing] localStorage can be used as an object
777
- const lastCopiedText = localStorage.perseusLastCopiedText;
778
- // $FlowIgnore[prop-missing] Unknown originalEvent
779
- const textToBePasted = e.originalEvent.clipboardData.getData("text");
780
-
781
- // Only intercept if we have widget data to paste and the user is
782
- // pasting something originally from Perseus.
783
- // TODO(sam/aria/alex): Make it so that you can paste arbitrary text
784
- // (e.g. from a text editor) instead of exactly what was copied, and
785
- // let the widgetJSON match up with it. This would let you copy text
786
- // into a buffer, perform complex operations on it, then paste it back.
787
- if (widgetJSON && lastCopiedText === textToBePasted) {
788
- e.preventDefault();
789
-
790
- const widgetData = JSON.parse(widgetJSON);
791
- const safeWidgetMapping = this._safeWidgetNameMapping(widgetData);
792
-
793
- // Use safe widget name map to construct the new widget data
794
- // TODO(aria/alex): Don't use `rWidgetSplit` or other piecemeal
795
- // regexes directly; abstract this out so that we don't have to
796
- // worry about potential edge cases.
797
- const safeWidgetData = {};
798
- for (const [key, data] of Object.entries(widgetData)) {
799
- safeWidgetData[safeWidgetMapping[key]] = data;
800
- }
801
- const newWidgets = _.extend(safeWidgetData, this.props.widgets);
802
-
803
- // Use safe widget name map to construct new text
804
- const safeText = lastCopiedText.replace(rWidgetSplit, (syntax) => {
805
- const match = Util.rWidgetParts.exec(syntax);
806
- // $FlowFixMe[incompatible-use] exec could return null.
807
- const completeWidget = match[0];
808
- // $FlowFixMe[incompatible-use] exec could return null.
809
- const widget = match[1];
810
- return completeWidget.replace(
811
- widget,
812
- safeWidgetMapping[widget],
783
+ _maybePasteWidgets: (e: React.SyntheticEvent<HTMLTextAreaElement>) => void =
784
+ (e: React.SyntheticEvent<HTMLTextAreaElement>) => {
785
+ // Use the data from localStorage to paste any widgets we copied
786
+ // before. Avoid name conflicts by renumbering pasted widgets so that
787
+ // their numbers are always higher than the highest numbered widget of
788
+ // their type.
789
+ // TODO(sam): Fix widget numbering in the widget editor titles
790
+
791
+ const widgetJSON = localStorage.perseusLastCopiedWidgets;
792
+ const lastCopiedText = localStorage.perseusLastCopiedText;
793
+ const textToBePasted =
794
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'originalEvent' does not exist on type 'SyntheticEvent<HTMLTextAreaElement, Event>'.
795
+ e.originalEvent.clipboardData.getData("text");
796
+
797
+ // Only intercept if we have widget data to paste and the user is
798
+ // pasting something originally from Perseus.
799
+ // TODO(sam/aria/alex): Make it so that you can paste arbitrary text
800
+ // (e.g. from a text editor) instead of exactly what was copied, and
801
+ // let the widgetJSON match up with it. This would let you copy text
802
+ // into a buffer, perform complex operations on it, then paste it back.
803
+ if (widgetJSON && lastCopiedText === textToBePasted) {
804
+ e.preventDefault();
805
+
806
+ const widgetData = JSON.parse(widgetJSON);
807
+ const safeWidgetMapping =
808
+ this._safeWidgetNameMapping(widgetData);
809
+
810
+ // Use safe widget name map to construct the new widget data
811
+ // TODO(aria/alex): Don't use `rWidgetSplit` or other piecemeal
812
+ // regexes directly; abstract this out so that we don't have to
813
+ // worry about potential edge cases.
814
+ const safeWidgetData: Record<string, any> = {};
815
+ for (const [key, data] of Object.entries(widgetData)) {
816
+ safeWidgetData[safeWidgetMapping[key]] = data;
817
+ }
818
+ const newWidgets = _.extend(safeWidgetData, this.props.widgets);
819
+
820
+ // Use safe widget name map to construct new text
821
+ const safeText = lastCopiedText.replace(
822
+ rWidgetSplit,
823
+ (syntax) => {
824
+ const match = Util.rWidgetParts.exec(syntax);
825
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'.
826
+ const completeWidget = match[0];
827
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'.
828
+ const widget = match[1];
829
+ return completeWidget.replace(
830
+ widget,
831
+ safeWidgetMapping[widget],
832
+ );
833
+ },
813
834
  );
814
- });
815
835
 
816
- // Add pasted text to previous content, replacing selected text to
817
- // replicate normal paste behavior.
818
- const textarea = e.currentTarget;
819
- const selectionStart = textarea.selectionStart;
820
- const newContent =
821
- this.state.textAreaValue.substr(0, selectionStart) +
822
- safeText +
823
- this.state.textAreaValue.substr(textarea.selectionEnd);
824
-
825
- // See componentDidUpdate() for how this flag is used
826
- this.lastUserValue = this.state.textAreaValue;
827
- this.props.onChange(
828
- {content: newContent, widgets: newWidgets},
829
- () => {
830
- const expectedCursorPosition =
831
- selectionStart + safeText.length;
832
- Util.textarea.moveCursor(textarea, expectedCursorPosition);
833
- },
834
- );
835
- }
836
- };
836
+ // Add pasted text to previous content, replacing selected text to
837
+ // replicate normal paste behavior.
838
+ const textarea = e.currentTarget;
839
+ const selectionStart = textarea.selectionStart;
840
+ const newContent =
841
+ this.state.textAreaValue.substr(0, selectionStart) +
842
+ safeText +
843
+ this.state.textAreaValue.substr(textarea.selectionEnd);
837
844
 
838
- _safeWidgetNameMapping: (widgetData: {[name: string]: any, ...}) => {...} =
839
- (widgetData: {[name: string]: any, ...}) => {
840
- // Helper function for _maybePasteWidgets.
841
- // For each widget about to be pasted, construct a mapping from
842
- // old widget name to a new widget name that doesn't have conflicts
843
- // with widgets already in the editor.
844
- // eg. If there is an "image 2" already present in the editor and we're
845
- // about to paste in two new images, return
846
- // { "image 1": "image 3", "image 2": "image 4" }
847
-
848
- // List of widgets about to be pasted as [[name, number], ...]
849
- const widgets = _.keys(widgetData).map((name) => name.split(" "));
850
- const widgetTypes = _.uniq(widgets.map((widget) => widget[0]));
851
-
852
- // List of existing widgets as [[name, number], ...]
853
- const existingWidgets = _.keys(this.props.widgets).map((name) =>
854
- name.split(" "),
855
- );
845
+ // See componentDidUpdate() for how this flag is used
846
+ this.lastUserValue = this.state.textAreaValue;
847
+ this.props.onChange(
848
+ {content: newContent, widgets: newWidgets},
849
+ () => {
850
+ const expectedCursorPosition =
851
+ selectionStart + safeText.length;
852
+ Util.textarea.moveCursor(
853
+ textarea,
854
+ expectedCursorPosition,
855
+ );
856
+ },
857
+ );
858
+ }
859
+ };
856
860
 
857
- // Mapping of widget type to a safe (non-conflicting) number
858
- // eg. { "image": 2, "dropdown": 1 }
859
- const safeWidgetNums = {};
860
- _.each(widgetTypes, (type) => {
861
- safeWidgetNums[type] = _.chain(existingWidgets)
862
- .filter((existingWidget) => existingWidget[0] === type)
863
- .map((existingWidget) => +existingWidget[1] + 1)
864
- .max()
865
- .value();
866
- // If there are no existing widgets _.max returns -Infinity
867
- safeWidgetNums[type] = Math.max(safeWidgetNums[type], 1);
868
- });
861
+ _safeWidgetNameMapping: (widgetData: {
862
+ [name: string]: any;
863
+ }) => Record<any, any> = (widgetData: {[name: string]: any}) => {
864
+ // Helper function for _maybePasteWidgets.
865
+ // For each widget about to be pasted, construct a mapping from
866
+ // old widget name to a new widget name that doesn't have conflicts
867
+ // with widgets already in the editor.
868
+ // eg. If there is an "image 2" already present in the editor and we're
869
+ // about to paste in two new images, return
870
+ // { "image 1": "image 3", "image 2": "image 4" }
871
+
872
+ // List of widgets about to be pasted as [[name, number], ...]
873
+ const widgets = _.keys(widgetData).map((name) => name.split(" "));
874
+ const widgetTypes = _.uniq(widgets.map((widget) => widget[0]));
875
+
876
+ // List of existing widgets as [[name, number], ...]
877
+ const existingWidgets = _.keys(this.props.widgets).map((name) =>
878
+ name.split(" "),
879
+ );
869
880
 
870
- // Construct mapping, incrementing the vals in safeWidgetNums as we go
871
- const safeWidgetMapping = {};
872
- _.each(widgets, (widget) => {
873
- const widgetName = widget.join(" ");
874
- const widgetType = widget[0];
881
+ // Mapping of widget type to a safe (non-conflicting) number
882
+ // eg. { "image": 2, "dropdown": 1 }
883
+ const safeWidgetNums: Record<string, any> = {};
884
+ _.each(widgetTypes, (type) => {
885
+ safeWidgetNums[type] = _.chain(existingWidgets)
886
+ .filter((existingWidget) => existingWidget[0] === type)
887
+ .map((existingWidget) => +existingWidget[1] + 1)
888
+ .max()
889
+ .value();
890
+ // If there are no existing widgets _.max returns -Infinity
891
+ safeWidgetNums[type] = Math.max(safeWidgetNums[type], 1);
892
+ });
875
893
 
876
- safeWidgetMapping[
877
- widgetName
878
- ] = `${widgetType} ${safeWidgetNums[widgetType]}`;
879
- safeWidgetNums[widgetType]++;
880
- });
894
+ // Construct mapping, incrementing the vals in safeWidgetNums as we go
895
+ const safeWidgetMapping: Record<string, any> = {};
896
+ _.each(widgets, (widget) => {
897
+ const widgetName = widget.join(" ");
898
+ const widgetType = widget[0];
881
899
 
882
- return safeWidgetMapping;
883
- };
900
+ safeWidgetMapping[
901
+ widgetName
902
+ ] = `${widgetType} ${safeWidgetNums[widgetType]}`;
903
+ safeWidgetNums[widgetType]++;
904
+ });
905
+
906
+ return safeWidgetMapping;
907
+ };
884
908
 
885
909
  _addWidgetToContent: (
886
910
  oldContent: string,
887
- cursorRange: $ReadOnlyArray<number>,
911
+ cursorRange: ReadonlyArray<number>,
888
912
  widgetType: string,
889
913
  ) => void = (
890
914
  oldContent: string,
891
- cursorRange: $ReadOnlyArray<number>,
915
+ cursorRange: ReadonlyArray<number>,
892
916
  widgetType: string,
893
917
  ) => {
894
918
  // eslint-disable-next-line react/no-string-refs
@@ -897,11 +921,12 @@ class Editor extends React.Component<EditorProps, EditorState> {
897
921
  // Note: we have to use _.map here instead of Array::map
898
922
  // because the results of a .match might be null if no
899
923
  // widgets were found.
924
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'RegExpMatchArray | null' is not assignable to parameter of type 'Collection<any>'.
900
925
  const allWidgetIds = _.map(oldContent.match(rWidgetSplit), (syntax) => {
901
926
  const match = Util.rWidgetParts.exec(syntax);
902
- // $FlowFixMe[incompatible-use] exec could return null.
927
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'.
903
928
  const type = match[2];
904
- // $FlowFixMe[incompatible-use] exec could return null.
929
+ // @ts-expect-error [FEI-5003] - TS2531 - Object is possibly 'null'.
905
930
  const num = +match[3];
906
931
  return [type, num];
907
932
  });
@@ -939,10 +964,12 @@ class Editor extends React.Component<EditorProps, EditorState> {
939
964
  const newWidgets = _.clone(this.props.widgets);
940
965
  newWidgets[id] = {
941
966
  options: Widgets.getEditor(widgetType)?.defaultProps,
967
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'string' is not assignable to type '"video" | "image" | "iframe" | "table" | "radio" | "definition" | "group" | "matrix" | "categorizer" | "cs-program" | "dropdown" | "example-graphie-widget" | "example-widget" | ... 26 more ... | "unit-input"'.
942
968
  type: widgetType,
943
969
  // Track widget version on creation, so that a widget editor
944
970
  // without a valid version prop can only possibly refer to a
945
971
  // pre-versioning creation time.
972
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'Version | null | undefined' is not assignable to type 'Version | undefined'.
946
973
  version: Widgets.getVersion(widgetType),
947
974
  };
948
975
 
@@ -957,6 +984,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
957
984
  Util.textarea.moveCursor(
958
985
  // findDOMNode can return Text but we know this is Element
959
986
  // $FlowFixMe[incompatible-call]
987
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'Element | Text | null' is not assignable to parameter of type 'HTMLTextAreaElement'.
960
988
  textarea,
961
989
  // We want to put the cursor after the widget
962
990
  // and after any added newlines
@@ -972,14 +1000,16 @@ class Editor extends React.Component<EditorProps, EditorState> {
972
1000
 
973
1001
  this._addWidgetToContent(
974
1002
  this.props.content,
1003
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'selectionStart' does not exist on type 'ReactInstance'. | TS2339 - Property 'selectionEnd' does not exist on type 'ReactInstance'.
975
1004
  [textarea.selectionStart, textarea.selectionEnd],
976
1005
  widgetType,
977
1006
  );
1007
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
978
1008
  textarea.focus();
979
1009
  };
980
1010
 
981
- addTemplate: (e: SyntheticEvent<HTMLTextAreaElement>) => void = (
982
- e: SyntheticEvent<HTMLTextAreaElement>,
1011
+ addTemplate: (e: React.SyntheticEvent<HTMLTextAreaElement>) => void = (
1012
+ e: React.SyntheticEvent<HTMLTextAreaElement>,
983
1013
  ) => {
984
1014
  const templateType = e.currentTarget.value;
985
1015
  if (templateType === "") {
@@ -1049,6 +1079,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1049
1079
  .chain()
1050
1080
  .map((id) => {
1051
1081
  // eslint-disable-next-line react/no-string-refs
1082
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
1052
1083
  const issuesFunc = this.refs[id].getSaveWarnings;
1053
1084
  const issues = issuesFunc ? issuesFunc() : [];
1054
1085
  return _.map(issues, (issue) => id + ": " + issue);
@@ -1068,7 +1099,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1068
1099
  * textarea should be refined with an instanceof check to
1069
1100
  * HTMLTextAreaElement so that these props are available.
1070
1101
  */
1071
- // $FlowFixMe[prop-missing]
1102
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'focus' does not exist on type 'Element | Text'.
1072
1103
  textarea.focus();
1073
1104
  }
1074
1105
  };
@@ -1083,27 +1114,28 @@ class Editor extends React.Component<EditorProps, EditorState> {
1083
1114
  * textarea should be refined with an instanceof check to
1084
1115
  * HTMLTextAreaElement so that these props are available.
1085
1116
  */
1086
- // $FlowFixMe[prop-missing]
1117
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'selectionStart' does not exist on type 'Element | Text'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
1087
1118
  textarea.selectionStart = textarea.value.length;
1088
- // $FlowFixMe[prop-missing]
1119
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'selectionEnd' does not exist on type 'Element | Text'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
1089
1120
  textarea.selectionEnd = textarea.value.length;
1090
1121
  }
1091
1122
  };
1092
1123
 
1093
- serialize: (options: any) => {|
1094
- content: string,
1095
- images: any,
1096
- replace: any | void,
1097
- widgets: {...},
1098
- |} = (options: any) => {
1124
+ serialize: (options?: any) => {
1125
+ content: string;
1126
+ images: any;
1127
+ replace: any | undefined;
1128
+ widgets: Record<any, any>;
1129
+ } = (options: any) => {
1099
1130
  // need to serialize the widgets since the state might not be
1100
1131
  // completely represented in props. ahem //transformer// (and
1101
1132
  // interactive-graph and plotter).
1102
- const widgets = {};
1133
+ const widgets: Record<string, any> = {};
1103
1134
  // eslint-disable-next-line react/no-string-refs
1104
1135
  const widgetIds = _.intersection(this.widgetIds, _.keys(this.refs));
1105
1136
  _.each(widgetIds, (id) => {
1106
1137
  // eslint-disable-next-line react/no-string-refs
1138
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
1107
1139
  widgets[id] = this.refs[id].serialize();
1108
1140
  });
1109
1141
 
@@ -1131,7 +1163,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1131
1163
  };
1132
1164
  };
1133
1165
 
1134
- render(): React.Element<"div"> {
1166
+ render(): React.ReactNode {
1135
1167
  let pieces;
1136
1168
  let widgets;
1137
1169
  let underlayPieces;
@@ -1139,7 +1171,10 @@ class Editor extends React.Component<EditorProps, EditorState> {
1139
1171
  let templatesDropDown;
1140
1172
  let widgetsAndTemplates;
1141
1173
  let wordCountDisplay;
1142
- const katexErrorList = [];
1174
+ const katexErrorList: Array<{
1175
+ math: string;
1176
+ message: never;
1177
+ }> = [];
1143
1178
 
1144
1179
  if (this.props.showWordCount) {
1145
1180
  const numChars = PerseusMarkdown.characterCount(this.props.content);
@@ -1172,6 +1207,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1172
1207
  // Normal text
1173
1208
  underlayPieces.push(pieces[i]);
1174
1209
 
1210
+ // @ts-expect-error [FEI-5003] - TS2554 - Expected 2 arguments, but got 1.
1175
1211
  const ast = PerseusMarkdown.parse(pieces[i]);
1176
1212
 
1177
1213
  PerseusMarkdown.traverseContent(ast, (node) => {
@@ -1181,9 +1217,10 @@ class Editor extends React.Component<EditorProps, EditorState> {
1181
1217
  katex.renderToString(content, {
1182
1218
  colorIsTextColor: true,
1183
1219
  });
1184
- } catch (e) {
1220
+ } catch (e: any) {
1185
1221
  katexErrorList.push({
1186
1222
  math: content,
1223
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'any' is not assignable to type 'never'.
1187
1224
  message: e.message,
1188
1225
  });
1189
1226
  }
@@ -1242,6 +1279,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1242
1279
 
1243
1280
  const insertTemplateString = "Insert template\u2026";
1244
1281
  templatesDropDown = (
1282
+ // @ts-expect-error [FEI-5003] - TS2322 - Type '(e: SyntheticEvent<HTMLTextAreaElement, Event>) => void' is not assignable to type 'ChangeEventHandler<HTMLSelectElement>'.
1245
1283
  <select onChange={this.addTemplate}>
1246
1284
  <option value="">{insertTemplateString}</option>
1247
1285
  <option disabled>--</option>
@@ -1257,7 +1295,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1257
1295
  );
1258
1296
 
1259
1297
  if (!this.props.immutableWidgets) {
1260
- const widgetNodes = ((Object.values(widgets): any): React.Node);
1298
+ const widgetNodes = Object.values(widgets) as React.ReactNode;
1261
1299
  widgetsAndTemplates = (
1262
1300
  <div className="perseus-editor-widgets">
1263
1301
  <div className="perseus-editor-widgets-selectors">
@@ -1329,7 +1367,7 @@ class Editor extends React.Component<EditorProps, EditorState> {
1329
1367
  borderTop: "none",
1330
1368
  padding: 4,
1331
1369
  backgroundColor: "pink",
1332
- };
1370
+ } as const;
1333
1371
 
1334
1372
  return (
1335
1373
  <div