aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-01 01:11:04 +0900
committernsfisis <nsfisis@gmail.com>2026-02-01 01:30:59 +0900
commit5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70 (patch)
treed82a35ab80b6f44813b0ebc9f5e7ca2c29ea300b
parent6dedddc545e2f1930bdc2256784eb1551bd4231d (diff)
downloadnsfisis.dev-5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70.tar.gz
nsfisis.dev-5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70.tar.zst
nsfisis.dev-5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70.zip
refactor(nuldoc): more fluent DOM DSL
-rw-r--r--services/nuldoc/lib/nuldoc.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/components/global_footer.rb4
-rw-r--r--services/nuldoc/lib/nuldoc/components/global_headers.rb66
-rw-r--r--services/nuldoc/lib/nuldoc/components/page_layout.rb45
-rw-r--r--services/nuldoc/lib/nuldoc/components/pagination.rb39
-rw-r--r--services/nuldoc/lib/nuldoc/components/post_page_entry.rb29
-rw-r--r--services/nuldoc/lib/nuldoc/components/slide_page_entry.rb29
-rw-r--r--services/nuldoc/lib/nuldoc/components/static_script.rb10
-rw-r--r--services/nuldoc/lib/nuldoc/components/static_stylesheet.rb4
-rw-r--r--services/nuldoc/lib/nuldoc/components/table_of_contents.rb16
-rw-r--r--services/nuldoc/lib/nuldoc/components/tag_list.rb14
-rw-r--r--services/nuldoc/lib/nuldoc/dom.rb99
-rw-r--r--services/nuldoc/lib/nuldoc/dom/atom_xml.rb26
-rw-r--r--services/nuldoc/lib/nuldoc/dom/html.rb56
-rw-r--r--services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb93
-rw-r--r--services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb36
-rw-r--r--services/nuldoc/lib/nuldoc/markdown/transform.rb29
-rw-r--r--services/nuldoc/lib/nuldoc/pages/about_page.rb119
-rw-r--r--services/nuldoc/lib/nuldoc/pages/atom_page.rb36
-rw-r--r--services/nuldoc/lib/nuldoc/pages/home_page.rb48
-rw-r--r--services/nuldoc/lib/nuldoc/pages/not_found_page.rb14
-rw-r--r--services/nuldoc/lib/nuldoc/pages/post_list_page.rb25
-rw-r--r--services/nuldoc/lib/nuldoc/pages/post_page.rb66
-rw-r--r--services/nuldoc/lib/nuldoc/pages/slide_list_page.rb20
-rw-r--r--services/nuldoc/lib/nuldoc/pages/slide_page.rb108
-rw-r--r--services/nuldoc/lib/nuldoc/pages/tag_list_page.rb36
-rw-r--r--services/nuldoc/lib/nuldoc/pages/tag_page.rb28
-rw-r--r--services/nuldoc/lib/nuldoc/render.rb4
-rw-r--r--services/nuldoc/lib/nuldoc/renderers/html.rb2
-rw-r--r--services/nuldoc/lib/nuldoc/renderers/xml.rb2
30 files changed, 643 insertions, 462 deletions
diff --git a/services/nuldoc/lib/nuldoc.rb b/services/nuldoc/lib/nuldoc.rb
index 2cd2a032..99c1b9fd 100644
--- a/services/nuldoc/lib/nuldoc.rb
+++ b/services/nuldoc/lib/nuldoc.rb
@@ -10,6 +10,8 @@ require 'toml-rb'
require 'webrick'
require_relative 'nuldoc/dom'
+require_relative 'nuldoc/dom/atom_xml'
+require_relative 'nuldoc/dom/html'
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 8d4143fb..879aac55 100644
--- a/services/nuldoc/lib/nuldoc/components/global_footer.rb
+++ b/services/nuldoc/lib/nuldoc/components/global_footer.rb
@@ -1,10 +1,10 @@
module Nuldoc
module Components
class GlobalFooter
- extend Dom
+ extend DOM::HTML
def self.render(config:)
- footer({ 'class' => 'footer' }, "&copy; #{config.site.copyright_year} #{config.site.author}")
+ 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 b06c6173..fb19096a 100644
--- a/services/nuldoc/lib/nuldoc/components/global_headers.rb
+++ b/services/nuldoc/lib/nuldoc/components/global_headers.rb
@@ -1,53 +1,65 @@
module Nuldoc
module Components
class DefaultGlobalHeader
- extend Dom
+ extend DOM::HTML
def self.render(config:)
- header({ 'class' => 'header' },
- div({ 'class' => 'site-logo' },
- a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev')))
+ header(class: 'header') do
+ div(class: 'site-logo') do
+ a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ end
+ end
end
end
class AboutGlobalHeader
- extend Dom
+ extend DOM::HTML
def self.render(config:)
- header({ 'class' => 'header' },
- div({ 'class' => 'site-logo' },
- a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev')))
+ header(class: 'header') do
+ div(class: 'site-logo') do
+ a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ end
+ end
end
end
class BlogGlobalHeader
- extend Dom
+ extend DOM::HTML
def self.render(config:)
- header({ 'class' => 'header' },
- div({ 'class' => 'site-logo' },
- a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev')),
- div({ 'class' => 'site-name' }, config.sites.blog.site_name),
- nav({ 'class' => 'nav' },
- ul({},
- li({}, a({ 'href' => "https://#{config.sites.about.fqdn}/" }, 'About')),
- li({}, a({ 'href' => '/posts/' }, 'Posts')),
- li({}, a({ 'href' => '/tags/' }, 'Tags')))))
+ header(class: 'header') do
+ div(class: 'site-logo') do
+ a(href: "https://#{config.sites.default.fqdn}/") { text 'nsfisis.dev' }
+ end
+ 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: '/posts/') { text 'Posts' } }
+ li { a(href: '/tags/') { text 'Tags' } }
+ end
+ end
+ end
end
end
class SlidesGlobalHeader
- extend Dom
+ extend DOM::HTML
def self.render(config:)
- header({ 'class' => 'header' },
- div({ 'class' => 'site-logo' },
- a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev')),
- nav({ 'class' => 'nav' },
- ul({},
- li({}, a({ 'href' => "https://#{config.sites.about.fqdn}/" }, 'About')),
- li({}, a({ 'href' => '/slides/' }, 'Slides')),
- li({}, a({ 'href' => '/tags/' }, 'Tags')))))
+ header(class: 'header') do
+ div(class: 'site-logo') do
+ 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: '/slides/') { text 'Slides' } }
+ li { a(href: '/tags/') { text 'Tags' } }
+ end
+ end
+ end
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/page_layout.rb b/services/nuldoc/lib/nuldoc/components/page_layout.rb
index 5d14ec0d..4ddd0968 100644
--- a/services/nuldoc/lib/nuldoc/components/page_layout.rb
+++ b/services/nuldoc/lib/nuldoc/components/page_layout.rb
@@ -1,34 +1,33 @@
module Nuldoc
module Components
class PageLayout
- extend Dom
+ extend DOM::HTML
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)
- elem('html', { 'lang' => 'ja-JP' },
- elem('head', {},
- 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_keywords && !meta_keywords.empty? ? meta({ 'name' => 'keywords',
- 'content' => meta_keywords.join(',') }) : nil,
- meta({ 'property' => 'og:type', 'content' => 'article' }),
- 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' }),
- meta_atom_feed_href ? link({ 'rel' => 'alternate', 'href' => meta_atom_feed_href,
- 'type' => 'application/atom+xml' }) : nil,
- link({ 'rel' => 'icon', 'href' => '/favicon.svg', 'type' => 'image/svg+xml' }),
- elem('title', {}, meta_title),
- StaticStylesheet.render(file_name: '/style.css', config: config)),
- children)
+ 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(property: 'og:type', content: 'article')
+ 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: 'icon', href: '/favicon.svg', type: 'image/svg+xml')
+ title { text meta_title }
+ StaticStylesheet.render(file_name: '/style.css', config: config)
+ end
+ child children
+ end
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/pagination.rb b/services/nuldoc/lib/nuldoc/components/pagination.rb
index 500b81f9..61978cb1 100644
--- a/services/nuldoc/lib/nuldoc/components/pagination.rb
+++ b/services/nuldoc/lib/nuldoc/components/pagination.rb
@@ -1,29 +1,34 @@
module Nuldoc
module Components
class Pagination
- extend Dom
+ extend DOM::HTML
def self.render(current_page:, total_pages:, base_path:)
- return div({}) if total_pages <= 1
+ return div if total_pages <= 1
pages = generate_page_numbers(current_page, total_pages)
- nav({ 'class' => 'pagination' },
- div({ 'class' => 'pagination-prev' },
- current_page > 1 ? a({ 'href' => page_url_at(base_path, current_page - 1) }, '前へ') : nil),
- *pages.map do |page|
- if page == '...'
- div({ 'class' => 'pagination-elipsis' }, "\u2026")
- elsif page == current_page
- div({ 'class' => 'pagination-page pagination-page-current' },
- span({}, page.to_s))
- else
- div({ 'class' => 'pagination-page' },
- a({ 'href' => page_url_at(base_path, page) }, 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,
- div({ 'class' => 'pagination-next' },
- current_page < total_pages ? a({ 'href' => page_url_at(base_path, current_page + 1) }, '次へ') : nil))
+ end
+ end
+ div(class: 'pagination-next') do
+ a(href: page_url_at(base_path, current_page + 1)) { text '次へ' } if current_page < total_pages
+ end
+ end
end
def self.generate_page_numbers(current_page, total_pages)
diff --git a/services/nuldoc/lib/nuldoc/components/post_page_entry.rb b/services/nuldoc/lib/nuldoc/components/post_page_entry.rb
index 5232bc6b..623c2be6 100644
--- a/services/nuldoc/lib/nuldoc/components/post_page_entry.rb
+++ b/services/nuldoc/lib/nuldoc/components/post_page_entry.rb
@@ -1,24 +1,29 @@
module Nuldoc
module Components
class PostPageEntry
- extend Dom
+ extend DOM::HTML
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)
- article({ 'class' => 'post-entry' },
- a({ 'href' => post.href },
- header({ 'class' => 'entry-header' }, h2({}, post.title)),
- section({ 'class' => 'entry-content' }, p({}, post.description)),
- footer({ 'class' => 'entry-footer' },
- elem('time', { 'datetime' => published }, published),
- ' 投稿',
- has_updates ? '、' : nil,
- has_updates ? elem('time', { 'datetime' => updated }, updated) : nil,
- has_updates ? ' 更新' : nil,
- post.tags.length.positive? ? TagList.render(tags: post.tags, config: config) : nil)))
+ 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 } }
+ footer(class: 'entry-footer') do
+ time(datetime: published) { text published }
+ text ' 投稿'
+ if has_updates
+ text '、'
+ time(datetime: updated) { text updated }
+ text ' 更新'
+ end
+ TagList.render(tags: post.tags, config: config) if post.tags.length.positive?
+ end
+ end
+ end
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 b80f52c8..c78d3d23 100644
--- a/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb
+++ b/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb
@@ -1,24 +1,29 @@
module Nuldoc
module Components
class SlidePageEntry
- extend Dom
+ extend DOM::HTML
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)
- article({ 'class' => 'post-entry' },
- a({ 'href' => slide.href },
- header({ 'class' => 'entry-header' }, h2({}, slide.title)),
- section({ 'class' => 'entry-content' }, p({}, slide.description)),
- footer({ 'class' => 'entry-footer' },
- elem('time', { 'datetime' => published }, published),
- ' 登壇',
- has_updates ? '、' : nil,
- has_updates ? elem('time', { 'datetime' => updated }, updated) : nil,
- has_updates ? ' 更新' : nil,
- slide.tags.length.positive? ? TagList.render(tags: slide.tags, config: config) : nil)))
+ 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 } }
+ footer(class: 'entry-footer') do
+ time(datetime: published) { text published }
+ text ' 登壇'
+ if has_updates
+ text '、'
+ time(datetime: updated) { text updated }
+ text ' 更新'
+ end
+ TagList.render(tags: slide.tags, config: config) if slide.tags.length.positive?
+ end
+ end
+ end
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/static_script.rb b/services/nuldoc/lib/nuldoc/components/static_script.rb
index 755dc3fc..5c8af53d 100644
--- a/services/nuldoc/lib/nuldoc/components/static_script.rb
+++ b/services/nuldoc/lib/nuldoc/components/static_script.rb
@@ -1,15 +1,15 @@
module Nuldoc
module Components
class StaticScript
- extend Dom
+ extend DOM::HTML
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)
hash = ComponentUtils.calculate_file_hash(file_path)
- attrs = { 'src' => "#{file_name}?h=#{hash}" }
- attrs['type'] = type if type
- attrs['defer'] = defer if defer
- script(attrs)
+ attrs = { src: "#{file_name}?h=#{hash}" }
+ attrs[:type] = type if type
+ attrs[:defer] = defer if defer
+ script(**attrs)
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb b/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb
index 3127286d..5246ee1c 100644
--- a/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb
+++ b/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb
@@ -1,12 +1,12 @@
module Nuldoc
module Components
class StaticStylesheet
- extend Dom
+ extend DOM::HTML
def self.render(file_name:, config:, site: nil)
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 b3a5b531..0be95706 100644
--- a/services/nuldoc/lib/nuldoc/components/table_of_contents.rb
+++ b/services/nuldoc/lib/nuldoc/components/table_of_contents.rb
@@ -1,18 +1,20 @@
module Nuldoc
module Components
class TableOfContents
- extend Dom
+ extend DOM::HTML
def self.render(toc:)
- nav({ 'class' => 'toc' },
- h2({}, '目次'),
- ul({}, *toc.items.map { |entry| toc_entry_component(entry) }))
+ nav(class: 'toc') do
+ h2 { text '目次' }
+ ul { toc.items.each { |entry| toc_entry_component(entry) } }
+ end
end
def self.toc_entry_component(entry)
- li({},
- a({ 'href' => "##{entry.id}" }, entry.text),
- entry.children.length.positive? ? ul({}, *entry.children.map { |child| toc_entry_component(child) }) : nil)
+ 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
diff --git a/services/nuldoc/lib/nuldoc/components/tag_list.rb b/services/nuldoc/lib/nuldoc/components/tag_list.rb
index 0c566f32..57d11ab0 100644
--- a/services/nuldoc/lib/nuldoc/components/tag_list.rb
+++ b/services/nuldoc/lib/nuldoc/components/tag_list.rb
@@ -1,14 +1,16 @@
module Nuldoc
module Components
class TagList
- extend Dom
+ extend DOM::HTML
def self.render(tags:, config:)
- ul({ 'class' => 'entry-tags' },
- *tags.map do |slug|
- li({ 'class' => 'tag' },
- span({ 'class' => 'tag-inner' }, text(config.tag_label(slug))))
- end)
+ ul(class: 'entry-tags') do
+ tags.each do |slug|
+ li(class: 'tag') do
+ span(class: 'tag-inner') { text config.tag_label(slug) }
+ end
+ end
+ end
end
end
end
diff --git a/services/nuldoc/lib/nuldoc/dom.rb b/services/nuldoc/lib/nuldoc/dom.rb
index 7e28ac06..ec802fb6 100644
--- a/services/nuldoc/lib/nuldoc/dom.rb
+++ b/services/nuldoc/lib/nuldoc/dom.rb
@@ -11,49 +11,53 @@ module Nuldoc
def kind = :element
end
- module Dom
+ module DOM
+ CHILDREN_STACK_KEY = :__nuldoc_dom_children_stack
+ private_constant :CHILDREN_STACK_KEY
+
module_function
def text(content)
- Text.new(content: content)
+ node = Text.new(content: content)
+ _auto_append(node)
+ node
end
def raw_html(html)
- RawHTML.new(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, attributes = {}, *children)
- Element.new(
+ def elem(name, **attrs, &)
+ children = _collect_children(&)
+ node = Element.new(
name: name,
- attributes: attributes || {},
- children: flatten_children(children)
+ attributes: attrs.transform_keys(&:to_s),
+ children: children
)
+ _auto_append(node)
+ node
end
- def a(attributes = {}, *children) = elem('a', attributes, *children)
- def article(attributes = {}, *children) = elem('article', attributes, *children)
- def button(attributes = {}, *children) = elem('button', attributes, *children)
- def div(attributes = {}, *children) = elem('div', attributes, *children)
- def footer(attributes = {}, *children) = elem('footer', attributes, *children)
- def h1(attributes = {}, *children) = elem('h1', attributes, *children)
- def h2(attributes = {}, *children) = elem('h2', attributes, *children)
- def h3(attributes = {}, *children) = elem('h3', attributes, *children)
- def h4(attributes = {}, *children) = elem('h4', attributes, *children)
- def h5(attributes = {}, *children) = elem('h5', attributes, *children)
- def h6(attributes = {}, *children) = elem('h6', attributes, *children)
- def header(attributes = {}, *children) = elem('header', attributes, *children)
- def img(attributes = {}) = elem('img', attributes)
- def li(attributes = {}, *children) = elem('li', attributes, *children)
- def link(attributes = {}) = elem('link', attributes)
- def meta(attributes = {}) = elem('meta', attributes)
- def nav(attributes = {}, *children) = elem('nav', attributes, *children)
- def ol(attributes = {}, *children) = elem('ol', attributes, *children)
- def p(attributes = {}, *children) = elem('p', attributes, *children)
- def script(attributes = {}, *children) = elem('script', attributes, *children)
- def section(attributes = {}, *children) = elem('section', attributes, *children)
- def span(attributes = {}, *children) = elem('span', attributes, *children)
- def ul(attributes = {}, *children) = elem('ul', attributes, *children)
-
def add_class(element, klass)
classes = element.attributes['class']
if classes.nil?
@@ -114,23 +118,26 @@ module Nuldoc
private
- def flatten_children(children)
- result = []
- children.each do |child|
- case child
- when nil, false
- next
- when String
- result.push(text(child))
- when Array
- result.concat(flatten_children(child))
- else
- result.push(child)
- end
+ def _collect_children(&block)
+ return [] unless block
+
+ stack = Thread.current[CHILDREN_STACK_KEY] ||= []
+ stack.push([])
+ begin
+ yield
+ stack.last
+ ensure
+ stack.pop
end
- result
end
- module_function :flatten_children
+ 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
new file mode 100644
index 00000000..9ab10822
--- /dev/null
+++ b/services/nuldoc/lib/nuldoc/dom/atom_xml.rb
@@ -0,0 +1,26 @@
+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/html.rb b/services/nuldoc/lib/nuldoc/dom/html.rb
new file mode 100644
index 00000000..1d9b1cab
--- /dev/null
+++ b/services/nuldoc/lib/nuldoc/dom/html.rb
@@ -0,0 +1,56 @@
+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/markdown/parser/block_parser.rb b/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb
index 6a135b5b..583d7201 100644
--- a/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb
+++ b/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Parser
class BlockParser
- extend Dom
+ extend DOM::HTML
HeaderBlock = Struct.new(:level, :id, :attributes, :heading_element, keyword_init: true)
FootnoteBlock = Struct.new(:id, :children, keyword_init: true)
@@ -117,13 +117,13 @@ module Nuldoc
language = match[1].empty? ? nil : match[1]
meta_string = match[2].strip
- attributes = {}
- attributes['language'] = language if language
+ attrs = {}
+ attrs[:language] = language if language
if meta_string && !meta_string.empty?
filename_match = meta_string.match(/filename="([^"]+)"/)
- attributes['filename'] = filename_match[1] if filename_match
- attributes['numbered'] = 'true' if meta_string.include?('numbered')
+ attrs[:filename] = filename_match[1] if filename_match
+ attrs[:numbered] = 'true' if meta_string.include?('numbered')
end
code_lines = []
@@ -137,7 +137,7 @@ module Nuldoc
end
code = code_lines.join("\n")
- elem('codeblock', attributes, text(code))
+ elem('codeblock', **attrs) { text code }
end
def try_note_block(scanner)
@@ -148,13 +148,13 @@ module Nuldoc
block_type = match[1]
attr_string = match[2].strip
- attributes = {}
+ attrs = {}
if block_type == 'edit'
# Parse {editat="..." operation="..."}
editat_match = attr_string.match(/editat="([^"]+)"/)
operation_match = attr_string.match(/operation="([^"]+)"/)
- attributes['editat'] = editat_match[1] if editat_match
- attributes['operation'] = operation_match[1] if operation_match
+ attrs[:editat] = editat_match[1] if editat_match
+ attrs[:operation] = operation_match[1] if operation_match
end
# Collect content until :::
@@ -174,7 +174,7 @@ module Nuldoc
# 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', attributes, *child_elements)
+ elem('note', **attrs) { child(*child_elements) }
end
def try_heading(scanner)
@@ -188,7 +188,7 @@ module Nuldoc
text_before, id, attributes = Attributes.parse_trailing_attributes(raw_text)
inline_nodes = InlineParser.parse(text_before.strip)
- heading_element = elem('h', {}, *inline_nodes)
+ heading_element = elem('h') { child(*inline_nodes) }
HeaderBlock.new(level: level, id: id, attributes: attributes, heading_element: heading_element)
end
@@ -198,7 +198,7 @@ module Nuldoc
return nil unless match
scanner.advance
- elem('hr', {})
+ hr
end
def try_footnote_def(scanner)
@@ -255,11 +255,10 @@ module Nuldoc
build_table_row(cells, false, alignment)
end
- table_children = []
- table_children << elem('thead', {}, header_row)
- table_children << elem('tbody', {}, *body_rows) unless body_rows.empty?
-
- elem('table', {}, *table_children)
+ table do
+ thead { child header_row }
+ tbody { child(*body_rows) } unless body_rows.empty?
+ end
end
def parse_table_alignment(separator_line)
@@ -287,15 +286,15 @@ module Nuldoc
def build_table_row(cells, is_header, alignment)
cell_elements = cells.each_with_index.map do |cell_text, i|
- attributes = {}
+ attrs = {}
align = alignment[i]
- attributes['align'] = align if align && align != 'default'
+ attrs[:align] = align if align && align != 'default'
tag = is_header ? 'th' : 'td'
inline_nodes = InlineParser.parse(cell_text)
- elem(tag, attributes, *inline_nodes)
+ elem(tag, **attrs) { child(*inline_nodes) }
end
- elem('tr', {}, *cell_elements)
+ tr { child(*cell_elements) }
end
def try_blockquote(scanner)
@@ -312,7 +311,7 @@ module Nuldoc
children = parse_blocks(inner_scanner)
child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) }
- elem('blockquote', {}, *child_elements)
+ blockquote { child(*child_elements) }
end
def try_ordered_list(scanner)
@@ -396,14 +395,14 @@ module Nuldoc
# Determine tight/loose
is_tight = items.none? { |item| item[:has_blank] }
- attributes = {}
- attributes['__tight'] = is_tight ? 'true' : 'false'
+ attrs = {}
+ attrs[:__tight] = is_tight ? 'true' : 'false'
# Check for task list items
is_task_list = false
if type == :unordered
is_task_list = items.any? { |item| item[:lines].first&.match?(/^\[[ xX]\]\s/) }
- attributes['type'] = 'task' if is_task_list
+ attrs[:type] = 'task' if is_task_list
end
list_items = items.map do |item|
@@ -411,20 +410,20 @@ module Nuldoc
end
if type == :ordered
- ol(attributes, *list_items)
+ ol(**attrs) { child(*list_items) }
else
- ul(attributes, *list_items)
+ ul(**attrs) { child(*list_items) }
end
end
def build_list_item(item, is_task_list)
- attributes = {}
+ attrs = {}
content = item[:lines].join("\n")
if is_task_list
task_match = content.match(/^\[( |[xX])\]\s(.*)$/m)
if task_match
- attributes['checked'] = task_match[1] == ' ' ? 'false' : 'true'
+ attrs[:checked] = task_match[1] == ' ' ? 'false' : 'true'
content = task_match[2]
end
end
@@ -435,9 +434,12 @@ module Nuldoc
# 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) }
- child_elements = [p({}, *InlineParser.parse(content))] if child_elements.empty?
+ if child_elements.empty?
+ inline_nodes = InlineParser.parse(content)
+ child_elements = [p { child(*inline_nodes) }]
+ end
- li(attributes, *child_elements)
+ li(**attrs) { child(*child_elements) }
end
def try_html_block(scanner)
@@ -464,24 +466,26 @@ module Nuldoc
attr_str = inner_match[1]
inner_content = inner_match[2].strip
- attributes = {}
+ attrs = {}
attr_str.scan(/([\w-]+)="([^"]*)"/) do |key, value|
- attributes[key] = value
+ attrs[key.to_sym] = value
end
if inner_content.empty?
- div(attributes)
+ div(**attrs)
else
inner_scanner = LineScanner.new(inner_content)
children = parse_blocks(inner_scanner)
- child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) }
- div(attributes, *child_elements)
+ 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
else
- div({ 'class' => 'raw-html' }, raw_html(html_content))
+ div(class: 'raw-html') { raw_html html_content }
end
else
- div({ 'class' => 'raw-html' }, raw_html(html_content))
+ div(class: 'raw-html') { raw_html html_content }
end
end
@@ -513,7 +517,7 @@ module Nuldoc
text_content = lines.join("\n")
inline_nodes = InlineParser.parse(text_content)
- p({}, *inline_nodes)
+ p { child(*inline_nodes) }
end
# --- Section hierarchy ---
@@ -526,13 +530,13 @@ module Nuldoc
unless footnote_blocks.empty?
footnote_elements = footnote_blocks.map do |fb|
- elem('footnote', { 'id' => fb.id }, *fb.children)
+ elem('footnote', id: fb.id) { child(*fb.children) }
end
- footnote_section = section({ 'class' => 'footnotes' }, *footnote_elements)
+ footnote_section = section(class: 'footnotes') { child(*footnote_elements) }
article_content.push(footnote_section)
end
- elem('__root__', {}, article({}, *article_content))
+ elem('__root__') { article { child(*article_content) } }
end
def build_section_hierarchy(blocks)
@@ -594,7 +598,10 @@ module Nuldoc
attributes = section_info[:attributes].dup
attributes['id'] = section_info[:id] if section_info[:id]
- section(attributes, section_info[:heading], *section_info[:children])
+ section(**attributes.transform_keys(&:to_sym)) do
+ child section_info[:heading]
+ child(*section_info[:children])
+ end
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 3d6f9ac7..c1715904 100644
--- a/services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb
+++ b/services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Parser
class InlineParser
- extend Dom
+ extend DOM::HTML
class << self
INLINE_HTML_TAGS = %w[del mark sub sup ins br].freeze
@@ -80,7 +80,7 @@ module Nuldoc
url = text[(pos + 1)...close]
return nil unless url.match?(%r{^https?://\S+$})
- nodes << a({ 'href' => url, 'class' => 'url' }, text(url))
+ nodes << a(href: url, class: 'url') { text url }
close + 1
end
@@ -103,7 +103,7 @@ module Nuldoc
# Strip one leading and one trailing space if both present
content = content[1...-1] if content.length >= 2 && content[0] == ' ' && content[-1] == ' '
- nodes << elem('code', {}, text(content))
+ nodes << code { text content }
close_pos + tick_count
end
@@ -143,7 +143,7 @@ module Nuldoc
inner = text[(pos + open_tag.length)...close_pos]
children = parse_inline(inner, 0, inner.length)
- nodes << elem(tag, {}, *children)
+ nodes << elem(tag) { child(*children) }
return close_pos + close_tag.length
end
@@ -168,12 +168,12 @@ module Nuldoc
inner = text[(bracket_close + 2)...paren_close].strip
url, title = parse_url_title(inner)
- attributes = {}
- attributes['src'] = url if url
- attributes['alt'] = alt unless alt.empty?
- attributes['title'] = title if title
+ attrs = {}
+ attrs[:src] = url if url
+ attrs[:alt] = alt unless alt.empty?
+ attrs[:title] = title if title
- nodes << img(attributes)
+ nodes << img(**attrs)
paren_close + 1
end
@@ -194,9 +194,9 @@ module Nuldoc
inner = text[(bracket_close + 2)...paren_close].strip
url, title = parse_url_title(inner)
- attributes = {}
- attributes['href'] = url if url
- attributes['title'] = title if title
+ attrs = {}
+ attrs[:href] = url if url
+ attrs[:title] = title if title
children = parse_inline(link_text, 0, link_text.length)
@@ -204,9 +204,9 @@ module Nuldoc
is_autolink = children.length == 1 &&
children[0].kind == :text &&
children[0].content == url
- attributes['class'] = 'url' if is_autolink
+ attrs[:class] = 'url' if is_autolink
- nodes << a(attributes, *children)
+ nodes << a(**attrs) { child(*children) }
paren_close + 1
end
@@ -221,7 +221,7 @@ module Nuldoc
return nil if inner.include?('[') || inner.include?(']')
return nil if inner.empty?
- nodes << elem('footnoteref', { 'reference' => inner })
+ nodes << elem('footnoteref', reference: inner)
close + 1
end
@@ -233,7 +233,7 @@ module Nuldoc
inner = text[(pos + 2)...close]
children = parse_inline(inner, 0, inner.length)
- nodes << elem('strong', {}, *children)
+ nodes << strong { child(*children) }
close + 2
end
@@ -253,7 +253,7 @@ module Nuldoc
return nil if inner.empty?
children = parse_inline(inner, 0, inner.length)
- nodes << elem('em', {}, *children)
+ nodes << em { child(*children) }
return i + 1
end
else
@@ -271,7 +271,7 @@ module Nuldoc
inner = text[(pos + 2)...close]
children = parse_inline(inner, 0, inner.length)
- nodes << elem('del', {}, *children)
+ nodes << del { child(*children) }
close + 2
end
diff --git a/services/nuldoc/lib/nuldoc/markdown/transform.rb b/services/nuldoc/lib/nuldoc/markdown/transform.rb
index 45be7ddd..76968fb8 100644
--- a/services/nuldoc/lib/nuldoc/markdown/transform.rb
+++ b/services/nuldoc/lib/nuldoc/markdown/transform.rb
@@ -1,6 +1,6 @@
module Nuldoc
class Transform
- include Dom
+ include DOM::HTML
def self.to_html(doc)
new(doc).to_html
@@ -89,7 +89,7 @@ module Nuldoc
break
end
nodes.push(text(match[1])) unless match[1].empty?
- nodes.push(a({ 'href' => match[2], 'class' => 'url' }, text(match[2])))
+ nodes.push(a(href: match[2], class: 'url') { text match[2] })
rest = match[3]
end
nodes
@@ -148,7 +148,7 @@ module Nuldoc
raise '[nuldoc.tohtml] <h> element must be inside <section>' unless current_section
section_id = current_section.attributes['id']
- a_element = a({}, *c.children)
+ a_element = a { child(*c.children) }
a_element.attributes['href'] = "##{section_id}"
c.children.replace([a_element])
end
@@ -181,9 +181,10 @@ module Nuldoc
operation_attr = n.attributes['operation']
is_edit_block = editat_attr && operation_attr
- label_element = div({ 'class' => 'admonition-label' },
- text(is_edit_block ? "#{editat_attr} #{operation_attr}" : 'NOTE'))
- content_element = div({ 'class' => 'admonition-content' }, *n.children.dup)
+ label_element = div(class: 'admonition-label') do
+ 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])
@@ -218,14 +219,10 @@ module Nuldoc
n.attributes.delete('reference')
n.attributes['class'] = 'footnote'
n.children.replace([
- a(
- {
- 'id' => "footnoteref--#{reference}",
- 'class' => 'footnote',
- 'href' => "#footnote--#{reference}"
- },
- text("[#{footnote_number}]")
- )
+ a(id: "footnoteref--#{reference}", class: 'footnote',
+ href: "#footnote--#{reference}") do
+ text "[#{footnote_number}]"
+ end
])
end
@@ -246,7 +243,7 @@ module Nuldoc
old_children = n.children.dup
n.children.replace([
- a({ 'href' => "#footnoteref--#{id}" }, text("#{footnote_number}. ")),
+ a(href: "#footnoteref--#{id}") { text "#{footnote_number}. " },
*old_children
])
end
@@ -304,7 +301,7 @@ module Nuldoc
if filename
n.attributes.delete('filename')
n.children.replace([
- div({ 'class' => 'filename' }, text(filename)),
+ div(class: 'filename') { text filename },
raw_html(highlighted)
])
else
diff --git a/services/nuldoc/lib/nuldoc/pages/about_page.rb b/services/nuldoc/lib/nuldoc/pages/about_page.rb
index 7dfe7e6a..755ec233 100644
--- a/services/nuldoc/lib/nuldoc/pages/about_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/about_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class AboutPage
- extend Dom
+ extend DOM::HTML
def self.render(slides:, config:)
sorted_slides = slides.sort_by { |s| GeneratorUtils.published_date(s) }.reverse
@@ -12,57 +12,72 @@ module Nuldoc
meta_title: "About|#{config.sites.about.site_name}",
site: 'about',
config: config,
- children: elem('body', { 'class' => 'single' },
- Components::AboutGlobalHeader.render(config: config),
- elem('main', { 'class' => 'main' },
- article({ 'class' => 'post-single' },
- header({ 'class' => 'post-header' },
- h1({ 'class' => 'post-title' }, 'nsfisis'),
- div({ 'class' => 'my-icon' },
- div({ 'id' => 'myIcon' },
- img({ 'src' => '/favicon.svg' })),
- Components::StaticScript.render(
- site: 'about',
- file_name: '/my-icon.js',
- defer: 'true',
- config: config
- ))),
- div({ 'class' => 'post-content' },
- section({},
- h2({}, '読み方'),
- p({}, '読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。')),
- section({},
- h2({}, 'アカウント'),
- ul({},
- li({}, a({ 'href' => 'https://twitter.com/nsfisis',
- 'target' => '_blank',
- 'rel' => 'noreferrer' },
- 'Twitter (現 𝕏): @nsfisis')),
- li({}, a({ 'href' => 'https://github.com/nsfisis',
- 'target' => '_blank',
- 'rel' => 'noreferrer' },
- 'GitHub: @nsfisis')))),
- section({},
- h2({}, '仕事'),
- ul({},
- li({}, '2021-01~現在: ',
- a({ 'href' => 'https://www.dgcircus.com/',
- 'target' => '_blank',
- 'rel' => 'noreferrer' },
- 'デジタルサーカス株式会社')))),
- section({},
- h2({}, '登壇'),
- ul({},
- *sorted_slides.map do |slide|
- slide_url = "https://#{config.sites.slides.fqdn}#{slide.href}"
- slide_date = Revision.date_to_string(
- GeneratorUtils.published_date(slide)
- )
- li({},
- a({ 'href' => slide_url },
- "#{slide_date}: #{slide.event} (#{slide.talk_type})"))
- end))))),
- Components::GlobalFooter.render(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 '読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。' }
+ 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
+ li do
+ a(href: 'https://github.com/nsfisis', target: '_blank', rel: 'noreferrer') do
+ text 'GitHub: @nsfisis'
+ 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 'デジタルサーカス株式会社'
+ 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})"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ Components::GlobalFooter.render(config: config)
+ end
)
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/atom_page.rb b/services/nuldoc/lib/nuldoc/pages/atom_page.rb
index 68c21193..e9f9541c 100644
--- a/services/nuldoc/lib/nuldoc/pages/atom_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/atom_page.rb
@@ -1,25 +1,27 @@
module Nuldoc
module Pages
class AtomPage
- extend Dom
+ extend DOM::AtomXML
def self.render(feed:)
- elem('feed', { 'xmlns' => 'http://www.w3.org/2005/Atom' },
- elem('id', {}, feed.id),
- elem('title', {}, feed.title),
- link({ 'rel' => 'alternate', 'href' => feed.link_to_alternate }),
- link({ 'rel' => 'self', 'href' => feed.link_to_self }),
- elem('author', {}, elem('name', {}, feed.author)),
- elem('updated', {}, feed.updated),
- *feed.entries.map do |entry|
- elem('entry', {},
- elem('id', {}, entry.id),
- link({ 'rel' => 'alternate', 'href' => entry.link_to_alternate }),
- elem('title', {}, entry.title),
- elem('summary', {}, entry.summary),
- elem('published', {}, entry.published),
- elem('updated', {}, entry.updated))
- end)
+ 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
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/home_page.rb b/services/nuldoc/lib/nuldoc/pages/home_page.rb
index 3a7df7cc..405197d1 100644
--- a/services/nuldoc/lib/nuldoc/pages/home_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/home_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class HomePage
- extend Dom
+ extend DOM::HTML
def self.render(config:)
Components::PageLayout.render(
@@ -11,24 +11,34 @@ module Nuldoc
meta_atom_feed_href: "https://#{config.sites.default.fqdn}/atom.xml",
site: 'default',
config: config,
- children: elem('body', { 'class' => 'single' },
- Components::DefaultGlobalHeader.render(config: config),
- elem('main', { 'class' => 'main' },
- article({ 'class' => 'post-single' },
- article({ 'class' => 'post-entry' },
- a({ 'href' => "https://#{config.sites.about.fqdn}/" },
- header({ 'class' => 'entry-header' }, h2({}, 'About')))),
- article({ 'class' => 'post-entry' },
- a({ 'href' => "https://#{config.sites.blog.fqdn}/posts/" },
- header({ 'class' => 'entry-header' }, h2({}, 'Blog')))),
- article({ 'class' => 'post-entry' },
- a({ 'href' => "https://#{config.sites.slides.fqdn}/slides/" },
- header({ 'class' => 'entry-header' }, h2({}, 'Slides')))),
- article({ 'class' => 'post-entry' },
- a({ 'href' => "https://repos.#{config.sites.default.fqdn}/" },
- header({ 'class' => 'entry-header' },
- h2({}, 'Repositories')))))),
- Components::GlobalFooter.render(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' } }
+ end
+ end
+ article(class: 'post-entry') do
+ a(href: "https://#{config.sites.blog.fqdn}/posts/") do
+ header(class: 'entry-header') { h2 { text 'Blog' } }
+ end
+ end
+ article(class: 'post-entry') do
+ a(href: "https://#{config.sites.slides.fqdn}/slides/") do
+ header(class: 'entry-header') { h2 { text 'Slides' } }
+ end
+ end
+ 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
+ Components::GlobalFooter.render(config: config)
+ 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 53023e47..08a9f2f5 100644
--- a/services/nuldoc/lib/nuldoc/pages/not_found_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/not_found_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class NotFoundPage
- extend Dom
+ extend DOM::HTML
def self.render(site:, config:)
global_header = case site
@@ -19,11 +19,13 @@ module Nuldoc
meta_title: "Page Not Found|#{site_entry.site_name}",
site: site,
config: config,
- children: elem('body', { 'class' => 'single' },
- global_header.render(config: config),
- elem('main', { 'class' => 'main' },
- article({}, div({ 'class' => 'not-found' }, '404'))),
- Components::GlobalFooter.render(config: config))
+ children: body(class: 'single') do
+ global_header.render(config: config)
+ main(class: 'main') do
+ article { div(class: 'not-found') { text '404' } }
+ end
+ Components::GlobalFooter.render(config: config)
+ 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 c6978735..dfa77b6f 100644
--- a/services/nuldoc/lib/nuldoc/pages/post_list_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/post_list_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class PostListPage
- extend Dom
+ extend DOM::HTML
def self.render(posts:, config:, current_page:, total_pages:)
page_title = '投稿一覧'
@@ -16,17 +16,18 @@ module Nuldoc
meta_atom_feed_href: "https://#{config.sites.blog.fqdn}/posts/atom.xml",
site: 'blog',
config: config,
- children: elem('body', { 'class' => 'list' },
- Components::BlogGlobalHeader.render(config: config),
- elem('main', { 'class' => 'main' },
- header({ 'class' => 'page-header' },
- h1({}, "#{page_title}#{page_info_suffix}")),
- Components::Pagination.render(current_page: current_page, total_pages: total_pages,
- base_path: '/posts/'),
- *posts.map { |post| Components::PostPageEntry.render(post: post, config: config) },
- Components::Pagination.render(current_page: current_page, total_pages: total_pages,
- base_path: '/posts/')),
- Components::GlobalFooter.render(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/')
+ end
+ Components::GlobalFooter.render(config: config)
+ end
)
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/post_page.rb b/services/nuldoc/lib/nuldoc/pages/post_page.rb
index a8ccda17..d98dcd5d 100644
--- a/services/nuldoc/lib/nuldoc/pages/post_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/post_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class PostPage
- extend Dom
+ extend DOM::HTML
def self.render(doc:, config:)
Components::PageLayout.render(
@@ -11,34 +11,42 @@ module Nuldoc
meta_title: "#{doc.title}|#{config.sites.blog.site_name}",
site: 'blog',
config: config,
- children: elem('body', { 'class' => 'single' },
- Components::BlogGlobalHeader.render(config: config),
- elem('main', { 'class' => 'main' },
- article({ 'class' => 'post-single' },
- header({ 'class' => 'post-header' },
- h1({ 'class' => 'post-title' }, doc.title),
- doc.tags.length.positive? ? ul({ 'class' => 'post-tags' },
- *doc.tags.map do |slug|
- li({ 'class' => 'tag' },
- a({ 'class' => 'tag-inner',
- 'href' => "/tags/#{slug}/" },
- config.tag_label(slug)))
- end) : nil),
- if doc.toc && doc.toc.items.length.positive?
- Components::TableOfContents.render(toc: doc.toc)
- end,
- div({ 'class' => 'post-content' },
- section({ 'id' => 'changelog' },
- h2({}, a({ 'href' => '#changelog' }, '更新履歴')),
- ol({},
- *doc.revisions.map do |rev|
- ds = Revision.date_to_string(rev.date)
- li({ 'class' => 'revision' },
- elem('time', { 'datetime' => ds }, ds),
- ": #{rev.remark}")
- end)),
- *doc.root.children[0].children))),
- Components::GlobalFooter.render(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) }
+ 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}"
+ end
+ end
+ end
+ end
+ child(*doc.root.children[0].children)
+ end
+ end
+ end
+ Components::GlobalFooter.render(config: config)
+ 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 9dc25d30..86a5fb40 100644
--- a/services/nuldoc/lib/nuldoc/pages/slide_list_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/slide_list_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class SlideListPage
- extend Dom
+ extend DOM::HTML
def self.render(slides:, config:)
page_title = 'スライド一覧'
@@ -14,14 +14,16 @@ module Nuldoc
meta_atom_feed_href: "https://#{config.sites.slides.fqdn}/slides/atom.xml",
site: 'slides',
config: config,
- children: elem('body', { 'class' => 'list' },
- Components::SlidesGlobalHeader.render(config: config),
- elem('main', { 'class' => 'main' },
- header({ 'class' => 'page-header' }, h1({}, page_title)),
- *sorted.map do |slide|
- Components::SlidePageEntry.render(slide: slide, config: config)
- end),
- Components::GlobalFooter.render(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)
+ end
+ end
+ Components::GlobalFooter.render(config: config)
+ end
)
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/slide_page.rb b/services/nuldoc/lib/nuldoc/pages/slide_page.rb
index ac54bb85..0259c1e4 100644
--- a/services/nuldoc/lib/nuldoc/pages/slide_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/slide_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class SlidePage
- extend Dom
+ extend DOM::HTML
def self.render(slide:, config:)
Components::PageLayout.render(
@@ -11,54 +11,64 @@ module Nuldoc
meta_title: "#{slide.title} (#{slide.event})|#{config.sites.slides.site_name}",
site: 'slides',
config: config,
- children: elem('body', { 'class' => 'single' },
- Components::StaticStylesheet.render(site: 'slides', file_name: '/slides.css',
- config: config),
- Components::SlidesGlobalHeader.render(config: config),
- elem('main', { 'class' => 'main' },
- article({ 'class' => 'post-single' },
- header({ 'class' => 'post-header' },
- h1({ 'class' => 'post-title' }, slide.title),
- slide.tags.length.positive? ? ul({ 'class' => 'post-tags' },
- *slide.tags.map do |slug|
- li({ 'class' => 'tag' },
- a({ 'class' => 'tag-inner',
- 'href' => "/tags/#{slug}/" },
- config.tag_label(slug)))
- end) : nil),
- div({ 'class' => 'post-content' },
- section({ 'id' => 'changelog' },
- h2({}, a({ 'href' => '#changelog' }, '更新履歴')),
- ol({},
- *slide.revisions.map do |rev|
- ds = Revision.date_to_string(rev.date)
- li({ 'class' => 'revision' },
- elem('time', { 'datetime' => ds }, ds),
- ": #{rev.remark}")
- end)),
- elem('canvas',
- { 'id' => 'slide', 'data-slide-link' => slide.slide_link }),
- div({ 'class' => 'controllers' },
- div({ 'class' => 'controllers-buttons' },
- button({ 'id' => 'prev', 'type' => 'button' },
- elem('svg', { 'width' => '20', 'height' => '20',
- 'viewBox' => '0 0 24 24', 'fill' => 'none',
- 'stroke' => 'currentColor',
- 'stroke-width' => '2' },
- elem('path', { 'd' => 'M15 18l-6-6 6-6' }))),
- button({ 'id' => 'next', 'type' => 'button' },
- elem('svg', { 'width' => '20', 'height' => '20',
- 'viewBox' => '0 0 24 24', 'fill' => 'none',
- 'stroke' => 'currentColor',
- 'stroke-width' => '2' },
- elem('path', { 'd' => 'M9 18l6-6-6-6' }))))),
- Components::StaticScript.render(
- site: 'slides',
- file_name: '/slide.js',
- type: 'module',
- config: config
- )))),
- Components::GlobalFooter.render(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) }
+ 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}"
+ 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')
+ 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')
+ end
+ end
+ end
+ end
+ Components::StaticScript.render(
+ site: 'slides',
+ file_name: '/slide.js',
+ type: 'module',
+ config: config
+ )
+ end
+ end
+ end
+ Components::GlobalFooter.render(config: config)
+ 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 612a50b5..16b3df05 100644
--- a/services/nuldoc/lib/nuldoc/pages/tag_list_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/tag_list_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class TagListPage
- extend Dom
+ extend DOM::HTML
def self.render(tags:, site:, config:)
page_title = 'タグ一覧'
@@ -16,22 +16,26 @@ module Nuldoc
meta_title: "#{page_title}|#{site_entry.site_name}",
site: site,
config: config,
- children: elem('body', { 'class' => 'list' },
- global_header.render(config: config),
- elem('main', { 'class' => 'main' },
- header({ 'class' => 'page-header' }, h1({}, page_title)),
- *sorted_tags.map 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: 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}"
- article({ 'class' => 'post-entry' },
- a({ 'href' => tag.href },
- header({ 'class' => 'entry-header' }, h2({}, tag.tag_label)),
- footer({ 'class' => 'entry-footer' }, footer_text)))
- end),
- Components::GlobalFooter.render(config: config))
+ 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
+ Components::GlobalFooter.render(config: config)
+ end
)
end
end
diff --git a/services/nuldoc/lib/nuldoc/pages/tag_page.rb b/services/nuldoc/lib/nuldoc/pages/tag_page.rb
index 38c55652..4bc08a8c 100644
--- a/services/nuldoc/lib/nuldoc/pages/tag_page.rb
+++ b/services/nuldoc/lib/nuldoc/pages/tag_page.rb
@@ -1,7 +1,7 @@
module Nuldoc
module Pages
class TagPage
- extend Dom
+ extend DOM::HTML
def self.render(tag_slug:, pages:, site:, config:)
tag_label = config.tag_label(tag_slug)
@@ -18,18 +18,20 @@ module Nuldoc
meta_atom_feed_href: "https://#{site_entry.fqdn}/tags/#{tag_slug}/atom.xml",
site: site,
config: config,
- children: elem('body', { 'class' => 'list' },
- global_header.render(config: config),
- elem('main', { 'class' => 'main' },
- header({ 'class' => 'page-header' }, h1({}, page_title)),
- *pages.map do |page|
- if page.respond_to?(:event)
- Components::SlidePageEntry.render(slide: page, config: config)
- else
- Components::PostPageEntry.render(post: page, config: config)
- end
- end),
- Components::GlobalFooter.render(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)
+ end
+ end
+ end
+ Components::GlobalFooter.render(config: config)
+ end
)
end
end
diff --git a/services/nuldoc/lib/nuldoc/render.rb b/services/nuldoc/lib/nuldoc/render.rb
index facd670b..39cd25bf 100644
--- a/services/nuldoc/lib/nuldoc/render.rb
+++ b/services/nuldoc/lib/nuldoc/render.rb
@@ -3,9 +3,9 @@ module Nuldoc
def render(root, renderer_type)
case renderer_type
when :html
- HtmlRenderer.new.render(root)
+ HTMLRenderer.new.render(root)
when :xml
- XmlRenderer.new.render(root)
+ XMLRenderer.new.render(root)
else
raise "Unknown renderer: #{renderer_type}"
end
diff --git a/services/nuldoc/lib/nuldoc/renderers/html.rb b/services/nuldoc/lib/nuldoc/renderers/html.rb
index 956750c5..89cd0ede 100644
--- a/services/nuldoc/lib/nuldoc/renderers/html.rb
+++ b/services/nuldoc/lib/nuldoc/renderers/html.rb
@@ -1,5 +1,5 @@
module Nuldoc
- class HtmlRenderer
+ class HTMLRenderer
DTD = {
'a' => { type: :inline },
'article' => { type: :block },
diff --git a/services/nuldoc/lib/nuldoc/renderers/xml.rb b/services/nuldoc/lib/nuldoc/renderers/xml.rb
index c27fe256..a1494003 100644
--- a/services/nuldoc/lib/nuldoc/renderers/xml.rb
+++ b/services/nuldoc/lib/nuldoc/renderers/xml.rb
@@ -1,5 +1,5 @@
module Nuldoc
- class XmlRenderer
+ class XMLRenderer
BLOCK_ELEMENTS = %w[feed entry author].freeze
def render(root)