diff --git a/assets/stylesheets/pages/_threejs.scss b/assets/stylesheets/pages/_threejs.scss new file mode 100644 index 00000000..32b39ce1 --- /dev/null +++ b/assets/stylesheets/pages/_threejs.scss @@ -0,0 +1,56 @@ +._threejs { + // Code blocks + pre, code { + background-color: #f5f5f5; + border-radius: 3px; + padding: 0.2em 0.4em; + } + + pre { + padding: 1em; + margin: 1em 0; + overflow: auto; + + code { + background: none; + padding: 0; + } + } + + // Links + a { + color: #049EF4; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + + // Headings + h2 { + margin-top: 2em; + padding-bottom: 0.3em; + border-bottom: 1px solid #eaecef; + } + + h3 { + margin-top: 1.5em; + } + + // Tables + table { + border-collapse: collapse; + margin: 1em 0; + width: 100%; + } + + th, td { + border: 1px solid #dfe2e5; + padding: 6px 13px; + } + + tr:nth-child(2n) { + background-color: #f6f8fa; + } +} \ No newline at end of file diff --git a/docs/file-scrapers.md b/docs/file-scrapers.md index 2f8fd8f3..cdfed337 100644 --- a/docs/file-scrapers.md +++ b/docs/file-scrapers.md @@ -289,3 +289,11 @@ it to `docs/sqlite` ```sh curl https://sqlite.org/2022/sqlite-doc-3400000.zip | bsdtar --extract --file - --directory=docs/sqlite/ --strip-components=1 ``` + +## Three.js + +```sh +git clone --depth 1 --branch r${VERSION} https://github.com/mrdoob/three.js.git +mv three.js/docs/ docs/threejs~${VERSION}/ +rm -rf three.js/ +``` diff --git a/lib/docs/filters/threejs/clean_html.rb b/lib/docs/filters/threejs/clean_html.rb new file mode 100644 index 00000000..5a15b7b9 --- /dev/null +++ b/lib/docs/filters/threejs/clean_html.rb @@ -0,0 +1,348 @@ +module Docs + class Threejs + class CleanHtmlFilter < Filter + def call + # Remove unnecessary elements + css('head, script, style').remove + + # Add syntax highlighting CSS + style = doc.document.create_element('style') + style.content = <<-CSS + .highlight { background: #272b30; color: #e9ecef; border-radius: 4px; margin: 1em 0; } + .highlight pre { margin: 0; padding: 10px; } + .highlight .k { color: #cc7832; font-weight: bold; } /* Keyword */ + .highlight .kd { color: #cc7832; font-weight: bold; } /* Keyword.Declaration */ + .highlight .nb { color: #6897bb; } /* Name.Builtin */ + .highlight .nx { color: #ffc66d; } /* Name.Other */ + .highlight .nf { color: #ffc66d; } /* Name.Function */ + .highlight .mi { color: #6897bb; } /* Literal.Number.Integer */ + .highlight .s1 { color: #6a8759; } /* Literal.String.Single */ + .highlight .s2 { color: #6a8759; } /* Literal.String.Double */ + .highlight .c1 { color: #808080; font-style: italic; } /* Comment.Single */ + .highlight .lineno { color: #606366; margin-right: 10px; -webkit-user-select: none; user-select: none; } + .highlight-javascript { padding: 0; } + + /* Method signatures */ + .sig { padding: 5px 10px; } + .sig-name { color: #ffc66d; } + .sig-param { color: #e9ecef; } + .sig-param .sig-type { color: #6897bb; } + .sig-returns { color: #cc7832; } + .sig-returns .sig-type { color: #6897bb; } + .sig-paren { color: #e9ecef; } + .property .pre { color: #cc7832; } + + /* Inline code */ + code.literal { background: #2b2b2b; padding: 2px 4px; border-radius: 3px; } + code.literal .pre { color: #e9ecef; } + + /* Links */ + .reference { color: #6897bb; text-decoration: none; } + .reference:hover { text-decoration: underline; } + .reference.external { color: #6a8759; } + + /* Notes */ + .admonition.note { background: #2b2b2b; padding: 12px 15px; border-left: 4px solid #6897bb; margin: 1em 0; } + .admonition-title { color: #6897bb; font-weight: bold; margin: 0 0 5px 0; } + CSS + doc.at_css('head') ? doc.at_css('head').add_child(style) : doc.add_child(style) + + # Create a wrapper div for better styling + if root = at_css('body') + content = root.inner_html + else + content = doc.inner_html + end + + # Create Django-like structure + content = <<-HTML +
#{name}
"
+ end
+
+ node.inner_html = content
+ end
+
+ # Clean up property formatting
+ css('h3').each do |node|
+ node.inner_html = node.inner_html.gsub(/\[property:([^\]]+?)\s+([^\]]+?)\]/) do |match|
+ type, name = $1, $2
+ "#{text}
"
+ end
+
+ # Handle internal page links without text
+ node.inner_html = node.inner_html.gsub(/\[page:([^\]]+?)\]/) do |match|
+ path = $1
+ "#{path}
"
+ end
+ end
+
+ # Fix all href attributes to be lowercase and remove .html
+ css('a[href]').each do |link|
+ next if link['href'].start_with?('http')
+ link['href'] = link['href'].remove('../').downcase.sub(/\.html$/, '')
+ link['class'] = 'reference internal'
+ end
+
+ # Add section classes
+ css('h2').each do |node|
+ node['class'] = 'section-title'
+ section = node.next_element
+ if section
+ wrapper = doc.document.create_element('div')
+ wrapper['class'] = 'section'
+ node.after(wrapper)
+ wrapper.add_child(node)
+ current = section
+ while current && current.name != 'h2'
+ next_el = current.next
+ wrapper.add_child(current)
+ current = next_el
+ end
+ end
+ end
+
+ # Format description paragraphs
+ css('p.desc').each do |node|
+ node['class'] = 'section-desc'
+ end
+
+ # Handle inline code/backticks in text
+ css('p, li, dt, dd').each do |node|
+ next if node.at_css('pre') # Skip if contains a code block
+
+ # Replace backticks with proper code formatting
+ node.inner_html = node.inner_html.gsub(/`([^`]+)`/) do |match|
+ code = $1
+ "#{code}
"
+ end
+ end
+
+ # Handle inline code in property descriptions
+ css('.property-type').each do |node|
+ node.inner_html = node.inner_html.gsub(/`([^`]+)`/) do |match|
+ code = $1
+ "#{code}
"
+ end
+ end
+
+ # Clean up code blocks
+ css('pre').each do |node|
+ wrapper = doc.document.create_element('div')
+ wrapper['class'] = 'highlight'
+ node.replace(wrapper)
+
+ div = doc.document.create_element('div')
+ div['class'] = 'highlight-javascript notranslate'
+ wrapper.add_child(div)
+
+ pre = doc.document.create_element('pre')
+ pre['class'] = ''
+ div.add_child(pre)
+
+ # Format the code content
+ code = node.content.strip
+
+ # Add syntax highlighting spans
+ highlighted_code = highlight_javascript(code)
+
+ pre.inner_html = highlighted_code
+ end
+
+ # Add proper heading IDs and classes
+ css('h1, h2, h3, h4').each do |node|
+ node['id'] ||= node.content.strip.downcase.gsub(/[^\w]+/, '-')
+ existing_class = node['class'].to_s
+ node['class'] = "#{existing_class} section-header"
+ end
+
+ # Remove interactive examples
+ css('.threejs_example_container').remove
+
+ # Add note styling
+ css('p').each do |node|
+ if node.content.start_with?('Note:')
+ wrapper = doc.document.create_element('div')
+ wrapper['class'] = 'admonition note'
+
+ title = doc.document.create_element('p')
+ title['class'] = 'first admonition-title'
+ title.content = 'Note'
+
+ content = doc.document.create_element('p')
+ content['class'] = 'last'
+ content.inner_html = node.inner_html.sub('Note:', '').strip
+
+ wrapper.add_child(title)
+ wrapper.add_child(content)
+ node.replace(wrapper)
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ def highlight_javascript(code)
+ code = code.gsub(/\b(function|return|var|let|const|if|else|for|while|do|switch|case|break|continue|new|try|catch|throw|this|typeof|instanceof|in|of|class|extends|super|import|export|default|null|undefined|true|false)\b/, '\1') # keywords
+ code = code.gsub(/\b(\d+(\.\d+)?)\b/, '\1') # numbers
+ code = code.gsub(/'([^']*)'/, '\'\1\'') # single quotes
+ code = code.gsub(/"([^"]*)"/, '"\1"') # double quotes
+ code = code.gsub(/`([^`]*)`/, '`\1`') # template literals
+ code = code.gsub(/\/\/[^\n]*/, '\0') # single line comments
+ code = code.gsub(/\b(console|document|window|Array|Object|String|Number|Boolean|Function|Symbol|Map|Set|Promise|async|await)\b/, '\1') # built-ins
+ code = code.gsub(/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/, '\1(') # function calls
+ code = code.gsub(/\b(addEventListener|querySelector|getElementById|setTimeout|setInterval)\b/, '\1') # common methods
+
+ # Add line numbers
+ lines = code.split("\n")
+ numbered_lines = lines.map.with_index(1) do |line, i|
+ "#{i}#{line}"
+ end
+
+ numbered_lines.join("\n")
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/docs/filters/threejs/entries.rb b/lib/docs/filters/threejs/entries.rb
new file mode 100644
index 00000000..fa14628e
--- /dev/null
+++ b/lib/docs/filters/threejs/entries.rb
@@ -0,0 +1,54 @@
+module Docs
+ class Threejs
+ class EntriesFilter < Docs::EntriesFilter
+ def get_name
+ # Try to get name from the title first
+ if title = at_css('.lesson-title h1')&.content
+ title
+ else
+ # Fallback to path-based name for API docs
+ slug.split('/').last.gsub('.html', '').titleize
+ end
+ end
+
+ def get_type
+ if slug.start_with?('api/en/')
+ # For API documentation, use the section as type
+ # e.g. "api/en/animation/AnimationAction" -> "Animation"
+ path_parts = slug.split('/')
+ if path_parts.length >= 3
+ path_parts[2].titleize
+ else
+ 'API'
+ end
+ elsif slug.start_with?('manual/en/')
+ # For manual pages, get the section from the path
+ # e.g. "manual/en/introduction/Creating-a-scene" -> "Introduction"
+ path_parts = slug.split('/')
+ if path_parts.length >= 3
+ path_parts[2].titleize
+ else
+ 'Manual'
+ end
+ else
+ 'Other'
+ end
+ end
+
+ def additional_entries
+ entries = []
+
+ # Get all methods and properties from h3 headings
+ css('h3').each do |node|
+ name = node.content.strip
+ # Skip if it's a constructor or doesn't have an ID
+ next if name == get_name || !node['id']
+
+ entries << [name, node['id'], get_type]
+ end
+
+ entries
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/docs/scrapers/threejs.rb b/lib/docs/scrapers/threejs.rb
new file mode 100644
index 00000000..9f6a27cc
--- /dev/null
+++ b/lib/docs/scrapers/threejs.rb
@@ -0,0 +1,85 @@
+module Docs
+ class Threejs < FileScraper
+ self.name = 'Three.js'
+ self.type = 'threejs'
+ self.slug = 'threejs'
+ self.links = {
+ home: 'https://threejs.org/',
+ code: 'https://github.com/mrdoob/three.js'
+ }
+
+ html_filters.push 'threejs/entries', 'threejs/clean_html'
+
+ # The content is directly in the body
+ options[:container] = 'body'
+
+ options[:skip] = %w(
+ prettify.js
+ lesson.js
+ lang.css
+ lesson.css
+ editor.html
+ list.js
+ page.js
+ )
+
+ options[:only_patterns] = [
+ /\Aapi\/en\/.+\.html/, # API documentation
+ /\Amanual\/en\/.+\.html/ # Manual pages
+ ]
+
+ options[:skip_patterns] = [
+ /examples/,
+ /\A_/,
+ /\Aresources\//,
+ /\Ascenes\//
+ ]
+
+ options[:attribution] = <<-HTML
+ © 2010–#{Time.current.year} Three.js Authors