| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
- import { Command, EditorView } from '@codemirror/view'
- import {
- closeSearchPanel,
- openSearchPanel,
- searchPanelOpen,
- } from '@codemirror/search'
- import { toggleRanges, wrapRanges } from '../../commands/ranges'
- import {
- ancestorListType,
- toggleListForRanges,
- unwrapBulletList,
- unwrapDescriptionList,
- unwrapNumberedList,
- wrapInBulletList,
- wrapInDescriptionList,
- wrapInNumberedList,
- } from './lists'
- import { snippet } from '@codemirror/autocomplete'
- import { snippets } from './snippets'
- import { minimumListDepthForSelection } from '../../utils/tree-operations/ancestors'
- import { isVisual } from '../visual/visual'
- import { sendSearchEvent } from '@/features/event-tracking/search-events'
- export const toggleBold = toggleRanges('\\textbf')
- export const toggleItalic = toggleRanges('\\textit')
- // TODO: apply as a snippet?
- // TODO: read URL from clipboard?
- export const wrapInHref = wrapRanges('\\href{}{', '}', false, (range, view) =>
- isVisual(view) ? range : EditorSelection.cursor(range.from - 2)
- )
- export const toggleBulletList = toggleListForRanges('itemize')
- export const toggleNumberedList = toggleListForRanges('enumerate')
- export const wrapInInlineMath = wrapRanges('\\(', '\\)')
- export const wrapInDisplayMath = wrapRanges('\n\\[', '\\]\n')
- export const ensureEmptyLine = (
- state: EditorState,
- range: SelectionRange,
- direction: 'above' | 'below' = 'below'
- ) => {
- let pos = range.anchor
- let suffix = ''
- let prefix = ''
- const line = state.doc.lineAt(pos)
- if (line.text.trim().length) {
- if (direction === 'below') {
- pos = Math.min(line.to + 1, state.doc.length)
- } else {
- pos = Math.max(line.from - 1, 0)
- }
- const neighbouringLine = state.doc.lineAt(pos)
- if (neighbouringLine.length && direction === 'below') {
- suffix = '\n'
- } else if (neighbouringLine.length && direction === 'above') {
- prefix = '\n'
- }
- }
- return { pos, suffix, prefix }
- }
- export const insertFigure: Command = view => {
- const { state, dispatch } = view
- const { pos, suffix } = ensureEmptyLine(state, state.selection.main)
- const template = `\n${snippets.figure}\n${suffix}`
- snippet(template)({ state, dispatch }, { label: 'Figure' }, pos, pos)
- return true
- }
- export const insertTable = (view: EditorView, sizeX: number, sizeY: number) => {
- const { state, dispatch } = view
- const visual = isVisual(view)
- const placeholder = visual ? '' : '#{}'
- const placeholderAtStart = visual ? '#{}' : ''
- const { pos, suffix } = ensureEmptyLine(state, state.selection.main)
- const template = `${placeholderAtStart}\n\\begin{table}
- \t\\centering
- \t\\begin{tabular}{${'c'.repeat(sizeX)}}
- ${(
- '\t\t' +
- `${placeholder} & ${placeholder}`.repeat(sizeX - 1) +
- '\\\\\n'
- ).repeat(sizeY)}\t\\end{tabular}
- \t\\caption{Caption}
- \t\\label{tab:placeholder}
- \\end{table}${suffix}`
- snippet(template)({ state, dispatch }, { label: 'Table' }, pos, pos)
- return true
- }
- export const insertCite: Command = view => {
- const { state, dispatch } = view
- const pos = state.selection.main.anchor
- const template = snippets.cite
- snippet(template)({ state, dispatch }, { label: 'Cite' }, pos, pos)
- return true
- }
- export const insertRef: Command = view => {
- const { state, dispatch } = view
- const pos = state.selection.main.anchor
- const template = snippets.ref
- snippet(template)({ state, dispatch }, { label: 'Ref' }, pos, pos)
- return true
- }
- export const indentDecrease: Command = view => {
- if (minimumListDepthForSelection(view.state) < 2) {
- return false
- }
- switch (ancestorListType(view.state)) {
- case 'itemize':
- return unwrapBulletList(view)
- case 'enumerate':
- return unwrapNumberedList(view)
- case 'description':
- return unwrapDescriptionList(view)
- default:
- return false
- }
- }
- export const cursorIsAtStartOfListItem = (state: EditorState) => {
- return state.selection.ranges.every(range => {
- const line = state.doc.lineAt(range.from)
- const prefix = state.sliceDoc(line.from, range.from)
- return /\\item\s*$/.test(prefix)
- })
- }
- export const indentIncrease: Command = view => {
- if (minimumListDepthForSelection(view.state) < 1) {
- return false
- }
- switch (ancestorListType(view.state)) {
- case 'itemize':
- return wrapInBulletList(view)
- case 'enumerate':
- return wrapInNumberedList(view)
- case 'description':
- return wrapInDescriptionList(view)
- default:
- return false
- }
- }
- export const toggleSearch: Command = view => {
- if (searchPanelOpen(view.state)) {
- closeSearchPanel(view)
- } else {
- sendSearchEvent('search-open', {
- searchType: 'document',
- method: 'button',
- location: 'toolbar',
- mode: isVisual(view) ? 'visual' : 'source',
- })
- openSearchPanel(view)
- }
- return true
- }
- export const addComment = () => {
- window.dispatchEvent(new Event('add-new-review-comment'))
- }
|