diff --git a/pxtlib/github.ts b/pxtlib/github.ts index 66d85f547553..706299bb0eaa 100644 --- a/pxtlib/github.ts +++ b/pxtlib/github.ts @@ -299,8 +299,9 @@ namespace pxt.github { }) } - async loadTutorialMarkdown(repoPath: string, tag?: string) { - const tutorialResponse = (await downloadMarkdownTutorialInfoAsync(repoPath, tag)).resp; + async loadTutorialMarkdown(repopath: string, tag?: string) { + repopath = normalizeTutorialPath(repopath); + const tutorialResponse = (await downloadMarkdownTutorialInfoAsync(repopath, tag)).resp; const repo = tutorialResponse.markdown as { filename: string, repo: GHTutorialRepoInfo }; @@ -1441,4 +1442,34 @@ namespace pxt.github { status: null })) } + + export function normalizeTutorialPath(repopath: string) { + // repopath will be one of these three formats: + // 1. owner/repo/path/to/file + // 2. github:owner/repo/path/to/file + // 3. https://github.com/owner/repo/path/to/file + // + // That third format is not a valid URL (a proper github URL will have /blob/branchname/ + // in it after the repo) but we used to support it so maintain backwards compatibility + if (/^github\:/i.test(repopath)) { + repopath = repopath.slice(7) + } + if (/^https?\:/i.test(repopath)) { + const parsed = new URL(repopath); + + // remove the leading slash + repopath = parsed.pathname.slice(1); + + // check if this is an actual link to a file in github, for example: + // Mojang/EducationContent/blob/master/computing/unit-2/lesson-1.md + // + // Note the "/blob/master/" which is not present in example 3 above + const fullURLMatch = /^((?:[^\/]+\/){2})blob\/[^\/]+\/(.*)\.md$/.exec(repopath) + if (fullURLMatch) { + repopath = fullURLMatch[1] + fullURLMatch[2]; + } + } + + return repopath; + } } \ No newline at end of file diff --git a/tests/pxt-editor-test/editorrunner.ts b/tests/pxt-editor-test/editorrunner.ts index a50fa9930b3b..8803f8e1ded7 100644 --- a/tests/pxt-editor-test/editorrunner.ts +++ b/tests/pxt-editor-test/editorrunner.ts @@ -340,6 +340,33 @@ describe("updateHistory", () => { }); }); + +describe("pxt.github.normalizeTutorialPath", () => { + const testPath = "Mojang/EducationContent/computing/unit-2/lesson-1"; + + it("should parse repos of the format owner/repo/path/to/file", () => { + chai.expect(pxt.github.normalizeTutorialPath(testPath)).equals(testPath); + }); + + it("should parse repos of the format github:owner/repo/path/to/file", () => { + const path = "github:" + testPath; + chai.expect(pxt.github.normalizeTutorialPath(path)).equals(testPath); + }); + + it("should parse repos of the format https://github.com/owner/repo/path/to/file", () => { + const path = "https://github.com/" + testPath; + chai.expect(pxt.github.normalizeTutorialPath(path)).equals(testPath); + + const path2 = "http://github.com/" + testPath; + chai.expect(pxt.github.normalizeTutorialPath(path2)).equals(testPath); + }); + + it("should parse actual links to markdown files in github", () => { + const url = "https://github.com/Mojang/EducationContent/blob/master/computing/unit-2/lesson-1.md"; + chai.expect(pxt.github.normalizeTutorialPath(url)).equals(testPath); + }); +}); + function createProjectText(): pxt.workspace.ScriptText { // A realistic timeline of project edits const dates = [ diff --git a/webapp/src/idbworkspace.ts b/webapp/src/idbworkspace.ts index dfe03ec2c016..a26868b1ef32 100644 --- a/webapp/src/idbworkspace.ts +++ b/webapp/src/idbworkspace.ts @@ -537,9 +537,8 @@ export function initGitHubDb() { } async loadTutorialMarkdown(repopath: string, tag?: string) { - if (repopath.indexOf(":") !== -1) { - repopath = repopath.split(":").pop(); - } + repopath = pxt.github.normalizeTutorialPath(repopath); + const cache = await getGitHubCacheAsync(); const id = this.tutorialCacheKey(repopath, tag);