이 주제에 대해 너무 깊이 파고들지는 않겠지만, 주말에 다른 용도로 Nokogiri를 사용해 봤습니다. 꽤 중독성이 강하더군요. Nokogiri가 아직 생생하게 기억에 남아 있을 때 임베딩 코드도 한번 살펴볼까 싶었습니다.
제가 이 주제에 관심을 갖는 이유는, 뉴스 및 블로그 사이트에서 Discourse가 더 널리 사용되기를 바라기 때문입니다. 만약 그런 일이 일어난다면, 새로운 사이트 소유자들이 현재 임베딩 기능에 대해 좌절감을 느끼게 될 것이라고 상상할 수 있습니다. 이를 개선하기 위한 아이디어를 하나 제안합니다:
EmbeddableHost 모델에 두 가지 새로운 선택 속성을 추가합니다:
target_selector: 임베딩할 콘텐츠를 포함하는 외부 CSS 셀렉터
exclude_selectors: target_selector로 선택된 콘텐츠에서 제외할 CSS 셀렉터 목록
관리자/임베딩 페이지의 각 임베더블 호스트 행에 “구성” 버튼이 추가되어야 합니다. 이 버튼을 클릭하면 이메일/미리보기 요약 페이지와 유사한 페이지가 열립니다.
호스트 구성 페이지에는 호스트의 target_selector 및 exclude_selectors 설정을 입력하는 필드와, 제공된 값을 특정 웹 페이지에 대해 테스트할 수 있는 URL 필드가 있는 양식이 있어야 합니다. 테스트는 기본적으로 제공된 target_selector 및 exclude_selectors 값으로 TopicEmbed.parse_html을 실행한 후 결과를 표시하는 것입니다.
parse_html 코드 변경 사항은 테스트하기가 쉽습니다. 가능한 접근 방식을 아래에 제시합니다. 참고로 이 코드는 개념 증명(PoC)일 뿐입니다:
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
# 호스트에 `target_selector`가 설정되지 않은 경우 Readability로 폴백
read_doc = Readability::Document.new(html, opts)
end
###########################################################################
새로운 클래스를 생성하지 않고 테스트하기 위해, TopicEmbed 클래스에 추가된 기본적인 article_content 메서드는 다음과 같습니다:
def self.article_content(html, target_selector, exclude_selectors = [])
doc = Nokogiri::HTML(html)
# 주석과 스크립트 태그 제거
doc.xpath('//comment()').each { |i| i.remove }
doc.css("script, style").each { |i| i.remove }
# target_selector에 대한 NodeSet 가져오기
# 반환된 세트가 비어 있으면 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
# 단순히 재미로, 소스가 허용되면 iframe 허용
# remove_empty_nodes 단계에서 iframe이 제거되지 않도록 `[data-sanitized="true"]` 사용
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가 càng 구체적일수록 exclude_selectors를 구성하기가 쉬워집니다. 예를 들어, WordPress 사이트에서 .entry-content가 target_selector로 선택된다면 추가 구성은 필요하지 않습니다. 사이트 소유자가 기본적인 .entry-content html보다 더 많은 것을 얻고 싶다면, 호스트 구성 페이지에서 어떻게 해야 하는지 알아낼 수 있습니다.
제가 볼 수 있는 유일한 실질적인 문제는 HTML이 매우 일관되지 않은 호스트에 대한 것입니다. 이 경우 Ruby Readability를 폴백으로 유지하여 처리할 수 있습니다.