commands.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
  2. import { Command } from '@codemirror/view'
  3. import {
  4. closeSearchPanel,
  5. openSearchPanel,
  6. searchPanelOpen,
  7. } from '@codemirror/search'
  8. import { toggleRanges, wrapRanges } from '../../commands/ranges'
  9. import {
  10. ancestorListType,
  11. toggleListForRanges,
  12. unwrapBulletList,
  13. unwrapNumberedList,
  14. wrapInBulletList,
  15. wrapInNumberedList,
  16. } from './lists'
  17. import { snippet } from '@codemirror/autocomplete'
  18. import { snippets } from './snippets'
  19. import { minimumListDepthForSelection } from '../../utils/tree-operations/ancestors'
  20. export const toggleBold = toggleRanges('\\textbf')
  21. export const toggleItalic = toggleRanges('\\textit')
  22. export const wrapInHref = wrapRanges('\\href{}{', '}', false, range =>
  23. EditorSelection.cursor(range.from - 2)
  24. )
  25. export const toggleBulletList = toggleListForRanges('itemize')
  26. export const toggleNumberedList = toggleListForRanges('enumerate')
  27. export const wrapInInlineMath = wrapRanges('\\(', '\\)')
  28. export const wrapInDisplayMath = wrapRanges('\n\\[', '\\]\n')
  29. export const ensureEmptyLine = (state: EditorState, range: SelectionRange) => {
  30. let pos = range.anchor
  31. let suffix = ''
  32. const line = state.doc.lineAt(pos)
  33. if (line.text.trim().length) {
  34. pos = Math.min(line.to + 1, state.doc.length)
  35. const nextLine = state.doc.lineAt(pos)
  36. if (nextLine.length) {
  37. suffix = '\n'
  38. }
  39. }
  40. return { pos, suffix }
  41. }
  42. export const insertFigure: Command = view => {
  43. const { state, dispatch } = view
  44. const { pos, suffix } = ensureEmptyLine(state, state.selection.main)
  45. const template = `\n${snippets.figure}\n${suffix}`
  46. snippet(template)({ state, dispatch }, { label: 'Figure' }, pos, pos)
  47. return true
  48. }
  49. export const insertTable: Command = view => {
  50. const { state, dispatch } = view
  51. const { pos, suffix } = ensureEmptyLine(state, state.selection.main)
  52. const template = `\n${snippets.table}\n${suffix}`
  53. snippet(template)({ state, dispatch }, { label: 'Table' }, pos, pos)
  54. return true
  55. }
  56. export const insertCite: Command = view => {
  57. const { state, dispatch } = view
  58. const pos = state.selection.main.anchor
  59. const template = snippets.cite
  60. snippet(template)({ state, dispatch }, { label: 'Cite' }, pos, pos)
  61. return true
  62. }
  63. export const insertRef: Command = view => {
  64. const { state, dispatch } = view
  65. const pos = state.selection.main.anchor
  66. const template = snippets.ref
  67. snippet(template)({ state, dispatch }, { label: 'Ref' }, pos, pos)
  68. return true
  69. }
  70. export const indentDecrease: Command = view => {
  71. if (minimumListDepthForSelection(view.state) < 2) {
  72. return false
  73. }
  74. switch (ancestorListType(view.state)) {
  75. case 'itemize':
  76. return unwrapBulletList(view)
  77. case 'enumerate':
  78. return unwrapNumberedList(view)
  79. default:
  80. return false
  81. }
  82. }
  83. export const cursorIsAtStartOfListItem = (state: EditorState) => {
  84. return state.selection.ranges.every(range => {
  85. const line = state.doc.lineAt(range.from)
  86. const prefix = state.sliceDoc(line.from, range.from)
  87. return /\\item\s*$/.test(prefix)
  88. })
  89. }
  90. export const indentIncrease: Command = view => {
  91. if (minimumListDepthForSelection(view.state) < 1) {
  92. return false
  93. }
  94. switch (ancestorListType(view.state)) {
  95. case 'itemize':
  96. return wrapInBulletList(view)
  97. case 'enumerate':
  98. return wrapInNumberedList(view)
  99. default:
  100. return false
  101. }
  102. }
  103. export const toggleSearch: Command = view => {
  104. if (searchPanelOpen(view.state)) {
  105. closeSearchPanel(view)
  106. } else {
  107. openSearchPanel(view)
  108. }
  109. return true
  110. }