@zzish/math-rich-input 0.1.51 → 0.1.53
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 +1125 -192
- package/dist/standalone.js +1 -1
- package/package.json +3 -3
- package/src/MathRichArea.jsx +5 -2
- package/src/MathRichInput.jsx +1139 -237
- package/src/Toolbar.jsx +3 -4
- package/src/mathRichInputDebug.js +98 -0
- package/src/mathRichInputHelper.js +128 -17
package/dist/index.js
CHANGED
|
@@ -20283,9 +20283,6 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
|
|
|
20283
20283
|
var _this;
|
|
20284
20284
|
_classCallCheck(this, Toolbar);
|
|
20285
20285
|
_this = _callSuper(this, Toolbar, [props]);
|
|
20286
|
-
_defineProperty(_this, "handleButtonClick", function (event, buttonName) {
|
|
20287
|
-
_this.suppliedClickHandler(event, buttonName);
|
|
20288
|
-
});
|
|
20289
20286
|
_defineProperty(_this, "showAccentBar", function (event) {
|
|
20290
20287
|
if (!_this.state.showAccents) _this.accentLetter = _this.suppliedAccentLetterFetcher();
|
|
20291
20288
|
_this.setState({
|
|
@@ -20305,6 +20302,8 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
|
|
|
20305
20302
|
});
|
|
20306
20303
|
});
|
|
20307
20304
|
_defineProperty(_this, "handleButtonClick", function (event, buttonName) {
|
|
20305
|
+
event.preventDefault();
|
|
20306
|
+
event.stopPropagation();
|
|
20308
20307
|
_this.suppliedClickHandler(event, buttonName);
|
|
20309
20308
|
});
|
|
20310
20309
|
_defineProperty(_this, "handleAccentButtonClick", function (event, oldCharacter, newCharacter) {
|
|
@@ -20479,6 +20478,9 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
|
|
|
20479
20478
|
}, "\xE1"), enableMath && /*#__PURE__*/React__default["default"].createElement("button", {
|
|
20480
20479
|
name: "Equation",
|
|
20481
20480
|
className: "Toolbar-button TB-wide",
|
|
20481
|
+
onClick: function onClick(e) {
|
|
20482
|
+
return e.preventDefault();
|
|
20483
|
+
},
|
|
20482
20484
|
onMouseDown: function onMouseDown(e) {
|
|
20483
20485
|
return _this2.handleButtonClick(e, "Equation");
|
|
20484
20486
|
}
|
|
@@ -20668,6 +20670,73 @@ function determineMimeType(text) {
|
|
|
20668
20670
|
{this.test("$a bcd> e$", "application/x-tex")}
|
|
20669
20671
|
*/
|
|
20670
20672
|
|
|
20673
|
+
var DEBUG_MATH_RICH_INPUT = false;
|
|
20674
|
+
function getNodePath(node) {
|
|
20675
|
+
var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
20676
|
+
if (!node) return null;
|
|
20677
|
+
var parts = [];
|
|
20678
|
+
var current = node;
|
|
20679
|
+
while (current && current !== root && parts.length < 8) {
|
|
20680
|
+
if (current.nodeType === 3) {
|
|
20681
|
+
parts.unshift("#text(\"".concat(current.nodeValue, "\")"));
|
|
20682
|
+
} else {
|
|
20683
|
+
var name = current.nodeName ? current.nodeName.toLowerCase() : "unknown";
|
|
20684
|
+
var className = current.className && typeof current.className === "string" ? ".".concat(current.className.split(" ").filter(Boolean).join(".")) : "";
|
|
20685
|
+
parts.unshift("".concat(name).concat(className));
|
|
20686
|
+
}
|
|
20687
|
+
current = current.parentNode;
|
|
20688
|
+
}
|
|
20689
|
+
if (root) parts.unshift("root");
|
|
20690
|
+
return parts.join(" > ");
|
|
20691
|
+
}
|
|
20692
|
+
function describeNode(node) {
|
|
20693
|
+
var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
20694
|
+
if (!node) return null;
|
|
20695
|
+
return {
|
|
20696
|
+
nodeName: node.nodeName,
|
|
20697
|
+
nodeType: node.nodeType,
|
|
20698
|
+
text: node.nodeType === 3 && typeof node.nodeValue === "string" ? node.nodeValue : undefined,
|
|
20699
|
+
textLength: node.nodeType === 3 && typeof node.nodeValue === "string" ? node.nodeValue.length : undefined,
|
|
20700
|
+
childCount: node.childNodes ? node.childNodes.length : undefined,
|
|
20701
|
+
path: getNodePath(node, root)
|
|
20702
|
+
};
|
|
20703
|
+
}
|
|
20704
|
+
function getSelectionSnapshot() {
|
|
20705
|
+
var editableDiv = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
20706
|
+
if (typeof window === "undefined" || !window.getSelection) return null;
|
|
20707
|
+
var selection = window.getSelection();
|
|
20708
|
+
if (!selection) return null;
|
|
20709
|
+
var snapshot = {
|
|
20710
|
+
activeElement: typeof document !== "undefined" && document.activeElement ? {
|
|
20711
|
+
nodeName: document.activeElement.nodeName,
|
|
20712
|
+
className: document.activeElement.className
|
|
20713
|
+
} : null,
|
|
20714
|
+
rangeCount: selection.rangeCount,
|
|
20715
|
+
isCollapsed: selection.isCollapsed,
|
|
20716
|
+
anchorOffset: selection.anchorOffset,
|
|
20717
|
+
focusOffset: selection.focusOffset,
|
|
20718
|
+
anchorNode: describeNode(selection.anchorNode, editableDiv),
|
|
20719
|
+
focusNode: describeNode(selection.focusNode, editableDiv)
|
|
20720
|
+
};
|
|
20721
|
+
if (selection.rangeCount > 0) {
|
|
20722
|
+
var range = selection.getRangeAt(0);
|
|
20723
|
+
snapshot.range = {
|
|
20724
|
+
collapsed: range.collapsed,
|
|
20725
|
+
startOffset: range.startOffset,
|
|
20726
|
+
endOffset: range.endOffset,
|
|
20727
|
+
startContainer: describeNode(range.startContainer, editableDiv),
|
|
20728
|
+
endContainer: describeNode(range.endContainer, editableDiv)
|
|
20729
|
+
};
|
|
20730
|
+
}
|
|
20731
|
+
return snapshot;
|
|
20732
|
+
}
|
|
20733
|
+
function debugMathRichInput(label) {
|
|
20734
|
+
return;
|
|
20735
|
+
}
|
|
20736
|
+
if (typeof window !== "undefined") {
|
|
20737
|
+
window.__MRI_DEBUG__ = DEBUG_MATH_RICH_INPUT;
|
|
20738
|
+
}
|
|
20739
|
+
|
|
20671
20740
|
var RAW_TEXT_MATH_START_TAG_TEX = "\\[";
|
|
20672
20741
|
var RAW_TEXT_MATH_END_TAG_TEX = "\\]";
|
|
20673
20742
|
var RAW_TEXT_MATH_START_TAG_HTML_MATH = "<math>";
|
|
@@ -21309,6 +21378,55 @@ function findFirstTextNode(node) {
|
|
|
21309
21378
|
}
|
|
21310
21379
|
return null;
|
|
21311
21380
|
}
|
|
21381
|
+
function findLastTextNode(node) {
|
|
21382
|
+
if (isTextNode(node)) return node;
|
|
21383
|
+
var nodes = node.childNodes;
|
|
21384
|
+
for (var i = nodes.length - 1; i >= 0; i--) {
|
|
21385
|
+
var found_node = findLastTextNode(nodes[i]);
|
|
21386
|
+
if (found_node !== null) return found_node;
|
|
21387
|
+
}
|
|
21388
|
+
return null;
|
|
21389
|
+
}
|
|
21390
|
+
function getTextPositionFromElementRangeEndpoint(container, offset) {
|
|
21391
|
+
if (isTextNode(container)) return {
|
|
21392
|
+
node: container,
|
|
21393
|
+
offset: offset
|
|
21394
|
+
};
|
|
21395
|
+
var nodes = container.childNodes;
|
|
21396
|
+
if (nodes === null || nodes === undefined || nodes.length === 0) return null;
|
|
21397
|
+
|
|
21398
|
+
// A collapsed selection inside contentEditable may report the editable SPAN
|
|
21399
|
+
// itself as the container and the child index as the offset. The previous
|
|
21400
|
+
// implementation always picked the first descendant text node, which moved
|
|
21401
|
+
// the caret to the beginning after React re-rendered the controlled input.
|
|
21402
|
+
if (offset > 0) {
|
|
21403
|
+
var previousChild = nodes[Math.min(offset - 1, nodes.length - 1)];
|
|
21404
|
+
var previousTextNode = findLastTextNode(previousChild);
|
|
21405
|
+
if (previousTextNode !== null) {
|
|
21406
|
+
return {
|
|
21407
|
+
node: previousTextNode,
|
|
21408
|
+
offset: previousTextNode.nodeValue.length
|
|
21409
|
+
};
|
|
21410
|
+
}
|
|
21411
|
+
}
|
|
21412
|
+
if (offset < nodes.length) {
|
|
21413
|
+
var nextTextNode = findFirstTextNode(nodes[offset]);
|
|
21414
|
+
if (nextTextNode !== null) {
|
|
21415
|
+
return {
|
|
21416
|
+
node: nextTextNode,
|
|
21417
|
+
offset: 0
|
|
21418
|
+
};
|
|
21419
|
+
}
|
|
21420
|
+
}
|
|
21421
|
+
var fallbackTextNode = findFirstTextNode(container);
|
|
21422
|
+
if (fallbackTextNode !== null) {
|
|
21423
|
+
return {
|
|
21424
|
+
node: fallbackTextNode,
|
|
21425
|
+
offset: 0
|
|
21426
|
+
};
|
|
21427
|
+
}
|
|
21428
|
+
return null;
|
|
21429
|
+
}
|
|
21312
21430
|
function findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, globalOffset) {
|
|
21313
21431
|
var result = {
|
|
21314
21432
|
offset: 0
|
|
@@ -21350,6 +21468,10 @@ function findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, globalOffset
|
|
|
21350
21468
|
function getRangeParams(editableDiv) {
|
|
21351
21469
|
// console.log ("getRangeParams")
|
|
21352
21470
|
// console.log("Inner html: "+editableDiv.innerHTML)
|
|
21471
|
+
debugMathRichInput("getRangeParams:start", {
|
|
21472
|
+
innerHTML: editableDiv ? editableDiv.innerHTML : null,
|
|
21473
|
+
selection: getSelectionSnapshot(editableDiv)
|
|
21474
|
+
});
|
|
21353
21475
|
var isSupported = typeof window.getSelection !== "undefined";
|
|
21354
21476
|
if (!isSupported) {
|
|
21355
21477
|
console.error("Can't get range params, browser does not support window.getSelection");
|
|
@@ -21361,15 +21483,27 @@ function getRangeParams(editableDiv) {
|
|
|
21361
21483
|
return null;
|
|
21362
21484
|
}
|
|
21363
21485
|
var range = selection.getRangeAt(0);
|
|
21486
|
+
debugMathRichInput("getRangeParams:range-before-normalize", {
|
|
21487
|
+
startContainer: describeNode(range.startContainer, editableDiv),
|
|
21488
|
+
startOffset: range.startOffset,
|
|
21489
|
+
endContainer: describeNode(range.endContainer, editableDiv),
|
|
21490
|
+
endOffset: range.endOffset
|
|
21491
|
+
});
|
|
21364
21492
|
|
|
21365
21493
|
// logAllDescendants(editableDiv)
|
|
21366
|
-
// If the range is not within a text node, move it to
|
|
21367
|
-
//
|
|
21494
|
+
// If the range is not within a text node, move it to the text node implied
|
|
21495
|
+
// by the element-container offset. This typically happens when the browser
|
|
21496
|
+
// reports a collapsed contentEditable selection as (SPAN, childIndex).
|
|
21368
21497
|
if (range.startContainer.nodeType !== 3) {
|
|
21369
|
-
var
|
|
21370
|
-
if (
|
|
21371
|
-
|
|
21372
|
-
|
|
21498
|
+
var textPosition = getTextPositionFromElementRangeEndpoint(range.startContainer, range.startOffset);
|
|
21499
|
+
if (textPosition !== null) {
|
|
21500
|
+
debugMathRichInput("getRangeParams:normalize-start-element", {
|
|
21501
|
+
from: describeNode(range.startContainer, editableDiv),
|
|
21502
|
+
fromOffset: range.startOffset,
|
|
21503
|
+
to: describeNode(textPosition.node, editableDiv),
|
|
21504
|
+
toOffset: textPosition.offset
|
|
21505
|
+
});
|
|
21506
|
+
range.setStart(textPosition.node, textPosition.offset);
|
|
21373
21507
|
} else {
|
|
21374
21508
|
// Handle empty content case - return default range params
|
|
21375
21509
|
// This is normal behavior after selecting all and deleting content
|
|
@@ -21385,10 +21519,15 @@ function getRangeParams(editableDiv) {
|
|
|
21385
21519
|
}
|
|
21386
21520
|
}
|
|
21387
21521
|
if (range.endContainer.nodeType !== 3) {
|
|
21388
|
-
var
|
|
21389
|
-
if (
|
|
21390
|
-
|
|
21391
|
-
|
|
21522
|
+
var _textPosition = getTextPositionFromElementRangeEndpoint(range.endContainer, range.endOffset);
|
|
21523
|
+
if (_textPosition !== null) {
|
|
21524
|
+
debugMathRichInput("getRangeParams:normalize-end-element", {
|
|
21525
|
+
from: describeNode(range.endContainer, editableDiv),
|
|
21526
|
+
fromOffset: range.endOffset,
|
|
21527
|
+
to: describeNode(_textPosition.node, editableDiv),
|
|
21528
|
+
toOffset: _textPosition.offset
|
|
21529
|
+
});
|
|
21530
|
+
range.setEnd(_textPosition.node, _textPosition.offset);
|
|
21392
21531
|
} else {
|
|
21393
21532
|
// Handle empty content case - return default range params
|
|
21394
21533
|
// This is normal behavior after selecting all and deleting content
|
|
@@ -21434,11 +21573,20 @@ function getRangeParams(editableDiv) {
|
|
|
21434
21573
|
startGlobalOffset: startResult.offset,
|
|
21435
21574
|
endGlobalOffset: endResult.offset
|
|
21436
21575
|
};
|
|
21576
|
+
debugMathRichInput("getRangeParams:result", {
|
|
21577
|
+
rangeParams: rangeParams,
|
|
21578
|
+
selection: getSelectionSnapshot(editableDiv)
|
|
21579
|
+
});
|
|
21437
21580
|
return rangeParams;
|
|
21438
21581
|
}
|
|
21439
21582
|
function setRangeParamsFromGlobalOffsets(editableDiv, params) {
|
|
21440
21583
|
var new_range = null;
|
|
21441
21584
|
try {
|
|
21585
|
+
debugMathRichInput("setRangeParamsFromGlobalOffsets:start", {
|
|
21586
|
+
params: params,
|
|
21587
|
+
innerHTML: editableDiv ? editableDiv.innerHTML : null,
|
|
21588
|
+
selectionBefore: getSelectionSnapshot(editableDiv)
|
|
21589
|
+
});
|
|
21442
21590
|
if (params === null || params === undefined) throw new Error("Can't set range params: params === " + params);
|
|
21443
21591
|
var startResult = findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, params.startGlobalOffset);
|
|
21444
21592
|
var endResult = findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, params.endGlobalOffset);
|
|
@@ -21458,6 +21606,18 @@ function setRangeParamsFromGlobalOffsets(editableDiv, params) {
|
|
|
21458
21606
|
var selection = window.getSelection();
|
|
21459
21607
|
selection.removeAllRanges();
|
|
21460
21608
|
selection.addRange(new_range);
|
|
21609
|
+
debugMathRichInput("setRangeParamsFromGlobalOffsets:after", {
|
|
21610
|
+
params: params,
|
|
21611
|
+
startResult: {
|
|
21612
|
+
node: describeNode(startResult.node, editableDiv),
|
|
21613
|
+
offset: startResult.offset
|
|
21614
|
+
},
|
|
21615
|
+
endResult: {
|
|
21616
|
+
node: describeNode(endResult.node, editableDiv),
|
|
21617
|
+
offset: endResult.offset
|
|
21618
|
+
},
|
|
21619
|
+
selectionAfter: getSelectionSnapshot(editableDiv)
|
|
21620
|
+
});
|
|
21461
21621
|
} catch (error) {
|
|
21462
21622
|
console.error(error);
|
|
21463
21623
|
console.log("Trying to set range:");
|
|
@@ -21471,6 +21631,11 @@ function setRangeParams(editableDiv, params) {
|
|
|
21471
21631
|
|
|
21472
21632
|
var new_range = null;
|
|
21473
21633
|
try {
|
|
21634
|
+
debugMathRichInput("setRangeParams:start", {
|
|
21635
|
+
params: params,
|
|
21636
|
+
innerHTML: editableDiv ? editableDiv.innerHTML : null,
|
|
21637
|
+
selectionBefore: getSelectionSnapshot(editableDiv)
|
|
21638
|
+
});
|
|
21474
21639
|
if (params === null || params === undefined) {
|
|
21475
21640
|
console.error("Can't set range params: params === " + params);
|
|
21476
21641
|
return;
|
|
@@ -21530,6 +21695,7 @@ function setRangeParams(editableDiv, params) {
|
|
|
21530
21695
|
startNode = findFirstTextNode(editableDiv);
|
|
21531
21696
|
startOffset = 0;
|
|
21532
21697
|
}
|
|
21698
|
+
if (startNode === null) return;
|
|
21533
21699
|
if (!isTextNode(startNode)) {
|
|
21534
21700
|
console.warn("Start node found for range start is not a text a node");
|
|
21535
21701
|
console.log(editableDiv.childNodes);
|
|
@@ -21545,6 +21711,7 @@ function setRangeParams(editableDiv, params) {
|
|
|
21545
21711
|
endNode = findFirstTextNode(editableDiv);
|
|
21546
21712
|
endOffset = 0;
|
|
21547
21713
|
}
|
|
21714
|
+
if (endNode === null) return;
|
|
21548
21715
|
if (!isTextNode(endNode)) {
|
|
21549
21716
|
console.warn("End node found for range start is not a text a node");
|
|
21550
21717
|
console.log(editableDiv.childNodes);
|
|
@@ -21557,7 +21724,7 @@ function setRangeParams(editableDiv, params) {
|
|
|
21557
21724
|
}
|
|
21558
21725
|
new_range = document.createRange();
|
|
21559
21726
|
if (startOffset > startNode.nodeValue.length) {
|
|
21560
|
-
console.warn("endOffset > endNode.nodeValue.length when setting range: " + startOffset + " > " + startNode.nodeValue.length + " - " +
|
|
21727
|
+
console.warn("endOffset > endNode.nodeValue.length when setting range: " + startOffset + " > " + startNode.nodeValue.length + " - " + startNode.nodeType + " - " + startNode.nodeValue);
|
|
21561
21728
|
startOffset = startNode.nodeValue.length;
|
|
21562
21729
|
}
|
|
21563
21730
|
new_range.setStart(startNode, startOffset);
|
|
@@ -21572,6 +21739,14 @@ function setRangeParams(editableDiv, params) {
|
|
|
21572
21739
|
var selection = window.getSelection();
|
|
21573
21740
|
selection.removeAllRanges();
|
|
21574
21741
|
selection.addRange(new_range);
|
|
21742
|
+
debugMathRichInput("setRangeParams:after", {
|
|
21743
|
+
params: params,
|
|
21744
|
+
startNode: describeNode(startNode, editableDiv),
|
|
21745
|
+
startOffset: startOffset,
|
|
21746
|
+
endNode: describeNode(endNode, editableDiv),
|
|
21747
|
+
endOffset: endOffset,
|
|
21748
|
+
selectionAfter: getSelectionSnapshot(editableDiv)
|
|
21749
|
+
});
|
|
21575
21750
|
} catch (error) {
|
|
21576
21751
|
console.error(error);
|
|
21577
21752
|
console.log("Trying to set range:");
|
|
@@ -21798,6 +21973,29 @@ var SMALL_SPACE_LENGTH = SMALL_SPACE.length;
|
|
|
21798
21973
|
// const SMALL_SPACE_REG_EXP = new RegExp(SMALL_SPACE, "g")
|
|
21799
21974
|
|
|
21800
21975
|
var MARK = getMark();
|
|
21976
|
+
var INLINE_STYLE_COMMANDS = {
|
|
21977
|
+
bold: "bold",
|
|
21978
|
+
italic: "italic",
|
|
21979
|
+
underline: "underline",
|
|
21980
|
+
subscript: "subscript",
|
|
21981
|
+
superscript: "superscript"
|
|
21982
|
+
};
|
|
21983
|
+
var INLINE_STYLE_TAGS = {
|
|
21984
|
+
bold: ["b", "strong"],
|
|
21985
|
+
italic: ["i", "em"],
|
|
21986
|
+
underline: ["u"],
|
|
21987
|
+
subscript: ["sub"],
|
|
21988
|
+
superscript: ["sup"]
|
|
21989
|
+
};
|
|
21990
|
+
var INLINE_STYLE_TAG_NAMES = ["b", "strong", "i", "em", "u", "sub", "sup"];
|
|
21991
|
+
var INLINE_STYLE_WRAPPER_TAGS = {
|
|
21992
|
+
bold: "strong",
|
|
21993
|
+
italic: "em",
|
|
21994
|
+
underline: "u",
|
|
21995
|
+
subscript: "sub",
|
|
21996
|
+
superscript: "sup"
|
|
21997
|
+
};
|
|
21998
|
+
var EMPTY_INLINE_FORMATTING_REG_EXP = /<(B|STRONG|I|EM|U|SUB|SUP)>(?:\s| |\u00a0|\u200b|\ufeff)*<\/\1>/gi;
|
|
21801
21999
|
|
|
21802
22000
|
// const LATEX_MARKER_START = "<annotation encoding=\"application/x-tex\">"
|
|
21803
22001
|
// const LATEX_MARKER_END = "</annotation>"
|
|
@@ -21939,12 +22137,34 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
21939
22137
|
return countNodeTypes(_this2.editableDiv);
|
|
21940
22138
|
});
|
|
21941
22139
|
_defineProperty(_this2, "applyChangesToComponent", function (value, mimeType, rangeParams, isExpertMode, selectedTab) {
|
|
22140
|
+
var applyOptions = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
|
|
21942
22141
|
// rangeParams, isExpertMode and selectedTab can be null, in which case we will set them
|
|
21943
22142
|
if (rangeParams === null) rangeParams = _this2.lastSetRangeParams;
|
|
21944
|
-
|
|
22143
|
+
debugMathRichInput("applyChangesToComponent:start", {
|
|
22144
|
+
value: value,
|
|
22145
|
+
mimeType: mimeType,
|
|
21945
22146
|
rangeParams: rangeParams,
|
|
21946
|
-
|
|
21947
|
-
|
|
22147
|
+
propsValue: _this2.props.value,
|
|
22148
|
+
propsMimeType: _this2.props.mimeType,
|
|
22149
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
22150
|
+
selection: getSelectionSnapshot(_this2.editableDiv),
|
|
22151
|
+
skipControlledRender: applyOptions.skipControlledRender === true
|
|
22152
|
+
});
|
|
22153
|
+
if (applyOptions.skipControlledRender === true) {
|
|
22154
|
+
// Native contenteditable input has already mutated the DOM and kept the
|
|
22155
|
+
// browser selection in the right place. Re-rendering the same value via
|
|
22156
|
+
// dangerouslySetInnerHTML can replace text nodes under React 19 and move
|
|
22157
|
+
// the caret to the beginning. Programmatic edits still use the normal
|
|
22158
|
+
// render-and-restore path.
|
|
22159
|
+
_this2.skipNextControlledValueRender = true;
|
|
22160
|
+
_this2.skipNextControlledValue = value;
|
|
22161
|
+
_this2.afterComponentUpdateData = null;
|
|
22162
|
+
} else {
|
|
22163
|
+
_this2.afterComponentUpdateData = {
|
|
22164
|
+
rangeParams: rangeParams,
|
|
22165
|
+
value: value
|
|
22166
|
+
};
|
|
22167
|
+
}
|
|
21948
22168
|
var options = _this2.props.options || DEFAULT_OPTIONS;
|
|
21949
22169
|
if (isExpertMode === null) isExpertMode = options.isExpertMode;
|
|
21950
22170
|
if (selectedTab === null) selectedTab = options.selectedTab;
|
|
@@ -21968,6 +22188,10 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
21968
22188
|
isExpertMode: isExpertMode,
|
|
21969
22189
|
selectedTab: selectedTab
|
|
21970
22190
|
});
|
|
22191
|
+
debugMathRichInput("applyChangesToComponent:after-onChange", {
|
|
22192
|
+
afterComponentUpdateData: _this2.afterComponentUpdateData,
|
|
22193
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
22194
|
+
});
|
|
21971
22195
|
});
|
|
21972
22196
|
_defineProperty(_this2, "initialiseHistory", function () {
|
|
21973
22197
|
if (_this2.initialHistoryState == null) _this2.initialHistoryState = {
|
|
@@ -22011,14 +22235,43 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22011
22235
|
});
|
|
22012
22236
|
_defineProperty(_this2, "_setRangeParams", function (params) {
|
|
22013
22237
|
try {
|
|
22238
|
+
debugMathRichInput("MathRichInput:_setRangeParams:start", {
|
|
22239
|
+
params: params,
|
|
22240
|
+
selectionBefore: getSelectionSnapshot(_this2.editableDiv)
|
|
22241
|
+
});
|
|
22014
22242
|
setRangeParams(_this2.editableDiv, params);
|
|
22015
22243
|
_this2.setOldRangeParams(params);
|
|
22016
|
-
_this2.
|
|
22244
|
+
_this2.lastSetRangeParams = params;
|
|
22245
|
+
debugMathRichInput("MathRichInput:_setRangeParams:after", {
|
|
22246
|
+
params: params,
|
|
22247
|
+
oldRangeParams: _this2.oldRangeParams,
|
|
22248
|
+
selectionAfter: getSelectionSnapshot(_this2.editableDiv)
|
|
22249
|
+
});
|
|
22017
22250
|
} catch (error) {
|
|
22018
22251
|
console.error(error);
|
|
22019
22252
|
// console.log(this.editableDiv.childNodes);
|
|
22020
22253
|
}
|
|
22021
22254
|
});
|
|
22255
|
+
_defineProperty(_this2, "restoreRangeAfterBrowserWork", function (params, reason) {
|
|
22256
|
+
if (params === null || params === undefined) return;
|
|
22257
|
+
var restore = function restore(delay) {
|
|
22258
|
+
window.setTimeout(function () {
|
|
22259
|
+
if (_this2.state.showEquationEditor) return;
|
|
22260
|
+
if (!_this2.state.hasFocus && document.activeElement !== _this2.editableDiv) {
|
|
22261
|
+
return;
|
|
22262
|
+
}
|
|
22263
|
+
debugMathRichInput("restoreRangeAfterBrowserWork", {
|
|
22264
|
+
reason: reason,
|
|
22265
|
+
delay: delay,
|
|
22266
|
+
params: params,
|
|
22267
|
+
selectionBefore: getSelectionSnapshot(_this2.editableDiv)
|
|
22268
|
+
});
|
|
22269
|
+
_this2._setRangeParams(params);
|
|
22270
|
+
}, delay);
|
|
22271
|
+
};
|
|
22272
|
+
restore(0);
|
|
22273
|
+
restore(50);
|
|
22274
|
+
});
|
|
22022
22275
|
_defineProperty(_this2, "handleKeyDownBackspace", function (e) {
|
|
22023
22276
|
var rangeParams = _this2._getRangeParams();
|
|
22024
22277
|
var start_node_index = rangeParams.startNodeIndex;
|
|
@@ -22146,7 +22399,11 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22146
22399
|
_defineProperty(_this2, "isWordModifier", function (e) {
|
|
22147
22400
|
var _this2$getPlatformMod = _this2.getPlatformModifiers(),
|
|
22148
22401
|
isMac = _this2$getPlatformMod.isMac;
|
|
22149
|
-
|
|
22402
|
+
// Borough production is pinned to math-rich-input 0.1.48, where Option +
|
|
22403
|
+
// Arrow did not get custom word navigation. Keep Mac Option + Arrow as
|
|
22404
|
+
// normal character movement to preserve the editor's existing UX; Command +
|
|
22405
|
+
// Arrow is handled by isLineModifier below.
|
|
22406
|
+
return isMac ? false : e.ctrlKey;
|
|
22150
22407
|
});
|
|
22151
22408
|
// Check if line navigation modifier is pressed
|
|
22152
22409
|
_defineProperty(_this2, "isLineModifier", function (e) {
|
|
@@ -22603,6 +22860,17 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22603
22860
|
});
|
|
22604
22861
|
_defineProperty(_this2, "handleKeyDown", function (e) {
|
|
22605
22862
|
try {
|
|
22863
|
+
debugMathRichInput("handleKeyDown:start", {
|
|
22864
|
+
key: e.key,
|
|
22865
|
+
altKey: e.altKey,
|
|
22866
|
+
metaKey: e.metaKey,
|
|
22867
|
+
ctrlKey: e.ctrlKey,
|
|
22868
|
+
shiftKey: e.shiftKey,
|
|
22869
|
+
hasFocus: _this2.state.hasFocus,
|
|
22870
|
+
propsValue: _this2.props.value,
|
|
22871
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
22872
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
22873
|
+
});
|
|
22606
22874
|
// Lets set up or update the undo redo history
|
|
22607
22875
|
_this2.initialiseHistory();
|
|
22608
22876
|
if ((e.key === "z" || e.key === "Z") && (e.ctrlKey || e.metaKey)) {
|
|
@@ -22659,7 +22927,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22659
22927
|
}
|
|
22660
22928
|
}
|
|
22661
22929
|
}
|
|
22662
|
-
|
|
22930
|
+
var _this2$getPlatformMod3 = _this2.getPlatformModifiers(),
|
|
22931
|
+
isMac = _this2$getPlatformMod3.isMac;
|
|
22932
|
+
if (!isMac && e.altKey && e.key === "Alt") {
|
|
22663
22933
|
var key = _this2.fetchAccentLetter();
|
|
22664
22934
|
if (key === "a" || key === "e" || key === "i" || key === "o" || key === "u" || key === "c" || key === "l" || key === "n" || key === "s" || key === "?" || key === "!") {
|
|
22665
22935
|
// console.log("showing/hiding accent bar");
|
|
@@ -22676,9 +22946,11 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22676
22946
|
return;
|
|
22677
22947
|
}
|
|
22678
22948
|
_this2.keyPressed = e.key; // store for use in methods
|
|
22679
|
-
_this2.
|
|
22680
|
-
|
|
22681
|
-
|
|
22949
|
+
if (_this2.state.showAccentBar) {
|
|
22950
|
+
_this2.setState({
|
|
22951
|
+
keyPressed: e.key
|
|
22952
|
+
});
|
|
22953
|
+
}
|
|
22682
22954
|
|
|
22683
22955
|
// Hide the accent bar if a key is typed except when it is an automatic key repeat press
|
|
22684
22956
|
// If it is automatic key repeat, then keyPressStartTime will be greater than 0
|
|
@@ -22698,10 +22970,16 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22698
22970
|
}
|
|
22699
22971
|
if (e.key === "ArrowLeft") {
|
|
22700
22972
|
_this2.handleKeyDownArrowLeft(e);
|
|
22973
|
+
debugMathRichInput("handleKeyDown:after-arrow-left", {
|
|
22974
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
22975
|
+
});
|
|
22701
22976
|
return;
|
|
22702
22977
|
}
|
|
22703
22978
|
if (e.key === "ArrowRight") {
|
|
22704
22979
|
_this2.handleKeyDownArrowRight(e);
|
|
22980
|
+
debugMathRichInput("handleKeyDown:after-arrow-right", {
|
|
22981
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
22982
|
+
});
|
|
22705
22983
|
return;
|
|
22706
22984
|
}
|
|
22707
22985
|
if (e.key === "ArrowUp") {
|
|
@@ -22756,6 +23034,10 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22756
23034
|
|
|
22757
23035
|
// Stop editing if within rendered katex node, if somehow the cursor ends up there (it shouldn't)
|
|
22758
23036
|
var params = _this2._getRangeParams();
|
|
23037
|
+
// No range means no caret to protect — a field the browser has not placed a selection in yet. It
|
|
23038
|
+
// reads as null on the first keystroke after a click on a toolbar button, and dereferencing it threw
|
|
23039
|
+
// on every Cmd, every Cmd+A and every Cmd+V the teacher pressed.
|
|
23040
|
+
if (params === null || params === undefined) return;
|
|
22759
23041
|
var startNode = _this2._findNodeWithIndex(params.startNodeIndex);
|
|
22760
23042
|
var endNode = _this2._findNodeWithIndex(params.endNodeIndex);
|
|
22761
23043
|
if (!_this2.editableDivIsPlainText()) {
|
|
@@ -22794,13 +23076,26 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22794
23076
|
});
|
|
22795
23077
|
_defineProperty(_this2, "handleKeyUp", function (e) {
|
|
22796
23078
|
try {
|
|
23079
|
+
debugMathRichInput("handleKeyUp:start", {
|
|
23080
|
+
key: e.key,
|
|
23081
|
+
altKey: e.altKey,
|
|
23082
|
+
metaKey: e.metaKey,
|
|
23083
|
+
ctrlKey: e.ctrlKey,
|
|
23084
|
+
shiftKey: e.shiftKey,
|
|
23085
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23086
|
+
});
|
|
22797
23087
|
_this2.keyPressed = e.key;
|
|
22798
23088
|
_this2.keyPressStartTime = -1;
|
|
22799
23089
|
|
|
22800
23090
|
// If the cursor is in a katex node, then move it to the next node (unless there is no
|
|
22801
23091
|
// net node in which case move it to the previous node)
|
|
22802
23092
|
if (e.key === "ArrowRight") {
|
|
23093
|
+
// `_getRangeParams` returns null whenever the document's selection is not inside this field —
|
|
23094
|
+
// which happens in ordinary use, not only in error: a toolbar button taking focus is enough. It
|
|
23095
|
+
// was dereferenced straight away, so the key handler THREW and everything after it in the same
|
|
23096
|
+
// press was skipped.
|
|
22803
23097
|
var rangeParams = _this2._getRangeParams();
|
|
23098
|
+
if (!rangeParams) return;
|
|
22804
23099
|
var index = rangeParams.startNodeIndex;
|
|
22805
23100
|
if (index >= 0) {
|
|
22806
23101
|
if (isKatexNode(_this2._findNodeWithIndex(index))) {
|
|
@@ -22829,7 +23124,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22829
23124
|
}
|
|
22830
23125
|
}
|
|
22831
23126
|
if (e.key === "ArrowLeft") {
|
|
23127
|
+
// Same guard, same reason — see ArrowRight above.
|
|
22832
23128
|
var _rangeParams = _this2._getRangeParams();
|
|
23129
|
+
if (!_rangeParams) return;
|
|
22833
23130
|
var _index = _rangeParams.startNodeIndex;
|
|
22834
23131
|
|
|
22835
23132
|
// If the cursor is in a katex node, then move it to the previous node (unless there is no
|
|
@@ -22869,8 +23166,22 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22869
23166
|
try {
|
|
22870
23167
|
// console.log("handleInput")
|
|
22871
23168
|
if (_this2.isComposing) return;
|
|
23169
|
+
debugMathRichInput("handleInput:start", {
|
|
23170
|
+
inputType: event && event.nativeEvent ? event.nativeEvent.inputType : null,
|
|
23171
|
+
data: event && event.nativeEvent ? event.nativeEvent.data : null,
|
|
23172
|
+
propsValue: _this2.props.value,
|
|
23173
|
+
propsMimeType: _this2.props.mimeType,
|
|
23174
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
23175
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23176
|
+
});
|
|
23177
|
+
_this2.cleanupEmptyInlineFormattingElements();
|
|
22872
23178
|
var raw_text = elementToMarkedRawText(_this2.editableDiv, null, 0, _this2.enableHtml());
|
|
22873
23179
|
var params = _this2._getRangeParams();
|
|
23180
|
+
debugMathRichInput("handleInput:after-range", {
|
|
23181
|
+
raw_text: raw_text,
|
|
23182
|
+
params: params,
|
|
23183
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23184
|
+
});
|
|
22874
23185
|
if (params === null || params === undefined) {
|
|
22875
23186
|
// console.log(
|
|
22876
23187
|
// "handleInput: Empty content detected, using default range params"
|
|
@@ -22889,7 +23200,10 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22889
23200
|
var _mimeType = _this2.getMimeType(_nodeCounts.html > 0, _nodeCounts.math > 0);
|
|
22890
23201
|
var _raw_text = elementToMarkedRawText(_this2.editableDiv, null, 0, _this2.enableHtml());
|
|
22891
23202
|
_raw_text = removeEncodingIfPlainText(_raw_text, _mimeType, _this2.enableHtml());
|
|
22892
|
-
|
|
23203
|
+
_raw_text = _this2.stripEmptyInlineFormattingTags(_raw_text);
|
|
23204
|
+
_this2.applyChangesToComponent(_raw_text, _mimeType, defaultParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
|
|
23205
|
+
skipControlledRender: true
|
|
23206
|
+
});
|
|
22893
23207
|
return;
|
|
22894
23208
|
} else if (params.startNodeIndex === null || params.startNodeIndex === undefined) {
|
|
22895
23209
|
// console.log(
|
|
@@ -22910,6 +23224,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22910
23224
|
var hasMath = hasMathContent || nodeCounts.math > 0;
|
|
22911
23225
|
var mimeType = _this2.getMimeType(hasHtml, hasMath);
|
|
22912
23226
|
raw_text = removeEncodingIfPlainText(raw_text, mimeType, _this2.enableHtml());
|
|
23227
|
+
raw_text = _this2.stripEmptyInlineFormattingTags(raw_text);
|
|
22913
23228
|
|
|
22914
23229
|
// Use valid params or fallback to defaults
|
|
22915
23230
|
var validParams = params || {
|
|
@@ -22920,7 +23235,16 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22920
23235
|
startGlobalOffset: 0,
|
|
22921
23236
|
endGlobalOffset: 0
|
|
22922
23237
|
};
|
|
22923
|
-
_this2.
|
|
23238
|
+
_this2.setOldRangeParams(validParams);
|
|
23239
|
+
_this2.applyChangesToComponent(raw_text, mimeType, validParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
|
|
23240
|
+
skipControlledRender: true
|
|
23241
|
+
});
|
|
23242
|
+
debugMathRichInput("handleInput:after-apply", {
|
|
23243
|
+
raw_text: raw_text,
|
|
23244
|
+
mimeType: mimeType,
|
|
23245
|
+
validParams: validParams,
|
|
23246
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23247
|
+
});
|
|
22924
23248
|
} catch (error) {
|
|
22925
23249
|
console.error(error);
|
|
22926
23250
|
}
|
|
@@ -22965,44 +23289,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
22965
23289
|
});
|
|
22966
23290
|
return;
|
|
22967
23291
|
}
|
|
22968
|
-
var
|
|
22969
|
-
|
|
22970
|
-
var italic = false;
|
|
22971
|
-
var underline = false;
|
|
22972
|
-
var superscript = false;
|
|
22973
|
-
var subscript = false;
|
|
22974
|
-
do {
|
|
22975
|
-
if (node.nodeType === 1) {
|
|
22976
|
-
var nodeName = node.nodeName.toLowerCase();
|
|
22977
|
-
if (nodeName === "b" || nodeName === "strong") bold = true;else if (nodeName === "i" || nodeName === "em") italic = true;else if (nodeName === "u") underline = true;else if (nodeName === "sub") subscript = true;else if (nodeName === "sup") superscript = true;
|
|
22978
|
-
} else if (node.classList !== null && node.classList !== undefined && node.classList.contains("MathRichInput")) break;
|
|
22979
|
-
node = node.parentNode;
|
|
22980
|
-
} while (node !== null);
|
|
22981
|
-
var activeButtons = {
|
|
22982
|
-
bold: bold,
|
|
22983
|
-
italic: italic,
|
|
22984
|
-
underline: underline,
|
|
22985
|
-
subscript: subscript,
|
|
22986
|
-
superscript: superscript
|
|
22987
|
-
};
|
|
22988
|
-
// console.log(activeButtons)
|
|
22989
|
-
|
|
22990
|
-
var update = true;
|
|
22991
|
-
if (_this2.lastSetActiveButtons !== null) {
|
|
22992
|
-
update = false;
|
|
22993
|
-
for (var style in activeButtons) {
|
|
22994
|
-
if (activeButtons[style] !== _this2.lastSetActiveButtons[style]) {
|
|
22995
|
-
update = true;
|
|
22996
|
-
break;
|
|
22997
|
-
}
|
|
22998
|
-
}
|
|
22999
|
-
}
|
|
23000
|
-
if (update) {
|
|
23001
|
-
_this2.lastSetActiveButtons = activeButtons;
|
|
23002
|
-
_this2.setState({
|
|
23003
|
-
activeButtons: activeButtons
|
|
23004
|
-
});
|
|
23005
|
-
}
|
|
23292
|
+
var activeButtons = _this2.getActiveButtonsFromSelection(rangeParams);
|
|
23293
|
+
_this2.setActiveButtonsIfChanged(activeButtons);
|
|
23006
23294
|
} catch (error) {
|
|
23007
23295
|
console.error(error);
|
|
23008
23296
|
}
|
|
@@ -23054,8 +23342,29 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23054
23342
|
var pasteHtml = clipboardData.getData("text/html");
|
|
23055
23343
|
var pasteText = clipboardData.getData("text/plain");
|
|
23056
23344
|
|
|
23057
|
-
//
|
|
23058
|
-
|
|
23345
|
+
// Remove whatever was selected FIRST, then read the caret.
|
|
23346
|
+
//
|
|
23347
|
+
// The order used to be the other way round, and that is why pasting over a selection emptied the
|
|
23348
|
+
// field instead of replacing it. `rangeParams` is a NODE INDEX plus an offset, and
|
|
23349
|
+
// `deleteFromDocument()` removes the very nodes it counts: read before the delete, the index points
|
|
23350
|
+
// at something that no longer exists, so `_findNodeWithIndex` below finds nothing, the marked text
|
|
23351
|
+
// it builds carries no mark, and the pasted text is inserted nowhere. The value the component then
|
|
23352
|
+
// reports is correct while the editable div is left empty — which is exactly what a teacher sees.
|
|
23353
|
+
//
|
|
23354
|
+
// It only ever worked on an empty field because the canonical empty state is a single `<p>` holding
|
|
23355
|
+
// one small space: deleting that selection leaves the same node structure standing, so the stale
|
|
23356
|
+
// index still happens to resolve.
|
|
23357
|
+
// Read the caret BEFORE, so there is something to fall back on, and AGAIN after the delete.
|
|
23358
|
+
var paramsBeforeDelete = _this2._getRangeParams();
|
|
23359
|
+
var selection = window.getSelection();
|
|
23360
|
+
if (selection && selection.rangeCount > 0) {
|
|
23361
|
+
selection.deleteFromDocument();
|
|
23362
|
+
}
|
|
23363
|
+
|
|
23364
|
+
// The caret AFTER the delete is the one that counts: `rangeParams` is a node index, and the delete
|
|
23365
|
+
// removed the very nodes it counts. The pre-delete value is kept only as a fallback — bailing out
|
|
23366
|
+
// here having already emptied the selection is how a paste turns into a deletion.
|
|
23367
|
+
var rangeParams = _this2._getRangeParams() || paramsBeforeDelete;
|
|
23059
23368
|
if (!rangeParams) {
|
|
23060
23369
|
console.warn("Could not get range params for paste");
|
|
23061
23370
|
return;
|
|
@@ -23063,12 +23372,6 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23063
23372
|
|
|
23064
23373
|
// Store old range params for cursor positioning (like equation editor)
|
|
23065
23374
|
_this2.setOldRangeParams(rangeParams);
|
|
23066
|
-
|
|
23067
|
-
// Clear any existing selection
|
|
23068
|
-
var selection = window.getSelection();
|
|
23069
|
-
if (selection.rangeCount > 0) {
|
|
23070
|
-
selection.deleteFromDocument();
|
|
23071
|
-
}
|
|
23072
23375
|
var finalText = "";
|
|
23073
23376
|
var finalMimeType = _this2.props.mimeType || "text/html";
|
|
23074
23377
|
var hasMathContent = false;
|
|
@@ -23171,38 +23474,41 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23171
23474
|
|
|
23172
23475
|
// Insert the new content at the marked position
|
|
23173
23476
|
var newRawText = removeMarks(insertCharacterBeforeMarks(markedText, finalText));
|
|
23174
|
-
|
|
23175
23477
|
// Convert any remaining \[...\] LaTeX expressions to <math>...</math> format
|
|
23176
23478
|
// This ensures consistency when paste adds <math> tags alongside existing \[...\] expressions
|
|
23177
23479
|
if (_this2.enableMath()) {
|
|
23178
23480
|
newRawText = newRawText.replace(/\\\[([^\]]*?)\\\]/g, "<math>$1</math>");
|
|
23179
23481
|
}
|
|
23180
23482
|
|
|
23181
|
-
|
|
23483
|
+
/*
|
|
23484
|
+
* Where the caret lands after a paste: at the END of what was pasted.
|
|
23485
|
+
*
|
|
23486
|
+
* The node index walks to just past the last formula, and that part was always right. The OFFSET
|
|
23487
|
+
* within that node was not: it stopped at the boundary, so pasting "…multiply it by 3." left the
|
|
23488
|
+
* caret between the 3 and the full stop. Everything after the last `</math>` is plain text, and
|
|
23489
|
+
* plain text is the one thing whose rendered length is its written length — so the tail can
|
|
23490
|
+
* simply be stepped over. (Markup in the tail is not that, and keeps the old position rather than
|
|
23491
|
+
* a guessed one.)
|
|
23492
|
+
*
|
|
23493
|
+
* Deriving the position from scratch was tried twice and both attempts landed FURTHER away, each
|
|
23494
|
+
* needing an exact model of how the rendered document is measured and each getting a corner of it
|
|
23495
|
+
* wrong. Walking the structure that is already there needs no such model.
|
|
23496
|
+
*/
|
|
23182
23497
|
var newRangeParams = null;
|
|
23183
23498
|
if (hasMathContent) {
|
|
23184
|
-
// Use same approach as equation editor for math content
|
|
23185
23499
|
var oldRangeParams = _this2.getOldRangeParams();
|
|
23186
23500
|
var mathTagCount = (finalText.match(/<math>/gi) || []).length;
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23192
|
-
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23197
|
-
newRangeParams = {
|
|
23198
|
-
startNodeIndex: oldRangeParams.startNodeIndex + mathTagCount * 2,
|
|
23199
|
-
startOffset: SMALL_SPACE_LENGTH,
|
|
23200
|
-
endNodeIndex: oldRangeParams.startNodeIndex + mathTagCount * 2,
|
|
23201
|
-
endOffset: SMALL_SPACE_LENGTH
|
|
23202
|
-
};
|
|
23203
|
-
}
|
|
23501
|
+
var startNodeIndex = _this2.props.value === "" ? mathTagCount * 2 : oldRangeParams.startNodeIndex + mathTagCount * 2;
|
|
23502
|
+
var lastMathEnd = finalText.toLowerCase().lastIndexOf("</math>");
|
|
23503
|
+
var tail = lastMathEnd === -1 ? "" : finalText.substring(lastMathEnd + "</math>".length);
|
|
23504
|
+
var startOffset = tail.indexOf("<") === -1 ? SMALL_SPACE_LENGTH + tail.length : SMALL_SPACE_LENGTH;
|
|
23505
|
+
newRangeParams = {
|
|
23506
|
+
startNodeIndex: startNodeIndex,
|
|
23507
|
+
startOffset: startOffset,
|
|
23508
|
+
endNodeIndex: startNodeIndex,
|
|
23509
|
+
endOffset: startOffset
|
|
23510
|
+
};
|
|
23204
23511
|
} else {
|
|
23205
|
-
// For text content, use global offset like before
|
|
23206
23512
|
var newGlobalOffset = rangeParams.startGlobalOffset + finalText.length;
|
|
23207
23513
|
newRangeParams = {
|
|
23208
23514
|
startGlobalOffset: newGlobalOffset,
|
|
@@ -23225,6 +23531,23 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23225
23531
|
|
|
23226
23532
|
// Apply the change using the component's standard flow
|
|
23227
23533
|
_this2.applyChangesToComponent(cleanedRawText, actualMimeType, newRangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
|
|
23534
|
+
|
|
23535
|
+
// A paste cannot rely on being re-rendered. It removed the selected content from the DOM itself,
|
|
23536
|
+
// and if the host already holds the value being applied — the same words pasted back into the
|
|
23537
|
+
// field they came from — no prop changes, nothing re-renders, and the field is left empty while
|
|
23538
|
+
// the value is correct everywhere else. So: give a real render its chance, then put the content
|
|
23539
|
+
// back if none came.
|
|
23540
|
+
var rangeAfterPaste = newRangeParams;
|
|
23541
|
+
queueMicrotask(function () {
|
|
23542
|
+
try {
|
|
23543
|
+
if (_this2.reconcileEditableDomWithRender(true)) {
|
|
23544
|
+
_this2._setRangeParams(rangeAfterPaste);
|
|
23545
|
+
_this2.updateActiveButtons();
|
|
23546
|
+
}
|
|
23547
|
+
} catch (error) {
|
|
23548
|
+
console.error(error);
|
|
23549
|
+
}
|
|
23550
|
+
});
|
|
23228
23551
|
}
|
|
23229
23552
|
} catch (error) {
|
|
23230
23553
|
console.error("Error in paste handler:", error);
|
|
@@ -23237,6 +23560,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23237
23560
|
var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
23238
23561
|
try {
|
|
23239
23562
|
var rangeParams = _this2._getRangeParams();
|
|
23563
|
+
debugMathRichInput("showEquationEditor:start", {
|
|
23564
|
+
moveCursorOnInsert: moveCursorOnInsert,
|
|
23565
|
+
nodeProvided: Boolean(node),
|
|
23566
|
+
rangeParams: rangeParams,
|
|
23567
|
+
oldRangeParams: _this2.oldRangeParams,
|
|
23568
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
23569
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23570
|
+
});
|
|
23240
23571
|
if (rangeParams.startNodeIndex < 0) {
|
|
23241
23572
|
console.error("Could not find position of cursor in node list");
|
|
23242
23573
|
_this2.setOldRangeParams({
|
|
@@ -23279,6 +23610,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23279
23610
|
_this2.editingExistingEquation = false;
|
|
23280
23611
|
_this2.editingNode = null;
|
|
23281
23612
|
}
|
|
23613
|
+
_this2.setOldRangeParams(rangeParams);
|
|
23282
23614
|
_this2.markedRawText = elementToMarkedRawText(_this2.editableDiv, node, offset, _this2.enableHtml());
|
|
23283
23615
|
// console.log("Marked text:"+this.markedRawText)
|
|
23284
23616
|
_this2.moveCursorOnInsert = moveCursorOnInsert;
|
|
@@ -23286,11 +23618,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23286
23618
|
showEquationEditor: true,
|
|
23287
23619
|
equationEditorLatex: latex
|
|
23288
23620
|
});
|
|
23621
|
+
debugMathRichInput("showEquationEditor:after-setState", {
|
|
23622
|
+
rangeParams: rangeParams,
|
|
23623
|
+
markedRawText: _this2.markedRawText,
|
|
23624
|
+
latex: latex,
|
|
23625
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23626
|
+
});
|
|
23289
23627
|
} catch (error) {
|
|
23290
23628
|
console.error(error);
|
|
23291
23629
|
}
|
|
23292
23630
|
});
|
|
23293
23631
|
_defineProperty(_this2, "handleMouseDown", function (event) {
|
|
23632
|
+
_this2.isMouseDownInEditable = true;
|
|
23633
|
+
window.setTimeout(function () {
|
|
23634
|
+
_this2.isMouseDownInEditable = false;
|
|
23635
|
+
}, 0);
|
|
23294
23636
|
_this2.showEquationEditorOnMouseUp = false;
|
|
23295
23637
|
var node = null;
|
|
23296
23638
|
try {
|
|
@@ -23325,7 +23667,12 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23325
23667
|
|
|
23326
23668
|
// Clear selection anchor on mouse click
|
|
23327
23669
|
_this2.selectionAnchor = null;
|
|
23670
|
+
|
|
23671
|
+
// A click that lands with the selection outside this field — a toolbar button, another field —
|
|
23672
|
+
// gives no range at all. Dereferenced unguarded, this threw on every such click.
|
|
23328
23673
|
var params = _this2._getRangeParams();
|
|
23674
|
+
if (!params) return;
|
|
23675
|
+
_this2.setOldRangeParams(params);
|
|
23329
23676
|
var startNode = _this2._findNodeWithIndex(params.startNodeIndex);
|
|
23330
23677
|
var endNode = _this2._findNodeWithIndex(params.endNodeIndex);
|
|
23331
23678
|
if (startNode === null || endNode === null) {
|
|
@@ -23381,7 +23728,15 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23381
23728
|
_defineProperty(_this2, "handleEquationEditorInsert", function (latex) {
|
|
23382
23729
|
try {
|
|
23383
23730
|
// console.log("Inserting latex: "+latex)
|
|
23384
|
-
|
|
23731
|
+
debugMathRichInput("handleEquationEditorInsert:start", {
|
|
23732
|
+
latex: latex,
|
|
23733
|
+
markedRawText: _this2.markedRawText,
|
|
23734
|
+
oldRangeParams: _this2.getOldRangeParams(),
|
|
23735
|
+
moveCursorOnInsert: _this2.moveCursorOnInsert,
|
|
23736
|
+
propsValue: _this2.props.value,
|
|
23737
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
23738
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23739
|
+
});
|
|
23385
23740
|
var start_pos = _this2.markedRawText.indexOf(MARK);
|
|
23386
23741
|
var end_pos = _this2.markedRawText.indexOf(MARK, start_pos + MARK.length);
|
|
23387
23742
|
var oldRangeParams = _this2.getOldRangeParams();
|
|
@@ -23423,6 +23778,10 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23423
23778
|
_this2.applyChangesToComponent(_new_raw_text, _mimeType2, _new_rangeParams7, _this2.props.useExpertMode, _this2.props.selectedTab);
|
|
23424
23779
|
_this2.setState({
|
|
23425
23780
|
showEquationEditor: false
|
|
23781
|
+
}, function () {
|
|
23782
|
+
_this2.editableDiv.focus();
|
|
23783
|
+
_this2._setRangeParams(_new_rangeParams7);
|
|
23784
|
+
_this2.restoreRangeAfterBrowserWork(_new_rangeParams7, "equation-empty");
|
|
23426
23785
|
});
|
|
23427
23786
|
return;
|
|
23428
23787
|
}
|
|
@@ -23441,28 +23800,48 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23441
23800
|
endOffset: SMALL_SPACE_LENGTH
|
|
23442
23801
|
};
|
|
23443
23802
|
} else {
|
|
23444
|
-
//
|
|
23445
|
-
//
|
|
23446
|
-
|
|
23447
|
-
|
|
23448
|
-
|
|
23449
|
-
var
|
|
23803
|
+
// In this editor a rendered equation counts as one logical character
|
|
23804
|
+
// in global-offset space. Put the caret immediately after that math
|
|
23805
|
+
// character instead of guessing rendered child-node indexes.
|
|
23806
|
+
var _oldRangeParams = _this2.getOldRangeParams();
|
|
23807
|
+
if (_oldRangeParams !== null && _oldRangeParams.startGlobalOffset !== null && _oldRangeParams.startGlobalOffset !== undefined) {
|
|
23808
|
+
var afterInsertedMathOffset = _oldRangeParams.startGlobalOffset + 1;
|
|
23450
23809
|
new_rangeParams = {
|
|
23451
|
-
startGlobalOffset:
|
|
23452
|
-
endGlobalOffset:
|
|
23810
|
+
startGlobalOffset: afterInsertedMathOffset,
|
|
23811
|
+
endGlobalOffset: afterInsertedMathOffset
|
|
23812
|
+
};
|
|
23813
|
+
} else if (_oldRangeParams !== null && _oldRangeParams.startNodeIndex !== null && _oldRangeParams.startNodeIndex !== undefined) {
|
|
23814
|
+
new_rangeParams = {
|
|
23815
|
+
startNodeIndex: _oldRangeParams.startNodeIndex + 2,
|
|
23816
|
+
startOffset: SMALL_SPACE_LENGTH,
|
|
23817
|
+
endNodeIndex: _oldRangeParams.startNodeIndex + 2,
|
|
23818
|
+
endOffset: SMALL_SPACE_LENGTH
|
|
23453
23819
|
};
|
|
23454
23820
|
}
|
|
23455
|
-
// If
|
|
23821
|
+
// If old range params are unavailable, keep the current fallback.
|
|
23456
23822
|
}
|
|
23457
23823
|
}
|
|
23458
23824
|
// console.log("New cursor pos: "+new_rangeParams.startNodeIndex+"->"+new_rangeParams.startOffset)
|
|
23459
23825
|
_this2.setOldRangeParams(new_rangeParams);
|
|
23826
|
+
debugMathRichInput("handleEquationEditorInsert:before-apply", {
|
|
23827
|
+
new_raw_text: new_raw_text,
|
|
23828
|
+
new_rangeParams: new_rangeParams,
|
|
23829
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23830
|
+
});
|
|
23460
23831
|
var nodeCounts = _this2._countNodeTypes();
|
|
23461
23832
|
var mimeType = _this2.getMimeType(nodeCounts.html > 0, true);
|
|
23462
23833
|
new_raw_text = removeEncodingIfPlainText(new_raw_text, mimeType, _this2.enableHtml());
|
|
23463
23834
|
_this2.applyChangesToComponent(new_raw_text, mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
|
|
23464
23835
|
_this2.setState({
|
|
23465
23836
|
showEquationEditor: false
|
|
23837
|
+
}, function () {
|
|
23838
|
+
_this2.editableDiv.focus();
|
|
23839
|
+
_this2._setRangeParams(new_rangeParams);
|
|
23840
|
+
_this2.restoreRangeAfterBrowserWork(new_rangeParams, "equation-insert");
|
|
23841
|
+
debugMathRichInput("handleEquationEditorInsert:after-close-restore", {
|
|
23842
|
+
new_rangeParams: new_rangeParams,
|
|
23843
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
23844
|
+
});
|
|
23466
23845
|
});
|
|
23467
23846
|
} catch (error) {
|
|
23468
23847
|
console.error(error);
|
|
@@ -23479,13 +23858,22 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23479
23858
|
_defineProperty(_this2, "styleText", function (event, style) {
|
|
23480
23859
|
try {
|
|
23481
23860
|
style = style.toLowerCase();
|
|
23861
|
+
var command = _this2.getStyleCommand(style);
|
|
23862
|
+
var activeBefore = _this2.getActiveButtonsFromSelection();
|
|
23863
|
+
var selection = document.getSelection();
|
|
23864
|
+
var isCollapsed = selection === null || selection.rangeCount === 0 || selection.getRangeAt(0).collapsed;
|
|
23482
23865
|
var success = false;
|
|
23866
|
+
var insertedTypingStylePlaceholder = false;
|
|
23483
23867
|
|
|
23484
23868
|
// This first part is a workaround whereby executing subscript or superscript
|
|
23485
23869
|
// within an existing subscript or superscript makes the text even smaller instead
|
|
23486
23870
|
// of toggling it off. Thus we manually toggle it off with some jigerry pokery!
|
|
23487
|
-
if (
|
|
23488
|
-
|
|
23871
|
+
if (isCollapsed && activeBefore[style] && _this2.moveTrailingWhitespaceOutsideInlineStyle(style, command)) {
|
|
23872
|
+
success = true;
|
|
23873
|
+
} else if (isCollapsed && !activeBefore[style] && selection && selection.rangeCount > 0) {
|
|
23874
|
+
success = _this2.insertInlineStylePlaceholder(style);
|
|
23875
|
+
insertedTypingStylePlaceholder = success;
|
|
23876
|
+
} else if (style === "subscript" && activeBefore.subscript || style === "superscript" && activeBefore.superscript) {
|
|
23489
23877
|
var range = selection.getRangeAt(0);
|
|
23490
23878
|
if (range.endContainer === range.startContainer && range.endOffset === range.startOffset) {
|
|
23491
23879
|
success = document.execCommand("insertText", false, " ");
|
|
@@ -23494,78 +23882,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23494
23882
|
selection.addRange(range);
|
|
23495
23883
|
}
|
|
23496
23884
|
success = document.execCommand("removeFormat", false, null);
|
|
23497
|
-
if (
|
|
23498
|
-
if (
|
|
23499
|
-
if (
|
|
23885
|
+
if (activeBefore.bold) success = document.execCommand("bold", false, null);
|
|
23886
|
+
if (activeBefore.italic) success = document.execCommand("italic", false, null);
|
|
23887
|
+
if (activeBefore.underline) success = document.execCommand("underline", false, null);
|
|
23500
23888
|
} else {
|
|
23501
|
-
|
|
23502
|
-
|
|
23503
|
-
var _range = _selection.getRangeAt(0);
|
|
23504
|
-
if (_range.collapsed) {
|
|
23505
|
-
// For cursor position (no selection), use insertHTML with styled wrapper
|
|
23506
|
-
// This creates a formatted element that will maintain styling for future typing
|
|
23507
|
-
var isToggleOff = _this2.state.activeButtons[style];
|
|
23508
|
-
if (!isToggleOff) {
|
|
23509
|
-
// Turning style ON - insert a styled wrapper element
|
|
23510
|
-
var wrapperTag;
|
|
23511
|
-
switch (style) {
|
|
23512
|
-
case "bold":
|
|
23513
|
-
wrapperTag = "strong";
|
|
23514
|
-
break;
|
|
23515
|
-
case "italic":
|
|
23516
|
-
wrapperTag = "em";
|
|
23517
|
-
break;
|
|
23518
|
-
case "underline":
|
|
23519
|
-
wrapperTag = "u";
|
|
23520
|
-
break;
|
|
23521
|
-
case "subscript":
|
|
23522
|
-
wrapperTag = "sub";
|
|
23523
|
-
break;
|
|
23524
|
-
case "superscript":
|
|
23525
|
-
wrapperTag = "sup";
|
|
23526
|
-
break;
|
|
23527
|
-
default:
|
|
23528
|
-
wrapperTag = "span";
|
|
23529
|
-
break;
|
|
23530
|
-
}
|
|
23531
|
-
|
|
23532
|
-
// Insert an invisible character wrapped in the styled element
|
|
23533
|
-
// This provides a "landing zone" for future typing
|
|
23534
|
-
var styledElement = "<".concat(wrapperTag, ">\u200B</").concat(wrapperTag, ">");
|
|
23535
|
-
success = document.execCommand("insertHTML", false, styledElement);
|
|
23536
|
-
|
|
23537
|
-
// Move cursor inside the styled element
|
|
23538
|
-
setTimeout(function () {
|
|
23539
|
-
var newSelection = document.getSelection();
|
|
23540
|
-
if (newSelection && newSelection.rangeCount > 0) {
|
|
23541
|
-
var newRange = newSelection.getRangeAt(0);
|
|
23542
|
-
var styledNode = newRange.startContainer.parentElement;
|
|
23543
|
-
if (styledNode && styledNode.tagName.toLowerCase() === wrapperTag) {
|
|
23544
|
-
newRange.setStart(styledNode, 0);
|
|
23545
|
-
newRange.setEnd(styledNode, 0);
|
|
23546
|
-
newSelection.removeAllRanges();
|
|
23547
|
-
newSelection.addRange(newRange);
|
|
23548
|
-
}
|
|
23549
|
-
}
|
|
23550
|
-
}, 0);
|
|
23551
|
-
} else {
|
|
23552
|
-
// Turning style OFF - just apply execCommand normally
|
|
23553
|
-
success = document.execCommand(style, false, null);
|
|
23554
|
-
}
|
|
23555
|
-
} else {
|
|
23556
|
-
// For text selection, use regular execCommand
|
|
23557
|
-
success = document.execCommand(style, false, null);
|
|
23558
|
-
}
|
|
23889
|
+
if (selection && selection.rangeCount > 0) {
|
|
23890
|
+
success = document.execCommand(command, false, null);
|
|
23559
23891
|
}
|
|
23560
23892
|
}
|
|
23561
|
-
var
|
|
23562
|
-
|
|
23563
|
-
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
|
|
23568
|
-
});
|
|
23893
|
+
var rangeParams = _this2._getRangeParams();
|
|
23894
|
+
var removedEmptyTags = !insertedTypingStylePlaceholder && _this2.cleanupEmptyInlineFormattingElements();
|
|
23895
|
+
if (removedEmptyTags && rangeParams !== null && rangeParams !== undefined) {
|
|
23896
|
+
_this2._setRangeParams(rangeParams);
|
|
23897
|
+
_this2.applyCurrentEditableDomToComponent(rangeParams);
|
|
23898
|
+
}
|
|
23899
|
+
_this2.setActiveButtonsIfChanged(_this2.getActiveButtonsFromSelection(rangeParams));
|
|
23569
23900
|
return success;
|
|
23570
23901
|
} catch (error) {
|
|
23571
23902
|
console.error(error);
|
|
@@ -23573,8 +23904,15 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23573
23904
|
});
|
|
23574
23905
|
_defineProperty(_this2, "handleToolbarButtonClick", function (event, buttonName) {
|
|
23575
23906
|
try {
|
|
23907
|
+
debugMathRichInput("handleToolbarButtonClick:start", {
|
|
23908
|
+
buttonName: buttonName,
|
|
23909
|
+
eventType: event ? event.type : null,
|
|
23910
|
+
selection: getSelectionSnapshot(_this2.editableDiv),
|
|
23911
|
+
oldRangeParams: _this2.oldRangeParams
|
|
23912
|
+
});
|
|
23576
23913
|
if (buttonName === "Equation") {
|
|
23577
23914
|
_this2.showEquationEditor(true);
|
|
23915
|
+
return;
|
|
23578
23916
|
}
|
|
23579
23917
|
var style = buttonName;
|
|
23580
23918
|
var success = _this2.styleText(event, style);
|
|
@@ -23886,13 +24224,36 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23886
24224
|
});
|
|
23887
24225
|
_defineProperty(_this2, "handleFocus", function (event) {
|
|
23888
24226
|
var onFocus = _this2.props.onFocus;
|
|
24227
|
+
var currentRangeParams = null;
|
|
24228
|
+
try {
|
|
24229
|
+
currentRangeParams = _this2._getRangeParams();
|
|
24230
|
+
} catch (error) {
|
|
24231
|
+
currentRangeParams = null;
|
|
24232
|
+
}
|
|
24233
|
+
var savedRangeParams = _this2.getOldRangeParams();
|
|
24234
|
+
var shouldRestoreSavedRange = !_this2.isMouseDownInEditable && _this2.props.value !== "" && _this2.rangeIsAtStart(currentRangeParams) && _this2.rangeHasNonZeroPosition(savedRangeParams);
|
|
24235
|
+
debugMathRichInput("handleFocus:start", {
|
|
24236
|
+
propsValue: _this2.props.value,
|
|
24237
|
+
oldRangeParams: _this2.oldRangeParams,
|
|
24238
|
+
currentRangeParams: currentRangeParams,
|
|
24239
|
+
shouldRestoreSavedRange: shouldRestoreSavedRange,
|
|
24240
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
24241
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
24242
|
+
});
|
|
23889
24243
|
if (onFocus != null) {
|
|
23890
24244
|
onFocus();
|
|
23891
24245
|
}
|
|
23892
24246
|
try {
|
|
23893
|
-
_this2._setRangeParams(_this2.getOldRangeParams());
|
|
23894
24247
|
_this2.setState({
|
|
23895
24248
|
hasFocus: true
|
|
24249
|
+
}, function () {
|
|
24250
|
+
if (shouldRestoreSavedRange) {
|
|
24251
|
+
_this2._setRangeParams(savedRangeParams);
|
|
24252
|
+
_this2.restoreRangeAfterBrowserWork(savedRangeParams, "focus-restore");
|
|
24253
|
+
}
|
|
24254
|
+
debugMathRichInput("handleFocus:after-setState", {
|
|
24255
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
24256
|
+
});
|
|
23896
24257
|
});
|
|
23897
24258
|
} catch (error) {
|
|
23898
24259
|
console.error(error);
|
|
@@ -23900,6 +24261,22 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23900
24261
|
});
|
|
23901
24262
|
_defineProperty(_this2, "handleBlur", function (event) {
|
|
23902
24263
|
var onBlur = _this2.props.onBlur;
|
|
24264
|
+
var currentRangeParams = null;
|
|
24265
|
+
try {
|
|
24266
|
+
currentRangeParams = _this2._getRangeParams();
|
|
24267
|
+
if (_this2.rangeHasNonZeroPosition(currentRangeParams)) {
|
|
24268
|
+
_this2.setOldRangeParams(currentRangeParams);
|
|
24269
|
+
}
|
|
24270
|
+
} catch (error) {
|
|
24271
|
+
currentRangeParams = null;
|
|
24272
|
+
}
|
|
24273
|
+
debugMathRichInput("handleBlur:start", {
|
|
24274
|
+
propsValue: _this2.props.value,
|
|
24275
|
+
oldRangeParams: _this2.oldRangeParams,
|
|
24276
|
+
currentRangeParams: currentRangeParams,
|
|
24277
|
+
innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
|
|
24278
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
24279
|
+
});
|
|
23903
24280
|
if (onBlur != null) {
|
|
23904
24281
|
onBlur();
|
|
23905
24282
|
}
|
|
@@ -23909,6 +24286,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
23909
24286
|
hasFocus: false,
|
|
23910
24287
|
showAccentBar: false
|
|
23911
24288
|
});
|
|
24289
|
+
debugMathRichInput("handleBlur:after-setState", {
|
|
24290
|
+
selection: getSelectionSnapshot(_this2.editableDiv)
|
|
24291
|
+
});
|
|
23912
24292
|
} catch (error) {
|
|
23913
24293
|
console.error(error);
|
|
23914
24294
|
}
|
|
@@ -24040,18 +24420,20 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24040
24420
|
|
|
24041
24421
|
// Elements that need to be set before the equation editor is shown
|
|
24042
24422
|
_this2.markedRawText = null;
|
|
24043
|
-
|
|
24044
|
-
|
|
24045
|
-
|
|
24046
|
-
|
|
24047
|
-
endOffset: 0
|
|
24048
|
-
};
|
|
24423
|
+
// No selection has been captured yet. Do not default this to the start of
|
|
24424
|
+
// the editor: React 19 focus/click ordering makes handleFocus restore that
|
|
24425
|
+
// synthetic 0,0 range and moves the caret to the beginning on every click.
|
|
24426
|
+
_this2.oldRangeParams = null;
|
|
24049
24427
|
_this2.moveCursorOnInsert = false;
|
|
24050
24428
|
|
|
24051
24429
|
// Elements defining the current state of range
|
|
24052
24430
|
_this2.afterComponentUpdateData = null; // Set this to update the selection after rendering
|
|
24053
|
-
_this2.
|
|
24054
|
-
|
|
24431
|
+
_this2.lastSetRangeParams = null; // This is set automatically each time the range params are set with setRangeParams
|
|
24432
|
+
_this2.skipNextControlledValueRender = false;
|
|
24433
|
+
_this2.skipNextControlledValue = null;
|
|
24434
|
+
_this2.isMouseDownInEditable = false;
|
|
24435
|
+
_this2.lastRenderedEditableHtml = null;
|
|
24436
|
+
_this2.preserveNativeDomRangeParams = null;
|
|
24055
24437
|
_this2.lastSetActiveButtons = null;
|
|
24056
24438
|
_this2.onChangeCallback = _this2.props.onChange;
|
|
24057
24439
|
|
|
@@ -24141,12 +24523,403 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24141
24523
|
var params = _objectSpread2({}, this.oldRangeParams);
|
|
24142
24524
|
return params;
|
|
24143
24525
|
}
|
|
24526
|
+
}, {
|
|
24527
|
+
key: "rangeIsAtStart",
|
|
24528
|
+
value: function rangeIsAtStart(params) {
|
|
24529
|
+
if (params === null || params === undefined) return false;
|
|
24530
|
+
if (params.startGlobalOffset !== null && params.startGlobalOffset !== undefined && params.endGlobalOffset !== null && params.endGlobalOffset !== undefined) {
|
|
24531
|
+
return params.startGlobalOffset === 0 && params.endGlobalOffset === 0;
|
|
24532
|
+
}
|
|
24533
|
+
return params.startNodeIndex === 0 && params.endNodeIndex === 0 && params.startOffset === 0 && params.endOffset === 0;
|
|
24534
|
+
}
|
|
24535
|
+
}, {
|
|
24536
|
+
key: "rangeHasNonZeroPosition",
|
|
24537
|
+
value: function rangeHasNonZeroPosition(params) {
|
|
24538
|
+
if (params === null || params === undefined) return false;
|
|
24539
|
+
if (params.startGlobalOffset !== null && params.startGlobalOffset !== undefined) {
|
|
24540
|
+
return params.startGlobalOffset > 0;
|
|
24541
|
+
}
|
|
24542
|
+
return params.startNodeIndex > 0 || params.endNodeIndex > 0 || params.startOffset > 0 || params.endOffset > 0;
|
|
24543
|
+
}
|
|
24544
|
+
}, {
|
|
24545
|
+
key: "getCurrentRawTextForProps",
|
|
24546
|
+
value: function getCurrentRawTextForProps(nextProps) {
|
|
24547
|
+
if (!this.editableDiv) return null;
|
|
24548
|
+
try {
|
|
24549
|
+
var rawText = elementToMarkedRawText(this.editableDiv, null, 0, this.enableHtml());
|
|
24550
|
+
rawText = this.stripEmptyInlineFormattingTags(rawText);
|
|
24551
|
+
return removeEncodingIfPlainText(rawText, nextProps.mimeType, this.enableHtml());
|
|
24552
|
+
} catch (error) {
|
|
24553
|
+
debugMathRichInput("getCurrentRawTextForProps:error", {
|
|
24554
|
+
error: error,
|
|
24555
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24556
|
+
});
|
|
24557
|
+
return null;
|
|
24558
|
+
}
|
|
24559
|
+
}
|
|
24560
|
+
}, {
|
|
24561
|
+
key: "shouldSkipRedundantFocusedRender",
|
|
24562
|
+
value: function shouldSkipRedundantFocusedRender(nextProps, nextState) {
|
|
24563
|
+
if (!this.editableDiv) return false;
|
|
24564
|
+
if (nextState !== this.state) return false;
|
|
24565
|
+
if (nextState.hasFocus !== true) return false;
|
|
24566
|
+
if (this.afterComponentUpdateData !== null && this.afterComponentUpdateData !== undefined) {
|
|
24567
|
+
return false;
|
|
24568
|
+
}
|
|
24569
|
+
var currentRawText = this.getCurrentRawTextForProps(nextProps);
|
|
24570
|
+
var shouldSkip = currentRawText === nextProps.value;
|
|
24571
|
+
debugMathRichInput("shouldComponentUpdate:redundant-focused-check", {
|
|
24572
|
+
shouldSkip: shouldSkip,
|
|
24573
|
+
currentRawText: currentRawText,
|
|
24574
|
+
nextValue: nextProps.value,
|
|
24575
|
+
currentPropsValue: this.props.value,
|
|
24576
|
+
nextMimeType: nextProps.mimeType,
|
|
24577
|
+
currentMimeType: this.props.mimeType,
|
|
24578
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24579
|
+
});
|
|
24580
|
+
if (shouldSkip) {
|
|
24581
|
+
var currentRangeParams = this.getRangeParamsPreservingInlinePlaceholder();
|
|
24582
|
+
if (this.rangeHasNonZeroPosition(currentRangeParams)) {
|
|
24583
|
+
this.setOldRangeParams(currentRangeParams);
|
|
24584
|
+
}
|
|
24585
|
+
}
|
|
24586
|
+
return shouldSkip;
|
|
24587
|
+
}
|
|
24588
|
+
}, {
|
|
24589
|
+
key: "isSelectionInsideInlinePlaceholder",
|
|
24590
|
+
value: function isSelectionInsideInlinePlaceholder() {
|
|
24591
|
+
if (typeof document === "undefined") return false;
|
|
24592
|
+
var selection = document.getSelection ? document.getSelection() : null;
|
|
24593
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
24594
|
+
var range = selection.getRangeAt(0);
|
|
24595
|
+
if (!range.collapsed) return false;
|
|
24596
|
+
var node = range.startContainer;
|
|
24597
|
+
if (!node || node.nodeType !== 3) return false;
|
|
24598
|
+
if (node.nodeValue !== SMALL_SPACE) return false;
|
|
24599
|
+
if (range.startOffset !== SMALL_SPACE_LENGTH) return false;
|
|
24600
|
+
var parent = node.parentNode;
|
|
24601
|
+
if (!parent || parent.nodeType !== 1) return false;
|
|
24602
|
+
var tagName = parent.nodeName.toLowerCase();
|
|
24603
|
+
if (!INLINE_STYLE_TAG_NAMES.includes(tagName)) return false;
|
|
24604
|
+
return parent.textContent === SMALL_SPACE;
|
|
24605
|
+
}
|
|
24606
|
+
}, {
|
|
24607
|
+
key: "getRangeParamsPreservingInlinePlaceholder",
|
|
24608
|
+
value: function getRangeParamsPreservingInlinePlaceholder() {
|
|
24609
|
+
var selectionInsideInlinePlaceholder = this.isSelectionInsideInlinePlaceholder();
|
|
24610
|
+
var rangeParams = this._getRangeParams();
|
|
24611
|
+
if (!selectionInsideInlinePlaceholder || !rangeParams) {
|
|
24612
|
+
return rangeParams;
|
|
24613
|
+
}
|
|
24614
|
+
|
|
24615
|
+
// Global offsets ignore the zero-width placeholder; node index preserves it.
|
|
24616
|
+
return _objectSpread2(_objectSpread2({}, rangeParams), {}, {
|
|
24617
|
+
startGlobalOffset: null,
|
|
24618
|
+
endGlobalOffset: null
|
|
24619
|
+
});
|
|
24620
|
+
}
|
|
24621
|
+
}, {
|
|
24622
|
+
key: "getEditableHtmlForRender",
|
|
24623
|
+
value: function getEditableHtmlForRender(html) {
|
|
24624
|
+
var currentRawText = this.editableDiv ? this.getCurrentRawTextForProps(this.props) : null;
|
|
24625
|
+
var liveEditableHtml = this.editableDiv ? this.editableDiv.innerHTML : null;
|
|
24626
|
+
var canPreserveNativeDom = this.state.hasFocus === true && this.editableDiv !== null && this.editableDiv !== undefined && (this.afterComponentUpdateData === null || this.afterComponentUpdateData === undefined) && this.lastRenderedEditableHtml !== null && currentRawText === this.props.value;
|
|
24627
|
+
var preserveRangeParams = canPreserveNativeDom ? this.getRangeParamsPreservingInlinePlaceholder() : null;
|
|
24628
|
+
debugMathRichInput("render:editable-html", {
|
|
24629
|
+
canPreserveNativeDom: canPreserveNativeDom,
|
|
24630
|
+
selectionInsideInlinePlaceholder: this.isSelectionInsideInlinePlaceholder(),
|
|
24631
|
+
propsValue: this.props.value,
|
|
24632
|
+
generatedHtml: html,
|
|
24633
|
+
renderedHtml: canPreserveNativeDom ? liveEditableHtml : html,
|
|
24634
|
+
liveEditableHtml: liveEditableHtml,
|
|
24635
|
+
lastRenderedEditableHtml: this.lastRenderedEditableHtml,
|
|
24636
|
+
currentRawText: currentRawText,
|
|
24637
|
+
preserveRangeParams: preserveRangeParams,
|
|
24638
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
24639
|
+
hasFocus: this.state.hasFocus,
|
|
24640
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24641
|
+
});
|
|
24642
|
+
if (canPreserveNativeDom) {
|
|
24643
|
+
this.lastRenderedEditableHtml = liveEditableHtml;
|
|
24644
|
+
this.preserveNativeDomRangeParams = preserveRangeParams;
|
|
24645
|
+
return liveEditableHtml;
|
|
24646
|
+
}
|
|
24647
|
+
this.lastRenderedEditableHtml = html;
|
|
24648
|
+
this.preserveNativeDomRangeParams = null;
|
|
24649
|
+
return html;
|
|
24650
|
+
}
|
|
24651
|
+
}, {
|
|
24652
|
+
key: "stripEmptyInlineFormattingTags",
|
|
24653
|
+
value: function stripEmptyInlineFormattingTags(rawText) {
|
|
24654
|
+
if (rawText === null || rawText === undefined) return rawText;
|
|
24655
|
+
var cleaned = rawText;
|
|
24656
|
+
var previous = null;
|
|
24657
|
+
while (previous !== cleaned) {
|
|
24658
|
+
previous = cleaned;
|
|
24659
|
+
cleaned = cleaned.replace(EMPTY_INLINE_FORMATTING_REG_EXP, "");
|
|
24660
|
+
}
|
|
24661
|
+
return cleaned;
|
|
24662
|
+
}
|
|
24663
|
+
}, {
|
|
24664
|
+
key: "cleanupEmptyInlineFormattingElements",
|
|
24665
|
+
value: function cleanupEmptyInlineFormattingElements() {
|
|
24666
|
+
if (!this.editableDiv) return false;
|
|
24667
|
+
var removed = false;
|
|
24668
|
+
this.editableDiv.querySelectorAll("b,strong,i,em,u,sub,sup").forEach(function (node) {
|
|
24669
|
+
var text = node.textContent || "";
|
|
24670
|
+
var hasMeaningfulText = text.replace(/[\s\u00a0\u200b\ufeff]/g, "") !== "";
|
|
24671
|
+
var hasProtectedContent = node.querySelector("br,img,math,.katex") !== null;
|
|
24672
|
+
if (!hasMeaningfulText && !hasProtectedContent) {
|
|
24673
|
+
node.remove();
|
|
24674
|
+
removed = true;
|
|
24675
|
+
}
|
|
24676
|
+
});
|
|
24677
|
+
if (removed) {
|
|
24678
|
+
this.editableDiv.normalize();
|
|
24679
|
+
}
|
|
24680
|
+
return removed;
|
|
24681
|
+
}
|
|
24682
|
+
}, {
|
|
24683
|
+
key: "getStyleCommand",
|
|
24684
|
+
value: function getStyleCommand(style) {
|
|
24685
|
+
return INLINE_STYLE_COMMANDS[style] || style;
|
|
24686
|
+
}
|
|
24687
|
+
}, {
|
|
24688
|
+
key: "insertInlineStylePlaceholder",
|
|
24689
|
+
value: function insertInlineStylePlaceholder(style) {
|
|
24690
|
+
var wrapperTag = INLINE_STYLE_WRAPPER_TAGS[style] || "span";
|
|
24691
|
+
var selection = document.getSelection();
|
|
24692
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
24693
|
+
var range = selection.getRangeAt(0);
|
|
24694
|
+
if (!range.collapsed) return false;
|
|
24695
|
+
range.deleteContents();
|
|
24696
|
+
var wrapper = document.createElement(wrapperTag);
|
|
24697
|
+
var textNode = document.createTextNode(SMALL_SPACE);
|
|
24698
|
+
wrapper.appendChild(textNode);
|
|
24699
|
+
range.insertNode(wrapper);
|
|
24700
|
+
var nextRange = document.createRange();
|
|
24701
|
+
nextRange.setStart(textNode, SMALL_SPACE_LENGTH);
|
|
24702
|
+
nextRange.collapse(true);
|
|
24703
|
+
selection.removeAllRanges();
|
|
24704
|
+
selection.addRange(nextRange);
|
|
24705
|
+
var rangeParams = this.getRangeParamsPreservingInlinePlaceholder();
|
|
24706
|
+
if (rangeParams) this.setOldRangeParams(rangeParams);
|
|
24707
|
+
debugMathRichInput("insertInlineStylePlaceholder:after", {
|
|
24708
|
+
style: style,
|
|
24709
|
+
wrapperTag: wrapperTag,
|
|
24710
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
24711
|
+
rangeParams: rangeParams,
|
|
24712
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24713
|
+
});
|
|
24714
|
+
return true;
|
|
24715
|
+
}
|
|
24716
|
+
}, {
|
|
24717
|
+
key: "getActiveButtonsFromCommandState",
|
|
24718
|
+
value: function getActiveButtonsFromCommandState() {
|
|
24719
|
+
var query = function query(command) {
|
|
24720
|
+
try {
|
|
24721
|
+
return document.queryCommandState(command);
|
|
24722
|
+
} catch (error) {
|
|
24723
|
+
return false;
|
|
24724
|
+
}
|
|
24725
|
+
};
|
|
24726
|
+
return {
|
|
24727
|
+
bold: query("bold"),
|
|
24728
|
+
italic: query("italic"),
|
|
24729
|
+
underline: query("underline"),
|
|
24730
|
+
subscript: query("subscript"),
|
|
24731
|
+
superscript: query("superscript")
|
|
24732
|
+
};
|
|
24733
|
+
}
|
|
24734
|
+
}, {
|
|
24735
|
+
key: "isSelectionCollapsed",
|
|
24736
|
+
value: function isSelectionCollapsed() {
|
|
24737
|
+
var selection = document.getSelection();
|
|
24738
|
+
if (!selection || selection.rangeCount === 0) return true;
|
|
24739
|
+
return selection.getRangeAt(0).collapsed;
|
|
24740
|
+
}
|
|
24741
|
+
}, {
|
|
24742
|
+
key: "getActiveButtonsFromAncestorState",
|
|
24743
|
+
value: function getActiveButtonsFromAncestorState() {
|
|
24744
|
+
var rangeParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
24745
|
+
if (rangeParams === null || rangeParams === undefined) {
|
|
24746
|
+
rangeParams = this._getRangeParams();
|
|
24747
|
+
}
|
|
24748
|
+
var activeButtons = {
|
|
24749
|
+
bold: false,
|
|
24750
|
+
italic: false,
|
|
24751
|
+
underline: false,
|
|
24752
|
+
subscript: false,
|
|
24753
|
+
superscript: false
|
|
24754
|
+
};
|
|
24755
|
+
if (rangeParams === null || rangeParams === undefined || rangeParams.startNodeIndex < 0) {
|
|
24756
|
+
return activeButtons;
|
|
24757
|
+
}
|
|
24758
|
+
var node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
24759
|
+
if (node === null || node === undefined) return activeButtons;
|
|
24760
|
+
do {
|
|
24761
|
+
if (node.nodeType === 1) {
|
|
24762
|
+
var nodeName = node.nodeName.toLowerCase();
|
|
24763
|
+
if (nodeName === "b" || nodeName === "strong") activeButtons.bold = true;else if (nodeName === "i" || nodeName === "em") activeButtons.italic = true;else if (nodeName === "u") activeButtons.underline = true;else if (nodeName === "sub") activeButtons.subscript = true;else if (nodeName === "sup") activeButtons.superscript = true;
|
|
24764
|
+
} else if (node.classList !== null && node.classList !== undefined && node.classList.contains("MathRichInput")) break;
|
|
24765
|
+
node = node.parentNode;
|
|
24766
|
+
} while (node !== null);
|
|
24767
|
+
return activeButtons;
|
|
24768
|
+
}
|
|
24769
|
+
}, {
|
|
24770
|
+
key: "getActiveButtonsFromSelection",
|
|
24771
|
+
value: function getActiveButtonsFromSelection() {
|
|
24772
|
+
var rangeParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
24773
|
+
var commandState = this.getActiveButtonsFromCommandState();
|
|
24774
|
+
var ancestorState = this.getActiveButtonsFromAncestorState(rangeParams);
|
|
24775
|
+
return {
|
|
24776
|
+
bold: commandState.bold || ancestorState.bold,
|
|
24777
|
+
italic: commandState.italic || ancestorState.italic,
|
|
24778
|
+
underline: commandState.underline || ancestorState.underline,
|
|
24779
|
+
subscript: commandState.subscript || ancestorState.subscript,
|
|
24780
|
+
superscript: commandState.superscript || ancestorState.superscript
|
|
24781
|
+
};
|
|
24782
|
+
}
|
|
24783
|
+
}, {
|
|
24784
|
+
key: "setActiveButtonsIfChanged",
|
|
24785
|
+
value: function setActiveButtonsIfChanged(activeButtons) {
|
|
24786
|
+
var update = true;
|
|
24787
|
+
if (this.lastSetActiveButtons !== null) {
|
|
24788
|
+
update = false;
|
|
24789
|
+
for (var style in activeButtons) {
|
|
24790
|
+
if (activeButtons[style] !== this.lastSetActiveButtons[style]) {
|
|
24791
|
+
update = true;
|
|
24792
|
+
break;
|
|
24793
|
+
}
|
|
24794
|
+
}
|
|
24795
|
+
}
|
|
24796
|
+
if (update) {
|
|
24797
|
+
this.lastSetActiveButtons = activeButtons;
|
|
24798
|
+
this.setState({
|
|
24799
|
+
activeButtons: activeButtons
|
|
24800
|
+
});
|
|
24801
|
+
}
|
|
24802
|
+
}
|
|
24803
|
+
}, {
|
|
24804
|
+
key: "getInlineStyleAncestor",
|
|
24805
|
+
value: function getInlineStyleAncestor(style) {
|
|
24806
|
+
var tags = INLINE_STYLE_TAGS[style];
|
|
24807
|
+
if (!tags) return null;
|
|
24808
|
+
var selection = document.getSelection();
|
|
24809
|
+
if (!selection || selection.rangeCount === 0) return null;
|
|
24810
|
+
var node = selection.getRangeAt(0).startContainer;
|
|
24811
|
+
if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;
|
|
24812
|
+
while (node && node !== this.editableDiv) {
|
|
24813
|
+
if (node.nodeType === Node.ELEMENT_NODE && tags.includes(node.nodeName.toLowerCase())) {
|
|
24814
|
+
return node;
|
|
24815
|
+
}
|
|
24816
|
+
node = node.parentNode;
|
|
24817
|
+
}
|
|
24818
|
+
return null;
|
|
24819
|
+
}
|
|
24820
|
+
}, {
|
|
24821
|
+
key: "isRangeAtEndOfNode",
|
|
24822
|
+
value: function isRangeAtEndOfNode(range, node) {
|
|
24823
|
+
try {
|
|
24824
|
+
var afterRange = document.createRange();
|
|
24825
|
+
afterRange.setStart(range.startContainer, range.startOffset);
|
|
24826
|
+
afterRange.setEnd(node, node.childNodes.length);
|
|
24827
|
+
var textAfterCaret = afterRange.toString().replace(/[\s\u00a0\u200b\ufeff]/g, "");
|
|
24828
|
+
if (textAfterCaret === "") return true;
|
|
24829
|
+
} catch (error) {
|
|
24830
|
+
// Fall back to boundary comparison below if the browser rejects the range.
|
|
24831
|
+
}
|
|
24832
|
+
var nodeEndRange = document.createRange();
|
|
24833
|
+
nodeEndRange.selectNodeContents(node);
|
|
24834
|
+
nodeEndRange.collapse(false);
|
|
24835
|
+
return range.compareBoundaryPoints(Range.START_TO_START, nodeEndRange) === 0;
|
|
24836
|
+
}
|
|
24837
|
+
}, {
|
|
24838
|
+
key: "moveTrailingWhitespaceOutsideInlineStyle",
|
|
24839
|
+
value: function moveTrailingWhitespaceOutsideInlineStyle(style, command) {
|
|
24840
|
+
var selection = document.getSelection();
|
|
24841
|
+
if (!selection || selection.rangeCount === 0) return false;
|
|
24842
|
+
var range = selection.getRangeAt(0);
|
|
24843
|
+
if (!range.collapsed) return false;
|
|
24844
|
+
var ancestor = this.getInlineStyleAncestor(style);
|
|
24845
|
+
if (!ancestor || !this.isRangeAtEndOfNode(range, ancestor)) return false;
|
|
24846
|
+
if (range.startContainer.nodeType !== Node.TEXT_NODE) return false;
|
|
24847
|
+
var textNode = range.startContainer;
|
|
24848
|
+
var text = textNode.nodeValue || "";
|
|
24849
|
+
var moveStart = range.startOffset;
|
|
24850
|
+
while (moveStart > 0) {
|
|
24851
|
+
var _char = text.charAt(moveStart - 1);
|
|
24852
|
+
if (_char !== " " && _char !== "\xA0") break;
|
|
24853
|
+
moveStart -= 1;
|
|
24854
|
+
}
|
|
24855
|
+
if (moveStart === range.startOffset) return false;
|
|
24856
|
+
var movedText = text.substring(moveStart, range.startOffset);
|
|
24857
|
+
textNode.nodeValue = text.substring(0, moveStart) + text.substring(range.startOffset);
|
|
24858
|
+
var outsideNode = ancestor.nextSibling;
|
|
24859
|
+
if (!outsideNode || outsideNode.nodeType !== Node.TEXT_NODE) {
|
|
24860
|
+
outsideNode = document.createTextNode(movedText);
|
|
24861
|
+
ancestor.parentNode.insertBefore(outsideNode, ancestor.nextSibling);
|
|
24862
|
+
} else {
|
|
24863
|
+
outsideNode.nodeValue = movedText + outsideNode.nodeValue;
|
|
24864
|
+
}
|
|
24865
|
+
var nextRange = document.createRange();
|
|
24866
|
+
nextRange.setStart(outsideNode, movedText.length);
|
|
24867
|
+
nextRange.collapse(true);
|
|
24868
|
+
selection.removeAllRanges();
|
|
24869
|
+
selection.addRange(nextRange);
|
|
24870
|
+
try {
|
|
24871
|
+
if (document.queryCommandState(command)) {
|
|
24872
|
+
document.execCommand(command, false, null);
|
|
24873
|
+
}
|
|
24874
|
+
} catch (error) {
|
|
24875
|
+
// DOM position is the source of truth here; command state is best effort.
|
|
24876
|
+
}
|
|
24877
|
+
var rangeParams = this._getRangeParams();
|
|
24878
|
+
if (rangeParams) {
|
|
24879
|
+
this.setOldRangeParams(rangeParams);
|
|
24880
|
+
this.applyCurrentEditableDomToComponent(rangeParams);
|
|
24881
|
+
}
|
|
24882
|
+
debugMathRichInput("moveTrailingWhitespaceOutsideInlineStyle:after", {
|
|
24883
|
+
style: style,
|
|
24884
|
+
command: command,
|
|
24885
|
+
movedText: movedText,
|
|
24886
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
24887
|
+
rangeParams: rangeParams,
|
|
24888
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24889
|
+
});
|
|
24890
|
+
return true;
|
|
24891
|
+
}
|
|
24892
|
+
}, {
|
|
24893
|
+
key: "applyCurrentEditableDomToComponent",
|
|
24894
|
+
value: function applyCurrentEditableDomToComponent() {
|
|
24895
|
+
var rangeParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
24896
|
+
if (!this.editableDiv) return;
|
|
24897
|
+
if (rangeParams === null || rangeParams === undefined) {
|
|
24898
|
+
rangeParams = this._getRangeParams();
|
|
24899
|
+
}
|
|
24900
|
+
if (rangeParams === null || rangeParams === undefined) return;
|
|
24901
|
+
var rawText = elementToMarkedRawText(this.editableDiv, null, 0, this.enableHtml());
|
|
24902
|
+
rawText = this.stripEmptyInlineFormattingTags(rawText);
|
|
24903
|
+
var nodeCounts = this._countNodeTypes();
|
|
24904
|
+
var hasMath = /<math>/i.test(rawText) || nodeCounts.math > 0;
|
|
24905
|
+
var hasHtml = nodeCounts.html > 0 || /<[^>]+>/i.test(rawText);
|
|
24906
|
+
var mimeType = this.getMimeType(hasHtml, hasMath);
|
|
24907
|
+
rawText = removeEncodingIfPlainText(rawText, mimeType, this.enableHtml());
|
|
24908
|
+
rawText = this.stripEmptyInlineFormattingTags(rawText);
|
|
24909
|
+
this.applyChangesToComponent(rawText, mimeType, rangeParams, this.props.useExpertMode, this.props.selectedTab, {
|
|
24910
|
+
skipControlledRender: true
|
|
24911
|
+
});
|
|
24912
|
+
}
|
|
24144
24913
|
}, {
|
|
24145
24914
|
key: "_getRangeParams",
|
|
24146
24915
|
value: function _getRangeParams() {
|
|
24147
24916
|
var editableDiv = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
24148
24917
|
if (editableDiv === null) editableDiv = this.editableDiv;
|
|
24149
24918
|
var rangeParams = getRangeParams(editableDiv);
|
|
24919
|
+
debugMathRichInput("MathRichInput:_getRangeParams", {
|
|
24920
|
+
rangeParams: rangeParams,
|
|
24921
|
+
selection: getSelectionSnapshot(editableDiv)
|
|
24922
|
+
});
|
|
24150
24923
|
return rangeParams;
|
|
24151
24924
|
}
|
|
24152
24925
|
}, {
|
|
@@ -24175,6 +24948,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24175
24948
|
value: function componentDidMount() {
|
|
24176
24949
|
try {
|
|
24177
24950
|
setupKatex();
|
|
24951
|
+
debugMathRichInput("componentDidMount", {
|
|
24952
|
+
propsValue: this.props.value,
|
|
24953
|
+
propsMimeType: this.props.mimeType,
|
|
24954
|
+
autofocus: this.props.autofocus,
|
|
24955
|
+
autoFocus: this.props.autoFocus,
|
|
24956
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
24957
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24958
|
+
});
|
|
24178
24959
|
this.editableDiv.addEventListener("keydown", this.handleKeyDown);
|
|
24179
24960
|
this.editableDiv.addEventListener("keyup", this.handleKeyUp);
|
|
24180
24961
|
this.editableDiv.addEventListener("paste", this.handlePaste);
|
|
@@ -24196,14 +24977,137 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24196
24977
|
console.error(error);
|
|
24197
24978
|
}
|
|
24198
24979
|
}
|
|
24980
|
+
}, {
|
|
24981
|
+
key: "shouldComponentUpdate",
|
|
24982
|
+
value: function shouldComponentUpdate(nextProps, nextState) {
|
|
24983
|
+
if (this.skipNextControlledValueRender === true) {
|
|
24984
|
+
var canSkip = nextProps.value === this.skipNextControlledValue && nextState === this.state;
|
|
24985
|
+
debugMathRichInput("shouldComponentUpdate:native-input-gate", {
|
|
24986
|
+
canSkip: canSkip,
|
|
24987
|
+
nextValue: nextProps.value,
|
|
24988
|
+
skipNextControlledValue: this.skipNextControlledValue,
|
|
24989
|
+
currentPropsValue: this.props.value,
|
|
24990
|
+
stateChanged: nextState !== this.state,
|
|
24991
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
24992
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
24993
|
+
});
|
|
24994
|
+
this.skipNextControlledValueRender = false;
|
|
24995
|
+
this.skipNextControlledValue = null;
|
|
24996
|
+
if (canSkip) return false;
|
|
24997
|
+
}
|
|
24998
|
+
if (this.shouldSkipRedundantFocusedRender(nextProps, nextState)) {
|
|
24999
|
+
debugMathRichInput("shouldComponentUpdate:skip-redundant-focused-render", {
|
|
25000
|
+
nextValue: nextProps.value,
|
|
25001
|
+
currentPropsValue: this.props.value,
|
|
25002
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25003
|
+
});
|
|
25004
|
+
return false;
|
|
25005
|
+
}
|
|
25006
|
+
return true;
|
|
25007
|
+
}
|
|
25008
|
+
|
|
25009
|
+
/**
|
|
25010
|
+
* The browser's own reading of an HTML string.
|
|
25011
|
+
*
|
|
25012
|
+
* React renders `<p>​…</P>`; the DOM reports `<p>…</p>` — same content, different bytes. So a
|
|
25013
|
+
* comparison against `innerHTML` has to be made in the DOM's spelling, or it never matches and the
|
|
25014
|
+
* reconciliation below would rewrite the field on every single update.
|
|
25015
|
+
*/
|
|
25016
|
+
}, {
|
|
25017
|
+
key: "normaliseHtml",
|
|
25018
|
+
value: function normaliseHtml(html) {
|
|
25019
|
+
var probe = document.createElement("span");
|
|
25020
|
+
probe.innerHTML = html;
|
|
25021
|
+
return probe.innerHTML;
|
|
25022
|
+
}
|
|
25023
|
+
|
|
25024
|
+
/**
|
|
25025
|
+
* Put back what React believes it rendered, when the live DOM has drifted away from it.
|
|
25026
|
+
*
|
|
25027
|
+
* WHY THIS IS NEEDED AT ALL. `dangerouslySetInnerHTML` writes only when the html STRING changes: React
|
|
25028
|
+
* compares the new `__html` with the previous one and, finding them equal, leaves the DOM alone. That is
|
|
25029
|
+
* sound as long as React is the only thing that touches the DOM — and here it is not. `handlePaste`
|
|
25030
|
+
* removes the selected content itself, through `selection.deleteFromDocument()`, so the field empties
|
|
25031
|
+
* behind React's back while React's record of it still says "full".
|
|
25032
|
+
*
|
|
25033
|
+
* Paste the same words back into the field they came from and the two mistakes meet: the DOM is empty,
|
|
25034
|
+
* the html React computes is identical to the html it rendered last time, so it writes nothing — and
|
|
25035
|
+
* the field stays blank while `value`, the command, and the row in the database are all correct. The
|
|
25036
|
+
* teacher sees their text vanish on paste; nothing anywhere reports an error.
|
|
25037
|
+
*
|
|
25038
|
+
* It writes back the SAME string React itself passed to `dangerouslySetInnerHTML` on this render, so
|
|
25039
|
+
* nothing reaches the DOM here that React was not already putting there — the trust boundary is the
|
|
25040
|
+
* one that existed before, not a new one.
|
|
25041
|
+
*
|
|
25042
|
+
* Only reconciled after a PROGRAMMATIC edit (`afterComponentUpdateData` is set), or when the paste
|
|
25043
|
+
* asks directly (`force`). Ordinary typing goes down the native-input path, where the browser has
|
|
25044
|
+
* already put the character in the right place and the caret with it, and rewriting the DOM there
|
|
25045
|
+
* would move the caret to the front on every keystroke.
|
|
25046
|
+
*
|
|
25047
|
+
* `force` exists because a re-render is not guaranteed to happen AT ALL. When the host already holds
|
|
25048
|
+
* the value being applied — paste the same words back into the field they came from — the prop never
|
|
25049
|
+
* changes, so nothing re-renders and `componentDidUpdate` never runs. `lastRenderedEditableHtml` is
|
|
25050
|
+
* then exactly right: it is the html for that unchanged value, which is what the DOM should hold.
|
|
25051
|
+
*
|
|
25052
|
+
* Returns whether it wrote, so the caller knows whether the caret needs putting back.
|
|
25053
|
+
*/
|
|
25054
|
+
}, {
|
|
25055
|
+
key: "reconcileEditableDomWithRender",
|
|
25056
|
+
value: function reconcileEditableDomWithRender() {
|
|
25057
|
+
var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
25058
|
+
if (!this.editableDiv) return false;
|
|
25059
|
+
if (!force && (this.afterComponentUpdateData === null || this.afterComponentUpdateData === undefined)) return false;
|
|
25060
|
+
var html = force ? rawTextToHtml(katex$1, this.props.value || "", this.props.mimeType) : this.lastRenderedEditableHtml;
|
|
25061
|
+
if (html === null || html === undefined) return false;
|
|
25062
|
+
if (this.editableDiv.innerHTML === this.normaliseHtml(html)) return false;
|
|
25063
|
+
debugMathRichInput("reconcile", {
|
|
25064
|
+
force: force,
|
|
25065
|
+
liveInnerHTML: this.editableDiv.innerHTML,
|
|
25066
|
+
html: html
|
|
25067
|
+
});
|
|
25068
|
+
this.editableDiv.innerHTML = html;
|
|
25069
|
+
this.lastRenderedEditableHtml = html;
|
|
25070
|
+
return true;
|
|
25071
|
+
}
|
|
24199
25072
|
}, {
|
|
24200
25073
|
key: "componentDidUpdate",
|
|
24201
25074
|
value: function componentDidUpdate() {
|
|
24202
25075
|
try {
|
|
25076
|
+
debugMathRichInput("componentDidUpdate:start", {
|
|
25077
|
+
propsValue: this.props.value,
|
|
25078
|
+
propsMimeType: this.props.mimeType,
|
|
25079
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
25080
|
+
hasFocus: this.state.hasFocus,
|
|
25081
|
+
showEquationEditor: this.state.showEquationEditor,
|
|
25082
|
+
innerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
25083
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25084
|
+
});
|
|
25085
|
+
// Before the caret is restored, not after: the offsets below are counted through the text, so
|
|
25086
|
+
// restoring them against an empty field puts the caret at 0 and loses the position as well.
|
|
25087
|
+
this.reconcileEditableDomWithRender();
|
|
24203
25088
|
if (this.afterComponentUpdateData !== null && this.afterComponentUpdateData !== undefined && this.afterComponentUpdateData.value === this.props.value) {
|
|
25089
|
+
debugMathRichInput("componentDidUpdate:restore-before", {
|
|
25090
|
+
rangeParams: this.afterComponentUpdateData.rangeParams,
|
|
25091
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25092
|
+
});
|
|
24204
25093
|
this._setRangeParams(this.afterComponentUpdateData.rangeParams);
|
|
24205
25094
|
this.afterComponentUpdateData = null;
|
|
24206
25095
|
this.updateActiveButtons();
|
|
25096
|
+
debugMathRichInput("componentDidUpdate:restore-after", {
|
|
25097
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25098
|
+
});
|
|
25099
|
+
}
|
|
25100
|
+
if (this.preserveNativeDomRangeParams !== null && this.preserveNativeDomRangeParams !== undefined && this.state.hasFocus === true) {
|
|
25101
|
+
var rangeParams = this.preserveNativeDomRangeParams;
|
|
25102
|
+
this.preserveNativeDomRangeParams = null;
|
|
25103
|
+
debugMathRichInput("componentDidUpdate:preserve-restore-before", {
|
|
25104
|
+
rangeParams: rangeParams,
|
|
25105
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25106
|
+
});
|
|
25107
|
+
this._setRangeParams(rangeParams);
|
|
25108
|
+
debugMathRichInput("componentDidUpdate:preserve-restore-after", {
|
|
25109
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25110
|
+
});
|
|
24207
25111
|
}
|
|
24208
25112
|
} catch (error) {
|
|
24209
25113
|
console.error(error);
|
|
@@ -24216,20 +25120,31 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24216
25120
|
if (rangeParams === null || rangeParams === undefined) rangeParams = this._getRangeParams();
|
|
24217
25121
|
if (rangeParams.startOffset !== 0) console.warn("Unexpected startOffset " + rangeParams.startOffset + " in insertAfterSmallSpace");
|
|
24218
25122
|
var node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
24219
|
-
|
|
24220
|
-
|
|
24221
|
-
|
|
24222
|
-
|
|
25123
|
+
if (node === null || node === undefined || node.nodeValue === null) {
|
|
25124
|
+
console.warn("Could not find small-space text node");
|
|
25125
|
+
return;
|
|
25126
|
+
}
|
|
25127
|
+
|
|
25128
|
+
// The initial empty editor contains a hidden zero-width space. Handling
|
|
25129
|
+
// the first printable key by re-rendering through React replaces the
|
|
25130
|
+
// text node and drops Chrome's selection to the start under React 19.
|
|
25131
|
+
// Mutate the already-focused DOM node, then skip the matching controlled
|
|
25132
|
+
// render exactly like the normal contenteditable input path.
|
|
25133
|
+
node.nodeValue = node.nodeValue.substring(0, 1) + new_char + node.nodeValue.substring(1);
|
|
24223
25134
|
var new_rangeParams = {
|
|
24224
25135
|
startNodeIndex: rangeParams.startNodeIndex,
|
|
24225
|
-
|
|
25136
|
+
startOffset: 1 + new_char.length,
|
|
24226
25137
|
endNodeIndex: rangeParams.startNodeIndex,
|
|
24227
|
-
|
|
25138
|
+
endOffset: 1 + new_char.length,
|
|
24228
25139
|
startGlobalOffset: rangeParams.startGlobalOffset + new_char.length,
|
|
24229
25140
|
endGlobalOffset: rangeParams.endGlobalOffset + new_char.length
|
|
24230
25141
|
};
|
|
25142
|
+
this._setRangeParams(new_rangeParams);
|
|
25143
|
+
var new_raw_text = elementToMarkedRawText(this.editableDiv, null, 0, this.enableHtml());
|
|
24231
25144
|
new_raw_text = removeEncodingIfPlainText(new_raw_text, this.props.mimeType, this.enableHtml());
|
|
24232
|
-
this.applyChangesToComponent(new_raw_text, this.props.mimeType, new_rangeParams, this.props.useExpertMode, this.props.selectedTab
|
|
25145
|
+
this.applyChangesToComponent(new_raw_text, this.props.mimeType, new_rangeParams, this.props.useExpertMode, this.props.selectedTab, {
|
|
25146
|
+
skipControlledRender: true
|
|
25147
|
+
});
|
|
24233
25148
|
} catch (error) {
|
|
24234
25149
|
console.error(error);
|
|
24235
25150
|
}
|
|
@@ -24243,6 +25158,23 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24243
25158
|
console.log(this.props);
|
|
24244
25159
|
}
|
|
24245
25160
|
var html = rawTextToHtml(katex$1, this.props.value || "", this.props.mimeType);
|
|
25161
|
+
var editableHtml = this.getEditableHtmlForRender(html);
|
|
25162
|
+
debugMathRichInput("render", {
|
|
25163
|
+
propsValue: this.props.value,
|
|
25164
|
+
propsMimeType: this.props.mimeType,
|
|
25165
|
+
html: html,
|
|
25166
|
+
editableHtml: editableHtml,
|
|
25167
|
+
state: {
|
|
25168
|
+
hasFocus: this.state.hasFocus,
|
|
25169
|
+
showEquationEditor: this.state.showEquationEditor,
|
|
25170
|
+
showAccentBar: this.state.showAccentBar,
|
|
25171
|
+
keyPressed: this.state.keyPressed
|
|
25172
|
+
},
|
|
25173
|
+
afterComponentUpdateData: this.afterComponentUpdateData,
|
|
25174
|
+
oldRangeParams: this.oldRangeParams,
|
|
25175
|
+
editableInnerHTML: this.editableDiv ? this.editableDiv.innerHTML : null,
|
|
25176
|
+
selection: getSelectionSnapshot(this.editableDiv)
|
|
25177
|
+
});
|
|
24246
25178
|
|
|
24247
25179
|
// Debug render math
|
|
24248
25180
|
// if (this.props.value && this.props.value.includes("<math>")) {
|
|
@@ -24301,7 +25233,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24301
25233
|
onCompositionStart: this.handleCompositionStart,
|
|
24302
25234
|
onCompositionEnd: this.handleCompositionEnd,
|
|
24303
25235
|
dangerouslySetInnerHTML: {
|
|
24304
|
-
__html:
|
|
25236
|
+
__html: editableHtml
|
|
24305
25237
|
},
|
|
24306
25238
|
"data-placeholder": this.props.placeholder || ""
|
|
24307
25239
|
}))), this.state.showEquationEditor && /*#__PURE__*/React__default["default"].createElement(EquationEditorModal, {
|
|
@@ -24320,7 +25252,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
|
|
|
24320
25252
|
function getAllTextNodes(node) {
|
|
24321
25253
|
var nodes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
24322
25254
|
if (node === null) {
|
|
24323
|
-
console.warn("
|
|
25255
|
+
console.warn("Developer supplied a null node. Ignoring");
|
|
24324
25256
|
return nodes;
|
|
24325
25257
|
}
|
|
24326
25258
|
if (nodes === null) {
|
|
@@ -24345,8 +25277,9 @@ function getAllTextNodes(node) {
|
|
|
24345
25277
|
return nodes;
|
|
24346
25278
|
}
|
|
24347
25279
|
function renderMathInNode(node) {
|
|
25280
|
+
if (node === null || node === undefined) return;
|
|
24348
25281
|
try {
|
|
24349
|
-
var textNodes = getAllTextNodes(node);
|
|
25282
|
+
var textNodes = getAllTextNodes(node, []);
|
|
24350
25283
|
for (var i = 0; i < textNodes.length; i++) {
|
|
24351
25284
|
try {
|
|
24352
25285
|
var text = textNodes[i].nodeValue;
|