Простой редактор на основе разноцветных областей текста

#javascript #regex

Вопрос:

Мне нужно раскрасить код текстовой области для инструмента редактирования HTML/CSS/JS/результатов разделения. В этом примере я хочу, чтобы теги были одного цвета, а весь остальной текст — другого цвета.

Всегда используется моноширинный шрифт.

Поэтому я решил отделить текст тегов от остальных и поместить каждую часть в отдельную текстовую область поверх основной. Недостающая половина (обычный текст для тегов и текст тегов для остальных) заменяется пробелами, чтобы все оставалось в правильном положении.

Ниже приведен рабочий фрагмент (я провел только простые тесты на ввод текста).

У меня есть пара вопросов:

  1. Есть ли какие-либо серьезные недостатки в этом подходе? Будет ли конечный пользователь отключен от какой-либо обычной текстовой операции в текстовой области?
  2. Я использую следующее регулярное выражение для отделения тегов от остального текста:
     elem.value.match(/(<[^<>] >)|([sS]*?(?=(<[^<>] >)))|((?<=(<[^<>] >))[sS]*)/g);
     

идея здесь состоит в том, чтобы сопоставить либо тег (<[^<>] >) , либо что-то, за чем следует тег, либо что-то, что следует за ним.

Можно ли упростить это выражение?

В идеале, чтобы он говорил «захват» или «не тег», мне трудно определить последнее.

 const TAB = '  ';

const get = id => document.getElementById(id);

const [main, tag, txt] = [get('main'), get('tag'), get('txt')];

const colorizeHTML = evt => { 
    const elem = evt.target;
    const arr = elem.value.split(/(?!^)(?=<[^<>] >)|(?<=<[^<>] >)(?!$)/);
    if (!arr) return;
    let [str_tag, str_txt] = ['', ''];
    arr.forEach(s => {
        const repl = s.replace(/[^n ]/g, 'xa0');
        if (s[0] == '<' amp;amp; s[s.length - 1] == '>') {
            str_tag  = s;
            str_txt  = repl;
        } else {
            str_tag  = repl;
            str_txt  = s;
        }
    });
    tag.value = str_tag;
    txt.value = str_txt;
}

const onKeyDownDefault = evt => {
    if (evt.key == "Tab") {
        evt.preventDefault();
        document.execCommand('insertText', false, TAB);
    }
}

const onScroll = evt => {
    const scr = evt.target.scrollTop;
    tag.scrollTop = scr;
    txt.scrollTop = scr;
}

main.addEventListener('keydown', onKeyDownDefault);
main.addEventListener('scroll', onScroll);

['input', 'keyup', 'mouseup', 'drop', 'paste'].forEach(nm => { main.addEventListener(nm, colorizeHTML) });

main.value = 'Chapter 12<div id="sec1">n<div>Standard Curves</div>n<div id="tot1"></div>n<button id="btn1">Toggle points</button>n</div>';
colorizeHTML({ target: main }); 
 * {
  margin: 0px;
  padding: 0px;
  box-sizing: border-box;
}

body {
  background-color: #000;
  color: #999;
}

textarea:focus {
  outline: none;
}

#wrapper {
  margin: 5px;
  position: relative;
  width: 400px;
  height: 170px;
}

#tag, #txt {
  position: absolute;
  left: 0px;
  top: 0px;
  pointer-events: none;
}

#main, #tag, #txt {
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0);
  resize: none;
  font-family: monospace;
  padding: 3px;
}

#main {
  border: solid 1px #666;
  background: rgba(8, 15, 32, 1.0);
  caret-color: #91d6db;
}

#main::selection {
  background: rgba(0, 255, 128, 0.15);
}

#main::-moz-selection {
  background: rgba(0, 255, 128, 0.15);
}

#tag {
    color: #4576e6;
}

#txt {
    color: #9bc6ca;
}

.clean_scroll {
  overflow: scroll;
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.clean_scroll::-webkit-scrollbar {
    display: none;
} 
 <div id="wrapper">
<textarea id="main" class="clean_scroll"></textarea>
<textarea id="tag" class="clean_scroll"></textarea>
<textarea id="txt" class="clean_scroll"></textarea>
</div> 

Комментарии:

1. Я вижу один ГЛАВНЫЙ недостаток … когда вы нажимаете вкладку, вы переключаетесь между текстовыми областями, и вы можете писать напрямую tag или txt без какого-либо события, создавая беспорядок дублированных цветов в одном и том же месте, и один раз, когда я нажимал случайные клавиши, он начал писать черным (не знаю, когда).

2. Я добавил код для обработки вкладки на месте

3. Захват фокуса внутри элемента убивает a11y. Вам необходимо предоставить хотя бы функциональность (и aria-label объяснение этого) для esc ключа для выхода из текстовой области для пользователей, не использующих мышь.