aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-01 11:35:02 +0900
committernsfisis <nsfisis@gmail.com>2026-02-01 11:37:12 +0900
commit376781b3592203de7d3ceb01b1d7a07dc5bd87a7 (patch)
tree0b859587d5f766ff805ecb440a194ce1dcc8ba79
parent6aea40c400108a2a6191fc10cbd81abda975c027 (diff)
downloadnsfisis.dev-376781b3592203de7d3ceb01b1d7a07dc5bd87a7.tar.gz
nsfisis.dev-376781b3592203de7d3ceb01b1d7a07dc5bd87a7.tar.zst
nsfisis.dev-376781b3592203de7d3ceb01b1d7a07dc5bd87a7.zip
refactor(nuldoc): DOM builder
-rw-r--r--services/nuldoc/lib/nuldoc.rb5
-rw-r--r--services/nuldoc/lib/nuldoc/components/global_footer.rb11
-rw-r--r--services/nuldoc/lib/nuldoc/components/global_headers.rb50
-rw-r--r--services/nuldoc/lib/nuldoc/components/page_layout.rb43
-rw-r--r--services/nuldoc/lib/nuldoc/components/pagination.rb61
-rw-r--r--services/nuldoc/lib/nuldoc/components/post_page_entry.rb24
-rw-r--r--services/nuldoc/lib/nuldoc/components/slide_page_entry.rb24
-rw-r--r--services/nuldoc/lib/nuldoc/components/static_script.rb21
-rw-r--r--services/nuldoc/lib/nuldoc/components/static_stylesheet.rb15
-rw-r--r--services/nuldoc/lib/nuldoc/components/table_of_contents.rb17
-rw-r--r--services/nuldoc/lib/nuldoc/components/tag_list.rb14
-rw-r--r--services/nuldoc/lib/nuldoc/dom.rb92
-rw-r--r--services/nuldoc/lib/nuldoc/dom/atom_xml.rb26
-rw-r--r--services/nuldoc/lib/nuldoc/dom/atom_xml_builder.rb16
-rw-r--r--services/nuldoc/lib/nuldoc/dom/builder.rb67
-rw-r--r--services/nuldoc/lib/nuldoc/dom/html.rb56
-rw-r--r--services/nuldoc/lib/nuldoc/dom/html_builder.rb46
-rw-r--r--services/nuldoc/lib/nuldoc/generators/about.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/atom.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/home.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/not_found.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/post.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/post_list.rb4
-rw-r--r--services/nuldoc/lib/nuldoc/generators/slide.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/slide_list.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/tag.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/generators/tag_list.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb920
-rw-r--r--services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb582
-rw-r--r--services/nuldoc/lib/nuldoc/markdown/transform.rb86
-rw-r--r--services/nuldoc/lib/nuldoc/pages/about_page.rb113
-rw-r--r--services/nuldoc/lib/nuldoc/pages/atom_page.rb39
-rw-r--r--services/nuldoc/lib/nuldoc/pages/home_page.rb53
-rw-r--r--services/nuldoc/lib/nuldoc/pages/not_found_page.rb25
-rw-r--r--services/nuldoc/lib/nuldoc/pages/post_list_page.rb39
-rw-r--r--services/nuldoc/lib/nuldoc/pages/post_page.rb63
-rw-r--r--services/nuldoc/lib/nuldoc/pages/slide_list_page.rb30
-rw-r--r--services/nuldoc/lib/nuldoc/pages/slide_page.rb98
-rw-r--r--services/nuldoc/lib/nuldoc/pages/tag_list_page.rb46
-rw-r--r--services/nuldoc/lib/nuldoc/pages/tag_page.rb42
-rw-r--r--services/nuldoc/lib/nuldoc/renderers/html.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/renderers/xml.rb2
42 files changed, 1439 insertions, 1311 deletions
diff --git a/services/nuldoc/lib/nuldoc.rb b/services/nuldoc/lib/nuldoc.rb
index 83df42d6..cb94051d 100644
--- a/services/nuldoc/lib/nuldoc.rb
+++ b/services/nuldoc/lib/nuldoc.rb
@@ -11,8 +11,9 @@ require 'webrick'
require_relative 'nuldoc/pipeline'
require_relative 'nuldoc/dom'
-require_relative 'nuldoc/dom/atom_xml'
-require_relative 'nuldoc/dom/html'
+require_relative 'nuldoc/dom/builder'
+require_relative 'nuldoc/dom/html_builder'
+require_relative 'nuldoc/dom/atom_xml_builder'
require_relative 'nuldoc/revision'
require_relative 'nuldoc/config'
require_relative 'nuldoc/page'
diff --git a/services/nuldoc/lib/nuldoc/components/global_footer.rb b/services/nuldoc/lib/nuldoc/components/global_footer.rb
index 879aac55..8440d7d8 100644
--- a/services/nuldoc/lib/nuldoc/components/global_footer.rb
+++ b/services/nuldoc/lib/nuldoc/components/global_footer.rb
@@ -1,10 +1,13 @@
module Nuldoc
module Components
- class GlobalFooter
- extend DOM::HTML
+ class GlobalFooter < DOM::HTMLBuilder
+ def initialize(config:)
+ super()
+ @config = config
+ end
- def self.render(config:)
- footer(class: 'footer') { text "&copy; #{config.site.copyright_year} #{config.site.author}" }
+ def build
+ footer(class: 'footer') { text "&copy; #{@config.site.copyright_year} #{@config.site.author}" }
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/global_headers.rb b/services/nuldoc/lib/nuldoc/components/global_headers.rb
index fb19096a..ab62a5f0 100644
--- a/services/nuldoc/lib/nuldoc/components/global_headers.rb
+++ b/services/nuldoc/lib/nuldoc/components/global_headers.rb
@@ -1,41 +1,50 @@
module Nuldoc
module Components
- class DefaultGlobalHeader
- extend DOM::HTML
+ class DefaultGlobalHeader < DOM::HTMLBuilder
+ def initialize(config:)
+ super()
+ @config = config
+ end
- def self.render(config:)
+ def build
header(class: 'header') do
div(class: 'site-logo') do
- a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ a(href: "https://#{@config.sites.default.fqdn}/") { text 'nsfisis.dev' }
end
end
end
end
- class AboutGlobalHeader
- extend DOM::HTML
+ class AboutGlobalHeader < DOM::HTMLBuilder
+ def initialize(config:)
+ super()
+ @config = config
+ end
- def self.render(config:)
+ def build
header(class: 'header') do
div(class: 'site-logo') do
- a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ a(href: "https://#{@config.sites.default.fqdn}/") { text 'nsfisis.dev' }
end
end
end
end
- class BlogGlobalHeader
- extend DOM::HTML
+ class BlogGlobalHeader < DOM::HTMLBuilder
+ def initialize(config:)
+ super()
+ @config = config
+ end
- def self.render(config:)
+ def build
header(class: 'header') do
div(class: 'site-logo') do
- a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ a(href: "https://#{@config.sites.default.fqdn}/") { text 'nsfisis.dev' }
end
- div(class: 'site-name') { text config.sites.blog.site_name }
+ div(class: 'site-name') { text @config.sites.blog.site_name }
nav(class: 'nav') do
ul do
- li { a(href: "https://#{config.sites.about.fqdn}/") { text 'About' } }
+ li { a(href: "https://#{@config.sites.about.fqdn}/") { text 'About' } }
li { a(href: '/posts/') { text 'Posts' } }
li { a(href: '/tags/') { text 'Tags' } }
end
@@ -44,17 +53,20 @@ module Nuldoc
end
end
- class SlidesGlobalHeader
- extend DOM::HTML
+ class SlidesGlobalHeader < DOM::HTMLBuilder
+ def initialize(config:)
+ super()
+ @config = config
+ end
- def self.render(config:)
+ def build
header(class: 'header') do
div(class: 'site-logo') do
- a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ a(href: "https://#{@config.sites.default.fqdn}/") { text 'nsfisis.dev' }
end
nav(class: 'nav') do
ul do
- li { a(href: "https://#{config.sites.about.fqdn}/") { text 'About' } }
+ li { a(href: "https://#{@config.sites.about.fqdn}/") { text 'About' } }
li { a(href: '/slides/') { text 'Slides' } }
li { a(href: '/tags/') { text 'Tags' } }
end
diff --git a/services/nuldoc/lib/nuldoc/components/page_layout.rb b/services/nuldoc/lib/nuldoc/components/page_layout.rb
index 4ddd0968..d1b31744 100644
--- a/services/nuldoc/lib/nuldoc/components/page_layout.rb
+++ b/services/nuldoc/lib/nuldoc/components/page_layout.rb
@@ -1,32 +1,45 @@
module Nuldoc
module Components
- class PageLayout
- extend DOM::HTML
+ class PageLayout < DOM::HTMLBuilder
+ def initialize(meta_copyright_year:, meta_description:, meta_title:, site:, config:, children:,
+ meta_keywords: nil, meta_atom_feed_href: nil)
+ super()
+ @meta_copyright_year = meta_copyright_year
+ @meta_description = meta_description
+ @meta_title = meta_title
+ @site = site
+ @config = config
+ @children = children
+ @meta_keywords = meta_keywords
+ @meta_atom_feed_href = meta_atom_feed_href
+ end
- def self.render(meta_copyright_year:, meta_description:, meta_title:, site:, config:, children:,
- meta_keywords: nil, meta_atom_feed_href: nil)
- site_entry = config.site_entry(site)
+ def build
+ site_entry = @config.site_entry(@site)
html(lang: 'ja-JP') do
head do
meta(charset: 'UTF-8')
meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0')
- meta(name: 'author', content: config.site.author)
- meta(name: 'copyright', content: "&copy; #{meta_copyright_year} #{config.site.author}")
- meta(name: 'description', content: meta_description)
- meta(name: 'keywords', content: meta_keywords.join(',')) if meta_keywords && !meta_keywords.empty?
+ meta(name: 'author', content: @config.site.author)
+ meta(name: 'copyright', content: "&copy; #{@meta_copyright_year} #{@config.site.author}")
+ meta(name: 'description', content: @meta_description)
+ if @meta_keywords && !@meta_keywords.empty?
+ meta(name: 'keywords',
+ content: @meta_keywords.join(','))
+ end
meta(property: 'og:type', content: 'article')
- meta(property: 'og:title', content: meta_title)
- meta(property: 'og:description', content: meta_description)
+ meta(property: 'og:title', content: @meta_title)
+ meta(property: 'og:description', content: @meta_description)
meta(property: 'og:site_name', content: site_entry.site_name)
meta(property: 'og:locale', content: 'ja_JP')
meta(name: 'Hatena::Bookmark', content: 'nocomment')
- link(rel: 'alternate', href: meta_atom_feed_href, type: 'application/atom+xml') if meta_atom_feed_href
+ link(rel: 'alternate', href: @meta_atom_feed_href, type: 'application/atom+xml') if @meta_atom_feed_href
link(rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml')
- title { text meta_title }
- StaticStylesheet.render(file_name: '/style.css', config: config)
+ title { text @meta_title }
+ render(StaticStylesheet, file_name: '/style.css', config: @config)
end
- child children
+ child @children
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/pagination.rb b/services/nuldoc/lib/nuldoc/components/pagination.rb
index 61978cb1..3a6a5dc6 100644
--- a/services/nuldoc/lib/nuldoc/components/pagination.rb
+++ b/services/nuldoc/lib/nuldoc/components/pagination.rb
@@ -1,37 +1,46 @@
module Nuldoc
module Components
- class Pagination
- extend DOM::HTML
-
- def self.render(current_page:, total_pages:, base_path:)
- return div if total_pages <= 1
+ class Pagination < DOM::HTMLBuilder
+ def initialize(current_page:, total_pages:, base_path:)
+ super()
+ @current_page = current_page
+ @total_pages = total_pages
+ @base_path = base_path
+ end
- pages = generate_page_numbers(current_page, total_pages)
+ def build
+ if @total_pages <= 1
+ div
+ else
+ pages = generate_page_numbers(@current_page, @total_pages)
- nav(class: 'pagination') do
- div(class: 'pagination-prev') do
- a(href: page_url_at(base_path, current_page - 1)) { text '前へ' } if current_page > 1
- end
- pages.each do |page|
- if page == '...'
- div(class: 'pagination-elipsis') { text "\u2026" }
- elsif page == current_page
- div(class: 'pagination-page pagination-page-current') do
- span { text page.to_s }
- end
- else
- div(class: 'pagination-page') do
- a(href: page_url_at(base_path, page)) { text page.to_s }
+ nav(class: 'pagination') do
+ div(class: 'pagination-prev') do
+ a(href: page_url_at(@base_path, @current_page - 1)) { text '前へ' } if @current_page > 1
+ end
+ pages.each do |page|
+ if page == '...'
+ div(class: 'pagination-elipsis') { text "\u2026" }
+ elsif page == @current_page
+ div(class: 'pagination-page pagination-page-current') do
+ span { text page.to_s }
+ end
+ else
+ div(class: 'pagination-page') do
+ a(href: page_url_at(@base_path, page)) { text page.to_s }
+ end
end
end
- end
- div(class: 'pagination-next') do
- a(href: page_url_at(base_path, current_page + 1)) { text '次へ' } if current_page < total_pages
+ div(class: 'pagination-next') do
+ a(href: page_url_at(@base_path, @current_page + 1)) { text '次へ' } if @current_page < @total_pages
+ end
end
end
end
- def self.generate_page_numbers(current_page, total_pages)
+ private
+
+ def generate_page_numbers(current_page, total_pages)
pages = Set.new
pages.add(1)
pages.add([1, current_page - 1].max)
@@ -57,11 +66,9 @@ module Nuldoc
result
end
- def self.page_url_at(base_path, page)
+ def page_url_at(base_path, page)
page == 1 ? base_path : "#{base_path}#{page}/"
end
-
- private_class_method :generate_page_numbers, :page_url_at
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/post_page_entry.rb b/services/nuldoc/lib/nuldoc/components/post_page_entry.rb
index 623c2be6..4a9ab6c6 100644
--- a/services/nuldoc/lib/nuldoc/components/post_page_entry.rb
+++ b/services/nuldoc/lib/nuldoc/components/post_page_entry.rb
@@ -1,17 +1,21 @@
module Nuldoc
module Components
- class PostPageEntry
- extend DOM::HTML
+ class PostPageEntry < DOM::HTMLBuilder
+ def initialize(post:, config:)
+ super()
+ @post = post
+ @config = config
+ end
- def self.render(post:, config:)
- published = Revision.date_to_string(GeneratorUtils.published_date(post))
- updated = Revision.date_to_string(GeneratorUtils.updated_date(post))
- has_updates = GeneratorUtils.any_updates?(post)
+ def build
+ published = Revision.date_to_string(GeneratorUtils.published_date(@post))
+ updated = Revision.date_to_string(GeneratorUtils.updated_date(@post))
+ has_updates = GeneratorUtils.any_updates?(@post)
article(class: 'post-entry') do
- a(href: post.href) do
- header(class: 'entry-header') { h2 { text post.title } }
- section(class: 'entry-content') { p { text post.description } }
+ a(href: @post.href) do
+ header(class: 'entry-header') { h2 { text @post.title } }
+ section(class: 'entry-content') { p { text @post.description } }
footer(class: 'entry-footer') do
time(datetime: published) { text published }
text ' 投稿'
@@ -20,7 +24,7 @@ module Nuldoc
time(datetime: updated) { text updated }
text ' 更新'
end
- TagList.render(tags: post.tags, config: config) if post.tags.length.positive?
+ render(TagList, tags: @post.tags, config: @config) if @post.tags.length.positive?
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb b/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb
index c78d3d23..4aabc425 100644
--- a/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb
+++ b/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb
@@ -1,17 +1,21 @@
module Nuldoc
module Components
- class SlidePageEntry
- extend DOM::HTML
+ class SlidePageEntry < DOM::HTMLBuilder
+ def initialize(slide:, config:)
+ super()
+ @slide = slide
+ @config = config
+ end
- def self.render(slide:, config:)
- published = Revision.date_to_string(GeneratorUtils.published_date(slide))
- updated = Revision.date_to_string(GeneratorUtils.updated_date(slide))
- has_updates = GeneratorUtils.any_updates?(slide)
+ def build
+ published = Revision.date_to_string(GeneratorUtils.published_date(@slide))
+ updated = Revision.date_to_string(GeneratorUtils.updated_date(@slide))
+ has_updates = GeneratorUtils.any_updates?(@slide)
article(class: 'post-entry') do
- a(href: slide.href) do
- header(class: 'entry-header') { h2 { text slide.title } }
- section(class: 'entry-content') { p { text slide.description } }
+ a(href: @slide.href) do
+ header(class: 'entry-header') { h2 { text @slide.title } }
+ section(class: 'entry-content') { p { text @slide.description } }
footer(class: 'entry-footer') do
time(datetime: published) { text published }
text ' 登壇'
@@ -20,7 +24,7 @@ module Nuldoc
time(datetime: updated) { text updated }
text ' 更新'
end
- TagList.render(tags: slide.tags, config: config) if slide.tags.length.positive?
+ render(TagList, tags: @slide.tags, config: @config) if @slide.tags.length.positive?
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/static_script.rb b/services/nuldoc/lib/nuldoc/components/static_script.rb
index 5c8af53d..fd6e1c3d 100644
--- a/services/nuldoc/lib/nuldoc/components/static_script.rb
+++ b/services/nuldoc/lib/nuldoc/components/static_script.rb
@@ -1,14 +1,21 @@
module Nuldoc
module Components
- class StaticScript
- extend DOM::HTML
+ class StaticScript < DOM::HTMLBuilder
+ def initialize(file_name:, config:, site: nil, type: nil, defer: nil)
+ super()
+ @file_name = file_name
+ @config = config
+ @site = site
+ @type = type
+ @defer = defer
+ end
- def self.render(file_name:, config:, site: nil, type: nil, defer: nil)
- file_path = File.join(Dir.pwd, config.locations.static_dir, site || '_all', file_name)
+ def build
+ file_path = File.join(Dir.pwd, @config.locations.static_dir, @site || '_all', @file_name)
hash = ComponentUtils.calculate_file_hash(file_path)
- attrs = { src: "#{file_name}?h=#{hash}" }
- attrs[:type] = type if type
- attrs[:defer] = defer if defer
+ attrs = { src: "#{@file_name}?h=#{hash}" }
+ attrs[:type] = @type if @type
+ attrs[:defer] = @defer if @defer
script(**attrs)
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb b/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb
index 5246ee1c..f0925e18 100644
--- a/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb
+++ b/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb
@@ -1,12 +1,17 @@
module Nuldoc
module Components
- class StaticStylesheet
- extend DOM::HTML
+ class StaticStylesheet < DOM::HTMLBuilder
+ def initialize(file_name:, config:, site: nil)
+ super()
+ @file_name = file_name
+ @config = config
+ @site = site
+ end
- def self.render(file_name:, config:, site: nil)
- file_path = File.join(Dir.pwd, config.locations.static_dir, site || '_all', file_name)
+ def build
+ file_path = File.join(Dir.pwd, @config.locations.static_dir, @site || '_all', @file_name)
hash = ComponentUtils.calculate_file_hash(file_path)
- link(rel: 'stylesheet', href: "#{file_name}?h=#{hash}")
+ link(rel: 'stylesheet', href: "#{@file_name}?h=#{hash}")
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/table_of_contents.rb b/services/nuldoc/lib/nuldoc/components/table_of_contents.rb
index 0be95706..499ca681 100644
--- a/services/nuldoc/lib/nuldoc/components/table_of_contents.rb
+++ b/services/nuldoc/lib/nuldoc/components/table_of_contents.rb
@@ -1,23 +1,26 @@
module Nuldoc
module Components
- class TableOfContents
- extend DOM::HTML
+ class TableOfContents < DOM::HTMLBuilder
+ def initialize(toc:)
+ super()
+ @toc = toc
+ end
- def self.render(toc:)
+ def build
nav(class: 'toc') do
h2 { text '目次' }
- ul { toc.items.each { |entry| toc_entry_component(entry) } }
+ ul { @toc.items.each { |entry| toc_entry_component(entry) } }
end
end
- def self.toc_entry_component(entry)
+ private
+
+ def toc_entry_component(entry)
li do
a(href: "##{entry.id}") { text entry.text }
ul { entry.children.each { |c| toc_entry_component(c) } } if entry.children.length.positive?
end
end
-
- private_class_method :toc_entry_component
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/tag_list.rb b/services/nuldoc/lib/nuldoc/components/tag_list.rb
index 57d11ab0..acbcaaf3 100644
--- a/services/nuldoc/lib/nuldoc/components/tag_list.rb
+++ b/services/nuldoc/lib/nuldoc/components/tag_list.rb
@@ -1,13 +1,17 @@
module Nuldoc
module Components
- class TagList
- extend DOM::HTML
+ class TagList < DOM::HTMLBuilder
+ def initialize(tags:, config:)
+ super()
+ @tags = tags
+ @config = config
+ end
- def self.render(tags:, config:)
+ def build
ul(class: 'entry-tags') do
- tags.each do |slug|
+ @tags.each do |slug|
li(class: 'tag') do
- span(class: 'tag-inner') { text config.tag_label(slug) }
+ span(class: 'tag-inner') { text @config.tag_label(slug) }
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/dom.rb b/services/nuldoc/lib/nuldoc/dom.rb
index ec802fb6..abe625fe 100644
--- a/services/nuldoc/lib/nuldoc/dom.rb
+++ b/services/nuldoc/lib/nuldoc/dom.rb
@@ -1,63 +1,19 @@
module Nuldoc
- Text = Struct.new(:content, keyword_init: true) do
+ Text = Data.define(:content) do
def kind = :text
end
- RawHTML = Struct.new(:html, keyword_init: true) do
+ RawNode = Data.define(:content) do
def kind = :raw
end
- Element = Struct.new(:name, :attributes, :children, keyword_init: true) do
+ Element = Data.define(:name, :attributes, :children) do
def kind = :element
end
module DOM
- CHILDREN_STACK_KEY = :__nuldoc_dom_children_stack
- private_constant :CHILDREN_STACK_KEY
-
module_function
- def text(content)
- node = Text.new(content: content)
- _auto_append(node)
- node
- end
-
- def raw_html(html)
- node = RawHTML.new(html: html)
- _auto_append(node)
- node
- end
-
- def child(*nodes)
- stack = Thread.current[CHILDREN_STACK_KEY]
- return unless stack && !stack.empty?
-
- nodes.each do |node|
- case node
- when nil, false
- next
- when String
- stack.last.push(Text.new(content: node))
- when Array
- node.each { |n| child(n) }
- else
- stack.last.push(node)
- end
- end
- end
-
- def elem(name, **attrs, &)
- children = _collect_children(&)
- node = Element.new(
- name: name,
- attributes: attrs.transform_keys(&:to_s),
- children: children
- )
- _auto_append(node)
- node
- end
-
def add_class(element, klass)
classes = element.attributes['class']
if classes.nil?
@@ -104,6 +60,24 @@ module Nuldoc
end
end
+ def map_children_recursively(element, &)
+ element.children.map! do |c|
+ c = yield(c)
+ map_children_recursively(c, &) if c.kind == :element
+ c
+ end
+ end
+
+ def map_element_of_type(root, element_name, &)
+ map_children_recursively(root) do |n|
+ if n.kind == :element && n.name == element_name
+ yield(n)
+ else
+ n
+ end
+ end
+ end
+
def process_text_nodes_in_element(element)
new_children = []
element.children.each do |child|
@@ -115,29 +89,5 @@ module Nuldoc
end
element.children.replace(new_children)
end
-
- private
-
- def _collect_children(&block)
- return [] unless block
-
- stack = Thread.current[CHILDREN_STACK_KEY] ||= []
- stack.push([])
- begin
- yield
- stack.last
- ensure
- stack.pop
- end
- end
-
- def _auto_append(node)
- stack = Thread.current[CHILDREN_STACK_KEY]
- return unless stack && !stack.empty?
-
- stack.last.push(node)
- end
-
- module_function :_collect_children, :_auto_append
end
end
diff --git a/services/nuldoc/lib/nuldoc/dom/atom_xml.rb b/services/nuldoc/lib/nuldoc/dom/atom_xml.rb
deleted file mode 100644
index 9ab10822..00000000
--- a/services/nuldoc/lib/nuldoc/dom/atom_xml.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module Nuldoc
- module DOM
- module AtomXML
- def self.extended(base)
- base.extend(DOM)
- end
-
- def self.included(base)
- base.include(DOM)
- end
-
- module_function
-
- def author(**attrs, &) = DOM.elem('author', **attrs, &)
- def entry(**attrs, &) = DOM.elem('entry', **attrs, &)
- def feed(**attrs, &) = DOM.elem('feed', **attrs, &)
- def id(**attrs, &) = DOM.elem('id', **attrs, &)
- def link(**attrs) = DOM.elem('link', **attrs)
- def name(**attrs, &) = DOM.elem('name', **attrs, &)
- def published(**attrs, &) = DOM.elem('published', **attrs, &)
- def summary(**attrs, &) = DOM.elem('summary', **attrs, &)
- def title(**attrs, &) = DOM.elem('title', **attrs, &)
- def updated(**attrs, &) = DOM.elem('updated', **attrs, &)
- end
- end
-end
diff --git a/services/nuldoc/lib/nuldoc/dom/atom_xml_builder.rb b/services/nuldoc/lib/nuldoc/dom/atom_xml_builder.rb
new file mode 100644
index 00000000..2d865b6e
--- /dev/null
+++ b/services/nuldoc/lib/nuldoc/dom/atom_xml_builder.rb
@@ -0,0 +1,16 @@
+module Nuldoc
+ module DOM
+ class AtomXMLBuilder < Builder
+ def author(**attrs, &) = elem('author', **attrs, &)
+ def entry(**attrs, &) = elem('entry', **attrs, &)
+ def feed(**attrs, &) = elem('feed', **attrs, &)
+ def id(**attrs, &) = elem('id', **attrs, &)
+ def link(**attrs) = elem('link', **attrs)
+ def name(**attrs, &) = elem('name', **attrs, &)
+ def published(**attrs, &) = elem('published', **attrs, &)
+ def summary(**attrs, &) = elem('summary', **attrs, &)
+ def title(**attrs, &) = elem('title', **attrs, &)
+ def updated(**attrs, &) = elem('updated', **attrs, &)
+ end
+ end
+end
diff --git a/services/nuldoc/lib/nuldoc/dom/builder.rb b/services/nuldoc/lib/nuldoc/dom/builder.rb
new file mode 100644
index 00000000..2d8751c5
--- /dev/null
+++ b/services/nuldoc/lib/nuldoc/dom/builder.rb
@@ -0,0 +1,67 @@
+module Nuldoc
+ module DOM
+ class Builder
+ def initialize
+ @stack = []
+ end
+
+ def build(&)
+ instance_eval(&)
+ end
+
+ def text(content)
+ try_append(Text.new(content: content))
+ end
+
+ def raw(content)
+ try_append(RawNode.new(content: content))
+ end
+
+ def render(component, **)
+ try_append(component.new(**).build)
+ end
+
+ def child(*nodes)
+ return if @stack.empty?
+
+ nodes.each do |node|
+ case node
+ when nil, false
+ next
+ when String
+ try_append(Text.new(content: node))
+ when Array
+ node.each { |n| child(n) }
+ else
+ try_append(node)
+ end
+ end
+ end
+
+ def elem(name, **attrs, &)
+ try_append(
+ Element.new(
+ name: name,
+ attributes: attrs.transform_keys(&:to_s),
+ children: collect_children(&)
+ )
+ )
+ end
+
+ private
+
+ def collect_children
+ return [] unless block_given?
+
+ @stack.push([])
+ yield
+ @stack.pop
+ end
+
+ def try_append(node)
+ @stack.last.push(node) unless @stack.empty?
+ node
+ end
+ end
+ end
+end
diff --git a/services/nuldoc/lib/nuldoc/dom/html.rb b/services/nuldoc/lib/nuldoc/dom/html.rb
deleted file mode 100644
index 1d9b1cab..00000000
--- a/services/nuldoc/lib/nuldoc/dom/html.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module Nuldoc
- module DOM
- module HTML
- def self.extended(base)
- base.extend(DOM)
- end
-
- def self.included(base)
- base.include(DOM)
- end
-
- module_function
-
- def a(**attrs, &) = DOM.elem('a', **attrs, &)
- def article(**attrs, &) = DOM.elem('article', **attrs, &)
- def blockquote(**attrs, &) = DOM.elem('blockquote', **attrs, &)
- def body(**attrs, &) = DOM.elem('body', **attrs, &)
- def button(**attrs, &) = DOM.elem('button', **attrs, &)
- def canvas(**attrs, &) = DOM.elem('canvas', **attrs, &)
- def code(**attrs, &) = DOM.elem('code', **attrs, &)
- def del(**attrs, &) = DOM.elem('del', **attrs, &)
- def div(**attrs, &) = DOM.elem('div', **attrs, &)
- def em(**attrs, &) = DOM.elem('em', **attrs, &)
- def footer(**attrs, &) = DOM.elem('footer', **attrs, &)
- def h1(**attrs, &) = DOM.elem('h1', **attrs, &)
- def h2(**attrs, &) = DOM.elem('h2', **attrs, &)
- def h3(**attrs, &) = DOM.elem('h3', **attrs, &)
- def h4(**attrs, &) = DOM.elem('h4', **attrs, &)
- def h5(**attrs, &) = DOM.elem('h5', **attrs, &)
- def h6(**attrs, &) = DOM.elem('h6', **attrs, &)
- def head(**attrs, &) = DOM.elem('head', **attrs, &)
- def header(**attrs, &) = DOM.elem('header', **attrs, &)
- def hr(**attrs) = DOM.elem('hr', **attrs)
- def html(**attrs, &) = DOM.elem('html', **attrs, &)
- def img(**attrs) = DOM.elem('img', **attrs)
- def li(**attrs, &) = DOM.elem('li', **attrs, &)
- def link(**attrs) = DOM.elem('link', **attrs)
- def main(**attrs, &) = DOM.elem('main', **attrs, &)
- def meta(**attrs) = DOM.elem('meta', **attrs)
- def nav(**attrs, &) = DOM.elem('nav', **attrs, &)
- def ol(**attrs, &) = DOM.elem('ol', **attrs, &)
- def p(**attrs, &) = DOM.elem('p', **attrs, &)
- def script(**attrs, &) = DOM.elem('script', **attrs, &)
- def section(**attrs, &) = DOM.elem('section', **attrs, &)
- def span(**attrs, &) = DOM.elem('span', **attrs, &)
- def strong(**attrs, &) = DOM.elem('strong', **attrs, &)
- def table(**attrs, &) = DOM.elem('table', **attrs, &)
- def tbody(**attrs, &) = DOM.elem('tbody', **attrs, &)
- def thead(**attrs, &) = DOM.elem('thead', **attrs, &)
- def time(**attrs, &) = DOM.elem('time', **attrs, &)
- def title(**attrs, &) = DOM.elem('title', **attrs, &)
- def tr(**attrs, &) = DOM.elem('tr', **attrs, &)
- def ul(**attrs, &) = DOM.elem('ul', **attrs, &)
- end
- end
-end
diff --git a/services/nuldoc/lib/nuldoc/dom/html_builder.rb b/services/nuldoc/lib/nuldoc/dom/html_builder.rb
new file mode 100644
index 00000000..2a506640
--- /dev/null
+++ b/services/nuldoc/lib/nuldoc/dom/html_builder.rb
@@ -0,0 +1,46 @@
+module Nuldoc
+ module DOM
+ class HTMLBuilder < Builder
+ def a(**attrs, &) = elem('a', **attrs, &)
+ def article(**attrs, &) = elem('article', **attrs, &)
+ def blockquote(**attrs, &) = elem('blockquote', **attrs, &)
+ def body(**attrs, &) = elem('body', **attrs, &)
+ def button(**attrs, &) = elem('button', **attrs, &)
+ def canvas(**attrs, &) = elem('canvas', **attrs, &)
+ def code(**attrs, &) = elem('code', **attrs, &)
+ def del(**attrs, &) = elem('del', **attrs, &)
+ def div(**attrs, &) = elem('div', **attrs, &)
+ def em(**attrs, &) = elem('em', **attrs, &)
+ def footer(**attrs, &) = elem('footer', **attrs, &)
+ def h1(**attrs, &) = elem('h1', **attrs, &)
+ def h2(**attrs, &) = elem('h2', **attrs, &)
+ def h3(**attrs, &) = elem('h3', **attrs, &)
+ def h4(**attrs, &) = elem('h4', **attrs, &)
+ def h5(**attrs, &) = elem('h5', **attrs, &)
+ def h6(**attrs, &) = elem('h6', **attrs, &)
+ def head(**attrs, &) = elem('head', **attrs, &)
+ def header(**attrs, &) = elem('header', **attrs, &)
+ def hr(**attrs) = elem('hr', **attrs)
+ def html(**attrs, &) = elem('html', **attrs, &)
+ def img(**attrs) = elem('img', **attrs)
+ def li(**attrs, &) = elem('li', **attrs, &)
+ def link(**attrs) = elem('link', **attrs)
+ def main(**attrs, &) = elem('main', **attrs, &)
+ def meta(**attrs) = elem('meta', **attrs)
+ def nav(**attrs, &) = elem('nav', **attrs, &)
+ def ol(**attrs, &) = elem('ol', **attrs, &)
+ def p(**attrs, &) = elem('p', **attrs, &)
+ def script(**attrs, &) = elem('script', **attrs, &)
+ def section(**attrs, &) = elem('section', **attrs, &)
+ def span(**attrs, &) = elem('span', **attrs, &)
+ def strong(**attrs, &) = elem('strong', **attrs, &)
+ def table(**attrs, &) = elem('table', **attrs, &)
+ def tbody(**attrs, &) = elem('tbody', **attrs, &)
+ def thead(**attrs, &) = elem('thead', **attrs, &)
+ def time(**attrs, &) = elem('time', **attrs, &)
+ def title(**attrs, &) = elem('title', **attrs, &)
+ def tr(**attrs, &) = elem('tr', **attrs, &)
+ def ul(**attrs, &) = elem('ul', **attrs, &)
+ end
+ end
+end
diff --git a/services/nuldoc/lib/nuldoc/generators/about.rb b/services/nuldoc/lib/nuldoc/generators/about.rb
index e64f0d1d..d5b9dec4 100644
--- a/services/nuldoc/lib/nuldoc/generators/about.rb
+++ b/services/nuldoc/lib/nuldoc/generators/about.rb
@@ -7,7 +7,7 @@ module Nuldoc
end
def generate
- html = Pages::AboutPage.render(slides: @slides, config: @config)
+ html = Pages::AboutPage.new(slides: @slides, config: @config).render
Page.new(
root: html,
diff --git a/services/nuldoc/lib/nuldoc/generators/atom.rb b/services/nuldoc/lib/nuldoc/generators/atom.rb
index 74750eb7..4b24bb2f 100644
--- a/services/nuldoc/lib/nuldoc/generators/atom.rb
+++ b/services/nuldoc/lib/nuldoc/generators/atom.rb
@@ -46,7 +46,7 @@ module Nuldoc
)
Page.new(
- root: Pages::AtomPage.render(feed: feed),
+ root: Pages::AtomPage.new(feed: feed).render,
renderer: :xml,
site: @site,
dest_file_path: feed_path,
diff --git a/services/nuldoc/lib/nuldoc/generators/home.rb b/services/nuldoc/lib/nuldoc/generators/home.rb
index 54f8753f..1e9b36a3 100644
--- a/services/nuldoc/lib/nuldoc/generators/home.rb
+++ b/services/nuldoc/lib/nuldoc/generators/home.rb
@@ -6,7 +6,7 @@ module Nuldoc
end
def generate
- html = Pages::HomePage.render(config: @config)
+ html = Pages::HomePage.new(config: @config).render
Page.new(
root: html,
diff --git a/services/nuldoc/lib/nuldoc/generators/not_found.rb b/services/nuldoc/lib/nuldoc/generators/not_found.rb
index cffe1df8..bd139d72 100644
--- a/services/nuldoc/lib/nuldoc/generators/not_found.rb
+++ b/services/nuldoc/lib/nuldoc/generators/not_found.rb
@@ -7,7 +7,7 @@ module Nuldoc
end
def generate
- html = Pages::NotFoundPage.render(site: @site, config: @config)
+ html = Pages::NotFoundPage.new(site: @site, config: @config).render
Page.new(
root: html,
diff --git a/services/nuldoc/lib/nuldoc/generators/post.rb b/services/nuldoc/lib/nuldoc/generators/post.rb
index 0d5a3afc..ca46a460 100644
--- a/services/nuldoc/lib/nuldoc/generators/post.rb
+++ b/services/nuldoc/lib/nuldoc/generators/post.rb
@@ -28,7 +28,7 @@ module Nuldoc
end
def generate
- html = Pages::PostPage.render(doc: @doc, config: @config)
+ html = Pages::PostPage.new(doc: @doc, config: @config).render
content_dir = File.join(Dir.pwd, @config.locations.content_dir)
dest_file_path = File.join(
diff --git a/services/nuldoc/lib/nuldoc/generators/post_list.rb b/services/nuldoc/lib/nuldoc/generators/post_list.rb
index 680a0c32..344e9b01 100644
--- a/services/nuldoc/lib/nuldoc/generators/post_list.rb
+++ b/services/nuldoc/lib/nuldoc/generators/post_list.rb
@@ -15,12 +15,12 @@ module Nuldoc
page_posts = @posts[page_index * posts_per_page, posts_per_page]
current_page = page_index + 1
- html = Pages::PostListPage.render(
+ html = Pages::PostListPage.new(
posts: page_posts,
config: @config,
current_page: current_page,
total_pages: total_pages
- )
+ ).render
dest_file_path = current_page == 1 ? '/posts/index.html' : "/posts/#{current_page}/index.html"
href = current_page == 1 ? '/posts/' : "/posts/#{current_page}/"
diff --git a/services/nuldoc/lib/nuldoc/generators/slide.rb b/services/nuldoc/lib/nuldoc/generators/slide.rb
index 58fde56e..334fd55e 100644
--- a/services/nuldoc/lib/nuldoc/generators/slide.rb
+++ b/services/nuldoc/lib/nuldoc/generators/slide.rb
@@ -11,7 +11,7 @@ module Nuldoc
end
def generate
- html = Pages::SlidePage.render(slide: @slide, config: @config)
+ html = Pages::SlidePage.new(slide: @slide, config: @config).render
content_dir = File.join(Dir.pwd, @config.locations.content_dir)
dest_file_path = File.join(
diff --git a/services/nuldoc/lib/nuldoc/generators/slide_list.rb b/services/nuldoc/lib/nuldoc/generators/slide_list.rb
index 8d23e4b4..c3a42e27 100644
--- a/services/nuldoc/lib/nuldoc/generators/slide_list.rb
+++ b/services/nuldoc/lib/nuldoc/generators/slide_list.rb
@@ -7,7 +7,7 @@ module Nuldoc
end
def generate
- html = Pages::SlideListPage.render(slides: @slides, config: @config)
+ html = Pages::SlideListPage.new(slides: @slides, config: @config).render
Page.new(
root: html,
diff --git a/services/nuldoc/lib/nuldoc/generators/tag.rb b/services/nuldoc/lib/nuldoc/generators/tag.rb
index 7a5f7a7b..f114c0b5 100644
--- a/services/nuldoc/lib/nuldoc/generators/tag.rb
+++ b/services/nuldoc/lib/nuldoc/generators/tag.rb
@@ -12,7 +12,7 @@ module Nuldoc
end
def generate
- html = Pages::TagPage.render(tag_slug: @tag_slug, pages: @pages, site: @site, config: @config)
+ html = Pages::TagPage.new(tag_slug: @tag_slug, pages: @pages, site: @site, config: @config).render
TagPageData.new(
root: html,
diff --git a/services/nuldoc/lib/nuldoc/generators/tag_list.rb b/services/nuldoc/lib/nuldoc/generators/tag_list.rb
index 089b6f0c..106687c8 100644
--- a/services/nuldoc/lib/nuldoc/generators/tag_list.rb
+++ b/services/nuldoc/lib/nuldoc/generators/tag_list.rb
@@ -8,7 +8,7 @@ module Nuldoc
end
def generate
- html = Pages::TagListPage.render(tags: @tags, site: @site, config: @config)
+ html = Pages::TagListPage.new(tags: @tags, site: @site, config: @config).render
Page.new(
root: html,
diff --git a/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb b/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb
index 583d7201..b914882f 100644
--- a/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb
+++ b/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb
@@ -1,607 +1,607 @@
module Nuldoc
module Parser
- class BlockParser
- extend DOM::HTML
-
+ class BlockParser < DOM::HTMLBuilder
HeaderBlock = Struct.new(:level, :id, :attributes, :heading_element, keyword_init: true)
FootnoteBlock = Struct.new(:id, :children, keyword_init: true)
- class << self
- def parse(text)
- scanner = LineScanner.new(text)
- blocks = parse_blocks(scanner)
- build_document(blocks)
- end
+ def self.parse(text)
+ new.parse(text)
+ end
- private
+ def parse(text)
+ scanner = LineScanner.new(text)
+ blocks = parse_blocks(scanner)
+ build_document(blocks)
+ end
- # --- Block parsing ---
+ private
- def parse_blocks(scanner)
- blocks = []
- until scanner.eof?
- block = parse_block(scanner)
- blocks << block if block
- end
- blocks
- end
+ # --- Block parsing ---
- def parse_block(scanner)
- return nil if scanner.eof?
+ def parse_blocks(scanner)
+ blocks = []
+ until scanner.eof?
+ block = parse_block(scanner)
+ blocks << block if block
+ end
+ blocks
+ end
- line = scanner.peek
+ def parse_block(scanner)
+ return nil if scanner.eof?
- # 1. Blank line
- if line.strip.empty?
- scanner.advance
- return nil
- end
+ line = scanner.peek
- # 2. HTML comment
- if (result = try_html_comment(scanner))
- return result
- end
+ # 1. Blank line
+ if line.strip.empty?
+ scanner.advance
+ return nil
+ end
- # 3. Fenced code block
- if (result = try_fenced_code(scanner))
- return result
- end
+ # 2. HTML comment
+ if (result = try_html_comment(scanner))
+ return result
+ end
- # 4. Note/Edit block
- if (result = try_note_block(scanner))
- return result
- end
+ # 3. Fenced code block
+ if (result = try_fenced_code(scanner))
+ return result
+ end
- # 5. Heading
- if (result = try_heading(scanner))
- return result
- end
+ # 4. Note/Edit block
+ if (result = try_note_block(scanner))
+ return result
+ end
- # 6. Horizontal rule
- if (result = try_hr(scanner))
- return result
- end
+ # 5. Heading
+ if (result = try_heading(scanner))
+ return result
+ end
- # 7. Footnote definition
- if (result = try_footnote_def(scanner))
- return result
- end
+ # 6. Horizontal rule
+ if (result = try_hr(scanner))
+ return result
+ end
- # 8. Table
- if (result = try_table(scanner))
- return result
- end
+ # 7. Footnote definition
+ if (result = try_footnote_def(scanner))
+ return result
+ end
- # 9. Blockquote
- if (result = try_blockquote(scanner))
- return result
- end
+ # 8. Table
+ if (result = try_table(scanner))
+ return result
+ end
- # 10. Ordered list
- if (result = try_ordered_list(scanner))
- return result
- end
+ # 9. Blockquote
+ if (result = try_blockquote(scanner))
+ return result
+ end
- # 11. Unordered list
- if (result = try_unordered_list(scanner))
- return result
- end
+ # 10. Ordered list
+ if (result = try_ordered_list(scanner))
+ return result
+ end
- # 12. HTML block
- if (result = try_html_block(scanner))
- return result
- end
+ # 11. Unordered list
+ if (result = try_unordered_list(scanner))
+ return result
+ end
- # 13. Paragraph
- parse_paragraph(scanner)
+ # 12. HTML block
+ if (result = try_html_block(scanner))
+ return result
end
- def try_html_comment(scanner)
- line = scanner.peek
- return nil unless line.strip.start_with?('<!--')
+ # 13. Paragraph
+ parse_paragraph(scanner)
+ end
- content = +''
- until scanner.eof?
- l = scanner.advance
- content << l << "\n"
- break if l.include?('-->')
- end
- nil # skip comments
+ def try_html_comment(scanner)
+ line = scanner.peek
+ return nil unless line.strip.start_with?('<!--')
+
+ content = +''
+ until scanner.eof?
+ l = scanner.advance
+ content << l << "\n"
+ break if l.include?('-->')
end
+ nil # skip comments
+ end
- def try_fenced_code(scanner)
- match = scanner.match(/^```(\S*)(.*)$/)
- return nil unless match
+ def try_fenced_code(scanner)
+ match = scanner.match(/^```(\S*)(.*)$/)
+ return nil unless match
- scanner.advance
- language = match[1].empty? ? nil : match[1]
- meta_string = match[2].strip
+ scanner.advance
+ language = match[1].empty? ? nil : match[1]
+ meta_string = match[2].strip
- attrs = {}
- attrs[:language] = language if language
+ attrs = {}
+ attrs[:language] = language if language
- if meta_string && !meta_string.empty?
- filename_match = meta_string.match(/filename="([^"]+)"/)
- attrs[:filename] = filename_match[1] if filename_match
- attrs[:numbered] = 'true' if meta_string.include?('numbered')
- end
+ if meta_string && !meta_string.empty?
+ filename_match = meta_string.match(/filename="([^"]+)"/)
+ attrs[:filename] = filename_match[1] if filename_match
+ attrs[:numbered] = 'true' if meta_string.include?('numbered')
+ end
- code_lines = []
- until scanner.eof?
- l = scanner.peek
- if l.start_with?('```')
- scanner.advance
- break
- end
- code_lines << scanner.advance
+ code_lines = []
+ until scanner.eof?
+ l = scanner.peek
+ if l.start_with?('```')
+ scanner.advance
+ break
end
-
- code = code_lines.join("\n")
- elem('codeblock', **attrs) { text code }
+ code_lines << scanner.advance
end
- def try_note_block(scanner)
- match = scanner.match(/^:::(note|edit)(.*)$/)
- return nil unless match
-
- scanner.advance
- block_type = match[1]
- attr_string = match[2].strip
+ code = code_lines.join("\n")
+ elem('codeblock', **attrs) { text code }
+ end
- attrs = {}
- if block_type == 'edit'
- # Parse {editat="..." operation="..."}
- editat_match = attr_string.match(/editat="([^"]+)"/)
- operation_match = attr_string.match(/operation="([^"]+)"/)
- attrs[:editat] = editat_match[1] if editat_match
- attrs[:operation] = operation_match[1] if operation_match
- end
+ def try_note_block(scanner)
+ match = scanner.match(/^:::(note|edit)(.*)$/)
+ return nil unless match
- # Collect content until :::
- content_lines = []
- until scanner.eof?
- l = scanner.peek
- if l.strip == ':::'
- scanner.advance
- break
- end
- content_lines << scanner.advance
- end
+ scanner.advance
+ block_type = match[1]
+ attr_string = match[2].strip
- inner_text = content_lines.join("\n")
- inner_scanner = LineScanner.new(inner_text)
- children = parse_blocks(inner_scanner)
+ attrs = {}
+ if block_type == 'edit'
+ # Parse {editat="..." operation="..."}
+ editat_match = attr_string.match(/editat="([^"]+)"/)
+ operation_match = attr_string.match(/operation="([^"]+)"/)
+ attrs[:editat] = editat_match[1] if editat_match
+ attrs[:operation] = operation_match[1] if operation_match
+ end
- # Convert children - they are block elements already
- child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) }
- elem('note', **attrs) { child(*child_elements) }
+ # Collect content until :::
+ content_lines = []
+ until scanner.eof?
+ l = scanner.peek
+ if l.strip == ':::'
+ scanner.advance
+ break
+ end
+ content_lines << scanner.advance
end
- def try_heading(scanner)
- match = scanner.match(/^(\#{1,5})\s+(.+)$/)
- return nil unless match
+ inner_text = content_lines.join("\n")
+ inner_scanner = LineScanner.new(inner_text)
+ children = parse_blocks(inner_scanner)
- scanner.advance
- level = match[1].length
- raw_text = match[2]
+ # Convert children - they are block elements already
+ child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawNode) }
+ elem('note', **attrs) { child(*child_elements) }
+ end
- text_before, id, attributes = Attributes.parse_trailing_attributes(raw_text)
+ def try_heading(scanner)
+ match = scanner.match(/^(\#{1,5})\s+(.+)$/)
+ return nil unless match
- inline_nodes = InlineParser.parse(text_before.strip)
- heading_element = elem('h') { child(*inline_nodes) }
+ scanner.advance
+ level = match[1].length
+ raw_text = match[2]
- HeaderBlock.new(level: level, id: id, attributes: attributes, heading_element: heading_element)
- end
+ text_before, id, attributes = Attributes.parse_trailing_attributes(raw_text)
- def try_hr(scanner)
- match = scanner.match(/^---+\s*$/)
- return nil unless match
+ inline_nodes = InlineParser.parse(text_before.strip)
+ heading_element = elem('h') { child(*inline_nodes) }
- scanner.advance
- hr
- end
+ HeaderBlock.new(level: level, id: id, attributes: attributes, heading_element: heading_element)
+ end
- def try_footnote_def(scanner)
- match = scanner.match(/^\[\^([^\]]+)\]:\s*(.*)$/)
- return nil unless match
+ def try_hr(scanner)
+ match = scanner.match(/^---+\s*$/)
+ return nil unless match
- scanner.advance
- id = match[1]
- first_line = match[2]
+ scanner.advance
+ hr
+ end
- content_lines = [first_line]
- # Continuation lines: 4-space indent
- until scanner.eof?
- l = scanner.peek
- break unless l.start_with?(' ')
+ def try_footnote_def(scanner)
+ match = scanner.match(/^\[\^([^\]]+)\]:\s*(.*)$/)
+ return nil unless match
- content_lines << scanner.advance[4..]
+ scanner.advance
+ id = match[1]
+ first_line = match[2]
- end
+ content_lines = [first_line]
+ # Continuation lines: 4-space indent
+ until scanner.eof?
+ l = scanner.peek
+ break unless l.start_with?(' ')
- inner_text = content_lines.join("\n").strip
- inner_scanner = LineScanner.new(inner_text)
- children = parse_blocks(inner_scanner)
+ content_lines << scanner.advance[4..]
- child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) }
- FootnoteBlock.new(id: id, children: child_elements)
end
- def try_table(scanner)
- # Check if this looks like a table
- return nil unless scanner.peek.start_with?('|')
+ inner_text = content_lines.join("\n").strip
+ inner_scanner = LineScanner.new(inner_text)
+ children = parse_blocks(inner_scanner)
- # Quick lookahead: second line must be a separator
- return nil if scanner.pos + 1 >= scanner.lines.length
- return nil unless scanner.lines[scanner.pos + 1].match?(/^\|[\s:|-]+\|$/)
+ child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawNode) }
+ FootnoteBlock.new(id: id, children: child_elements)
+ end
- # Collect table lines
- lines = []
- while !scanner.eof? && scanner.peek.start_with?('|')
- lines << scanner.peek
- scanner.advance
- end
+ def try_table(scanner)
+ # Check if this looks like a table
+ return nil unless scanner.peek.start_with?('|')
- header_line = lines[0]
- separator_line = lines[1]
- body_lines = lines[2..] || []
+ # Quick lookahead: second line must be a separator
+ return nil if scanner.pos + 1 >= scanner.lines.length
+ return nil unless scanner.lines[scanner.pos + 1].match?(/^\|[\s:|-]+\|$/)
- alignment = parse_table_alignment(separator_line)
- header_cells = parse_table_row_cells(header_line)
- header_row = build_table_row(header_cells, true, alignment)
+ # Collect table lines
+ lines = []
+ while !scanner.eof? && scanner.peek.start_with?('|')
+ lines << scanner.peek
+ scanner.advance
+ end
- body_rows = body_lines.map do |bl|
- cells = parse_table_row_cells(bl)
- build_table_row(cells, false, alignment)
- end
+ header_line = lines[0]
+ separator_line = lines[1]
+ body_lines = lines[2..] || []
- table do
- thead { child header_row }
- tbody { child(*body_rows) } unless body_rows.empty?
- end
- end
+ alignment = parse_table_alignment(separator_line)
+ header_cells = parse_table_row_cells(header_line)
+ header_row = build_table_row(header_cells, true, alignment)
- def parse_table_alignment(separator_line)
- cells = separator_line.split('|').map(&:strip).reject(&:empty?)
- cells.map do |cell|
- left = cell.start_with?(':')
- right = cell.end_with?(':')
- if left && right
- 'center'
- elsif right
- 'right'
- elsif left
- 'left'
- end
- end
+ body_rows = body_lines.map do |bl|
+ cells = parse_table_row_cells(bl)
+ build_table_row(cells, false, alignment)
end
- def parse_table_row_cells(line)
- # Strip leading and trailing |, then split by |
- stripped = line.strip
- stripped = stripped[1..] if stripped.start_with?('|')
- stripped = stripped[0...-1] if stripped.end_with?('|')
- stripped.split('|').map(&:strip)
+ table do
+ thead { child header_row }
+ tbody { child(*body_rows) } unless body_rows.empty?
end
+ end
- def build_table_row(cells, is_header, alignment)
- cell_elements = cells.each_with_index.map do |cell_text, i|
- attrs = {}
- align = alignment[i]
- attrs[:align] = align if align && align != 'default'
-
- tag = is_header ? 'th' : 'td'
- inline_nodes = InlineParser.parse(cell_text)
- elem(tag, **attrs) { child(*inline_nodes) }
+ def parse_table_alignment(separator_line)
+ cells = separator_line.split('|').map(&:strip).reject(&:empty?)
+ cells.map do |cell|
+ left = cell.start_with?(':')
+ right = cell.end_with?(':')
+ if left && right
+ 'center'
+ elsif right
+ 'right'
+ elsif left
+ 'left'
end
- tr { child(*cell_elements) }
end
+ end
- def try_blockquote(scanner)
- return nil unless scanner.peek.start_with?('> ') || scanner.peek == '>'
+ def parse_table_row_cells(line)
+ # Strip leading and trailing |, then split by |
+ stripped = line.strip
+ stripped = stripped[1..] if stripped.start_with?('|')
+ stripped = stripped[0...-1] if stripped.end_with?('|')
+ stripped.split('|').map(&:strip)
+ end
- lines = []
- while !scanner.eof? && (scanner.peek.start_with?('> ') || scanner.peek == '>')
- line = scanner.advance
- lines << (line == '>' ? '' : line[2..])
- end
+ def build_table_row(cells, is_header, alignment)
+ cell_elements = cells.each_with_index.map do |cell_text, i|
+ attrs = {}
+ align = alignment[i]
+ attrs[:align] = align if align && align != 'default'
- inner_text = lines.join("\n")
- inner_scanner = LineScanner.new(inner_text)
- children = parse_blocks(inner_scanner)
- child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) }
+ tag = is_header ? 'th' : 'td'
+ inline_nodes = InlineParser.parse(cell_text)
+ elem(tag, **attrs) { child(*inline_nodes) }
+ end
+ tr { child(*cell_elements) }
+ end
+
+ def try_blockquote(scanner)
+ return nil unless scanner.peek.start_with?('> ') || scanner.peek == '>'
- blockquote { child(*child_elements) }
+ lines = []
+ while !scanner.eof? && (scanner.peek.start_with?('> ') || scanner.peek == '>')
+ line = scanner.advance
+ lines << (line == '>' ? '' : line[2..])
end
- def try_ordered_list(scanner)
- match = scanner.match(/^(\d+)\.\s+(.*)$/)
- return nil unless match
+ inner_text = lines.join("\n")
+ inner_scanner = LineScanner.new(inner_text)
+ children = parse_blocks(inner_scanner)
+ child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawNode) }
- items = parse_list_items(scanner, :ordered)
- return nil if items.empty?
+ blockquote { child(*child_elements) }
+ end
- build_list(:ordered, items)
- end
+ def try_ordered_list(scanner)
+ match = scanner.match(/^(\d+)\.\s+(.*)$/)
+ return nil unless match
- def try_unordered_list(scanner)
- match = scanner.match(/^\*\s+(.*)$/)
- return nil unless match
+ items = parse_list_items(scanner, :ordered)
+ return nil if items.empty?
- items = parse_list_items(scanner, :unordered)
- return nil if items.empty?
+ build_list(:ordered, items)
+ end
- build_list(:unordered, items)
- end
+ def try_unordered_list(scanner)
+ match = scanner.match(/^\*\s+(.*)$/)
+ return nil unless match
- def parse_list_items(scanner, type)
- items = []
- marker_re = type == :ordered ? /^(\d+)\.\s+(.*)$/ : /^\*\s+(.*)$/
- indent_size = 4
+ items = parse_list_items(scanner, :unordered)
+ return nil if items.empty?
- while !scanner.eof? && (m = scanner.match(marker_re))
- scanner.advance
- first_line = type == :ordered ? m[2] : m[1]
+ build_list(:unordered, items)
+ end
- content_lines = [first_line]
- has_blank = false
+ def parse_list_items(scanner, type)
+ items = []
+ marker_re = type == :ordered ? /^(\d+)\.\s+(.*)$/ : /^\*\s+(.*)$/
+ indent_size = 4
- # Collect continuation lines and sub-items
- until scanner.eof?
- l = scanner.peek
+ while !scanner.eof? && (m = scanner.match(marker_re))
+ scanner.advance
+ first_line = type == :ordered ? m[2] : m[1]
- # Blank line might be part of loose list
- if l.strip.empty?
- # Check if next non-blank line is still part of the list
- next_pos = scanner.pos + 1
- next_pos += 1 while next_pos < scanner.lines.length && scanner.lines[next_pos].strip.empty?
+ content_lines = [first_line]
+ has_blank = false
- if next_pos < scanner.lines.length
- next_line = scanner.lines[next_pos]
- if next_line.start_with?(' ' * indent_size) || next_line.match?(marker_re)
- has_blank = true
- content_lines << ''
- scanner.advance
- next
- end
- end
- break
- end
+ # Collect continuation lines and sub-items
+ until scanner.eof?
+ l = scanner.peek
+
+ # Blank line might be part of loose list
+ if l.strip.empty?
+ # Check if next non-blank line is still part of the list
+ next_pos = scanner.pos + 1
+ next_pos += 1 while next_pos < scanner.lines.length && scanner.lines[next_pos].strip.empty?
- # Indented continuation (sub-items or content)
- if l.start_with?(' ' * indent_size)
- content_lines << scanner.advance[indent_size..]
- next
+ if next_pos < scanner.lines.length
+ next_line = scanner.lines[next_pos]
+ if next_line.start_with?(' ' * indent_size) || next_line.match?(marker_re)
+ has_blank = true
+ content_lines << ''
+ scanner.advance
+ next
+ end
end
+ break
+ end
- # New list item at same level
- break if l.match?(marker_re)
+ # Indented continuation (sub-items or content)
+ if l.start_with?(' ' * indent_size)
+ content_lines << scanner.advance[indent_size..]
+ next
+ end
- # Non-indented, non-marker line - might be continuation of tight paragraph
- break if l.strip.empty?
- break if l.match?(/^[#>*\d]/) && !l.match?(/^\d+\.\s/) # another block element
+ # New list item at same level
+ break if l.match?(marker_re)
- # Paragraph continuation
- content_lines << scanner.advance
- end
+ # Non-indented, non-marker line - might be continuation of tight paragraph
+ break if l.strip.empty?
+ break if l.match?(/^[#>*\d]/) && !l.match?(/^\d+\.\s/) # another block element
- items << { lines: content_lines, has_blank: has_blank }
+ # Paragraph continuation
+ content_lines << scanner.advance
end
- items
+ items << { lines: content_lines, has_blank: has_blank }
end
- def build_list(type, items)
- # Determine tight/loose
- is_tight = items.none? { |item| item[:has_blank] }
+ items
+ end
- attrs = {}
- attrs[:__tight] = is_tight ? 'true' : 'false'
+ def build_list(type, items)
+ # Determine tight/loose
+ is_tight = items.none? { |item| item[:has_blank] }
- # Check for task list items
- is_task_list = false
- if type == :unordered
- is_task_list = items.any? { |item| item[:lines].first&.match?(/^\[[ xX]\]\s/) }
- attrs[:type] = 'task' if is_task_list
- end
+ attrs = {}
+ attrs[:__tight] = is_tight ? 'true' : 'false'
- list_items = items.map do |item|
- build_list_item(item, is_task_list)
- end
-
- if type == :ordered
- ol(**attrs) { child(*list_items) }
- else
- ul(**attrs) { child(*list_items) }
- end
+ # Check for task list items
+ is_task_list = false
+ if type == :unordered
+ is_task_list = items.any? { |item| item[:lines].first&.match?(/^\[[ xX]\]\s/) }
+ attrs[:type] = 'task' if is_task_list
end
- def build_list_item(item, is_task_list)
- attrs = {}
- content = item[:lines].join("\n")
+ list_items = items.map do |item|
+ build_list_item(item, is_task_list)
+ end
- if is_task_list
- task_match = content.match(/^\[( |[xX])\]\s(.*)$/m)
- if task_match
- attrs[:checked] = task_match[1] == ' ' ? 'false' : 'true'
- content = task_match[2]
- end
- end
+ if type == :ordered
+ ol(**attrs) { child(*list_items) }
+ else
+ ul(**attrs) { child(*list_items) }
+ end
+ end
- # Parse inner content as blocks
- inner_scanner = LineScanner.new(content)
- children = parse_blocks(inner_scanner)
+ def build_list_item(item, is_task_list)
+ attrs = {}
+ content = item[:lines].join("\n")
- # If no block-level elements were created, wrap in paragraph
- child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) }
- if child_elements.empty?
- inline_nodes = InlineParser.parse(content)
- child_elements = [p { child(*inline_nodes) }]
+ if is_task_list
+ task_match = content.match(/^\[( |[xX])\]\s(.*)$/m)
+ if task_match
+ attrs[:checked] = task_match[1] == ' ' ? 'false' : 'true'
+ content = task_match[2]
end
+ end
- li(**attrs) { child(*child_elements) }
+ # Parse inner content as blocks
+ inner_scanner = LineScanner.new(content)
+ children = parse_blocks(inner_scanner)
+
+ # If no block-level elements were created, wrap in paragraph
+ child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawNode) }
+ if child_elements.empty?
+ inline_nodes = InlineParser.parse(content)
+ child_elements = [p { child(*inline_nodes) }]
end
- def try_html_block(scanner)
- line = scanner.peek
- match = line.match(/^<(div|details|summary)(\s[^>]*)?>/)
- return nil unless match
+ li(**attrs) { child(*child_elements) }
+ end
- tag = match[1]
- lines = []
- close_tag = "</#{tag}>"
+ def try_html_block(scanner)
+ line = scanner.peek
+ match = line.match(/^<(div|details|summary)(\s[^>]*)?>/)
+ return nil unless match
- until scanner.eof?
- l = scanner.advance
- lines << l
- break if l.include?(close_tag)
- end
+ tag = match[1]
+ lines = []
+ close_tag = "</#{tag}>"
- html_content = lines.join("\n")
+ until scanner.eof?
+ l = scanner.advance
+ lines << l
+ break if l.include?(close_tag)
+ end
- if tag == 'div'
- # Parse inner content for div blocks
- inner_match = html_content.match(%r{<div([^>]*)>(.*)</div>}m)
- if inner_match
- attr_str = inner_match[1]
- inner_content = inner_match[2].strip
+ html_content = lines.join("\n")
- attrs = {}
- attr_str.scan(/([\w-]+)="([^"]*)"/) do |key, value|
- attrs[key.to_sym] = value
- end
+ if tag == 'div'
+ # Parse inner content for div blocks
+ inner_match = html_content.match(%r{<div([^>]*)>(.*)</div>}m)
+ if inner_match
+ attr_str = inner_match[1]
+ inner_content = inner_match[2].strip
- if inner_content.empty?
- div(**attrs)
- else
- inner_scanner = LineScanner.new(inner_content)
- children = parse_blocks(inner_scanner)
- child_elements = children.compact.select do |c|
- c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML)
- end
- div(**attrs) { child(*child_elements) }
- end
+ attrs = {}
+ attr_str.scan(/([\w-]+)="([^"]*)"/) do |key, value|
+ attrs[key.to_sym] = value
+ end
+
+ if inner_content.empty?
+ div(**attrs)
else
- div(class: 'raw-html') { raw_html html_content }
+ inner_scanner = LineScanner.new(inner_content)
+ children = parse_blocks(inner_scanner)
+ child_elements = children.compact.select do |c|
+ c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawNode)
+ end
+ div(**attrs) { child(*child_elements) }
end
else
- div(class: 'raw-html') { raw_html html_content }
+ div(class: 'raw-html') { raw html_content }
end
+ else
+ div(class: 'raw-html') { raw html_content }
end
+ end
- def parse_paragraph(scanner)
- lines = []
- until scanner.eof?
- l = scanner.peek
- break if l.strip.empty?
- break if l.match?(/^```/)
- break if l.match?(/^\#{1,5}\s/)
- break if l.match?(/^---+\s*$/)
- break if l.match?(/^>\s/)
- break if l.match?(/^\*\s/)
- break if l.match?(/^\d+\.\s/)
- if l.match?(/^\|/) &&
- scanner.pos + 1 < scanner.lines.length &&
- scanner.lines[scanner.pos + 1].match?(/^\|[\s:|-]+\|$/)
- break
- end
- break if l.match?(/^:::/)
- break if l.match?(/^<!--/)
- break if l.match?(/^<(div|details|summary)/)
- break if l.match?(/^\[\^[^\]]+\]:/)
-
- lines << scanner.advance
+ def parse_paragraph(scanner)
+ lines = []
+ until scanner.eof?
+ l = scanner.peek
+ break if l.strip.empty?
+ break if l.match?(/^```/)
+ break if l.match?(/^\#{1,5}\s/)
+ break if l.match?(/^---+\s*$/)
+ break if l.match?(/^>\s/)
+ break if l.match?(/^\*\s/)
+ break if l.match?(/^\d+\.\s/)
+ if l.match?(/^\|/) &&
+ scanner.pos + 1 < scanner.lines.length &&
+ scanner.lines[scanner.pos + 1].match?(/^\|[\s:|-]+\|$/)
+ break
end
+ break if l.match?(/^:::/)
+ break if l.match?(/^<!--/)
+ break if l.match?(/^<(div|details|summary)/)
+ break if l.match?(/^\[\^[^\]]+\]:/)
- return nil if lines.empty?
-
- text_content = lines.join("\n")
- inline_nodes = InlineParser.parse(text_content)
- p { child(*inline_nodes) }
+ lines << scanner.advance
end
- # --- Section hierarchy ---
+ return nil if lines.empty?
- def build_document(blocks)
- footnote_blocks = blocks.select { |b| b.is_a?(FootnoteBlock) }
- non_footnote_blocks = blocks.reject { |b| b.is_a?(FootnoteBlock) }
-
- article_content = build_section_hierarchy(non_footnote_blocks)
+ text_content = lines.join("\n")
+ inline_nodes = InlineParser.parse(text_content)
+ p { child(*inline_nodes) }
+ end
- unless footnote_blocks.empty?
- footnote_elements = footnote_blocks.map do |fb|
- elem('footnote', id: fb.id) { child(*fb.children) }
- end
- footnote_section = section(class: 'footnotes') { child(*footnote_elements) }
- article_content.push(footnote_section)
- end
+ # --- Section hierarchy ---
- elem('__root__') { article { child(*article_content) } }
- end
+ def build_document(blocks)
+ footnote_blocks = blocks.select { |b| b.is_a?(FootnoteBlock) }
+ non_footnote_blocks = blocks.reject { |b| b.is_a?(FootnoteBlock) }
- def build_section_hierarchy(blocks)
- result = []
- section_stack = []
+ article_content = build_section_hierarchy(non_footnote_blocks)
- blocks.each do |block|
- if block.is_a?(HeaderBlock)
- level = block.level
+ unless footnote_blocks.empty?
+ footnote_elements = footnote_blocks.map do |fb|
+ elem('footnote', id: fb.id) { child(*fb.children) }
+ end
+ footnote_section = section(class: 'footnotes') { child(*footnote_elements) }
+ article_content.push(footnote_section)
+ end
- while !section_stack.empty? && section_stack.last[:level] >= level
- closed = section_stack.pop
- section_el = create_section_element(closed)
- if section_stack.empty?
- result.push(section_el)
- else
- section_stack.last[:children].push(section_el)
- end
- end
+ elem('__root__') { article { child(*article_content) } }
+ end
- section_stack.push({
- id: block.id,
- attributes: block.attributes,
- level: level,
- heading: block.heading_element,
- children: []
- })
- else
- next if block.nil?
+ def build_section_hierarchy(blocks)
+ result = []
+ section_stack = []
- targets = if section_stack.empty?
- result
- else
- section_stack.last[:children]
- end
+ blocks.each do |block|
+ if block.is_a?(HeaderBlock)
+ level = block.level
- if block.is_a?(Array)
- targets.concat(block)
+ while !section_stack.empty? && section_stack.last[:level] >= level
+ closed = section_stack.pop
+ section_el = create_section_element(closed)
+ if section_stack.empty?
+ result.push(section_el)
else
- targets.push(block)
+ section_stack.last[:children].push(section_el)
end
end
- end
- until section_stack.empty?
- closed = section_stack.pop
- section_el = create_section_element(closed)
- if section_stack.empty?
- result.push(section_el)
+ section_stack.push({
+ id: block.id,
+ attributes: block.attributes,
+ level: level,
+ heading: block.heading_element,
+ children: []
+ })
+ else
+ next if block.nil?
+
+ targets = if section_stack.empty?
+ result
+ else
+ section_stack.last[:children]
+ end
+
+ if block.is_a?(Array)
+ targets.concat(block)
else
- section_stack.last[:children].push(section_el)
+ targets.push(block)
end
end
+ end
- result
+ until section_stack.empty?
+ closed = section_stack.pop
+ section_el = create_section_element(closed)
+ if section_stack.empty?
+ result.push(section_el)
+ else
+ section_stack.last[:children].push(section_el)
+ end
end
- def create_section_element(section_info)
- attributes = section_info[:attributes].dup
- attributes['id'] = section_info[:id] if section_info[:id]
+ result
+ end
- section(**attributes.transform_keys(&:to_sym)) do
- child section_info[:heading]
- child(*section_info[:children])
- end
+ def create_section_element(section_info)
+ attributes = section_info[:attributes].dup
+ attributes['id'] = section_info[:id] if section_info[:id]
+
+ section(**attributes.transform_keys(&:to_sym)) do
+ child section_info[:heading]
+ child(*section_info[:children])
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb b/services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb
index c1715904..79ad45f8 100644
--- a/services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb
+++ b/services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb
@@ -1,382 +1,382 @@
module Nuldoc
module Parser
- class InlineParser
- extend DOM::HTML
+ class InlineParser < DOM::HTMLBuilder
+ INLINE_HTML_TAGS = %w[del mark sub sup ins br].freeze
+ SELF_CLOSING_TAGS = %w[br].freeze
- class << self
- INLINE_HTML_TAGS = %w[del mark sub sup ins br].freeze
- SELF_CLOSING_TAGS = %w[br].freeze
+ def self.parse(text)
+ new.parse(text)
+ end
- def parse(text)
- parse_inline(text, 0, text.length)
- end
+ def parse(text)
+ parse_inline(text, 0, text.length)
+ end
- private
+ private
- def parse_inline(text, start, stop)
- nodes = []
- pos = start
+ def parse_inline(text, start, stop)
+ nodes = []
+ pos = start
- while pos < stop
- # Try each inline pattern
- matched = try_escape(text, pos, stop, nodes) ||
- try_code_span(text, pos, stop, nodes) ||
- try_autolink(text, pos, stop, nodes) ||
- try_inline_html(text, pos, stop, nodes) ||
- try_image(text, pos, stop, nodes) ||
- try_link(text, pos, stop, nodes) ||
- try_footnote_ref(text, pos, stop, nodes) ||
- try_bold(text, pos, stop, nodes) ||
- try_italic(text, pos, stop, nodes) ||
- try_strikethrough(text, pos, stop, nodes) ||
- try_typographic(text, pos, stop, nodes)
+ while pos < stop
+ # Try each inline pattern
+ matched = try_escape(text, pos, stop, nodes) ||
+ try_code_span(text, pos, stop, nodes) ||
+ try_autolink(text, pos, stop, nodes) ||
+ try_inline_html(text, pos, stop, nodes) ||
+ try_image(text, pos, stop, nodes) ||
+ try_link(text, pos, stop, nodes) ||
+ try_footnote_ref(text, pos, stop, nodes) ||
+ try_bold(text, pos, stop, nodes) ||
+ try_italic(text, pos, stop, nodes) ||
+ try_strikethrough(text, pos, stop, nodes) ||
+ try_typographic(text, pos, stop, nodes)
- if matched
- pos = matched
- else
- # Plain character
- char_end = pos + 1
- # Collect consecutive plain characters
- while char_end < stop
- break if special_char?(text[char_end])
+ if matched
+ pos = matched
+ else
+ # Plain character
+ char_end = pos + 1
+ # Collect consecutive plain characters
+ while char_end < stop
+ break if special_char?(text[char_end])
- char_end += 1
- end
- nodes << text(text[pos...char_end])
- pos = char_end
+ char_end += 1
end
- end
-
- merge_text_nodes(nodes)
- end
-
- def special_char?(ch)
- case ch
- when '\\', '`', '<', '!', '[', '*', '~', '.', '-', "'", '"'
- true
- else
- false
+ nodes << text(text[pos...char_end])
+ pos = char_end
end
end
- def try_escape(text, pos, _stop, nodes)
- return nil unless text[pos] == '\\'
- return nil if pos + 1 >= text.length
-
- next_char = text[pos + 1]
- return unless '\\`*_{}[]()#+-.!~|>:'.include?(next_char)
+ merge_text_nodes(nodes)
+ end
- nodes << text(next_char)
- pos + 2
+ def special_char?(ch)
+ case ch
+ when '\\', '`', '<', '!', '[', '*', '~', '.', '-', "'", '"'
+ true
+ else
+ false
end
+ end
- def try_autolink(text, pos, stop, nodes)
- return nil unless text[pos] == '<'
+ def try_escape(text, pos, _stop, nodes)
+ return nil unless text[pos] == '\\'
+ return nil if pos + 1 >= text.length
- # Match <URL> where URL starts with http:// or https://
- close = text.index('>', pos + 1)
- return nil unless close && close < stop
+ next_char = text[pos + 1]
+ return unless '\\`*_{}[]()#+-.!~|>:'.include?(next_char)
- url = text[(pos + 1)...close]
- return nil unless url.match?(%r{^https?://\S+$})
+ nodes << text(next_char)
+ pos + 2
+ end
- nodes << a(href: url, class: 'url') { text url }
- close + 1
- end
+ def try_autolink(text, pos, stop, nodes)
+ return nil unless text[pos] == '<'
- def try_code_span(text, pos, stop, nodes)
- return nil unless text[pos] == '`'
+ # Match <URL> where URL starts with http:// or https://
+ close = text.index('>', pos + 1)
+ return nil unless close && close < stop
- # Count opening backticks
- tick_count = 0
- i = pos
- while i < stop && text[i] == '`'
- tick_count += 1
- i += 1
- end
+ url = text[(pos + 1)...close]
+ return nil unless url.match?(%r{^https?://\S+$})
- # Find matching closing backticks
- close_pos = text.index('`' * tick_count, i)
- return nil unless close_pos && close_pos < stop
+ nodes << a(href: url, class: 'url') { text url }
+ close + 1
+ end
- content = text[i...close_pos]
- # Strip one leading and one trailing space if both present
- content = content[1...-1] if content.length >= 2 && content[0] == ' ' && content[-1] == ' '
+ def try_code_span(text, pos, stop, nodes)
+ return nil unless text[pos] == '`'
- nodes << code { text content }
- close_pos + tick_count
+ # Count opening backticks
+ tick_count = 0
+ i = pos
+ while i < stop && text[i] == '`'
+ tick_count += 1
+ i += 1
end
- def try_inline_html(text, pos, stop, nodes)
- return nil unless text[pos] == '<'
+ # Find matching closing backticks
+ close_pos = text.index('`' * tick_count, i)
+ return nil unless close_pos && close_pos < stop
- # Self-closing tags
- SELF_CLOSING_TAGS.each do |tag|
- pattern = "<#{tag}>"
- len = pattern.length
- if pos + len <= stop && text[pos, len].downcase == pattern
- nodes << elem(tag)
- return pos + len
- end
- pattern_sc = "<#{tag} />"
- len_sc = pattern_sc.length
- if pos + len_sc <= stop && text[pos, len_sc].downcase == pattern_sc
- nodes << elem(tag)
- return pos + len_sc
- end
- pattern_sc2 = "<#{tag}/>"
- len_sc2 = pattern_sc2.length
- if pos + len_sc2 <= stop && text[pos, len_sc2].downcase == pattern_sc2
- nodes << elem(tag)
- return pos + len_sc2
- end
- end
+ content = text[i...close_pos]
+ # Strip one leading and one trailing space if both present
+ content = content[1...-1] if content.length >= 2 && content[0] == ' ' && content[-1] == ' '
- # Opening tags with content
- (INLINE_HTML_TAGS - SELF_CLOSING_TAGS).each do |tag|
- open_tag = "<#{tag}>"
- close_tag = "</#{tag}>"
- next unless pos + open_tag.length <= stop && text[pos, open_tag.length].downcase == open_tag
+ nodes << code { text content }
+ close_pos + tick_count
+ end
- close_pos = text.index(close_tag, pos + open_tag.length)
- next unless close_pos && close_pos + close_tag.length <= stop
+ def try_inline_html(text, pos, stop, nodes)
+ return nil unless text[pos] == '<'
- inner = text[(pos + open_tag.length)...close_pos]
- children = parse_inline(inner, 0, inner.length)
- nodes << elem(tag) { child(*children) }
- return close_pos + close_tag.length
+ # Self-closing tags
+ SELF_CLOSING_TAGS.each do |tag|
+ pattern = "<#{tag}>"
+ len = pattern.length
+ if pos + len <= stop && text[pos, len].downcase == pattern
+ nodes << elem(tag)
+ return pos + len
end
+ pattern_sc = "<#{tag} />"
+ len_sc = pattern_sc.length
+ if pos + len_sc <= stop && text[pos, len_sc].downcase == pattern_sc
+ nodes << elem(tag)
+ return pos + len_sc
+ end
+ pattern_sc2 = "<#{tag}/>"
+ len_sc2 = pattern_sc2.length
+ if pos + len_sc2 <= stop && text[pos, len_sc2].downcase == pattern_sc2
+ nodes << elem(tag)
+ return pos + len_sc2
+ end
+ end
+
+ # Opening tags with content
+ (INLINE_HTML_TAGS - SELF_CLOSING_TAGS).each do |tag|
+ open_tag = "<#{tag}>"
+ close_tag = "</#{tag}>"
+ next unless pos + open_tag.length <= stop && text[pos, open_tag.length].downcase == open_tag
- nil
+ close_pos = text.index(close_tag, pos + open_tag.length)
+ next unless close_pos && close_pos + close_tag.length <= stop
+
+ inner = text[(pos + open_tag.length)...close_pos]
+ children = parse_inline(inner, 0, inner.length)
+ nodes << elem(tag) { child(*children) }
+ return close_pos + close_tag.length
end
- def try_image(text, pos, stop, nodes)
- return nil unless text[pos] == '!' && pos + 1 < stop && text[pos + 1] == '['
+ nil
+ end
- # Find ]
- bracket_close = find_matching_bracket(text, pos + 1, stop)
- return nil unless bracket_close
+ def try_image(text, pos, stop, nodes)
+ return nil unless text[pos] == '!' && pos + 1 < stop && text[pos + 1] == '['
- alt = text[(pos + 2)...bracket_close]
+ # Find ]
+ bracket_close = find_matching_bracket(text, pos + 1, stop)
+ return nil unless bracket_close
- # Expect (
- return nil unless bracket_close + 1 < stop && text[bracket_close + 1] == '('
+ alt = text[(pos + 2)...bracket_close]
- paren_close = find_matching_paren(text, bracket_close + 1, stop)
- return nil unless paren_close
+ # Expect (
+ return nil unless bracket_close + 1 < stop && text[bracket_close + 1] == '('
- inner = text[(bracket_close + 2)...paren_close].strip
- url, title = parse_url_title(inner)
+ paren_close = find_matching_paren(text, bracket_close + 1, stop)
+ return nil unless paren_close
- attrs = {}
- attrs[:src] = url if url
- attrs[:alt] = alt unless alt.empty?
- attrs[:title] = title if title
+ inner = text[(bracket_close + 2)...paren_close].strip
+ url, title = parse_url_title(inner)
- nodes << img(**attrs)
- paren_close + 1
- end
+ attrs = {}
+ attrs[:src] = url if url
+ attrs[:alt] = alt unless alt.empty?
+ attrs[:title] = title if title
- def try_link(text, pos, stop, nodes)
- return nil unless text[pos] == '['
+ nodes << img(**attrs)
+ paren_close + 1
+ end
- bracket_close = find_matching_bracket(text, pos, stop)
- return nil unless bracket_close
+ def try_link(text, pos, stop, nodes)
+ return nil unless text[pos] == '['
- link_text = text[(pos + 1)...bracket_close]
+ bracket_close = find_matching_bracket(text, pos, stop)
+ return nil unless bracket_close
- # Expect (
- return nil unless bracket_close + 1 < stop && text[bracket_close + 1] == '('
+ link_text = text[(pos + 1)...bracket_close]
- paren_close = find_matching_paren(text, bracket_close + 1, stop)
- return nil unless paren_close
+ # Expect (
+ return nil unless bracket_close + 1 < stop && text[bracket_close + 1] == '('
- inner = text[(bracket_close + 2)...paren_close].strip
- url, title = parse_url_title(inner)
+ paren_close = find_matching_paren(text, bracket_close + 1, stop)
+ return nil unless paren_close
- attrs = {}
- attrs[:href] = url if url
- attrs[:title] = title if title
+ inner = text[(bracket_close + 2)...paren_close].strip
+ url, title = parse_url_title(inner)
- children = parse_inline(link_text, 0, link_text.length)
+ attrs = {}
+ attrs[:href] = url if url
+ attrs[:title] = title if title
- # Check if autolink
- is_autolink = children.length == 1 &&
- children[0].kind == :text &&
- children[0].content == url
- attrs[:class] = 'url' if is_autolink
+ children = parse_inline(link_text, 0, link_text.length)
- nodes << a(**attrs) { child(*children) }
- paren_close + 1
- end
+ # Check if autolink
+ is_autolink = children.length == 1 &&
+ children[0].kind == :text &&
+ children[0].content == url
+ attrs[:class] = 'url' if is_autolink
- def try_footnote_ref(text, pos, stop, nodes)
- return nil unless text[pos] == '[' && pos + 1 < stop && text[pos + 1] == '^'
+ nodes << a(**attrs) { child(*children) }
+ paren_close + 1
+ end
- close = text.index(']', pos + 2)
- return nil unless close && close < stop
+ def try_footnote_ref(text, pos, stop, nodes)
+ return nil unless text[pos] == '[' && pos + 1 < stop && text[pos + 1] == '^'
- # Make sure there's no nested [ or ( between
- inner = text[(pos + 2)...close]
- return nil if inner.include?('[') || inner.include?(']')
- return nil if inner.empty?
+ close = text.index(']', pos + 2)
+ return nil unless close && close < stop
- nodes << elem('footnoteref', reference: inner)
- close + 1
- end
+ # Make sure there's no nested [ or ( between
+ inner = text[(pos + 2)...close]
+ return nil if inner.include?('[') || inner.include?(']')
+ return nil if inner.empty?
- def try_bold(text, pos, stop, nodes)
- return nil unless text[pos] == '*' && pos + 1 < stop && text[pos + 1] == '*'
+ nodes << elem('footnoteref', reference: inner)
+ close + 1
+ end
- close = text.index('**', pos + 2)
- return nil unless close && close + 2 <= stop
+ def try_bold(text, pos, stop, nodes)
+ return nil unless text[pos] == '*' && pos + 1 < stop && text[pos + 1] == '*'
- inner = text[(pos + 2)...close]
- children = parse_inline(inner, 0, inner.length)
- nodes << strong { child(*children) }
- close + 2
- end
+ close = text.index('**', pos + 2)
+ return nil unless close && close + 2 <= stop
- def try_italic(text, pos, stop, nodes)
- return nil unless text[pos] == '*'
- return nil if pos + 1 < stop && text[pos + 1] == '*'
+ inner = text[(pos + 2)...close]
+ children = parse_inline(inner, 0, inner.length)
+ nodes << strong { child(*children) }
+ close + 2
+ end
- # Find closing * that is not **
- i = pos + 1
- while i < stop
- if text[i] == '*'
- # Make sure it's not **
- if i + 1 < stop && text[i + 1] == '*'
- i += 2
- else
- inner = text[(pos + 1)...i]
- return nil if inner.empty?
+ def try_italic(text, pos, stop, nodes)
+ return nil unless text[pos] == '*'
+ return nil if pos + 1 < stop && text[pos + 1] == '*'
- children = parse_inline(inner, 0, inner.length)
- nodes << em { child(*children) }
- return i + 1
- end
+ # Find closing * that is not **
+ i = pos + 1
+ while i < stop
+ if text[i] == '*'
+ # Make sure it's not **
+ if i + 1 < stop && text[i + 1] == '*'
+ i += 2
else
- i += 1
+ inner = text[(pos + 1)...i]
+ return nil if inner.empty?
+
+ children = parse_inline(inner, 0, inner.length)
+ nodes << em { child(*children) }
+ return i + 1
end
+ else
+ i += 1
end
- nil
end
+ nil
+ end
- def try_strikethrough(text, pos, stop, nodes)
- return nil unless text[pos] == '~' && pos + 1 < stop && text[pos + 1] == '~'
-
- close = text.index('~~', pos + 2)
- return nil unless close && close + 2 <= stop
+ def try_strikethrough(text, pos, stop, nodes)
+ return nil unless text[pos] == '~' && pos + 1 < stop && text[pos + 1] == '~'
- inner = text[(pos + 2)...close]
- children = parse_inline(inner, 0, inner.length)
- nodes << del { child(*children) }
- close + 2
- end
+ close = text.index('~~', pos + 2)
+ return nil unless close && close + 2 <= stop
- def try_typographic(text, pos, stop, nodes)
- # Ellipsis
- if text[pos] == '.' && pos + 2 < stop && text[pos + 1] == '.' && text[pos + 2] == '.'
- nodes << text("\u2026")
- return pos + 3
- end
+ inner = text[(pos + 2)...close]
+ children = parse_inline(inner, 0, inner.length)
+ nodes << del { child(*children) }
+ close + 2
+ end
- # Em dash (must check before en dash)
- if text[pos] == '-' && pos + 2 < stop && text[pos + 1] == '-' && text[pos + 2] == '-'
- nodes << text("\u2014")
- return pos + 3
- end
+ def try_typographic(text, pos, stop, nodes)
+ # Ellipsis
+ if text[pos] == '.' && pos + 2 < stop && text[pos + 1] == '.' && text[pos + 2] == '.'
+ nodes << text("\u2026")
+ return pos + 3
+ end
- # En dash
- # Make sure it's not ---
- if text[pos] == '-' && pos + 1 < stop && text[pos + 1] == '-' && (pos + 2 >= stop || text[pos + 2] != '-')
- nodes << text("\u2013")
- return pos + 2
- end
+ # Em dash (must check before en dash)
+ if text[pos] == '-' && pos + 2 < stop && text[pos + 1] == '-' && text[pos + 2] == '-'
+ nodes << text("\u2014")
+ return pos + 3
+ end
- # Smart quotes
- try_smart_quotes(text, pos, stop, nodes)
+ # En dash
+ # Make sure it's not ---
+ if text[pos] == '-' && pos + 1 < stop && text[pos + 1] == '-' && (pos + 2 >= stop || text[pos + 2] != '-')
+ nodes << text("\u2013")
+ return pos + 2
end
- def try_smart_quotes(text, pos, _stop, nodes)
- ch = text[pos]
- return nil unless ["'", '"'].include?(ch)
+ # Smart quotes
+ try_smart_quotes(text, pos, stop, nodes)
+ end
- prev_char = pos.positive? ? text[pos - 1] : nil
- is_opening = prev_char.nil? || prev_char == ' ' || prev_char == "\n" || prev_char == '(' || prev_char == '['
+ def try_smart_quotes(text, pos, _stop, nodes)
+ ch = text[pos]
+ return nil unless ["'", '"'].include?(ch)
- nodes << if ch == "'"
- text(is_opening ? "\u2018" : "\u2019")
- else
- text(is_opening ? "\u201C" : "\u201D")
- end
- pos + 1
- end
+ prev_char = pos.positive? ? text[pos - 1] : nil
+ is_opening = prev_char.nil? || prev_char == ' ' || prev_char == "\n" || prev_char == '(' || prev_char == '['
- def find_matching_bracket(text, pos, stop)
- return nil unless text[pos] == '['
+ nodes << if ch == "'"
+ text(is_opening ? "\u2018" : "\u2019")
+ else
+ text(is_opening ? "\u201C" : "\u201D")
+ end
+ pos + 1
+ end
- depth = 0
- i = pos
- while i < stop
- case text[i]
- when '\\'
- i += 2
- next
- when '['
- depth += 1
- when ']'
- depth -= 1
- return i if depth.zero?
- end
- i += 1
+ def find_matching_bracket(text, pos, stop)
+ return nil unless text[pos] == '['
+
+ depth = 0
+ i = pos
+ while i < stop
+ case text[i]
+ when '\\'
+ i += 2
+ next
+ when '['
+ depth += 1
+ when ']'
+ depth -= 1
+ return i if depth.zero?
end
- nil
+ i += 1
end
+ nil
+ end
- def find_matching_paren(text, pos, stop)
- return nil unless text[pos] == '('
+ def find_matching_paren(text, pos, stop)
+ return nil unless text[pos] == '('
- depth = 0
- i = pos
- while i < stop
- case text[i]
- when '\\'
- i += 2
- next
- when '('
- depth += 1
- when ')'
- depth -= 1
- return i if depth.zero?
- end
- i += 1
+ depth = 0
+ i = pos
+ while i < stop
+ case text[i]
+ when '\\'
+ i += 2
+ next
+ when '('
+ depth += 1
+ when ')'
+ depth -= 1
+ return i if depth.zero?
end
- nil
+ i += 1
end
+ nil
+ end
- def parse_url_title(inner)
- # URL might be followed by "title"
- match = inner.match(/^(\S+)\s+"([^"]*)"$/)
- if match
- [match[1], match[2]]
- else
- [inner, nil]
- end
+ def parse_url_title(inner)
+ # URL might be followed by "title"
+ match = inner.match(/^(\S+)\s+"([^"]*)"$/)
+ if match
+ [match[1], match[2]]
+ else
+ [inner, nil]
end
+ end
- def merge_text_nodes(nodes)
- result = []
- nodes.each do |node|
- if node.kind == :text && !result.empty? && result.last.kind == :text
- result[-1] = text(result.last.content + node.content)
- else
- result << node
- end
+ def merge_text_nodes(nodes)
+ result = []
+ nodes.each do |node|
+ if node.kind == :text && !result.empty? && result.last.kind == :text
+ result[-1] = text(result.last.content + node.content)
+ else
+ result << node
end
- result
end
+ result
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/markdown/transform.rb b/services/nuldoc/lib/nuldoc/markdown/transform.rb
index 76968fb8..0e20493e 100644
--- a/services/nuldoc/lib/nuldoc/markdown/transform.rb
+++ b/services/nuldoc/lib/nuldoc/markdown/transform.rb
@@ -1,12 +1,13 @@
module Nuldoc
- class Transform
- include DOM::HTML
+ class Transform < DOM::HTMLBuilder
+ include DOM
def self.to_html(doc)
new(doc).to_html
end
def initialize(doc)
+ super()
@doc = doc
end
@@ -37,15 +38,15 @@ module Nuldoc
new_children = []
current_text = +''
- n.children.each do |child|
- if child.kind == :text
- current_text << child.content
+ n.children.each do |c|
+ if c.kind == :text
+ current_text << c.content
else
unless current_text.empty?
new_children.push(text(current_text))
current_text = +''
end
- new_children.push(child)
+ new_children.push(c)
end
end
@@ -160,23 +161,25 @@ module Nuldoc
def transform_section_title_element
section_level = 1
- g = proc do |c|
- next unless c.kind == :element
+ g = proc do |parent|
+ parent.children.each_with_index do |c, i|
+ next unless c.kind == :element
- if c.name == 'section'
- section_level += 1
- c.attributes['__sectionLevel'] = section_level.to_s
+ if c.name == 'section'
+ section_level += 1
+ c.attributes['__sectionLevel'] = section_level.to_s
+ end
+ g.call(c)
+ section_level -= 1 if c.name == 'section'
+ parent.children[i] = c.with(name: "h#{section_level}") if c.name == 'h'
end
- for_each_child(c, &g)
- section_level -= 1 if c.name == 'section'
- c.name = "h#{section_level}" if c.name == 'h'
end
- for_each_child(@doc.root, &g)
+ g.call(@doc.root)
end
def transform_note_element
- for_each_element_of_type(@doc.root, 'note') do |n|
+ map_element_of_type(@doc.root, 'note') do |n|
editat_attr = n.attributes['editat']
operation_attr = n.attributes['operation']
is_edit_block = editat_attr && operation_attr
@@ -185,9 +188,9 @@ module Nuldoc
text(is_edit_block ? "#{editat_attr} #{operation_attr}" : 'NOTE')
end
content_element = div(class: 'admonition-content') { child(*n.children.dup) }
- n.name = 'div'
add_class(n, 'admonition')
n.children.replace([label_element, content_element])
+ n.with(name: 'div')
end
end
@@ -205,9 +208,9 @@ module Nuldoc
footnote_counter = 0
footnote_map = {}
- for_each_element_of_type(@doc.root, 'footnoteref') do |n|
+ map_element_of_type(@doc.root, 'footnoteref') do |n|
reference = n.attributes['reference']
- next unless reference
+ next n unless reference
unless footnote_map.key?(reference)
footnote_counter += 1
@@ -215,7 +218,6 @@ module Nuldoc
end
footnote_number = footnote_map[reference]
- n.name = 'sup'
n.attributes.delete('reference')
n.attributes['class'] = 'footnote'
n.children.replace([
@@ -224,19 +226,18 @@ module Nuldoc
text "[#{footnote_number}]"
end
])
+ n.with(name: 'sup')
end
- for_each_element_of_type(@doc.root, 'footnote') do |n|
+ map_element_of_type(@doc.root, 'footnote') do |n|
id = n.attributes['id']
unless id && footnote_map.key?(id)
- n.name = 'span'
n.children.replace([])
- next
+ next n.with(name: 'span')
end
footnote_number = footnote_map[id]
- n.name = 'div'
n.attributes.delete('id')
n.attributes['class'] = 'footnote'
n.attributes['id'] = "footnote--#{id}"
@@ -246,6 +247,7 @@ module Nuldoc
a(href: "#footnoteref--#{id}") { text "#{footnote_number}. " },
*old_children
])
+ n.with(name: 'div')
end
end
@@ -257,39 +259,34 @@ module Nuldoc
is_tight = n.attributes['__tight'] == 'true'
next unless is_tight
- n.children.each do |child|
- next unless child.kind == :element && child.name == 'li'
+ n.children.each do |c|
+ next unless c.kind == :element && c.name == 'li'
new_grand_children = []
- child.children.each do |grand_child|
+ c.children.each do |grand_child|
if grand_child.kind == :element && grand_child.name == 'p'
new_grand_children.concat(grand_child.children)
else
new_grand_children.push(grand_child)
end
end
- child.children.replace(new_grand_children)
+ c.children.replace(new_grand_children)
end
end
end
def transform_and_highlight_code_block_element
- for_each_child_recursively(@doc.root) do |n|
- next unless n.kind == :element && n.name == 'codeblock'
+ map_children_recursively(@doc.root) do |n|
+ next n unless n.kind == :element && n.name == 'codeblock'
language = n.attributes['language'] || 'text'
filename = n.attributes['filename']
numbered = n.attributes['numbered']
source_code_node = n.children[0]
- source_code = if source_code_node.kind == :text
- source_code_node.content.rstrip
- else
- source_code_node.html.rstrip
- end
+ source_code = source_code_node.content.rstrip
highlighted = highlight_code(source_code, language)
- n.name = 'div'
n.attributes['class'] = 'codeblock'
n.attributes.delete('language')
@@ -302,11 +299,12 @@ module Nuldoc
n.attributes.delete('filename')
n.children.replace([
div(class: 'filename') { text filename },
- raw_html(highlighted)
+ raw(highlighted)
])
else
- n.children.replace([raw_html(highlighted)])
+ n.children.replace([raw(highlighted)])
end
+ n.with(name: 'div')
end
end
@@ -351,8 +349,8 @@ module Nuldoc
next unless section_id
heading_text = ''
- node.children.each do |child|
- heading_text = inner_text(child) if child.kind == :element && child.name == 'a'
+ node.children.each do |c|
+ heading_text = inner_text(c) if c.kind == :element && c.name == 'a'
end
entry = { id: section_id, text: heading_text, level: level, children: [] }
@@ -394,12 +392,12 @@ module Nuldoc
return root if root.kind == :element && root.name == 'section' && root.children.include?(target)
if root.kind == :element
- root.children.each do |child|
- next unless child.kind == :element
+ root.children.each do |c|
+ next unless c.kind == :element
- return child if child.name == 'section' && child.children.include?(target)
+ return c if c.name == 'section' && c.children.include?(target)
- result = find_parent_section(child, target)
+ result = find_parent_section(c, target)
return result if result
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/about_page.rb b/services/nuldoc/lib/nuldoc/pages/about_page.rb
index 755ec233..07391f9a 100644
--- a/services/nuldoc/lib/nuldoc/pages/about_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/about_page.rb
@@ -1,73 +1,78 @@
module Nuldoc
module Pages
class AboutPage
- extend DOM::HTML
+ def initialize(slides:, config:)
+ @slides = slides
+ @config = config
+ end
- def self.render(slides:, config:)
- sorted_slides = slides.sort_by { |s| GeneratorUtils.published_date(s) }.reverse
+ def render
+ config = @config
+ sorted_slides = @slides.sort_by { |s| GeneratorUtils.published_date(s) }.reverse
- Components::PageLayout.render(
+ Components::PageLayout.new(
meta_copyright_year: config.site.copyright_year,
meta_description: 'このサイトの著者について',
meta_title: "About|#{config.sites.about.site_name}",
site: 'about',
config: config,
- children: body(class: 'single') do
- Components::AboutGlobalHeader.render(config: config)
- main(class: 'main') do
- article(class: 'post-single') do
- header(class: 'post-header') do
- h1(class: 'post-title') { text 'nsfisis' }
- div(class: 'my-icon') do
- div(id: 'myIcon') { img(src: '/favicon.svg') }
- Components::StaticScript.render(
- site: 'about',
- file_name: '/my-icon.js',
- defer: 'true',
- config: config
- )
- end
- end
- div(class: 'post-content') do
- section do
- h2 { text '読み方' }
- p { text '読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。' }
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'single' do
+ render(Components::AboutGlobalHeader, config: config)
+ main class: 'main' do
+ article class: 'post-single' do
+ header class: 'post-header' do
+ h1(class: 'post-title') { text 'nsfisis' }
+ div class: 'my-icon' do
+ div(id: 'myIcon') { img(src: '/favicon.svg') }
+ render(Components::StaticScript,
+ site: 'about',
+ file_name: '/my-icon.js',
+ defer: 'true',
+ config: config)
+ end
end
- section do
- h2 { text 'アカウント' }
- ul do
- li do
- a(href: 'https://twitter.com/nsfisis', target: '_blank', rel: 'noreferrer') do
- text 'Twitter (現 𝕏): @nsfisis'
+ div class: 'post-content' do
+ section do
+ h2 { text '読み方' }
+ p { text '読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。' }
+ end
+ section do
+ h2 { text 'アカウント' }
+ ul do
+ li do
+ a href: 'https://twitter.com/nsfisis', target: '_blank', rel: 'noreferrer' do
+ text 'Twitter (現 𝕏): @nsfisis'
+ end
end
- end
- li do
- a(href: 'https://github.com/nsfisis', target: '_blank', rel: 'noreferrer') do
- text 'GitHub: @nsfisis'
+ li do
+ a href: 'https://github.com/nsfisis', target: '_blank', rel: 'noreferrer' do
+ text 'GitHub: @nsfisis'
+ end
end
end
end
- end
- section do
- h2 { text '仕事' }
- ul do
- li do
- text '2021-01~現在: '
- a(href: 'https://www.dgcircus.com/', target: '_blank', rel: 'noreferrer') do
- text 'デジタルサーカス株式会社'
+ section do
+ h2 { text '仕事' }
+ ul do
+ li do
+ text '2021-01~現在: '
+ a href: 'https://www.dgcircus.com/', target: '_blank', rel: 'noreferrer' do
+ text 'デジタルサーカス株式会社'
+ end
end
end
end
- end
- section do
- h2 { text '登壇' }
- ul do
- sorted_slides.each do |slide|
- slide_url = "https://#{config.sites.slides.fqdn}#{slide.href}"
- slide_date = Revision.date_to_string(GeneratorUtils.published_date(slide))
- li do
- a(href: slide_url) do
- text "#{slide_date}: #{slide.event} (#{slide.talk_type})"
+ section do
+ h2 { text '登壇' }
+ ul do
+ sorted_slides.each do |slide|
+ slide_url = "https://#{config.sites.slides.fqdn}#{slide.href}"
+ slide_date = Revision.date_to_string(GeneratorUtils.published_date(slide))
+ li do
+ a href: slide_url do
+ text "#{slide_date}: #{slide.event} (#{slide.talk_type})"
+ end
end
end
end
@@ -75,10 +80,10 @@ module Nuldoc
end
end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/atom_page.rb b/services/nuldoc/lib/nuldoc/pages/atom_page.rb
index e9f9541c..bf49f548 100644
--- a/services/nuldoc/lib/nuldoc/pages/atom_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/atom_page.rb
@@ -1,24 +1,29 @@
module Nuldoc
module Pages
class AtomPage
- extend DOM::AtomXML
+ def initialize(feed:)
+ @feed = feed
+ end
- def self.render(feed:)
- feed(xmlns: 'http://www.w3.org/2005/Atom') do
- id { text feed.id }
- title { text feed.title }
- link(rel: 'alternate', href: feed.link_to_alternate)
- link(rel: 'self', href: feed.link_to_self)
- author { name { text feed.author } }
- updated { text feed.updated }
- feed.entries.each do |entry|
- entry do
- id { text entry.id }
- link(rel: 'alternate', href: entry.link_to_alternate)
- title { text entry.title }
- summary { text entry.summary }
- published { text entry.published }
- updated { text entry.updated }
+ def render
+ feed = @feed
+ DOM::AtomXMLBuilder.new.build do
+ feed xmlns: 'http://www.w3.org/2005/Atom' do
+ id { text feed.id }
+ title { text feed.title }
+ link(rel: 'alternate', href: feed.link_to_alternate)
+ link(rel: 'self', href: feed.link_to_self)
+ author { name { text feed.author } }
+ updated { text feed.updated }
+ feed.entries.each do |entry|
+ entry do
+ id { text entry.id }
+ link(rel: 'alternate', href: entry.link_to_alternate)
+ title { text entry.title }
+ summary { text entry.summary }
+ published { text entry.published }
+ updated { text entry.updated }
+ end
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/home_page.rb b/services/nuldoc/lib/nuldoc/pages/home_page.rb
index 405197d1..22c33c08 100644
--- a/services/nuldoc/lib/nuldoc/pages/home_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/home_page.rb
@@ -1,45 +1,50 @@
module Nuldoc
module Pages
class HomePage
- extend DOM::HTML
+ def initialize(config:)
+ @config = config
+ end
- def self.render(config:)
- Components::PageLayout.render(
+ def render
+ config = @config
+ Components::PageLayout.new(
meta_copyright_year: config.site.copyright_year,
meta_description: 'nsfisis のサイト',
meta_title: config.sites.default.site_name,
meta_atom_feed_href: "https://#{config.sites.default.fqdn}/atom.xml",
site: 'default',
config: config,
- children: body(class: 'single') do
- Components::DefaultGlobalHeader.render(config: config)
- main(class: 'main') do
- article(class: 'post-single') do
- article(class: 'post-entry') do
- a(href: "https://#{config.sites.about.fqdn}/") do
- header(class: 'entry-header') { h2 { text 'About' } }
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'single' do
+ render(Components::DefaultGlobalHeader, config: config)
+ main class: 'main' do
+ article class: 'post-single' do
+ article class: 'post-entry' do
+ a href: "https://#{config.sites.about.fqdn}/" do
+ header(class: 'entry-header') { h2 { text 'About' } }
+ end
end
- end
- article(class: 'post-entry') do
- a(href: "https://#{config.sites.blog.fqdn}/posts/") do
- header(class: 'entry-header') { h2 { text 'Blog' } }
+ article class: 'post-entry' do
+ a href: "https://#{config.sites.blog.fqdn}/posts/" do
+ header(class: 'entry-header') { h2 { text 'Blog' } }
+ end
end
- end
- article(class: 'post-entry') do
- a(href: "https://#{config.sites.slides.fqdn}/slides/") do
- header(class: 'entry-header') { h2 { text 'Slides' } }
+ article class: 'post-entry' do
+ a href: "https://#{config.sites.slides.fqdn}/slides/" do
+ header(class: 'entry-header') { h2 { text 'Slides' } }
+ end
end
- end
- article(class: 'post-entry') do
- a(href: "https://repos.#{config.sites.default.fqdn}/") do
- header(class: 'entry-header') { h2 { text 'Repositories' } }
+ article class: 'post-entry' do
+ a href: "https://repos.#{config.sites.default.fqdn}/" do
+ header(class: 'entry-header') { h2 { text 'Repositories' } }
+ end
end
end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/not_found_page.rb b/services/nuldoc/lib/nuldoc/pages/not_found_page.rb
index 08a9f2f5..b7b80285 100644
--- a/services/nuldoc/lib/nuldoc/pages/not_found_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/not_found_page.rb
@@ -1,9 +1,14 @@
module Nuldoc
module Pages
class NotFoundPage
- extend DOM::HTML
+ def initialize(site:, config:)
+ @site = site
+ @config = config
+ end
- def self.render(site:, config:)
+ def render
+ site = @site
+ config = @config
global_header = case site
when 'about' then Components::AboutGlobalHeader
when 'blog' then Components::BlogGlobalHeader
@@ -13,20 +18,22 @@ module Nuldoc
site_entry = config.site_entry(site)
- Components::PageLayout.render(
+ Components::PageLayout.new(
meta_copyright_year: config.site.copyright_year,
meta_description: 'リクエストされたページが見つかりません',
meta_title: "Page Not Found|#{site_entry.site_name}",
site: site,
config: config,
- children: body(class: 'single') do
- global_header.render(config: config)
- main(class: 'main') do
- article { div(class: 'not-found') { text '404' } }
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'single' do
+ render(global_header, config: config)
+ main class: 'main' do
+ article { div(class: 'not-found') { text '404' } }
+ end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/post_list_page.rb b/services/nuldoc/lib/nuldoc/pages/post_list_page.rb
index dfa77b6f..a7de1f31 100644
--- a/services/nuldoc/lib/nuldoc/pages/post_list_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/post_list_page.rb
@@ -1,34 +1,45 @@
module Nuldoc
module Pages
class PostListPage
- extend DOM::HTML
+ def initialize(posts:, config:, current_page:, total_pages:)
+ @posts = posts
+ @config = config
+ @current_page = current_page
+ @total_pages = total_pages
+ end
- def self.render(posts:, config:, current_page:, total_pages:)
+ def render
+ posts = @posts
+ config = @config
+ current_page = @current_page
+ total_pages = @total_pages
page_title = '投稿一覧'
page_info_suffix = " (#{current_page}ページ目)"
meta_title = "#{page_title}#{page_info_suffix}|#{config.sites.blog.site_name}"
meta_description = "投稿した記事の一覧#{page_info_suffix}"
- Components::PageLayout.render(
+ Components::PageLayout.new(
meta_copyright_year: config.site.copyright_year,
meta_description: meta_description,
meta_title: meta_title,
meta_atom_feed_href: "https://#{config.sites.blog.fqdn}/posts/atom.xml",
site: 'blog',
config: config,
- children: body(class: 'list') do
- Components::BlogGlobalHeader.render(config: config)
- main(class: 'main') do
- header(class: 'page-header') { h1 { text "#{page_title}#{page_info_suffix}" } }
- Components::Pagination.render(current_page: current_page, total_pages: total_pages,
- base_path: '/posts/')
- posts.each { |post| Components::PostPageEntry.render(post: post, config: config) }
- Components::Pagination.render(current_page: current_page, total_pages: total_pages,
- base_path: '/posts/')
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'list' do
+ render(Components::BlogGlobalHeader, config: config)
+ main class: 'main' do
+ header(class: 'page-header') { h1 { text "#{page_title}#{page_info_suffix}" } }
+ render(Components::Pagination, current_page: current_page, total_pages: total_pages,
+ base_path: '/posts/')
+ posts.each { |post| render(Components::PostPageEntry, post: post, config: config) }
+ render(Components::Pagination, current_page: current_page, total_pages: total_pages,
+ base_path: '/posts/')
+ end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/post_page.rb b/services/nuldoc/lib/nuldoc/pages/post_page.rb
index d98dcd5d..bcd5a644 100644
--- a/services/nuldoc/lib/nuldoc/pages/post_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/post_page.rb
@@ -1,53 +1,60 @@
module Nuldoc
module Pages
class PostPage
- extend DOM::HTML
+ def initialize(doc:, config:)
+ @doc = doc
+ @config = config
+ end
- def self.render(doc:, config:)
- Components::PageLayout.render(
+ def render
+ doc = @doc
+ config = @config
+ Components::PageLayout.new(
meta_copyright_year: GeneratorUtils.published_date(doc).year,
meta_description: doc.description,
meta_keywords: doc.tags.map { |slug| config.tag_label(slug) },
meta_title: "#{doc.title}|#{config.sites.blog.site_name}",
site: 'blog',
config: config,
- children: body(class: 'single') do
- Components::BlogGlobalHeader.render(config: config)
- main(class: 'main') do
- article(class: 'post-single') do
- header(class: 'post-header') do
- h1(class: 'post-title') { text doc.title }
- if doc.tags.length.positive?
- ul(class: 'post-tags') do
- doc.tags.each do |slug|
- li(class: 'tag') do
- a(class: 'tag-inner', href: "/tags/#{slug}/") { text config.tag_label(slug) }
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'single' do
+ render(Components::BlogGlobalHeader, config: config)
+ main class: 'main' do
+ article class: 'post-single' do
+ header class: 'post-header' do
+ h1(class: 'post-title') { text doc.title }
+ if doc.tags.length.positive?
+ ul class: 'post-tags' do
+ doc.tags.each do |slug|
+ li class: 'tag' do
+ a(class: 'tag-inner', href: "/tags/#{slug}/") { text config.tag_label(slug) }
+ end
end
end
end
end
- end
- Components::TableOfContents.render(toc: doc.toc) if doc.toc && doc.toc.items.length.positive?
- div(class: 'post-content') do
- section(id: 'changelog') do
- h2 { a(href: '#changelog') { text '更新履歴' } }
- ol do
- doc.revisions.each do |rev|
- ds = Revision.date_to_string(rev.date)
- li(class: 'revision') do
- time(datetime: ds) { text ds }
- text ": #{rev.remark}"
+ render(Components::TableOfContents, toc: doc.toc) if doc.toc && doc.toc.items.length.positive?
+ div class: 'post-content' do
+ section id: 'changelog' do
+ h2 { a(href: '#changelog') { text '更新履歴' } }
+ ol do
+ doc.revisions.each do |rev|
+ ds = Revision.date_to_string(rev.date)
+ li class: 'revision' do
+ time(datetime: ds) { text ds }
+ text ": #{rev.remark}"
+ end
end
end
end
+ child(*doc.root.children[0].children)
end
- child(*doc.root.children[0].children)
end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/slide_list_page.rb b/services/nuldoc/lib/nuldoc/pages/slide_list_page.rb
index 86a5fb40..62ca6552 100644
--- a/services/nuldoc/lib/nuldoc/pages/slide_list_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/slide_list_page.rb
@@ -1,30 +1,36 @@
module Nuldoc
module Pages
class SlideListPage
- extend DOM::HTML
+ def initialize(slides:, config:)
+ @slides = slides
+ @config = config
+ end
- def self.render(slides:, config:)
+ def render
+ config = @config
page_title = 'スライド一覧'
- sorted = slides.sort_by { |s| GeneratorUtils.published_date(s) }.reverse
+ sorted = @slides.sort_by { |s| GeneratorUtils.published_date(s) }.reverse
- Components::PageLayout.render(
+ Components::PageLayout.new(
meta_copyright_year: config.site.copyright_year,
meta_description: '登壇したイベントで使用したスライドの一覧',
meta_title: "#{page_title}|#{config.sites.slides.site_name}",
meta_atom_feed_href: "https://#{config.sites.slides.fqdn}/slides/atom.xml",
site: 'slides',
config: config,
- children: body(class: 'list') do
- Components::SlidesGlobalHeader.render(config: config)
- main(class: 'main') do
- header(class: 'page-header') { h1 { text page_title } }
- sorted.each do |slide|
- Components::SlidePageEntry.render(slide: slide, config: config)
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'list' do
+ render(Components::SlidesGlobalHeader, config: config)
+ main class: 'main' do
+ header(class: 'page-header') { h1 { text page_title } }
+ sorted.each do |slide|
+ render(Components::SlidePageEntry, slide: slide, config: config)
+ end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/slide_page.rb b/services/nuldoc/lib/nuldoc/pages/slide_page.rb
index 0259c1e4..0e76599d 100644
--- a/services/nuldoc/lib/nuldoc/pages/slide_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/slide_page.rb
@@ -1,75 +1,81 @@
module Nuldoc
module Pages
class SlidePage
- extend DOM::HTML
+ def initialize(slide:, config:)
+ @slide = slide
+ @config = config
+ end
- def self.render(slide:, config:)
- Components::PageLayout.render(
+ def render
+ slide = @slide
+ config = @config
+ Components::PageLayout.new(
meta_copyright_year: GeneratorUtils.published_date(slide).year,
meta_description: "「#{slide.title}」(#{slide.event} で登壇)",
meta_keywords: slide.tags.map { |slug| config.tag_label(slug) },
meta_title: "#{slide.title} (#{slide.event})|#{config.sites.slides.site_name}",
site: 'slides',
config: config,
- children: body(class: 'single') do
- Components::StaticStylesheet.render(site: 'slides', file_name: '/slides.css', config: config)
- Components::SlidesGlobalHeader.render(config: config)
- main(class: 'main') do
- article(class: 'post-single') do
- header(class: 'post-header') do
- h1(class: 'post-title') { text slide.title }
- if slide.tags.length.positive?
- ul(class: 'post-tags') do
- slide.tags.each do |slug|
- li(class: 'tag') do
- a(class: 'tag-inner', href: "/tags/#{slug}/") { text config.tag_label(slug) }
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'single' do
+ render(Components::StaticStylesheet, site: 'slides', file_name: '/slides.css', config: config)
+ render(Components::SlidesGlobalHeader, config: config)
+ main class: 'main' do
+ article class: 'post-single' do
+ header class: 'post-header' do
+ h1(class: 'post-title') { text slide.title }
+ if slide.tags.length.positive?
+ ul class: 'post-tags' do
+ slide.tags.each do |slug|
+ li class: 'tag' do
+ a(class: 'tag-inner', href: "/tags/#{slug}/") { text config.tag_label(slug) }
+ end
end
end
end
end
- end
- div(class: 'post-content') do
- section(id: 'changelog') do
- h2 { a(href: '#changelog') { text '更新履歴' } }
- ol do
- slide.revisions.each do |rev|
- ds = Revision.date_to_string(rev.date)
- li(class: 'revision') do
- time(datetime: ds) { text ds }
- text ": #{rev.remark}"
+ div class: 'post-content' do
+ section id: 'changelog' do
+ h2 { a(href: '#changelog') { text '更新履歴' } }
+ ol do
+ slide.revisions.each do |rev|
+ ds = Revision.date_to_string(rev.date)
+ li class: 'revision' do
+ time(datetime: ds) { text ds }
+ text ": #{rev.remark}"
+ end
end
end
end
- end
- canvas(id: 'slide', 'data-slide-link': slide.slide_link)
- div(class: 'controllers') do
- div(class: 'controllers-buttons') do
- button(id: 'prev', type: 'button') do
- elem('svg', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none',
- stroke: 'currentColor', 'stroke-width': '2') do
- elem('path', d: 'M15 18l-6-6 6-6')
+ canvas(id: 'slide', 'data-slide-link': slide.slide_link)
+ div class: 'controllers' do
+ div class: 'controllers-buttons' do
+ button id: 'prev', type: 'button' do
+ elem 'svg', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none',
+ stroke: 'currentColor', 'stroke-width': '2' do
+ elem('path', d: 'M15 18l-6-6 6-6')
+ end
end
- end
- button(id: 'next', type: 'button') do
- elem('svg', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none',
- stroke: 'currentColor', 'stroke-width': '2') do
- elem('path', d: 'M9 18l6-6-6-6')
+ button id: 'next', type: 'button' do
+ elem 'svg', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none',
+ stroke: 'currentColor', 'stroke-width': '2' do
+ elem('path', d: 'M9 18l6-6-6-6')
+ end
end
end
end
+ render(Components::StaticScript,
+ site: 'slides',
+ file_name: '/slide.js',
+ type: 'module',
+ config: config)
end
- Components::StaticScript.render(
- site: 'slides',
- file_name: '/slide.js',
- type: 'module',
- config: config
- )
end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/tag_list_page.rb b/services/nuldoc/lib/nuldoc/pages/tag_list_page.rb
index 16b3df05..0d8dab2a 100644
--- a/services/nuldoc/lib/nuldoc/pages/tag_list_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/tag_list_page.rb
@@ -1,42 +1,50 @@
module Nuldoc
module Pages
class TagListPage
- extend DOM::HTML
+ def initialize(tags:, site:, config:)
+ @tags = tags
+ @site = site
+ @config = config
+ end
- def self.render(tags:, site:, config:)
+ def render
+ site = @site
+ config = @config
page_title = 'タグ一覧'
global_header = site == 'blog' ? Components::BlogGlobalHeader : Components::SlidesGlobalHeader
site_entry = config.site_entry(site)
- sorted_tags = tags.sort_by(&:tag_slug)
+ sorted_tags = @tags.sort_by(&:tag_slug)
- Components::PageLayout.render(
+ Components::PageLayout.new(
meta_copyright_year: config.site.copyright_year,
meta_description: 'タグの一覧',
meta_title: "#{page_title}|#{site_entry.site_name}",
site: site,
config: config,
- children: body(class: 'list') do
- global_header.render(config: config)
- main(class: 'main') do
- header(class: 'page-header') { h1 { text page_title } }
- sorted_tags.each do |tag|
- posts_text = tag.num_of_posts.zero? ? '' : "#{tag.num_of_posts}件の記事"
- slides_text = tag.num_of_slides.zero? ? '' : "#{tag.num_of_slides}件のスライド"
- separator = !posts_text.empty? && !slides_text.empty? ? '、' : ''
- footer_text = "#{posts_text}#{separator}#{slides_text}"
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'list' do
+ render(global_header, config: config)
+ main class: 'main' do
+ header(class: 'page-header') { h1 { text page_title } }
+ sorted_tags.each do |tag|
+ posts_text = tag.num_of_posts.zero? ? '' : "#{tag.num_of_posts}件の記事"
+ slides_text = tag.num_of_slides.zero? ? '' : "#{tag.num_of_slides}件のスライド"
+ separator = !posts_text.empty? && !slides_text.empty? ? '、' : ''
+ footer_text = "#{posts_text}#{separator}#{slides_text}"
- article(class: 'post-entry') do
- a(href: tag.href) do
- header(class: 'entry-header') { h2 { text tag.tag_label } }
- footer(class: 'entry-footer') { text footer_text }
+ article class: 'post-entry' do
+ a href: tag.href do
+ header(class: 'entry-header') { h2 { text tag.tag_label } }
+ footer(class: 'entry-footer') { text footer_text }
+ end
end
end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/tag_page.rb b/services/nuldoc/lib/nuldoc/pages/tag_page.rb
index 4bc08a8c..f0946abd 100644
--- a/services/nuldoc/lib/nuldoc/pages/tag_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/tag_page.rb
@@ -1,38 +1,48 @@
module Nuldoc
module Pages
class TagPage
- extend DOM::HTML
+ def initialize(tag_slug:, pages:, site:, config:)
+ @tag_slug = tag_slug
+ @pages = pages
+ @site = site
+ @config = config
+ end
- def self.render(tag_slug:, pages:, site:, config:)
- tag_label = config.tag_label(tag_slug)
+ def render
+ config = @config
+ site = @site
+ pages = @pages
+ tag_label = config.tag_label(@tag_slug)
page_title = "タグ「#{tag_label}」一覧"
global_header = site == 'blog' ? Components::BlogGlobalHeader : Components::SlidesGlobalHeader
site_entry = config.site_entry(site)
- Components::PageLayout.render(
+ Components::PageLayout.new(
meta_copyright_year: GeneratorUtils.published_date(pages.last).year,
meta_description: "タグ「#{tag_label}」のついた記事またはスライドの一覧",
meta_keywords: [tag_label],
meta_title: "#{page_title}|#{site_entry.site_name}",
- meta_atom_feed_href: "https://#{site_entry.fqdn}/tags/#{tag_slug}/atom.xml",
+ meta_atom_feed_href: "https://#{site_entry.fqdn}/tags/#{@tag_slug}/atom.xml",
site: site,
config: config,
- children: body(class: 'list') do
- global_header.render(config: config)
- main(class: 'main') do
- header(class: 'page-header') { h1 { text page_title } }
- pages.each do |page|
- if page.respond_to?(:event)
- Components::SlidePageEntry.render(slide: page, config: config)
- else
- Components::PostPageEntry.render(post: page, config: config)
+ children: DOM::HTMLBuilder.new.build do
+ body class: 'list' do
+ render(global_header, config: config)
+ main class: 'main' do
+ header(class: 'page-header') { h1 { text page_title } }
+ pages.each do |page|
+ if page.respond_to?(:event)
+ render(Components::SlidePageEntry, slide: page, config: config)
+ else
+ render(Components::PostPageEntry, post: page, config: config)
+ end
end
end
+ render(Components::GlobalFooter, config: config)
end
- Components::GlobalFooter.render(config: config)
end
- )
+ ).build
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/renderers/html.rb b/services/nuldoc/lib/nuldoc/renderers/html.rb
index 89cd0ede..d71692e5 100644
--- a/services/nuldoc/lib/nuldoc/renderers/html.rb
+++ b/services/nuldoc/lib/nuldoc/renderers/html.rb
@@ -87,7 +87,7 @@ module Nuldoc
when :text
text_node_to_html(node, _indent_level: indent_level, is_in_pre: is_in_pre)
when :raw
- node.html
+ node.content
when :element
element_node_to_html(node, indent_level: indent_level, is_in_pre: is_in_pre)
end
diff --git a/services/nuldoc/lib/nuldoc/renderers/xml.rb b/services/nuldoc/lib/nuldoc/renderers/xml.rb
index a1494003..7eaac5c4 100644
--- a/services/nuldoc/lib/nuldoc/renderers/xml.rb
+++ b/services/nuldoc/lib/nuldoc/renderers/xml.rb
@@ -23,7 +23,7 @@ module Nuldoc
when :text
text_node_to_xml(node)
when :raw
- node.html
+ node.content
when :element
element_node_to_xml(node, indent_level: indent_level)
end