mirror of https://github.com/freeCodeCamp/devdocs
parent
c14d91e966
commit
2f0ad7c09f
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
#{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
HTML
|
||||
|
||||
doc.inner_html = content
|
||||
|
||||
# Handle source links
|
||||
css('h2').each do |node|
|
||||
if node.content.strip == 'Source'
|
||||
content = node.next_element&.inner_html
|
||||
if content
|
||||
# Clean up any existing formatting
|
||||
content = content.gsub(/<[^>]+>/, '')
|
||||
# Extract the path from the content
|
||||
if content =~ /src\/(.*?)\.js/
|
||||
path = "/#{$1}.js"
|
||||
formatted_link = %Q(<a class="reference external" href="https://github.com/mrdoob/three.js/blob/master/src#{path}">src#{path}</a>)
|
||||
node.next_element.inner_html = formatted_link if node.next_element
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Handle method signatures
|
||||
css('h3').each do |node|
|
||||
content = node.inner_html
|
||||
|
||||
# Handle [method:this methodName]( param1, param2, ... ) format
|
||||
content = content.gsub(/\[method:this\s+([^\]]+)\]\s*\((.*?)\)/) do |match|
|
||||
method_name, params_str = $1, $2
|
||||
|
||||
# Format parameters
|
||||
params = params_str.split(',').map do |param|
|
||||
param = param.strip
|
||||
if param.include?(' ')
|
||||
type, name = param.split(' ', 2).map(&:strip)
|
||||
"<span class='sig-param'><span class='sig-type'>#{type}</span> <span class='sig-name'>#{name}</span></span>"
|
||||
else
|
||||
"<span class='sig-param'>#{param}</span>"
|
||||
end
|
||||
end.join("<span class='sig-paren'>, </span>")
|
||||
|
||||
"<dt class='sig sig-object js' id='#{method_name}'>" \
|
||||
"<span class='property'><span class='pre'>this</span></span>." \
|
||||
"<span class='sig-name descname'>#{method_name}</span>" \
|
||||
"<span class='sig-paren'>(</span>" \
|
||||
"#{params}" \
|
||||
"<span class='sig-paren'>)</span></dt>"
|
||||
end
|
||||
|
||||
# Handle [method:returnType methodName]( param1, param2, ... ) format
|
||||
content = content.gsub(/\[method:([^\s\]]+)\s+([^\]]+)\]\s*\((.*?)\)/) do |match|
|
||||
return_type, method_name, params_str = $1, $2, $3
|
||||
next if method_name.start_with?('this') # Skip if already handled above
|
||||
|
||||
# Format parameters
|
||||
params = params_str.split(',').map do |param|
|
||||
param = param.strip
|
||||
if param.include?(' ')
|
||||
type, name = param.split(' ', 2).map(&:strip)
|
||||
"<span class='sig-param'><span class='sig-type'>#{type}</span> <span class='sig-name'>#{name}</span></span>"
|
||||
else
|
||||
"<span class='sig-param'>#{param}</span>"
|
||||
end
|
||||
end.join("<span class='sig-paren'>, </span>")
|
||||
|
||||
"<dt class='sig sig-object js' id='#{method_name}'>" \
|
||||
"<span class='sig-name descname'>#{method_name}</span>" \
|
||||
"<span class='sig-paren'>(</span>" \
|
||||
"#{params}" \
|
||||
"<span class='sig-paren'>)</span>" \
|
||||
"<span class='sig-returns'><span class='sig-colon'>:</span> " \
|
||||
"<span class='sig-type'>#{return_type}</span></span></dt>"
|
||||
end
|
||||
|
||||
# Handle [method:returnType methodName] format (no parameters)
|
||||
content = content.gsub(/\[method:([^\s\]]+)\s+([^\]]+)\](?!\()/) do |match|
|
||||
return_type, method_name = $1, $2
|
||||
"<dt class='sig sig-object js' id='#{method_name}'>" \
|
||||
"<span class='sig-name descname'>#{method_name}</span>" \
|
||||
"<span class='sig-paren'>(</span>" \
|
||||
"<span class='sig-paren'>)</span>" \
|
||||
"<span class='sig-returns'><span class='sig-colon'>:</span> " \
|
||||
"<span class='sig-type'>#{return_type}</span></span></dt>"
|
||||
end
|
||||
|
||||
node.inner_html = content
|
||||
end
|
||||
|
||||
# Handle [name] placeholders in headers and constructor
|
||||
css('h1, h3').each do |node|
|
||||
content = node.inner_html
|
||||
|
||||
# Replace [name] with class name
|
||||
content = content.gsub(/\[name\]/) do
|
||||
name = slug.split('/').last.gsub('.html', '')
|
||||
"<span class='descname'>#{name}</span>"
|
||||
end
|
||||
|
||||
# Format constructor parameters
|
||||
content = content.gsub(/\[param:([^\]]+?)\s+([^\]]+?)\]/) do |match|
|
||||
type, name = $1, $2
|
||||
"<span class='sig-param'><span class='sig-type'>#{type}</span> <code class='sig-name'>#{name}</code></span>"
|
||||
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
|
||||
"<dt class='sig sig-object js'>" \
|
||||
"<span class='sig-name descname'>#{name}</span>" \
|
||||
"<span class='sig-colon'>:</span> " \
|
||||
"<span class='sig-type'>#{type}</span></dt>"
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up external links
|
||||
css('*').each do |node|
|
||||
next if node.text?
|
||||
|
||||
# Handle example links [example:tag Title]
|
||||
node.inner_html = node.inner_html.gsub(/\[example:([^\s\]]+)\s+([^\]]+)\]/) do |match|
|
||||
tag, title = $1, $2
|
||||
"<a class='reference external' href='https://threejs.org/examples/##{tag}'>#{title}</a>"
|
||||
end
|
||||
|
||||
# Handle external links with [link:url text] format
|
||||
node.inner_html = node.inner_html.gsub(/\[link:([^\s\]]+)\s+([^\]]+)\]/) do |match|
|
||||
url, text = $1, $2
|
||||
"<a class='reference external' href='#{url}'>#{text}</a>"
|
||||
end
|
||||
|
||||
# Handle external links with [link:url] format
|
||||
node.inner_html = node.inner_html.gsub(/\[link:([^\]]+)\]/) do |match|
|
||||
url = $1
|
||||
"<a class='reference external' href='#{url}'>#{url}</a>"
|
||||
end
|
||||
|
||||
# Handle internal page links with text
|
||||
node.inner_html = node.inner_html.gsub(/\[page:([^\]]+?)\s+([^\]]+?)\]/) do
|
||||
path, text = $1, $2
|
||||
"<a class='reference internal' href='#{path.downcase}'><code class='xref js js-#{path.downcase}'>#{text}</code></a>"
|
||||
end
|
||||
|
||||
# Handle internal page links without text
|
||||
node.inner_html = node.inner_html.gsub(/\[page:([^\]]+?)\]/) do |match|
|
||||
path = $1
|
||||
"<a class='reference internal' href='#{path.downcase}'><code class='xref js js-#{path.downcase}'>#{path}</code></a>"
|
||||
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 class='docutils literal notranslate'><span class='pre'>#{code}</span></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 class='docutils literal notranslate'><span class='pre'>#{code}</span></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/, '<span class="k">\1</span>') # keywords
|
||||
code = code.gsub(/\b(\d+(\.\d+)?)\b/, '<span class="mi">\1</span>') # numbers
|
||||
code = code.gsub(/'([^']*)'/, '<span class="s1">\'\1\'</span>') # single quotes
|
||||
code = code.gsub(/"([^"]*)"/, '<span class="s2">"\1"</span>') # double quotes
|
||||
code = code.gsub(/`([^`]*)`/, '<span class="s2">`\1`</span>') # template literals
|
||||
code = code.gsub(/\/\/[^\n]*/, '<span class="c1">\0</span>') # single line comments
|
||||
code = code.gsub(/\b(console|document|window|Array|Object|String|Number|Boolean|Function|Symbol|Map|Set|Promise|async|await)\b/, '<span class="nb">\1</span>') # built-ins
|
||||
code = code.gsub(/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/, '<span class="nx">\1</span>(') # function calls
|
||||
code = code.gsub(/\b(addEventListener|querySelector|getElementById|setTimeout|setInterval)\b/, '<span class="nx">\1</span>') # common methods
|
||||
|
||||
# Add line numbers
|
||||
lines = code.split("\n")
|
||||
numbered_lines = lines.map.with_index(1) do |line, i|
|
||||
"<span class=\"lineno\">#{i}</span>#{line}"
|
||||
end
|
||||
|
||||
numbered_lines.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -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
|
@ -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<br>
|
||||
Licensed under the MIT License.
|
||||
HTML
|
||||
|
||||
self.class_attribute :release
|
||||
|
||||
version '160' do
|
||||
self.release = '160'
|
||||
self.base_url = "https://threejs.org/docs"
|
||||
end
|
||||
|
||||
def get_latest_version(opts)
|
||||
get_latest_github_release('mrdoob', 'three.js', opts)
|
||||
end
|
||||
|
||||
def initial_paths
|
||||
paths = []
|
||||
json_path = File.expand_path("docs/threejs~#{self.release}/list.json")
|
||||
json_content = File.read(json_path)
|
||||
json_data = JSON.parse(json_content)
|
||||
|
||||
# Process both API and manual sections
|
||||
process_documentation(json_data['en'], paths)
|
||||
paths
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_documentation(data, paths, prefix = '')
|
||||
data.each do |category, items|
|
||||
if items.is_a?(Hash)
|
||||
if items.values.first.is_a?(String)
|
||||
# This is a leaf node with actual pages
|
||||
items.each do |name, path|
|
||||
paths << "#{path}.html"
|
||||
end
|
||||
else
|
||||
# This is a category with subcategories
|
||||
items.each do |subcategory, subitems|
|
||||
process_documentation(items, paths, "#{prefix}#{category}/")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in new issue