bookish-press 0.4.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 (309) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/components/Auth.svelte +52 -0
  4. package/components/Auth.svelte.d.ts +16 -0
  5. package/components/Authentication.d.ts +8 -0
  6. package/components/Authentication.js +1 -0
  7. package/components/BookPreview.svelte +87 -0
  8. package/components/BookPreview.svelte.d.ts +18 -0
  9. package/components/External.svelte +4 -0
  10. package/components/External.svelte.d.ts +18 -0
  11. package/components/Link.svelte +5 -0
  12. package/components/Link.svelte.d.ts +19 -0
  13. package/components/chapter/Atom.svelte +36 -0
  14. package/components/chapter/Atom.svelte.d.ts +19 -0
  15. package/components/chapter/Block.svelte +32 -0
  16. package/components/chapter/Block.svelte.d.ts +17 -0
  17. package/components/chapter/Callout.svelte +10 -0
  18. package/components/chapter/Callout.svelte.d.ts +17 -0
  19. package/components/chapter/CaptionedCode.svelte +34 -0
  20. package/components/chapter/CaptionedCode.svelte.d.ts +17 -0
  21. package/components/chapter/ChapterBody.svelte +23 -0
  22. package/components/chapter/ChapterBody.svelte.d.ts +18 -0
  23. package/components/chapter/Citations.svelte +66 -0
  24. package/components/chapter/Citations.svelte.d.ts +17 -0
  25. package/components/chapter/Code.svelte +35 -0
  26. package/components/chapter/Code.svelte.d.ts +22 -0
  27. package/components/chapter/Comment.svelte +46 -0
  28. package/components/chapter/Comment.svelte.d.ts +17 -0
  29. package/components/chapter/Definition.svelte +38 -0
  30. package/components/chapter/Definition.svelte.d.ts +20 -0
  31. package/components/chapter/Embed.svelte +122 -0
  32. package/components/chapter/Embed.svelte.d.ts +18 -0
  33. package/components/chapter/ErrorMessage.svelte +4 -0
  34. package/components/chapter/ErrorMessage.svelte.d.ts +17 -0
  35. package/components/chapter/Footnote.svelte +37 -0
  36. package/components/chapter/Footnote.svelte.d.ts +19 -0
  37. package/components/chapter/Format.svelte +57 -0
  38. package/components/chapter/Format.svelte.d.ts +18 -0
  39. package/components/chapter/InlineCode.svelte +12 -0
  40. package/components/chapter/InlineCode.svelte.d.ts +17 -0
  41. package/components/chapter/Label.svelte +20 -0
  42. package/components/chapter/Label.svelte.d.ts +17 -0
  43. package/components/chapter/Link.svelte +51 -0
  44. package/components/chapter/Link.svelte.d.ts +17 -0
  45. package/components/chapter/List.svelte +14 -0
  46. package/components/chapter/List.svelte.d.ts +17 -0
  47. package/components/chapter/Marginal.svelte +48 -0
  48. package/components/chapter/Marginal.svelte.d.ts +19 -0
  49. package/components/chapter/Paragraph.svelte +15 -0
  50. package/components/chapter/Paragraph.svelte.d.ts +18 -0
  51. package/components/chapter/Python.svelte +81 -0
  52. package/components/chapter/Python.svelte.d.ts +18 -0
  53. package/components/chapter/Quote.svelte +16 -0
  54. package/components/chapter/Quote.svelte.d.ts +17 -0
  55. package/components/chapter/Rule.svelte +4 -0
  56. package/components/chapter/Rule.svelte.d.ts +17 -0
  57. package/components/chapter/Table.svelte +29 -0
  58. package/components/chapter/Table.svelte.d.ts +17 -0
  59. package/components/chapter/Text.svelte +62 -0
  60. package/components/chapter/Text.svelte.d.ts +17 -0
  61. package/components/chapter/renderPosition.d.ts +3 -0
  62. package/components/chapter/renderPosition.js +4 -0
  63. package/components/editor/BookishEditor.svelte +755 -0
  64. package/components/editor/BookishEditor.svelte.d.ts +22 -0
  65. package/components/editor/CalloutEditor.svelte +10 -0
  66. package/components/editor/CalloutEditor.svelte.d.ts +17 -0
  67. package/components/editor/CaptionedCodeEditor.svelte +22 -0
  68. package/components/editor/CaptionedCodeEditor.svelte.d.ts +17 -0
  69. package/components/editor/CaretContext.d.ts +18 -0
  70. package/components/editor/CaretContext.js +1 -0
  71. package/components/editor/CaretState.d.ts +47 -0
  72. package/components/editor/CaretState.js +1 -0
  73. package/components/editor/CaretUtilities.d.ts +5 -0
  74. package/components/editor/CaretUtilities.js +1 -0
  75. package/components/editor/CitationsEditor.svelte +42 -0
  76. package/components/editor/CitationsEditor.svelte.d.ts +17 -0
  77. package/components/editor/Clipboard.d.ts +3 -0
  78. package/components/editor/Clipboard.js +1 -0
  79. package/components/editor/Command.d.ts +18 -0
  80. package/components/editor/Command.js +1 -0
  81. package/components/editor/Commands.d.ts +3 -0
  82. package/components/editor/Commands.js +951 -0
  83. package/components/editor/ConfirmButton.svelte +43 -0
  84. package/components/editor/ConfirmButton.svelte.d.ts +18 -0
  85. package/components/editor/DefinitionEditor.svelte +28 -0
  86. package/components/editor/DefinitionEditor.svelte.d.ts +17 -0
  87. package/components/editor/EmbedEditor.svelte +87 -0
  88. package/components/editor/EmbedEditor.svelte.d.ts +17 -0
  89. package/components/editor/ImageChooser.svelte +70 -0
  90. package/components/editor/ImageChooser.svelte.d.ts +18 -0
  91. package/components/editor/InlineCodeEditor.svelte +9 -0
  92. package/components/editor/InlineCodeEditor.svelte.d.ts +17 -0
  93. package/components/editor/LabelEditor.svelte +23 -0
  94. package/components/editor/LabelEditor.svelte.d.ts +17 -0
  95. package/components/editor/LanguageEditor.svelte +11 -0
  96. package/components/editor/LanguageEditor.svelte.d.ts +17 -0
  97. package/components/editor/LinkEditor.svelte +75 -0
  98. package/components/editor/LinkEditor.svelte.d.ts +17 -0
  99. package/components/editor/Note.svelte +9 -0
  100. package/components/editor/Note.svelte.d.ts +23 -0
  101. package/components/editor/PositionEditor.svelte +14 -0
  102. package/components/editor/PositionEditor.svelte.d.ts +18 -0
  103. package/components/editor/QuoteEditor.svelte +5 -0
  104. package/components/editor/QuoteEditor.svelte.d.ts +17 -0
  105. package/components/editor/Switch.svelte +56 -0
  106. package/components/editor/Switch.svelte.d.ts +20 -0
  107. package/components/editor/TextEditor.svelte +193 -0
  108. package/components/editor/TextEditor.svelte.d.ts +23 -0
  109. package/components/editor/Toggle.svelte +38 -0
  110. package/components/editor/Toggle.svelte.d.ts +19 -0
  111. package/components/editor/Toolbar.svelte +202 -0
  112. package/components/editor/Toolbar.svelte.d.ts +20 -0
  113. package/components/editor/ToolbarGroup.svelte +41 -0
  114. package/components/editor/ToolbarGroup.svelte.d.ts +19 -0
  115. package/components/editor/ToolbarIcon.svelte +22 -0
  116. package/components/editor/ToolbarIcon.svelte.d.ts +16 -0
  117. package/components/editor/ToolbarSpacer.svelte +1 -0
  118. package/components/editor/ToolbarSpacer.svelte.d.ts +19 -0
  119. package/components/editor/URLEditor.svelte +15 -0
  120. package/components/editor/URLEditor.svelte.d.ts +18 -0
  121. package/components/editor/UndoState.d.ts +8 -0
  122. package/components/editor/UndoState.js +1 -0
  123. package/components/page/Acknowledgements.svelte +29 -0
  124. package/components/page/Acknowledgements.svelte.d.ts +14 -0
  125. package/components/page/Alert.svelte +11 -0
  126. package/components/page/Alert.svelte.d.ts +23 -0
  127. package/components/page/Authors.svelte +62 -0
  128. package/components/page/Authors.svelte.d.ts +20 -0
  129. package/components/page/BulkReferenceEditor.svelte +32 -0
  130. package/components/page/BulkReferenceEditor.svelte.d.ts +14 -0
  131. package/components/page/Chapter.svelte +301 -0
  132. package/components/page/Chapter.svelte.d.ts +18 -0
  133. package/components/page/ChapterContext.d.ts +10 -0
  134. package/components/page/ChapterContext.js +1 -0
  135. package/components/page/Contexts.d.ts +27 -0
  136. package/components/page/Contexts.js +21 -0
  137. package/components/page/DefinitionView.svelte +129 -0
  138. package/components/page/DefinitionView.svelte.d.ts +18 -0
  139. package/components/page/Description.svelte +22 -0
  140. package/components/page/Description.svelte.d.ts +14 -0
  141. package/components/page/Edition.svelte +106 -0
  142. package/components/page/Edition.svelte.d.ts +21 -0
  143. package/components/page/Glossary.svelte +61 -0
  144. package/components/page/Glossary.svelte.d.ts +14 -0
  145. package/components/page/Header.svelte +105 -0
  146. package/components/page/Header.svelte.d.ts +27 -0
  147. package/components/page/Index.svelte +117 -0
  148. package/components/page/Index.svelte.d.ts +14 -0
  149. package/components/page/Instructions.svelte +30 -0
  150. package/components/page/Instructions.svelte.d.ts +16 -0
  151. package/components/page/License.svelte +29 -0
  152. package/components/page/License.svelte.d.ts +14 -0
  153. package/components/page/Loading.svelte +13 -0
  154. package/components/page/Loading.svelte.d.ts +19 -0
  155. package/components/page/Media.svelte +76 -0
  156. package/components/page/Media.svelte.d.ts +14 -0
  157. package/components/page/MediaPreview.svelte +11 -0
  158. package/components/page/MediaPreview.svelte.d.ts +19 -0
  159. package/components/page/Outline.svelte +173 -0
  160. package/components/page/Outline.svelte.d.ts +19 -0
  161. package/components/page/Page.svelte +53 -0
  162. package/components/page/Page.svelte.d.ts +19 -0
  163. package/components/page/PossibleChapter.svelte +14 -0
  164. package/components/page/PossibleChapter.svelte.d.ts +14 -0
  165. package/components/page/PossibleReference.svelte +16 -0
  166. package/components/page/PossibleReference.svelte.d.ts +19 -0
  167. package/components/page/Print.svelte +8 -0
  168. package/components/page/Print.svelte.d.ts +14 -0
  169. package/components/page/Reference.svelte +145 -0
  170. package/components/page/Reference.svelte.d.ts +17 -0
  171. package/components/page/References.svelte +58 -0
  172. package/components/page/References.svelte.d.ts +14 -0
  173. package/components/page/Revisions.svelte +124 -0
  174. package/components/page/Revisions.svelte.d.ts +14 -0
  175. package/components/page/Search.svelte +108 -0
  176. package/components/page/Search.svelte.d.ts +14 -0
  177. package/components/page/Status.svelte +49 -0
  178. package/components/page/Status.svelte.d.ts +19 -0
  179. package/components/page/SubdomainEditor.svelte +30 -0
  180. package/components/page/SubdomainEditor.svelte.d.ts +14 -0
  181. package/components/page/TableOfContents.svelte +234 -0
  182. package/components/page/TableOfContents.svelte.d.ts +14 -0
  183. package/components/page/TableOfContentsImage.svelte +15 -0
  184. package/components/page/TableOfContentsImage.svelte.d.ts +16 -0
  185. package/components/page/TableOfContentsRow.svelte +57 -0
  186. package/components/page/TableOfContentsRow.svelte.d.ts +24 -0
  187. package/components/page/ThemeEditor.svelte +52 -0
  188. package/components/page/ThemeEditor.svelte.d.ts +14 -0
  189. package/components/page/ThemeEditorPreview.svelte +37 -0
  190. package/components/page/ThemeEditorPreview.svelte.d.ts +17 -0
  191. package/components/page/ThemeSetEditor.svelte +17 -0
  192. package/components/page/ThemeSetEditor.svelte.d.ts +18 -0
  193. package/components/page/ThemeVariableEditor.svelte +28 -0
  194. package/components/page/ThemeVariableEditor.svelte.d.ts +18 -0
  195. package/components/page/Unknown.svelte +29 -0
  196. package/components/page/Unknown.svelte.d.ts +16 -0
  197. package/components/page/margins.d.ts +2 -0
  198. package/components/page/margins.js +132 -0
  199. package/models/BookLoader.d.ts +2 -0
  200. package/models/BookLoader.js +67 -0
  201. package/models/Firebase.d.ts +4 -0
  202. package/models/Firebase.js +26 -0
  203. package/models/Firestore.d.ts +22 -0
  204. package/models/Firestore.js +194 -0
  205. package/models/book/Book.d.ts +60 -0
  206. package/models/book/Book.js +135 -0
  207. package/models/book/BookMedia.d.ts +23 -0
  208. package/models/book/BookMedia.js +102 -0
  209. package/models/book/Chapter.d.ts +70 -0
  210. package/models/book/Chapter.js +221 -0
  211. package/models/book/ChapterID.d.ts +10 -0
  212. package/models/book/ChapterID.js +10 -0
  213. package/models/book/Definition.d.ts +6 -0
  214. package/models/book/Definition.js +1 -0
  215. package/models/book/Edition.d.ts +111 -0
  216. package/models/book/Edition.js +466 -0
  217. package/models/book/Theme.d.ts +106 -0
  218. package/models/book/Theme.js +76 -0
  219. package/models/book/ThemeColors.d.ts +15 -0
  220. package/models/book/ThemeColors.js +1 -0
  221. package/models/chapter/AtomNode.d.ts +18 -0
  222. package/models/chapter/AtomNode.js +31 -0
  223. package/models/chapter/BlockNode.d.ts +21 -0
  224. package/models/chapter/BlockNode.js +117 -0
  225. package/models/chapter/BlocksNode.d.ts +50 -0
  226. package/models/chapter/BlocksNode.js +709 -0
  227. package/models/chapter/BlocksNode.test.d.ts +1 -0
  228. package/models/chapter/BlocksNode.test.js +302 -0
  229. package/models/chapter/Bookkeeping.d.ts +4 -0
  230. package/models/chapter/Bookkeeping.js +1 -0
  231. package/models/chapter/CalloutNode.d.ts +17 -0
  232. package/models/chapter/CalloutNode.js +36 -0
  233. package/models/chapter/Caret.d.ts +21 -0
  234. package/models/chapter/Caret.js +69 -0
  235. package/models/chapter/ChapterNode.d.ts +34 -0
  236. package/models/chapter/ChapterNode.js +80 -0
  237. package/models/chapter/CitationsNode.d.ts +16 -0
  238. package/models/chapter/CitationsNode.js +17 -0
  239. package/models/chapter/CodeNode.d.ts +30 -0
  240. package/models/chapter/CodeNode.js +64 -0
  241. package/models/chapter/CommentNode.d.ts +18 -0
  242. package/models/chapter/CommentNode.js +32 -0
  243. package/models/chapter/DefinitionNode.d.ts +12 -0
  244. package/models/chapter/DefinitionNode.js +20 -0
  245. package/models/chapter/Edit.d.ts +7 -0
  246. package/models/chapter/Edit.js +1 -0
  247. package/models/chapter/EmbedNode.d.ts +40 -0
  248. package/models/chapter/EmbedNode.js +89 -0
  249. package/models/chapter/ErrorNode.d.ts +17 -0
  250. package/models/chapter/ErrorNode.js +21 -0
  251. package/models/chapter/FootnoteNode.d.ts +18 -0
  252. package/models/chapter/FootnoteNode.js +29 -0
  253. package/models/chapter/FormatNode.d.ts +60 -0
  254. package/models/chapter/FormatNode.js +614 -0
  255. package/models/chapter/FormatNode.test.d.ts +1 -0
  256. package/models/chapter/FormatNode.test.js +54 -0
  257. package/models/chapter/InlineCodeNode.d.ts +12 -0
  258. package/models/chapter/InlineCodeNode.js +18 -0
  259. package/models/chapter/LabelNode.d.ts +17 -0
  260. package/models/chapter/LabelNode.js +22 -0
  261. package/models/chapter/LinkNode.d.ts +12 -0
  262. package/models/chapter/LinkNode.js +16 -0
  263. package/models/chapter/ListNode.d.ts +51 -0
  264. package/models/chapter/ListNode.js +370 -0
  265. package/models/chapter/ListNode.test.d.ts +1 -0
  266. package/models/chapter/ListNode.test.js +49 -0
  267. package/models/chapter/MetadataNode.d.ts +17 -0
  268. package/models/chapter/MetadataNode.js +24 -0
  269. package/models/chapter/Node.d.ts +36 -0
  270. package/models/chapter/Node.js +173 -0
  271. package/models/chapter/Node.test.d.ts +1 -0
  272. package/models/chapter/Node.test.js +27 -0
  273. package/models/chapter/ParagraphNode.d.ts +33 -0
  274. package/models/chapter/ParagraphNode.js +93 -0
  275. package/models/chapter/ParagraphNode.test.d.ts +1 -0
  276. package/models/chapter/ParagraphNode.test.js +21 -0
  277. package/models/chapter/Parser.d.ts +75 -0
  278. package/models/chapter/Parser.js +824 -0
  279. package/models/chapter/Parser.test.d.ts +1 -0
  280. package/models/chapter/Parser.test.js +25 -0
  281. package/models/chapter/Position.d.ts +2 -0
  282. package/models/chapter/Position.js +1 -0
  283. package/models/chapter/QuoteNode.d.ts +25 -0
  284. package/models/chapter/QuoteNode.js +61 -0
  285. package/models/chapter/ReferenceNode.d.ts +29 -0
  286. package/models/chapter/ReferenceNode.js +48 -0
  287. package/models/chapter/RootNode.d.ts +5 -0
  288. package/models/chapter/RootNode.js +1 -0
  289. package/models/chapter/RuleNode.d.ts +15 -0
  290. package/models/chapter/RuleNode.js +16 -0
  291. package/models/chapter/TableNode.d.ts +35 -0
  292. package/models/chapter/TableNode.js +176 -0
  293. package/models/chapter/TableNode.test.d.ts +1 -0
  294. package/models/chapter/TableNode.test.js +31 -0
  295. package/models/chapter/TextNode.d.ts +36 -0
  296. package/models/chapter/TextNode.js +136 -0
  297. package/models/chapter/TextNode.test.d.ts +1 -0
  298. package/models/chapter/TextNode.test.js +6 -0
  299. package/models/loadEdition.d.ts +5 -0
  300. package/models/loadEdition.js +16 -0
  301. package/package.json +189 -0
  302. package/util/getSubdomain.d.ts +3 -0
  303. package/util/getSubdomain.js +22 -0
  304. package/util/isMobile.d.ts +2 -0
  305. package/util/isMobile.js +6 -0
  306. package/util/mineReference.d.ts +3 -0
  307. package/util/mineReference.js +62 -0
  308. package/util/scrollToEyeLevel.d.ts +2 -0
  309. package/util/scrollToEyeLevel.js +5 -0
@@ -0,0 +1,951 @@
1
+ import CalloutNode from "../../models/chapter/CalloutNode";
2
+ import CitationsNode from "../../models/chapter/CitationsNode";
3
+ import CodeNode from "../../models/chapter/CodeNode";
4
+ import CommentNode from "../../models/chapter/CommentNode";
5
+ import DefinitionNode from "../../models/chapter/DefinitionNode";
6
+ import EmbedNode from "../../models/chapter/EmbedNode";
7
+ import FootnoteNode from "../../models/chapter/FootnoteNode";
8
+ import FormatNode from "../../models/chapter/FormatNode";
9
+ import InlineCodeNode from "../../models/chapter/InlineCodeNode";
10
+ import LabelNode from "../../models/chapter/LabelNode";
11
+ import LinkNode from "../../models/chapter/LinkNode";
12
+ import ListNode from "../../models/chapter/ListNode";
13
+ import ParagraphNode from "../../models/chapter/ParagraphNode";
14
+ import QuoteNode from "../../models/chapter/QuoteNode";
15
+ import RuleNode from "../../models/chapter/RuleNode";
16
+ import TableNode from "../../models/chapter/TableNode";
17
+ import TextNode from "../../models/chapter/TextNode";
18
+ import AtomNode from "../../models/chapter/AtomNode";
19
+ import BlocksNode from "../../models/chapter/BlocksNode";
20
+ // SVG Icons from: https://thenounproject.com/browse/collection-icon/minio-text-editor-bold-13520/?p=1
21
+ function insertTableRowColumn(context, table, format, row, before) {
22
+ const location = table.locate(format);
23
+ if (location === undefined)
24
+ return;
25
+ return rootWithNode(context, table, row ? table.withNewRow(location.row + (before ? 0 : 1)) : table.withNewColumn(location.column + (before ? 0 : 1)), t => t.getCell(location.row + (row && !before ? 1 : 0), location.column + (!row && !before ? 1 : 0)).getFirstCaret());
26
+ }
27
+ function deleteTableRowColumn(context, table, format, row) {
28
+ const location = table.locate(format);
29
+ if (location === undefined)
30
+ return;
31
+ return rootWithNode(context, table, row ? table.withoutRow(location.row) : table.withoutColumn(location.column), t => {
32
+ return (row ?
33
+ t.getCell(location.row === table.getRowCount() ? location.row - 1 : location.row, location.column) :
34
+ t.getCell(location.row, location.column === table.getColumnCount() ? location.column - 1 : location.column))?.getFirstCaret();
35
+ });
36
+ }
37
+ // A helper function that encapsulates boilerplate for replacing a node in a root and updating a caret.
38
+ function rootWithNode(context, original, replacement, caret) {
39
+ // If there was no original or replacement, do nothing. Saves commands from having to check.
40
+ if (original === undefined || replacement === undefined)
41
+ return;
42
+ // Make the new root. Bail on fail.
43
+ const newRoot = context.root.withNodeReplaced(original, replacement);
44
+ if (newRoot === undefined)
45
+ return;
46
+ // Generate a new caret based on the replacement, if there's a generator. Bail on fail.
47
+ const newCaret = caret?.call(undefined, replacement);
48
+ if (caret !== undefined && newCaret === undefined)
49
+ return;
50
+ // Return the edit
51
+ return { root: newRoot, range: newCaret !== undefined ? { start: newCaret, end: newCaret } : context.range };
52
+ }
53
+ // An ordered list of command specifications for keyboard and mouse input.
54
+ const commands = [
55
+ {
56
+ label: "—↑",
57
+ description: "insert row above",
58
+ category: "table",
59
+ control: false, alt: true, shift: false, key: "ArrowUp",
60
+ visible: context => context.table !== undefined,
61
+ active: context => context.table !== undefined && context.format !== undefined,
62
+ handler: context => context.table !== undefined && context.format !== undefined ? insertTableRowColumn(context, context.table, context.format, true, true) : undefined
63
+ },
64
+ {
65
+ label: "—↓",
66
+ description: "insert row below",
67
+ category: "table",
68
+ control: false, alt: true, shift: false, key: "ArrowDown",
69
+ visible: context => context.table !== undefined,
70
+ active: context => context.table !== undefined && context.format !== undefined,
71
+ handler: context => context.table !== undefined && context.format !== undefined ? insertTableRowColumn(context, context.table, context.format, true, false) : undefined
72
+ },
73
+ {
74
+ label: "→|",
75
+ description: "insert column after",
76
+ category: "table",
77
+ control: false, alt: true, shift: false, key: "ArrowRight",
78
+ visible: context => context.table !== undefined,
79
+ active: context => context.table !== undefined && context.format !== undefined,
80
+ handler: context => context.table !== undefined && context.format !== undefined ? insertTableRowColumn(context, context.table, context.format, false, false) : undefined
81
+ },
82
+ {
83
+ label: "|←",
84
+ description: "insert column before",
85
+ category: "table",
86
+ control: false, alt: true, shift: false, key: "ArrowLeft",
87
+ visible: context => context.table !== undefined,
88
+ active: context => context.table !== undefined && context.format !== undefined,
89
+ handler: context => context.table !== undefined && context.format !== undefined ? insertTableRowColumn(context, context.table, context.format, false, true) : undefined
90
+ },
91
+ {
92
+ label: "\u232B—",
93
+ description: "delete row",
94
+ category: "table",
95
+ control: false, alt: true, shift: false, key: "Backspace",
96
+ visible: context => context.table !== undefined,
97
+ active: context => context.format !== undefined && context.table !== undefined && context.table.getRowCount() > 1,
98
+ handler: context => context.format !== undefined && context.table !== undefined ? deleteTableRowColumn(context, context.table, context.format, true) : undefined
99
+ },
100
+ {
101
+ label: "\u232B|",
102
+ description: "delete column",
103
+ category: "table",
104
+ control: false, alt: true, shift: true, key: "Backspace",
105
+ visible: context => context.table !== undefined,
106
+ active: context => context.format !== undefined && context.table !== undefined && context.table.getColumnCount() > 1,
107
+ handler: context => context.format !== undefined && context.table !== undefined ? deleteTableRowColumn(context, context.table, context.format, false) : undefined
108
+ },
109
+ {
110
+ description: "move to previous character",
111
+ category: "navigation",
112
+ control: false, alt: false, shift: false, key: "ArrowLeft",
113
+ visible: () => false,
114
+ active: context => context.endIsTextOrAtom,
115
+ handler: context => {
116
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
117
+ const nextCaret = context.root.getAdjacentCaret(context.end, false);
118
+ const sortedRange = context.root.sortRange(context.range);
119
+ return { root: context.root, range: nextCaret === undefined ? { start: sortedRange.start, end: sortedRange.start } : { start: nextCaret, end: nextCaret } };
120
+ }
121
+ }
122
+ },
123
+ {
124
+ description: "move to previous word",
125
+ category: "navigation",
126
+ control: false, alt: true, shift: false, key: "ArrowLeft",
127
+ visible: false,
128
+ active: context => context.endIsTextOrAtom,
129
+ handler: context => {
130
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
131
+ const previous = context.end.node.previousWord(context.root, context.end.index);
132
+ return { root: context.root, range: { start: previous, end: previous } };
133
+ }
134
+ }
135
+ },
136
+ {
137
+ description: "move to paragraph start",
138
+ category: "navigation",
139
+ control: true, alt: false, shift: false, key: "ArrowLeft",
140
+ visible: false,
141
+ active: context => context.format !== undefined,
142
+ handler: context => {
143
+ if (context.format !== undefined) {
144
+ const first = context.format.getFirstTextNode();
145
+ return { root: context.root, range: { start: { node: first, index: 0 }, end: { node: first, index: 0 } } };
146
+ }
147
+ }
148
+ },
149
+ {
150
+ description: "expand selection to previous character",
151
+ category: "selection",
152
+ control: false, alt: false, shift: true, key: "ArrowLeft",
153
+ visible: false,
154
+ active: context => context.endIsTextOrAtom,
155
+ handler: context => {
156
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
157
+ const previous = context.root.getAdjacentCaret(context.end, false);
158
+ if (previous === undefined)
159
+ return;
160
+ return { root: context.root, range: { start: context.start, end: previous } };
161
+ }
162
+ }
163
+ },
164
+ {
165
+ description: "expand selection to previous word",
166
+ category: "selection",
167
+ control: false, alt: true, shift: true, key: "ArrowLeft",
168
+ visible: false,
169
+ active: context => context.endIsTextOrAtom,
170
+ handler: context => {
171
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
172
+ const previous = context.end.node.previousWord(context.root, context.end.index);
173
+ return { root: context.root, range: { start: context.start, end: previous } };
174
+ }
175
+ }
176
+ },
177
+ {
178
+ description: "expand selection to start",
179
+ category: "selection",
180
+ control: true, alt: false, shift: true, key: "ArrowLeft",
181
+ visible: false,
182
+ active: context => context.format !== undefined,
183
+ handler: context => {
184
+ if (context.format !== undefined) {
185
+ const first = context.format.getFirstTextNode();
186
+ return { root: context.root, range: { start: context.start, end: { node: first, index: 0 } } };
187
+ }
188
+ }
189
+ },
190
+ {
191
+ description: "move to next character",
192
+ category: "navigation",
193
+ control: false, alt: false, shift: false, key: "ArrowRight",
194
+ visible: false,
195
+ active: context => context.endIsTextOrAtom,
196
+ handler: context => {
197
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
198
+ const nextCaret = context.root.getAdjacentCaret(context.end, true);
199
+ const sortedRange = context.root.sortRange(context.range);
200
+ // If there is no next caret, move the caret to the largest of the current start and end.
201
+ return { root: context.root, range: nextCaret === undefined ? { start: sortedRange.end, end: sortedRange.end } : { start: nextCaret, end: nextCaret } };
202
+ }
203
+ }
204
+ },
205
+ {
206
+ description: "move to next word",
207
+ category: "navigation",
208
+ control: false, alt: true, shift: false, key: "ArrowRight",
209
+ visible: false,
210
+ active: context => context.endIsTextOrAtom,
211
+ handler: context => {
212
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
213
+ const next = context.end.node.nextWord(context.root, context.end.index);
214
+ return { root: context.root, range: { start: next, end: next } };
215
+ }
216
+ }
217
+ },
218
+ {
219
+ description: "move to paragraph end",
220
+ category: "navigation",
221
+ control: true, alt: false, shift: false, key: "ArrowRight",
222
+ visible: false,
223
+ active: context => context.format !== undefined,
224
+ handler: context => {
225
+ if (context.format !== undefined) {
226
+ const last = context.format.getLastTextNode();
227
+ const caret = { node: last, index: last.getLength() };
228
+ return { root: context.root, range: { start: caret, end: caret } };
229
+ }
230
+ }
231
+ },
232
+ {
233
+ description: "expand selection to next character",
234
+ category: "selection",
235
+ control: false, alt: false, shift: true, key: "ArrowRight",
236
+ visible: false,
237
+ active: context => context.endIsTextOrAtom,
238
+ handler: context => {
239
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
240
+ const next = context.root.getAdjacentCaret(context.end, true);
241
+ if (next === undefined)
242
+ return;
243
+ return { root: context.root, range: { start: context.start, end: next } };
244
+ }
245
+ }
246
+ },
247
+ {
248
+ description: "expand selection to next word",
249
+ category: "selection",
250
+ control: false, alt: true, shift: true, key: "ArrowRight",
251
+ visible: false,
252
+ active: context => context.endIsTextOrAtom,
253
+ handler: context => {
254
+ if (context.end.node instanceof TextNode || context.end.node instanceof AtomNode) {
255
+ const previous = context.end.node.nextWord(context.root, context.end.index);
256
+ return { root: context.root, range: { start: context.start, end: previous } };
257
+ }
258
+ }
259
+ },
260
+ {
261
+ description: "expand selection to end",
262
+ category: "selection",
263
+ control: true, alt: false, shift: true, key: "ArrowRight",
264
+ visible: false,
265
+ active: context => context.format !== undefined,
266
+ handler: context => {
267
+ if (context.format !== undefined) {
268
+ const last = context.format.getLastTextNode();
269
+ return { root: context.root, range: { start: context.start, end: { node: last, index: last.getLength() } } };
270
+ }
271
+ }
272
+ },
273
+ {
274
+ description: "expand selection to all",
275
+ category: "selection",
276
+ control: true, alt: false, shift: false, key: "a",
277
+ visible: false,
278
+ active: () => true,
279
+ handler: context => {
280
+ // If in a footnote, expand to the footnote.
281
+ if (context.atom) {
282
+ const format = context.atom.getMeta();
283
+ return { root: context.root, range: { start: format.getFirstCaret(), end: format.getLastCaret() } };
284
+ }
285
+ // If not in a paragraph, expand to all of the formats of the block.
286
+ else if (context.paragraph === undefined && context.block) {
287
+ const formats = context.block.getFormats();
288
+ if (formats.length > 0)
289
+ return { root: context.root, range: { start: formats[0].getFirstCaret(), end: formats[formats.length - 1].getLastCaret() } };
290
+ }
291
+ else {
292
+ // Find the first and last caret of the entire chapter.
293
+ const text = context.root?.getTextNodes();
294
+ if (text && text.length > 0) {
295
+ return {
296
+ root: context.root,
297
+ range: {
298
+ start: { node: text[0], index: 0 },
299
+ end: { node: text[text.length - 1], index: text[text.length - 1].getLength() }
300
+ }
301
+ };
302
+ }
303
+ }
304
+ }
305
+ },
306
+ {
307
+ description: "move up one line",
308
+ category: "navigation",
309
+ control: false, alt: false, shift: false, key: "ArrowUp",
310
+ visible: false,
311
+ active: context => context.startIsTextOrAtom,
312
+ handler: (context, utilities) => {
313
+ const above = utilities.getCaretOnLine(context.start, false);
314
+ return { root: context.root, range: { start: above, end: above } };
315
+ }
316
+ },
317
+ {
318
+ description: "move selection up one line",
319
+ category: "selection",
320
+ control: false, alt: false, shift: true, key: "ArrowUp",
321
+ visible: false,
322
+ active: context => context.startIsTextOrAtom,
323
+ handler: (context, utilities) => {
324
+ const above = utilities.getCaretOnLine(context.end, false);
325
+ return { root: context.root, range: { start: context.start, end: above } };
326
+ }
327
+ },
328
+ {
329
+ description: "move up down line",
330
+ category: "navigation",
331
+ control: false, alt: false, shift: false, key: "ArrowDown",
332
+ visible: false,
333
+ active: context => context.startIsTextOrAtom,
334
+ handler: (context, utilities) => {
335
+ const below = utilities.getCaretOnLine(context.start, true);
336
+ return { root: context.root, range: { start: below, end: below } };
337
+ }
338
+ },
339
+ {
340
+ description: "move selection down one line",
341
+ category: "selection",
342
+ control: false, alt: false, shift: true, key: "ArrowDown",
343
+ visible: false,
344
+ active: context => context.startIsTextOrAtom,
345
+ handler: (context, utilities) => {
346
+ const below = utilities.getCaretOnLine(context.end, true);
347
+ return { root: context.root, range: { start: context.start, end: below } };
348
+ }
349
+ },
350
+ {
351
+ description: "delete previous character",
352
+ category: "text",
353
+ control: false, alt: false, shift: false, key: "Backspace",
354
+ visible: false,
355
+ active: true,
356
+ handler: context => {
357
+ if (context.root === undefined)
358
+ return;
359
+ if (context.isSelection) {
360
+ const edit = context.root.withRangeFormatted(context.range, undefined);
361
+ if (edit === undefined)
362
+ return;
363
+ return rootWithNode(context, context.root, edit.root, () => edit.range.start);
364
+ }
365
+ else {
366
+ const edit = context.root.withoutAdjacentContent(context.start, false);
367
+ if (edit === undefined)
368
+ return;
369
+ return rootWithNode(context, context.root, edit.root, () => edit.range.start);
370
+ }
371
+ }
372
+ },
373
+ {
374
+ description: "delete next character",
375
+ category: "text",
376
+ control: false, alt: false, shift: false, key: "Delete",
377
+ visible: false,
378
+ active: true,
379
+ handler: context => {
380
+ if (context.root === undefined)
381
+ return;
382
+ if (context.isSelection) {
383
+ const edit = context.root.withRangeFormatted(context.range, undefined);
384
+ if (edit === undefined)
385
+ return;
386
+ return rootWithNode(context, context.root, edit.root, () => edit.range.start);
387
+ }
388
+ else {
389
+ const edit = context.root.withoutAdjacentContent(context.start, true);
390
+ if (edit === undefined)
391
+ return;
392
+ return rootWithNode(context, context.root, edit.root, () => edit.range.start);
393
+ }
394
+ }
395
+ },
396
+ {
397
+ description: "insert code newline",
398
+ category: "text",
399
+ control: false, alt: false, shift: false, key: "Enter",
400
+ visible: false,
401
+ active: context => context.code !== undefined,
402
+ handler: context => {
403
+ if (context.code?.getCodeNode() !== context.start.node)
404
+ return;
405
+ const newText = context.start.node.withCharacterAt("\n", context.start.index);
406
+ if (newText === undefined)
407
+ return;
408
+ return rootWithNode(context, context.start.node, newText, text => { return { node: text, index: context.start.index + 1 }; });
409
+ }
410
+ },
411
+ {
412
+ description: "split list item",
413
+ category: "list",
414
+ control: false, alt: false, shift: false, key: "Enter",
415
+ visible: false,
416
+ active: context => context.list !== undefined,
417
+ handler: context => {
418
+ if (context.list === undefined)
419
+ return;
420
+ const lastItem = context.list.getLastItem();
421
+ const lastCaret = lastItem?.getLastCaret();
422
+ // If we're at the last item in the list and it's empty, then add a
423
+ if (lastItem !== undefined && lastCaret !== undefined && lastCaret.node instanceof TextNode && lastCaret.node.getLength() === 0 && lastCaret.node === context.end.node && lastCaret.index === context.end.index) {
424
+ const listParent = context.list.getParent(context.root);
425
+ if (listParent instanceof ListNode) {
426
+ const newSublist = context.list.withoutItem(lastItem);
427
+ if (newSublist === undefined)
428
+ return;
429
+ const newListWithoutSublistItem = listParent.withChildReplaced(context.list, newSublist);
430
+ if (newListWithoutSublistItem === undefined)
431
+ return;
432
+ const newListWithSublistItem = newListWithoutSublistItem.withItemAfter(lastItem, newSublist);
433
+ if (newListWithSublistItem === undefined)
434
+ return;
435
+ return rootWithNode(context, listParent, newListWithSublistItem, () => lastCaret);
436
+ }
437
+ else if (listParent instanceof BlocksNode) {
438
+ const listWithoutItem = context.list.withoutItem(lastItem);
439
+ if (listWithoutItem === undefined)
440
+ return;
441
+ const newBlocks = listParent.withChildReplaced(context.list, listWithoutItem)?.withBlockInsertedAfter(listWithoutItem, new ParagraphNode(0, lastItem));
442
+ if (newBlocks === undefined)
443
+ return;
444
+ return rootWithNode(context, listParent, newBlocks, () => lastCaret);
445
+ }
446
+ }
447
+ else
448
+ return rootWithNode(context, context.list, context.list?.withItemSplit(context.range.start), newList => {
449
+ const index = context.list?.getItemContaining(context.range.start);
450
+ if (index === undefined)
451
+ return;
452
+ const item = newList.getItem(index + 1);
453
+ if (item === undefined)
454
+ return;
455
+ return item.getFirstCaret();
456
+ });
457
+ }
458
+ },
459
+ {
460
+ description: "split paragraph",
461
+ category: "paragraph",
462
+ control: false, alt: false, shift: false, key: "Enter",
463
+ visible: false,
464
+ active: context => context.atom === undefined && context.blocks !== undefined,
465
+ handler: context => {
466
+ const edit = context.blocks?.withSelectionSplit(context.range);
467
+ if (edit === undefined)
468
+ return;
469
+ return rootWithNode(context, context.blocks, edit.root, () => edit.range.start);
470
+ }
471
+ },
472
+ {
473
+ label: "indent",
474
+ icon: "indent.svg",
475
+ description: "indent list item",
476
+ category: "list",
477
+ control: false, alt: false, shift: false, key: "Tab",
478
+ visible: context => context.list !== undefined,
479
+ active: context => context.list !== undefined,
480
+ handler: context => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, true))
481
+ },
482
+ {
483
+ label: "unindent",
484
+ icon: 'svg/unindent.svg',
485
+ description: "unindent list item",
486
+ category: "list",
487
+ control: false, alt: false, shift: true, key: "Tab",
488
+ visible: context => context.list !== undefined,
489
+ active: context => context.list !== undefined && context.list.isInside(context.root, ListNode),
490
+ handler: context => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, false))
491
+ },
492
+ {
493
+ label: "plain",
494
+ icon: "clear.svg",
495
+ description: "clear formatting",
496
+ category: "text",
497
+ control: true, alt: false, shift: false, key: "0",
498
+ visible: true,
499
+ active: context => context.startIsText || context.endIsText,
500
+ handler: context => context.root.withRangeFormatted(context.range, "")
501
+ },
502
+ {
503
+ label: "bold",
504
+ icon: "bold.svg",
505
+ description: "bold",
506
+ category: "text",
507
+ control: true, alt: false, shift: false, key: "b",
508
+ visible: true,
509
+ active: context => context.startIsText || context.endIsText,
510
+ handler: context => context.root.withRangeFormatted(context.range, "*")
511
+ },
512
+ {
513
+ description: "italic",
514
+ icon: "italic.svg",
515
+ category: "text",
516
+ control: true, alt: false, shift: false, key: "i",
517
+ visible: true,
518
+ active: context => context.startIsText || context.endIsText,
519
+ handler: context => context.root.withRangeFormatted(context.range, "_")
520
+ },
521
+ {
522
+ label: "sub\u2099",
523
+ icon: "sub.svg",
524
+ description: "subscript",
525
+ category: "text",
526
+ control: true, alt: false, shift: false, key: ",",
527
+ visible: true,
528
+ active: context => context.startIsText || context.endIsText,
529
+ handler: context => context.root.withRangeFormatted(context.range, "v")
530
+ },
531
+ {
532
+ label: "super\u207F",
533
+ icon: "super.svg",
534
+ description: "superscript",
535
+ category: "text",
536
+ control: true, alt: false, shift: false, key: ".",
537
+ visible: true,
538
+ active: context => context.startIsText || context.endIsText,
539
+ handler: context => context.root.withRangeFormatted(context.range, "^")
540
+ },
541
+ {
542
+ label: "<code>",
543
+ icon: "code.svg",
544
+ description: "toggle code",
545
+ category: "annotation",
546
+ control: true, alt: false, shift: false, key: "j",
547
+ visible: true,
548
+ active: context => context.startIsText || context.endIsText,
549
+ handler: context => context.meta instanceof InlineCodeNode ?
550
+ rootWithNode(context, context.format, context.format?.withSegmentReplaced(context.meta, context.meta.getText())) :
551
+ context.root.withSegmentAtSelection(context.range, text => new InlineCodeNode(new TextNode(text)))
552
+ },
553
+ {
554
+ label: "link ⚭",
555
+ icon: "link.svg",
556
+ description: "toggle link",
557
+ category: "annotation",
558
+ control: true, alt: false, shift: false, key: "k",
559
+ visible: true,
560
+ active: context => (context.atom === undefined && context.meta === undefined) || context.meta instanceof LinkNode,
561
+ handler: context => context.meta instanceof LinkNode ?
562
+ rootWithNode(context, context.format, context.format?.withSegmentReplaced(context.meta, context.meta.getText())) :
563
+ context.root.withSegmentAtSelection(context.range, text => new LinkNode(new TextNode(text)))
564
+ },
565
+ {
566
+ label: "Aa",
567
+ description: "toggle definition",
568
+ category: "annotation",
569
+ control: true, alt: false, shift: false, key: "d",
570
+ visible: context => context.chapter && context.atom === undefined && context.meta === undefined,
571
+ active: context => context.chapter && context.atom === undefined && context.meta === undefined,
572
+ handler: context => context.meta instanceof DefinitionNode ?
573
+ rootWithNode(context, context.format, context.format?.withSegmentReplaced(context.meta, context.meta.getText())) :
574
+ context.root.withSegmentAtSelection(context.range, text => new DefinitionNode(new TextNode(text)))
575
+ },
576
+ {
577
+ label: "a\u1D43",
578
+ description: "insert footnote",
579
+ category: "annotation",
580
+ control: true, alt: false, shift: false, key: "f",
581
+ visible: context => context.chapter && context.atom === undefined && context.meta === undefined,
582
+ active: context => context.chapter && context.atom === undefined && context.meta === undefined,
583
+ handler: context => context.root.withSegmentAtSelection(context.range, text => new FootnoteNode(new FormatNode("", [new TextNode(text)])))
584
+ },
585
+ {
586
+ label: "a\u00b9",
587
+ description: "insert citations",
588
+ category: "annotation",
589
+ control: true, alt: false, shift: false, key: "t",
590
+ visible: context => context.chapter && context.atom === undefined && context.meta === undefined,
591
+ active: context => context.chapter && context.atom === undefined && context.meta === undefined,
592
+ handler: context => context.root.withSegmentAtSelection(context.range, () => new CitationsNode([]))
593
+ },
594
+ {
595
+ label: "•",
596
+ description: "insert label",
597
+ category: "annotation",
598
+ control: true, alt: false, shift: false, key: "l",
599
+ visible: context => context.chapter && context.atom === undefined && context.meta === undefined,
600
+ active: context => context.chapter && context.atom === undefined && context.meta === undefined,
601
+ handler: context => context.root.withSegmentAtSelection(context.range, () => new LabelNode(""))
602
+ },
603
+ {
604
+ label: "comment",
605
+ icon: "comment.svg",
606
+ description: "insert comment",
607
+ category: "annotation",
608
+ control: true, alt: false, shift: false, key: "'",
609
+ visible: context => context.chapter && context.atom === undefined && context.meta === undefined,
610
+ active: context => context.chapter && context.atom === undefined && context.meta === undefined,
611
+ handler: context => context.root.withSegmentAtSelection(context.range, text => new CommentNode(new FormatNode("", [new TextNode(text)])))
612
+ },
613
+ {
614
+ label: "paragraph",
615
+ icon: "paragraph.svg",
616
+ description: "format as paragraph",
617
+ category: "level",
618
+ control: true, alt: true, shift: false, code: "Digit0",
619
+ visible: context => context.paragraph !== undefined && context.paragraph.getLevel() !== 0,
620
+ active: context => context.paragraph !== undefined && context.paragraph.getLevel() !== 0,
621
+ handler: context => {
622
+ if (context.paragraph === undefined)
623
+ return;
624
+ return rootWithNode(context, context.paragraph, context.paragraph.withLevel(0));
625
+ }
626
+ },
627
+ {
628
+ label: "h1",
629
+ description: "format as 1st level header",
630
+ category: "level",
631
+ control: true, alt: true, shift: false, code: "Digit1",
632
+ visible: context => context.paragraph !== undefined,
633
+ active: context => context.paragraph !== undefined && context.paragraph.getLevel() !== 1,
634
+ handler: context => rootWithNode(context, context.paragraph, context.paragraph?.withLevel(1))
635
+ },
636
+ {
637
+ label: "h2",
638
+ description: "format as 2nd level header",
639
+ category: "level",
640
+ control: true, alt: true, shift: false, code: "Digit2",
641
+ visible: context => context.paragraph !== undefined,
642
+ active: context => context.paragraph !== undefined && context.paragraph.getLevel() !== 2,
643
+ handler: context => rootWithNode(context, context.paragraph, context.paragraph?.withLevel(2))
644
+ },
645
+ {
646
+ label: "h3",
647
+ description: "format as 3rd level header",
648
+ category: "level",
649
+ control: true, alt: true, shift: false, code: "Digit3",
650
+ visible: context => context.paragraph !== undefined,
651
+ active: context => context.paragraph !== undefined && context.paragraph.getLevel() !== 3,
652
+ handler: context => rootWithNode(context, context.paragraph, context.paragraph?.withLevel(3))
653
+ },
654
+ {
655
+ label: "\u2014",
656
+ description: "insert horizontal rule",
657
+ category: "block",
658
+ control: true, alt: false, shift: true, key: "h",
659
+ visible: context => context.paragraph !== undefined,
660
+ active: context => context.paragraph !== undefined && context.atParagraphStart,
661
+ handler: context => context.paragraph ? rootWithNode(context, context.blocks, context.blocks?.withBlockInsertedBefore(context.paragraph, new RuleNode())) : undefined
662
+ },
663
+ {
664
+ label: "🄰",
665
+ description: "insert callout",
666
+ category: "block",
667
+ control: true, alt: false, shift: true, key: "e",
668
+ visible: context => context.paragraph !== undefined,
669
+ active: context => context.paragraph !== undefined && context.atParagraphStart,
670
+ handler: context => {
671
+ // If the caret is in a paragraph in a blocks node, insert a callout before the current paragraph.
672
+ if (context.blocks && context.paragraph) {
673
+ // Make a new callout node with an empty paragraph, insert it before the paragraph the caret is in, and place the caret inside the empty paragraph.
674
+ const newParagraph = new ParagraphNode();
675
+ return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, new CalloutNode([newParagraph])), () => newParagraph.getFirstCaret());
676
+ }
677
+ }
678
+ },
679
+ {
680
+ label: "‟",
681
+ icon: "quote.svg",
682
+ description: "insert quote",
683
+ category: "block",
684
+ control: true, alt: false, shift: true, key: "u",
685
+ visible: context => context.paragraph !== undefined,
686
+ active: context => context.paragraph !== undefined && context.atParagraphStart,
687
+ handler: context => {
688
+ if (context.blocks && context.paragraph) {
689
+ // Make a new quote and place the caret inside the quote's first empty paragraph.
690
+ const newParagraph = new ParagraphNode();
691
+ return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, new QuoteNode([newParagraph])), () => newParagraph.getFirstCaret());
692
+ }
693
+ }
694
+ },
695
+ {
696
+ label: "<code>",
697
+ icon: "code.svg",
698
+ description: "insert code",
699
+ category: "block",
700
+ control: true, alt: false, shift: true, key: "s",
701
+ visible: context => context.paragraph !== undefined,
702
+ active: context => context.paragraph !== undefined && context.atParagraphStart,
703
+ handler: context => {
704
+ if (context.blocks && context.paragraph) {
705
+ const newCode = new CodeNode(new TextNode(), "plaintext", "|");
706
+ return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, newCode), () => { return { node: newCode.getCodeNode(), index: 0 }; });
707
+ }
708
+ }
709
+ },
710
+ {
711
+ label: "image/video",
712
+ icon: "media.svg",
713
+ description: "insert image or video",
714
+ category: "block",
715
+ control: true, alt: false, shift: true, key: "p",
716
+ visible: context => context.paragraph !== undefined,
717
+ active: context => context.paragraph !== undefined && context.atParagraphStart,
718
+ handler: context => {
719
+ if (context.blocks && context.paragraph) {
720
+ // Place the caret inside the code's code node.
721
+ const newEmbed = new EmbedNode("", "");
722
+ return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, newEmbed), () => newEmbed.getCaption().getFirstCaret());
723
+ }
724
+ }
725
+ },
726
+ {
727
+ label: "▦",
728
+ description: "insert table",
729
+ category: "block",
730
+ control: true, alt: false, shift: true, key: "\\",
731
+ visible: context => context.paragraph !== undefined,
732
+ active: context => context.paragraph !== undefined && context.atParagraphStart,
733
+ handler: context => {
734
+ if (context.blocks && context.paragraph) {
735
+ // Create a 3x3 of table rows and columns
736
+ // Return a caret corresponding to the first cell.
737
+ const newRows = [[], [], []];
738
+ for (let r = 0; r < Math.max(1, 3); r++) {
739
+ for (let c = 0; c < Math.max(1, 3); c++)
740
+ newRows[r].push(new FormatNode("", [new TextNode()]));
741
+ }
742
+ const newTable = new TableNode(newRows, "|", new FormatNode("", [new TextNode()]));
743
+ return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, newTable), () => { return { node: newTable.getRows()[0][0].getTextNodes()[0], index: 0 }; });
744
+ }
745
+ }
746
+ },
747
+ {
748
+ label: "bulleted",
749
+ icon: "bullets.svg",
750
+ description: "convert paragraph to bulleted list item",
751
+ category: "list",
752
+ control: true, alt: false, shift: true, key: "7",
753
+ visible: context => context.paragraph !== undefined && context.list === undefined,
754
+ active: context => context.paragraph !== undefined && context.list === undefined,
755
+ handler: context => rootWithNode(context, context.blocks, context.blocks?.withParagraphsAsLists(context.range, false))
756
+ },
757
+ {
758
+ label: "numbered",
759
+ icon: "numbers.svg",
760
+ description: "convert paragraph to numbered list item",
761
+ category: "list",
762
+ control: true, alt: false, shift: true, key: "8",
763
+ visible: context => context.paragraph !== undefined && context.list === undefined,
764
+ active: context => context.paragraph !== undefined && context.list === undefined,
765
+ handler: context => rootWithNode(context, context.blocks, context.blocks?.withParagraphsAsLists(context.range, true))
766
+ },
767
+ {
768
+ label: "bulleted",
769
+ icon: "bullets.svg",
770
+ description: "convert numbered list item to bulleted",
771
+ category: "list",
772
+ control: true, alt: false, shift: true, key: "7",
773
+ visible: context => context.list !== undefined && context.list.isNumbered(),
774
+ active: context => context.list !== undefined && context.list.isNumbered(),
775
+ handler: context => context.list ? rootWithNode(context, context.blocks, context.blocks?.withListAsStyle(context.list, false)) : undefined
776
+ },
777
+ {
778
+ label: "numbered",
779
+ icon: "numbers.svg",
780
+ description: "convert bulleted list item to numbered",
781
+ category: "list",
782
+ control: true, alt: false, shift: true, key: "8",
783
+ visible: context => context.list !== undefined && !context.list.isNumbered(),
784
+ active: context => context.list !== undefined && !context.list.isNumbered(),
785
+ handler: context => context.list ? rootWithNode(context, context.blocks, context.blocks?.withListAsStyle(context.list, true)) : undefined
786
+ },
787
+ {
788
+ label: "paragraph",
789
+ icon: "paragraph.svg",
790
+ description: "convert bulleted list item to paragraph",
791
+ category: "list",
792
+ control: true, alt: false, shift: true, key: ["7", "8"],
793
+ visible: context => context.list !== undefined,
794
+ active: context => context.list !== undefined,
795
+ handler: context => rootWithNode(context, context.blocks, context.blocks?.withListsAsParagraphs(context.range))
796
+ },
797
+ {
798
+ label: "undo",
799
+ icon: "undo.svg",
800
+ description: "undo the last command",
801
+ category: "history",
802
+ control: true, alt: false, shift: false, key: ["z"],
803
+ visible: true,
804
+ active: context => context.undoStack.length > 0 && context.undoPosition < context.undoStack.length - 1,
805
+ handler: context => context.undo()
806
+ },
807
+ {
808
+ label: "redo",
809
+ icon: "redo.svg",
810
+ description: "redo the most recently undone command",
811
+ category: "history",
812
+ control: true, alt: false, shift: true, key: ["z"],
813
+ visible: true,
814
+ active: context => context.undoPosition > 0,
815
+ handler: context => context.redo()
816
+ },
817
+ {
818
+ label: "✂",
819
+ description: "delete the selected content and copy it to the clipboard",
820
+ category: "clipboard",
821
+ control: true, alt: false, shift: false, key: ["x"],
822
+ visible: true,
823
+ active: context => context.root !== undefined && context.isSelection,
824
+ handler: context => {
825
+ if (context.root === undefined)
826
+ return;
827
+ // Save the copied content to the clipboard
828
+ const copy = context.root.copyRange(context.range);
829
+ const edit = context.root.withRangeFormatted(context.range, undefined);
830
+ if (edit === undefined || copy === undefined)
831
+ return;
832
+ context.handleCopy(copy);
833
+ return rootWithNode(context, context.root, edit.root, () => edit.range.start);
834
+ }
835
+ },
836
+ {
837
+ label: "🗎",
838
+ description: "copy the selected content to the clipboard",
839
+ category: "clipboard",
840
+ control: true, alt: false, shift: false, key: ["c"],
841
+ visible: true,
842
+ active: context => context.isSelection,
843
+ handler: context => {
844
+ if (context.root === undefined)
845
+ return;
846
+ // Save the copied content to the clipboard
847
+ const copy = context.root.copyRange(context.range);
848
+ if (copy !== undefined)
849
+ context.handleCopy(copy);
850
+ return undefined;
851
+ }
852
+ },
853
+ {
854
+ label: "⎘",
855
+ description: "paste the content from the clipboard",
856
+ category: "clipboard",
857
+ control: true, alt: false, shift: false, key: ["v"],
858
+ visible: true,
859
+ active: () => true,
860
+ handler: context => {
861
+ if (context.root === undefined)
862
+ return undefined;
863
+ // See if there's something on the clipboard.
864
+ if (navigator.clipboard) {
865
+ navigator.clipboard.read()
866
+ .then((items) => {
867
+ // If there's nothing, paste whatever is on the editor clipboard, if there is something.
868
+ if (items.length === 0 && context.clipboard !== undefined)
869
+ context.handlePaste(context, context.clipboard, true);
870
+ else {
871
+ for (const item of items) {
872
+ for (const type of item.types) {
873
+ if (type === "text/plain")
874
+ item.getType(type)
875
+ .then((blob) => {
876
+ blob.text().then(text => {
877
+ context.handlePaste(context, new TextNode(text), true);
878
+ });
879
+ });
880
+ }
881
+ }
882
+ }
883
+ });
884
+ }
885
+ // If there is no OS clipboard access, but there is something in the editor clipboard, copy it.
886
+ else if (context.clipboard !== undefined) {
887
+ return context.handlePaste(context, context.clipboard, false);
888
+ }
889
+ return undefined;
890
+ }
891
+ },
892
+ {
893
+ label: "insert",
894
+ description: "insert character",
895
+ category: "text",
896
+ control: false, alt: false, shift: undefined, key: undefined,
897
+ visible: false,
898
+ active: (context, key) => context && key !== undefined && key.length === 1,
899
+ handler: (context, utilities, key) => {
900
+ utilities;
901
+ const range = context.range;
902
+ if (key.length === 1) {
903
+ // Insert at the start.
904
+ let sortedRange = range.start.node.sortRange(range);
905
+ let insertionPoint = sortedRange.start;
906
+ let newRoot = context.root;
907
+ // Not a text node? Fail.
908
+ if (!(insertionPoint.node instanceof TextNode))
909
+ return;
910
+ // If there's a selection, remove it before inserting, and insert at the caret returned.
911
+ if (context.isSelection) {
912
+ if (context.code) {
913
+ if (context.code.getCodeNode() === sortedRange.start.node) {
914
+ const newText = insertionPoint.node.withoutRange(sortedRange);
915
+ if (newText === undefined)
916
+ return;
917
+ const edit = newRoot.withNodeReplaced(insertionPoint.node, newText);
918
+ if (edit === undefined)
919
+ return;
920
+ newRoot = edit;
921
+ insertionPoint = { node: newText, index: sortedRange.start.index };
922
+ }
923
+ }
924
+ else {
925
+ // Try to remove the range.
926
+ let edit = newRoot.withRangeFormatted(sortedRange, undefined);
927
+ // If we fail, fail to insert at the selection.
928
+ if (edit === undefined)
929
+ return;
930
+ newRoot = edit.root;
931
+ insertionPoint = edit.range.start;
932
+ }
933
+ }
934
+ // Not a text node? Fail.
935
+ if (!(insertionPoint.node instanceof TextNode))
936
+ return;
937
+ // Update the text node.
938
+ const newText = insertionPoint.node.withCharacterAt(key, insertionPoint.index);
939
+ if (newText === undefined)
940
+ return;
941
+ // Replace the text.
942
+ newRoot = newRoot.withNodeReplaced(insertionPoint.node, newText);
943
+ if (newRoot === undefined)
944
+ return;
945
+ // Update the chapter
946
+ return rootWithNode(context, context.root, newRoot, () => { return { node: newText, index: insertionPoint.index + 1 }; });
947
+ }
948
+ }
949
+ }
950
+ ];
951
+ export default commands;