@zzish/math-rich-input 0.1.51 → 0.1.52
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 +979 -163
- package/dist/standalone.js +1 -1
- package/package.json +3 -3
- package/src/MathRichArea.jsx +5 -2
- package/src/MathRichInput.jsx +991 -207
- package/src/Toolbar.jsx +3 -4
- package/src/mathRichInputDebug.js +98 -0
- package/src/mathRichInputHelper.js +128 -17
package/src/MathRichInput.jsx
CHANGED
|
@@ -34,6 +34,10 @@ import {
|
|
|
34
34
|
globalOffsetToPlainTextOffset,
|
|
35
35
|
plainTextOffsetToGlobalOffset,
|
|
36
36
|
} from "./mathRichInputHelper";
|
|
37
|
+
import {
|
|
38
|
+
debugMathRichInput,
|
|
39
|
+
getSelectionSnapshot,
|
|
40
|
+
} from "./mathRichInputDebug";
|
|
37
41
|
|
|
38
42
|
import "./MathRichInput.css";
|
|
39
43
|
import AccentBar from "./AccentBar";
|
|
@@ -50,6 +54,30 @@ const SMALL_SPACE_LENGTH = SMALL_SPACE.length;
|
|
|
50
54
|
|
|
51
55
|
const MARK = getMark();
|
|
52
56
|
const MARK_REG_EXP = new RegExp(MARK, "g");
|
|
57
|
+
const INLINE_STYLE_COMMANDS = {
|
|
58
|
+
bold: "bold",
|
|
59
|
+
italic: "italic",
|
|
60
|
+
underline: "underline",
|
|
61
|
+
subscript: "subscript",
|
|
62
|
+
superscript: "superscript",
|
|
63
|
+
};
|
|
64
|
+
const INLINE_STYLE_TAGS = {
|
|
65
|
+
bold: ["b", "strong"],
|
|
66
|
+
italic: ["i", "em"],
|
|
67
|
+
underline: ["u"],
|
|
68
|
+
subscript: ["sub"],
|
|
69
|
+
superscript: ["sup"],
|
|
70
|
+
};
|
|
71
|
+
const INLINE_STYLE_TAG_NAMES = ["b", "strong", "i", "em", "u", "sub", "sup"];
|
|
72
|
+
const INLINE_STYLE_WRAPPER_TAGS = {
|
|
73
|
+
bold: "strong",
|
|
74
|
+
italic: "em",
|
|
75
|
+
underline: "u",
|
|
76
|
+
subscript: "sub",
|
|
77
|
+
superscript: "sup",
|
|
78
|
+
};
|
|
79
|
+
const EMPTY_INLINE_FORMATTING_REG_EXP =
|
|
80
|
+
/<(B|STRONG|I|EM|U|SUB|SUP)>(?:\s| |\u00a0|\u200b|\ufeff)*<\/\1>/gi;
|
|
53
81
|
|
|
54
82
|
// const LATEX_MARKER_START = "<annotation encoding=\"application/x-tex\">"
|
|
55
83
|
// const LATEX_MARKER_END = "</annotation>"
|
|
@@ -190,17 +218,20 @@ export default class MathRichInput extends React.Component {
|
|
|
190
218
|
|
|
191
219
|
// Elements that need to be set before the equation editor is shown
|
|
192
220
|
this.markedRawText = null;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
endOffset: 0,
|
|
198
|
-
};
|
|
221
|
+
// No selection has been captured yet. Do not default this to the start of
|
|
222
|
+
// the editor: React 19 focus/click ordering makes handleFocus restore that
|
|
223
|
+
// synthetic 0,0 range and moves the caret to the beginning on every click.
|
|
224
|
+
this.oldRangeParams = null;
|
|
199
225
|
this.moveCursorOnInsert = false;
|
|
200
226
|
|
|
201
227
|
// Elements defining the current state of range
|
|
202
228
|
this.afterComponentUpdateData = null; // Set this to update the selection after rendering
|
|
203
|
-
this.
|
|
229
|
+
this.lastSetRangeParams = null; // This is set automatically each time the range params are set with setRangeParams
|
|
230
|
+
this.skipNextControlledValueRender = false;
|
|
231
|
+
this.skipNextControlledValue = null;
|
|
232
|
+
this.isMouseDownInEditable = false;
|
|
233
|
+
this.lastRenderedEditableHtml = null;
|
|
234
|
+
this.preserveNativeDomRangeParams = null;
|
|
204
235
|
|
|
205
236
|
this.lastSetActiveButtons = null;
|
|
206
237
|
|
|
@@ -292,12 +323,35 @@ export default class MathRichInput extends React.Component {
|
|
|
292
323
|
mimeType,
|
|
293
324
|
rangeParams,
|
|
294
325
|
isExpertMode,
|
|
295
|
-
selectedTab
|
|
326
|
+
selectedTab,
|
|
327
|
+
applyOptions = {}
|
|
296
328
|
) => {
|
|
297
329
|
// rangeParams, isExpertMode and selectedTab can be null, in which case we will set them
|
|
298
330
|
if (rangeParams === null) rangeParams = this.lastSetRangeParams;
|
|
299
331
|
|
|
300
|
-
|
|
332
|
+
debugMathRichInput("applyChangesToComponent:start", {
|
|
333
|
+
value,
|
|
334
|
+
mimeType,
|
|
335
|
+
rangeParams,
|
|
336
|
+
propsValue: this.props.value,
|
|
337
|
+
propsMimeType: this.props.mimeType,
|
|
338
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
339
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
340
|
+
skipControlledRender: applyOptions.skipControlledRender === true,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (applyOptions.skipControlledRender === true) {
|
|
344
|
+
// Native contenteditable input has already mutated the DOM and kept the
|
|
345
|
+
// browser selection in the right place. Re-rendering the same value via
|
|
346
|
+
// dangerouslySetInnerHTML can replace text nodes under React 19 and move
|
|
347
|
+
// the caret to the beginning. Programmatic edits still use the normal
|
|
348
|
+
// render-and-restore path.
|
|
349
|
+
this.skipNextControlledValueRender = true;
|
|
350
|
+
this.skipNextControlledValue = value;
|
|
351
|
+
this.afterComponentUpdateData = null;
|
|
352
|
+
} else {
|
|
353
|
+
this.afterComponentUpdateData = { rangeParams, value };
|
|
354
|
+
}
|
|
301
355
|
|
|
302
356
|
let options = this.props.options || DEFAULT_OPTIONS;
|
|
303
357
|
if (isExpertMode === null) isExpertMode = options.isExpertMode;
|
|
@@ -312,6 +366,10 @@ export default class MathRichInput extends React.Component {
|
|
|
312
366
|
|
|
313
367
|
// Call the parent handler to apply the changes
|
|
314
368
|
this.onChangeCallback({ value, mimeType }, { isExpertMode, selectedTab });
|
|
369
|
+
debugMathRichInput("applyChangesToComponent:after-onChange", {
|
|
370
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
371
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
372
|
+
});
|
|
315
373
|
};
|
|
316
374
|
|
|
317
375
|
initialiseHistory = () => {
|
|
@@ -392,23 +450,550 @@ export default class MathRichInput extends React.Component {
|
|
|
392
450
|
return params;
|
|
393
451
|
}
|
|
394
452
|
|
|
453
|
+
rangeIsAtStart(params) {
|
|
454
|
+
if (params === null || params === undefined) return false;
|
|
455
|
+
if (
|
|
456
|
+
params.startGlobalOffset !== null &&
|
|
457
|
+
params.startGlobalOffset !== undefined &&
|
|
458
|
+
params.endGlobalOffset !== null &&
|
|
459
|
+
params.endGlobalOffset !== undefined
|
|
460
|
+
) {
|
|
461
|
+
return params.startGlobalOffset === 0 && params.endGlobalOffset === 0;
|
|
462
|
+
}
|
|
463
|
+
return (
|
|
464
|
+
params.startNodeIndex === 0 &&
|
|
465
|
+
params.endNodeIndex === 0 &&
|
|
466
|
+
params.startOffset === 0 &&
|
|
467
|
+
params.endOffset === 0
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
rangeHasNonZeroPosition(params) {
|
|
472
|
+
if (params === null || params === undefined) return false;
|
|
473
|
+
if (
|
|
474
|
+
params.startGlobalOffset !== null &&
|
|
475
|
+
params.startGlobalOffset !== undefined
|
|
476
|
+
) {
|
|
477
|
+
return params.startGlobalOffset > 0;
|
|
478
|
+
}
|
|
479
|
+
return (
|
|
480
|
+
params.startNodeIndex > 0 ||
|
|
481
|
+
params.endNodeIndex > 0 ||
|
|
482
|
+
params.startOffset > 0 ||
|
|
483
|
+
params.endOffset > 0
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
getCurrentRawTextForProps(nextProps) {
|
|
488
|
+
if (!this.editableDiv) return null;
|
|
489
|
+
try {
|
|
490
|
+
let rawText = elementToMarkedRawText(
|
|
491
|
+
this.editableDiv,
|
|
492
|
+
null,
|
|
493
|
+
0,
|
|
494
|
+
this.enableHtml()
|
|
495
|
+
);
|
|
496
|
+
rawText = this.stripEmptyInlineFormattingTags(rawText);
|
|
497
|
+
return removeEncodingIfPlainText(
|
|
498
|
+
rawText,
|
|
499
|
+
nextProps.mimeType,
|
|
500
|
+
this.enableHtml()
|
|
501
|
+
);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
debugMathRichInput("getCurrentRawTextForProps:error", {
|
|
504
|
+
error,
|
|
505
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
506
|
+
});
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
shouldSkipRedundantFocusedRender(nextProps, nextState) {
|
|
512
|
+
if (!this.editableDiv) return false;
|
|
513
|
+
if (nextState !== this.state) return false;
|
|
514
|
+
if (nextState.hasFocus !== true) return false;
|
|
515
|
+
if (
|
|
516
|
+
this.afterComponentUpdateData !== null &&
|
|
517
|
+
this.afterComponentUpdateData !== undefined
|
|
518
|
+
) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const currentRawText = this.getCurrentRawTextForProps(nextProps);
|
|
523
|
+
const shouldSkip = currentRawText === nextProps.value;
|
|
524
|
+
debugMathRichInput("shouldComponentUpdate:redundant-focused-check", {
|
|
525
|
+
shouldSkip,
|
|
526
|
+
currentRawText,
|
|
527
|
+
nextValue: nextProps.value,
|
|
528
|
+
currentPropsValue: this.props.value,
|
|
529
|
+
nextMimeType: nextProps.mimeType,
|
|
530
|
+
currentMimeType: this.props.mimeType,
|
|
531
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
if (shouldSkip) {
|
|
535
|
+
const currentRangeParams = this.getRangeParamsPreservingInlinePlaceholder();
|
|
536
|
+
if (this.rangeHasNonZeroPosition(currentRangeParams)) {
|
|
537
|
+
this.setOldRangeParams(currentRangeParams);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return shouldSkip;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
isSelectionInsideInlinePlaceholder() {
|
|
545
|
+
if (typeof document === "undefined") return false;
|
|
546
|
+
|
|
547
|
+
const selection = document.getSelection ? document.getSelection() : null;
|
|
548
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
549
|
+
|
|
550
|
+
const range = selection.getRangeAt(0);
|
|
551
|
+
if (!range.collapsed) return false;
|
|
552
|
+
|
|
553
|
+
const node = range.startContainer;
|
|
554
|
+
if (!node || node.nodeType !== 3) return false;
|
|
555
|
+
if (node.nodeValue !== SMALL_SPACE) return false;
|
|
556
|
+
if (range.startOffset !== SMALL_SPACE_LENGTH) return false;
|
|
557
|
+
|
|
558
|
+
const parent = node.parentNode;
|
|
559
|
+
if (!parent || parent.nodeType !== 1) return false;
|
|
560
|
+
|
|
561
|
+
const tagName = parent.nodeName.toLowerCase();
|
|
562
|
+
if (!INLINE_STYLE_TAG_NAMES.includes(tagName)) return false;
|
|
563
|
+
|
|
564
|
+
return parent.textContent === SMALL_SPACE;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
getRangeParamsPreservingInlinePlaceholder() {
|
|
568
|
+
const selectionInsideInlinePlaceholder =
|
|
569
|
+
this.isSelectionInsideInlinePlaceholder();
|
|
570
|
+
const rangeParams = this._getRangeParams();
|
|
571
|
+
|
|
572
|
+
if (!selectionInsideInlinePlaceholder || !rangeParams) {
|
|
573
|
+
return rangeParams;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Global offsets ignore the zero-width placeholder; node index preserves it.
|
|
577
|
+
return {
|
|
578
|
+
...rangeParams,
|
|
579
|
+
startGlobalOffset: null,
|
|
580
|
+
endGlobalOffset: null,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
getEditableHtmlForRender(html) {
|
|
585
|
+
const currentRawText = this.editableDiv
|
|
586
|
+
? this.getCurrentRawTextForProps(this.props)
|
|
587
|
+
: null;
|
|
588
|
+
const liveEditableHtml = this.editableDiv ? this.editableDiv.innerHTML : null;
|
|
589
|
+
const canPreserveNativeDom =
|
|
590
|
+
this.state.hasFocus === true &&
|
|
591
|
+
this.editableDiv !== null &&
|
|
592
|
+
this.editableDiv !== undefined &&
|
|
593
|
+
(this.afterComponentUpdateData === null ||
|
|
594
|
+
this.afterComponentUpdateData === undefined) &&
|
|
595
|
+
this.lastRenderedEditableHtml !== null &&
|
|
596
|
+
currentRawText === this.props.value;
|
|
597
|
+
const preserveRangeParams = canPreserveNativeDom
|
|
598
|
+
? this.getRangeParamsPreservingInlinePlaceholder()
|
|
599
|
+
: null;
|
|
600
|
+
|
|
601
|
+
debugMathRichInput("render:editable-html", {
|
|
602
|
+
canPreserveNativeDom,
|
|
603
|
+
selectionInsideInlinePlaceholder:
|
|
604
|
+
this.isSelectionInsideInlinePlaceholder(),
|
|
605
|
+
propsValue: this.props.value,
|
|
606
|
+
generatedHtml: html,
|
|
607
|
+
renderedHtml: canPreserveNativeDom ? liveEditableHtml : html,
|
|
608
|
+
liveEditableHtml,
|
|
609
|
+
lastRenderedEditableHtml: this.lastRenderedEditableHtml,
|
|
610
|
+
currentRawText,
|
|
611
|
+
preserveRangeParams,
|
|
612
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
613
|
+
hasFocus: this.state.hasFocus,
|
|
614
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
if (canPreserveNativeDom) {
|
|
618
|
+
this.lastRenderedEditableHtml = liveEditableHtml;
|
|
619
|
+
this.preserveNativeDomRangeParams = preserveRangeParams;
|
|
620
|
+
return liveEditableHtml;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.lastRenderedEditableHtml = html;
|
|
624
|
+
this.preserveNativeDomRangeParams = null;
|
|
625
|
+
return html;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
stripEmptyInlineFormattingTags(rawText) {
|
|
629
|
+
if (rawText === null || rawText === undefined) return rawText;
|
|
630
|
+
let cleaned = rawText;
|
|
631
|
+
let previous = null;
|
|
632
|
+
while (previous !== cleaned) {
|
|
633
|
+
previous = cleaned;
|
|
634
|
+
cleaned = cleaned.replace(EMPTY_INLINE_FORMATTING_REG_EXP, "");
|
|
635
|
+
}
|
|
636
|
+
return cleaned;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
cleanupEmptyInlineFormattingElements() {
|
|
640
|
+
if (!this.editableDiv) return false;
|
|
641
|
+
|
|
642
|
+
let removed = false;
|
|
643
|
+
this.editableDiv
|
|
644
|
+
.querySelectorAll("b,strong,i,em,u,sub,sup")
|
|
645
|
+
.forEach((node) => {
|
|
646
|
+
const text = node.textContent || "";
|
|
647
|
+
const hasMeaningfulText = text.replace(/[\s\u00a0\u200b\ufeff]/g, "") !== "";
|
|
648
|
+
const hasProtectedContent =
|
|
649
|
+
node.querySelector("br,img,math,.katex") !== null;
|
|
650
|
+
|
|
651
|
+
if (!hasMeaningfulText && !hasProtectedContent) {
|
|
652
|
+
node.remove();
|
|
653
|
+
removed = true;
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
if (removed) {
|
|
658
|
+
this.editableDiv.normalize();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return removed;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
getStyleCommand(style) {
|
|
665
|
+
return INLINE_STYLE_COMMANDS[style] || style;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
insertInlineStylePlaceholder(style) {
|
|
669
|
+
const wrapperTag = INLINE_STYLE_WRAPPER_TAGS[style] || "span";
|
|
670
|
+
const selection = document.getSelection();
|
|
671
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
672
|
+
|
|
673
|
+
const range = selection.getRangeAt(0);
|
|
674
|
+
if (!range.collapsed) return false;
|
|
675
|
+
|
|
676
|
+
range.deleteContents();
|
|
677
|
+
|
|
678
|
+
const wrapper = document.createElement(wrapperTag);
|
|
679
|
+
const textNode = document.createTextNode(SMALL_SPACE);
|
|
680
|
+
wrapper.appendChild(textNode);
|
|
681
|
+
range.insertNode(wrapper);
|
|
682
|
+
|
|
683
|
+
const nextRange = document.createRange();
|
|
684
|
+
nextRange.setStart(textNode, SMALL_SPACE_LENGTH);
|
|
685
|
+
nextRange.collapse(true);
|
|
686
|
+
selection.removeAllRanges();
|
|
687
|
+
selection.addRange(nextRange);
|
|
688
|
+
|
|
689
|
+
const rangeParams = this.getRangeParamsPreservingInlinePlaceholder();
|
|
690
|
+
if (rangeParams) this.setOldRangeParams(rangeParams);
|
|
691
|
+
|
|
692
|
+
debugMathRichInput("insertInlineStylePlaceholder:after", {
|
|
693
|
+
style,
|
|
694
|
+
wrapperTag,
|
|
695
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
696
|
+
rangeParams,
|
|
697
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
getActiveButtonsFromCommandState() {
|
|
704
|
+
const query = (command) => {
|
|
705
|
+
try {
|
|
706
|
+
return document.queryCommandState(command);
|
|
707
|
+
} catch (error) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
return {
|
|
713
|
+
bold: query("bold"),
|
|
714
|
+
italic: query("italic"),
|
|
715
|
+
underline: query("underline"),
|
|
716
|
+
subscript: query("subscript"),
|
|
717
|
+
superscript: query("superscript"),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
isSelectionCollapsed() {
|
|
722
|
+
const selection = document.getSelection();
|
|
723
|
+
if (!selection || selection.rangeCount === 0) return true;
|
|
724
|
+
return selection.getRangeAt(0).collapsed;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
getActiveButtonsFromAncestorState(rangeParams = null) {
|
|
728
|
+
if (rangeParams === null || rangeParams === undefined) {
|
|
729
|
+
rangeParams = this._getRangeParams();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
let activeButtons = {
|
|
733
|
+
bold: false,
|
|
734
|
+
italic: false,
|
|
735
|
+
underline: false,
|
|
736
|
+
subscript: false,
|
|
737
|
+
superscript: false,
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
if (
|
|
741
|
+
rangeParams === null ||
|
|
742
|
+
rangeParams === undefined ||
|
|
743
|
+
rangeParams.startNodeIndex < 0
|
|
744
|
+
) {
|
|
745
|
+
return activeButtons;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
749
|
+
if (node === null || node === undefined) return activeButtons;
|
|
750
|
+
|
|
751
|
+
do {
|
|
752
|
+
if (node.nodeType === 1) {
|
|
753
|
+
let nodeName = node.nodeName.toLowerCase();
|
|
754
|
+
if (nodeName === "b" || nodeName === "strong") activeButtons.bold = true;
|
|
755
|
+
else if (nodeName === "i" || nodeName === "em")
|
|
756
|
+
activeButtons.italic = true;
|
|
757
|
+
else if (nodeName === "u") activeButtons.underline = true;
|
|
758
|
+
else if (nodeName === "sub") activeButtons.subscript = true;
|
|
759
|
+
else if (nodeName === "sup") activeButtons.superscript = true;
|
|
760
|
+
} else if (
|
|
761
|
+
node.classList !== null &&
|
|
762
|
+
node.classList !== undefined &&
|
|
763
|
+
node.classList.contains("MathRichInput")
|
|
764
|
+
)
|
|
765
|
+
break;
|
|
766
|
+
node = node.parentNode;
|
|
767
|
+
} while (node !== null);
|
|
768
|
+
|
|
769
|
+
return activeButtons;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
getActiveButtonsFromSelection(rangeParams = null) {
|
|
773
|
+
const commandState = this.getActiveButtonsFromCommandState();
|
|
774
|
+
const ancestorState = this.getActiveButtonsFromAncestorState(rangeParams);
|
|
775
|
+
return {
|
|
776
|
+
bold: commandState.bold || ancestorState.bold,
|
|
777
|
+
italic: commandState.italic || ancestorState.italic,
|
|
778
|
+
underline: commandState.underline || ancestorState.underline,
|
|
779
|
+
subscript: commandState.subscript || ancestorState.subscript,
|
|
780
|
+
superscript: commandState.superscript || ancestorState.superscript,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
setActiveButtonsIfChanged(activeButtons) {
|
|
785
|
+
let update = true;
|
|
786
|
+
if (this.lastSetActiveButtons !== null) {
|
|
787
|
+
update = false;
|
|
788
|
+
for (var style in activeButtons) {
|
|
789
|
+
if (activeButtons[style] !== this.lastSetActiveButtons[style]) {
|
|
790
|
+
update = true;
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (update) {
|
|
797
|
+
this.lastSetActiveButtons = activeButtons;
|
|
798
|
+
this.setState({ activeButtons: activeButtons });
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
getInlineStyleAncestor(style) {
|
|
803
|
+
const tags = INLINE_STYLE_TAGS[style];
|
|
804
|
+
if (!tags) return null;
|
|
805
|
+
|
|
806
|
+
const selection = document.getSelection();
|
|
807
|
+
if (!selection || selection.rangeCount === 0) return null;
|
|
808
|
+
|
|
809
|
+
let node = selection.getRangeAt(0).startContainer;
|
|
810
|
+
if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;
|
|
811
|
+
|
|
812
|
+
while (node && node !== this.editableDiv) {
|
|
813
|
+
if (
|
|
814
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
815
|
+
tags.includes(node.nodeName.toLowerCase())
|
|
816
|
+
) {
|
|
817
|
+
return node;
|
|
818
|
+
}
|
|
819
|
+
node = node.parentNode;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
isRangeAtEndOfNode(range, node) {
|
|
826
|
+
try {
|
|
827
|
+
const afterRange = document.createRange();
|
|
828
|
+
afterRange.setStart(range.startContainer, range.startOffset);
|
|
829
|
+
afterRange.setEnd(node, node.childNodes.length);
|
|
830
|
+
const textAfterCaret = afterRange
|
|
831
|
+
.toString()
|
|
832
|
+
.replace(/[\s\u00a0\u200b\ufeff]/g, "");
|
|
833
|
+
if (textAfterCaret === "") return true;
|
|
834
|
+
} catch (error) {
|
|
835
|
+
// Fall back to boundary comparison below if the browser rejects the range.
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const nodeEndRange = document.createRange();
|
|
839
|
+
nodeEndRange.selectNodeContents(node);
|
|
840
|
+
nodeEndRange.collapse(false);
|
|
841
|
+
return range.compareBoundaryPoints(Range.START_TO_START, nodeEndRange) === 0;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
moveTrailingWhitespaceOutsideInlineStyle(style, command) {
|
|
845
|
+
const selection = document.getSelection();
|
|
846
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
847
|
+
|
|
848
|
+
const range = selection.getRangeAt(0);
|
|
849
|
+
if (!range.collapsed) return false;
|
|
850
|
+
|
|
851
|
+
const ancestor = this.getInlineStyleAncestor(style);
|
|
852
|
+
if (!ancestor || !this.isRangeAtEndOfNode(range, ancestor)) return false;
|
|
853
|
+
if (range.startContainer.nodeType !== Node.TEXT_NODE) return false;
|
|
854
|
+
|
|
855
|
+
const textNode = range.startContainer;
|
|
856
|
+
const text = textNode.nodeValue || "";
|
|
857
|
+
let moveStart = range.startOffset;
|
|
858
|
+
while (moveStart > 0) {
|
|
859
|
+
const char = text.charAt(moveStart - 1);
|
|
860
|
+
if (char !== " " && char !== "\u00a0") break;
|
|
861
|
+
moveStart -= 1;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (moveStart === range.startOffset) return false;
|
|
865
|
+
|
|
866
|
+
const movedText = text.substring(moveStart, range.startOffset);
|
|
867
|
+
textNode.nodeValue =
|
|
868
|
+
text.substring(0, moveStart) + text.substring(range.startOffset);
|
|
869
|
+
|
|
870
|
+
let outsideNode = ancestor.nextSibling;
|
|
871
|
+
if (!outsideNode || outsideNode.nodeType !== Node.TEXT_NODE) {
|
|
872
|
+
outsideNode = document.createTextNode(movedText);
|
|
873
|
+
ancestor.parentNode.insertBefore(outsideNode, ancestor.nextSibling);
|
|
874
|
+
} else {
|
|
875
|
+
outsideNode.nodeValue = movedText + outsideNode.nodeValue;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const nextRange = document.createRange();
|
|
879
|
+
nextRange.setStart(outsideNode, movedText.length);
|
|
880
|
+
nextRange.collapse(true);
|
|
881
|
+
selection.removeAllRanges();
|
|
882
|
+
selection.addRange(nextRange);
|
|
883
|
+
|
|
884
|
+
try {
|
|
885
|
+
if (document.queryCommandState(command)) {
|
|
886
|
+
document.execCommand(command, false, null);
|
|
887
|
+
}
|
|
888
|
+
} catch (error) {
|
|
889
|
+
// DOM position is the source of truth here; command state is best effort.
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const rangeParams = this._getRangeParams();
|
|
893
|
+
if (rangeParams) {
|
|
894
|
+
this.setOldRangeParams(rangeParams);
|
|
895
|
+
this.applyCurrentEditableDomToComponent(rangeParams);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
debugMathRichInput("moveTrailingWhitespaceOutsideInlineStyle:after", {
|
|
899
|
+
style,
|
|
900
|
+
command,
|
|
901
|
+
movedText,
|
|
902
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
903
|
+
rangeParams,
|
|
904
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
905
|
+
});
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
applyCurrentEditableDomToComponent(rangeParams = null) {
|
|
910
|
+
if (!this.editableDiv) return;
|
|
911
|
+
if (rangeParams === null || rangeParams === undefined) {
|
|
912
|
+
rangeParams = this._getRangeParams();
|
|
913
|
+
}
|
|
914
|
+
if (rangeParams === null || rangeParams === undefined) return;
|
|
915
|
+
|
|
916
|
+
let rawText = elementToMarkedRawText(
|
|
917
|
+
this.editableDiv,
|
|
918
|
+
null,
|
|
919
|
+
0,
|
|
920
|
+
this.enableHtml()
|
|
921
|
+
);
|
|
922
|
+
rawText = this.stripEmptyInlineFormattingTags(rawText);
|
|
923
|
+
|
|
924
|
+
const nodeCounts = this._countNodeTypes();
|
|
925
|
+
const hasMath = /<math>/i.test(rawText) || nodeCounts.math > 0;
|
|
926
|
+
const hasHtml = nodeCounts.html > 0 || /<[^>]+>/i.test(rawText);
|
|
927
|
+
const mimeType = this.getMimeType(hasHtml, hasMath);
|
|
928
|
+
|
|
929
|
+
rawText = removeEncodingIfPlainText(
|
|
930
|
+
rawText,
|
|
931
|
+
mimeType,
|
|
932
|
+
this.enableHtml()
|
|
933
|
+
);
|
|
934
|
+
rawText = this.stripEmptyInlineFormattingTags(rawText);
|
|
935
|
+
|
|
936
|
+
this.applyChangesToComponent(
|
|
937
|
+
rawText,
|
|
938
|
+
mimeType,
|
|
939
|
+
rangeParams,
|
|
940
|
+
this.props.useExpertMode,
|
|
941
|
+
this.props.selectedTab,
|
|
942
|
+
{ skipControlledRender: true }
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
395
946
|
_getRangeParams(editableDiv = null) {
|
|
396
947
|
if (editableDiv === null) editableDiv = this.editableDiv;
|
|
397
948
|
let rangeParams = getRangeParams(editableDiv);
|
|
949
|
+
debugMathRichInput("MathRichInput:_getRangeParams", {
|
|
950
|
+
rangeParams,
|
|
951
|
+
selection: getSelectionSnapshot(editableDiv),
|
|
952
|
+
});
|
|
398
953
|
return rangeParams;
|
|
399
954
|
}
|
|
400
955
|
|
|
401
956
|
_setRangeParams = (params) => {
|
|
402
957
|
try {
|
|
958
|
+
debugMathRichInput("MathRichInput:_setRangeParams:start", {
|
|
959
|
+
params,
|
|
960
|
+
selectionBefore: getSelectionSnapshot(this.editableDiv),
|
|
961
|
+
});
|
|
403
962
|
setRangeParams(this.editableDiv, params);
|
|
404
963
|
this.setOldRangeParams(params);
|
|
405
|
-
this.
|
|
964
|
+
this.lastSetRangeParams = params;
|
|
965
|
+
debugMathRichInput("MathRichInput:_setRangeParams:after", {
|
|
966
|
+
params,
|
|
967
|
+
oldRangeParams: this.oldRangeParams,
|
|
968
|
+
selectionAfter: getSelectionSnapshot(this.editableDiv),
|
|
969
|
+
});
|
|
406
970
|
} catch (error) {
|
|
407
971
|
console.error(error);
|
|
408
972
|
// console.log(this.editableDiv.childNodes);
|
|
409
973
|
}
|
|
410
974
|
};
|
|
411
975
|
|
|
976
|
+
restoreRangeAfterBrowserWork = (params, reason) => {
|
|
977
|
+
if (params === null || params === undefined) return;
|
|
978
|
+
const restore = (delay) => {
|
|
979
|
+
window.setTimeout(() => {
|
|
980
|
+
if (this.state.showEquationEditor) return;
|
|
981
|
+
if (!this.state.hasFocus && document.activeElement !== this.editableDiv) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
debugMathRichInput("restoreRangeAfterBrowserWork", {
|
|
985
|
+
reason,
|
|
986
|
+
delay,
|
|
987
|
+
params,
|
|
988
|
+
selectionBefore: getSelectionSnapshot(this.editableDiv),
|
|
989
|
+
});
|
|
990
|
+
this._setRangeParams(params);
|
|
991
|
+
}, delay);
|
|
992
|
+
};
|
|
993
|
+
restore(0);
|
|
994
|
+
restore(50);
|
|
995
|
+
};
|
|
996
|
+
|
|
412
997
|
_findNodeWithIndex(index) {
|
|
413
998
|
return findNodeWithIndex(this.editableDiv, index);
|
|
414
999
|
}
|
|
@@ -596,7 +1181,11 @@ export default class MathRichInput extends React.Component {
|
|
|
596
1181
|
// Check if word navigation modifier is pressed
|
|
597
1182
|
isWordModifier = (e) => {
|
|
598
1183
|
const { isMac } = this.getPlatformModifiers();
|
|
599
|
-
|
|
1184
|
+
// Borough production is pinned to math-rich-input 0.1.48, where Option +
|
|
1185
|
+
// Arrow did not get custom word navigation. Keep Mac Option + Arrow as
|
|
1186
|
+
// normal character movement to preserve the editor's existing UX; Command +
|
|
1187
|
+
// Arrow is handled by isLineModifier below.
|
|
1188
|
+
return isMac ? false : e.ctrlKey;
|
|
600
1189
|
};
|
|
601
1190
|
|
|
602
1191
|
// Check if line navigation modifier is pressed
|
|
@@ -1160,6 +1749,17 @@ export default class MathRichInput extends React.Component {
|
|
|
1160
1749
|
|
|
1161
1750
|
handleKeyDown = (e) => {
|
|
1162
1751
|
try {
|
|
1752
|
+
debugMathRichInput("handleKeyDown:start", {
|
|
1753
|
+
key: e.key,
|
|
1754
|
+
altKey: e.altKey,
|
|
1755
|
+
metaKey: e.metaKey,
|
|
1756
|
+
ctrlKey: e.ctrlKey,
|
|
1757
|
+
shiftKey: e.shiftKey,
|
|
1758
|
+
hasFocus: this.state.hasFocus,
|
|
1759
|
+
propsValue: this.props.value,
|
|
1760
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
1761
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
1762
|
+
});
|
|
1163
1763
|
// Lets set up or update the undo redo history
|
|
1164
1764
|
this.initialiseHistory();
|
|
1165
1765
|
|
|
@@ -1246,7 +1846,8 @@ export default class MathRichInput extends React.Component {
|
|
|
1246
1846
|
}
|
|
1247
1847
|
}
|
|
1248
1848
|
|
|
1249
|
-
|
|
1849
|
+
const { isMac } = this.getPlatformModifiers();
|
|
1850
|
+
if (!isMac && e.altKey && e.key === "Alt") {
|
|
1250
1851
|
let key = this.fetchAccentLetter();
|
|
1251
1852
|
if (
|
|
1252
1853
|
key === "a" ||
|
|
@@ -1262,7 +1863,9 @@ export default class MathRichInput extends React.Component {
|
|
|
1262
1863
|
key === "!"
|
|
1263
1864
|
) {
|
|
1264
1865
|
// console.log("showing/hiding accent bar");
|
|
1265
|
-
this.setState({
|
|
1866
|
+
this.setState({
|
|
1867
|
+
showAccentBar: !this.state.showAccentBar,
|
|
1868
|
+
});
|
|
1266
1869
|
} else {
|
|
1267
1870
|
// console.log("hiding accent bar");
|
|
1268
1871
|
this.setState({ showAccentBar: false });
|
|
@@ -1272,7 +1875,9 @@ export default class MathRichInput extends React.Component {
|
|
|
1272
1875
|
}
|
|
1273
1876
|
|
|
1274
1877
|
this.keyPressed = e.key; // store for use in methods
|
|
1275
|
-
this.
|
|
1878
|
+
if (this.state.showAccentBar) {
|
|
1879
|
+
this.setState({ keyPressed: e.key });
|
|
1880
|
+
}
|
|
1276
1881
|
|
|
1277
1882
|
// Hide the accent bar if a key is typed except when it is an automatic key repeat press
|
|
1278
1883
|
// If it is automatic key repeat, then keyPressStartTime will be greater than 0
|
|
@@ -1291,10 +1896,16 @@ export default class MathRichInput extends React.Component {
|
|
|
1291
1896
|
}
|
|
1292
1897
|
if (e.key === "ArrowLeft") {
|
|
1293
1898
|
this.handleKeyDownArrowLeft(e);
|
|
1899
|
+
debugMathRichInput("handleKeyDown:after-arrow-left", {
|
|
1900
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
1901
|
+
});
|
|
1294
1902
|
return;
|
|
1295
1903
|
}
|
|
1296
1904
|
if (e.key === "ArrowRight") {
|
|
1297
1905
|
this.handleKeyDownArrowRight(e);
|
|
1906
|
+
debugMathRichInput("handleKeyDown:after-arrow-right", {
|
|
1907
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
1908
|
+
});
|
|
1298
1909
|
return;
|
|
1299
1910
|
}
|
|
1300
1911
|
if (e.key === "ArrowUp") {
|
|
@@ -1403,6 +2014,14 @@ export default class MathRichInput extends React.Component {
|
|
|
1403
2014
|
|
|
1404
2015
|
handleKeyUp = (e) => {
|
|
1405
2016
|
try {
|
|
2017
|
+
debugMathRichInput("handleKeyUp:start", {
|
|
2018
|
+
key: e.key,
|
|
2019
|
+
altKey: e.altKey,
|
|
2020
|
+
metaKey: e.metaKey,
|
|
2021
|
+
ctrlKey: e.ctrlKey,
|
|
2022
|
+
shiftKey: e.shiftKey,
|
|
2023
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2024
|
+
});
|
|
1406
2025
|
this.keyPressed = e.key;
|
|
1407
2026
|
this.keyPressStartTime = -1;
|
|
1408
2027
|
|
|
@@ -1491,6 +2110,17 @@ export default class MathRichInput extends React.Component {
|
|
|
1491
2110
|
// console.log("handleInput")
|
|
1492
2111
|
if (this.isComposing) return;
|
|
1493
2112
|
|
|
2113
|
+
debugMathRichInput("handleInput:start", {
|
|
2114
|
+
inputType: event && event.nativeEvent ? event.nativeEvent.inputType : null,
|
|
2115
|
+
data: event && event.nativeEvent ? event.nativeEvent.data : null,
|
|
2116
|
+
propsValue: this.props.value,
|
|
2117
|
+
propsMimeType: this.props.mimeType,
|
|
2118
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
2119
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
this.cleanupEmptyInlineFormattingElements();
|
|
2123
|
+
|
|
1494
2124
|
let raw_text = elementToMarkedRawText(
|
|
1495
2125
|
this.editableDiv,
|
|
1496
2126
|
null,
|
|
@@ -1498,6 +2128,11 @@ export default class MathRichInput extends React.Component {
|
|
|
1498
2128
|
this.enableHtml()
|
|
1499
2129
|
);
|
|
1500
2130
|
const params = this._getRangeParams();
|
|
2131
|
+
debugMathRichInput("handleInput:after-range", {
|
|
2132
|
+
raw_text,
|
|
2133
|
+
params,
|
|
2134
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2135
|
+
});
|
|
1501
2136
|
if (params === null || params === undefined) {
|
|
1502
2137
|
// console.log(
|
|
1503
2138
|
// "handleInput: Empty content detected, using default range params"
|
|
@@ -1528,12 +2163,14 @@ export default class MathRichInput extends React.Component {
|
|
|
1528
2163
|
mimeType,
|
|
1529
2164
|
this.enableHtml()
|
|
1530
2165
|
);
|
|
2166
|
+
raw_text = this.stripEmptyInlineFormattingTags(raw_text);
|
|
1531
2167
|
this.applyChangesToComponent(
|
|
1532
2168
|
raw_text,
|
|
1533
2169
|
mimeType,
|
|
1534
2170
|
defaultParams,
|
|
1535
2171
|
this.props.useExpertMode,
|
|
1536
|
-
this.props.selectedTab
|
|
2172
|
+
this.props.selectedTab,
|
|
2173
|
+
{ skipControlledRender: true }
|
|
1537
2174
|
);
|
|
1538
2175
|
return;
|
|
1539
2176
|
} else if (
|
|
@@ -1564,23 +2201,33 @@ export default class MathRichInput extends React.Component {
|
|
|
1564
2201
|
this.enableHtml()
|
|
1565
2202
|
);
|
|
1566
2203
|
|
|
1567
|
-
|
|
1568
|
-
const validParams = params || {
|
|
1569
|
-
startNodeIndex: 0,
|
|
1570
|
-
startOffset: 0,
|
|
1571
|
-
endNodeIndex: 0,
|
|
1572
|
-
endOffset: 0,
|
|
1573
|
-
startGlobalOffset: 0,
|
|
1574
|
-
endGlobalOffset: 0,
|
|
1575
|
-
};
|
|
2204
|
+
raw_text = this.stripEmptyInlineFormattingTags(raw_text);
|
|
1576
2205
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
2206
|
+
// Use valid params or fallback to defaults
|
|
2207
|
+
const validParams = params || {
|
|
2208
|
+
startNodeIndex: 0,
|
|
2209
|
+
startOffset: 0,
|
|
2210
|
+
endNodeIndex: 0,
|
|
2211
|
+
endOffset: 0,
|
|
2212
|
+
startGlobalOffset: 0,
|
|
2213
|
+
endGlobalOffset: 0,
|
|
2214
|
+
};
|
|
2215
|
+
this.setOldRangeParams(validParams);
|
|
2216
|
+
|
|
2217
|
+
this.applyChangesToComponent(
|
|
2218
|
+
raw_text,
|
|
2219
|
+
mimeType,
|
|
1580
2220
|
validParams,
|
|
1581
2221
|
this.props.useExpertMode,
|
|
1582
|
-
this.props.selectedTab
|
|
2222
|
+
this.props.selectedTab,
|
|
2223
|
+
{ skipControlledRender: true }
|
|
1583
2224
|
);
|
|
2225
|
+
debugMathRichInput("handleInput:after-apply", {
|
|
2226
|
+
raw_text,
|
|
2227
|
+
mimeType,
|
|
2228
|
+
validParams,
|
|
2229
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2230
|
+
});
|
|
1584
2231
|
} catch (error) {
|
|
1585
2232
|
console.error(error);
|
|
1586
2233
|
}
|
|
@@ -1622,46 +2269,8 @@ export default class MathRichInput extends React.Component {
|
|
|
1622
2269
|
this.setState({ activeButtons: activeButtons });
|
|
1623
2270
|
return;
|
|
1624
2271
|
}
|
|
1625
|
-
let
|
|
1626
|
-
|
|
1627
|
-
let italic = false;
|
|
1628
|
-
let underline = false;
|
|
1629
|
-
let superscript = false;
|
|
1630
|
-
let subscript = false;
|
|
1631
|
-
do {
|
|
1632
|
-
if (node.nodeType === 1) {
|
|
1633
|
-
let nodeName = node.nodeName.toLowerCase();
|
|
1634
|
-
if (nodeName === "b" || nodeName === "strong") bold = true;
|
|
1635
|
-
else if (nodeName === "i" || nodeName === "em") italic = true;
|
|
1636
|
-
else if (nodeName === "u") underline = true;
|
|
1637
|
-
else if (nodeName === "sub") subscript = true;
|
|
1638
|
-
else if (nodeName === "sup") superscript = true;
|
|
1639
|
-
} else if (
|
|
1640
|
-
node.classList !== null &&
|
|
1641
|
-
node.classList !== undefined &&
|
|
1642
|
-
node.classList.contains("MathRichInput")
|
|
1643
|
-
)
|
|
1644
|
-
break;
|
|
1645
|
-
node = node.parentNode;
|
|
1646
|
-
} while (node !== null);
|
|
1647
|
-
|
|
1648
|
-
let activeButtons = { bold, italic, underline, subscript, superscript };
|
|
1649
|
-
// console.log(activeButtons)
|
|
1650
|
-
|
|
1651
|
-
let update = true;
|
|
1652
|
-
if (this.lastSetActiveButtons !== null) {
|
|
1653
|
-
update = false;
|
|
1654
|
-
for (var style in activeButtons) {
|
|
1655
|
-
if (activeButtons[style] !== this.lastSetActiveButtons[style]) {
|
|
1656
|
-
update = true;
|
|
1657
|
-
break;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
if (update) {
|
|
1662
|
-
this.lastSetActiveButtons = activeButtons;
|
|
1663
|
-
this.setState({ activeButtons: activeButtons });
|
|
1664
|
-
}
|
|
2272
|
+
let activeButtons = this.getActiveButtonsFromSelection(rangeParams);
|
|
2273
|
+
this.setActiveButtonsIfChanged(activeButtons);
|
|
1665
2274
|
} catch (error) {
|
|
1666
2275
|
console.error(error);
|
|
1667
2276
|
}
|
|
@@ -1939,6 +2548,14 @@ export default class MathRichInput extends React.Component {
|
|
|
1939
2548
|
componentDidMount() {
|
|
1940
2549
|
try {
|
|
1941
2550
|
setupKatex();
|
|
2551
|
+
debugMathRichInput("componentDidMount", {
|
|
2552
|
+
propsValue: this.props.value,
|
|
2553
|
+
propsMimeType: this.props.mimeType,
|
|
2554
|
+
autofocus: this.props.autofocus,
|
|
2555
|
+
autoFocus: this.props.autoFocus,
|
|
2556
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
2557
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2558
|
+
});
|
|
1942
2559
|
|
|
1943
2560
|
this.editableDiv.addEventListener("keydown", this.handleKeyDown);
|
|
1944
2561
|
this.editableDiv.addEventListener("keyup", this.handleKeyUp);
|
|
@@ -1961,16 +2578,82 @@ export default class MathRichInput extends React.Component {
|
|
|
1961
2578
|
}
|
|
1962
2579
|
}
|
|
1963
2580
|
|
|
2581
|
+
shouldComponentUpdate(nextProps, nextState) {
|
|
2582
|
+
if (this.skipNextControlledValueRender === true) {
|
|
2583
|
+
const canSkip =
|
|
2584
|
+
nextProps.value === this.skipNextControlledValue &&
|
|
2585
|
+
nextState === this.state;
|
|
2586
|
+
|
|
2587
|
+
debugMathRichInput("shouldComponentUpdate:native-input-gate", {
|
|
2588
|
+
canSkip,
|
|
2589
|
+
nextValue: nextProps.value,
|
|
2590
|
+
skipNextControlledValue: this.skipNextControlledValue,
|
|
2591
|
+
currentPropsValue: this.props.value,
|
|
2592
|
+
stateChanged: nextState !== this.state,
|
|
2593
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
2594
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2595
|
+
});
|
|
2596
|
+
|
|
2597
|
+
this.skipNextControlledValueRender = false;
|
|
2598
|
+
this.skipNextControlledValue = null;
|
|
2599
|
+
|
|
2600
|
+
if (canSkip) return false;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
if (this.shouldSkipRedundantFocusedRender(nextProps, nextState)) {
|
|
2604
|
+
debugMathRichInput("shouldComponentUpdate:skip-redundant-focused-render", {
|
|
2605
|
+
nextValue: nextProps.value,
|
|
2606
|
+
currentPropsValue: this.props.value,
|
|
2607
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2608
|
+
});
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
return true;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
1964
2615
|
componentDidUpdate() {
|
|
1965
2616
|
try {
|
|
2617
|
+
debugMathRichInput("componentDidUpdate:start", {
|
|
2618
|
+
propsValue: this.props.value,
|
|
2619
|
+
propsMimeType: this.props.mimeType,
|
|
2620
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
2621
|
+
hasFocus: this.state.hasFocus,
|
|
2622
|
+
showEquationEditor: this.state.showEquationEditor,
|
|
2623
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
2624
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2625
|
+
});
|
|
1966
2626
|
if (
|
|
1967
2627
|
this.afterComponentUpdateData !== null &&
|
|
1968
2628
|
this.afterComponentUpdateData !== undefined &&
|
|
1969
2629
|
this.afterComponentUpdateData.value === this.props.value
|
|
1970
2630
|
) {
|
|
2631
|
+
debugMathRichInput("componentDidUpdate:restore-before", {
|
|
2632
|
+
rangeParams: this.afterComponentUpdateData.rangeParams,
|
|
2633
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2634
|
+
});
|
|
1971
2635
|
this._setRangeParams(this.afterComponentUpdateData.rangeParams);
|
|
1972
2636
|
this.afterComponentUpdateData = null;
|
|
1973
2637
|
this.updateActiveButtons();
|
|
2638
|
+
debugMathRichInput("componentDidUpdate:restore-after", {
|
|
2639
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
if (
|
|
2643
|
+
this.preserveNativeDomRangeParams !== null &&
|
|
2644
|
+
this.preserveNativeDomRangeParams !== undefined &&
|
|
2645
|
+
this.state.hasFocus === true
|
|
2646
|
+
) {
|
|
2647
|
+
const rangeParams = this.preserveNativeDomRangeParams;
|
|
2648
|
+
this.preserveNativeDomRangeParams = null;
|
|
2649
|
+
debugMathRichInput("componentDidUpdate:preserve-restore-before", {
|
|
2650
|
+
rangeParams,
|
|
2651
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2652
|
+
});
|
|
2653
|
+
this._setRangeParams(rangeParams);
|
|
2654
|
+
debugMathRichInput("componentDidUpdate:preserve-restore-after", {
|
|
2655
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2656
|
+
});
|
|
1974
2657
|
}
|
|
1975
2658
|
} catch (error) {
|
|
1976
2659
|
console.error(error);
|
|
@@ -1980,6 +2663,14 @@ export default class MathRichInput extends React.Component {
|
|
|
1980
2663
|
showEquationEditor = (moveCursorOnInsert = false, node = null) => {
|
|
1981
2664
|
try {
|
|
1982
2665
|
let rangeParams = this._getRangeParams();
|
|
2666
|
+
debugMathRichInput("showEquationEditor:start", {
|
|
2667
|
+
moveCursorOnInsert,
|
|
2668
|
+
nodeProvided: Boolean(node),
|
|
2669
|
+
rangeParams,
|
|
2670
|
+
oldRangeParams: this.oldRangeParams,
|
|
2671
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
2672
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2673
|
+
});
|
|
1983
2674
|
if (rangeParams.startNodeIndex < 0) {
|
|
1984
2675
|
console.error("Could not find position of cursor in node list");
|
|
1985
2676
|
|
|
@@ -2030,6 +2721,7 @@ export default class MathRichInput extends React.Component {
|
|
|
2030
2721
|
this.editingNode = null;
|
|
2031
2722
|
}
|
|
2032
2723
|
|
|
2724
|
+
this.setOldRangeParams(rangeParams);
|
|
2033
2725
|
this.markedRawText = elementToMarkedRawText(
|
|
2034
2726
|
this.editableDiv,
|
|
2035
2727
|
node,
|
|
@@ -2042,12 +2734,22 @@ export default class MathRichInput extends React.Component {
|
|
|
2042
2734
|
showEquationEditor: true,
|
|
2043
2735
|
equationEditorLatex: latex,
|
|
2044
2736
|
});
|
|
2737
|
+
debugMathRichInput("showEquationEditor:after-setState", {
|
|
2738
|
+
rangeParams,
|
|
2739
|
+
markedRawText: this.markedRawText,
|
|
2740
|
+
latex,
|
|
2741
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2742
|
+
});
|
|
2045
2743
|
} catch (error) {
|
|
2046
2744
|
console.error(error);
|
|
2047
2745
|
}
|
|
2048
2746
|
};
|
|
2049
2747
|
|
|
2050
2748
|
handleMouseDown = (event) => {
|
|
2749
|
+
this.isMouseDownInEditable = true;
|
|
2750
|
+
window.setTimeout(() => {
|
|
2751
|
+
this.isMouseDownInEditable = false;
|
|
2752
|
+
}, 0);
|
|
2051
2753
|
this.showEquationEditorOnMouseUp = false;
|
|
2052
2754
|
let node = null;
|
|
2053
2755
|
try {
|
|
@@ -2086,8 +2788,9 @@ export default class MathRichInput extends React.Component {
|
|
|
2086
2788
|
// Clear selection anchor on mouse click
|
|
2087
2789
|
this.selectionAnchor = null;
|
|
2088
2790
|
|
|
2089
|
-
|
|
2090
|
-
|
|
2791
|
+
let params = this._getRangeParams();
|
|
2792
|
+
this.setOldRangeParams(params);
|
|
2793
|
+
let startNode = this._findNodeWithIndex(params.startNodeIndex);
|
|
2091
2794
|
let endNode = this._findNodeWithIndex(params.endNodeIndex);
|
|
2092
2795
|
|
|
2093
2796
|
if (startNode === null || endNode === null) {
|
|
@@ -2153,6 +2856,15 @@ export default class MathRichInput extends React.Component {
|
|
|
2153
2856
|
handleEquationEditorInsert = (latex) => {
|
|
2154
2857
|
try {
|
|
2155
2858
|
// console.log("Inserting latex: "+latex)
|
|
2859
|
+
debugMathRichInput("handleEquationEditorInsert:start", {
|
|
2860
|
+
latex,
|
|
2861
|
+
markedRawText: this.markedRawText,
|
|
2862
|
+
oldRangeParams: this.getOldRangeParams(),
|
|
2863
|
+
moveCursorOnInsert: this.moveCursorOnInsert,
|
|
2864
|
+
propsValue: this.props.value,
|
|
2865
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
2866
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2867
|
+
});
|
|
2156
2868
|
|
|
2157
2869
|
let start_pos = this.markedRawText.indexOf(MARK);
|
|
2158
2870
|
let end_pos = this.markedRawText.indexOf(MARK, start_pos + MARK.length);
|
|
@@ -2205,15 +2917,19 @@ export default class MathRichInput extends React.Component {
|
|
|
2205
2917
|
this.enableHtml()
|
|
2206
2918
|
);
|
|
2207
2919
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2920
|
+
this.applyChangesToComponent(
|
|
2921
|
+
new_raw_text,
|
|
2922
|
+
mimeType,
|
|
2923
|
+
new_rangeParams,
|
|
2924
|
+
this.props.useExpertMode,
|
|
2925
|
+
this.props.selectedTab
|
|
2926
|
+
);
|
|
2927
|
+
this.setState({ showEquationEditor: false }, () => {
|
|
2928
|
+
this.editableDiv.focus();
|
|
2929
|
+
this._setRangeParams(new_rangeParams);
|
|
2930
|
+
this.restoreRangeAfterBrowserWork(new_rangeParams, "equation-empty");
|
|
2931
|
+
});
|
|
2932
|
+
return;
|
|
2217
2933
|
}
|
|
2218
2934
|
|
|
2219
2935
|
// Handle case of latex returned, in which case we update the katex node
|
|
@@ -2235,25 +2951,42 @@ export default class MathRichInput extends React.Component {
|
|
|
2235
2951
|
endOffset: SMALL_SPACE_LENGTH,
|
|
2236
2952
|
};
|
|
2237
2953
|
} else {
|
|
2238
|
-
//
|
|
2239
|
-
//
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2954
|
+
// In this editor a rendered equation counts as one logical character
|
|
2955
|
+
// in global-offset space. Put the caret immediately after that math
|
|
2956
|
+
// character instead of guessing rendered child-node indexes.
|
|
2957
|
+
const oldRangeParams = this.getOldRangeParams();
|
|
2958
|
+
if (
|
|
2959
|
+
oldRangeParams !== null &&
|
|
2960
|
+
oldRangeParams.startGlobalOffset !== null &&
|
|
2961
|
+
oldRangeParams.startGlobalOffset !== undefined
|
|
2962
|
+
) {
|
|
2963
|
+
const afterInsertedMathOffset = oldRangeParams.startGlobalOffset + 1;
|
|
2247
2964
|
new_rangeParams = {
|
|
2248
|
-
startGlobalOffset:
|
|
2249
|
-
endGlobalOffset:
|
|
2965
|
+
startGlobalOffset: afterInsertedMathOffset,
|
|
2966
|
+
endGlobalOffset: afterInsertedMathOffset,
|
|
2967
|
+
};
|
|
2968
|
+
} else if (
|
|
2969
|
+
oldRangeParams !== null &&
|
|
2970
|
+
oldRangeParams.startNodeIndex !== null &&
|
|
2971
|
+
oldRangeParams.startNodeIndex !== undefined
|
|
2972
|
+
) {
|
|
2973
|
+
new_rangeParams = {
|
|
2974
|
+
startNodeIndex: oldRangeParams.startNodeIndex + 2,
|
|
2975
|
+
startOffset: SMALL_SPACE_LENGTH,
|
|
2976
|
+
endNodeIndex: oldRangeParams.startNodeIndex + 2,
|
|
2977
|
+
endOffset: SMALL_SPACE_LENGTH,
|
|
2250
2978
|
};
|
|
2251
2979
|
}
|
|
2252
|
-
// If
|
|
2980
|
+
// If old range params are unavailable, keep the current fallback.
|
|
2253
2981
|
}
|
|
2254
2982
|
}
|
|
2255
2983
|
// console.log("New cursor pos: "+new_rangeParams.startNodeIndex+"->"+new_rangeParams.startOffset)
|
|
2256
2984
|
this.setOldRangeParams(new_rangeParams);
|
|
2985
|
+
debugMathRichInput("handleEquationEditorInsert:before-apply", {
|
|
2986
|
+
new_raw_text,
|
|
2987
|
+
new_rangeParams,
|
|
2988
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
2989
|
+
});
|
|
2257
2990
|
|
|
2258
2991
|
let nodeCounts = this._countNodeTypes();
|
|
2259
2992
|
let mimeType = this.getMimeType(nodeCounts.html > 0, true);
|
|
@@ -2270,7 +3003,15 @@ export default class MathRichInput extends React.Component {
|
|
|
2270
3003
|
this.props.useExpertMode,
|
|
2271
3004
|
this.props.selectedTab
|
|
2272
3005
|
);
|
|
2273
|
-
|
|
3006
|
+
this.setState({ showEquationEditor: false }, () => {
|
|
3007
|
+
this.editableDiv.focus();
|
|
3008
|
+
this._setRangeParams(new_rangeParams);
|
|
3009
|
+
this.restoreRangeAfterBrowserWork(new_rangeParams, "equation-insert");
|
|
3010
|
+
debugMathRichInput("handleEquationEditorInsert:after-close-restore", {
|
|
3011
|
+
new_rangeParams,
|
|
3012
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3013
|
+
});
|
|
3014
|
+
});
|
|
2274
3015
|
} catch (error) {
|
|
2275
3016
|
console.error(error);
|
|
2276
3017
|
}
|
|
@@ -2288,16 +3029,37 @@ export default class MathRichInput extends React.Component {
|
|
|
2288
3029
|
styleText = (event, style) => {
|
|
2289
3030
|
try {
|
|
2290
3031
|
style = style.toLowerCase();
|
|
3032
|
+
const command = this.getStyleCommand(style);
|
|
3033
|
+
const activeBefore = this.getActiveButtonsFromSelection();
|
|
3034
|
+
const selection = document.getSelection();
|
|
3035
|
+
const isCollapsed =
|
|
3036
|
+
selection === null ||
|
|
3037
|
+
selection.rangeCount === 0 ||
|
|
3038
|
+
selection.getRangeAt(0).collapsed;
|
|
2291
3039
|
let success = false;
|
|
3040
|
+
let insertedTypingStylePlaceholder = false;
|
|
2292
3041
|
|
|
2293
3042
|
// This first part is a workaround whereby executing subscript or superscript
|
|
2294
3043
|
// within an existing subscript or superscript makes the text even smaller instead
|
|
2295
3044
|
// of toggling it off. Thus we manually toggle it off with some jigerry pokery!
|
|
2296
3045
|
if (
|
|
2297
|
-
|
|
2298
|
-
|
|
3046
|
+
isCollapsed &&
|
|
3047
|
+
activeBefore[style] &&
|
|
3048
|
+
this.moveTrailingWhitespaceOutsideInlineStyle(style, command)
|
|
3049
|
+
) {
|
|
3050
|
+
success = true;
|
|
3051
|
+
} else if (
|
|
3052
|
+
isCollapsed &&
|
|
3053
|
+
!activeBefore[style] &&
|
|
3054
|
+
selection &&
|
|
3055
|
+
selection.rangeCount > 0
|
|
3056
|
+
) {
|
|
3057
|
+
success = this.insertInlineStylePlaceholder(style);
|
|
3058
|
+
insertedTypingStylePlaceholder = success;
|
|
3059
|
+
} else if (
|
|
3060
|
+
(style === "subscript" && activeBefore.subscript) ||
|
|
3061
|
+
(style === "superscript" && activeBefore.superscript)
|
|
2299
3062
|
) {
|
|
2300
|
-
const selection = document.getSelection();
|
|
2301
3063
|
let range = selection.getRangeAt(0);
|
|
2302
3064
|
if (
|
|
2303
3065
|
range.endContainer === range.startContainer &&
|
|
@@ -2309,90 +3071,29 @@ export default class MathRichInput extends React.Component {
|
|
|
2309
3071
|
selection.addRange(range);
|
|
2310
3072
|
}
|
|
2311
3073
|
success = document.execCommand("removeFormat", false, null);
|
|
2312
|
-
if (
|
|
3074
|
+
if (activeBefore.bold)
|
|
2313
3075
|
success = document.execCommand("bold", false, null);
|
|
2314
|
-
if (
|
|
3076
|
+
if (activeBefore.italic)
|
|
2315
3077
|
success = document.execCommand("italic", false, null);
|
|
2316
|
-
if (
|
|
3078
|
+
if (activeBefore.underline)
|
|
2317
3079
|
success = document.execCommand("underline", false, null);
|
|
2318
3080
|
} else {
|
|
2319
|
-
const selection = document.getSelection();
|
|
2320
3081
|
if (selection && selection.rangeCount > 0) {
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
if (range.collapsed) {
|
|
2324
|
-
// For cursor position (no selection), use insertHTML with styled wrapper
|
|
2325
|
-
// This creates a formatted element that will maintain styling for future typing
|
|
2326
|
-
const isToggleOff = this.state.activeButtons[style];
|
|
2327
|
-
|
|
2328
|
-
if (!isToggleOff) {
|
|
2329
|
-
// Turning style ON - insert a styled wrapper element
|
|
2330
|
-
let wrapperTag;
|
|
2331
|
-
switch (style) {
|
|
2332
|
-
case "bold":
|
|
2333
|
-
wrapperTag = "strong";
|
|
2334
|
-
break;
|
|
2335
|
-
case "italic":
|
|
2336
|
-
wrapperTag = "em";
|
|
2337
|
-
break;
|
|
2338
|
-
case "underline":
|
|
2339
|
-
wrapperTag = "u";
|
|
2340
|
-
break;
|
|
2341
|
-
case "subscript":
|
|
2342
|
-
wrapperTag = "sub";
|
|
2343
|
-
break;
|
|
2344
|
-
case "superscript":
|
|
2345
|
-
wrapperTag = "sup";
|
|
2346
|
-
break;
|
|
2347
|
-
default:
|
|
2348
|
-
wrapperTag = "span";
|
|
2349
|
-
break;
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
// Insert an invisible character wrapped in the styled element
|
|
2353
|
-
// This provides a "landing zone" for future typing
|
|
2354
|
-
const styledElement = `<${wrapperTag}>\u200B</${wrapperTag}>`;
|
|
2355
|
-
success = document.execCommand(
|
|
2356
|
-
"insertHTML",
|
|
2357
|
-
false,
|
|
2358
|
-
styledElement
|
|
2359
|
-
);
|
|
2360
|
-
|
|
2361
|
-
// Move cursor inside the styled element
|
|
2362
|
-
setTimeout(() => {
|
|
2363
|
-
const newSelection = document.getSelection();
|
|
2364
|
-
if (newSelection && newSelection.rangeCount > 0) {
|
|
2365
|
-
const newRange = newSelection.getRangeAt(0);
|
|
2366
|
-
const styledNode = newRange.startContainer.parentElement;
|
|
2367
|
-
if (
|
|
2368
|
-
styledNode &&
|
|
2369
|
-
styledNode.tagName.toLowerCase() === wrapperTag
|
|
2370
|
-
) {
|
|
2371
|
-
newRange.setStart(styledNode, 0);
|
|
2372
|
-
newRange.setEnd(styledNode, 0);
|
|
2373
|
-
newSelection.removeAllRanges();
|
|
2374
|
-
newSelection.addRange(newRange);
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
}, 0);
|
|
2378
|
-
} else {
|
|
2379
|
-
// Turning style OFF - just apply execCommand normally
|
|
2380
|
-
success = document.execCommand(style, false, null);
|
|
2381
|
-
}
|
|
2382
|
-
} else {
|
|
2383
|
-
// For text selection, use regular execCommand
|
|
2384
|
-
success = document.execCommand(style, false, null);
|
|
2385
|
-
}
|
|
3082
|
+
success = document.execCommand(command, false, null);
|
|
2386
3083
|
}
|
|
2387
3084
|
}
|
|
2388
3085
|
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
3086
|
+
const rangeParams = this._getRangeParams();
|
|
3087
|
+
const removedEmptyTags =
|
|
3088
|
+
!insertedTypingStylePlaceholder &&
|
|
3089
|
+
this.cleanupEmptyInlineFormattingElements();
|
|
3090
|
+
if (removedEmptyTags && rangeParams !== null && rangeParams !== undefined) {
|
|
3091
|
+
this._setRangeParams(rangeParams);
|
|
3092
|
+
this.applyCurrentEditableDomToComponent(rangeParams);
|
|
3093
|
+
}
|
|
3094
|
+
this.setActiveButtonsIfChanged(
|
|
3095
|
+
this.getActiveButtonsFromSelection(rangeParams)
|
|
3096
|
+
);
|
|
2396
3097
|
return success;
|
|
2397
3098
|
} catch (error) {
|
|
2398
3099
|
console.error(error);
|
|
@@ -2401,8 +3102,15 @@ export default class MathRichInput extends React.Component {
|
|
|
2401
3102
|
|
|
2402
3103
|
handleToolbarButtonClick = (event, buttonName) => {
|
|
2403
3104
|
try {
|
|
3105
|
+
debugMathRichInput("handleToolbarButtonClick:start", {
|
|
3106
|
+
buttonName,
|
|
3107
|
+
eventType: event ? event.type : null,
|
|
3108
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3109
|
+
oldRangeParams: this.oldRangeParams,
|
|
3110
|
+
});
|
|
2404
3111
|
if (buttonName === "Equation") {
|
|
2405
3112
|
this.showEquationEditor(true);
|
|
3113
|
+
return;
|
|
2406
3114
|
}
|
|
2407
3115
|
let style = buttonName;
|
|
2408
3116
|
let success = this.styleText(event, style);
|
|
@@ -2751,10 +3459,10 @@ export default class MathRichInput extends React.Component {
|
|
|
2751
3459
|
}
|
|
2752
3460
|
};
|
|
2753
3461
|
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3462
|
+
insertAfterSmallSpace(new_char, rangeParams) {
|
|
3463
|
+
try {
|
|
3464
|
+
if (rangeParams === null || rangeParams === undefined)
|
|
3465
|
+
rangeParams = this._getRangeParams();
|
|
2758
3466
|
|
|
2759
3467
|
if (rangeParams.startOffset !== 0)
|
|
2760
3468
|
console.warn(
|
|
@@ -2763,29 +3471,41 @@ export default class MathRichInput extends React.Component {
|
|
|
2763
3471
|
" in insertAfterSmallSpace"
|
|
2764
3472
|
);
|
|
2765
3473
|
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
3474
|
+
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
3475
|
+
if (node === null || node === undefined || node.nodeValue === null) {
|
|
3476
|
+
console.warn("Could not find small-space text node");
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// The initial empty editor contains a hidden zero-width space. Handling
|
|
3481
|
+
// the first printable key by re-rendering through React replaces the
|
|
3482
|
+
// text node and drops Chrome's selection to the start under React 19.
|
|
3483
|
+
// Mutate the already-focused DOM node, then skip the matching controlled
|
|
3484
|
+
// render exactly like the normal contenteditable input path.
|
|
3485
|
+
node.nodeValue =
|
|
3486
|
+
node.nodeValue.substring(0, 1) +
|
|
3487
|
+
new_char +
|
|
3488
|
+
node.nodeValue.substring(1);
|
|
3489
|
+
|
|
3490
|
+
let new_rangeParams = {
|
|
3491
|
+
startNodeIndex: rangeParams.startNodeIndex,
|
|
3492
|
+
startOffset: 1 + new_char.length,
|
|
3493
|
+
endNodeIndex: rangeParams.startNodeIndex,
|
|
3494
|
+
endOffset: 1 + new_char.length,
|
|
3495
|
+
startGlobalOffset: rangeParams.startGlobalOffset + new_char.length,
|
|
3496
|
+
endGlobalOffset: rangeParams.endGlobalOffset + new_char.length,
|
|
3497
|
+
};
|
|
3498
|
+
this._setRangeParams(new_rangeParams);
|
|
3499
|
+
|
|
3500
|
+
let new_raw_text = elementToMarkedRawText(
|
|
3501
|
+
this.editableDiv,
|
|
3502
|
+
null,
|
|
3503
|
+
0,
|
|
3504
|
+
this.enableHtml()
|
|
3505
|
+
);
|
|
3506
|
+
|
|
3507
|
+
new_raw_text = removeEncodingIfPlainText(
|
|
3508
|
+
new_raw_text,
|
|
2789
3509
|
this.props.mimeType,
|
|
2790
3510
|
this.enableHtml()
|
|
2791
3511
|
);
|
|
@@ -2793,12 +3513,13 @@ export default class MathRichInput extends React.Component {
|
|
|
2793
3513
|
this.applyChangesToComponent(
|
|
2794
3514
|
new_raw_text,
|
|
2795
3515
|
this.props.mimeType,
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
3516
|
+
new_rangeParams,
|
|
3517
|
+
this.props.useExpertMode,
|
|
3518
|
+
this.props.selectedTab,
|
|
3519
|
+
{ skipControlledRender: true }
|
|
3520
|
+
);
|
|
3521
|
+
} catch (error) {
|
|
3522
|
+
console.error(error);
|
|
2802
3523
|
}
|
|
2803
3524
|
}
|
|
2804
3525
|
|
|
@@ -2895,12 +3616,39 @@ export default class MathRichInput extends React.Component {
|
|
|
2895
3616
|
|
|
2896
3617
|
handleFocus = (event) => {
|
|
2897
3618
|
const { onFocus } = this.props;
|
|
3619
|
+
let currentRangeParams = null;
|
|
3620
|
+
try {
|
|
3621
|
+
currentRangeParams = this._getRangeParams();
|
|
3622
|
+
} catch (error) {
|
|
3623
|
+
currentRangeParams = null;
|
|
3624
|
+
}
|
|
3625
|
+
const savedRangeParams = this.getOldRangeParams();
|
|
3626
|
+
const shouldRestoreSavedRange =
|
|
3627
|
+
!this.isMouseDownInEditable &&
|
|
3628
|
+
this.props.value !== "" &&
|
|
3629
|
+
this.rangeIsAtStart(currentRangeParams) &&
|
|
3630
|
+
this.rangeHasNonZeroPosition(savedRangeParams);
|
|
3631
|
+
debugMathRichInput("handleFocus:start", {
|
|
3632
|
+
propsValue: this.props.value,
|
|
3633
|
+
oldRangeParams: this.oldRangeParams,
|
|
3634
|
+
currentRangeParams,
|
|
3635
|
+
shouldRestoreSavedRange,
|
|
3636
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
3637
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3638
|
+
});
|
|
2898
3639
|
if (onFocus != null) {
|
|
2899
3640
|
onFocus();
|
|
2900
3641
|
}
|
|
2901
3642
|
try {
|
|
2902
|
-
this.
|
|
2903
|
-
|
|
3643
|
+
this.setState({ hasFocus: true }, () => {
|
|
3644
|
+
if (shouldRestoreSavedRange) {
|
|
3645
|
+
this._setRangeParams(savedRangeParams);
|
|
3646
|
+
this.restoreRangeAfterBrowserWork(savedRangeParams, "focus-restore");
|
|
3647
|
+
}
|
|
3648
|
+
debugMathRichInput("handleFocus:after-setState", {
|
|
3649
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3650
|
+
});
|
|
3651
|
+
});
|
|
2904
3652
|
} catch (error) {
|
|
2905
3653
|
console.error(error);
|
|
2906
3654
|
}
|
|
@@ -2908,12 +3656,31 @@ export default class MathRichInput extends React.Component {
|
|
|
2908
3656
|
|
|
2909
3657
|
handleBlur = (event) => {
|
|
2910
3658
|
const { onBlur } = this.props;
|
|
3659
|
+
let currentRangeParams = null;
|
|
3660
|
+
try {
|
|
3661
|
+
currentRangeParams = this._getRangeParams();
|
|
3662
|
+
if (this.rangeHasNonZeroPosition(currentRangeParams)) {
|
|
3663
|
+
this.setOldRangeParams(currentRangeParams);
|
|
3664
|
+
}
|
|
3665
|
+
} catch (error) {
|
|
3666
|
+
currentRangeParams = null;
|
|
3667
|
+
}
|
|
3668
|
+
debugMathRichInput("handleBlur:start", {
|
|
3669
|
+
propsValue: this.props.value,
|
|
3670
|
+
oldRangeParams: this.oldRangeParams,
|
|
3671
|
+
currentRangeParams,
|
|
3672
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
3673
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3674
|
+
});
|
|
2911
3675
|
if (onBlur != null) {
|
|
2912
3676
|
onBlur();
|
|
2913
3677
|
}
|
|
2914
3678
|
try {
|
|
2915
3679
|
// console.log("Hiding accent bar 4");
|
|
2916
3680
|
this.setState({ hasFocus: false, showAccentBar: false });
|
|
3681
|
+
debugMathRichInput("handleBlur:after-setState", {
|
|
3682
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3683
|
+
});
|
|
2917
3684
|
} catch (error) {
|
|
2918
3685
|
console.error(error);
|
|
2919
3686
|
}
|
|
@@ -3039,6 +3806,23 @@ export default class MathRichInput extends React.Component {
|
|
|
3039
3806
|
this.props.value || "",
|
|
3040
3807
|
this.props.mimeType
|
|
3041
3808
|
);
|
|
3809
|
+
const editableHtml = this.getEditableHtmlForRender(html);
|
|
3810
|
+
debugMathRichInput("render", {
|
|
3811
|
+
propsValue: this.props.value,
|
|
3812
|
+
propsMimeType: this.props.mimeType,
|
|
3813
|
+
html,
|
|
3814
|
+
editableHtml,
|
|
3815
|
+
state: {
|
|
3816
|
+
hasFocus: this.state.hasFocus,
|
|
3817
|
+
showEquationEditor: this.state.showEquationEditor,
|
|
3818
|
+
showAccentBar: this.state.showAccentBar,
|
|
3819
|
+
keyPressed: this.state.keyPressed,
|
|
3820
|
+
},
|
|
3821
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
3822
|
+
oldRangeParams: this.oldRangeParams,
|
|
3823
|
+
editableInnerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
3824
|
+
selection: getSelectionSnapshot(this.editableDiv),
|
|
3825
|
+
});
|
|
3042
3826
|
|
|
3043
3827
|
// Debug render math
|
|
3044
3828
|
// if (this.props.value && this.props.value.includes("<math>")) {
|
|
@@ -3103,7 +3887,7 @@ export default class MathRichInput extends React.Component {
|
|
|
3103
3887
|
onCompositionStart={this.handleCompositionStart}
|
|
3104
3888
|
onCompositionEnd={this.handleCompositionEnd}
|
|
3105
3889
|
dangerouslySetInnerHTML={{
|
|
3106
|
-
__html:
|
|
3890
|
+
__html: editableHtml,
|
|
3107
3891
|
}}
|
|
3108
3892
|
data-placeholder={this.props.placeholder || ""}
|
|
3109
3893
|
></span>
|