@inkbrick/react 0.0.0-stage → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FormulaPalette-WFRPRHS5.js +320 -0
- package/dist/FormulaPalette-WFRPRHS5.js.map +1 -0
- package/dist/chunk-TPUX4C45.js +2309 -0
- package/dist/chunk-TPUX4C45.js.map +1 -0
- package/dist/index.d.ts +1431 -0
- package/dist/index.js +4880 -0
- package/dist/index.js.map +1 -0
- package/dist/mathCss-FLPE6QBM.js +361 -0
- package/dist/mathCss-FLPE6QBM.js.map +1 -0
- package/package.json +46 -3
- package/src/block-handle/BlockHandleController.ts +93 -0
- package/src/block-handle/BlockHandleHost.tsx +108 -0
- package/src/block-handle/DefaultBlockHandle.tsx +71 -0
- package/src/block-handle/DefaultBlockMenu.tsx +114 -0
- package/src/block-handle/DropIndicatorHost.tsx +58 -0
- package/src/block-handle/actions.tsx +146 -0
- package/src/block-handle/blockDrag.ts +215 -0
- package/src/block-handle/geometry.ts +64 -0
- package/src/block-handle/gripGesture.ts +108 -0
- package/src/block-handle/types.ts +65 -0
- package/src/block-handle/useBlockDrag.ts +21 -0
- package/src/block-handle/useBlockHandles.ts +125 -0
- package/src/blocks/BlockChildren.tsx +17 -0
- package/src/blocks/BlockProps.ts +5 -0
- package/src/blocks/CalloutBlock.tsx +61 -0
- package/src/blocks/CodeBlock.tsx +96 -0
- package/src/blocks/DividerBlock.tsx +15 -0
- package/src/blocks/Editable.tsx +84 -0
- package/src/blocks/FormulaBlock.tsx +173 -0
- package/src/blocks/HeadingBlock.tsx +22 -0
- package/src/blocks/ImageBlock.tsx +362 -0
- package/src/blocks/ListBlock.tsx +86 -0
- package/src/blocks/NoteBlock.tsx +10 -0
- package/src/blocks/PageBlock.tsx +10 -0
- package/src/blocks/ParagraphBlock.tsx +20 -0
- package/src/blocks/QuoteBlock.tsx +18 -0
- package/src/blocks/UnknownBlockView.tsx +27 -0
- package/src/blocks/defineBlock.ts +49 -0
- package/src/clipboard/html/blocks.ts +379 -0
- package/src/clipboard/html/inline.ts +92 -0
- package/src/clipboard/html/style.ts +92 -0
- package/src/clipboard/html/tags.ts +77 -0
- package/src/clipboard/htmlToFragment.ts +41 -0
- package/src/clipboard/useClipboard.ts +296 -0
- package/src/code/highlight.ts +56 -0
- package/src/code/languages.ts +236 -0
- package/src/code/scan/css.ts +164 -0
- package/src/code/scan/generic.ts +328 -0
- package/src/code/scan/html.ts +129 -0
- package/src/code/scan/markdown.ts +58 -0
- package/src/code/scan/shared.ts +81 -0
- package/src/code/scan/yaml.ts +34 -0
- package/src/code/types.ts +34 -0
- package/src/context/EditorContext.ts +9 -0
- package/src/context/EditorProvider.tsx +172 -0
- package/src/controllers/adapters.tsx +63 -0
- package/src/editing/BlockSelectionController.ts +27 -0
- package/src/editing/CaretController.ts +27 -0
- package/src/editing/EditorView.ts +108 -0
- package/src/editing/ViewportController.ts +63 -0
- package/src/editing/beforeInput.ts +276 -0
- package/src/editing/deleteAtBoundary.ts +16 -0
- package/src/editing/inputActions.ts +109 -0
- package/src/editing/renderCode.ts +69 -0
- package/src/editing/renderInline.ts +144 -0
- package/src/editing/selection.ts +262 -0
- package/src/editing/useEditorInput.ts +223 -0
- package/src/editing/useSelectionState.ts +46 -0
- package/src/formula/FormulaController.ts +51 -0
- package/src/formula/FormulaPalette.tsx +79 -0
- package/src/formula/FormulaPopover.tsx +181 -0
- package/src/formula/LazyFormulaPalette.tsx +42 -0
- package/src/formula/actions.ts +138 -0
- package/src/formula/snippet.ts +70 -0
- package/src/formula/symbols.ts +264 -0
- package/src/hooks/useBlock.ts +21 -0
- package/src/hooks/useBlockController.ts +30 -0
- package/src/hooks/useChildren.ts +16 -0
- package/src/hooks/useDocument.ts +16 -0
- package/src/hooks/useEditor.ts +13 -0
- package/src/hooks/useEditorView.ts +13 -0
- package/src/hooks/useHistory.ts +46 -0
- package/src/hooks/useReadOnly.ts +21 -0
- package/src/hooks/useT.ts +10 -0
- package/src/i18n/en.ts +182 -0
- package/src/i18n/ru.ts +174 -0
- package/src/i18n/translate.ts +77 -0
- package/src/icons/ICONS_LICENSE.md +50 -0
- package/src/icons/Icon.tsx +41 -0
- package/src/icons/lucide.ts +234 -0
- package/src/images/ImageError.ts +39 -0
- package/src/images/UploadTracker.ts +49 -0
- package/src/images/insertImages.ts +142 -0
- package/src/images/types.ts +22 -0
- package/src/images/upload.ts +62 -0
- package/src/index.ts +102 -0
- package/src/keymap/defaultKeymap.ts +189 -0
- package/src/keymap/keymap.ts +42 -0
- package/src/keymap/shortcut.ts +105 -0
- package/src/keymap/types.ts +40 -0
- package/src/math/FormulaView.tsx +62 -0
- package/src/math/mathCss.ts +4 -0
- package/src/math/renderer.ts +198 -0
- package/src/registry/defaultBlockComponents.ts +26 -0
- package/src/registry/resolveBlockComponent.ts +14 -0
- package/src/renderer/BlockRenderer.tsx +63 -0
- package/src/renderer/DocumentRenderer.tsx +33 -0
- package/src/renderer/DocumentView.tsx +94 -0
- package/src/renderer/virtual/VirtualChildList.tsx +355 -0
- package/src/renderer/virtual/estimate.ts +62 -0
- package/src/renderer/virtual/virtualRenderer.tsx +35 -0
- package/src/slash/DefaultSlashMenu.tsx +119 -0
- package/src/slash/SlashMenuController.ts +66 -0
- package/src/slash/SlashMenuHost.tsx +48 -0
- package/src/slash/applySlashItem.ts +57 -0
- package/src/slash/defaultSlashItems.tsx +186 -0
- package/src/slash/filter.ts +29 -0
- package/src/slash/items.ts +30 -0
- package/src/slash/syncSlashMenu.ts +67 -0
- package/src/slash/types.ts +21 -0
- package/src/toolbar/DefaultToolbar.tsx +281 -0
- package/src/toolbar/ToolbarController.ts +100 -0
- package/src/toolbar/ToolbarHost.tsx +81 -0
- package/src/toolbar/formatting.ts +75 -0
- package/src/toolbar/rich.ts +58 -0
- package/src/toolbar/syncToolbar.ts +39 -0
- package/src/toolbar/types.ts +21 -0
- package/src/ui/EditorUIContext.ts +26 -0
- package/src/ui/presentation.ts +24 -0
- package/src/ui/types.ts +70 -0
- package/README.md +0 -3
package/src/index.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export * from './context/EditorContext';
|
|
2
|
+
export * from './context/EditorProvider';
|
|
3
|
+
export { definePresentation } from './ui/presentation';
|
|
4
|
+
export type { EditorPresentation } from './ui/presentation';
|
|
5
|
+
|
|
6
|
+
export * from './hooks/useEditor';
|
|
7
|
+
export * from './hooks/useEditorView';
|
|
8
|
+
export * from './hooks/useDocument';
|
|
9
|
+
export * from './hooks/useBlock';
|
|
10
|
+
export * from './hooks/useChildren';
|
|
11
|
+
export * from './hooks/useHistory';
|
|
12
|
+
export * from './hooks/useReadOnly';
|
|
13
|
+
export * from './hooks/useBlockController';
|
|
14
|
+
export { defineBlock } from './blocks/defineBlock';
|
|
15
|
+
export type { ReactBlockDefinition, CustomBlockViewProps } from './blocks/defineBlock';
|
|
16
|
+
export { htmlToFragment, embeddedFragment } from './clipboard/htmlToFragment';
|
|
17
|
+
export type { ImageOptions } from './images/types';
|
|
18
|
+
export { uploadImage, normalizeImageUrl } from './images/upload';
|
|
19
|
+
|
|
20
|
+
export * from './blocks/BlockProps';
|
|
21
|
+
export * from './blocks/BlockChildren';
|
|
22
|
+
export * from './blocks/UnknownBlockView';
|
|
23
|
+
export * from './blocks/Editable';
|
|
24
|
+
export * from './blocks/PageBlock';
|
|
25
|
+
export * from './blocks/NoteBlock';
|
|
26
|
+
export * from './blocks/ParagraphBlock';
|
|
27
|
+
export * from './blocks/HeadingBlock';
|
|
28
|
+
export * from './blocks/ListBlock';
|
|
29
|
+
export * from './blocks/QuoteBlock';
|
|
30
|
+
export * from './blocks/CodeBlock';
|
|
31
|
+
export * from './blocks/DividerBlock';
|
|
32
|
+
export * from './blocks/CalloutBlock';
|
|
33
|
+
export * from './blocks/ImageBlock';
|
|
34
|
+
|
|
35
|
+
export * from './renderer/BlockRenderer';
|
|
36
|
+
export * from './renderer/DocumentView';
|
|
37
|
+
export { fullRenderer } from './renderer/DocumentRenderer';
|
|
38
|
+
export type { DocumentRenderer, ChildListProps } from './renderer/DocumentRenderer';
|
|
39
|
+
export { virtualRenderer } from './renderer/virtual/virtualRenderer';
|
|
40
|
+
export type { VirtualRendererOptions } from './renderer/virtual/virtualRenderer';
|
|
41
|
+
export { estimateBlockHeight } from './renderer/virtual/estimate';
|
|
42
|
+
export type { BlockHeightEstimate } from './renderer/virtual/estimate';
|
|
43
|
+
|
|
44
|
+
export { EditorView } from './editing/EditorView';
|
|
45
|
+
|
|
46
|
+
export type {
|
|
47
|
+
BlockViewProps,
|
|
48
|
+
BlockComponent,
|
|
49
|
+
BlockComponentRegistry,
|
|
50
|
+
EditorSlots,
|
|
51
|
+
EditorUIConfig,
|
|
52
|
+
UnknownBlockViewProps,
|
|
53
|
+
} from './ui/types';
|
|
54
|
+
export { useEditorUI } from './ui/EditorUIContext';
|
|
55
|
+
export { defaultBlockComponents } from './registry/defaultBlockComponents';
|
|
56
|
+
export { resolveBlockComponent } from './registry/resolveBlockComponent';
|
|
57
|
+
|
|
58
|
+
export { highlightCode, normalizeLanguage, HIGHLIGHT_LIMIT } from './code/highlight';
|
|
59
|
+
export type { CodeHighlighter, HighlightToken, HighlightTokenType } from './code/types';
|
|
60
|
+
|
|
61
|
+
export { temmlRenderer, mountFormula, ensureMathStyles } from './math/renderer';
|
|
62
|
+
export type { FormulaRenderer, FormulaRenderOptions, FormulaState } from './math/renderer';
|
|
63
|
+
export { FormulaView } from './math/FormulaView';
|
|
64
|
+
|
|
65
|
+
export { useT, useLocale } from './hooks/useT';
|
|
66
|
+
export { registerLocale, getLocales, resolveMessages, createTranslator } from './i18n/translate';
|
|
67
|
+
export type { TFunction, MessageParams } from './i18n/translate';
|
|
68
|
+
export { en } from './i18n/en';
|
|
69
|
+
export type { MessageKey, Messages, PartialMessages } from './i18n/en';
|
|
70
|
+
export { ru } from './i18n/ru';
|
|
71
|
+
|
|
72
|
+
export { Icon } from './icons/Icon';
|
|
73
|
+
export type { IconName, IconProps } from './icons/Icon';
|
|
74
|
+
|
|
75
|
+
export type { KeyBinding, KeymapContext } from './keymap/types';
|
|
76
|
+
export { defaultKeymap } from './keymap/defaultKeymap';
|
|
77
|
+
export { resolveKeymap, unbind } from './keymap/keymap';
|
|
78
|
+
export { matchesShortcut } from './keymap/shortcut';
|
|
79
|
+
|
|
80
|
+
export type { SlashMenuItem, SlashMenuProps } from './slash/types';
|
|
81
|
+
export { defaultSlashItems } from './slash/defaultSlashItems';
|
|
82
|
+
export { filterSlashItems } from './slash/filter';
|
|
83
|
+
export { DefaultSlashMenu } from './slash/DefaultSlashMenu';
|
|
84
|
+
export type { ToolbarProps, InlineFormat } from './toolbar/types';
|
|
85
|
+
export { DefaultToolbar } from './toolbar/DefaultToolbar';
|
|
86
|
+
export type {
|
|
87
|
+
BlockHandleProps,
|
|
88
|
+
BlockMenuProps,
|
|
89
|
+
BlockMenuAction,
|
|
90
|
+
DropIndicatorProps,
|
|
91
|
+
GripProps,
|
|
92
|
+
} from './block-handle/types';
|
|
93
|
+
export { DefaultBlockHandle } from './block-handle/DefaultBlockHandle';
|
|
94
|
+
export { DefaultDropIndicator } from './block-handle/DropIndicatorHost';
|
|
95
|
+
export { useBlockDrag, useBlockDragState } from './block-handle/useBlockDrag';
|
|
96
|
+
export type { BlockDragApi, BlockDropTarget, BlockRects } from './block-handle/blockDrag';
|
|
97
|
+
export type {
|
|
98
|
+
DragState as BlockDragState,
|
|
99
|
+
DropIndicatorRect,
|
|
100
|
+
DropPosition,
|
|
101
|
+
} from './block-handle/BlockHandleController';
|
|
102
|
+
export { DefaultBlockMenu } from './block-handle/DefaultBlockMenu';
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { CaretResult } from '@inkbrick/engine';
|
|
2
|
+
import type { Alignment } from '@inkbrick/document';
|
|
3
|
+
|
|
4
|
+
import { applySlashItem } from '../slash/applySlashItem';
|
|
5
|
+
import { visibleSlashItems } from '../slash/items';
|
|
6
|
+
import { isFormattable } from '../toolbar/formatting';
|
|
7
|
+
import { syncToolbar } from '../toolbar/syncToolbar';
|
|
8
|
+
import { selectAllText } from '../editing/selection';
|
|
9
|
+
import { openInlineFormula } from '../formula/actions';
|
|
10
|
+
import { setAlignInSelection } from '../toolbar/rich';
|
|
11
|
+
import { deleteForwardAtEnd } from '../editing/deleteAtBoundary';
|
|
12
|
+
import type { KeyBinding, KeymapContext } from './types';
|
|
13
|
+
|
|
14
|
+
const caretAtEdge =
|
|
15
|
+
(edge: 'start' | 'end') =>
|
|
16
|
+
({ editor, event, selection }: KeymapContext): boolean => {
|
|
17
|
+
const current = selection();
|
|
18
|
+
|
|
19
|
+
if (event.isComposing || !current?.collapsed) return false;
|
|
20
|
+
if (edge === 'start') return current.from.offset === 0;
|
|
21
|
+
|
|
22
|
+
const block = editor.store.getBlock(current.from.blockId);
|
|
23
|
+
|
|
24
|
+
return !!block && current.from.offset === editor.engine.inline.length(block.content);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const slashOpen = ({ view }: KeymapContext) => {
|
|
28
|
+
const state = view.slash.getState();
|
|
29
|
+
|
|
30
|
+
return state.open && state.blockId !== null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function visibleItems({ editor, view, slashItems }: KeymapContext) {
|
|
34
|
+
const state = view.slash.getState();
|
|
35
|
+
|
|
36
|
+
return visibleSlashItems(editor, slashItems, state.blockId!, state.query);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const slashMove = (direction: 1 | -1) => (context: KeymapContext) => {
|
|
40
|
+
const visible = visibleItems(context);
|
|
41
|
+
|
|
42
|
+
if (visible.length === 0) return false;
|
|
43
|
+
|
|
44
|
+
context.view.slash.move(direction, visible.length);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function indent(context: KeymapContext) {
|
|
48
|
+
const { editor, event } = context;
|
|
49
|
+
const selection = context.selection();
|
|
50
|
+
|
|
51
|
+
if (!selection || selection.from.blockId !== selection.to.blockId) return;
|
|
52
|
+
|
|
53
|
+
const block = editor.store.getBlock(selection.from.blockId);
|
|
54
|
+
|
|
55
|
+
if (!block) return;
|
|
56
|
+
|
|
57
|
+
if (block.flavour === 'block:code') {
|
|
58
|
+
if (!event.shiftKey) context.run(() => context.replaceSelection(selection, ' '));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
context.run(() => {
|
|
63
|
+
context.exec({
|
|
64
|
+
type: event.shiftKey ? 'outdentBlock' : 'indentBlock',
|
|
65
|
+
payload: { blockId: block.id },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { blockId: block.id, offset: selection.from.offset };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const toggle = (format: string) => (context: KeymapContext) => context.toggleFormat(format);
|
|
73
|
+
|
|
74
|
+
const align = (value: Alignment) => (context: KeymapContext) => {
|
|
75
|
+
const selection = context.selection();
|
|
76
|
+
|
|
77
|
+
if (!selection || !setAlignInSelection(context.editor, context.view, selection, value))
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Стандартные привязки редактора, в порядке проверки. Свои привязки передаются в `<EditorProvider keymap>`:
|
|
83
|
+
* тот же `id` заменяет стандартную, новый `id` проверяется раньше стандартных.
|
|
84
|
+
*/
|
|
85
|
+
export const defaultKeymap: readonly KeyBinding[] = [
|
|
86
|
+
{
|
|
87
|
+
id: 'slash.next',
|
|
88
|
+
keys: ['ArrowDown', 'Shift-ArrowDown'],
|
|
89
|
+
when: slashOpen,
|
|
90
|
+
run: slashMove(1),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'slash.previous',
|
|
94
|
+
keys: ['ArrowUp', 'Shift-ArrowUp'],
|
|
95
|
+
when: slashOpen,
|
|
96
|
+
run: slashMove(-1),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'slash.accept',
|
|
100
|
+
keys: ['Enter', 'Shift-Enter', 'Tab', 'Shift-Tab'],
|
|
101
|
+
when: slashOpen,
|
|
102
|
+
run: (context) => {
|
|
103
|
+
const state = context.view.slash.getState();
|
|
104
|
+
const visible = visibleItems(context);
|
|
105
|
+
const item = visible[Math.min(state.activeIndex, visible.length - 1)];
|
|
106
|
+
|
|
107
|
+
if (item) {
|
|
108
|
+
applySlashItem(context.editor, context.view, item);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Подходящих пунктов нет: меню закрывается, клавиша работает как обычно (например, Tab-отступ).
|
|
113
|
+
context.view.slash.close();
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'slash.close',
|
|
120
|
+
keys: ['Escape', 'Shift-Escape'],
|
|
121
|
+
when: slashOpen,
|
|
122
|
+
run: ({ view }) => view.slash.close(),
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Выделить всё: нативное поведение ненадёжно для документа, начинающегося с нередактируемого блока.
|
|
126
|
+
{ id: 'selectAll', keys: 'Mod-a', run: ({ root }) => selectAllText(root) },
|
|
127
|
+
{ id: 'undo', keys: 'Mod-z', run: (context) => context.undo() },
|
|
128
|
+
{ id: 'redo', keys: ['Mod-Shift-z', 'Mod-y'], run: (context) => context.redo() },
|
|
129
|
+
{
|
|
130
|
+
id: 'link',
|
|
131
|
+
keys: 'Mod-k',
|
|
132
|
+
run: (context) => {
|
|
133
|
+
const selection = context.selection();
|
|
134
|
+
|
|
135
|
+
if (!selection || selection.collapsed || !isFormattable(context.editor, selection)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
syncToolbar(context.editor, context.view);
|
|
140
|
+
context.view.toolbar.openLink();
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
{ id: 'format.bold', keys: 'Mod-b', run: toggle('bold') },
|
|
145
|
+
{ id: 'format.italic', keys: 'Mod-i', run: toggle('italic') },
|
|
146
|
+
{ id: 'format.underline', keys: 'Mod-u', run: toggle('underline') },
|
|
147
|
+
{ id: 'format.code', keys: 'Mod-e', run: toggle('code') },
|
|
148
|
+
{ id: 'format.strike', keys: 'Mod-Shift-x', run: toggle('strike') },
|
|
149
|
+
|
|
150
|
+
{ id: 'align.left', keys: 'Mod-Shift-l', run: align('left') },
|
|
151
|
+
{ id: 'align.center', keys: 'Mod-Shift-e', run: align('center') },
|
|
152
|
+
{ id: 'align.right', keys: 'Mod-Shift-r', run: align('right') },
|
|
153
|
+
{ id: 'align.justify', keys: 'Mod-Shift-j', run: align('justify') },
|
|
154
|
+
|
|
155
|
+
{
|
|
156
|
+
id: 'formula.inline',
|
|
157
|
+
keys: 'Mod-Shift-m',
|
|
158
|
+
run: ({ editor, view }) => openInlineFormula(editor, view),
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
{ id: 'indent', keys: ['Tab', 'Shift-Tab'], run: indent },
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* Backspace в начале и Delete в конце блока обрабатываются уже на keydown: Safari не присылает beforeinput,
|
|
165
|
+
* если сам считает, что удалять нечего (пустой блок, соседний блок без текста), и клавиша бы не сработала.
|
|
166
|
+
*/
|
|
167
|
+
{
|
|
168
|
+
id: 'delete.blockStart',
|
|
169
|
+
keys: 'Backspace',
|
|
170
|
+
when: caretAtEdge('start'),
|
|
171
|
+
run: (context) => {
|
|
172
|
+
const { blockId } = context.selection()!.from;
|
|
173
|
+
|
|
174
|
+
context.run(() =>
|
|
175
|
+
context.exec<CaretResult>({ type: 'deleteBackward', payload: { blockId, offset: 0 } }),
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'delete.blockEnd',
|
|
181
|
+
keys: 'Delete',
|
|
182
|
+
when: caretAtEdge('end'),
|
|
183
|
+
run: (context) => {
|
|
184
|
+
const { blockId } = context.selection()!.from;
|
|
185
|
+
|
|
186
|
+
context.run(() => deleteForwardAtEnd(context.editor, blockId));
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defaultKeymap } from './defaultKeymap';
|
|
2
|
+
import { matchesShortcut } from './shortcut';
|
|
3
|
+
import type { KeyBinding, KeymapContext } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Собирает итоговый список привязок: свои привязки с `id` стандартной занимают её место, остальные идут
|
|
7
|
+
* перед стандартными (проверяются первыми).
|
|
8
|
+
*/
|
|
9
|
+
export function resolveKeymap(custom: readonly KeyBinding[] = []): readonly KeyBinding[] {
|
|
10
|
+
if (custom.length === 0) return defaultKeymap;
|
|
11
|
+
|
|
12
|
+
const overrides = new Map(custom.map((binding) => [binding.id, binding]));
|
|
13
|
+
const defaultIds = new Set(defaultKeymap.map((binding) => binding.id));
|
|
14
|
+
|
|
15
|
+
return [
|
|
16
|
+
...custom.filter((binding) => !defaultIds.has(binding.id)),
|
|
17
|
+
...defaultKeymap.map((binding) => overrides.get(binding.id) ?? binding),
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function unbind(id: string): KeyBinding {
|
|
22
|
+
return { id, keys: [], run: () => false };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function handleKeyDown(bindings: readonly KeyBinding[], context: KeymapContext): boolean {
|
|
26
|
+
const { event } = context;
|
|
27
|
+
|
|
28
|
+
for (const binding of bindings) {
|
|
29
|
+
const keys = typeof binding.keys === 'string' ? [binding.keys] : binding.keys;
|
|
30
|
+
|
|
31
|
+
if (!keys.some((spec) => matchesShortcut(event, spec))) continue;
|
|
32
|
+
if (binding.when && !binding.when(context)) continue;
|
|
33
|
+
|
|
34
|
+
if (binding.run(context) === false) continue;
|
|
35
|
+
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
interface Shortcut {
|
|
2
|
+
mod: boolean;
|
|
3
|
+
ctrl: boolean;
|
|
4
|
+
meta: boolean;
|
|
5
|
+
alt: boolean;
|
|
6
|
+
shift: boolean;
|
|
7
|
+
key: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MODIFIER = /^(mod|ctrl|control|meta|cmd|alt|option|shift)-/i;
|
|
11
|
+
|
|
12
|
+
const KEY_ALIASES: Record<string, string> = {
|
|
13
|
+
esc: 'escape',
|
|
14
|
+
space: ' ',
|
|
15
|
+
up: 'arrowup',
|
|
16
|
+
down: 'arrowdown',
|
|
17
|
+
left: 'arrowleft',
|
|
18
|
+
right: 'arrowright',
|
|
19
|
+
del: 'delete',
|
|
20
|
+
return: 'enter',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const cache = new Map<string, Shortcut>();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Разбирает запись вида `Mod-Shift-z`, `Tab`, `Shift-ArrowDown`: модификаторы (`Mod`, `Ctrl`, `Meta`/`Cmd`,
|
|
27
|
+
* `Alt`/`Option`, `Shift`) через дефис, затем клавиша (как `KeyboardEvent.key`, регистр не важен).
|
|
28
|
+
*/
|
|
29
|
+
function parse(spec: string): Shortcut {
|
|
30
|
+
const cached = cache.get(spec);
|
|
31
|
+
|
|
32
|
+
if (cached) return cached;
|
|
33
|
+
|
|
34
|
+
const shortcut: Shortcut = {
|
|
35
|
+
mod: false,
|
|
36
|
+
ctrl: false,
|
|
37
|
+
meta: false,
|
|
38
|
+
alt: false,
|
|
39
|
+
shift: false,
|
|
40
|
+
key: '',
|
|
41
|
+
};
|
|
42
|
+
let rest = spec.trim();
|
|
43
|
+
|
|
44
|
+
for (let match = MODIFIER.exec(rest); match; match = MODIFIER.exec(rest)) {
|
|
45
|
+
switch (match[1].toLowerCase()) {
|
|
46
|
+
case 'mod':
|
|
47
|
+
shortcut.mod = true;
|
|
48
|
+
break;
|
|
49
|
+
case 'ctrl':
|
|
50
|
+
case 'control':
|
|
51
|
+
shortcut.ctrl = true;
|
|
52
|
+
break;
|
|
53
|
+
case 'meta':
|
|
54
|
+
case 'cmd':
|
|
55
|
+
shortcut.meta = true;
|
|
56
|
+
break;
|
|
57
|
+
case 'alt':
|
|
58
|
+
case 'option':
|
|
59
|
+
shortcut.alt = true;
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
shortcut.shift = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
rest = rest.slice(match[0].length);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const key = rest.toLowerCase();
|
|
69
|
+
|
|
70
|
+
if (key === '') throw new Error(`Invalid shortcut "${spec}": no key`);
|
|
71
|
+
|
|
72
|
+
shortcut.key = KEY_ALIASES[key] ?? key;
|
|
73
|
+
cache.set(spec, shortcut);
|
|
74
|
+
|
|
75
|
+
return shortcut;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function matchesKey(event: KeyboardEvent, key: string): boolean {
|
|
79
|
+
const pressed = event.key.toLowerCase();
|
|
80
|
+
|
|
81
|
+
if (pressed === key) return true;
|
|
82
|
+
|
|
83
|
+
// Ctrl+B на русской раскладке даёт key «и»: для латинских букв сверяем физическую клавишу.
|
|
84
|
+
return (
|
|
85
|
+
/^[a-z]$/.test(key) && !/^[a-z]$/.test(pressed) && event.code === `Key${key.toUpperCase()}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Нажата ли ровно эта комбинация: Shift и Alt должны совпадать точно; Ctrl/Meta, если не упомянуты,
|
|
91
|
+
* не должны быть нажаты (`Mod` принимает любую из двух).
|
|
92
|
+
*/
|
|
93
|
+
export function matchesShortcut(event: KeyboardEvent, spec: string): boolean {
|
|
94
|
+
const shortcut = parse(spec);
|
|
95
|
+
|
|
96
|
+
if (event.shiftKey !== shortcut.shift || event.altKey !== shortcut.alt) return false;
|
|
97
|
+
|
|
98
|
+
if (shortcut.mod) {
|
|
99
|
+
if (!(event.ctrlKey || event.metaKey)) return false;
|
|
100
|
+
} else if (event.ctrlKey !== shortcut.ctrl || event.metaKey !== shortcut.meta) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return matchesKey(event, shortcut.key);
|
|
105
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CaretResult, Editor, EditorIntent } from '@inkbrick/engine';
|
|
2
|
+
|
|
3
|
+
import type { EditorView } from '../editing/EditorView';
|
|
4
|
+
import type { DocSelection } from '../editing/selection';
|
|
5
|
+
import type { SlashMenuItem } from '../slash/types';
|
|
6
|
+
|
|
7
|
+
export interface KeymapContext {
|
|
8
|
+
readonly editor: Editor;
|
|
9
|
+
readonly view: EditorView;
|
|
10
|
+
readonly root: HTMLElement;
|
|
11
|
+
readonly event: KeyboardEvent;
|
|
12
|
+
readonly slashItems: readonly SlashMenuItem[];
|
|
13
|
+
|
|
14
|
+
selection(): DocSelection | null;
|
|
15
|
+
exec<T = unknown>(intent: EditorIntent): T | undefined;
|
|
16
|
+
run(action: () => CaretResult | undefined): void;
|
|
17
|
+
replaceSelection(selection: DocSelection, text: string): CaretResult | undefined;
|
|
18
|
+
undo(): void;
|
|
19
|
+
redo(): void;
|
|
20
|
+
toggleFormat(format: string): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KeyBinding {
|
|
24
|
+
/**
|
|
25
|
+
* Уникальное имя. Привязка с тем же `id`, что у стандартной, заменяет её (на её месте в порядке проверки);
|
|
26
|
+
* остальные проверяются раньше стандартных.
|
|
27
|
+
*/
|
|
28
|
+
readonly id: string;
|
|
29
|
+
/**
|
|
30
|
+
* Комбинации: `'Mod-b'`, `'Mod-Shift-z'`, `'Tab'`, `'Shift-Tab'`, `'ArrowDown'`. `Mod` — Ctrl или Cmd.
|
|
31
|
+
* Shift и Alt учитываются точно (`'Tab'` не срабатывает на Shift+Tab). Пустой массив отключает привязку.
|
|
32
|
+
*/
|
|
33
|
+
readonly keys: string | readonly string[];
|
|
34
|
+
readonly when?: (context: KeymapContext) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Обработчик. `false` — «не моё»: событие идёт дальше по списку привязок и в браузер.
|
|
37
|
+
* Любой другой результат считается обработкой: `preventDefault` вызывается автоматически.
|
|
38
|
+
*/
|
|
39
|
+
readonly run: (context: KeymapContext) => boolean | void;
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type ElementType } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useEditorUI } from '../ui/EditorUIContext';
|
|
4
|
+
import { ensureMathStyles, mountFormula } from './renderer';
|
|
5
|
+
|
|
6
|
+
export interface FormulaViewProps {
|
|
7
|
+
readonly latex: string;
|
|
8
|
+
readonly display?: boolean;
|
|
9
|
+
readonly showError?: boolean;
|
|
10
|
+
readonly onSettled?: (state: 'ready' | 'error', message?: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Формула, нарисованная выбранным в `EditorProvider` подсчётчиком (по умолчанию Temml → MathML). Пока считается и при
|
|
15
|
+
* ошибке виден исходный код. Атрибуты: `data-inkbrick-part="formula-render"`, `data-state="empty|source|loading|ready|error"`.
|
|
16
|
+
*/
|
|
17
|
+
export function FormulaView({
|
|
18
|
+
latex,
|
|
19
|
+
display = false,
|
|
20
|
+
showError = false,
|
|
21
|
+
onSettled,
|
|
22
|
+
}: FormulaViewProps) {
|
|
23
|
+
const { formulaRenderer } = useEditorUI();
|
|
24
|
+
const ref = useRef<HTMLElement | null>(null);
|
|
25
|
+
const [error, setError] = useState<string | null>(null);
|
|
26
|
+
const Tag: ElementType = display ? 'div' : 'span';
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const target = ref.current;
|
|
30
|
+
|
|
31
|
+
if (!target) return;
|
|
32
|
+
|
|
33
|
+
ensureMathStyles(globalThis.document);
|
|
34
|
+
setError(null);
|
|
35
|
+
|
|
36
|
+
return mountFormula(target, latex, {
|
|
37
|
+
display,
|
|
38
|
+
renderer: formulaRenderer,
|
|
39
|
+
onSettled: (state, message) => {
|
|
40
|
+
setError(state === 'error' ? (message ?? '') : null);
|
|
41
|
+
onSettled?.(state, message);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}, [latex, display, formulaRenderer, onSettled]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
<Tag
|
|
49
|
+
ref={(node: HTMLElement | null) => {
|
|
50
|
+
ref.current = node;
|
|
51
|
+
}}
|
|
52
|
+
data-inkbrick-part="formula-render"
|
|
53
|
+
data-display={display || undefined}
|
|
54
|
+
/>
|
|
55
|
+
{showError && error !== null && (
|
|
56
|
+
<span data-inkbrick-part="formula-error" role="alert">
|
|
57
|
+
{error}
|
|
58
|
+
</span>
|
|
59
|
+
)}
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Стили MathML-вывода Temml (https://temml.org, лицензия MIT, © Ron Kok) — файл dist/Temml-Local.css без @font-face.
|
|
2
|
+
// Файл сгенерирован из пакета temml; не правьте вручную. Лицензия — в `LICENSE` пакета temml.
|
|
3
|
+
export const MATH_CSS: string =
|
|
4
|
+
"math {\n font-family: \"Cambria Math\", 'STIXTwoMath-Regular', 'NotoSansMath-Regular', math;\n font-style: normal;\n font-weight: normal;\n line-height: normal;\n font-size-adjust: none;\n text-indent: 0;\n text-transform: none;\n letter-spacing: normal;\n word-wrap: normal;\n direction: ltr;\n /* Prevent Firefox from omitting the dot on i or j. */\n font-feature-settings: \"dtls\" off;\n}\n\nmath * {\n border-color: currentColor;\n}\n\n/* display: block is necessary in Firefox and Safari.\n * Not in Chromium, which recognizes display: \"block math\" written inline. */\n math.tml-display {\n display: block;\n width: 100%;\n}\n\n*.mathcal {\n /* NotoSans */\n font-feature-settings: 'ss01';\n}\n\nmath .mathscr {\n font-family: \"Temml\";\n}\n\nmo.tml-prime {\n font-family: Temml;\n}\n\n/* Cramped superscripts in WebKit */\nmfrac > :nth-child(2),\nmsqrt,\nmover > :first-child {\n math-shift: compact\n}\n\n.menclose {\n display: inline-block;\n position: relative;\n padding: 0.5ex 0ex;\n}\n.tml-cancelto {\n display: inline-block;\n position: absolute;\n top: 0;\n left: 0;\n padding: 0.5ex 0ex;\n background-color: currentColor;\n /* Use the SVG as an alpha mask (painted by background-color) */\n -webkit-mask-image: url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='black' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%25' y1='100%25' stroke='black' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>\");\n mask-image: url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='black' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%25' y1='100%25' stroke='black' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>\");\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n -webkit-mask-size: 100% 100%;\n mask-size: 100% 100%;\n -webkit-mask-position: 0 0;\n mask-position: 0 0;\n}\n\n@supports (-moz-appearance: none) {\n /* \\vec w/o italic correction for Firefox */\n .tml-vec {\n transform: scale(0.75)\n }\n /* Fix \\cancelto in Firefox */\n .ff-narrow {\n width: 0em;\n }\n .ff-nudge-left {\n margin-left: -0.2em;\n }\n /* Squash hidden mrows in \\bordermatrix. Messes with centering, but necessary to squash. */\n .ff-squash mtd {\n display: block;\n height: 0;\n }\n}\n\n@supports (not (-moz-appearance: none)) {\n /* Chromium and WebKit */\n /* prime vertical alignment */\n mo.tml-prime {\n font-family: Temml;\n }\n /* Italic correction on superscripts */\n .tml-sml-pad {\n padding-left: 0.05em;\n }\n .tml-med-pad {\n padding-left: 0.10em;\n }\n .tml-lrg-pad {\n padding-left: 0.15em;\n }\n}\n\n@supports (-webkit-backdrop-filter: blur(1px)) {\n /* WebKit vertical & italic correction on accents */\n .wbk-acc {\n /* lower by x-height distance */\n transform: translate(0em, 0.431em);\n }\n .wbk-sml {\n transform: translate(0.07em, 0);\n }\n .wbk-sml-acc {\n transform: translate(0.07em, 0.431em);\n }\n .wbk-sml-vec {\n transform: scale(0.75) translate(0.07em, 0);\n }\n .wbk-med {\n transform: translate(0.14em, 0);\n }\n .wbk-med-acc {\n transform: translate(0.14em, 0.431em);\n }\n .wbk-med-vec {\n transform: scale(0.75) translate(0.14em, 0);\n }\n .wbk-lrg {\n transform: translate(0.21em, 0);\n }\n .wbk-lrg-acc {\n transform: translate(0.21em, 0.431em);\n }\n .wbk-lrg-vec {\n transform: scale(0.75) translate(0.21em, 0);\n }\n}\n\n/* \\cancel & \\phase use background images. Get them to print. */\nmenclose {\n -webkit-print-color-adjust: exact; /* Chrome & Edge */\n print-color-adjust: exact;\n}\n\n/* Array cell justification in Firefox & WebKit */\n.tml-right {\n text-align: right;\n}\n.tml-left {\n text-align: left;\n}\n\n/* For CD labels that grow to the left in Firefox and WebKit */\n.tml-shift-left { margin-left:-200% }\n\n/* Styles for Chromium only */\n@supports (not (-webkit-backdrop-filter: blur(1px))) and (not (-moz-appearance: none)) {\n /* Italic correction on accents */\n .chr-sml {\n transform: translate(0.07em, 0)\n }\n .chr-sml-vec {\n transform: scale(0.75) translate(0.07em, 0)\n }\n .chr-med {\n transform: translate(0.14em, 0)\n }\n .chr-med-vec {\n transform: scale(0.75) translate(0.14em, 0)\n }\n .chr-lrg {\n transform: translate(0.21em, 0)\n }\n .chr-lrg-vec {\n transform: scale(0.75) translate(0.21em, 0)\n }\n\n /* For CD labels that grow to the left */\n .tml-shift-left { margin-left:-100% }\n\n /* MathML Core & Chromium do not support the MathML 3.0 element <menclose> attributes. */\n /* So use styles. */\n menclose {\n position: relative;\n padding: 0.5ex 0ex;\n }\n \n .tml-overline {\n padding: 0.1em 0 0 0;\n border-top: 0.065em solid;\n }\n\n .tml-underline {\n padding: 0 0 0.1em 0;\n border-bottom: 0.065em solid;\n }\n\n .tml-cancel {\n display: inline-block;\n position: absolute;\n left: 0.5px;\n bottom: 0;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n }\n .upstrike {\n clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);\n }\n .downstrike {\n clip-path: polygon(0em 0.05em, 0.05em 0em, 100% calc(100% - 0.05em), calc(100% - 0.05em) 100%);\n }\n .sout {\n clip-path: polygon(0em calc(55% + 0.0333em), 0em calc(55% - 0.0333em), 100% calc(55% - 0.0333em), 100% calc(55% + 0.0333em));\n }\n .tml-xcancel {\n clip-path: polygon(0.05em 0em, 0em 0.05em, calc(50% - 0.05em) 50%, 0em calc(100% - 0.05em), 0.05em 100%, 50% calc(50% + 0.05em), calc(100% - 0.05em) 100%, 100% calc(100% - 0.05em), calc(50% + 0.05em) 50%, 100% 0.05em, calc(100% - 0.05em) 0%, 50% calc(50% - 0.05em));\n }\n\n .longdiv-top {\n border-top: 0.067em solid;\n padding: 0.1em 0.2em 0.2em 0.433em;\n }\n .longdiv-arc {\n position: absolute;\n top: 0;\n bottom: 0.1em;\n left: -0.4em;\n width: 0.7em;\n border: 0.067em solid;\n transform: translateY(-0.067em);\n border-radius: 70%;\n clip-path: inset(0 0 0 0.4em);\n box-sizing: border-box;}\n .menclose {display: inline-block;\n text-align: left;\n position: relative;\n }\n \n .phasor-bottom {\n border-bottom: 0.067em solid;\n padding: 0.2em 0.2em 0.1em 0.6em;\n }\n .phasor-angle {\n display: inline-block;\n position: absolute;\n left: 0.5px;\n bottom: -0.04em;\n height: 100%;\n aspect-ratio: 0.5;\n background-color: currentColor;\n clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);\n }\n\n .tml-fbox {\n padding: 3pt;\n border: 1px solid;\n }\n\n .circle-pad {\n padding: 0.267em;\n }\n .textcircle {\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n left: 0;\n border: 0.067em solid;\n border-radius: 50%;\n }\n\n .actuarial {\n padding: 0.03889em 0.03889em 0 0.03889em;\n border-width: 0.08em 0.08em 0em 0em;\n border-style: solid;\n margin-right: 0.03889em;\n }\n\n /* Stretch \\widetilde */\n .tml-tilde-2 {\n transform: scale(2.0, 1.1)\n }\n .tml-tilde-3 {\n transform: scale(3.0, 1.1)\n }\n .tml-tilde-4 {\n transform: scale(4.0, 1.1)\n }\n /* Stretch \\widehat */\n .tml-hat-1 {\n transform: scale(1.9, 1.0)\n }\n .tml-hat-2 {\n transform: scale(3.9, 1.0)\n }\n .tml-hat-3 {\n transform: scale(5.8, 1.0)\n }\n .tml-hat-4 {\n transform: scale(7.8, 1.0)\n }\n \n /* set array cell justification */\n .tml-right {\n text-align: -webkit-right;\n }\n .tml-left {\n text-align: -webkit-left;\n }\n}\n\n.special-fraction {\n font-family: 'STIX TWO', 'Times New Roman', Times, Tinos, serif;\n}\n\n/* flex-wrap for line-breaking in Chromium */\nmath {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n}\nmath > mrow {\n padding: 0.5ex 0ex;\n}\n\n/* Default mtd top padding is 0.5ex per MathML-Core and user-agent CSS */\n/* We adjust for jot and small */\nmtable.tml-jot > mtr > mtd {\n padding-top: 0.7ex;\n padding-bottom: 0.7ex;\n}\nmtable.tml-small mtd {\n padding-top: 0.35ex;\n padding-bottom: 0.35ex;\n}\n\n/* Firefox */\n@-moz-document url-prefix() {\n /* Avoid flex-wrap */\n math { display: inline; }\n math > mrow { padding: 0 }\n /* Adjust Firefox spacing between array rows */\n mtd, mtable.tml-small mtd { padding-top: 0; padding-bottom: 0; }\n mtable.tml-jot > mtr > mtd { padding-top: 0.2ex; padding-bottom: 0.2ex; }\n}\n\n/* AMS environment auto-numbering via CSS counter. */\n.tml-eqn::before {\n counter-increment: tmlEqnNo;\n content: \"(\" counter(tmlEqnNo) \")\";\n}\n";
|