Skip to content

Instantly share code, notes, and snippets.

@simonw

simonw/gist.md Secret

Created October 28, 2024 04:09
Show Gist options
  • Save simonw/e0a784d258925e84af2a00c98d61accc to your computer and use it in GitHub Desktop.
Save simonw/e0a784d258925e84af2a00c98d61accc to your computer and use it in GitHub Desktop.

Create GitHub Gists with JSON from a Textarea

human (Oct 27, 2024, 08:57 PM)

Write an artifact - no React - that uses fetch() against the GitHub Gist API to create a Gist containing JSON from a textarea on the page

assistant (Oct 27, 2024, 08:57 PM)

I'll help create a simple HTML/JavaScript interface for creating GitHub Gists.

create GitHub Gist Creator

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GitHub Gist Creator</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            max-width: 800px;
            margin: 2rem auto;
            padding: 0 1rem;
        }
        .container {
            background-color: #f8f9fa;
            padding: 2rem;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        textarea {
            width: 100%;
            min-height: 200px;
            margin: 1rem 0;
            padding: 0.5rem;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-family: monospace;
        }
        input[type="text"] {
            width: 100%;
            padding: 0.5rem;
            margin: 0.5rem 0;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            background-color: #2ea44f;
            color: white;
            border: none;
            padding: 0.75rem 1.5rem;
            border-radius: 6px;
            cursor: pointer;
            font-size: 1rem;
        }
        button:disabled {
            background-color: #94d3a2;
            cursor: not-allowed;
        }
        .error {
            color: #dc3545;
            margin-top: 1rem;
        }
        .success {
            color: #28a745;
            margin-top: 1rem;
        }
        #result {
            margin-top: 1rem;
            word-break: break-all;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>GitHub Gist Creator</h1>
        <div>
            <label for="token">GitHub Personal Access Token:</label>
            <input type="text" id="token" placeholder="ghp_your_token_here" />
            <small>Need a token? <a href="https://github.com/settings/tokens" target="_blank">Create one here</a> with gist scope.</small>
        </div>
        <div>
            <label for="filename">Filename (including .json extension):</label>
            <input type="text" id="filename" placeholder="example.json" value="gist.json" />
        </div>
        <div>
            <label for="description">Gist Description:</label>
            <input type="text" id="description" placeholder="My JSON Gist" />
        </div>
        <div>
            <label for="content">JSON Content:</label>
            <textarea id="content" placeholder='{"example": "Enter your JSON here"}'></textarea>
        </div>
        <button onclick="createGist()" id="submit-btn">Create Gist</button>
        <div id="result"></div>
    </div>

    <script>
        async function createGist() {
            const token = document.getElementById('token').value.trim();
            const filename = document.getElementById('filename').value.trim();
            const description = document.getElementById('description').value.trim();
            let content = document.getElementById('content').value.trim();
            const resultDiv = document.getElementById('result');
            const submitBtn = document.getElementById('submit-btn');

            // Basic validation
            if (!token) {
                resultDiv.innerHTML = '<div class="error">Please enter a GitHub token</div>';
                return;
            }
            if (!filename) {
                resultDiv.innerHTML = '<div class="error">Please enter a filename</div>';
                return;
            }
            if (!content) {
                resultDiv.innerHTML = '<div class="error">Please enter some JSON content</div>';
                return;
            }

            // Validate JSON
            try {
                // Try to parse and then stringify to ensure proper formatting
                content = JSON.stringify(JSON.parse(content), null, 2);
            } catch (e) {
                resultDiv.innerHTML = '<div class="error">Invalid JSON: ' + e.message + '</div>';
                return;
            }

            // Prepare the request
            const gistData = {
                description: description,
                public: true,
                files: {
                    [filename]: {
                        content: content
                    }
                }
            };

            // Disable button during request
            submitBtn.disabled = true;
            resultDiv.innerHTML = 'Creating gist...';

            try {
                const response = await fetch('https://api.github.com/gists', {
                    method: 'POST',
                    headers: {
                        'Accept': 'application/vnd.github.v3+json',
                        'Authorization': `Bearer ${token}`,
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(gistData)
                });

                const data = await response.json();

                if (response.ok) {
                    resultDiv.innerHTML = `
                        <div class="success">
                            Gist created successfully!<br>
                            <a href="${data.html_url}" target="_blank">View your gist</a><br>
                            Raw URL: <a href="${data.files[filename].raw_url}" target="_blank">${data.files[filename].raw_url}</a>
                        </div>
                    `;
                } else {
                    throw new Error(data.message || 'Failed to create gist');
                }
            } catch (error) {
                resultDiv.innerHTML = `<div class="error">Error: ${error.message}</div>`;
            } finally {
                submitBtn.disabled = false;
            }
        }

        // Add example JSON to textarea on page load
        document.getElementById('content').value = JSON.stringify({
            "example": "value",
            "numbers": [1, 2, 3],
            "nested": {
                "key": "value"
            }
        }, null, 2);
    </script>
</body>
</html>

I've created a standalone HTML page that provides a simple interface for creating GitHub Gists containing JSON content. Here are the key features:

  1. Input fields for:

    • GitHub Personal Access Token (required for authentication)
    • Filename (defaults to gist.json)
    • Gist description (optional)
    • JSON content (with a pre-filled example)
  2. Functionality:

    • JSON validation before submission
    • Pretty-printing of JSON input
    • Error handling for invalid JSON and API errors
    • Success feedback with links to both the Gist and raw JSON
    • Disabled button state during API calls
  3. Security considerations:

    • The token is never stored
    • Uses the GitHub API v3
    • Uses Bearer token authentication

To use this, you'll need to:

  1. Create a GitHub Personal Access Token with the gist scope at https://github.com/settings/tokens
  2. Enter your token in the input field
  3. Modify the JSON content as needed
  4. Click "Create Gist"

The page includes some basic styling and error handling to make it user-friendly. Would you like me to explain any particular part of the implementation or make any modifications?

human (Oct 27, 2024, 09:07 PM)

Use public: false and turn this into an async function which accepts the description and an object of {filename: content} and a PAT and returns the object from the response

assistant (Oct 27, 2024, 09:07 PM)

I'll modify the code to create a reusable async function for creating private Gists.

create GitHub Private Gist Creator Function

/**
 * Creates a private GitHub Gist
 * @param {string} description - Gist description
 * @param {Object.<string, string>} files - Object with filenames as keys and content as values
 * @param {string} token - GitHub Personal Access Token with gist scope
 * @returns {Promise<Object>} - GitHub Gist API response object
 * @throws {Error} - If the API request fails
 */
async function createPrivateGist(description, files, token) {
    if (!token) {
        throw new Error('GitHub token is required');
    }
    
    if (!files || Object.keys(files).length === 0) {
        throw new Error('At least one file is required');
    }

    // Transform the files object into the format expected by GitHub's API
    const gistFiles = Object.entries(files).reduce((acc, [filename, content]) => {
        acc[filename] = { content };
        return acc;
    }, {});

    const gistData = {
        description: description || '',
        public: false,
        files: gistFiles
    };

    const response = await fetch('https://api.github.com/gists', {
        method: 'POST',
        headers: {
            'Accept': 'application/vnd.github.v3+json',
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(gistData)
    });

    const data = await response.json();

    if (!response.ok) {
        throw new Error(data.message || `Failed to create gist: ${response.status}`);
    }

    return data;
}

// Example usage:
/*
const files = {
    'example.json': JSON.stringify({ key: 'value' }, null, 2),
    'data.json': JSON.stringify({ numbers: [1, 2, 3] }, null, 2)
};

try {
    const gist = await createPrivateGist(
        'My private gist description',
        files,
        'ghp_your_token_here'
    );
    
    console.log('Gist URL:', gist.html_url);
    console.log('Raw URLs:', Object.entries(gist.files).reduce((acc, [filename, file]) => {
        acc[filename] = file.raw_url;
        return acc;
    }, {}));
} catch (error) {
    console.error('Error creating gist:', error.message);
}
*/

I've created a reusable async function that:

  1. Takes three parameters:

    • description (string) - The Gist description
    • files (object) - Key-value pairs of filename to content
    • token (string) - GitHub Personal Access Token
  2. Returns:

    • The complete Gist object from GitHub's API response
    • Contains URLs, file info, and other metadata
  3. Includes:

    • Input validation
    • Error handling
    • TypeScript-style JSDoc comments
    • Example usage commented out at the bottom

You can use it like this:

const result = await createPrivateGist(
    'My API Data',
    {
        'data.json': JSON.stringify({ hello: 'world' }, null, 2)
    },
    'your_github_token'
);

console.log(result.html_url);  // https://gist.github.com/...
console.log(result.files['data.json'].raw_url);  // Raw file URL

The function will throw an error if:

  • No token is provided
  • No files are provided
  • The API request fails

Would you like me to modify anything about the implementation?

@simonw
Copy link
Author

simonw commented Oct 28, 2024

@simonw
Copy link
Author

simonw commented Oct 28, 2024

CleanShot 2024-10-27 at 21 37 12@2x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment