feat: add Gitea support with new widgets and API integration
This commit is contained in:
6
.env
6
.env
@@ -1 +1,5 @@
|
|||||||
REACT_APP_GITHUB_TOKEN=your_github_token_here
|
REACT_APP_GITHUB_TOKEN=your_github_token_here
|
||||||
|
REACT_APP_GITEA_TOKEN=your_gitea_token_here
|
||||||
|
GITEA_API_URL='https://git.konceptkit.com/'
|
||||||
|
GITHUB_API_URL='https://api.github.com/'
|
||||||
|
GITEA_ACCESS_TOKEN='de037d3d8b7268acd0dc734a83799e4f3761bad3'
|
||||||
@@ -78,3 +78,4 @@ npm install recharts # Commit activity charts
|
|||||||
npm install react-tooltip # Tooltips for commit details
|
npm install react-tooltip # Tooltips for commit details
|
||||||
|
|
||||||
|
|
||||||
|
Is there a limit with gitea Api?
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
// In your main app
|
// In your main app
|
||||||
import GitHubWidget from "./components/GitHubWidget";
|
import { GitHubWidget, GiteaWidget } from "./components/GitWidget";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-slate-800 py-20">
|
<div className="bg-slate-800 py-20">
|
||||||
<GitHubWidget
|
<GitHubWidget
|
||||||
defaultRepo="comfy-org/comfyui-frontend"
|
defaultRepo="kayela-c/puck"
|
||||||
limit={8}
|
limit={8}
|
||||||
title="Recent Activity"
|
title="Recent Activity"
|
||||||
// token={process.env.REACT_APP_GITHUB_TOKEN} // Optional for private repos
|
// token={process.env.REACT_APP_GITHUB_TOKEN} // Optional for private repos
|
||||||
/>
|
/>
|
||||||
|
<GiteaWidget
|
||||||
|
defaultRepo="andika/membership-fe"
|
||||||
|
limit={8}
|
||||||
|
title="Recent Activity"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
// components/GitHubWidget/index.js
|
|
||||||
export { default } from './GitHubWidget';
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// components/GitHubWidget/GitHubCommitItem.jsx
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { FaCodeBranch, FaUser, FaCalendar } from 'react-icons/fa';
|
import { FaCodeBranch, FaUser, FaCalendar } from 'react-icons/fa';
|
||||||
@@ -9,34 +8,34 @@ const GitHubCommitItem = ({ commit }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="github-commit-item">
|
<div className="github-commit-item">
|
||||||
<div className="commit-header">
|
<div className="github-commit-header">
|
||||||
<a
|
<a
|
||||||
href={html_url}
|
href={html_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="commit-sha"
|
className="github-commit-sha"
|
||||||
>
|
>
|
||||||
<FaCodeBranch /> {sha.substring(0, 7)}
|
<FaCodeBranch /> {sha.substring(0, 7)}
|
||||||
</a>
|
</a>
|
||||||
<span className="commit-time">
|
<span className="github-commit-time">
|
||||||
<FaCalendar /> {formatDistanceToNow(new Date(commitData.author.date))} ago
|
<FaCalendar /> {formatDistanceToNow(new Date(commitData.author.date))} ago
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="commit-message">
|
<div className="github-commit-message">
|
||||||
<ReactMarkdown>
|
<ReactMarkdown>
|
||||||
{commitData.message.split('\n')[0]}
|
{commitData.message.split('\n')[0]}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{author && (
|
{author && (
|
||||||
<div className="commit-author">
|
<div className="github-commit-author">
|
||||||
<img
|
<img
|
||||||
src={author.avatar_url}
|
src={author.avatar_url}
|
||||||
alt={commitData.author.name}
|
alt={commitData.author.name}
|
||||||
className="author-avatar"
|
className="github-author-avatar"
|
||||||
/>
|
/>
|
||||||
<span className="author-name">
|
<span className="github-author-name">
|
||||||
<FaUser /> {commitData.author.name}
|
<FaUser /> {commitData.author.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
// components/GitHubWidget/GitHubWidget.jsx
|
import React, { useState } from "react";
|
||||||
import React, { useState } from 'react';
|
import useGitHubCommits from "../../hooks/useGitHubCommits";
|
||||||
import useGitHubCommits from '../../hooks/useGitHubCommits';
|
import GitHubCommitItem from "./GitHubCommitItem";
|
||||||
import GitHubCommitItem from './GitHubCommitItem';
|
import { FaGithub, FaSync, FaExclamationTriangle } from "react-icons/fa";
|
||||||
import { FaGithub, FaSync, FaExclamationTriangle } from 'react-icons/fa';
|
|
||||||
|
|
||||||
|
import "../../styles/GitHubWidget.css";
|
||||||
|
|
||||||
const GitHubWidget = ({
|
export const GitHubWidget = ({
|
||||||
defaultRepo = 'facebook/react',
|
defaultRepo = "facebook/react",
|
||||||
token = null,
|
token = null,
|
||||||
limit = 5,
|
limit = 5,
|
||||||
title = 'Recent Commits'
|
title = "Recent Commits",
|
||||||
}) => {
|
}) => {
|
||||||
const [repo, setRepo] = useState(defaultRepo);
|
const [repo, setRepo] = useState(defaultRepo);
|
||||||
const [inputRepo, setInputRepo] = useState(defaultRepo);
|
const [inputRepo, setInputRepo] = useState(defaultRepo);
|
||||||
|
|
||||||
const { commits, loading, error } = useGitHubCommits(repo, token, limit);
|
const { commits, loading, error } = useGitHubCommits(repo, token);
|
||||||
|
|
||||||
const handleRepoChange = (e) => {
|
const handleRepoChange = (e) => {
|
||||||
setInputRepo(e.target.value);
|
setInputRepo(e.target.value);
|
||||||
@@ -22,17 +22,17 @@ const GitHubWidget = ({
|
|||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (inputRepo.includes('/')) {
|
if (inputRepo.includes("/")) {
|
||||||
setRepo(inputRepo);
|
setRepo(inputRepo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="github-widget border-[#e1e4e8] rounded-3xl shadow-sm">
|
<div className="github-widget border-[#e1e4e8] rounded-2xl shadow-sm border-l-8 border-l-slate-200">
|
||||||
<div className="widget-header">
|
<div className="github-widget-header rounded-t-2xl">
|
||||||
<FaGithub className="github-icon" />
|
<FaGithub className="github-icon" />
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
<form onSubmit={handleSubmit} className="repo-form">
|
<form onSubmit={handleSubmit} className="github-repo-form">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={inputRepo}
|
value={inputRepo}
|
||||||
@@ -40,48 +40,44 @@ const GitHubWidget = ({
|
|||||||
placeholder="owner/repo"
|
placeholder="owner/repo"
|
||||||
className="repo-input"
|
className="repo-input"
|
||||||
/>
|
/>
|
||||||
<button type="submit" className="refresh-btn">
|
<button type="submit" className="github-refresh-btn">
|
||||||
<FaSync />
|
<FaSync />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="widget-body">
|
<div className="github-widget-body">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="loading">Loading commits...</div>
|
<div className="github-loading">Loading commits...</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="error">
|
<div className="github-error">
|
||||||
<FaExclamationTriangle />
|
<FaExclamationTriangle />
|
||||||
<p>Error: {error}</p>
|
<p>Error: {error}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="commits-list">
|
<div className="github-commits-list">
|
||||||
{commits.length > 0 ? (
|
{commits.length > 0 ? (
|
||||||
commits.map((commit) => (
|
commits.map((commit) => (
|
||||||
<GitHubCommitItem
|
<GitHubCommitItem key={commit.sha} commit={commit} />
|
||||||
key={commit.sha}
|
|
||||||
commit={commit}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="no-commits">No commits found</div>
|
<div className="github-no-commits">No commits found</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="widget-footer">
|
<div className="github-widget-footer">
|
||||||
<a
|
<a
|
||||||
href={`https://github.com/${repo}`}
|
href={`https://github.com/${repo}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="view-all"
|
className="github-view-all"
|
||||||
>
|
>
|
||||||
View on GitHub →
|
View Git →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GitHubWidget;
|
|
||||||
55
src/components/GitWidget/GiteaCommitItem.tsx
Normal file
55
src/components/GitWidget/GiteaCommitItem.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
|
import { FaCodeBranch, FaUser, FaCalendar, FaCode } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const GiteaCommitItem = ({ commit, giteaUrl, repo }) => {
|
||||||
|
const { sha, commit: commitData, author, html_url } = commit;
|
||||||
|
|
||||||
|
// Gitea's commit structure is slightly different
|
||||||
|
const commitMessage = commitData?.message || '';
|
||||||
|
const commitAuthor = commitData?.author?.name || '';
|
||||||
|
const commitDate = commitData?.author?.date || '';
|
||||||
|
|
||||||
|
// Build URL for Gitea
|
||||||
|
const commitUrl = html_url || `${giteaUrl}/${repo}/commit/${sha}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gitea-commit-item">
|
||||||
|
<div className="commit-header">
|
||||||
|
<a
|
||||||
|
href={commitUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="commit-sha"
|
||||||
|
>
|
||||||
|
<FaCode /> {sha.substring(0, 7)}
|
||||||
|
</a>
|
||||||
|
<span className="commit-time">
|
||||||
|
<FaCalendar /> {commitDate ? formatDistanceToNow(new Date(commitDate)) + ' ago' : 'Unknown date'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="commit-message">
|
||||||
|
{commitMessage.split('\n')[0]}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="commit-author">
|
||||||
|
{author?.avatar_url && (
|
||||||
|
<img
|
||||||
|
src={author.avatar_url}
|
||||||
|
alt={commitAuthor}
|
||||||
|
className="author-avatar"
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="author-name">
|
||||||
|
<FaUser /> {commitAuthor || 'Unknown'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GiteaCommitItem;
|
||||||
200
src/components/GitWidget/GiteaWidget.tsx
Normal file
200
src/components/GitWidget/GiteaWidget.tsx
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import useGiteaCommits from "../../hooks/useGiteaCommits";
|
||||||
|
import GiteaCommitItem from "./GiteaCommitItem";
|
||||||
|
import "../../styles/GiteaWidget.css";
|
||||||
|
import {
|
||||||
|
FaCodeBranch,
|
||||||
|
FaSync,
|
||||||
|
FaExclamationTriangle,
|
||||||
|
FaServer,
|
||||||
|
} from "react-icons/fa";
|
||||||
|
|
||||||
|
export const GiteaWidget = ({
|
||||||
|
defaultGiteaUrl = "https://git.konceptkit.com/",
|
||||||
|
defaultRepo = "owner/repo",
|
||||||
|
token = null,
|
||||||
|
limit = 5,
|
||||||
|
branch = "main",
|
||||||
|
title = "Recent Commits",
|
||||||
|
}) => {
|
||||||
|
const [giteaUrl, setGiteaUrl] = useState(defaultGiteaUrl);
|
||||||
|
const [repo, setRepo] = useState(defaultRepo);
|
||||||
|
const [inputGiteaUrl, setInputGiteaUrl] = useState(defaultGiteaUrl);
|
||||||
|
const [inputRepo, setInputRepo] = useState(defaultRepo);
|
||||||
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
|
|
||||||
|
const { commits, loading, error } = useGiteaCommits(
|
||||||
|
giteaUrl,
|
||||||
|
repo,
|
||||||
|
token,
|
||||||
|
limit,
|
||||||
|
branch
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Validate URL
|
||||||
|
let url = inputGiteaUrl;
|
||||||
|
if (!url.startsWith("http")) {
|
||||||
|
url = `https://${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash if present
|
||||||
|
url = url.replace(/\/$/, "");
|
||||||
|
|
||||||
|
setGiteaUrl(url);
|
||||||
|
|
||||||
|
// Validate repo format
|
||||||
|
if (inputRepo.includes("/")) {
|
||||||
|
setRepo(inputRepo);
|
||||||
|
} else {
|
||||||
|
alert('Repository must be in format "owner/repo"');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example preset repositories
|
||||||
|
const presetRepos = [
|
||||||
|
{ label: "Documentation", value: "owner/docs" },
|
||||||
|
{ label: "API Server", value: "owner/api-server" },
|
||||||
|
{ label: "Web App", value: "owner/web-app" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gitea-widget border-[#e1e4e8] rounded-2xl shadow-sm border-l-8 border-l-lime-600">
|
||||||
|
<div className="github-widget-header rounded-t-2xl">
|
||||||
|
<FaServer className="gitea-icon" />
|
||||||
|
<h3>{title}</h3>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="advanced-toggle"
|
||||||
|
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||||
|
>
|
||||||
|
{showAdvanced ? "Simple" : "Advanced"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showAdvanced ? (
|
||||||
|
<form onSubmit={handleSubmit} className="config-form">
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
<FaServer /> Gitea Instance URL
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputGiteaUrl}
|
||||||
|
onChange={(e) => setInputGiteaUrl(e.target.value)}
|
||||||
|
placeholder="https://gitea.example.com"
|
||||||
|
className="url-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
<FaCodeBranch /> Repository
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputRepo}
|
||||||
|
onChange={(e) => setInputRepo(e.target.value)}
|
||||||
|
placeholder="owner/repository"
|
||||||
|
className="repo-input"
|
||||||
|
/>
|
||||||
|
<div className="preset-repos">
|
||||||
|
{presetRepos.map((preset) => (
|
||||||
|
<button
|
||||||
|
key={preset.value}
|
||||||
|
type="button"
|
||||||
|
className="preset-btn"
|
||||||
|
onClick={() => setInputRepo(preset.value)}
|
||||||
|
>
|
||||||
|
{preset.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" className="refresh-btn">
|
||||||
|
<FaSync /> Load Commits
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleSubmit} className="simple-form">
|
||||||
|
<div className="input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputRepo}
|
||||||
|
onChange={(e) => setInputRepo(e.target.value)}
|
||||||
|
placeholder="owner/repo"
|
||||||
|
className="simple-input"
|
||||||
|
/>
|
||||||
|
<button type="submit" className="simple-btn">
|
||||||
|
<FaSync />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="current-config">
|
||||||
|
<small>
|
||||||
|
Instance: <code>{giteaUrl}</code> | Repo: <code>{repo}</code> |
|
||||||
|
Branch: <code>{branch}</code>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="widget-body">
|
||||||
|
{loading ? (
|
||||||
|
<div className="loading">
|
||||||
|
<div className="spinner"></div>
|
||||||
|
Loading commits from {repo}...
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="error">
|
||||||
|
<FaExclamationTriangle />
|
||||||
|
<div className="error-details">
|
||||||
|
<p>Failed to load commits</p>
|
||||||
|
<small>{error}</small>
|
||||||
|
<p className="help-text">
|
||||||
|
Ensure:
|
||||||
|
<ul>
|
||||||
|
<li>Gitea instance is accessible</li>
|
||||||
|
<li>Repository exists and is accessible</li>
|
||||||
|
<li>API token has correct permissions (if private repo)</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="commits-list">
|
||||||
|
{commits.length > 0 ? (
|
||||||
|
commits.map((commit) => (
|
||||||
|
<GiteaCommitItem
|
||||||
|
key={commit.sha}
|
||||||
|
commit={commit}
|
||||||
|
giteaUrl={giteaUrl}
|
||||||
|
repo={repo}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="no-commits">
|
||||||
|
No commits found in the {branch} branch
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="widget-footer">
|
||||||
|
<a
|
||||||
|
href={`${giteaUrl}/${repo}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="view-all"
|
||||||
|
>
|
||||||
|
View on Gitea →
|
||||||
|
</a>
|
||||||
|
<span className="commit-count">{commits.length} commits shown</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
2
src/components/GitWidget/index.ts
Normal file
2
src/components/GitWidget/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./GitHubWidget";
|
||||||
|
export * from "./GiteaWidget";
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-header {
|
.github-widget-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@@ -19,19 +19,19 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-header h3 {
|
.github-widget-header h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-form {
|
.github-repo-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-input {
|
.github-repo-input {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border: 1px solid #d1d5da;
|
border: 1px solid #d1d5da;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-btn {
|
.github-refresh-btn {
|
||||||
background: #2ea44f;
|
background: #2ea44f;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-body {
|
.github-widget-body {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,14 +63,14 @@
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit-header {
|
.github-commit-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit-sha {
|
.github-commit-sha {
|
||||||
color: #0366d6;
|
color: #0366d6;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -78,62 +78,62 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit-time {
|
.github-commit-time {
|
||||||
color: #6a737d;
|
color: #6a737d;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit-message {
|
.github-commit-message {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit-author {
|
.github-commit-author {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-avatar {
|
.github-author-avatar {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-name {
|
.github-author-name {
|
||||||
color: #586069;
|
color: #586069;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading,
|
.github-loading,
|
||||||
.error,
|
.github-error,
|
||||||
.no-commits {
|
.github-no-commits {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: #586069;
|
color: #586069;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.github-error {
|
||||||
color: #cb2431;
|
color: #cb2431;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-footer {
|
.github-widget-footer {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border-top: 1px solid #e1e4e8;
|
border-top: 1px solid #e1e4e8;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-all {
|
.github-view-all {
|
||||||
color: #0366d6;
|
color: #0366d6;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-all:hover {
|
.github-view-all:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
324
src/styles/GiteaWidget.css
Normal file
324
src/styles/GiteaWidget.css
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
/* components/GiteaWidget/GiteaWidget.css */
|
||||||
|
.gitea-widget {
|
||||||
|
background: #f8fafc;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #d1d9e0;
|
||||||
|
background: linear-gradient(135deg, #609926 0%, #467f1c 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gitea-icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-toggle {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-toggle:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-form {
|
||||||
|
padding: 16px;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #eaecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-input,
|
||||||
|
.repo-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-input:focus,
|
||||||
|
.repo-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #609926;
|
||||||
|
box-shadow: 0 0 0 3px rgba(96, 153, 38, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-repos {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn {
|
||||||
|
background: #edf2f7;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn:hover {
|
||||||
|
background: #e2e8f0;
|
||||||
|
border-color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simple-form {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #eaecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simple-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simple-btn {
|
||||||
|
background: #609926;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-config {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-config code {
|
||||||
|
background: #e2e8f0;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gitea-commit-item {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #eaecef;
|
||||||
|
background: white;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gitea-commit-item:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gitea-commit-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-sha {
|
||||||
|
color: #609926;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-time {
|
||||||
|
color: #718096;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-message {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-author {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-avatar {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
color: #4a5568;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #718096;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #e2e8f0;
|
||||||
|
border-top: 3px solid #609926;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: #e53e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-details {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-details small {
|
||||||
|
color: #718096;
|
||||||
|
display: block;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 16px;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text ul {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-commits {
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #a0aec0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-all {
|
||||||
|
color: #609926;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-all:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-count {
|
||||||
|
color: #718096;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
background: #609926;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
background: #4d7c1f;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
@import "tailwindcss/components";
|
@import "tailwindcss/components";
|
||||||
@import "tailwindcss/utilities"; */
|
@import "tailwindcss/utilities"; */
|
||||||
|
|
||||||
@import "./widget.css";
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
|||||||
77
src/utils/giteaApi.ts
Normal file
77
src/utils/giteaApi.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Gitea API configuration
|
||||||
|
|
||||||
|
const createGiteaApi = (baseUrl, token = null) => {
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = `token ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: `${baseUrl}/api/v1`,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Fetch commits for a repository
|
||||||
|
fetchCommits: async (owner, repo, limit = 10, branch = 'main') => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(
|
||||||
|
`/repos/${owner}/${repo}/commits`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
limit,
|
||||||
|
page: 1,
|
||||||
|
sha: branch, // Optional branch parameter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching Gitea commits:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch user repositories
|
||||||
|
fetchUserRepos: async (username) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/users/${username}/repos`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user repos:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch repository information
|
||||||
|
fetchRepoInfo: async (owner, repo) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/repos/${owner}/${repo}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching repo info:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search repositories
|
||||||
|
searchRepos: async (query) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get('/repos/search', {
|
||||||
|
params: { q: query, limit: 10 }
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching repos:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createGiteaApi;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
// utils/githubApi.js
|
// utils/githubApi.js
|
||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
const GITHUB_API = 'https://api.github.com';
|
const GITHUB_API = "https://api.github.com";
|
||||||
|
|
||||||
export const fetchCommits = async (repo, token = null, limit = 10) => {
|
export const fetchCommits = async (repo, token = null, limit = 10) => {
|
||||||
const [owner, repoName] = repo.split('/');
|
const [owner, repoName] = repo.split("/");
|
||||||
|
|
||||||
const headers = {};
|
const headers = {};
|
||||||
if (token) {
|
if (token) {
|
||||||
headers.Authorization = `token ${token}`;
|
headers.Authorization = `token ${token}`;
|
||||||
@@ -18,14 +18,14 @@ export const fetchCommits = async (repo, token = null, limit = 10) => {
|
|||||||
headers,
|
headers,
|
||||||
params: {
|
params: {
|
||||||
per_page: limit,
|
per_page: limit,
|
||||||
page: 1
|
page: 1,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching GitHub commits:', error);
|
console.error("Error fetching GitHub commits:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -37,13 +37,12 @@ export const fetchUserRepos = async (username, token = null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(`${GITHUB_API}/users/${username}/repos`, {
|
||||||
`${GITHUB_API}/users/${username}/repos`,
|
headers,
|
||||||
{ headers }
|
});
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching user repos:', error);
|
console.error("Error fetching user repos:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user