Jelajahi Sumber

Merge pull request #8148 from overleaf/ta-pr-dictionary-style

Polish Dictionary Modal UI

GitOrigin-RevId: 48aceb56c84a218380c619ecc5cd527f5062d3c4
Timothée Alby 4 tahun lalu
induk
melakukan
c66278cf89

+ 1 - 1
services/web/app/views/project/editor/left-menu.pug

@@ -120,7 +120,7 @@ aside#left-menu.full-size(
 			if dictionaryEditorEnabled
 				.form-controls(ng-controller="DictionaryModalController")
 					label #{translate("dictionary")}
-					button.btn.btn-default.btn-sm(ng-click="openModal()") #{translate("edit")}
+					button.btn.btn-default.btn-xs(ng-click="openModal()") #{translate("edit")}
 
 					dictionary-modal(
 						handle-hide="handleHide"

+ 49 - 21
services/web/frontend/js/features/dictionary/components/dictionary-modal-content.tsx

@@ -1,4 +1,4 @@
-import { useCallback } from 'react'
+import { useCallback, useMemo } from 'react'
 import { useTranslation } from 'react-i18next'
 import { Alert, Button, Modal } from 'react-bootstrap'
 import Icon from '../../../shared/components/icon'
@@ -6,11 +6,14 @@ import Tooltip from '../../../shared/components/tooltip'
 import useAsync from '../../../shared/hooks/use-async'
 import { postJSON } from '../../../infrastructure/fetch-json'
 import ignoredWords from '../ignored-words'
+import BetaBadge from '../../../shared/components/beta-badge'
 
 type DictionaryModalContentProps = {
   handleHide: () => void
 }
 
+const wordsSortFunction = (a, b) => a.localeCompare(b)
+
 export default function DictionaryModalContent({
   handleHide,
 }: DictionaryModalContentProps) {
@@ -32,10 +35,31 @@ export default function DictionaryModalContent({
     [runAsync]
   )
 
+  const betaBadgeTooltipProps = useMemo(() => {
+    return {
+      id: 'dictionary-modal-tooltip',
+      placement: 'bottom',
+      className: 'tooltip-wide',
+      text: (
+        <>
+          We are beta testing the dictionary manager.
+          <br />
+          Click to give feedback
+        </>
+      ),
+    }
+  }, [])
+
   return (
     <>
       <Modal.Header closeButton>
-        <Modal.Title>{t('edit_dictionary')}</Modal.Title>
+        <Modal.Title>
+          {t('edit_dictionary')}{' '}
+          <BetaBadge
+            tooltip={betaBadgeTooltipProps}
+            url="https://forms.gle/8cLBEW6HU9mDKBPX9"
+          />
+        </Modal.Title>
       </Modal.Header>
 
       <Modal.Body>
@@ -44,26 +68,30 @@ export default function DictionaryModalContent({
         ) : null}
 
         {ignoredWords.learnedWords?.size > 0 ? (
-          <ul className="list-unstyled">
-            {[...ignoredWords.learnedWords].sort().map(learnedWord => (
-              <li key={learnedWord}>
-                <Tooltip
-                  id={`tooltip-remove-learned-word-${learnedWord}`}
-                  description={t('edit_dictionary_remove')}
-                >
-                  <Button
-                    className="btn-link action-btn"
-                    onClick={() => handleRemove(learnedWord)}
+          <ul className="list-unstyled dictionary-entries-list">
+            {[...ignoredWords.learnedWords]
+              .sort(wordsSortFunction)
+              .map(learnedWord => (
+                <li key={learnedWord} className="dictionary-entry">
+                  <span className="dictionary-entry-name">{learnedWord}</span>
+                  <Tooltip
+                    id={`tooltip-remove-learned-word-${learnedWord}`}
+                    description={t('edit_dictionary_remove')}
+                    overlayProps={{ delay: 0 }}
                   >
-                    <Icon
-                      type="trash-o"
-                      accessibilityLabel={t('edit_dictionary_remove')}
-                    />
-                  </Button>
-                </Tooltip>
-                {learnedWord}
-              </li>
-            ))}
+                    <Button
+                      bsStyle="danger"
+                      bsSize="xs"
+                      onClick={() => handleRemove(learnedWord)}
+                    >
+                      <Icon
+                        type="trash-o"
+                        accessibilityLabel={t('edit_dictionary_remove')}
+                      />
+                    </Button>
+                  </Tooltip>
+                </li>
+              ))}
           </ul>
         ) : (
           <i>{t('edit_dictionary_empty')}</i>

+ 1 - 0
services/web/frontend/stylesheets/app/editor.less

@@ -15,6 +15,7 @@
 @import './editor/publish-modal.less';
 @import './editor/outline.less';
 @import './editor/logs.less';
+@import './editor/dictionary.less';
 
 @ui-layout-toggler-def-height: 50px;
 @ui-resizer-size: 7px;

+ 29 - 0
services/web/frontend/stylesheets/app/editor/dictionary.less

@@ -0,0 +1,29 @@
+#dictionary-modal {
+  .modal-body {
+    padding: 0;
+  }
+}
+
+.dictionary-entries-list {
+  overflow-y: scroll;
+  max-height: calc(100vh - 225px);
+  margin: 0;
+  padding: @padding-sm;
+}
+
+.dictionary-entry {
+  word-break: break-all;
+  display: flex;
+  padding: @padding-sm;
+  border-bottom: solid 1px @modal-header-border-color;
+  align-items: center;
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+
+.dictionary-entry-name {
+  flex-grow: 1;
+  padding-right: @padding-xs;
+}

+ 0 - 6
services/web/frontend/stylesheets/app/editor/left-menu.less

@@ -126,9 +126,3 @@
   background-color: #999;
   z-index: 99;
 }
-
-#dictionary-modal {
-  li {
-    word-break: break-all;
-  }
-}

+ 2 - 2
services/web/test/frontend/features/dictionary/components/dictionary-modal-content.test.js

@@ -33,7 +33,7 @@ describe('<DictionaryModalContent />', function () {
 
   it('removes words', async function () {
     fetchMock.post('/spelling/unlearn', 200)
-    setLearnedWords(['foo', 'bar'])
+    setLearnedWords(['Foo', 'bar'])
     renderWithEditorContext(<DictionaryModal show handleHide={() => {}} />)
     screen.getByText('bar')
     const [firstButton] = screen.getAllByRole('button', {
@@ -41,7 +41,7 @@ describe('<DictionaryModalContent />', function () {
     })
     fireEvent.click(firstButton)
     expect(screen.queryByText('bar')).to.not.exist
-    screen.getByText('foo')
+    screen.getByText('Foo')
   })
 
   it('handles errors', async function () {