@zzish/math-rich-input 0.1.56 → 0.1.57
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/index.js +568 -35
- package/dist/standalone.js +1 -1
- package/package.json +1 -1
- package/src/MathRichInput.jsx +244 -38
- package/src/editTransaction.js +173 -0
- package/src/standalone.js +4 -1
package/package.json
CHANGED
package/src/MathRichInput.jsx
CHANGED
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
debugMathRichInput,
|
|
40
40
|
getSelectionSnapshot,
|
|
41
41
|
} from "./mathRichInputDebug";
|
|
42
|
+
import {
|
|
43
|
+
editableText,
|
|
44
|
+
transactionFromBeforeInput,
|
|
45
|
+
} from "./editTransaction";
|
|
42
46
|
|
|
43
47
|
import "./MathRichInput.css";
|
|
44
48
|
import AccentBar from "./AccentBar";
|
|
@@ -245,6 +249,9 @@ export default class MathRichInput extends React.Component {
|
|
|
245
249
|
this.initialHistoryState = null;
|
|
246
250
|
this.undoHistory = [];
|
|
247
251
|
this.redoHistory = [];
|
|
252
|
+
this.historySequence = 0;
|
|
253
|
+
this.pendingTransaction = null;
|
|
254
|
+
this.compositionStart = null;
|
|
248
255
|
this.accent = "";
|
|
249
256
|
|
|
250
257
|
// keeps track of whether user is composing in an IME - see https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
|
|
@@ -359,15 +366,29 @@ export default class MathRichInput extends React.Component {
|
|
|
359
366
|
if (isExpertMode === null) isExpertMode = options.isExpertMode;
|
|
360
367
|
if (selectedTab === null) selectedTab = options.selectedTab;
|
|
361
368
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (
|
|
369
|
+
const transaction = applyOptions.transaction || null;
|
|
370
|
+
const historyState = { value, mimeType, rangeParams, transaction };
|
|
371
|
+
if (applyOptions.recordHistory !== false) {
|
|
372
|
+
// Add this to the history. The transaction is retained beside the rendered value so a host can
|
|
373
|
+
// restore annotation snapshots on undo/redo instead of interpreting an inverse spelling anew.
|
|
374
|
+
this.undoHistory.push(historyState);
|
|
375
|
+
if (this.undoHistory.length > MAX_HISTORY_LENGTH) this.undoHistory.shift();
|
|
376
|
+
}
|
|
365
377
|
|
|
366
378
|
// Debug what's being passed to parent
|
|
367
379
|
// console.log("APPLY CHANGES:", { value, mimeType });
|
|
368
380
|
|
|
369
381
|
// Call the parent handler to apply the changes
|
|
370
|
-
this.onChangeCallback(
|
|
382
|
+
this.onChangeCallback(
|
|
383
|
+
{
|
|
384
|
+
value,
|
|
385
|
+
mimeType,
|
|
386
|
+
...(transaction?.edit ? { edit: transaction.edit } : {}),
|
|
387
|
+
...(transaction?.intent ? { intent: transaction.intent } : {}),
|
|
388
|
+
...(transaction?.history ? { history: transaction.history } : {}),
|
|
389
|
+
},
|
|
390
|
+
{ isExpertMode, selectedTab }
|
|
391
|
+
);
|
|
371
392
|
if (applyOptions.skipControlledRender === true) {
|
|
372
393
|
const pendingNativeValue = value;
|
|
373
394
|
// Give a scheduled controlled echo one short render window. If the host
|
|
@@ -395,6 +416,7 @@ export default class MathRichInput extends React.Component {
|
|
|
395
416
|
value: this.props.value,
|
|
396
417
|
mimeType: this.props.mimeType,
|
|
397
418
|
rangeParams: this._getRangeParams(),
|
|
419
|
+
transaction: null,
|
|
398
420
|
};
|
|
399
421
|
};
|
|
400
422
|
|
|
@@ -425,37 +447,62 @@ export default class MathRichInput extends React.Component {
|
|
|
425
447
|
};
|
|
426
448
|
|
|
427
449
|
undo = () => {
|
|
428
|
-
const currentState = this.undoHistory.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (previousState === null || previousState === undefined) {
|
|
432
|
-
previousState = this.initialHistoryState;
|
|
433
|
-
}
|
|
450
|
+
const currentState = this.undoHistory[this.undoHistory.length - 1];
|
|
451
|
+
if (!currentState) return;
|
|
452
|
+
this.undoHistory.pop();
|
|
434
453
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
this.redoHistory.push(currentState);
|
|
439
|
-
} else this.redoHistory.push(currentState);
|
|
454
|
+
const previousState = this.undoHistory[this.undoHistory.length - 1] || this.initialHistoryState;
|
|
455
|
+
if (!previousState) return;
|
|
456
|
+
this.redoHistory.push(currentState);
|
|
440
457
|
|
|
458
|
+
const transaction = this.historyTransaction(currentState, "undo");
|
|
441
459
|
this.applyChangesToComponent(
|
|
442
460
|
previousState.value,
|
|
443
461
|
previousState.mimeType,
|
|
444
|
-
previousState.rangeParams
|
|
462
|
+
previousState.rangeParams,
|
|
463
|
+
null,
|
|
464
|
+
null,
|
|
465
|
+
{ transaction, recordHistory: false }
|
|
445
466
|
);
|
|
446
467
|
};
|
|
447
468
|
|
|
448
469
|
redo = () => {
|
|
449
470
|
const redoState = this.redoHistory.pop();
|
|
450
|
-
if (
|
|
471
|
+
if (redoState !== null && redoState !== undefined) {
|
|
451
472
|
this.applyChangesToComponent(
|
|
452
473
|
redoState.value,
|
|
453
474
|
redoState.mimeType,
|
|
454
|
-
redoState.rangeParams
|
|
475
|
+
redoState.rangeParams,
|
|
476
|
+
null,
|
|
477
|
+
null,
|
|
478
|
+
{ transaction: this.historyTransaction(redoState, "redo"), recordHistory: false }
|
|
455
479
|
);
|
|
480
|
+
this.undoHistory.push(redoState);
|
|
456
481
|
}
|
|
457
482
|
};
|
|
458
483
|
|
|
484
|
+
/** Build an explicit forward/inverse transaction for one history entry. */
|
|
485
|
+
historyTransaction = (state, direction) => {
|
|
486
|
+
const original = state?.transaction;
|
|
487
|
+
if (!original?.history?.id || !original.edit) {
|
|
488
|
+
return { intent: direction, history: original?.history ? { id: original.history.id, direction } : undefined };
|
|
489
|
+
}
|
|
490
|
+
const edit = original.edit;
|
|
491
|
+
const after = edit.before.slice(0, edit.from) + edit.insert + edit.before.slice(edit.to);
|
|
492
|
+
const inverse = {
|
|
493
|
+
before: after,
|
|
494
|
+
from: edit.from,
|
|
495
|
+
to: edit.from + edit.insert.length,
|
|
496
|
+
insert: edit.before.slice(edit.from, edit.to),
|
|
497
|
+
...(edit.boundary ? { boundary: edit.boundary } : {}),
|
|
498
|
+
};
|
|
499
|
+
return {
|
|
500
|
+
intent: direction,
|
|
501
|
+
history: { id: original.history.id, direction },
|
|
502
|
+
edit: direction === "undo" ? inverse : edit,
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
|
|
459
506
|
setOldRangeParams(params) {
|
|
460
507
|
this.oldRangeParams = params;
|
|
461
508
|
}
|
|
@@ -913,7 +960,11 @@ export default class MathRichInput extends React.Component {
|
|
|
913
960
|
const rangeParams = this._getRangeParams();
|
|
914
961
|
if (rangeParams) {
|
|
915
962
|
this.setOldRangeParams(rangeParams);
|
|
916
|
-
this.applyCurrentEditableDomToComponent(rangeParams
|
|
963
|
+
this.applyCurrentEditableDomToComponent(rangeParams, {
|
|
964
|
+
edit: { before: editableText(this.editableDiv), from: 0, to: 0, insert: "" },
|
|
965
|
+
intent: "format",
|
|
966
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
967
|
+
});
|
|
917
968
|
}
|
|
918
969
|
|
|
919
970
|
debugMathRichInput("moveTrailingWhitespaceOutsideInlineStyle:after", {
|
|
@@ -927,7 +978,7 @@ export default class MathRichInput extends React.Component {
|
|
|
927
978
|
return true;
|
|
928
979
|
}
|
|
929
980
|
|
|
930
|
-
applyCurrentEditableDomToComponent(rangeParams = null) {
|
|
981
|
+
applyCurrentEditableDomToComponent(rangeParams = null, transaction = null) {
|
|
931
982
|
if (!this.editableDiv) return;
|
|
932
983
|
if (rangeParams === null || rangeParams === undefined) {
|
|
933
984
|
rangeParams = this._getRangeParams();
|
|
@@ -960,7 +1011,7 @@ export default class MathRichInput extends React.Component {
|
|
|
960
1011
|
rangeParams,
|
|
961
1012
|
this.props.useExpertMode,
|
|
962
1013
|
this.props.selectedTab,
|
|
963
|
-
{ skipControlledRender: true }
|
|
1014
|
+
{ skipControlledRender: true, transaction }
|
|
964
1015
|
);
|
|
965
1016
|
}
|
|
966
1017
|
|
|
@@ -1028,6 +1079,7 @@ export default class MathRichInput extends React.Component {
|
|
|
1028
1079
|
}
|
|
1029
1080
|
|
|
1030
1081
|
handleKeyDownBackspace = (e) => {
|
|
1082
|
+
const beforeText = editableText(this.editableDiv);
|
|
1031
1083
|
let rangeParams = this._getRangeParams();
|
|
1032
1084
|
let start_node_index = rangeParams.startNodeIndex;
|
|
1033
1085
|
let end_node_index = rangeParams.endNodeIndex;
|
|
@@ -1082,7 +1134,14 @@ export default class MathRichInput extends React.Component {
|
|
|
1082
1134
|
this.getMimeType(false, false),
|
|
1083
1135
|
new_rangeParams,
|
|
1084
1136
|
this.props.useExpertMode,
|
|
1085
|
-
this.props.selectedTab
|
|
1137
|
+
this.props.selectedTab,
|
|
1138
|
+
{
|
|
1139
|
+
transaction: {
|
|
1140
|
+
edit: { before: beforeText, from: 0, to: beforeText.length, insert: "" },
|
|
1141
|
+
intent: "cut",
|
|
1142
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
1143
|
+
},
|
|
1144
|
+
}
|
|
1086
1145
|
);
|
|
1087
1146
|
e.preventDefault();
|
|
1088
1147
|
return;
|
|
@@ -2142,6 +2201,9 @@ export default class MathRichInput extends React.Component {
|
|
|
2142
2201
|
// console.log("handleInput")
|
|
2143
2202
|
if (this.isComposing) return;
|
|
2144
2203
|
|
|
2204
|
+
const transaction = this.pendingTransaction;
|
|
2205
|
+
this.pendingTransaction = null;
|
|
2206
|
+
|
|
2145
2207
|
debugMathRichInput("handleInput:start", {
|
|
2146
2208
|
inputType: event && event.nativeEvent ? event.nativeEvent.inputType : null,
|
|
2147
2209
|
data: event && event.nativeEvent ? event.nativeEvent.data : null,
|
|
@@ -2202,7 +2264,7 @@ export default class MathRichInput extends React.Component {
|
|
|
2202
2264
|
defaultParams,
|
|
2203
2265
|
this.props.useExpertMode,
|
|
2204
2266
|
this.props.selectedTab,
|
|
2205
|
-
{ skipControlledRender: true }
|
|
2267
|
+
{ skipControlledRender: true, transaction }
|
|
2206
2268
|
);
|
|
2207
2269
|
return;
|
|
2208
2270
|
} else if (
|
|
@@ -2252,7 +2314,7 @@ export default class MathRichInput extends React.Component {
|
|
|
2252
2314
|
validParams,
|
|
2253
2315
|
this.props.useExpertMode,
|
|
2254
2316
|
this.props.selectedTab,
|
|
2255
|
-
{ skipControlledRender: true }
|
|
2317
|
+
{ skipControlledRender: true, transaction }
|
|
2256
2318
|
);
|
|
2257
2319
|
debugMathRichInput("handleInput:after-apply", {
|
|
2258
2320
|
raw_text,
|
|
@@ -2350,6 +2412,7 @@ export default class MathRichInput extends React.Component {
|
|
|
2350
2412
|
|
|
2351
2413
|
handlePaste = (event) => {
|
|
2352
2414
|
try {
|
|
2415
|
+
this.pendingTransaction = null;
|
|
2353
2416
|
let clipboardData = event.clipboardData || window.clipboardData;
|
|
2354
2417
|
if (!clipboardData) {
|
|
2355
2418
|
return;
|
|
@@ -2375,6 +2438,8 @@ export default class MathRichInput extends React.Component {
|
|
|
2375
2438
|
console.warn("Could not get range params for paste");
|
|
2376
2439
|
return;
|
|
2377
2440
|
}
|
|
2441
|
+
const beforePaste = editableText(this.editableDiv);
|
|
2442
|
+
const pasteSelection = this.captureSelectionOffsets();
|
|
2378
2443
|
|
|
2379
2444
|
// Store old range params for cursor positioning (like equation editor)
|
|
2380
2445
|
this.setOldRangeParams(rangeParams);
|
|
@@ -2647,12 +2712,32 @@ export default class MathRichInput extends React.Component {
|
|
|
2647
2712
|
);
|
|
2648
2713
|
|
|
2649
2714
|
// Apply the change using the component's standard flow
|
|
2715
|
+
const insertionText = (() => {
|
|
2716
|
+
if (!/<[^>]+>/.test(finalText)) return finalText;
|
|
2717
|
+
const probe = document.createElement("div");
|
|
2718
|
+
probe.innerHTML = finalText;
|
|
2719
|
+
return editableText(probe).replace(/\ufffc/g, "");
|
|
2720
|
+
})();
|
|
2721
|
+
const transaction = pasteSelection
|
|
2722
|
+
? {
|
|
2723
|
+
edit: {
|
|
2724
|
+
before: beforePaste,
|
|
2725
|
+
from: pasteSelection.from,
|
|
2726
|
+
to: pasteSelection.to,
|
|
2727
|
+
insert: insertionText,
|
|
2728
|
+
},
|
|
2729
|
+
intent: "paste",
|
|
2730
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
2731
|
+
}
|
|
2732
|
+
: { intent: "paste", history: { id: `mri-${++this.historySequence}`, direction: "forward" } };
|
|
2733
|
+
|
|
2650
2734
|
this.applyChangesToComponent(
|
|
2651
2735
|
cleanedRawText,
|
|
2652
2736
|
actualMimeType,
|
|
2653
2737
|
newRangeParams,
|
|
2654
2738
|
this.props.useExpertMode,
|
|
2655
|
-
this.props.selectedTab
|
|
2739
|
+
this.props.selectedTab,
|
|
2740
|
+
{ transaction }
|
|
2656
2741
|
);
|
|
2657
2742
|
|
|
2658
2743
|
// A paste cannot rely on being re-rendered. It removed the selected content from the DOM itself,
|
|
@@ -2693,6 +2778,7 @@ export default class MathRichInput extends React.Component {
|
|
|
2693
2778
|
|
|
2694
2779
|
this.editableDiv.addEventListener("keydown", this.handleKeyDown);
|
|
2695
2780
|
this.editableDiv.addEventListener("keyup", this.handleKeyUp);
|
|
2781
|
+
this.editableDiv.addEventListener("beforeinput", this.handleBeforeInput);
|
|
2696
2782
|
this.editableDiv.addEventListener("paste", this.handlePaste);
|
|
2697
2783
|
this.editableDiv.addEventListener("copy", this.handleCopy);
|
|
2698
2784
|
if (this.props.autofocus === true) this.editableDiv.focus();
|
|
@@ -2705,6 +2791,7 @@ export default class MathRichInput extends React.Component {
|
|
|
2705
2791
|
try {
|
|
2706
2792
|
this.editableDiv.removeEventListener("keydown", this.handleKeyDown);
|
|
2707
2793
|
this.editableDiv.removeEventListener("keyup", this.handleKeyUp);
|
|
2794
|
+
this.editableDiv.removeEventListener("beforeinput", this.handleBeforeInput);
|
|
2708
2795
|
this.editableDiv.removeEventListener("paste", this.handlePaste);
|
|
2709
2796
|
this.editableDiv.removeEventListener("copy", this.handleCopy);
|
|
2710
2797
|
} catch (error) {
|
|
@@ -3310,12 +3397,19 @@ export default class MathRichInput extends React.Component {
|
|
|
3310
3397
|
}
|
|
3311
3398
|
|
|
3312
3399
|
const rangeParams = this._getRangeParams();
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
this.cleanupEmptyInlineFormattingElements();
|
|
3316
|
-
if (removedEmptyTags && rangeParams !== null && rangeParams !== undefined) {
|
|
3400
|
+
if (!insertedTypingStylePlaceholder) this.cleanupEmptyInlineFormattingElements();
|
|
3401
|
+
if (rangeParams !== null && rangeParams !== undefined) {
|
|
3317
3402
|
this._setRangeParams(rangeParams);
|
|
3318
|
-
this.applyCurrentEditableDomToComponent(rangeParams
|
|
3403
|
+
this.applyCurrentEditableDomToComponent(rangeParams, {
|
|
3404
|
+
edit: {
|
|
3405
|
+
before: editableText(this.editableDiv),
|
|
3406
|
+
from: 0,
|
|
3407
|
+
to: 0,
|
|
3408
|
+
insert: "",
|
|
3409
|
+
},
|
|
3410
|
+
intent: "format",
|
|
3411
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
3412
|
+
});
|
|
3319
3413
|
}
|
|
3320
3414
|
this.setActiveButtonsIfChanged(
|
|
3321
3415
|
this.getActiveButtonsFromSelection(rangeParams)
|
|
@@ -3371,6 +3465,7 @@ export default class MathRichInput extends React.Component {
|
|
|
3371
3465
|
|
|
3372
3466
|
insertRawText = (new_text, selectInsertedText = false) => {
|
|
3373
3467
|
try {
|
|
3468
|
+
const beforeText = editableText(this.editableDiv);
|
|
3374
3469
|
let rangeParams = this._getRangeParams();
|
|
3375
3470
|
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
3376
3471
|
let offset = rangeParams.endOffset;
|
|
@@ -3422,7 +3517,19 @@ export default class MathRichInput extends React.Component {
|
|
|
3422
3517
|
this.props.mimeType,
|
|
3423
3518
|
new_rangeParams,
|
|
3424
3519
|
this.props.useExpertMode,
|
|
3425
|
-
this.props.selectedTab
|
|
3520
|
+
this.props.selectedTab,
|
|
3521
|
+
{
|
|
3522
|
+
transaction: {
|
|
3523
|
+
edit: {
|
|
3524
|
+
before: beforeText,
|
|
3525
|
+
from: rangeParams.startGlobalOffset,
|
|
3526
|
+
to: rangeParams.endGlobalOffset,
|
|
3527
|
+
insert: new_text,
|
|
3528
|
+
},
|
|
3529
|
+
intent: "input",
|
|
3530
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
3531
|
+
},
|
|
3532
|
+
}
|
|
3426
3533
|
);
|
|
3427
3534
|
} catch (error) {
|
|
3428
3535
|
console.error(error);
|
|
@@ -3577,6 +3684,7 @@ export default class MathRichInput extends React.Component {
|
|
|
3577
3684
|
|
|
3578
3685
|
removeNewLine = () => {
|
|
3579
3686
|
try {
|
|
3687
|
+
const beforeText = editableText(this.editableDiv);
|
|
3580
3688
|
let rangeParams = this._getRangeParams();
|
|
3581
3689
|
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
3582
3690
|
let prevNode = this._findNodeWithIndex(rangeParams.endNodeIndex - 1);
|
|
@@ -3613,7 +3721,19 @@ export default class MathRichInput extends React.Component {
|
|
|
3613
3721
|
this.props.mimeType,
|
|
3614
3722
|
new_rangeParams,
|
|
3615
3723
|
this.props.useExpertMode,
|
|
3616
|
-
this.props.selectedTab
|
|
3724
|
+
this.props.selectedTab,
|
|
3725
|
+
{
|
|
3726
|
+
transaction: {
|
|
3727
|
+
edit: {
|
|
3728
|
+
before: beforeText,
|
|
3729
|
+
from: Math.max(0, rangeParams.endGlobalOffset - 1),
|
|
3730
|
+
to: rangeParams.endGlobalOffset,
|
|
3731
|
+
insert: "",
|
|
3732
|
+
},
|
|
3733
|
+
intent: "input",
|
|
3734
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
3735
|
+
},
|
|
3736
|
+
}
|
|
3617
3737
|
);
|
|
3618
3738
|
} catch (error) {
|
|
3619
3739
|
console.error(error);
|
|
@@ -3622,6 +3742,7 @@ export default class MathRichInput extends React.Component {
|
|
|
3622
3742
|
|
|
3623
3743
|
insertNewLine = () => {
|
|
3624
3744
|
try {
|
|
3745
|
+
const beforeText = editableText(this.editableDiv);
|
|
3625
3746
|
let rangeParams = this._getRangeParams();
|
|
3626
3747
|
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
3627
3748
|
let offset = rangeParams.endOffset;
|
|
@@ -3678,14 +3799,26 @@ export default class MathRichInput extends React.Component {
|
|
|
3678
3799
|
this.props.mimeType,
|
|
3679
3800
|
new_rangeParams,
|
|
3680
3801
|
this.props.useExpertMode,
|
|
3681
|
-
this.props.selectedTab
|
|
3802
|
+
this.props.selectedTab,
|
|
3803
|
+
{
|
|
3804
|
+
transaction: {
|
|
3805
|
+
edit: {
|
|
3806
|
+
before: beforeText,
|
|
3807
|
+
from: rangeParams.startGlobalOffset,
|
|
3808
|
+
to: rangeParams.endGlobalOffset,
|
|
3809
|
+
insert: "\n",
|
|
3810
|
+
},
|
|
3811
|
+
intent: "input",
|
|
3812
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
3813
|
+
},
|
|
3814
|
+
}
|
|
3682
3815
|
);
|
|
3683
3816
|
} catch (error) {
|
|
3684
3817
|
console.error(error);
|
|
3685
3818
|
}
|
|
3686
3819
|
};
|
|
3687
3820
|
|
|
3688
|
-
|
|
3821
|
+
insertAfterSmallSpace(new_char, rangeParams) {
|
|
3689
3822
|
try {
|
|
3690
3823
|
if (rangeParams === null || rangeParams === undefined)
|
|
3691
3824
|
rangeParams = this._getRangeParams();
|
|
@@ -3697,6 +3830,7 @@ export default class MathRichInput extends React.Component {
|
|
|
3697
3830
|
" in insertAfterSmallSpace"
|
|
3698
3831
|
);
|
|
3699
3832
|
|
|
3833
|
+
const beforeText = editableText(this.editableDiv);
|
|
3700
3834
|
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
3701
3835
|
if (node === null || node === undefined || node.nodeValue === null) {
|
|
3702
3836
|
console.warn("Could not find small-space text node");
|
|
@@ -3736,14 +3870,21 @@ export default class MathRichInput extends React.Component {
|
|
|
3736
3870
|
this.enableHtml()
|
|
3737
3871
|
);
|
|
3738
3872
|
|
|
3739
|
-
|
|
3873
|
+
this.applyChangesToComponent(
|
|
3740
3874
|
new_raw_text,
|
|
3741
3875
|
this.props.mimeType,
|
|
3742
3876
|
new_rangeParams,
|
|
3743
3877
|
this.props.useExpertMode,
|
|
3744
3878
|
this.props.selectedTab,
|
|
3745
|
-
{
|
|
3746
|
-
|
|
3879
|
+
{
|
|
3880
|
+
skipControlledRender: true,
|
|
3881
|
+
transaction: {
|
|
3882
|
+
edit: { before: beforeText, from: rangeParams.startGlobalOffset, to: rangeParams.startGlobalOffset, insert: new_char },
|
|
3883
|
+
intent: "input",
|
|
3884
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
3885
|
+
},
|
|
3886
|
+
}
|
|
3887
|
+
);
|
|
3747
3888
|
} catch (error) {
|
|
3748
3889
|
console.error(error);
|
|
3749
3890
|
}
|
|
@@ -3751,6 +3892,7 @@ export default class MathRichInput extends React.Component {
|
|
|
3751
3892
|
|
|
3752
3893
|
insertAccentLetter = (event, old_char, new_char) => {
|
|
3753
3894
|
try {
|
|
3895
|
+
const beforeText = editableText(this.editableDiv);
|
|
3754
3896
|
let rangeParams = this._getRangeParams();
|
|
3755
3897
|
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
3756
3898
|
let offset = rangeParams.startOffset;
|
|
@@ -3797,7 +3939,19 @@ export default class MathRichInput extends React.Component {
|
|
|
3797
3939
|
this.props.mimeType,
|
|
3798
3940
|
new_rangeParams,
|
|
3799
3941
|
this.props.useExpertMode,
|
|
3800
|
-
this.props.selectedTab
|
|
3942
|
+
this.props.selectedTab,
|
|
3943
|
+
{
|
|
3944
|
+
transaction: {
|
|
3945
|
+
edit: {
|
|
3946
|
+
before: beforeText,
|
|
3947
|
+
from: old_char === null ? rangeParams.startGlobalOffset : Math.max(0, rangeParams.startGlobalOffset - 1),
|
|
3948
|
+
to: old_char === null ? rangeParams.startGlobalOffset : rangeParams.startGlobalOffset,
|
|
3949
|
+
insert: new_char,
|
|
3950
|
+
},
|
|
3951
|
+
intent: "input",
|
|
3952
|
+
history: { id: `mri-${++this.historySequence}`, direction: "forward" },
|
|
3953
|
+
},
|
|
3954
|
+
}
|
|
3801
3955
|
);
|
|
3802
3956
|
} catch (error) {
|
|
3803
3957
|
console.error(error);
|
|
@@ -3913,11 +4067,63 @@ export default class MathRichInput extends React.Component {
|
|
|
3913
4067
|
};
|
|
3914
4068
|
|
|
3915
4069
|
handleCompositionStart = (event) => {
|
|
4070
|
+
const offsets = this.captureSelectionOffsets();
|
|
4071
|
+
this.compositionStart = {
|
|
4072
|
+
before: editableText(this.editableDiv),
|
|
4073
|
+
from: offsets?.from ?? 0,
|
|
4074
|
+
to: offsets?.to ?? 0,
|
|
4075
|
+
};
|
|
3916
4076
|
this.isComposing = true;
|
|
3917
4077
|
};
|
|
3918
4078
|
|
|
3919
4079
|
handleCompositionEnd = (event) => {
|
|
3920
4080
|
this.isComposing = false;
|
|
4081
|
+
this.compositionStart = null;
|
|
4082
|
+
};
|
|
4083
|
+
|
|
4084
|
+
captureSelectionOffsets = () => {
|
|
4085
|
+
try {
|
|
4086
|
+
const selection = window.getSelection();
|
|
4087
|
+
if (!selection || selection.rangeCount === 0) return null;
|
|
4088
|
+
const range = selection.getRangeAt(0);
|
|
4089
|
+
const before = editableText(this.editableDiv);
|
|
4090
|
+
const endpoint = (container, offset) => {
|
|
4091
|
+
const probe = document.createRange();
|
|
4092
|
+
probe.selectNodeContents(this.editableDiv);
|
|
4093
|
+
probe.setEnd(container, offset);
|
|
4094
|
+
const wrapper = document.createElement("span");
|
|
4095
|
+
wrapper.appendChild(probe.cloneContents());
|
|
4096
|
+
return editableText(wrapper).length;
|
|
4097
|
+
};
|
|
4098
|
+
const from = endpoint(range.startContainer, range.startOffset);
|
|
4099
|
+
const to = endpoint(range.endContainer, range.endOffset);
|
|
4100
|
+
return { before, from: Math.min(from, to), to: Math.max(from, to) };
|
|
4101
|
+
} catch (_error) {
|
|
4102
|
+
return null;
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
|
|
4106
|
+
/** Capture an input intent before the browser mutates the contenteditable DOM. */
|
|
4107
|
+
handleBeforeInput = (event) => {
|
|
4108
|
+
try {
|
|
4109
|
+
const captured = transactionFromBeforeInput(
|
|
4110
|
+
this.editableDiv,
|
|
4111
|
+
event,
|
|
4112
|
+
this.compositionStart
|
|
4113
|
+
? { before: this.compositionStart.before, from: this.compositionStart.from, to: this.compositionStart.to }
|
|
4114
|
+
: null
|
|
4115
|
+
);
|
|
4116
|
+
const id = `mri-${++this.historySequence}`;
|
|
4117
|
+
this.pendingTransaction = {
|
|
4118
|
+
edit: captured.edit || undefined,
|
|
4119
|
+
intent: captured.intent,
|
|
4120
|
+
history: { id, direction: "forward" },
|
|
4121
|
+
};
|
|
4122
|
+
} catch (_error) {
|
|
4123
|
+
this.pendingTransaction = {
|
|
4124
|
+
intent: "programmatic",
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
3921
4127
|
};
|
|
3922
4128
|
|
|
3923
4129
|
// Comprehensive LaTeX pattern detection and auto-wrapping in math tags
|