Przeglądaj źródła

Move symbol palette to module and add overlay (#5396)

GitOrigin-RevId: 649dd56aaecd60662bd2bf534bda323ee541874f
Alf Eaton 4 lat temu
rodzic
commit
3feca1ffc6
31 zmienionych plików z 245 dodań i 1572 usunięć
  1. 1 1
      services/web/app/views/project/editor.pug
  2. 0 44
      services/web/app/views/project/editor/editor-no-symbol-palette.pug
  3. 7 5
      services/web/app/views/project/editor/editor-pane.pug
  4. 1 4
      services/web/app/views/project/editor/editor.pug
  5. 0 2
      services/web/app/views/project/editor/symbol-palette.pug
  6. 1 0
      services/web/config/settings.defaults.js
  7. 4 1
      services/web/frontend/extracted-translations.json
  8. 2 33
      services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt.js
  9. 0 61
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js
  10. 0 103
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js
  11. 0 29
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-info-link.js
  12. 0 67
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-item.js
  13. 0 86
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-items.js
  14. 0 44
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js
  15. 0 27
      services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js
  16. 0 14
      services/web/frontend/js/features/symbol-palette/components/symbol-palette.js
  17. 0 6
      services/web/frontend/js/features/symbol-palette/controllers/symbol-palette-controller.js
  18. 0 872
      services/web/frontend/js/features/symbol-palette/data/symbols.json
  19. 0 45
      services/web/frontend/js/features/symbol-palette/utils/categories.js
  20. 0 1
      services/web/frontend/js/ide/editor/EditorManager.js
  21. 44 0
      services/web/frontend/js/shared/components/upgrade-benefits.js
  22. 12 0
      services/web/frontend/js/shared/context/editor-context.js
  23. 0 20
      services/web/frontend/stories/symbol-palette.stories.js
  24. 4 0
      services/web/frontend/stylesheets/_style_includes.less
  25. 0 1
      services/web/frontend/stylesheets/app/editor.less
  26. 8 0
      services/web/frontend/stylesheets/app/editor/toolbar.less
  27. 1 0
      services/web/frontend/stylesheets/core/ol-light-variables.less
  28. 1 0
      services/web/frontend/stylesheets/core/variables.less
  29. 155 0
      services/web/frontend/stylesheets/modules/symbol-palette.less
  30. 4 2
      services/web/locales/en.json
  31. 0 104
      services/web/test/frontend/features/symbol-palette/components/symbol-palette.test.js

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

@@ -88,7 +88,7 @@ block content
 					ng-class="{ 'ide-history-open' : (ui.view == 'history' && history.isV2) }",
 					layout="main",
 					ng-hide="state.loading",
-					resize-on="layout:chat:resize,history:toggle,layout:flat-screen:toggle",
+					resize-on="layout:chat:resize,history:toggle,layout:flat-screen:toggle,symbol-palette-toggled",
 					minimum-restore-size-west="130"
 					custom-toggler-pane=hasFeature('custom-togglers') ? "west" : false
 					custom-toggler-msg-when-open=hasFeature('custom-togglers') ? translate("tooltip_hide_filetree") : false

+ 0 - 44
services/web/app/views/project/editor/editor-no-symbol-palette.pug

@@ -1,44 +0,0 @@
-.ui-layout-center(
-		ng-controller="ReviewPanelController",
-		ng-class="{\
-			'rp-unsupported': editor.showRichText,\
-			'rp-state-current-file': (reviewPanel.subView === SubViews.CUR_FILE),\
-			'rp-state-current-file-expanded': (reviewPanel.subView === SubViews.CUR_FILE && ui.reviewPanelOpen),\
-			'rp-state-current-file-mini': (reviewPanel.subView === SubViews.CUR_FILE && !ui.reviewPanelOpen),\
-			'rp-state-overview': (reviewPanel.subView === SubViews.OVERVIEW),\
-			'rp-size-mini': ui.miniReviewPanelVisible,\
-			'rp-size-expanded': ui.reviewPanelOpen,\
-			'rp-layout-left': reviewPanel.layoutToLeft,\
-			'rp-loading-threads': reviewPanel.loadingThreads,\
-		}"
-	)
-		.loading-panel(
-			ng-show="(!editor.sharejs_doc || editor.opening) && !editor.error_state",
-			style=showRichText ? "top: 32px" : "",
-		)
-			span(ng-show="editor.open_doc_id")
-				i.fa.fa-spin.fa-refresh
-				|   #{translate("loading")}…
-			span(ng-show="!editor.open_doc_id")
-				i.fa.fa-arrow-left
-				|   #{translate("open_a_file_on_the_left")}
-
-		if moduleIncludesAvailable('editor:main')
-			!= moduleIncludes('editor:main', locals)
-		else
-			.toolbar.toolbar-editor
-
-		.multi-selection-ongoing(
-			ng-show="multiSelectedCount > 0"
-		)
-			.multi-selection-message
-				h4 {{ multiSelectedCount }} #{translate('files_selected')}
-
-		if showNewSourceEditor
-			if moduleIncludesAvailable('editor:source-editor')
-				!= moduleIncludes('editor:source-editor', locals)
-		else
-			include ./source-editor
-
-		if !isRestrictedTokenMember
-			include ./review-panel

+ 7 - 5
services/web/app/views/project/editor/editor-with-symbol-palette.pug → services/web/app/views/project/editor/editor-pane.pug

@@ -17,10 +17,10 @@
 			vertical-resizable-panes="symbol-palette-resizer"
 			vertical-resizable-panes-hidden-externally-on="symbol-palette-toggled"
 			vertical-resizable-panes-hidden-initially="true"
-			vertical-resizable-panes-default-size="196"
-			vertical-resizable-panes-min-size="144"
+			vertical-resizable-panes-default-size="250"
+			vertical-resizable-panes-min-size="250"
 			vertical-resizable-panes-max-size="336"
-			vertical-resizable-panes-resize-on="layout:flat-screen:toggle"
+			vertical-resizable-panes-resize-on="layout:flat-screen:toggle,symbol-palette-toggled"
 		)
 			.div(vertical-resizable-top)
 
@@ -55,5 +55,7 @@
 				if !isRestrictedTokenMember
 							include ./review-panel
 
-			.div(vertical-resizable-bottom)
-				include ./symbol-palette
+			if moduleIncludesAvailable('editor:symbol-palette')
+				.div(vertical-resizable-bottom)
+					!= moduleIncludes('editor:symbol-palette', locals)
+			

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

@@ -12,10 +12,7 @@ div.full-size(
 	custom-toggler-msg-when-open=hasFeature('custom-togglers') ? translate("tooltip_hide_pdf") : false
 	custom-toggler-msg-when-closed=hasFeature('custom-togglers') ? translate("tooltip_show_pdf") : false
 )
-	if showSymbolPalette
-		include ./editor-with-symbol-palette
-	else
-		include ./editor-no-symbol-palette
+	include ./editor-pane
 
 	.ui-layout-east
 		div(ng-if="ui.pdfLayout == 'sideBySide'")

+ 0 - 2
services/web/app/views/project/editor/symbol-palette.pug

@@ -1,2 +0,0 @@
-if showSymbolPalette
-	symbol-palette(show="editor.showSymbolPalette" handle-select="editor.insertSymbol")

+ 1 - 0
services/web/config/settings.defaults.js

@@ -737,6 +737,7 @@ module.exports = {
     tprLinkedFileInfo: [],
     tprLinkedFileRefreshError: [],
     contactUsModal: [],
+    editorToolbarButtons: [],
   },
 
   moduleImportSequence: ['launchpad', 'server-ce-scripts', 'user-activate'],

+ 4 - 1
services/web/frontend/extracted-translations.json

@@ -90,8 +90,8 @@
   "file_name_in_this_project": "",
   "file_outline": "",
   "files_cannot_include_invalid_characters": "",
-  "find_out_more_about_latex_symbols": "",
   "find_out_more_about_the_file_outline": "",
+  "find_the_symbols_you_need_with_premium": "",
   "first_error_popup_label": "",
   "following_paths_conflict": "",
   "free_accounts_have_timeout_upgrade_to_increase": "",
@@ -170,6 +170,7 @@
   "layout": "",
   "learn_how_to_make_documents_compile_quickly": "",
   "learn_more_about_link_sharing": "",
+  "learn_more_about_the_symbol_palette": "",
   "link_sharing_is_off": "",
   "link_sharing_is_on": "",
   "link_to_github": "",
@@ -345,6 +346,7 @@
   "too_recently_compiled": "",
   "total_words": "",
   "try_it_for_free": "",
+  "try_premium_for_free": "",
   "try_recompile_project": "",
   "try_refresh_page": "",
   "turn_off_link_sharing": "",
@@ -367,6 +369,7 @@
   "view_warning": "",
   "view_warning_plural": "",
   "we_cant_find_any_sections_or_subsections_in_this_file": "",
+  "with_premium_subscription_you_also_get": "",
   "word_count": "",
   "work_offline": "",
   "work_with_non_overleaf_users": "",

+ 2 - 33
services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt.js

@@ -1,9 +1,9 @@
 import { useTranslation } from 'react-i18next'
 import { useEditorContext } from '../../../shared/context/editor-context'
-import Icon from '../../../shared/components/icon'
 import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
 import { memo } from 'react'
 import PdfLogEntry from './pdf-log-entry'
+import UpgradeBenefits from '../../../shared/components/upgrade-benefits'
 
 function TimeoutUpgradePrompt() {
   const { t } = useTranslation()
@@ -26,38 +26,7 @@ function TimeoutUpgradePrompt() {
           <p>{t('free_accounts_have_timeout_upgrade_to_increase')}</p>
           <p>{t('plus_upgraded_accounts_receive')}:</p>
           <div>
-            <ul className="list-unstyled">
-              <li>
-                <Icon type="check" />
-                &nbsp;
-                {t('unlimited_projects')}
-              </li>
-              <li>
-                <Icon type="check" />
-                &nbsp;
-                {t('collabs_per_proj', { collabcount: 'Multiple' })}
-              </li>
-              <li>
-                <Icon type="check" />
-                &nbsp;
-                {t('full_doc_history')}
-              </li>
-              <li>
-                <Icon type="check" />
-                &nbsp;
-                {t('sync_to_dropbox')}
-              </li>
-              <li>
-                <Icon type="check" />
-                &nbsp;
-                {t('sync_to_github')}
-              </li>
-              <li>
-                <Icon type="check" />
-                &nbsp;
-                {t('compile_larger_projects')}
-              </li>
-            </ul>
+            <UpgradeBenefits />
           </div>
           {isProjectOwner && (
             <p className="text-center">

+ 0 - 61
services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js

@@ -1,61 +0,0 @@
-import { TabPanels, TabPanel } from '@reach/tabs'
-import { useTranslation } from 'react-i18next'
-import PropTypes from 'prop-types'
-import SymbolPaletteItems from './symbol-palette-items'
-
-export default function SymbolPaletteBody({
-  categories,
-  categorisedSymbols,
-  filteredSymbols,
-  handleSelect,
-  focusInput,
-}) {
-  const { t } = useTranslation()
-
-  // searching with matches: show the matched symbols
-  // searching with no matches: show a message
-  // note: include empty tab panels so that aria-controls on tabs can still reference the panel ids
-  if (filteredSymbols) {
-    return (
-      <>
-        {filteredSymbols.length ? (
-          <SymbolPaletteItems
-            items={filteredSymbols}
-            handleSelect={handleSelect}
-            focusInput={focusInput}
-          />
-        ) : (
-          <div className="symbol-palette-empty">{t('no_symbols_found')}</div>
-        )}
-
-        <TabPanels>
-          {categories.map(category => (
-            <TabPanel key={category.id} tabIndex={-1} />
-          ))}
-        </TabPanels>
-      </>
-    )
-  }
-
-  // not searching: show the symbols grouped by category
-  return (
-    <TabPanels>
-      {categories.map(category => (
-        <TabPanel key={category.id} tabIndex={-1}>
-          <SymbolPaletteItems
-            items={categorisedSymbols[category.id]}
-            handleSelect={handleSelect}
-            focusInput={focusInput}
-          />
-        </TabPanel>
-      ))}
-    </TabPanels>
-  )
-}
-SymbolPaletteBody.propTypes = {
-  categories: PropTypes.arrayOf(PropTypes.object).isRequired,
-  categorisedSymbols: PropTypes.object,
-  filteredSymbols: PropTypes.arrayOf(PropTypes.object),
-  handleSelect: PropTypes.func.isRequired,
-  focusInput: PropTypes.func.isRequired,
-}

+ 0 - 103
services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js

@@ -1,103 +0,0 @@
-import { Tabs } from '@reach/tabs'
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import PropTypes from 'prop-types'
-import { matchSorter } from 'match-sorter'
-
-import symbols from '../data/symbols.json'
-import { buildCategorisedSymbols, createCategories } from '../utils/categories'
-import SymbolPaletteSearch from './symbol-palette-search'
-import SymbolPaletteBody from './symbol-palette-body'
-import SymbolPaletteTabs from './symbol-palette-tabs'
-// import SymbolPaletteInfoLink from './symbol-palette-info-link'
-import BetaBadge from '../../../shared/components/beta-badge'
-
-import '@reach/tabs/styles.css'
-
-export default function SymbolPaletteContent({ handleSelect }) {
-  const [input, setInput] = useState('')
-
-  const { t } = useTranslation()
-
-  // build the list of categories with translated labels
-  const categories = useMemo(() => createCategories(t), [t])
-
-  // group the symbols by category
-  const categorisedSymbols = useMemo(
-    () => buildCategorisedSymbols(categories),
-    [categories]
-  )
-
-  // select symbols which match the input
-  const filteredSymbols = useMemo(() => {
-    if (input === '') {
-      return null
-    }
-
-    const words = input.trim().split(/\s+/)
-
-    return words.reduceRight(
-      (symbols, word) =>
-        matchSorter(symbols, word, {
-          keys: ['command', 'description', 'character', 'aliases'],
-          threshold: matchSorter.rankings.CONTAINS,
-        }),
-      symbols
-    )
-  }, [input])
-
-  const inputRef = useRef(null)
-
-  // allow the input to be focused
-  const focusInput = useCallback(() => {
-    if (inputRef.current) {
-      inputRef.current.focus()
-    }
-  }, [])
-
-  // focus the input when the symbol palette is opened
-  useEffect(() => {
-    if (inputRef.current) {
-      inputRef.current.focus()
-    }
-  }, [])
-
-  return (
-    <Tabs className="symbol-palette-container">
-      <div className="symbol-palette">
-        <div className="symbol-palette-header">
-          <SymbolPaletteTabs
-            categories={categories}
-            disabled={input.length > 0}
-          />
-          <div className="symbol-palette-header-group">
-            <BetaBadge
-              tooltip={{
-                id: 'tooltip-symbol-palette-beta',
-                text:
-                  'The Symbol Palette is a beta feature. Click here to give feedback.',
-                placement: 'top',
-              }}
-              url="https://forms.gle/BybHV5svGE8rJ6Ki9"
-            />
-            {/* NOTE: replace the beta badge with this info link when rolling out to all users */}
-            {/* <SymbolPaletteInfoLink /> */}
-            <SymbolPaletteSearch setInput={setInput} inputRef={inputRef} />
-          </div>
-        </div>
-        <div className="symbol-palette-body">
-          <SymbolPaletteBody
-            categories={categories}
-            categorisedSymbols={categorisedSymbols}
-            filteredSymbols={filteredSymbols}
-            handleSelect={handleSelect}
-            focusInput={focusInput}
-          />
-        </div>
-      </div>
-    </Tabs>
-  )
-}
-SymbolPaletteContent.propTypes = {
-  handleSelect: PropTypes.func.isRequired,
-}

+ 0 - 29
services/web/frontend/js/features/symbol-palette/components/symbol-palette-info-link.js

@@ -1,29 +0,0 @@
-import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap'
-import { useTranslation } from 'react-i18next'
-
-export default function SymbolPaletteInfoLink() {
-  const { t } = useTranslation()
-
-  return (
-    <OverlayTrigger
-      placement="top"
-      trigger={['hover', 'focus']}
-      overlay={
-        <Tooltip id="tooltip-symbol-palette-info">
-          {t('find_out_more_about_latex_symbols')}
-        </Tooltip>
-      }
-    >
-      <Button
-        bsStyle="link"
-        bsSize="small"
-        className="symbol-palette-info-link"
-        href="https://www.overleaf.com/learn/latex/List_of_Greek_letters_and_math_symbols"
-        target="_blank"
-        rel="noopener noreferrer"
-      >
-        <span className="info-badge" />
-      </Button>
-    </OverlayTrigger>
-  )
-}

+ 0 - 67
services/web/frontend/js/features/symbol-palette/components/symbol-palette-item.js

@@ -1,67 +0,0 @@
-import { useEffect, useRef } from 'react'
-import { OverlayTrigger, Tooltip } from 'react-bootstrap'
-import PropTypes from 'prop-types'
-
-export default function SymbolPaletteItem({
-  focused,
-  handleSelect,
-  handleKeyDown,
-  symbol,
-}) {
-  const buttonRef = useRef(null)
-
-  // call focus() on this item when appropriate
-  useEffect(() => {
-    if (
-      focused &&
-      buttonRef.current &&
-      document.activeElement?.closest('.symbol-palette-items')
-    ) {
-      buttonRef.current.focus()
-    }
-  }, [focused])
-
-  return (
-    <OverlayTrigger
-      placement="top"
-      trigger={['hover', 'focus']}
-      overlay={
-        <Tooltip id={`tooltip-symbol-${symbol.codepoint}`}>
-          <div className="symbol-palette-item-description">
-            {symbol.description}
-          </div>
-          <div className="symbol-palette-item-command">{symbol.command}</div>
-          {symbol.notes && (
-            <div className="symbol-palette-item-notes">{symbol.notes}</div>
-          )}
-        </Tooltip>
-      }
-    >
-      <button
-        key={symbol.codepoint}
-        className="symbol-palette-item"
-        onClick={() => handleSelect(symbol)}
-        onKeyDown={handleKeyDown}
-        tabIndex={focused ? 0 : -1}
-        ref={buttonRef}
-        role="option"
-        aria-label={symbol.description}
-        aria-selected={focused ? 'true' : 'false'}
-      >
-        {symbol.character}
-      </button>
-    </OverlayTrigger>
-  )
-}
-SymbolPaletteItem.propTypes = {
-  symbol: PropTypes.shape({
-    codepoint: PropTypes.string.isRequired,
-    description: PropTypes.string.isRequired,
-    command: PropTypes.string.isRequired,
-    character: PropTypes.string.isRequired,
-    notes: PropTypes.string,
-  }),
-  handleKeyDown: PropTypes.func.isRequired,
-  handleSelect: PropTypes.func.isRequired,
-  focused: PropTypes.bool,
-}

+ 0 - 86
services/web/frontend/js/features/symbol-palette/components/symbol-palette-items.js

@@ -1,86 +0,0 @@
-import { useCallback, useEffect, useState } from 'react'
-import PropTypes from 'prop-types'
-import SymbolPaletteItem from './symbol-palette-item'
-
-export default function SymbolPaletteItems({
-  items,
-  handleSelect,
-  focusInput,
-}) {
-  const [focusedIndex, setFocusedIndex] = useState(0)
-
-  // reset the focused item when the list of items changes
-  useEffect(() => {
-    setFocusedIndex(0)
-  }, [items])
-
-  // navigate through items with left and right arrows
-  const handleKeyDown = useCallback(
-    event => {
-      if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
-        return
-      }
-
-      switch (event.key) {
-        // focus previous item
-        case 'ArrowLeft':
-        case 'ArrowUp':
-          setFocusedIndex(index => (index > 0 ? index - 1 : items.length - 1))
-          break
-
-        // focus next item
-        case 'ArrowRight':
-        case 'ArrowDown':
-          setFocusedIndex(index => (index < items.length - 1 ? index + 1 : 0))
-          break
-
-        // focus first item
-        case 'Home':
-          setFocusedIndex(0)
-          break
-
-        // focus last item
-        case 'End':
-          setFocusedIndex(items.length - 1)
-          break
-
-        // allow the default action
-        case 'Enter':
-        case ' ':
-          break
-
-        // any other key returns focus to the input
-        default:
-          focusInput()
-          break
-      }
-    },
-    [focusInput, items.length]
-  )
-
-  return (
-    <div className="symbol-palette-items" role="listbox" aria-label="Symbols">
-      {items.map((symbol, index) => (
-        <SymbolPaletteItem
-          key={symbol.codepoint}
-          symbol={symbol}
-          handleSelect={symbol => {
-            handleSelect(symbol)
-            setFocusedIndex(index)
-          }}
-          handleKeyDown={handleKeyDown}
-          focused={index === focusedIndex}
-        />
-      ))}
-    </div>
-  )
-}
-SymbolPaletteItems.propTypes = {
-  items: PropTypes.arrayOf(
-    PropTypes.shape({
-      codepoint: PropTypes.string.isRequired,
-    })
-  ).isRequired,
-  handleSelect: PropTypes.func.isRequired,
-  focusInput: PropTypes.func.isRequired,
-}

+ 0 - 44
services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js

@@ -1,44 +0,0 @@
-import { useCallback, useEffect, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import PropTypes from 'prop-types'
-import { FormControl } from 'react-bootstrap'
-import useDebounce from '../../../shared/hooks/use-debounce'
-
-export default function SymbolPaletteSearch({ setInput, inputRef }) {
-  const [localInput, setLocalInput] = useState('')
-
-  // debounce the search input until a typing delay
-  const debouncedLocalInput = useDebounce(localInput, 250)
-
-  useEffect(() => {
-    setInput(debouncedLocalInput)
-  }, [debouncedLocalInput, setInput])
-
-  const { t } = useTranslation()
-
-  const inputRefCallback = useCallback(
-    element => {
-      inputRef.current = element
-    },
-    [inputRef]
-  )
-
-  return (
-    <FormControl
-      className="symbol-palette-search"
-      type="search"
-      inputRef={inputRefCallback}
-      id="symbol-palette-input"
-      aria-label="Search"
-      value={localInput}
-      placeholder={t('search') + '…'}
-      onChange={event => {
-        setLocalInput(event.target.value)
-      }}
-    />
-  )
-}
-SymbolPaletteSearch.propTypes = {
-  setInput: PropTypes.func.isRequired,
-  inputRef: PropTypes.object.isRequired,
-}

+ 0 - 27
services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js

@@ -1,27 +0,0 @@
-import { TabList, Tab } from '@reach/tabs'
-import PropTypes from 'prop-types'
-
-export default function SymbolPaletteTabs({ categories, disabled }) {
-  return (
-    <TabList aria-label="Symbol Categories" className="symbol-palette-tab-list">
-      {categories.map(category => (
-        <Tab
-          key={category.id}
-          disabled={disabled}
-          className="symbol-palette-tab"
-        >
-          {category.label}
-        </Tab>
-      ))}
-    </TabList>
-  )
-}
-SymbolPaletteTabs.propTypes = {
-  categories: PropTypes.arrayOf(
-    PropTypes.shape({
-      id: PropTypes.string.isRequired,
-      label: PropTypes.string.isRequired,
-    })
-  ).isRequired,
-  disabled: PropTypes.bool,
-}

+ 0 - 14
services/web/frontend/js/features/symbol-palette/components/symbol-palette.js

@@ -1,14 +0,0 @@
-import PropTypes from 'prop-types'
-import SymbolPaletteContent from './symbol-palette-content'
-
-export default function SymbolPalette({ show, handleSelect }) {
-  if (!show) {
-    return null
-  }
-
-  return <SymbolPaletteContent handleSelect={handleSelect} />
-}
-SymbolPalette.propTypes = {
-  show: PropTypes.bool,
-  handleSelect: PropTypes.func.isRequired,
-}

+ 0 - 6
services/web/frontend/js/features/symbol-palette/controllers/symbol-palette-controller.js

@@ -1,6 +0,0 @@
-import App from '../../../base'
-import { react2angular } from 'react2angular'
-
-import SymbolPalette from '../components/symbol-palette'
-
-App.component('symbolPalette', react2angular(SymbolPalette))

+ 0 - 872
services/web/frontend/js/features/symbol-palette/data/symbols.json

@@ -1,872 +0,0 @@
-[
-  {
-    "category": "Greek",
-    "command": "\\alpha",
-    "codepoint": "U+1D6FC",
-    "description": "Lowercase Greek letter alpha",
-    "aliases": ["a", "α"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\beta",
-    "codepoint": "U+1D6FD",
-    "description": "Lowercase Greek letter beta",
-    "aliases": ["b", "β"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\gamma",
-    "codepoint": "U+1D6FE",
-    "description": "Lowercase Greek letter gamma",
-    "aliases": ["γ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\delta",
-    "codepoint": "U+1D6FF",
-    "description": "Lowercase Greek letter delta",
-    "aliases": ["δ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\varepsilon",
-    "codepoint": "U+1D700",
-    "description": "Lowercase Greek letter epsilon, varepsilon",
-    "aliases": ["ε"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\epsilon",
-    "codepoint": "U+1D716",
-    "description": "Lowercase Greek letter epsilon lunate",
-    "aliases": ["ε"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\zeta",
-    "codepoint": "U+1D701",
-    "description": "Lowercase Greek letter zeta",
-    "aliases": ["ζ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\eta",
-    "codepoint": "U+1D702",
-    "description": "Lowercase Greek letter eta",
-    "aliases": ["η"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\vartheta",
-    "codepoint": "U+1D717",
-    "description": "Lowercase Greek letter curly theta, vartheta",
-    "aliases": ["θ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\theta",
-    "codepoint": "U+1D703",
-    "description": "Lowercase Greek letter theta",
-    "aliases": ["θ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\iota",
-    "codepoint": "U+1D704",
-    "description": "Lowercase Greek letter iota",
-    "aliases": ["ι"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\kappa",
-    "codepoint": "U+1D705",
-    "description": "Lowercase Greek letter kappa",
-    "aliases": ["κ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\lambda",
-    "codepoint": "U+1D706",
-    "description": "Lowercase Greek letter lambda",
-    "aliases": ["λ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\mu",
-    "codepoint": "U+1D707",
-    "description": "Lowercase Greek letter mu",
-    "aliases": ["μ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\nu",
-    "codepoint": "U+1D708",
-    "description": "Lowercase Greek letter nu",
-    "aliases": ["ν"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\xi",
-    "codepoint": "U+1D709",
-    "description": "Lowercase Greek letter xi",
-    "aliases": ["ξ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\pi",
-    "codepoint": "U+1D70B",
-    "description": "Lowercase Greek letter pi",
-    "aliases": ["π"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\varrho",
-    "codepoint": "U+1D71A",
-    "description": "Lowercase Greek letter rho, varrho",
-    "aliases": ["ρ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\rho",
-    "codepoint": "U+1D70C",
-    "description": "Lowercase Greek letter rho",
-    "aliases": ["ρ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\sigma",
-    "codepoint": "U+1D70E",
-    "description": "Lowercase Greek letter sigma",
-    "aliases": ["σ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\varsigma",
-    "codepoint": "U+1D70D",
-    "description": "Lowercase Greek letter final sigma, varsigma",
-    "aliases": ["ς"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\tau",
-    "codepoint": "U+1D70F",
-    "description": "Lowercase Greek letter tau",
-    "aliases": ["τ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\upsilon",
-    "codepoint": "U+1D710",
-    "description": "Lowercase Greek letter upsilon",
-    "aliases": ["υ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\phi",
-    "codepoint": "U+1D719",
-    "description": "Lowercase Greek letter phi",
-    "aliases": ["φ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\varphi",
-    "codepoint": "U+1D711",
-    "description": "Lowercase Greek letter phi, varphi",
-    "aliases": ["φ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\chi",
-    "codepoint": "U+1D712",
-    "description": "Lowercase Greek letter chi",
-    "aliases": ["χ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\psi",
-    "codepoint": "U+1D713",
-    "description": "Lowercase Greek letter psi",
-    "aliases": ["ψ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\omega",
-    "codepoint": "U+1D714",
-    "description": "Lowercase Greek letter omega",
-    "aliases": ["ω"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Gamma",
-    "codepoint": "U+00393",
-    "description": "Uppercase Greek letter Gamma",
-    "aliases": ["Γ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Delta",
-    "codepoint": "U+00394",
-    "description": "Uppercase Greek letter Delta",
-    "aliases": ["Δ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Theta",
-    "codepoint": "U+00398",
-    "description": "Uppercase Greek letter Theta",
-    "aliases": ["Θ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Lambda",
-    "codepoint": "U+0039B",
-    "description": "Uppercase Greek letter Lambda",
-    "aliases": ["Λ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Xi",
-    "codepoint": "U+0039E",
-    "description": "Uppercase Greek letter Xi",
-    "aliases": ["Ξ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Pi",
-    "codepoint": "U+003A0",
-    "description": "Uppercase Greek letter Pi",
-    "aliases": ["Π"],
-    "notes": "Use \\prod for the product."
-  },
-  {
-    "category": "Greek",
-    "command": "\\Sigma",
-    "codepoint": "U+003A3",
-    "description": "Uppercase Greek letter Sigma",
-    "aliases": ["Σ"],
-    "notes": "Use \\sum for the sum."
-  },
-  {
-    "category": "Greek",
-    "command": "\\Upsilon",
-    "codepoint": "U+003A5",
-    "description": "Uppercase Greek letter Upsilon",
-    "aliases": ["Υ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Phi",
-    "codepoint": "U+003A6",
-    "description": "Uppercase Greek letter Phi",
-    "aliases": ["Φ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Psi",
-    "codepoint": "U+003A8",
-    "description": "Uppercase Greek letter Psi",
-    "aliases": ["Ψ"],
-    "notes": ""
-  },
-  {
-    "category": "Greek",
-    "command": "\\Omega",
-    "codepoint": "U+003A9",
-    "description": "Uppercase Greek letter Omega",
-    "aliases": ["Ω"],
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\neq",
-    "codepoint": "U+02260",
-    "description": "Not equal",
-    "aliases": ["!="],
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\leq",
-    "codepoint": "U+02264",
-    "description": "Less than or equal",
-    "aliases": ["<="],
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\geq",
-    "codepoint": "U+02265",
-    "description": "Greater than or equal",
-    "aliases": [">="],
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\ll",
-    "codepoint": "U+0226A",
-    "description": "Much less than",
-    "aliases": ["<<"],
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\gg",
-    "codepoint": "U+0226B",
-    "description": "Much greater than",
-    "aliases": [">>"],
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\prec",
-    "codepoint": "U+0227A",
-    "description": "Precedes",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\succ",
-    "codepoint": "U+0227B",
-    "description": "Succeeds",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\in",
-    "codepoint": "U+02208",
-    "description": "Set membership",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\notin",
-    "codepoint": "U+02209",
-    "description": "Negated set membership",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\ni",
-    "codepoint": "U+0220B",
-    "description": "Contains",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\subset",
-    "codepoint": "U+02282",
-    "description": "Subset",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\subseteq",
-    "codepoint": "U+02286",
-    "description": "Subset or equals",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\supset",
-    "codepoint": "U+02283",
-    "description": "Superset",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\simeq",
-    "codepoint": "U+02243",
-    "description": "Similar",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\approx",
-    "codepoint": "U+02248",
-    "description": "Approximate",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\equiv",
-    "codepoint": "U+02261",
-    "description": "Identical with",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\cong",
-    "codepoint": "U+02245",
-    "description": "Congruent with",
-    "notes": ""
-  },
-  {
-    "category": "Relations",
-    "command": "\\mid",
-    "codepoint": "U+02223",
-    "description": "Mid, divides, vertical bar, modulus, absolute value",
-    "notes": "Use \\lvert...\\rvert for the absolute value."
-  },
-  {
-    "category": "Relations",
-    "command": "\\nmid",
-    "codepoint": "U+02224",
-    "description": "Negated mid, not divides",
-    "notes": "Requires \\usepackage{amssymb}."
-  },
-  {
-    "category": "Relations",
-    "command": "\\parallel",
-    "codepoint": "U+02225",
-    "description": "Parallel, double vertical bar, norm",
-    "notes": "Use \\lVert...\\rVert for the norm."
-  },
-  {
-    "category": "Relations",
-    "command": "\\perp",
-    "codepoint": "U+027C2",
-    "description": "Perpendicular",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\times",
-    "codepoint": "U+000D7",
-    "description": "Cross product, multiplication",
-    "aliases": ["x"],
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\div",
-    "codepoint": "U+000F7",
-    "description": "Division",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\cap",
-    "codepoint": "U+02229",
-    "description": "Intersection",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\cup",
-    "codepoint": "U+0222A",
-    "description": "Union",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\cdot",
-    "codepoint": "U+022C5",
-    "description": "Dot product, multiplication",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\cdots",
-    "codepoint": "U+022EF",
-    "description": "Centered dots",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\bullet",
-    "codepoint": "U+02219",
-    "description": "Bullet",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\circ",
-    "codepoint": "U+025E6",
-    "description": "Circle",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\wedge",
-    "codepoint": "U+02227",
-    "description": "Wedge, logical and",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\vee",
-    "codepoint": "U+02228",
-    "description": "Vee, logical or",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\setminus",
-    "codepoint": "U+0005C",
-    "description": "Set minus, backslash",
-    "notes": "Use \\backslash for a backslash."
-  },
-  {
-    "category": "Operators",
-    "command": "\\oplus",
-    "codepoint": "U+02295",
-    "description": "Plus sign in circle",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\otimes",
-    "codepoint": "U+02297",
-    "description": "Multiply sign in circle",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\sum",
-    "codepoint": "U+02211",
-    "description": "Summation operator",
-    "notes": "Use \\Sigma for the letter Sigma."
-  },
-  {
-    "category": "Operators",
-    "command": "\\prod",
-    "codepoint": "U+0220F",
-    "description": "Product operator",
-    "notes": "Use \\Pi for the letter Pi."
-  },
-  {
-    "category": "Operators",
-    "command": "\\bigcap",
-    "codepoint": "U+022C2",
-    "description": "Intersection operator",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\bigcup",
-    "codepoint": "U+022C3",
-    "description": "Union operator",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\int",
-    "codepoint": "U+0222B",
-    "description": "Integral operator",
-    "notes": ""
-  },
-  {
-    "category": "Operators",
-    "command": "\\iint",
-    "codepoint": "U+0222C",
-    "description": "Double integral operator",
-    "notes": "Requires \\usepackage{amsmath}."
-  },
-  {
-    "category": "Operators",
-    "command": "\\iiint",
-    "codepoint": "U+0222D",
-    "description": "Triple integral operator",
-    "notes": "Requires \\usepackage{amsmath}."
-  },
-  {
-    "category": "Arrows",
-    "command": "\\leftarrow",
-    "codepoint": "U+02190",
-    "description": "Leftward arrow",
-    "aliases": ["<-"],
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\rightarrow",
-    "codepoint": "U+02192",
-    "description": "Rightward arrow",
-    "aliases": ["->"],
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\leftrightarrow",
-    "codepoint": "U+02194",
-    "description": "Left and right arrow",
-    "aliases": ["<->"],
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\uparrow",
-    "codepoint": "U+02191",
-    "description": "Upward arrow",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\downarrow",
-    "codepoint": "U+02193",
-    "description": "Downward arrow",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\Leftarrow",
-    "codepoint": "U+021D0",
-    "description": "Is implied by",
-    "aliases": ["<="],
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\Rightarrow",
-    "codepoint": "U+021D2",
-    "description": "Implies",
-    "aliases": ["=>"],
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\Leftrightarrow",
-    "codepoint": "U+021D4",
-    "description": "Left and right double arrow",
-    "aliases": ["<=>"],
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\mapsto",
-    "codepoint": "U+021A6",
-    "description": "Maps to, rightward",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\nearrow",
-    "codepoint": "U+02197",
-    "description": "NE pointing arrow",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\searrow",
-    "codepoint": "U+02198",
-    "description": "SE pointing arrow",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\rightleftharpoons",
-    "codepoint": "U+021CC",
-    "description": "Right harpoon over left",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\leftharpoonup",
-    "codepoint": "U+021BC",
-    "description": "Left harpoon up",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\rightharpoonup",
-    "codepoint": "U+021C0",
-    "description": "Right harpoon up",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\leftharpoondown",
-    "codepoint": "U+021BD",
-    "description": "Left harpoon down",
-    "notes": ""
-  },
-  {
-    "category": "Arrows",
-    "command": "\\rightharpoondown",
-    "codepoint": "U+021C1",
-    "description": "Right harpoon down",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\infty",
-    "codepoint": "U+0221E",
-    "description": "Infinity",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\partial",
-    "codepoint": "U+1D715",
-    "description": "Partial differential",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\nabla",
-    "codepoint": "U+02207",
-    "description": "Nabla, del, hamilton operator",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\varnothing",
-    "codepoint": "U+02300",
-    "description": "Empty set",
-    "notes": "Requires \\usepackage{amssymb}."
-  },
-  {
-    "category": "Misc",
-    "command": "\\forall",
-    "codepoint": "U+02200",
-    "description": "For all",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\exists",
-    "codepoint": "U+02203",
-    "description": "There exists",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\neg",
-    "codepoint": "U+000AC",
-    "description": "Not sign",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\Re",
-    "codepoint": "U+0211C",
-    "description": "Real part",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\Im",
-    "codepoint": "U+02111",
-    "description": "Imaginary part",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\Box",
-    "codepoint": "U+025A1",
-    "description": "Square",
-    "notes": "Requires \\usepackage{amssymb}."
-  },
-  {
-    "category": "Misc",
-    "command": "\\triangle",
-    "codepoint": "U+025B3",
-    "description": "Triangle",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\aleph",
-    "codepoint": "U+02135",
-    "description": "Hebrew letter aleph",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\wp",
-    "codepoint": "U+02118",
-    "description": "Weierstrass letter p",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\#",
-    "codepoint": "U+00023",
-    "description": "Number sign, hashtag",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\$",
-    "codepoint": "U+00024",
-    "description": "Dollar sign",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\%",
-    "codepoint": "U+00025",
-    "description": "Percent sign",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\&",
-    "codepoint": "U+00026",
-    "description": "Et sign, and, ampersand",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\{",
-    "codepoint": "U+0007B",
-    "description": "Left curly brace",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\}",
-    "codepoint": "U+0007D",
-    "description": "Right curly brace",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\langle",
-    "codepoint": "U+027E8",
-    "description": "Left angle bracket, bra",
-    "notes": ""
-  },
-  {
-    "category": "Misc",
-    "command": "\\rangle",
-    "codepoint": "U+027E9",
-    "description": "Right angle bracket, ket",
-    "notes": ""
-  }
-]

+ 0 - 45
services/web/frontend/js/features/symbol-palette/utils/categories.js

@@ -1,45 +0,0 @@
-import symbols from '../data/symbols.json'
-
-export function createCategories(t) {
-  return [
-    {
-      id: 'Greek',
-      label: t('category_greek'),
-    },
-    {
-      id: 'Arrows',
-      label: t('category_arrows'),
-    },
-    {
-      id: 'Operators',
-      label: t('category_operators'),
-    },
-    {
-      id: 'Relations',
-      label: t('category_relations'),
-    },
-    {
-      id: 'Misc',
-      label: t('category_misc'),
-    },
-  ]
-}
-
-export function buildCategorisedSymbols(categories) {
-  const output = {}
-
-  for (const category of categories) {
-    output[category.id] = []
-  }
-
-  for (const item of symbols) {
-    if (item.category in output) {
-      item.character = String.fromCodePoint(
-        parseInt(item.codepoint.replace(/^U\+0*/, ''), 16)
-      )
-      output[item.category].push(item)
-    }
-  }
-
-  return output
-}

+ 0 - 1
services/web/frontend/js/ide/editor/EditorManager.js

@@ -19,7 +19,6 @@ import './components/spellMenu'
 import './directives/aceEditor'
 import './directives/toggleSwitch'
 import './controllers/SavingNotificationController'
-import '../../features/symbol-palette/controllers/symbol-palette-controller'
 let EditorManager
 
 export default EditorManager = (function () {

+ 44 - 0
services/web/frontend/js/shared/components/upgrade-benefits.js

@@ -0,0 +1,44 @@
+import Icon from './icon'
+import { useTranslation } from 'react-i18next'
+import { memo } from 'react'
+
+function UpgradeBenefits() {
+  const { t } = useTranslation()
+
+  return (
+    <ul className="list-unstyled upgrade-benefits">
+      <li>
+        <Icon type="check" />
+        &nbsp;
+        {t('unlimited_projects')}
+      </li>
+      <li>
+        <Icon type="check" />
+        &nbsp;
+        {t('collabs_per_proj', { collabcount: 'Multiple' })}
+      </li>
+      <li>
+        <Icon type="check" />
+        &nbsp;
+        {t('full_doc_history')}
+      </li>
+      <li>
+        <Icon type="check" />
+        &nbsp;
+        {t('sync_to_dropbox')}
+      </li>
+      <li>
+        <Icon type="check" />
+        &nbsp;
+        {t('sync_to_github')}
+      </li>
+      <li>
+        <Icon type="check" />
+        &nbsp;
+        {t('compile_larger_projects')}
+      </li>
+    </ul>
+  )
+}
+
+export default memo(UpgradeBenefits)

+ 12 - 0
services/web/frontend/js/shared/context/editor-context.js

@@ -29,6 +29,9 @@ EditorContext.Provider.propTypes = {
     hasPremiumCompile: PropTypes.bool,
     loading: PropTypes.bool,
     renameProject: PropTypes.func.isRequired,
+    showSymbolPalette: PropTypes.bool,
+    toggleSymbolPalette: PropTypes.func,
+    insertSymbol: PropTypes.func,
     isProjectOwner: PropTypes.bool,
     isRestrictedTokenMember: PropTypes.bool,
     rootFolder: PropTypes.shape({
@@ -72,6 +75,9 @@ export function EditorProvider({ children, settings }) {
   const [projectName, setProjectName] = useScopeValue('project.name')
   const [rootFolder] = useScopeValue('rootFolder')
   const [permissionsLevel] = useScopeValue('permissionsLevel')
+  const [showSymbolPalette] = useScopeValue('editor.showSymbolPalette')
+  const [toggleSymbolPalette] = useScopeValue('editor.toggleSymbolPalette')
+  const [insertSymbol] = useScopeValue('editor.insertSymbol')
 
   useEffect(() => {
     if (ide?.socket) {
@@ -123,6 +129,9 @@ export function EditorProvider({ children, settings }) {
       isProjectOwner: owner?._id === window.user.id,
       isRestrictedTokenMember: window.isRestrictedTokenMember,
       rootFolder,
+      showSymbolPalette,
+      toggleSymbolPalette,
+      insertSymbol,
     }),
     [
       cobranding,
@@ -132,6 +141,9 @@ export function EditorProvider({ children, settings }) {
       permissionsLevel,
       owner?._id,
       rootFolder,
+      showSymbolPalette,
+      toggleSymbolPalette,
+      insertSymbol,
     ]
   )
 

+ 0 - 20
services/web/frontend/stories/symbol-palette.stories.js

@@ -1,20 +0,0 @@
-import SymbolPalette from '../js/features/symbol-palette/components/symbol-palette'
-
-export const Interactive = args => {
-  return (
-    <div style={{ maxWidth: 700, marginTop: 120 }}>
-      <SymbolPalette {...args} />
-    </div>
-  )
-}
-
-export default {
-  title: 'Symbol Palette',
-  component: SymbolPalette,
-  args: {
-    show: true,
-  },
-  argTypes: {
-    handleSelect: { action: 'handleSelect' },
-  },
-}

+ 4 - 0
services/web/frontend/stylesheets/_style_includes.less

@@ -99,3 +99,7 @@
 @import 'app/editor/history-v2.less';
 @import 'app/metrics.less';
 @import 'app/open-in-overleaf.less';
+
+// module styles
+// TODO: find a way for modules to add styles dynamically
+@import 'modules/symbol-palette.less';

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

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

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

@@ -330,6 +330,14 @@
     color: @toolbar-btn-active-color;
     background-color: @toolbar-btn-active-bg-color;
     box-shadow: @toolbar-btn-active-shadow;
+
+    &:focus {
+      color: @toolbar-btn-active-color;
+
+      &:not(:focus-visible) {
+        outline: none;
+      }
+    }
   }
 
   &:focus {

+ 1 - 0
services/web/frontend/stylesheets/core/ol-light-variables.less

@@ -136,3 +136,4 @@
 @symbol-palette-item-color: @ol-blue-gray-3;
 @symbol-palette-selected-tab-bg: #fff;
 @symbol-palette-selected-tab-color: @ol-blue;
+@symbol-palette-text-shadow-color: @ol-blue-gray-1;

+ 1 - 0
services/web/frontend/stylesheets/core/variables.less

@@ -1121,3 +1121,4 @@
 @symbol-palette-item-color: #fff;
 @symbol-palette-selected-tab-bg: @ol-blue-gray-4;
 @symbol-palette-selected-tab-color: #fff;
+@symbol-palette-text-shadow-color: @ol-blue-gray-6;

+ 155 - 0
services/web/frontend/stylesheets/modules/symbol-palette.less

@@ -0,0 +1,155 @@
+.symbol-palette-container {
+  height: 100%;
+  width: 100%;
+  position: relative;
+
+  .symbol-palette {
+    display: flex;
+    flex-direction: column;
+    background: @symbol-palette-bg;
+    color: @symbol-palette-color;
+    width: 100%;
+    height: 100%;
+    min-height: 220px; // allow space for the overlay contents
+  }
+
+  .symbol-palette-header-outer {
+    flex-shrink: 0;
+    display: flex;
+    flex-wrap: nowrap;
+    justify-content: space-between;
+    align-items: flex-start;
+    font-family: @font-family-sans-serif;
+    font-size: 16px;
+    background: @symbol-palette-header-background;
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  .symbol-palette-header {
+    flex: 1;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .symbol-palette-tab-list[data-reach-tab-list] {
+    background: none;
+    border-bottom: none;
+    flex-wrap: wrap;
+  }
+
+  .symbol-palette-tab[data-reach-tab][data-selected] {
+    background: @symbol-palette-selected-tab-bg;
+    color: @symbol-palette-selected-tab-color;
+    border-bottom-color: transparent;
+  }
+
+  .symbol-palette-body {
+    flex: 1;
+    overflow-y: auto;
+  }
+
+  .symbol-palette-items {
+    display: flex;
+    flex-wrap: wrap;
+    padding: @padding-xs;
+  }
+
+  .symbol-palette-item {
+    font-family: 'Stix Two Math', serif;
+    font-size: 24px;
+    line-height: 42px;
+    height: 42px;
+    width: 42px;
+    margin: @margin-xs;
+    color: @symbol-palette-item-color;
+    background: @symbol-palette-item-bg;
+    border: 1px solid transparent;
+    border-radius: @border-radius-base;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .symbol-palette-item-command {
+    font-family: monospace;
+    font-weight: bold;
+  }
+
+  .symbol-palette-item-notes {
+    margin-top: @margin-xs;
+  }
+
+  .symbol-palette-empty {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: @padding-sm;
+  }
+
+  .symbol-palette-search {
+    padding: 2px @padding-sm;
+    margin: @margin-xs;
+    line-height: 1;
+    height: auto;
+    width: auto;
+  }
+
+  .symbol-palette-header-group {
+    display: flex;
+    align-items: center;
+    white-space: nowrap;
+    margin-left: @margin-xs;
+  }
+
+  .symbol-palette-info-link,
+  .symbol-palette-info-link:focus,
+  .symbol-palette-info-link:hover {
+    color: inherit;
+  }
+
+  .symbol-palette-close-button {
+    background: transparent;
+    color: @symbol-palette-color;
+    padding-left: @padding-sm;
+    padding-right: @padding-sm;
+    margin-left: @margin-xs;
+    font-size: 24px;
+    font-weight: bold;
+    line-height: 1;
+  }
+
+  .symbol-palette-overlay {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background: fade(@symbol-palette-bg, 75%);
+    color: @symbol-palette-color;
+    display: flex;
+    flex-direction: column;
+    padding: 0 @padding-lg @padding-sm;
+    align-items: center;
+    text-shadow: 0 0 8px @symbol-palette-text-shadow-color;
+    min-height: 200px;
+    overflow: auto;
+
+    h4 {
+      font-weight: bold;
+      color: @symbol-palette-color;
+      text-align: center;
+    }
+
+    .symbol-palette-close-button {
+      position: absolute;
+      top: 0;
+      right: 0;
+    }
+
+    .upgrade-benefits {
+      column-count: 2;
+    }
+  }
+}

+ 4 - 2
services/web/locales/en.json

@@ -30,7 +30,6 @@
   "log_entry_maximum_entries": "Maximum log entries limit hit",
   "log_entry_maximum_entries_title": "__total__ issues total. Showing the first __displayed__",
   "log_entry_maximum_entries_message": "<0>Tip</0>: Try to fix the first error and recompile. Often one error causes many later error messages",
-  "log_entry_description": "Log entry with level: __level__",
   "navigate_log_source": "Navigate to log position in source code: __location__",
   "other_output_files": "Download other output files",
   "refresh": "Refresh",
@@ -1484,7 +1483,10 @@
   "category_relations": "Relations",
   "category_misc": "Misc",
   "no_symbols_found": "No symbols found",
-  "find_out_more_about_latex_symbols": "Find out more about LaTeX symbols",
+  "learn_more_about_the_symbol_palette": "Learn more about the Symbol Palette and how to use it",
+  "find_the_symbols_you_need_with_premium": "Find the symbols you need faster with Overleaf Premium",
+  "with_premium_subscription_you_also_get": "With an Overleaf Premium subscription you also get",
+  "try_premium_for_free": "Try Premium for free",
   "search": "Search",
   "also": "Also",
   "add_email": "Add Email",

+ 0 - 104
services/web/test/frontend/features/symbol-palette/components/symbol-palette.test.js

@@ -1,104 +0,0 @@
-import { expect } from 'chai'
-import sinon from 'sinon'
-import { screen, render, fireEvent, waitFor } from '@testing-library/react'
-import SymbolPalette from '../../../../../frontend/js/features/symbol-palette/components/symbol-palette'
-
-describe('symbol palette', function () {
-  let clock
-
-  before(function () {
-    clock = sinon.useFakeTimers({
-      toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'],
-    })
-  })
-
-  after(function () {
-    clock.runAll()
-    clock.restore()
-  })
-
-  it('handles keyboard interaction', async function () {
-    this.timeout(10000)
-
-    const handleSelect = sinon.stub()
-
-    const { container } = render(
-      <SymbolPalette show handleSelect={handleSelect} />
-    )
-
-    // check the number of tabs
-    const tabs = await screen.findAllByRole('tab')
-    expect(tabs).to.have.length(5)
-
-    let selectedTab
-    let symbols
-
-    // the first tab should be selected
-    selectedTab = await screen.getByRole('tab', { selected: true })
-    expect(selectedTab.textContent).to.equal('Greek')
-    symbols = await screen.findAllByRole('option')
-    expect(symbols).to.have.length(39)
-
-    // click to select the third tab
-    tabs[2].click()
-    selectedTab = await screen.getByRole('tab', { selected: true })
-    expect(selectedTab.textContent).to.equal('Operators')
-    symbols = await screen.findAllByRole('option')
-    expect(symbols).to.have.length(20)
-
-    // press the left arrow to select the second tab
-    fireEvent.keyDown(selectedTab, { key: 'ArrowLeft' })
-    selectedTab = await screen.getByRole('tab', { selected: true })
-    expect(selectedTab.textContent).to.equal('Arrows')
-    symbols = await screen.findAllByRole('option')
-    expect(symbols).to.have.length(16)
-
-    // select the search input
-    const input = await screen.getByRole('searchbox')
-    input.click()
-
-    // type in the search input
-    fireEvent.change(input, { target: { value: 'pi' } })
-
-    // make sure all scheduled microtasks have executed
-    clock.runAll()
-
-    // wait for the symbols to be filtered
-    await waitFor(async () => {
-      symbols = await screen.findAllByRole('option')
-      expect(symbols).to.have.length(2)
-    })
-
-    // check the tabs are disabled
-    expect(selectedTab.disabled).to.be.true
-
-    // press Tab to select the symbols
-    fireEvent.keyDown(container, { key: 'Tab' })
-
-    // get the selected symbol
-    let selectedSymbol
-
-    selectedSymbol = await screen.getByRole('option', { selected: true })
-    expect(selectedSymbol.textContent).to.equal('𝜋')
-
-    // move to the next symbol
-    fireEvent.keyDown(selectedSymbol, { key: 'ArrowRight' })
-
-    // wait for the symbol to be selected
-    selectedSymbol = await screen.getByRole('option', { selected: true })
-    expect(selectedSymbol.textContent).to.equal('Π')
-
-    // click on the selected symbol
-    selectedSymbol.click()
-
-    expect(handleSelect).to.have.been.calledWith({
-      aliases: ['Π'],
-      category: 'Greek',
-      character: 'Π',
-      codepoint: 'U+003A0',
-      command: '\\Pi',
-      description: 'Uppercase Greek letter Pi',
-      notes: 'Use \\prod for the product.',
-    })
-  })
-})