dictionary-modal-content.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { useCallback, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import useAsync from '../../../shared/hooks/use-async'
  4. import { postJSON } from '../../../infrastructure/fetch-json'
  5. import { debugConsole } from '@/utils/debugging'
  6. import {
  7. OLModalBody,
  8. OLModalFooter,
  9. OLModalHeader,
  10. OLModalTitle,
  11. } from '@/features/ui/components/ol/ol-modal'
  12. import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
  13. import OLNotification from '@/features/ui/components/ol/ol-notification'
  14. import OLButton from '@/features/ui/components/ol/ol-button'
  15. import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
  16. import { bsVersion } from '@/features/utils/bootstrap-5'
  17. import { learnedWords as initialLearnedWords } from '@/features/source-editor/extensions/spelling/learned-words'
  18. type DictionaryModalContentProps = {
  19. handleHide: () => void
  20. }
  21. const wordsSortFunction = (a: string, b: string) => a.localeCompare(b)
  22. export default function DictionaryModalContent({
  23. handleHide,
  24. }: DictionaryModalContentProps) {
  25. const { t } = useTranslation()
  26. const [learnedWords, setLearnedWords] = useState<Set<string>>(
  27. initialLearnedWords.global
  28. )
  29. const { isError, runAsync } = useAsync()
  30. const handleRemove = useCallback(
  31. word => {
  32. runAsync(postJSON('/spelling/unlearn', { body: { word } }))
  33. .then(() => {
  34. setLearnedWords(value => {
  35. value.delete(word)
  36. return new Set(value)
  37. })
  38. window.dispatchEvent(
  39. new CustomEvent('editor:remove-learned-word', { detail: word })
  40. )
  41. })
  42. .catch(debugConsole.error)
  43. },
  44. [runAsync]
  45. )
  46. return (
  47. <>
  48. <OLModalHeader closeButton>
  49. <OLModalTitle>{t('edit_dictionary')}</OLModalTitle>
  50. </OLModalHeader>
  51. <OLModalBody>
  52. {isError ? (
  53. <OLNotification
  54. type="error"
  55. content={t('generic_something_went_wrong')}
  56. />
  57. ) : null}
  58. {learnedWords.size > 0 ? (
  59. <ul className="list-unstyled dictionary-entries-list">
  60. {[...learnedWords].sort(wordsSortFunction).map(learnedWord => (
  61. <li key={learnedWord} className="dictionary-entry">
  62. <span className="dictionary-entry-name">{learnedWord}</span>
  63. <OLTooltip
  64. id={`tooltip-remove-learned-word-${learnedWord}`}
  65. description={t('edit_dictionary_remove')}
  66. overlayProps={{ delay: 0 }}
  67. >
  68. <OLIconButton
  69. variant="danger"
  70. size="sm"
  71. onClick={() => handleRemove(learnedWord)}
  72. bs3Props={{ bsSize: 'xsmall' }}
  73. icon={
  74. bsVersion({
  75. bs5: 'delete',
  76. bs3: 'trash-o',
  77. }) as string
  78. }
  79. accessibilityLabel={t('edit_dictionary_remove')}
  80. />
  81. </OLTooltip>
  82. </li>
  83. ))}
  84. </ul>
  85. ) : (
  86. <p className="dictionary-empty-body text-center">
  87. <i>{t('edit_dictionary_empty')}</i>
  88. </p>
  89. )}
  90. </OLModalBody>
  91. <OLModalFooter>
  92. <OLButton variant="secondary" onClick={handleHide}>
  93. {t('close')}
  94. </OLButton>
  95. </OLModalFooter>
  96. </>
  97. )
  98. }