@xcanwin/manyoyo 5.9.3 → 5.10.3
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/README.md +12 -0
- package/bin/manyoyo.js +51 -7
- package/lib/image-build.js +160 -0
- package/lib/runtime-resolver.js +25 -1
- package/lib/web/frontend/app.css +121 -55
- package/lib/web/frontend/app.html +12 -6
- package/lib/web/frontend/app.js +214 -34
- package/lib/web/frontend/codemirror-entry.js +13 -0
- package/lib/web/frontend/codemirror.bundle.js +13 -0
- package/lib/web/frontend/file-browser.js +220 -29
- package/lib/web/server.js +179 -10
- package/lib/worktrees.js +132 -0
- package/package.json +2 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
creatingAgent: false,
|
|
61
61
|
agentTemplateSaving: false,
|
|
62
62
|
configSnapshot: null,
|
|
63
|
+
configEditor: null,
|
|
63
64
|
sessionDetail: null,
|
|
64
65
|
sessionDetailError: '',
|
|
65
66
|
sessionDetailRequestId: 0,
|
|
@@ -82,6 +83,8 @@
|
|
|
82
83
|
title: '',
|
|
83
84
|
tip: '',
|
|
84
85
|
currentPath: '',
|
|
86
|
+
pathDraft: '',
|
|
87
|
+
parentPath: '',
|
|
85
88
|
entries: [],
|
|
86
89
|
error: ''
|
|
87
90
|
},
|
|
@@ -167,11 +170,13 @@
|
|
|
167
170
|
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
168
171
|
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
169
172
|
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
170
|
-
const
|
|
173
|
+
const directoryPickerPathInput = document.getElementById('directoryPickerPathInput');
|
|
174
|
+
const directoryPickerVisitBtn = document.getElementById('directoryPickerVisitBtn');
|
|
175
|
+
const directoryPickerStatus = document.getElementById('directoryPickerStatus');
|
|
176
|
+
const directoryPickerMkdirBtn = document.getElementById('directoryPickerMkdirBtn');
|
|
171
177
|
const directoryPickerList = document.getElementById('directoryPickerList');
|
|
172
178
|
const directoryPickerError = document.getElementById('directoryPickerError');
|
|
173
179
|
const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
|
|
174
|
-
const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
|
|
175
180
|
const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
|
|
176
181
|
const activeTitle = document.getElementById('activeTitle');
|
|
177
182
|
const activeMeta = document.getElementById('activeMeta');
|
|
@@ -810,6 +815,70 @@
|
|
|
810
815
|
directoryPickerError.textContent = text;
|
|
811
816
|
}
|
|
812
817
|
|
|
818
|
+
function ensureConfigCodeEditor() {
|
|
819
|
+
if (!configEditor || state.configEditor || !window.ManyoyoCodeEditor || typeof window.ManyoyoCodeEditor.create !== 'function') {
|
|
820
|
+
return state.configEditor;
|
|
821
|
+
}
|
|
822
|
+
state.configEditor = window.ManyoyoCodeEditor.create(configEditor, {
|
|
823
|
+
doc: '',
|
|
824
|
+
language: 'javascript',
|
|
825
|
+
readOnly: true,
|
|
826
|
+
onChange: function () {
|
|
827
|
+
if (!state.configSaveMessage) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
state.configSaveMessage = '';
|
|
831
|
+
showConfigStatus('');
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
return state.configEditor;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function setConfigEditorValue(value, readOnly) {
|
|
838
|
+
const text = String(value == null ? '' : value);
|
|
839
|
+
const editor = ensureConfigCodeEditor();
|
|
840
|
+
if (editor) {
|
|
841
|
+
editor.setValue(text);
|
|
842
|
+
editor.setLanguage('javascript');
|
|
843
|
+
editor.setReadOnly(readOnly !== false);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if ('value' in configEditor) {
|
|
847
|
+
configEditor.value = text;
|
|
848
|
+
configEditor.readOnly = readOnly !== false;
|
|
849
|
+
} else {
|
|
850
|
+
configEditor.textContent = text;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function getConfigEditorValue() {
|
|
855
|
+
const editor = ensureConfigCodeEditor();
|
|
856
|
+
if (editor) {
|
|
857
|
+
return editor.getValue();
|
|
858
|
+
}
|
|
859
|
+
if ('value' in configEditor) {
|
|
860
|
+
return configEditor.value || '';
|
|
861
|
+
}
|
|
862
|
+
return configEditor && configEditor.textContent ? configEditor.textContent : '';
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function setDirectoryPickerStatus(message) {
|
|
866
|
+
if (!directoryPickerStatus) return;
|
|
867
|
+
directoryPickerStatus.textContent = String(message || '').trim() || '未加载';
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function joinDirectoryPath(basePath, childName) {
|
|
871
|
+
const base = String(basePath || '/').trim() || '/';
|
|
872
|
+
const child = String(childName || '').trim();
|
|
873
|
+
if (!child) {
|
|
874
|
+
return base;
|
|
875
|
+
}
|
|
876
|
+
if (base === '/') {
|
|
877
|
+
return '/' + child.replace(/^\/+/, '');
|
|
878
|
+
}
|
|
879
|
+
return base.replace(/\/+$/, '') + '/' + child.replace(/^\/+/, '');
|
|
880
|
+
}
|
|
881
|
+
|
|
813
882
|
function envMapToText(envMap) {
|
|
814
883
|
if (!envMap || typeof envMap !== 'object') {
|
|
815
884
|
return '';
|
|
@@ -2448,8 +2517,7 @@
|
|
|
2448
2517
|
configPath.textContent = lines.filter(Boolean).join('\n');
|
|
2449
2518
|
}
|
|
2450
2519
|
if (configEditor) {
|
|
2451
|
-
|
|
2452
|
-
configEditor.value = typeof config.raw === 'string' ? config.raw : '';
|
|
2520
|
+
setConfigEditorValue(typeof config.raw === 'string' ? config.raw : '', config.editable === false);
|
|
2453
2521
|
}
|
|
2454
2522
|
}
|
|
2455
2523
|
|
|
@@ -2493,7 +2561,7 @@
|
|
|
2493
2561
|
try {
|
|
2494
2562
|
await api('/api/config', {
|
|
2495
2563
|
method: 'PUT',
|
|
2496
|
-
body: JSON.stringify({ raw:
|
|
2564
|
+
body: JSON.stringify({ raw: getConfigEditorValue() })
|
|
2497
2565
|
});
|
|
2498
2566
|
const config = await fetchConfigSnapshot();
|
|
2499
2567
|
renderConfigModalSnapshot(config);
|
|
@@ -2553,39 +2621,73 @@
|
|
|
2553
2621
|
if (directoryPickerTip) {
|
|
2554
2622
|
directoryPickerTip.textContent = picker.tip || '';
|
|
2555
2623
|
}
|
|
2556
|
-
if (
|
|
2557
|
-
|
|
2624
|
+
if (directoryPickerPathInput) {
|
|
2625
|
+
directoryPickerPathInput.value = picker.pathDraft || picker.currentPath || '/';
|
|
2558
2626
|
}
|
|
2559
2627
|
showDirectoryPickerError(picker.error);
|
|
2560
|
-
if (
|
|
2561
|
-
|
|
2628
|
+
if (directoryPickerVisitBtn) {
|
|
2629
|
+
directoryPickerVisitBtn.disabled = picker.loading || !(picker.pathDraft || picker.currentPath);
|
|
2630
|
+
}
|
|
2631
|
+
if (directoryPickerMkdirBtn) {
|
|
2632
|
+
directoryPickerMkdirBtn.disabled = picker.loading || !picker.currentPath;
|
|
2562
2633
|
}
|
|
2563
2634
|
if (directoryPickerSelectBtn) {
|
|
2564
2635
|
directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
|
|
2565
2636
|
}
|
|
2637
|
+
if (picker.loading) {
|
|
2638
|
+
setDirectoryPickerStatus('读取目录中');
|
|
2639
|
+
} else if (picker.entries.length) {
|
|
2640
|
+
setDirectoryPickerStatus('共 ' + picker.entries.length + ' 项');
|
|
2641
|
+
} else if (picker.currentPath) {
|
|
2642
|
+
setDirectoryPickerStatus('共 0 项');
|
|
2643
|
+
} else {
|
|
2644
|
+
setDirectoryPickerStatus('未加载');
|
|
2645
|
+
}
|
|
2566
2646
|
if (!directoryPickerList) {
|
|
2567
2647
|
return;
|
|
2568
2648
|
}
|
|
2569
2649
|
directoryPickerList.innerHTML = '';
|
|
2570
2650
|
if (picker.loading) {
|
|
2571
2651
|
const loading = document.createElement('div');
|
|
2572
|
-
loading.className = 'empty';
|
|
2652
|
+
loading.className = 'files-empty';
|
|
2573
2653
|
loading.textContent = '目录加载中...';
|
|
2574
2654
|
directoryPickerList.appendChild(loading);
|
|
2575
2655
|
return;
|
|
2576
2656
|
}
|
|
2657
|
+
if (picker.parentPath) {
|
|
2658
|
+
const parentButton = document.createElement('button');
|
|
2659
|
+
parentButton.type = 'button';
|
|
2660
|
+
parentButton.className = 'files-entry files-entry-parent';
|
|
2661
|
+
parentButton.title = picker.parentPath;
|
|
2662
|
+
parentButton.innerHTML = `
|
|
2663
|
+
<span class="files-entry-name">
|
|
2664
|
+
<span class="files-entry-title">..</span>
|
|
2665
|
+
</span>
|
|
2666
|
+
<span class="files-entry-meta">上一级</span>
|
|
2667
|
+
`;
|
|
2668
|
+
parentButton.addEventListener('click', function () {
|
|
2669
|
+
loadDirectoryPicker(picker.parentPath);
|
|
2670
|
+
});
|
|
2671
|
+
directoryPickerList.appendChild(parentButton);
|
|
2672
|
+
}
|
|
2577
2673
|
if (!picker.entries.length) {
|
|
2578
2674
|
const empty = document.createElement('div');
|
|
2579
|
-
empty.className = 'empty';
|
|
2580
|
-
empty.
|
|
2675
|
+
empty.className = 'files-empty';
|
|
2676
|
+
empty.innerHTML = ' ';
|
|
2581
2677
|
directoryPickerList.appendChild(empty);
|
|
2582
2678
|
return;
|
|
2583
2679
|
}
|
|
2584
2680
|
picker.entries.forEach(function (entry) {
|
|
2585
2681
|
const btn = document.createElement('button');
|
|
2586
2682
|
btn.type = 'button';
|
|
2587
|
-
btn.className = '
|
|
2588
|
-
btn.
|
|
2683
|
+
btn.className = 'files-entry';
|
|
2684
|
+
btn.title = String(entry.path || entry.name || '');
|
|
2685
|
+
btn.innerHTML = `
|
|
2686
|
+
<span class="files-entry-name">
|
|
2687
|
+
<span class="files-entry-title">${escapeHtml(entry.name || entry.path || '未命名')}</span>
|
|
2688
|
+
</span>
|
|
2689
|
+
<span class="files-entry-meta">目录</span>
|
|
2690
|
+
`;
|
|
2589
2691
|
btn.addEventListener('click', function () {
|
|
2590
2692
|
loadDirectoryPicker(entry.path);
|
|
2591
2693
|
});
|
|
@@ -2597,15 +2699,15 @@
|
|
|
2597
2699
|
const picker = state.directoryPicker;
|
|
2598
2700
|
picker.loading = true;
|
|
2599
2701
|
picker.error = '';
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
}
|
|
2702
|
+
const nextPath = String(targetPath || picker.pathDraft || picker.currentPath || '/').trim() || '/';
|
|
2703
|
+
picker.pathDraft = nextPath;
|
|
2603
2704
|
renderDirectoryPicker();
|
|
2604
2705
|
try {
|
|
2605
2706
|
const params = new URLSearchParams();
|
|
2606
|
-
params.set('path',
|
|
2707
|
+
params.set('path', nextPath);
|
|
2607
2708
|
const data = await api('/api/fs/directories?' + params.toString());
|
|
2608
|
-
picker.currentPath = data.currentPath ||
|
|
2709
|
+
picker.currentPath = data.currentPath || nextPath;
|
|
2710
|
+
picker.pathDraft = picker.currentPath;
|
|
2609
2711
|
picker.parentPath = data.parentPath || '';
|
|
2610
2712
|
picker.entries = Array.isArray(data.entries) ? data.entries : [];
|
|
2611
2713
|
} catch (e) {
|
|
@@ -2617,6 +2719,32 @@
|
|
|
2617
2719
|
}
|
|
2618
2720
|
}
|
|
2619
2721
|
|
|
2722
|
+
async function createDirectoryInPicker() {
|
|
2723
|
+
const picker = state.directoryPicker;
|
|
2724
|
+
if (picker.loading || !picker.currentPath) {
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
const input = window.prompt('请输入新目录名称');
|
|
2728
|
+
const name = String(input || '').trim();
|
|
2729
|
+
if (!name) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
picker.loading = true;
|
|
2733
|
+
picker.error = '';
|
|
2734
|
+
renderDirectoryPicker();
|
|
2735
|
+
try {
|
|
2736
|
+
await api('/api/fs/directories/mkdir', {
|
|
2737
|
+
method: 'POST',
|
|
2738
|
+
body: JSON.stringify({ path: joinDirectoryPath(picker.currentPath, name) })
|
|
2739
|
+
});
|
|
2740
|
+
await loadDirectoryPicker(picker.currentPath);
|
|
2741
|
+
} catch (e) {
|
|
2742
|
+
picker.loading = false;
|
|
2743
|
+
picker.error = e && e.message ? e.message : '创建目录失败';
|
|
2744
|
+
renderDirectoryPicker();
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2620
2748
|
function closeDirectoryPicker() {
|
|
2621
2749
|
state.directoryPicker.open = false;
|
|
2622
2750
|
state.directoryPicker.loading = false;
|
|
@@ -2624,6 +2752,7 @@
|
|
|
2624
2752
|
state.directoryPicker.title = '';
|
|
2625
2753
|
state.directoryPicker.tip = '';
|
|
2626
2754
|
state.directoryPicker.currentPath = '';
|
|
2755
|
+
state.directoryPicker.pathDraft = '';
|
|
2627
2756
|
state.directoryPicker.parentPath = '';
|
|
2628
2757
|
state.directoryPicker.entries = [];
|
|
2629
2758
|
state.directoryPicker.error = '';
|
|
@@ -2651,6 +2780,7 @@
|
|
|
2651
2780
|
picker.title = '选择 hostPath';
|
|
2652
2781
|
picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
|
|
2653
2782
|
picker.currentPath = (createHostPath.value || '').trim() || '/';
|
|
2783
|
+
picker.pathDraft = picker.currentPath;
|
|
2654
2784
|
renderDirectoryPicker();
|
|
2655
2785
|
loadDirectoryPicker(picker.currentPath);
|
|
2656
2786
|
}
|
|
@@ -2855,6 +2985,42 @@
|
|
|
2855
2985
|
return sorted.length && sorted[0] && sorted[0].name ? sorted[0].name : '';
|
|
2856
2986
|
}
|
|
2857
2987
|
|
|
2988
|
+
function findPreferredSessionNameAfterRemoval(sessions, removedName) {
|
|
2989
|
+
const removedSessionName = String(removedName || '').trim();
|
|
2990
|
+
if (!removedSessionName) {
|
|
2991
|
+
return '';
|
|
2992
|
+
}
|
|
2993
|
+
const removedRef = parseSessionKey(removedSessionName);
|
|
2994
|
+
const remaining = (Array.isArray(sessions) ? sessions : []).filter(function (session) {
|
|
2995
|
+
return session
|
|
2996
|
+
&& session.name
|
|
2997
|
+
&& session.name !== removedSessionName
|
|
2998
|
+
&& session.containerName === removedRef.containerName;
|
|
2999
|
+
});
|
|
3000
|
+
if (!remaining.length) {
|
|
3001
|
+
return '';
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
const removedRank = getSessionAgentCreationRank({ agentId: removedRef.agentId });
|
|
3005
|
+
if (removedRef.agentId && removedRef.agentId !== WEB_DEFAULT_AGENT_ID && removedRank > 0) {
|
|
3006
|
+
const lowerRanked = remaining
|
|
3007
|
+
.filter(function (session) { return getSessionAgentCreationRank(session) < removedRank; })
|
|
3008
|
+
.sort(function (a, b) { return getSessionAgentCreationRank(b) - getSessionAgentCreationRank(a); });
|
|
3009
|
+
if (lowerRanked.length && lowerRanked[0] && lowerRanked[0].name) {
|
|
3010
|
+
return lowerRanked[0].name;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
const higherRanked = remaining
|
|
3014
|
+
.filter(function (session) { return getSessionAgentCreationRank(session) > removedRank; })
|
|
3015
|
+
.sort(function (a, b) { return getSessionAgentCreationRank(a) - getSessionAgentCreationRank(b); });
|
|
3016
|
+
if (higherRanked.length && higherRanked[0] && higherRanked[0].name) {
|
|
3017
|
+
return higherRanked[0].name;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
return findLatestCreatedSessionName(remaining, removedRef.containerName);
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2858
3024
|
function createDisclosureButton(expanded, label) {
|
|
2859
3025
|
const button = document.createElement('button');
|
|
2860
3026
|
button.type = 'button';
|
|
@@ -3140,12 +3306,15 @@
|
|
|
3140
3306
|
}
|
|
3141
3307
|
|
|
3142
3308
|
function renderSessions() {
|
|
3309
|
+
const directoryCount = new Set(state.sessions.map(function (session) {
|
|
3310
|
+
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
3311
|
+
}).filter(Boolean)).size;
|
|
3143
3312
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
3144
3313
|
return session && session.containerName ? session.containerName : '';
|
|
3145
3314
|
}).filter(Boolean)).size;
|
|
3146
3315
|
sessionCount.textContent = state.loadingSessions
|
|
3147
3316
|
? '加载中...'
|
|
3148
|
-
: `${
|
|
3317
|
+
: `${directoryCount} 个 目录 / ${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
|
|
3149
3318
|
|
|
3150
3319
|
if (state.loadingSessions) {
|
|
3151
3320
|
renderSessionsLoading();
|
|
@@ -3884,16 +4053,6 @@
|
|
|
3884
4053
|
});
|
|
3885
4054
|
}
|
|
3886
4055
|
|
|
3887
|
-
if (configEditor) {
|
|
3888
|
-
configEditor.addEventListener('input', function () {
|
|
3889
|
-
if (!state.configSaveMessage) {
|
|
3890
|
-
return;
|
|
3891
|
-
}
|
|
3892
|
-
state.configSaveMessage = '';
|
|
3893
|
-
showConfigStatus('');
|
|
3894
|
-
});
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
4056
|
if (createCancelBtn) {
|
|
3898
4057
|
createCancelBtn.addEventListener('click', function () {
|
|
3899
4058
|
closeCreateModal();
|
|
@@ -3919,14 +4078,32 @@
|
|
|
3919
4078
|
});
|
|
3920
4079
|
}
|
|
3921
4080
|
|
|
3922
|
-
if (
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
4081
|
+
if (directoryPickerPathInput) {
|
|
4082
|
+
directoryPickerPathInput.addEventListener('input', function () {
|
|
4083
|
+
state.directoryPicker.pathDraft = directoryPickerPathInput.value;
|
|
4084
|
+
});
|
|
4085
|
+
directoryPickerPathInput.addEventListener('keydown', function (event) {
|
|
4086
|
+
if (event.key === 'Enter') {
|
|
4087
|
+
event.preventDefault();
|
|
4088
|
+
loadDirectoryPicker();
|
|
3926
4089
|
}
|
|
3927
4090
|
});
|
|
3928
4091
|
}
|
|
3929
4092
|
|
|
4093
|
+
if (directoryPickerVisitBtn) {
|
|
4094
|
+
directoryPickerVisitBtn.addEventListener('click', function () {
|
|
4095
|
+
loadDirectoryPicker();
|
|
4096
|
+
});
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
if (directoryPickerMkdirBtn) {
|
|
4100
|
+
directoryPickerMkdirBtn.addEventListener('click', function () {
|
|
4101
|
+
createDirectoryInPicker().catch(function (e) {
|
|
4102
|
+
showDirectoryPickerError(e && e.message ? e.message : '创建目录失败');
|
|
4103
|
+
});
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
|
|
3930
4107
|
if (directoryPickerSelectBtn) {
|
|
3931
4108
|
directoryPickerSelectBtn.addEventListener('click', function () {
|
|
3932
4109
|
applyPickedDirectory();
|
|
@@ -4440,11 +4617,14 @@
|
|
|
4440
4617
|
const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
|
|
4441
4618
|
if (!yes) return;
|
|
4442
4619
|
try {
|
|
4620
|
+
const current = state.active;
|
|
4443
4621
|
const targetContainerName = activeSession && activeSession.containerName ? activeSession.containerName : '';
|
|
4622
|
+
const fallbackSessionName = findPreferredSessionNameAfterRemoval(state.sessions, current);
|
|
4444
4623
|
await api('/api/sessions/' + encodeURIComponent(state.active) + '/remove-with-history', {
|
|
4445
4624
|
method: 'POST'
|
|
4446
4625
|
});
|
|
4447
4626
|
await refreshSessions({
|
|
4627
|
+
preferredName: fallbackSessionName || '',
|
|
4448
4628
|
preferredContainerName: targetContainerName,
|
|
4449
4629
|
withLoading: true,
|
|
4450
4630
|
reloadMessages: true
|
|
@@ -35,8 +35,10 @@ function createEditor(parent, options = {}) {
|
|
|
35
35
|
const initialDoc = String(options.doc || '');
|
|
36
36
|
const initialLanguage = String(options.language || 'text').trim();
|
|
37
37
|
const initialReadOnly = options.readOnly !== false;
|
|
38
|
+
const onChange = typeof options.onChange === 'function' ? options.onChange : null;
|
|
38
39
|
const languageCompartment = new Compartment();
|
|
39
40
|
const readOnlyCompartment = new Compartment();
|
|
41
|
+
let suppressChange = false;
|
|
40
42
|
const view = new EditorView({
|
|
41
43
|
parent: target,
|
|
42
44
|
state: EditorState.create({
|
|
@@ -44,6 +46,12 @@ function createEditor(parent, options = {}) {
|
|
|
44
46
|
extensions: [
|
|
45
47
|
basicSetup,
|
|
46
48
|
EditorView.lineWrapping,
|
|
49
|
+
EditorView.updateListener.of(function (update) {
|
|
50
|
+
if (!update.docChanged || suppressChange || !onChange) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
onChange(update.state.doc.toString());
|
|
54
|
+
}),
|
|
47
55
|
readOnlyCompartment.of([
|
|
48
56
|
EditorState.readOnly.of(initialReadOnly),
|
|
49
57
|
EditorView.editable.of(!initialReadOnly)
|
|
@@ -65,6 +73,7 @@ function createEditor(parent, options = {}) {
|
|
|
65
73
|
return {
|
|
66
74
|
setValue(nextValue) {
|
|
67
75
|
const text = String(nextValue == null ? '' : nextValue);
|
|
76
|
+
suppressChange = true;
|
|
68
77
|
view.dispatch({
|
|
69
78
|
changes: {
|
|
70
79
|
from: 0,
|
|
@@ -72,6 +81,10 @@ function createEditor(parent, options = {}) {
|
|
|
72
81
|
insert: text
|
|
73
82
|
}
|
|
74
83
|
});
|
|
84
|
+
suppressChange = false;
|
|
85
|
+
},
|
|
86
|
+
getValue() {
|
|
87
|
+
return view.state.doc.toString();
|
|
75
88
|
},
|
|
76
89
|
setLanguage(nextLanguage) {
|
|
77
90
|
view.dispatch({
|
|
@@ -31583,8 +31583,10 @@
|
|
|
31583
31583
|
const initialDoc = String(options.doc || "");
|
|
31584
31584
|
const initialLanguage = String(options.language || "text").trim();
|
|
31585
31585
|
const initialReadOnly = options.readOnly !== false;
|
|
31586
|
+
const onChange = typeof options.onChange === "function" ? options.onChange : null;
|
|
31586
31587
|
const languageCompartment = new Compartment();
|
|
31587
31588
|
const readOnlyCompartment = new Compartment();
|
|
31589
|
+
let suppressChange = false;
|
|
31588
31590
|
const view = new EditorView({
|
|
31589
31591
|
parent: target,
|
|
31590
31592
|
state: EditorState.create({
|
|
@@ -31592,6 +31594,12 @@
|
|
|
31592
31594
|
extensions: [
|
|
31593
31595
|
basicSetup,
|
|
31594
31596
|
EditorView.lineWrapping,
|
|
31597
|
+
EditorView.updateListener.of(function(update) {
|
|
31598
|
+
if (!update.docChanged || suppressChange || !onChange) {
|
|
31599
|
+
return;
|
|
31600
|
+
}
|
|
31601
|
+
onChange(update.state.doc.toString());
|
|
31602
|
+
}),
|
|
31595
31603
|
readOnlyCompartment.of([
|
|
31596
31604
|
EditorState.readOnly.of(initialReadOnly),
|
|
31597
31605
|
EditorView.editable.of(!initialReadOnly)
|
|
@@ -31612,6 +31620,7 @@
|
|
|
31612
31620
|
return {
|
|
31613
31621
|
setValue(nextValue) {
|
|
31614
31622
|
const text = String(nextValue == null ? "" : nextValue);
|
|
31623
|
+
suppressChange = true;
|
|
31615
31624
|
view.dispatch({
|
|
31616
31625
|
changes: {
|
|
31617
31626
|
from: 0,
|
|
@@ -31619,6 +31628,10 @@
|
|
|
31619
31628
|
insert: text
|
|
31620
31629
|
}
|
|
31621
31630
|
});
|
|
31631
|
+
suppressChange = false;
|
|
31632
|
+
},
|
|
31633
|
+
getValue() {
|
|
31634
|
+
return view.state.doc.toString();
|
|
31622
31635
|
},
|
|
31623
31636
|
setLanguage(nextLanguage) {
|
|
31624
31637
|
view.dispatch({
|