Выбрать дочерние узлы первого уровня можно через
contents(). Но понадобится рекурсия. Когда очередной узел
текстовый, в нём можно менять содержание, но если он
элемент, то надо внутрь него лезть тем же
contents()
RegExp'ы с глобальной заменой лучше простого replace на случай вставки Copy-Paste текста с большим количеством двойных дефисов, например. Поэтому же событие
input
, а не клавиатурные.
Кроме того
жо.. сложность выскочила с позицией курсора: при замене содержимого он сползает в начало узла. Ок, можно получить
Selection и запомнить
Range до всех замен и потом восстановить. Но тут кроется вторая засада: при замене меняется длина текста: два дефиса на одно тире. Поэтому нужно ещё и ловить изменение длины текста при замене в узле, в котором находится курсор.
Вроде, получилось.
Fiddle. Код:
var curNode
, startOffset
, arrIN = ['--','<<','>>','->','<-']
, arrOUT = ['—','«','»','→','←']
;
arrIN = arrIN.map(function(s){ return new RegExp(s,'g')});
$('#edit').on('input', onInput);
function onInput() {
var selObj = window.getSelection()
, range = selObj.getRangeAt(0)
;
curNode = range.startContainer;
startOffset = range.startOffset;
$(this).contents().each(walk);
range.setStart(curNode, startOffset);
}
function walk(i, node){
var len
, diff
;
if( node.nodeType === Node.TEXT_NODE) {
len = node.textContent.length;
node.textContent = replacer(node.textContent);
diff = len - node.textContent.length;
if( node === curNode && diff) {
startOffset -= diff;
}
} else if( node.nodeType === Node.ELEMENT_NODE) {
$(node).contents().each(walk);
}
}
function replacer(s) {
var i;
for( i = 0; i < arrIN.length; i++) s = s.replace( arrIN[i], arrOUT[i]);
return s;
}