Самохостинг, версия 2026.1.5 (коммит 2727e4d). Мы ведём сообщество в режиме рассылки и принимаем ответы по электронной почте.
Один из участников ответил из Outlook с довольно длинным сообщением, и копия, которая появилась на сайте и была отправлена обратно в рассылку, оборвалась на полпути. Всё, начиная с первого нумерованного списка, просто исчезло. Никаких ошибок, ничего в логах — сообщение просто обрывалось посреди предложения.
Сначала меня удивило то, что маркированные списки, расположенные ранее в том же письме, прошли нормально. Обрезался только нумерованный список, а также всё, что шло после него.
Проблема в методе extract_from_word в файле lib/email/receiver.rb. Для писем из Outlook/Word он рассматривает первого дочернего элемента .WordSection1, который не является <p> или <ul>, как начало подписи или пересланного сообщения, и удаляет его вместе со всеми последующими соседними элементами:
elided =
doc.css(
“.WordSection1 > :not(p):not(ul):first-of-type, .WordSection1 > :not(p):not(ul):first-of-type ~ *”,
).remove
Outlook рендерит нумерованные списки как <ol>, а в селекторе <ol> не исключён. Таким образом, нумерованный список становится первым элементом, который не является <p> или <ul>, и приёмник обрывает сообщение именно в этом месте. Маркированные списки (<ul>) исключены, поэтому они сохраняются, а нумерованный — нет.
Воспроизведение
- В Outlook (настольная версия) ответьте на уведомление. Напишите абзац, затем нумерованный список, затем ещё один абзац.
- Созданный пост сохраняет текст до нумерованного списка и отбрасывает сам список и всё, что находится ниже него.
Минимальное HTML-тело, которое вызывает проблему (легко добавить в тест приёмника):
Вводный абзац.
Шаги:
- Первый
- Второй
Этот завершающий абзац тоже исчезает.
В результате вы получаете только два вводных абзаца.
Проблема всё ещё присутствует в ветке main — селектор не менялся. Я сначала поискал в категории багов, но не нашёл уже зарегистрированной такой проблемы.
Предлагаемое исправление
Относиться к <ol> так же, как к <ul>, поскольку нумерованный список — это содержимое, а не подпись:
“.WordSection1 > :not(p):not(ul):not(ol):first-of-type, .WordSection1 > :not(p):not(ul):not(ol):first-of-type ~ *”
Это сохранит список и при этом всё ещё будет удалять реальные маркеры подписи/пересылки (таблицы, div, hr, изображения). Готов открыть PR с тестовым письмом и падающим тестом, если это поможет.