<div contenteditable> как замена textarea

В ситуации, когда нужно, чтобы ваша textarea росла в высоту вместе с контентом, заставить её так себя вести можно только javascript'ом. Вероятно, это потребует создания скрытого блока, копирования в него содержимого textarea, высчитывания высоты и присвоения значения этой высоты вашей textarea, причём вся эта радость - при каждом нажатии клавиши. Жуть!

Но есть другой вариант: использовать вместо textarea обычный div с атрибутом contenteditable.

Сразу возникает проблема того, что div не ведёт себя, как элемент формы, и не может передавать своё содержимое вместе с остальными input'ами при submit'е, но она, к счастью, легко решаема с помощью простого скрипта:

Форма:

<form class='post_form'>
    ...
    <textarea id="post_body" name="..." style='display:none;'></textarea>
    <div id='post_body_div' contenteditable></div>
    ...
    <input name="..." type="submit" value="Сохранить" />
</form>

Скрипт: (WARNING: coffeescript syntax)

$('.post_form input[type=submit]').click (e) ->
    e.stopPropagation()
    e.preventDefault()
    form = $('.post_form')
    body = form.find('#post_body_div').text()
    ... # проверки правильности ввода
    form.find('#post_body').val(body)
    form.submit()

Следующая проблема, с которой вы столкнетесь, - тот факт, что содержимое contenteditable-div'а - это не то же самое, что содержимое textarea. Разница в том, что каждый абзац в таком div'е - это отдельный div, p или что-нибудь ещё, в зависимости от вашего браузера, а каждый пробел, если их больше 2 подряд, - это &nbsp.

Для кого-то это будет нормально, но у нас тут маркдаун используется и это настоящая проблема. Можно получать текст из div'а так, как в скрипте выше, с помощью .text(), но тогда мы лишаемся всех переводов строки, т.к. в contenteditable блоках они не вставляются при нажатии enter'а (вместо них добавляется новый div, p или что там у вас за браузер?).

Кроме того, "лишние" пробелы становятся &nbsp, что тоже не катит для маркдауна (например делает невозможным создание блока code). Это решаем простой заменой:

body = form.find('#post_body_div').text().replace(/&nbsp;/g, ' ')

И напоследок: надо не забыть вставить текст из textarea в наш div при открытии страницы, чтобы посты можно было редактировать.

Итак, делаем:

# Подменяем событие нажатия Enter
$(document).on 'page:change', ->
    $('#post_body_div').text( $('#post_body').val() ).keydown (e) ->
        if e.which == 13 # Enter
            e.preventDefault()
            if $(this).text().slice(-1) != '\n'
                $(this).append('\n')
            $(this).paste_newline()

# вставляет перевод строки в текущей позиции курсора
$.fn.paste_newline = ->
    if (window.getSelection)
        selection = window.getSelection()
        range = selection.getRangeAt(0)
        br = document.createTextNode("\n")
        range.deleteContents();
        range.insertNode(br);
        range.setStartAfter(br);
        range.setEndAfter(br);
        range.collapse(false);
        selection.removeAllRanges();
        selection.addRange(range);

...

PROFIT!!! Testing needed!