Skip to content

Commit b2d2066

Browse files
committed
Multi-version support
Ref freeCodeCamp#25.
1 parent bd6e27e commit b2d2066

19 files changed

Lines changed: 244 additions & 102 deletions

File tree

assets/javascripts/app/app.coffee

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
docs = @settings.getDocs()
7676
for doc in @DOCS
7777
(if docs.indexOf(doc.slug) >= 0 then @docs else @disabledDocs).add(doc)
78+
@migrateDocs()
7879
@docs.sort()
7980
@disabledDocs.sort()
8081
@docs.load @start.bind(@), @onBootError.bind(@), readCache: true, writeCache: true
@@ -99,21 +100,33 @@
99100
@entries.add doc.entries.all()
100101
return
101102

103+
migrateDocs: ->
104+
for slug in @settings.getDocs() when not @docs.findBy('slug', slug)
105+
needsSaving = true
106+
if doc = @disabledDocs.findBy('slug_without_version', slug)
107+
@disabledDocs.remove(doc)
108+
@docs.add(doc)
109+
110+
@saveDocs() if needsSaving
111+
102112
enableDoc: (doc, _onSuccess, onError) ->
103113
return if @docs.contains(doc)
104114
onSuccess = =>
105115
@disabledDocs.remove(doc)
106116
@docs.add(doc)
107117
@docs.sort()
108118
@initDoc(doc)
109-
@settings.setDocs(doc.slug for doc in @docs.all())
119+
@saveDocs()
110120
_onSuccess()
111-
@appCache?.updateInBackground()
112121
return
113122

114123
doc.load onSuccess, onError, writeCache: true
115124
return
116125

126+
saveDocs: ->
127+
@settings.setDocs(doc.slug for doc in @docs.all())
128+
@appCache?.updateInBackground()
129+
117130
welcomeBack: ->
118131
visitCount = @settings.get('count')
119132
@settings.set 'count', ++visitCount

assets/javascripts/app/router.coffee

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class app.Router
3939
return
4040

4141
doc: (context, next) ->
42-
if doc = app.docs.findBy('slug', context.params.doc) or app.disabledDocs.findBy('slug', context.params.doc)
42+
if doc = app.docs.findBySlug(context.params.doc) or app.disabledDocs.findBySlug(context.params.doc)
4343
context.doc = doc
4444
context.entry = doc.toEntry()
4545
@triggerRoute 'entry'
@@ -48,7 +48,7 @@ class app.Router
4848
return
4949

5050
type: (context, next) ->
51-
doc = app.docs.findBy 'slug', context.params.doc
51+
doc = app.docs.findBySlug(context.params.doc)
5252

5353
if type = doc?.types.findBy 'slug', context.params.type
5454
context.doc = doc
@@ -59,7 +59,7 @@ class app.Router
5959
return
6060

6161
entry: (context, next) ->
62-
doc = app.docs.findBy 'slug', context.params.doc
62+
doc = app.docs.findBySlug(context.params.doc)
6363

6464
if entry = doc?.findEntryByPathAndHash(context.params.path, context.hash)
6565
context.doc = doc

assets/javascripts/collections/docs.coffee

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
class app.collections.Docs extends app.Collection
22
@model: 'Doc'
33

4+
findBySlug: (slug) ->
5+
@findBy('slug', slug) or @findBy('slug_without_version', slug)
6+
47
sort: ->
58
@models.sort (a, b) ->
69
a = a.name.toLowerCase()

assets/javascripts/models/doc.coffee

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ class app.models.Doc extends app.Model
44
constructor: ->
55
super
66
@reset @
7+
[@slug_without_version, @version] = @slug.split('~v')
8+
@icon = @slug_without_version
79
@text = @toEntry().text
810

911
reset: (data) ->

assets/javascripts/templates/pages/offline_tmpl.coffee

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ canICloseTheTab = ->
5151

5252
app.templates.offlineDoc = (doc, status) ->
5353
outdated = doc.isOutdated(status)
54+
version = if doc.version then " (#{doc.version})" else ''
5455

5556
html = """
5657
<tr data-slug="#{doc.slug}"#{if outdated then ' class="_highlight"' else ''}>
57-
<td class="_docs-name _icon-#{doc.slug}">#{doc.name}</td>
58+
<td class="_docs-name _icon-#{doc.icon}">#{doc.name}#{version}</td>
5859
<td class="_docs-size">#{Math.ceil(doc.db_size / 100000) / 10} MB</td>
5960
"""
6061

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
app.templates.path = (doc, type, entry) ->
2-
html = """<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Cspan+class%3D"pl-s1">#{doc.fullPath()}" class="_path-item _icon-#{doc.slug}">#{doc.name}</a>"""
2+
html = """<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Cspan+class%3D"pl-s1">#{doc.fullPath()}" class="_path-item _icon-#{doc.icon}">#{doc.name}</a>"""
33
html += """<a href="#{type.fullPath()}" class="_path-item">#{type.name}</a>""" if type
44
html += """<span class="_path-item">#{$.escape entry.name}</span>""" if entry
55
html

assets/javascripts/templates/sidebar_tmpl.coffee

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
templates = app.templates
22

33
templates.sidebarDoc = (doc, options = {}) ->
4-
link = """<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Cspan+class%3D"pl-s1">#{doc.fullPath()}" class="_list-item _icon-#{doc.slug} """
4+
link = """<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Cspan+class%3D"pl-s1">#{doc.fullPath()}" class="_list-item _icon-#{doc.icon} """
55
link += if options.disabled then '_list-disabled' else '_list-dir'
66
link += """" data-slug="#{doc.slug}" title="#{doc.name}">"""
77
if options.disabled
@@ -22,7 +22,7 @@ templates.sidebarResult = (entry) ->
2222
"""<span class="_list-enable" data-enable="#{entry.doc.slug}">Enable</span>"""
2323
else
2424
"""<span class="_list-reveal" data-reset-list title="Reveal in list"></span>"""
25-
"""<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Cspan+class%3D"pl-s1">#{entry.fullPath()}" class="_list-item _list-hover _list-result _icon-#{entry.doc.slug}">#{addon}#{$.escape entry.name}</a>"""
25+
"""<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Cspan+class%3D"pl-s1">#{entry.fullPath()}" class="_list-item _list-hover _list-result _icon-#{entry.doc.icon}">#{addon}#{$.escape entry.name}</a>"""
2626

2727
templates.sidebarNoResults = ->
2828
html = """ <div class="_list-note">No results.</div> """
@@ -35,11 +35,13 @@ templates.sidebarPageLink = (count) ->
3535
"""<span class="_list-item _list-pagelink">Show more\u2026 (#{count})</span>"""
3636

3737
templates.sidebarLabel = (doc, options = {}) ->
38-
label = """<label class="_list-item _list-label _icon-#{doc.slug}"""
38+
label = """<label class="_list-item _list-label _icon-#{doc.icon}"""
3939
label += ' _list-label-off' unless options.checked
4040
label += """"><input type="checkbox" name="#{doc.slug}" class="_list-checkbox" """
4141
label += 'checked' if options.checked
42-
label + ">#{doc.name}</label>"
42+
label += ">#{doc.name}"
43+
label += " (#{doc.version})" if doc.version
44+
label + "</label>"
4345

4446
templates.sidebarDisabledList = (options) ->
4547
"""<div class="_disabled-list">#{templates.render 'sidebarDoc', options.docs, disabled: true}</div>"""

lib/app.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,23 @@ def docs
115115
end
116116
end
117117

118+
def find_doc(slug)
119+
settings.docs[slug] || begin
120+
slug = "#{slug}~v"
121+
settings.docs.each do |_slug, _doc|
122+
return _doc if _slug.start_with?(slug)
123+
end
124+
nil
125+
end
126+
end
127+
128+
def user_has_docs?(slug)
129+
docs.include?(slug) || begin
130+
slug = "#{slug}~v"
131+
docs.any? { |_slug| _slug.start_with?(slug) }
132+
end
133+
end
134+
118135
def doc_index_urls
119136
docs.each_with_object [] do |slug, result|
120137
if doc = settings.docs[slug]
@@ -247,14 +264,14 @@ def supports_js_redirection?
247264
settings.news_feed
248265
end
249266

250-
get %r{\A/(\w+)(\-[\w\-]+)?(/.*)?\z} do |doc, type, rest|
251-
return 404 unless @doc = settings.docs[doc]
267+
get %r{\A/([\w~\.]+)(\-[\w\-]+)?(/.*)?\z} do |doc, type, rest|
268+
return 404 unless @doc = find_doc(doc)
252269

253270
if rest.nil?
254271
redirect "/#{doc}#{type}/#{query_string_for_redirection}"
255272
elsif rest.length > 1 && rest.end_with?('/')
256273
redirect "/#{doc}#{type}#{rest[0...-1]}#{query_string_for_redirection}"
257-
elsif docs.include?(doc) && supports_js_redirection?
274+
elsif user_has_docs?(doc) && supports_js_redirection?
258275
redirect_via_js(request.path)
259276
else
260277
erb :other

lib/docs.rb

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,44 @@ def self.all
3131
Dir["#{root_path}/docs/scrapers/**/*.rb"].
3232
map { |file| File.basename(file, '.rb') }.
3333
sort!.
34-
map(&method(:find)).
34+
map { |name| const_get(name.camelize) }.
3535
reject(&:abstract)
3636
end
3737

38-
def self.find(name)
38+
def self.all_versions
39+
all.flat_map(&:versions)
40+
end
41+
42+
def self.find(name, version)
3943
const = name.camelize
40-
const_get(const)
44+
doc = const_get(const)
45+
46+
if version.present?
47+
doc = doc.versions.find { |klass| klass.version == version }
48+
raise DocNotFound.new(%(could not find version "#{version}" for doc "#{name}"), name) unless doc
49+
else
50+
doc = doc.versions.first
51+
end
52+
53+
doc
4154
rescue NameError => error
4255
if error.name.to_s == const
43-
raise DocNotFound.new("failed to locate doc class '#{name}'", name)
56+
raise DocNotFound.new(%(could not find doc "#{name}"), name)
4457
else
4558
raise error
4659
end
4760
end
4861

49-
def self.generate_page(name, page_id)
50-
find(name).store_page(store, page_id)
62+
def self.generate_page(name, version, page_id)
63+
find(name, version).store_page(store, page_id)
5164
end
5265

53-
def self.generate(name)
54-
find(name).store_pages(store)
66+
def self.generate(name, version)
67+
find(name, version).store_pages(store)
5568
end
5669

5770
def self.generate_manifest
58-
Manifest.new(store, all).store
71+
Manifest.new(store, all_versions).store
5972
end
6073

6174
def self.store

lib/docs/core/doc.rb

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,39 @@ def inherited(subclass)
1212
subclass.type = type
1313
end
1414

15+
def version(version = nil, &block)
16+
return @version if version.nil?
17+
18+
klass = Class.new(self)
19+
klass.class_exec(&block)
20+
klass.name = name
21+
klass.slug = slug
22+
klass.version = version
23+
klass.links = links
24+
@versions ||= []
25+
@versions << klass
26+
klass
27+
end
28+
29+
def version=(value)
30+
@version = value.to_s
31+
end
32+
33+
def versions
34+
@versions.presence || [self]
35+
end
36+
37+
def version?
38+
version.present?
39+
end
40+
1541
def name
1642
@name || super.try(:demodulize)
1743
end
1844

1945
def slug
20-
@slug || name.try(:downcase)
46+
slug = @slug || name.try(:downcase)
47+
version? ? "#{slug}~v#{version}" : slug
2148
end
2249

2350
def path

0 commit comments

Comments
 (0)