From 8458c97d0cbcb9efb24ca0943db1302eb5ab39b0 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Wed, 20 Aug 2025 15:00:33 +0200 Subject: [PATCH] Adds Github Import and Export support Building this in as a more sensible way to share SB libraries. --- Library/Std/Gist.md | 10 +-- Library/Std/Github.md | 203 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 Library/Std/Github.md diff --git a/Library/Std/Gist.md b/Library/Std/Gist.md index 29473424..73cf1604 100644 --- a/Library/Std/Gist.md +++ b/Library/Std/Gist.md @@ -31,7 +31,7 @@ event.listen { return { { id = "github-gist", - name = "Gist" + name = "Github: Gist" }, } end @@ -44,11 +44,7 @@ event.listen { run = function(event) local url = event.data.url local gistUrl = githubGist.extractGistId(url) - local resp = http.request("https://api.github.com/gists/" .. gistUrl, { - headers = { - Accept = "application/vnd.github.v3+json" - } - }) + local resp = http.request("https://api.github.com/gists/" .. gistUrl) if not resp.ok then editor.flashNotification("Failed, see console for error") js.log("Error", resp) @@ -121,7 +117,7 @@ event.listen { return { { id = "github-gist", - name = "Gist" + name = "Github: Gist" }, } end diff --git a/Library/Std/Github.md b/Library/Std/Github.md new file mode 100644 index 00000000..5a53f692 --- /dev/null +++ b/Library/Std/Github.md @@ -0,0 +1,203 @@ +#meta + +[[^Library/Std/Import]] and [[^Library/Std/Export]] support for [Github repo files](https://github.com). + +# Configuration +If you only want to _import_ from Github URLs, no configuration is required. + +To _export_ got a Github repo, you need to get a [personal Github token](https://github.com/settings/personal-access-tokens) (with repo permissions). Configure your token somewhere in Space Lua (use a `space-lua` block), ideally a `SECRETS` page. This configuration is shared with [[^Library/Std/Gist]]. + +```lua +config.set("github.token", "your token") +``` + +In addition, you need to configure a name and email that will be part of the commit: + +```lua +config.set("github.name", "John Doe") +config.set("github.email", "john@doe.com") +``` + +# Implementation + +## Constants +```space-lua +-- priority: 50 +github = { + fmUrlKey = "githubUrl", +} +``` + +## Import +```space-lua +-- Import discovery +event.listen { + name = "import:discover", + run = function(event) + local url = event.data.url + if github.extractData(url) then + return { + { + id = "github-file", + name = "Github: Repo file", + }, + } + end + end +} + +-- Import implementation +event.listen { + name = "import:run:github-file", + run = function(event) + local url = event.data.url + local repo, branch, path = github.extractData(url) + -- Fetch content via the Github API, unauthenticated + local resp = http.request(github.buildUrlWithBranch(repo, branch, path), {method = "GET"}) + if not resp.ok then + editor.flashNotification("Failed, see console for error") + js.log("Error", resp) + return + end + local content = encoding.utf8Decode(encoding.base64Decode(resp.body.content)) + local fm = index.extractFrontmatter(content) + local suggestedPath = path:gsub("%.md$", "") + if table.includes(fm.frontmatter.tags, "meta") then + -- Maybe more of a library function + suggestedPath = "Library/" .. suggestedPath + end + -- Ask for location to import to + local localPath = editor.prompt("Save to", suggestedPath) + if not localPath then + return + end + if space.fileExists(localPath .. ".md") then + editor.flashNotification("Page already exists, won't do that", "error") + return + end + space.writePage(localPath, content) + editor.flashNotification("Imported to " .. localPath) + editor.navigate({kind="page", page=localPath}) + local updated = index.patchFrontmatter(editor.getText(), + { + {op="set-key", path=github.fmUrlKey, value=url} + }) + editor.setText(updated) + end +} +``` + +## Export +```space-lua +-- returns (something/bla, branch, path) +function github.extractData(url) + if url == nil then + return nil + end + return url:match("github%.com/([^/]+/[^/]+)/[^/]+/([^/]+)/(.+)") +end + +function github.buildUrl(repo, path) + return "https://api.github.com/repos/" .. repo .. "/contents/" .. path +end + +function github.buildUrlWithBranch(repo, branch, path) + return github.buildUrl(repo, path) .. "?ref=" .. branch +end + +function github.checkConfig() + if not config.get("github.token") then + error("github.token needs to be set") + end + if not config.get("github.name") then + error("github.name needs to be set") + return + end + if not config.get("github.email") then + error("github.email needs to be set") + return + end +end + +-- Export discovery +event.listen { + name = "export:discover", + run = function(event) + return { + { + id = "github-file", + name = "Github: Repo file" + }, + } + end +} + +-- Export implementation +event.listen { + name = "export:run:github-file", + run = function(event) + -- Check configuration + local checkOk, err = pcall(github.checkConfig) + if not checkOk then + editor.flashNotification(err, "error") + return + end + -- Extract the URL from frontmatter if set + local text = event.data.text + local fm = index.extractFrontmatter(text, { + removeKeys = {github.fmUrlKey}, + }) + local repo, branch, path = github.extractData(fm.frontmatter[github.fmUrlKey]) + local sha = nil -- will be set for existing files + if not repo then + -- Not there? This will be a new file + repo = editor.prompt "Github repo (user/repo):" + if not repo then + return + end + branch = editor.prompt("Branch:", "main") + if not branch then + return + end + path = editor.prompt("File path:", editor.getCurrentPage() .. ".md") + if not path then + return + end + else + -- We did find an existing file, let's fetch it to get the SHA + local oldContent = githubGist.request(github.buildUrlWithBranch(repo, branch, path), "GET") + if not oldContent.ok then + editor.flashNotification("Could not fetch existing file", "error") + return + end + sha = oldContent.body.sha + end + -- Ask for a commit message + local message = editor.prompt("Commit message:", "Commit") + -- Push the change + local resp = githubGist.request(github.buildUrl(repo, path), "PUT", { + message = message, + committer = { + name = config.get("github.name"), + email = config.get("github.email"), + }, + branch = branch, + sha = sha, + content = encoding.base64Encode(fm.text) + }) + if resp.ok then + editor.flashNotification "Published file successfully" + local url = "https://github.com/" .. repo .. "/blob/" .. branch .. "/" .. path + local updated = index.patchFrontmatter(editor.getText(), + { + {op="set-key", path=github.fmUrlKey, value=url} + }) + editor.setText(updated) + editor.flashNotification "Done!" + else + editor.flashNotification("Error, check console") + js.log("Error", resp) + end + end +} +``` \ No newline at end of file