whistle.mockbubu 2.2.7 → 2.2.8
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/package.json +1 -1
- package/public/js/app.js +236 -32
- package/public/js/app.js.map +1 -1
package/package.json
CHANGED
package/public/js/app.js
CHANGED
|
@@ -1466,8 +1466,10 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1466
1466
|
/* harmony import */ var core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/esnext.iterator.for-each.js */ "./node_modules/.pnpm/core-js@3.8.3/node_modules/core-js/modules/esnext.iterator.for-each.js");
|
|
1467
1467
|
/* harmony import */ var core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_1__);
|
|
1468
1468
|
/* harmony import */ var monaco_editor__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! monaco-editor */ "include-loader!./node_modules/.pnpm/monaco-editor@0.55.1/node_modules/monaco-editor/esm/vs/editor/editor.main.js");
|
|
1469
|
-
/* harmony import */ var
|
|
1469
|
+
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! vue */ "./node_modules/.pnpm/vue@2.7.14/node_modules/vue/dist/vue.runtime.esm.js");
|
|
1470
1470
|
/* harmony import */ var _utils_monaco_previews__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../utils/monaco-previews */ "./src/utils/monaco-previews.js");
|
|
1471
|
+
/* harmony import */ var _utils_monaco_fast_paste__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../utils/monaco-fast-paste */ "./src/utils/monaco-fast-paste.js");
|
|
1472
|
+
|
|
1471
1473
|
|
|
1472
1474
|
|
|
1473
1475
|
|
|
@@ -1507,16 +1509,18 @@ const __default__ = {
|
|
|
1507
1509
|
function getMonacoTheme() {
|
|
1508
1510
|
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'vs-dark' : 'vs';
|
|
1509
1511
|
}
|
|
1510
|
-
const monacoRef = (0,
|
|
1511
|
-
const monacoInstance = (0,
|
|
1512
|
-
const error = (0,
|
|
1513
|
-
const internalChange = (0,
|
|
1514
|
-
const showScrollTop = (0,
|
|
1515
|
-
const scrollTimer = (0,
|
|
1516
|
-
const isButtonHovered = (0,
|
|
1517
|
-
const lastScrollTime = (0,
|
|
1512
|
+
const monacoRef = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(null);
|
|
1513
|
+
const monacoInstance = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(null);
|
|
1514
|
+
const error = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(false);
|
|
1515
|
+
const internalChange = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(false);
|
|
1516
|
+
const showScrollTop = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(false);
|
|
1517
|
+
const scrollTimer = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(null);
|
|
1518
|
+
const isButtonHovered = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(false);
|
|
1519
|
+
const lastScrollTime = (0,vue__WEBPACK_IMPORTED_MODULE_5__.ref)(0);
|
|
1518
1520
|
let previewTimer = null;
|
|
1519
1521
|
let themeObserver = null;
|
|
1522
|
+
let contentChangeTimer = null;
|
|
1523
|
+
let fastPasteHandle = null;
|
|
1520
1524
|
function applyPreviews(editorInstance) {
|
|
1521
1525
|
if (!editorInstance) return;
|
|
1522
1526
|
if (previewTimer) clearTimeout(previewTimer);
|
|
@@ -1564,23 +1568,51 @@ const __default__ = {
|
|
|
1564
1568
|
enabled: 'on'
|
|
1565
1569
|
}
|
|
1566
1570
|
});
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1571
|
+
|
|
1572
|
+
// ─── 安装大内容快速粘贴优化(独立模块) ─────────────────────────────────────
|
|
1573
|
+
fastPasteHandle = (0,_utils_monaco_fast_paste__WEBPACK_IMPORTED_MODULE_4__.installFastPaste)(monacoInstance.value, {
|
|
1574
|
+
isReadOnly: () => props.readOnly,
|
|
1575
|
+
onContentChange: raw => {
|
|
1576
|
+
try {
|
|
1577
|
+
const jsonData = JSON.parse(raw);
|
|
1578
|
+
error.value = false;
|
|
1579
|
+
internalChange.value = true;
|
|
1580
|
+
emit('json-change', jsonData);
|
|
1581
|
+
emit('input', jsonData);
|
|
1582
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_5__.nextTick)(() => {
|
|
1583
|
+
internalChange.value = false;
|
|
1584
|
+
});
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
error.value = true;
|
|
1587
|
+
emit('has-error', err);
|
|
1588
|
+
}
|
|
1589
|
+
applyPreviews(monacoInstance.value);
|
|
1582
1590
|
}
|
|
1583
|
-
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
// ─── 常规内容变更处理 ────────────────────────────────────────────────────────
|
|
1594
|
+
monacoInstance.value.onDidChangeModelContent(() => {
|
|
1595
|
+
if (internalChange.value || fastPasteHandle?.isPasting) return;
|
|
1596
|
+
// 防抖:避免连续编辑时频繁触发 JSON.parse + Vue 响应式更新
|
|
1597
|
+
if (contentChangeTimer) clearTimeout(contentChangeTimer);
|
|
1598
|
+
contentChangeTimer = setTimeout(() => {
|
|
1599
|
+
if (!monacoInstance.value) return;
|
|
1600
|
+
const raw = monacoInstance.value.getValue();
|
|
1601
|
+
try {
|
|
1602
|
+
const jsonData = JSON.parse(raw);
|
|
1603
|
+
error.value = false;
|
|
1604
|
+
internalChange.value = true;
|
|
1605
|
+
emit('json-change', jsonData);
|
|
1606
|
+
emit('input', jsonData);
|
|
1607
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_5__.nextTick)(() => {
|
|
1608
|
+
internalChange.value = false;
|
|
1609
|
+
});
|
|
1610
|
+
} catch (e) {
|
|
1611
|
+
error.value = true;
|
|
1612
|
+
emit('has-error', e);
|
|
1613
|
+
}
|
|
1614
|
+
applyPreviews(monacoInstance.value);
|
|
1615
|
+
}, 300);
|
|
1584
1616
|
});
|
|
1585
1617
|
monacoInstance.value.onDidScrollChange(() => {
|
|
1586
1618
|
lastScrollTime.value = Date.now();
|
|
@@ -1608,6 +1640,11 @@ const __default__ = {
|
|
|
1608
1640
|
themeObserver = null;
|
|
1609
1641
|
}
|
|
1610
1642
|
if (previewTimer) clearTimeout(previewTimer);
|
|
1643
|
+
if (contentChangeTimer) clearTimeout(contentChangeTimer);
|
|
1644
|
+
if (fastPasteHandle) {
|
|
1645
|
+
fastPasteHandle.dispose();
|
|
1646
|
+
fastPasteHandle = null;
|
|
1647
|
+
}
|
|
1611
1648
|
if (monacoInstance.value) {
|
|
1612
1649
|
(monacoInstance.value._imageWidgets || []).forEach(w => monacoInstance.value.removeContentWidget(w));
|
|
1613
1650
|
if (monacoInstance.value._colorDecIds?.length) {
|
|
@@ -1680,34 +1717,32 @@ const __default__ = {
|
|
|
1680
1717
|
});
|
|
1681
1718
|
|
|
1682
1719
|
// ─── 生命周期 ─────────────────────────────────────────────
|
|
1683
|
-
(0,
|
|
1720
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_5__.onMounted)(() => {
|
|
1684
1721
|
initMonaco();
|
|
1685
1722
|
});
|
|
1686
|
-
(0,
|
|
1723
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_5__.onBeforeUnmount)(() => {
|
|
1687
1724
|
destroyMonaco();
|
|
1688
1725
|
if (scrollTimer.value) clearTimeout(scrollTimer.value);
|
|
1689
1726
|
});
|
|
1690
1727
|
|
|
1691
1728
|
// ─── 监听外部 value 变化 ──────────────────────────────────
|
|
1692
|
-
(0,
|
|
1729
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_5__.watch)(() => props.value, async val => {
|
|
1693
1730
|
if (internalChange.value) return;
|
|
1694
1731
|
if (monacoInstance.value) {
|
|
1695
1732
|
const str = valueToString(val);
|
|
1696
1733
|
if (str !== monacoInstance.value.getValue()) {
|
|
1697
1734
|
internalChange.value = true;
|
|
1698
1735
|
monacoInstance.value.setValue(str);
|
|
1699
|
-
await (0,
|
|
1736
|
+
await (0,vue__WEBPACK_IMPORTED_MODULE_5__.nextTick)();
|
|
1700
1737
|
internalChange.value = false;
|
|
1701
1738
|
applyPreviews(monacoInstance.value);
|
|
1702
1739
|
}
|
|
1703
1740
|
error.value = false;
|
|
1704
1741
|
}
|
|
1705
|
-
}, {
|
|
1706
|
-
deep: true
|
|
1707
1742
|
});
|
|
1708
1743
|
|
|
1709
1744
|
// 只读状态变化时动态更新
|
|
1710
|
-
(0,
|
|
1745
|
+
(0,vue__WEBPACK_IMPORTED_MODULE_5__.watch)(() => props.readOnly, val => {
|
|
1711
1746
|
monacoInstance.value?.updateOptions({
|
|
1712
1747
|
readOnly: val
|
|
1713
1748
|
});
|
|
@@ -1727,6 +1762,8 @@ const __default__ = {
|
|
|
1727
1762
|
lastScrollTime,
|
|
1728
1763
|
previewTimer,
|
|
1729
1764
|
themeObserver,
|
|
1765
|
+
contentChangeTimer,
|
|
1766
|
+
fastPasteHandle,
|
|
1730
1767
|
applyPreviews,
|
|
1731
1768
|
localeMap,
|
|
1732
1769
|
valueToString,
|
|
@@ -11843,6 +11880,173 @@ function createSingletonHook(hook) {
|
|
|
11843
11880
|
|
|
11844
11881
|
/***/ }),
|
|
11845
11882
|
|
|
11883
|
+
/***/ "./src/utils/monaco-fast-paste.js":
|
|
11884
|
+
/*!****************************************!*\
|
|
11885
|
+
!*** ./src/utils/monaco-fast-paste.js ***!
|
|
11886
|
+
\****************************************/
|
|
11887
|
+
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
11888
|
+
|
|
11889
|
+
"use strict";
|
|
11890
|
+
__webpack_require__.r(__webpack_exports__);
|
|
11891
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
11892
|
+
/* harmony export */ installFastPaste: function() { return /* binding */ installFastPaste; }
|
|
11893
|
+
/* harmony export */ });
|
|
11894
|
+
/* harmony import */ var monaco_editor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! monaco-editor */ "include-loader!./node_modules/.pnpm/monaco-editor@0.55.1/node_modules/monaco-editor/esm/vs/editor/editor.main.js");
|
|
11895
|
+
/**
|
|
11896
|
+
* Monaco 大文本粘贴优化模块
|
|
11897
|
+
*
|
|
11898
|
+
* 解决 Monaco standalone 版本粘贴大文本时的性能问题。
|
|
11899
|
+
* 原因:Monaco 的 executeEdits 对大文本做 piece tree 增量操作 + 同步 tokenization,
|
|
11900
|
+
* 导致 3.5MB 文本粘贴耗时 20s+。
|
|
11901
|
+
* 方案:拦截粘贴事件,用 model.setValue() 直接重建 buffer(毫秒级),
|
|
11902
|
+
* 并提供自定义 undo/redo 栈弥补 setValue 丢失原生 undo 的问题。
|
|
11903
|
+
*/
|
|
11904
|
+
|
|
11905
|
+
|
|
11906
|
+
// 超过此行数时启用快速粘贴
|
|
11907
|
+
const FAST_PASTE_LINE_THRESHOLD = 10000;
|
|
11908
|
+
|
|
11909
|
+
/**
|
|
11910
|
+
* 为 Monaco 编辑器实例安装快速粘贴优化
|
|
11911
|
+
* @param {object} editor - Monaco editor 实例
|
|
11912
|
+
* @param {object} options
|
|
11913
|
+
* @param {Function} options.onContentChange - 内容变更后的回调,参数为 model.getValue()
|
|
11914
|
+
* @param {Function} [options.isReadOnly] - 返回当前是否只读
|
|
11915
|
+
* @returns {{ dispose: Function }} 调用 dispose() 移除所有监听
|
|
11916
|
+
*/
|
|
11917
|
+
function installFastPaste(editor, {
|
|
11918
|
+
onContentChange,
|
|
11919
|
+
isReadOnly
|
|
11920
|
+
}) {
|
|
11921
|
+
const undoStack = [];
|
|
11922
|
+
let redoStack = [];
|
|
11923
|
+
let isPasting = false;
|
|
11924
|
+
|
|
11925
|
+
/**
|
|
11926
|
+
* 执行 setValue 并通知外部
|
|
11927
|
+
*/
|
|
11928
|
+
function setValueAndNotify(model, newText) {
|
|
11929
|
+
isPasting = true;
|
|
11930
|
+
model.setValue(newText);
|
|
11931
|
+
isPasting = false;
|
|
11932
|
+
// 切 plaintext 阻止 setValue 后的同步 tokenization,延迟恢复
|
|
11933
|
+
monaco_editor__WEBPACK_IMPORTED_MODULE_0__.editor.setModelLanguage(model, 'plaintext');
|
|
11934
|
+
setTimeout(() => {
|
|
11935
|
+
monaco_editor__WEBPACK_IMPORTED_MODULE_0__.editor.setModelLanguage(model, 'json');
|
|
11936
|
+
}, 50);
|
|
11937
|
+
// 通知外部
|
|
11938
|
+
setTimeout(() => {
|
|
11939
|
+
onContentChange?.(model.getValue());
|
|
11940
|
+
}, 100);
|
|
11941
|
+
}
|
|
11942
|
+
|
|
11943
|
+
// ─── document 级 paste 拦截 ─────────────────────────────────────────
|
|
11944
|
+
const handlePaste = e => {
|
|
11945
|
+
if (!editor || isReadOnly?.()) return;
|
|
11946
|
+
if (!editor.hasTextFocus()) return;
|
|
11947
|
+
const model = editor.getModel();
|
|
11948
|
+
if (!model) return;
|
|
11949
|
+
const clipText = e.clipboardData?.getData('text/plain');
|
|
11950
|
+
const t0 = performance.now();
|
|
11951
|
+
if (!clipText || clipText.length < 500) return;
|
|
11952
|
+
const oldLineCount = model.getLineCount();
|
|
11953
|
+
const newLineCount = clipText.split('\n').length;
|
|
11954
|
+
// 旧内容 和 即将粘贴的新内容 行数均超过设定值时触发
|
|
11955
|
+
const shouldUseFastPaste = oldLineCount > FAST_PASTE_LINE_THRESHOLD && newLineCount > FAST_PASTE_LINE_THRESHOLD;
|
|
11956
|
+
if (!shouldUseFastPaste) return;
|
|
11957
|
+
e.preventDefault();
|
|
11958
|
+
e.stopImmediatePropagation();
|
|
11959
|
+
const selection = editor.getSelection();
|
|
11960
|
+
|
|
11961
|
+
// 保存旧内容到 undo 栈
|
|
11962
|
+
const t1 = performance.now();
|
|
11963
|
+
const oldText = model.getValue();
|
|
11964
|
+
undoStack.push(oldText);
|
|
11965
|
+
redoStack = [];
|
|
11966
|
+
|
|
11967
|
+
// 拼接新文本
|
|
11968
|
+
const startOffset = model.getOffsetAt(selection.getStartPosition());
|
|
11969
|
+
const endOffset = model.getOffsetAt(selection.getEndPosition());
|
|
11970
|
+
const newText = oldText.substring(0, startOffset) + clipText + oldText.substring(endOffset);
|
|
11971
|
+
const t2 = performance.now();
|
|
11972
|
+
|
|
11973
|
+
// setValue
|
|
11974
|
+
isPasting = true;
|
|
11975
|
+
model.setValue(newText);
|
|
11976
|
+
const t3 = performance.now();
|
|
11977
|
+
|
|
11978
|
+
// 设置光标
|
|
11979
|
+
const cursorOffset = startOffset + clipText.length;
|
|
11980
|
+
const cursorPos = model.getPositionAt(cursorOffset);
|
|
11981
|
+
editor.setPosition(cursorPos);
|
|
11982
|
+
editor.revealPosition(cursorPos);
|
|
11983
|
+
isPasting = false;
|
|
11984
|
+
|
|
11985
|
+
// 切 plaintext 阻止同步 tokenization
|
|
11986
|
+
monaco_editor__WEBPACK_IMPORTED_MODULE_0__.editor.setModelLanguage(model, 'plaintext');
|
|
11987
|
+
const t4 = performance.now();
|
|
11988
|
+
console.log(`[FastPaste] ✅ 完成! chars=${clipText.length}, oldLines=${oldLineCount}, newLines=${newLineCount}, ` + `拼接=${(t2 - t1).toFixed(1)}ms, setValue=${(t3 - t2).toFixed(1)}ms, ` + `总耗时=${(t4 - t0).toFixed(1)}ms`);
|
|
11989
|
+
|
|
11990
|
+
// 延迟恢复 JSON(只 tokenize 可视区)
|
|
11991
|
+
setTimeout(() => {
|
|
11992
|
+
monaco_editor__WEBPACK_IMPORTED_MODULE_0__.editor.setModelLanguage(model, 'json');
|
|
11993
|
+
}, 50);
|
|
11994
|
+
|
|
11995
|
+
// 延迟通知外部
|
|
11996
|
+
setTimeout(() => {
|
|
11997
|
+
onContentChange?.(model.getValue());
|
|
11998
|
+
}, 300);
|
|
11999
|
+
};
|
|
12000
|
+
document.addEventListener('paste', handlePaste, true);
|
|
12001
|
+
|
|
12002
|
+
// ─── 自定义 undo/redo(先走 Monaco 内部,空了再走自定义栈)──────────
|
|
12003
|
+
const undoDisposable = editor.addCommand(monaco_editor__WEBPACK_IMPORTED_MODULE_0__.KeyMod.CtrlCmd | monaco_editor__WEBPACK_IMPORTED_MODULE_0__.KeyCode.KeyZ, () => {
|
|
12004
|
+
const model = editor.getModel();
|
|
12005
|
+
if (!model) return;
|
|
12006
|
+
|
|
12007
|
+
// 先尝试 Monaco 内部 undo
|
|
12008
|
+
const before = model.getValue();
|
|
12009
|
+
editor.trigger('keyboard', 'undo', null);
|
|
12010
|
+
const after = model.getValue();
|
|
12011
|
+
if (before !== after) return;
|
|
12012
|
+
|
|
12013
|
+
// Monaco undo stack 已空,从自定义栈恢复
|
|
12014
|
+
if (undoStack.length > 0) {
|
|
12015
|
+
redoStack.push(before);
|
|
12016
|
+
setValueAndNotify(model, undoStack.pop());
|
|
12017
|
+
}
|
|
12018
|
+
});
|
|
12019
|
+
const redoDisposable = editor.addCommand(monaco_editor__WEBPACK_IMPORTED_MODULE_0__.KeyMod.CtrlCmd | monaco_editor__WEBPACK_IMPORTED_MODULE_0__.KeyMod.Shift | monaco_editor__WEBPACK_IMPORTED_MODULE_0__.KeyCode.KeyZ, () => {
|
|
12020
|
+
const model = editor.getModel();
|
|
12021
|
+
if (!model) return;
|
|
12022
|
+
|
|
12023
|
+
// 先尝试 Monaco 内部 redo
|
|
12024
|
+
const before = model.getValue();
|
|
12025
|
+
editor.trigger('keyboard', 'redo', null);
|
|
12026
|
+
const after = model.getValue();
|
|
12027
|
+
if (before !== after) return;
|
|
12028
|
+
|
|
12029
|
+
// Monaco redo stack 已空,从自定义栈恢复
|
|
12030
|
+
if (redoStack.length > 0) {
|
|
12031
|
+
undoStack.push(before);
|
|
12032
|
+
setValueAndNotify(model, redoStack.pop());
|
|
12033
|
+
}
|
|
12034
|
+
});
|
|
12035
|
+
|
|
12036
|
+
// ─── 返回 isPasting 状态的 getter + dispose ─────────────────────────
|
|
12037
|
+
return {
|
|
12038
|
+
/** 当前是否在执行快速粘贴(外部 onDidChangeModelContent 应跳过) */
|
|
12039
|
+
get isPasting() {
|
|
12040
|
+
return isPasting;
|
|
12041
|
+
},
|
|
12042
|
+
dispose() {
|
|
12043
|
+
document.removeEventListener('paste', handlePaste, true);
|
|
12044
|
+
}
|
|
12045
|
+
};
|
|
12046
|
+
}
|
|
12047
|
+
|
|
12048
|
+
/***/ }),
|
|
12049
|
+
|
|
11846
12050
|
/***/ "./src/utils/monaco-previews.js":
|
|
11847
12051
|
/*!**************************************!*\
|
|
11848
12052
|
!*** ./src/utils/monaco-previews.js ***!
|