求助:基于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 的改进! gitportal-poc-update-2020-12-09 18-46-26

顺便说一下。我知道 radicle 和其他具有类似目标的项目。它们都很好,但每个项目都有不同的解决问题的方法。这很好——有几个团队致力于为开发者提供独立的社区平台,以进行协作和共享开发。


更多关于求助:基于Golang/wasm + Svelte的P2P Git门户开发(开源)的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于求助:基于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封装和响应式界面能显著提升用户体验。

回到顶部