수정가능 범위 하이라이트, IMPORT 추가, 스크립트 선택 필드Select2 로 변경
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
|||||||
|
.editableArea--single-line {
|
||||||
|
background: #dde969;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editableArea--multi-line {
|
||||||
|
background: #ffb74e;
|
||||||
|
}
|
||||||
+1052
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
src/main/resources/static/plugins/vs/plugin/constrained-editor-plugin/constrainedEditorPlugin.min.js
Vendored
+2
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+7
@@ -0,0 +1,7 @@
|
|||||||
|
.editableArea--single-line {
|
||||||
|
background: #dde969;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editableArea--multi-line {
|
||||||
|
background: #ffb74e;
|
||||||
|
}
|
||||||
+212
@@ -0,0 +1,212 @@
|
|||||||
|
import validators from './utils/validators.js';
|
||||||
|
import { TypeMustBe } from './utils/definedErrors.js';
|
||||||
|
import constrainedModel from './constrainedModel.js';
|
||||||
|
|
||||||
|
export function constrainedEditor(monaco) {
|
||||||
|
/**
|
||||||
|
* Injected Dependencies
|
||||||
|
*/
|
||||||
|
if (monaco === undefined) {
|
||||||
|
throw new Error([
|
||||||
|
"Please pass the monaco global variable into function as",
|
||||||
|
"(eg:)constrainedEditor({ range : monaco.range });",
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} editorInstance This should be the monaco editor instance.
|
||||||
|
* @description This is the listener function to check whether the cursor is at checkpoints
|
||||||
|
* (i.e) the point where editable and non editable portions meet
|
||||||
|
*/
|
||||||
|
const listenerFn = function (editorInstance) {
|
||||||
|
const model = editorInstance.getModel();
|
||||||
|
if (model._isCursorAtCheckPoint) {
|
||||||
|
const selections = editorInstance.getSelections();
|
||||||
|
const positions = selections.map(function (selection) {
|
||||||
|
return {
|
||||||
|
lineNumber: selection.positionLineNumber,
|
||||||
|
column: selection.positionColumn
|
||||||
|
}
|
||||||
|
});
|
||||||
|
model._isCursorAtCheckPoint(positions);
|
||||||
|
model._currentCursorPositions = selections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const _uriRestrictionMap = {};
|
||||||
|
const { isInstanceValid, isModelValid, isRangesValid } = validators.initWith(monaco);
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} editorInstance This should be the monaco editor instance
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
const initInEditorInstance = function (editorInstance) {
|
||||||
|
if (isInstanceValid(editorInstance)) {
|
||||||
|
const domNode = editorInstance.getDomNode();
|
||||||
|
manipulator._listener = listenerFn.bind(API, editorInstance);
|
||||||
|
manipulator._editorInstance = editorInstance;
|
||||||
|
manipulator._editorInstance._isInDevMode = false;
|
||||||
|
domNode.addEventListener('keydown', manipulator._listener, true);
|
||||||
|
editorInstance.onDidChangeModel(function () {
|
||||||
|
// domNode - refers old dom node
|
||||||
|
domNode.removeEventListener('keydown', manipulator._listener, true)
|
||||||
|
const newDomNode = editorInstance.getDomNode(); // Gets Current dom node
|
||||||
|
newDomNode.addEventListener('keydown', manipulator._listener, true);
|
||||||
|
domNode = newDomNode;
|
||||||
|
})
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
TypeMustBe(
|
||||||
|
'ICodeEditor',
|
||||||
|
'editorInstance',
|
||||||
|
'This type interface can be found in monaco editor documentation'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} model This should be the monaco editor model instance. Refer https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.itextmodel.html
|
||||||
|
* @param {*} ranges This should be the array of range objects. Refer constrained editor plugin documentation
|
||||||
|
* @returns model
|
||||||
|
*/
|
||||||
|
const addRestrictionsTo = function (model, ranges) {
|
||||||
|
if (isModelValid(model)) {
|
||||||
|
if (isRangesValid(ranges)) {
|
||||||
|
const modelToConstrain = constrainedModel(model, ranges, monaco, manipulator._editorInstance);
|
||||||
|
_uriRestrictionMap[modelToConstrain.uri.toString()] = modelToConstrain;
|
||||||
|
return modelToConstrain;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
TypeMustBe(
|
||||||
|
'Array<RangeRestrictionObject>',
|
||||||
|
'ranges',
|
||||||
|
'Please refer constrained editor documentation for proper structure'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
TypeMustBe(
|
||||||
|
'ICodeEditor',
|
||||||
|
'editorInstance',
|
||||||
|
'This type interface can be found in monaco editor documentation'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} model This should be the monaco editor model instance. Refer https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.itextmodel.html
|
||||||
|
* @returns {Boolean} True if the restrictions are removed
|
||||||
|
*/
|
||||||
|
const removeRestrictionsIn = function (model) {
|
||||||
|
if (isModelValid(model)) {
|
||||||
|
const uri = model.uri.toString();
|
||||||
|
const restrictedModel = _uriRestrictionMap[uri];
|
||||||
|
if (restrictedModel) {
|
||||||
|
return restrictedModel.disposeRestrictions();
|
||||||
|
} else {
|
||||||
|
console.warn('Current Model is not a restricted Model');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
TypeMustBe(
|
||||||
|
'ICodeEditor',
|
||||||
|
'editorInstance',
|
||||||
|
'This type interface can be found in monaco editor documentation'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {Boolean} True if the constrainer is disposed
|
||||||
|
*/
|
||||||
|
const disposeConstrainer = function () {
|
||||||
|
if (manipulator._editorInstance) {
|
||||||
|
const instance = manipulator._editorInstance;
|
||||||
|
const domNode = instance.getDomNode();
|
||||||
|
domNode.removeEventListener('keydown', manipulator._listener);
|
||||||
|
delete manipulator._listener;
|
||||||
|
delete manipulator._editorInstance._isInDevMode;
|
||||||
|
delete manipulator._editorInstance._devModeAction;
|
||||||
|
delete manipulator._editorInstance;
|
||||||
|
for (let key in _uriRestrictionMap) {
|
||||||
|
delete _uriRestrictionMap[key];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description This function used to make the developer to find the ranges of selected portions
|
||||||
|
*/
|
||||||
|
const toggleDevMode = function () {
|
||||||
|
if (manipulator._editorInstance._isInDevMode) {
|
||||||
|
manipulator._editorInstance._isInDevMode = false;
|
||||||
|
manipulator._editorInstance._devModeAction.dispose();
|
||||||
|
delete manipulator._editorInstance._devModeAction;
|
||||||
|
} else {
|
||||||
|
manipulator._editorInstance._isInDevMode = true;
|
||||||
|
manipulator._editorInstance._devModeAction = manipulator._editorInstance.addAction({
|
||||||
|
id: 'showRange',
|
||||||
|
label: 'Show Range in console',
|
||||||
|
contextMenuGroupId: 'navigation',
|
||||||
|
contextMenuOrder: 1.5,
|
||||||
|
run: function (editor) {
|
||||||
|
const selections = editor.getSelections();
|
||||||
|
const ranges = selections.reduce(function (acc, { startLineNumber, endLineNumber, startColumn, endColumn }) {
|
||||||
|
acc.push('range : ' + JSON.stringify([
|
||||||
|
startLineNumber,
|
||||||
|
startColumn,
|
||||||
|
endLineNumber,
|
||||||
|
endColumn
|
||||||
|
]));
|
||||||
|
return acc;
|
||||||
|
}, []).join('\n');
|
||||||
|
console.log(`Selected Ranges : \n` + JSON.stringify(ranges, null, 2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main Function starts here
|
||||||
|
*/
|
||||||
|
// @internal
|
||||||
|
const manipulator = {
|
||||||
|
/**
|
||||||
|
* These variables should not be modified by external code
|
||||||
|
* This has to be used for debugging and testing
|
||||||
|
*/
|
||||||
|
_listener: null,
|
||||||
|
_editorInstance: null,
|
||||||
|
_uriRestrictionMap: _uriRestrictionMap,
|
||||||
|
_injectedResources: monaco
|
||||||
|
}
|
||||||
|
const API = Object.create(manipulator);
|
||||||
|
const exposedMethods = {
|
||||||
|
/**
|
||||||
|
* These functions are exposed to the user
|
||||||
|
* These functions should be protected from editing
|
||||||
|
*/
|
||||||
|
initializeIn: initInEditorInstance,
|
||||||
|
addRestrictionsTo: addRestrictionsTo,
|
||||||
|
removeRestrictionsIn: removeRestrictionsIn,
|
||||||
|
disposeConstrainer: disposeConstrainer,
|
||||||
|
toggleDevMode: toggleDevMode
|
||||||
|
}
|
||||||
|
for (let methodName in exposedMethods) {
|
||||||
|
Object.defineProperty(API, methodName, {
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
value: exposedMethods[methodName]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Object.freeze(API);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default constrainedEditor;
|
||||||
+560
@@ -0,0 +1,560 @@
|
|||||||
|
import deepClone from './utils/deepClone.js';
|
||||||
|
import enums from './utils/enums.js';
|
||||||
|
export const constrainedModel = function (model, ranges, monaco) {
|
||||||
|
const rangeConstructor = monaco.Range;
|
||||||
|
const sortRangesInAscendingOrder = function (rangeObject1, rangeObject2) {
|
||||||
|
const rangeA = rangeObject1.range;
|
||||||
|
const rangeB = rangeObject2.range;
|
||||||
|
if (
|
||||||
|
rangeA[0] < rangeB[0] ||
|
||||||
|
(rangeA[0] === rangeB[0] && rangeA[3] < rangeB[1])
|
||||||
|
) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const normalizeRange = function (range, content) {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const noOfLines = lines.length;
|
||||||
|
const normalizedRange = [];
|
||||||
|
range.forEach(function (value, index) {
|
||||||
|
if (value === 0) {
|
||||||
|
throw new Error('Range values cannot be zero');//No I18n
|
||||||
|
}
|
||||||
|
switch (index) {
|
||||||
|
case 0: {
|
||||||
|
if (value < 0) {
|
||||||
|
throw new Error('Start Line of Range cannot be negative');//No I18n
|
||||||
|
} else if (value > noOfLines) {
|
||||||
|
throw new Error('Provided Start Line(' + value + ') is out of bounds. Max Lines in content is ' + noOfLines);//No I18n
|
||||||
|
}
|
||||||
|
normalizedRange[index] = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1: {
|
||||||
|
let actualStartCol = value;
|
||||||
|
const startLineNo = normalizedRange[0];
|
||||||
|
const maxCols = lines[startLineNo - 1].length
|
||||||
|
if (actualStartCol < 0) {
|
||||||
|
actualStartCol = maxCols - Math.abs(actualStartCol);
|
||||||
|
if (actualStartCol < 0) {
|
||||||
|
throw new Error('Provided Start Column(' + value + ') is out of bounds. Max Column in line ' + startLineNo + ' is ' + maxCols);//No I18n
|
||||||
|
}
|
||||||
|
} else if (actualStartCol > (maxCols + 1)) {
|
||||||
|
throw new Error('Provided Start Column(' + value + ') is out of bounds. Max Column in line ' + startLineNo + ' is ' + maxCols);//No I18n
|
||||||
|
}
|
||||||
|
normalizedRange[index] = actualStartCol;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: {
|
||||||
|
let actualEndLine = value;
|
||||||
|
if (actualEndLine < 0) {
|
||||||
|
actualEndLine = noOfLines - Math.abs(value);
|
||||||
|
if (actualEndLine < 0) {
|
||||||
|
throw new Error('Provided End Line(' + value + ') is out of bounds. Max Lines in content is ' + noOfLines);//No I18n
|
||||||
|
}
|
||||||
|
if (actualEndLine < normalizedRange[0]) {
|
||||||
|
console.warn('Provided End Line(' + value + ') is less than the start Line, the Restriction may not behave as expected');//No I18n
|
||||||
|
}
|
||||||
|
} else if (value > noOfLines) {
|
||||||
|
throw new Error('Provided End Line(' + value + ') is out of bounds. Max Lines in content is ' + noOfLines);//No I18n
|
||||||
|
}
|
||||||
|
normalizedRange[index] = actualEndLine;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: {
|
||||||
|
let actualEndCol = value;
|
||||||
|
const endLineNo = normalizedRange[2];
|
||||||
|
const maxCols = lines[endLineNo - 1].length
|
||||||
|
if (actualEndCol < 0) {
|
||||||
|
actualEndCol = maxCols - Math.abs(actualEndCol);
|
||||||
|
if (actualEndCol < 0) {
|
||||||
|
throw new Error('Provided End Column(' + value + ') is out of bounds. Max Column in line ' + endLineNo + ' is ' + maxCols);//No I18n
|
||||||
|
}
|
||||||
|
} else if (actualEndCol > (maxCols + 1)) {
|
||||||
|
throw new Error('Provided Start Column(' + value + ') is out of bounds. Max Column in line ' + endLineNo + ' is ' + maxCols);//No I18n
|
||||||
|
}
|
||||||
|
normalizedRange[index] = actualEndCol;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return normalizedRange;
|
||||||
|
}
|
||||||
|
let restrictions = deepClone(ranges).sort(sortRangesInAscendingOrder);
|
||||||
|
const prepareRestrictions = function (restrictions) {
|
||||||
|
const content = model.getValue();
|
||||||
|
restrictions.forEach(function (restriction, index) {
|
||||||
|
const range = normalizeRange(restriction.range, content);
|
||||||
|
const startLine = range[0];
|
||||||
|
const startCol = range[1];
|
||||||
|
const endLine = range[2];
|
||||||
|
const endCol = range[3];
|
||||||
|
restriction._originalRange = range.slice();
|
||||||
|
restriction.range = new rangeConstructor(startLine, startCol, endLine, endCol);
|
||||||
|
restriction.index = index;
|
||||||
|
if (!restriction.allowMultiline) {
|
||||||
|
restriction.allowMultiline = rangeConstructor.spansMultipleLines(restriction.range)
|
||||||
|
}
|
||||||
|
if (!restriction.label) {
|
||||||
|
restriction.label = `[${startLine},${startCol} -> ${endLine}${endCol}]`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const getCurrentEditableRanges = function () {
|
||||||
|
return restrictions.reduce(function (acc, restriction) {
|
||||||
|
acc[restriction.label] = {
|
||||||
|
allowMultiline: restriction.allowMultiline || false,
|
||||||
|
index: restriction.index,
|
||||||
|
range: Object.assign({}, restriction.range),
|
||||||
|
originalRange: restriction._originalRange.slice()
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
const getValueInEditableRanges = function () {
|
||||||
|
return restrictions.reduce(function (acc, restriction) {
|
||||||
|
acc[restriction.label] = model.getValueInRange(restriction.range);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
const updateValueInEditableRanges = function (object, forceMoveMarkers) {
|
||||||
|
if (typeof object === 'object' && !Array.isArray(object)) {
|
||||||
|
forceMoveMarkers = typeof forceMoveMarkers === 'boolean' ? forceMoveMarkers : false;
|
||||||
|
const restrictionsMap = restrictions.reduce(function (acc, restriction) {
|
||||||
|
if (restriction.label) {
|
||||||
|
acc[restriction.label] = restriction;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
for (let label in object) {
|
||||||
|
const restriction = restrictionsMap[label];
|
||||||
|
if (restriction) {
|
||||||
|
const value = object[label];
|
||||||
|
if (doesChangeHasMultilineConflict(restriction, value)) {
|
||||||
|
throw new Error('Multiline change is not allowed for ' + label);
|
||||||
|
}
|
||||||
|
const newRange = deepClone(restriction.range);
|
||||||
|
newRange.endLine = newRange.startLine + value.split('\n').length - 1;
|
||||||
|
newRange.endColumn = value.split('\n').pop().length;
|
||||||
|
if (isChangeInvalidAsPerUser(restriction, value, newRange)) {
|
||||||
|
throw new Error('Change is invalidated by validate function of ' + label);
|
||||||
|
}
|
||||||
|
model.applyEdits([{
|
||||||
|
forceMoveMarkers: !!forceMoveMarkers,
|
||||||
|
range: restriction.range,
|
||||||
|
text: value
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
console.error('No restriction found for ' + label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Value must be an object');//No I18n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const disposeRestrictions = function () {
|
||||||
|
model._restrictionChangeListener.dispose();
|
||||||
|
window.removeEventListener("error", handleUnhandledPromiseRejection);
|
||||||
|
delete model.editInRestrictedArea;
|
||||||
|
delete model.disposeRestrictions;
|
||||||
|
delete model.getValueInEditableRanges;
|
||||||
|
delete model.updateValueInEditableRanges;
|
||||||
|
delete model.updateRestrictions;
|
||||||
|
delete model.getCurrentEditableRanges;
|
||||||
|
delete model.toggleHighlightOfEditableAreas;
|
||||||
|
delete model._hasHighlight;
|
||||||
|
delete model._isRestrictedModel;
|
||||||
|
delete model._isCursorAtCheckPoint;
|
||||||
|
delete model._currentCursorPositions;
|
||||||
|
delete model._editableRangeChangeListener;
|
||||||
|
delete model._restrictionChangeListener;
|
||||||
|
delete model._oldDecorations;
|
||||||
|
delete model._oldDecorationsSource;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
const isCursorAtCheckPoint = function (positions) {
|
||||||
|
positions.some(function (position) {
|
||||||
|
const posLineNumber = position.lineNumber;
|
||||||
|
const posCol = position.column;
|
||||||
|
const length = restrictions.length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const range = restrictions[i].range;
|
||||||
|
if (
|
||||||
|
(range.startLineNumber === posLineNumber && range.startColumn === posCol) ||
|
||||||
|
(range.endLineNumber === posLineNumber && range.endColumn === posCol)
|
||||||
|
) {
|
||||||
|
model.pushStackElement();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const addEditableRangeListener = function (callback) {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
model._editableRangeChangeListener.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const triggerChangeListenersWith = function (currentChanges, allChanges) {
|
||||||
|
const currentRanges = getCurrentEditableRanges();
|
||||||
|
model._editableRangeChangeListener.forEach(function (callback) {
|
||||||
|
callback.call(model, currentChanges, allChanges, currentRanges);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const doUndo = function () {
|
||||||
|
return Promise.resolve().then(function () {
|
||||||
|
model.editInRestrictedArea = true;
|
||||||
|
model.undo();
|
||||||
|
model.editInRestrictedArea = false;
|
||||||
|
if (model._hasHighlight && model._oldDecorationsSource) {
|
||||||
|
// id present in the decorations info will be omitted by monaco
|
||||||
|
// So we don't need to remove the old decorations id
|
||||||
|
model.deltaDecorations(model._oldDecorations, model._oldDecorationsSource);
|
||||||
|
model._oldDecorationsSource.forEach(function (object) {
|
||||||
|
object.range = model.getDecorationRange(object.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const updateRange = function (restriction, range, finalLine, finalColumn, changes, changeIndex) {
|
||||||
|
let oldRangeEndLineNumber = range.endLineNumber;
|
||||||
|
let oldRangeEndColumn = range.endColumn;
|
||||||
|
restriction.prevRange = range;
|
||||||
|
restriction.range = range.setEndPosition(finalLine, finalColumn);
|
||||||
|
const length = restrictions.length;
|
||||||
|
let changesLength = changes.length;
|
||||||
|
const diffInCol = finalColumn - oldRangeEndColumn;
|
||||||
|
const diffInRow = finalLine - oldRangeEndLineNumber;
|
||||||
|
|
||||||
|
const cursorPositions = model._currentCursorPositions || [];
|
||||||
|
const noOfCursorPositions = cursorPositions.length;
|
||||||
|
// if (noOfCursorPositions > 0) {
|
||||||
|
if (changesLength !== noOfCursorPositions) {
|
||||||
|
changes = changes.filter(function (change) {
|
||||||
|
const range = change.range;
|
||||||
|
for (let i = 0; i < noOfCursorPositions; i++) {
|
||||||
|
const cursorPosition = cursorPositions[i];
|
||||||
|
if (
|
||||||
|
(range.startLineNumber === cursorPosition.startLineNumber) &&
|
||||||
|
(range.endLineNumber === cursorPosition.endLineNumber) &&
|
||||||
|
(range.startColumn === cursorPosition.startColumn) &&
|
||||||
|
(range.endColumn === cursorPosition.endColumn)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
changesLength = changes.length;
|
||||||
|
}
|
||||||
|
if (diffInRow !== 0) {
|
||||||
|
for (let i = restriction.index + 1; i < length; i++) {
|
||||||
|
const nextRestriction = restrictions[i];
|
||||||
|
const nextRange = nextRestriction.range;
|
||||||
|
if (oldRangeEndLineNumber === nextRange.startLineNumber) {
|
||||||
|
nextRange.startColumn += diffInCol;
|
||||||
|
}
|
||||||
|
if (oldRangeEndLineNumber === nextRange.endLineNumber) {
|
||||||
|
nextRange.endColumn += diffInCol;
|
||||||
|
}
|
||||||
|
nextRange.startLineNumber += diffInRow;
|
||||||
|
nextRange.endLineNumber += diffInRow;
|
||||||
|
nextRestriction.range = nextRange;
|
||||||
|
}
|
||||||
|
for (let i = changeIndex + 1; i < changesLength; i++) {
|
||||||
|
const nextChange = changes[i];
|
||||||
|
const rangeInChange = nextChange.range;
|
||||||
|
const rangeAsString = rangeInChange.toString();
|
||||||
|
const rangeMapValue = rangeMap[rangeAsString];
|
||||||
|
delete rangeMap[rangeAsString];
|
||||||
|
if (oldRangeEndLineNumber === rangeInChange.startLineNumber) {
|
||||||
|
rangeInChange.startColumn += diffInCol;
|
||||||
|
}
|
||||||
|
if (oldRangeEndLineNumber === rangeInChange.endLineNumber) {
|
||||||
|
rangeInChange.endColumn += diffInCol;
|
||||||
|
}
|
||||||
|
rangeInChange.startLineNumber += diffInRow;
|
||||||
|
rangeInChange.endLineNumber += diffInRow;
|
||||||
|
nextChange.range = rangeInChange;
|
||||||
|
rangeMap[rangeInChange.toString()] = rangeMapValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only Column might have changed
|
||||||
|
for (let i = restriction.index + 1; i < length; i++) {
|
||||||
|
const nextRestriction = restrictions[i];
|
||||||
|
const nextRange = nextRestriction.range;
|
||||||
|
if (nextRange.startLineNumber > oldRangeEndLineNumber) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nextRange.startColumn += diffInCol;
|
||||||
|
nextRange.endColumn += diffInCol;
|
||||||
|
nextRestriction.range = nextRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = changeIndex + 1; i < changesLength; i++) {
|
||||||
|
// rangeMap
|
||||||
|
const nextChange = changes[i];
|
||||||
|
const rangeInChange = nextChange.range;
|
||||||
|
const rangeAsString = rangeInChange.toString();
|
||||||
|
const rangeMapValue = rangeMap[rangeAsString];
|
||||||
|
delete rangeMap[rangeAsString];
|
||||||
|
if (rangeInChange.startLineNumber > oldRangeEndLineNumber) {
|
||||||
|
rangeMap[rangeInChange.toString()] = rangeMapValue;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
rangeInChange.startColumn += diffInCol;
|
||||||
|
rangeInChange.endColumn += diffInCol;
|
||||||
|
nextChange.range = rangeInChange;
|
||||||
|
rangeMap[rangeInChange.toString()] = rangeMapValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
const getInfoFrom = function (change, editableRange) {
|
||||||
|
const info = {};
|
||||||
|
const range = change.range;
|
||||||
|
// Get State
|
||||||
|
if (change.text === '') {
|
||||||
|
info.isDeletion = true;
|
||||||
|
} else if (
|
||||||
|
(range.startLineNumber === range.endLineNumber) &&
|
||||||
|
(range.startColumn === range.endColumn)
|
||||||
|
) {
|
||||||
|
info.isAddition = true;
|
||||||
|
} else {
|
||||||
|
info.isReplacement = true;
|
||||||
|
}
|
||||||
|
// Get Position Of Range
|
||||||
|
info.startLineOfRange = range.startLineNumber === editableRange.startLineNumber;
|
||||||
|
info.startColumnOfRange = range.startColumn === editableRange.startColumn;
|
||||||
|
|
||||||
|
info.endLineOfRange = range.endLineNumber === editableRange.endLineNumber;
|
||||||
|
info.endColumnOfRange = range.endColumn === editableRange.endColumn;
|
||||||
|
|
||||||
|
info.middleLineOfRange = !info.startLineOfRange && !info.endLineOfRange;
|
||||||
|
|
||||||
|
// Editable Range Span
|
||||||
|
if (editableRange.startLineNumber === editableRange.endLineNumber) {
|
||||||
|
info.rangeIsSingleLine = true;
|
||||||
|
} else {
|
||||||
|
info.rangeIsMultiLine = true;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
const updateRestrictions = function (ranges) {
|
||||||
|
restrictions = deepClone(ranges).sort(sortRangesInAscendingOrder);
|
||||||
|
prepareRestrictions(restrictions);
|
||||||
|
};
|
||||||
|
const toggleHighlightOfEditableAreas = function () {
|
||||||
|
if (!model._hasHighlight) {
|
||||||
|
const decorations = restrictions.map(function (restriction) {
|
||||||
|
const decoration = {
|
||||||
|
range: restriction.range,
|
||||||
|
options: {
|
||||||
|
className: restriction.allowMultiline ?
|
||||||
|
enums.MULTI_LINE_HIGHLIGHT_CLASS :
|
||||||
|
enums.SINGLE_LINE_HIGHLIGHT_CLASS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (restriction.label) {
|
||||||
|
decoration.hoverMessage = restriction.label;
|
||||||
|
}
|
||||||
|
return decoration;
|
||||||
|
});
|
||||||
|
model._oldDecorations = model.deltaDecorations([], decorations);
|
||||||
|
model._oldDecorationsSource = decorations.map(function (decoration, index) {
|
||||||
|
return Object.assign({}, decoration, { id: model._oldDecorations[index] });
|
||||||
|
});
|
||||||
|
model._hasHighlight = true;
|
||||||
|
} else {
|
||||||
|
model.deltaDecorations(model._oldDecorations, []);
|
||||||
|
delete model._oldDecorations;
|
||||||
|
delete model._oldDecorationsSource;
|
||||||
|
model._hasHighlight = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleUnhandledPromiseRejection = function () {
|
||||||
|
console.debug('handler for unhandled promise rejection');
|
||||||
|
};
|
||||||
|
const setAllRangesToPrev = function (rangeMap) {
|
||||||
|
for (let key in rangeMap) {
|
||||||
|
const restriction = rangeMap[key];
|
||||||
|
restriction.range = restriction.prevRange;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const doesChangeHasMultilineConflict = function (restriction, text) {
|
||||||
|
return !restriction.allowMultiline && text.includes('\n');
|
||||||
|
};
|
||||||
|
const isChangeInvalidAsPerUser = function (restriction, value, range) {
|
||||||
|
return restriction.validate && !restriction.validate(value, range, restriction.lastInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const manipulatorApi = {
|
||||||
|
_isRestrictedModel: true,
|
||||||
|
_isRestrictedValueValid: true,
|
||||||
|
_editableRangeChangeListener: [],
|
||||||
|
_isCursorAtCheckPoint: isCursorAtCheckPoint,
|
||||||
|
_currentCursorPositions: []
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareRestrictions(restrictions);
|
||||||
|
model._hasHighlight = false;
|
||||||
|
manipulatorApi._restrictionChangeListener = model.onDidChangeContent(function (contentChangedEvent) {
|
||||||
|
const isUndoing = contentChangedEvent.isUndoing;
|
||||||
|
model._isRestrictedValueValid = true;
|
||||||
|
if (!(isUndoing && model.editInRestrictedArea)) {
|
||||||
|
const changes = contentChangedEvent.changes.sort(sortRangesInAscendingOrder);
|
||||||
|
const rangeMap = {};
|
||||||
|
const length = restrictions.length;
|
||||||
|
const isAllChangesValid = changes.every(function (change) {
|
||||||
|
const editedRange = change.range;
|
||||||
|
const rangeAsString = editedRange.toString();
|
||||||
|
rangeMap[rangeAsString] = null;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const restriction = restrictions[i];
|
||||||
|
const range = restriction.range;
|
||||||
|
if (range.containsRange(editedRange)) {
|
||||||
|
if (doesChangeHasMultilineConflict(restriction, change.text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rangeMap[rangeAsString] = restriction;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
if (isAllChangesValid) {
|
||||||
|
changes.forEach(function (change, changeIndex) {
|
||||||
|
const changedRange = change.range;
|
||||||
|
const restriction = rangeMap[changedRange.toString()];
|
||||||
|
const editableRange = restriction.range;
|
||||||
|
const text = change.text || '';
|
||||||
|
/**
|
||||||
|
* Things to check before implementing the change
|
||||||
|
* - A | D | R => Addition | Deletion | Replacement
|
||||||
|
* - MC | SC => MultiLineChange | SingleLineChange
|
||||||
|
* - SOR | MOR | EOR => Change Occured in - Start Of Range | Middle Of Range | End Of Range
|
||||||
|
* - SSL | SML => Editable Range - Spans Single Line | Spans Multiple Line
|
||||||
|
*/
|
||||||
|
const noOfLinesAdded = (text.match(/\n/g) || []).length;
|
||||||
|
const noOfColsAddedAtLastLine = text.split(/\n/g).pop().length;
|
||||||
|
|
||||||
|
const lineDiffInRange = changedRange.endLineNumber - changedRange.startLineNumber;
|
||||||
|
const colDiffInRange = changedRange.endColumn - changedRange.startColumn;
|
||||||
|
|
||||||
|
let finalLine = editableRange.endLineNumber;
|
||||||
|
let finalColumn = editableRange.endColumn;
|
||||||
|
|
||||||
|
let columnsCarriedToEnd = 0;
|
||||||
|
if (
|
||||||
|
(editableRange.endLineNumber === changedRange.startLineNumber) ||
|
||||||
|
(editableRange.endLineNumber === changedRange.endLineNumber)
|
||||||
|
) {
|
||||||
|
columnsCarriedToEnd += (editableRange.endColumn - changedRange.startColumn) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = getInfoFrom(change, editableRange);
|
||||||
|
restriction.lastInfo = info;
|
||||||
|
if (info.isAddition || info.isReplacement) {
|
||||||
|
if (info.rangeIsSingleLine) {
|
||||||
|
/**
|
||||||
|
* Only Column Change has occurred , so regardless of the position of the change
|
||||||
|
* Addition of noOfCols is enough
|
||||||
|
*/
|
||||||
|
if (noOfLinesAdded === 0) {
|
||||||
|
finalColumn += noOfColsAddedAtLastLine;
|
||||||
|
} else {
|
||||||
|
finalLine += noOfLinesAdded;
|
||||||
|
if (info.startColumnOfRange) {
|
||||||
|
finalColumn += noOfColsAddedAtLastLine
|
||||||
|
} else if (info.endColumnOfRange) {
|
||||||
|
finalColumn = (noOfColsAddedAtLastLine + 1)
|
||||||
|
} else {
|
||||||
|
finalColumn = (noOfColsAddedAtLastLine + columnsCarriedToEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (info.rangeIsMultiLine) {
|
||||||
|
// Handling for Start Of Range is not required
|
||||||
|
finalLine += noOfLinesAdded;
|
||||||
|
if (info.endLineOfRange) {
|
||||||
|
if (noOfLinesAdded === 0) {
|
||||||
|
finalColumn += noOfColsAddedAtLastLine;
|
||||||
|
} else {
|
||||||
|
finalColumn = (columnsCarriedToEnd + noOfColsAddedAtLastLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (info.isDeletion || info.isReplacement) {
|
||||||
|
if (info.rangeIsSingleLine) {
|
||||||
|
finalColumn -= colDiffInRange;
|
||||||
|
}
|
||||||
|
if (info.rangeIsMultiLine) {
|
||||||
|
if (info.endLineOfRange) {
|
||||||
|
finalLine -= lineDiffInRange;
|
||||||
|
finalColumn -= colDiffInRange;
|
||||||
|
} else {
|
||||||
|
finalLine -= lineDiffInRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateRange(restriction, editableRange, finalLine, finalColumn, changes, changeIndex);
|
||||||
|
});
|
||||||
|
const values = model.getValueInEditableRanges();
|
||||||
|
const currentlyEditedRanges = {};
|
||||||
|
for (let key in rangeMap) {
|
||||||
|
const restriction = rangeMap[key];
|
||||||
|
const range = restriction.range;
|
||||||
|
const rangeString = restriction.label || range.toString();
|
||||||
|
const value = values[rangeString];
|
||||||
|
if (isChangeInvalidAsPerUser(restriction, value, range)) {
|
||||||
|
setAllRangesToPrev(rangeMap);
|
||||||
|
doUndo();
|
||||||
|
return; // Breaks the loop and prevents the triggerChangeListener
|
||||||
|
}
|
||||||
|
currentlyEditedRanges[rangeString] = value;
|
||||||
|
}
|
||||||
|
if (model._hasHighlight) {
|
||||||
|
model._oldDecorationsSource.forEach(function (object) {
|
||||||
|
object.range = model.getDecorationRange(object.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
triggerChangeListenersWith(currentlyEditedRanges, values);
|
||||||
|
} else {
|
||||||
|
doUndo();
|
||||||
|
}
|
||||||
|
} else if (model.editInRestrictedArea) {
|
||||||
|
model._isRestrictedValueValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.onerror = handleUnhandledPromiseRejection;
|
||||||
|
const exposedApi = {
|
||||||
|
editInRestrictedArea: false,
|
||||||
|
getCurrentEditableRanges: getCurrentEditableRanges,
|
||||||
|
getValueInEditableRanges: getValueInEditableRanges,
|
||||||
|
disposeRestrictions: disposeRestrictions,
|
||||||
|
onDidChangeContentInEditableRange: addEditableRangeListener,
|
||||||
|
updateRestrictions: updateRestrictions,
|
||||||
|
updateValueInEditableRanges: updateValueInEditableRanges,
|
||||||
|
toggleHighlightOfEditableAreas: toggleHighlightOfEditableAreas
|
||||||
|
}
|
||||||
|
for (let funcName in manipulatorApi) {
|
||||||
|
Object.defineProperty(model, funcName, {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: manipulatorApi[funcName]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (let apiName in exposedApi) {
|
||||||
|
Object.defineProperty(model, apiName, {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: exposedApi[apiName]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
export default constrainedModel;
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
export const deepClone = (function () {
|
||||||
|
const byPassPrimitives = function (value, callback) {
|
||||||
|
if (typeof value !== 'object' || value === null) {
|
||||||
|
return this.freeze ? Object.freeze(value) : value;
|
||||||
|
}
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return this.freeze ? Object.freeze(new Date(value)) : new Date(value);
|
||||||
|
}
|
||||||
|
return callback.call(this, value);
|
||||||
|
}
|
||||||
|
const cloneArray = function (array, callback) {
|
||||||
|
const keys = Object.keys(array);
|
||||||
|
const arrayClone = new Array(keys.length)
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
arrayClone[keys[i]] = byPassPrimitives.call(this, array[keys[i]], callback);
|
||||||
|
}
|
||||||
|
return arrayClone;
|
||||||
|
}
|
||||||
|
const cloner = function (object) {
|
||||||
|
return byPassPrimitives.call(this, object, function (object) {
|
||||||
|
if (Array.isArray(object)) {
|
||||||
|
return cloneArray.call(this, object, cloner)
|
||||||
|
}
|
||||||
|
const clone = {};
|
||||||
|
for (let key in object) {
|
||||||
|
if (!this.withProto && Object.hasOwnProperty.call(object, key) === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
clone[key] = byPassPrimitives.call(this, object[key], cloner);
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const config = (function () {
|
||||||
|
const constructOptionForCode = function (value) {
|
||||||
|
const options = [
|
||||||
|
'withProto',
|
||||||
|
'freeze'
|
||||||
|
]
|
||||||
|
this[value] = options.reduce(function (acc, option) {
|
||||||
|
if (acc[option] = (value >= this[option])) {
|
||||||
|
value -= this[option]
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}.bind(this), {})
|
||||||
|
}
|
||||||
|
const codes = Object.create(Object.defineProperties({}, {
|
||||||
|
withProto: { value: 1 },
|
||||||
|
freeze: { value: 2 }
|
||||||
|
}));
|
||||||
|
for (let i = 0; i <= 3; i++) {
|
||||||
|
constructOptionForCode.call(codes, i);
|
||||||
|
}
|
||||||
|
return codes;
|
||||||
|
}());
|
||||||
|
const methods = {
|
||||||
|
withProto: cloner.bind(config[1]),
|
||||||
|
andFreeze: cloner.bind(config[2]),
|
||||||
|
withProtoAndFreeze: cloner.bind(config[3])
|
||||||
|
}
|
||||||
|
const API = cloner.bind(config[0]);
|
||||||
|
for (let methodName in methods) {
|
||||||
|
Object.defineProperty(API, methodName, {
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
value: methods[methodName]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return API;
|
||||||
|
}());
|
||||||
|
|
||||||
|
export default deepClone;
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
export const TypeMustBe = function (type, key, additional) {
|
||||||
|
return 'The value for the ' + key + ' should be of type ' + (Array.isArray(type) ? type.join(' | ') : type) + '. ' + (additional || '')
|
||||||
|
}
|
||||||
|
const definedErrors = {
|
||||||
|
TypeMustBe : TypeMustBe
|
||||||
|
};
|
||||||
|
export default definedErrors;
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
export const enums = {
|
||||||
|
SINGLE_LINE_HIGHLIGHT_CLASS: 'editableArea--single-line',
|
||||||
|
MULTI_LINE_HIGHLIGHT_CLASS: 'editableArea--multi-line'
|
||||||
|
}
|
||||||
|
export default enums;
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
const validators = {
|
||||||
|
initWith: function (monaco) {
|
||||||
|
const dummyDiv = document.createElement('div');
|
||||||
|
const dummyEditorInstance = monaco.editor.create(dummyDiv);
|
||||||
|
const editorInstanceConstructorName = dummyEditorInstance.constructor.name;
|
||||||
|
const editorModelConstructorName = dummyEditorInstance.getModel().constructor.name;
|
||||||
|
const instanceCheck = function (valueToValidate) {
|
||||||
|
return valueToValidate.constructor.name === editorInstanceConstructorName;
|
||||||
|
}
|
||||||
|
const modelCheck = function (valueToValidate) {
|
||||||
|
return valueToValidate.constructor.name === editorModelConstructorName;
|
||||||
|
}
|
||||||
|
const rangesCheck = function (ranges) {
|
||||||
|
if (Array.isArray(ranges)) {
|
||||||
|
return ranges.every(function (rangeObj) {
|
||||||
|
if (typeof rangeObj === 'object' && rangeObj.constructor.name === 'Object') {
|
||||||
|
if (!rangeObj.hasOwnProperty('range')) return false;
|
||||||
|
if (!Array.isArray(rangeObj.range)) return false;
|
||||||
|
if (rangeObj.range.length !== 4) return false;
|
||||||
|
if (!(rangeObj.range.every(num => num > 0 && parseInt(num) === num))) return false;
|
||||||
|
if (rangeObj.hasOwnProperty('allowMultiline')) {
|
||||||
|
if (typeof rangeObj.allowMultiline !== 'boolean') return false;
|
||||||
|
}
|
||||||
|
if (rangeObj.hasOwnProperty('label')) {
|
||||||
|
if (typeof rangeObj.label !== 'string') return false;
|
||||||
|
}
|
||||||
|
if (rangeObj.hasOwnProperty('validate')) {
|
||||||
|
if (typeof rangeObj.validate !== 'function') return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isInstanceValid: instanceCheck,
|
||||||
|
isModelValid: modelCheck,
|
||||||
|
isRangesValid: rangesCheck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default validators;
|
||||||
Reference in New Issue
Block a user