mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-05 05:28:18 +00:00
Merge pull request #19528 from overleaf/dp-equation-preview
Equation Preview GitOrigin-RevId: 98e71e5d2c1a83d6c9fa685eeee1f4b93a5a3da1
This commit is contained in:
parent
a8a655ff3b
commit
c07d2f3fa2
22 changed files with 346 additions and 64 deletions
|
@ -324,6 +324,7 @@ const _ProjectController = {
|
|||
const splitTests = [
|
||||
!anonymous && 'bib-file-tpr-prompt',
|
||||
'compile-log-events',
|
||||
'math-preview',
|
||||
'null-test-share-modal',
|
||||
'paywall-cta',
|
||||
'pdf-caching-cached-url-lookup',
|
||||
|
@ -673,6 +674,7 @@ const _ProjectController = {
|
|||
fontFamily: user.ace.fontFamily || 'lucida',
|
||||
lineHeight: user.ace.lineHeight || 'normal',
|
||||
overallTheme: user.ace.overallTheme,
|
||||
mathPreview: user.ace.mathPreview,
|
||||
},
|
||||
privilegeLevel,
|
||||
anonymous,
|
||||
|
|
|
@ -372,6 +372,9 @@ async function updateUserSettings(req, res, next) {
|
|||
if (req.body.lineHeight != null) {
|
||||
user.ace.lineHeight = req.body.lineHeight
|
||||
}
|
||||
if (req.body.mathPreview != null) {
|
||||
user.ace.mathPreview = req.body.mathPreview
|
||||
}
|
||||
await user.save()
|
||||
|
||||
const newEmail = req.body.email?.trim().toLowerCase()
|
||||
|
|
|
@ -86,6 +86,7 @@ const UserSchema = new Schema(
|
|||
syntaxValidation: { type: Boolean },
|
||||
fontFamily: { type: String },
|
||||
lineHeight: { type: String },
|
||||
mathPreview: { type: Boolean, default: true },
|
||||
},
|
||||
features: {
|
||||
collaborators: {
|
||||
|
|
|
@ -396,6 +396,7 @@
|
|||
"enter_any_size_including_units_or_valid_latex_command": "",
|
||||
"enter_image_url": "",
|
||||
"enter_the_confirmation_code": "",
|
||||
"equation_preview": "",
|
||||
"error": "",
|
||||
"error_opening_document": "",
|
||||
"error_opening_document_detail": "",
|
||||
|
|
|
@ -16,10 +16,13 @@ import SettingsOverallTheme from './settings/settings-overall-theme'
|
|||
import SettingsPdfViewer from './settings/settings-pdf-viewer'
|
||||
import SettingsSpellCheckLanguage from './settings/settings-spell-check-language'
|
||||
import SettingsSyntaxValidation from './settings/settings-syntax-validation'
|
||||
import SettingsMathPreview from './settings/settings-math-preview'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
|
||||
export default function SettingsMenu() {
|
||||
const { t } = useTranslation()
|
||||
const anonymous = getMeta('ol-anonymous')
|
||||
const enableMathPreview = useFeatureFlag('math-preview')
|
||||
|
||||
if (anonymous) {
|
||||
return null
|
||||
|
@ -37,6 +40,7 @@ export default function SettingsMenu() {
|
|||
<SettingsAutoComplete />
|
||||
<SettingsAutoCloseBrackets />
|
||||
<SettingsSyntaxValidation />
|
||||
{enableMathPreview && <SettingsMathPreview />}
|
||||
<SettingsEditorTheme />
|
||||
<SettingsOverallTheme />
|
||||
<SettingsKeybindings />
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectSettingsContext } from '../../context/project-settings-context'
|
||||
import SettingsMenuSelect from './settings-menu-select'
|
||||
|
||||
export default function SettingsMathPreview() {
|
||||
const { t } = useTranslation()
|
||||
const { mathPreview, setMathPreview } = useProjectSettingsContext()
|
||||
|
||||
return (
|
||||
<SettingsMenuSelect
|
||||
onChange={setMathPreview}
|
||||
value={mathPreview}
|
||||
options={[
|
||||
{
|
||||
value: true,
|
||||
label: t('on'),
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: t('off'),
|
||||
},
|
||||
]}
|
||||
label={t('equation_preview')}
|
||||
name="mathPreview"
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -26,6 +26,7 @@ type ProjectSettingsSetterContextValue = {
|
|||
setFontFamily: (fontFamily: UserSettings['fontFamily']) => void
|
||||
setLineHeight: (lineHeight: UserSettings['lineHeight']) => void
|
||||
setPdfViewer: (pdfViewer: UserSettings['pdfViewer']) => void
|
||||
setMathPreview: (mathPreview: UserSettings['mathPreview']) => void
|
||||
}
|
||||
|
||||
type ProjectSettingsContextValue = Partial<ProjectSettings> &
|
||||
|
@ -69,6 +70,8 @@ export const ProjectSettingsProvider: FC = ({ children }) => {
|
|||
setLineHeight,
|
||||
pdfViewer,
|
||||
setPdfViewer,
|
||||
mathPreview,
|
||||
setMathPreview,
|
||||
} = useUserWideSettings()
|
||||
|
||||
useProjectWideSettingsSocketListener()
|
||||
|
@ -103,6 +106,8 @@ export const ProjectSettingsProvider: FC = ({ children }) => {
|
|||
setLineHeight,
|
||||
pdfViewer,
|
||||
setPdfViewer,
|
||||
mathPreview,
|
||||
setMathPreview,
|
||||
}),
|
||||
[
|
||||
compiler,
|
||||
|
@ -133,6 +138,8 @@ export const ProjectSettingsProvider: FC = ({ children }) => {
|
|||
setLineHeight,
|
||||
pdfViewer,
|
||||
setPdfViewer,
|
||||
mathPreview,
|
||||
setMathPreview,
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ export default function useUserWideSettings() {
|
|||
fontFamily,
|
||||
lineHeight,
|
||||
pdfViewer,
|
||||
mathPreview,
|
||||
} = userSettings
|
||||
|
||||
const setOverallTheme = useSetOverallTheme()
|
||||
|
@ -85,6 +86,13 @@ export default function useUserWideSettings() {
|
|||
[saveUserSettings]
|
||||
)
|
||||
|
||||
const setMathPreview = useCallback(
|
||||
(mathPreview: UserSettings['mathPreview']) => {
|
||||
saveUserSettings('mathPreview', mathPreview)
|
||||
},
|
||||
[saveUserSettings]
|
||||
)
|
||||
|
||||
return {
|
||||
autoComplete,
|
||||
setAutoComplete,
|
||||
|
@ -106,5 +114,7 @@ export default function useUserWideSettings() {
|
|||
setLineHeight,
|
||||
pdfViewer,
|
||||
setPdfViewer,
|
||||
mathPreview,
|
||||
setMathPreview,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import { toolbarPanel } from './toolbar/toolbar-panel'
|
|||
import { geometryChangeEvent } from './geometry-change-event'
|
||||
import { docName } from './doc-name'
|
||||
import { fileTreeItemDrop } from './file-tree-item-drop'
|
||||
import { mathPreview } from './math-preview'
|
||||
|
||||
const moduleExtensions: Array<() => Extension> = importOverleafModules(
|
||||
'sourceEditorExtensions'
|
||||
|
@ -125,6 +126,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
|
|||
emptyLineFiller(),
|
||||
trackChanges(options.currentDoc, options.changeManager),
|
||||
visual(options.visual),
|
||||
mathPreview(options.settings.mathPreview),
|
||||
toolbarPanel(),
|
||||
verticalOverflow(),
|
||||
highlightActiveLine(options.visual.visual),
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
import {
|
||||
repositionTooltips,
|
||||
showTooltip,
|
||||
Tooltip,
|
||||
ViewPlugin,
|
||||
} from '@codemirror/view'
|
||||
import {
|
||||
Compartment,
|
||||
EditorState,
|
||||
Extension,
|
||||
StateField,
|
||||
TransactionSpec,
|
||||
} from '@codemirror/state'
|
||||
import { loadMathJax } from '../../mathjax/load-mathjax'
|
||||
import { descendantsOfNodeWithType } from '../utils/tree-query'
|
||||
import {
|
||||
mathAncestorNode,
|
||||
parseMathContainer,
|
||||
} from '../utils/tree-operations/math'
|
||||
import { documentCommands } from '../languages/latex/document-commands'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
|
||||
const REPOSITION_EVENT = 'editor:repositionMathTooltips'
|
||||
|
||||
export const mathPreview = (enabled: boolean): Extension => {
|
||||
if (!isSplitTestEnabled('math-preview')) {
|
||||
return []
|
||||
}
|
||||
|
||||
return mathPreviewConf.of(enabled ? mathPreviewStateField : [])
|
||||
}
|
||||
|
||||
const mathPreviewConf = new Compartment()
|
||||
|
||||
export const setMathPreview = (enabled: boolean): TransactionSpec => ({
|
||||
effects: mathPreviewConf.reconfigure(enabled ? mathPreviewStateField : []),
|
||||
})
|
||||
|
||||
const mathPreviewStateField = StateField.define<readonly Tooltip[]>({
|
||||
create: buildTooltips,
|
||||
|
||||
update(tooltips, tr) {
|
||||
if (tr.docChanged || tr.selection) {
|
||||
tooltips = buildTooltips(tr.state)
|
||||
}
|
||||
|
||||
return tooltips
|
||||
},
|
||||
|
||||
provide: field => [
|
||||
showTooltip.computeN([field], state => state.field(field)),
|
||||
|
||||
ViewPlugin.define(view => {
|
||||
const listener = () => repositionTooltips(view)
|
||||
window.addEventListener(REPOSITION_EVENT, listener)
|
||||
return {
|
||||
destroy() {
|
||||
window.removeEventListener(REPOSITION_EVENT, listener)
|
||||
},
|
||||
}
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
const renderMath = async (
|
||||
content: string,
|
||||
displayMode: boolean,
|
||||
element: HTMLElement,
|
||||
definitions: string
|
||||
) => {
|
||||
const MathJax = await loadMathJax()
|
||||
|
||||
MathJax.texReset([0]) // equation numbering is disabled, but this is still needed
|
||||
|
||||
try {
|
||||
await MathJax.tex2svgPromise(definitions)
|
||||
} catch {
|
||||
// ignore errors thrown during parsing command definitions
|
||||
}
|
||||
|
||||
const math = await MathJax.tex2svgPromise(content, {
|
||||
...MathJax.getMetricsFor(element),
|
||||
display: displayMode,
|
||||
})
|
||||
element.textContent = ''
|
||||
element.append(math)
|
||||
}
|
||||
|
||||
function buildTooltips(state: EditorState): readonly Tooltip[] {
|
||||
const tooltips: Tooltip[] = []
|
||||
|
||||
for (const range of state.selection.ranges) {
|
||||
if (range.empty) {
|
||||
const pos = range.from
|
||||
const content = buildTooltipContent(state, pos)
|
||||
if (content) {
|
||||
const tooltip: Tooltip = {
|
||||
pos,
|
||||
above: true,
|
||||
arrow: false,
|
||||
create() {
|
||||
const dom = document.createElement('div')
|
||||
dom.append(content)
|
||||
dom.className = 'ol-cm-math-tooltip'
|
||||
|
||||
return { dom, overlap: true, offset: { x: 0, y: 8 } }
|
||||
},
|
||||
}
|
||||
|
||||
tooltips.push(tooltip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tooltips
|
||||
}
|
||||
|
||||
const buildTooltipContent = (
|
||||
state: EditorState,
|
||||
pos: number
|
||||
): HTMLDivElement | null => {
|
||||
// if anywhere inside Math, render the whole Math content
|
||||
const ancestorNode = mathAncestorNode(state, pos)
|
||||
if (!ancestorNode) return null
|
||||
|
||||
const [node] = descendantsOfNodeWithType(ancestorNode, 'Math', 'Math')
|
||||
if (!node) return null
|
||||
|
||||
const math = parseMathContainer(state, node, ancestorNode)
|
||||
if (!math || !math.content.length) return null
|
||||
|
||||
const element = document.createElement('div')
|
||||
element.style.opacity = '0'
|
||||
element.style.transition = 'opacity .01s ease-in'
|
||||
element.textContent = math.content
|
||||
|
||||
let definitions = ''
|
||||
const commandState = state.field(documentCommands, false)
|
||||
|
||||
if (commandState?.items) {
|
||||
for (const command of commandState.items) {
|
||||
if (command.type === 'definition' && command.raw) {
|
||||
definitions += `${command.raw}\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderMath(math.content, math.displayMode, element, definitions)
|
||||
.then(() => {
|
||||
element.style.opacity = '1'
|
||||
window.dispatchEvent(new Event(REPOSITION_EVENT))
|
||||
})
|
||||
.catch(error => {
|
||||
debugConsole.error(error)
|
||||
})
|
||||
|
||||
return element
|
||||
}
|
|
@ -82,6 +82,10 @@ import {
|
|||
createSpaceCommand,
|
||||
hasSpaceSubstitution,
|
||||
} from '@/features/source-editor/extensions/visual/visual-widgets/space'
|
||||
import {
|
||||
mathAncestorNode,
|
||||
parseMathContainer,
|
||||
} from '../../utils/tree-operations/math'
|
||||
|
||||
type Options = {
|
||||
previewByPath: (path: string) => PreviewPath | null
|
||||
|
@ -760,13 +764,7 @@ export const atomicDecorations = (options: Options) => {
|
|||
return false // no markup in input content
|
||||
} else if (nodeRef.type.is('Math')) {
|
||||
// math equations
|
||||
let passToMathJax = true
|
||||
|
||||
const ancestorNode =
|
||||
ancestorNodeOfType(state, nodeRef.from, '$MathContainer') ||
|
||||
ancestorNodeOfType(state, nodeRef.from, 'EquationEnvironment') ||
|
||||
// NOTE: EquationArrayEnvironment can be nested inside EquationEnvironment
|
||||
ancestorNodeOfType(state, nodeRef.from, 'EquationArrayEnvironment')
|
||||
const ancestorNode = mathAncestorNode(state, nodeRef.from)
|
||||
|
||||
if (
|
||||
ancestorNode &&
|
||||
|
@ -774,57 +772,19 @@ export const atomicDecorations = (options: Options) => {
|
|||
? shouldDecorateFromLineEdges(state, ancestorNode)
|
||||
: shouldDecorate(state, ancestorNode))
|
||||
) {
|
||||
// the content of the Math element, without braces
|
||||
const innerContent = state.doc
|
||||
.sliceString(nodeRef.from, nodeRef.to)
|
||||
.trim()
|
||||
const math = parseMathContainer(state, nodeRef, ancestorNode)
|
||||
|
||||
// only replace when there's content inside the braces
|
||||
if (innerContent.length) {
|
||||
let content = innerContent
|
||||
let displayMode = false
|
||||
|
||||
if (ancestorNode.type.is('$Environment')) {
|
||||
const environmentName = getEnvironmentName(ancestorNode, state)
|
||||
if (environmentName) {
|
||||
// use the outer content of environments that MathJax supports
|
||||
// https://docs.mathjax.org/en/latest/input/tex/macros/index.html#environments
|
||||
if (environmentName === 'tikzcd') {
|
||||
passToMathJax = false
|
||||
}
|
||||
if (
|
||||
environmentName !== 'math' &&
|
||||
environmentName !== 'displaymath'
|
||||
) {
|
||||
content = state.doc
|
||||
.sliceString(ancestorNode.from, ancestorNode.to)
|
||||
.trim()
|
||||
}
|
||||
|
||||
if (environmentName !== 'math') {
|
||||
displayMode = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
ancestorNode.type.is('BracketMath') ||
|
||||
Boolean(ancestorNode.getChild('DisplayMath'))
|
||||
) {
|
||||
displayMode = true
|
||||
}
|
||||
}
|
||||
if (passToMathJax) {
|
||||
decorations.push(
|
||||
Decoration.replace({
|
||||
widget: new MathWidget(
|
||||
content,
|
||||
displayMode,
|
||||
commandDefinitions
|
||||
),
|
||||
block: displayMode,
|
||||
}).range(ancestorNode.from, ancestorNode.to)
|
||||
)
|
||||
}
|
||||
if (math && math.passToMathJax) {
|
||||
decorations.push(
|
||||
Decoration.replace({
|
||||
widget: new MathWidget(
|
||||
math.content,
|
||||
math.displayMode,
|
||||
commandDefinitions
|
||||
),
|
||||
block: math.displayMode,
|
||||
}).range(ancestorNode.from, ancestorNode.to)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ import { debugConsole } from '@/utils/debugging'
|
|||
import { useMetadataContext } from '@/features/ide-react/context/metadata-context'
|
||||
import { useUserContext } from '@/shared/context/user-context'
|
||||
import { useReferencesContext } from '@/features/ide-react/context/references-context'
|
||||
import { setMathPreview } from '@/features/source-editor/extensions/math-preview'
|
||||
|
||||
function useCodeMirrorScope(view: EditorView) {
|
||||
const { fileTreeData } = useFileTreeData()
|
||||
|
@ -96,6 +97,7 @@ function useCodeMirrorScope(view: EditorView) {
|
|||
autoPairDelimiters,
|
||||
mode,
|
||||
syntaxValidation,
|
||||
mathPreview,
|
||||
} = userSettings
|
||||
|
||||
const [cursorHighlights] = useScopeValue<Record<string, Highlight[]>>(
|
||||
|
@ -153,6 +155,7 @@ function useCodeMirrorScope(view: EditorView) {
|
|||
autoPairDelimiters,
|
||||
mode,
|
||||
syntaxValidation,
|
||||
mathPreview,
|
||||
})
|
||||
|
||||
const currentDocRef = useRef({
|
||||
|
@ -385,6 +388,11 @@ function useCodeMirrorScope(view: EditorView) {
|
|||
view.dispatch(setSyntaxValidation(syntaxValidation))
|
||||
}, [view, syntaxValidation])
|
||||
|
||||
useEffect(() => {
|
||||
settingsRef.current.mathPreview = mathPreview
|
||||
view.dispatch(setMathPreview(mathPreview))
|
||||
}, [view, mathPreview])
|
||||
|
||||
const emitSyncToPdf = useScopeEventEmitter('cursor:editor:syncToPdf')
|
||||
|
||||
const handleGoToLine = useCallback(
|
||||
|
|
|
@ -46,6 +46,7 @@ const countCommandUsage = (context: CompletionContext) => {
|
|||
>()
|
||||
|
||||
const commandListProjection = context.state.field(documentCommands)
|
||||
|
||||
if (!commandListProjection.items) {
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ export class Command extends ProjectionItem {
|
|||
readonly title: string = ''
|
||||
readonly optionalArgCount: number = 0
|
||||
readonly requiredArgCount: number = 0
|
||||
readonly type: 'usage' | 'definition' = 'usage'
|
||||
readonly raw: string | undefined = undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,16 +64,16 @@ export const enterNode = (
|
|||
argCountNumber--
|
||||
}
|
||||
|
||||
const thisCommand: Readonly<Command> = {
|
||||
items.push({
|
||||
line: state.doc.lineAt(node.from).number,
|
||||
title: commandNameText,
|
||||
from: node.from,
|
||||
to: node.to,
|
||||
optionalArgCount: commandDefinitionHasOptionalArgument ? 1 : 0,
|
||||
requiredArgCount: argCountNumber,
|
||||
}
|
||||
|
||||
items.push(thisCommand)
|
||||
type: 'definition',
|
||||
raw: state.sliceDoc(node.from, node.to),
|
||||
})
|
||||
} else if (
|
||||
node.type.is('UnknownCommand') ||
|
||||
node.type.is('KnownCommand') ||
|
||||
|
@ -112,7 +114,7 @@ export const enterNode = (
|
|||
commandNode.getChildren('$Argument')
|
||||
const text = state.doc.sliceString(ctrlSeq.from, ctrlSeq.to)
|
||||
|
||||
const thisCommand = {
|
||||
items.push({
|
||||
line: state.doc.lineAt(commandNode.from).number,
|
||||
title: text,
|
||||
from: commandNode.from,
|
||||
|
@ -120,7 +122,8 @@ export const enterNode = (
|
|||
optionalArgCount: optionalArguments.length,
|
||||
requiredArgCount:
|
||||
commandArgumentsIncludingOptional.length - optionalArguments.length,
|
||||
}
|
||||
items.push(thisCommand)
|
||||
type: 'usage',
|
||||
raw: undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import { getEnvironmentName } from './environments'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { SyntaxNode, SyntaxNodeRef } from '@lezer/common'
|
||||
import { ancestorNodeOfType } from './ancestors'
|
||||
|
||||
export const mathAncestorNode = (state: EditorState, pos: number) =>
|
||||
ancestorNodeOfType(state, pos, '$MathContainer') ||
|
||||
ancestorNodeOfType(state, pos, 'EquationEnvironment') ||
|
||||
// NOTE: EquationArrayEnvironment can be nested inside EquationEnvironment
|
||||
ancestorNodeOfType(state, pos, 'EquationArrayEnvironment')
|
||||
|
||||
export const parseMathContainer = (
|
||||
state: EditorState,
|
||||
nodeRef: SyntaxNodeRef,
|
||||
ancestorNode: SyntaxNode
|
||||
) => {
|
||||
// the content of the Math element, without braces
|
||||
const innerContent = state.doc.sliceString(nodeRef.from, nodeRef.to).trim()
|
||||
|
||||
if (!innerContent.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
let content = innerContent
|
||||
let displayMode = false
|
||||
let passToMathJax = true
|
||||
|
||||
if (ancestorNode.type.is('$Environment')) {
|
||||
const environmentName = getEnvironmentName(ancestorNode, state)
|
||||
if (environmentName) {
|
||||
// use the outer content of environments that MathJax supports
|
||||
// https://docs.mathjax.org/en/latest/input/tex/macros/index.html#environments
|
||||
if (environmentName === 'tikzcd') {
|
||||
passToMathJax = false
|
||||
}
|
||||
if (environmentName !== 'math' && environmentName !== 'displaymath') {
|
||||
content = state.doc
|
||||
.sliceString(ancestorNode.from, ancestorNode.to)
|
||||
.trim()
|
||||
}
|
||||
|
||||
if (environmentName !== 'math') {
|
||||
displayMode = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
ancestorNode.type.is('BracketMath') ||
|
||||
Boolean(ancestorNode.getChild('DisplayMath'))
|
||||
) {
|
||||
displayMode = true
|
||||
}
|
||||
}
|
||||
|
||||
return { content, displayMode, passToMathJax }
|
||||
}
|
|
@ -23,6 +23,7 @@ const defaultSettings: UserSettings = {
|
|||
fontSize: 12,
|
||||
fontFamily: 'monaco',
|
||||
lineHeight: 'normal',
|
||||
mathPreview: true,
|
||||
}
|
||||
|
||||
type UserSettingsContextValue = {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
@import './editor/search.less';
|
||||
@import './editor/publish-template.less';
|
||||
@import './editor/online-users.less';
|
||||
@import './editor/math-preview.less';
|
||||
@import './editor/hotkeys.less';
|
||||
@import './editor/review-panel.less';
|
||||
@import './editor/publish-modal.less';
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.ol-cm-math-tooltip {
|
||||
box-shadow: 0px 2px 4px 0px #1e253029;
|
||||
border: 1px solid #e7e9ee !important;
|
||||
border-radius: 4px;
|
||||
background-color: white !important;
|
||||
max-height: 400px;
|
||||
max-width: 800px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
}
|
|
@ -565,6 +565,7 @@
|
|||
"enter_your_email_address": "Enter your email address",
|
||||
"enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password": "Enter your email address below, and we will send you a link to reset your password",
|
||||
"enter_your_new_password": "Enter your new password",
|
||||
"equation_preview": "Equation preview",
|
||||
"error": "Error",
|
||||
"error_opening_document": "Error opening document",
|
||||
"error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.",
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { screen, within } from '@testing-library/dom'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsMathPreview from '@/features/editor-left-menu/components/settings/settings-math-preview'
|
||||
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
|
||||
|
||||
describe('<SettingsMathPreview />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.reset()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
renderWithEditorContext(<SettingsMathPreview />)
|
||||
|
||||
const select = screen.getByLabelText('Equation preview')
|
||||
|
||||
const optionOn = within(select).getByText('On')
|
||||
expect(optionOn.getAttribute('value')).to.equal('true')
|
||||
|
||||
const optionOff = within(select).getByText('Off')
|
||||
expect(optionOff.getAttribute('value')).to.equal('false')
|
||||
})
|
||||
})
|
|
@ -54,6 +54,7 @@ const defaultUserSettings = {
|
|||
autoPairDelimiters: true,
|
||||
trackChanges: true,
|
||||
syntaxValidation: false,
|
||||
mathPreview: true,
|
||||
}
|
||||
|
||||
export function EditorProviders({
|
||||
|
|
|
@ -16,4 +16,5 @@ export type UserSettings = {
|
|||
fontSize: number
|
||||
fontFamily: FontFamily
|
||||
lineHeight: LineHeight
|
||||
mathPreview: boolean
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue