feat: add bold/italic shortcuts and toolbar buttons for Typst editor
Build and Deploy Verso / deploy (push) Successful in 14m39s
Build and Deploy Verso / deploy (push) Successful in 14m39s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-1
@@ -12,7 +12,10 @@ import { MathDropdown } from './math-dropdown'
|
||||
import { InsertListDropdown } from './insert-list-dropdown'
|
||||
import { TableDropdown } from './table-dropdown'
|
||||
import { LegacyTableDropdown } from './table-inserter-dropdown-legacy'
|
||||
import { withinFormattingCommand } from '@/features/source-editor/utils/tree-operations/formatting'
|
||||
import {
|
||||
withinFormattingCommand,
|
||||
withinTypstFormatting,
|
||||
} from '@/features/source-editor/utils/tree-operations/formatting'
|
||||
import { isMac } from '@/shared/utils/os'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
|
||||
@@ -41,6 +44,7 @@ export const ToolbarItems: FC<{
|
||||
const { features } = useProjectContext()
|
||||
const permissions = usePermissionsContext()
|
||||
const isActive = withinFormattingCommand(state)
|
||||
const isTypstActive = withinTypstFormatting(state)
|
||||
|
||||
const symbolPaletteAvailable = getMeta('ol-symbolPaletteAvailable')
|
||||
const showGroup = (group: string) => !overflowed || overflowed.has(group)
|
||||
@@ -189,6 +193,33 @@ export const ToolbarItems: FC<{
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{languageName === 'typst' && (
|
||||
<>
|
||||
{showGroup('group-format') && (
|
||||
<div
|
||||
className="ol-cm-toolbar-button-group"
|
||||
aria-label={t('toolbar_text_style')}
|
||||
>
|
||||
<ToolbarButton
|
||||
id="toolbar-format-bold"
|
||||
label={t('toolbar_bold')}
|
||||
command={commands.toggleTypstBold}
|
||||
active={isTypstActive('Strong')}
|
||||
icon="format_bold"
|
||||
shortcut={isMac ? '⌘B' : 'Ctrl+B'}
|
||||
/>
|
||||
<ToolbarButton
|
||||
id="toolbar-format-italic"
|
||||
label={t('toolbar_italic')}
|
||||
command={commands.toggleTypstItalic}
|
||||
active={isTypstActive('Emphasis')}
|
||||
icon="format_italic"
|
||||
shortcut={isMac ? '⌘I' : 'Ctrl+I'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -25,6 +25,8 @@ import { sendSearchEvent } from '@/features/event-tracking/search-events'
|
||||
|
||||
export const toggleBold = toggleRanges('\\textbf')
|
||||
export const toggleItalic = toggleRanges('\\textit')
|
||||
export const toggleTypstBold = wrapRanges('*', '*')
|
||||
export const toggleTypstItalic = wrapRanges('_', '_')
|
||||
|
||||
// TODO: apply as a snippet?
|
||||
// TODO: read URL from clipboard?
|
||||
|
||||
@@ -10,6 +10,7 @@ import { styleTags, tags as t } from '@lezer/highlight'
|
||||
import { parser } from '../../lezer-typst/typst.mjs'
|
||||
import { typstCompletions } from './complete'
|
||||
import { typstDocumentOutline } from './document-outline'
|
||||
import { shortcuts } from './shortcuts'
|
||||
|
||||
// Note on tree structure: rules starting with a lowercase letter in the grammar
|
||||
// are inline (no tree node), so their children are promoted to the parent.
|
||||
@@ -111,5 +112,6 @@ export const typst = () => {
|
||||
TypstLanguage.data.of({ autocomplete: typstCompletions }),
|
||||
typstDocumentOutline,
|
||||
syntaxHighlighting(typstHighlightStyle),
|
||||
shortcuts(),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Prec } from '@codemirror/state'
|
||||
import { keymap } from '@codemirror/view'
|
||||
import { wrapRanges } from '../../commands/ranges'
|
||||
|
||||
export const shortcuts = () => {
|
||||
return Prec.high(
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-b',
|
||||
mac: 'Mod-b',
|
||||
preventDefault: true,
|
||||
run: wrapRanges('*', '*'),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-i',
|
||||
mac: 'Mod-i',
|
||||
preventDefault: true,
|
||||
run: wrapRanges('_', '_'),
|
||||
},
|
||||
])
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,20 @@ import {
|
||||
matchingAncestor,
|
||||
} from '@/features/source-editor/utils/tree-operations/ancestors'
|
||||
|
||||
export type TypstFormattingNode = 'Strong' | 'Emphasis'
|
||||
|
||||
export const withinTypstFormatting = (state: EditorState) => {
|
||||
const tree = syntaxTree(state)
|
||||
|
||||
return (nodeTypeName: TypstFormattingNode): boolean => {
|
||||
const isFormatted = (range: SelectionRange): boolean => {
|
||||
const node = tree.resolveInner(range.from, -1)
|
||||
return Boolean(matchingAncestor(node, n => n.type.name === nodeTypeName))
|
||||
}
|
||||
return state.selection.ranges.every(isFormatted)
|
||||
}
|
||||
}
|
||||
|
||||
export type FormattingCommand = '\\textbf' | '\\textit'
|
||||
export type FormattingNodeType = string | number
|
||||
|
||||
|
||||
Reference in New Issue
Block a user