index.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { EditorView, ViewPlugin } from '@codemirror/view'
  2. import {
  3. Compartment,
  4. Facet,
  5. StateEffect,
  6. StateField,
  7. TransactionSpec,
  8. } from '@codemirror/state'
  9. import { misspelledWordsField, resetMisspelledWords } from './misspelled-words'
  10. import {
  11. ignoredWordsField,
  12. resetSpellChecker,
  13. updateAfterAddingIgnoredWord,
  14. } from './ignored-words'
  15. import { addWordToCache, cacheField, removeWordFromCache } from './cache'
  16. import { spellingMenuField } from './context-menu'
  17. import { SpellChecker } from './spellchecker'
  18. import { parserWatcher } from '../wait-for-parser'
  19. const spellCheckLanguageConf = new Compartment()
  20. const spellCheckLanguageFacet = Facet.define<string | undefined>()
  21. type Options = { spellCheckLanguage?: string }
  22. /*
  23. * Create the spelling extensions array, based on options passed.
  24. */
  25. export const spelling = ({ spellCheckLanguage }: Options) => {
  26. return [
  27. EditorView.baseTheme({
  28. '.ol-cm-spelling-error': {
  29. textDecorationColor: 'red',
  30. textDecorationLine: 'underline',
  31. textDecorationStyle: 'dotted',
  32. textDecorationThickness: '2px',
  33. textDecorationSkipInk: 'none',
  34. textUnderlineOffset: '0.2em',
  35. },
  36. '.cm-tooltip.ol-cm-spelling-context-menu-tooltip': {
  37. borderWidth: '0',
  38. },
  39. }),
  40. parserWatcher,
  41. spellCheckLanguageConf.of(spellCheckLanguageFacet.of(spellCheckLanguage)),
  42. spellCheckField,
  43. misspelledWordsField,
  44. ignoredWordsField,
  45. cacheField,
  46. spellingMenuField,
  47. ]
  48. }
  49. const spellCheckField = StateField.define<SpellChecker | null>({
  50. create(state) {
  51. const [spellCheckLanguage] = state.facet(spellCheckLanguageFacet)
  52. return spellCheckLanguage ? new SpellChecker(spellCheckLanguage) : null
  53. },
  54. update(value, tr) {
  55. for (const effect of tr.effects) {
  56. if (effect.is(setSpellCheckLanguageEffect)) {
  57. value?.destroy()
  58. return effect.value ? new SpellChecker(effect.value) : null
  59. }
  60. }
  61. return value
  62. },
  63. provide(field) {
  64. return [
  65. ViewPlugin.define(view => {
  66. return {
  67. destroy: () => {
  68. view.state.field(field)?.destroy()
  69. },
  70. }
  71. }),
  72. EditorView.domEventHandlers({
  73. focus: (event, view) => {
  74. if (view.state.facet(EditorView.editable)) {
  75. view.state.field(field)?.spellCheckAsap(view)
  76. }
  77. },
  78. }),
  79. EditorView.updateListener.of(update => {
  80. if (update.state.facet(EditorView.editable)) {
  81. update.state.field(field)?.handleUpdate(update)
  82. }
  83. }),
  84. ]
  85. },
  86. })
  87. const setSpellCheckLanguageEffect = StateEffect.define<string | undefined>()
  88. export const setSpelling = ({
  89. spellCheckLanguage,
  90. }: Options): TransactionSpec => {
  91. return {
  92. effects: [
  93. resetMisspelledWords.of(null),
  94. spellCheckLanguageConf.reconfigure(
  95. spellCheckLanguageFacet.of(spellCheckLanguage)
  96. ),
  97. setSpellCheckLanguageEffect.of(spellCheckLanguage),
  98. ],
  99. }
  100. }
  101. export const addLearnedWord = (
  102. spellCheckLanguage: string,
  103. word: string
  104. ): TransactionSpec => {
  105. return {
  106. effects: [
  107. addWordToCache.of({
  108. lang: spellCheckLanguage,
  109. wordText: word,
  110. value: true,
  111. }),
  112. updateAfterAddingIgnoredWord.of(word),
  113. ],
  114. }
  115. }
  116. export const removeLearnedWord = (
  117. spellCheckLanguage: string,
  118. word: string
  119. ): TransactionSpec => {
  120. return {
  121. effects: [
  122. removeWordFromCache.of({
  123. lang: spellCheckLanguage,
  124. wordText: word,
  125. }),
  126. ],
  127. }
  128. }
  129. export const resetLearnedWords = (): TransactionSpec => {
  130. return {
  131. effects: [resetSpellChecker.of(null)],
  132. }
  133. }