求助:基于Golang/wasm + Svelte的P2P Git门户开发(开源)
求助:基于Golang/wasm + Svelte的P2P Git门户开发(开源) 求助! 我正在为我的点对点 Git 门户项目寻求帮助,该项目将 Golang/wasm 的“无后端”与 Svelte 前端相结合。
我已经有一个可工作的概念验证,并希望在改进它的同时,为点对点的 GitHub 替代方案 制定愿景和设计,该方案将在浏览器中从点对点或静态存储运行(因此无需服务器端代码或第三方,也无需管理服务器)。
具体来说,我希望在 Svelte 的 UI/UX 以及 Golang 的后端风格任务方面获得帮助(两者都易于学习且使用有趣)。这也是一个难得的机会,可以在我的帮助下,通过构建你相信的有用项目来学习。我还没有分解这些任务,但在 GitHub 的“活动与帮助机会”部分有一个总结(此处:https://github.com/happybeing/p2p-git-portal-poc#activity–opportunities-to-help)。
功能
- 新建仓库 - 输入目录名并点击“新建仓库”。每个新仓库将出现在列表中,并且会自动添加一些示例问题(由 git-bug 和 go-git 提供)。显然,它需要一个用于添加和编辑问题的 UI 😔
- 克隆仓库 将尝试克隆提供的示例或你输入的任何 URL,但存在限制。首先,第一次加载应用程序和进行克隆都需要一些时间,并且目前还没有进度指示,所以请耐心等待。但也要注意,要从 GitHub 克隆,你需要使用代理来避免 CORS 错误(参见此处)。你可以使用浏览器插件在浏览器中禁用 CORS 来从 GitLab 克隆,但你还需要在 URL 末尾添加“.git”。
- 选择仓库 通过点击目录名(在你克隆或创建了一些仓库之后)。这将列出最近的几次提交以及存在的任何问题。请注意,克隆的仓库目前还不显示问题(只有新仓库会显示),并且新仓库是空的,所以也不显示任何问题。
注意:上传仓库 功能尚未集成,请忽略!
你可以在 http://gitch.happybeing.com 上实时试用(这是一个静态的 Golang/wasm 应用,没有服务器端代码)。
这一切都是通过在浏览器中运行的文件系统上创建 Git 仓库,并使用 go-git 和增强版的 git-bug(它提供了对 Git 仓库内问题的支持)对这些仓库进行操作来实现的。
更多详情 请参见 GitHub 仓库:https://github.com/happybeing/p2p-git-portal-poc
所以,概念验证是可行的,但在让普通人相信它有用之前,还需要大量工作,这就是我寻求帮助的原因——有很多事情要做!
这是目前的应用,非常需要 UI/UX 的改进!

顺便说一下。我知道 radicle 和其他具有类似目标的项目。它们都很好,但每个项目都有不同的解决问题的方法。这很好——有几个团队致力于为开发者提供独立的社区平台,以进行协作和共享开发。
更多关于求助:基于Golang/wasm + Svelte的P2P Git门户开发(开源)的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于求助:基于Golang/wasm + Svelte的P2P Git门户开发(开源)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这是一个非常有趣的项目!将Go/wasm与Svelte结合创建无后端的P2P Git门户确实是个创新的想法。以下是针对你提到的几个技术点的具体实现建议:
1. Svelte UI/UX改进示例
<!-- RepoList.svelte - 改进的仓库列表组件 -->
<script>
export let repos = [];
export let selectedRepo = null;
export let isLoading = false;
// 仓库状态管理
const repoStatus = new Map();
</script>
<div class="repo-container">
{#if isLoading}
<div class="loading-indicator">
<div class="spinner"></div>
<span>加载中...</span>
</div>
{/if}
<div class="repo-grid">
{#each repos as repo (repo.id)}
<div
class="repo-card {selectedRepo?.id === repo.id ? 'selected' : ''}"
on:click={() => selectedRepo = repo}
>
<div class="repo-header">
<h3>{repo.name}</h3>
<span class="repo-type">{repo.type}</span>
</div>
<div class="repo-stats">
<span class="stat">
<i class="icon-commit"></i>
{repo.commits?.length || 0}
</span>
<span class="stat">
<i class="icon-issue"></i>
{repo.issues?.length || 0}
</span>
</div>
<div class="repo-actions">
<button on:click|stopPropagation={() => openRepo(repo)}>
打开
</button>
</div>
</div>
{/each}
</div>
</div>
<style>
.repo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.repo-card {
border: 1px solid #e1e4e8;
border-radius: 6px;
padding: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.repo-card:hover {
border-color: #0366d6;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.repo-card.selected {
border-color: #0366d6;
background-color: #f6f8fa;
}
.loading-indicator {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #0366d6;
border-radius: 50%;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
margin-right: 0.5rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
2. Go/wasm Git操作优化
// git_operations.go - 改进的Git操作封装
package main
import (
"context"
"fmt"
"syscall/js"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
// GitManager 管理Git操作
type GitManager struct {
repos map[string]*git.Repository
}
// NewGitManager 创建Git管理器
func NewGitManager() *GitManager {
return &GitManager{
repos: make(map[string]*git.Repository),
}
}
// CloneWithProgress 带进度显示的克隆
func (gm *GitManager) CloneWithProgress(ctx context.Context, url, dir string) (js.Value, error) {
progress := make(chan git.Progress)
done := make(chan error)
// 启动克隆协程
go func() {
_, err := git.PlainClone(dir, false, &git.CloneOptions{
URL: url,
Progress: progress,
})
done <- err
}()
// 创建JavaScript回调
progressCallback := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) > 0 {
msg := args[0].String()
fmt.Printf("克隆进度: %s\n", msg)
}
return nil
})
defer progressCallback.Release()
// 监听进度
for {
select {
case p := <-progress:
// 发送进度到前端
js.Global().Call("updateCloneProgress", p.String())
case err := <-done:
if err != nil {
return js.Null(), err
}
// 加载克隆的仓库
repo, err := git.PlainOpen(dir)
if err != nil {
return js.Null(), err
}
gm.repos[dir] = repo
// 返回仓库信息给JavaScript
repoInfo := map[string]interface{}{
"name": dir,
"url": url,
"type": "cloned",
}
return js.ValueOf(repoInfo), nil
case <-ctx.Done():
return js.Null(), ctx.Err()
}
}
}
// CreateRepo 创建新仓库
func (gm *GitManager) CreateRepo(dir string) (js.Value, error) {
repo, err := git.PlainInit(dir, false)
if err != nil {
return js.Null(), err
}
// 创建初始提交
wt, err := repo.Worktree()
if err != nil {
return js.Null(), err
}
// 添加README文件
readmePath := dir + "/README.md"
readmeContent := fmt.Sprintf("# %s\n\n这是一个新的Git仓库。", dir)
if err := ioutil.WriteFile(readmePath, []byte(readmeContent), 0644); err != nil {
return js.Null(), err
}
_, err = wt.Add("README.md")
if err != nil {
return js.Null(), err
}
commit, err := wt.Commit("Initial commit", &git.CommitOptions{
Author: &object.Signature{
Name: "Git Portal User",
Email: "user@gitportal.local",
When: time.Now(),
},
})
if err != nil {
return js.Null(), err
}
gm.repos[dir] = repo
// 创建示例问题
if err := gm.createSampleIssues(repo, dir); err != nil {
fmt.Printf("创建示例问题失败: %v\n", err)
}
repoInfo := map[string]interface{}{
"name": dir,
"commit": commit.String(),
"type": "new",
"created": time.Now().Format(time.RFC3339),
}
return js.ValueOf(repoInfo), nil
}
// GetRepoCommits 获取仓库提交历史
func (gm *GitManager) GetRepoCommits(dir string, limit int) ([]map[string]interface{}, error) {
repo, exists := gm.repos[dir]
if !exists {
var err error
repo, err = git.PlainOpen(dir)
if err != nil {
return nil, err
}
gm.repos[dir] = repo
}
ref, err := repo.Head()
if err != nil {
return nil, err
}
cIter, err := repo.Log(&git.LogOptions{From: ref.Hash()})
if err != nil {
return nil, err
}
var commits []map[string]interface{}
count := 0
err = cIter.ForEach(func(c *object.Commit) error {
if count >= limit {
return fmt.Errorf("limit reached")
}
commit := map[string]interface{}{
"hash": c.Hash.String(),
"message": c.Message,
"author": map[string]string{
"name": c.Author.Name,
"email": c.Author.Email,
},
"date": c.Author.When.Format(time.RFC3339),
}
commits = append(commits, commit)
count++
return nil
})
return commits, nil
}
3. WebAssembly与Svelte集成优化
// main.go - 改进的wasm入口点
package main
import (
"syscall/js"
)
func main() {
// 初始化Git管理器
gitMgr := NewGitManager()
// 暴露API给JavaScript
js.Global().Set("GitPortal", map[string]interface{}{
"cloneRepo": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return js.ValueOf(map[string]interface{}{
"error": "需要URL和目录参数",
})
}
url := args[0].String()
dir := args[1].String()
// 使用Promise处理异步操作
promise := js.Global().Get("Promise").New(js.FuncOf(func(this js.Value, resolveArgs []js.Value) interface{} {
resolve := resolveArgs[0]
reject := resolveArgs[1]
go func() {
result, err := gitMgr.CloneWithProgress(context.Background(), url, dir)
if err != nil {
reject.Invoke(js.ValueOf(map[string]interface{}{
"error": err.Error(),
}))
return
}
resolve.Invoke(result)
}()
return nil
}))
return promise
}),
"createRepo": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return js.ValueOf(map[string]interface{}{
"error": "需要目录参数",
})
}
dir := args[0].String()
promise := js.Global().Get("Promise").New(js.FuncOf(func(this js.Value, resolveArgs []js.Value) interface{} {
resolve := resolveArgs[0]
reject := resolveArgs[1]
go func() {
result, err := gitMgr.CreateRepo(dir)
if err != nil {
reject.Invoke(js.ValueOf(map[string]interface{}{
"error": err.Error(),
}))
return
}
resolve.Invoke(result)
}()
return nil
}))
return promise
}),
"getCommits": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return js.ValueOf(map[string]interface{}{
"error": "需要目录和限制参数",
})
}
dir := args[0].String()
limit := args[1].Int()
promise := js.Global().Get("Promise").New(js.FuncOf(func(this js.Value, resolveArgs []js.Value) interface{} {
resolve := resolveArgs[0]
reject := resolveArgs[1]
go func() {
commits, err := gitMgr.GetRepoCommits(dir, limit)
if err != nil {
reject.Invoke(js.ValueOf(map[string]interface{}{
"error": err.Error(),
}))
return
}
resolve.Invoke(js.ValueOf(commits))
}()
return nil
}))
return promise
}),
})
// 保持wasm运行
select {}
}
4. 问题管理界面示例
<!-- IssueManager.svelte -->
<script>
import { onMount } from 'svelte';
export let repoName = '';
let issues = [];
let newIssue = { title: '', description: '' };
let isLoading = false;
onMount(async () => {
await loadIssues();
});
async function loadIssues() {
isLoading = true;
try {
// 调用wasm获取问题
const result = await window.GitPortal.getIssues(repoName);
issues = result || [];
} catch (error) {
console.error('加载问题失败:', error);
}
isLoading = false;
}
async function createIssue() {
if (!newIssue.title.trim()) return;
try {
await window.GitPortal.createIssue(repoName, newIssue);
newIssue = { title: '', description: '' };
await loadIssues();
} catch (error) {
console.error('创建问题失败:', error);
}
}
</script>
<div class="issue-manager">
<h2>{repoName} - 问题管理</h2>
<!-- 创建新问题 -->
<div class="create-issue">
<input
type="text"
bind:value={newIssue.title}
placeholder="问题标题"
/>
<textarea
bind:value={newIssue.description}
placeholder="问题描述"
rows="3"
></textarea>
<button on:click={createIssue}>
创建问题
</button>
</div>
<!-- 问题列表 -->
<div class="issues-list">
{#if isLoading}
<div class="loading">加载中...</div>
{:else if issues.length === 0}
<div class="empty-state">暂无问题</div>
{:else}
{#each issues as issue (issue.id)}
<div class="issue-item">
<div class="issue-header">
<h3>{issue.title}</h3>
<span class="issue-status {issue.status}">
{issue.status}
</span>
</div>
<p class="issue-description">{issue.description}</p>
<div class="issue-meta">
<span>#{issue.id}</span>
<span>创建于: {issue.createdAt}</span>
</div>
</div>
{/each}
{/if}
</div>
</div>
<style>
.issue-manager {
padding: 1rem;
}
.create-issue {
margin-bottom: 2rem;
padding: 1rem;
background: #f6f8fa;
border-radius: 6px;
}
.create-issue input,
.create-issue textarea {
width: 100%;
margin-bottom: 0.5rem;
padding: 0.5rem;
border: 1px solid #e1e4e8;
border-radius: 4px;
}
.issue-item {
border: 1px solid #e1e4e8;
border-radius: 6px;
padding: 1rem;
margin-bottom: 1rem;
}
.issue-status {
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
}
.issue-status.open {
background: #28a745;
color: white;
}
.issue-status.closed {
background: #6c757d;
color: white;
}
</style>
这些示例代码可以直接集成到你的项目中,改进UI/UX体验并优化Go/wasm的Git操作。进度指示、Promise封装和响应式界面能显著提升用户体验。

