aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-10-07 15:03:16 -0400
committerbobzel <zzzman@gmail.com>2022-10-07 15:03:16 -0400
commit0e41cb323c486b23c70e53d14a563adaf0eeef9e (patch)
tree3cc353c16996c58709cdf5ec8ca8fbe88fe6bb89
parent69eb6d5514c36bd7065d2702cb2fb71e62c039ff (diff)
fixes for equations : :eq as option to ctrl-m inside a text box. added background for equations. fixed cursor focus issues.
-rw-r--r--src/client/views/DocumentDecorations.tsx30
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx142
-rw-r--r--src/client/views/nodes/EquationBox.tsx4
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx38
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx18
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts9
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts9
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts26
8 files changed, 177 insertions, 99 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1d6075606..8b8c32642 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -605,26 +605,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
render() {
const bounds = this.Bounds;
- const seldoc = SelectionManager.Views().slice(-1)[0];
- if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+ const seldocview = SelectionManager.Views().slice(-1)[0];
+ if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return null;
}
// hide the decorations if the parent chooses to hide it or if the document itself hides it
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup || this._isRounding || this._isRotating;
- const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
- const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
+ const hideResizers = seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating;
+ const hideTitle = seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
+ const hideDocumentButtonBar = seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
const hideOpenButton =
- seldoc.props.hideOpenButton ||
- seldoc.rootDoc.hideOpenButton ||
+ seldocview.props.hideOpenButton ||
+ seldocview.rootDoc.hideOpenButton ||
SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
this._isRounding ||
this._isRotating;
const hideDeleteButton =
this._isRounding ||
this._isRotating ||
- seldoc.props.hideDeleteButton ||
- seldoc.rootDoc.hideDeleteButton ||
+ seldocview.props.hideDeleteButton ||
+ seldocview.rootDoc.hideDeleteButton ||
SelectionManager.Views().some(docView => {
const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
@@ -678,15 +678,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
- const useRotation = true; // when do we want an object to not rotate?
- const rotation = NumCast(seldoc.rootDoc._jitterRotation);
+ const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate?
+ const rotation = NumCast(seldocview.rootDoc._jitterRotation);
const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
// Radius constants
- const useRounding = seldoc.ComponentView instanceof ImageBox || seldoc.ComponentView instanceof FormattedTextBox;
- const borderRadius = numberValue(StrCast(seldoc.rootDoc.borderRounding));
- const docMax = Math.min(NumCast(seldoc.rootDoc.width) / 2, NumCast(seldoc.rootDoc.height) / 2);
+ const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox;
+ const borderRadius = numberValue(StrCast(seldocview.rootDoc.borderRounding));
+ const docMax = Math.min(NumCast(seldocview.rootDoc.width) / 2, NumCast(seldocview.rootDoc.height) / 2);
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
const radiusHandle = (borderRadius / docMax) * maxDist;
const radiusHandleLocation = Math.min(radiusHandle, maxDist);
@@ -739,7 +739,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
<div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
+ {seldocview.props.renderDepth <= 1 || !seldocview.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
</>
)}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 5ea6d567a..d74da9748 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
-import { observer } from "mobx-react";
+import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
@@ -9,19 +9,20 @@ import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
-import "./ComparisonBox.scss";
+import './ComparisonBox.scss';
import { DocumentView, DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import React = require("react");
-
+import React = require('react');
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
- protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(ComparisonBox, fieldKey);
+ }
+ protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined;
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
- @observable _animating = "";
+ @observable _animating = '';
protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
this._disposers[disposerId]?.();
@@ -29,7 +30,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
// create disposers identified by disposerId to remove drag & drop listeners
this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc);
}
- }
+ };
@undoBatch
private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
@@ -40,88 +41,113 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.dataDoc[fieldKey] = droppedDocs[0];
}
}
- }
+ };
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
- e.button !== 2 && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => {
- // on click, animate slider movement to the targetWidth
- this._animating = "all 200ms";
- this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth();
- setTimeout(action(() => this._animating = ""), 200);
- }), false);
- }
+ e.button !== 2 &&
+ setupMoveUpEvents(
+ this,
+ e,
+ this.onPointerMove,
+ emptyFunction,
+ action(() => {
+ // on click, animate slider movement to the targetWidth
+ this._animating = 'all 200ms';
+ this.layoutDoc._clipWidth = (targetWidth * 100) / this.props.PanelWidth();
+ setTimeout(
+ action(() => (this._animating = '')),
+ 200
+ );
+ }),
+ false
+ );
+ };
@action
private onPointerMove = ({ movementX }: PointerEvent) => {
- const width = movementX * this.props.ScreenToLocalTransform().Scale + NumCast(this.layoutDoc._clipWidth) / 100 * this.props.PanelWidth();
+ const width = movementX * this.props.ScreenToLocalTransform().Scale + (NumCast(this.layoutDoc._clipWidth) / 100) * this.props.PanelWidth();
if (width && width > 5 && width < this.props.PanelWidth()) {
- this.layoutDoc._clipWidth = width * 100 / this.props.PanelWidth();
+ this.layoutDoc._clipWidth = (width * 100) / this.props.PanelWidth();
}
return false;
- }
+ };
@undoBatch
clearDoc = (e: React.MouseEvent, fieldKey: string) => {
e.stopPropagation; // prevent click event action (slider movement) in registerSliding
delete this.dataDoc[fieldKey];
- }
+ };
docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
- if (property === StyleProp.PointerEvents) return "none";
+ if (property === StyleProp.PointerEvents) return 'none';
return this.props.styleProvider?.(doc, props, property);
- }
+ };
render() {
- const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
+ const clipWidth = NumCast(this.layoutDoc._clipWidth) + '%';
const clearButton = (which: string) => {
- return <div className={`clear-button ${which}`}
- onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
- onClick={e => this.clearDoc(e, which)}>
- <FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
- </div>;
+ return (
+ <div
+ className={`clear-button ${which}`}
+ onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
+ onClick={e => this.clearDoc(e, which)}>
+ <FontAwesomeIcon className={`clear-button ${which}`} icon={'times'} size="sm" />
+ </div>
+ );
};
const displayDoc = (which: string) => {
const whichDoc = Cast(this.dataDoc[which], Doc, null);
// if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc;
- return whichDoc ? <>
- <DocumentView
- ref={(r) => {
- whichDoc !== targetDoc && r?.focus(whichDoc);
- }}
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- isContentActive={returnFalse}
- isDocumentActive={returnFalse}
- styleProvider={this.docStyleProvider}
- Document={targetDoc}
- DataDoc={undefined}
- hideLinkButton={true}
- pointerEvents={returnNone} />
- {clearButton(which)}
- </> : // placeholder image if doc is missing
+ return whichDoc ? (
+ <>
+ <DocumentView
+ ref={r => {
+ whichDoc !== targetDoc && r?.focus(whichDoc, {});
+ }}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+ isContentActive={returnFalse}
+ isDocumentActive={returnFalse}
+ styleProvider={this.docStyleProvider}
+ Document={targetDoc}
+ DataDoc={undefined}
+ hideLinkButton={true}
+ pointerEvents={returnNone}
+ />
+ {clearButton(which)}
+ </> // placeholder image if doc is missing
+ ) : (
<div className="placeholder">
- <FontAwesomeIcon className="upload-icon" icon={"cloud-upload-alt"} size="lg" />
- </div>;
+ <FontAwesomeIcon className="upload-icon" icon={'cloud-upload-alt'} size="lg" />
+ </div>
+ );
};
const displayBox = (which: string, index: number, cover: number) => {
- return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
- onPointerDown={e => this.registerSliding(e, cover)}
- ref={ele => this.createDropTarget(ele, which, index)} >
- {displayDoc(which)}
- </div>;
+ return (
+ <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
+ {displayDoc(which)}
+ </div>
+ );
};
return (
- <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
- {displayBox(this.fieldKey === "data" ? "compareBox-after" : `${this.fieldKey}2`, 1, this.props.PanelWidth() - 3)}
- <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
- {displayBox(this.fieldKey === "data" ? "compareBox-before" : `${this.fieldKey}1`, 0, 0)}
+ <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+ {displayBox(this.fieldKey === 'data' ? 'compareBox-after' : `${this.fieldKey}2`, 1, this.props.PanelWidth() - 3)}
+ <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
+ {displayBox(this.fieldKey === 'data' ? 'compareBox-before' : `${this.fieldKey}1`, 0, 0)}
</div>
- <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)`, cursor: NumCast(this.layoutDoc._clipWidth) < 5 ? "e-resize" : NumCast(this.layoutDoc._clipWidth) / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? "w-resize" : undefined }}
- onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ >
+ <div
+ className="slide-bar"
+ style={{
+ left: `calc(${clipWidth} - 0.5px)`,
+ cursor: NumCast(this.layoutDoc._clipWidth) < 5 ? 'e-resize' : NumCast(this.layoutDoc._clipWidth) / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? 'w-resize' : undefined,
+ }}
+ onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+ >
<div className="slide-handle" />
</div>
- </div >);
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 0bd30bce9..c279341cc 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -7,6 +7,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { Docs } from '../../documents/Documents';
+import { undoBatch } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
@@ -45,7 +46,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
{ fireImmediately: true }
);
}
- plot: any;
+
@action
keyPressed = (e: KeyboardEvent) => {
const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', ''));
@@ -76,6 +77,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc);
};
+ @undoBatch
onChange = (str: string) => {
this.dataDoc.text = str;
const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 15d0f88f6..e09155ac2 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -4,11 +4,14 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
+import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
import { Cast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { Docs } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
@@ -33,7 +36,7 @@ export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() {
componentDidMount() {
this.props.setContentView?.(this);
reaction(
- () => [DocListCast(this.dataDoc[this.fieldKey]).lastElement()?.text, this.layoutDoc.width, this.layoutDoc.height, this.dataDoc.xRange, this.dataDoc.yRange],
+ () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.dataDoc.xRange, this.dataDoc.yRange],
() => this.createGraph()
);
}
@@ -53,8 +56,9 @@ export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._plotEle = ele || this._plotEle;
const width = this.props.PanelWidth();
const height = this.props.PanelHeight();
- const fn = StrCast(DocListCast(this.dataDoc.data).lastElement()?.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)');
+ const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
try {
+ this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
this._plot = functionPlot({
target: '#' + this._plotEle.id,
width,
@@ -62,17 +66,34 @@ export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() {
xAxis: { domain: Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10]) },
yAxis: { domain: Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9]) },
grid: true,
- data: [
- {
- fn,
- // derivative: { fn: "2 * x", updateOnMouseMove: true }
- },
- ],
+ data: fns.map(fn => ({
+ fn,
+ // derivative: { fn: "2 * x", updateOnMouseMove: true }
+ })),
});
} catch (e) {
console.log(e);
}
};
+
+ @undoBatch
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.complete.docDragData?.droppedDocuments.length) {
+ e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
+ de.complete.docDragData.droppedDocuments.map(doc => Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc));
+ return false;
+ }
+ return false;
+ };
+
+ _dropDisposer: any;
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ this._dropDisposer?.();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);
+ }
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
+ };
@computed get theGraph() {
return <div id={`${this._plotId}`} ref={r => r && this.createGraph(r)} style={{ position: 'absolute', width: '100%', height: '100%' }} onPointerDown={e => e.stopPropagation()} />;
}
@@ -80,6 +101,7 @@ export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() {
TraceMobx();
return (
<div
+ ref={this.createDropTarget}
style={{
pointerEvents: !this.isContentActive() ? 'all' : undefined,
width: this.props.PanelWidth(),
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 98d611ca6..4895dcdc5 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,6 +1,7 @@
import EquationEditor from 'equation-editor-react';
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
+import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom';
import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
@@ -21,7 +22,7 @@ export class EquationView {
e.stopPropagation();
};
- ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} setEditor={this.setEditor} tbox={tbox} />, this.dom);
+ ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />, this.dom);
(this as any).dom = this.dom;
}
_editor: EquationEditor | undefined;
@@ -29,6 +30,9 @@ export class EquationView {
destroy() {
ReactDOM.unmountComponentAtNode(this.dom);
}
+ setSelection() {
+ this._editor?.mathField.focus();
+ }
selectNode() {
this._editor?.mathField.focus();
}
@@ -40,6 +44,7 @@ interface IEquationViewInternal {
tbox: FormattedTextBox;
width: number;
height: number;
+ getPos: () => number;
setEditor: (editor: EquationEditor | undefined) => void;
}
@@ -67,11 +72,22 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
return (
<div
className="equationView"
+ onKeyDown={e => {
+ if (e.key === 'Enter') {
+ this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new TextSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos() + 1))));
+ this.props.tbox.EditorView!.focus();
+ e.preventDefault();
+ }
+ e.stopPropagation();
+ }}
+ onKeyPress={e => e.stopPropagation()}
style={{
position: 'relative',
display: 'inline-block',
width: this.props.width,
height: this.props.height,
+ background: 'white',
+ borderRadius: '10%',
bottom: 3,
}}>
<EquationEditor
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 31552cf1b..34bfb9e6f 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -2,7 +2,7 @@ import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinU
import { redo, undo } from 'prosemirror-history';
import { Schema } from 'prosemirror-model';
import { splitListItem, wrapInList } from 'prosemirror-schema-list';
-import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc';
import { GetEffectiveAcl } from '../../../../fields/util';
@@ -143,7 +143,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
- bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }))));
+ bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ if (canEdit(state)) {
+ const tr = state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }));
+ dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))));
+ }
+ });
for (let i = 1; i <= 6; i++) {
bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 2eb62c38d..7ddd1a2c4 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -295,6 +295,15 @@ export class RichTextRules {
return state.tr;
}),
+ // create an inline equation node
+ // eq:<equation>>
+ new InputRule(new RegExp(/:eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => {
+ const fieldKey = 'math' + Utils.GenerateGuid();
+ this.TextBox.dataDoc[fieldKey] = match[1];
+ const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
+ return tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1)));
+ }),
+
// create an inline view of a document {{ <layoutKey> : <Doc> }}
// {{:Doc}} => show default view of document
// {{<layout>}} => show layout for this doc
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 5142b7da6..66d747bf7 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -157,6 +157,18 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
+ equation: {
+ inline: true,
+ attrs: {
+ fieldKey: { default: '' },
+ },
+ group: 'inline',
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+ return ['div', { ...node.attrs, ...attrs }];
+ },
+ },
+
// :: NodeSpec The text node.
text: {
group: 'inline',
@@ -260,20 +272,6 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
- equation: {
- inline: true,
- attrs: {
- fieldKey: { default: '' },
- },
- atom: true,
- group: 'inline',
- draggable: false,
- toDOM(node) {
- const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ['div', { ...node.attrs, ...attrs }];
- },
- },
-
video: {
inline: true,
attrs: {