Skip to content

Commit 43957f4

Browse files
committed
fix focus bug and add insert function button
1 parent d1172c1 commit 43957f4

File tree

10 files changed

+122
-18
lines changed

10 files changed

+122
-18
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The default keyboard shortcuts have changed, it is recommended to reset to the d
1010

1111
### Added
1212

13+
- A new command and button for inserting function calls easier.
1314
- More TyX commands for working with TyX itself: `fileNewFromTemplate`, `fileClose`, `openSettings` and `openDocumentSettings`.
1415
- A hint for the command which will be executed when clicking some command button is now shown in the status bar.
1516

@@ -20,6 +21,7 @@ The default keyboard shortcuts have changed, it is recommended to reset to the d
2021
### Fixed
2122

2223
- Various issues regarding closing a file.
24+
- Commands continue to work when switching between the main editor and nested editors.
2325

2426
## [0.2.9] - 2025-10-08
2527

docs/commands.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,14 @@ Insert Function Call
108108

109109
Insert a function call node with the given function (and optionally initial parameter values or inline customization).
110110

111+
If no parameters are passed, a modal is used to pick the function.
112+
111113
Example: ``insertFunctionCall footnote``
112114

113115
Example: ``insertFunctionCall ["h", [{"type": "length", "value": "10", "unit": "pt"}]]``
114116

117+
Example: ``insertFunctionCall``
118+
115119

116120
Toggle Math Inline
117121
^^^^^^^^^^^^^^^^^^
@@ -262,13 +266,15 @@ Previews the current file as a PDF.
262266

263267
Example: ``filePreview``
264268

269+
265270
Open Settings
266271
~~~~~~~~~~~~~
267272

268273
Opens the app settings modal.
269274

270275
Example: ``openSettings``
271276

277+
272278
Open Document Settings
273279
~~~~~~~~~~~~~~~~~~~~~~
274280

src/components/CommandActionIcon.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { ActionIcon, ActionIconProps } from "@mantine/core"
1+
import { ActionIcon, ActionIconProps, Tooltip } from "@mantine/core"
22
import { executeCommandSequence } from "../commands"
33
import { setLocalStorage } from "../utilities/hooks"
44

55
const CommandActionIcon = (
6-
props: ActionIconProps & { component?: any; command: string },
6+
props: ActionIconProps & { component?: any; command: string; label?: string },
77
) => {
8-
return (
8+
const icon = (
99
<ActionIcon
1010
onMouseEnter={() => setLocalStorage("Hover Command", props.command)}
1111
onMouseLeave={() => setLocalStorage("Hover Command", null)}
@@ -17,6 +17,12 @@ const CommandActionIcon = (
1717
{...props}
1818
/>
1919
)
20+
21+
if (props.label) {
22+
return <Tooltip label={props.label}>{icon}</Tooltip>
23+
}
24+
25+
return icon
2026
}
2127

2228
export default CommandActionIcon

src/components/plugins/CurrentEditorPlugin.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
2+
import { COMMAND_PRIORITY_HIGH, FOCUS_COMMAND } from "lexical"
23
import { useEffect } from "react"
34

45
const CurrentEditorPlugin = () => {
@@ -14,6 +15,18 @@ const CurrentEditorPlugin = () => {
1415
}
1516
}, [editor])
1617

18+
useEffect(() => {
19+
return editor.registerCommand(
20+
FOCUS_COMMAND,
21+
() => {
22+
window.currentEditor = editor
23+
24+
return false
25+
},
26+
COMMAND_PRIORITY_HIGH,
27+
)
28+
}, [editor])
29+
1730
return null
1831
}
1932

src/components/plugins/FunctionCallPlugin.tsx

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary"
55
import { LexicalNestedComposer } from "@lexical/react/LexicalNestedComposer"
66
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"
77
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"
8-
import { Button, TextInput } from "@mantine/core"
8+
import { Autocomplete, Button } from "@mantine/core"
99
import { modals } from "@mantine/modals"
10-
import { IconDeviceFloppy, IconFunction } from "@tabler/icons-react"
10+
import { IconDeviceFloppy, IconFunction, IconPlus } from "@tabler/icons-react"
1111
import {
1212
$createNodeSelection,
1313
$getNodeByKey,
@@ -17,10 +17,12 @@ import {
1717
LexicalEditor,
1818
NodeKey,
1919
} from "lexical"
20-
import { useEffect, useState } from "react"
20+
import { useEffect, useMemo, useState } from "react"
21+
import { executeCommandSequence } from "../../commands"
2122
import { stringifyFunction } from "../../compilers/lexical2typst"
2223
import { getFunctions } from "../../functions"
2324
import { TyXValue } from "../../models"
25+
import { backupEditorSelection, restoreEditorSelection } from "../../utilities"
2426
import TyXValueEditor from "../TyXValueEditor"
2527
import CurrentEditorPlugin from "./CurrentEditorPlugin"
2628
import {
@@ -77,17 +79,18 @@ export const FunctionCallEditModal = ({
7779
modals.closeAll()
7880
}
7981

80-
const functions = getFunctions()
82+
const functions = useMemo(() => getFunctions(), [])
8183

8284
return (
8385
<>
84-
<TextInput
86+
<Autocomplete
8587
autoCapitalize="off"
8688
autoCorrect="off"
8789
label="Function"
8890
leftSection={<IconFunction />}
8991
value={name}
90-
onChange={(e) => setName(e.currentTarget.value)}
92+
onChange={setName}
93+
data={Object.keys(functions).sort()}
9194
/>
9295
{functions[name]?.positional?.map(
9396
(parameterDescription, positionIndex) => (
@@ -233,13 +236,58 @@ export const FunctionCallEditor = ({
233236
)
234237
}
235238

239+
export const InsertFunctionCallModal = () => {
240+
const [name, setName] = useState("")
241+
242+
const functions = useMemo(() => getFunctions(), [])
243+
244+
const insert = () => {
245+
restoreEditorSelection()
246+
executeCommandSequence(`insertFunctionCall ${name}`)
247+
modals.closeAll()
248+
}
249+
250+
return (
251+
<>
252+
<Autocomplete
253+
data-autofocus
254+
autoCapitalize="off"
255+
autoCorrect="off"
256+
label="Function"
257+
leftSection={<IconFunction />}
258+
value={name}
259+
onChange={setName}
260+
onKeyDown={(e) => {
261+
if (e.key === "Enter") {
262+
e.preventDefault()
263+
insert()
264+
}
265+
}}
266+
data={Object.keys(functions).sort()}
267+
/>
268+
<Button mt="xs" fullWidth leftSection={<IconPlus />} onClick={insert}>
269+
Insert
270+
</Button>
271+
</>
272+
)
273+
}
274+
236275
const FunctionCallPlugin = () => {
237276
const [editor] = useLexicalComposerContext()
238277

239278
useEffect(() => {
240279
return editor.registerCommand(
241280
INSERT_FUNCTION_CALL_COMMAND,
242281
(payload) => {
282+
if (payload === undefined) {
283+
backupEditorSelection()
284+
modals.open({
285+
title: "Insert Function Call",
286+
children: <InsertFunctionCallModal />,
287+
})
288+
return true
289+
}
290+
243291
const node =
244292
typeof payload === "string"
245293
? $createFunctionCallNode(payload, undefined, undefined)

src/components/plugins/ToolbarPlugin.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
IconDeviceFloppy,
3333
IconEye,
3434
IconFileCode,
35+
IconFunction,
3536
IconH1,
3637
IconH2,
3738
IconH3,
@@ -65,7 +66,6 @@ import {
6566
$isElementNode,
6667
$isNodeSelection,
6768
$isRangeSelection,
68-
$setSelection,
6969
BaseSelection,
7070
CAN_REDO_COMMAND,
7171
CAN_UNDO_COMMAND,
@@ -97,7 +97,11 @@ import {
9797
getSelectedNode,
9898
} from "../../resources/playground"
9999
import { getSettings } from "../../settings"
100-
import { showSuccessMessage } from "../../utilities"
100+
import {
101+
getEditorSelection,
102+
setEditorSelection,
103+
showSuccessMessage,
104+
} from "../../utilities"
101105
import { useLocalStorage } from "../../utilities/hooks"
102106
import CommandActionIcon from "../CommandActionIcon"
103107
import { OPEN_LINK_POPUP_COMMAND } from "./tyxCommands"
@@ -139,6 +143,7 @@ const ToolbarControl = React.forwardRef<
139143
if (command) {
140144
return (
141145
<CommandActionIcon
146+
label={label}
142147
className="toolbar-control"
143148
size={30}
144149
variant={active ? undefined : "default"}
@@ -350,6 +355,9 @@ const InsertControls = () => {
350355
<ToolbarControl label="Insert image" onClick={insertImage}>
351356
<IconPhoto />
352357
</ToolbarControl>
358+
<ToolbarControl label="Insert function" command="insertFunctionCall">
359+
<IconFunction />
360+
</ToolbarControl>
353361
<ToolbarControl label="Insert Typst code" command="insertTypstCode">
354362
<IconCodeAsterisk />
355363
</ToolbarControl>
@@ -535,20 +543,16 @@ const LinkControls = () => {
535543
const ref = useRef<HTMLInputElement>(null)
536544

537545
const save = () => {
538-
window.currentEditor?.update(() => {
539-
$setSelection(selection?.clone() ?? null)
540-
})
546+
setEditorSelection(selection)
541547
executeCommandSequence(`toggleLink ${ref.current!.value}`)
542548
setOpened(false)
543549
}
544550

545551
const changeOpened = (opened: boolean) => {
546552
if (!opened) {
547-
window.currentEditor?.update(() => {
548-
$setSelection(selection?.clone() ?? null)
549-
})
553+
setEditorSelection(selection)
550554
} else {
551-
setSelection(window.currentEditor?.read(() => $getSelection()) ?? null)
555+
setSelection(getEditorSelection())
552556
}
553557
setOpened(opened)
554558
}

src/components/plugins/functionCall.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { TyXRootNode, TyXValue } from "../../models"
1919
import { FunctionCallEditor } from "./FunctionCallPlugin"
2020

2121
export const INSERT_FUNCTION_CALL_COMMAND: LexicalCommand<
22+
| undefined
2223
| string
2324
| [string, TyXValue[] | undefined, Record<string, TyXValue> | undefined]
2425
> = createCommand()

src/components/plugins/tyxCommands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ export const FILE_PREVIEW_COMMAND: LexicalCommand<void> = createCommand()
1515
export const OPEN_SETTINGS_COMMAND: LexicalCommand<void> = createCommand()
1616
export const OPEN_DOCUMENT_SETTINGS_COMMAND: LexicalCommand<void> =
1717
createCommand()
18+
19+
export const INSERT_FUNCTION: LexicalCommand<void> = createCommand()

src/shortcuts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: [string, string][] = [
4747
["mod+l", "insertTypstCode"],
4848
["mod+m", "insertMath true"],
4949
["mod+shift+m", "toggleMathInline"],
50+
["mod+shift+f", "insertFunctionCall"],
5051
["mod+k", "openLinkPopup"],
5152
["mod+;", "openSettings"],
5253
["mod+shift+;", "openDocumentSettings"],

src/utilities/index.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { modals } from "@mantine/modals"
66
import { notifications } from "@mantine/notifications"
77
import { IconCheck, IconExclamationMark } from "@tabler/icons-react"
88
import i18n from "i18next"
9+
import { $getSelection, $setSelection, BaseSelection } from "lexical"
910
import { TyXDocument } from "../models"
1011
import { getLocalStorage } from "./hooks"
1112

@@ -67,3 +68,23 @@ export const getCurrentDocument = () => {
6768
const currentDocument = getLocalStorage<number>("Current Document")
6869
return openDocuments[currentDocument]
6970
}
71+
72+
export const setEditorSelection = (selection: BaseSelection | null) => {
73+
window.currentEditor?.update(() => {
74+
$setSelection(selection?.clone() ?? null)
75+
})
76+
}
77+
78+
export const getEditorSelection = () => {
79+
return window.currentEditor?.read(() => $getSelection()) ?? null
80+
}
81+
82+
let savedEditorSelection: BaseSelection | null
83+
84+
export const backupEditorSelection = () => {
85+
savedEditorSelection = getEditorSelection()
86+
}
87+
88+
export const restoreEditorSelection = () => {
89+
setEditorSelection(savedEditorSelection)
90+
}

0 commit comments

Comments
 (0)