Skip to content

Commit e7683d4

Browse files
committed
Rework the URL slug system
1 parent a669432 commit e7683d4

8 files changed

Lines changed: 293 additions & 102 deletions

File tree

CourseIndexPaths.gd

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class_name CourseIndexPaths
2+
extends RefCounted
3+
4+
5+
const COURSES := {
6+
"learn_gdscript": "res://course/CourseLearnGDScriptIndex.gd"
7+
}
8+
9+
const COURSE_SLUG_ALIASES := {
10+
"course": "learn_gdscript"
11+
}
12+
13+
static var _course_index_cache := {}
14+
15+
16+
static func get_course_index_instance(course_id: String) -> CourseIndex:
17+
var effective_id := course_id
18+
if not COURSES.has(course_id) and COURSE_SLUG_ALIASES.has(course_id):
19+
effective_id = COURSE_SLUG_ALIASES[course_id]
20+
21+
var index: CourseIndex = _course_index_cache.get(effective_id, null)
22+
if index:
23+
return index
24+
25+
var index_path: String = COURSES.get(effective_id, "")
26+
if not index_path:
27+
push_error("Invalid course %s and no alias found" % [course_id])
28+
return null
29+
30+
var index_script: GDScript = load(index_path)
31+
if not index_script:
32+
push_error("Course %s at %s failed to load" % [course_id, index_path])
33+
return null
34+
35+
index = index_script.new()
36+
_course_index_cache[course_id] = index
37+
return index

CourseIndexPaths.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://b3461ed37w8i

autoload/NavigationManager.gd

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ var arguments := { }
1919

2020
var _current_unload_type := -1
2121
var _url_normalization_regex := RegExpGroup.compile(
22-
"^(?<prefix>user:\\/\\/|res:\\/\\/|\\.*?\\/+)(?<url>.*)\\.(?<extension>(t?res|bbcode))",
22+
r"^(?<prefix>user:\/\/|res:\/\/|\.*?\/+)#?(?<course>.*?)\/(?<lesson>.*)\.(?<extension>t?res|bbcode)(?<practice>#P[0-9]+)?",
2323
)
24+
var _practice_regex := RegEx.create_from_string("#P[0-9]+")
2425
var _lesson_cache := { }
2526

2627

@@ -136,29 +137,54 @@ func navigate_to(metadata: String) -> void:
136137
if not regex_result:
137138
push_error("`%s` is not a valid resource or bbcode path" % [metadata])
138139
return
140+
139141
var normalized := NormalizedUrl.new(regex_result)
140-
141-
if not normalized.path:
142+
143+
var course_index := CourseIndexPaths.get_course_index_instance(normalized.course_path)
144+
if not course_index:
145+
push_error("'%s' is not a valid course" % [normalized.course_path])
146+
return
147+
148+
# legacy practices support
149+
var full_lesson_path := "%s.%s" % [normalized.lesson_path, normalized.extension]
150+
var alias := course_index.get_real_slug_from_slug(full_lesson_path)
151+
if alias != full_lesson_path:
152+
var new_path := "%s%s/%s" % [normalized.protocol, normalized.course_path, alias]
153+
regex_result = _url_normalization_regex.search(new_path)
154+
normalized = NormalizedUrl.new(regex_result)
155+
156+
if not normalized.lesson_path:
142157
push_error("`%s` is not a valid path" % metadata)
143158
return
144159

145-
var file_path := normalized.get_file_path()
160+
var lesson_path := course_index.get_lesson_path_from_slug("%s.%s" % [normalized.lesson_path, normalized.extension])
146161

147-
var resource := get_navigation_resource(file_path)
148-
if not (resource is BBCodeParser.ParseNode):
149-
push_error("`%s` is not a resource" % file_path)
162+
var lesson := get_navigation_resource(lesson_path)
163+
if not (lesson is BBCodeParser.ParseNode):
164+
push_error("`%s` is not a lesson" % lesson_path)
150165
return
151166

152-
history.push_back(file_path)
167+
var effective_path := lesson_path
168+
if normalized.practice_index > -1:
169+
var practice := BBCodeUtils.get_lesson_practice(lesson, normalized.practice_index)
170+
if not practice is BBCodeParser.ParseNode:
171+
push_error("'%s' does not have a practice at index '%s'" % [lesson_path, normalized.practice_index])
172+
return
173+
effective_path += "#P%s" % [normalized.practice_index]
174+
175+
history.push_back(effective_path)
153176
_push_javascript_state(normalized.get_web_url())
154177

155178
emit_signal("navigation_requested")
156179

157180

158181
func get_navigation_resource(resource_id: String) -> BBCodeParser.ParseNode:
159-
var is_lesson := resource_id.ends_with("lesson.bbcode")
160-
161-
var bbcode_path := resource_id if is_lesson else resource_id.get_base_dir().path_join("lesson.bbcode")
182+
var practice_index_match := _practice_regex.search(resource_id)
183+
var is_practice := practice_index_match != null
184+
185+
var bbcode_path := resource_id
186+
if is_practice:
187+
bbcode_path = bbcode_path.left(-practice_index_match.strings[0].length())
162188

163189
var lesson_data: BBCodeParser.ParseNode = null
164190
if _lesson_cache.has(bbcode_path):
@@ -183,18 +209,11 @@ func get_navigation_resource(resource_id: String) -> BBCodeParser.ParseNode:
183209
lesson_data = result.root.children[0]
184210
_lesson_cache[bbcode_path] = lesson_data
185211

186-
if is_lesson:
187-
return lesson_data
188-
189-
# If it's not a lesson, it's a practice. May support some other types in future.
190-
var practice_count := BBCodeUtils.get_lesson_practice_count(lesson_data)
191-
for i in practice_count:
192-
var other_practice := BBCodeUtils.get_lesson_practice(lesson_data, i)
193-
var other_practice_id := BBCodeUtils.get_practice_id(other_practice)
194-
if other_practice_id == resource_id:
195-
return other_practice
212+
if is_practice:
213+
var practice_idx := int(practice_index_match.strings[0].substr(2))
214+
return BBCodeUtils.get_lesson_practice(lesson_data, practice_idx)
196215

197-
return null
216+
return lesson_data
198217

199218

200219
# Handle back requests
@@ -318,25 +337,41 @@ func _push_javascript_state(url: String) -> void:
318337

319338
class NormalizedUrl:
320339
var protocol := ""
321-
var path := ""
340+
var course_path := ""
341+
var lesson_path := ""
342+
var practice_index := -1
322343
var extension := ""
323344

324345

325346
func _init(regex_result: RegExMatch) -> void:
326347
protocol = regex_result.get_string("prefix")
327-
path = regex_result.get_string("url")
348+
course_path = regex_result.get_string("course")
349+
lesson_path = regex_result.get_string("lesson")
328350
extension = regex_result.get_string("extension")
351+
352+
var practice_string := regex_result.get_string("practice")
353+
if practice_string:
354+
practice_index = int(practice_string.substr(2))
329355
if protocol in ["//", "/"]:
330356
protocol = "res://"
331357

332358

333359
func get_file_path() -> String:
334-
return "%s%s.%s" % [protocol, path, extension]
360+
var file_path := "%s%s/%s.%s" % [protocol, course_path, lesson_path, extension]
361+
if practice_index > -1:
362+
file_path += "#P" % [practice_index]
363+
return file_path
335364

336365

337366
func get_web_url() -> String:
338-
return "%s.%s" % [path, extension]
367+
var url := "%s/%s.%s" % [course_path, lesson_path, extension]
368+
if practice_index > -1:
369+
url += "#P%s" % [practice_index]
370+
return url
339371

340372

341373
func _to_string() -> String:
342-
return protocol + path
374+
var string := "%s%s/%s"
375+
if practice_index > -1:
376+
string += "#P%s" % [practice_index]
377+
return string

0 commit comments

Comments
 (0)