Browse Source

Merge branch 'V20211115' into fix-683

pull/756/head^2
zhoupzh 3 years ago
parent
commit
f97068ae9c
25 changed files with 676 additions and 75 deletions
  1. +1
    -0
      models/org.go
  2. +22
    -1
      models/repo.go
  3. +4
    -1
      models/repo_activity_custom.go
  4. +7
    -0
      models/repo_statistic.go
  5. +12
    -0
      modules/cron/tasks_basic.go
  6. +18
    -14
      modules/setting/setting.go
  7. +24
    -9
      modules/storage/obs.go
  8. +14
    -0
      options/locale/locale_en-US.ini
  9. +15
    -0
      options/locale/locale_zh-CN.ini
  10. +3
    -0
      routers/api/v1/api.go
  11. +147
    -0
      routers/api/v1/repo/repo_dashbord.go
  12. +6
    -0
      routers/repo/http.go
  13. +1
    -0
      routers/repo/repo_statistic.go
  14. +14
    -0
      routers/repo/user_data_analysis.go
  15. +115
    -36
      routers/repo/view.go
  16. +3
    -1
      routers/routes/routes.go
  17. +2
    -2
      templates/base/head.tmpl
  18. +2
    -2
      templates/base/head_home.tmpl
  19. +1
    -1
      templates/org/home.tmpl
  20. +9
    -0
      templates/repo/contributors.tmpl
  21. +13
    -7
      templates/repo/home.tmpl
  22. +109
    -0
      web_src/js/components/Contributors.vue
  23. +74
    -0
      web_src/js/features/letteravatar.js
  24. +18
    -1
      web_src/js/index.js
  25. +42
    -0
      web_src/less/openi.less

+ 1
- 0
models/org.go View File

@@ -182,6 +182,7 @@ func CreateOrganization(org, owner *User) (err error) {
if _, err = sess.Insert(&OrgUser{ if _, err = sess.Insert(&OrgUser{
UID: owner.ID, UID: owner.ID,
OrgID: org.ID, OrgID: org.ID,
IsPublic: setting.Service.DefaultOrgMemberVisible,
}); err != nil { }); err != nil {
return fmt.Errorf("insert org-user relation: %v", err) return fmt.Errorf("insert org-user relation: %v", err)
} }


+ 22
- 1
models/repo.go View File

@@ -210,9 +210,12 @@ type Repository struct {
Balance string `xorm:"NOT NULL DEFAULT '0'"` Balance string `xorm:"NOT NULL DEFAULT '0'"`
BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"` BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"`


// git clone total count
// git clone and git pull total count
CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"` CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"`


// only git clone total count
GitCloneCnt int64 `xorm:"NOT NULL DEFAULT 0"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`


@@ -2473,6 +2476,24 @@ func (repo *Repository) IncreaseCloneCnt() {
return return
} }


func (repo *Repository) IncreaseGitCloneCnt() {
sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return
}
if _, err := sess.Exec("UPDATE `repository` SET git_clone_cnt = git_clone_cnt + 1 WHERE id = ?", repo.ID); err != nil {
return
}

if err := sess.Commit(); err != nil {
return
}

return
}

func UpdateRepositoryCommitNum(repo *Repository) error { func UpdateRepositoryCommitNum(repo *Repository) error {
if _, err := x.Exec("UPDATE `repository` SET num_commit = ? where id = ?", repo.NumCommit, repo.ID); err != nil { if _, err := x.Exec("UPDATE `repository` SET num_commit = ? where id = ?", repo.NumCommit, repo.ID); err != nil {
return err return err


+ 4
- 1
models/repo_activity_custom.go View File

@@ -11,7 +11,8 @@ import (


type ContributorWithUserId struct { type ContributorWithUserId struct {
git.Contributor git.Contributor
UserId int64
UserId int64
IsAdmin bool
} }


func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) { func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) {
@@ -144,6 +145,7 @@ func GetTop10Contributor(repoPath string) ([]ContributorWithUserId, error) {
contributorDistinctDict[user.Email] = ContributorWithUserId{ contributorDistinctDict[user.Email] = ContributorWithUserId{
contributor, contributor,
user.ID, user.ID,
user.IsAdmin,
} }
} else { } else {


@@ -156,6 +158,7 @@ func GetTop10Contributor(repoPath string) ([]ContributorWithUserId, error) {
contributorDistinctDict[contributor.Email] = ContributorWithUserId{ contributorDistinctDict[contributor.Email] = ContributorWithUserId{
contributor, contributor,
-1, -1,
false,
} }
} else { } else {
value.CommitCnt += contributor.CommitCnt value.CommitCnt += contributor.CommitCnt


+ 7
- 0
models/repo_statistic.go View File

@@ -158,6 +158,13 @@ func InsertRepoStat(repoStat *RepoStatistic) (int64, error) {
return xStatistic.Insert(repoStat) return xStatistic.Insert(repoStat)
} }


func RestoreRepoStatFork(numForks int64, repoId int64) error {
sql := "update repo_statistic set num_forks=? where repo_id=?"

_, err := xStatistic.Exec(sql, numForks, repoId)
return err
}

func UpdateRepoStat(repoStat *RepoStatistic) error { func UpdateRepoStat(repoStat *RepoStatistic) error {
sql := "update repo_statistic set impact=?,completeness=?,liveness=?,project_health=?,team_health=?,growth=?,radar_total=? where repo_id=? and date=?" sql := "update repo_statistic set impact=?,completeness=?,liveness=?,project_health=?,team_health=?,growth=?,radar_total=? where repo_id=? and date=?"




+ 12
- 0
modules/cron/tasks_basic.go View File

@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/migrations"
repository_service "code.gitea.io/gitea/modules/repository" repository_service "code.gitea.io/gitea/modules/repository"
api_repo "code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/repo" "code.gitea.io/gitea/routers/repo"
mirror_service "code.gitea.io/gitea/services/mirror" mirror_service "code.gitea.io/gitea/services/mirror"
) )
@@ -195,6 +196,17 @@ func registerHandleUserStatistic() {
}) })
} }


func registerHandleClearRepoStatisticFile() {
RegisterTaskFatal("handle_repo_clear_statistic_file", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@daily",
}, func(ctx context.Context, _ *models.User, _ Config) error {
api_repo.ClearUnusedStatisticsFile()
return nil
})
}

func initBasicTasks() { func initBasicTasks() {
registerUpdateMirrorTask() registerUpdateMirrorTask()
registerRepoHealthCheck() registerRepoHealthCheck()


+ 18
- 14
modules/setting/setting.go View File

@@ -163,6 +163,7 @@ var (
// UI settings // UI settings
UI = struct { UI = struct {
ExplorePagingNum int ExplorePagingNum int
ContributorPagingNum int
IssuePagingNum int IssuePagingNum int
RepoSearchPagingNum int RepoSearchPagingNum int
MembersPagingNum int MembersPagingNum int
@@ -203,19 +204,20 @@ var (
Keywords string Keywords string
} `ini:"ui.meta"` } `ini:"ui.meta"`
}{ }{
ExplorePagingNum: 20,
IssuePagingNum: 10,
RepoSearchPagingNum: 10,
MembersPagingNum: 20,
FeedMaxCommitNum: 5,
GraphMaxCommitNum: 100,
CodeCommentLines: 4,
ReactionMaxUserNum: 10,
ThemeColorMetaTag: `#6cc644`,
MaxDisplayFileSize: 8388608,
DefaultTheme: `gitea`,
Themes: []string{`gitea`, `arc-green`},
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
ExplorePagingNum: 20,
ContributorPagingNum: 50,
IssuePagingNum: 10,
RepoSearchPagingNum: 10,
MembersPagingNum: 20,
FeedMaxCommitNum: 5,
GraphMaxCommitNum: 100,
CodeCommentLines: 4,
ReactionMaxUserNum: 10,
ThemeColorMetaTag: `#6cc644`,
MaxDisplayFileSize: 8388608,
DefaultTheme: `gitea`,
Themes: []string{`gitea`, `arc-green`},
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
Notification: struct { Notification: struct {
MinTimeout time.Duration MinTimeout time.Duration
TimeoutStep time.Duration TimeoutStep time.Duration
@@ -545,6 +547,7 @@ var (
GrowthCommit float64 GrowthCommit float64
GrowthComments float64 GrowthComments float64
RecordBeginTime string RecordBeginTime string
Path string
}{} }{}
) )


@@ -1325,7 +1328,8 @@ func SetRadarMapConfig() {
RadarMap.GrowthContributors = sec.Key("growth_contributors").MustFloat64(0.2) RadarMap.GrowthContributors = sec.Key("growth_contributors").MustFloat64(0.2)
RadarMap.GrowthCommit = sec.Key("growth_commit").MustFloat64(0.2) RadarMap.GrowthCommit = sec.Key("growth_commit").MustFloat64(0.2)
RadarMap.GrowthComments = sec.Key("growth_comments").MustFloat64(0.2) RadarMap.GrowthComments = sec.Key("growth_comments").MustFloat64(0.2)
RadarMap.RecordBeginTime = sec.Key("record_beigin_time").MustString("2021-11-04")
RadarMap.RecordBeginTime = sec.Key("record_beigin_time").MustString("2021-11-05")
RadarMap.Path = sec.Key("PATH").MustString("data/projectborad")


} }




+ 24
- 9
modules/storage/obs.go View File

@@ -10,11 +10,11 @@ import (
"strconv" "strconv"
"strings" "strings"


"github.com/unknwon/com"

"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/obs" "code.gitea.io/gitea/modules/obs"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"

"github.com/unknwon/com"
) )


type FileInfo struct { type FileInfo struct {
@@ -178,30 +178,45 @@ func GetObsListObject(jobName, parentDir string) ([]FileInfo, error) {
input := &obs.ListObjectsInput{} input := &obs.ListObjectsInput{}
input.Bucket = setting.Bucket input.Bucket = setting.Bucket
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/") input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/")
strPrefix := strings.Split(input.Prefix, "/")
output, err := ObsCli.ListObjects(input) output, err := ObsCli.ListObjects(input)
fileInfos := make([]FileInfo, 0) fileInfos := make([]FileInfo, 0)
if err == nil { if err == nil {
for _, val := range output.Contents { for _, val := range output.Contents {
str1 := strings.Split(val.Key, "/") str1 := strings.Split(val.Key, "/")
var isDir bool var isDir bool
var fileName,nextParentDir string
var fileName, nextParentDir string
if strings.HasSuffix(val.Key, "/") { if strings.HasSuffix(val.Key, "/") {
//dirs in next level dir
if len(str1)-len(strPrefix) > 2 {
continue
}
fileName = str1[len(str1)-2] fileName = str1[len(str1)-2]
isDir = true isDir = true
nextParentDir = fileName
if fileName == parentDir || (fileName + "/") == setting.OutPutPath {
if parentDir == "" {
nextParentDir = fileName
} else {
nextParentDir = parentDir + "/" + fileName
}

if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath {
continue continue
} }
} else { } else {
//files in next level dir
if len(str1)-len(strPrefix) > 1 {
continue
}
fileName = str1[len(str1)-1] fileName = str1[len(str1)-1]
isDir = false isDir = false
nextParentDir = parentDir
} }


fileInfo := FileInfo{ fileInfo := FileInfo{
ModTime: val.LastModified.Format("2006-01-02 15:04:05"),
ModTime: val.LastModified.Local().Format("2006-01-02 15:04:05"),
FileName: fileName, FileName: fileName,
Size: val.Size,
IsDir:isDir,
Size: val.Size,
IsDir: isDir,
ParenDir: nextParentDir, ParenDir: nextParentDir,
} }
fileInfos = append(fileInfos, fileInfo) fileInfos = append(fileInfos, fileInfo)
@@ -242,7 +257,7 @@ func GetObsCreateSignedUrl(jobName, parentDir, fileName string) (string, error)
input := &obs.CreateSignedUrlInput{} input := &obs.CreateSignedUrlInput{}
input.Bucket = setting.Bucket input.Bucket = setting.Bucket
input.Key = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/") input.Key = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/")
input.Expires = 60 * 60 input.Expires = 60 * 60
input.Method = obs.HttpMethodGet input.Method = obs.HttpMethodGet




+ 14
- 0
options/locale/locale_en-US.ini View File

@@ -218,6 +218,7 @@ show_only_private = Showing only private
show_only_public = Showing only public show_only_public = Showing only public


issues.in_your_repos = In your repositories issues.in_your_repos = In your repositories
contributors = Contributors


[explore] [explore]
repos = Repositories repos = Repositories
@@ -2158,6 +2159,19 @@ repos.stars = Stars
repos.forks = Forks repos.forks = Forks
repos.issues = Issues repos.issues = Issues
repos.size = Size repos.size = Size
repos.id=ID
repos.projectName=Project Name
repos.isPrivate=Private
repos.openi=OpenI
repos.visit=Visit
repos.download=Code Download
repos.pr=PR
repos.commit=Commit
repos.closedIssues=Closed Issue
repos.contributor=Contributor
repos.yes=Yes
repos.no=No



datasets.dataset_manage_panel= Dataset Manage datasets.dataset_manage_panel= Dataset Manage
datasets.owner=Owner datasets.owner=Owner


+ 15
- 0
options/locale/locale_zh-CN.ini View File

@@ -220,6 +220,8 @@ show_only_public=只显示公开的


issues.in_your_repos=属于该用户项目的 issues.in_your_repos=属于该用户项目的


contributors=贡献者

[explore] [explore]
repos=项目 repos=项目
users=用户 users=用户
@@ -2160,6 +2162,19 @@ repos.stars=点赞数
repos.forks=派生数 repos.forks=派生数
repos.issues=任务数 repos.issues=任务数
repos.size=大小 repos.size=大小
repos.id=ID
repos.projectName=项目名称
repos.isPrivate=私有
repos.openi=OpenI指数
repos.visit=浏览量
repos.download=代码下载量
repos.pr=PR数
repos.commit=Commit数
repos.closedIssues=已解决任务数
repos.contributor=贡献者数
repos.yes=是
repos.no=否



datasets.dataset_manage_panel=数据集管理 datasets.dataset_manage_panel=数据集管理
datasets.owner=所有者 datasets.owner=所有者


+ 3
- 0
routers/api/v1/api.go View File

@@ -527,8 +527,11 @@ func RegisterRoutes(m *macaron.Macaron) {
//Project board //Project board
m.Group("/projectboard", func() { m.Group("/projectboard", func() {


m.Get("/restoreFork", adminReq, repo.RestoreForkNumber)

m.Group("/project", func() { m.Group("/project", func() {
m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics) m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics)
m.Get("/download", adminReq, repo.ServeAllProjectsPeriodStatisticsFile)
m.Group("/:id", func() { m.Group("/:id", func() {
m.Get("", adminReq, repo.GetProjectLatestStatistics) m.Get("", adminReq, repo.GetProjectLatestStatistics)
m.Get("/period", adminReq, repo.GetProjectPeriodStatistics) m.Get("/period", adminReq, repo.GetProjectPeriodStatistics)


+ 147
- 0
routers/api/v1/repo/repo_dashbord.go View File

@@ -1,8 +1,12 @@
package repo package repo


import ( import (
"encoding/csv"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"os"
"path"
"strconv" "strconv"
"time" "time"


@@ -21,6 +25,7 @@ type ProjectsPeriodData struct {
LastUpdatedTime string `json:"lastUpdatedTime"` LastUpdatedTime string `json:"lastUpdatedTime"`
PageSize int `json:"pageSize"` PageSize int `json:"pageSize"`
TotalPage int `json:"totalPage"` TotalPage int `json:"totalPage"`
TotalCount int64 `json:"totalCount"`
PageRecords []*models.RepoStatistic `json:"pageRecords"` PageRecords []*models.RepoStatistic `json:"pageRecords"`
} }


@@ -50,6 +55,19 @@ type ProjectLatestData struct {
Top10 []UserInfo `json:"top10"` Top10 []UserInfo `json:"top10"`
} }


func RestoreForkNumber(ctx *context.Context) {
repos, err := models.GetAllRepositories()
if err != nil {
log.Error("GetAllRepositories failed: %v", err.Error())
return
}
for _, repo := range repos {
models.RestoreRepoStatFork(int64(repo.NumForks), repo.ID)
}

ctx.JSON(http.StatusOK, struct{}{})
}

func GetAllProjectsPeriodStatistics(ctx *context.Context) { func GetAllProjectsPeriodStatistics(ctx *context.Context) {


recordBeginTime, err := getRecordBeginTime() recordBeginTime, err := getRecordBeginTime()
@@ -94,6 +112,7 @@ func GetAllProjectsPeriodStatistics(ctx *context.Context) {
RecordBeginTime: recordBeginTime.Format(DATE_FORMAT), RecordBeginTime: recordBeginTime.Format(DATE_FORMAT),
PageSize: pageSize, PageSize: pageSize,
TotalPage: getTotalPage(total, pageSize), TotalPage: getTotalPage(total, pageSize),
TotalCount: total,
LastUpdatedTime: latestUpdatedTime, LastUpdatedTime: latestUpdatedTime,
PageRecords: models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)), PageRecords: models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)),
} }
@@ -102,6 +121,125 @@ func GetAllProjectsPeriodStatistics(ctx *context.Context) {


} }


func ServeAllProjectsPeriodStatisticsFile(ctx *context.Context) {

recordBeginTime, err := getRecordBeginTime()
if err != nil {
log.Error("Can not get record begin time", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
return
}
beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
if err != nil {
log.Error("Parameter is wrong", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong"))
return
}
q := ctx.QueryTrim("q")
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
pageSize := 1000
orderBy := getOrderBy(ctx)

_, latestDate, err := models.GetRepoStatLastUpdatedTime()
if err != nil {
log.Error("Can not query the last updated time.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error"))
return
}

countSql := generateCountSql(beginTime, endTime, latestDate, q)
total, err := models.CountRepoStatByRawSql(countSql)
if err != nil {
log.Error("Can not query total count.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error"))
return
}

fileName := getFileName(ctx, beginTime, endTime)

if err := os.MkdirAll(setting.RadarMap.Path, os.ModePerm); err != nil {
ctx.Error(http.StatusBadRequest, fmt.Errorf("Failed to create dir %s: %v", setting.AvatarUploadPath, err).Error())
}

totalPage := getTotalPage(total, pageSize)

f, e := os.Create(fileName)
defer f.Close()
if e != nil {
log.Warn("Failed to create file", e)
}
writer := csv.NewWriter(f)
writer.Write(allProjectsPeroidHeader(ctx))
for i := 0; i <= totalPage; i++ {

pageRecords := models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, i+1, pageSize))
for _, record := range pageRecords {
e = writer.Write(allProjectsPeroidValues(record, ctx))
if e != nil {
log.Warn("Failed to write record", e)
}
}
writer.Flush()

}

ctx.ServeFile(fileName)

}

func getFileName(ctx *context.Context, beginTime time.Time, endTime time.Time) string {
baseName := setting.RadarMap.Path + "/"
if ctx.QueryTrim("type") != "" {
baseName = baseName + ctx.QueryTrim("type") + "_"
}
if ctx.QueryTrim("q") != "" {
baseName = baseName + ctx.QueryTrim("q") + "_"
}
baseName = baseName + beginTime.Format(DATE_FORMAT) + "_to_" + endTime.Format(DATE_FORMAT) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + ".csv"
return baseName
}

func ClearUnusedStatisticsFile() {
fileInfos, err := ioutil.ReadDir(setting.RadarMap.Path)
if err != nil {
log.Warn("can not read dir: "+setting.RadarMap.Path, err)
return
}

for _, fileInfo := range fileInfos {
if !fileInfo.IsDir() && fileInfo.ModTime().Before(time.Now().AddDate(0, 0, -1)) {
os.Remove(path.Join(setting.RadarMap.Path, fileInfo.Name()))
}
}

}

func allProjectsPeroidHeader(ctx *context.Context) []string {

return []string{ctx.Tr("repos.id"), ctx.Tr("repos.projectName"), ctx.Tr("repos.isPrivate"), ctx.Tr("repos.openi"), ctx.Tr("repos.visit"), ctx.Tr("repos.download"), ctx.Tr("repos.pr"), ctx.Tr("repos.commit"),
ctx.Tr("repos.watches"), ctx.Tr("repos.stars"), ctx.Tr("repos.forks"), ctx.Tr("repos.issues"), ctx.Tr("repos.closedIssues"), ctx.Tr("repos.contributor")}

}

func allProjectsPeroidValues(rs *models.RepoStatistic, ctx *context.Context) []string {
return []string{strconv.FormatInt(rs.RepoID, 10), rs.Name, getIsPrivateDisplay(rs.IsPrivate, ctx), strconv.FormatFloat(rs.RadarTotal, 'f', 2, 64),
strconv.FormatInt(rs.NumVisits, 10), strconv.FormatInt(rs.NumDownloads, 10), strconv.FormatInt(rs.NumPulls, 10), strconv.FormatInt(rs.NumCommits, 10),
strconv.FormatInt(rs.NumWatches, 10), strconv.FormatInt(rs.NumStars, 10), strconv.FormatInt(rs.NumForks, 10), strconv.FormatInt(rs.NumIssues, 10),
strconv.FormatInt(rs.NumClosedIssues, 10), strconv.FormatInt(rs.NumContributor, 10),
}
}

func getIsPrivateDisplay(private bool, ctx *context.Context) string {
if private {
return ctx.Tr("repos.yes")
} else {
return ctx.Tr("repos.no")
}
}

func GetProjectLatestStatistics(ctx *context.Context) { func GetProjectLatestStatistics(ctx *context.Context) {
repoId := ctx.Params(":id") repoId := ctx.Params(":id")
recordBeginTime, err := getRecordBeginTime() recordBeginTime, err := getRecordBeginTime()
@@ -151,6 +289,15 @@ func GetProjectLatestStatistics(ctx *context.Context) {


for _, contributor := range contributors { for _, contributor := range contributors {
mode := repository.GetCollaboratorMode(contributor.UserId) mode := repository.GetCollaboratorMode(contributor.UserId)
if mode == -1 {
if contributor.IsAdmin {
mode = int(models.AccessModeAdmin)
}
if contributor.UserId == repository.OwnerID {
mode = int(models.AccessModeOwner)
}
}

pr := models.GetPullCountByUserAndRepoId(repoIdInt, contributor.UserId) pr := models.GetPullCountByUserAndRepoId(repoIdInt, contributor.UserId)
userInfo := UserInfo{ userInfo := UserInfo{
User: contributor.Committer, User: contributor.Committer,


+ 6
- 0
routers/repo/http.go View File

@@ -317,6 +317,12 @@ func HTTP(ctx *context.Context) {
go repo.IncreaseCloneCnt() go repo.IncreaseCloneCnt()
} }


if ctx.Req.Method == "POST" {
if strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
go repo.IncreaseGitCloneCnt()
}
}

w := ctx.Resp w := ctx.Resp
r := ctx.Req.Request r := ctx.Req.Request
cfg := &serviceConfig{ cfg := &serviceConfig{


+ 1
- 0
routers/repo/repo_statistic.go View File

@@ -93,6 +93,7 @@ func RepoStatisticDaily(date string) {
IsPrivate: repo.IsPrivate, IsPrivate: repo.IsPrivate,
NumWatches: int64(repo.NumWatches), NumWatches: int64(repo.NumWatches),
NumStars: int64(repo.NumStars), NumStars: int64(repo.NumStars),
NumForks: int64(repo.NumForks),
NumDownloads: repo.CloneCnt, NumDownloads: repo.CloneCnt,
NumComments: numComments, NumComments: numComments,
NumVisits: int64(numVisits), NumVisits: int64(numVisits),


+ 14
- 0
routers/repo/user_data_analysis.go View File

@@ -1,13 +1,27 @@
package repo package repo


import ( import (
"fmt"
"net/http"
"time" "time"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )


func QueryUserStaticData(ctx *context.Context) {
startDate := ctx.Query("startDate")
endDate := ctx.Query("endDate")
log.Info("startDate=" + startDate + " endDate=" + endDate)
startTime, _ := time.Parse("2006-01-02", startDate)
endTime, _ := time.Parse("2006-01-02", endDate)
log.Info("startTime=" + fmt.Sprint(startTime.Unix()) + " endDate=" + fmt.Sprint(endTime.Unix()))
ctx.JSON(http.StatusOK, models.QueryUserStaticData(startTime.Unix(), endTime.Unix()))

}

func TimingCountDataByDate(date string) { func TimingCountDataByDate(date string) {


t, _ := time.Parse("2006-01-02", date) t, _ := time.Parse("2006-01-02", date)


+ 115
- 36
routers/repo/view.go View File

@@ -12,6 +12,7 @@ import (
"fmt" "fmt"
gotemplate "html/template" gotemplate "html/template"
"io/ioutil" "io/ioutil"
"net/http"
"net/url" "net/url"
"path" "path"
"strings" "strings"
@@ -31,11 +32,12 @@ import (
) )


const ( const (
tplRepoEMPTY base.TplName = "repo/empty"
tplRepoHome base.TplName = "repo/home"
tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks"
tplMigrating base.TplName = "repo/migrating"
tplRepoEMPTY base.TplName = "repo/empty"
tplRepoHome base.TplName = "repo/home"
tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks"
tplMigrating base.TplName = "repo/migrating"
tplContributors base.TplName = "repo/contributors"
) )


type namedBlob struct { type namedBlob struct {
@@ -575,19 +577,29 @@ func safeURL(address string) string {
} }


type ContributorInfo struct { type ContributorInfo struct {
UserInfo *models.User // nil for contributor who is not a registered user
Email string
CommitCnt int
UserInfo *models.User // nil for contributor who is not a registered user
RelAvatarLink string `json:"rel_avatar_link"`
UserName string `json:"user_name"`
Email string `json:"email"`
CommitCnt int `json:"commit_cnt"`
} }


func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo{
type GetContributorsInfo struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
Count int `json:"count"`
ContributorInfo []*ContributorInfo `json:"contributor_info"`
}

func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo {
for _, c := range contributorInfos { for _, c := range contributorInfos {
if strings.Compare(c.Email,email) == 0 {
if strings.Compare(c.Email, email) == 0 {
return c return c
} }
} }
return nil return nil
} }

// Home render repository home page // Home render repository home page
func Home(ctx *context.Context) { func Home(ctx *context.Context) {
if len(ctx.Repo.Units) > 0 { if len(ctx.Repo.Units) > 0 {
@@ -596,35 +608,41 @@ func Home(ctx *context.Context) {
if err == nil && contributors != nil { if err == nil && contributors != nil {
startTime := time.Now() startTime := time.Now()
var contributorInfos []*ContributorInfo var contributorInfos []*ContributorInfo
contributorInfoHash:= make(map[string]*ContributorInfo)
contributorInfoHash := make(map[string]*ContributorInfo)
count := 0
for _, c := range contributors { for _, c := range contributors {
if strings.Compare(c.Email,"") == 0 {
if count >= 25 {
continue
}
if strings.Compare(c.Email, "") == 0 {
continue continue
} }
// get user info from committer email // get user info from committer email
user, err := models.GetUserByActivateEmail(c.Email) user, err := models.GetUserByActivateEmail(c.Email)
if err == nil { if err == nil {
// committer is system user, get info through user's primary email // committer is system user, get info through user's primary email
if existedContributorInfo,ok:=contributorInfoHash[user.Email];ok {
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
// existed: same primary email, different committer name // existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt existedContributorInfo.CommitCnt += c.CommitCnt
}else{
} else {
// new committer info // new committer info
var newContributor = &ContributorInfo{ var newContributor = &ContributorInfo{
user, user.Email,c.CommitCnt,
user, user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt,
} }
contributorInfos = append(contributorInfos, newContributor )
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[user.Email] = newContributor contributorInfoHash[user.Email] = newContributor
} }
} else { } else {
// committer is not system user // committer is not system user
if existedContributorInfo,ok:=contributorInfoHash[c.Email];ok {
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
// existed: same primary email, different committer name // existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt existedContributorInfo.CommitCnt += c.CommitCnt
}else{
} else {
var newContributor = &ContributorInfo{ var newContributor = &ContributorInfo{
user, c.Email,c.CommitCnt,
user, "", "",c.Email, c.CommitCnt,
} }
count++
contributorInfos = append(contributorInfos, newContributor) contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[c.Email] = newContributor contributorInfoHash[c.Email] = newContributor
} }
@@ -632,7 +650,7 @@ func Home(ctx *context.Context) {
} }
ctx.Data["ContributorInfo"] = contributorInfos ctx.Data["ContributorInfo"] = contributorInfos
var duration = time.Since(startTime) var duration = time.Since(startTime)
log.Info("getContributorInfo cost: %v seconds",duration.Seconds())
log.Info("getContributorInfo cost: %v seconds", duration.Seconds())
} }
if ctx.Repo.Repository.IsBeingCreated() { if ctx.Repo.Repository.IsBeingCreated() {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
@@ -699,13 +717,13 @@ func renderLicense(ctx *context.Context) {
log.Error("failed to get license content: %v, err:%v", f, err) log.Error("failed to get license content: %v, err:%v", f, err)
continue continue
} }
if bytes.Compare(buf,license) == 0 {
log.Info("got matched license:%v",f)
if bytes.Compare(buf, license) == 0 {
log.Info("got matched license:%v", f)
ctx.Data["LICENSE"] = f ctx.Data["LICENSE"] = f
return return
} }
} }
log.Info("not found matched license,repo:%v",ctx.Repo.Repository.Name)
log.Info("not found matched license,repo:%v", ctx.Repo.Repository.Name)
} }


func renderLanguageStats(ctx *context.Context) { func renderLanguageStats(ctx *context.Context) {
@@ -806,31 +824,31 @@ func renderCode(ctx *context.Context) {
baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath()) baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath())
defer baseGitRepo.Close() defer baseGitRepo.Close()
if err != nil { if err != nil {
log.Error("error open baseRepo:%s",ctx.Repo.Repository.BaseRepo.RepoPath())
log.Error("error open baseRepo:%s", ctx.Repo.Repository.BaseRepo.RepoPath())
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
}else{
if _,error:= baseGitRepo.GetBranch(ctx.Repo.BranchName);error==nil{
} else {
if _, error := baseGitRepo.GetBranch(ctx.Repo.BranchName); error == nil {
//base repo has the same branch, then compare between current repo branch and base repo's branch //base repo has the same branch, then compare between current repo branch and base repo's branch
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName
ctx.SetParams("*",compareUrl)
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName
ctx.SetParams("*", compareUrl)
ctx.Data["UpstreamSameBranchName"] = true ctx.Data["UpstreamSameBranchName"] = true
}else{
} else {
//else, compare between current repo branch and base repo's default branch //else, compare between current repo branch and base repo's default branch
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch
ctx.SetParams("*",compareUrl)
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch
ctx.SetParams("*", compareUrl)
ctx.Data["UpstreamSameBranchName"] = false ctx.Data["UpstreamSameBranchName"] = false
} }
_, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx) _, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx)
defer headGitRepo.Close() defer headGitRepo.Close()
if compareInfo!= nil {
if compareInfo.Commits!=nil {
log.Info("compareInfoCommits数量:%d",compareInfo.Commits.Len())
if compareInfo != nil {
if compareInfo.Commits != nil {
log.Info("compareInfoCommits数量:%d", compareInfo.Commits.Len())
ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len() ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len()
}else{
} else {
log.Info("compareInfo nothing different") log.Info("compareInfo nothing different")
ctx.Data["FetchUpstreamCnt"] = 0 ctx.Data["FetchUpstreamCnt"] = 0
} }
}else{
} else {
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
} }
} }
@@ -898,3 +916,64 @@ func Forks(ctx *context.Context) {


ctx.HTML(200, tplForks) ctx.HTML(200, tplForks)
} }

func Contributors(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplContributors)
}

func ContributorsAPI(ctx *context.Context) {
count := 0
errorCode := 0
errorMsg := ""
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath())
var contributorInfos []*ContributorInfo
if err == nil && contributors != nil {
contributorInfoHash := make(map[string]*ContributorInfo)
for _, c := range contributors {
if strings.Compare(c.Email, "") == 0 {
continue
}
// get user info from committer email
user, err := models.GetUserByActivateEmail(c.Email)
if err == nil {
// committer is system user, get info through user's primary email
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
} else {
// new committer info
var newContributor = &ContributorInfo{
user, user.RelAvatarLink(),user.Name, user.Email,c.CommitCnt,
}
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[user.Email] = newContributor
}
} else {
// committer is not system user
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
} else {
var newContributor = &ContributorInfo{
user, "", "",c.Email,c.CommitCnt,
}
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[c.Email] = newContributor
}
}
}
} else {
log.Error("GetContributors failed: %v", err)
errorCode = -1
errorMsg = err.Error()
}

ctx.JSON(http.StatusOK, GetContributorsInfo{
ErrorCode: errorCode,
ErrorMsg: errorMsg,
Count: count,
ContributorInfo: contributorInfos,
})
}

+ 3
- 1
routers/routes/routes.go View File

@@ -791,9 +791,11 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef()) }, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef())


m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action) m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
m.Get("/tool/query_user_static", repo.QueryUserStaticData)
// Grouping for those endpoints not requiring authentication // Grouping for those endpoints not requiring authentication
m.Group("/:username/:reponame", func() { m.Group("/:username/:reponame", func() {
m.Get("/contributors", repo.Contributors)
m.Get("/contributors/list", repo.ContributorsAPI)
m.Group("/milestone", func() { m.Group("/milestone", func() {
m.Get("/:id", repo.MilestoneIssuesAndPulls) m.Get("/:id", repo.MilestoneIssuesAndPulls)
}, reqRepoIssuesOrPullsReader, context.RepoRef()) }, reqRepoIssuesOrPullsReader, context.RepoRef())


+ 2
- 2
templates/base/head.tmpl View File

@@ -180,8 +180,8 @@
var _hmt = _hmt || []; var _hmt = _hmt || [];
(function() { (function() {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?7c4ef0a24be6109ab22e63c832ab21cf";
var s = document.getElementsByTagName("script")[0];
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
</script> </script>


+ 2
- 2
templates/base/head_home.tmpl View File

@@ -181,8 +181,8 @@
var _hmt = _hmt || []; var _hmt = _hmt || [];
(function() { (function() {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?7c4ef0a24be6109ab22e63c832ab21cf";
var s = document.getElementsByTagName("script")[0];
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
</script> </script>


+ 1
- 1
templates/org/home.tmpl View File

@@ -39,7 +39,7 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
<strong>{{.i18n.Tr "org.people"}}</strong> <strong>{{.i18n.Tr "org.people"}}</strong>
<div class="ui right"> <div class="ui right">
<a class="text grey" href="{{.OrgLink}}/members">{{.Org.NumMembers}} {{svg "octicon-chevron-right" 16}}</a>
<a class="text grey" href="{{.OrgLink}}/members">{{.MembersTotal}} {{svg "octicon-chevron-right" 16}}</a>
</div> </div>
<!-- {{if .IsOrganizationMember}} --> <!-- {{if .IsOrganizationMember}} -->


+ 9
- 0
templates/repo/contributors.tmpl View File

@@ -0,0 +1,9 @@
{{template "base/head" .}}
<div class="repository watchers">
{{template "repo/header" .}}
<div class="ui container" id="Contributors">
</div>
</div>
{{template "base/footer" .}}

+ 13
- 7
templates/repo/home.tmpl View File

@@ -4,7 +4,7 @@
font-size: 1.0em; font-size: 1.0em;
margin-bottom: 1.0rem; margin-bottom: 1.0rem;
} }
#contributorInfo > a:nth-child(n+25){
#contributorInfo > a:nth-child(n+26){
display:none; display:none;
} }
#contributorInfo > a{ #contributorInfo > a{
@@ -329,9 +329,15 @@


<div> <div>
<h4 class="ui header"> <h4 class="ui header">
{{$lenCon := len .ContributorInfo}}
{{if lt $lenCon 25 }}
<strong>贡献者 ({{len .ContributorInfo}})</strong> <strong>贡献者 ({{len .ContributorInfo}})</strong>
{{else}}
<strong>贡献者 ({{len .ContributorInfo}}+)</strong>
{{end}}
<div class="ui right"> <div class="ui right">
<a class="membersmore text grey" href="javascript:;">全部 {{svg "octicon-chevron-right" 16}}</a>
<a class="membersmore text grey" href="{{.RepoLink}}/contributors">全部 {{svg "octicon-chevron-right" 16}}</a>
</div> </div>
</h4> </h4>
<div class="ui members" id="contributorInfo"> <div class="ui members" id="contributorInfo">
@@ -353,10 +359,10 @@
</div> </div>


<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){
$(".membersmore").click(function(){
$("#contributorInfo > a:nth-child(n+25)").show();
});
});
// $(document).ready(function(){
// $(".membersmore").click(function(){
// $("#contributorInfo > a:nth-child(n+25)").show();
// });
// });
</script> </script>
{{template "base/footer" .}} {{template "base/footer" .}}

+ 109
- 0
web_src/js/components/Contributors.vue View File

@@ -0,0 +1,109 @@
<template>
<div class="ui container">
<div class="row git-user-content">
<h3 class="ui header">
<div class="ui breadcrumb">
<a class="section" href="/">代码</a>
<div class="divider"> / </div>
<div class="active section" >贡献者({{totalNum}})</div>
</div>
</h3>
<div class="ui horizontal relaxed list">
<div class="item user-list-item" v-for="(contributor,i) in contributors_list_page" >
<a v-if="contributor.user_name" :href="AppSubUrl +'/'+ contributor.user_name"><img class="ui avatar s16 image js-popover-card" :src="contributor.rel_avatar_link"></a>
<a v-else :href="'mailto:' + contributor.email "><img class="ui avatar s16 image js-popover-card" :avatar="contributor.email"></a>
<div class="content">
<div class="header" >
<a v-if="contributor.user_name" :href="AppSubUrl +'/'+ contributor.user_name">{{contributor.user_name}}</a>
<a v-else :href="'mailto:' + contributor.email ">{{contributor.email}}</a>
</div>
<span class="commit-btn">Commits: {{contributor.commit_cnt}}</span>
</div>
</div>
</div>
</div>
<div class="ui container" style="margin-top:50px;text-align:center">
<el-pagination
background
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-size="pageSize"
layout="total, prev, pager, next"
:total="totalNum">
</el-pagination>
</div>
</div>
</template>

<script>

const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;

export default {
data() {
return {
url:'',
contributors_list:[],
contributors_list_page:[],
currentPage:1,
pageSize:50,
totalNum:0,
AppSubUrl:AppSubUrl
};
},
methods: {

getContributorsList(){
this.$axios.get(this.url+'/contributors/list').then((res)=>{
this.contributors_list = res.data.contributor_info
this.totalNum = this.contributors_list.length
this.contributors_list_page = this.contributors_list.slice(0,this.pageSize)
})
},
handleCurrentChange(val){
this.contributors_list_page = this.contributors_list.slice((val-1)*this.pageSize,val*this.pageSize)
},
},
computed:{
},
watch: {

},
created(){
this.url=document.head.querySelector("[property~='og:url'][content]").content
this.getContributorsList()
},

updated(){
if(document.querySelectorAll('img[avatar]').length!==0){
window.LetterAvatar.transform()
}
}
};
</script>

<style scoped>
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active {
background-color: #5bb973;
color: #FFF;
}
/deep/ .el-pagination.is-background .el-pager li.active {
color: #fff;
cursor: default;
}
/deep/ .el-pagination.is-background .el-pager li:hover {
color: #5bb973;
}
/deep/ .el-pagination.is-background .el-pager li:not(.disabled):hover {
color: #5bb973;
}
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active:hover {
background-color: #5bb973;
color: #FFF;
}
</style>

+ 74
- 0
web_src/js/features/letteravatar.js View File

@@ -0,0 +1,74 @@
/**
* LetterAvatar
*
* Artur Heinze
* Create Letter avatar based on Initials
* based on https://gist.github.com/leecrossley/6027780
*/
(function(w, d){
function LetterAvatar (name, size, color) {
name = name || '';
size = size || 60;
var colours = [
"#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50",
"#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"
],
nameSplit = String(name).split(' '),
initials, charIndex, colourIndex, canvas, context, dataURI;
if (nameSplit.length == 1) {
initials = nameSplit[0] ? nameSplit[0].charAt(0):'?';
} else {
initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
}
if (w.devicePixelRatio) {
size = (size * w.devicePixelRatio);
}
charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64;
colourIndex = charIndex % 20;
canvas = d.createElement('canvas');
canvas.width = size;
canvas.height = size;
context = canvas.getContext("2d");
context.fillStyle = color ? color : colours[colourIndex - 1];
context.fillRect (0, 0, canvas.width, canvas.height);
context.font = Math.round(canvas.width/2)+"px 'Microsoft Yahei'";
context.textAlign = "center";
context.fillStyle = "#FFF";
context.fillText(initials, size / 2, size / 1.5);
dataURI = canvas.toDataURL();
canvas = null;
return dataURI;
}
LetterAvatar.transform = function() {
Array.prototype.forEach.call(d.querySelectorAll('img[avatar]'), function(img, name, color) {
name = img.getAttribute('avatar');
color = img.getAttribute('color');
img.src = LetterAvatar(name, img.getAttribute('width'), color);
img.removeAttribute('avatar');
img.setAttribute('alt', name);
});
};
// AMD support
if (typeof define === 'function' && define.amd) {
define(function () { return LetterAvatar; });
// CommonJS and Node.js module support.
} else if (typeof exports !== 'undefined') {
// Support Node.js specific `module.exports` (which can be a function)
if (typeof module != 'undefined' && module.exports) {
exports = module.exports = LetterAvatar;
}
// But always support CommonJS module 1.1.1 spec (`exports` cannot be a function)
exports.LetterAvatar = LetterAvatar;
} else {
window.LetterAvatar = LetterAvatar;
d.addEventListener('DOMContentLoaded', function(event) {
LetterAvatar.transform();
});
}
})(window, document);

+ 18
- 1
web_src/js/index.js View File

@@ -4,7 +4,7 @@


import './publicpath.js'; import './publicpath.js';
import './polyfills.js'; import './polyfills.js';
import './features/letteravatar.js'
import Vue from 'vue'; import Vue from 'vue';
import ElementUI from 'element-ui'; import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'; import 'element-ui/lib/theme-chalk/index.css';
@@ -37,6 +37,7 @@ import ObsUploader from './components/ObsUploader.vue';
import EditAboutInfo from './components/EditAboutInfo.vue'; import EditAboutInfo from './components/EditAboutInfo.vue';
import Images from './components/Images.vue' import Images from './components/Images.vue'
import EditTopics from './components/EditTopics.vue' import EditTopics from './components/EditTopics.vue'
import Contributors from './components/Contributors.vue'


Vue.use(ElementUI); Vue.use(ElementUI);
Vue.prototype.$axios = axios; Vue.prototype.$axios = axios;
@@ -2969,6 +2970,7 @@ $(document).ready(async () => {
initObsUploader(); initObsUploader();
initVueEditAbout(); initVueEditAbout();
initVueEditTopic(); initVueEditTopic();
initVueContributors();
initVueImages(); initVueImages();
initTeamSettings(); initTeamSettings();
initCtrlEnterSubmit(); initCtrlEnterSubmit();
@@ -3682,6 +3684,21 @@ function initVueEditTopic() {
render:h=>h(EditTopics) render:h=>h(EditTopics)
}) })
} }

function initVueContributors() {
const el = document.getElementById('Contributors');
if (!el) {
return;
}

new Vue({
el:'#Contributors',
render:h=>h(Contributors)
})
}


function initVueImages() { function initVueImages() {
const el = document.getElementById('images'); const el = document.getElementById('images');
console.log("el",el) console.log("el",el)


+ 42
- 0
web_src/less/openi.less View File

@@ -243,6 +243,48 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;}
display: inline-block; display: inline-block;
width: 100%; width: 100%;
} }
.git-user-content{
margin-bottom: 16px;
word-break: break-all;
}
.row.git-user-content .ui.avatar.s16.image {
width: 50px !important;
height: 50px !important;
vertical-align: middle;
border-radius: 500rem;}
.user-list-item {
padding: 0.3em 0px !important;
margin: 15px 0 !important;
width: 220px !important;
}
.row.git-user-content .content {
margin-left: 6px;
display: inline-block;
vertical-align: top !important;
overflow: hidden;
}
.row.git-user-content .content .header {
font-weight: bold;
}
.item.user-list-item .header {
line-height: 23px !important;
font-size: 16px !important;
text-overflow: ellipsis;
overflow: hidden;
width: 145px;
white-space:nowrap;
}
.item.user-list-item .header a {
color: #587284 !important;
}
.row.git-user-content .content .commit-btn {
margin-top: 6px;
float: left;
font-size: 11px;
color: #40485b !important;
}


.dropdown-menu { .dropdown-menu {
position: relative; position: relative;


Loading…
Cancel
Save