index.ts 3.5 KB

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