@difizen/libro-core 0.0.2-alpha.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/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/es/add-cell/index.d.ts +4 -0
  4. package/es/add-cell/index.d.ts.map +1 -0
  5. package/es/add-cell/index.js +3 -0
  6. package/es/add-cell/index.less +38 -0
  7. package/es/add-cell/libro-add-cell-module.d.ts +3 -0
  8. package/es/add-cell/libro-add-cell-module.d.ts.map +1 -0
  9. package/es/add-cell/libro-add-cell-module.js +4 -0
  10. package/es/add-cell/libro-add-cell-slot-contribution.d.ts +12 -0
  11. package/es/add-cell/libro-add-cell-slot-contribution.d.ts.map +1 -0
  12. package/es/add-cell/libro-add-cell-slot-contribution.js +53 -0
  13. package/es/add-cell/libro-add-cell-view.d.ts +21 -0
  14. package/es/add-cell/libro-add-cell-view.d.ts.map +1 -0
  15. package/es/add-cell/libro-add-cell-view.js +129 -0
  16. package/es/cell/index.d.ts +10 -0
  17. package/es/cell/index.d.ts.map +1 -0
  18. package/es/cell/index.js +9 -0
  19. package/es/cell/libro-cell-contribution.d.ts +13 -0
  20. package/es/cell/libro-cell-contribution.d.ts.map +1 -0
  21. package/es/cell/libro-cell-contribution.js +41 -0
  22. package/es/cell/libro-cell-model.d.ts +19 -0
  23. package/es/cell/libro-cell-model.d.ts.map +1 -0
  24. package/es/cell/libro-cell-model.js +103 -0
  25. package/es/cell/libro-cell-module.d.ts +10 -0
  26. package/es/cell/libro-cell-module.d.ts.map +1 -0
  27. package/es/cell/libro-cell-module.js +30 -0
  28. package/es/cell/libro-cell-protocol.d.ts +42 -0
  29. package/es/cell/libro-cell-protocol.d.ts.map +1 -0
  30. package/es/cell/libro-cell-protocol.js +11 -0
  31. package/es/cell/libro-cell-service.d.ts +28 -0
  32. package/es/cell/libro-cell-service.d.ts.map +1 -0
  33. package/es/cell/libro-cell-service.js +242 -0
  34. package/es/cell/libro-cell-view.d.ts +65 -0
  35. package/es/cell/libro-cell-view.d.ts.map +1 -0
  36. package/es/cell/libro-cell-view.js +191 -0
  37. package/es/cell/libro-edit-cell-view.d.ts +32 -0
  38. package/es/cell/libro-edit-cell-view.d.ts.map +1 -0
  39. package/es/cell/libro-edit-cell-view.js +72 -0
  40. package/es/cell/libro-executable-cell-model.d.ts +14 -0
  41. package/es/cell/libro-executable-cell-model.d.ts.map +1 -0
  42. package/es/cell/libro-executable-cell-model.js +7 -0
  43. package/es/cell/libro-executable-cell-view.d.ts +27 -0
  44. package/es/cell/libro-executable-cell-view.d.ts.map +1 -0
  45. package/es/cell/libro-executable-cell-view.js +66 -0
  46. package/es/cell/libro-markdown-cell-model.d.ts +9 -0
  47. package/es/cell/libro-markdown-cell-model.d.ts.map +1 -0
  48. package/es/cell/libro-markdown-cell-model.js +7 -0
  49. package/es/collapse-service.d.ts +36 -0
  50. package/es/collapse-service.d.ts.map +1 -0
  51. package/es/collapse-service.js +55 -0
  52. package/es/command/document-commands.d.ts +6 -0
  53. package/es/command/document-commands.d.ts.map +1 -0
  54. package/es/command/document-commands.js +13 -0
  55. package/es/command/index.d.ts +7 -0
  56. package/es/command/index.d.ts.map +1 -0
  57. package/es/command/index.js +6 -0
  58. package/es/command/kernel-command.d.ts +5 -0
  59. package/es/command/kernel-command.d.ts.map +1 -0
  60. package/es/command/kernel-command.js +39 -0
  61. package/es/command/libro-command-contribution.d.ts +14 -0
  62. package/es/command/libro-command-contribution.d.ts.map +1 -0
  63. package/es/command/libro-command-contribution.js +1751 -0
  64. package/es/command/libro-command-register.d.ts +22 -0
  65. package/es/command/libro-command-register.d.ts.map +1 -0
  66. package/es/command/libro-command-register.js +105 -0
  67. package/es/command/module.d.ts +3 -0
  68. package/es/command/module.d.ts.map +1 -0
  69. package/es/command/module.js +4 -0
  70. package/es/command/notebook-commands.d.ts +6 -0
  71. package/es/command/notebook-commands.d.ts.map +1 -0
  72. package/es/command/notebook-commands.js +468 -0
  73. package/es/components/cell-protocol.d.ts +20 -0
  74. package/es/components/cell-protocol.d.ts.map +1 -0
  75. package/es/components/cell-protocol.js +4 -0
  76. package/es/components/dnd-cell-item-render.d.ts +12 -0
  77. package/es/components/dnd-cell-item-render.d.ts.map +1 -0
  78. package/es/components/dnd-cell-item-render.js +253 -0
  79. package/es/components/dnd-component/custom-drag-layer.d.ts +9 -0
  80. package/es/components/dnd-component/custom-drag-layer.d.ts.map +1 -0
  81. package/es/components/dnd-component/custom-drag-layer.js +141 -0
  82. package/es/components/dnd-component/default-dnd-content.d.ts +10 -0
  83. package/es/components/dnd-component/default-dnd-content.d.ts.map +1 -0
  84. package/es/components/dnd-component/default-dnd-content.js +257 -0
  85. package/es/components/dnd-component/dnd-context.d.ts +3 -0
  86. package/es/components/dnd-component/dnd-context.d.ts.map +1 -0
  87. package/es/components/dnd-component/dnd-context.js +20 -0
  88. package/es/components/dnd-component/dnd-list.d.ts +10 -0
  89. package/es/components/dnd-component/dnd-list.d.ts.map +1 -0
  90. package/es/components/dnd-component/dnd-list.js +115 -0
  91. package/es/components/dnd-component/index.d.ts +5 -0
  92. package/es/components/dnd-component/index.d.ts.map +1 -0
  93. package/es/components/dnd-component/index.js +4 -0
  94. package/es/components/index.d.ts +6 -0
  95. package/es/components/index.d.ts.map +1 -0
  96. package/es/components/index.js +5 -0
  97. package/es/components/libro-component.d.ts +5 -0
  98. package/es/components/libro-component.d.ts.map +1 -0
  99. package/es/components/libro-component.js +35 -0
  100. package/es/components/libro-side-toolbar-menu.d.ts +21 -0
  101. package/es/components/libro-side-toolbar-menu.d.ts.map +1 -0
  102. package/es/components/libro-side-toolbar-menu.js +58 -0
  103. package/es/components/libro-view-header.d.ts +3 -0
  104. package/es/components/libro-view-header.d.ts.map +1 -0
  105. package/es/components/libro-view-header.js +37 -0
  106. package/es/configuration/libro-configuration-contribution.d.ts +5 -0
  107. package/es/configuration/libro-configuration-contribution.d.ts.map +1 -0
  108. package/es/configuration/libro-configuration-contribution.js +24 -0
  109. package/es/configuration/libro-configuration.d.ts +10 -0
  110. package/es/configuration/libro-configuration.d.ts.map +1 -0
  111. package/es/configuration/libro-configuration.js +81 -0
  112. package/es/content/index.d.ts +5 -0
  113. package/es/content/index.d.ts.map +1 -0
  114. package/es/content/index.js +4 -0
  115. package/es/content/libro-content-contribution.d.ts +11 -0
  116. package/es/content/libro-content-contribution.d.ts.map +1 -0
  117. package/es/content/libro-content-contribution.js +32 -0
  118. package/es/content/libro-content-module.d.ts +3 -0
  119. package/es/content/libro-content-module.d.ts.map +1 -0
  120. package/es/content/libro-content-module.js +5 -0
  121. package/es/content/libro-content-protocol.d.ts +8 -0
  122. package/es/content/libro-content-protocol.d.ts.map +1 -0
  123. package/es/content/libro-content-protocol.js +2 -0
  124. package/es/content/libro-content-service.d.ts +9 -0
  125. package/es/content/libro-content-service.d.ts.map +1 -0
  126. package/es/content/libro-content-service.js +35 -0
  127. package/es/index.d.ts +20 -0
  128. package/es/index.d.ts.map +1 -0
  129. package/es/index.js +19 -0
  130. package/es/index.less +682 -0
  131. package/es/libro-context-key.d.ts +22 -0
  132. package/es/libro-context-key.d.ts.map +1 -0
  133. package/es/libro-context-key.js +80 -0
  134. package/es/libro-keybind-registry.d.ts +5 -0
  135. package/es/libro-keybind-registry.d.ts.map +1 -0
  136. package/es/libro-keybind-registry.js +68 -0
  137. package/es/libro-model.d.ts +129 -0
  138. package/es/libro-model.d.ts.map +1 -0
  139. package/es/libro-model.js +803 -0
  140. package/es/libro-protocol.d.ts +241 -0
  141. package/es/libro-protocol.d.ts.map +1 -0
  142. package/es/libro-protocol.js +34 -0
  143. package/es/libro-service.d.ts +34 -0
  144. package/es/libro-service.d.ts.map +1 -0
  145. package/es/libro-service.js +180 -0
  146. package/es/libro-view-tracker.d.ts +6 -0
  147. package/es/libro-view-tracker.d.ts.map +1 -0
  148. package/es/libro-view-tracker.js +13 -0
  149. package/es/libro-view.d.ts +151 -0
  150. package/es/libro-view.d.ts.map +1 -0
  151. package/es/libro-view.js +1963 -0
  152. package/es/material-from-designer.d.ts +27 -0
  153. package/es/material-from-designer.d.ts.map +1 -0
  154. package/es/material-from-designer.js +550 -0
  155. package/es/module.d.ts +3 -0
  156. package/es/module.d.ts.map +1 -0
  157. package/es/module.js +55 -0
  158. package/es/output/index.d.ts +6 -0
  159. package/es/output/index.d.ts.map +1 -0
  160. package/es/output/index.js +5 -0
  161. package/es/output/output-area.d.ts +51 -0
  162. package/es/output/output-area.d.ts.map +1 -0
  163. package/es/output/output-area.js +346 -0
  164. package/es/output/output-contribution.d.ts +11 -0
  165. package/es/output/output-contribution.d.ts.map +1 -0
  166. package/es/output/output-contribution.js +31 -0
  167. package/es/output/output-model.d.ts +27 -0
  168. package/es/output/output-model.d.ts.map +1 -0
  169. package/es/output/output-model.js +95 -0
  170. package/es/output/output-module.d.ts +3 -0
  171. package/es/output/output-module.d.ts.map +1 -0
  172. package/es/output/output-module.js +6 -0
  173. package/es/output/output-protocol.d.ts +130 -0
  174. package/es/output/output-protocol.d.ts.map +1 -0
  175. package/es/output/output-protocol.js +18 -0
  176. package/es/slot/index.d.ts +5 -0
  177. package/es/slot/index.d.ts.map +1 -0
  178. package/es/slot/index.js +4 -0
  179. package/es/slot/libro-slot-manager.d.ts +13 -0
  180. package/es/slot/libro-slot-manager.d.ts.map +1 -0
  181. package/es/slot/libro-slot-manager.js +57 -0
  182. package/es/slot/libro-slot-protocol.d.ts +19 -0
  183. package/es/slot/libro-slot-protocol.d.ts.map +1 -0
  184. package/es/slot/libro-slot-protocol.js +5 -0
  185. package/es/slot/libro-slot-view.d.ts +9 -0
  186. package/es/slot/libro-slot-view.d.ts.map +1 -0
  187. package/es/slot/libro-slot-view.js +81 -0
  188. package/es/slot/module.d.ts +3 -0
  189. package/es/slot/module.d.ts.map +1 -0
  190. package/es/slot/module.js +5 -0
  191. package/es/theme/libro-color-registry.d.ts +6 -0
  192. package/es/theme/libro-color-registry.d.ts.map +1 -0
  193. package/es/theme/libro-color-registry.js +494 -0
  194. package/es/toolbar/change-cell-to-selector.d.ts +5 -0
  195. package/es/toolbar/change-cell-to-selector.d.ts.map +1 -0
  196. package/es/toolbar/change-cell-to-selector.js +48 -0
  197. package/es/toolbar/hide-all-selector.d.ts +5 -0
  198. package/es/toolbar/hide-all-selector.d.ts.map +1 -0
  199. package/es/toolbar/hide-all-selector.js +99 -0
  200. package/es/toolbar/index.d.ts +4 -0
  201. package/es/toolbar/index.d.ts.map +1 -0
  202. package/es/toolbar/index.js +3 -0
  203. package/es/toolbar/index.less +59 -0
  204. package/es/toolbar/libro-toolbar-protocol.d.ts +4 -0
  205. package/es/toolbar/libro-toolbar-protocol.d.ts.map +1 -0
  206. package/es/toolbar/libro-toolbar-protocol.js +1 -0
  207. package/es/toolbar/libro-toolbar.d.ts +8 -0
  208. package/es/toolbar/libro-toolbar.d.ts.map +1 -0
  209. package/es/toolbar/libro-toolbar.js +223 -0
  210. package/es/toolbar/module.d.ts +3 -0
  211. package/es/toolbar/module.d.ts.map +1 -0
  212. package/es/toolbar/module.js +5 -0
  213. package/es/toolbar/restart-clear-outputs-contribution.d.ts +5 -0
  214. package/es/toolbar/restart-clear-outputs-contribution.d.ts.map +1 -0
  215. package/es/toolbar/restart-clear-outputs-contribution.js +23 -0
  216. package/es/toolbar/restart-clear-outputs-modal.d.ts +6 -0
  217. package/es/toolbar/restart-clear-outputs-modal.d.ts.map +1 -0
  218. package/es/toolbar/restart-clear-outputs-modal.js +30 -0
  219. package/es/toolbar/save-icon.d.ts +3 -0
  220. package/es/toolbar/save-icon.d.ts.map +1 -0
  221. package/es/toolbar/save-icon.js +54 -0
  222. package/es/toolbar/shutdown-contribution.d.ts +5 -0
  223. package/es/toolbar/shutdown-contribution.d.ts.map +1 -0
  224. package/es/toolbar/shutdown-contribution.js +23 -0
  225. package/es/toolbar/shutdown-modal.d.ts +6 -0
  226. package/es/toolbar/shutdown-modal.d.ts.map +1 -0
  227. package/es/toolbar/shutdown-modal.js +30 -0
  228. package/es/toolbar/side-toolar-more-select.d.ts +4 -0
  229. package/es/toolbar/side-toolar-more-select.d.ts.map +1 -0
  230. package/es/toolbar/side-toolar-more-select.js +161 -0
  231. package/es/typings/index.d.ts +1 -0
  232. package/package.json +70 -0
  233. package/src/add-cell/index.less +38 -0
  234. package/src/add-cell/index.ts +3 -0
  235. package/src/add-cell/libro-add-cell-module.ts +9 -0
  236. package/src/add-cell/libro-add-cell-slot-contribution.ts +31 -0
  237. package/src/add-cell/libro-add-cell-view.tsx +101 -0
  238. package/src/cell/README.md +14 -0
  239. package/src/cell/index.ts +9 -0
  240. package/src/cell/libro-cell-contribution.ts +38 -0
  241. package/src/cell/libro-cell-model.ts +61 -0
  242. package/src/cell/libro-cell-module.ts +40 -0
  243. package/src/cell/libro-cell-protocol.ts +53 -0
  244. package/src/cell/libro-cell-service.ts +157 -0
  245. package/src/cell/libro-cell-view.tsx +128 -0
  246. package/src/cell/libro-edit-cell-view.tsx +60 -0
  247. package/src/cell/libro-executable-cell-model.ts +32 -0
  248. package/src/cell/libro-executable-cell-view.ts +74 -0
  249. package/src/cell/libro-markdown-cell-model.ts +20 -0
  250. package/src/collapse-service.ts +69 -0
  251. package/src/command/document-commands.ts +18 -0
  252. package/src/command/index.ts +6 -0
  253. package/src/command/kernel-command.ts +41 -0
  254. package/src/command/libro-command-contribution.ts +1339 -0
  255. package/src/command/libro-command-register.ts +171 -0
  256. package/src/command/module.ts +9 -0
  257. package/src/command/notebook-commands.ts +498 -0
  258. package/src/components/cell-protocol.ts +22 -0
  259. package/src/components/dnd-cell-item-render.tsx +308 -0
  260. package/src/components/dnd-component/custom-drag-layer.tsx +145 -0
  261. package/src/components/dnd-component/default-dnd-content.tsx +275 -0
  262. package/src/components/dnd-component/dnd-context.tsx +28 -0
  263. package/src/components/dnd-component/dnd-list.tsx +116 -0
  264. package/src/components/dnd-component/index.tsx +4 -0
  265. package/src/components/index.ts +5 -0
  266. package/src/components/libro-component.tsx +30 -0
  267. package/src/components/libro-side-toolbar-menu.tsx +82 -0
  268. package/src/components/libro-view-header.tsx +33 -0
  269. package/src/configuration/libro-configuration-contribution.ts +29 -0
  270. package/src/configuration/libro-configuration.ts +88 -0
  271. package/src/content/index.ts +4 -0
  272. package/src/content/libro-content-contribution.ts +17 -0
  273. package/src/content/libro-content-module.ts +9 -0
  274. package/src/content/libro-content-protocol.ts +8 -0
  275. package/src/content/libro-content-service.ts +30 -0
  276. package/src/index.less +682 -0
  277. package/src/index.tsx +19 -0
  278. package/src/libro-context-key.ts +80 -0
  279. package/src/libro-keybind-registry.ts +43 -0
  280. package/src/libro-model.ts +614 -0
  281. package/src/libro-protocol.ts +322 -0
  282. package/src/libro-service.ts +121 -0
  283. package/src/libro-view-tracker.ts +9 -0
  284. package/src/libro-view.tsx +1399 -0
  285. package/src/material-from-designer.tsx +457 -0
  286. package/src/module.ts +90 -0
  287. package/src/output/index.ts +5 -0
  288. package/src/output/output-area.tsx +255 -0
  289. package/src/output/output-contribution.ts +18 -0
  290. package/src/output/output-model.tsx +66 -0
  291. package/src/output/output-module.ts +10 -0
  292. package/src/output/output-protocol.ts +164 -0
  293. package/src/slot/index.ts +4 -0
  294. package/src/slot/libro-slot-manager.ts +37 -0
  295. package/src/slot/libro-slot-protocol.ts +28 -0
  296. package/src/slot/libro-slot-view.tsx +51 -0
  297. package/src/slot/module.ts +9 -0
  298. package/src/theme/libro-color-registry.ts +371 -0
  299. package/src/toolbar/change-cell-to-selector.tsx +58 -0
  300. package/src/toolbar/hide-all-selector.tsx +126 -0
  301. package/src/toolbar/index.less +59 -0
  302. package/src/toolbar/index.ts +3 -0
  303. package/src/toolbar/libro-toolbar-protocol.ts +4 -0
  304. package/src/toolbar/libro-toolbar.tsx +192 -0
  305. package/src/toolbar/module.ts +11 -0
  306. package/src/toolbar/restart-clear-outputs-contribution.tsx +10 -0
  307. package/src/toolbar/restart-clear-outputs-modal.tsx +37 -0
  308. package/src/toolbar/save-icon.tsx +47 -0
  309. package/src/toolbar/shutdown-contribution.tsx +10 -0
  310. package/src/toolbar/shutdown-modal.tsx +37 -0
  311. package/src/toolbar/side-toolar-more-select.tsx +172 -0
  312. package/src/typings/index.d.ts +1 -0
@@ -0,0 +1,1399 @@
1
+ import { ToTopOutlined } from '@ant-design/icons';
2
+ import { concatMultilineString } from '@difizen/libro-common';
3
+ import { ConfigurationService, useConfigurationValue } from '@difizen/mana-app';
4
+ import { getOrigin, prop, watch } from '@difizen/mana-app';
5
+ import { Deferred, Disposable, DisposableCollection, Emitter } from '@difizen/mana-app';
6
+ import {
7
+ Slot,
8
+ ViewManager,
9
+ BaseView,
10
+ view,
11
+ ViewInstance,
12
+ ViewOption,
13
+ } from '@difizen/mana-app';
14
+ import { inject, transient, useInject, equals } from '@difizen/mana-app';
15
+ import { Spin, Button, BackTop, FloatButton } from 'antd';
16
+ import type { FC, ForwardRefExoticComponent, RefAttributes } from 'react';
17
+ import { useEffect, useRef, useCallback, memo, forwardRef } from 'react';
18
+ import { v4 } from 'uuid';
19
+
20
+ import {
21
+ CellService,
22
+ ExecutableCellModel,
23
+ ExecutableCellView,
24
+ EditorCellView,
25
+ } from './cell/index.js';
26
+ import { CollapseServiceFactory } from './collapse-service.js';
27
+ import type { CollapseService } from './collapse-service.js';
28
+ import {
29
+ CustomDragLayer,
30
+ DefaultDndContent,
31
+ DndCellItemRender,
32
+ DndContext,
33
+ DndList,
34
+ } from './components/index.js';
35
+ import { LibroViewHeader } from './components/libro-view-header.js';
36
+ import {
37
+ AutoInsertWhenNoCell,
38
+ EnterEditModeWhenAddCell,
39
+ HeaderToolbarVisible,
40
+ RightContentFixed,
41
+ } from './configuration/libro-configuration.js';
42
+ import { LirboContextKey } from './libro-context-key.js';
43
+ import { LibroModel } from './libro-model.js';
44
+ import { NotebookService, notebookViewFactoryId } from './libro-protocol.js';
45
+ import type {
46
+ CellOptions,
47
+ CellView,
48
+ DndContentProps,
49
+ DndItemProps,
50
+ NotebookView,
51
+ NotebookModel,
52
+ NotebookOption,
53
+ } from './libro-protocol.js';
54
+ import { LibroService } from './libro-service.js';
55
+ import { LibroSlotManager, LibroSlotView } from './slot/index.js';
56
+ import './index.less';
57
+
58
+ export const LibroContentComponent = memo(function LibroContentComponent() {
59
+ const libroSlotManager = useInject(LibroSlotManager);
60
+ const ref = useRef<HTMLDivElement>(null);
61
+ const libroViewTopRef = useRef<HTMLDivElement>(null);
62
+ const libroViewRightContentRef = useRef<HTMLDivElement>(null);
63
+ const libroViewLeftContentRef = useRef<HTMLDivElement>(null);
64
+ const libroViewContentRef = useRef<HTMLDivElement>(null);
65
+ const instance = useInject<LibroView>(ViewInstance);
66
+ const HeaderRender = getOrigin(instance.headerRender);
67
+ const [headerVisible] = useConfigurationValue(HeaderToolbarVisible);
68
+ const [rightContentFixed] = useConfigurationValue(RightContentFixed);
69
+
70
+ const handleScroll = useCallback(() => {
71
+ const cellRightToolbar = instance.container?.current?.getElementsByClassName(
72
+ 'libro-cell-right-toolbar',
73
+ )[instance.model.activeIndex] as HTMLDivElement;
74
+ const activeCellOffsetY =
75
+ instance.activeCell?.container?.current?.getBoundingClientRect().y;
76
+ const activeCellOffsetRight =
77
+ instance.activeCell?.container?.current?.getBoundingClientRect().right;
78
+ const activeOutput =
79
+ ExecutableCellView.is(instance.activeCell) && instance.activeCell?.outputArea;
80
+ const activeOutputOffsetBottom =
81
+ activeOutput && activeOutput.length > 0
82
+ ? activeOutput?.outputs[
83
+ activeOutput.length - 1
84
+ ].container?.current?.getBoundingClientRect().bottom
85
+ : instance.activeCell?.container?.current?.getBoundingClientRect().bottom;
86
+ const libroViewTopOffsetBottom =
87
+ libroViewTopRef.current?.getBoundingClientRect().bottom;
88
+
89
+ if (!cellRightToolbar) {
90
+ return;
91
+ }
92
+ if (
93
+ activeCellOffsetY !== undefined &&
94
+ libroViewTopOffsetBottom !== undefined &&
95
+ activeOutputOffsetBottom !== undefined &&
96
+ activeCellOffsetY <= libroViewTopOffsetBottom + 12 &&
97
+ activeOutputOffsetBottom >= libroViewTopOffsetBottom &&
98
+ activeCellOffsetRight !== undefined
99
+ ) {
100
+ cellRightToolbar.style.cssText = `position:fixed;top:${
101
+ libroViewTopOffsetBottom + 12
102
+ }px;left:${activeCellOffsetRight + 44 - 34}px;right:unset;`;
103
+ } else {
104
+ cellRightToolbar.style.cssText = ' position: absolute;top: 0px;right: -44px;';
105
+ }
106
+ }, [instance]);
107
+
108
+ useEffect(() => {
109
+ if (
110
+ rightContentFixed &&
111
+ libroViewRightContentRef.current &&
112
+ libroViewContentRef.current &&
113
+ libroViewLeftContentRef.current
114
+ ) {
115
+ libroViewContentRef.current.style.cssText = 'display: block;';
116
+ libroViewRightContentRef.current.style.cssText =
117
+ 'position: absolute;top:44px;right:20px';
118
+ libroViewLeftContentRef.current.style.cssText = 'padding-right: 80px;';
119
+ }
120
+ }, [rightContentFixed]);
121
+
122
+ return (
123
+ <>
124
+ {headerVisible && (
125
+ <div className="libro-view-top" ref={libroViewTopRef}>
126
+ <HeaderRender />
127
+ </div>
128
+ )}
129
+ <div
130
+ className="libro-view-content"
131
+ onScroll={handleScroll}
132
+ ref={libroViewContentRef}
133
+ >
134
+ <div className="libro-view-content-left" ref={libroViewLeftContentRef}>
135
+ <DndContext>
136
+ <CustomDragLayer />
137
+ <DndList libroView={instance} ref={ref}>
138
+ <Slot
139
+ name={libroSlotManager.getSlotName(instance, 'list')}
140
+ slotView={LibroSlotView}
141
+ />
142
+ </DndList>
143
+ </DndContext>
144
+ </div>
145
+ <div className="libro-view-content-right" ref={libroViewRightContentRef}>
146
+ {/* {tocVisible && instance.toc && <ViewRender view={instance.toc} />} */}
147
+ <Slot
148
+ name={libroSlotManager.getSlotName(instance, 'right')}
149
+ slotView={LibroSlotView}
150
+ />
151
+ </div>
152
+ <FloatButton.BackTop target={() => libroViewContentRef.current || document}>
153
+ <div className="libro-totop-button">
154
+ <Button shape="circle" icon={<ToTopOutlined />} />
155
+ </div>
156
+ </FloatButton.BackTop>
157
+ <Slot
158
+ name={libroSlotManager.getSlotName(instance, 'content')}
159
+ slotView={LibroSlotView}
160
+ />
161
+ </div>
162
+ <Slot
163
+ name={libroSlotManager.getSlotName(instance, 'container')}
164
+ slotView={LibroSlotView}
165
+ />
166
+ </>
167
+ );
168
+ });
169
+
170
+ export const LibroRender = forwardRef<HTMLDivElement>(
171
+ function LibroRender(_props, ref) {
172
+ const instance = useInject<LibroView>(ViewInstance);
173
+ const libroService = useInject(LibroService);
174
+
175
+ const handleMouseDown = useCallback(
176
+ (e: React.MouseEvent<HTMLDivElement>) => {
177
+ if (e.defaultPrevented) {
178
+ return;
179
+ }
180
+ if (!instance.model.commandMode) {
181
+ instance.enterCommandMode(true);
182
+ }
183
+ },
184
+ [instance],
185
+ );
186
+
187
+ const handFocus = useCallback(
188
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
189
+ (_e: React.FocusEvent<HTMLDivElement>) => {
190
+ if (!equals(libroService.active, instance)) {
191
+ libroService.active = instance;
192
+ }
193
+ if (!equals(libroService.focus, instance)) {
194
+ libroService.focus = instance;
195
+ }
196
+ },
197
+ [instance, libroService],
198
+ );
199
+
200
+ const handBlur = useCallback(
201
+ (e: React.FocusEvent<HTMLDivElement>) => {
202
+ if (typeof ref !== 'function' && !ref?.current?.contains(e.relatedTarget)) {
203
+ instance.enterCommandMode(false);
204
+ libroService.focus = undefined;
205
+ instance.onBlurEmitter.fire('');
206
+ }
207
+ },
208
+ [instance, libroService, ref],
209
+ );
210
+
211
+ return (
212
+ <div
213
+ className={`${instance.model.libroViewClass} libro-view`}
214
+ onMouseDown={handleMouseDown}
215
+ ref={ref}
216
+ tabIndex={0}
217
+ onFocus={handFocus}
218
+ onBlur={handBlur}
219
+ >
220
+ <LibroContentComponent />
221
+ </div>
222
+ );
223
+ },
224
+ );
225
+
226
+ @transient()
227
+ @view(notebookViewFactoryId)
228
+ export class LibroView extends BaseView implements NotebookView {
229
+ protected override toDispose = new DisposableCollection();
230
+ model: NotebookModel;
231
+ headerRender: FC = LibroViewHeader;
232
+ loadingRender: FC = () => (
233
+ <div className="libro-loading">
234
+ <Spin />
235
+ </div>
236
+ );
237
+ dndContentRender: FC<DndContentProps> = DefaultDndContent;
238
+ dndItemRender: ForwardRefExoticComponent<
239
+ DndItemProps & RefAttributes<HTMLDivElement>
240
+ > = DndCellItemRender;
241
+ protected onCellCreateEmitter: Emitter<CellView> = new Emitter();
242
+ get onCellCreate() {
243
+ return this.onCellCreateEmitter.event;
244
+ }
245
+
246
+ onBlurEmitter: Emitter = new Emitter();
247
+ get onBlur() {
248
+ return this.onBlurEmitter.event;
249
+ }
250
+
251
+ @inject(CellService) cellService: CellService;
252
+ @inject(LibroService) libroService: LibroService;
253
+ @inject(LibroSlotManager) libroSlotManager: LibroSlotManager;
254
+ @inject(LirboContextKey) contextKey: LirboContextKey;
255
+
256
+ @inject(ViewManager) protected viewManager: ViewManager;
257
+ @inject(ConfigurationService) protected configurationService: ConfigurationService;
258
+ protected notebookService: NotebookService;
259
+ protected collapseService: CollapseService;
260
+ isDragging = false;
261
+
262
+ @prop()
263
+ collapserVisible = false;
264
+
265
+ get hasModal() {
266
+ return this.model.cells.some((item) => item.hasModal);
267
+ }
268
+
269
+ @prop()
270
+ saving?: boolean;
271
+
272
+ onSaveEmitter: Emitter<boolean> = new Emitter();
273
+ get onSave() {
274
+ return this.onSaveEmitter.event;
275
+ }
276
+
277
+ runCellEmitter: Emitter<CellView> = new Emitter();
278
+ get onRunCell() {
279
+ return this.runCellEmitter.event;
280
+ }
281
+
282
+ protected initializedDefer = new Deferred<void>();
283
+
284
+ get initialized() {
285
+ return this.initializedDefer.promise;
286
+ }
287
+
288
+ constructor(
289
+ @inject(ViewOption) options: NotebookOption,
290
+ @inject(CollapseServiceFactory) collapseServiceFactory: CollapseServiceFactory,
291
+ @inject(NotebookService) notebookService: NotebookService,
292
+ ) {
293
+ super();
294
+ if (options.id) {
295
+ this.id = options.id;
296
+ }
297
+ this.notebookService = notebookService;
298
+ this.model = this.notebookService.getOrCreateModel(options);
299
+ this.collapseService = collapseServiceFactory({ view: this });
300
+ this.collapserVisible = this.collapseService.collapserVisible;
301
+
302
+ this.initialize();
303
+ this.initView();
304
+ }
305
+
306
+ initView() {
307
+ // this.configurationService.get(TOCVisible).then(() => {
308
+ // this.viewManager.getOrCreateView(MarkdownCellTocView, { id: this.id }).then(toc => {
309
+ // this.toc = toc;
310
+ // toc.parent = this;
311
+ // });
312
+ // });
313
+ // this.viewManager
314
+ // .getOrCreateView(LibroKeybindInstrutionsView, { id: 'libro-keybind-instructions' })
315
+ // .then(keybindInstrutions => {
316
+ // this.keybindInstrutionsView = keybindInstrutions;
317
+ // });
318
+ }
319
+
320
+ async initialize() {
321
+ this.model.isInitialized = false;
322
+ const options = await this.model.initialize();
323
+ this.configurationService
324
+ .get(AutoInsertWhenNoCell)
325
+ .then((value) => {
326
+ const isAutoInsertWhenNoCell = value;
327
+ if (isAutoInsertWhenNoCell && options.length === 0) {
328
+ this.addCell(
329
+ { id: v4(), cell: { cell_type: 'code', source: '', metadata: {} } },
330
+ 0,
331
+ );
332
+ }
333
+ return;
334
+ })
335
+ .catch(() => {
336
+ //
337
+ });
338
+ await this.insertCells(options);
339
+ // 第一次insert不需要历史
340
+ setTimeout(() => {
341
+ this.model.sharedModel.clearUndoHistory();
342
+ // 未初始化完成不做渲染,防止重复渲染多次
343
+ this.model.isInitialized = true;
344
+ this.configurationService
345
+ .get(EnterEditModeWhenAddCell)
346
+ .then((value) => {
347
+ if (value) {
348
+ this.enterEditMode();
349
+ } else {
350
+ this.enterCommandMode(true);
351
+ }
352
+ return;
353
+ })
354
+ .catch(() => {
355
+ //
356
+ });
357
+ this.toDispose.push(
358
+ watch(this.model, 'cells', () => {
359
+ this.model.onChange?.();
360
+ }),
361
+ );
362
+ this.initializedDefer.resolve();
363
+ }, 0);
364
+ }
365
+
366
+ override view = LibroRender;
367
+
368
+ override onViewMount = () => {
369
+ this.libroService.active = this;
370
+ this.libroSlotManager.setup(this);
371
+ };
372
+
373
+ override onViewUnmount = () => {
374
+ if (equals(this.libroService.active, this)) {
375
+ this.libroService.active = undefined;
376
+ }
377
+ };
378
+
379
+ async getCellViewByOption(option: CellOptions) {
380
+ const toDispose = new DisposableCollection();
381
+ option.cell.metadata.trusted = this.model.trusted;
382
+ const cellView = await this.cellService.getOrCreateView(option, this.id);
383
+ cellView.parent = this;
384
+ this.onCellCreateEmitter.fire(cellView);
385
+ toDispose.push(
386
+ Disposable.create(() => {
387
+ this.deleteCell(cellView);
388
+ }),
389
+ );
390
+ const disposable = cellView.onDisposed(() => {
391
+ toDispose.dispose();
392
+ });
393
+ toDispose.push(disposable);
394
+ return cellView;
395
+ }
396
+
397
+ focus = () => {
398
+ if (this.container?.current?.contains(document.activeElement)) {
399
+ return;
400
+ }
401
+ this.container?.current?.focus();
402
+ };
403
+
404
+ insertCells = async (options: CellOptions[], position?: number): Promise<void> => {
405
+ const cellView = await Promise.all(
406
+ options.map(async (option) => {
407
+ const newView = await this.getCellViewByOption(option);
408
+ return newView;
409
+ }),
410
+ );
411
+
412
+ this.model.insertCells(cellView, position);
413
+ };
414
+
415
+ selectCell = (cell: CellView) => {
416
+ this.model.active = cell;
417
+ this.model.selectCell(cell);
418
+ };
419
+
420
+ addCell = async (option: CellOptions, position?: number) => {
421
+ const cellView = await this.getCellViewByOption(option);
422
+ this.model.addCell(cellView, position);
423
+ };
424
+
425
+ addCellAbove = async (option: CellOptions, position?: number) => {
426
+ const cellView = await this.getCellViewByOption(option);
427
+ this.model.addCell(cellView, position, 'above');
428
+ };
429
+
430
+ get activeCell(): CellView | undefined {
431
+ return this.model.active;
432
+ }
433
+
434
+ findCellIndex = (cell: CellView) => {
435
+ const cellList = this.model.getCells();
436
+ if (cell) {
437
+ const cellIndex = cellList.findIndex((item) => {
438
+ return item.id === cell.id;
439
+ });
440
+ return cellIndex;
441
+ }
442
+ return -1;
443
+ };
444
+
445
+ deleteCell = (cell: CellView) => {
446
+ const deleteIndex = this.model.getCells().findIndex((item) => {
447
+ return equals(item, cell);
448
+ });
449
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
450
+ const startIndex = this.model.getCells().findIndex((item) => {
451
+ return equals(item, this.model.selections[0]);
452
+ });
453
+ const endIndex = startIndex + this.model.selections.length;
454
+ getOrigin(this.model.sharedModel).transact(() => {
455
+ getOrigin(this.model.sharedModel).deleteCellRange(startIndex, endIndex);
456
+ });
457
+ this.configurationService
458
+ .get(AutoInsertWhenNoCell)
459
+ .then((value) => {
460
+ const isAutoInsertWhenNoCell = value;
461
+ if (isAutoInsertWhenNoCell && this.model.cells.length === 0) {
462
+ this.addCell(
463
+ { id: v4(), cell: { cell_type: 'code', source: '', metadata: {} } },
464
+ 0,
465
+ );
466
+ }
467
+ return;
468
+ })
469
+ .catch(() => {
470
+ //
471
+ });
472
+ } else {
473
+ if (deleteIndex > -1) {
474
+ this.model.deletedCells.push(cell);
475
+ this.model.deleteCell(cell.id);
476
+ cell.isAttached = false;
477
+ }
478
+ this.configurationService
479
+ .get(AutoInsertWhenNoCell)
480
+ .then((value) => {
481
+ const isAutoInsertWhenNoCell = value;
482
+ if (isAutoInsertWhenNoCell && this.model.cells.length === 0) {
483
+ this.addCell(
484
+ { id: v4(), cell: { cell_type: 'code', source: '', metadata: {} } },
485
+ 0,
486
+ );
487
+ }
488
+ return;
489
+ })
490
+ .catch(() => {
491
+ //
492
+ });
493
+ }
494
+ };
495
+
496
+ executeCellRun(cell: CellView) {
497
+ this.runCellEmitter.fire(cell);
498
+ return cell.run();
499
+ }
500
+
501
+ runCells = async (cells: CellView[]) => {
502
+ if (this.model.canRun && !this.model.canRun()) {
503
+ return false;
504
+ }
505
+
506
+ return Promise.all(
507
+ cells.map((cell) => {
508
+ if (ExecutableCellModel.is(cell.model)) {
509
+ return this.executeCellRun(cell);
510
+ }
511
+ return undefined;
512
+ }),
513
+ )
514
+ .then((resultList) => {
515
+ return resultList.every((item) => !!item);
516
+ })
517
+ .catch((reason: any) => {
518
+ if (reason.message.startsWith('KernelReplyNotOK')) {
519
+ return undefined;
520
+ } else {
521
+ throw reason;
522
+ }
523
+ });
524
+ };
525
+
526
+ runAllCell = async () => {
527
+ this.runCells(this.model.cells);
528
+ };
529
+
530
+ runAllAbove = async (cell: CellView) => {
531
+ const index = this.findCellIndex(cell);
532
+ this.runCells(this.model.cells.slice(0, index));
533
+ };
534
+
535
+ runAllBelow = async (cell: CellView) => {
536
+ const index = this.findCellIndex(cell);
537
+ this.runCells(this.model.cells.slice(index));
538
+ };
539
+
540
+ runCell = async (cell: CellView) => {
541
+ this.enterCommandMode(true);
542
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
543
+ await this.runCells(this.model.selections);
544
+ } else {
545
+ await this.runCells([cell]);
546
+ }
547
+ };
548
+
549
+ runCellandSelectNext = async (cell: CellView) => {
550
+ this.enterCommandMode(true);
551
+ this.collapseCell(cell, false);
552
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
553
+ const toRunCells = this.model.selections;
554
+ const selectIndex = this.findCellIndex(
555
+ this.model.selections[this.model.selections.length - 1],
556
+ );
557
+ if (selectIndex >= 0 && selectIndex < this.model.cells.length - 1) {
558
+ this.model.selectCell(this.model.cells[selectIndex + 1]);
559
+ }
560
+ if (selectIndex === this.model.cells.length - 1) {
561
+ this.addCell(
562
+ { id: v4(), cell: { cell_type: cell.model.type, source: '', metadata: {} } },
563
+ selectIndex + 1,
564
+ )
565
+ .then(() => {
566
+ this.enterEditMode();
567
+ return;
568
+ })
569
+ .catch(() => {
570
+ //
571
+ });
572
+ }
573
+ this.runCells(toRunCells);
574
+ if (this.activeCell) {
575
+ this.model.scrollToView(this.activeCell);
576
+ }
577
+ } else {
578
+ const selectIndex = this.findCellIndex(cell);
579
+ if (selectIndex >= 0 && selectIndex < this.model.cells.length - 1) {
580
+ this.model.selectCell(this.model.cells[selectIndex + 1]);
581
+ }
582
+ if (selectIndex === this.model.cells.length - 1) {
583
+ this.addCell(
584
+ { id: v4(), cell: { cell_type: cell.model.type, source: '', metadata: {} } },
585
+ selectIndex + 1,
586
+ )
587
+ .then(() => {
588
+ this.enterEditMode();
589
+ return;
590
+ })
591
+ .catch(() => {
592
+ //
593
+ });
594
+ }
595
+ this.runCells([cell]);
596
+ if (this.activeCell) {
597
+ this.model.scrollToView(this.activeCell);
598
+ }
599
+ }
600
+ };
601
+
602
+ runCellandInsertBelow = async (cell: CellView) => {
603
+ this.enterCommandMode(true);
604
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
605
+ const insertIndex = this.findCellIndex(
606
+ this.model.selections[this.model.selections.length - 1],
607
+ );
608
+ this.addCell(
609
+ { id: v4(), cell: { cell_type: cell.model.type, source: '', metadata: {} } },
610
+ insertIndex + 1,
611
+ );
612
+ this.runCells(this.model.selections);
613
+ } else {
614
+ const insertIndex = this.findCellIndex(cell);
615
+ this.addCell(
616
+ { id: v4(), cell: { cell_type: cell.model.type, source: '', metadata: {} } },
617
+ insertIndex + 1,
618
+ );
619
+ this.runCells([cell]);
620
+ }
621
+ };
622
+
623
+ moveUpCell = (cell: CellView) => {
624
+ this.collapseCell(cell, false);
625
+ const previousCell = this.getPreviousVisibleCell(cell);
626
+ if (previousCell) {
627
+ this.collapseCell(previousCell, false);
628
+ }
629
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
630
+ for (const selectedCell of this.model.selections) {
631
+ const selectIndex = this.findCellIndex(selectedCell);
632
+ if (selectIndex === 0) {
633
+ return;
634
+ }
635
+ this.model.exchangeCells(this.model.selections, selectIndex - 1);
636
+ }
637
+ } else {
638
+ const sourceIndex = this.findCellIndex(cell);
639
+ if (sourceIndex > -1) {
640
+ this.model.exchangeCell(sourceIndex, sourceIndex - 1);
641
+ }
642
+ }
643
+ };
644
+
645
+ moveDownCell = (cell: CellView) => {
646
+ this.collapseCell(cell, false);
647
+ const nextCell = this.getNextVisibleCell(cell);
648
+ if (nextCell) {
649
+ this.collapseCell(nextCell, false);
650
+ }
651
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
652
+ for (let i = this.model.selections.length - 1; i > -1; i--) {
653
+ const selectIndex = this.findCellIndex(this.model.selections[i]);
654
+ if (selectIndex === this.model.cells.length - 1) {
655
+ return;
656
+ }
657
+ this.model.exchangeCells(this.model.selections, selectIndex + 1);
658
+ }
659
+ } else {
660
+ const sourceIndex = this.findCellIndex(cell);
661
+ if (sourceIndex > -1) {
662
+ this.model.exchangeCell(sourceIndex, sourceIndex + 1);
663
+ }
664
+ }
665
+ };
666
+
667
+ getPreviousVisibleCell(cell: CellView) {
668
+ const currentIndex = this.findCellIndex(cell);
669
+ return this.model.cells
670
+ .slice()
671
+ .reverse()
672
+ .find(
673
+ (item, index) =>
674
+ index > this.model.cells.length - currentIndex - 1 &&
675
+ item.collapsedHidden === false,
676
+ );
677
+ }
678
+
679
+ getNextVisibleCell(cell: CellView) {
680
+ const currentIndex = this.findCellIndex(cell);
681
+ return this.model.cells.find(
682
+ (item, index) => index > currentIndex && item.collapsedHidden === false,
683
+ );
684
+ }
685
+
686
+ copyCell = (cell: CellView) => {
687
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
688
+ this.model.clipboard = this.model.selections;
689
+ this.model.selections = [];
690
+ } else {
691
+ this.model.clipboard = cell;
692
+ }
693
+ this.model.lastClipboardInteraction = 'copy';
694
+ };
695
+
696
+ cutCell = (cell: CellView) => {
697
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
698
+ this.model.clipboard = this.model.selections;
699
+ this.model.selections = [];
700
+ for (const cutCell of this.model.clipboard) {
701
+ this.deleteCell(cutCell);
702
+ }
703
+ } else {
704
+ this.model.clipboard = cell;
705
+ this.deleteCell(cell);
706
+ }
707
+ this.model.lastClipboardInteraction = 'cut';
708
+ };
709
+
710
+ pasteCell = (cell: CellView) => {
711
+ let pasteIndex = this.model.getCells().findIndex((item) => {
712
+ return equals(item, cell);
713
+ });
714
+ const pasteCells = getOrigin(this.model.clipboard);
715
+ if (!this.model.lastClipboardInteraction) {
716
+ return;
717
+ }
718
+ if (!pasteCells) {
719
+ return;
720
+ }
721
+ if (this.model.lastClipboardInteraction === 'copy') {
722
+ if (Array.isArray(pasteCells)) {
723
+ for (const pasteCell of pasteCells) {
724
+ const cellOptions = pasteCell.toJSONWithoutId();
725
+ this.addCell({ id: v4(), cell: cellOptions }, pasteIndex + 1);
726
+ pasteIndex++;
727
+ }
728
+ } else {
729
+ const cellOptions = pasteCells.toJSONWithoutId();
730
+ this.addCell({ id: v4(), cell: cellOptions }, pasteIndex + 1);
731
+ }
732
+ } else {
733
+ if (Array.isArray(pasteCells)) {
734
+ for (const pasteCell of pasteCells) {
735
+ this.model.addCell(pasteCell, pasteIndex + 1);
736
+ this.model.deletedCells.splice(this.model.deletedCells.indexOf(pasteCell), 1);
737
+ pasteIndex++;
738
+ }
739
+ } else {
740
+ this.model.addCell(pasteCells, pasteIndex + 1);
741
+ this.model.deletedCells.splice(this.model.deletedCells.indexOf(pasteCells), 1);
742
+ }
743
+ this.model.lastClipboardInteraction = '';
744
+ }
745
+ };
746
+
747
+ pasteCellAbove = (cell: CellView) => {
748
+ let pasteIndex = this.model.getCells().findIndex((item) => {
749
+ return equals(item, cell);
750
+ });
751
+ const pasteCells = getOrigin(this.model.clipboard);
752
+ if (!this.model.lastClipboardInteraction) {
753
+ return;
754
+ }
755
+ if (!pasteCells) {
756
+ return;
757
+ }
758
+ if (this.model.lastClipboardInteraction === 'copy') {
759
+ if (Array.isArray(pasteCells)) {
760
+ for (const pasteCell of pasteCells) {
761
+ const cellOptions = pasteCell.toJSONWithoutId();
762
+ this.addCell({ id: v4(), cell: cellOptions }, pasteIndex);
763
+ pasteIndex++;
764
+ }
765
+ } else {
766
+ const cellOptions = pasteCells.toJSONWithoutId();
767
+ this.addCell({ id: v4(), cell: cellOptions }, pasteIndex);
768
+ }
769
+ } else {
770
+ if (Array.isArray(pasteCells)) {
771
+ for (const pasteCell of pasteCells) {
772
+ this.model.addCell(pasteCell, pasteIndex);
773
+ this.model.deletedCells.splice(this.model.deletedCells.indexOf(pasteCell), 1);
774
+ pasteIndex++;
775
+ }
776
+ } else {
777
+ this.model.addCell(pasteCells, pasteIndex);
778
+ this.model.deletedCells.splice(this.model.deletedCells.indexOf(pasteCells), 1);
779
+ }
780
+ this.model.lastClipboardInteraction = '';
781
+ }
782
+ };
783
+
784
+ invertCell = async (cell: CellView, type: string) => {
785
+ const cellIndex = this.model.getCells().findIndex((item) => {
786
+ return equals(item, cell);
787
+ });
788
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
789
+ for (const selectedCell of this.model.selections) {
790
+ const cellOptions: CellOptions = {
791
+ cell: { cell_type: type, source: selectedCell.toJSON().source, metadata: {} },
792
+ };
793
+ const cellView = await this.getCellViewByOption(cellOptions);
794
+ this.model.invertCell(cellView, cellIndex);
795
+ }
796
+ } else {
797
+ const cellOptions: CellOptions = {
798
+ cell: { cell_type: type, source: cell.toJSON().source, metadata: {} },
799
+ };
800
+ const cellView = await this.getCellViewByOption(cellOptions);
801
+ this.model.invertCell(cellView, cellIndex);
802
+ }
803
+ };
804
+
805
+ clearOutputs = (cell: CellView) => {
806
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
807
+ for (const selectedCell of this.model.selections) {
808
+ if (
809
+ ExecutableCellView.is(selectedCell) &&
810
+ ExecutableCellModel.is(selectedCell.model)
811
+ ) {
812
+ selectedCell.clearExecution();
813
+ selectedCell.model.hasOutputHidden = false;
814
+ }
815
+ }
816
+ } else {
817
+ if (ExecutableCellView.is(cell) && ExecutableCellModel.is(cell.model)) {
818
+ cell.clearExecution();
819
+ cell.model.hasOutputHidden = false;
820
+ }
821
+ }
822
+ };
823
+
824
+ clearAllOutputs = () => {
825
+ for (const cell of this.model.cells) {
826
+ if (ExecutableCellView.is(cell) && ExecutableCellModel.is(cell.model)) {
827
+ cell.clearExecution();
828
+ cell.model.hasOutputHidden = false;
829
+ }
830
+ }
831
+ };
832
+
833
+ hideCellCode = (cell: CellView) => {
834
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
835
+ for (const selectedCell of this.model.selections) {
836
+ selectedCell.hasInputHidden = true;
837
+ }
838
+ } else {
839
+ cell.hasInputHidden = true;
840
+ }
841
+ };
842
+
843
+ hideOrShowCellCode = (cell: CellView) => {
844
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
845
+ for (const selectedCell of this.model.selections) {
846
+ selectedCell.hasInputHidden = !selectedCell.hasInputHidden;
847
+ }
848
+ } else {
849
+ cell.hasInputHidden = !cell.hasInputHidden;
850
+ }
851
+ };
852
+
853
+ hideOutputs = (cell: CellView) => {
854
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
855
+ for (const selectedCell of this.model.selections) {
856
+ if (ExecutableCellModel.is(selectedCell.model)) {
857
+ selectedCell.model.hasOutputHidden = true;
858
+ }
859
+ }
860
+ } else {
861
+ if (ExecutableCellModel.is(cell.model)) {
862
+ cell.model.hasOutputHidden = true;
863
+ }
864
+ }
865
+ };
866
+
867
+ hideOrShowOutputs = (cell: CellView) => {
868
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
869
+ for (const selectedCell of this.model.selections) {
870
+ if (ExecutableCellModel.is(selectedCell.model)) {
871
+ selectedCell.model.hasOutputHidden = !selectedCell.model.hasOutputHidden;
872
+ }
873
+ }
874
+ } else {
875
+ if (ExecutableCellModel.is(cell.model)) {
876
+ cell.model.hasOutputHidden = !cell.model.hasOutputHidden;
877
+ }
878
+ }
879
+ };
880
+
881
+ hideAllOutputs = () => {
882
+ for (const cell of this.model.cells) {
883
+ if (ExecutableCellModel.is(cell.model)) {
884
+ cell.model.hasOutputHidden = true;
885
+ }
886
+ }
887
+ };
888
+
889
+ hideAllCellCode = () => {
890
+ for (const cell of this.model.cells) {
891
+ cell.hasInputHidden = true;
892
+ }
893
+ };
894
+
895
+ showCellCode = (cell: CellView) => {
896
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
897
+ for (const selectedCell of this.model.selections) {
898
+ selectedCell.hasInputHidden = false;
899
+ }
900
+ } else {
901
+ cell.hasInputHidden = false;
902
+ }
903
+ };
904
+
905
+ showAllCellCode = () => {
906
+ for (const cell of this.model.cells) {
907
+ cell.hasInputHidden = false;
908
+ }
909
+ };
910
+
911
+ showCellOutputs = (cell: CellView) => {
912
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
913
+ for (const selectedCell of this.model.selections) {
914
+ if (ExecutableCellModel.is(selectedCell.model)) {
915
+ selectedCell.model.hasOutputHidden = false;
916
+ }
917
+ }
918
+ } else {
919
+ if (ExecutableCellModel.is(cell.model)) {
920
+ cell.model.hasOutputHidden = false;
921
+ }
922
+ }
923
+ };
924
+
925
+ showAllCellOutputs = () => {
926
+ for (const cell of this.model.cells) {
927
+ if (ExecutableCellModel.is(cell.model)) {
928
+ cell.model.hasOutputHidden = false;
929
+ }
930
+ }
931
+ };
932
+
933
+ /**
934
+ * Whether a cell is selected.
935
+ */
936
+ isSelected(cell: CellView): boolean {
937
+ if (this.activeCell === cell) {
938
+ return true;
939
+ }
940
+ if (this.model.selections.length !== 0) {
941
+ return this.model.selections.findIndex((item) => item.id === cell.id) >= 0
942
+ ? true
943
+ : false;
944
+ }
945
+ return false;
946
+ }
947
+
948
+ extendSelectionAbove = () => {
949
+ if (this.activeCell) {
950
+ const previousCell = this.getPreviousVisibleCell(this.activeCell);
951
+ if (previousCell) {
952
+ this.collapseCell(previousCell, false);
953
+ }
954
+ const activeIndex = this.findCellIndex(this.activeCell);
955
+ if (this.findCellIndex(this.activeCell) > 0) {
956
+ this.extendContiguousSelectionTo(activeIndex - 1);
957
+ }
958
+ this.model.scrollToView(this.activeCell);
959
+ }
960
+ };
961
+
962
+ extendSelectionToTop = () => {
963
+ if (this.activeCell) {
964
+ if (this.findCellIndex(this.activeCell) > 0) {
965
+ this.extendContiguousSelectionTo(0);
966
+ }
967
+ this.model.scrollToView(this.activeCell);
968
+ }
969
+ };
970
+
971
+ extendSelectionBelow = () => {
972
+ if (this.activeCell) {
973
+ const nextCell = this.getNextVisibleCell(this.activeCell);
974
+ if (nextCell) {
975
+ this.collapseCell(nextCell, false);
976
+ }
977
+ const activeIndex = this.findCellIndex(this.activeCell);
978
+ if (this.findCellIndex(this.activeCell) >= 0) {
979
+ this.extendContiguousSelectionTo(activeIndex + 1);
980
+ }
981
+ this.model.scrollToView(this.activeCell);
982
+ }
983
+ };
984
+
985
+ extendSelectionToBottom = () => {
986
+ if (this.activeCell) {
987
+ if (this.findCellIndex(this.activeCell) > 0) {
988
+ this.extendContiguousSelectionTo(this.model.cells.length - 1);
989
+ }
990
+ this.model.scrollToView(this.activeCell);
991
+ }
992
+ };
993
+
994
+ /**
995
+ * Move the head of an existing contiguous selection to extend the selection.
996
+ *
997
+ * @param index - The new head of the existing selection.
998
+ *
999
+ * #### Notes
1000
+ * If there is no existing selection, the active cell is considered an
1001
+ * existing one-cell selection.
1002
+ *
1003
+ * If the new selection is a single cell, that cell becomes the active cell
1004
+ * and all cells are deselected.
1005
+ *
1006
+ * There is no change if there are no cells (i.e., activeCellIndex is -1).
1007
+ */
1008
+ extendContiguousSelectionTo(index: number): void {
1009
+ let selectIndex = index;
1010
+ let { head, anchor } = this.getContiguousSelection();
1011
+ if (this.activeCell) {
1012
+ // Handle the case of no current selection.
1013
+ if (anchor === null || head === null) {
1014
+ if (selectIndex === this.model.activeIndex) {
1015
+ // Already collapsed selection, nothing more to do.
1016
+ return;
1017
+ }
1018
+
1019
+ // We will start a new selection below.
1020
+ head = this.model.activeIndex;
1021
+ anchor = this.model.activeIndex;
1022
+ }
1023
+ // Move the active cell. We do this before the collapsing shortcut below.
1024
+ if (this.model.cells[selectIndex]) {
1025
+ this.model.active = this.model.cells[selectIndex];
1026
+ this.model.activeIndex = selectIndex;
1027
+ }
1028
+ // Make sure the index is valid, according to the rules for setting and clipping the
1029
+ // active cell index. This may change the index.
1030
+ selectIndex = this.model.activeIndex;
1031
+
1032
+ // Collapse the selection if it is only the active cell.
1033
+ if (selectIndex === anchor) {
1034
+ // this.deselectAll();
1035
+ this.model.selections = [];
1036
+ return;
1037
+ }
1038
+ this.model.selections = this.model.cells.slice(
1039
+ Math.min(anchor, selectIndex),
1040
+ Math.max(anchor, selectIndex) + 1,
1041
+ );
1042
+ }
1043
+ }
1044
+
1045
+ /**
1046
+ * Get the head and anchor of a contiguous cell selection.
1047
+ *
1048
+ * The head of a contiguous selection is always the active cell.
1049
+ *
1050
+ * If there are no cells selected, `{head: null, anchor: null}` is returned.
1051
+ *
1052
+ * Throws an error if the currently selected cells do not form a contiguous
1053
+ * selection.
1054
+ */
1055
+ getContiguousSelection():
1056
+ | { head: number; anchor: number }
1057
+ | { head: null; anchor: null } {
1058
+ if (this.model.selections.length !== 0 && this.activeCell) {
1059
+ const first = this.findCellIndex(this.model.selections[0]);
1060
+ const last = this.findCellIndex(
1061
+ this.model.selections[this.model.selections.length - 1],
1062
+ );
1063
+ // Check that the active cell is one of the endpoints of the selection.
1064
+ const activeIndex = this.findCellIndex(this.activeCell);
1065
+ if (first !== activeIndex && last !== activeIndex) {
1066
+ throw new Error('Active cell not at endpoint of selection');
1067
+ }
1068
+
1069
+ // Determine the head and anchor of the selection.
1070
+ if (first === activeIndex) {
1071
+ return { head: first, anchor: last };
1072
+ } else {
1073
+ return { head: last, anchor: first };
1074
+ }
1075
+ }
1076
+ return { head: null, anchor: null };
1077
+ }
1078
+
1079
+ enableOutputScrolling = (cell: CellView) => {
1080
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
1081
+ for (const selectedCell of this.model.selections) {
1082
+ if (ExecutableCellModel.is(selectedCell.model)) {
1083
+ selectedCell.model.hasOutputsScrolled = true;
1084
+ }
1085
+ }
1086
+ } else {
1087
+ if (ExecutableCellModel.is(cell.model)) {
1088
+ cell.model.hasOutputsScrolled = true;
1089
+ }
1090
+ }
1091
+ };
1092
+
1093
+ disableOutputScrolling = (cell: CellView) => {
1094
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
1095
+ for (const selectedCell of this.model.selections) {
1096
+ if (ExecutableCellModel.is(selectedCell.model)) {
1097
+ selectedCell.model.hasOutputsScrolled = false;
1098
+ }
1099
+ }
1100
+ } else {
1101
+ if (ExecutableCellModel.is(cell.model)) {
1102
+ cell.model.hasOutputsScrolled = false;
1103
+ }
1104
+ }
1105
+ };
1106
+
1107
+ disposed = false;
1108
+
1109
+ override dispose() {
1110
+ if (!this.disposed) {
1111
+ this.libroService.deleteLibroViewFromCache(this);
1112
+ this.toDispose.dispose();
1113
+ }
1114
+ this.disposed = true;
1115
+ }
1116
+
1117
+ enterCommandMode = (isInLibro: boolean) => {
1118
+ if (this.hasModal) {
1119
+ return;
1120
+ }
1121
+ if (this.model.enterCommandMode) {
1122
+ this.model.enterCommandMode();
1123
+ }
1124
+ if (isInLibro) {
1125
+ this.container?.current?.focus();
1126
+ }
1127
+ };
1128
+
1129
+ enterEditMode = () => {
1130
+ if (this.model.enterEditMode) {
1131
+ this.model.enterEditMode();
1132
+ }
1133
+ };
1134
+
1135
+ moveCursorDown = (cell: CellView) => {
1136
+ const newSelectedCell = this.getNextVisibleCell(cell);
1137
+ if (newSelectedCell) {
1138
+ this.model.selectCell(newSelectedCell);
1139
+ this.model.selections = [];
1140
+ this.model.scrollToView(newSelectedCell);
1141
+ }
1142
+ };
1143
+
1144
+ moveCursorUp = (cell: CellView) => {
1145
+ const newSelectedCell = this.getPreviousVisibleCell(cell);
1146
+ if (newSelectedCell) {
1147
+ this.model.selectCell(newSelectedCell);
1148
+ this.model.selections = [];
1149
+ this.model.scrollToView(newSelectedCell);
1150
+ }
1151
+ };
1152
+
1153
+ mergeCellBelow = async (cell: CellView) => {
1154
+ const { cells, selections } = this.model;
1155
+ if (selections.length > 1) {
1156
+ this.mergeCells(cell);
1157
+ return;
1158
+ }
1159
+ const selectedIndex = this.findCellIndex(cell);
1160
+ if (selectedIndex >= cells.length - 1) {
1161
+ return;
1162
+ }
1163
+ const nextCell = cells[selectedIndex + 1];
1164
+ const source = concatMultilineString([
1165
+ cell.model.value + '\n',
1166
+ nextCell.model.value,
1167
+ ]);
1168
+ const cellView = await this.getCellViewByOption({
1169
+ id: v4(),
1170
+ cell: { cell_type: cell.model.type, source, metadata: {} },
1171
+ });
1172
+ if (this.model instanceof LibroModel) {
1173
+ this.model.activeIndex = selectedIndex;
1174
+ const cellData = [cellView].map((_cell) => {
1175
+ (this.model as LibroModel).cellViewCache.set(_cell.model.id, _cell);
1176
+ return _cell.toJSON();
1177
+ });
1178
+ this.model.sharedModel.transact(() => {
1179
+ this.model.sharedModel.deleteCell(selectedIndex);
1180
+ this.model.sharedModel.insertCells(selectedIndex, cellData);
1181
+ this.model.sharedModel.deleteCell(selectedIndex + 1);
1182
+ });
1183
+ }
1184
+ };
1185
+
1186
+ mergeCellAbove = async (cell: CellView) => {
1187
+ const { cells, selections } = this.model;
1188
+ if (selections.length > 1) {
1189
+ this.mergeCells(cell);
1190
+ return;
1191
+ }
1192
+ const selectedIndex = this.findCellIndex(cell);
1193
+ if (selectedIndex <= 0) {
1194
+ return;
1195
+ }
1196
+ const prevCell = cells[selectedIndex - 1];
1197
+ const source = concatMultilineString([
1198
+ prevCell.model.value + '\n' + cell.model.value,
1199
+ ]);
1200
+ const cellView = await this.getCellViewByOption({
1201
+ id: v4(),
1202
+ cell: { cell_type: cell.model.type, source, metadata: {} },
1203
+ });
1204
+ if (this.model instanceof LibroModel) {
1205
+ this.model.activeIndex = selectedIndex - 1;
1206
+ const cellData = [cellView].map((_cell) => {
1207
+ (this.model as LibroModel).cellViewCache.set(_cell.model.id, _cell);
1208
+ return _cell.toJSON();
1209
+ });
1210
+ this.model.sharedModel.transact(() => {
1211
+ this.model.sharedModel.deleteCell(selectedIndex - 1);
1212
+ this.model.sharedModel.insertCells(selectedIndex - 1, cellData);
1213
+ this.model.sharedModel.deleteCell(selectedIndex);
1214
+ });
1215
+ }
1216
+ };
1217
+
1218
+ mergeCells = async (cell: CellView) => {
1219
+ const { selections } = this.model;
1220
+ if (selections.length <= 1) {
1221
+ return;
1222
+ }
1223
+ const selectionsValue: string[] = [];
1224
+ const selectionsIndex: number[] = [];
1225
+ selections.map((item) => {
1226
+ selectionsValue.push(item.model.value);
1227
+ selectionsValue.push('\n');
1228
+ const index = this.findCellIndex(item);
1229
+ selectionsIndex.push(index);
1230
+ });
1231
+ const source = concatMultilineString(selectionsValue);
1232
+ const cellView = await this.getCellViewByOption({
1233
+ id: v4(),
1234
+ cell: { cell_type: cell.model.type, source, metadata: {} },
1235
+ });
1236
+
1237
+ if (this.model instanceof LibroModel) {
1238
+ this.model.activeIndex = Math.min(...selectionsIndex);
1239
+ const cellData = [cellView].map((_cell) => {
1240
+ (this.model as LibroModel).cellViewCache.set(_cell.model.id, _cell);
1241
+ return _cell.toJSON();
1242
+ });
1243
+ const startIndex = Math.min(...selectionsIndex);
1244
+ const endIndex = Math.max(...selectionsIndex);
1245
+ this.model.sharedModel.transact(() => {
1246
+ this.model.sharedModel.deleteCellRange(startIndex, endIndex + 1);
1247
+ this.model.sharedModel.insertCells(Math.min(...selectionsIndex), cellData);
1248
+ });
1249
+ }
1250
+ };
1251
+ selectAllCell = () => {
1252
+ this.model.selections = this.model.cells;
1253
+ };
1254
+
1255
+ splitCell = async (cell: CellView) => {
1256
+ const index = this.findCellIndex(cell);
1257
+ if (EditorCellView.is(cell)) {
1258
+ const selections = cell.getSelections();
1259
+ const offsets = [0];
1260
+ for (let i = 0; i < selections.length; i++) {
1261
+ // append start and end to handle selections
1262
+ // cursors will have same start and end
1263
+ const { start, end } = cell.getSelectionsOffsetAt(selections[i]);
1264
+ if (start < end) {
1265
+ offsets.push(start);
1266
+ offsets.push(end);
1267
+ } else if (end < start) {
1268
+ offsets.push(end);
1269
+ offsets.push(start);
1270
+ } else {
1271
+ offsets.push(start);
1272
+ }
1273
+ }
1274
+
1275
+ offsets.push(cell.model.value.length);
1276
+
1277
+ const splitCells = await Promise.all(
1278
+ offsets.slice(0, -1).map(async (offset, offsetIdx) => {
1279
+ const cellView = await this.getCellViewByOption({
1280
+ id: v4(),
1281
+ cell: {
1282
+ cell_type: cell.model.type,
1283
+ source: cell.model.value
1284
+ .slice(offset, offsets[offsetIdx + 1])
1285
+ .replace(/^\n+/, '')
1286
+ .replace(/\n+$/, ''),
1287
+ metadata: {},
1288
+ },
1289
+ });
1290
+ return cellView;
1291
+ }),
1292
+ );
1293
+ this.model.splitCell(splitCells, index);
1294
+ }
1295
+ };
1296
+ restartClearOutput = () => {
1297
+ if (this.model.restart) {
1298
+ this.model.restart();
1299
+ }
1300
+ this.clearAllOutputs();
1301
+ };
1302
+
1303
+ closeAndShutdown = () => {
1304
+ if (this.model.shutdown) {
1305
+ this.model.shutdown();
1306
+ }
1307
+ };
1308
+
1309
+ /**
1310
+ * Set the markdown header level of a cell.
1311
+ */
1312
+ setMarkdownHeader = async (cell: CellView, level: number) => {
1313
+ if (this.model.selections.length !== 0 && this.isSelected(cell)) {
1314
+ const { selections } = this.model;
1315
+ const selectionsValue: string[] = [];
1316
+ const selectionsIndex: number[] = [];
1317
+ const cellViews = await Promise.all(
1318
+ selections.map(async (item) => {
1319
+ let source = item.model.value;
1320
+ const regex = /^(#+\s*)|^(\s*)/;
1321
+ const newHeader = Array(level + 1).join('#') + ' ';
1322
+ const matches = regex.exec(source);
1323
+ if (matches) {
1324
+ source = source.slice(matches[0].length);
1325
+ }
1326
+ source = newHeader + source;
1327
+ selectionsValue.push(source);
1328
+ const index = this.findCellIndex(item);
1329
+ selectionsIndex.push(index);
1330
+ const cellView = await this.getCellViewByOption({
1331
+ id: v4(),
1332
+ cell: { cell_type: 'markdown', source, metadata: {} },
1333
+ });
1334
+ return cellView;
1335
+ }),
1336
+ );
1337
+ // TODO: why is this needed?
1338
+ // const source = concatMultilineString(selectionsValue);
1339
+
1340
+ if (this.model instanceof LibroModel) {
1341
+ this.model.activeIndex = Math.min(...selectionsIndex);
1342
+ const cellData = cellViews.map((_cell) => {
1343
+ (this.model as LibroModel).cellViewCache.set(_cell.model.id, _cell);
1344
+ return _cell.toJSON();
1345
+ });
1346
+ const startIndex = Math.min(...selectionsIndex);
1347
+ const endIndex = Math.max(...selectionsIndex);
1348
+ this.model.sharedModel.transact(() => {
1349
+ this.model.sharedModel.deleteCellRange(startIndex, endIndex + 1);
1350
+ this.model.sharedModel.insertCells(startIndex, cellData);
1351
+ });
1352
+ }
1353
+ } else {
1354
+ const index = this.findCellIndex(cell);
1355
+ let source = cell.model.value;
1356
+ const regex = /^(#+\s*)|^(\s*)/;
1357
+ const newHeader = Array(level + 1).join('#') + ' ';
1358
+ const matches = regex.exec(source);
1359
+ if (matches) {
1360
+ source = source.slice(matches[0].length);
1361
+ }
1362
+ source = newHeader + source;
1363
+ const cellView = await this.getCellViewByOption({
1364
+ id: v4(),
1365
+ cell: { cell_type: 'markdown', source, metadata: {} },
1366
+ });
1367
+
1368
+ if (this.model instanceof LibroModel) {
1369
+ this.model.activeIndex = index;
1370
+ const cellData = [cellView].map((_cell) => {
1371
+ (this.model as LibroModel).cellViewCache.set(_cell.model.id, _cell);
1372
+ return _cell.toJSON();
1373
+ });
1374
+ this.model.sharedModel.transact(() => {
1375
+ this.model.sharedModel.deleteCell(index);
1376
+ this.model.sharedModel.insertCells(index, cellData);
1377
+ });
1378
+ }
1379
+ }
1380
+ this.enterCommandMode(true);
1381
+ };
1382
+
1383
+ collapseCell(cell: CellView, collspse: boolean) {
1384
+ this.collapseService.setHeadingCollapse(cell, collspse);
1385
+ }
1386
+
1387
+ save() {
1388
+ this.saving = true;
1389
+ try {
1390
+ this.model.saveNotebookContent();
1391
+ this.onSaveEmitter.fire(true);
1392
+ this.model.dirty = false;
1393
+ } catch (ex) {
1394
+ this.onSaveEmitter.fire(false);
1395
+ } finally {
1396
+ this.saving = false;
1397
+ }
1398
+ }
1399
+ }