Skip to content

Instantly share code, notes, and snippets.

@stsrki
Last active March 21, 2022 14:09
Show Gist options
  • Save stsrki/bfeb1c229ab7eff0dbb24fc9425e52fd to your computer and use it in GitHub Desktop.
Save stsrki/bfeb1c229ab7eff0dbb24fc9425e52fd to your computer and use it in GitHub Desktop.
Behave.js modified version
function BehaveHooks() {
var hooks = {};
return {
add: function (hookName, fn) {
if (typeof hookName == "object") {
var i;
for (i = 0; i < hookName.length; i++) {
var theHook = hookName[i];
if (!hooks[theHook]) {
hooks[theHook] = [];
}
hooks[theHook].push(fn);
}
} else {
if (!hooks[hookName]) {
hooks[hookName] = [];
}
hooks[hookName].push(fn);
}
},
get: function (hookName) {
if (hooks[hookName]) {
return hooks[hookName];
}
}
};
}
const _hooks = BehaveHooks();
export function Behave(userOpts) {
if (typeof String.prototype.repeat !== 'function') {
String.prototype.repeat = function (times) {
if (times < 1) {
return '';
}
if (times % 2) {
return this.repeat(times - 1) + this;
}
var half = this.repeat(times / 2);
return half + half;
};
}
if (typeof Array.prototype.filter !== 'function') {
Array.prototype.filter = function (func /*, thisp */) {
if (this === null) {
throw new TypeError();
}
var t = Object(this),
len = t.length >>> 0;
if (typeof func != "function") {
throw new TypeError();
}
var res = [],
thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (func.call(thisp, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
var defaults = {
textarea: null,
replaceTab: true,
softTabs: true,
tabSize: 4,
autoOpen: true,
overwrite: true,
autoStrip: true,
autoIndent: true,
fence: false
},
tab,
newLine,
charSettings = {
keyMap: [
{ open: "\"", close: "\"", canBreak: false },
{ open: "'", close: "'", canBreak: false },
{ open: "(", close: ")", canBreak: false },
{ open: "[", close: "]", canBreak: true },
{ open: "{", close: "}", canBreak: true }
]
},
utils = {
_callHook: function (hookName, passData) {
var hooks = _hooks.get(hookName);
passData = typeof passData == "boolean" && passData === false ? false : true;
if (hooks) {
if (passData) {
var theEditor = defaults.textarea,
textVal = theEditor.value,
caretPos = utils.cursor.get(),
i;
for (i = 0; i < hooks.length; i++) {
hooks[i].call(undefined, {
editor: {
element: theEditor,
text: textVal,
levelsDeep: utils.levelsDeep()
},
caret: {
pos: caretPos
},
lines: {
current: utils.cursor.getLine(textVal, caretPos),
total: utils.editor.getLines(textVal)
}
});
}
} else {
for (i = 0; i < hooks.length; i++) {
hooks[i].call(undefined);
}
}
}
},
defineNewLine: function () {
var ta = document.createElement('textarea');
ta.value = "\n";
if (ta.value.length == 2) {
newLine = "\r\n";
} else {
newLine = "\n";
}
},
defineTabSize: function (tabSize) {
if (typeof defaults.textarea.style.OTabSize != "undefined") {
defaults.textarea.style.OTabSize = tabSize; return;
}
if (typeof defaults.textarea.style.MozTabSize != "undefined") {
defaults.textarea.style.MozTabSize = tabSize; return;
}
if (typeof defaults.textarea.style.tabSize != "undefined") {
defaults.textarea.style.tabSize = tabSize; return;
}
},
cursor: {
getLine: function (textVal, pos) {
return ((textVal.substring(0, pos)).split("\n")).length;
},
get: function () {
if (typeof document.createElement('textarea').selectionStart === "number") {
return defaults.textarea.selectionStart;
} else if (document.selection) {
var caretPos = 0,
range = defaults.textarea.createTextRange(),
rangeDupe = document.selection.createRange().duplicate(),
rangeDupeBookmark = rangeDupe.getBookmark();
range.moveToBookmark(rangeDupeBookmark);
while (range.moveStart('character', -1) !== 0) {
caretPos++;
}
return caretPos;
}
},
set: function (start, end) {
if (!end) {
end = start;
}
if (defaults.textarea.setSelectionRange) {
defaults.textarea.focus();
defaults.textarea.setSelectionRange(start, end);
} else if (defaults.textarea.createTextRange) {
var range = defaults.textarea.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
},
selection: function () {
var textAreaElement = defaults.textarea,
start = 0,
end = 0,
normalizedValue,
range,
textInputRange,
len,
endRange;
if (typeof textAreaElement.selectionStart == "number" && typeof textAreaElement.selectionEnd == "number") {
start = textAreaElement.selectionStart;
end = textAreaElement.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == textAreaElement) {
normalizedValue = utils.editor.get();
len = normalizedValue.length;
textInputRange = textAreaElement.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
endRange = textAreaElement.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split(newLine).length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split(newLine).length - 1;
}
}
}
}
return start == end ? false : {
start: start,
end: end
};
}
},
editor: {
getLines: function (textVal) {
return (textVal).split("\n").length;
},
get: function () {
return defaults.textarea.value.replace(/\r/g, '');
},
set: function (data, raiseInputEvent) {
defaults.textarea.value = data;
if (raiseInputEvent) {
defaults.textarea.dispatchEvent(new Event('input', { bubbles: true }));
}
}
},
fenceRange: function () {
if (typeof defaults.fence == "string") {
var data = utils.editor.get(),
pos = utils.cursor.get(),
hacked = 0,
matchedFence = data.indexOf(defaults.fence),
matchCase = 0;
while (matchedFence >= 0) {
matchCase++;
if (pos < (matchedFence + hacked)) {
break;
}
hacked += matchedFence + defaults.fence.length;
data = data.substring(matchedFence + defaults.fence.length);
matchedFence = data.indexOf(defaults.fence);
}
if ((hacked) < pos && ((matchedFence + hacked) > pos) && matchCase % 2 === 0) {
return true;
}
return false;
} else {
return true;
}
},
isEven: function (_this, i) {
return i % 2;
},
levelsDeep: function () {
var pos = utils.cursor.get(),
val = utils.editor.get();
var left = val.substring(0, pos),
levels = 0,
i, j;
for (i = 0; i < left.length; i++) {
for (j = 0; j < charSettings.keyMap.length; j++) {
if (charSettings.keyMap[j].canBreak) {
if (charSettings.keyMap[j].open == left.charAt(i)) {
levels++;
}
if (charSettings.keyMap[j].close == left.charAt(i)) {
levels--;
}
}
}
}
var toDecrement = 0,
quoteMap = ["'", "\""];
for (i = 0; i < charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].canBreak) {
for (j in quoteMap) {
toDecrement += left.split(quoteMap[j]).filter(utils.isEven).join('').split(charSettings.keyMap[i].open).length - 1;
}
}
}
var finalLevels = levels - toDecrement;
return finalLevels >= 0 ? finalLevels : 0;
},
deepExtend: function (destination, source) {
for (var property in source) {
if (source[property] && source[property].constructor &&
source[property].constructor === Object) {
destination[property] = destination[property] || {};
utils.deepExtend(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
},
addEvent: function addEvent(element, eventName, func) {
if (element.addEventListener) {
element.addEventListener(eventName, func, false);
} else if (element.attachEvent) {
element.attachEvent("on" + eventName, func);
}
},
removeEvent: function addEvent(element, eventName, func) {
if (element.addEventListener) {
element.removeEventListener(eventName, func, false);
} else if (element.attachEvent) {
element.detachEvent("on" + eventName, func);
}
},
preventDefaultEvent: function (e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
},
intercept = {
tabKey: function (e) {
if (!utils.fenceRange()) { return; }
if (e.keyCode == 9) {
utils.preventDefaultEvent(e);
var toReturn = true;
utils._callHook('tab:before');
var selection = utils.cursor.selection(),
pos = utils.cursor.get(),
val = utils.editor.get();
if (selection) {
var tempStart = selection.start;
while (tempStart--) {
if (val.charAt(tempStart) == "\n") {
selection.start = tempStart + 1;
break;
}
}
var toIndent = val.substring(selection.start, selection.end),
lines = toIndent.split("\n"),
i;
if (e.shiftKey) {
for (i = 0; i < lines.length; i++) {
if (lines[i].substring(0, tab.length) == tab) {
lines[i] = lines[i].substring(tab.length);
}
}
toIndent = lines.join("\n");
utils.editor.set(val.substring(0, selection.start) + toIndent + val.substring(selection.end));
utils.cursor.set(selection.start, selection.start + toIndent.length);
} else {
for (i in lines) {
lines[i] = tab + lines[i];
}
toIndent = lines.join("\n");
utils.editor.set(val.substring(0, selection.start) + toIndent + val.substring(selection.end));
utils.cursor.set(selection.start, selection.start + toIndent.length);
}
} else {
var left = val.substring(0, pos),
right = val.substring(pos),
edited = left + tab + right;
if (e.shiftKey) {
if (val.substring(pos - tab.length, pos) == tab) {
edited = val.substring(0, pos - tab.length) + right;
utils.editor.set(edited);
utils.cursor.set(pos - tab.length);
}
} else {
utils.editor.set(edited);
utils.cursor.set(pos + tab.length);
toReturn = false;
}
}
utils._callHook('tab:after');
}
return toReturn;
},
enterKey: function (e) {
if (!utils.fenceRange()) { return; }
if (e.keyCode == 13) {
utils.preventDefaultEvent(e);
utils._callHook('enter:before');
var pos = utils.cursor.get(),
val = utils.editor.get(),
left = val.substring(0, pos),
right = val.substring(pos),
leftChar = left.charAt(left.length - 1),
rightChar = right.charAt(0),
numTabs = utils.levelsDeep(),
ourIndent = "",
closingBreak = "",
finalCursorPos,
i;
if (!numTabs) {
finalCursorPos = 1;
} else {
while (numTabs--) {
ourIndent += tab;
}
ourIndent = ourIndent;
finalCursorPos = ourIndent.length + 1;
for (i = 0; i < charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar) {
closingBreak = newLine;
}
}
}
var edited = left + newLine + ourIndent + closingBreak + (ourIndent.substring(0, ourIndent.length - tab.length)) + right;
utils.editor.set(edited);
utils.cursor.set(pos + finalCursorPos);
utils._callHook('enter:after');
}
},
deleteKey: function (e) {
if (!utils.fenceRange()) { return; }
if (e.keyCode == 8) {
utils.preventDefaultEvent(e);
utils._callHook('delete:before');
var pos = utils.cursor.get(),
val = utils.editor.get(),
left = val.substring(0, pos),
right = val.substring(pos),
leftChar = left.charAt(left.length - 1),
rightChar = right.charAt(0),
i;
if (utils.cursor.selection() === false) {
for (i = 0; i < charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar) {
var edited = val.substring(0, pos - 1) + val.substring(pos + 1);
utils.editor.set(edited);
utils.cursor.set(pos - 1);
return;
}
}
var edited = val.substring(0, pos - 1) + val.substring(pos);
utils.editor.set(edited, true);
utils.cursor.set(pos - 1);
} else {
var sel = utils.cursor.selection(),
edited = val.substring(0, sel.start) + val.substring(sel.end);
utils.editor.set(edited, true);
utils.cursor.set(pos);
}
utils._callHook('delete:after');
}
}
},
charFuncs = {
openedChar: function (_char, e) {
utils.preventDefaultEvent(e);
utils._callHook('openChar:before');
var pos = utils.cursor.get(),
val = utils.editor.get(),
left = val.substring(0, pos),
right = val.substring(pos),
edited = left + _char.open + _char.close + right;
defaults.textarea.value = edited;
utils.cursor.set(pos + 1);
utils._callHook('openChar:after');
},
closedChar: function (_char, e) {
var pos = utils.cursor.get(),
val = utils.editor.get(),
toOverwrite = val.substring(pos, pos + 1);
if (toOverwrite == _char.close) {
utils.preventDefaultEvent(e);
utils._callHook('closeChar:before');
utils.cursor.set(utils.cursor.get() + 1);
utils._callHook('closeChar:after');
return true;
}
return false;
}
},
action = {
filter: function (e) {
if (!utils.fenceRange()) { return; }
var theCode = e.which || e.keyCode;
if (theCode == 39 || theCode == 40 && e.which === 0) { return; }
var _char = String.fromCharCode(theCode),
i;
for (i = 0; i < charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].close == _char) {
var didClose = defaults.overwrite && charFuncs.closedChar(charSettings.keyMap[i], e);
if (!didClose && charSettings.keyMap[i].open == _char && defaults.autoOpen) {
charFuncs.openedChar(charSettings.keyMap[i], e);
}
} else if (charSettings.keyMap[i].open == _char && defaults.autoOpen) {
charFuncs.openedChar(charSettings.keyMap[i], e);
}
}
},
listen: function () {
if (defaults.replaceTab) { utils.addEvent(defaults.textarea, 'keydown', intercept.tabKey); }
if (defaults.autoIndent) { utils.addEvent(defaults.textarea, 'keydown', intercept.enterKey); }
if (defaults.autoStrip) { utils.addEvent(defaults.textarea, 'keydown', intercept.deleteKey); }
utils.addEvent(defaults.textarea, 'keypress', action.filter);
utils.addEvent(defaults.textarea, 'keydown', function () { utils._callHook('keydown'); });
utils.addEvent(defaults.textarea, 'keyup', function () { utils._callHook('keyup'); });
utils.addEvent(defaults.textarea, 'blur', function () {
defaults.textarea.dispatchEvent(new Event('change', { bubbles: true }));
});
}
},
init = function (opts) {
if (opts.textarea) {
utils._callHook('init:before', false);
utils.deepExtend(defaults, opts);
utils.defineNewLine();
if (defaults.softTabs) {
tab = " ".repeat(defaults.tabSize);
} else {
tab = "\t";
utils.defineTabSize(defaults.tabSize);
}
action.listen();
utils._callHook('init:after', false);
}
};
this.destroy = function () {
utils.removeEvent(defaults.textarea, 'keydown', intercept.tabKey);
utils.removeEvent(defaults.textarea, 'keydown', intercept.enterKey);
utils.removeEvent(defaults.textarea, 'keydown', intercept.deleteKey);
utils.removeEvent(defaults.textarea, 'keypress', action.filter);
};
init(userOpts);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment