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')}:
-
- -
-
-
- {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')}
-
-
+
{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>Tip0>: 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.',
- })
- })
-})