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 { InsertListDropdown } from './insert-list-dropdown'
|
||||||
import { TableDropdown } from './table-dropdown'
|
import { TableDropdown } from './table-dropdown'
|
||||||
import { LegacyTableDropdown } from './table-inserter-dropdown-legacy'
|
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 { isMac } from '@/shared/utils/os'
|
||||||
import { useProjectContext } from '@/shared/context/project-context'
|
import { useProjectContext } from '@/shared/context/project-context'
|
||||||
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
|
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
|
||||||
@@ -41,6 +44,7 @@ export const ToolbarItems: FC<{
|
|||||||
const { features } = useProjectContext()
|
const { features } = useProjectContext()
|
||||||
const permissions = usePermissionsContext()
|
const permissions = usePermissionsContext()
|
||||||
const isActive = withinFormattingCommand(state)
|
const isActive = withinFormattingCommand(state)
|
||||||
|
const isTypstActive = withinTypstFormatting(state)
|
||||||
|
|
||||||
const symbolPaletteAvailable = getMeta('ol-symbolPaletteAvailable')
|
const symbolPaletteAvailable = getMeta('ol-symbolPaletteAvailable')
|
||||||
const showGroup = (group: string) => !overflowed || overflowed.has(group)
|
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 toggleBold = toggleRanges('\\textbf')
|
||||||
export const toggleItalic = toggleRanges('\\textit')
|
export const toggleItalic = toggleRanges('\\textit')
|
||||||
|
export const toggleTypstBold = wrapRanges('*', '*')
|
||||||
|
export const toggleTypstItalic = wrapRanges('_', '_')
|
||||||
|
|
||||||
// TODO: apply as a snippet?
|
// TODO: apply as a snippet?
|
||||||
// TODO: read URL from clipboard?
|
// 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 { parser } from '../../lezer-typst/typst.mjs'
|
||||||
import { typstCompletions } from './complete'
|
import { typstCompletions } from './complete'
|
||||||
import { typstDocumentOutline } from './document-outline'
|
import { typstDocumentOutline } from './document-outline'
|
||||||
|
import { shortcuts } from './shortcuts'
|
||||||
|
|
||||||
// Note on tree structure: rules starting with a lowercase letter in the grammar
|
// 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.
|
// 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 }),
|
TypstLanguage.data.of({ autocomplete: typstCompletions }),
|
||||||
typstDocumentOutline,
|
typstDocumentOutline,
|
||||||
syntaxHighlighting(typstHighlightStyle),
|
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,
|
matchingAncestor,
|
||||||
} from '@/features/source-editor/utils/tree-operations/ancestors'
|
} 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 FormattingCommand = '\\textbf' | '\\textit'
|
||||||
export type FormattingNodeType = string | number
|
export type FormattingNodeType = string | number
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user