수정가능 범위 하이라이트, 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