mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 18:03:42 -05:00
Merge pull request #14305 from overleaf/mj-table-buttons
[visual] Add toolbar actions to delete table and manage captions GitOrigin-RevId: 7a6aefd77fc4a66a1b78ae0727d4ece962fdd040
This commit is contained in:
parent
8caa1f8e14
commit
583222d5a5
12 changed files with 286 additions and 24 deletions
|
@ -125,7 +125,9 @@ export const Cell: FC<{
|
||||||
toDisplay.substring.bind(toDisplay)
|
toDisplay.substring.bind(toDisplay)
|
||||||
)
|
)
|
||||||
loadMathJax().then(async MathJax => {
|
loadMathJax().then(async MathJax => {
|
||||||
await MathJax.typesetPromise([renderDiv.current])
|
if (renderDiv.current) {
|
||||||
|
await MathJax.typesetPromise([renderDiv.current])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [cellData.content, editing])
|
}, [cellData.content, editing])
|
||||||
|
|
|
@ -6,10 +6,17 @@ import {
|
||||||
RowPosition,
|
RowPosition,
|
||||||
RowSeparator,
|
RowSeparator,
|
||||||
generateTable,
|
generateTable,
|
||||||
|
parseTableEnvironment,
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from '@codemirror/view'
|
||||||
import { SyntaxNode } from '@lezer/common'
|
import { SyntaxNode } from '@lezer/common'
|
||||||
|
|
||||||
|
export type TableEnvironmentData = {
|
||||||
|
table: { from: number; to: number }
|
||||||
|
caption?: { from: number; to: number }
|
||||||
|
label?: { from: number; to: number }
|
||||||
|
}
|
||||||
|
|
||||||
const TableContext = createContext<
|
const TableContext = createContext<
|
||||||
| {
|
| {
|
||||||
table: TableData
|
table: TableData
|
||||||
|
@ -19,6 +26,7 @@ const TableContext = createContext<
|
||||||
rowSeparators: RowSeparator[]
|
rowSeparators: RowSeparator[]
|
||||||
cellSeparators: CellSeparator[][]
|
cellSeparators: CellSeparator[][]
|
||||||
positions: Positions
|
positions: Positions
|
||||||
|
tableEnvironment?: TableEnvironmentData
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>(undefined)
|
>(undefined)
|
||||||
|
@ -26,7 +34,8 @@ const TableContext = createContext<
|
||||||
export const TableProvider: FC<{
|
export const TableProvider: FC<{
|
||||||
tabularNode: SyntaxNode
|
tabularNode: SyntaxNode
|
||||||
view: EditorView
|
view: EditorView
|
||||||
}> = ({ tabularNode, view, children }) => {
|
tableNode: SyntaxNode | null
|
||||||
|
}> = ({ tabularNode, view, children, tableNode }) => {
|
||||||
const tableData = generateTable(tabularNode, view.state)
|
const tableData = generateTable(tabularNode, view.state)
|
||||||
|
|
||||||
// TODO: Validate better that the table matches the column definition
|
// TODO: Validate better that the table matches the column definition
|
||||||
|
@ -40,9 +49,21 @@ export const TableProvider: FC<{
|
||||||
cells: tableData.cellPositions,
|
cells: tableData.cellPositions,
|
||||||
columnDeclarations: tableData.specification,
|
columnDeclarations: tableData.specification,
|
||||||
rowPositions: tableData.rowPositions,
|
rowPositions: tableData.rowPositions,
|
||||||
|
tabular: { from: tabularNode.from, to: tabularNode.to },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tableEnvironment = tableNode
|
||||||
|
? parseTableEnvironment(tableNode)
|
||||||
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContext.Provider value={{ ...tableData, positions }}>
|
<TableContext.Provider
|
||||||
|
value={{
|
||||||
|
...tableData,
|
||||||
|
positions,
|
||||||
|
tableEnvironment,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</TableContext.Provider>
|
</TableContext.Provider>
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,6 +47,7 @@ export type Positions = {
|
||||||
cells: CellPosition[][]
|
cells: CellPosition[][]
|
||||||
columnDeclarations: { from: number; to: number }
|
columnDeclarations: { from: number; to: number }
|
||||||
rowPositions: RowPosition[]
|
rowPositions: RowPosition[]
|
||||||
|
tabular: { from: number; to: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableRenderingError: FC<{
|
export const TableRenderingError: FC<{
|
||||||
|
@ -79,7 +80,8 @@ export const TableRenderingError: FC<{
|
||||||
export const Tabular: FC<{
|
export const Tabular: FC<{
|
||||||
tabularNode: SyntaxNode
|
tabularNode: SyntaxNode
|
||||||
view: EditorView
|
view: EditorView
|
||||||
}> = ({ tabularNode, view }) => {
|
tableNode: SyntaxNode | null
|
||||||
|
}> = ({ tabularNode, view, tableNode }) => {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallbackRender={() => (
|
fallbackRender={() => (
|
||||||
|
@ -88,7 +90,11 @@ export const Tabular: FC<{
|
||||||
>
|
>
|
||||||
<CodeMirrorViewContextProvider value={view}>
|
<CodeMirrorViewContextProvider value={view}>
|
||||||
<TabularProvider>
|
<TabularProvider>
|
||||||
<TableProvider tabularNode={tabularNode} view={view}>
|
<TableProvider
|
||||||
|
tabularNode={tabularNode}
|
||||||
|
view={view}
|
||||||
|
tableNode={tableNode}
|
||||||
|
>
|
||||||
<SelectionContextProvider>
|
<SelectionContextProvider>
|
||||||
<EditingContextProvider>
|
<EditingContextProvider>
|
||||||
<TabularWrapper />
|
<TabularWrapper />
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from '@codemirror/view'
|
||||||
import { ColumnDefinition, Positions } from '../tabular'
|
import { ColumnDefinition, Positions } from '../tabular'
|
||||||
import { ChangeSpec } from '@codemirror/state'
|
import { ChangeSpec, EditorSelection } from '@codemirror/state'
|
||||||
import {
|
import {
|
||||||
CellSeparator,
|
CellSeparator,
|
||||||
RowSeparator,
|
RowSeparator,
|
||||||
parseColumnSpecifications,
|
parseColumnSpecifications,
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { TableSelection } from '../contexts/selection-context'
|
import { TableSelection } from '../contexts/selection-context'
|
||||||
|
import { ensureEmptyLine } from '../../../extensions/toolbar/commands'
|
||||||
|
import { TableEnvironmentData } from '../contexts/table-context'
|
||||||
|
import {
|
||||||
|
extendBackwardsOverEmptyLines,
|
||||||
|
extendForwardsOverEmptyLines,
|
||||||
|
} from '../../../extensions/visual/selection'
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
export enum BorderTheme {
|
export enum BorderTheme {
|
||||||
|
@ -295,3 +301,135 @@ export const insertColumn = (
|
||||||
})
|
})
|
||||||
view.dispatch({ changes })
|
view.dispatch({ changes })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const removeNodes = (
|
||||||
|
view: EditorView,
|
||||||
|
...nodes: ({ from: number; to: number } | undefined)[]
|
||||||
|
) => {
|
||||||
|
const changes: ChangeSpec[] = []
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node !== undefined) {
|
||||||
|
changes.push({ from: node.from, to: node.to, insert: '' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.dispatch({
|
||||||
|
changes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const contains = (
|
||||||
|
{ from: outerFrom, to: outerTo }: { from: number; to: number },
|
||||||
|
{ from: innerFrom, to: innerTo }: { from: number; to: number }
|
||||||
|
) => {
|
||||||
|
return outerFrom <= innerFrom && outerTo >= innerTo
|
||||||
|
}
|
||||||
|
|
||||||
|
export const moveCaption = (
|
||||||
|
view: EditorView,
|
||||||
|
positions: Positions,
|
||||||
|
target: 'above' | 'below',
|
||||||
|
tableEnvironment?: TableEnvironmentData
|
||||||
|
) => {
|
||||||
|
const changes: ChangeSpec[] = []
|
||||||
|
const position =
|
||||||
|
target === 'above' ? positions.tabular.from : positions.tabular.to
|
||||||
|
const cursor = EditorSelection.cursor(position)
|
||||||
|
|
||||||
|
if (tableEnvironment?.caption) {
|
||||||
|
const { caption: existingCaption } = tableEnvironment
|
||||||
|
if (
|
||||||
|
(existingCaption.from < positions.tabular.from && target === 'above') ||
|
||||||
|
(existingCaption.from > positions.tabular.to && target === 'below')
|
||||||
|
) {
|
||||||
|
// It's already in the right place
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pos, prefix, suffix } = ensureEmptyLine(view.state, cursor, target)
|
||||||
|
|
||||||
|
if (!tableEnvironment?.caption) {
|
||||||
|
let labelText = '\\label{tab:my_table}'
|
||||||
|
if (tableEnvironment?.label) {
|
||||||
|
// We have a label, but no caption. Move the label after our caption
|
||||||
|
changes.push({
|
||||||
|
from: tableEnvironment.label.from,
|
||||||
|
to: tableEnvironment.label.to,
|
||||||
|
insert: '',
|
||||||
|
})
|
||||||
|
labelText = view.state.sliceDoc(
|
||||||
|
tableEnvironment.label.from,
|
||||||
|
tableEnvironment.label.to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
changes.push({
|
||||||
|
...gobbleEmptyLines(view, pos, 2, target),
|
||||||
|
insert: `${prefix}\\caption{Caption}\n${labelText}${suffix}`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const { caption: existingCaption, label: existingLabel } = tableEnvironment
|
||||||
|
// We have a caption, and we need to move it
|
||||||
|
let currentCaption = view.state.sliceDoc(
|
||||||
|
existingCaption.from,
|
||||||
|
existingCaption.to
|
||||||
|
)
|
||||||
|
if (existingLabel && !contains(existingCaption, existingLabel)) {
|
||||||
|
// Move label with it
|
||||||
|
const labelText = view.state.sliceDoc(
|
||||||
|
existingLabel.from,
|
||||||
|
existingLabel.to
|
||||||
|
)
|
||||||
|
currentCaption += `\n${labelText}`
|
||||||
|
changes.push({
|
||||||
|
from: existingLabel.from,
|
||||||
|
to: existingLabel.to,
|
||||||
|
insert: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
changes.push({
|
||||||
|
...gobbleEmptyLines(view, pos, 2, target),
|
||||||
|
insert: `${prefix}${currentCaption}${suffix}`,
|
||||||
|
})
|
||||||
|
// remove exsisting caption
|
||||||
|
changes.push({
|
||||||
|
from: existingCaption.from,
|
||||||
|
to: existingCaption.to,
|
||||||
|
insert: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
view.dispatch({ changes })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeCaption = (
|
||||||
|
view: EditorView,
|
||||||
|
tableEnvironment?: TableEnvironmentData
|
||||||
|
) => {
|
||||||
|
if (tableEnvironment?.caption && tableEnvironment.label) {
|
||||||
|
if (contains(tableEnvironment.caption, tableEnvironment.label)) {
|
||||||
|
return removeNodes(view, tableEnvironment.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removeNodes(view, tableEnvironment?.caption, tableEnvironment?.label)
|
||||||
|
}
|
||||||
|
|
||||||
|
const gobbleEmptyLines = (
|
||||||
|
view: EditorView,
|
||||||
|
pos: number,
|
||||||
|
lines: number,
|
||||||
|
target: 'above' | 'below'
|
||||||
|
) => {
|
||||||
|
const line = view.state.doc.lineAt(pos)
|
||||||
|
if (line.length !== 0) {
|
||||||
|
return { from: pos, to: pos }
|
||||||
|
}
|
||||||
|
if (target === 'above') {
|
||||||
|
return {
|
||||||
|
from: extendBackwardsOverEmptyLines(view.state.doc, line, lines),
|
||||||
|
to: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
from: pos,
|
||||||
|
to: extendForwardsOverEmptyLines(view.state.doc, line, lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {
|
||||||
BorderTheme,
|
BorderTheme,
|
||||||
insertColumn,
|
insertColumn,
|
||||||
insertRow,
|
insertRow,
|
||||||
|
moveCaption,
|
||||||
|
removeCaption,
|
||||||
|
removeNodes,
|
||||||
removeRowOrColumns,
|
removeRowOrColumns,
|
||||||
setAlignment,
|
setAlignment,
|
||||||
setBorders,
|
setBorders,
|
||||||
|
@ -18,7 +21,8 @@ import { useTableContext } from '../contexts/table-context'
|
||||||
export const Toolbar = memo(function Toolbar() {
|
export const Toolbar = memo(function Toolbar() {
|
||||||
const { selection } = useSelectionContext()
|
const { selection } = useSelectionContext()
|
||||||
const view = useCodeMirrorViewContext()
|
const view = useCodeMirrorViewContext()
|
||||||
const { positions, rowSeparators, cellSeparators } = useTableContext()
|
const { positions, rowSeparators, cellSeparators, tableEnvironment } =
|
||||||
|
useTableContext()
|
||||||
if (!selection) {
|
if (!selection) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -27,12 +31,15 @@ export const Toolbar = memo(function Toolbar() {
|
||||||
<ToolbarDropdown
|
<ToolbarDropdown
|
||||||
id="table-generator-caption-dropdown"
|
id="table-generator-caption-dropdown"
|
||||||
label="Caption below"
|
label="Caption below"
|
||||||
disabled
|
disabled={!tableEnvironment}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="ol-cm-toolbar-menu-item"
|
className="ol-cm-toolbar-menu-item"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
removeCaption(view, tableEnvironment)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
No caption
|
No caption
|
||||||
</button>
|
</button>
|
||||||
|
@ -40,6 +47,9 @@ export const Toolbar = memo(function Toolbar() {
|
||||||
className="ol-cm-toolbar-menu-item"
|
className="ol-cm-toolbar-menu-item"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
moveCaption(view, positions, 'above', tableEnvironment)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Caption above
|
Caption above
|
||||||
</button>
|
</button>
|
||||||
|
@ -47,6 +57,9 @@ export const Toolbar = memo(function Toolbar() {
|
||||||
className="ol-cm-toolbar-menu-item"
|
className="ol-cm-toolbar-menu-item"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
moveCaption(view, positions, 'below', tableEnvironment)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Caption below
|
Caption below
|
||||||
</button>
|
</button>
|
||||||
|
@ -212,7 +225,9 @@ export const Toolbar = memo(function Toolbar() {
|
||||||
icon="delete_forever"
|
icon="delete_forever"
|
||||||
id="table-generator-remove-table"
|
id="table-generator-remove-table"
|
||||||
label="Delete table"
|
label="Delete table"
|
||||||
disabled
|
command={() => {
|
||||||
|
removeNodes(view, tableEnvironment?.table ?? positions.tabular)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { EditorState } from '@codemirror/state'
|
import { EditorState } from '@codemirror/state'
|
||||||
import { SyntaxNode } from '@lezer/common'
|
import { SyntaxNode } from '@lezer/common'
|
||||||
import { ColumnDefinition, TableData } from './tabular'
|
import { ColumnDefinition, TableData } from './tabular'
|
||||||
|
import { TableEnvironmentData } from './contexts/table-context'
|
||||||
|
|
||||||
const ALIGNMENT_CHARACTERS = ['c', 'l', 'r', 'p']
|
const ALIGNMENT_CHARACTERS = ['c', 'l', 'r', 'p']
|
||||||
|
|
||||||
|
@ -279,3 +280,21 @@ export function generateTable(
|
||||||
cellSeparators,
|
cellSeparators,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseTableEnvironment(tableNode: SyntaxNode) {
|
||||||
|
const tableEnvironment: TableEnvironmentData = {
|
||||||
|
table: { from: tableNode.from, to: tableNode.to },
|
||||||
|
}
|
||||||
|
tableNode.cursor().iterate(({ type, from, to }) => {
|
||||||
|
if (tableEnvironment.caption && tableEnvironment.label) {
|
||||||
|
// Stop looking once we've found both caption and label
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (type.is('Caption')) {
|
||||||
|
tableEnvironment.caption = { from, to }
|
||||||
|
} else if (type.is('Label')) {
|
||||||
|
tableEnvironment.label = { from, to }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return tableEnvironment
|
||||||
|
}
|
||||||
|
|
|
@ -32,21 +32,32 @@ export const toggleNumberedList = toggleListForRanges('enumerate')
|
||||||
export const wrapInInlineMath = wrapRanges('\\(', '\\)')
|
export const wrapInInlineMath = wrapRanges('\\(', '\\)')
|
||||||
export const wrapInDisplayMath = wrapRanges('\n\\[', '\\]\n')
|
export const wrapInDisplayMath = wrapRanges('\n\\[', '\\]\n')
|
||||||
|
|
||||||
export const ensureEmptyLine = (state: EditorState, range: SelectionRange) => {
|
export const ensureEmptyLine = (
|
||||||
|
state: EditorState,
|
||||||
|
range: SelectionRange,
|
||||||
|
direction: 'above' | 'below' = 'below'
|
||||||
|
) => {
|
||||||
let pos = range.anchor
|
let pos = range.anchor
|
||||||
let suffix = ''
|
let suffix = ''
|
||||||
|
let prefix = ''
|
||||||
|
|
||||||
const line = state.doc.lineAt(pos)
|
const line = state.doc.lineAt(pos)
|
||||||
|
|
||||||
if (line.text.trim().length) {
|
if (line.text.trim().length) {
|
||||||
pos = Math.min(line.to + 1, state.doc.length)
|
if (direction === 'below') {
|
||||||
const nextLine = state.doc.lineAt(pos)
|
pos = Math.min(line.to + 1, state.doc.length)
|
||||||
|
} else {
|
||||||
|
pos = Math.max(line.from - 1, 0)
|
||||||
|
}
|
||||||
|
const neighbouringLine = state.doc.lineAt(pos)
|
||||||
|
|
||||||
if (nextLine.length) {
|
if (neighbouringLine.length && direction === 'below') {
|
||||||
suffix = '\n'
|
suffix = '\n'
|
||||||
|
} else if (neighbouringLine.length && direction === 'above') {
|
||||||
|
prefix = '\n'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { pos, suffix }
|
return { pos, suffix, prefix }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const insertFigure: Command = view => {
|
export const insertFigure: Command = view => {
|
||||||
|
@ -66,6 +77,8 @@ export const insertTable = (view: EditorView, sizeX: number, sizeY: number) => {
|
||||||
${('\t\t' + '#{} & #{}'.repeat(sizeX - 1) + '\\\\\n').repeat(
|
${('\t\t' + '#{} & #{}'.repeat(sizeX - 1) + '\\\\\n').repeat(
|
||||||
sizeY
|
sizeY
|
||||||
)}\\end{tabular}
|
)}\\end{tabular}
|
||||||
|
\t\\caption{Caption}
|
||||||
|
\t\\label{tab:my_label}
|
||||||
\\end{table}${suffix}`
|
\\end{table}${suffix}`
|
||||||
snippet(template)({ state, dispatch }, { label: 'Table' }, pos, pos)
|
snippet(template)({ state, dispatch }, { label: 'Table' }, pos, pos)
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -42,7 +42,10 @@ import { DividerWidget } from './visual-widgets/divider'
|
||||||
import { PreambleWidget } from './visual-widgets/preamble'
|
import { PreambleWidget } from './visual-widgets/preamble'
|
||||||
import { EndDocumentWidget } from './visual-widgets/end-document'
|
import { EndDocumentWidget } from './visual-widgets/end-document'
|
||||||
import { EnvironmentLineWidget } from './visual-widgets/environment-line'
|
import { EnvironmentLineWidget } from './visual-widgets/environment-line'
|
||||||
import { ListEnvironmentName } from '../../utils/tree-operations/ancestors'
|
import {
|
||||||
|
ListEnvironmentName,
|
||||||
|
ancestorOfNodeWithType,
|
||||||
|
} from '../../utils/tree-operations/ancestors'
|
||||||
import { InlineGraphicsWidget } from './visual-widgets/inline-graphics'
|
import { InlineGraphicsWidget } from './visual-widgets/inline-graphics'
|
||||||
import getMeta from '../../../../utils/meta'
|
import getMeta from '../../../../utils/meta'
|
||||||
import { EditableGraphicsWidget } from './visual-widgets/editable-graphics'
|
import { EditableGraphicsWidget } from './visual-widgets/editable-graphics'
|
||||||
|
@ -310,11 +313,19 @@ export const atomicDecorations = (options: Options) => {
|
||||||
nodeRef.type.is('TabularEnvironment')
|
nodeRef.type.is('TabularEnvironment')
|
||||||
) {
|
) {
|
||||||
if (shouldDecorate(state, nodeRef)) {
|
if (shouldDecorate(state, nodeRef)) {
|
||||||
|
const tableNode = ancestorOfNodeWithType(
|
||||||
|
nodeRef.node,
|
||||||
|
'TableEnvironment'
|
||||||
|
)
|
||||||
decorations.push(
|
decorations.push(
|
||||||
Decoration.replace({
|
Decoration.replace({
|
||||||
widget: new TabularWidget(
|
widget: new TabularWidget(
|
||||||
nodeRef.node,
|
nodeRef.node,
|
||||||
state.doc.sliceString(nodeRef.from, nodeRef.to)
|
state.doc.sliceString(
|
||||||
|
(tableNode ?? nodeRef).from,
|
||||||
|
(tableNode ?? nodeRef).to
|
||||||
|
),
|
||||||
|
tableNode
|
||||||
),
|
),
|
||||||
block: true,
|
block: true,
|
||||||
}).range(nodeRef.from, nodeRef.to)
|
}).range(nodeRef.from, nodeRef.to)
|
||||||
|
|
|
@ -35,9 +35,17 @@ export const placeSelectionInsideBlock = (
|
||||||
return { selection, effects: EditorView.scrollIntoView(line.to) }
|
return { selection, effects: EditorView.scrollIntoView(line.to) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extendBackwardsOverEmptyLines = (doc: Text, line: Line) => {
|
export const extendBackwardsOverEmptyLines = (
|
||||||
|
doc: Text,
|
||||||
|
line: Line,
|
||||||
|
limit: number = Number.POSITIVE_INFINITY
|
||||||
|
) => {
|
||||||
let { number, from } = line
|
let { number, from } = line
|
||||||
for (let lineNumber = number - 1; lineNumber > 0; lineNumber--) {
|
for (
|
||||||
|
let lineNumber = number - 1;
|
||||||
|
lineNumber > 0 && number - lineNumber <= limit;
|
||||||
|
lineNumber--
|
||||||
|
) {
|
||||||
const line = doc.line(lineNumber)
|
const line = doc.line(lineNumber)
|
||||||
if (line.text.trim().length > 0) {
|
if (line.text.trim().length > 0) {
|
||||||
break
|
break
|
||||||
|
@ -47,9 +55,17 @@ export const extendBackwardsOverEmptyLines = (doc: Text, line: Line) => {
|
||||||
return from
|
return from
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extendForwardsOverEmptyLines = (doc: Text, line: Line) => {
|
export const extendForwardsOverEmptyLines = (
|
||||||
|
doc: Text,
|
||||||
|
line: Line,
|
||||||
|
limit: number = Number.POSITIVE_INFINITY
|
||||||
|
) => {
|
||||||
let { number, to } = line
|
let { number, to } = line
|
||||||
for (let lineNumber = number + 1; lineNumber <= doc.lines; lineNumber++) {
|
for (
|
||||||
|
let lineNumber = number + 1;
|
||||||
|
lineNumber <= doc.lines && lineNumber - number <= limit;
|
||||||
|
lineNumber++
|
||||||
|
) {
|
||||||
const line = doc.line(lineNumber)
|
const line = doc.line(lineNumber)
|
||||||
if (line.text.trim().length > 0) {
|
if (line.text.trim().length > 0) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -6,7 +6,11 @@ import { Tabular } from '../../../components/table-generator/tabular'
|
||||||
export class TabularWidget extends WidgetType {
|
export class TabularWidget extends WidgetType {
|
||||||
private element: HTMLElement | undefined
|
private element: HTMLElement | undefined
|
||||||
|
|
||||||
constructor(private node: SyntaxNode, private content: string) {
|
constructor(
|
||||||
|
private tabularNode: SyntaxNode,
|
||||||
|
private content: string,
|
||||||
|
private tableNode: SyntaxNode | null
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +19,11 @@ export class TabularWidget extends WidgetType {
|
||||||
this.element.classList.add('ol-cm-tabular')
|
this.element.classList.add('ol-cm-tabular')
|
||||||
this.element.style.backgroundColor = 'rgba(125, 125, 125, 0.05)'
|
this.element.style.backgroundColor = 'rgba(125, 125, 125, 0.05)'
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Tabular view={view} tabularNode={this.node} />,
|
<Tabular
|
||||||
|
view={view}
|
||||||
|
tabularNode={this.tabularNode}
|
||||||
|
tableNode={this.tableNode}
|
||||||
|
/>,
|
||||||
this.element
|
this.element
|
||||||
)
|
)
|
||||||
return this.element
|
return this.element
|
||||||
|
@ -23,7 +31,10 @@ export class TabularWidget extends WidgetType {
|
||||||
|
|
||||||
eq(widget: TabularWidget): boolean {
|
eq(widget: TabularWidget): boolean {
|
||||||
return (
|
return (
|
||||||
this.node.from === widget.node.from && this.content === widget.content
|
this.tabularNode.from === widget.tabularNode.from &&
|
||||||
|
this.tableNode?.from === widget.tableNode?.from &&
|
||||||
|
this.tableNode?.to === widget.tableNode?.to &&
|
||||||
|
this.content === widget.content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,8 @@
|
||||||
VerbatimEnvName,
|
VerbatimEnvName,
|
||||||
TikzPictureEnvName,
|
TikzPictureEnvName,
|
||||||
FigureEnvName,
|
FigureEnvName,
|
||||||
ListEnvName
|
ListEnvName,
|
||||||
|
TableEnvName
|
||||||
}
|
}
|
||||||
|
|
||||||
@external specialize {CtrlSym} specializeCtrlSym from "./tokens.mjs" {
|
@external specialize {CtrlSym} specializeCtrlSym from "./tokens.mjs" {
|
||||||
|
@ -521,6 +522,7 @@ KnownEnvironment {
|
||||||
| TikzPictureEnvironment
|
| TikzPictureEnvironment
|
||||||
| FigureEnvironment
|
| FigureEnvironment
|
||||||
| ListEnvironment
|
| ListEnvironment
|
||||||
|
| TableEnvironment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +553,12 @@ TabularEnvironment[@isGroup="$Environment"] {
|
||||||
EndEnv<TabularEnvName>
|
EndEnv<TabularEnvName>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableEnvironment[@isGroup="$Environment"] {
|
||||||
|
BeginEnv<TableEnvName>
|
||||||
|
Content<Text>
|
||||||
|
EndEnv<TableEnvName>
|
||||||
|
}
|
||||||
|
|
||||||
EquationEnvironment[@isGroup="$Environment"] {
|
EquationEnvironment[@isGroup="$Environment"] {
|
||||||
BeginEnv<EquationEnvName>
|
BeginEnv<EquationEnvName>
|
||||||
Content<Math?>
|
Content<Math?>
|
||||||
|
|
|
@ -76,6 +76,7 @@ import {
|
||||||
TopRuleCtrlSeq,
|
TopRuleCtrlSeq,
|
||||||
MidRuleCtrlSeq,
|
MidRuleCtrlSeq,
|
||||||
BottomRuleCtrlSeq,
|
BottomRuleCtrlSeq,
|
||||||
|
TableEnvName,
|
||||||
} from './latex.terms.mjs'
|
} from './latex.terms.mjs'
|
||||||
|
|
||||||
function nameChar(ch) {
|
function nameChar(ch) {
|
||||||
|
@ -736,6 +737,7 @@ const otherKnownEnvNames = {
|
||||||
subfigure: FigureEnvName,
|
subfigure: FigureEnvName,
|
||||||
enumerate: ListEnvName,
|
enumerate: ListEnvName,
|
||||||
itemize: ListEnvName,
|
itemize: ListEnvName,
|
||||||
|
table: TableEnvName,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const specializeEnvName = (name, terms) => {
|
export const specializeEnvName = (name, terms) => {
|
||||||
|
|
Loading…
Reference in a new issue