diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-01 01:11:04 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-01 01:30:59 +0900 |
| commit | 5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70 (patch) | |
| tree | d82a35ab80b6f44813b0ebc9f5e7ca2c29ea300b | |
| parent | 6dedddc545e2f1930bdc2256784eb1551bd4231d (diff) | |
| download | nsfisis.dev-5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70.tar.gz nsfisis.dev-5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70.tar.zst nsfisis.dev-5d8e7205ccbf8d7baaf4133faec49a44c5ae3c70.zip | |
refactor(nuldoc): more fluent DOM DSL
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' }, "© #{config.site.copyright_year} #{config.site.author}") + footer(class: 'footer') { text "© #{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' => "© #{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: "© #{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) |
