Я не буду сильно увлекаться этим, но в выходные я использовал Nokogiri для чего-то другого. Это немного затягивает. Решил взглянуть на код встраивания, пока Nokogiri ещё свеж в памяти.
Мой интерес к этому заключается в том, что я хотел бы видеть более широкое использование Discourse новостными и блог-сайтами. Если бы это произошло, я могу представить, что новые владельцы сайтов будут разочарованы текущей функциональностью встраивания. Вот одна идея по её улучшению:
Добавить два новых необязательных атрибута к модели EmbeddableHost:
target_selector: внешний CSS-селектор, содержащий контент, который должен быть встроен.
exclude_selectors: список CSS-селекторов, которые должны быть исключены из контента, выбранного с помощью target_selector.
На странице «Администрирование / Встраивание» к каждой строке хоста встраивания должна быть добавлена кнопка «Настроить». При нажатии на неё открывается страница, похожая на страницу «Электронная почта / Предварительный просмотр сводки».
Страница «Настройка хоста» будет содержать форму с полями для ввода настроек target_selector и exclude_selectors хоста, а также поле URL, позволяющее проверить переданные значения на конкретной веб-странице. Тест по сути будет просто запускать TopicEmbed.parse_html с переданными значениями target_selector и exclude_selectors, а затем отображать результаты.
Изменения в коде parse_html легко протестировать. Вот возможный подход. Обратите внимание, этот код является лишь концептуальным доказательством:
изменено в topic_embed.rb (discourse/app/models/topic_embed.rb at main · discourse/discourse · GitHub)
###########################################################################
# `target_selector` и `exclude_selectors` в идеале должны быть получены из записи `EmbeddableHost` домена
# эти конкретные настройки использовались для тестирования на boingboing.net
target_selector = 'article'
exclude_selectors = ['.article-header, .share-comments-container', '.boing-single-post-rev-content', '.next-post-list-container', '.boing-end-of-article-container-on-single-post-pages']
if defined?(target_selector) && target_selector.present?
read_doc = article_content(html, target_selector, exclude_selectors)
else
# возврат к Readability, если `target_selector` не установлен для хоста
read_doc = Readability::Document.new(html, opts)
end
###########################################################################
Для тестирования без создания нового класса, вот базовый метод article_content, добавленный в класс TopicEmbed:
def self.article_content(html, target_selector, exclude_selectors = [])
doc = Nokogiri::HTML(html)
# удаление комментариев и тегов script
doc.xpath('//comment()').each { |i| i.remove }
doc.css("script, style").each { |i| i.remove }
# получение NodeSet для target_selector
# возможно, здесь стоит сделать возврат к Readability, если возвращённый набор пуст
selected_nodes = doc.css(target_selector)
# исключение узлов
unless exclude_selectors.empty?
selected_nodes.css(*exclude_selectors).each do |node|
node.remove
end
end
# обработка размеров изображений, возможно, потребуется доработка
selected_nodes.css('img').each do |img|
img.remove_attribute('width')
img.remove_attribute('height')
end
# просто так, разрешаем iframes, если их источник разрешён
# используем `[data-sanitized="true"]`, чтобы предотвратить удаление iframes на шаге remove_empty_nodes
allowed_iframe_sources = SiteSetting.allowed_iframes.split('|')
selected_nodes.css('iframe').each do |iframe|
allowed = allowed_iframe_sources.any? do |allowed_source|
iframe['src'].start_with?(allowed_source)
end
if allowed
iframe['data-sanitized'] = 'true'
iframe['width'] = '690'
iframe['height'] = '388'
else
iframe.remove
end
end
# удаление пустых узлов 'p' и 'div'
selected_nodes.css('p', 'div').each do |node|
node.remove if node.content.strip.empty? && !node.at_css('iframe[data-sanitized="true"]')
end
# преобразование узлов в строку и возврат объекта с методом `content`
content = selected_nodes.to_s
OpenStruct.new(content: content)
end
Я почти уверен, что потребуется лишь немного поднастроить это для нескольких доменов, чтобы всё заработало правильно. Результаты, которые я получаю для BBS, пока хорошие.
Цель — создать что-то, что владельцы сайтов смогут легко понять и настроить самостоятельно. При таком подходе чем более специфичным будет target_selector, тем проще будет настроить exclude_selectors. Например, для сайта на WordPress, если в качестве target_selector выбрано .entry-content, дальнейшая настройка не потребуется. Если владельцы сайтов захотят получить больше, чем базовый HTML .entry-content, они смогут разобраться, как это сделать на странице «Настройка хоста».
Единственная реальная проблема, которую я вижу, — это хосты с очень несогласованным HTML. Этот случай можно решить, оставив Ruby Readability в качестве запасного варианта.