From 3feca1ffc62a8c47d0372a2bb1d5497746d4f98e Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Wed, 10 Nov 2021 10:50:01 +0000 Subject: [PATCH] Move symbol palette to module and add overlay (#5396) GitOrigin-RevId: 649dd56aaecd60662bd2bf534bda323ee541874f --- services/web/app/views/project/editor.pug | 2 +- .../editor/editor-no-symbol-palette.pug | 44 - ...ith-symbol-palette.pug => editor-pane.pug} | 12 +- .../web/app/views/project/editor/editor.pug | 5 +- .../views/project/editor/symbol-palette.pug | 2 - services/web/config/settings.defaults.js | 1 + .../web/frontend/extracted-translations.json | 5 +- .../components/timeout-upgrade-prompt.js | 35 +- .../components/symbol-palette-body.js | 61 -- .../components/symbol-palette-content.js | 103 --- .../components/symbol-palette-info-link.js | 29 - .../components/symbol-palette-item.js | 67 -- .../components/symbol-palette-items.js | 86 -- .../components/symbol-palette-search.js | 44 - .../components/symbol-palette-tabs.js | 27 - .../components/symbol-palette.js | 14 - .../controllers/symbol-palette-controller.js | 6 - .../features/symbol-palette/data/symbols.json | 872 ------------------ .../symbol-palette/utils/categories.js | 45 - .../frontend/js/ide/editor/EditorManager.js | 1 - .../js/shared/components/upgrade-benefits.js | 44 + .../js/shared/context/editor-context.js | 12 + .../stories/symbol-palette.stories.js | 20 - .../frontend/stylesheets/_style_includes.less | 4 + .../web/frontend/stylesheets/app/editor.less | 1 - .../stylesheets/app/editor/toolbar.less | 8 + .../stylesheets/core/ol-light-variables.less | 1 + .../frontend/stylesheets/core/variables.less | 1 + .../stylesheets/modules/symbol-palette.less | 155 ++++ services/web/locales/en.json | 6 +- .../components/symbol-palette.test.js | 104 --- 31 files changed, 245 insertions(+), 1572 deletions(-) delete mode 100644 services/web/app/views/project/editor/editor-no-symbol-palette.pug rename services/web/app/views/project/editor/{editor-with-symbol-palette.pug => editor-pane.pug} (88%) delete mode 100644 services/web/app/views/project/editor/symbol-palette.pug delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-info-link.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-item.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-items.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js delete mode 100644 services/web/frontend/js/features/symbol-palette/components/symbol-palette.js delete mode 100644 services/web/frontend/js/features/symbol-palette/controllers/symbol-palette-controller.js delete mode 100644 services/web/frontend/js/features/symbol-palette/data/symbols.json delete mode 100644 services/web/frontend/js/features/symbol-palette/utils/categories.js create mode 100644 services/web/frontend/js/shared/components/upgrade-benefits.js delete mode 100644 services/web/frontend/stories/symbol-palette.stories.js create mode 100644 services/web/frontend/stylesheets/modules/symbol-palette.less delete mode 100644 services/web/test/frontend/features/symbol-palette/components/symbol-palette.test.js diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 485ddabdf5..5e79e1a1c1 100644 --- a/services/web/app/views/project/editor.pug +++ b/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 diff --git a/services/web/app/views/project/editor/editor-no-symbol-palette.pug b/services/web/app/views/project/editor/editor-no-symbol-palette.pug deleted file mode 100644 index afd39aa542..0000000000 --- a/services/web/app/views/project/editor/editor-no-symbol-palette.pug +++ /dev/null @@ -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 diff --git a/services/web/app/views/project/editor/editor-with-symbol-palette.pug b/services/web/app/views/project/editor/editor-pane.pug similarity index 88% rename from services/web/app/views/project/editor/editor-with-symbol-palette.pug rename to services/web/app/views/project/editor/editor-pane.pug index ce8c7d547c..0444e56fff 100644 --- a/services/web/app/views/project/editor/editor-with-symbol-palette.pug +++ b/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) + diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index 5b575ba463..438a759f3f 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/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'") diff --git a/services/web/app/views/project/editor/symbol-palette.pug b/services/web/app/views/project/editor/symbol-palette.pug deleted file mode 100644 index 660aa78cc1..0000000000 --- a/services/web/app/views/project/editor/symbol-palette.pug +++ /dev/null @@ -1,2 +0,0 @@ -if showSymbolPalette - symbol-palette(show="editor.showSymbolPalette" handle-select="editor.insertSymbol") diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 4b35c05c45..61d0a336d5 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -737,6 +737,7 @@ module.exports = { tprLinkedFileInfo: [], tprLinkedFileRefreshError: [], contactUsModal: [], + editorToolbarButtons: [], }, moduleImportSequence: ['launchpad', 'server-ce-scripts', 'user-activate'], diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 82381b10e7..68c258d4b7 100644 --- a/services/web/frontend/extracted-translations.json +++ b/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": "", diff --git a/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt.js b/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt.js index fd6fcd477c..ac888ee2b9 100644 --- a/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt.js +++ b/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() {

{t('free_accounts_have_timeout_upgrade_to_increase')}

{t('plus_upgraded_accounts_receive')}:

- +
{isProjectOwner && (

diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js deleted file mode 100644 index c4f47e325d..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js +++ /dev/null @@ -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 ? ( - - ) : ( -

{t('no_symbols_found')}
- )} - - - {categories.map(category => ( - - ))} - - - ) - } - - // not searching: show the symbols grouped by category - return ( - - {categories.map(category => ( - - - - ))} - - ) -} -SymbolPaletteBody.propTypes = { - categories: PropTypes.arrayOf(PropTypes.object).isRequired, - categorisedSymbols: PropTypes.object, - filteredSymbols: PropTypes.arrayOf(PropTypes.object), - handleSelect: PropTypes.func.isRequired, - focusInput: PropTypes.func.isRequired, -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js deleted file mode 100644 index 39d5e6d5c1..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js +++ /dev/null @@ -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 ( - -
-
- 0} - /> -
- - {/* NOTE: replace the beta badge with this info link when rolling out to all users */} - {/* */} - -
-
-
- -
-
-
- ) -} -SymbolPaletteContent.propTypes = { - handleSelect: PropTypes.func.isRequired, -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-info-link.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-info-link.js deleted file mode 100644 index a2a7036352..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-info-link.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap' -import { useTranslation } from 'react-i18next' - -export default function SymbolPaletteInfoLink() { - const { t } = useTranslation() - - return ( - - {t('find_out_more_about_latex_symbols')} - - } - > - - - ) -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-item.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-item.js deleted file mode 100644 index a892f33cf8..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-item.js +++ /dev/null @@ -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 ( - -
- {symbol.description} -
-
{symbol.command}
- {symbol.notes && ( -
{symbol.notes}
- )} - - } - > - -
- ) -} -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, -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-items.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-items.js deleted file mode 100644 index 44835261f5..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-items.js +++ /dev/null @@ -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 ( -
- {items.map((symbol, index) => ( - { - handleSelect(symbol) - setFocusedIndex(index) - }} - handleKeyDown={handleKeyDown} - focused={index === focusedIndex} - /> - ))} -
- ) -} -SymbolPaletteItems.propTypes = { - items: PropTypes.arrayOf( - PropTypes.shape({ - codepoint: PropTypes.string.isRequired, - }) - ).isRequired, - handleSelect: PropTypes.func.isRequired, - focusInput: PropTypes.func.isRequired, -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js deleted file mode 100644 index cf5a1eb2a7..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js +++ /dev/null @@ -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 ( - { - setLocalInput(event.target.value) - }} - /> - ) -} -SymbolPaletteSearch.propTypes = { - setInput: PropTypes.func.isRequired, - inputRef: PropTypes.object.isRequired, -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js deleted file mode 100644 index ba74b0c1cd..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js +++ /dev/null @@ -1,27 +0,0 @@ -import { TabList, Tab } from '@reach/tabs' -import PropTypes from 'prop-types' - -export default function SymbolPaletteTabs({ categories, disabled }) { - return ( - - {categories.map(category => ( - - {category.label} - - ))} - - ) -} -SymbolPaletteTabs.propTypes = { - categories: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - }) - ).isRequired, - disabled: PropTypes.bool, -} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette.js deleted file mode 100644 index 9348431bff..0000000000 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette.js +++ /dev/null @@ -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 -} -SymbolPalette.propTypes = { - show: PropTypes.bool, - handleSelect: PropTypes.func.isRequired, -} diff --git a/services/web/frontend/js/features/symbol-palette/controllers/symbol-palette-controller.js b/services/web/frontend/js/features/symbol-palette/controllers/symbol-palette-controller.js deleted file mode 100644 index ff975cc0c3..0000000000 --- a/services/web/frontend/js/features/symbol-palette/controllers/symbol-palette-controller.js +++ /dev/null @@ -1,6 +0,0 @@ -import App from '../../../base' -import { react2angular } from 'react2angular' - -import SymbolPalette from '../components/symbol-palette' - -App.component('symbolPalette', react2angular(SymbolPalette)) diff --git a/services/web/frontend/js/features/symbol-palette/data/symbols.json b/services/web/frontend/js/features/symbol-palette/data/symbols.json deleted file mode 100644 index af160b3eed..0000000000 --- a/services/web/frontend/js/features/symbol-palette/data/symbols.json +++ /dev/null @@ -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": "" - } -] diff --git a/services/web/frontend/js/features/symbol-palette/utils/categories.js b/services/web/frontend/js/features/symbol-palette/utils/categories.js deleted file mode 100644 index 383f08cf78..0000000000 --- a/services/web/frontend/js/features/symbol-palette/utils/categories.js +++ /dev/null @@ -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 -} diff --git a/services/web/frontend/js/ide/editor/EditorManager.js b/services/web/frontend/js/ide/editor/EditorManager.js index f3ab9a4974..ca05f98b91 100644 --- a/services/web/frontend/js/ide/editor/EditorManager.js +++ b/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 () { diff --git a/services/web/frontend/js/shared/components/upgrade-benefits.js b/services/web/frontend/js/shared/components/upgrade-benefits.js new file mode 100644 index 0000000000..c0a19d987f --- /dev/null +++ b/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 ( +
    +
  • + +   + {t('unlimited_projects')} +
  • +
  • + +   + {t('collabs_per_proj', { collabcount: 'Multiple' })} +
  • +
  • + +   + {t('full_doc_history')} +
  • +
  • + +   + {t('sync_to_dropbox')} +
  • +
  • + +   + {t('sync_to_github')} +
  • +
  • + +   + {t('compile_larger_projects')} +
  • +
+ ) +} + +export default memo(UpgradeBenefits) diff --git a/services/web/frontend/js/shared/context/editor-context.js b/services/web/frontend/js/shared/context/editor-context.js index 8f4c66db1e..d878465a61 100644 --- a/services/web/frontend/js/shared/context/editor-context.js +++ b/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, ] ) diff --git a/services/web/frontend/stories/symbol-palette.stories.js b/services/web/frontend/stories/symbol-palette.stories.js deleted file mode 100644 index 0237b99725..0000000000 --- a/services/web/frontend/stories/symbol-palette.stories.js +++ /dev/null @@ -1,20 +0,0 @@ -import SymbolPalette from '../js/features/symbol-palette/components/symbol-palette' - -export const Interactive = args => { - return ( -
- -
- ) -} - -export default { - title: 'Symbol Palette', - component: SymbolPalette, - args: { - show: true, - }, - argTypes: { - handleSelect: { action: 'handleSelect' }, - }, -} diff --git a/services/web/frontend/stylesheets/_style_includes.less b/services/web/frontend/stylesheets/_style_includes.less index b0ee05685b..bc846f5fcc 100644 --- a/services/web/frontend/stylesheets/_style_includes.less +++ b/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'; diff --git a/services/web/frontend/stylesheets/app/editor.less b/services/web/frontend/stylesheets/app/editor.less index 34942a88de..91ba6dccff 100644 --- a/services/web/frontend/stylesheets/app/editor.less +++ b/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; diff --git a/services/web/frontend/stylesheets/app/editor/toolbar.less b/services/web/frontend/stylesheets/app/editor/toolbar.less index 4e3b32e4ae..92651b7103 100644 --- a/services/web/frontend/stylesheets/app/editor/toolbar.less +++ b/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 { diff --git a/services/web/frontend/stylesheets/core/ol-light-variables.less b/services/web/frontend/stylesheets/core/ol-light-variables.less index 910fd1a9a2..67ef18d7ae 100644 --- a/services/web/frontend/stylesheets/core/ol-light-variables.less +++ b/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; diff --git a/services/web/frontend/stylesheets/core/variables.less b/services/web/frontend/stylesheets/core/variables.less index 3033d62894..953e31ee4b 100644 --- a/services/web/frontend/stylesheets/core/variables.less +++ b/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; diff --git a/services/web/frontend/stylesheets/modules/symbol-palette.less b/services/web/frontend/stylesheets/modules/symbol-palette.less new file mode 100644 index 0000000000..7431bc402b --- /dev/null +++ b/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; + } + } +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 66a1d91710..ac351c3782 100644 --- a/services/web/locales/en.json +++ b/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: 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", diff --git a/services/web/test/frontend/features/symbol-palette/components/symbol-palette.test.js b/services/web/test/frontend/features/symbol-palette/components/symbol-palette.test.js deleted file mode 100644 index cae926e686..0000000000 --- a/services/web/test/frontend/features/symbol-palette/components/symbol-palette.test.js +++ /dev/null @@ -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( - - ) - - // 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.', - }) - }) -})