mirror of
https://github.com/microsoft/monaco-editor.git
synced 2025-12-22 17:25:39 +01:00
536 lines
16 KiB
JavaScript
536 lines
16 KiB
JavaScript
let editor;
|
|
let files = {};
|
|
let currentFile = null;
|
|
let closedError = true;
|
|
let disabledMove = false;
|
|
let selectionsContainer;
|
|
let rightSelector;
|
|
let leftSelector;
|
|
let selectorMenu;
|
|
let globalPoseMenu = 0;
|
|
|
|
require.config({
|
|
paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs' }
|
|
});
|
|
|
|
require(['vs/editor/editor.main'], function () {
|
|
editor = monaco.editor.create(document.getElementById('editor'), {
|
|
value: [
|
|
'// A simple JavaScript script to test touch event for monaco',
|
|
'',
|
|
'function greetUser(name) {',
|
|
' console.log("Hello, " + name + "!");',
|
|
'}',
|
|
'',
|
|
'function factorial(n) {',
|
|
' if (n === 0) {',
|
|
' return 1;',
|
|
' }',
|
|
' return n * factorial(n - 1);',
|
|
'}',
|
|
'',
|
|
'function findMax(arr) {',
|
|
' let max = arr[0];',
|
|
' for (let i = 1; i < arr.length; i++) {',
|
|
' if (arr[i] > max) {',
|
|
' max = arr[i];',
|
|
' }',
|
|
' }',
|
|
' return max;',
|
|
'}',
|
|
'',
|
|
'function reverseString(str) {',
|
|
' return str.split("").reverse().join("");',
|
|
'}',
|
|
'',
|
|
'greetUser("Alice");',
|
|
'console.log("Factorial of 5:", factorial(5));',
|
|
'console.log("Max in [1, 2, 3, 4, 5]:", findMax([1, 2, 3, 4, 5]));',
|
|
'console.log("Reverse of \'JavaScript\':", reverseString("JavaScript"));',
|
|
'',
|
|
'let numbers = [1, 2, 3, 4, 5];',
|
|
'let doubled = numbers.map(function(num) {',
|
|
' return num * 2;',
|
|
'});',
|
|
'console.log("Doubled numbers:", doubled);',
|
|
'',
|
|
'let person = {',
|
|
' firstName: "John",',
|
|
' lastName: "Doe",',
|
|
' age: 30,',
|
|
' fullName: function() {',
|
|
' return this.firstName + " " + this.lastName;',
|
|
' }',
|
|
'};',
|
|
'console.log("Person\'s full name:", person.fullName());',
|
|
'',
|
|
'function fetchData(url) {',
|
|
' return new Promise((resolve, reject) => {',
|
|
' setTimeout(() => {',
|
|
' resolve("Data from " + url);',
|
|
' }, 1000);',
|
|
' });',
|
|
'}',
|
|
'',
|
|
'fetchData("https://api.example.com").then(data => {',
|
|
' console.log(data);',
|
|
'}).catch(error => {',
|
|
' console.error("Error fetching data:", error);',
|
|
'});'
|
|
].join('\n'),
|
|
language: 'javascript',
|
|
theme: 'vs-dark',
|
|
minimap: { enabled: false },
|
|
wordWrap: 'off',
|
|
lineDecorationsWidth: 0,
|
|
lineNumbersMinChars: 3,
|
|
lineNumbers: 'on',
|
|
tabSize: 2,
|
|
contextmenu: false
|
|
});
|
|
|
|
function undo() {
|
|
editor.trigger('keyboard', 'undo', null);
|
|
}
|
|
|
|
// Function to perform redo in Monaco Editor
|
|
function redo() {
|
|
editor.trigger('keyboard', 'redo', null);
|
|
}
|
|
|
|
function selectAll() {
|
|
editor.focus();
|
|
const id = document.querySelector('.tap.selected').getAttribute('data-file-id');
|
|
const model = files[id].model;
|
|
if (model) {
|
|
const fullRange = model.getFullModelRange();
|
|
editor.setSelection(fullRange);
|
|
}
|
|
}
|
|
|
|
function copy() {
|
|
const selection = editor.getSelection();
|
|
const selectedText = editor.getModel().getValueInRange(selection);
|
|
navigator.clipboard
|
|
.writeText(selectedText)
|
|
.then(() => {
|
|
error('Copied to clipboard');
|
|
})
|
|
.catch((err) => {
|
|
error('Error in copying', err);
|
|
});
|
|
}
|
|
|
|
// Function to cut the current selection in Monaco Editor
|
|
function cut() {
|
|
const selection = editor.getSelection();
|
|
const selectedText = editor.getModel().getValueInRange(selection);
|
|
navigator.clipboard
|
|
.writeText(selectedText)
|
|
.then(() => {
|
|
editor.executeEdits('', [{ range: selection, text: '' }]);
|
|
})
|
|
.catch((err) => {
|
|
editor.executeEdits('', [{ range: selection, text: '' }]);
|
|
});
|
|
}
|
|
|
|
// Function to paste the content from the clipboard into Monaco Editor
|
|
function paste() {
|
|
navigator.clipboard
|
|
.readText()
|
|
.then((text) => {
|
|
const selection = editor.getSelection();
|
|
editor.executeEdits('', [{ range: selection, text }]);
|
|
console.log('Pasted from clipboard');
|
|
})
|
|
.catch((err) => {
|
|
console.error('Failed to paste: ', err);
|
|
});
|
|
}
|
|
function formatDocument() {
|
|
editor.focus();
|
|
editor.getAction('editor.action.formatDocument').run();
|
|
}
|
|
// Expose functions to global scope
|
|
window.undo = undo;
|
|
window.redo = redo;
|
|
window.undo = undo;
|
|
window.copy = copy;
|
|
window.cut = cut;
|
|
window.paste = paste;
|
|
window.selectAll = selectAll;
|
|
window.formatDocument = formatDocument;
|
|
|
|
function initializeSelector() {
|
|
// Create the selectors container
|
|
selectionsContainer = document.createElement('div');
|
|
selectionsContainer.className = 'selections hidden';
|
|
|
|
// Create the right-selector and left-selector divs
|
|
rightSelector = document.createElement('div');
|
|
rightSelector.className = 'selctos right-selector';
|
|
leftSelector = document.createElement('div');
|
|
leftSelector.className = 'selctos left-selector';
|
|
|
|
// Append the selectors to the container
|
|
selectionsContainer.appendChild(rightSelector);
|
|
selectionsContainer.appendChild(leftSelector);
|
|
|
|
// Add the selections container to the Monaco editor scrollable area
|
|
const editorScrollableNode = document.querySelector('.lines-content.monaco-editor-background');
|
|
editorScrollableNode.insertAdjacentElement('afterbegin', selectionsContainer);
|
|
|
|
// selectors
|
|
|
|
selectorMenu = document.createElement('div');
|
|
selectorMenu.className = 'menu-selector hidden';
|
|
// Create the outer container
|
|
const outsetContainer = document.createElement('div');
|
|
outsetContainer.className = 'outset-shit-select';
|
|
|
|
// Create the inner menu container
|
|
const insetMenu = document.createElement('div');
|
|
insetMenu.className = 'inset-menu';
|
|
|
|
// Create and append menu items to the inner menu container
|
|
const menuItems = [
|
|
{ className: 'copy', text: 'Copy', action: copy },
|
|
{ className: 'cut', text: 'Cut', action: cut },
|
|
{ className: 'paste', text: 'Paste', action: paste },
|
|
{ className: 'select', text: 'Select all', action: selectAll },
|
|
{ className: 'undo', text: 'Undo', action: undo },
|
|
{ className: 'redo', text: 'Redo', action: redo },
|
|
{ className: 'format', text: 'Format document', action: formatDocument }
|
|
];
|
|
|
|
menuItems.forEach((item) => {
|
|
const div = document.createElement('div');
|
|
div.className = `mnc ${item.className}`;
|
|
div.innerHTML = `<span>${item.text}</span>`;
|
|
div.ontouchstart = function () {
|
|
this.classList.add('hovered');
|
|
};
|
|
div.ontouchmove = function () {
|
|
this.classList.remove('hovered');
|
|
};
|
|
div.ontouchend = function () {
|
|
this.classList.remove('hovered');
|
|
item.action();
|
|
selectorMenu.classList.add('hidden');
|
|
};
|
|
insetMenu.appendChild(div);
|
|
});
|
|
|
|
// Create and append the right-arrow element
|
|
const leftArrow = document.createElement('div');
|
|
leftArrow.className = 'right-arrow-mnc left';
|
|
leftArrow.ontouchstart = function () {
|
|
this.classList.add('hovered');
|
|
};
|
|
leftArrow.ontouchmove = function () {
|
|
this.classList.remove('hovered');
|
|
};
|
|
|
|
leftArrow.ontouchend = function () {
|
|
this.classList.remove('hovered');
|
|
const containerWidth = outsetContainer.offsetWidth;
|
|
let newScrollPosition = currentScrollPosition - containerWidth;
|
|
if (newScrollPosition < 0) {
|
|
newScrollPosition = 0;
|
|
}
|
|
outsetContainer.scrollTo({
|
|
left: newScrollPosition,
|
|
behavior: 'smooth'
|
|
});
|
|
currentScrollPosition = newScrollPosition;
|
|
if (newScrollPosition < 5) {
|
|
selectorMenu.classList.remove('scrolled');
|
|
}
|
|
};
|
|
|
|
const rightArrow = document.createElement('div');
|
|
rightArrow.className = 'right-arrow-mnc';
|
|
rightArrow.ontouchstart = function () {
|
|
this.classList.add('hovered');
|
|
};
|
|
rightArrow.ontouchmove = function () {
|
|
this.classList.remove('hovered');
|
|
};
|
|
|
|
let currentScrollPosition = 0;
|
|
rightArrow.ontouchend = function () {
|
|
this.classList.remove('hovered');
|
|
const containerWidth = outsetContainer.offsetWidth;
|
|
const scrollWidth = outsetContainer.scrollWidth;
|
|
let newScrollPosition = currentScrollPosition + containerWidth;
|
|
if (newScrollPosition > scrollWidth) {
|
|
newScrollPosition = scrollWidth;
|
|
}
|
|
outsetContainer.scrollTo({
|
|
left: newScrollPosition,
|
|
behavior: 'smooth'
|
|
});
|
|
currentScrollPosition = newScrollPosition;
|
|
selectorMenu.classList.add('scrolled');
|
|
};
|
|
|
|
outsetContainer.appendChild(insetMenu);
|
|
selectorMenu.appendChild(outsetContainer);
|
|
selectorMenu.appendChild(rightArrow);
|
|
selectorMenu.appendChild(leftArrow);
|
|
|
|
selectorMenu.addEventListener('touchstart', (event) => {
|
|
const touch = event.touches[0];
|
|
event.preventDefault();
|
|
disabledMove = true;
|
|
});
|
|
|
|
selectorMenu.addEventListener('touchmove', (event) => {
|
|
const touch = event.touches[0];
|
|
event.preventDefault();
|
|
disabledMove = true;
|
|
});
|
|
|
|
selectorMenu.addEventListener('touchend', (event) => {
|
|
const touch = event.touches[0];
|
|
event.preventDefault();
|
|
disabledMove = false;
|
|
});
|
|
|
|
editorScrollableNode.insertAdjacentElement('afterbegin', selectorMenu);
|
|
|
|
function handleSelectorTouch(selector, isLeft) {
|
|
let touchStartPos;
|
|
let initialSelection;
|
|
let touchMoved = false; // Track if touchmove occurred
|
|
let touchEndTimeout; // Timeout to handle fast moves
|
|
|
|
selector.addEventListener('touchstart', (event) => {
|
|
const touch = event.touches[0];
|
|
touchStartPos = { x: touch.clientX, y: touch.clientY };
|
|
initialSelection = editor.getSelection();
|
|
touchMoved = false; // Reset touchMoved flag
|
|
if (touchEndTimeout) clearTimeout(touchEndTimeout);
|
|
event.preventDefault();
|
|
disabledMove = true;
|
|
});
|
|
|
|
selector.addEventListener('touchmove', (event) => {
|
|
const touch = event.touches[0];
|
|
const target = editor.getTargetAtClientPoint(touch.clientX, touch.clientY);
|
|
|
|
if (target && target.position) {
|
|
const newSelection = isLeft
|
|
? new monaco.Selection(
|
|
target.position.lineNumber,
|
|
target.position.column,
|
|
initialSelection.endLineNumber,
|
|
initialSelection.endColumn
|
|
)
|
|
: new monaco.Selection(
|
|
initialSelection.startLineNumber,
|
|
initialSelection.startColumn,
|
|
target.position.lineNumber,
|
|
target.position.column
|
|
);
|
|
editor.setSelection(newSelection);
|
|
touchMoved = true; // Mark that touchmove occurred
|
|
}
|
|
event.preventDefault();
|
|
});
|
|
|
|
selector.addEventListener('touchend', (event) => {
|
|
if (!touchMoved) {
|
|
const touch = event.changedTouches[0];
|
|
const target = editor.getTargetAtClientPoint(touch.clientX, touch.clientY);
|
|
|
|
if (target && target.position) {
|
|
const newSelection = isLeft
|
|
? new monaco.Selection(
|
|
target.position.lineNumber,
|
|
target.position.column,
|
|
initialSelection.endLineNumber,
|
|
initialSelection.endColumn
|
|
)
|
|
: new monaco.Selection(
|
|
initialSelection.startLineNumber,
|
|
initialSelection.startColumn,
|
|
target.position.lineNumber,
|
|
target.position.column
|
|
);
|
|
editor.setSelection(newSelection);
|
|
}
|
|
}
|
|
touchStartPos = null;
|
|
initialSelection = null;
|
|
touchMoved = false;
|
|
});
|
|
}
|
|
|
|
handleSelectorTouch(leftSelector, true);
|
|
handleSelectorTouch(rightSelector, false);
|
|
}
|
|
|
|
const editorElement = document.getElementById('editor');
|
|
let touchTimeout;
|
|
let touchCount = 0;
|
|
let startPosition;
|
|
let isSelecting = false;
|
|
|
|
function getLineYCoordinate(position) {
|
|
const lineTop = editor.getTopForLineNumber(position.lineNumber);
|
|
const editorCoords = editorElement.getBoundingClientRect();
|
|
const lineY = window.scrollY + editorCoords.top + lineTop - tapsHeight;
|
|
return lineY;
|
|
}
|
|
|
|
function isPositionInSelection(position) {
|
|
const selection = editor.getSelection();
|
|
if (!selection || selection.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
const startPosition = selection.getStartPosition();
|
|
const endPosition = selection.getEndPosition();
|
|
|
|
// Compare the position with the selection range
|
|
const isInRange =
|
|
(position.lineNumber > startPosition.lineNumber ||
|
|
(position.lineNumber === startPosition.lineNumber &&
|
|
position.column >= startPosition.column)) &&
|
|
(position.lineNumber < endPosition.lineNumber ||
|
|
(position.lineNumber === endPosition.lineNumber && position.column <= endPosition.column));
|
|
|
|
return isInRange;
|
|
}
|
|
|
|
editorElement.addEventListener('touchstart', (event) => {
|
|
if (selectorMenu.contains(event.target) || selectionsContainer.contains(event.target)) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
touchCount++;
|
|
if (touchTimeout) {
|
|
clearTimeout(touchTimeout);
|
|
}
|
|
|
|
touchTimeout = setTimeout(() => {
|
|
// in here, want get YValue based on line offset top like the value of the rouched line to document
|
|
isSelecting = true;
|
|
if (!isPositionInSelection(startPosition)) {
|
|
editor.setPosition(startPosition);
|
|
}
|
|
let YValue = getLineYCoordinate(startPosition);
|
|
showContextMenu(event.touches[0].clientX, YValue - 50);
|
|
}, 1000); // 1 second long press to show context menu
|
|
const touch = event.touches[0];
|
|
const target = editor.getTargetAtClientPoint(touch.clientX, touch.clientY);
|
|
if (target && target.position) {
|
|
startPosition = target.position;
|
|
}
|
|
});
|
|
|
|
editorElement.addEventListener('touchend', () => {
|
|
if (touchTimeout) {
|
|
clearTimeout(touchTimeout);
|
|
}
|
|
if (isSelecting) {
|
|
isSelecting = false;
|
|
}
|
|
if (disabledMove) {
|
|
disabledMove = false;
|
|
}
|
|
});
|
|
|
|
const tapsHeight = -3; // assuming you have some element on top that takes some height
|
|
const leftLength = document.querySelector('.monaco-editor .margin').offsetWidth;
|
|
|
|
editorElement.addEventListener('touchmove', (event) => {
|
|
if (touchTimeout) {
|
|
clearTimeout(touchTimeout);
|
|
}
|
|
|
|
if (disabledMove) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
if (startPosition && isSelecting) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
const touch = event.touches[0];
|
|
const target = editor.getTargetAtClientPoint(touch.clientX, touch.clientY);
|
|
if (target && target.position) {
|
|
editor.setSelection(
|
|
new monaco.Selection(
|
|
startPosition.lineNumber,
|
|
startPosition.column,
|
|
target.position.lineNumber,
|
|
target.position.column
|
|
)
|
|
);
|
|
isSelecting = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
editor.onDidChangeCursorSelection((e) => {
|
|
const selection = e.selection;
|
|
const startPosition = selection.getStartPosition();
|
|
const endPosition = selection.getEndPosition();
|
|
|
|
if (selection.isEmpty()) {
|
|
// Hide the selections container when there's no selection
|
|
selectionsContainer.classList.add('hidden');
|
|
selectorMenu.classList.add('hidden');
|
|
return;
|
|
}
|
|
|
|
// Get the top position of the start and end lines
|
|
const startLineTop = editor.getTopForLineNumber(startPosition.lineNumber);
|
|
const endLineTop = editor.getTopForLineNumber(endPosition.lineNumber);
|
|
|
|
// Get the position of the start and end of the selection in client coordinates
|
|
const startCoords = editor.getScrolledVisiblePosition(startPosition);
|
|
const endCoords = editor.getScrolledVisiblePosition(endPosition);
|
|
|
|
if (startCoords && endCoords) {
|
|
const editorCoords = editorElement.getBoundingClientRect();
|
|
|
|
// Calculate positions for the selectors based on line number top positions
|
|
const leftSelectorX = editorCoords.left + startCoords.left - leftLength;
|
|
const leftSelectorY = editorCoords.top + startLineTop - tapsHeight;
|
|
const rightSelectorX = editorCoords.left + endCoords.left - leftLength;
|
|
const rightSelectorY = editorCoords.top + endLineTop - tapsHeight;
|
|
|
|
// Update the left-selector position
|
|
leftSelector.style.transform = `translateX(${leftSelectorX - 30}px) translateY(${
|
|
leftSelectorY - 8
|
|
}px)`;
|
|
// Update the right-selector position
|
|
rightSelector.style.transform = `translateX(${rightSelectorX - 30}px) translateY(${
|
|
rightSelectorY - 8
|
|
}px)`;
|
|
globalPoseMenu = leftSelectorY - 50;
|
|
}
|
|
|
|
// Show the selections container when there is a selection
|
|
selectionsContainer.classList.remove('hidden');
|
|
});
|
|
|
|
initializeSelector();
|
|
|
|
function showContextMenu(x, y) {
|
|
let top = globalPoseMenu == 0 ? y : globalPoseMenu;
|
|
if (top < 50) {
|
|
top = 60;
|
|
}
|
|
selectorMenu.style.transform = `translateY(${top}px)`;
|
|
selectorMenu.classList.remove('hidden');
|
|
}
|
|
|
|
editorElement.addEventListener('click', (event) => {
|
|
event.stopPropagation();
|
|
});
|
|
});
|