Skip to content

Commit a002200

Browse files
authored
Merge pull request #2753 from roman-kashitsyn/add-typst-docs
feat: add Typst documentation (0.15.1)
2 parents 9e13e63 + 06df2b7 commit a002200

7 files changed

Lines changed: 435 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
module Docs
2+
class Typst
3+
class CleanHtmlFilter < Filter
4+
def call
5+
# The container keeps the sidebar so internal_urls can crawl it.
6+
# Once we have collected all links, we keep only the page body.
7+
@doc = at_css('main') || raise("missing main on #{current_url}")
8+
9+
css('.breadcrumbs', '.page-end-buttons', '.sources-link', '.tooltip-context',
10+
'.search', 'button.copy').remove
11+
12+
# Category lists rely on CSS columns to separate names and descriptions.
13+
# We separate them with an em-dash to make them readable.
14+
css('.catgrid > li > a + span').each { |node| node.before(' — ') }
15+
16+
# Adjacent type pills are unions, separated visually by the site's CSS.
17+
css('.pill + .pill').each { |node| node.before(' or ') }
18+
19+
# Parameter types, modifiers, and defaults live inside headings.
20+
# Move them into a paragraph so headings stay readable.
21+
css('h1, h2, h3, h4, h5, h6').each do |heading|
22+
heading.css('> .additional-info', '> small').reverse_each do |node|
23+
node.css('small.mod').each { |modifier| modifier.after(' ') }
24+
node.css('> div').each do |types|
25+
types.name = 'span'
26+
types.after(' ')
27+
end
28+
node.name = 'p'
29+
heading.after(node)
30+
end
31+
end
32+
33+
css('pre').each do |node|
34+
code = node.at_css('> code')
35+
language = code && code['data-lang']
36+
if language
37+
# Prism doesn't support Typst yet,
38+
# but we leave the language annotation in case it's implemented later.
39+
language = 'typst' if language.start_with?('typ')
40+
node['data-language'] = language == 'sh' ? 'bash' : language
41+
end
42+
node.content = node.content
43+
end
44+
45+
css('.code-definition').each do |node|
46+
node.css('div.arguments .overview-param').each { |param| param.before("\n ") }
47+
node.css('div.arguments').each do |args|
48+
args.add_child("\n")
49+
args.name = 'span'
50+
end
51+
node.name = 'pre'
52+
end
53+
54+
# Open examples work without the source site's toggle scripts.
55+
css('details').each { |node| node['open'] = '' }
56+
css('summary img').remove
57+
58+
# The original symbol picker needs JS to display character metadata.
59+
# A table retains every symbol and its anchor for offline lookup.
60+
css('.symbol-grid').each do |grid|
61+
table = Nokogiri::XML::Node.new('table', doc.document)
62+
table.inner_html = '<thead><tr><th>Name</th><th>Symbol</th><th>Unicode name</th><th>LaTeX</th></tr></thead><tbody></tbody>'
63+
body = table.at_css('tbody')
64+
grid.css('li[id]').each do |symbol|
65+
row = Nokogiri::XML::Node.new('tr', doc.document)
66+
row['id'] = symbol['id']
67+
name = symbol.at_css('code') || raise("missing name for ##{symbol['id']} on #{current_url}")
68+
code = Nokogiri::XML::Node.new('code', doc.document)
69+
code.content = name.content
70+
cell = Nokogiri::XML::Node.new('td', doc.document)
71+
cell.add_child(code)
72+
row.add_child(cell)
73+
[symbol['data-value'], symbol['data-unic-name']].each do |value|
74+
cell = Nokogiri::XML::Node.new('td', doc.document)
75+
cell.content = value.to_s
76+
row.add_child(cell)
77+
end
78+
cell = Nokogiri::XML::Node.new('td', doc.document)
79+
if symbol['data-latex-name'].present?
80+
latex = Nokogiri::XML::Node.new('code', doc.document)
81+
latex.content = symbol['data-latex-name']
82+
cell.add_child(latex)
83+
end
84+
row.add_child(cell)
85+
body.add_child(row)
86+
end
87+
grid.replace(table)
88+
end
89+
css('p:contains("Click on a")').each do |node|
90+
node.remove if node.content.include?('to copy it to the clipboard')
91+
end
92+
93+
css('[class]', '[style]', '[aria-describedby]', '[data-lang]').each do |node|
94+
%w(class style aria-describedby data-lang).each { |attribute| node.remove_attribute(attribute) }
95+
end
96+
97+
doc
98+
end
99+
end
100+
end
101+
end

‎lib/docs/filters/typst/entries.rb‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
module Docs
2+
class Typst
3+
class EntriesFilter < Docs::EntriesFilter
4+
NAMESPACES = %w(math pdf html).freeze
5+
6+
def get_name
7+
heading = at_css('main h1') || raise("missing h1 on #{current_url}")
8+
definition = definition_node(heading)
9+
return heading.content.strip unless definition
10+
11+
prefix = NAMESPACES.include?(category) ? "#{category}." : ''
12+
suffix = definition.name == 'code' ? '()' : ''
13+
"#{prefix}#{definition.content.strip}#{suffix}"
14+
end
15+
16+
def get_type
17+
return 'Language' if %w(syntax styling scripting context).include?(category)
18+
return name if slug.split('/').length == 1
19+
20+
crumb = at_css('.breadcrumbs li:nth-last-child(3) a')
21+
raise "missing category breadcrumb on #{current_url}" unless crumb
22+
crumb.content.strip
23+
end
24+
25+
def additional_entries
26+
return [] if root_page?
27+
28+
entries = css('main .scoped-definition').map do |node|
29+
# Definitions belong to a type/function (array.map, table.cell).
30+
# Grouped functions and constants belong to a module (calc.abs, math.upright).
31+
prefix = node['id'].start_with?('definitions-') ? name.delete_suffix('()') : namespace
32+
# Nested types, such as color.spot, have their own definitions.
33+
scopes = node['id'].split('-definitions-')[0...-1]
34+
scopes.map! { |scope| scope.sub(/\A(?:definitions|functions)-/, '') }
35+
prefix = ([prefix] + scopes).join('.')
36+
definition = definition_node(node)
37+
raise "missing name in ##{node['id']} on #{current_url}" unless definition
38+
suffix = definition.name == 'code' && function_definition?(node) ? '()' : ''
39+
["#{prefix}.#{definition.content.strip}#{suffix}", node['id']]
40+
end
41+
42+
css('main h3[id^="parameters-"] > code').each do |node|
43+
entries << ["#{name.delete_suffix('()')}.#{node.content.strip}", node.parent['id']]
44+
end
45+
46+
if category == 'symbols'
47+
css('.symbol-grid li[id]').each do |node|
48+
entries << ["#{slug.split('/').last}.#{symbol_name(node)}", node['id']]
49+
end
50+
elsif type == 'Language'
51+
css('main h2[id]').each do |node|
52+
entries << ["#{name}: #{node.content.strip}", node['id']]
53+
end
54+
end
55+
56+
entries
57+
end
58+
59+
private
60+
61+
def category
62+
slug.split('/').first
63+
end
64+
65+
def namespace
66+
NAMESPACES.include?(category) ? category : slug.split('/').last
67+
end
68+
69+
def definition_node(heading)
70+
heading.at_css('> code') || heading.at_css('> .pill')
71+
end
72+
73+
def symbol_name(node)
74+
code = node.at_css('code')
75+
raise "missing name for ##{node['id']} on #{current_url}" unless code
76+
code.content
77+
end
78+
79+
def function_definition?(node)
80+
sibling = node.next_element
81+
while sibling && !sibling.name.match?(/\Ah[1-6]\z/)
82+
return true if sibling['class'].to_s.split.include?('code-definition')
83+
sibling = sibling.next_element
84+
end
85+
false
86+
end
87+
end
88+
end
89+
end

‎lib/docs/scrapers/typst.rb‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module Docs
2+
class Typst < UrlScraper
3+
self.type = 'simple'
4+
self.release = '0.15.1'
5+
self.base_url = 'https://typst.app/docs/reference/'
6+
self.links = {
7+
home: 'https://typst.app/',
8+
code: 'https://github.com/typst/typst'
9+
}
10+
11+
html_filters.push 'typst/entries', 'typst/clean_html'
12+
13+
options[:container] = '.main-grid'
14+
# Some rendered examples exceed the core filter's 120 KB image limit.
15+
# The largest image in the bundle is 355 KB.
16+
options[:max_image_size] = 400_000
17+
18+
options[:attribution] = <<-HTML
19+
&copy; Typst GmbH and contributors<br>
20+
Licensed under the Apache License, Version 2.0.
21+
HTML
22+
23+
def get_latest_version(opts)
24+
get_latest_github_release('typst', 'typst', opts)
25+
end
26+
end
27+
end

‎public/icons/docs/typst/16.png‎

383 Bytes
Loading

‎public/icons/docs/typst/16@2x.png‎

482 Bytes
Loading

‎public/icons/docs/typst/SOURCE‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
https://typst.app/assets/favicon-16x16.png
2+
https://typst.app/assets/favicon-32x32.png

0 commit comments

Comments
 (0)