-
Notifications
You must be signed in to change notification settings - Fork 68
Add Snippets support to Github Files #832
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ntindle
wants to merge
13
commits into
GitbookIO:main
Choose a base branch
from
Significant-Gravitas:snippets-impl
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
adc8613
feat: add snippets to github files
ntindle e322f97
Revert "feat: add snippets to github files"
ntindle d514747
feat: add snippets to github files
ntindle b8c0b81
fix: format
ntindle 651e247
feat: after testing, do some fixes
ntindle 3738254
fix: formatting
ntindle 3ac9e74
fix: hiding and showing header
ntindle 1246ddc
fix: remove loggin
ntindle 67bec93
fix: lint
ntindle 39186fc
feat: cleanup
ntindle 22566ee
feat: add simple snippet block + support multi snippets
ntindle f0364ad
fix: name
ntindle 6d5f643
Merge branch 'main' into snippets-impl
ntindle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,17 @@ | ||
import { ExposableError } from '@gitbook/runtime'; | ||
import { GithubInstallationConfiguration, GithubRuntimeContext } from './types'; | ||
import { GithubInstallationConfiguration, GithubRuntimeContext, GithubSnippetProps } from './types'; | ||
|
||
export interface GithubProps { | ||
url: string; | ||
} | ||
|
||
const constructGithubUrl = (owner: string, repo: string, branch: string, filePath: string) => { | ||
return `https://github.com/${owner}/${repo}/blob/${branch}/${filePath}`; | ||
}; | ||
|
||
Comment on lines
+8
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically this won't work for self hosted github enterprise server instances, not sure if that matters. If so, we can change the config to include a baseline root URL |
||
const splitGithubUrl = (url: string) => { | ||
const permalinkRegex = | ||
/^https?:\/\/github\.com\/([\w-]+)\/([\w-]+)\/blob\/([a-f0-9]+)\/(.+?)#(.+)$/; | ||
const wholeFileRegex = /^https?:\/\/github\.com\/([\w-]+)\/([\w-]+)\/blob\/([\w.-]+)\/(.+)$/; | ||
// Enhanced patterns to handle more GitHub URL formats including branches, tags, and commits | ||
const generalBlobRegex = /^https?:\/\/github\.com\/([\w-]+)\/([\w-]+)\/blob\/([^\/]+)\/(.+?)(?:#(.+))?$/; | ||
const multipleLineRegex = /^L\d+-L\d+$/; | ||
|
||
let orgName = ''; | ||
|
@@ -17,41 +20,27 @@ const splitGithubUrl = (url: string) => { | |
let fileName = ''; | ||
let lines: number[] = []; | ||
|
||
if (url.match(permalinkRegex)) { | ||
const match = url.match(permalinkRegex); | ||
if (!match) { | ||
return; | ||
} | ||
|
||
orgName = match[1]; | ||
repoName = match[2]; | ||
ref = match[3]; | ||
fileName = match[4]; | ||
const hash = match[5]; | ||
|
||
if (hash !== '') { | ||
if (url.match(permalinkRegex)) { | ||
if (hash.match(multipleLineRegex)) { | ||
lines = hash.replace(/L/g, '').split('-').map(Number); | ||
} else { | ||
const singleLineNumberArray: number[] = []; | ||
const parsedInt = parseInt(hash.replace(/L/g, ''), 10); | ||
singleLineNumberArray.push(parsedInt); | ||
singleLineNumberArray.push(parsedInt); | ||
lines = singleLineNumberArray; | ||
} | ||
// Try to match general blob pattern (handles branches, tags, commits) | ||
const generalMatch = url.match(generalBlobRegex); | ||
if (generalMatch) { | ||
orgName = generalMatch[1]; | ||
repoName = generalMatch[2]; | ||
ref = generalMatch[3]; | ||
fileName = generalMatch[4]; | ||
const hash = generalMatch[5]; | ||
|
||
// Handle line numbers if present | ||
if (hash && hash !== '') { | ||
if (hash.match(multipleLineRegex)) { | ||
lines = hash.replace(/L/g, '').split('-').map(Number); | ||
} else if (hash.startsWith('L')) { | ||
const singleLineNumberArray: number[] = []; | ||
const parsedInt = parseInt(hash.replace(/L/g, ''), 10); | ||
singleLineNumberArray.push(parsedInt); | ||
singleLineNumberArray.push(parsedInt); | ||
lines = singleLineNumberArray; | ||
} | ||
} | ||
} else if (url.match(wholeFileRegex)) { | ||
const match = url.match(wholeFileRegex); | ||
if (!match) { | ||
return; | ||
} | ||
|
||
orgName = match[1]; | ||
repoName = match[2]; | ||
ref = match[3]; | ||
fileName = match[4]; | ||
} | ||
return { | ||
orgName, | ||
|
@@ -66,6 +55,31 @@ const getLinesFromGithubFile = (content: string[], lines: number[]) => { | |
return content.slice(lines[0] - 1, lines[1]); | ||
}; | ||
|
||
const extractSnippetSection = (content: string, snippetTag: string) => { | ||
const lines = content.split('\n'); | ||
const startMarker = `--8<-- [start:${snippetTag}]`; | ||
const endMarker = `--8<-- [end:${snippetTag}]`; | ||
|
||
let startIndex = -1; | ||
let endIndex = -1; | ||
|
||
for (let i = 0; i < lines.length; i++) { | ||
const line = lines[i].trim(); | ||
if (line.includes(startMarker)) { | ||
startIndex = i + 1; // Start from the line after the marker | ||
} else if (line.includes(endMarker) && startIndex !== -1) { | ||
endIndex = i; // End at the line before the marker | ||
break; | ||
} | ||
} | ||
|
||
if (startIndex === -1 || endIndex === -1) { | ||
return null; // Snippet tag not found | ||
} | ||
|
||
return lines.slice(startIndex, endIndex).join('\n'); | ||
}; | ||
|
||
const getHeaders = (authorise: boolean, accessToken = '') => { | ||
const headers: { 'User-Agent': string; Authorization?: string } = { | ||
'User-Agent': 'request', | ||
|
@@ -144,3 +158,63 @@ export const getGithubContent = async (url: string, context: GithubRuntimeContex | |
|
||
return { content, fileName: urlObject.fileName }; | ||
}; | ||
|
||
export const getGithubSnippetContent = async ( | ||
url: string, | ||
snippetTag: string, | ||
context: GithubRuntimeContext, | ||
) => { | ||
const urlObject = splitGithubUrl(url); | ||
if (!urlObject) { | ||
return; | ||
} | ||
|
||
let content: string | boolean = ''; | ||
const configuration = context.environment.installation | ||
?.configuration as GithubInstallationConfiguration; | ||
const accessToken = configuration.oauth_credentials?.access_token; | ||
if (!accessToken) { | ||
throw new ExposableError('Integration is not authenticated with GitHub'); | ||
} | ||
|
||
content = await fetchGithubFile( | ||
urlObject.orgName, | ||
urlObject.repoName, | ||
urlObject.fileName, | ||
urlObject.ref, | ||
accessToken, | ||
); | ||
|
||
if (content && snippetTag) { | ||
const snippetContent = extractSnippetSection(content, snippetTag); | ||
if (snippetContent === null) { | ||
throw new ExposableError(`Snippet tag '${snippetTag}' not found in file`); | ||
} | ||
content = snippetContent; | ||
} | ||
|
||
return { content, fileName: urlObject.fileName }; | ||
}; | ||
|
||
export const getGithubContentByParams = async ( | ||
owner: string, | ||
repo: string, | ||
branch: string, | ||
filePath: string, | ||
context: GithubRuntimeContext, | ||
) => { | ||
const url = constructGithubUrl(owner, repo, branch, filePath); | ||
return await getGithubContent(url, context); | ||
}; | ||
|
||
export const getGithubSnippetContentByParams = async ( | ||
owner: string, | ||
repo: string, | ||
branch: string, | ||
filePath: string, | ||
snippetTag: string, | ||
context: GithubRuntimeContext, | ||
) => { | ||
const url = constructGithubUrl(owner, repo, branch, filePath); | ||
return await getGithubSnippetContent(url, snippetTag, context); | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these could probably use some work on clarity