Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions contrib/github-hop.popclipext/Config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: GitHub Hop
icon: iconify:simple-icons:github
identifier: com.ajanlab.popclip.github-hop
popclip version: 4631
version: 1.0.0
description: 选中项目名 → 直达 GitHub 仓库首页。支持 owner/repo 格式直跳和 API 智能解析。

actions:
- title: GitHub
icon: iconify:simple-icons:github
requirements: [text]
shell script file: Source/github-hop.sh
31 changes: 31 additions & 0 deletions contrib/github-hop.popclipext/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# GitHub Hop

By [ajanlab](https://github.com/ajanlab)

Select any text → instantly jump to its GitHub repo homepage.

## Features

- **owner/repo** (e.g. `facebook/react`) → instant jump, zero latency
- **Project name** (e.g. `lodash`) → resolves via GitHub Search API to the best matching repo
- **Author name** (e.g. `@ajanlab`) → auto-strip `@`, search GitHub
- **Fallback** → API failure / rate limit / timeout → gracefully degrades to GitHub search

## Requirements

- PopClip 4631 (2023 or later)
- macOS 10.15+
- Internet connection (for API resolution and browser open)

## Usage

Select any text in any app, then click the GitHub icon in PopClip's toolbar.

## Changelog

### 1.0.0 (2026-07-14)
- Initial release
- owner/repo format → direct jump
- Project name → GitHub Search API resolution with smart matching
- @username → auto-strip and search
- Transparent fallback to GitHub search page on any API failure
75 changes: 75 additions & 0 deletions contrib/github-hop.popclipext/Source/github-hop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash
# GitHub Hop — selected text → GitHub repo page
# 1. "owner/repo" → direct jump (no API, instant)
# 2. other → resolve via GitHub Search API → jump to best repo
# 3. API fails / times out / rate limited → fallback to search page

set -euo pipefail

readonly PYTHON="/usr/bin/python3"
readonly CURL="/usr/bin/curl"

# Step 1: clean selected text
raw_text=$(printf '%s\n' "${POPCLIP_TEXT:-}" | $PYTHON -c "
import sys
text = sys.stdin.read().strip().strip('\"').strip(\"'\").strip()
if text.startswith('@'): text = text[1:]
print(text)
")

[ -z "$raw_text" ] && exit 1

# Step 2: URL-encode
encoded=$(printf '%s\n' "$raw_text" | $PYTHON -c "
import urllib.parse, sys
print(urllib.parse.quote(sys.stdin.read().strip(), safe=''))
")

# Step 3: owner/repo → direct jump (no API)
if printf '%s\n' "$raw_text" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9_.-]*/[a-zA-Z0-9_][a-zA-Z0-9_.-]*$'; then
# nohup: PopClip sends SIGHUP to child processes; without it the browser never opens
nohup /usr/bin/open "https://github.com/$raw_text" >/dev/null 2>&1 &
exit 0
fi

# Step 4: try GitHub API
response=$($CURL -sf --connect-timeout 3 --max-time 5 \
-H "Accept: application/vnd.github+json" \
-H "User-Agent: github-hop-popclip/1.0" \
"https://api.github.com/search/repositories?q=$encoded&per_page=3&sort=stars&order=desc" 2>/dev/null || true)

# Step 5: parse API response — pass raw_text as argv[1], not env var
# (avoid confusion with PopClip-injected POPCLIP_TEXT in sub-process)
target="FALLBACK"
if [ -n "$response" ]; then
target=$(echo "$response" | $PYTHON -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
items = data.get('items', [])
if 'api rate limit' in data.get('message', '').lower():
print('FALLBACK'); sys.exit(0)
if not items:
print('FALLBACK'); sys.exit(0)
txt = sys.argv[1].strip().lower() if len(sys.argv) > 1 else ''
best, score = items[0], 0
for r in items:
n = r['name'].lower()
if txt == n: best, score = r, 3; break
elif txt in n or n in txt: s = 2
elif any(w in n for w in txt.split()): s = 1
else: s = 0
if s > score or (s == score and r.get('stargazers_count',0) > best.get('stargazers_count',0)):
best, score = r, s
print(best['full_name'])
except:
print('FALLBACK')
" "$raw_text" 2>/dev/null || echo "FALLBACK")
fi

# Step 6: open in browser
if [ "$target" = "FALLBACK" ] || [ -z "$target" ]; then
nohup /usr/bin/open "https://github.com/search?q=$encoded" >/dev/null 2>&1 &
else
nohup /usr/bin/open "https://github.com/$target" >/dev/null 2>&1 &
fi